From 48e1ff39c36ac922a1b549210e7cb2058de9deb1 Mon Sep 17 00:00:00 2001 From: Johannes Wolf Date: Wed, 13 May 2026 15:08:19 +0200 Subject: [PATCH 1/3] Make Barycentric Coordinates Support Tuples --- src/coordinate.typ | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/coordinate.typ b/src/coordinate.typ index 03914ae0..718560f5 100644 --- a/src/coordinate.typ +++ b/src/coordinate.typ @@ -84,23 +84,25 @@ ) } -#let resolve-barycentric(ctx, inverse, c) = { - // dictionary of numbers +#let resolve-barycentric(resolve, ctx, inverse, c) = { + // (bary: (anchor: , ...)) or (bary: ((, ), ...)) + let pairs = if type(c.bary) == dictionary { + c.bary.pairs() + } else { + c.bary + } return vector.scale( - c.bary.pairs().fold( + pairs.fold( (0, 0, 0), (vec, (k, v)) => { - vector.add( - vec, - vector.scale( - resolve-anchor(ctx, inverse, k), - v - ) - ) - } - ), - 1 / c.bary.values().sum() - ) + let (_, pt) = resolve(ctx, k) + vector.add( + vec, + vector.scale(pt, v) + ) + }), + 1 / pairs.map(((k, v)) => v).sum() + ) } #let resolve-relative(resolve, ctx, c) = { @@ -404,7 +406,7 @@ if cached-inverse == none { cached-inverse = matrix.inverse(ctx.transform) } - resolve-barycentric(ctx, cached-inverse, c) + resolve-barycentric(resolve, ctx, cached-inverse, c) } else if t in ("element", "anchor") { if cached-inverse == none { cached-inverse = matrix.inverse(ctx.transform) From 219a4b291af3d70639b74d354424f6ee1d9355f7 Mon Sep 17 00:00:00 2001 From: Johannes Wolf Date: Wed, 13 May 2026 15:08:49 +0200 Subject: [PATCH 2/3] Add Coordinate System Documentation to PDF Manual --- manual.typ | 184 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) diff --git a/manual.typ b/manual.typ index 0e87bcd3..f1f4c19a 100644 --- a/manual.typ +++ b/manual.typ @@ -160,6 +160,190 @@ Instead its size will grow and shrink to fit the drawn graphic. By default $1$ coordinate unit is $1 "cm"$, this can be changed by setting the `length:` parameter. +== Coordinates +=== XYZ +Specifies a point as a multiple of the $x$, $y$, and $z$ vectors of the current +canvas' transformation. + +/ Syntax: + - `(x type:number, y type:number, z type:number = 0)` + - `(x: type:number = 0, y: type:number = 0, z: type:number = 0)` + +```example +// Short form: (x, y, [z]) +line((0, 0), (1, 0, 0)) + +// Dictionary form (x:, y:, z:) +line((x: 0cm, y: 0), (y: 1)) +``` + +=== Polar +Specifies a point in polar coordinates. + +/ Syntax: + - `(α type:angle, r type:number or (type:number, type:number))` + - `(angle: type:angle, radius: type:number or (type:number, type:number))` + +```example +circle((0, 0)) + +// Draw a line from (0, 0) to 30deg on the unit circle +line((0, 0), (30deg, 1), + mark: (start: ")>", end: ")>")) +``` + +=== Previous +You can access the previous coordinate by passing an empty array `()`. The initial +previous coordinate is at `(0, 0, 0)`. + +```example +// Draw a line to (1, 0) +line((), (1, 0)) + +// A dot at the previous coordinate, (1, 0, 0) +circle((), radius: 1pt, fill: black) +``` + +=== Relative +You can add two coordinates using the relative coordinate syntax. + +/ Syntax: + - (rel: type:coordinate) specifies a coordinate relative to the previous coordinate `()` + - (rel: type:coordinate, to: type:coordinat) specifies a coordinate relative to `to` by adding both vectors + +```example +anchor("a", (1,3)) + +set-style(radius: 1pt, fill: black, content: (padding: 0.8em)) +circle("a") +content((), [A], anchor: "north") + +circle((rel: (2, 1), to: "a")) +content((), `(rel: (2, 1), to: "a")`, anchor: "south") +``` + +=== Barycentric +In the barycentric coordinate system a point is expressed as the linear combination +of multiple vectors. The idea is that you specify vectors $v_1, v_2, ..., v_n$ +and numbers $alpha_1, alpha_2, ..., alpha_n$. Then the barycentric coordinate +specified by these vectors and numbers is +$(alpha_1 v_1 + alpha_2 v_2 + dots.c + alpha_n v_n)/(alpha_1 + alpha_2 + dots.c + alpha_n)$. + +/ Syntax: + - `(bary: (anchor: type:float, ...))`, where `anchor` is the name of an element or anchor. + - `(bary: ((type:coordinate, type:float), ...))` + +```example +anchor("a", (90deg, 2)) +anchor("b", (210deg, 2)) +anchor("c", (330deg, 2)) + +line("a", "b", "c", close: true) + +// Place points as a combination of a, b and c +set-style(radius: 1pt, fill: black, content: (padding: 0.8em)) +circle((bary: (a: 0, b: 0, c: 1)), name: "ca") +content("ca", [A], anchor: "west") + +circle((bary: (a: 0.5, b: 0.2, c: 0.3)), name: "cb") +content("cb", [B], anchor: "east") + +circle((bary: (("a", 0.8), ("b", 0.8), ("c", 0.8))), name: "cc") +content("cc", [C], anchor: "east") +``` + +=== Anchor +Defines a point relative to a named element using anchors. + +/ Syntax: + - `element type:string` gives the `"default"` anchor of element `element`, `"center"` for most elements + - `element.anchor type:string` gives the named anchor `anchor` of element `element` + - `element. type:string` gives the border anchor of `element` at angle `angle`, that is the coordinate at which a ray with angle `angle` intersects the elements path + - `element. type:string` gives the path anchor of `element` at distance `number`, that is the coordinat traveled along the path by distance `number` + - `(name: type:string, anchor: type:string or type:number or type:ratio or type:angle)` explicit, typed form + +```example +hobby((0, 0), (1, 2), (2, 1), (3, 3), name: "l") + +set-style(radius: 1pt, fill: black, content: (padding: 0.8em)) +// Access a named anchor +circle("l.end") +content((), `l.end`, anchor: "south") + +// Access a relative path anchor +circle("l.25%") +content((), `l.25%`, anchor: "south-east") + +// Access an absolute path anchor +circle((name: "l", anchor: 0.5cm)) +content((), `(name: "l", anchor: 0.5cm)`, anchor: "west") +``` + +```example +rect((0, 0), (2, 2), name: "r") + +set-style(radius: 1pt, fill: black, content: (padding: 0.8em)) +// Access the default anchor +circle("r") +content((), `r`, anchor: "south") + +// Access a border anchor +circle("r.30deg") +content((), `r.30deg`, anchor: "west") +``` + +=== Perpendicular + +=== Tangent + +=== Projection + +/ Syntax: + - `(p type:coordinate, "⟂", a type:coordinate, b type:coordinate)` + - `(p type:coordinate, "_|_", a type:coordinate, b type:coordinate)` + - `(project: type:coordinate, onto: (a type:coordinate, b type:coordinate))` + +```example +line((1, 1), (3, 2), name: "l") + +anchor("pt", (1.5, 2)) + +// Project "pt" onto the line from "l.start" to "l.end" +anchor("x", (project: "pt", onto: ("l.start", "l.end"))) + +set-style(radius: 1pt, fill: black, content: (padding: 0.8em)) +circle("pt") +content((), [pt], anchor: "south") +circle("x") +content((), [x], anchor: "north") + +line("pt", "x") +``` + +=== Callback/Function +Specifies a coordinate by evaluating a callback function with resolved +coordinates passed as arguments. + +/ Syntax: + - `(type:function, type:coordinate, ...)` an array of a funciton and zero or more coordinates + +Thet function gets called with $n$ vectors, where $n$ is the number of coordinates passed along with the function. + +```example +anchor("a", (1.2, 3.7)) + +set-style(radius: 1pt, fill: black, content: (padding: 0.8em)) +// Access the default anchor +circle("a") +content((), `A`, anchor: "north") + +// Pass a custom function that rounds each component of "a" +circle((a => { + a.map(calc.round) +}, "a")) +content((), `function`, anchor: "south") +``` + == Styling You can style draw elements by passing the relevant named arguments to their draw functions. All elements that draw something have From ad4520831adf0101c517b55563b7fb75c5c6695f Mon Sep 17 00:00:00 2001 From: Johannes Wolf Date: Wed, 13 May 2026 15:13:02 +0200 Subject: [PATCH 3/3] Update Changelog --- CHANGES.md | 1 + manual.typ | 6 +++--- src/coordinate.typ | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 206b537c..4973db01 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,7 @@ # 0.5.3 - Fixed mark `inset:` style getting applied twice (#1099) - Fixes `matrix.diag` function typo +- Barycentric coordinates now support contstruction using tuples of coordinates and floats/ratios # 0.5.1 - Added the `boolean` draw function for path boolean operations. diff --git a/manual.typ b/manual.typ index f1f4c19a..73a416e6 100644 --- a/manual.typ +++ b/manual.typ @@ -230,8 +230,8 @@ specified by these vectors and numbers is $(alpha_1 v_1 + alpha_2 v_2 + dots.c + alpha_n v_n)/(alpha_1 + alpha_2 + dots.c + alpha_n)$. / Syntax: - - `(bary: (anchor: type:float, ...))`, where `anchor` is the name of an element or anchor. - - `(bary: ((type:coordinate, type:float), ...))` + - `(bary: (anchor: type:float or type:ratio, ...))`, where `anchor` is the name of an element or anchor. + - `(bary: ((type:coordinate, type:float or type:ratio), ...))` ```example anchor("a", (90deg, 2)) @@ -245,7 +245,7 @@ set-style(radius: 1pt, fill: black, content: (padding: 0.8em)) circle((bary: (a: 0, b: 0, c: 1)), name: "ca") content("ca", [A], anchor: "west") -circle((bary: (a: 0.5, b: 0.2, c: 0.3)), name: "cb") +circle((bary: (a: 50%, b: 20%, c: 30%)), name: "cb") content("cb", [B], anchor: "east") circle((bary: (("a", 0.8), ("b", 0.8), ("c", 0.8))), name: "cc") diff --git a/src/coordinate.typ b/src/coordinate.typ index 718560f5..2e7c1c4f 100644 --- a/src/coordinate.typ +++ b/src/coordinate.typ @@ -85,7 +85,7 @@ } #let resolve-barycentric(resolve, ctx, inverse, c) = { - // (bary: (anchor: , ...)) or (bary: ((, ), ...)) + // (bary: (anchor: , ...)) or (bary: ((, ), ...)) let pairs = if type(c.bary) == dictionary { c.bary.pairs() } else { @@ -98,10 +98,10 @@ let (_, pt) = resolve(ctx, k) vector.add( vec, - vector.scale(pt, v) + vector.scale(pt, if type(v) == ratio { v / 100% } else { v }) ) }), - 1 / pairs.map(((k, v)) => v).sum() + 1 / pairs.map(((k, v)) => if type(v) == ratio { v / 100% } else { v }).sum() ) }