Add an optional pentagon shape for block and group ports, configured via the element-level `port-shape`, `port-kind` and `port-size` parameters. Individual ports can override them with `shape`, `kind`, `size`, `stroke` and `fill`. Clock ports with a pentagon shape drop the clock triangle and default to a gray fill. Wires attach to the outer end of the pentagon by default. With the new `shell` parameter on `element.elmt` they attach to the inner tip instead. Each port is exposed as a CeTZ group with a `default` anchor plus the cardinal sub-anchors `.east`, `.west`, `.north` and `.south` on the pentagon's bounding box, so wires can attach to any face. For non-pentagon ports the sub-anchors collapse to the default attach point, and the bare `<elmt-id>-port-<port-id>` anchor resolves as before. Add the `port-shape` and `port-shape-attach` examples and the `gallery/test_port_shape` gallery entry.
171 lines
6.5 KiB
Typst
171 lines
6.5 KiB
Typst
#import "/src/cetz.typ": coordinate, draw
|
|
#import "ports.typ": add-port, add-ports
|
|
#import "../util.typ"
|
|
|
|
#let find-port(ports, id) = {
|
|
for (side, side-ports) in ports {
|
|
for (i, port) in side-ports.enumerate() {
|
|
if port.id == id {
|
|
return (side, i)
|
|
}
|
|
}
|
|
}
|
|
panic("Could not find port with id " + str(id))
|
|
}
|
|
|
|
#let default-draw-shape(id, tl, tr, br, bl, fill, stroke) = {
|
|
return ({}, tl, tr, br, bl)
|
|
}
|
|
|
|
/// Draws an element
|
|
/// - draw-shape (function): Draw function
|
|
/// - x (number, dictionary): The x position (bottom-left corner).
|
|
///
|
|
/// 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 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
|
|
/// - name-anchor (str): Anchor for the optional name
|
|
/// - ports (dictionary): Dictionary of ports. The keys are cardinal directions ("north", "east", "south" and/or "west"). The values are arrays of ports (dictionaries) with the following fields:
|
|
/// - `id` (`str`): (Required) Port id
|
|
/// - `name` (`str`): Optional name displayed *in* the block
|
|
/// - `clock` (`bool`): Whether it is a clock port (triangle symbol)
|
|
/// - `vertical` (`bool`): Whether the name should be drawn vertically
|
|
/// - `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)
|
|
/// - port-name-padding (number, length): Padding between the port name label and the port anchor point (default `2pt`)
|
|
/// - 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
|
|
/// - stroke (stroke): Border stroke
|
|
/// - 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
|
|
/// - ports-y (dictionary): Dictionary of the ports y offsets (used with `auto-ports: false`)
|
|
/// - shell (bool): If true, wires attach to the *inside* tip of pentagon-shaped ports instead of the outside base (and, together with `port-names-outside: auto`, port names are drawn outside). Useful for containers that wrap child elements (default `false`)
|
|
/// - debug (dictionary): Dictionary of debug options.
|
|
///
|
|
/// Supported fields include:
|
|
/// - `ports`: if true, shows dots on all ports of the element
|
|
#let elmt(
|
|
draw-shape: default-draw-shape,
|
|
x: none,
|
|
y: none,
|
|
w: none,
|
|
h: none,
|
|
name: none,
|
|
name-anchor: "center",
|
|
ports: (:),
|
|
ports-margins: (:),
|
|
port-name-padding: 2pt,
|
|
port-shape: none,
|
|
port-kind: "in",
|
|
port-size: 1em,
|
|
fill: none,
|
|
stroke: black + 1pt,
|
|
id: "",
|
|
auto-ports: true,
|
|
ports-y: (:),
|
|
shell: false,
|
|
debug: (
|
|
ports: false,
|
|
),
|
|
) = 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) {
|
|
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) {
|
|
let from = y.from
|
|
let to = y.to
|
|
let (to-side, i) = find-port(ports, to)
|
|
let margins = (0%, 0%)
|
|
if to-side in ports-margins {
|
|
margins = ports-margins.at(to-side)
|
|
}
|
|
|
|
let dy
|
|
let top-margin
|
|
if to-side in ("east", "west") {
|
|
let used-pct = 100% - margins.at(0) - margins.at(1)
|
|
let used-height = height * used-pct / 100%
|
|
top-margin = height * margins.at(0) / 100%
|
|
|
|
dy = used-height * (i + 1) / (ports.at(to-side).len() + 1)
|
|
|
|
if not auto-ports {
|
|
top-margin = 0
|
|
dy = ports-y.at(to)(height)
|
|
}
|
|
} else if to-side == "north" {
|
|
dy = 0
|
|
top-margin = 0
|
|
} else if to-side == "south" {
|
|
dy = height
|
|
top-margin = 0
|
|
}
|
|
|
|
let (ctx, from-pos) = coordinate.resolve(ctx, from)
|
|
y = from-pos.at(1) + dy - height + top-margin
|
|
}
|
|
|
|
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)
|
|
func
|
|
|
|
if (name != none) {
|
|
draw.content(
|
|
(name: id, anchor: name-anchor),
|
|
anchor: if name-anchor in util.valid-anchors { name-anchor } else { "center" },
|
|
padding: 0.5em,
|
|
align(center)[*#name*],
|
|
)
|
|
}
|
|
|
|
if auto-ports {
|
|
add-ports(
|
|
id,
|
|
tl,
|
|
tr,
|
|
br,
|
|
bl,
|
|
ports,
|
|
ports-margins,
|
|
debug: debug.ports,
|
|
name-padding: port-name-padding,
|
|
port-shape: port-shape,
|
|
port-kind: port-kind,
|
|
port-size: port-size,
|
|
elmt-stroke: stroke,
|
|
elmt-fill: fill,
|
|
wire-inside: shell,
|
|
)
|
|
}
|
|
})
|