diff --git a/doc/examples.typ b/doc/examples.typ index a182e59..704d4c0 100644 --- a/doc/examples.typ +++ b/doc/examples.typ @@ -79,6 +79,81 @@ wire.stub("p", "south", name: "south", length: 15pt) wire.stub("p", "west", name: "west", length: 3em) ```) +#let breakout = example(``` +// 16 breakouts: 4 directions × 4 label positions, laid out +// two per row so the labels have room to breathe. +let dirs = ("north-east", "north-west", "south-east", "south-west") +let poss = ("north-east", "north-west", "south-east", "south-west") +let short = ( + "north-east": "NE", "north-west": "NW", + "south-east": "SE", "south-west": "SW", +) +let cols = 2 +let dx = 5 +let dy = 2 +for (i, dir) in dirs.enumerate() { + for (j, pos) in poss.enumerate() { + let n = i * poss.len() + j + let r = calc.floor(n / cols) + let c = calc.rem(n, cols) + draw.circle((c * dx, -r * dy), radius: .05) + wire.breakout("b" + str(n), + x: c * dx, y: -r * dy, + direction: dir, + port-side: if dir.ends-with("east") { "west" } else { "east" }, + name: short.at(dir) + "/" + short.at(pos), + name-pos: pos) + } +} +```) + +#let breakout-label = example(``` +// name-pos matches direction → label sits at the end point. +wire.breakout("b1", x: 0, y: 0, direction: "north-east", + port-side: "east", + name: "at end", name-pos: "north-east") + +// name-pos opposite to direction → label would cover the line, so +// it falls back to the start point. +wire.breakout("b2", x: 4, y: 0, direction: "north-east", + port-side: "east", + name: "at start", name-pos: "south-west") +```, vertical: true) + +#let breakout-wire = example(``` +// Bus fan-out: a horizontal wire from the left block to the lower +// right block carries the full 10-bit bus. A breakout in the middle +// of that wire taps bits [9:5] and routes them to the upper right +// block via an L-shaped zigzag (up, then right). +element.block(x: 0, y: 0, w: 2, h: 2, id: "src", + fill: util.colors.blue, + ports: (east: ((id: "out"),))) +element.block(x: 6, y: 0, w: 2, h: 2, id: "dstL", + fill: util.colors.green, + ports: (west: ((id: "in"),))) +element.block(x: 6, y: 4, w: 2, h: 2, id: "dstU", + fill: util.colors.orange, + ports: (west: ((id: "in"),))) + +// Full bus wire, labelled "[9:0]" on the left. +wire.wire("wbus", ("src-port-out", "dstL-port-in"), + name: "[9:0]", name-pos: "start") + +// Breakout tapped from the midpoint of the bus. +wire.breakout("tap", + x: (rel: 2, to: "src-port-out"), + y: (from: "src-port-out"), + direction: "north-east", + port-side: "west", + name: "[9:5]", name-pos: "north-east") + +// L-shaped wire from the breakout up and then right to the upper +// block's input. +wire.wire("wtap", ("tap-port-tap", "dstU-port-in"), + style: "zigzag", zigzag-dir: "horizontal", + zigzag-ratio: 100%) +```) + #let gate-and = example(``` gates.gate-and(x: 0, y: 0, w: 1.5, h: 1.5) gates.gate-and(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all") diff --git a/gallery/platypus.png b/gallery/platypus.png index 7e0a912..cd954d8 100644 Binary files a/gallery/platypus.png and b/gallery/platypus.png differ diff --git a/gallery/test.png b/gallery/test.png index c4e9480..2c5d0c6 100644 Binary files a/gallery/test.png and b/gallery/test.png differ diff --git a/gallery/test2.png b/gallery/test2.png index c4e9480..2c5d0c6 100644 Binary files a/gallery/test2.png and b/gallery/test2.png differ diff --git a/gallery/test3.png b/gallery/test3.png index 8af0ba5..ac6032a 100644 Binary files a/gallery/test3.png and b/gallery/test3.png differ diff --git a/gallery/test4.png b/gallery/test4.png index 4f36ffc..57ae67b 100644 Binary files a/gallery/test4.png and b/gallery/test4.png differ diff --git a/gallery/test5.png b/gallery/test5.png index e23a31f..ecb2eec 100644 Binary files a/gallery/test5.png and b/gallery/test5.png differ diff --git a/gallery/test6.png b/gallery/test6.png index 409663e..eb67604 100644 Binary files a/gallery/test6.png and b/gallery/test6.png differ diff --git a/gallery/test7.png b/gallery/test7.png index cfe3ee4..99d3505 100644 Binary files a/gallery/test7.png and b/gallery/test7.png differ diff --git a/gallery/test_breakout.png b/gallery/test_breakout.png new file mode 100644 index 0000000..8de3904 Binary files /dev/null and b/gallery/test_breakout.png differ diff --git a/gallery/test_breakout.typ b/gallery/test_breakout.typ new file mode 100644 index 0000000..22b992a --- /dev/null +++ b/gallery/test_breakout.typ @@ -0,0 +1,117 @@ +// Demonstrates the wire.breakout element used to split off slices of a +// bus that connects blocks. The main bus wires are always laid out +// orthogonally; breakouts diagonally tap intermediate points on those +// wires and route the sub-bus to another block via an L-shaped zigzag. +#import "/src/lib.typ": circuit, element, util, wire + +#set page(width: auto, height: auto, margin: .5cm) + +#circuit({ + // ---- Source block on the left, main sink on the right ---- + element.block(x: 0, y: 0, w: 2, h: 2, id: "src", fill: util.colors.blue, ports: ( + east: ((id: "out"),), + )) + + element.block(x: 15, y: 0, w: 2, h: 2, id: "dstMain", fill: util.colors.green, ports: ( + west: ((id: "in"),), + )) + + // Full bus from src to dstMain, labelled [15:0] at the start. + wire.wire("wbus", ("src-port-out", "dstMain-port-in"), name: "[15:0]", name-pos: "start") + + // ---- Upper-left sink: taps bits [11:8] via NW breakout ---- + element.block(x: 0, y: 4, w: 2, h: 2, id: "dstNW", fill: util.colors.yellow, ports: ( + east: ((id: "in"),), + )) + + wire.breakout( + "tapNE", + x: (rel: 3, to: "src-port-out"), + y: (from: "src-port-out"), + direction: "north-east", + port-side: "west", + name: "[15:12]", + name-pos: "north-east", + ) + + // L-shape: up first, then left into dstNW. + wire.wire( + "wNW", + ("tapNE-port-tapNE", "dstNW-port-in"), + style: "zigzag", + zigzag-dir: "horizontal", + zigzag-ratio: 100%, + ) + + // ---- Upper-right sink: taps bits [15:12] via NE breakout ---- + element.block(x: 15, y: 4, w: 2, h: 2, id: "dstNE", fill: util.colors.orange, ports: ( + west: ((id: "in"),), + )) + + wire.breakout( + "tapNW", + x: (rel: 7, to: "src-port-out"), + y: (from: "src-port-out"), + direction: "north-west", + port-side: "east", + name: "[11:8]", + name-pos: "north-east", + ) + + // L-shape: up first, then right into dstNE. + wire.wire( + "wNE", + ("tapNW-port-tapNW", "dstNE-port-in"), + style: "zigzag", + zigzag-dir: "horizontal", + zigzag-ratio: 100%, + ) + + // ---- Lower-left sink: taps bits [3:0] via SW breakout ---- + element.block(x: 0, y: -4, w: 2, h: 2, id: "dstSW", fill: util.colors.purple, ports: ( + east: ((id: "in"),), + )) + + wire.breakout( + "tapSE", + x: (rel: 5, to: "src-port-out"), + y: (from: "src-port-out"), + direction: "south-east", + port-side: "west", + name: "[7:4]", + name-pos: "south-east", + ) + + // L-shape: down first, then left into dstSW. + wire.wire( + "wSW", + ("tapSE-port-tapSE", "dstSW-port-in"), + style: "zigzag", + zigzag-dir: "horizontal", + zigzag-ratio: 100%, + ) + + // ---- Lower-right sink: taps bits [7:4] via SE breakout ---- + element.block(x: 15, y: -4, w: 2, h: 2, id: "dstSE", fill: util.colors.pink, ports: ( + west: ((id: "in"),), + )) + + wire.breakout( + "tapSW", + x: (rel: 11, to: "src-port-out"), + y: (from: "src-port-out"), + direction: "south-west", + port-side: "east", + name: "[3:0]", + name-pos: "south-west", + ) + + // L-shape: down first, then right into dstSE. + wire.wire( + "wSE", + ("tapSW-port-tapSW", "dstSE-port-in"), + style: "zigzag", + zigzag-dir: "horizontal", + zigzag-ratio: 100%, + ) +}) diff --git a/gallery/test_yrel.png b/gallery/test_yrel.png new file mode 100644 index 0000000..8d902b5 Binary files /dev/null and b/gallery/test_yrel.png differ diff --git a/gallery/test_yrel.typ b/gallery/test_yrel.typ new file mode 100644 index 0000000..36a2cfb --- /dev/null +++ b/gallery/test_yrel.typ @@ -0,0 +1,46 @@ +// Demonstrates the `(rel: , to: )` shorthand for the `y` +// parameter (analogous to the existing shorthand for `x`). +#import "/src/lib.typ": circuit, element, util, wire + +#set page(width: auto, height: auto, margin: .5cm) + +#circuit({ + // Anchor block: absolute placement. + element.block( + x: 0, y: 0, w: 2, h: 1.5, + id: "A", + fill: util.colors.orange, + ports: (east: ((id: "out"),)) + ) + + // X relative, Y absolute (existing feature). + element.block( + x: (rel: 1, to: "A.east"), y: 0, w: 2, h: 1.5, + id: "B", + fill: util.colors.yellow, + ports: (west: ((id: "in"),), east: ((id: "out"),)) + ) + + // Both X and Y relative to another element's anchor (new feature). + element.block( + x: (rel: 1, to: "B.east"), + y: (rel: 2, to: "B.south-west"), + w: 2, h: 1.5, + id: "C", + fill: util.colors.green, + ports: (west: ((id: "in"),)) + ) + + // Y relative only, X absolute. + element.block( + x: 0, + y: (rel: -2.5, to: "A.south-west"), + w: 2, h: 1.5, + id: "D", + fill: util.colors.blue, + ports: (east: ((id: "out"),)) + ) + + wire.wire("wAB", ("A-port-out", "B-port-in")) + wire.wire("wBC", ("B-port-out", "C-port-in"), style: "zigzag") +}) diff --git a/manual.pdf b/manual.pdf index f4e1052..ec39148 100644 Binary files a/manual.pdf and b/manual.pdf differ diff --git a/manual.typ b/manual.typ index ebe0c88..f1cfa11 100644 --- a/manual.typ +++ b/manual.typ @@ -1,4 +1,4 @@ -#import "@preview/tidy:0.4.1" +#import "@preview/tidy:0.4.3" #import "/src/cetz.typ": draw, canvas #import "src/lib.typ" #import "doc/examples.typ" diff --git a/src/cetz.typ b/src/cetz.typ index 980a619..adb796c 100644 --- a/src/cetz.typ +++ b/src/cetz.typ @@ -1 +1 @@ -#import "@preview/cetz:0.3.4": * \ No newline at end of file +#import "@preview/cetz:0.5.2": * \ No newline at end of file diff --git a/src/circuit.typ b/src/circuit.typ index f89389f..958fa6e 100644 --- a/src/circuit.typ +++ b/src/circuit.typ @@ -1,5 +1,5 @@ #import "/src/cetz.typ": canvas -#import "@preview/tidy:0.3.0" +#import "@preview/tidy:0.4.3" /// Draws a block circuit diagram /// diff --git a/src/elements/element.typ b/src/elements/element.typ index 09adfd3..92bc853 100644 --- a/src/elements/element.typ +++ b/src/elements/element.typ @@ -24,7 +24,9 @@ /// If it is a dictionary, it should be in the format `(rel: number, to: str)`, where `rel` is the offset and `to` the base anchor /// - y (number, dictionary): The y position (bottom-left corner). /// -/// If it is a dictionary, it should be in the format `(from: str, to: str)`, where `from` is the base anchor and `to` is the id of the port to align with the anchor +/// If it is a dictionary, it can take one of two forms: +/// - `(rel: number, to: str)`: `rel` is the y offset and `to` the base anchor (analogous to `x`) +/// - `(from: str, to: str)`: `from` is the base anchor and `to` is the id of the port to align with the anchor /// - w (number): Width of the element /// - h (number): Height of the element /// - name (none, str): Optional name of the block @@ -74,13 +76,12 @@ if h == none { panic("Parameter h must be set") } if (type(x) == dictionary) { - let offset = x.rel - let to = x.to - let (ctx, to-pos) = coordinate.resolve(ctx, (rel: (offset, 0), to: to)) - x = to-pos.at(0) + x = util.resolve-rel-coord(ctx, x, 0) } - if (type(y) == dictionary) { + if (type(y) == dictionary and "rel" in y) { + y = util.resolve-rel-coord(ctx, y, 1) + } else if (type(y) == dictionary) { let from = y.from let to = y.to let (to-side, i) = find-port(ports, to) diff --git a/src/elements/logic/gate.typ b/src/elements/logic/gate.typ index bd0fb23..5d9f4de 100644 --- a/src/elements/logic/gate.typ +++ b/src/elements/logic/gate.typ @@ -1,4 +1,5 @@ #import "/src/cetz.typ": draw, coordinate +#import "/src/util.typ" #import "../ports.typ": add-ports, add-port #import "../element.typ" @@ -49,13 +50,12 @@ if h == none { panic("Parameter h must be set") } if (type(x) == dictionary) { - let offset = x.rel - let to = x.to - let (ctx, to-pos) = coordinate.resolve(ctx, (rel: (offset, 0), to: to)) - x = to-pos.at(0) + x = util.resolve-rel-coord(ctx, x, 0) } - if (type(y) == dictionary) { + if (type(y) == dictionary and "rel" in y) { + y = util.resolve-rel-coord(ctx, y, 1) + } else if (type(y) == dictionary) { let from = y.from let to = y.to diff --git a/src/elements/logic/iec_gate.typ b/src/elements/logic/iec_gate.typ index 08d97f1..0656b05 100644 --- a/src/elements/logic/iec_gate.typ +++ b/src/elements/logic/iec_gate.typ @@ -1,4 +1,5 @@ #import "/src/cetz.typ": draw, coordinate +#import "/src/util.typ" #import "../ports.typ": add-ports, add-port #import "../element.typ" @@ -60,13 +61,12 @@ if h == none { panic("Parameter h must be set") } if (type(x) == dictionary) { - let offset = x.rel - let to = x.to - let (ctx, to-pos) = coordinate.resolve(ctx, (rel: (offset, 0), to: to)) - x = to-pos.at(0) + x = util.resolve-rel-coord(ctx, x, 0) } - if (type(y) == dictionary) { + if (type(y) == dictionary and "rel" in y) { + y = util.resolve-rel-coord(ctx, y, 1) + } else if (type(y) == dictionary) { let from = y.from let to = y.to diff --git a/src/util.typ b/src/util.typ index a444434..bee181f 100644 --- a/src/util.typ +++ b/src/util.typ @@ -1,3 +1,23 @@ +#import "cetz.typ": coordinate + +/// Resolves a relative coordinate shorthand of the form `(rel: , to: )` +/// to a single scalar along the requested axis. +/// +/// Used to implement the shared `(rel:, to:)` shorthand accepted by the `x` and +/// `y` parameters of `elmt`, `gate` and `iec-gate`. +/// +/// - ctx (dictionary): The current CeTZ context +/// - val (dictionary): A dictionary with fields `rel` (number) and `to` (anchor) +/// - axis (int): `0` for x, `1` for y +/// -> number +#let resolve-rel-coord(ctx, val, axis) = { + let offset = val.rel + let to = val.to + let delta = if axis == 0 { (offset, 0) } else { (0, offset) } + let (_, to-pos) = coordinate.resolve(ctx, (rel: delta, to: to)) + return to-pos.at(axis) +} + /// Predefined color palette /// #let w = 4 /// #box(width: 100%, align(center)[ @@ -73,4 +93,45 @@ #let valid-anchors = ( "center", "north", "east", "west", "south", "north-east", "north-west", "south-east", "south-west" -) \ No newline at end of file +) + +/// Normalizes a coordinate so that 2D points (x, y) are padded +/// to (x, y, 0) as required by CeTZ's coordinate resolver. +/// Also handles function-based and dictionary-based coordinates +/// recursively. +/// +/// - coord (any): A CeTZ-compatible coordinate +/// -> any +#let normalize-coord(coord) = { + if type(coord) == array { + if coord.len() == 2 { + let (a, b) = coord + // Raw 2D point: (x, y) -> (x, y, 0) + if type(a) in (int, float) and type(b) in (int, float) { + return (a, b, 0) + } + // Function-based coordinate: (fn, point) + if type(a) == function { + let orig = a + return (v => { + let result = orig(v) + if type(result) == array and result.len() == 2 { + let (rx, ry) = result + if type(rx) in (int, float) and type(ry) in (int, float) { + return (rx, ry, 0) + } + } + return result + }, b) + } + } + } + if type(coord) == dictionary { + let result = (:) + for (key, value) in coord { + result.insert(key, normalize-coord(value)) + } + return result + } + return coord +} \ No newline at end of file diff --git a/src/wire.typ b/src/wire.typ index cbf7c4b..c9b85fc 100644 --- a/src/wire.typ +++ b/src/wire.typ @@ -1,5 +1,5 @@ #import "/src/cetz.typ": draw, coordinate -#import "util.typ": opposite-anchor +#import "util.typ": opposite-anchor, normalize-coord, resolve-rel-coord /// List of valid wire styles /// #examples.wires @@ -68,8 +68,8 @@ let (ctx, p0) = coordinate.resolve(ctx, start) let (ctx, p3) = coordinate.resolve(ctx, end) - p0 = (x: p0.first(), y: p0.last()) - p3 = (x: p3.first(), y: p3.last()) + p0 = (x: p0.first(), y: p0.at(1)) + p3 = (x: p3.first(), y: p3.at(1)) let dx1 = margin-start let dx2 = margin-end @@ -86,13 +86,13 @@ if side-end == "east" { dx2 *= -1 } - p1 = (p0.x + dx1, p0.y) - p2 = (p3.x - dx2, p0.y) + p1 = (p0.x + dx1, p0.y, 0) + p2 = (p3.x - dx2, p0.y, 0) let points = ( start, (horizontal: p1, vertical: ()), - (horizontal: (), vertical: (0, dodge-y)), + (horizontal: (), vertical: (0, dodge-y, 0)), (horizontal: p2, vertical: ()), (horizontal: (), vertical: end), end @@ -147,18 +147,18 @@ // points that are closest to the edge points - let p1 = (p0.x + dx1, p0.y + dy1) - let p5 = (p6.x + dx2, p6.y + dy2) + let p1 = (p0.x + dx1, p0.y + dy1, 0) + let p5 = (p6.x + dx2, p6.y + dy2, 0) // middle point let center_x = p0.x + box_width * center_horizontal / 100% let center_y = p0.y + box_height * center_vertical / 100% - let p3 = (center_x, center_y) + let p3 = (center_x, center_y, 0) // setting up the points for that touch the guides - let p2 = (0,0) - let p4 = (0,0) + let p2 = (0, 0, 0) + let p4 = (0, 0, 0) if side-start in ("north", "south") { p2 = (horizontal: p3, vertical: p1) } else { @@ -240,6 +240,9 @@ if pts.len() != 2 { panic("Wrong number of points (got " + str(pts.len()) + " instead of 2)") } + + // Normalize user-provided coordinates: (x, y) -> (x, y, 0) + let pts = pts.map(normalize-coord) let stroke = ( paint: color, @@ -400,3 +403,141 @@ ) } } + +/// Draws a wire breakout: a 45° diagonal line starting at (x, y) and +/// ending 1 unit away in one of the four diagonal directions. The end +/// point exposes a port anchor named `-port-` so wires can attach +/// to it as if it were a block port. A synthesized `.center` anchor +/// is placed opposite `port-side`, so the cardinal side the port faces +/// can be deduced by comparing the port with the `.center` anchor. +/// +/// #examples.breakout +/// +/// Label positions are anchored so that the label sits neatly next to the +/// end point of the breakout. When the label direction matches the +/// breakout direction (which would overlap the line) the label is +/// automatically placed at the start point instead so the text does not +/// cover the line. +/// +/// #examples.breakout-label +/// +/// The port anchor makes it easy to fan out a bus: +/// +/// #examples.breakout-wire +/// +/// - id (str): The breakout id (also used as the port id) +/// - x (number, dictionary): The starting x position (same forms as `element.block`) +/// - y (number, dictionary): The starting y position (same forms as `element.block`) +/// - direction (str): One of `"north-east"`, `"north-west"`, `"south-east"`, `"south-west"` +/// - length (length, number): The diagonal length (default `1em`, matches port size) +/// - port-side (str): Cardinal side used for wire-attach behavior (`"north"`, `"east"`, `"south"`, `"west"`) +/// - stroke (stroke): Line stroke (paint + thickness) +/// - name (none, str): Optional label displayed near the breakout +/// - name-pos (none, str): Label position, one of `"north-west"`, `"north-east"`, `"south-west"`, `"south-east"` +/// - debug (bool): If true, shows a red dot at the port anchor +#let breakout( + id, + x: none, y: none, + direction: "north-east", + length: 1em, + port-side: "east", + stroke: black + 1pt, + name: none, + name-pos: none, + debug: false, +) = draw.get-ctx(ctx => { + if x == none { panic("breakout: parameter x must be set") } + if y == none { panic("breakout: parameter y must be set") } + + let valid-directions = ("north-east", "north-west", "south-east", "south-west") + if not direction in valid-directions { + panic("breakout: invalid direction '" + repr(direction) + + "'; must be one of " + repr(valid-directions)) + } + let valid-sides = ("north", "east", "south", "west") + if not port-side in valid-sides { + panic("breakout: invalid port-side '" + repr(port-side) + + "'; must be one of " + repr(valid-sides)) + } + + // Diagonal component: length / sqrt(2). Resolve to a canvas-space + // number so we can do arithmetic with anchor coordinates. + let d-raw = length / calc.sqrt(2) + let (_, d-vec) = coordinate.resolve(ctx, (d-raw, 0, 0)) + let d = d-vec.at(0) + + // Direction sign vector for (dx, dy). + let sx = if direction.ends-with("east") { 1 } else { -1 } + let sy = if direction.starts-with("north") { 1 } else { -1 } + + // Resolve x. + let sx0 = x + if type(x) == dictionary { + if "rel" in x { + sx0 = resolve-rel-coord(ctx, x, 0) + } else if "from" in x { + let (_, from-pos) = coordinate.resolve(ctx, x.from) + sx0 = from-pos.at(0) + } else { + panic("breakout: unsupported x dictionary: " + repr(x)) + } + } + // Resolve y. + let sy0 = y + if type(y) == dictionary { + if "rel" in y { + sy0 = resolve-rel-coord(ctx, y, 1) + } else if "from" in y { + let (_, from-pos) = coordinate.resolve(ctx, y.from) + sy0 = from-pos.at(1) + } else { + panic("breakout: unsupported y dictionary: " + repr(y)) + } + } + + let start = (sx0, sy0) + let end = (rel: (sx * d, sy * d), to: start) + + // Offset used for the synthesized center anchor: opposite of port-side. + let center-offset = ( + "north": (0, -1em), + "east": (-1em, 0), + "south": (0, 1em), + "west": (1em, 0), + ).at(port-side) + + let port-anchor-name = id + "-port-" + id + + draw.group(name: id, { + draw.line(start, end, stroke: stroke) + draw.anchor("start", start) + draw.anchor("end", end) + draw.anchor("center", (rel: center-offset, to: end)) + + if name != none and name-pos != none { + let label-anchor = ( + "north-west": "south-east", + "north-east": "south-west", + "south-west": "north-east", + "south-east": "north-west", + ).at(name-pos) + + // Overlap case: label direction is opposite to breakout direction, + // so the label at the end point would extend back over the line. + // Anchor it at the start point instead. + let label-pos = if name-pos == opposite-anchor(direction) { start } else { end } + + draw.content(label-pos, anchor: label-anchor, padding: 3pt, name) + } + }) + + // Port anchor emitted at top level so it can be referenced by its + // bare name (mirrors add-port's approach in ports.typ). + if debug { + draw.circle(end, name: port-anchor-name, + radius: .1, stroke: none, fill: red) + } else { + draw.hide(draw.circle(end, name: port-anchor-name, + radius: 0, stroke: none)) + } +}) diff --git a/typst.toml b/typst.toml index b83700a..fb35880 100644 --- a/typst.toml +++ b/typst.toml @@ -1,7 +1,7 @@ [package] name = "circuiteria" version = "0.2.1" -compiler = "0.13.1" +compiler = "0.15.0" repository = "https://git.kb28.ch/HEL/circuiteria" entrypoint = "src/lib.typ" authors = [