33 Commits

Author SHA1 Message Date
2d56678d43 Merge pull request 'v0.2.0: updates and minor fixes' (#4) from dev into main
Reviewed-on: #4
2025-02-23 13:21:00 +00:00
a935484665 updated changelog 2025-02-23 14:15:59 +01:00
c0a42aa8a6 fixed raw decode warning + updated doc 2025-02-23 14:12:50 +01:00
37dce1f7c9 updated to CeTZ 0.3.2 and Typst 0.13.0 2025-02-23 13:26:28 +01:00
a9633f25a3 updated to Typst 0.12.0 + CeTZ 0.3.1 2024-10-30 16:24:51 +01:00
a929f506ac modified gallery.bash to compile recursively 2024-10-30 16:19:01 +01:00
aa9a082ba5 fixed missing bit i on dependencies 2024-10-30 16:18:32 +01:00
6f502f2e18 updated CHANGELOG.md 2024-10-02 18:58:11 +02:00
03e9904d43 bumped to 0.1.0 for publication 2024-10-02 18:54:42 +02:00
b8a84b1430 Merge pull request 'prep for release v0.0.2' (#1) from dev into main
Reviewed-on: #1
2024-06-15 12:20:48 +00:00
94d4ac8328 updated CHANGELOG.md 2024-06-15 14:14:07 +02:00
01fe756862 added format specification in the manual 2024-06-15 14:13:07 +02:00
995564382a added color support for json, yaml and xml 2024-06-15 13:17:27 +02:00
7c62a16146 updated CHANGELOG.md 2024-06-14 11:23:47 +02:00
9f8bf02522 updated gallery 2024-06-14 11:20:43 +02:00
b7e1db1dbd added support for colored ranges 2024-06-14 11:20:32 +02:00
4b33ab4a56 added support for loading dictionary structure 2024-06-14 10:17:19 +02:00
49db07b9ee added CHANGELOG.md 2024-06-14 07:42:20 +02:00
d75e447bf3 fixed full page diagrams 2024-06-14 07:41:52 +02:00
99b9037719 updated package version to 0.0.2 2024-06-13 12:49:18 +02:00
a51c6a976d added all-bit-i config option 2024-06-13 12:48:26 +02:00
06f4a2ec5b updated manual 2024-06-13 12:23:22 +02:00
f061bd964e added width param to render 2024-06-13 12:02:31 +02:00
8bcedbfc53 resized README images 2024-05-19 18:08:52 +02:00
bdbed8f8b0 completed README.md 2024-05-19 18:03:52 +02:00
56bfe03c44 regenerated gallery examples 2024-05-19 17:36:24 +02:00
e584dc4e36 fixed margin (for full-page) 2024-05-19 17:34:55 +02:00
6d59f4bc92 added manual 2024-05-19 17:30:06 +02:00
03361db784 fixed margins 2024-05-19 17:29:46 +02:00
8d93e6473e added XML parser 2024-05-19 17:29:16 +02:00
44fd298edb added riscv examples 2024-05-19 13:54:49 +02:00
5e0e680f60 fixed separator hiding inside structure 2024-05-19 13:53:24 +02:00
4ccfa43e51 fixed arrows + left labels 2024-05-19 13:49:43 +02:00
36 changed files with 1248 additions and 53 deletions

21
CHANGELOG.md Normal file
View File

@ -0,0 +1,21 @@
# Changelog
## [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
- ported all features from the [python package](https://git.kb28.ch/HEL/rivet/)

View File

@ -1,3 +1,41 @@
# rivet-typst # rivet-typst
Register / Instruction Visualizer & Explainer Tool with Typst, using CeTZ RIVET _(Register / Instruction Visualizer & Explainer Tool)_ is a [Typst](https://typst.app) package for visualizing binary instructions or describing the contents of a register, using the [CeTZ](https://typst.app/universe/package/cetz) package.
It is based on the [homonymous Python script](https://git.kb28.ch/HEL/rivet/)
## Examples
<table>
<tr>
<td>
<a href="./gallery/example1.typ">
<img src="./gallery/example1.png" width="1000px">
</a>
</td>
</tr>
<tr>
<td>A bit of eveything</td>
</tr>
<tr>
<td>
<a href="./gallery/example2.typ">
<img src="./gallery/example2.png" width="1000px">
</a>
</td>
</tr>
<tr>
<td>RISC-V memory instructions (blueprint)</td>
</tr>
</table>
*Click on the example image to jump to the code.*
## Usage
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.2.0": schema
#let doc = schema.load("path/to/schema.yaml")
#schema.render(doc)
```

63
docs/config.typ Normal file
View File

@ -0,0 +1,63 @@
/// Creates a dictionary of all configuration parameters
///
/// - default-font-family (str): The default font family
/// - 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
/// - text-color (color): The default color used to display text
/// - link-color (color): The color used to display links and arrows
/// - bit-i-color (color): The color used to display bit indices
/// - border-color (color): The color used to display borders
/// - bit-width (float): The width of a bit
/// - bit-height (float): The height of a bit
/// - description-margin (float): The margin between descriptions
/// - 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): 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
/// - force-descs-on-side (bool): If true, descriptions are placed on the side of the structure, otherwise, they are placed as close as possible to the bit
/// - left-labels (bool): If true, descriptions are put on the left, otherwise, they default to the right hand side
/// - width (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
/// - all-bit-i (bool): If true, all bit indices will be rendered, otherwise, only the ends of each range will be displayed
/// -> dictionary
#let config(
default-font-family: "Ubuntu Mono",
default-font-size: 15pt,
italic-font-family: "Ubuntu Mono",
italic-font-size: 12pt,
background: white,
text-color: black,
link-color: black,
bit-i-color: black,
border-color: black,
bit-width: 30,
bit-height: 30,
description-margin: 10,
dash-length: 6,
dash-space: 4,
arrow-size: 10,
margins: (20, 20, 20, 20),
arrow-margin: 4,
values-gap: 5,
arrow-label-distance: 5,
force-descs-on-side: false,
left-labels: false,
width: 1200,
height: 800,
full-page: false,
all-bit-i: true
) = {}
/// Dark theme config
/// - ..args (any): see @@config()
#let dark(..args) = {}
/// Blueprint theme config
/// - ..args (any): see @@config()
#let blueprint(..args) = {}

72
docs/examples.typ Normal file
View File

@ -0,0 +1,72 @@
#import "../src/lib.typ": schema
#import "../src/util.typ"
#let example-preamble = "import \"../src/lib.typ\": *;"
#let example(src, show-src: true, vertical: false, fill: true) = {
src = src.text.trim()
let full-src = example-preamble + src
let body = eval(full-src)
block(width: 100%,
align(center,
box(
stroke: black + 1pt,
radius: .5em,
fill: if fill {orange.lighten(95%)} else {none},
if show-src {
let src-block = align(left, raw(src, lang: "typc"))
table(
columns: if vertical {1} else {2},
inset: 1em,
align: horizon + center,
stroke: none,
body,
if vertical {table.hline()} else {table.vline()}, src-block
)
} else {
table(
inset: 1em,
body
)
}
)
)
)
}
#let config-config = example(raw("
let ex = schema.load(```yaml
structures:
main:
bits: 4
ranges:
3-0:
name: default
```)
schema.render(ex, config: config.config())
"))
#let config-dark = example(raw("
let ex = schema.load(```yaml
structures:
main:
bits: 4
ranges:
3-0:
name: dark
```)
schema.render(ex, config: config.dark())
"))
#let config-blueprint = example(raw("
let ex = schema.load(```yaml
structures:
main:
bits: 4
ranges:
3-0:
name: blueprint
```)
schema.render(ex, config: config.blueprint())
"))

19
docs/schema.typ Normal file
View File

@ -0,0 +1,19 @@
/// Loads a schema from a file or a raw block.
/// This function returns a dictionary of structures
///
/// Supported formats: #schema.valid-extensions.map(e => raw("." + e)).join(", ")
/// - 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 @@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%) = {}

BIN
gallery/example1.pdf Normal file

Binary file not shown.

BIN
gallery/example1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

6
gallery/example1.typ Normal file
View File

@ -0,0 +1,6 @@
#import "../src/lib.typ": schema, config
#let example = schema.load("/gallery/example1.yaml")
#schema.render(example, config: config.config(
full-page: true
))

73
gallery/example1.yaml Normal file
View File

@ -0,0 +1,73 @@
structures:
main:
bits: 32
ranges:
31-28:
name: cond
27:
name: 0
26:
name: 1
25:
name: I
24:
name: P
description: pre / post indexing bit
values:
0: post, add offset after transfer
1: pre, add offset before transfer
23:
name: U
description: up / down bit
values:
0: down, subtract offset from base
1: up, addition offset to base
22:
name: B
description: byte / word bit
values:
0: transfer word quantity
1: transfer byte quantity
21:
name: W
description: write-back bit
values:
0: no write-back
1: write address into base
20:
name: L
description: load / store bit
values:
0: store to memory
1: load from memory
19-16:
name: Rn
description: base register
15-12:
name: Rd
description: source / destination register
11-0:
name: offset
depends-on: 25
values:
0:
description: offset is an immediate value
structure: immediateOffset
1:
description: offset is a register
structure: registerOffset
immediateOffset:
bits: 12
ranges:
11-0:
name: 12-bit immediate offset
description: unsigned number
registerOffset:
bits: 12
ranges:
11-4:
name: shift
description: shift applied to Rm
3-0:
name: Rm
description: offset register

BIN
gallery/example2.pdf Normal file

Binary file not shown.

BIN
gallery/example2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

7
gallery/example2.typ Normal file
View File

@ -0,0 +1,7 @@
#import "../src/lib.typ": schema, config
#let example = schema.load("/gallery/example2.yaml")
#schema.render(example, config: config.blueprint(
full-page: true,
left-labels: true
))

78
gallery/example2.yaml Normal file
View File

@ -0,0 +1,78 @@
structures:
main:
bits: 32
ranges:
31-20:
name: src
depends-on: 5
values:
0:
description:
structure: srcImmediate
1:
description:
structure: srcRegister
19-15:
name: rs1
14-12:
name: funct3
description: function modifier
values:
000: byte
001: half-word
"010": word
100: upper byte (load only)
101: upper half (load only)
11-7:
name: dst
depends-on: 5
values:
0:
description:
structure: dstRegister
1:
description:
structure: dstImmediate
6:
name: 0
5:
name: I
4:
name: 0
3:
name: 0
2:
name: 0
1:
name: 1
0:
name: 1
srcImmediate:
bits: 12
ranges:
11-0:
name: src
description: source memory address
srcRegister:
bits: 12
ranges:
11-5:
name: dstU
description: destination address upper bits
4-0:
name: rs2
description: source register
dstImmediate:
bits: 5
ranges:
4-0:
name: destL
description: destination address lower bits
dstRegister:
bits: 5
ranges:
4-0:
name: rd
description: destination register

BIN
gallery/example3.pdf Normal file

Binary file not shown.

BIN
gallery/example3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

23
gallery/example3.typ Normal file
View File

@ -0,0 +1,23 @@
#import "../src/lib.typ": schema, config
#let example = schema.load("/gallery/example1.yaml")
//#schema.render(example)
= Chapter 1
#lorem(50)
= Chapter 2
#lorem(50)
== Section 2.1
#lorem(20)
#figure(
schema.render(example, config: config.config(all-bit-i: false)),
caption: "Test schema"
)
#lorem(20)
= Chapter 3

12
gallery/example3.yaml Normal file
View File

@ -0,0 +1,12 @@
structures:
main:
bits: 32
ranges:
31-24:
name: op
23-16:
name: r1
15-8:
name: r2
7-0:
name: r3

View File

@ -0,0 +1,60 @@
structures:
main:
bits: 32
ranges:
31-20:
name: op2
depends-on: 5
values:
0:
description: second operand is an immediate value
structure: immediateOp
1:
description: second operand is a register
structure: registerOp
19-15:
name: rs1
14-12:
name: funct3
description: operation
values:
000: add / sub
100: xor
110: or
111: and
001: sl
101: sr
11-7:
name: rd
6:
name: 0
5:
name: I
4:
name: 1
3:
name: 0
2:
name: 0
1:
name: 1
0:
name: 1
immediateOp:
bits: 12
ranges:
11-0:
name: 12-bit immediate value
description: signed number
registerOp:
bits: 12
ranges:
11-5:
name: funct7
description: function modifier
values:
0000000: default (add, srl)
"0100000": sub, sra
4-0:
name: rs2
description: second register operand

View File

@ -0,0 +1,34 @@
structures:
main:
bits: 32
ranges:
31-25:
name: imm
24-20:
name: rs2
19-15:
name: rs1
14-12:
name: funct3
description: function modifier
values:
000: if equal
001: if not equal
100: if less
101: if greater or equal
11-7:
name: imm
6:
name: 1
5:
name: 1
4:
name: 0
3:
name: 0
2:
name: 0
1:
name: 1
0:
name: 1

View File

@ -0,0 +1,78 @@
structures:
main:
bits: 32
ranges:
31-20:
name: src
depends-on: 5
values:
0:
description:
structure: srcImmediate
1:
description:
structure: srcRegister
19-15:
name: rs1
14-12:
name: funct3
description: function modifier
values:
000: byte
001: half-word
"010": word
100: upper byte (load only)
101: upper half (load only)
11-7:
name: dst
depends-on: 5
values:
0:
description:
structure: dstRegister
1:
description:
structure: dstImmediate
6:
name: 0
5:
name: I
4:
name: 0
3:
name: 0
2:
name: 0
1:
name: 1
0:
name: 1
srcImmediate:
bits: 12
ranges:
11-0:
name: src
description: source memory address
srcRegister:
bits: 12
ranges:
11-5:
name: dstU
description: destination address upper bits
4-0:
name: rs2
description: source register
dstImmediate:
bits: 5
ranges:
4-0:
name: destL
description: destination address lower bits
dstRegister:
bits: 5
ranges:
4-0:
name: rd
description: destination register

BIN
gallery/riscv/riscv.pdf Normal file

Binary file not shown.

14
gallery/riscv/riscv.typ Normal file
View File

@ -0,0 +1,14 @@
#import "../../src/lib.typ": *
#let conf = config.config(
full-page: true,
left-labels: true
)
#let alu = schema.load("/gallery/riscv/alu_instr.yaml")
#schema.render(alu, config: conf)
#let branch = schema.load("/gallery/riscv/branch_instr.yaml")
#schema.render(branch, config: conf)
#let mem = schema.load("/gallery/riscv/mem_instr.yaml")
#schema.render(mem, config: conf)

View File

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

Binary file not shown.

View File

@ -13,4 +13,127 @@
#let test-xml = schema.load("/gallery/test.xml") #let test-xml = schema.load("/gallery/test.xml")
#schema.render(test-xml, config: config.dark( #schema.render(test-xml, config: config.dark(
full-page: true full-page: true
))
#let test-raw = schema.load(```yaml
structures:
main:
bits: 4
ranges:
3-0:
name: test
```)
#schema.render(test-raw, config: config.config(
full-page: true
))
#let test-typ = schema.load((
structures: (
main: (
bits: 32,
ranges: (
"31-28": (name: "cond"),
"27": (name: "0"),
"26": (name: "1"),
"25": (name: "I"),
"24": (
name: "P",
description: "pre / post indexing bit",
values: (
"0": "post, add offset after transfer",
"1": "pre, add offset before transfer"
)
),
"23": (
name: "U",
description: "up / down bit",
values: (
"0": "down, subtract offset from base",
"1": "up, addition offset to base"
)
),
"22": (
name: "B",
description: "byte / word bit",
values: (
"0": "transfer word quantity",
"1": "transfer byte quantity"
)
),
"21": (
name: "W",
description: "write-back bit",
values: (
"0": "no write-back",
"1": "write address into base"
)
),
"20": (
name: "L",
description: "load / store bit",
values: (
"0": "store to memory",
"1": "load from memory"
)
),
"19-16": (
name: "Rn",
description: "base register"
),
"15-12": (
name: "Rd",
description: "source / destination register"
),
"11-0": (
name: "offset",
depends-on: "25",
values: (
"0": (
description: "offset is an immediate value",
structure: "immediateOffset"
),
"1": (
description: "offset is a register",
structure: "registerOffset"
)
)
)
)
),
immediateOffset: (
bits: 12,
ranges: (
"11-0": (
name: "12-bit immediate offset",
description: "unsigned number"
)
)
),
registerOffset: (
bits: 12,
ranges: (
"11-4": (
name: "shift",
description: "shift applied to Rm"
),
"3-0": (
name: "Rm",
description: "offset register"
)
)
)
),
colors: (
main: (
"31-28": red,
"11-4": green
),
registerOffset: (
"11-4": rgb(240, 140, 80)
)
)
))
#schema.render(test-typ, config: config.config(
full-page: true
)) ))

View File

@ -65,4 +65,7 @@
<description>offset register</description> <description>offset register</description>
</range> </range>
</structure> </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> </schema>

View File

@ -71,3 +71,11 @@ structures:
3-0: 3-0:
name: Rm name: Rm
description: offset register 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

BIN
manual.pdf Normal file

Binary file not shown.

308
manual.typ Normal file
View File

@ -0,0 +1,308 @@
#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)
})
#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 display-name = func
if full {
display-name = target
}
if not var {
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.
= 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 "@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:
- #doc-ref("config.config", full: true): the default theme, black on white
#examples.config-config
- #doc-ref("config.dark", full: true): a dark theme, with white text and lines on a black background
#examples.config-dark
- #doc-ref("config.blueprint", full: true): a blueprint theme, with white text and lines on a blue background
#examples.config-blueprint
#pagebreak(weak: true)
= Reference
#let doc-config = tidy.parse-module(
read("docs/config.typ"),
name: "config",
old-syntax: true,
scope: (
doc-ref: doc-ref
)
)
#tidy.show-module(doc-config, sort-functions: false)
#pagebreak()
#let doc-schema = tidy.parse-module(
read("docs/schema.typ"),
name: "schema",
old-syntax: true,
scope: (
schema: schema,
doc-ref: doc-ref
)
)
#tidy.show-module(doc-schema, sort-functions: false)

View File

@ -22,7 +22,8 @@
left-labels: false, left-labels: false,
width: 1200, width: 1200,
height: 800, height: 800,
full-page: false full-page: false,
all-bit-i: true
) = { ) = {
return ( return (
default-font-family: default-font-family, default-font-family: default-font-family,
@ -48,7 +49,8 @@
left-labels: left-labels, left-labels: left-labels,
width: width, width: width,
height: height, height: height,
full-page: full-page full-page: full-page,
all-bit-i: all-bit-i
) )
} }

View File

@ -1,4 +1,4 @@
#let version = version((0,0,1)) #let version = version(0,2,0)
#import "config.typ" #import "config.typ"
#import "schema.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 "range.typ" as rng
#import "structure.typ" #import "structure.typ"
@ -32,20 +32,12 @@
if italic { if italic {
text-params.insert("style", "italic") text-params.insert("style", "italic")
} }
let content-params = (:)
if fill != none {
content-params.insert("fill", fill)
content-params.insert("frame", "rect")
content-params.insert("padding", (4pt, 0pt))
}
draw.content( draw.content(
(x, -y), (x, -y),
text(txt, fill: color, size: size, ..text-params), text(txt, fill: color, size: size, ..text-params),
anchor: anchor, anchor: anchor,
stroke: none, stroke: none
..content-params
) )
} }
@ -117,7 +109,7 @@
let txt-col = config.text-color let txt-col = config.text-color
let bit-w = config.bit-height // Why ? I don't remember let bit-w = config.bit-height // Why ? I don't remember
let gap = config.values-gap let gap = config.values-gap
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
@ -159,16 +151,16 @@
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) shapes += draw-link(config, mid-x, start-y, desc-x, desc-y)
let txt-anchor = "west" let txt-x = desc-x
if config.left-labels { if config.left-labels {
txt-anchor -= "east" txt-x -= range_.description.len() * config.default-font-size / 2pt
} }
shapes += draw-text( shapes += draw-text(
range_.description, range_.description,
config.text-color, config.text-color,
desc-x, desc-y + bit-h / 2, txt-x, desc-y + bit-h / 2,
anchor: "west" anchor: "west"
) )
@ -176,7 +168,7 @@
if range_.values != none and range_.depends-on == none { if range_.values != none and range_.depends-on == none {
let shapes_ let shapes_
(shapes_, _, desc-y) = draw-values(config, range_.values, desc-x, desc-y) (shapes_, _, desc-y) = draw-values(config, range_.values, txt-x, desc-y)
shapes += shapes_ shapes += shapes_
} }
@ -253,7 +245,7 @@
#let draw-dependency( #let draw-dependency(
draw-struct, config, draw-struct, config,
struct, structures, bits-x, bits-y, range_, desc-x, desc-y struct, schema, bits-x, bits-y, range_, desc-x, desc-y
) = { ) = {
let shapes = () let shapes = ()
@ -266,7 +258,8 @@
let width = rng.bits(range_) * bit-w let width = rng.bits(range_) * bit-w
shapes += draw-underbracket(config, start-x, start-x + width, bits-y) shapes += draw-underbracket(config, start-x, start-x + width, bits-y)
let depend-range = struct.ranges.at(rng.key(..range_.depends-on)) 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-range-y = bits-y + bit-h * 1.5
let prev-depend-y = if depend-range.last-value-y == -1 { let prev-depend-y = if depend-range.last-value-y == -1 {
@ -300,7 +293,7 @@
val-struct = structure.load("", val-struct) val-struct = structure.load("", val-struct)
let shapes_ let shapes_
(shapes_, ..) = draw-struct(config, val-struct, structures, ox: depend-start-x, oy: desc-y) (shapes_, ..) = draw-struct(config, val-struct, schema, ox: depend-start-x, oy: desc-y)
shapes += shapes_ shapes += shapes_
let y = desc-y + bit-h * 1.5 let y = desc-y + bit-h * 1.5
@ -331,15 +324,18 @@
prev-range-y = prev-depend-y prev-range-y = prev-depend-y
depend-range.last-value-y = prev-depend-y depend-range.last-value-y = prev-depend-y
(shapes_, desc-y) = draw-struct(config, structures.at(data.structure), structures, ox: start-x, oy: desc-y) (shapes_, desc-y) = draw-struct(config, schema.structures.at(data.structure), schema, ox: start-x, oy: desc-y)
shapes += shapes_ shapes += shapes_
} }
return (shapes, desc-x, desc-y) struct.ranges.at(depend-key) = depend-range
return (shapes, desc-x, desc-y, struct)
} }
#let draw-structure(config, struct, structures, ox: 0, oy: 0) = { #let draw-structure(config, struct, schema, ox: 0, oy: 0) = {
let shapes let shapes
let colors = schema.at("colors", default: (:))
let bg-col = config.background let bg-col = config.background
let txt-col = config.text-color let txt-col = config.text-color
let border-col = config.border-color let border-col = config.border-color
@ -349,22 +345,60 @@
let (bits-x, bits-y) = (ox, oy + bit-h) let (bits-x, bits-y) = (ox, oy + bit-h)
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 = (:)
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 = struct.bits - i - 1 + start-bit
bit-colors.insert(str(real-i), col)
}
}
}
let range-boundaries = ()
for r in struct.ranges.values() {
let i = struct.bits - r.end - 1 + start-bit
range-boundaries.push(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 // 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.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)
}
}
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
shapes += draw-text( let real-i = struct.bits - i - 1 + start-bit
str(struct.bits - i - 1 + start-bit),
txt-col, if real-i in indices {
bit-x + bit-w / 2, shapes += draw-text(
oy + bit-h / 2 str(real-i),
) txt-col,
bit-x + bit-w / 2,
oy + bit-h / 2
)
}
// Draw separator // Draw separator
if i != 0 { if i != 0 and not i in range-boundaries {
shapes += draw-line(border-col, (bit-x, bits-y), (bit-x, bits-y + bit-h)) 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))
} }
} }
@ -397,10 +431,10 @@
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))
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)
if range_.description != "" { if range_.description != "" {
//draw.circle((desc-x, -desc-y), radius: 5, fill: red)
let shapes_ let shapes_
(shapes_, desc-x, desc-y) = draw-description( (shapes_, desc-x, desc-y) = draw-description(
config, range_, start-x, bits-y, width, desc-x, desc-y config, range_, start-x, bits-y, width, desc-x, desc-y
@ -413,9 +447,9 @@
for range_ in ranges { for range_ in ranges {
if range_.values() != none and range_.depends-on != none { if range_.values() != none and range_.depends-on != none {
let shapes_ let shapes_
(shapes_, desc-x, desc-y) = draw-dependency( (shapes_, desc-x, desc-y, struct) = draw-dependency(
draw-structure, config, draw-structure, config,
struct, structures, bits-x, bits-y, range_, desc-x, desc-y struct, schema, bits-x, bits-y, range_, desc-x, desc-y,
) )
shapes += shapes_ shapes += shapes_
} }
@ -424,13 +458,13 @@
return (shapes, desc-y) return (shapes, desc-y)
} }
#let render(config, structures) = { #let render(config, schema, width: 100%) = {
set text( set text(
font: config.default-font-family, font: config.default-font-family,
size: config.default-font-size size: config.default-font-size
) )
let main = structures.main let main = schema.structures.main
let ox = config.margins.at(3) let ox = config.margins.at(3)
if config.left-labels { if config.left-labels {
ox = config.width - ox - main.bits * config.bit-width ox = config.width - ox - main.bits * config.bit-width
@ -440,7 +474,8 @@
( (
width: auto, width: auto,
height: auto, height: auto,
fill: config.background fill: config.background,
margin: 0cm
) )
} else { } else {
(:) (:)
@ -448,15 +483,53 @@
set page(..params) set page(..params)
canvas(length: 1pt, background: config.background, { let cnvs = canvas(length: 1pt, background: config.background, {
let (shapes, _) = draw-structure( let (shapes, _) = draw-structure(
config, main, structures, config, main, schema,
ox: ox, ox: ox,
oy: config.margins.at(0) oy: config.margins.at(0)
) )
shapes // Workaround for margins
//draw.circle((300, -3000), fill: red, radius: 2) 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) = { #let make(config) = {

View File

@ -23,25 +23,70 @@
} }
} }
#let load(path-or-schema, config: auto) = { #let parse-raw(schema) = {
let lang = schema.lang
let content = bytes(schema.text)
if not lang in valid-extensions {
let fmts = valid-extensions.join(", ")
fmts = "(" + fmts + ")"
panic("Unsupported format '" + lang + "'. Valid formats: " + fmts)
}
if lang == "yaml" {
return yaml(content)
} else if lang == "json" {
return json(content)
} else if lang == "xml" {
return xml-loader.parse(xml(content).first())
}
}
#let load(path-or-schema) = {
let schema = if type(path-or-schema) == str { let schema = if type(path-or-schema) == str {
parse-file(path-or-schema) parse-file(path-or-schema)
} else if type(path-or-schema) == dictionary {
path-or-schema
} else { } else {
parse-raw(path-or-schema) 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 = (:) let structures = (:)
for (id, data) in schema.structures { for (id, data) in schema.structures {
id = str(id) id = str(id)
structures.insert(id, structure.load(id, data)) structures.insert(id, structure.load(id, data))
} }
return structures return (
structures: structures,
colors: schema.at("colors", default: (:))
)
} }
#let render(structures, config: auto) = { #let render(schema, width: 100%, config: auto) = {
if config == auto { if config == auto {
config = conf.config() config = conf.config()
} }
let renderer_ = renderer.make(config) let renderer_ = renderer.make(config)
(renderer_.render)(structures) (renderer_.render)(schema, width: width)
} }

View File

@ -81,11 +81,12 @@
) )
} }
#let load(path) = { #let parse(content) = {
let content = xml(path).first()
let struct-elmts = content.children.filter(e => "tag" in e and e.tag == "structure") 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 structures = (:)
let colors = (:)
for struct-elmt in struct-elmts { for struct-elmt in struct-elmts {
structures.insert( structures.insert(
@ -94,7 +95,23 @@
) )
} }
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 ( return (
structures: structures structures: structures,
colors: colors
) )
}
#let load(path) = {
let content = xml(path).first()
return parse(content)
} }

View File

@ -1,7 +1,7 @@
[package] [package]
name = "rivet" name = "rivet"
version = "0.0.1" version = "0.2.0"
compiler = "0.11.0" compiler = "0.13.0"
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 = [
@ -11,4 +11,4 @@ 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 with Typst, using CeTZ"
keywords = [ "assembly", "instruction", "binary" ] keywords = [ "assembly", "instruction", "binary" ]
exclude = [ "/gallery/*" ] exclude = [ "gallery", "justfile", "docs" ]