14 Commits

25 changed files with 340 additions and 77 deletions

View File

@ -1,9 +1,20 @@
# Changelog
## [v0.0.2] - _WIP_
## [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
### Added
- `width` parameter to `schema.render` for easier integration
- `all-bit-i` config option
- colored ranges
- format specification in the manual
## [v0.0.1] - 2024-05-19
- initial version

View File

@ -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.*
## 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
#import "src/lib.typ": schema
#import "@preview/rivet:0.2.0": schema
#let doc = schema.load("path/to/schema.yaml")
#schema.render(doc)
```
## Installing
> TODO
```

View File

@ -1,7 +1,7 @@
/// Creates a dictionary of all configuration parameters
///
/// - 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-size (length): The absolute italic font size
/// - background (color): The diagram background color
@ -15,7 +15,7 @@
/// - dash-length (float): The length of individual dashes (for dashed lines)
/// - dash-space (float): The space between two dashes (for dashed lines)
/// - 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
/// - values-gap (float): The gap between individual values
/// - arrow-label-distance (float): The distance between arrows and their labels
@ -55,9 +55,9 @@
) = {}
/// Dark theme config
/// - ..args (any): see #doc-ref("config.config")
/// - ..args (any): see @@config()
#let dark(..args) = {}
/// Blueprint theme config
/// - ..args (any): see #doc-ref("config.config")
/// - ..args (any): see @@config()
#let blueprint(..args) = {}

View File

@ -1,4 +1,3 @@
#import "@preview/cetz:0.2.2": draw
#import "../src/lib.typ": schema
#import "../src/util.typ"
@ -14,7 +13,7 @@
box(
stroke: black + 1pt,
radius: .5em,
fill: if fill {yellow.lighten(80%)} else {none},
fill: if fill {orange.lighten(95%)} else {none},
if show-src {
let src-block = align(left, raw(src, lang: "typc"))
table(

View File

@ -2,15 +2,18 @@
/// This function returns a dictionary of structures
///
/// 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. \
/// 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
/// - path-or-schema (str, raw, dictionary):
/// #list(
/// [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
#let load(path-or-schema) = {}
/// Renders the given schema
/// This functions
/// - schema (dictionary): A schema dictionary, as returned by #doc-ref("schema.load")
/// - schema (dictionary): A schema dictionary, as returned by @@load()
/// - config (auto, dictionary): The configuration parameters, as returned by #doc-ref("config.config")
/// - width (ratio, length): The width of the generated figure
#let render(schema, config: auto, width: 100%) = {}

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

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 487 KiB

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

View File

@ -101,5 +101,11 @@
}
}
}
},
"colors": {
"main": {
"31-28": "#FF0000",
"11-4": [34, 176, 43]
}
}
}

Binary file not shown.

View File

@ -65,4 +65,7 @@
<description>offset register</description>
</range>
</structure>
<color structure="main" color="#FF0000" start="28" end="31" />
<color structure="main" color="#255961" start="4" end="11" />
<color structure="immediateOffset" color="89,97,37" start="4" end="11" />
</schema>

View File

@ -71,3 +71,11 @@ structures:
3-0:
name: Rm
description: offset register
colors:
main:
31-28: "#3EFA6B"
25-23:
- 100
- 150
- 200

12
justfile Normal file
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

Binary file not shown.

View File

@ -1,20 +1,24 @@
#import "@preview/tidy:0.3.0"
#import "@preview/tidy:0.4.1"
#import "@preview/codly:1.2.0": codly-init, codly
#import "@preview/codly-languages:0.1.7": codly-languages
#import "@preview/showybox:2.0.4": showybox
#import "src/lib.typ"
#import "src/schema.typ"
#import "docs/examples.typ"
#show: codly-init
#codly(languages: codly-languages)
#set heading(numbering: (..num) => if num.pos().len() < 4 {
numbering("1.1", ..num)
})
#{
outline(indent: true, depth: 3)
}
#set page(numbering: "1/1", header: align(right)[rivet #sym.dash.em v#lib.version])
#let doc-ref(target, full: false, var: false) = {
let (module, func) = target.split(".")
let label-name = module + func
let label-name = module + "-" + func
let display-name = func
if full {
display-name = target
@ -23,26 +27,249 @@
label-name += "()"
display-name += "()"
}
link(label(label-name))[#display-name]
link(label(label-name), raw(display-name))
}
#let note(it) = showybox(
title: "Note",
title-style: (
color: white,
weight: "bold"
),
frame: (
title-color: blue.lighten(30%),
border-color: blue.darken(40%)
),
it
)
#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
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 is a port of the #link("https://git.kb28.ch/HEL/rivet")[homonymous Python script] for Typst. For more information on the schema format, please check out the original project's #link("https://git.kb28.ch/HEL/rivet/src/branch/main/format.md")[format.md]
This is a port of the #link("https://git.kb28.ch/HEL/rivet")[homonymous Python script] for Typst.
= 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à !
#pad(left: 1em)[```typ
#import "src/lib.typ": schema
#import "@preview/rivet:0.1.0": schema
#let doc = schema.load("path/to/schema.yaml")
#schema.render(doc)
```]
= 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.
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.
== Main layout
A schema contains a dictionary of structures. The 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]
```json
{
"structures": {
"main": {
...
},
"struct1": {
...
},
"struct2": {
...
},
...
}
}
```
#pagebreak(weak: true)
== Structure <format-structure>
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.
```json
"main": {
"bits": 8,
"ranges": {
"7-4": {
...
},
"3-2": {
...
},
"1": {
...
},
"0": {
...
}
}
}
```
== Range <format-range>
A range represents a group of consecutive bits. It can have a name (displayed in the bit cells), a description (displayed under the structure) and / or values.
For values depending on other ranges, see #link(<format-dependencies>)[Dependencies].
#note[
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)
]
```json
"3-2": {
"name": "op",
"description": "Logical operation",
"values": {
"00": "AND",
"01": "OR",
"10": "XOR",
"11": "NAND"
}
}
```
#pagebreak(weak: true)
== Dependencies <format-dependencies>
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)
```json
"7-4": {
...
"depends-on": "0",
"values": {
"0": {
"description": "immediate value",
"structure": "immediateValue"
},
"1": {
"description": "value in register",
"structure": "registerValue"
}
}
}
```
Finally, add the sub-structures to the structure dictionary:
```json
{
"structures": {
"main": {
...
},
"immediateValue": {
"bits": 4,
...
},
"registerValue": {
"bits": 4,
...
},
...
}
}
```
#pagebreak(weak: true)
== Colors <format-colors>
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:
```json
{
"structures": {
...
},
"colors": {
...
}
}
```
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:
```json
"colors": {
"main": {
"31-28": "#ABCDEF",
"27-20": "12,34,56"
},
"registerValue": {
"19-10": [12, 34, 56]
}
}
```
Valid color formats are:
- hex string starting with `#`, e.g. `"#23fa78"`
- array of three integers (only JSON, YAML and Typst), e.g. `[35, 250, 120]`
- string of three comma-separated integers (useful for XML), e.g. `"35,250,120"`
- a Typst color (only Typst), e.g. `colors.green` or `rgb(35, 250, 120)`
#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:
```xml
<schema>
<structure id="main" bits="8">
...
</structure>
...
<color structure="main" color="#FF0000" start="4" end="7" />
<color structure="main" color="255,0,0" start="0" end="3" />
</schema>
```
]
#pagebreak(weak: true)
= Config presets
Aside from the default config, some example presets are also provided:
@ -60,6 +287,7 @@ Aside from the default config, some example presets are also provided:
#let doc-config = tidy.parse-module(
read("docs/config.typ"),
name: "config",
old-syntax: true,
scope: (
doc-ref: doc-ref
)
@ -71,6 +299,7 @@ Aside from the default config, some example presets are also provided:
#let doc-schema = tidy.parse-module(
read("docs/schema.typ"),
name: "schema",
old-syntax: true,
scope: (
schema: schema,
doc-ref: doc-ref

View File

@ -1,4 +1,4 @@
#let version = version((0,0,2))
#let version = version(0,2,0)
#import "config.typ"
#import "schema.typ"

View File

@ -1,4 +1,4 @@
#import "@preview/cetz:0.2.2": canvas, draw
#import "@preview/cetz:0.3.2": canvas, draw
#import "range.typ" as rng
#import "structure.typ"
@ -373,7 +373,7 @@
// Draw rectangle around structure
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 {
indices = ()
for r in struct.ranges.values() {

View File

@ -25,7 +25,7 @@
#let parse-raw(schema) = {
let lang = schema.lang
let content = schema.text
let content = bytes(schema.text)
if not lang in valid-extensions {
let fmts = valid-extensions.join(", ")
fmts = "(" + fmts + ")"
@ -33,11 +33,11 @@
}
if lang == "yaml" {
return yaml.decode(content)
return yaml(content)
} else if lang == "json" {
return json.decode(content)
return json(content)
} else if lang == "xml" {
return xml-loader.parse(xml.decode(content).first())
return xml-loader.parse(xml(content).first())
}
}
@ -50,6 +50,28 @@
parse-raw(path-or-schema)
}
if "colors" in schema {
for struct in schema.colors.keys() {
for (span, col) in schema.colors.at(struct) {
if type(col) == str {
if col.starts-with("#") {
col = rgb(col)
} else {
let (r, g, b) = col.split(",").map(v => int(v))
col = rgb(r, g, b)
}
} else if type(col) == array {
col = rgb(..col)
} else if type(col) != color {
panic("Invalid color format")
}
schema.colors.at(struct).at(span) = col
}
}
} else {
schema.insert("colors", (:))
}
let structures = (:)
for (id, data) in schema.structures {
id = str(id)

View File

@ -83,8 +83,10 @@
#let parse(content) = {
let struct-elmts = content.children.filter(e => "tag" in e and e.tag == "structure")
let color-elmts = content.children.filter(e => "tag" in e and e.tag == "color")
let structures = (:)
let colors = (:)
for struct-elmt in struct-elmts {
structures.insert(
@ -93,8 +95,19 @@
)
}
for color-elmt in color-elmts {
let struct = color-elmt.attrs.structure
if not struct in colors {
colors.insert(struct, (:))
}
let span = color-elmt.attrs.end + "-" + color-elmt.attrs.start
colors.at(struct).insert(span, color-elmt.attrs.color)
}
return (
structures: structures
structures: structures,
colors: colors
)
}

View File

@ -1,7 +1,7 @@
[package]
name = "rivet"
version = "0.0.2"
compiler = "0.11.0"
version = "0.2.0"
compiler = "0.13.0"
repository = "https://git.kb28.ch/HEL/rivet-typst"
entrypoint = "src/lib.typ"
authors = [
@ -11,4 +11,4 @@ categories = [ "visualization" ]
license = "Apache-2.0"
description = "Register / Instruction Visualizer & Explainer Tool with Typst, using CeTZ"
keywords = [ "assembly", "instruction", "binary" ]
exclude = [ "/gallery/*" ]
exclude = [ "gallery", "justfile", "docs" ]