forked from HEL/circuiteria
Introduce a new `zigzag-at` parameter on `wire.wire` as an alternative
to `zigzag-ratio` for positioning the middle bend segment of a
`style: "zigzag"` wire. Instead of a fraction of the endpoint span,
the bend is placed on the axis of a referenced anchor via CeTZ's
perpendicular operator (`|-`):
- zigzag-dir "vertical": segment sits at anchor.x
- zigzag-dir "horizontal": segment sits at anchor.y
Accepted forms:
- str — raw CeTZ anchor name (e.g. port anchor
"elmt-port-id" or "elmt.center")
- array / native CeTZ dict — used as-is
- (rel: <length>, to: <coord>) with scalar rel — ergonomic axial
offset, rewritten to (rel: (dx, 0), to: ...) for vertical dir or
(rel: (0, dy), to: ...) for horizontal dir
`zigzag-ratio` default flipped from `50%` to `auto` (still resolves
to 50%) to allow detecting an explicit user override. Panics on
misuse:
- `zigzag-at` set together with an explicit `zigzag-ratio`
- `zigzag-at` set with a non-zigzag `style`
Add `examples.zigzag-at` demonstrating both the string-anchor and the
ergonomic offset forms.
471 lines
15 KiB
Typst
471 lines
15 KiB
Typst
#import "example.typ": example
|
||
|
||
#let alu = example(```
|
||
element.alu(x: 0, y: 0, w: 1, h: 2, id: "alu")
|
||
wire.stub("alu-port-in1", "west")
|
||
wire.stub("alu-port-in2", "west")
|
||
wire.stub("alu-port-out", "east")
|
||
```)
|
||
|
||
#let block = example(```
|
||
element.block(
|
||
x: 0, y: 0, w: 2, h: 2, id: "block",
|
||
ports: (
|
||
north: ((id: "clk", clock: true),),
|
||
west: ((id: "in1", name: "A"),
|
||
(id: "in2", name: "B")),
|
||
east: ((id: "out", name: "C"),)
|
||
)
|
||
)
|
||
wire.stub("block-port-clk", "north")
|
||
wire.stub("block-port-in1", "west")
|
||
wire.stub("block-port-in2", "west")
|
||
wire.stub("block-port-out", "east")
|
||
```)
|
||
|
||
#let align-with-ports = example(```
|
||
// `align-with` reuses another port's coord
|
||
// (E/W: y, N/S: x). Forms:
|
||
// 1. "id" — local
|
||
// 2. (element:"e", port:"p") — cross-elmt
|
||
// 3. "raw-anchor" — CeTZ anchor
|
||
element.block(x: 0, y: 0, w: 2, h: 2, id: "b1",
|
||
ports: (
|
||
west: ((id: "a", name: "A", y: 30%),),
|
||
east: ((id: "oa", name: "A'",
|
||
"align-with": "a"),),
|
||
))
|
||
element.block(x: 4, y: -.3, w: 2, h: 2.6, id: "b2",
|
||
ports: (
|
||
west: ((id: "ia", name: "A",
|
||
"align-with":
|
||
(element: "b1", port: "oa")),),
|
||
east: ((id: "o", name: "C"),),
|
||
))
|
||
wire.stub("b1-port-a", "west")
|
||
wire.wire("w", ("b1-port-oa", "b2-port-ia"),
|
||
style: "direct")
|
||
wire.stub("b2-port-o", "east")
|
||
```, vertical: true)
|
||
|
||
#let extender = example(```
|
||
element.extender(
|
||
x: 0, y: 0, w: 3, h: 1,
|
||
id: "extender"
|
||
)
|
||
wire.stub("extender-port-in", "west")
|
||
wire.stub("extender-port-out", "east")
|
||
```)
|
||
|
||
#let multiplexer = example(```
|
||
element.multiplexer(
|
||
x: 0, y: 0, w: 1, h: 3,
|
||
id: "multiplexer",
|
||
entries: 3
|
||
)
|
||
wire.stub("multiplexer.north", "north")
|
||
wire.stub("multiplexer-port-out", "east")
|
||
|
||
element.multiplexer(
|
||
x: 0, y: -4, w: 1, h: 3,
|
||
id: "multiplexer2",
|
||
entries: ("A", "B", "C")
|
||
)
|
||
wire.stub("multiplexer2.south", "south")
|
||
wire.stub("multiplexer2-port-out", "east")
|
||
|
||
for i in range(3) {
|
||
wire.stub("multiplexer-port-in" + str(i), "west")
|
||
wire.stub("multiplexer2-port-in" + str(i), "west")
|
||
}
|
||
```)
|
||
|
||
#let wires = example(```
|
||
for i in range(5) {
|
||
draw.circle((i * 3, 0), radius: .1, name: "p" + str(i * 2))
|
||
draw.circle((i * 3 + 2, 1), radius: .1, name: "p" + str(i * 2 + 1))
|
||
draw.content((i * 3 + 1, -1), raw(wire.wire-styles.at(i)))
|
||
}
|
||
wire.wire("w1", ("p0", "p1"), style: "direct")
|
||
wire.wire("w2", ("p2", "p3"), style: "zigzag")
|
||
wire.wire("w3", ("p4", "p5"), style: "dodge",
|
||
dodge-y: -0.5, dodge-margins: (0.5, 0.5))
|
||
wire.wire("w4", ("p6","p7"), style: "guided",
|
||
guided-center:(20%, 40%), guided-margins: (90%,87%),
|
||
guided-sides: ("north","south"))
|
||
wire.wire("w5", ("p8", "p9"), style: "u-shape",
|
||
u-side: "east", u-depth: 0.5)
|
||
|
||
```, vertical: true)
|
||
|
||
#let stub = example(```
|
||
draw.circle((0, 0), radius: .1, name: "p")
|
||
wire.stub("p", "north", name: "north", length: 1)
|
||
wire.stub("p", "east", name: "east", vertical: true)
|
||
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")
|
||
```, vertical: true)
|
||
|
||
#let gate-nand = example(```
|
||
gates.gate-nand(x: 0, y: 0, w: 1.5, h: 1.5)
|
||
gates.gate-nand(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||
```, vertical: true)
|
||
|
||
#let gate-buf = example(```
|
||
gates.gate-buf(x: 0, y: 0, w: 1.5, h: 1.5)
|
||
gates.gate-buf(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||
```, vertical: true)
|
||
|
||
#let gate-not = example(```
|
||
gates.gate-not(x: 0, y: 0, w: 1.5, h: 1.5)
|
||
gates.gate-not(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||
```, vertical: true)
|
||
|
||
#let gate-or = example(```
|
||
gates.gate-or(x: 0, y: 0, w: 1.5, h: 1.5)
|
||
gates.gate-or(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||
```, vertical: true)
|
||
|
||
#let gate-nor = example(```
|
||
gates.gate-nor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||
gates.gate-nor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||
```, vertical: true)
|
||
|
||
#let gate-xor = example(```
|
||
gates.gate-xor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||
gates.gate-xor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||
```, vertical: true)
|
||
|
||
#let gate-xnor = example(```
|
||
gates.gate-xnor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||
gates.gate-xnor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||
```, vertical: true)
|
||
|
||
#let iec-gate-and = example(```
|
||
gates.iec-gate-and(x: 0, y: 0, w: 1.5, h: 1.5)
|
||
gates.iec-gate-and(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||
```, vertical: true)
|
||
|
||
#let iec-gate-nand = example(```
|
||
gates.iec-gate-nand(x: 0, y: 0, w: 1.5, h: 1.5)
|
||
gates.iec-gate-nand(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||
```, vertical: true)
|
||
|
||
#let iec-gate-or = example(```
|
||
gates.iec-gate-or(x: 0, y: 0, w: 1.5, h: 1.5)
|
||
gates.iec-gate-or(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||
```, vertical: true)
|
||
|
||
#let iec-gate-nor = example(```
|
||
gates.iec-gate-nor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||
gates.iec-gate-nor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||
```, vertical: true)
|
||
|
||
#let iec-gate-xor = example(```
|
||
gates.iec-gate-xor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||
gates.iec-gate-xor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||
```, vertical: true)
|
||
|
||
#let iec-gate-xnor = example(```
|
||
gates.iec-gate-xnor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||
gates.iec-gate-xnor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||
```, vertical: true)
|
||
|
||
#let iec-gate-buf = example(```
|
||
gates.iec-gate-buf(x: 0, y: 0, w: 1.5, h: 1.5)
|
||
gates.iec-gate-buf(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||
```, vertical: true)
|
||
|
||
#let iec-gate-not = example(```
|
||
gates.iec-gate-not(x: 0, y: 0, w: 1.5, h: 1.5)
|
||
gates.iec-gate-not(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||
```, vertical: true)
|
||
|
||
#let group = example(```
|
||
element.group(
|
||
id: "g1", name: "Group 1", stroke: (dash: "dashed"),
|
||
{
|
||
element.block(id: "b1", w: 2, h: 2,
|
||
x: 0, y: 1.5,
|
||
ports: (east: ((id: "out"),)),
|
||
fill: util.colors.green
|
||
)
|
||
element.block(id: "b2", w: 2, h: 1,
|
||
x: 0, y: 0,
|
||
ports: (east: ((id: "out"),)),
|
||
fill: util.colors.orange
|
||
)
|
||
}
|
||
)
|
||
element.block(id: "b3", w: 2, h: 3,
|
||
x: (rel: 1, to: "g1.east"),
|
||
y: (from: "b1-port-out", to: "in1"),
|
||
ports: (west: ((id: "in1"), (id: "in2"))),
|
||
fill: util.colors.blue
|
||
)
|
||
wire.wire("w1", ("b1-port-out", "b3-port-in1"))
|
||
wire.wire("w2", ("b2-port-out", "b3-port-in2"),
|
||
style: "zigzag")
|
||
```)
|
||
|
||
#let group-ports = example(```
|
||
// outside-port-names: true (default) — labels outside the border
|
||
element.group(
|
||
id: "g1", name: "outside (default)",
|
||
stroke: (dash: "dashed"),
|
||
fill: util.colors.blue.lighten(60%),
|
||
padding: 2em,
|
||
outside-port-names: true,
|
||
port-name-padding: 4pt,
|
||
ports: (
|
||
west: ((id: "in", name: "D"),),
|
||
east: ((id: "out", name: "Q"),),
|
||
),
|
||
{ element.block(id: "b1", w: 2, h: 1.5, x: 0, y: 0,
|
||
fill: util.colors.green,
|
||
ports: (west: ((id: "d", name: "d"),),
|
||
east: ((id: "q", name: "q"),))) }
|
||
)
|
||
wire.wire("wG1in", ("g1-port-in", "b1-port-d"), directed: true)
|
||
wire.wire("wG1out", ("b1-port-q", "g1-port-out"), directed: true)
|
||
|
||
// outside-port-names: false — labels inside the border (block style)
|
||
// with extra padding via port-name-padding
|
||
element.group(
|
||
id: "g2", name: "inside",
|
||
stroke: (dash: "dashed"),
|
||
fill: util.colors.orange.lighten(60%),
|
||
padding: 2em,
|
||
outside-port-names: false,
|
||
port-name-padding: 0.4em,
|
||
ports: (
|
||
west: ((id: "in", name: "D"),),
|
||
east: ((id: "out", name: "Q"),),
|
||
),
|
||
{ element.block(id: "b2", w: 2, h: 1.5, x: 6, y: 0,
|
||
fill: util.colors.green,
|
||
ports: (west: ((id: "d"),), east: ((id: "q"),))) }
|
||
)
|
||
wire.stub("g2-port-in", "west")
|
||
wire.stub("g2-port-out", "east")
|
||
```, vertical: true)
|
||
|
||
#let port-names-outside = example(```
|
||
element.block(
|
||
x: 0, y: 0, w: 2, h: 2, id: "block",
|
||
port-names-outside: true,
|
||
port-name-padding: 4pt,
|
||
ports: (
|
||
north: ((id: "clk", name: "CLK", clock: true),),
|
||
west: ((id: "in1", name: "A"),
|
||
(id: "in2", name: "B")),
|
||
east: ((id: "out", name: "Y"),)
|
||
)
|
||
)
|
||
```)
|
||
|
||
#let port-shape = example(```
|
||
// Block with pentagon-shaped ports.
|
||
// - port-shape / port-kind / port-size are element-level defaults.
|
||
// - Any port field (shape, kind, size, stroke, fill) overrides per port.
|
||
// - kind: "in" → tip points into the block (default)
|
||
// kind: "out" → tip points away from the block
|
||
// - clock ports with shape: "pentagon" drop their triangle and default
|
||
// to a gray fill.
|
||
element.block(
|
||
id: "b", w: 3, h: 3, x: 0, y: 0,
|
||
fill: util.colors.yellow.lighten(60%),
|
||
port-shape: "pentagon",
|
||
port-size: 1em,
|
||
ports: (
|
||
// west: default input
|
||
west: ((id: "in", name: "D"),),
|
||
// east: output — tip points away
|
||
east: ((id: "out", name: "Q", kind: "out"),),
|
||
// north: clock port — pentagon replaces the triangle, gray fill
|
||
north: ((id: "clk", name: "CLK", clock: true),),
|
||
// south: styled output with per-port fill and stroke
|
||
south: ((id: "rdy", name: "RDY", kind: "out",
|
||
fill: util.colors.blue, stroke: red + 1.5pt),),
|
||
),
|
||
)
|
||
// Wires connect at the outer end of each pentagon.
|
||
wire.stub("b-port-in", "west")
|
||
wire.stub("b-port-out", "east")
|
||
wire.stub("b-port-clk", "north")
|
||
wire.stub("b-port-rdy", "south")
|
||
```)
|
||
|
||
#let shell = example(```
|
||
// A shell block acts as a container: port name labels are drawn
|
||
// outside by default, and wires attach at the *inside* tip of
|
||
// pentagon-shaped ports so child elements can sit within.
|
||
element.block(
|
||
id: "sh", w: 5, h: 3, x: 0, y: 0,
|
||
shell: true,
|
||
port-shape: "pentagon",
|
||
fill: util.colors.blue.lighten(80%),
|
||
ports: (
|
||
west: ((id: "in", name: "IN"),),
|
||
east: ((id: "out", name: "OUT", kind: "out"),),
|
||
),
|
||
{
|
||
element.block(id: "inner", w: 2, h: 1.5, x: 1.5, y: 0.75,
|
||
fill: util.colors.yellow,
|
||
ports: (west: ((id: "a"),), east: ((id: "b"),)))
|
||
}
|
||
)
|
||
wire.wire("w-in", ("sh-port-in", "inner-port-a"))
|
||
wire.wire("w-out", ("inner-port-b", "sh-port-out"))
|
||
```)
|
||
|
||
#let intersection = example(```
|
||
wire.wire("w1", ((0, 0), (1, 1)), style: "zigzag")
|
||
wire.wire("w2", ((0, 0), (1, -.5)),
|
||
style: "zigzag", zigzag-ratio: 80%)
|
||
wire.intersection("w1.zig")
|
||
```)
|
||
|
||
#let u-shape-wire = example(```
|
||
// east <-> east (u-side auto-detected)
|
||
element.block(x: 0, y: 0, w: 1.5, h: 1.5, id: "eA",
|
||
fill: util.colors.orange,
|
||
ports: (east: ((id: "out"),)))
|
||
element.block(x: 0.5, y: -2.5, w: 1.5, h: 1.5, id: "eB",
|
||
fill: util.colors.orange,
|
||
ports: (east: ((id: "out"),)))
|
||
wire.wire("uEE", ("eA-port-out", "eB-port-out"),
|
||
style: "u-shape", u-depth: 0.5)
|
||
|
||
// west <-> west (auto-detected)
|
||
element.block(x: 6, y: 0, w: 1.5, h: 1.5, id: "wA",
|
||
fill: util.colors.yellow,
|
||
ports: (west: ((id: "in"),)))
|
||
element.block(x: 6.5, y: -2.5, w: 1.5, h: 1.5, id: "wB",
|
||
fill: util.colors.yellow,
|
||
ports: (west: ((id: "in"),)))
|
||
wire.wire("uWW", ("wA-port-in", "wB-port-in"),
|
||
style: "u-shape", u-depth: 0.5)
|
||
|
||
// north <-> north (auto-detected)
|
||
element.block(x: 10, y: -3, w: 1.5, h: 1.5, id: "nA",
|
||
fill: util.colors.green,
|
||
ports: (north: ((id: "out"),)))
|
||
element.block(x: 12.5, y: -2.5, w: 1.5, h: 1.5, id: "nB",
|
||
fill: util.colors.green,
|
||
ports: (north: ((id: "out"),)))
|
||
wire.wire("uNN", ("nA-port-out", "nB-port-out"),
|
||
style: "u-shape", u-depth: 0.5)
|
||
|
||
// south <-> south with explicit u-side override
|
||
element.block(x: 16, y: -3, w: 1.5, h: 1.5, id: "sA",
|
||
fill: util.colors.pink,
|
||
ports: (south: ((id: "out"),)))
|
||
element.block(x: 18.5, y: -3.5, w: 1.5, h: 1.5, id: "sB",
|
||
fill: util.colors.pink,
|
||
ports: (south: ((id: "out"),)))
|
||
wire.wire("uSS", ("sA-port-out", "sB-port-out"),
|
||
style: "u-shape", u-side: "south", u-depth: 0.5)
|
||
```, vertical: true)
|
||
|
||
#let zigzag-at = example(```
|
||
// `zigzag-at` places the middle bend of a zigzag wire
|
||
// on the axis of a referenced anchor, overriding
|
||
// `zigzag-ratio`.
|
||
element.block(x: 0, y: 0, w: 1.5, h: 1.5, id: "src",
|
||
ports: (east: ((id: "o", name: "O"),),))
|
||
element.block(x: 3, y: -1.2, w: 1, h: 1, id: "ref",
|
||
fill: util.colors.pink,
|
||
ports: (west: ((id: "p", name: "R"),),))
|
||
element.block(x: 5.5, y: -2, w: 1.5, h: 1.5, id: "dst",
|
||
ports: (west: ((id: "i", name: "I"),),))
|
||
// String form: anchor name of a port.
|
||
wire.wire("w1", ("src-port-o", "dst-port-i"),
|
||
style: "zigzag",
|
||
zigzag-at: "ref-port-p")
|
||
// Ergonomic offset: shift 8pt right of ref.east.
|
||
wire.wire("w2", ("src-port-o", "dst-port-i"),
|
||
style: "zigzag",
|
||
zigzag-at: (rel: 8pt, to: "ref.east"))
|
||
```, vertical: true)
|