Merge pull request 'prep for release v0.0.2' (#1) from dev into main

Reviewed-on: #1
This commit is contained in:
Louis Heredero 2024-06-15 12:20:48 +00:00
commit b8a84b1430
25 changed files with 557 additions and 47 deletions

12
CHANGELOG.md Normal file
View File

@ -0,0 +1,12 @@
# Changelog
## [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

@ -24,6 +24,7 @@
/// - width (float): TODO -> remove /// - width (float): TODO -> remove
/// - 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
/// -> dictionary /// -> dictionary
#let config( #let config(
default-font-family: "Ubuntu Mono", default-font-family: "Ubuntu Mono",
@ -49,7 +50,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
) = {} ) = {}
/// Dark theme config /// Dark theme config

View File

@ -2,13 +2,15 @@
/// This function returns a dictionary of structures /// This function returns a dictionary of structures
/// ///
/// 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): If it is a string, defines the path to load. \ /// - 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 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 /// This functions
/// - structures (dictionary): A schema dictionary, as returned by #doc-ref("schema.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")
#let render(structures, config: auto) /// - width (ratio, length): The width of the generated figure
#let render(schema, config: auto, width: 100%) = {}

40
gallery.bash Normal file
View File

@ -0,0 +1,40 @@
#!/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: 378 KiB

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 KiB

After

Width:  |  Height:  |  Size: 302 KiB

BIN
gallery/example3.pdf Normal file

Binary file not shown.

BIN
gallery/example3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 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

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

Binary file not shown.

View File

@ -25,4 +25,115 @@ structures:
```) ```)
#schema.render(test-raw, config: config.config( #schema.render(test-raw, config: config.config(
full-page: true 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

Binary file not shown.

View File

@ -1,4 +1,6 @@
#import "@preview/tidy:0.3.0" #import "@preview/tidy:0.3.0"
#import "@preview/codelst:2.0.1": sourcecode
#import "@preview/showybox:2.0.1": 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"
@ -26,13 +28,26 @@
link(label(label-name))[#display-name] link(label(label-name))[#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) #show link: set text(blue)
= 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
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 = Usage
@ -43,6 +58,185 @@ Simply import `schema` from #link("src/lib.typ") and call `schema.load` to parse
#schema.render(doc) #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]
#sourcecode[```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.
#sourcecode[```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)
]
#sourcecode[```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)
#sourcecode[```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:
#sourcecode[```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:
#sourcecode[```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:
#sourcecode[```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:
#sourcecode[```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 = Config presets
Aside from the default config, some example presets are also provided: Aside from the default config, some example presets are also provided:

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,0,2))
#import "config.typ" #import "config.typ"
#import "schema.typ" #import "schema.typ"

View File

@ -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
) )
} }
@ -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 = ()
@ -301,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
@ -332,7 +324,7 @@
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_
} }
@ -341,8 +333,9 @@
return (shapes, desc-x, desc-y, struct) 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
@ -352,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.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))
} }
} }
@ -400,13 +431,7 @@
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-rect( shapes += draw-line(border-col, (start-x, bits-y), (start-x, bits-y + bit-h))
bg-col,
start-x + bit-w / 2,
name-y - bit-h * 0.3,
width - bit-w,
bit-h * 0.6
)
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 != "" {
@ -424,7 +449,7 @@
let shapes_ let shapes_
(shapes_, desc-x, desc-y, struct) = 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_
} }
@ -433,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
@ -458,9 +483,9 @@
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)
) )
@ -475,6 +500,36 @@
fill: 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

@ -44,22 +44,49 @@
#let load(path-or-schema) = { #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

@ -83,8 +83,10 @@
#let parse(content) = { #let parse(content) = {
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(
@ -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 ( return (
structures: structures structures: structures,
colors: colors
) )
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rivet" name = "rivet"
version = "0.0.1" version = "0.0.2"
compiler = "0.11.0" compiler = "0.11.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"