Skip to content

Commit 2ef2efd

Browse files
johannes-wolfJL710
andauthored
canvas: Use new curve API (Typst 0.13) (#814)
* canvas: Use new curve API * typst: Bump to 0.13.0-rc1 * tests: Fix tests & update refs * perf: Some performance improvements (#816) Gives ~2 seconds when running tests. * ci: Use typst 0.13.0 * add content auto-scale note for scale (#819) * changelog: Update changelog --------- Co-authored-by: JL710 <76447362+JL710@users.noreply.github.com>
1 parent b9aac9a commit 2ef2efd

File tree

20 files changed

+147
-168
lines changed

20 files changed

+147
-168
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ jobs:
2121
with:
2222
crate: just
2323
- name: Install tytanic
24-
uses: baptiste0928/cargo-install@v2.2.0
24+
uses: baptiste0928/cargo-install@v3
2525
with:
2626
crate: tytanic
27+
git: https://github.com/tingerrr/tytanic.git
2728
- uses: typst-community/setup-typst@v3
2829
with:
29-
typst-version: '0.12.0'
30+
typst-version: '0.13.0'
3031
cache-dependency-path: src/deps.typ
3132
- run: |
3233
just install @local

CHANGES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# 0.3.3
2+
- Require Typst 0.13.0, port test cases over to Tytanic
3+
- Add note about contents `auto-scale` feature
4+
- Various performance improvements
5+
- Make use of the new `curve` API
6+
17
# 0.3.2
28

39
- Added a new `polygon` element for drawing regular polygons

src/canvas.typ

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
transform:
4747
((1, 0,-.5, 0),
4848
(0,-1,+.5, 0),
49-
(0, 0, .0, 0),
49+
(0, 0, 0, 0), // FIXME: This should not be zero for Z! Changing it destroys mark & decorations in 3D space.
5050
(0, 0, .0, 1)),
5151
// Nodes, stores anchors and paths
5252
nodes: (:),
@@ -76,6 +76,8 @@
7676
let padding = util.as-padding-dict(padding)
7777
bounds = aabb.padded(bounds, padding)
7878

79+
let (offset-x, offset-y, ..) = bounds.low
80+
7981
// Final canvas size
8082
let (width, height, ..) = vector.scale(aabb.size(bounds), length)
8183

@@ -87,7 +89,7 @@
8789
for drawable in drawables {
8890
// Typst path elements have strange bounding boxes. We need to
8991
// offset all paths to start at (0, 0) to make gradients work.
90-
let (x, y, _) = if drawable.type == "path" {
92+
let (segment-x, segment-y, _) = if drawable.type == "path" {
9193
vector.sub(
9294
aabb.aabb(path-util.bounds(drawable.segments)).low,
9395
bounds.low)
@@ -97,45 +99,53 @@
9799

98100
place(top + left, float: false, if drawable.type == "path" {
99101
let vertices = ()
100-
for ((kind, ..pts)) in drawable.segments {
101-
pts = pts.map(c => {
102-
((c.at(0) - bounds.low.at(0) - x) * length,
103-
(c.at(1) - bounds.low.at(1) - y) * length)
104-
})
105-
assert(
106-
kind in ("line", "cubic"),
107-
message: "Path segments must be of type line, cubic")
108-
109-
if kind == "cubic" {
110-
let a = pts.at(0)
111-
let b = pts.at(1)
112-
let ctrla = relative(a, pts.at(2))
113-
let ctrlb = relative(b, pts.at(3))
114-
115-
vertices.push((a, (0pt, 0pt), ctrla))
116-
vertices.push((b, ctrlb, (0pt, 0pt)))
102+
103+
let transform-point((x, y, _)) = {
104+
((x - offset-x - segment-x) * length,
105+
(y - offset-y - segment-y) * length)
106+
}
107+
108+
for ((kind, ..rest)) in drawable.segments {
109+
if kind == "sub" {
110+
// TODO: Support sub-paths by converting
111+
// Also support move commands.
112+
// Refactor path arrays to typst style curves.
113+
} else if kind == "cubic" {
114+
let pts = rest.map(transform-point)
115+
116+
vertices.push(curve.move(pts.at(0)))
117+
vertices.push(curve.cubic(pts.at(2), pts.at(3), pts.at(1)))
117118
} else {
118-
vertices += pts
119+
let pts = rest.map(transform-point)
120+
121+
vertices.push(curve.move(pts.at(0)))
122+
for i in range(1, pts.len()) {
123+
vertices.push(curve.line(pts.at(i)))
124+
}
119125
}
120126
}
127+
128+
if (drawable.at("close", default: false)) {
129+
vertices.push(curve.close(mode: "straight"))
130+
}
131+
121132
if type(drawable.stroke) == dictionary and "thickness" in drawable.stroke and type(drawable.stroke.thickness) != std.length {
122133
drawable.stroke.thickness *= length
123134
}
124-
path(
135+
std.curve(
125136
stroke: drawable.stroke,
126137
fill: drawable.fill,
127138
fill-rule: drawable.at("fill-rule", default: "non-zero"),
128-
closed: drawable.at("close", default: false),
129139
..vertices,
130140
)
131141
} else if drawable.type == "content" {
132142
let (width, height) = std.measure(drawable.body)
133143
move(
134-
dx: (drawable.pos.at(0) - bounds.low.at(0)) * length - width / 2,
135-
dy: (drawable.pos.at(1) - bounds.low.at(1)) * length - height / 2,
144+
dx: (drawable.pos.at(0) - offset-x) * length - width / 2,
145+
dy: (drawable.pos.at(1) - offset-y) * length - height / 2,
136146
drawable.body,
137147
)
138-
}, dx: x * length, dy: y * length)
148+
}, dx: segment-x * length, dy: segment-y * length)
139149
}
140150
}))
141151
})}

src/coordinate.typ

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
// dictionary of numbers
9393
return vector.scale(
9494
c.bary.pairs().fold(
95-
vector.new(3),
95+
(0, 0, 0),
9696
(vec, (k, v)) => {
9797
vector.add(
9898
vec,

src/draw/transformations.typ

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@
148148
/// circle((0,0))
149149
/// ```
150150
///
151+
/// Note that content like text does not scale automatically. See `auto-scale` styling of content for that.
152+
///
151153
/// - ..args (float, ratio): A single value to scale the transformation matrix by or per axis
152154
/// scaling factors. Accepts a single float or ratio value or any combination of the named arguments
153155
/// `x`, `y` and `z` to set per axis scaling factors. A ratio of 100% is the same as the value $1$.

src/matrix.typ

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -248,15 +248,17 @@
248248
/// -> vector
249249
#let mul4x4-vec3(mat, vec, w: 1) = {
250250
assert(vec.len() <= 4)
251-
let out = (0, 0, 0)
252-
for m in range(0, 3) {
253-
let v = (mat.at(m).at(0) * vec.at(0, default: 0)
254-
+ mat.at(m).at(1) * vec.at(1, default: 0)
255-
+ mat.at(m).at(2) * vec.at(2, default: 0)
256-
+ mat.at(m).at(3) * vec.at(3, default: w))
257-
out.at(m) = v
258-
}
259-
return out
251+
252+
let x = vec.at(0)
253+
let y = vec.at(1)
254+
let z = vec.at(2, default: 0)
255+
let w = vec.at(3, default: w)
256+
257+
let ((a1,a2,a3,a4), (b1,b2,b3,b4), (c1,c2,c3,c4), _) = mat
258+
return (
259+
a1 * x + a2 * y + a3 * z + a4 * w,
260+
b1 * x + b2 * y + b3 * z + b4 * w,
261+
c1 * x + c2 * y + c3 * z + c4 * w)
260262
}
261263

262264
// Multiply matrix with vector
@@ -319,7 +321,7 @@
319321
return inverted
320322
}
321323

322-
/// Swaps the ath column with the bth column.
324+
/// Swaps the a-th column with the b-th column.
323325
///
324326
/// - mat (matrix): Matrix
325327
/// - a (int): The index of column a.

src/vector.typ

Lines changed: 20 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,3 @@
1-
/// Returns a new vector of dimension `dim` with all fields set to `init` (defaults to 0).
2-
///
3-
/// - dim (int): Vector dimension
4-
/// - init (float): Initial value of all fields
5-
/// -> vector
6-
#let new(dim, init: 0) = {
7-
return range(0, dim).map(x => init)
8-
}
9-
10-
/// Returns the dimension of a vector.
11-
///
12-
/// - v (vector): The vector to find the dimension of.
13-
/// -> int
14-
#let dim(v) = {
15-
assert(
16-
type(v) == array,
17-
message: "Expected vector to be of array type, got: " + repr(v)
18-
)
19-
return v.len()
20-
}
21-
22-
231
/// Converts a vector to a row or column matrix.
242
///
253
/// - v (vector): The vector to convert.
@@ -35,13 +13,13 @@
3513
}
3614
}
3715

38-
/// Ensures a vector has an exact dimension. This is done by passing another vector `init` that has the required dimension. If the original vector does not have enough dimensions, the values from `init` will be inserted. It is recommended to use a zero vector for `init`.
16+
/// Ensures a vector has an exact number of components. This is done by passing another vector `init` that has the required dimension. If the original vector does not have enough dimensions, the values from `init` will be inserted. It is recommended to use a zero vector for `init`.
3917
///
4018
/// - v (vector): The vector to ensure.
4119
/// - init (vector): The vector to check the dimension against.
4220
/// -> vector
4321
#let as-vec(v, init: (0, 0, 0)) = {
44-
for i in range(0, calc.min(dim(v), dim(init))) {
22+
for i in range(0, calc.min(v.len(), init.len())) {
4523
init.at(i) = v.at(i)
4624
}
4725
return init
@@ -62,12 +40,9 @@
6240
/// - v2 (vector): The vector on the right hand side.
6341
/// -> vector
6442
#let add(v1, v2) = {
65-
if dim(v1) != dim(v2) {
66-
v1 = as-vec(v1)
67-
v2 = as-vec(v2)
68-
}
69-
assert(dim(v1) == dim(v2), message: "Cannot add vectors, " + repr(v1) + " and " + repr(v2) + " are not of the same dimensions.")
70-
return v1.zip(v2).map(((a, b)) => a + b)
43+
range(0, calc.max(v1.len(), v2.len())).map(i => {
44+
v1.at(i, default: 0) + v2.at(i, default: 0)
45+
})
7146
}
7247

7348
/// Subtracts two vectors of the same dimension
@@ -76,12 +51,9 @@
7651
/// - v2 (vector): The vector on the right hand side.
7752
/// -> vector
7853
#let sub(v1, v2) = {
79-
if dim(v1) != dim(v2) {
80-
v1 = as-vec(v1)
81-
v2 = as-vec(v2)
82-
}
83-
assert(dim(v1) == dim(v2), message: "Cannot subtract vectors, " + repr(v1) + " and " + repr(v2) + " are not of the same dimensions.")
84-
return v1.zip(v2).map(((a, b)) => a - b)
54+
range(0, calc.max(v1.len(), v2.len())).map(i => {
55+
v1.at(i, default: 0) - v2.at(i, default: 0)
56+
})
8557
}
8658

8759
/// Calculates the distance between two vectors by subtracting the length of vector `a` from vector `b`.
@@ -122,7 +94,7 @@
12294
/// - v2 (vector): The vector on the right hand side.
12395
/// -> float
12496
#let dot(v1, v2) = {
125-
assert(dim(v1) == dim(v2))
97+
assert(v1.len() == v2.len())
12698
return v1.enumerate().fold(0, (s, t) => s + t.at(1) * v2.at(t.at(0)))
12799
}
128100

@@ -131,11 +103,14 @@
131103
/// - v2 (vector): The vector on the right hand side.
132104
/// -> vector
133105
#let cross(v1, v2) = {
134-
assert(dim(v1) == 3 and dim(v2) == 3)
135-
let x = v1.at(1) * v2.at(2) - v1.at(2) * v2.at(1)
136-
let y = v1.at(2) * v2.at(0) - v1.at(0) * v2.at(2)
137-
let z = v1.at(0) * v2.at(1) - v1.at(1) * v2.at(0)
138-
return (x, y, z)
106+
assert(v1.len() == 3 and v2.len() == 3)
107+
108+
let (x1, y1, z1) = v1
109+
let (x2, y2, z2) = v2
110+
111+
return (y1 * z2 - z1 * y2,
112+
z1 * x2 - x1 * z2,
113+
x1 * y2 - y1 * x2)
139114
}
140115

141116
/// Calculates the angle between two vectors and the x-axis in 2d space
@@ -152,8 +127,9 @@
152127
/// - c (vector): The vector to measure the angle at.
153128
/// - v2 (vector): The vector to measure the angle to.
154129
#let angle(v1, c, v2) = {
155-
assert(dim(v1) == dim(v2), message: "Vectors " + repr(v1) + " and " + repr(v2) + " do not have the same dimensions.")
156-
if dim(v1) == 2 or dim(v1) == 3 {
130+
assert(v1.len() == v2.len(),
131+
message: "Vectors " + repr(v1) + " and " + repr(v2) + " do not have the same dimensions.")
132+
if v1.len() == 2 or v1.len() == 3 {
157133
v1 = sub(v1, c)
158134
v2 = sub(v2, c)
159135
return calc.acos(dot(norm(v1), norm(v2)))

tests/angle/ref/1.png

26.7 KB
Loading

0 commit comments

Comments
 (0)