Compare commits

..
Author SHA1 Message Date
HEL b7dd2cb718 Merge branch 'dev' into comp/electrical 2025-03-31 13:05:43 +02:00
HEL ac3e577020 fixed gallery.bash substitution 2024-11-10 01:46:25 +01:00
HEL ba0f138ef2 added gallery example for resistors/capacitors 2024-11-10 01:43:03 +01:00
HEL 60898d1e60 updated manual 2024-11-10 01:40:50 +01:00
HEL b8a7adb58f fixed wrong capacitor symbol
(no comment...)
2024-11-10 01:40:32 +01:00
HEL cc8478a3c7 added resistor and capacitor 2024-11-09 23:05:09 +01:00
66 changed files with 431 additions and 1876 deletions

No files matched your search

+7 -7
View File
@@ -3,7 +3,7 @@
Circuiteria is a [Typst](https://typst.app) package for drawing block circuit diagrams using the [CeTZ](https://typst.app/universe/package/cetz) package.
<p align="center">
<img src="./gallery/platypus.png" alt="A platypus / Perry the Platypus meme made with colored rounded rectangles">
<img src="./gallery/platypus.png" alt="Perry the platypus">
</p>
## Examples
@@ -11,17 +11,17 @@ Circuiteria is a [Typst](https://typst.app) package for drawing block circuit di
<tr>
<td colspan="2">
<a href="./gallery/test.typ">
<img src="./gallery/test.png" width="500px" alt="Block diagram of a multi-cycle RISC-V FPGA processor">
<img src="./gallery/test.png" width="500px">
</a>
</td>
</tr>
<tr>
<td colspan="2">A bit of everything</td>
<td colspan="2">A bit of eveything</td>
</tr>
<tr>
<td colspan="2">
<a href="./gallery/test5.typ">
<img src="./gallery/test5.png" width="500px" alt="Block diagram of a single-cycle RISC-V processor">
<img src="./gallery/test5.png" width="500px">
</a>
</td>
</tr>
@@ -31,12 +31,12 @@ Circuiteria is a [Typst](https://typst.app) package for drawing block circuit di
<tr>
<td>
<a href="./gallery/test4.typ">
<img src="./gallery/test4.png" width="250px" alt="Top-level block diagram of a single-cycle RISC-V processor">
<img src="./gallery/test4.png" width="250px">
</a>
</td>
<td>
<a href="./gallery/test6.typ">
<img src="./gallery/test6.png" width="250px" alt="Block diagram of a simple single-cycle RISC-V ALU">
<img src="./gallery/test6.png" width="250px">
</a>
</td>
</tr>
@@ -56,7 +56,7 @@ For more information, see the [manual](manual.pdf)
To use this package, simply import [circuiteria](https://typst.app/universe/package/circuiteria) and call the `circuit` function:
```typ
#import "@preview/circuiteria:0.2.1"
#import "@preview/circuiteria:0.2.0"
#circuiteria.circuit({
import circuiteria: *
...
+1 -1
View File
@@ -1,4 +1,4 @@
#import "/src/cetz.typ": draw
#import "@preview/cetz:0.3.2": draw
#import "../src/circuit.typ": circuit
#import "../src/util.typ"
+16 -249
View File
@@ -56,7 +56,7 @@ for i in range(3) {
```)
#let wires = example(```
for i in range(5) {
for i in range(3) {
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)))
@@ -65,12 +65,6 @@ 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(```
@@ -81,81 +75,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")
@@ -196,46 +115,6 @@ 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"),
@@ -263,94 +142,6 @@ 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 intersection = example(```
wire.wire("w1", ((0, 0), (1, 1)), style: "zigzag")
wire.wire("w2", ((0, 0), (1, -.5)),
@@ -358,44 +149,20 @@ wire.wire("w2", ((0, 0), (1, -.5)),
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)
#let capacitor = example(```
electrical.capacitor(
x: 0, y: 0, w: 2, h: 1, id: "a",
scales: (100%, 80%), gap: 0.3
)
// 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)
electrical.capacitor(
x: 4, y: -0.5, w: 1, h: 2, id: "b",
vertical: true, symbols: ([+], none)
)
```, vertical: true)
#let resistor = example(```
electrical.resistor(x: 0, y: 0, w: 2, h: 0.5, id: "a", zigzags: 8)
electrical.resistor(x: 4, y: -0.5, w: 0.5, h: 2, id: "b", vertical: true)
electrical.resistor(x: 6.5, y: 0, w: 2, h: 0.5, id: "c", zigzags: none)
```, vertical: true)
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 45 KiB

+1 -1
View File
@@ -1,4 +1,4 @@
#import "@preview/circuiteria:0.2.1": *
#import "../src/lib.typ": *
#set page(width: auto, height: auto, margin: .5cm)
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 142 KiB

+1 -1
View File
@@ -1,4 +1,4 @@
#import "@preview/circuiteria:0.2.1": circuit, element, util, wire
#import "../src/lib.typ": circuit, element, util, wire
#set page(width: auto, height: auto, margin: .5cm)
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 142 KiB

+1 -1
View File
@@ -1,4 +1,4 @@
#import "@preview/circuiteria:0.2.1": circuit, element, util, wire
#import "../src/lib.typ": circuit, element, util, wire
#set page(width: auto, height: auto, margin: .5cm)
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 81 KiB

+55 -1
View File
@@ -1,4 +1,5 @@
#import "@preview/circuiteria:0.2.1": circuit, element, util, wire
#import "@preview/cetz:0.3.2": draw
#import "../src/lib.typ": circuit, element, util, wire
#set page(width: auto, height: auto, margin: .5cm)
@@ -80,4 +81,57 @@
element.gate-xnor(
x: 9, y: -6, w: 2, h: 2, id: "xnor"
)
element.resistor(
x: 0, y: -8, w: 2, h: 0.5, id: "res1"
)
element.capacitor(
x: 3, y: (from: "res1-port-1", to: "0"),
w: 2, h: 0.6,
id: "cap1",
scales: (100%, 80%),
symbols: ([+], [-])
)
element.resistor(
x: (rel: 1, to: "cap1-port-1"),
y: (from: "cap1-port-1", to: "0"),
w: 0.5, h: 2,
id: "res2",
vertical: true,
zigzags: 8
)
element.capacitor(
x: (rel: 1, to: "res2.east"),
y: (from: "res2-port-1", to: "1"),
w: 0.5, h: 2,
id: "cap2",
vertical: true,
symbols: ([a], [b])
)
element.resistor(
x: (rel: 1, to: "cap2-port-0"),
y: (from: "cap2-port-0", to: "0"),
w: 2, h: 0.5,
id: "res3",
zigzags: none
)
element.resistor(
x: (rel: 1, to: "res3-port-1"),
y: (from: "res3-port-1", to: "0"),
w: 0.5, h: 2,
id: "res4",
zigzags: none,
vertical: true
)
wire.wire("w4", ("res1-port-1", "cap1-port-0"))
wire.wire("w5", ("cap1-port-1", "res2-port-0"))
wire.wire("w6", ("res2-port-1", "cap2-port-1"))
wire.wire("w7", ("cap2-port-0", "res3-port-0"))
wire.wire("w8", ("res3-port-1", "res4-port-0"))
})
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 159 KiB

+2 -2
View File
@@ -1,5 +1,5 @@
#import "/src/cetz.typ": draw
#import "@preview/circuiteria:0.2.1": *
#import "@preview/cetz:0.3.2": draw
#import "../src/lib.typ": *
#set page(width: auto, height: auto, margin: .5cm)
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 275 KiB

+2 -2
View File
@@ -1,5 +1,5 @@
#import "/src/cetz.typ": draw
#import "@preview/circuiteria:0.2.1": *
#import "@preview/cetz:0.3.2": draw
#import "../src/lib.typ": *
#set page(width: auto, height: auto, margin: .5cm)
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 76 KiB

+2 -2
View File
@@ -1,5 +1,5 @@
#import "/src/cetz.typ": draw
#import "@preview/circuiteria:0.2.1": *
#import "@preview/cetz:0.3.2": draw, vector
#import "../src/lib.typ": *
#set page(width: auto, height: auto, margin: .5cm)
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

-97
View File
@@ -1,97 +0,0 @@
#import "@preview/circuiteria:0.2.1": circuit, element, util, wire
#set page(width: auto, height: auto, margin: .5cm)
#circuit({
element.iec-gate-buf(
x: 0,
y: 0,
w: 2,
h: 2,
id: "iec-buf",
inputs: 1,
)
wire.stub("iec-buf-port-in0", "west")
element.iec-gate-not(
x: 3,
y: 0,
w: 2,
h: 2,
id: "iec-not",
inputs: 1,
)
wire.stub("iec-not-port-in0", "west")
element.iec-gate-and(
id: "iec-and",
x: 0,
y: -3,
w: 2,
h: 2,
inputs: 2,
)
for i in range(2) {
wire.stub("iec-and-port-in" + str(i), "west")
}
element.iec-gate-nand(
id: "iec-nand",
x: 3,
y: -3,
w: 2,
h: 2,
inputs: 2,
)
for i in range(2) {
wire.stub("iec-nand-port-in" + str(i), "west")
}
element.iec-gate-or(
id: "iec-or",
x: 0,
y: -6,
w: 2,
h: 2,
inputs: 2,
)
for i in range(2) {
wire.stub("iec-or-port-in" + str(i), "west")
}
element.iec-gate-nor(
id: "iec-nor",
x: 3,
y: -6,
w: 2,
h: 2,
inputs: 2,
)
for i in range(2) {
wire.stub("iec-nor-port-in" + str(i), "west")
}
element.iec-gate-xor(
id: "iec-xor",
x: 0,
y: -9,
w: 2,
h: 2,
inputs: 2,
)
for i in range(2) {
wire.stub("iec-xor-port-in" + str(i), "west")
}
element.iec-gate-xnor(
id: "iec-nxor",
x: 3,
y: -9,
w: 2,
h: 2,
inputs: 2,
)
for i in range(2) {
wire.stub("iec-nxor-port-in" + str(i), "west")
}
})
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

-117
View File
@@ -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.

Before

Width:  |  Height:  |  Size: 23 KiB

-86
View File
@@ -1,86 +0,0 @@
// Demonstrates ports on a group element.
// outside-port-names: true (default) — labels outside the group border
// outside-port-names: false — labels inside (block-style)
#import "/src/lib.typ": circuit, element, util, wire
#set page(width: auto, height: auto, margin: .5cm)
#circuit({
// --- Group with port labels INSIDE (outside-port-names: false) ---
element.group(
id: "cpu",
name: "CPU (labels outside)",
name-anchor: "north",
stroke: (dash: "dashed"),
fill: util.colors.yellow.lighten(60%),
padding: 1em,
port-name-padding: .5em,
outside-port-names: true,
ports: (
west: (
(id: "in1", name: "D1"),
(id: "in2", name: "D2"),
),
east: ((id: "out", name: "Q"),),
south: (
(id: "rst", name: "RST", vertical: true),
(id: "done", name: "DONE", vertical: true),
),
),
{
element.block(
id: "reg2",
w: 2.5,
h: 3,
x: 0,
y: 0,
fill: util.colors.green,
ports: (
west: ((id: "d1"), (id: "d2")),
east: ((id: "q"),),
north: ((id: "clk", clock: true),),
),
)
},
)
// --- Group with port labels INSIDE (outside-port-names: false) ---
element.group(
id: "cpu2",
name: "CPU (labels inside)",
name-anchor: "north",
stroke: (dash: "dashed"),
fill: util.colors.yellow.lighten(60%),
padding: 4em,
outside-port-names: false,
port-name-padding: 0.5em,
ports: (
west: (
(id: "in1", name: "D1"),
(id: "in2", name: "D2"),
),
east: ((id: "out", name: "Q"),),
south: (
(id: "rst", name: "RST", vertical: true),
(id: "done", name: "DONE", vertical: true),
),
),
{
element.block(
id: "reg2",
w: 2.5,
h: 3,
x: 10,
y: 0,
fill: util.colors.green,
ports: (
west: ((id: "d1"), (id: "d2")),
east: ((id: "q"),),
north: ((id: "clk", clock: true),),
),
)
},
)
wire.stub("cpu2-port-in1", "west", name: "A")
wire.stub("cpu2-port-in2", "west", name: "B")
wire.stub("cpu2-port-out", "east", name: "Q")
})
Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

-117
View File
@@ -1,117 +0,0 @@
// Demonstrates pentagon-shaped ports.
//
// - Element-level defaults: `port-shape`, `port-kind`, `port-size`.
// - Per-port overrides: `shape`, `kind`, `size`, `stroke`, `fill`.
// - Wire endpoints connect at the outer end of each pentagon.
// - Clock ports with pentagon shape drop the clock triangle and default
// to a gray fill.
#import "/src/lib.typ": circuit, element, util, wire
#set page(width: auto, height: auto, margin: .5cm)
#circuit({
// -----------------------------------------------------------------
// 1) All four sides × input/output
// -----------------------------------------------------------------
element.block(
id: "grid", w: 4, h: 4, x: 0, y: 0,
name: "sides × in/out", name-anchor: "center",
fill: util.colors.yellow.lighten(60%),
port-shape: "pentagon",
ports: (
west: (
(id: "wi", name: "W-in"),
(id: "wo", name: "W-out", kind: "out"),
),
east: (
(id: "ei", name: "E-in"),
(id: "eo", name: "E-out", kind: "out"),
),
north: (
(id: "ni", name: "N-in", vertical: true),
(id: "no", name: "N-out", kind: "out", vertical: true),
),
south: (
(id: "si", name: "S-in", vertical: true),
(id: "so", name: "S-out", kind: "out", vertical: true),
),
),
)
// -----------------------------------------------------------------
// 2) Per-port styling: fill and stroke overrides + size override
// -----------------------------------------------------------------
element.block(
id: "styled", w: 3, h: 3, x: 10, y: 0,
name: "per-port styling", name-anchor: "center",
fill: util.colors.green.lighten(40%),
port-shape: "pentagon",
ports: (
west: ((id: "a", name: "A", fill: util.colors.blue),),
east: ((id: "b", name: "B", kind: "out",
stroke: red + 1.5pt, fill: util.colors.pink),),
north: ((id: "c", name: "C", size: 1.5em),),
south: ((id: "d", name: "D", kind: "out",
fill: util.colors.orange),),
),
)
// -----------------------------------------------------------------
// 3) Clock port with pentagon → gray fill, no triangle
// -----------------------------------------------------------------
element.block(
id: "clk", w: 3, h: 2.5, x: 16, y: 0,
name: "clock + pentagon", name-anchor: "center",
fill: util.colors.orange.lighten(60%),
port-shape: "pentagon",
ports: (
west: ((id: "d", name: "D"),),
east: ((id: "q", name: "Q", kind: "out"),),
north: ((id: "clk", name: "CLK", clock: true),),
),
)
// -----------------------------------------------------------------
// 4) Two pentagon-ported blocks connected by a wire
// (wire terminates at outer edge of each pentagon)
// -----------------------------------------------------------------
element.block(
id: "src", w: 2, h: 1.5, x: 0, y: -5,
name: "src", name-anchor: "center",
fill: util.colors.pink,
port-shape: "pentagon",
ports: (east: ((id: "q", name: "Q", kind: "out"),)),
)
element.block(
id: "dst", w: 2, h: 1.5, x: 6, y: -5,
name: "dst", name-anchor: "center",
fill: util.colors.blue,
port-shape: "pentagon",
ports: (west: ((id: "d", name: "D"),)),
)
wire.wire("w-sd", ("src-port-q", "dst-port-d"))
// -----------------------------------------------------------------
// 5) Group with pentagon ports enclosing a child block
// -----------------------------------------------------------------
element.group(
id: "g", name: "group w/ pentagon ports", name-anchor: "north",
stroke: (dash: "dashed"),
fill: util.colors.purple.lighten(70%),
padding: 2em,
port-shape: "pentagon",
ports: (
west: ((id: "in", name: "IN"),),
east: ((id: "out", name: "OUT", kind: "out"),),
),
{
element.block(
id: "inner", w: 2, h: 1.5, x: 12, y: -5,
fill: util.colors.green,
ports: (west: ((id: "d"),), east: ((id: "q"),)),
)
},
)
wire.wire("wgi", ("g-port-in", "inner-port-d"))
wire.wire("wgo", ("inner-port-q", "g-port-out"))
})
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

-53
View File
@@ -1,53 +0,0 @@
// 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

-46
View File
@@ -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")
})
BIN
View File
Binary file not shown.
+26 -10
View File
@@ -1,9 +1,10 @@
#import "@preview/tidy:0.4.3"
#import "/src/cetz.typ": draw, canvas
#import "@preview/tidy:0.4.1"
#import "@preview/cetz:0.3.2": draw, canvas
#import "src/lib.typ"
#import "doc/examples.typ"
#import "src/circuit.typ": circuit
#import "src/element.typ"
#import "src/electrical.typ"
#import "src/gates.typ"
#import "src/util.typ"
#import "src/wire.typ"
@@ -105,7 +106,7 @@ This package provides a way to make beautiful block circuit diagrams using the C
Simply import Circuiteria and call the `circuit` function:
#pad(left: 1em)[```typ
#import "@preview/circuiteria:0.2.1"
#import "@preview/circuiteria:0.2.0"
#circuiteria.circuit({
import circuiteria: *
...
@@ -195,12 +196,7 @@ If you have installed Circuiteria directly in your project, import #link("src/li
read("src/elements/logic/and.typ") + "\n" +
read("src/elements/logic/buf.typ") + "\n" +
read("src/elements/logic/or.typ") + "\n" +
read("src/elements/logic/xor.typ") + "\n" +
read("src/elements/logic/iec_gate.typ") + "\n" +
read("src/elements/logic/iec_and.typ") + "\n" +
read("src/elements/logic/iec_buf.typ") + "\n" +
read("src/elements/logic/iec_or.typ") + "\n" +
read("src/elements/logic/iec_xor.typ"),
read("src/elements/logic/xor.typ"),
name: "gates",
old-syntax: true,
scope: (
@@ -215,4 +211,24 @@ If you have installed Circuiteria directly in your project, import #link("src/li
)
)
#tidy.show-module(gates-docs, sort-functions: false)
#tidy.show-module(gates-docs, sort-functions: false)
#pagebreak()
#let electrical-docs = tidy.parse-module(
read("src/elements/electrical/capacitor.typ") + "\n" +
read("src/elements/electrical/resistor.typ") + "\n",
name: "electrical",
scope: (
element: element,
circuit: circuit,
electrical: electrical,
draw: draw,
wire: wire,
tidy: tidy,
examples: examples,
doc-ref: doc-ref
)
)
#tidy.show-module(electrical-docs, sort-functions: false)
-1
View File
@@ -1 +0,0 @@
#import "@preview/cetz:0.5.2": *
+4 -3
View File
@@ -1,5 +1,5 @@
#import "/src/cetz.typ": canvas
#import "@preview/tidy:0.4.3"
#import "@preview/cetz:0.3.2": canvas
#import "@preview/tidy:0.3.0"
/// Draws a block circuit diagram
///
@@ -9,5 +9,6 @@
/// - length (length, ratio): Optional base unit
/// -> none
#let circuit(body, length: 2em) = {
set text(font: "Source Sans 3")
canvas(length: length, body)
}
}
+2
View File
@@ -0,0 +1,2 @@
#import "elements/electrical/capacitor.typ": capacitor
#import "elements/electrical/resistor.typ": resistor
+3 -5
View File
@@ -11,10 +11,8 @@
#import "elements/logic/or.typ": gate-or, gate-nor
#import "elements/logic/xor.typ": gate-xor, gate-xnor
#import "elements/logic/buf.typ": gate-buf, gate-not
#import "elements/logic/iec_gate.typ": iec-gate
#import "elements/logic/iec_and.typ": iec-gate-and, iec-gate-nand
#import "elements/logic/iec_buf.typ": iec-gate-buf, iec-gate-not
#import "elements/logic/iec_or.typ": iec-gate-or, iec-gate-nor
#import "elements/logic/iec_xor.typ": iec-gate-xor, iec-gate-xnor
#import "elements/electrical/resistor.typ": resistor
#import "elements/electrical/capacitor.typ": capacitor
#import "elements/group.typ": group
+1 -1
View File
@@ -1,4 +1,4 @@
#import "/src/cetz.typ": draw
#import "@preview/cetz:0.3.2": draw
#import "element.typ"
#import "ports.typ": add-port
+6 -25
View File
@@ -1,9 +1,9 @@
#import "/src/cetz.typ": draw
#import "@preview/cetz:0.3.2": draw
#import "element.typ"
#let draw-shape(id, tl, tr, br, bl, fill, stroke, radius: 0.5em) = {
#let draw-shape(id, tl, tr, br, bl, fill, stroke) = {
let f = draw.rect(
radius: radius,
radius: 0.5em,
inset: 0.5em,
fill: fill,
stroke: stroke,
@@ -16,15 +16,7 @@
/// Draws a block element
///
/// #examples.block
///
/// Ports can be given a pentagon shape (with configurable direction, size,
/// stroke and fill) via `port-shape` and per-port overrides:
/// #examples.port-shape
///
/// Port name labels can be placed outside the block via `port-names-outside`:
/// #examples.port-names-outside
/// For other parameters description, see #doc-ref("element.elmt")
/// - radius (number, length, ratio, dictionary): The corner radius of the block. See CeTZ documentation for more information
/// For parameters description, see #doc-ref("element.elmt")
#let block(
x: none,
y: none,
@@ -34,20 +26,14 @@
name-anchor: "center",
ports: (),
ports-margins: (),
port-name-padding: 2pt,
port-names-outside: false,
port-shape: none,
port-kind: "in",
port-size: 1em,
fill: none,
radius: 0.5em,
stroke: black + 1pt,
id: "",
debug: (
ports: false
)
) = element.elmt(
draw-shape: draw-shape.with(radius: radius),
draw-shape: draw-shape,
x: x,
y: y,
w: w,
@@ -56,13 +42,8 @@
name-anchor: name-anchor,
ports: ports,
ports-margins: ports-margins,
port-name-padding: port-name-padding,
port-names-outside: port-names-outside,
port-shape: port-shape,
port-kind: port-kind,
port-size: port-size,
fill: fill,
stroke: stroke,
id: id,
debug: debug
)
)
+138
View File
@@ -0,0 +1,138 @@
#import "@preview/cetz:0.2.2": draw
#import "../element.typ"
#import "../ports.typ": add-port
#let draw-shape(
id, tl, tr, br, bl,
fill, stroke,
vertical: false,
gap: 0.2,
scales: (100%, 100%),
symbols: (none, none)
) = {
let (x0, y0) = tl
let (x1, y1) = br
let w = x1 - x0
let h = y1 - y0
let (o0, s0) = if vertical {(y0, h)} else {(x0, w)}
let (o1, s1) = if vertical {(x0, w)} else {(y0, h)}
let m1 = o1 + s1 / 2
let pt(i, j) = if vertical {
(j, i)
} else {
(i, j)
}
let size0 = s1 * scales.first() / 100%
let size1 = s1 * scales.last() / 100%
if type(gap) == ratio {
gap = gap / 100%
} else {
gap = gap / calc.abs(s0)
}
let r0 = 0.5 - gap / 2
let r1 = 0.5 + gap / 2
// Coordinates in (main axis, secondary axis) format
let p0 = pt(o0, m1)
let p1 = pt(o0 + r0 * s0, m1)
let p2 = pt(o0 + r1 * s0, m1)
let p3 = pt(o0 + s0, m1)
let p4 = pt(o0 + r0 * s0, m1 - size0 / 2)
let p5 = pt(o0 + r0 * s0, m1 + size0 / 2)
let p6 = pt(o0 + r1 * s0, m1 - size1 / 2)
let p7 = pt(o0 + r1 * s0, m1 + size1 / 2)
let line = draw.line.with(stroke: stroke)
let f = draw.group(name: id, {
line(p0, p1)
line(p2, p3)
line(p4, p5)
line(p6, p7)
if symbols.first() != none {
draw.content(
p1,
symbols.first(),
anchor: if vertical {"south-west"} else {"south-east"},
padding: 2pt
)
}
if symbols.last() != none {
draw.content(
p2,
symbols.last(),
anchor: if vertical {"north-west"} else {"south-west"},
padding: 2pt
)
}
})
return (f, tl, tr, br, bl)
}
/// Draws a capacitor
///
/// #examples.capacitor
/// For other parameters description, see #doc-ref("element.elmt")
/// - vertical (bool): Whether the element is vertical or horizontal.
/// - If false, port 0 is placed on the west side and port 1 on the east.\
/// - If true, they are on the north, respectively the south sides
/// - gap (number, ratio): The gap between both sides
/// - if it is a number (int or float), it is interpreted as an absolute canvas-unit length
/// - if it is a ratio, it is interpreted as proportional to the capacitor's length (width if horizontal, height if vertical)
/// - scales (array): A pair of ratios, the sizes of the sides relative to the capacitor's height (width if vertical).
/// - symbols (array): A pair of content or strings (or none values) to attach on the sides of the capacitor
#let capacitor(
x: none,
y: none,
w: none,
h: none,
name: none,
name-anchor: "center",
vertical: false,
gap: 0.2,
scales: (100%, 100%),
symbols: (none, none),
fill: none,
stroke: black + 1pt,
id: "",
debug: (
ports: false
)
) = {
let ports = if vertical {(
north: ((id: "0"),),
south: ((id: "1"),)
)} else {(
west: ((id: "0"),),
east: ((id: "1"),)
)}
element.elmt(
draw-shape: draw-shape.with(
vertical: vertical,
gap: gap,
scales: scales,
symbols: symbols
),
x: x,
y: y,
w: w,
h: h,
name: name,
name-anchor: name-anchor,
ports: ports,
fill: fill,
stroke: stroke,
id: id,
debug: debug
)
}
+106
View File
@@ -0,0 +1,106 @@
#import "@preview/cetz:0.2.2": draw
#import "../element.typ"
#import "../ports.typ": add-port
#let draw-shape(
id, tl, tr, br, bl,
fill, stroke,
zigzags: 6,
vertical: false
) = {
let (x0, y0) = tl
let (x1, y1) = br
let w = x1 - x0
let h = y1 - y0
let (o0, s0) = if vertical {(y0, h)} else {(x0, w)}
let (o1, s1) = if vertical {(x0, w)} else {(y0, h)}
let m1 = o1 + s1 / 2
let pt(i, j) = if vertical {
(j, i)
} else {
(i, j)
}
let p0 = pt(o0, m1)
let p1 = pt(o0 + 0.2 * s0, m1)
let p2 = pt(o0 + 0.8 * s0, m1)
let p3 = pt(o0 + s0, m1)
if zigzags == none {
let p4 = pt(o0 + 0.2 * s0, o1)
let p5 = pt(o0 + 0.8 * s0, o1 + s1)
let f = draw.group(name: id, {
draw.line(p0, p1)
draw.line(p2, p3)
draw.rect(p4, p5, stroke: stroke, fill: fill)
})
return (f, tl, tr, br, bl)
}
let pts = (p0, p1)
for i in range(zigzags) {
let r = ((i+0.5) / zigzags * 0.6 + 0.2)
let pos = pt(o0 + r * s0, o1 + s1 * calc.rem(i, 2))
pts.push(pos)
}
pts += (p2, p3)
let f = draw.group(name: id, {
draw.line(..pts, stroke: stroke)
})
return (f, tl, tr, br, bl)
}
/// Draws a resistor
///
/// #examples.resistor
/// For other parameters description, see #doc-ref("element.elmt")
/// - vertical (bool): Whether the element is vertical or horizontal. If false, port 0 is placed on the west side and port 1 on the east. If true, they are on the north, respectively the south sides
/// - zigzags (number, none): Number of zigzags to draw. If none, a rectangle is drawn
#let resistor(
x: none,
y: none,
w: none,
h: none,
name: none,
name-anchor: "center",
vertical: false,
zigzags: 6,
fill: none,
stroke: black + 1pt,
id: "",
debug: (
ports: false
)
) = {
let ports = if vertical {(
north: ((id: "0"),),
south: ((id: "1"),)
)} else {(
west: ((id: "0"),),
east: ((id: "1"),)
)}
element.elmt(
draw-shape: draw-shape.with(
vertical: vertical,
zigzags: zigzags
),
x: x,
y: y,
w: w,
h: h,
name: name,
name-anchor: name-anchor,
ports: ports,
fill: fill,
stroke: stroke,
id: id,
debug: debug
)
}
+8 -26
View File
@@ -1,4 +1,4 @@
#import "/src/cetz.typ": draw, coordinate
#import "@preview/cetz:0.3.2": draw, coordinate
#import "ports.typ": add-ports, add-port
#import "../util.typ"
@@ -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
@@ -37,11 +35,6 @@
/// - `clock` (`bool`): Whether it is a clock port (triangle symbol)
/// - `vertical` (`bool`): Whether the name should be drawn vertically
/// - ports-margins (dictionary): Dictionary of ports margins (used with automatic port placement). They keys are cardinal directions ("north", "east", "south", "west"). The values are tuples of (`<start>`, `<end>`) margins (numbers)
/// - port-name-padding (number, length): Padding between the port name label and the port anchor point (default `2pt`)
/// - port-names-outside (bool): If true, port name labels are drawn on the outside of the block instead of the inside (default `false`)
/// - port-shape (none, str): Default port shape for all ports of this element. Set to `"pentagon"` to draw pentagon-shaped ports. Overridable per port via the port's `shape` field. See #examples.port-shape
/// - port-kind (str): Default direction of ports (`"in"` or `"out"`). Overridable per port via the port's `kind` field
/// - port-size (length): Default unit size of pentagon-shaped ports (default `1em`). Overridable per port via the port's `size` field
/// - fill (none, color): Fill color
/// - stroke (stroke): Border stroke
/// - id (str): The block id (for future reference)
@@ -61,11 +54,6 @@
name-anchor: "center",
ports: (:),
ports-margins: (:),
port-name-padding: 2pt,
port-names-outside: false,
port-shape: none,
port-kind: "in",
port-size: 1em,
fill: none,
stroke: black + 1pt,
id: "",
@@ -86,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)
@@ -150,14 +139,7 @@
tl, tr, br, bl,
ports,
ports-margins,
debug: debug.ports,
name-padding: port-name-padding,
outside-name: port-names-outside,
port-shape: port-shape,
port-kind: port-kind,
port-size: port-size,
elmt-stroke: stroke,
elmt-fill: fill,
debug: debug.ports
)
}
})
+1 -1
View File
@@ -1,4 +1,4 @@
#import "/src/cetz.typ": draw
#import "@preview/cetz:0.3.2": draw
#import "element.typ"
#import "ports.typ": add-port
+2 -45
View File
@@ -1,15 +1,9 @@
#import "/src/cetz.typ": draw, coordinate
#import "@preview/cetz:0.3.2": draw, coordinate
#import "../util.typ"
#import "ports.typ": add-ports
/// Draws a group of elements
///
/// #examples.group
///
/// Ports can also be defined on a group, just like on a block. By default
/// port labels are drawn *outside* the group border so they do not overlap
/// child elements. Set `outside-port-names: false` to draw them inside.
/// #examples.group-ports
/// - body (elements, function): Elements to group
/// - id (str): see #doc-ref("element.elmt")
/// - name (str): The group's name
@@ -22,14 +16,6 @@
/// - array: either (`<all>`,), (`<vertical>`, `<horizontal>`) or (`<top>`, `<right>`, `<bottom>`, `<left>`)
/// - dictionary: valid keys are "top", "right", "bottom" and "left"
/// - radius (number): The corner radius
/// - ports (dictionary): see #doc-ref("element.elmt")
/// - ports-margins (dictionary): see #doc-ref("element.elmt")
/// - outside-port-names (bool): If `true` (default), port name labels are drawn outside the group border so they do not overlap child elements. Set to `false` to draw labels inside, matching the block behaviour
/// - port-name-padding (number, length): Padding between the port name label and the group border (default `2pt`). Accepts anything CeTZ accepts as `padding` (number or length)
/// - port-shape (none, str): see #doc-ref("element.elmt")
/// - port-kind (str): see #doc-ref("element.elmt")
/// - port-size (length): see #doc-ref("element.elmt")
/// - debug (dictionary): see #doc-ref("element.elmt")
#let group(
body,
id: "",
@@ -38,17 +24,7 @@
fill: none,
stroke: black + 1pt,
padding: 0.5em,
radius: 0.5em,
ports: (:),
ports-margins: (:),
outside-port-names: true,
port-name-padding: 2pt,
port-shape: none,
port-kind: "in",
port-size: 1em,
debug: (
ports: false
)
radius: 0.5em
) = {
let min-x = none
let max-x = none
@@ -92,25 +68,6 @@
stroke: stroke,
fill: fill
)
add-ports(
id,
id + ".north-west",
id + ".north-east",
id + ".south-east",
id + ".south-west",
ports,
ports-margins,
debug: debug.ports,
outside-name: outside-port-names,
name-padding: port-name-padding,
port-shape: port-shape,
port-kind: port-kind,
port-size: port-size,
elmt-stroke: stroke,
elmt-fill: fill,
)
if name != none {
draw.content(
id + "." + name-anchor,
+1 -1
View File
@@ -1,4 +1,4 @@
#import "/src/cetz.typ": draw
#import "@preview/cetz:0.3.2": draw
#import "gate.typ"
#let draw-shape(id, tl, tr, br, bl, fill, stroke) = {
+1 -1
View File
@@ -1,4 +1,4 @@
#import "/src/cetz.typ": draw
#import "@preview/cetz:0.3.2": draw
#import "gate.typ"
#let draw-shape(id, tl, tr, br, bl, fill, stroke) = {
+6 -12
View File
@@ -1,5 +1,4 @@
#import "/src/cetz.typ": draw, coordinate
#import "/src/util.typ"
#import "@preview/cetz:0.3.2": draw, coordinate
#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
@@ -63,12 +63,6 @@
if to == "out" {
dy = height / 2
} else {
let all-ports = range(inputs).map(i => "in" + str(i))
let i = all-ports.position(p => p == to)
assert(
i != none,
message: "Unknown port '" + y.to + "' must be one of " + repr(all-ports)
)
dy = height * (i + 0.5) / inputs
}
-70
View File
@@ -1,70 +0,0 @@
#import "/src/cetz.typ": draw
// #import "iec_gate.typ" as iec-gate
#import "iec_gate.typ" as iec-gate
/// Draws an IEC-AND gate. This function is also available as `element.iec-gate-and()`
///
/// For parameters, see #doc-ref("gates.iec-gate")
/// #examples.iec-gate-and
#let iec-gate-and(
x: none,
y: none,
w: none,
h: none,
inputs: 2,
fill: none,
stroke: black + 1pt,
id: "",
inverted: (),
debug: (
ports: false
),
) = {
iec-gate.iec-gate(
x: x,
y: y,
w: w,
h: h,
inputs: inputs,
fill: fill,
stroke: stroke,
id: id,
inverted: inverted,
debug: debug,
symbol: $amp$,
)
}
/// Draws an IEC-NAND gate. This function is also available as `element.iec-gate-nand()`
///
/// For parameters, see #doc-ref("gates.iec-gate")
/// #examples.iec-gate-nand
#let iec-gate-nand(
x: none,
y: none,
w: none,
h: none,
inputs: 2,
fill: none,
stroke: black + 1pt,
id: "",
inverted: (),
debug: (
ports: false
),
) = {
iec-gate-and(
x: x,
y: y,
w: w,
h: h,
inputs: inputs,
fill: fill,
stroke: stroke,
id: id,
inverted: if inverted != "all" {inverted + ("out",)} else {inverted},
debug: debug,
)
}
-68
View File
@@ -1,68 +0,0 @@
#import "/src/cetz.typ": draw
#import "iec_gate.typ" as iec-gate
/// Draws an IEC buffer gate. This function is also available as `element.iec-gate-buf()`
///
/// For parameters, see #doc-ref("gates.iec-gate")
/// #examples.iec-gate-buf
#let iec-gate-buf(
x: none,
y: none,
w: none,
h: none,
inputs: 2,
fill: none,
stroke: black + 1pt,
id: "",
inverted: (),
debug: (
ports: false,
),
) = {
iec-gate.iec-gate(
x: x,
y: y,
w: w,
h: h,
inputs: inputs,
fill: fill,
stroke: stroke,
id: id,
inverted: inverted,
debug: debug,
symbol: "1",
)
}
/// Draws an IEC NOT gate. This function is also available as `element.iec-gate-not()`
///
/// For parameters, see #doc-ref("gates.iec-gate")
/// #examples.iec-gate-not
#let iec-gate-not(
x: none,
y: none,
w: none,
h: none,
inputs: 2,
fill: none,
stroke: black + 1pt,
id: "",
inverted: (),
debug: (
ports: false,
),
) = {
iec-gate-buf(
x: x,
y: y,
w: w,
h: h,
inputs: inputs,
fill: fill,
stroke: stroke,
id: id,
inverted: if inverted != "all" { inverted + ("out",) } else { inverted },
debug: debug,
)
}
-125
View File
@@ -1,125 +0,0 @@
#import "/src/cetz.typ": draw, coordinate
#import "/src/util.typ"
#import "../ports.typ": add-ports, add-port
#import "../element.typ"
#let default-draw-shape(id, tl, tr, br, bl, fill, stroke, symbol) = {
let shapes = draw.rect(
inset: 0.5em,
fill: fill,
stroke: stroke,
name: id,
bl, tr
)
shapes += draw.content(
id + ".center",
[*$ symbol $*]
)
return (shapes, tl, tr, br, bl)
}
/// Draws a logic gate. This function is also available as `element.iec-gate()`
///
/// - draw-shape (function): see #doc-ref("element.elmt")
/// - x (number, dictionary): see #doc-ref("element.elmt")
/// - y (number, dictionary): see #doc-ref("element.elmt")
/// - w (number): see #doc-ref("element.elmt")
/// - h (number): see #doc-ref("element.elmt")
/// - inputs (int): The number of inputs
/// - fill (none, color): see #doc-ref("element.elmt")
/// - stroke (stroke): see #doc-ref("element.elmt")
/// - id (str): see #doc-ref("element.elmt")
/// - inverted (str, array): Either "all" or an array of port ids to display as inverted
/// - inverted-radius (number): The radius of inverted ports dot
/// - debug (dictionary): see #doc-ref("element.elmt")
/// - symbol (str): The symbol to display at the center of the gate
#let iec-gate(
draw-shape: default-draw-shape,
x: none,
y: none,
w: none,
h: none,
inputs: 2,
fill: none,
stroke: black + 1pt,
id: "",
inverted: (),
inverted-radius: 0.1,
debug: (
ports: false
),
symbol: "",
) = draw.get-ctx(ctx => {
let width = w
let height = h
let x = x
let y = y
if x == none { panic("Parameter x must be set") }
if y == none { panic("Parameter y must be set") }
if w == none { panic("Parameter w must be set") }
if h == none { panic("Parameter h must be set") }
if (type(x) == dictionary) {
x = util.resolve-rel-coord(ctx, x, 0)
}
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 dy
if to == "out" {
dy = height / 2
} else {
dy = height * (i + 0.5) / inputs
}
let (ctx, from-pos) = coordinate.resolve(ctx, from)
y = from-pos.at(1) + dy - height
}
let tl = (x, y + height)
let tr = (x + width, y + height)
let br = (x + width, y)
let bl = (x, y)
// Workaround because CeTZ needs to have all draw functions in the body
let func = {}
(func, tl, tr, br, bl) = draw-shape(id, tl, tr, br, bl, fill, stroke, symbol)
func
let space = 100% / inputs
for i in range(inputs) {
let pct = (i + 0.5) * space
let port-pos = (tl, pct, bl)
let port-name = "in" + str(i)
if inverted == "all" or port-name in inverted {
draw.circle(
port-pos,
radius: inverted-radius,
anchor: "east",
stroke: stroke
)
port-pos = (rel: (-2 * inverted-radius, 0), to: port-pos)
}
add-port(
id, "west",
(id: port-name), port-pos,
debug: debug.ports
)
}
let out-pos = id + ".east"
if inverted == "all" or "out" in inverted {
draw.circle(out-pos, radius: inverted-radius, anchor: "west", stroke: stroke)
out-pos = (rel: (2 * inverted-radius, 0), to: out-pos)
}
add-port(
id, "east",
(id: "out"), out-pos,
debug: debug.ports
)
})
-67
View File
@@ -1,67 +0,0 @@
#import "/src/cetz.typ": draw
#import "iec_gate.typ" as iec-gate
/// Draws an IEC-OR gate. This function is also available as `element.iec-gate-or()`
///
/// For parameters, see #doc-ref("gates.iec-gate")
/// #examples.iec-gate-or
#let iec-gate-or(
x: none,
y: none,
w: none,
h: none,
inputs: 2,
fill: none,
stroke: black + 1pt,
id: "",
inverted: (),
debug: (
ports: false
)
) = {
iec-gate.iec-gate(
x: x,
y: y,
w: w,
h: h,
inputs: inputs,
fill: fill,
stroke: stroke,
id: id,
inverted: inverted,
debug: debug,
symbol: $>= 1$,
)
}
/// Draws an IEC-NOR gate. This function is also available as `element.iec-gate-nor()`
///
/// For parameters, see #doc-ref("gates.iec-gate")
/// #examples.iec-gate-nor
#let iec-gate-nor(
x: none,
y: none,
w: none,
h: none,
inputs: 2,
fill: none,
stroke: black + 1pt,
id: "",
inverted: (),
debug: (
ports: false
)
) = {
iec-gate-or(
x: x,
y: y,
w: w,
h: h,
inputs: inputs,
fill: fill,
stroke: stroke,
id: id,
inverted: if inverted != "all" {inverted + ("out",)} else {inverted},
debug: debug
)
}
-67
View File
@@ -1,67 +0,0 @@
#import "/src/cetz.typ": draw
#import "iec_gate.typ" as iec-gate
/// Draws an IEC-XOR gate. This function is also available as `element.iec-gate-xor()`
///
/// For parameters, see #doc-ref("gates.iec-gate")
/// #examples.iec-gate-xor
#let iec-gate-xor(
x: none,
y: none,
w: none,
h: none,
inputs: 2,
fill: none,
stroke: black + 1pt,
id: "",
inverted: (),
debug: (
ports: false
)
) = {
iec-gate.iec-gate(
x: x,
y: y,
w: w,
h: h,
inputs: inputs,
fill: fill,
stroke: stroke,
id: id,
inverted: inverted,
debug: debug,
symbol: $= 1$,
)
}
/// Draws an IEC-XNOR gate. This function is also available as `element.iec-gate-xnor()`
///
/// For parameters, see #doc-ref("gates.iec-gate")
/// #examples.iec-gate-xnor
#let iec-gate-xnor(
x: none,
y: none,
w: none,
h: none,
inputs: 2,
fill: none,
stroke: black + 1pt,
id: "",
inverted: (),
debug: (
ports: false
)
) = {
iec-gate-xor(
x: x,
y: y,
w: w,
h: h,
inputs: inputs,
fill: fill,
stroke: stroke,
id: id,
inverted: if inverted != "all" {inverted + ("out",)} else {inverted},
debug: debug
)
}
+1 -1
View File
@@ -1,4 +1,4 @@
#import "/src/cetz.typ": draw
#import "@preview/cetz:0.3.2": draw
#import "gate.typ"
#let draw-shape(id, tl, tr, br, bl, fill, stroke) = {
+1 -1
View File
@@ -1,4 +1,4 @@
#import "/src/cetz.typ": draw
#import "@preview/cetz:0.3.2": draw
#import "gate.typ"
#let space = 10%
+1 -1
View File
@@ -1,4 +1,4 @@
#import "/src/cetz.typ": draw
#import "@preview/cetz:0.3.2": draw
#import "../util.typ"
#import "element.typ"
#import "ports.typ": add-port
+18 -85
View File
@@ -1,89 +1,36 @@
#import "/src/cetz.typ": draw
#import "../util.typ": rotate-anchor, opposite-anchor, port-shape-vertices
#import "@preview/cetz:0.3.2": draw
#import "../util.typ": rotate-anchor
#let add-port(
elmt-id, side, port, pos,
prev: none,
next: none,
debug: false,
outside-name: false,
name-padding: 2pt,
port-shape: none,
port-kind: "in",
port-size: 1em,
elmt-stroke: black + 1pt,
elmt-fill: none,
debug: false
) = {
let name = port.at("name", default: "")
let name-rotate = port.at("vertical", default: false)
let is-clock = port.at("clock", default: false)
// Per-port overrides fall back to element-level defaults.
let shape = port.at("shape", default: port-shape)
let kind = port.at("kind", default: port-kind)
let size = port.at("size", default: port-size)
let shape-stroke = port.at("stroke", default: elmt-stroke)
// Pentagon fill defaults to the element's fill (so the pentagon covers the
// block-border segment it crosses). Clock ports default to gray. Per-port
// `fill` overrides both.
let shape-fill = port.at("fill",
default: if is-clock and shape == "pentagon" { gray } else { elmt-fill })
// When shape is enabled, wire-connection anchor moves outside the block, and
// inside-name labels reference the inner tip/base; otherwise both collapse to
// the boundary point (unchanged behaviour).
let outer-pos = pos
let inner-pos = pos
if shape == "pentagon" {
let sh = port-shape-vertices(side, kind, size)
let v = sh.vertices.map(off => (rel: off, to: pos))
draw.line(
v.at(0), v.at(1), v.at(2), v.at(3), v.at(4),
close: true,
stroke: shape-stroke,
fill: shape-fill,
)
outer-pos = (rel: sh.outer, to: pos)
inner-pos = (rel: sh.inner, to: pos)
} else if is-clock {
if (port.at("clock", default: false)) {
if prev == none or next == none {
panic("Clock port must have previous and next positions")
}
let csize = if port.at("small", default: false) {8pt} else {1em}
let size = if port.at("small", default: false) {8pt} else {1em}
let offset
if (side == "north") { offset = ( 0, -csize) }
else if (side == "east") { offset = (-csize, 0) }
else if (side == "south") { offset = ( 0, csize) }
else if (side == "west") { offset = ( csize, 0) }
if (side == "north") { offset = ( 0, -size) }
else if (side == "east") { offset = (-size, 0) }
else if (side == "south") { offset = ( 0, size) }
else if (side == "west") { offset = ( size, 0) }
let pos1 = (rel: offset, to: pos)
// TODO: use context or vectors to have the height relative to the width
draw.line(prev, pos1, next)
}
// When outside-name is true (e.g. for groups) the label is placed on the
// exterior of the element border so it does not overlap child elements.
// Horizontal labels: anchor = opposite of the side (e.g. east port → west-anchored text, outside).
// Vertical labels: same opposite side but rotated 90°, keeping text readable.
let name-anchor = if outside-name {
if name-rotate { rotate-anchor(opposite-anchor(side)) }
else { opposite-anchor(side) }
} else {
if name-rotate { rotate-anchor(side) } else { side }
}
// Label sits at the outer edge of the shape for outside-name, inner edge for
// inside-name. `name-padding` is thus naturally measured from the shape edge
// (or from the boundary when shape is disabled).
let label-pos = if outside-name { outer-pos } else { inner-pos }
draw.content(
label-pos,
anchor: name-anchor,
padding: name-padding,
pos,
anchor: if name-rotate {rotate-anchor(side)} else {side},
padding: 2pt,
angle: if name-rotate {90deg} else {0deg},
name
)
@@ -91,7 +38,7 @@
if debug {
draw.circle(
outer-pos,
pos,
name: id,
radius: .1,
stroke: none,
@@ -100,7 +47,7 @@
} else {
draw.hide(draw.circle(
outer-pos,
pos,
radius: 0,
stroke: none,
name: id
@@ -113,14 +60,7 @@
tl, tr, br, bl,
ports,
ports-margins,
debug: false,
outside-name: false,
name-padding: 2pt,
port-shape: none,
port-kind: "in",
port-size: 1em,
elmt-stroke: black + 1pt,
elmt-fill: none,
debug: false
) = {
let sides = (
"north": (tl, tr),
@@ -169,15 +109,8 @@
pos,
prev: pos-prev,
next: pos-next,
debug: debug,
outside-name: outside-name,
name-padding: name-padding,
port-shape: port-shape,
port-kind: port-kind,
port-size: port-size,
elmt-stroke: elmt-stroke,
elmt-fill: elmt-fill,
debug: debug
)
}
}
}
}
+1 -6
View File
@@ -2,9 +2,4 @@
#import "elements/logic/and.typ": gate-and, gate-nand
#import "elements/logic/or.typ": gate-or, gate-nor
#import "elements/logic/xor.typ": gate-xor, gate-xnor
#import "elements/logic/buf.typ": gate-buf, gate-not
#import "elements/logic/iec_gate.typ": iec-gate
#import "elements/logic/iec_and.typ": iec-gate-and, iec-gate-nand
#import "elements/logic/iec_or.typ": iec-gate-or, iec-gate-nor
#import "elements/logic/iec_buf.typ": iec-gate-buf, iec-gate-not
#import "elements/logic/iec_xor.typ": iec-gate-xor, iec-gate-xnor
#import "elements/logic/buf.typ": gate-buf, gate-not
+2 -2
View File
@@ -1,7 +1,7 @@
#let version = version(0, 2, 1)
#let name = "circuiteria"
#let version = version(0, 2, 0)
#import "circuit.typ": circuit
#import "electrical.typ"
#import "element.typ"
#import "gates.typ"
#import "util.typ"
+1 -111
View File
@@ -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)[
@@ -90,97 +70,7 @@
).at(anchor)
}
/// Returns the pentagon port shape vertices and the outer/inner anchor
/// offsets for the given side and direction.
///
/// The pentagon straddles the block boundary: 1 unit outside, 1 unit inside;
/// height ±0.5 units. For `kind: "in"` the tip points into the block; for
/// `kind: "out"` it points away.
///
/// - side (str): One of `"north"`, `"east"`, `"south"`, `"west"`
/// - kind (str): `"in"` or `"out"`
/// - u (length,number): The pentagon unit size
/// -> dictionary: `(vertices: (..5 pairs..), outer: (dx, dy), inner: (dx, dy))`
#let port-shape-vertices(side, kind, u) = {
// Base case: west port, input (tip east / into block).
// Vertices as (dx, dy) offsets relative to the boundary anchor.
let base-vertices = (
(-u, -u/2),
(-u, +u/2),
(u/2, +u/2),
(u, 0), // tip east (inner side)
(u/2, -u/2),
)
// For output, mirror horizontally so the tip points west (outside).
if kind == "out" {
base-vertices = base-vertices.map(p => (-p.at(0), p.at(1)))
}
// Outer (wire-connection) and inner (inside-label) anchors for a west port.
// Outer is always 1u outside the boundary along the port normal; inner is 1u
// inside. Independent of `kind` (only the pentagon fill/tip direction change).
let base-outer = (-u, 0)
let base-inner = (u, 0)
// Rotation from west's frame to target side:
// west: identity (x, y)
// north: (x, y) → (y, -x)
// east: (x, y) → (-x, -y)
// south: (x, y) → (-y, x)
let rot = if side == "west" { p => p }
else if side == "north" { p => (p.at(1), -p.at(0)) }
else if side == "east" { p => (-p.at(0), -p.at(1)) }
else if side == "south" { p => (-p.at(1), p.at(0)) }
else { panic("Unknown side: " + side) }
return (
vertices: base-vertices.map(rot),
outer: rot(base-outer),
inner: rot(base-inner),
)
}
#let valid-anchors = (
"center", "north", "east", "west", "south",
"north-east", "north-west", "south-east", "south-west"
)
/// 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
}
)
+10 -355
View File
@@ -1,12 +1,9 @@
#import "/src/cetz.typ": draw, coordinate
#import "util.typ": opposite-anchor, normalize-coord, resolve-rel-coord
#import "@preview/cetz:0.3.2": draw, coordinate
#import "util.typ": opposite-anchor
/// List of valid wire styles
/// #examples.wires
///
/// The `"u-shape"` style connects two ports that face the same cardinal side.
/// #examples.u-shape-wire
#let wire-styles = ("direct", "zigzag", "dodge", "guided", "u-shape")
#let wire-styles = ("direct", "zigzag", "dodge")
#let signal-width = 1pt
#let bus-width = 1.5pt
@@ -71,8 +68,8 @@
let (ctx, p0) = coordinate.resolve(ctx, start)
let (ctx, p3) = coordinate.resolve(ctx, end)
p0 = (x: p0.first(), y: p0.at(1))
p3 = (x: p3.first(), y: p3.at(1))
p0 = (x: p0.first(), y: p0.last())
p3 = (x: p3.first(), y: p3.last())
let dx1 = margin-start
let dx2 = margin-end
@@ -89,13 +86,13 @@
if side-end == "east" {
dx2 *= -1
}
p1 = (p0.x + dx1, p0.y, 0)
p2 = (p3.x - dx2, p0.y, 0)
p1 = (p0.x + dx1, p0.y)
p2 = (p3.x - dx2, p0.y)
let points = (
start,
(horizontal: p1, vertical: ()),
(horizontal: (), vertical: (0, dodge-y, 0)),
(horizontal: (), vertical: (0, dodge-y)),
(horizontal: p2, vertical: ()),
(horizontal: (), vertical: end),
end
@@ -112,182 +109,6 @@
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 _u-detect-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, ctx) = {
let start = pts.first()
let end = pts.last()
// Resolve the (auto|explicit) side
let side = u-side
if side == auto {
let side-start = _u-detect-side(start, ctx)
let side-end = _u-detect-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
let p1
let p2
if side == "east" {
let bx = calc.max(sx, ex) + u-depth
p1 = (bx, sy, 0)
p2 = (bx, ey, 0)
} else if side == "west" {
let bx = calc.min(sx, ex) - u-depth
p1 = (bx, sy, 0)
p2 = (bx, ey, 0)
} else if side == "north" {
let by = calc.max(sy, ey) + u-depth
p1 = (sx, by, 0)
p2 = (ex, by, 0)
} else if side == "south" {
let by = 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()
let (margin-start, margin-end) = margins
let (side-start, side-end) = sides
let (center_horizontal, center_vertical) = center-guides
let (ctx, p0) = coordinate.resolve(ctx, start)
let (ctx, p6) = coordinate.resolve(ctx, end)
p0 = (x: p0.first(), y: p0.at(1))
p6 = (x: p6.first(), y: p6.at(1))
let box_width = calc.abs(p6.x - p0.x)
let box_height = calc.abs(p6.y - p0.y)
// finding correct dx and dy
let dx1 = box_width * margin-start / 100%
if side-start == "west" {
dx1 *= -1
} else if side-start == "north" or side-start == "south" { dx1 = 0}
let dx2 = box_width * margin-end / 100%
if side-end == "west" {
dx2 *= -1
} else if side-end == "north" or side-end == "south" { dx2 = 0}
let dy1 = box_height * margin-start / 100%
if side-start == "south" {
dy1 *= -1
} else if side-start == "west" or side-start == "east" { dy1 = 0}
let dy2 = box_height * margin-end / 100%
if side-end == "south" {
dy2 *= -1
} else if side-end == "west" or side-end == "east" { dy2 = 0}
// points that are closest to the edge points
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, 0)
// setting up the points for that touch the guides
let p2 = (0, 0, 0)
let p4 = (0, 0, 0)
if side-start in ("north", "south") {
p2 = (horizontal: p3, vertical: p1)
} else {
p2 = (horizontal: p1, vertical: p3)
}
if side-end in ("north", "south") {
p4 = (horizontal: p3, vertical: p5)
} else if side-end in ("east", "west") {
p4 = (horizontal: p5, vertical: p3)
}
// returning
let points = (
start,
p1,
p2,
p3,
p4,
p5,
end
)
let anchors = (
"start": start,
"start2": points.at(1),
"guide-start": points.at(2),
"center": points.at(3),
"guide-end": points.at(4),
"end2": points.at(5),
"end": end
)
return (points, anchors)
}
/// Draws a wire between two points
/// - id (str): The wire's id, for future reference (anchors)
/// - pts (array): The two points (as CeTZ compatible coordinates, i.e. XY, relative positions, ids, etc.)
@@ -306,11 +127,6 @@
/// - 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 (number): Distance between the outermost endpoint (in the `u-side` direction) and the U-bend line (only with style "u-shape")
#let wire(
id, pts,
bus: false,
@@ -321,18 +137,13 @@
dashed: false,
style: "direct",
reverse: false,
guided-center: (50%, 50%),
guided-margins: (5%, 5%),
guided-sides: ("east", "west"),
directed: false,
rotate-name: true,
zigzag-ratio: 50%,
zigzag-dir: "vertical",
dodge-y: 0,
dodge-sides: ("east", "west"),
dodge-margins: (5%, 5%),
u-side: auto,
u-depth: 0.5,
dodge-margins: (5%, 5%)
) = draw.get-ctx(ctx => {
if not style in wire-styles {
panic("Invalid wire style '" + style + "'")
@@ -341,9 +152,6 @@
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,
@@ -370,21 +178,6 @@
dodge-sides,
ctx
)
} else if style == "guided" {
(points, anchors) = get-guided-wire(
pts,
guided-margins,
guided-sides,
guided-center,
ctx
)
} else if style == "u-shape" {
(points, anchors) = get-u-shape-wire(
pts,
u-depth,
u-side,
ctx
)
}
let mark = (fill: color)
@@ -510,142 +303,4 @@
name
)
}
}
/// 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 #doc-ref("wire.wire") with
/// `style: "u-shape"` can auto-detect the port side.
///
/// #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))
}
})
}
+3 -3
View File
@@ -1,7 +1,7 @@
[package]
name = "circuiteria"
version = "0.2.1"
compiler = "0.15.0"
version = "0.2.0"
compiler = "0.13.0"
repository = "https://git.kb28.ch/HEL/circuiteria"
entrypoint = "src/lib.typ"
authors = [
@@ -9,6 +9,6 @@ authors = [
]
categories = [ "visualization" ]
license = "Apache-2.0"
description = "Drawing block circuits made easy, using CeTZ"
description = "Drawing block circuits with Typst made easy, using CeTZ"
keywords = [ "circuit", "block", "draw" ]
exclude = [ "gallery", "justfile", "doc" ]