Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6595387c5d | ||
|
|
94268c0a22 | ||
|
|
d9abaf4a53 | ||
|
|
c14e2ceab9 | ||
|
|
13e3fee913 | ||
|
|
df4c956a0b | ||
|
|
ac087087c5 | ||
|
|
77eb4fa946 |
No files matched your search
@@ -23,6 +23,31 @@ wire.stub("block-port-in2", "west")
|
|||||||
wire.stub("block-port-out", "east")
|
wire.stub("block-port-out", "east")
|
||||||
```)
|
```)
|
||||||
|
|
||||||
|
#let align-with-ports = example(```
|
||||||
|
// `align-with` reuses another port's coord
|
||||||
|
// (E/W: y, N/S: x). Forms:
|
||||||
|
// 1. "id" — local
|
||||||
|
// 2. (element:"e", port:"p") — cross-elmt
|
||||||
|
// 3. "raw-anchor" — CeTZ anchor
|
||||||
|
element.block(x: 0, y: 0, w: 2, h: 2, id: "b1",
|
||||||
|
ports: (
|
||||||
|
west: ((id: "a", name: "A", y: 30%),),
|
||||||
|
east: ((id: "oa", name: "A'",
|
||||||
|
"align-with": "a"),),
|
||||||
|
))
|
||||||
|
element.block(x: 4, y: -.3, w: 2, h: 2.6, id: "b2",
|
||||||
|
ports: (
|
||||||
|
west: ((id: "ia", name: "A",
|
||||||
|
"align-with":
|
||||||
|
(element: "b1", port: "oa")),),
|
||||||
|
east: ((id: "o", name: "C"),),
|
||||||
|
))
|
||||||
|
wire.stub("b1-port-a", "west")
|
||||||
|
wire.wire("w", ("b1-port-oa", "b2-port-ia"),
|
||||||
|
style: "direct")
|
||||||
|
wire.stub("b2-port-o", "east")
|
||||||
|
```, vertical: true)
|
||||||
|
|
||||||
#let extender = example(```
|
#let extender = example(```
|
||||||
element.extender(
|
element.extender(
|
||||||
x: 0, y: 0, w: 3, h: 1,
|
x: 0, y: 0, w: 3, h: 1,
|
||||||
@@ -186,9 +211,143 @@ wire.wire("w2", ("b2-port-out", "b3-port-in2"),
|
|||||||
style: "zigzag")
|
style: "zigzag")
|
||||||
```)
|
```)
|
||||||
|
|
||||||
|
#let group-ports = example(```
|
||||||
|
// outside-port-names: true (default) — labels outside the border
|
||||||
|
element.group(
|
||||||
|
id: "g1", name: "outside (default)",
|
||||||
|
stroke: (dash: "dashed"),
|
||||||
|
fill: util.colors.blue.lighten(60%),
|
||||||
|
padding: 2em,
|
||||||
|
outside-port-names: true,
|
||||||
|
port-name-padding: 4pt,
|
||||||
|
ports: (
|
||||||
|
west: ((id: "in", name: "D"),),
|
||||||
|
east: ((id: "out", name: "Q"),),
|
||||||
|
),
|
||||||
|
{ element.block(id: "b1", w: 2, h: 1.5, x: 0, y: 0,
|
||||||
|
fill: util.colors.green,
|
||||||
|
ports: (west: ((id: "d", name: "d"),),
|
||||||
|
east: ((id: "q", name: "q"),))) }
|
||||||
|
)
|
||||||
|
wire.wire("wG1in", ("g1-port-in", "b1-port-d"), directed: true)
|
||||||
|
wire.wire("wG1out", ("b1-port-q", "g1-port-out"), directed: true)
|
||||||
|
|
||||||
|
// outside-port-names: false — labels inside the border (block style)
|
||||||
|
// with extra padding via port-name-padding
|
||||||
|
element.group(
|
||||||
|
id: "g2", name: "inside",
|
||||||
|
stroke: (dash: "dashed"),
|
||||||
|
fill: util.colors.orange.lighten(60%),
|
||||||
|
padding: 2em,
|
||||||
|
outside-port-names: false,
|
||||||
|
port-name-padding: 0.4em,
|
||||||
|
ports: (
|
||||||
|
west: ((id: "in", name: "D"),),
|
||||||
|
east: ((id: "out", name: "Q"),),
|
||||||
|
),
|
||||||
|
{ element.block(id: "b2", w: 2, h: 1.5, x: 6, y: 0,
|
||||||
|
fill: util.colors.green,
|
||||||
|
ports: (west: ((id: "d"),), east: ((id: "q"),))) }
|
||||||
|
)
|
||||||
|
wire.stub("g2-port-in", "west")
|
||||||
|
wire.stub("g2-port-out", "east")
|
||||||
|
```, vertical: true)
|
||||||
|
|
||||||
|
#let port-names-outside = example(```
|
||||||
|
element.block(
|
||||||
|
x: 0, y: 0, w: 2, h: 2, id: "block",
|
||||||
|
port-names-outside: true,
|
||||||
|
port-name-padding: 4pt,
|
||||||
|
ports: (
|
||||||
|
north: ((id: "clk", name: "CLK", clock: true),),
|
||||||
|
west: ((id: "in1", name: "A"),
|
||||||
|
(id: "in2", name: "B")),
|
||||||
|
east: ((id: "out", name: "Y"),)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
```)
|
||||||
|
|
||||||
|
#let port-shape = example(```
|
||||||
|
// Block with pentagon-shaped ports.
|
||||||
|
// - port-shape / port-kind / port-size are element-level defaults.
|
||||||
|
// - Any port field (shape, kind, size, stroke, fill) overrides per port.
|
||||||
|
// - kind: "in" → tip points into the block (default)
|
||||||
|
// kind: "out" → tip points away from the block
|
||||||
|
// - clock ports with shape: "pentagon" drop their triangle and default
|
||||||
|
// to a gray fill.
|
||||||
|
element.block(
|
||||||
|
id: "b", w: 3, h: 3, x: 0, y: 0,
|
||||||
|
fill: util.colors.yellow.lighten(60%),
|
||||||
|
port-shape: "pentagon",
|
||||||
|
port-size: 1em,
|
||||||
|
ports: (
|
||||||
|
// west: default input
|
||||||
|
west: ((id: "in", name: "D"),),
|
||||||
|
// east: output — tip points away
|
||||||
|
east: ((id: "out", name: "Q", kind: "out"),),
|
||||||
|
// north: clock port — pentagon replaces the triangle, gray fill
|
||||||
|
north: ((id: "clk", name: "CLK", clock: true),),
|
||||||
|
// south: styled output with per-port fill and stroke
|
||||||
|
south: ((id: "rdy", name: "RDY", kind: "out",
|
||||||
|
fill: util.colors.blue, stroke: red + 1.5pt),),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// Wires connect at the outer end of each pentagon.
|
||||||
|
wire.stub("b-port-in", "west")
|
||||||
|
wire.stub("b-port-out", "east")
|
||||||
|
wire.stub("b-port-clk", "north")
|
||||||
|
wire.stub("b-port-rdy", "south")
|
||||||
|
```)
|
||||||
|
|
||||||
|
#let shell = example(```
|
||||||
|
// A shell block acts as a container: port name labels are drawn
|
||||||
|
// outside by default, and wires attach at the *inside* tip of
|
||||||
|
// pentagon-shaped ports so child elements can sit within.
|
||||||
|
element.block(
|
||||||
|
id: "sh", w: 5, h: 3, x: 0, y: 0,
|
||||||
|
shell: true,
|
||||||
|
port-shape: "pentagon",
|
||||||
|
fill: util.colors.blue.lighten(80%),
|
||||||
|
ports: (
|
||||||
|
west: ((id: "in", name: "IN"),),
|
||||||
|
east: ((id: "out", name: "OUT", kind: "out"),),
|
||||||
|
),
|
||||||
|
{
|
||||||
|
element.block(id: "inner", w: 2, h: 1.5, x: 1.5, y: 0.75,
|
||||||
|
fill: util.colors.yellow,
|
||||||
|
ports: (west: ((id: "a"),), east: ((id: "b"),)))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
wire.wire("w-in", ("sh-port-in", "inner-port-a"))
|
||||||
|
wire.wire("w-out", ("inner-port-b", "sh-port-out"))
|
||||||
|
```)
|
||||||
|
|
||||||
#let intersection = example(```
|
#let intersection = example(```
|
||||||
wire.wire("w1", ((0, 0), (1, 1)), style: "zigzag")
|
wire.wire("w1", ((0, 0), (1, 1)), style: "zigzag")
|
||||||
wire.wire("w2", ((0, 0), (1, -.5)),
|
wire.wire("w2", ((0, 0), (1, -.5)),
|
||||||
style: "zigzag", zigzag-ratio: 80%)
|
style: "zigzag", zigzag-ratio: 80%)
|
||||||
wire.intersection("w1.zig")
|
wire.intersection("w1.zig")
|
||||||
```)
|
```)
|
||||||
|
|
||||||
|
#let port-shape-attach = example(
|
||||||
|
```
|
||||||
|
// Pentagon ports expose `.east`, `.west`, `.north`,
|
||||||
|
// `.south` sub-anchors on the pentagon boundary,
|
||||||
|
// letting wires attach to any face.
|
||||||
|
element.block(x: 0, y: 0, w: 2, h: 2, id: "a",
|
||||||
|
port-shape: "pentagon",
|
||||||
|
ports: (west: ((id: "p", name: "P"),),))
|
||||||
|
draw.circle((-2, 0.5), radius: .05, name: "L")
|
||||||
|
draw.circle(( 3, 1.5), radius: .05, name: "R")
|
||||||
|
draw.circle((-1, 2.5), radius: .05, name: "T")
|
||||||
|
// .west = outer flat base
|
||||||
|
wire.wire("w1", ("L", "a-port-p.west"), style: "direct")
|
||||||
|
// .east = tip inside the block
|
||||||
|
wire.wire("w2", ("R", "a-port-p.east"),
|
||||||
|
style: "direct", color: red)
|
||||||
|
// .north = top edge midpoint
|
||||||
|
wire.wire("w3", ("T", "a-port-p.north"),
|
||||||
|
style: "direct", color: blue)
|
||||||
|
```,
|
||||||
|
vertical: true,
|
||||||
|
)
|
||||||
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 274 KiB After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,86 @@
|
|||||||
|
// 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")
|
||||||
|
})
|
||||||
|
After Width: | Height: | Size: 39 KiB |
@@ -0,0 +1,117 @@
|
|||||||
|
// 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"))
|
||||||
|
})
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#import "@preview/tidy:0.4.1"
|
#import "@preview/tidy:0.4.3"
|
||||||
#import "/src/cetz.typ": draw, canvas
|
#import "/src/cetz.typ": draw, canvas
|
||||||
#import "src/lib.typ"
|
#import "src/lib.typ"
|
||||||
#import "doc/examples.typ"
|
#import "doc/examples.typ"
|
||||||
@@ -166,8 +166,24 @@ If you have installed Circuiteria directly in your project, import #link("src/li
|
|||||||
|
|
||||||
#pagebreak()
|
#pagebreak()
|
||||||
|
|
||||||
|
#let element-scope = (
|
||||||
|
element: element,
|
||||||
|
circuit: circuit,
|
||||||
|
draw: draw,
|
||||||
|
wire: wire,
|
||||||
|
tidy: tidy,
|
||||||
|
examples: examples,
|
||||||
|
doc-ref: doc-ref
|
||||||
|
)
|
||||||
|
|
||||||
|
#let elmt-docs = tidy.parse-module(
|
||||||
|
read("src/elements/element.typ"),
|
||||||
|
name: "element",
|
||||||
|
old-syntax: true,
|
||||||
|
scope: element-scope
|
||||||
|
)
|
||||||
|
|
||||||
#let element-docs = tidy.parse-module(
|
#let element-docs = tidy.parse-module(
|
||||||
read("src/elements/element.typ") + "\n" +
|
|
||||||
read("src/elements/alu.typ") + "\n" +
|
read("src/elements/alu.typ") + "\n" +
|
||||||
read("src/elements/block.typ") + "\n" +
|
read("src/elements/block.typ") + "\n" +
|
||||||
read("src/elements/extender.typ") + "\n" +
|
read("src/elements/extender.typ") + "\n" +
|
||||||
@@ -175,18 +191,19 @@ If you have installed Circuiteria directly in your project, import #link("src/li
|
|||||||
read("src/elements/group.typ"),
|
read("src/elements/group.typ"),
|
||||||
name: "element",
|
name: "element",
|
||||||
old-syntax: true,
|
old-syntax: true,
|
||||||
scope: (
|
scope: element-scope
|
||||||
element: element,
|
|
||||||
circuit: circuit,
|
|
||||||
draw: draw,
|
|
||||||
wire: wire,
|
|
||||||
tidy: tidy,
|
|
||||||
examples: examples,
|
|
||||||
doc-ref: doc-ref
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
#tidy.show-module(element-docs, sort-functions: false)
|
#tidy.show-module(elmt-docs, sort-functions: false, show-module-name: true)
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
examples.align-with-ports,
|
||||||
|
caption: [Example of the `align-with` port field (see #doc-ref("element.elmt")).],
|
||||||
|
kind: "example",
|
||||||
|
supplement: [Example],
|
||||||
|
) <elmt-align-with-example>
|
||||||
|
|
||||||
|
#tidy.show-module(element-docs, sort-functions: false, show-module-name: false)
|
||||||
|
|
||||||
#pagebreak()
|
#pagebreak()
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
#import "@preview/cetz:0.3.4": *
|
#import "@preview/cetz:0.5.2": *
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#import "/src/cetz.typ": canvas
|
#import "/src/cetz.typ": canvas
|
||||||
#import "@preview/tidy:0.3.0"
|
#import "@preview/tidy:0.4.3"
|
||||||
|
|
||||||
/// Draws a block circuit diagram
|
/// Draws a block circuit diagram
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -16,9 +16,22 @@
|
|||||||
/// Draws a block element
|
/// Draws a block element
|
||||||
///
|
///
|
||||||
/// #examples.block
|
/// #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")
|
/// 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
|
/// - radius (number, length, ratio, dictionary): The corner radius of the block. See CeTZ documentation for more information
|
||||||
|
/// - shell (bool): If true, the block behaves like a shell/container (analogous to a group): port name labels default to being drawn outside the block, and wires attach to the inside tip of pentagon-shaped ports. See #doc-ref("element.elmt")
|
||||||
|
///
|
||||||
|
/// Use `shell: true` to wrap child elements inside a bordered container without
|
||||||
|
/// having ports overlap the interior:
|
||||||
|
/// #examples.shell
|
||||||
#let block(
|
#let block(
|
||||||
|
..args,
|
||||||
x: none,
|
x: none,
|
||||||
y: none,
|
y: none,
|
||||||
w: none,
|
w: none,
|
||||||
@@ -27,15 +40,22 @@
|
|||||||
name-anchor: "center",
|
name-anchor: "center",
|
||||||
ports: (),
|
ports: (),
|
||||||
ports-margins: (),
|
ports-margins: (),
|
||||||
|
port-name-padding: 2pt,
|
||||||
|
port-names-outside: auto,
|
||||||
|
port-shape: none,
|
||||||
|
port-kind: "in",
|
||||||
|
port-size: 1em,
|
||||||
fill: none,
|
fill: none,
|
||||||
radius: 0.5em,
|
radius: 0.5em,
|
||||||
stroke: black + 1pt,
|
stroke: black + 1pt,
|
||||||
id: "",
|
id: "",
|
||||||
|
shell: false,
|
||||||
debug: (
|
debug: (
|
||||||
ports: false
|
ports: false
|
||||||
)
|
)
|
||||||
) = element.elmt(
|
) = element.elmt(
|
||||||
draw-shape: draw-shape.with(radius: radius),
|
draw-shape: draw-shape.with(radius: radius),
|
||||||
|
body: if args.pos().len() > 0 { args.pos().first() } else { none },
|
||||||
x: x,
|
x: x,
|
||||||
y: y,
|
y: y,
|
||||||
w: w,
|
w: w,
|
||||||
@@ -44,8 +64,14 @@
|
|||||||
name-anchor: name-anchor,
|
name-anchor: name-anchor,
|
||||||
ports: ports,
|
ports: ports,
|
||||||
ports-margins: ports-margins,
|
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,
|
fill: fill,
|
||||||
stroke: stroke,
|
stroke: stroke,
|
||||||
id: id,
|
id: id,
|
||||||
|
shell: shell,
|
||||||
debug: debug
|
debug: debug
|
||||||
)
|
)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#import "/src/cetz.typ": draw, coordinate
|
#import "/src/cetz.typ": coordinate, draw
|
||||||
#import "ports.typ": add-ports, add-port
|
#import "ports.typ": add-port, add-ports
|
||||||
#import "../util.typ"
|
#import "../util.typ"
|
||||||
|
|
||||||
#let find-port(ports, id) = {
|
#let find-port(ports, id) = {
|
||||||
@@ -34,12 +34,26 @@
|
|||||||
/// - `name` (`str`): Optional name displayed *in* the block
|
/// - `name` (`str`): Optional name displayed *in* the block
|
||||||
/// - `clock` (`bool`): Whether it is a clock port (triangle symbol)
|
/// - `clock` (`bool`): Whether it is a clock port (triangle symbol)
|
||||||
/// - `vertical` (`bool`): Whether the name should be drawn vertically
|
/// - `vertical` (`bool`): Whether the name should be drawn vertically
|
||||||
|
/// - `align-with` (`str`, `dictionary`): Optional. Reuse another port's placement coordinate (east/west: y, north/south: x). Accepted forms: `"<local-id>"` (same-element port; axis must match, transitively resolved, cycles panic), `(element: "<id>", port: "<id>")` (cross-element; axis not validated; target element must be drawn first), or `"<raw-anchor>"` (any unrecognised string is used as a CeTZ anchor name). See @elmt-align-with-example.
|
||||||
|
/// - `x` (length, ratio, function): Optional. North/south ports only. Explicit x-offset override measured from the left corner of the side. A `ratio` is used as a percentage along the side; a length/number is used as an absolute offset; a function is called with the element width and must return a length/number. Panics if used together with `align-with` or on an east/west port.
|
||||||
|
/// - `y` (length, ratio, function): Optional. East/west ports only. Analogous to `x`, measured from the top corner of the side and called with the element height when a function is passed.
|
||||||
|
/// - `shape` (`none`, `str`): Optional. Per-port override of the element-level `port-shape` (e.g. `"pentagon"`). Lets a single port on a block or group opt into (or out of) a shape independently of the element default. See the `port-shape` parameter below.
|
||||||
|
/// - `kind` (`str`): Optional. Per-port override of the element-level `port-kind` (`"in"` / `"out"`). Affects pentagon orientation.
|
||||||
|
/// - `size` (length): Optional. Per-port override of the element-level `port-size` (unit size of pentagon-shaped ports).
|
||||||
|
/// - `stroke` (stroke): Optional. Per-port override of the pentagon stroke. Defaults to the element stroke.
|
||||||
|
/// - `fill` (none, color): Optional. Per-port override of the pentagon fill. Defaults to the element fill (or `gray` for clock ports with `shape: "pentagon"`).
|
||||||
/// - 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)
|
/// - 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, auto): If true, port name labels are drawn on the outside of the block instead of the inside. When `auto` (default), follows the `shell` setting (outside if `shell: true`, inside otherwise)
|
||||||
|
/// - 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 the ports dictionary above); a per-port `shape: none` also removes the shape for just that port. 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
|
/// - fill (none, color): Fill color
|
||||||
/// - stroke (stroke): Border stroke
|
/// - stroke (stroke): Border stroke
|
||||||
/// - id (str): The block id (for future reference)
|
/// - id (str): The block id (for future reference)
|
||||||
/// - auto-ports (bool): Whether to use auto port placements or not. If false, `draw-shape` is responsible for adding the appropiate ports
|
/// - auto-ports (bool): Whether to use auto port placements or not. If false, `draw-shape` is responsible for adding the appropiate ports
|
||||||
/// - ports-y (dictionary): Dictionary of the ports y offsets (used with `auto-ports: false`)
|
/// - ports-y (dictionary): Dictionary of the ports y offsets (used with `auto-ports: false`)
|
||||||
|
/// - shell (bool): If true, the block behaves like a shell/container (analogous to a group): port name labels default to being drawn *outside* the block border, and wires attach to the *inside* tip of pentagon-shaped ports instead of the outside base. Useful for wrapping child elements without having ports overlap them (default `false`)
|
||||||
/// - debug (dictionary): Dictionary of debug options.
|
/// - debug (dictionary): Dictionary of debug options.
|
||||||
///
|
///
|
||||||
/// Supported fields include:
|
/// Supported fields include:
|
||||||
@@ -54,14 +68,21 @@
|
|||||||
name-anchor: "center",
|
name-anchor: "center",
|
||||||
ports: (:),
|
ports: (:),
|
||||||
ports-margins: (:),
|
ports-margins: (:),
|
||||||
|
port-name-padding: 2pt,
|
||||||
|
port-names-outside: auto,
|
||||||
|
port-shape: none,
|
||||||
|
port-kind: "in",
|
||||||
|
port-size: 1em,
|
||||||
fill: none,
|
fill: none,
|
||||||
stroke: black + 1pt,
|
stroke: black + 1pt,
|
||||||
id: "",
|
id: "",
|
||||||
auto-ports: true,
|
auto-ports: true,
|
||||||
ports-y: (:),
|
ports-y: (:),
|
||||||
|
shell: false,
|
||||||
|
body: none,
|
||||||
debug: (
|
debug: (
|
||||||
ports: false
|
ports: false,
|
||||||
)
|
),
|
||||||
) = draw.get-ctx(ctx => {
|
) = draw.get-ctx(ctx => {
|
||||||
let width = w
|
let width = w
|
||||||
let height = h
|
let height = h
|
||||||
@@ -95,7 +116,7 @@
|
|||||||
let used-pct = 100% - margins.at(0) - margins.at(1)
|
let used-pct = 100% - margins.at(0) - margins.at(1)
|
||||||
let used-height = height * used-pct / 100%
|
let used-height = height * used-pct / 100%
|
||||||
top-margin = height * margins.at(0) / 100%
|
top-margin = height * margins.at(0) / 100%
|
||||||
|
|
||||||
dy = used-height * (i + 1) / (ports.at(to-side).len() + 1)
|
dy = used-height * (i + 1) / (ports.at(to-side).len() + 1)
|
||||||
|
|
||||||
if not auto-ports {
|
if not auto-ports {
|
||||||
@@ -109,7 +130,7 @@
|
|||||||
dy = height
|
dy = height
|
||||||
top-margin = 0
|
top-margin = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
let (ctx, from-pos) = coordinate.resolve(ctx, from)
|
let (ctx, from-pos) = coordinate.resolve(ctx, from)
|
||||||
y = from-pos.at(1) + dy - height + top-margin
|
y = from-pos.at(1) + dy - height + top-margin
|
||||||
}
|
}
|
||||||
@@ -127,19 +148,37 @@
|
|||||||
if (name != none) {
|
if (name != none) {
|
||||||
draw.content(
|
draw.content(
|
||||||
(name: id, anchor: name-anchor),
|
(name: id, anchor: name-anchor),
|
||||||
anchor: if name-anchor in util.valid-anchors {name-anchor} else {"center"},
|
anchor: if name-anchor in util.valid-anchors { name-anchor } else { "center" },
|
||||||
padding: 0.5em,
|
padding: 0.5em,
|
||||||
align(center)[*#name*]
|
align(center)[*#name*],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if auto-ports {
|
if auto-ports {
|
||||||
|
let outside-name = if port-names-outside == auto { shell } else { port-names-outside }
|
||||||
add-ports(
|
add-ports(
|
||||||
id,
|
id,
|
||||||
tl, tr, br, bl,
|
tl,
|
||||||
|
tr,
|
||||||
|
br,
|
||||||
|
bl,
|
||||||
ports,
|
ports,
|
||||||
ports-margins,
|
ports-margins,
|
||||||
debug: debug.ports
|
width: width,
|
||||||
|
height: height,
|
||||||
|
debug: debug.ports,
|
||||||
|
name-padding: port-name-padding,
|
||||||
|
outside-name: outside-name,
|
||||||
|
port-shape: port-shape,
|
||||||
|
port-kind: port-kind,
|
||||||
|
port-size: port-size,
|
||||||
|
elmt-stroke: stroke,
|
||||||
|
elmt-fill: fill,
|
||||||
|
wire-inside: shell,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
if body != none {
|
||||||
|
body
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
#import "/src/cetz.typ": draw, coordinate
|
#import "/src/cetz.typ": draw, coordinate
|
||||||
#import "../util.typ"
|
#import "../util.typ"
|
||||||
|
#import "ports.typ": add-ports
|
||||||
|
|
||||||
/// Draws a group of elements
|
/// Draws a group of elements
|
||||||
///
|
///
|
||||||
/// #examples.group
|
/// #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
|
/// - body (elements, function): Elements to group
|
||||||
/// - id (str): see #doc-ref("element.elmt")
|
/// - id (str): see #doc-ref("element.elmt")
|
||||||
/// - name (str): The group's name
|
/// - name (str): The group's name
|
||||||
@@ -16,6 +22,14 @@
|
|||||||
/// - array: either (`<all>`,), (`<vertical>`, `<horizontal>`) or (`<top>`, `<right>`, `<bottom>`, `<left>`)
|
/// - array: either (`<all>`,), (`<vertical>`, `<horizontal>`) or (`<top>`, `<right>`, `<bottom>`, `<left>`)
|
||||||
/// - dictionary: valid keys are "top", "right", "bottom" and "left"
|
/// - dictionary: valid keys are "top", "right", "bottom" and "left"
|
||||||
/// - radius (number): The corner radius
|
/// - 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(
|
#let group(
|
||||||
body,
|
body,
|
||||||
id: "",
|
id: "",
|
||||||
@@ -24,7 +38,17 @@
|
|||||||
fill: none,
|
fill: none,
|
||||||
stroke: black + 1pt,
|
stroke: black + 1pt,
|
||||||
padding: 0.5em,
|
padding: 0.5em,
|
||||||
radius: 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
|
||||||
|
)
|
||||||
) = {
|
) = {
|
||||||
let min-x = none
|
let min-x = none
|
||||||
let max-x = none
|
let max-x = none
|
||||||
@@ -68,6 +92,25 @@
|
|||||||
stroke: stroke,
|
stroke: stroke,
|
||||||
fill: fill
|
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 {
|
if name != none {
|
||||||
draw.content(
|
draw.content(
|
||||||
id + "." + name-anchor,
|
id + "." + name-anchor,
|
||||||
|
|||||||
@@ -1,116 +1,446 @@
|
|||||||
#import "/src/cetz.typ": draw
|
#import "/src/cetz.typ": draw
|
||||||
#import "../util.typ": rotate-anchor
|
#import "../util.typ": opposite-anchor, port-shape-vertices, rotate-anchor
|
||||||
|
|
||||||
#let add-port(
|
#let add-port(
|
||||||
elmt-id, side, port, pos,
|
elmt-id,
|
||||||
|
side,
|
||||||
|
port,
|
||||||
|
pos,
|
||||||
prev: none,
|
prev: none,
|
||||||
next: none,
|
next: none,
|
||||||
debug: false
|
debug: false,
|
||||||
|
outside-name: false,
|
||||||
|
name-padding: 2pt,
|
||||||
|
port-shape: none,
|
||||||
|
port-kind: "in",
|
||||||
|
port-size: 1em,
|
||||||
|
elmt-stroke: black + 1pt,
|
||||||
|
elmt-fill: none,
|
||||||
|
wire-inside: false,
|
||||||
) = {
|
) = {
|
||||||
let name = port.at("name", default: "")
|
let name = port.at("name", default: "")
|
||||||
let name-rotate = port.at("vertical", default: false)
|
let name-rotate = port.at("vertical", default: false)
|
||||||
|
let is-clock = port.at("clock", default: false)
|
||||||
|
|
||||||
if (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 pentagon on a higher layer so it always renders on top of wires
|
||||||
|
// and neighbouring elements.
|
||||||
|
draw.on-layer(1, 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 prev == none or next == none {
|
if prev == none or next == none {
|
||||||
panic("Clock port must have previous and next positions")
|
panic("Clock port must have previous and next positions")
|
||||||
}
|
}
|
||||||
|
|
||||||
let size = if port.at("small", default: false) {8pt} else {1em}
|
let csize = if port.at("small", default: false) { 8pt } else { 1em }
|
||||||
let offset
|
let offset
|
||||||
if (side == "north") { offset = ( 0, -size) }
|
if (side == "north") { offset = (0, -csize) } else if (side == "east") {
|
||||||
else if (side == "east") { offset = (-size, 0) }
|
offset = (-csize, 0)
|
||||||
else if (side == "south") { offset = ( 0, size) }
|
} else if (side == "south") { offset = (0, csize) } else if (side == "west") {
|
||||||
else if (side == "west") { offset = ( size, 0) }
|
offset = (csize, 0)
|
||||||
|
}
|
||||||
|
|
||||||
let pos1 = (rel: offset, to: pos)
|
let pos1 = (rel: offset, to: pos)
|
||||||
|
|
||||||
// TODO: use context or vectors to have the height relative to the width
|
// TODO: use context or vectors to have the height relative to the width
|
||||||
draw.line(prev, pos1, next)
|
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 }
|
||||||
|
|
||||||
|
// For "shell" containers, wires must attach at the inside of the pentagon
|
||||||
|
// (the tip that points inward), not at the exterior base.
|
||||||
|
let anchor-pos = if wire-inside and shape == "pentagon" { inner-pos } else { outer-pos }
|
||||||
|
|
||||||
draw.content(
|
draw.content(
|
||||||
pos,
|
label-pos,
|
||||||
anchor: if name-rotate {rotate-anchor(side)} else {side},
|
anchor: name-anchor,
|
||||||
padding: 2pt,
|
padding: name-padding,
|
||||||
angle: if name-rotate {90deg} else {0deg},
|
angle: if name-rotate { 90deg } else { 0deg },
|
||||||
name
|
name,
|
||||||
)
|
)
|
||||||
let id = elmt-id + "-port-" + port.at("id")
|
let id = elmt-id + "-port-" + port.at("id")
|
||||||
|
|
||||||
|
// Cardinal sub-anchor positions.
|
||||||
|
// - Pentagon ports: the four world-cardinal midpoints of the pentagon's
|
||||||
|
// bounding box (all lie on the pentagon boundary).
|
||||||
|
// - Non-pentagon ports: all four collapse to the default attach point.
|
||||||
|
let sub-east = anchor-pos
|
||||||
|
let sub-west = anchor-pos
|
||||||
|
let sub-north = anchor-pos
|
||||||
|
let sub-south = anchor-pos
|
||||||
|
if shape == "pentagon" {
|
||||||
|
let sh = port-shape-vertices(side, kind, size)
|
||||||
|
sub-east = (rel: sh.east, to: pos)
|
||||||
|
sub-west = (rel: sh.west, to: pos)
|
||||||
|
sub-north = (rel: sh.north, to: pos)
|
||||||
|
sub-south = (rel: sh.south, to: pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose the port as a CeTZ group carrying the default attach point plus
|
||||||
|
// the four cardinal sub-anchors (`.east`, `.west`, `.north`, `.south`).
|
||||||
|
// The custom anchors override CeTZ's auto-generated bbox anchors of the
|
||||||
|
// same name.
|
||||||
|
draw.group(name: id, {
|
||||||
|
draw.anchor("default", anchor-pos)
|
||||||
|
draw.anchor("east", sub-east)
|
||||||
|
draw.anchor("west", sub-west)
|
||||||
|
draw.anchor("north", sub-north)
|
||||||
|
draw.anchor("south", sub-south)
|
||||||
|
})
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
draw.circle(
|
draw.circle(
|
||||||
pos,
|
anchor-pos,
|
||||||
name: id,
|
|
||||||
radius: .1,
|
radius: .1,
|
||||||
stroke: none,
|
stroke: none,
|
||||||
fill: red
|
fill: red,
|
||||||
)
|
)
|
||||||
|
|
||||||
} else {
|
|
||||||
draw.hide(draw.circle(
|
|
||||||
pos,
|
|
||||||
radius: 0,
|
|
||||||
stroke: none,
|
|
||||||
name: id
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize an `align-with` value to either:
|
||||||
|
// - `(local: <id>)` → reference to another port of the *same* element
|
||||||
|
// - `(external: <anchor-name>)` → CeTZ anchor name of a port in another element
|
||||||
|
// Accepted input forms:
|
||||||
|
// - `str` matching a local port id → local
|
||||||
|
// - `str` not matching any local port id → external, used as-is as CeTZ anchor
|
||||||
|
// - `(element: <str>, port: <str>)` → external, anchor = `<element>-port-<port>`
|
||||||
|
#let classify-align-with(raw-val, placements) = {
|
||||||
|
if type(raw-val) == dictionary {
|
||||||
|
if "element" in raw-val and "port" in raw-val {
|
||||||
|
return (external: raw-val.element + "-port-" + raw-val.port)
|
||||||
|
}
|
||||||
|
panic("`align-with` dictionary must contain `element` and `port` keys, got: " + repr(raw-val))
|
||||||
|
}
|
||||||
|
if type(raw-val) != str {
|
||||||
|
panic("`align-with` must be a string or `(element:, port:)` dictionary, got: " + repr(raw-val))
|
||||||
|
}
|
||||||
|
if raw-val in placements {
|
||||||
|
return (local: raw-val)
|
||||||
|
}
|
||||||
|
return (external: raw-val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve a single port's placement, following `align-with` references
|
||||||
|
// transitively and detecting cycles.
|
||||||
|
//
|
||||||
|
// Returns `(placements, result)` where `result` is either:
|
||||||
|
// - `(pct: <ratio>)` → percentage along the side (local placement)
|
||||||
|
// - `(external: <anchor-name>)` → align with the given remote CeTZ anchor
|
||||||
|
#let resolve-placement(placements, id, visiting, elmt-id: "") = {
|
||||||
|
let where = if elmt-id != "" { " (element '" + elmt-id + "')" } else { "" }
|
||||||
|
let p = placements.at(id)
|
||||||
|
if p.at("resolved", default: false) {
|
||||||
|
if "external-ref" in p {
|
||||||
|
return (placements, (external: p.external-ref))
|
||||||
|
}
|
||||||
|
return (placements, (pct: p.pct))
|
||||||
|
}
|
||||||
|
if id in visiting {
|
||||||
|
panic(
|
||||||
|
"align-with cycle involving port '"
|
||||||
|
+ id
|
||||||
|
+ "'"
|
||||||
|
+ where
|
||||||
|
+ " (path: "
|
||||||
|
+ visiting.join(" -> ")
|
||||||
|
+ " -> "
|
||||||
|
+ id
|
||||||
|
+ ")",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw = p.raw
|
||||||
|
let side = p.side
|
||||||
|
let is-ew = side in ("east", "west")
|
||||||
|
|
||||||
|
let has-align = "align-with" in raw
|
||||||
|
let has-x = "x" in raw
|
||||||
|
let has-y = "y" in raw
|
||||||
|
let has-num = has-x or has-y
|
||||||
|
|
||||||
|
if has-align and has-num {
|
||||||
|
panic("Port '" + id + "'" + where + ": cannot combine `align-with` with `x`/`y` override")
|
||||||
|
}
|
||||||
|
if is-ew and has-x {
|
||||||
|
panic(
|
||||||
|
"Port '"
|
||||||
|
+ id
|
||||||
|
+ "'"
|
||||||
|
+ where
|
||||||
|
+ " on side '"
|
||||||
|
+ side
|
||||||
|
+ "': use `y` for placement override on east/west sides, not `x`",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (not is-ew) and has-y {
|
||||||
|
panic(
|
||||||
|
"Port '"
|
||||||
|
+ id
|
||||||
|
+ "'"
|
||||||
|
+ where
|
||||||
|
+ " on side '"
|
||||||
|
+ side
|
||||||
|
+ "': use `x` for placement override on north/south sides, not `y`",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = none
|
||||||
|
if has-num {
|
||||||
|
let val = if is-ew { raw.y } else { raw.x }
|
||||||
|
let dim = if is-ew { p.height } else { p.width }
|
||||||
|
if type(val) == function {
|
||||||
|
val = val(dim)
|
||||||
|
}
|
||||||
|
let pct
|
||||||
|
if type(val) == ratio {
|
||||||
|
pct = val
|
||||||
|
} else {
|
||||||
|
// Numeric length: measured from `pt0` along the side.
|
||||||
|
pct = (val / dim) * 100%
|
||||||
|
}
|
||||||
|
result = (pct: pct)
|
||||||
|
} else if has-align {
|
||||||
|
let classified = classify-align-with(raw.at("align-with"), placements)
|
||||||
|
if "external" in classified {
|
||||||
|
// Cross-element / raw anchor reference: axis cannot be validated here
|
||||||
|
// (remote side unknown at eval time). Propagate as external.
|
||||||
|
result = (external: classified.external)
|
||||||
|
} else {
|
||||||
|
let ref-id = classified.local
|
||||||
|
let ref-side = placements.at(ref-id).side
|
||||||
|
let ref-ew = ref-side in ("east", "west")
|
||||||
|
if is-ew != ref-ew {
|
||||||
|
panic(
|
||||||
|
"Port '"
|
||||||
|
+ id
|
||||||
|
+ "'"
|
||||||
|
+ where
|
||||||
|
+ " on '"
|
||||||
|
+ side
|
||||||
|
+ "' cannot `align-with` port '"
|
||||||
|
+ ref-id
|
||||||
|
+ "' on '"
|
||||||
|
+ ref-side
|
||||||
|
+ "': incompatible axis",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let new-visiting = visiting + (id,)
|
||||||
|
let res = resolve-placement(placements, ref-id, new-visiting, elmt-id: elmt-id)
|
||||||
|
placements = res.at(0)
|
||||||
|
result = res.at(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = (pct: p.pct-auto)
|
||||||
|
}
|
||||||
|
|
||||||
|
if "external" in result {
|
||||||
|
p.insert("external-ref", result.external)
|
||||||
|
p.insert("pct", none)
|
||||||
|
} else {
|
||||||
|
p.insert("pct", result.pct)
|
||||||
|
}
|
||||||
|
p.insert("resolved", true)
|
||||||
|
placements.insert(id, p)
|
||||||
|
return (placements, result)
|
||||||
|
}
|
||||||
|
|
||||||
#let add-ports(
|
#let add-ports(
|
||||||
elmt-id,
|
elmt-id,
|
||||||
tl, tr, br, bl,
|
tl,
|
||||||
|
tr,
|
||||||
|
br,
|
||||||
|
bl,
|
||||||
ports,
|
ports,
|
||||||
ports-margins,
|
ports-margins,
|
||||||
debug: false
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
debug: false,
|
||||||
|
outside-name: false,
|
||||||
|
name-padding: 2pt,
|
||||||
|
port-shape: none,
|
||||||
|
port-kind: "in",
|
||||||
|
port-size: 1em,
|
||||||
|
elmt-stroke: black + 1pt,
|
||||||
|
elmt-fill: none,
|
||||||
|
wire-inside: false,
|
||||||
) = {
|
) = {
|
||||||
let sides = (
|
let sides = (
|
||||||
"north": (tl, tr),
|
"north": (tl, tr),
|
||||||
"east": (tr, br),
|
"east": (tr, br),
|
||||||
"south": (bl, br),
|
"south": (bl, br),
|
||||||
"west": (tl, bl)
|
"west": (tl, bl),
|
||||||
)
|
)
|
||||||
|
|
||||||
if type(ports) != dictionary {
|
if type(ports) != dictionary {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Phase 1: build placement table with per-port baseline pct. ----
|
||||||
|
let placements = (:)
|
||||||
|
// Preserve iteration order for phase 3.
|
||||||
|
let order = ()
|
||||||
|
// Per-side (pt0, pt1) after margin adjustment, and per-side spacing.
|
||||||
|
let side-info = (:)
|
||||||
|
|
||||||
for (side, props) in sides {
|
for (side, props) in sides {
|
||||||
let side-ports = ports.at(side, default: ())
|
let side-ports = ports.at(side, default: ())
|
||||||
|
let pt0 = props.at(0)
|
||||||
|
let pt1 = props.at(1)
|
||||||
|
|
||||||
|
if side in ports-margins {
|
||||||
|
let margins = ports-margins.at(side)
|
||||||
|
let a = (pt0, margins.at(0), pt1)
|
||||||
|
let b = (pt0, 100% - margins.at(1), pt1)
|
||||||
|
pt0 = a
|
||||||
|
pt1 = b
|
||||||
|
}
|
||||||
|
|
||||||
let space = 100% / (side-ports.len() + 1)
|
let space = 100% / (side-ports.len() + 1)
|
||||||
|
side-info.insert(side, (pt0: pt0, pt1: pt1, space: space))
|
||||||
|
|
||||||
for (i, port) in side-ports.enumerate() {
|
for (i, port) in side-ports.enumerate() {
|
||||||
let pct = (i + 1) * space
|
let pid = port.at("id")
|
||||||
let pt0 = props.at(0)
|
if pid in placements {
|
||||||
let pt1 = props.at(1)
|
panic(
|
||||||
|
"Duplicate port id '"
|
||||||
if side in ports-margins {
|
+ pid
|
||||||
let (a, b) = (pt0, pt1)
|
+ "' in element '"
|
||||||
let margins = ports-margins.at(side)
|
+ elmt-id
|
||||||
a = (pt0, margins.at(0), pt1)
|
+ "' on side '"
|
||||||
b = (pt0, 100% - margins.at(1), pt1)
|
+ side
|
||||||
pt0 = a
|
+ "' (existing side: '"
|
||||||
pt1 = b
|
+ placements.at(pid).side
|
||||||
|
+ "', new port: "
|
||||||
|
+ repr(port)
|
||||||
|
+ ")",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
let pct-auto = (i + 1) * space
|
||||||
let pos = (pt0, pct, pt1)
|
placements.insert(pid, (
|
||||||
let pct-prev = (i + 0.5) * space
|
side: side,
|
||||||
let pct-next = (i + 1.5) * space
|
raw: port,
|
||||||
let pos-prev = (pt0, pct-prev, pt1)
|
index: i,
|
||||||
let pos-next = (pt0, pct-next, pt1)
|
pct-auto: pct-auto,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
resolved: false,
|
||||||
|
))
|
||||||
|
order.push(pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Phase 2: resolve overrides & align-with references. ----
|
||||||
|
for pid in order {
|
||||||
|
let res = resolve-placement(placements, pid, (), elmt-id: elmt-id)
|
||||||
|
placements = res.at(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Phase 3: draw. ----
|
||||||
|
for pid in order {
|
||||||
|
let p = placements.at(pid)
|
||||||
|
let side = p.side
|
||||||
|
let info = side-info.at(side)
|
||||||
|
let pt0 = info.pt0
|
||||||
|
let pt1 = info.pt1
|
||||||
|
let space = info.space
|
||||||
|
let port = p.raw
|
||||||
|
let is-ew = side in ("east", "west")
|
||||||
|
|
||||||
|
let pos
|
||||||
|
let pos-prev
|
||||||
|
let pos-next
|
||||||
|
|
||||||
|
if "external-ref" in p {
|
||||||
|
// Cross-element alignment: build pos via CeTZ perpendicular so this
|
||||||
|
// port shares one coordinate with the remote anchor and stays on this
|
||||||
|
// element's side line for the other coordinate.
|
||||||
|
// east/west: same y as remote, x from this side (pt0.x == pt1.x)
|
||||||
|
// north/south: same x as remote, y from this side (pt0.y == pt1.y)
|
||||||
|
let ref = p.external-ref
|
||||||
|
if is-ew {
|
||||||
|
pos = (pt0, "|-", ref)
|
||||||
|
// Fallback prev/next: just the pos itself; clock ports do not
|
||||||
|
// combine meaningfully with cross-element align.
|
||||||
|
pos-prev = pos
|
||||||
|
pos-next = pos
|
||||||
|
} else {
|
||||||
|
pos = (ref, "|-", pt0)
|
||||||
|
pos-prev = pos
|
||||||
|
pos-next = pos
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let pct = p.pct
|
||||||
|
pos = (pt0, pct, pt1)
|
||||||
|
pos-prev = (pt0, pct - 0.5 * space, pt1)
|
||||||
|
pos-next = (pt0, pct + 0.5 * space, pt1)
|
||||||
|
|
||||||
if port.at("small", default: false) {
|
if port.at("small", default: false) {
|
||||||
pos-prev = (pos, 4pt, pt0)
|
pos-prev = (pos, 4pt, pt0)
|
||||||
pos-next = (pos, 4pt, pt1)
|
pos-next = (pos, 4pt, pt1)
|
||||||
}
|
}
|
||||||
|
|
||||||
add-port(
|
|
||||||
elmt-id,
|
|
||||||
side,
|
|
||||||
port,
|
|
||||||
pos,
|
|
||||||
prev: pos-prev,
|
|
||||||
next: pos-next,
|
|
||||||
debug: debug
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add-port(
|
||||||
|
elmt-id,
|
||||||
|
side,
|
||||||
|
port,
|
||||||
|
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,
|
||||||
|
wire-inside: wire-inside,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
green: rgb(127, 200, 172),
|
green: rgb(127, 200, 172),
|
||||||
pink: rgb(236, 127, 178),
|
pink: rgb(236, 127, 178),
|
||||||
purple: rgb(189, 151, 255),
|
purple: rgb(189, 151, 255),
|
||||||
blue: rgb(127, 203, 235)
|
blue: rgb(127, 203, 235),
|
||||||
)
|
)
|
||||||
|
|
||||||
/// Pads a string on the left with 0s to the given length
|
/// Pads a string on the left with 0s to the given length
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
north-west: "south-east",
|
north-west: "south-east",
|
||||||
north-east: "south-west",
|
north-east: "south-west",
|
||||||
south-east: "north-west",
|
south-east: "north-west",
|
||||||
south-west: "north-east"
|
south-west: "north-east",
|
||||||
).at(anchor)
|
).at(anchor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,11 +66,140 @@
|
|||||||
north-west: "north-east",
|
north-west: "north-east",
|
||||||
north-east: "south-east",
|
north-east: "south-east",
|
||||||
south-east: "south-west",
|
south-east: "south-west",
|
||||||
south-west: "north-west"
|
south-west: "north-west",
|
||||||
).at(anchor)
|
).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) }
|
||||||
|
|
||||||
|
// World-cardinal midpoints of the pentagon's bounding box. These are
|
||||||
|
// named in *world* coordinates (not rotated with the shape), so `.east`
|
||||||
|
// always sits at the world +x extent of the pentagon, `.north` at +y, etc.
|
||||||
|
// Consequently the tip's cardinal name follows its world direction:
|
||||||
|
// - west side, kind "in" → tip world +x → tip = .east
|
||||||
|
// - west side, kind "out" → tip world -x → tip = .west
|
||||||
|
// - east side, kind "in" → tip world -x → tip = .west
|
||||||
|
// - east side, kind "out" → tip world +x → tip = .east
|
||||||
|
// - north side, kind "in" → tip world -y → tip = .south
|
||||||
|
// - north side, kind "out" → tip world +y → tip = .north
|
||||||
|
// - south side, kind "in" → tip world +y → tip = .north
|
||||||
|
// - south side, kind "out" → tip world -y → tip = .south
|
||||||
|
//
|
||||||
|
// The pentagon bounding box extents depend only on the side's orientation
|
||||||
|
// (horizontal for west/east, vertical for north/south), not on `kind`.
|
||||||
|
let (hx, hy) = if side == "west" or side == "east" {
|
||||||
|
(u, u / 2)
|
||||||
|
} else {
|
||||||
|
(u / 2, u)
|
||||||
|
}
|
||||||
|
let world-east = (hx, 0)
|
||||||
|
let world-west = (-hx, 0)
|
||||||
|
let world-north = (0, hy)
|
||||||
|
let world-south = (0, -hy)
|
||||||
|
|
||||||
|
return (
|
||||||
|
vertices: base-vertices.map(rot),
|
||||||
|
outer: rot(base-outer),
|
||||||
|
inner: rot(base-inner),
|
||||||
|
east: world-east,
|
||||||
|
west: world-west,
|
||||||
|
north: world-north,
|
||||||
|
south: world-south,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#let valid-anchors = (
|
#let valid-anchors = (
|
||||||
"center", "north", "east", "west", "south",
|
"center",
|
||||||
"north-east", "north-west", "south-east", "south-west"
|
"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
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#import "/src/cetz.typ": draw, coordinate
|
#import "/src/cetz.typ": draw, coordinate
|
||||||
#import "util.typ": opposite-anchor
|
#import "util.typ": opposite-anchor, normalize-coord
|
||||||
|
|
||||||
/// List of valid wire styles
|
/// List of valid wire styles
|
||||||
/// #examples.wires
|
/// #examples.wires
|
||||||
@@ -68,8 +68,8 @@
|
|||||||
|
|
||||||
let (ctx, p0) = coordinate.resolve(ctx, start)
|
let (ctx, p0) = coordinate.resolve(ctx, start)
|
||||||
let (ctx, p3) = coordinate.resolve(ctx, end)
|
let (ctx, p3) = coordinate.resolve(ctx, end)
|
||||||
p0 = (x: p0.first(), y: p0.last())
|
p0 = (x: p0.first(), y: p0.at(1))
|
||||||
p3 = (x: p3.first(), y: p3.last())
|
p3 = (x: p3.first(), y: p3.at(1))
|
||||||
|
|
||||||
let dx1 = margin-start
|
let dx1 = margin-start
|
||||||
let dx2 = margin-end
|
let dx2 = margin-end
|
||||||
@@ -86,13 +86,13 @@
|
|||||||
if side-end == "east" {
|
if side-end == "east" {
|
||||||
dx2 *= -1
|
dx2 *= -1
|
||||||
}
|
}
|
||||||
p1 = (p0.x + dx1, p0.y)
|
p1 = (p0.x + dx1, p0.y, 0)
|
||||||
p2 = (p3.x - dx2, p0.y)
|
p2 = (p3.x - dx2, p0.y, 0)
|
||||||
|
|
||||||
let points = (
|
let points = (
|
||||||
start,
|
start,
|
||||||
(horizontal: p1, vertical: ()),
|
(horizontal: p1, vertical: ()),
|
||||||
(horizontal: (), vertical: (0, dodge-y)),
|
(horizontal: (), vertical: (0, dodge-y, 0)),
|
||||||
(horizontal: p2, vertical: ()),
|
(horizontal: p2, vertical: ()),
|
||||||
(horizontal: (), vertical: end),
|
(horizontal: (), vertical: end),
|
||||||
end
|
end
|
||||||
@@ -147,18 +147,18 @@
|
|||||||
|
|
||||||
|
|
||||||
// points that are closest to the edge points
|
// points that are closest to the edge points
|
||||||
let p1 = (p0.x + dx1, p0.y + dy1)
|
let p1 = (p0.x + dx1, p0.y + dy1, 0)
|
||||||
let p5 = (p6.x + dx2, p6.y + dy2)
|
let p5 = (p6.x + dx2, p6.y + dy2, 0)
|
||||||
|
|
||||||
|
|
||||||
// middle point
|
// middle point
|
||||||
let center_x = p0.x + box_width * center_horizontal / 100%
|
let center_x = p0.x + box_width * center_horizontal / 100%
|
||||||
let center_y = p0.y + box_height * center_vertical / 100%
|
let center_y = p0.y + box_height * center_vertical / 100%
|
||||||
let p3 = (center_x, center_y)
|
let p3 = (center_x, center_y, 0)
|
||||||
|
|
||||||
// setting up the points for that touch the guides
|
// setting up the points for that touch the guides
|
||||||
let p2 = (0,0)
|
let p2 = (0, 0, 0)
|
||||||
let p4 = (0,0)
|
let p4 = (0, 0, 0)
|
||||||
if side-start in ("north", "south") {
|
if side-start in ("north", "south") {
|
||||||
p2 = (horizontal: p3, vertical: p1)
|
p2 = (horizontal: p3, vertical: p1)
|
||||||
} else {
|
} else {
|
||||||
@@ -240,6 +240,9 @@
|
|||||||
if pts.len() != 2 {
|
if pts.len() != 2 {
|
||||||
panic("Wrong number of points (got " + str(pts.len()) + " instead of 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 = (
|
let stroke = (
|
||||||
paint: color,
|
paint: color,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "circuiteria"
|
name = "circuiteria"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
compiler = "0.13.1"
|
compiler = "0.15.0"
|
||||||
repository = "https://git.kb28.ch/HEL/circuiteria"
|
repository = "https://git.kb28.ch/HEL/circuiteria"
|
||||||
entrypoint = "src/lib.typ"
|
entrypoint = "src/lib.typ"
|
||||||
authors = [
|
authors = [
|
||||||
|
|||||||