Author SHA1 Message Date
dromell ab3ccfccea 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.
2026-08-18 10:30:17 +02:00
HEL 5ac65afaff Merge pull request 'Prepare release v0.3.1' (#19) from release/v0.3.1 into dev
Reviewed-on: #19
2026-06-16 13:29:47 +00:00
HEL 472562596f chore: fix typo in manual example 2026-06-15 13:16:04 +02:00
HEL 39e6acaed8 chore: fix typo in manual 2026-06-15 13:13:30 +02:00
HEL 6cc3cfcd00 chore: use preview import paths in examples 2026-06-15 13:10:59 +02:00
HEL 861c25026b fix: parse start struct attribute in xml-loader 2026-06-15 13:08:58 +02:00
HEL 768e63fd46 chore: remove"Typst" from description 2026-06-15 12:43:43 +02:00
HEL 2678aee584 chore: add alt text on README images 2026-06-15 12:42:36 +02:00
HEL 2228f22352 chore: update changelog 2026-06-14 13:33:04 +02:00
HEL a909398fbd chore: bump version to 0.3.1 2026-06-14 13:24:08 +02:00
HEL 05de90890f Merge pull request 'fix end line on non-consecutive ranges ending at the highest bit not being drawn' (#18) from ALVAROPING1/rivet-typst:dev into dev
Reviewed-on: #18
Reviewed-by: Louis Heredero <louis@herdac.ch>
2026-06-05 14:43:48 +00:00
ALVAROPING1 9b83b4c4a8 fix end line on non-consecutive ranges ending at the highest bit not being drawn 2026-06-05 14:45:34 +02:00
HEL df7899ee4b Merge pull request 'prevent drawing separators on starting lines of fields' (#17) from rajayonin/rivet-typst:fix-consecutive-ranges into dev
Reviewed-on: #17
Reviewed-by: Louis Heredero <louis@herdac.ch>
2025-11-28 11:37:43 +00:00
rajayonin b6f2fd99b8 prevent drawing separators on starting lines of fields
On non-consecutive ranges, we were drawing the starting line _on top_ of
the separator, which lead to some visual glitches in some viewers.
2025-11-25 09:02:49 +01:00
21 changed files with 10645 additions and 9057 deletions
+9
View File
@@ -1,5 +1,14 @@
# 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))
- Fix end line on non-consecutive ranges ending at the highest bit not being draw ([#18](https://git.kb28.ch/HEL/rivet-typst/pulls/18))
- Add documentation for `start` property of structures ([61f13df815f](https://git.kb28.ch/HEL/rivet-typst/commit/6f13df815f99fd6be8b6b3a02b4d53bcc8fe0aff))
## [v0.3.0] - 2025-05-03
- updated CeTZ to 0.3.4
- updated to Typst 0.13.1
+3 -3
View File
@@ -9,7 +9,7 @@ It is based on the [homonymous Python script](https://git.kb28.ch/HEL/rivet/)
<tr>
<td>
<a href="./gallery/example1.typ">
<img src="./gallery/example1.png" width="1000px">
<img src="./gallery/example1.png" width="1000px" alt="A black on white diagram showing the bit structure of a machine instruction, detailing operands, flags and selectors.">
</a>
</td>
</tr>
@@ -19,7 +19,7 @@ It is based on the [homonymous Python script](https://git.kb28.ch/HEL/rivet/)
<tr>
<td>
<a href="./gallery/example2.typ">
<img src="./gallery/example2.png" width="1000px">
<img src="./gallery/example2.png" width="1000px" alt="A white on blue diagram showing the bit structure of RISC-V memory instructions, detailing operands, flags and selectors.">
</a>
</td>
</tr>
@@ -35,7 +35,7 @@ For more information, see the [manual](manual.pdf)
To use this package, simply import `schema` from [rivet](https://typst.app/universe/package/rivet) and call `schema.load` to parse a schema description. Then use `schema.render` to render it, et voilà !
```typ
#import "@preview/rivet:0.3.0": schema
#import "@preview/rivet:0.3.1": schema
#let doc = schema.load(yaml("path/to/schema.yaml"))
#schema.render(doc)
```
+3 -1
View File
@@ -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
Binary file not shown.
+2 -2
View File
@@ -1,6 +1,6 @@
#import "../src/lib.typ": schema, config
#import "@preview/rivet:0.3.1": schema, config
#let example = schema.load("/gallery/example1.yaml")
#let example = schema.load(yaml("./example1.yaml"))
#schema.render(example, config: config.config(
full-page: true
))
Binary file not shown.
+2 -2
View File
@@ -1,6 +1,6 @@
#import "../src/lib.typ": schema, config
#import "@preview/rivet:0.3.1": schema, config
#let example = schema.load("/gallery/example2.yaml")
#let example = schema.load(yaml("./example2.yaml"))
#schema.render(example, config: config.blueprint(
full-page: true,
left-labels: true
Binary file not shown.
+2 -2
View File
@@ -1,6 +1,6 @@
#import "../src/lib.typ": schema, config
#import "@preview/rivet:0.3.1": schema, config
#let example = schema.load("/gallery/example1.yaml")
#let example = schema.load(yaml("/gallery/example1.yaml"))
//#schema.render(example)
= Chapter 1
+4 -4
View File
@@ -1,14 +1,14 @@
#import "../../src/lib.typ": *
#import "@preview/rivet:0.3.1": *
#let conf = config.config(
full-page: true,
left-labels: true
)
#let alu = schema.load("/gallery/riscv/alu_instr.yaml")
#let alu = schema.load(yaml("./alu_instr.yaml"))
#schema.render(alu, config: conf)
#let branch = schema.load("/gallery/riscv/branch_instr.yaml")
#let branch = schema.load(yaml("./branch_instr.yaml"))
#schema.render(branch, config: conf)
#let mem = schema.load("/gallery/riscv/mem_instr.yaml")
#let mem = schema.load(yaml("./mem_instr.yaml"))
#schema.render(mem, config: conf)
BIN
View File
Binary file not shown.
+5 -4
View File
@@ -1,16 +1,17 @@
#import "../src/lib.typ": *
#import "@preview/rivet:0.3.1": *
#let test-yaml = schema.load("/gallery/test.yaml")
#let test-yaml = schema.load(yaml("./test.yaml"))
#schema.render(test-yaml, config: config.config(
full-page: true
))
#let test-json = schema.load("/gallery/test.json")
#let test-json = schema.load(json("./test.json"))
#schema.render(test-json, config: config.blueprint(
full-page: true
))
#let test-xml = schema.load("/gallery/test.xml")
#let test-xml-raw = schema.xml-loader.parse(xml("./test.xml").first())
#let test-xml = schema.load(test-xml-raw)
#schema.render(test-xml, config: config.dark(
full-page: true
))
+9930 -8910
View File
File diff suppressed because it is too large Load Diff
+32 -2
View File
@@ -105,7 +105,7 @@ Since the XML format is quite different from the other, you might find it helpfu
A schema contains a dictionary of structures. There must be at least one defined structure named "main".
It can also optionnaly contain a "colors" dictionary. More details about this in #link(<format-colors>)[Colors]
It can also optionally contain a "colors" dictionary. More details about this in #link(<format-colors>)[Colors]
```json
{
@@ -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>
@@ -363,7 +393,7 @@ structures:
#let x = schema.xml-loader.load("schema.xml")
#let s = schema.load(x)
// From file
#let x = schema.xml-loader.parse(yaml("schema.yaml").first())
#let x = schema.xml-loader.parse(xml("schema.xml").first())
#let s = schema.load(x)
// Raw block
#let s = schema.load(```xml
-1
View File
@@ -1 +0,0 @@
#import "@preview/cetz:0.3.4": *
+2
View File
@@ -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,
)
}
+1 -1
View File
@@ -1,4 +1,4 @@
#let version = version(0,3,0)
#let version = version(0,3,1)
#import "config.typ"
#import "schema.typ"
-116
View File
@@ -1,116 +0,0 @@
#import "../cetz.typ": draw
#let BACKGROUND-LAYER = -1
#let draw-track(config, schema, struct, pos) = draw.group(name: "track", {
let colors = schema.at("colors", default: (:))
let total-width = struct.bits * config.bit-width
let bit-colors = (:)
if struct.name in colors {
for (s, col) in colors.at(struct.name) {
let (start, end) = rng.parse-span(s)
for i in range(start, end + 1) {
bit-colors.insert(str(i), col)
}
}
}
let boundaries = ()
for r in struct.ranges.values() {
boundaries.push(if config.ltr-bits {r.end} else {r.start})
boundaries.push(if config.ltr-bits {r.start - 1} else {r.end + 1})
}
draw.group(name: "bits", {
for i in range(struct.bits) {
let rank = struct.start + i
let j = if config.ltr-bits {i} else {struct.bits - i - 1}
let col = bit-colors.at(str(rank), default: config.background)
// Background color
draw.on-layer(BACKGROUND-LAYER, {
draw.rect(
(rel: (j * config.bit-width, 0), to: pos),
(rel: (config.bit-width, config.bit-height)),
fill: col,
stroke: none,
name: str(rank)
)
})
if j != struct.bits - 1 {
let top = str(rank) + ".north-east"
let bottom = str(rank) + ".south-east"
// Group boundary
if rank in boundaries {
draw.line(
top,
bottom,
stroke: config.border-color
)
// Bit separator
} else {
draw.line(
top,
((), 20%, bottom),
stroke: config.border-color
)
draw.line(
bottom,
((), 20%, top),
stroke: config.border-color
)
}
}
}
})
// Frame
draw.rect(
"bits.north-west", "bits.south-east",
name: "frame",
stroke: config.border-color + 2pt
)
// Names
for r in struct.ranges.values() {
let i0 = r.start
let i1 = r.end
if not config.ltr-bits {
(i0, i1) = (i1, i0)
}
let start = "bits." + str(i0) + ".north-west"
let end = "bits." + str(i1) + ".south-east"
draw.content(
(start, 50%, end),
r.name
)
}
let indices = if config.all-bit-i {
range(struct.start, struct.start + struct.bits)
} else {
struct.ranges.values().map(r => (r.start, r.end)).flatten().dedup()
}
for i in indices {
draw.content(
"bits." + str(i) + ".north",
anchor: "south",
str(i),
padding: 4pt // TODO: Maybe configurable
)
}
})
#let render(
config,
schema,
struct,
pos: (0, 0),
) = draw.group(name: struct.name, {
draw-track(config, schema, struct, pos)
})
+645 -5
View File
@@ -1,10 +1,650 @@
#import "cetz.typ": canvas
#import "render/structure.typ" as struct
#import "@preview/cetz:0.3.4": canvas, draw
#let render(config, schema, width: 100%) = {
canvas(length: 1pt, {
struct.render(config, schema, schema.structures.main)
#import "range.typ" as rng
#import "structure.typ"
#import "vec.typ"
#let draw-rect(color, x, y, width, height, thickness: 0) = {
let fill = none
let stroke = color + thickness * 1pt
if thickness == 0 {
fill = color
stroke = none
}
draw.rect((x, -y), (x + width, -y - height), fill: fill, stroke: stroke)
}
#let draw-text(
txt,
color,
x,
y,
anchor: "center",
font: none,
italic: false,
size: 1em,
fill: none
) = {
let text-params = (:)
if font != none {
text-params.insert("font", font)
}
if italic {
text-params.insert("style", "italic")
}
draw.content(
(x, -y),
text(txt, fill: color, size: size, ..text-params),
anchor: anchor,
stroke: none
)
}
// 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
draw.line((x0, -y0), (x1, -y1), stroke: color)
}
#let draw-lines(color, ..pts) = {
let pts = pts.pos().map(pt => (pt.at(0), -pt.at(1)))
draw.line(..pts, stroke: color)
}
#let draw-poly(color, ..pts, thickness: 0) = {
let pts = pts.pos().map(pt => (pt.at(0), -pt.at(1)))
let params = (
stroke: (paint: color, thickness: thickness),
fill: none
)
if thickness == 0 {
params = (
stroke: none,
fill: color
)
}
draw.line(..pts, ..params)
}
#let draw-underbracket(config, start, end, bits-y) = {
let bit-w = config.bit-width
let bit-h = config.bit-height
let x0 = start + bit-w / 2
let x1 = end - bit-w / 2
let y0 = bits-y + bit-h * 1.25
let y1 = bits-y + bit-h * 1.5
let col = config.link-color
draw-lines(col, (x0, y0), (x0, y1), (x1, y1), (x1, y0))
}
#let draw-link(
config,
start-x,
start-y,
end-x,
end-y
) = {
let bit-h = config.bit-height
let arrow-margin = config.arrow-margin
if end-x > start-x {
end-x -= arrow-margin
} else {
end-x += arrow-margin
}
draw-lines(
config.link-color,
(start-x, start-y + bit-h * 1.5),
(start-x, end-y + bit-h / 2),
(end-x, end-y + bit-h / 2),
)
}
#let draw-values(config, values, desc-x, desc-y) = {
let shapes = ()
let txt-col = config.text-color
let bit-w = config.bit-height // Why ? I don't remember
let gap = config.values-gap
for (val, desc) in values.pairs().sorted(key: p => p.first()) {
desc-y += gap
let txt = val + " = " + desc
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
} 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)
}
#let draw-description(
config,
range_,
start-x,
start-y,
width,
desc-x,
desc-y
) = {
let shapes = ()
let bit-w = config.bit-width
let bit-h = config.bit-height
if config.left-labels {
desc-x = calc.min(desc-x, start-x + width / 2 - bit-w)
} else {
desc-x = calc.max(desc-x, start-x + width / 2 + bit-w)
}
shapes += draw-underbracket(config, start-x, start-x + width, start-y)
let mid-x = start-x + width / 2
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 {
// 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
}
}
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
} 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_
(shapes_, _, desc-y) = draw-values(config, range_.values, txt-x, desc-y)
shapes += shapes_
}
desc-y += config.description-margin
return (shapes, desc-x, desc-y)
}
#let draw-arrow(config, start-x, start-y, end-x, end-y, label: "") = {
let shapes = ()
let dash-len = config.dash-length
let dash-space = config.dash-space
let arrow-size = config.arrow-size
let link-col = config.link-color
let txt-col = config.text-color
let arrow-label-dist = config.arrow-label-distance
let start = vec.vec(start-x, start-y)
let end = vec.vec(end-x, end-y)
let start-end = vec.sub(end, start)
let d = vec.normalize(start-end)
let dashes = int(vec.mag(start-end) / (dash-len + dash-space))
for i in range(dashes) {
let a = vec.add(
start,
vec.mul(d, i * (dash-len + dash-space))
)
let b = vec.add(
a,
vec.mul(d, dash-len)
)
shapes += draw-line(link-col, (a.x, a.y), (b.x, b.y))
}
let n = vec.vec(d.y, -d.x)
let width = arrow-size / 1.5
let p1 = vec.sub(
end,
vec.sub(
vec.mul(d, arrow-size),
vec.mul(n, width)
)
)
let p2 = vec.sub(
end,
vec.add(
vec.mul(d, arrow-size),
vec.mul(n, width)
)
)
shapes += draw-poly(
link-col,
(end.x, end.y),
(p1.x, p1.y),
(p2.x, p2.y)
)
if label != "" {
shapes += draw-text(
label,
txt-col,
(start.x + end.x) / 2,
(start.y + end.y) / 2 + arrow-label-dist,
anchor: "north"
)
}
return shapes
}
#let draw-dependency(
draw-struct, config,
struct, schema, bits-x, bits-y, range_, desc-x, desc-y
) = {
let shapes = ()
let bit-w = config.bit-width
let bit-h = config.bit-height
let arrow-margin = config.arrow-margin
let to-real-i(i) = {
return if config.ltr-bits {
i
} else {
struct.bits - i - 1
}
}
let start-i = to-real-i(if config.ltr-bits {range_.start} else {range_.end})
let start-x = bits-x + start-i * bit-w
let width = rng.bits(range_) * bit-w
shapes += draw-underbracket(config, start-x, start-x + width, bits-y)
let depend-key = rng.key(..range_.depends-on)
let depend-range = struct.ranges.at(depend-key)
let prev-range-y = bits-y + bit-h * 1.5
let prev-depend-y = if depend-range.last-value-y == -1 {
bits-y + bit-h * 1.5
} else {
depend-range.last-value-y
}
let depend-start-i = to-real-i(depend-range.end)
let depend-start-x = bits-x + depend-start-i * bit-w
let depend-width = rng.bits(depend-range) * bit-w
let depend-mid = depend-start-x + depend-width / 2
shapes += draw-underbracket(config, depend-start-x, depend-start-x + depend-width, bits-y)
for (val, data) in range_.values.pairs().sorted(key: p => p.first()) {
shapes += draw-arrow(config, depend-mid, prev-depend-y, depend-mid, desc-y - arrow-margin)
let val-ranges = (:)
for i in range(rng.bits(depend-range)) {
val-ranges.insert(
str(depend-range.end - i),
(name: val.at(i))
)
}
let val-struct = (
bits: rng.bits(depend-range),
start: depend-range.start,
ranges: val-ranges
)
val-struct = structure.load("", val-struct)
let shapes_
(shapes_, ..) = draw-struct(config, val-struct, schema, ox: depend-start-x, oy: desc-y)
shapes += shapes_
let y = desc-y + bit-h * 1.5
let x1
let x2
// Arrow from left to right
let i1 = to-real-i(range_.start)
let i2 = to-real-i(depend-range.end)
if i2 < i1 {
x1 = depend-start-x + depend-width + arrow-margin
x2 = start-x - arrow-margin
// Arrow from right to left
} else {
x1 = depend-start-x - arrow-margin
x2 = start-x + width + arrow-margin
}
shapes += draw-arrow(config, x1, y, x2, y, label: data.description)
shapes += draw-arrow(config,
start-x + width - bit-w,
prev-range-y,
start-x + width - bit-w,
desc-y + bit-h - arrow-margin
)
prev-depend-y = desc-y + bit-h * 2 + arrow-margin
prev-range-y = prev-depend-y
depend-range.last-value-y = prev-depend-y
(shapes_, desc-y) = draw-struct(config, schema.structures.at(data.structure), schema, ox: start-x, oy: desc-y)
shapes += shapes_
}
struct.ranges.at(depend-key) = depend-range
return (shapes, desc-x, desc-y, struct)
}
#let draw-structure(config, struct, schema, ox: 0, oy: 0) = {
let shapes
let colors = schema.at("colors", default: (:))
let bg-col = config.background
let txt-col = config.text-color
let border-col = config.border-color
let bit-w = config.bit-width
let bit-h = config.bit-height
let (bits-x, bits-y) = (ox, oy + bit-h)
let bits-width = struct.bits * bit-w
let start-bit = struct.start
let bit-colors = (:)
let to-real-i(i) = {
return if config.ltr-bits {
i - start-bit
} else {
struct.bits - i - 1 + start-bit
}
}
let to-bit-i(real-i) = {
return if config.ltr-bits {
real-i + start-bit
} else {
struct.bits - real-i - 1 + start-bit
}
}
for i in range(struct.bits) {
bit-colors.insert(str(i), bg-col)
}
if struct.name in colors {
for (s, col) in colors.at(struct.name) {
let (start, end) = rng.parse-span(s)
for i in range(start, end + 1) {
let real-i = to-real-i(i)
bit-colors.insert(str(real-i), col)
}
}
}
let range-boundaries = ()
for r in struct.ranges.values() {
let start-i = to-real-i(if config.ltr-bits {r.start} else {r.end})
let end-i = to-real-i(if config.ltr-bits {r.end} else {r.start}) + 1
range-boundaries.push(start-i)
range-boundaries.push(end-i)
}
// Draw colors
for i in range(struct.bits) {
let bit-x = ox + i * bit-w
shapes += draw-rect(bit-colors.at(str(i)), bit-x, bits-y, bit-w+1, bit-h)
}
// Draw rectangle around structure
shapes += draw-rect(border-col, bits-x, bits-y, bits-width, bit-h, thickness: 2)
let indices = range(struct.start, struct.start + struct.bits)
if not config.all-bit-i {
indices = ()
for r in struct.ranges.values() {
indices.push(r.start)
indices.push(r.end)
}
// ensure first and last bits are included
if not indices.contains(0) { indices.insert(0, struct.start) }
if not indices.contains(struct.bits + struct.start - 1) {
indices.push(struct.bits + struct.start - 1)
}
}
for i in range(struct.bits) {
let bit-x = ox + i * bit-w
let bit-i = to-bit-i(i)
if bit-i in indices {
shapes += draw-text(
str(bit-i),
txt-col,
bit-x + bit-w / 2,
oy + bit-h / 2
)
}
// Draw separator
if i != 0 and not i in range-boundaries {
shapes += draw-line(border-col, (bit-x, bits-y), (bit-x, bits-y + bit-h * 0.2))
shapes += draw-line(border-col, (bit-x, bits-y + bit-h * 0.8), (bit-x, bits-y + bit-h))
}
}
let ranges = structure.get-sorted-ranges(struct)
if config.ltr-bits {
ranges = ranges.rev()
}
if config.left-labels {
ranges = ranges.rev()
}
let desc-x
if config.force-descs-on-side {
desc-x = config.margins.at(3) + schema.structures.main.bits * bit-w
if config.left-labels {
desc-x = config.width - desc-x
}
} else {
desc-x = ox
if config.left-labels {
desc-x += struct.bits * bit-w
}
}
let desc-y = bits-y + bit-h * 2
// Names + simple descriptions
for (i, range_) in ranges.enumerate() {
let start-i = to-real-i(if config.ltr-bits {range_.start} else {range_.end})
let end-i = to-real-i(if config.ltr-bits {range_.end} else {range_.start})
let start-x = bits-x + start-i * bit-w
let width = rng.bits(range_) * bit-w
let name-x = start-x + width / 2
let name-y = bits-y + bit-h / 2
let line-x = if config.ltr-bits {start-x + width} else {start-x}
shapes += draw-line(border-col, (line-x, bits-y), (line-x, bits-y + bit-h))
shapes += draw-text(range_.name, txt-col, name-x, name-y, fill: bg-col)
// paint end line only if needed
let is-not-limit = if config.ltr-bits { start-i != 0 } else { end-i != struct.bits - 1 }
if (
is-not-limit
and ranges
.at(
i
+ {
if config.ltr-bits and i < ranges.len() - 1 { 1 } else { -1 }
},
)
.end
!= range_.start - 1
) {
line-x += if config.ltr-bits { -width } else { width }
shapes += draw-line(
border-col,
(line-x, bits-y),
(line-x, bits-y + bit-h),
)
}
if range_.description != "" {
let shapes_
(shapes_, desc-x, desc-y) = draw-description(
config, range_, start-x, bits-y, width, desc-x, desc-y
)
shapes += shapes_
}
}
// Dependencies
for range_ in ranges {
if range_.values() != none and range_.depends-on != none {
let shapes_
(shapes_, desc-x, desc-y, struct) = draw-dependency(
draw-structure, config,
struct, schema, bits-x, bits-y, range_, desc-x, desc-y,
)
shapes += shapes_
}
}
return (shapes, desc-y)
}
#let render(config, schema, width: 100%) = context {
set text(
font: config.default-font-family,
size: config.default-font-size
)
let main = schema.structures.main
let ox = config.margins.at(3)
if config.left-labels {
ox = config.width - ox - main.bits * config.bit-width
}
let params = if config.full-page {
(
width: auto,
height: auto,
fill: config.background,
margin: 0cm
)
} else {
(:)
}
set page(..params)
let cnvs = canvas(length: 1pt, background: config.background, {
let (shapes, _) = draw-structure(
config, main, schema,
ox: ox,
oy: config.margins.at(0)
)
// Workaround for margins
draw.group(name: "g", padding: config.margins, shapes)
draw.line(
"g.north-west",
"g.north-east",
"g.south-east",
"g.south-west",
stroke: none,
fill: none
)
})
if config.full-page {
cnvs
} else {
layout(size => {
let m = measure(cnvs)
let w = m.width
let h = m.height
let base-w = if type(width) == ratio {
size.width * width
} else {
width
}
let r = if w == 0 {
0
} else {
base-w / w
}
let new-w = w * r
let new-h = h * r
r *= 100%
box(
width: new-w,
height: new-h,
scale(x: r, y: r, cnvs, reflow: true)
)
})
}
}
#let make(config) = {
+3 -2
View File
@@ -76,8 +76,9 @@
}
return (
bits: elmt.attrs.bits,
ranges: ranges
bits: int(elmt.attrs.bits),
ranges: ranges,
start: elmt.attrs.at("start", default: 0)
)
}
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "rivet"
version = "0.3.0"
version = "0.3.1"
compiler = "0.13.1"
repository = "https://git.kb28.ch/HEL/rivet-typst"
entrypoint = "src/lib.typ"
@@ -9,6 +9,6 @@ authors = [
]
categories = [ "visualization" ]
license = "Apache-2.0"
description = "Register / Instruction Visualizer & Explainer Tool with Typst, using CeTZ"
description = "Register / Instruction Visualizer & Explainer Tool, using CeTZ"
keywords = [ "assembly", "instruction", "binary" ]
exclude = [ "gallery", "justfile", "docs" ]