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
HEL 6f13df815f documented start property of structures 2025-11-10 21:05:28 +01:00
HEL 1dd66fd587 Merge pull request 'Support for non-consecutive ranges' (#13) from rajayonin/rivet-typst:non-consecutive-ranges into dev
Reviewed-on: #13
Reviewed-by: Louis Heredero <louis@herdac.ch>
2025-10-21 06:02:04 +00:00
rajayonin c7c777f5fa ensure first and last bits are included when all-bit-i is set 2025-10-20 19:06:53 +02:00
rajayonin 7ef7f653b7 support for non-consecutive ranges
Paints the start/end line of a range if there was no previous range.
2025-10-20 18:01:28 +02:00
HEL dd6d38a282 fixed README example usage 2025-05-03 00:54:38 +02:00
HEL 6140cff3c5 updated CHANGELOG.md 2025-05-03 00:39:03 +02:00
HEL 1be1dccb6b bumped version in README 2025-05-03 00:35:35 +02:00
HEL 422681ba6e Merge pull request 'Left to right bits + Loading manual section' (#6) from feat/left-to-right-bits-1 into dev
Reviewed-on: #6
2025-04-15 17:45:51 +00:00
HEL 23af042a36 completed CHANGELOG 2025-04-15 19:37:29 +02:00
HEL c7d12bf6c6 bumped to 0.3.0 + completed manual 2025-04-15 19:32:05 +02:00
HEL 6394c8e5c5 updated manual 2025-04-15 18:30:38 +02:00
HEL 65d11fc920 added ltr-bits config option 2025-04-15 18:19:51 +02:00
HEL a935484665 updated changelog 2025-02-23 14:15:59 +01:00
HEL c0a42aa8a6 fixed raw decode warning + updated doc 2025-02-23 14:12:50 +01:00
HEL 37dce1f7c9 updated to CeTZ 0.3.2 and Typst 0.13.0 2025-02-23 13:26:28 +01:00
HEL a9633f25a3 updated to Typst 0.12.0 + CeTZ 0.3.1 2024-10-30 16:24:51 +01:00
HEL a929f506ac modified gallery.bash to compile recursively 2024-10-30 16:19:01 +01:00
HEL aa9a082ba5 fixed missing bit i on dependencies 2024-10-30 16:18:32 +01:00
HEL 6f502f2e18 updated CHANGELOG.md 2024-10-02 18:58:11 +02:00
HEL 03e9904d43 bumped to 0.1.0 for publication 2024-10-02 18:54:42 +02:00
29 changed files with 456 additions and 159 deletions
+26
View File
@@ -1,5 +1,31 @@
# Changelog # 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
- updated Tidy to 0.4.2
- updated Codly to 1.3.0 and codly-languages to 0.1.8
- added `ltr-bits` config option
- added a "Loading" section to the manual
## [v0.2.0] - 2025-02-23
- updated CeTZ to 0.3.2
- updated to Typst 0.13.0
- fixed missing bit index on dependencies
- updated docs (Tidy, codelst -> codly)
## [v0.1.0] - 2024-10-02
- prepared for publication in Typst Universe
## [v0.0.2] - 2024-06-15 ## [v0.0.2] - 2024-06-15
### Added ### Added
- `width` parameter to `schema.render` for easier integration - `width` parameter to `schema.render` for easier integration
+6 -9
View File
@@ -9,7 +9,7 @@ It is based on the [homonymous Python script](https://git.kb28.ch/HEL/rivet/)
<tr> <tr>
<td> <td>
<a href="./gallery/example1.typ"> <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> </a>
</td> </td>
</tr> </tr>
@@ -19,7 +19,7 @@ It is based on the [homonymous Python script](https://git.kb28.ch/HEL/rivet/)
<tr> <tr>
<td> <td>
<a href="./gallery/example2.typ"> <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> </a>
</td> </td>
</tr> </tr>
@@ -31,14 +31,11 @@ It is based on the [homonymous Python script](https://git.kb28.ch/HEL/rivet/)
*Click on the example image to jump to the code.* *Click on the example image to jump to the code.*
## Usage ## Usage
For information, see the [manual](manual.pdf) For more information, see the [manual](manual.pdf)
To use this package, simply import `schema` [src/lib.typ](src/lib.typ) and call `schema.load` to parse a schema description. Then use `schema.render` to render it, et voilà ! 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 ```typ
#import "src/lib.typ": schema #import "@preview/rivet:0.3.1": schema
#let doc = schema.load("path/to/schema.yaml") #let doc = schema.load(yaml("path/to/schema.yaml"))
#schema.render(doc) #schema.render(doc)
``` ```
## Installing
> TODO
+9 -5
View File
@@ -1,7 +1,7 @@
/// Creates a dictionary of all configuration parameters /// Creates a dictionary of all configuration parameters
/// ///
/// - default-font-family (str): The default font family /// - default-font-family (str): The default font family
/// - default font-size (length): The absolute default font size /// - default-font-size (length): The absolute default font size
/// - italic-font-family (str): The italic font family (for value descriptions) /// - italic-font-family (str): The italic font family (for value descriptions)
/// - italic-font-size (length): The absolute italic font size /// - italic-font-size (length): The absolute italic font size
/// - background (color): The diagram background color /// - background (color): The diagram background color
@@ -15,7 +15,7 @@
/// - dash-length (float): The length of individual dashes (for dashed lines) /// - dash-length (float): The length of individual dashes (for dashed lines)
/// - dash-space (float): The space between two dashes (for dashed lines) /// - dash-space (float): The space between two dashes (for dashed lines)
/// - arrow-size (float): The size of arrow heads /// - arrow-size (float): The size of arrow heads
/// - margins (tuple[float]): TODO -> remove /// - margins (tuple): TODO -> remove
/// - arrow-margin (float): The margin between arrows and the structures they link /// - arrow-margin (float): The margin between arrows and the structures they link
/// - values-gap (float): The gap between individual values /// - values-gap (float): The gap between individual values
/// - arrow-label-distance (float): The distance between arrows and their labels /// - arrow-label-distance (float): The distance between arrows and their labels
@@ -25,6 +25,8 @@
/// - height (float): TODO -> remove /// - height (float): TODO -> remove
/// - full-page (bool): If true, the page will be resized to fit the diagram and take the background color /// - 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 /// - 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 /// -> dictionary
#let config( #let config(
default-font-family: "Ubuntu Mono", default-font-family: "Ubuntu Mono",
@@ -51,13 +53,15 @@
width: 1200, width: 1200,
height: 800, height: 800,
full-page: false, full-page: false,
all-bit-i: true all-bit-i: true,
ltr-bits: false,
description-width: none
) = {} ) = {}
/// Dark theme config /// Dark theme config
/// - ..args (any): see #doc-ref("config.config") /// - ..args (any): see @@config()
#let dark(..args) = {} #let dark(..args) = {}
/// Blueprint theme config /// Blueprint theme config
/// - ..args (any): see #doc-ref("config.config") /// - ..args (any): see @@config()
#let blueprint(..args) = {} #let blueprint(..args) = {}
+1 -2
View File
@@ -1,4 +1,3 @@
#import "@preview/cetz:0.2.2": draw
#import "../src/lib.typ": schema #import "../src/lib.typ": schema
#import "../src/util.typ" #import "../src/util.typ"
@@ -14,7 +13,7 @@
box( box(
stroke: black + 1pt, stroke: black + 1pt,
radius: .5em, radius: .5em,
fill: if fill {yellow.lighten(80%)} else {none}, fill: if fill {orange.lighten(95%)} else {none},
if show-src { if show-src {
let src-block = align(left, raw(src, lang: "typc")) let src-block = align(left, raw(src, lang: "typc"))
table( table(
+9 -6
View File
@@ -1,16 +1,19 @@
/// Loads a schema from a file or a raw block. /// Loads a schema from a file or a raw block.
/// This function returns a dictionary of structures /// This function returns a dictionary of structures\
/// See the #link(<loading>)[Loading] chapter for examples of schema loading for each supported format
/// ///
/// Supported formats: #schema.valid-extensions.map(e => raw("." + e)).join(", ") /// Supported formats: #schema.valid-extensions.map(e => raw("." + e)).join(", ")
/// - path-or-schema (str, raw, dictionary): If it is a string, defines the path to load. \ /// - path-or-schema (str, raw, dictionary):
/// If it is a raw block, its content is directly parsed (the block's language will define the format to use) \ /// #list(
/// If it is a dictionary, it directly defines the schema structure /// [If it is a string, defines the path to load.\ #emoji.warning Warning: this will only work if this package is part of your project, as packages installed in the `@local` or `@preview` namespace cannot access project files],
/// [If it is a raw block, its content is directly parsed (the block's language will define the format to use)],
/// [If it is a dictionary, it directly defines the schema structure]
/// )
/// -> dictionary /// -> dictionary
#let load(path-or-schema) = {} #let load(path-or-schema) = {}
/// Renders the given schema /// Renders the given schema
/// This functions /// - schema (dictionary): A schema dictionary, as returned by @@load()
/// - schema (dictionary): A schema dictionary, as returned by #doc-ref("schema.load")
/// - config (auto, dictionary): The configuration parameters, as returned by #doc-ref("config.config") /// - config (auto, dictionary): The configuration parameters, as returned by #doc-ref("config.config")
/// - width (ratio, length): The width of the generated figure /// - width (ratio, length): The width of the generated figure
#let render(schema, config: auto, width: 100%) = {} #let render(schema, config: auto, width: 100%) = {}
-40
View File
@@ -1,40 +0,0 @@
#!/bin/bash
PDFS=false
while getopts "p" flag
do
case "${flag}" in
p) PDFS=true;;
esac
done
echo "Generating gallery images"
set -- ./gallery/example*.typ
cnt="$#"
i=1
for f
do
f2="${f/typ/png}"
echo "($i/$cnt) $f -> $f2"
typst c --root ./ "$f" "$f2"
i=$((i+1))
done
if [ "$PDFS" = true ]
then
echo
echo "Generating gallery PDFs"
set -- ./gallery/*.typ
cnt="$#"
i=1
for f
do
f2="${f/typ/pdf}"
echo "($i/$cnt) $f -> $f2"
typst c --root ./ "$f" "$f2"
i=$((i+1))
done
fi
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 KiB

After

Width:  |  Height:  |  Size: 378 KiB

+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( #schema.render(example, config: config.config(
full-page: true full-page: true
)) ))
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

After

Width:  |  Height:  |  Size: 303 KiB

+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( #schema.render(example, config: config.blueprint(
full-page: true, full-page: true,
left-labels: true left-labels: true
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 487 KiB

After

Width:  |  Height:  |  Size: 485 KiB

+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) //#schema.render(example)
= Chapter 1 = Chapter 1
Binary file not shown.
+4 -4
View File
@@ -1,14 +1,14 @@
#import "../../src/lib.typ": * #import "@preview/rivet:0.3.1": *
#let conf = config.config( #let conf = config.config(
full-page: true, full-page: true,
left-labels: 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) #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) #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) #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( #schema.render(test-yaml, config: config.config(
full-page: true 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( #schema.render(test-json, config: config.blueprint(
full-page: true 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( #schema.render(test-xml, config: config.dark(
full-page: true full-page: true
)) ))
+12
View File
@@ -0,0 +1,12 @@
# Local Variables:
# mode: makefile
# End:
gallery_dir := "./gallery"
set shell := ["bash", "-uc"]
manual:
typst c manual.typ manual.pdf
gallery:
for f in "{{gallery_dir}}"/*.typ; do typst c --root . "$f" "${f/typ/pdf}"; done
for f in "{{gallery_dir}}"/example*.typ; do typst c --root . "$f" "${f/typ/png}"; done
BIN
View File
Binary file not shown.
+206 -34
View File
@@ -1,22 +1,24 @@
#import "@preview/tidy:0.3.0" #import "@preview/tidy:0.4.2"
#import "@preview/codelst:2.0.1": sourcecode #import "@preview/codly:1.3.0": codly-init, codly
#import "@preview/showybox:2.0.1": showybox #import "@preview/codly-languages:0.1.8": codly-languages
#import "@preview/showybox:2.0.4": showybox
#import "src/lib.typ" #import "src/lib.typ"
#import "src/schema.typ" #import "src/schema.typ"
#import "docs/examples.typ" #import "docs/examples.typ"
#show: codly-init
#codly(languages: codly-languages)
#set heading(numbering: (..num) => if num.pos().len() < 4 { #set heading(numbering: (..num) => if num.pos().len() < 4 {
numbering("1.1", ..num) numbering("1.1", ..num)
}) })
#{
outline(indent: true, depth: 3)
}
#set page(numbering: "1/1", header: align(right)[rivet #sym.dash.em v#lib.version]) #set page(numbering: "1/1", header: align(right)[rivet #sym.dash.em v#lib.version])
#let doc-ref(target, full: false, var: false) = { #let doc-ref(target, full: false, var: false) = {
let (module, func) = target.split(".") let (module, func) = target.split(".")
let label-name = module + func let label-name = module + "-" + func
let display-name = func let display-name = func
if full { if full {
display-name = target display-name = target
@@ -25,7 +27,7 @@
label-name += "()" label-name += "()"
display-name += "()" display-name += "()"
} }
link(label(label-name))[#display-name] link(label(label-name), raw(display-name))
} }
#let note(it) = showybox( #let note(it) = showybox(
@@ -43,6 +45,37 @@
#show link: set text(blue) #show link: set text(blue)
#let sch = schema.load(```yaml
structures:
main:
bits: 5
ranges:
4:
name: R
description: Register
3:
name: I
description: Instruction
2:
name: V
description: Visualizer
1:
name: E
description: Explainer
0:
name: T
description: Tool
```)
#align(center, schema.render(sch, width: 50%, config: lib.config.config(left-labels: true)))
#v(1fr)
#box(
width: 100%,
stroke: black,
inset: 1em,
outline(indent: auto, depth: 3)
)
#pagebreak(weak: true)
= Introduction = Introduction
This package provides a way to make beautiful register diagrams using the CeTZ package. It can be used to document Assembly instructions or binary registers This package provides a way to make beautiful register diagrams using the CeTZ package. It can be used to document Assembly instructions or binary registers
@@ -51,26 +84,30 @@ This is a port of the #link("https://git.kb28.ch/HEL/rivet")[homonymous Python s
= Usage = Usage
Simply import `schema` from #link("src/lib.typ") and call `schema.load` to parse a schema description. Then use `schema.render` to render it, et voilà ! #let import-stmt = "#import \"@preview/rivet:" + str(lib.version) + "\""
#pad(left: 1em)[```typ
#import "src/lib.typ": schema Simply import the `schema` module and call `schema.load` to parse a schema description. Then use `schema.render` to render it, et voilà !
#let doc = schema.load("path/to/schema.yaml") #raw(block: true, lang: "typ", ```typ
$import: schema
#let doc = schema.load(yaml("path/to/schema.yaml"))
#schema.render(doc) #schema.render(doc)
```] ```.text.replace("$import", import-stmt))
Please read the #link(<loading>)[Loading] chapter for more detailed explanations on how to load schema descriptions.
= Format = Format
This section describes the structure of a schema definition. The examples given use the JSON syntax. For examples in different formats, see #link("https://git.kb28.ch/HEL/rivet-typst/src/branch/main/gallery/test.yaml")[test.yaml], #link("https://git.kb28.ch/HEL/rivet-typst/src/branch/main/gallery/test.json")[test.json] and #link("https://git.kb28.ch/HEL/rivet-typst/src/branch/main/gallery/test.xml")[test.xml]. You can also directly define a schema using Typst dictionaries and arrays. This section describes the structure of a schema definition. The examples given use the JSON syntax. For examples in different formats, see #link("https://git.kb28.ch/HEL/rivet-typst/src/branch/main/gallery/test.yaml")[test.yaml], #link("https://git.kb28.ch/HEL/rivet-typst/src/branch/main/gallery/test.json")[test.json] and #link("https://git.kb28.ch/HEL/rivet-typst/src/branch/main/gallery/test.xml")[test.xml]. You can also directly define a schema using Typst dictionaries and arrays.
Since the XML format is quite different from the other, you might find it helpful to look at the examples on GitHub to get familiar with it. Since the XML format is quite different from the other, you might find it helpful to look at the examples in the #link("https://git.kb28.ch/HEL/rivet-typst/src/branch/main/gallery/")[Gitea repo] to get familiar with it.
== Main layout == Main layout
A schema contains a dictionary of structures. The must be at least one defined structure named "main". 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]
#sourcecode[```json ```json
{ {
"structures": { "structures": {
"main": { "main": {
@@ -85,7 +122,7 @@ It can also optionnaly contain a "colors" dictionary. More details about this in
... ...
} }
} }
```] ```
#pagebreak(weak: true) #pagebreak(weak: true)
@@ -93,9 +130,9 @@ It can also optionnaly contain a "colors" dictionary. More details about this in
A structure has a given number of bits and one or multiple ranges. Each range of bits can have a name, a description and / or values with special meaning (see #link(<format-range>)[Range]). A range's structure can also depend on another range's value (see #link(<format-dependencies>)[Dependencies]). A structure has a given number of bits and one or multiple ranges. Each range of bits can have a name, a description and / or values with special meaning (see #link(<format-range>)[Range]). A range's structure can also depend on another range's value (see #link(<format-dependencies>)[Dependencies]).
The range name (or key) defines the left- and rightmost bits (e.g. `7-4` goes from bit 7 down to bit 4). Bits are displayed in big-endian, i.e. the leftmost bit has the highest value. The range name (or key) defines the left- and rightmost bits (e.g. `7-4` goes from bit 7 down to bit 4). The order in which you write the range is not important, meaning `7-4` is equivalent to `4-7`. Bits are displayed in big-endian, i.e. the leftmost bit has the highest value, except if you enable the `ltr-bits` #doc-ref("config.config") option.
#sourcecode[```json ```json
"main": { "main": {
"bits": 8, "bits": 8,
"ranges": { "ranges": {
@@ -113,7 +150,35 @@ The range name (or key) defines the left- and rightmost bits (e.g. `7-4` goes fr
} }
} }
} }
```] ```
=== Start <format-start>
By default, structures start at bit 0, but you may want to number bits from 1, or another arbitrary value. To do this, you can set the `start` property of a structure to the desired start value. For example,
```json
"main": {
"bits": 8,
"start": 4,
"ranges": {
"11-7": { ... },
"6-4": { ... }
}
}
```
#let start-schema = (structures: (main: (bits: 8, start: 4, ranges: ("11-7": (name: ""), "6-4": (name: "")))))
would render as
#align(
center,
schema.render(
schema.load(start-schema),
width: 50%
)
)
== Range <format-range> == Range <format-range>
@@ -125,7 +190,7 @@ For values depending on other ranges, see #link(<format-dependencies>)[Dependenc
In YAML, make sure to wrap values in quotes because some values can be interpreted as octal notation (e.g. 010 #sym.arrow.r 8) In YAML, make sure to wrap values in quotes because some values can be interpreted as octal notation (e.g. 010 #sym.arrow.r 8)
] ]
#sourcecode[```json ```json
"3-2": { "3-2": {
"name": "op", "name": "op",
"description": "Logical operation", "description": "Logical operation",
@@ -136,7 +201,37 @@ For values depending on other ranges, see #link(<format-dependencies>)[Dependenc
"11": "NAND" "11": "NAND"
} }
} }
```] ```
=== 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) #pagebreak(weak: true)
@@ -144,9 +239,9 @@ For values depending on other ranges, see #link(<format-dependencies>)[Dependenc
The structure of one range may depend on the value of another. To represent this situation, first indicate on the child range the range on which it depends. The structure of one range may depend on the value of another. To represent this situation, first indicate on the child range the range on which it depends.
Then, in its values, indicate which structure to use. A description can also be added (displayed above the horizontal dependency arrow) Then, in its values, indicate which structure to use. A description can also be added (displayed below the horizontal dependency arrow)
#sourcecode[```json ```json
"7-4": { "7-4": {
... ...
"depends-on": "0", "depends-on": "0",
@@ -161,11 +256,11 @@ Then, in its values, indicate which structure to use. A description can also be
} }
} }
} }
```] ```
Finally, add the sub-structures to the structure dictionary: Finally, add the sub-structures to the structure dictionary:
#sourcecode[```json ```json
{ {
"structures": { "structures": {
"main": { "main": {
@@ -182,7 +277,7 @@ Finally, add the sub-structures to the structure dictionary:
... ...
} }
} }
```] ```
#pagebreak(weak: true) #pagebreak(weak: true)
@@ -190,7 +285,7 @@ Finally, add the sub-structures to the structure dictionary:
You may want to highlight some ranges to make your diagram more readable. For this, you can use colors. Colors may be defined in a separate dictionary, at the same level as the "structures" dictionary: You may want to highlight some ranges to make your diagram more readable. For this, you can use colors. Colors may be defined in a separate dictionary, at the same level as the "structures" dictionary:
#sourcecode[```json ```json
{ {
"structures": { "structures": {
... ...
@@ -199,11 +294,11 @@ You may want to highlight some ranges to make your diagram more readable. For th
... ...
} }
} }
```] ```
It can contain color definitions for any number of ranges. For each range, you may then define a dictionary mapping bit ranges to a particular color: It can contain color definitions for any number of ranges. For each range, you may then define a dictionary mapping bit ranges to a particular color:
#sourcecode[```json ```json
"colors": { "colors": {
"main": { "main": {
"31-28": "#ABCDEF", "31-28": "#ABCDEF",
@@ -213,7 +308,7 @@ It can contain color definitions for any number of ranges. For each range, you m
"19-10": [12, 34, 56] "19-10": [12, 34, 56]
} }
} }
```] ```
Valid color formats are: Valid color formats are:
- hex string starting with `#`, e.g. `"#23fa78"` - hex string starting with `#`, e.g. `"#23fa78"`
@@ -223,7 +318,7 @@ Valid color formats are:
#note[ #note[
The XML format implements colors a bit differently. Instead of having a "colors" dictionary, color definitions are directly put on the same level as structure definitions. For this, you can use a `color` node with the attributes "structure", "color", "start" and "end", like so: The XML format implements colors a bit differently. Instead of having a "colors" dictionary, color definitions are directly put on the same level as structure definitions. For this, you can use a `color` node with the attributes "structure", "color", "start" and "end", like so:
#sourcecode[```xml ```xml
<schema> <schema>
<structure id="main" bits="8"> <structure id="main" bits="8">
... ...
@@ -232,11 +327,86 @@ Valid color formats are:
<color structure="main" color="#FF0000" start="4" end="7" /> <color structure="main" color="#FF0000" start="4" end="7" />
<color structure="main" color="255,0,0" start="0" end="3" /> <color structure="main" color="255,0,0" start="0" end="3" />
</schema> </schema>
```] ```
] ]
#pagebreak(weak: true) #pagebreak(weak: true)
= Loading <loading>
Due to current limitations of the Typst compiler, the package can only access its own files, unless directly included in your project. For this reason, rivet cannot load a schema from a path, and you will need to read the files yourself to pass their contents to the package.
Here are a number of ways you can load your schemas:
== JSON Format
````typ
// From file (ONLY IF PACKAGE INSTALLED IN PROJECT)
#let s = schema.load("schema.json")
// From file
#let s = schema.load(json("schema.json"))
// Raw block
#let s = schema.load(```json
{
"structures": {
"main": {
...
}
}
}
```)
````
== YAML Format
````typ
// From file (ONLY IF PACKAGE INSTALLED IN PROJECT)
#let s = schema.load("schema.yaml")
// From file
#let s = schema.load(yaml("schema.yaml"))
// Raw block
#let s = schema.load(```yaml
structures:
main:
...
```)
````
== Typst Format
```typ
#let s = schema.load((
structures: (
main: (
...
)
)
))
```
#pagebreak(weak: true)
== XML Format
````typ
// From file (ONLY IF PACKAGE INSTALLED IN PROJECT)
#let x = schema.xml-loader.load("schema.xml")
#let s = schema.load(x)
// From file
#let x = schema.xml-loader.parse(xml("schema.xml").first())
#let s = schema.load(x)
// Raw block
#let s = schema.load(```xml
<schema>
<structure id="main" bits="32">
...
</structure>
</schema>
```)
````
#pagebreak(weak: true)
= Config presets = Config presets
Aside from the default config, some example presets are also provided: Aside from the default config, some example presets are also provided:
@@ -254,6 +424,7 @@ Aside from the default config, some example presets are also provided:
#let doc-config = tidy.parse-module( #let doc-config = tidy.parse-module(
read("docs/config.typ"), read("docs/config.typ"),
name: "config", name: "config",
old-syntax: true,
scope: ( scope: (
doc-ref: doc-ref doc-ref: doc-ref
) )
@@ -265,6 +436,7 @@ Aside from the default config, some example presets are also provided:
#let doc-schema = tidy.parse-module( #let doc-schema = tidy.parse-module(
read("docs/schema.typ"), read("docs/schema.typ"),
name: "schema", name: "schema",
old-syntax: true,
scope: ( scope: (
schema: schema, schema: schema,
doc-ref: doc-ref doc-ref: doc-ref
+6 -2
View File
@@ -23,7 +23,9 @@
width: 1200, width: 1200,
height: 800, height: 800,
full-page: false, full-page: false,
all-bit-i: true all-bit-i: true,
ltr-bits: false,
description-width: none,
) = { ) = {
return ( return (
default-font-family: default-font-family, default-font-family: default-font-family,
@@ -50,7 +52,9 @@
width: width, width: width,
height: height, height: height,
full-page: full-page, full-page: full-page,
all-bit-i: all-bit-i 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,0,2)) #let version = version(0,3,1)
#import "config.typ" #import "config.typ"
#import "schema.typ" #import "schema.typ"
+3
View File
@@ -15,6 +15,9 @@
} }
let start = int(start-end.last()) let start = int(start-end.last())
let end = int(start-end.first()) let end = int(start-end.first())
if end < start {
(start, end) = (end, start)
}
return (start, end) return (start, end)
} }
+148 -33
View File
@@ -1,4 +1,4 @@
#import "@preview/cetz:0.2.2": canvas, draw #import "@preview/cetz:0.3.4": canvas, draw
#import "range.typ" as rng #import "range.typ" as rng
#import "structure.typ" #import "structure.typ"
@@ -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 draw-line(color, a, b) = {
let (x0, y0) = a let (x0, y0) = a
let (x1, y1) = b let (x1, y1) = b
@@ -113,15 +131,26 @@
for (val, desc) in values.pairs().sorted(key: p => p.first()) { for (val, desc) in values.pairs().sorted(key: p => p.first()) {
desc-y += gap desc-y += gap
let txt = val + " = " + desc let txt = val + " = " + desc
shapes += draw-text( if config.description-width == none {
txt, txt-col, desc-x + bit-w / 2, desc-y, shapes += draw-text(
anchor: "north-west", txt, txt-col, desc-x + bit-w / 2, desc-y,
font: config.italic-font-family, anchor: "north-west",
italic: true, font: config.italic-font-family,
size: config.italic-font-size 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) return (shapes, desc-x, desc-y)
@@ -149,22 +178,47 @@
shapes += draw-underbracket(config, start-x, start-x + width, start-y) shapes += draw-underbracket(config, start-x, start-x + width, start-y)
let mid-x = start-x + width / 2 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 let txt-x = desc-x
if config.left-labels { 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( if config.description-width == none {
range_.description, shapes += draw-text(
config.text-color, range_.description,
txt-x, desc-y + bit-h / 2, config.text-color,
anchor: "west" 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 { if range_.values != none and range_.depends-on == none {
let shapes_ let shapes_
@@ -253,7 +307,15 @@
let bit-h = config.bit-height let bit-h = config.bit-height
let arrow-margin = config.arrow-margin let arrow-margin = config.arrow-margin
let start-i = struct.bits - range_.end - 1 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 start-x = bits-x + start-i * bit-w
let width = rng.bits(range_) * bit-w let width = rng.bits(range_) * bit-w
@@ -268,7 +330,7 @@
depend-range.last-value-y depend-range.last-value-y
} }
let depend-start-i = struct.bits - depend-range.end - 1 let depend-start-i = to-real-i(depend-range.end)
let depend-start-x = bits-x + depend-start-i * bit-w let depend-start-x = bits-x + depend-start-i * bit-w
let depend-width = rng.bits(depend-range) * bit-w let depend-width = rng.bits(depend-range) * bit-w
let depend-mid = depend-start-x + depend-width / 2 let depend-mid = depend-start-x + depend-width / 2
@@ -302,7 +364,9 @@
let x2 let x2
// Arrow from left to right // Arrow from left to right
if depend-range.end > range_.start { 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 x1 = depend-start-x + depend-width + arrow-margin
x2 = start-x - arrow-margin x2 = start-x - arrow-margin
@@ -346,6 +410,23 @@
let bits-width = struct.bits * bit-w let bits-width = struct.bits * bit-w
let start-bit = struct.start let start-bit = struct.start
let bit-colors = (:) 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) { for i in range(struct.bits) {
bit-colors.insert(str(i), bg-col) bit-colors.insert(str(i), bg-col)
} }
@@ -353,15 +434,17 @@
for (s, col) in colors.at(struct.name) { for (s, col) in colors.at(struct.name) {
let (start, end) = rng.parse-span(s) let (start, end) = rng.parse-span(s)
for i in range(start, end + 1) { for i in range(start, end + 1) {
let real-i = struct.bits - i - 1 + start-bit let real-i = to-real-i(i)
bit-colors.insert(str(real-i), col) bit-colors.insert(str(real-i), col)
} }
} }
} }
let range-boundaries = () let range-boundaries = ()
for r in struct.ranges.values() { for r in struct.ranges.values() {
let i = struct.bits - r.end - 1 + start-bit let start-i = to-real-i(if config.ltr-bits {r.start} else {r.end})
range-boundaries.push(i) 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 // Draw colors
@@ -373,22 +456,27 @@
// Draw rectangle around structure // Draw rectangle around structure
shapes += draw-rect(border-col, bits-x, bits-y, bits-width, bit-h, thickness: 2) shapes += draw-rect(border-col, bits-x, bits-y, bits-width, bit-h, thickness: 2)
let indices = range(struct.bits) let indices = range(struct.start, struct.start + struct.bits)
if not config.all-bit-i { if not config.all-bit-i {
indices = () indices = ()
for r in struct.ranges.values() { for r in struct.ranges.values() {
indices.push(r.start) indices.push(r.start)
indices.push(r.end) 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) { for i in range(struct.bits) {
let bit-x = ox + i * bit-w let bit-x = ox + i * bit-w
let real-i = struct.bits - i - 1 + start-bit let bit-i = to-bit-i(i)
if real-i in indices { if bit-i in indices {
shapes += draw-text( shapes += draw-text(
str(real-i), str(bit-i),
txt-col, txt-col,
bit-x + bit-w / 2, bit-x + bit-w / 2,
oy + bit-h / 2 oy + bit-h / 2
@@ -403,13 +491,16 @@
} }
let ranges = structure.get-sorted-ranges(struct) let ranges = structure.get-sorted-ranges(struct)
if config.ltr-bits {
ranges = ranges.rev()
}
if config.left-labels { if config.left-labels {
ranges = ranges.rev() ranges = ranges.rev()
} }
let desc-x let desc-x
if config.force-descs-on-side { if config.force-descs-on-side {
desc-x = config.margins.at(3) + structures.main.bits * bit-w desc-x = config.margins.at(3) + schema.structures.main.bits * bit-w
if config.left-labels { if config.left-labels {
desc-x = config.width - desc-x desc-x = config.width - desc-x
} }
@@ -423,17 +514,41 @@
let desc-y = bits-y + bit-h * 2 let desc-y = bits-y + bit-h * 2
// Names + simple descriptions // Names + simple descriptions
for range_ in ranges { for (i, range_) in ranges.enumerate() {
let start-i = struct.bits - range_.end + start-bit - 1 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 start-x = bits-x + start-i * bit-w
let width = rng.bits(range_) * bit-w let width = rng.bits(range_) * bit-w
let name-x = start-x + width / 2 let name-x = start-x + width / 2
let name-y = bits-y + bit-h / 2 let name-y = bits-y + bit-h / 2
shapes += draw-line(border-col, (start-x, bits-y), (start-x, bits-y + bit-h)) 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) 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 != "" { if range_.description != "" {
let shapes_ let shapes_
(shapes_, desc-x, desc-y) = draw-description( (shapes_, desc-x, desc-y) = draw-description(
@@ -458,7 +573,7 @@
return (shapes, desc-y) return (shapes, desc-y)
} }
#let render(config, schema, width: 100%) = { #let render(config, schema, width: 100%) = context {
set text( set text(
font: config.default-font-family, font: config.default-font-family,
size: config.default-font-size size: config.default-font-size
+4 -4
View File
@@ -25,7 +25,7 @@
#let parse-raw(schema) = { #let parse-raw(schema) = {
let lang = schema.lang let lang = schema.lang
let content = schema.text let content = bytes(schema.text)
if not lang in valid-extensions { if not lang in valid-extensions {
let fmts = valid-extensions.join(", ") let fmts = valid-extensions.join(", ")
fmts = "(" + fmts + ")" fmts = "(" + fmts + ")"
@@ -33,11 +33,11 @@
} }
if lang == "yaml" { if lang == "yaml" {
return yaml.decode(content) return yaml(content)
} else if lang == "json" { } else if lang == "json" {
return json.decode(content) return json(content)
} else if lang == "xml" { } else if lang == "xml" {
return xml-loader.parse(xml.decode(content).first()) return xml-loader.parse(xml(content).first())
} }
} }
+3 -2
View File
@@ -76,8 +76,9 @@
} }
return ( return (
bits: elmt.attrs.bits, bits: int(elmt.attrs.bits),
ranges: ranges ranges: ranges,
start: elmt.attrs.at("start", default: 0)
) )
} }
+4 -4
View File
@@ -1,7 +1,7 @@
[package] [package]
name = "rivet" name = "rivet"
version = "0.0.2" version = "0.3.1"
compiler = "0.11.0" compiler = "0.13.1"
repository = "https://git.kb28.ch/HEL/rivet-typst" repository = "https://git.kb28.ch/HEL/rivet-typst"
entrypoint = "src/lib.typ" entrypoint = "src/lib.typ"
authors = [ authors = [
@@ -9,6 +9,6 @@ authors = [
] ]
categories = [ "visualization" ] categories = [ "visualization" ]
license = "Apache-2.0" 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" ] keywords = [ "assembly", "instruction", "binary" ]
exclude = [ "/gallery/*" ] exclude = [ "gallery", "justfile", "docs" ]