Compare commits

...
2 Commits
Author SHA1 Message Date
David Stadelmann 563bb7b1d2 feat: Add port-name-padding parameter
Replaces the hard-coded 2pt padding used when placing port name labels.
Configurable per element via port-name-padding on elmt (forwarded by
block and group), defaulting to 2pt to preserve existing layout.
2026-08-17 14:13:06 +02:00
David Stadelmann 0d1478c765 feat: Add ports on group elements
Groups now accept ports/ports-margins like blocks. Port name labels
default to outside the group border (outside-port-names: true) so they
don't overlap child elements; set to false for block-style inside
labels.
2026-08-17 14:12:50 +02:00
8 changed files with 7534 additions and 6769 deletions

No files matched your search

+42
View File
@@ -188,6 +188,48 @@ 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 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)),
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+86
View File
@@ -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")
})
+7343 -6761
View File
File diff suppressed because it is too large. Load diff
+2
View File
@@ -27,6 +27,7 @@
name-anchor: "center", name-anchor: "center",
ports: (), ports: (),
ports-margins: (), ports-margins: (),
port-name-padding: 2pt,
fill: none, fill: none,
radius: 0.5em, radius: 0.5em,
stroke: black + 1pt, stroke: black + 1pt,
@@ -44,6 +45,7 @@
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,
fill: fill, fill: fill,
stroke: stroke, stroke: stroke,
id: id, id: id,
+4 -1
View File
@@ -37,6 +37,7 @@
/// - `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
/// - 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`)
/// - 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)
@@ -56,6 +57,7 @@
name-anchor: "center", name-anchor: "center",
ports: (:), ports: (:),
ports-margins: (:), ports-margins: (:),
port-name-padding: 2pt,
fill: none, fill: none,
stroke: black + 1pt, stroke: black + 1pt,
id: "", id: "",
@@ -140,7 +142,8 @@
tl, tr, br, bl, tl, tr, br, bl,
ports, ports,
ports-margins, ports-margins,
debug: debug.ports debug: debug.ports,
name-padding: port-name-padding,
) )
} }
}) })
+33 -1
View File
@@ -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,11 @@
/// - 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)
/// - debug (dictionary): see #doc-ref("element.elmt")
#let group( #let group(
body, body,
id: "", id: "",
@@ -24,7 +35,14 @@
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,
debug: (
ports: false
)
) = { ) = {
let min-x = none let min-x = none
let max-x = none let max-x = none
@@ -68,6 +86,20 @@
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,
)
if name != none { if name != none {
draw.content( draw.content(
id + "." + name-anchor, id + "." + name-anchor,
+24 -6
View File
@@ -1,11 +1,13 @@
#import "/src/cetz.typ": draw #import "/src/cetz.typ": draw
#import "../util.typ": rotate-anchor #import "../util.typ": rotate-anchor, opposite-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,
) = { ) = {
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)
@@ -27,10 +29,22 @@
// 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 }
}
draw.content( draw.content(
pos, 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
) )
@@ -60,7 +74,9 @@
tl, tr, br, bl, tl, tr, br, bl,
ports, ports,
ports-margins, ports-margins,
debug: false debug: false,
outside-name: false,
name-padding: 2pt,
) = { ) = {
let sides = ( let sides = (
"north": (tl, tr), "north": (tl, tr),
@@ -109,7 +125,9 @@
pos, pos,
prev: pos-prev, prev: pos-prev,
next: pos-next, next: pos-next,
debug: debug debug: debug,
outside-name: outside-name,
name-padding: name-padding,
) )
} }
} }