diff --git a/doc/examples.typ b/doc/examples.typ index a182e59..4bd0e3b 100644 --- a/doc/examples.typ +++ b/doc/examples.typ @@ -186,6 +186,45 @@ 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, + 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) +element.group( + id: "g2", name: "inside", + stroke: (dash: "dashed"), + fill: util.colors.orange.lighten(60%), + padding: 2em, + outside-port-names: false, + 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(``` wire.wire("w1", ((0, 0), (1, 1)), style: "zigzag") wire.wire("w2", ((0, 0), (1, -.5)), diff --git a/gallery/platypus.png b/gallery/platypus.png index 7e0a912..cd954d8 100644 Binary files a/gallery/platypus.png and b/gallery/platypus.png differ diff --git a/gallery/test.png b/gallery/test.png index c4e9480..2c5d0c6 100644 Binary files a/gallery/test.png and b/gallery/test.png differ diff --git a/gallery/test2.png b/gallery/test2.png index c4e9480..2c5d0c6 100644 Binary files a/gallery/test2.png and b/gallery/test2.png differ diff --git a/gallery/test3.png b/gallery/test3.png index 8af0ba5..ac6032a 100644 Binary files a/gallery/test3.png and b/gallery/test3.png differ diff --git a/gallery/test4.png b/gallery/test4.png index 4f36ffc..57ae67b 100644 Binary files a/gallery/test4.png and b/gallery/test4.png differ diff --git a/gallery/test5.png b/gallery/test5.png index e23a31f..ecb2eec 100644 Binary files a/gallery/test5.png and b/gallery/test5.png differ diff --git a/gallery/test6.png b/gallery/test6.png index 409663e..eb67604 100644 Binary files a/gallery/test6.png and b/gallery/test6.png differ diff --git a/gallery/test7.png b/gallery/test7.png index cfe3ee4..99d3505 100644 Binary files a/gallery/test7.png and b/gallery/test7.png differ diff --git a/gallery/test_group_ports.png b/gallery/test_group_ports.png new file mode 100644 index 0000000..a5de0b4 Binary files /dev/null and b/gallery/test_group_ports.png differ diff --git a/gallery/test_group_ports.typ b/gallery/test_group_ports.typ new file mode 100644 index 0000000..76c2399 --- /dev/null +++ b/gallery/test_group_ports.typ @@ -0,0 +1,84 @@ +// 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 OUTSIDE (default) --- + element.group( + id: "cpu", + name: "CPU (labels outside)", + name-anchor: "north", + stroke: (dash: "dashed"), + fill: util.colors.yellow.lighten(60%), + padding: 1em, + 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, + 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") +}) diff --git a/manual.pdf b/manual.pdf index f4e1052..9030b69 100644 Binary files a/manual.pdf and b/manual.pdf differ diff --git a/manual.typ b/manual.typ index ebe0c88..f1cfa11 100644 --- a/manual.typ +++ b/manual.typ @@ -1,4 +1,4 @@ -#import "@preview/tidy:0.4.1" +#import "@preview/tidy:0.4.3" #import "/src/cetz.typ": draw, canvas #import "src/lib.typ" #import "doc/examples.typ" diff --git a/src/cetz.typ b/src/cetz.typ index 980a619..adb796c 100644 --- a/src/cetz.typ +++ b/src/cetz.typ @@ -1 +1 @@ -#import "@preview/cetz:0.3.4": * \ No newline at end of file +#import "@preview/cetz:0.5.2": * \ No newline at end of file diff --git a/src/circuit.typ b/src/circuit.typ index f89389f..958fa6e 100644 --- a/src/circuit.typ +++ b/src/circuit.typ @@ -1,5 +1,5 @@ #import "/src/cetz.typ": canvas -#import "@preview/tidy:0.3.0" +#import "@preview/tidy:0.4.3" /// Draws a block circuit diagram /// diff --git a/src/elements/group.typ b/src/elements/group.typ index 994072c..b4b814f 100644 --- a/src/elements/group.typ +++ b/src/elements/group.typ @@ -1,9 +1,15 @@ #import "/src/cetz.typ": 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 @@ -16,6 +22,10 @@ /// - array: either (``,), (``, ``) or (``, ``, ``, ``) /// - 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 +/// - debug (dictionary): see #doc-ref("element.elmt") #let group( body, id: "", @@ -24,7 +34,13 @@ fill: none, stroke: black + 1pt, padding: 0.5em, - radius: 0.5em + radius: 0.5em, + ports: (:), + ports-margins: (:), + outside-port-names: true, + debug: ( + ports: false + ) ) = { let min-x = none let max-x = none @@ -68,6 +84,19 @@ 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, + ) + if name != none { draw.content( id + "." + name-anchor, diff --git a/src/elements/ports.typ b/src/elements/ports.typ index b24cdfc..4c7f585 100644 --- a/src/elements/ports.typ +++ b/src/elements/ports.typ @@ -1,11 +1,12 @@ #import "/src/cetz.typ": draw -#import "../util.typ": rotate-anchor +#import "../util.typ": rotate-anchor, opposite-anchor #let add-port( elmt-id, side, port, pos, prev: none, next: none, - debug: false + debug: false, + outside-name: false, ) = { let name = port.at("name", default: "") let name-rotate = port.at("vertical", default: false) @@ -27,9 +28,21 @@ // 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 } + } + draw.content( pos, - anchor: if name-rotate {rotate-anchor(side)} else {side}, + anchor: name-anchor, padding: 2pt, angle: if name-rotate {90deg} else {0deg}, name @@ -60,7 +73,8 @@ tl, tr, br, bl, ports, ports-margins, - debug: false + debug: false, + outside-name: false, ) = { let sides = ( "north": (tl, tr), @@ -109,7 +123,8 @@ pos, prev: pos-prev, next: pos-next, - debug: debug + debug: debug, + outside-name: outside-name, ) } } diff --git a/src/util.typ b/src/util.typ index a444434..56af6a6 100644 --- a/src/util.typ +++ b/src/util.typ @@ -73,4 +73,45 @@ #let valid-anchors = ( "center", "north", "east", "west", "south", "north-east", "north-west", "south-east", "south-west" -) \ No newline at end of file +) + +/// 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 +} \ No newline at end of file diff --git a/src/wire.typ b/src/wire.typ index cbf7c4b..f6fd158 100644 --- a/src/wire.typ +++ b/src/wire.typ @@ -1,5 +1,5 @@ #import "/src/cetz.typ": draw, coordinate -#import "util.typ": opposite-anchor +#import "util.typ": opposite-anchor, normalize-coord /// List of valid wire styles /// #examples.wires @@ -68,8 +68,8 @@ let (ctx, p0) = coordinate.resolve(ctx, start) let (ctx, p3) = coordinate.resolve(ctx, end) - p0 = (x: p0.first(), y: p0.last()) - p3 = (x: p3.first(), y: p3.last()) + p0 = (x: p0.first(), y: p0.at(1)) + p3 = (x: p3.first(), y: p3.at(1)) let dx1 = margin-start let dx2 = margin-end @@ -86,13 +86,13 @@ if side-end == "east" { dx2 *= -1 } - p1 = (p0.x + dx1, p0.y) - p2 = (p3.x - dx2, p0.y) + p1 = (p0.x + dx1, p0.y, 0) + p2 = (p3.x - dx2, p0.y, 0) let points = ( start, (horizontal: p1, vertical: ()), - (horizontal: (), vertical: (0, dodge-y)), + (horizontal: (), vertical: (0, dodge-y, 0)), (horizontal: p2, vertical: ()), (horizontal: (), vertical: end), end @@ -147,18 +147,18 @@ // points that are closest to the edge points - let p1 = (p0.x + dx1, p0.y + dy1) - let p5 = (p6.x + dx2, p6.y + dy2) + 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) + let p3 = (center_x, center_y, 0) // setting up the points for that touch the guides - let p2 = (0,0) - let p4 = (0,0) + let p2 = (0, 0, 0) + let p4 = (0, 0, 0) if side-start in ("north", "south") { p2 = (horizontal: p3, vertical: p1) } else { @@ -240,6 +240,9 @@ 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, diff --git a/typst.toml b/typst.toml index b83700a..fb35880 100644 --- a/typst.toml +++ b/typst.toml @@ -1,7 +1,7 @@ [package] name = "circuiteria" version = "0.2.1" -compiler = "0.13.1" +compiler = "0.15.0" repository = "https://git.kb28.ch/HEL/circuiteria" entrypoint = "src/lib.typ" authors = [