forked from HEL/circuiteria
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f247c2efc | ||
|
|
beda61b9f4 | ||
|
|
2c6e5f44df |
No files matched your search
+96
-77
@@ -56,7 +56,7 @@ for i in range(3) {
|
||||
```)
|
||||
|
||||
#let wires = example(```
|
||||
for i in range(4) {
|
||||
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)))
|
||||
@@ -68,9 +68,103 @@ wire.wire("w3", ("p4", "p5"), style: "dodge",
|
||||
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 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,
|
||||
)
|
||||
|
||||
#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 u-at = example(
|
||||
```
|
||||
// `u-at` places the U-bend crossbar on the axis
|
||||
// of a referenced anchor, overriding `u-depth`.
|
||||
element.block(x: 0, y: 0, w: 1.5, h: 1.5, id: "eA",
|
||||
fill: util.colors.orange,
|
||||
ports: (east: ((id: "o1"), (id: "o2"))))
|
||||
element.block(x: 0, y: -3.5, w: 1.5, h: 1.5, id: "eB",
|
||||
fill: util.colors.orange,
|
||||
ports: (east: ((id: "i1"), (id: "i2"))))
|
||||
element.block(x: 4, y: -2.25, w: 0.6, h: 0.6, id: "ref",
|
||||
fill: util.colors.pink)
|
||||
// Align one U crossbar with ref.east.
|
||||
wire.wire("u1", ("eA-port-o1", "eB-port-i1"),
|
||||
style: "u-shape",
|
||||
u-at: "ref.east")
|
||||
// Ergonomic scalar offset: 1 right of ref.east.
|
||||
wire.wire("u2", ("eA-port-o2", "eB-port-i2"),
|
||||
style: "u-shape", color: util.colors.blue,
|
||||
u-at: (rel: 1, to: "ref.east"))
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let stub = example(```
|
||||
draw.circle((0, 0), radius: .1, name: "p")
|
||||
wire.stub("p", "north", name: "north", length: 1)
|
||||
@@ -79,81 +173,6 @@ 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")
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
@@ -1,117 +0,0 @@
|
||||
// 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%,
|
||||
)
|
||||
})
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
@@ -0,0 +1,53 @@
|
||||
// Demonstrates the u-shape wire style.
|
||||
// The u-side is auto-detected from the port anchors when both endpoints
|
||||
// belong to elements (deduced from the port position vs the element's
|
||||
// center anchor). It can also be set explicitly with u-side.
|
||||
#import "/src/lib.typ": circuit, element, util, wire
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
#circuit({
|
||||
// ---- east <-> east (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.6,
|
||||
name: "E-E")
|
||||
|
||||
// ---- 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.6,
|
||||
name: "W-W")
|
||||
|
||||
// ---- 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.6,
|
||||
name: "N-N")
|
||||
|
||||
// ---- south <-> south (explicit u-side override) ----
|
||||
element.block(x: 16, y: 0, w: 1.5, h: 1.5, id: "sA",
|
||||
fill: util.colors.pink,
|
||||
ports: (south: ((id: "out"),)))
|
||||
element.block(x: 18.5, y: -0.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.6,
|
||||
name: "S-S")
|
||||
})
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.1 KiB |
@@ -1,46 +0,0 @@
|
||||
// Demonstrates the `(rel: <number>, to: <anchor>)` 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")
|
||||
})
|
||||
+7188
-7795
File diff suppressed because it is too large.
Load diff
@@ -24,9 +24,7 @@
|
||||
/// 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 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
|
||||
/// 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
|
||||
/// - w (number): Width of the element
|
||||
/// - h (number): Height of the element
|
||||
/// - name (none, str): Optional name of the block
|
||||
@@ -76,12 +74,13 @@
|
||||
if h == none { panic("Parameter h must be set") }
|
||||
|
||||
if (type(x) == dictionary) {
|
||||
x = util.resolve-rel-coord(ctx, x, 0)
|
||||
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)
|
||||
}
|
||||
|
||||
if (type(y) == dictionary and "rel" in y) {
|
||||
y = util.resolve-rel-coord(ctx, y, 1)
|
||||
} else if (type(y) == dictionary) {
|
||||
if (type(y) == dictionary) {
|
||||
let from = y.from
|
||||
let to = y.to
|
||||
let (to-side, i) = find-port(ports, to)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#import "/src/cetz.typ": draw, coordinate
|
||||
#import "/src/util.typ"
|
||||
#import "../ports.typ": add-ports, add-port
|
||||
#import "../element.typ"
|
||||
|
||||
@@ -50,12 +49,13 @@
|
||||
if h == none { panic("Parameter h must be set") }
|
||||
|
||||
if (type(x) == dictionary) {
|
||||
x = util.resolve-rel-coord(ctx, x, 0)
|
||||
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)
|
||||
}
|
||||
|
||||
if (type(y) == dictionary and "rel" in y) {
|
||||
y = util.resolve-rel-coord(ctx, y, 1)
|
||||
} else if (type(y) == dictionary) {
|
||||
if (type(y) == dictionary) {
|
||||
let from = y.from
|
||||
let to = y.to
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#import "/src/cetz.typ": draw, coordinate
|
||||
#import "/src/util.typ"
|
||||
#import "../ports.typ": add-ports, add-port
|
||||
#import "../element.typ"
|
||||
|
||||
@@ -61,12 +60,13 @@
|
||||
if h == none { panic("Parameter h must be set") }
|
||||
|
||||
if (type(x) == dictionary) {
|
||||
x = util.resolve-rel-coord(ctx, x, 0)
|
||||
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)
|
||||
}
|
||||
|
||||
if (type(y) == dictionary and "rel" in y) {
|
||||
y = util.resolve-rel-coord(ctx, y, 1)
|
||||
} else if (type(y) == dictionary) {
|
||||
if (type(y) == dictionary) {
|
||||
let from = y.from
|
||||
let to = y.to
|
||||
|
||||
|
||||
@@ -1,23 +1,3 @@
|
||||
#import "cetz.typ": coordinate
|
||||
|
||||
/// Resolves a relative coordinate shorthand of the form `(rel: <number>, to: <anchor>)`
|
||||
/// 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)[
|
||||
|
||||
+268
-167
@@ -1,9 +1,16 @@
|
||||
#import "/src/cetz.typ": draw, coordinate
|
||||
#import "util.typ": opposite-anchor, normalize-coord, resolve-rel-coord
|
||||
#import "util.typ": opposite-anchor, normalize-coord
|
||||
|
||||
/// List of valid wire styles
|
||||
/// #examples.wires
|
||||
#let wire-styles = ("direct", "zigzag", "dodge", "guided")
|
||||
///
|
||||
/// The `"u-shape"` style connects two ports that face the same cardinal side.
|
||||
/// #examples.u-shape-wire
|
||||
///
|
||||
/// The `"u-shape"` crossbar can also be aligned to an anchor via `u-at`
|
||||
/// (analogous to `zigzag-at`).
|
||||
/// #examples.u-at
|
||||
#let wire-styles = ("direct", "zigzag", "dodge", "guided", "u-shape")
|
||||
#let signal-width = 1pt
|
||||
#let bus-width = 1.5pt
|
||||
|
||||
@@ -24,29 +31,95 @@
|
||||
return (pts, anchors)
|
||||
}
|
||||
|
||||
#let get-zigzag-wire(pts, ratio, dir) = {
|
||||
// Normalize an anchor-based placement value used by `zigzag-at`, `u-at`, and
|
||||
// the `s-at-*` family.
|
||||
//
|
||||
// `axis` selects which cartesian component the offset applies to:
|
||||
// - `"x"` → offset in x (used when the placement fixes an x coordinate, e.g.
|
||||
// a vertical bend line for horizontal wire flow).
|
||||
// - `"y"` → offset in y (used when the placement fixes a y coordinate).
|
||||
//
|
||||
// Accepted forms:
|
||||
// - `none` → unset; caller falls back to
|
||||
// depth/ratio-based placement.
|
||||
// - `str` / tuple / native CeTZ dict → passed through as a CeTZ coordinate.
|
||||
// - `(rel: <length>, to: <coord>)` with a *scalar* `rel`
|
||||
// → ergonomic axial offset, rewritten
|
||||
// to `(rel: (rel, 0), to: to)` for
|
||||
// `axis == "x"` or
|
||||
// `(rel: (0, rel), to: to)` for
|
||||
// `axis == "y"`. If `rel` is already
|
||||
// an array/tuple, the value is
|
||||
// passed through unchanged.
|
||||
#let normalize-scalar-offset(at, axis) = {
|
||||
if at == none { return none }
|
||||
if type(at) == dictionary and "rel" in at and "to" in at and type(at.rel) != array {
|
||||
let r = at.rel
|
||||
if axis == "x" {
|
||||
return (rel: (r, 0), to: at.to)
|
||||
} else {
|
||||
return (rel: (0, r), to: at.to)
|
||||
}
|
||||
}
|
||||
return at
|
||||
}
|
||||
|
||||
// Backwards-compatible wrapper: translates the old `dir` vocabulary
|
||||
// (`"vertical"` / `"horizontal"`) to the `axis` vocabulary used by
|
||||
// `normalize-scalar-offset`.
|
||||
#let normalize-zigzag-at(at, dir) = {
|
||||
let axis = if dir == "vertical" { "x" } else { "y" }
|
||||
return normalize-scalar-offset(at, axis)
|
||||
}
|
||||
|
||||
#let get-zigzag-wire(pts, ratio, dir, at: none) = {
|
||||
let start = pts.first()
|
||||
let end = pts.last()
|
||||
let mid = if dir == "vertical" {
|
||||
(start, ratio, (horizontal: end, vertical: ()))
|
||||
} else {
|
||||
(start, ratio, (horizontal: (), vertical: end))
|
||||
}
|
||||
|
||||
let points = if dir == "vertical" {
|
||||
(
|
||||
start,
|
||||
(horizontal: mid, vertical: ()),
|
||||
(horizontal: (), vertical: end),
|
||||
end
|
||||
)
|
||||
let points
|
||||
if at == none {
|
||||
// Ratio-based placement (default).
|
||||
let mid = if dir == "vertical" {
|
||||
(start, ratio, (horizontal: end, vertical: ()))
|
||||
} else {
|
||||
(start, ratio, (horizontal: (), vertical: end))
|
||||
}
|
||||
|
||||
points = if dir == "vertical" {
|
||||
(
|
||||
start,
|
||||
(horizontal: mid, vertical: ()),
|
||||
(horizontal: (), vertical: end),
|
||||
end
|
||||
)
|
||||
} else {
|
||||
(
|
||||
start,
|
||||
(horizontal: (), vertical: mid),
|
||||
(horizontal: end, vertical: ()),
|
||||
end
|
||||
)
|
||||
}
|
||||
} else {
|
||||
(
|
||||
start,
|
||||
(horizontal: (), vertical: mid),
|
||||
(horizontal: end, vertical: ()),
|
||||
end
|
||||
)
|
||||
// Anchor-based placement: the middle bend segment sits on the axis
|
||||
// extracted from `at` via CeTZ's perpendicular operator `|-`:
|
||||
// - vertical: middle segment at x = at.x
|
||||
// - horizontal: middle segment at y = at.y
|
||||
points = if dir == "vertical" {
|
||||
(
|
||||
start,
|
||||
(at, "|-", start),
|
||||
(at, "|-", end),
|
||||
end
|
||||
)
|
||||
} else {
|
||||
(
|
||||
start,
|
||||
(start, "|-", at),
|
||||
(end, "|-", at),
|
||||
end
|
||||
)
|
||||
}
|
||||
}
|
||||
let anchors = (
|
||||
"start": start,
|
||||
@@ -109,6 +182,111 @@
|
||||
return (points, anchors)
|
||||
}
|
||||
|
||||
#let _u-valid-sides = ("east", "west", "north", "south")
|
||||
|
||||
// Try to deduce the cardinal side a port anchor faces by comparing its
|
||||
// position to the center of the element it belongs to. Returns none if the
|
||||
// anchor is not a port-style anchor (i.e. does not contain "-port-") or if
|
||||
// resolution fails.
|
||||
#let detect-port-side(anchor, ctx) = {
|
||||
if type(anchor) != str { return none }
|
||||
let sep = "-port-"
|
||||
if not anchor.contains(sep) { return none }
|
||||
let elmt-id = anchor.split(sep).first()
|
||||
let port-pos = none
|
||||
let center-pos = none
|
||||
// coordinate.resolve panics on unknown anchors; there is no try/catch in
|
||||
// Typst, so we assume port anchors created by add-port always come with a
|
||||
// matching group of the same name. If not, the user must set u-side
|
||||
// explicitly.
|
||||
let (_, p1) = coordinate.resolve(ctx, anchor)
|
||||
let (_, p2) = coordinate.resolve(ctx, elmt-id + ".center")
|
||||
port-pos = p1
|
||||
center-pos = p2
|
||||
let dx = port-pos.at(0) - center-pos.at(0)
|
||||
let dy = port-pos.at(1) - center-pos.at(1)
|
||||
if calc.abs(dx) >= calc.abs(dy) {
|
||||
return if dx >= 0 { "east" } else { "west" }
|
||||
} else {
|
||||
return if dy >= 0 { "north" } else { "south" }
|
||||
}
|
||||
}
|
||||
|
||||
#let get-u-shape-wire(pts, u-depth, u-side, u-at, ctx) = {
|
||||
let start = pts.first()
|
||||
let end = pts.last()
|
||||
|
||||
// Resolve the (auto|explicit) side
|
||||
let side = u-side
|
||||
if side == auto {
|
||||
let side-start = detect-port-side(start, ctx)
|
||||
let side-end = detect-port-side(end, ctx)
|
||||
if side-start != none and side-end != none and side-start != side-end {
|
||||
panic(
|
||||
"u-shape: start port faces '" + side-start
|
||||
+ "' but end port faces '" + side-end
|
||||
+ "'; both must face the same side, or set u-side explicitly"
|
||||
)
|
||||
}
|
||||
if side-start != none { side = side-start }
|
||||
else if side-end != none { side = side-end }
|
||||
else {
|
||||
panic(
|
||||
"u-shape: could not auto-detect u-side (endpoints are not port anchors); "
|
||||
+ "please set u-side explicitly to one of " + repr(_u-valid-sides)
|
||||
)
|
||||
}
|
||||
}
|
||||
if not side in _u-valid-sides {
|
||||
panic("u-shape: invalid u-side '" + repr(side) + "'; must be one of " + repr(_u-valid-sides))
|
||||
}
|
||||
|
||||
let (ctx, s) = coordinate.resolve(ctx, start)
|
||||
let (ctx, e) = coordinate.resolve(ctx, end)
|
||||
let (sx, sy, ..) = s
|
||||
let (ex, ey, ..) = e
|
||||
|
||||
// If `u-at` is set, resolve its coordinate and extract the relevant axis
|
||||
// component; use it as the crossbar position instead of computing from
|
||||
// `u-depth`.
|
||||
let bar-at = none
|
||||
if u-at != none {
|
||||
let axis = if side == "east" or side == "west" { "x" } else { "y" }
|
||||
let at-norm = normalize-scalar-offset(u-at, axis)
|
||||
let (ctx2, r) = coordinate.resolve(ctx, at-norm)
|
||||
bar-at = if axis == "x" { r.first() } else { r.at(1) }
|
||||
}
|
||||
|
||||
let p1
|
||||
let p2
|
||||
if side == "east" {
|
||||
let bx = if bar-at != none { bar-at } else { calc.max(sx, ex) + u-depth }
|
||||
p1 = (bx, sy, 0)
|
||||
p2 = (bx, ey, 0)
|
||||
} else if side == "west" {
|
||||
let bx = if bar-at != none { bar-at } else { calc.min(sx, ex) - u-depth }
|
||||
p1 = (bx, sy, 0)
|
||||
p2 = (bx, ey, 0)
|
||||
} else if side == "north" {
|
||||
let by = if bar-at != none { bar-at } else { calc.max(sy, ey) + u-depth }
|
||||
p1 = (sx, by, 0)
|
||||
p2 = (ex, by, 0)
|
||||
} else if side == "south" {
|
||||
let by = if bar-at != none { bar-at } else { calc.min(sy, ey) - u-depth }
|
||||
p1 = (sx, by, 0)
|
||||
p2 = (ex, by, 0)
|
||||
}
|
||||
|
||||
let points = (start, p1, p2, end)
|
||||
let anchors = (
|
||||
"start": start,
|
||||
"u-start": p1,
|
||||
"u-end": p2,
|
||||
"end": end
|
||||
)
|
||||
return (points, anchors)
|
||||
}
|
||||
|
||||
#let get-guided-wire(pts, margins, sides, center-guides, ctx) = {
|
||||
let start = pts.first()
|
||||
let end = pts.last()
|
||||
@@ -204,14 +382,18 @@
|
||||
/// - reverse (bool): If true, the start and end points will be swapped (useful in cases where the start point depends on the end point, for example with perpendiculars)
|
||||
/// - directed (bool): If true, the wire will be directed, meaning an arrow will be drawn at the endpoint
|
||||
/// - rotate-name (bool): If true, the name will be rotated according to the wire's slope
|
||||
/// - zigzag-ratio (ratio): Position of the zigzag vertical relative to the horizontal span (only with style "zigzag")
|
||||
/// - zigzag-dir (str): The zigzag's direction. As either "vertical" or "horizontal" (only with dstyle "zigzag")
|
||||
/// - zigzag-ratio (auto, ratio): Position of the zigzag middle bend segment relative to the horizontal (or vertical) span, as a fraction between the two endpoints (only with style "zigzag"). Defaults to `auto`, which resolves to `50%`. Panics if combined with an explicitly set `zigzag-at`.
|
||||
/// - zigzag-dir (auto, str): The zigzag's direction, as either "vertical" or "horizontal" (only with style "zigzag"). If `auto`, it is deduced from the endpoints when they are port anchors of the form `<elmt-id>-port-<port-id>` by comparing each port's position with its element's `.center` anchor: east/west-facing ports yield `"vertical"` (wire exits horizontally) and north/south-facing ports yield `"horizontal"`. If the direction cannot be deduced (no port anchor on either endpoint, or conflicting directions), it falls back to `"vertical"`.
|
||||
/// - zigzag-at (none, str, array, dictionary): Optional. Anchor-based placement of the zigzag middle bend segment (only with style "zigzag"). When set, overrides `zigzag-ratio` (which must be left at its default `auto`). The middle segment sits on the axis extracted from the given coordinate: `x` for `zigzag-dir: "vertical"`, `y` for `"horizontal"`. Accepted forms: a raw CeTZ anchor name string (e.g. `"elmt.center"` or `"elmt-port-a"`); any native CeTZ coordinate (tuple, dict such as `(rel: (dx, dy), to: anchor)`); or the ergonomic scalar-offset form `(rel: <length>, to: <coord>)`, which is rewritten to a `(dx, 0)` offset for `"vertical"` and `(0, dy)` for `"horizontal"`. See #examples.zigzag-at
|
||||
/// - dodge-y (number): Y position to dodge the wire to (only with style "dodge")
|
||||
/// - dodge-sides (array): The start and end sides (going out of the connected element) of the wire (only with style "dodge")
|
||||
/// - dodge-margins (array): The start and end margins (i.e. space before dodging) of the wire (only with style "dodge")
|
||||
/// - guided-center (array): the horizontal and vertical guides of the center guides (only with style "guided")
|
||||
/// - guided-margins (array): the start and end of guided margins of the wire (only with style "guided")
|
||||
/// - guided-sides (array): the side of start and end array (must be either "north", "south", "west", "east") (only work with style "guided")
|
||||
/// - u-side (auto, str): The cardinal side both endpoints face (one of "east", "west", "north", "south"). If `auto`, it is deduced from the endpoints when they are port anchors of the form `<elmt-id>-port-<port-id>` by comparing each port's position with its element's `.center` anchor. Auto-detection panics if the two endpoints resolve to different sides, or if neither endpoint is a port anchor (only with style "u-shape")
|
||||
/// - u-depth (auto, number): Distance between the outermost endpoint (in the `u-side` direction) and the U-bend line (only with style "u-shape"). Defaults to `auto`, which resolves to `0.5`. Panics if combined with an explicitly set `u-at`.
|
||||
/// - u-at (none, str, array, dictionary): Optional. Anchor-based placement of the U-bend crossbar (only with style "u-shape"). When set, overrides `u-depth` (which must be left at its default `auto`). The crossbar sits on the axis extracted from the given coordinate: `x` for `u-side` in `("east", "west")`, `y` for `("north", "south")`. Accepted forms mirror `zigzag-at`: raw CeTZ anchor name string, any native CeTZ coordinate, or the ergonomic scalar-offset form `(rel: <length>, to: <coord>)`, which is rewritten to `(dx, 0)` for east/west and `(0, dy)` for north/south.
|
||||
#let wire(
|
||||
id, pts,
|
||||
bus: false,
|
||||
@@ -227,11 +409,15 @@
|
||||
guided-sides: ("east", "west"),
|
||||
directed: false,
|
||||
rotate-name: true,
|
||||
zigzag-ratio: 50%,
|
||||
zigzag-dir: "vertical",
|
||||
zigzag-ratio: auto,
|
||||
zigzag-dir: auto,
|
||||
zigzag-at: none,
|
||||
dodge-y: 0,
|
||||
dodge-sides: ("east", "west"),
|
||||
dodge-margins: (5%, 5%)
|
||||
dodge-margins: (5%, 5%),
|
||||
u-side: auto,
|
||||
u-depth: auto,
|
||||
u-at: none,
|
||||
) = draw.get-ctx(ctx => {
|
||||
if not style in wire-styles {
|
||||
panic("Invalid wire style '" + style + "'")
|
||||
@@ -241,6 +427,32 @@
|
||||
panic("Wrong number of points (got " + str(pts.len()) + " instead of 2)")
|
||||
}
|
||||
|
||||
// `zigzag-at` validation.
|
||||
if zigzag-at != none {
|
||||
if style != "zigzag" {
|
||||
panic("wire '" + id + "': `zigzag-at` requires style: \"zigzag\" (got \""
|
||||
+ style + "\")")
|
||||
}
|
||||
if zigzag-ratio != auto {
|
||||
panic("wire '" + id
|
||||
+ "': cannot combine `zigzag-at` with an explicit `zigzag-ratio`")
|
||||
}
|
||||
}
|
||||
let zigzag-ratio-effective = if zigzag-ratio == auto { 50% } else { zigzag-ratio }
|
||||
|
||||
// `u-at` validation.
|
||||
if u-at != none {
|
||||
if style != "u-shape" {
|
||||
panic("wire '" + id + "': `u-at` requires style: \"u-shape\" (got \""
|
||||
+ style + "\")")
|
||||
}
|
||||
if u-depth != auto {
|
||||
panic("wire '" + id
|
||||
+ "': cannot combine `u-at` with an explicit `u-depth`")
|
||||
}
|
||||
}
|
||||
let u-depth-effective = if u-depth == auto { 0.5 } else { u-depth }
|
||||
|
||||
// Normalize user-provided coordinates: (x, y) -> (x, y, 0)
|
||||
let pts = pts.map(normalize-coord)
|
||||
|
||||
@@ -259,7 +471,26 @@
|
||||
(points, anchors) = get-direct-wire(pts)
|
||||
|
||||
} else if style == "zigzag" {
|
||||
(points, anchors) = get-zigzag-wire(pts, zigzag-ratio, zigzag-dir)
|
||||
let zz-dir = zigzag-dir
|
||||
if zz-dir == auto {
|
||||
let side-start = detect-port-side(pts.first(), ctx)
|
||||
let side-end = detect-port-side(pts.last(), ctx)
|
||||
let dir-from-side(s) = if s == none { none } else if s == "east" or s == "west" { "vertical" } else { "horizontal" }
|
||||
let d-start = dir-from-side(side-start)
|
||||
let d-end = dir-from-side(side-end)
|
||||
// Fall back to "vertical" (the previous default) when the direction
|
||||
// cannot be deduced (no port anchors, or conflicting endpoints).
|
||||
if d-start != none and d-end != none and d-start != d-end { zz-dir = "vertical" }
|
||||
else if d-start != none { zz-dir = d-start }
|
||||
else if d-end != none { zz-dir = d-end }
|
||||
else { zz-dir = "vertical" }
|
||||
}
|
||||
(points, anchors) = get-zigzag-wire(
|
||||
pts,
|
||||
zigzag-ratio-effective,
|
||||
zz-dir,
|
||||
at: normalize-zigzag-at(zigzag-at, zz-dir),
|
||||
)
|
||||
|
||||
} else if style == "dodge" {
|
||||
(points, anchors) = get-dodge-wire(
|
||||
@@ -277,9 +508,17 @@
|
||||
guided-center,
|
||||
ctx
|
||||
)
|
||||
}
|
||||
} else if style == "u-shape" {
|
||||
(points, anchors) = get-u-shape-wire(
|
||||
pts,
|
||||
u-depth-effective,
|
||||
u-side,
|
||||
u-at,
|
||||
ctx
|
||||
)
|
||||
}
|
||||
|
||||
let mark = (fill: color)
|
||||
let mark = (fill: color)
|
||||
if directed {
|
||||
mark = (end: ">", fill: color)
|
||||
}
|
||||
@@ -403,141 +642,3 @@
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 `<id>-port-<id>` so wires can attach
|
||||
/// to it as if it were a block port. A synthesized `<id>.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))
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user