feat: add description-width config option
Descriptions and values are drawn as a single line that never wraps. Because a diagram is scaled to fit the width it is rendered into, the longest label is what decides that scale: one long description makes the whole diagram -- bit cells, indices and every other label with it -- shrink until that line fits. A register whose fields carry a sentence each is then unreadable at any page width, and since each diagram is measured on its own, a document full of them renders every one at a different size. Setting description-width wraps labels at that width instead, so the length of a description no longer decides how large the diagram is drawn. Values wrap at the same right edge as the description they belong to, and the option composes with left-labels, force-descs-on-side and ltr-bits. Wrapping means a label is no longer one line tall, so labels can no longer be spaced by a fixed line height. Each is now offset by the height it actually occupies, measured before it is placed, which is why render gained a context block. The option defaults to none, which keeps the single-line behaviour on the original code paths. With it unset every diagram in the gallery renders byte-identical to before.
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
- Added `description-width` config option, wrapping descriptions and values instead of drawing them on a single line
|
||||
|
||||
## [v0.3.1] - 2026-06-14
|
||||
- Support for non-consecutive ranges ([#13](https://git.kb28.ch/HEL/rivet-typst/pulls/13))
|
||||
- Prevent drawing separators on starting lines of fields ([#17](https://git.kb28.ch/HEL/rivet-typst/pulls/17))
|
||||
|
||||
+3
-1
@@ -26,6 +26,7 @@
|
||||
/// - full-page (bool): If true, the page will be resized to fit the diagram and take the background color
|
||||
/// - all-bit-i (bool): If true, all bit indices will be rendered, otherwise, only the ends of each range will be displayed
|
||||
/// - ltr-bits (bool): If true, bits are placed with the LSB on the left instead of the right
|
||||
/// - description-width (float | none): If set, descriptions and values wrap at this width instead of being drawn on a single line
|
||||
/// -> dictionary
|
||||
#let config(
|
||||
default-font-family: "Ubuntu Mono",
|
||||
@@ -53,7 +54,8 @@
|
||||
height: 800,
|
||||
full-page: false,
|
||||
all-bit-i: true,
|
||||
ltr-bits: false
|
||||
ltr-bits: false,
|
||||
description-width: none
|
||||
) = {}
|
||||
|
||||
/// Dark theme config
|
||||
|
||||
+30
@@ -203,6 +203,36 @@ For values depending on other ranges, see #link(<format-dependencies>)[Dependenc
|
||||
}
|
||||
```
|
||||
|
||||
=== Wrapping descriptions <format-description-width>
|
||||
|
||||
Descriptions and values are drawn on a single line. A diagram is scaled to fit
|
||||
the width it is given, so a long description makes the whole diagram, bit cells
|
||||
and all, shrink until it fits:
|
||||
|
||||
#let long-schema = (structures: (main: (bits: 16, ranges: (
|
||||
"15-8": (name: "op", description: "Operation to perform on the two operands"),
|
||||
"7-0": (
|
||||
name: "flags",
|
||||
description: "Flags applied to the operation, see the table below for the meaning of each bit",
|
||||
values: ("00000001": "carry in, added to the result before the flags are evaluated")
|
||||
)
|
||||
))))
|
||||
#let long-sch = schema.load(long-schema)
|
||||
|
||||
#align(center, schema.render(long-sch, width: 75%))
|
||||
|
||||
Setting the #doc-ref("config.config") option `description-width` wraps them at
|
||||
that width instead, leaving the diagram at its natural size. The width is given
|
||||
in the same units as `bit-width`, and values wrap at the same right edge as the
|
||||
description they belong to. Each label is offset by the height it actually
|
||||
occupies, so wrapping onto several lines does not make labels overlap.
|
||||
|
||||
```typ
|
||||
schema.render(sch, config: config.config(description-width: 300))
|
||||
```
|
||||
|
||||
#align(center, schema.render(long-sch, width: 75%, config: lib.config.config(description-width: 300)))
|
||||
|
||||
#pagebreak(weak: true)
|
||||
|
||||
== Dependencies <format-dependencies>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
full-page: false,
|
||||
all-bit-i: true,
|
||||
ltr-bits: false,
|
||||
description-width: none,
|
||||
) = {
|
||||
return (
|
||||
default-font-family: default-font-family,
|
||||
@@ -53,6 +54,7 @@
|
||||
full-page: full-page,
|
||||
all-bit-i: all-bit-i,
|
||||
ltr-bits: ltr-bits,
|
||||
description-width: description-width,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+72
-18
@@ -41,6 +41,24 @@
|
||||
)
|
||||
}
|
||||
|
||||
// A wrapped label is built as plain content so that its height can be measured
|
||||
// before it is placed, and the label after it offset by what it really takes.
|
||||
#let text-block(txt, color, width, font: none, italic: false, size: 1em) = {
|
||||
let text-params = (:)
|
||||
if font != none {
|
||||
text-params.insert("font", font)
|
||||
}
|
||||
if italic {
|
||||
text-params.insert("style", "italic")
|
||||
}
|
||||
|
||||
box(width: width * 1pt, text(txt, fill: color, size: size, ..text-params))
|
||||
}
|
||||
|
||||
#let draw-block(block, x, y) = {
|
||||
draw.content((x, -y), block, anchor: "north-west", stroke: none)
|
||||
}
|
||||
|
||||
#let draw-line(color, a, b) = {
|
||||
let (x0, y0) = a
|
||||
let (x1, y1) = b
|
||||
@@ -113,15 +131,26 @@
|
||||
for (val, desc) in values.pairs().sorted(key: p => p.first()) {
|
||||
desc-y += gap
|
||||
let txt = val + " = " + desc
|
||||
shapes += draw-text(
|
||||
txt, txt-col, desc-x + bit-w / 2, desc-y,
|
||||
anchor: "north-west",
|
||||
font: config.italic-font-family,
|
||||
italic: true,
|
||||
size: config.italic-font-size
|
||||
)
|
||||
if config.description-width == none {
|
||||
shapes += draw-text(
|
||||
txt, txt-col, desc-x + bit-w / 2, desc-y,
|
||||
anchor: "north-west",
|
||||
font: config.italic-font-family,
|
||||
italic: true,
|
||||
size: config.italic-font-size
|
||||
)
|
||||
|
||||
desc-y += config.italic-font-size / 1.2pt
|
||||
desc-y += config.italic-font-size / 1.2pt
|
||||
} else {
|
||||
let block = text-block(
|
||||
txt, txt-col, config.description-width - bit-w / 2,
|
||||
font: config.italic-font-family,
|
||||
italic: true,
|
||||
size: config.italic-font-size
|
||||
)
|
||||
shapes += draw-block(block, desc-x + bit-w / 2, desc-y)
|
||||
desc-y += measure(block).height.pt()
|
||||
}
|
||||
}
|
||||
|
||||
return (shapes, desc-x, desc-y)
|
||||
@@ -149,22 +178,47 @@
|
||||
shapes += draw-underbracket(config, start-x, start-x + width, start-y)
|
||||
|
||||
let mid-x = start-x + width / 2
|
||||
shapes += draw-link(config, mid-x, start-y, desc-x, desc-y)
|
||||
let link-y = if config.description-width == none {
|
||||
desc-y
|
||||
} else {
|
||||
// wrapped labels hang from desc-y, so aim at the middle of their first line
|
||||
desc-y + config.default-font-size.pt() / 2 - bit-h / 2
|
||||
}
|
||||
shapes += draw-link(config, mid-x, start-y, desc-x, link-y)
|
||||
|
||||
let txt-x = desc-x
|
||||
|
||||
if config.left-labels {
|
||||
txt-x -= range_.description.len() * config.default-font-size / 2pt
|
||||
// desc-x is the right edge of the label when they are on the left, so shift
|
||||
// to its left edge: exactly the box width when wrapping, an estimate from
|
||||
// the character count otherwise
|
||||
txt-x -= if config.description-width == none {
|
||||
range_.description.len() * config.default-font-size / 2pt
|
||||
} else {
|
||||
config.description-width
|
||||
}
|
||||
}
|
||||
|
||||
shapes += draw-text(
|
||||
range_.description,
|
||||
config.text-color,
|
||||
txt-x, desc-y + bit-h / 2,
|
||||
anchor: "west"
|
||||
)
|
||||
if config.description-width == none {
|
||||
shapes += draw-text(
|
||||
range_.description,
|
||||
config.text-color,
|
||||
txt-x, desc-y + bit-h / 2,
|
||||
anchor: "west"
|
||||
)
|
||||
|
||||
desc-y += config.default-font-size / 0.75pt
|
||||
desc-y += config.default-font-size / 0.75pt
|
||||
} else {
|
||||
let block = text-block(
|
||||
range_.description,
|
||||
config.text-color,
|
||||
config.description-width,
|
||||
font: config.default-font-family,
|
||||
size: config.default-font-size
|
||||
)
|
||||
shapes += draw-block(block, txt-x, desc-y)
|
||||
desc-y += measure(block).height.pt()
|
||||
}
|
||||
|
||||
if range_.values != none and range_.depends-on == none {
|
||||
let shapes_
|
||||
@@ -519,7 +573,7 @@
|
||||
return (shapes, desc-y)
|
||||
}
|
||||
|
||||
#let render(config, schema, width: 100%) = {
|
||||
#let render(config, schema, width: 100%) = context {
|
||||
set text(
|
||||
font: config.default-font-family,
|
||||
size: config.default-font-size
|
||||
|
||||
Reference in New Issue
Block a user