Skip to content

Commit

Permalink
canvas: Use new curve API (Typst 0.13) (#814)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
johannes-wolf and JL710 authored Feb 24, 2025
1 parent b9aac9a commit 2ef2efd
Show file tree
Hide file tree
Showing 20 changed files with 147 additions and 168 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ jobs:
with:
crate: just
- name: Install tytanic
uses: baptiste0928/cargo-install@v2.2.0
uses: baptiste0928/cargo-install@v3
with:
crate: tytanic
git: https://github.com/tingerrr/tytanic.git
- uses: typst-community/setup-typst@v3
with:
typst-version: '0.12.0'
typst-version: '0.13.0'
cache-dependency-path: src/deps.typ
- run: |
just install @local
Expand Down
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 0.3.3
- Require Typst 0.13.0, port test cases over to Tytanic
- Add note about contents `auto-scale` feature
- Various performance improvements
- Make use of the new `curve` API

# 0.3.2

- Added a new `polygon` element for drawing regular polygons
Expand Down
60 changes: 35 additions & 25 deletions src/canvas.typ
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
transform:
((1, 0,-.5, 0),
(0,-1,+.5, 0),
(0, 0, .0, 0),
(0, 0, 0, 0), // FIXME: This should not be zero for Z! Changing it destroys mark & decorations in 3D space.
(0, 0, .0, 1)),
// Nodes, stores anchors and paths
nodes: (:),
Expand Down Expand Up @@ -76,6 +76,8 @@
let padding = util.as-padding-dict(padding)
bounds = aabb.padded(bounds, padding)

let (offset-x, offset-y, ..) = bounds.low

// Final canvas size
let (width, height, ..) = vector.scale(aabb.size(bounds), length)

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

place(top + left, float: false, if drawable.type == "path" {
let vertices = ()
for ((kind, ..pts)) in drawable.segments {
pts = pts.map(c => {
((c.at(0) - bounds.low.at(0) - x) * length,
(c.at(1) - bounds.low.at(1) - y) * length)
})
assert(
kind in ("line", "cubic"),
message: "Path segments must be of type line, cubic")

if kind == "cubic" {
let a = pts.at(0)
let b = pts.at(1)
let ctrla = relative(a, pts.at(2))
let ctrlb = relative(b, pts.at(3))

vertices.push((a, (0pt, 0pt), ctrla))
vertices.push((b, ctrlb, (0pt, 0pt)))

let transform-point((x, y, _)) = {
((x - offset-x - segment-x) * length,
(y - offset-y - segment-y) * length)
}

for ((kind, ..rest)) in drawable.segments {
if kind == "sub" {
// TODO: Support sub-paths by converting
// Also support move commands.
// Refactor path arrays to typst style curves.
} else if kind == "cubic" {
let pts = rest.map(transform-point)

vertices.push(curve.move(pts.at(0)))
vertices.push(curve.cubic(pts.at(2), pts.at(3), pts.at(1)))
} else {
vertices += pts
let pts = rest.map(transform-point)

vertices.push(curve.move(pts.at(0)))
for i in range(1, pts.len()) {
vertices.push(curve.line(pts.at(i)))
}
}
}

if (drawable.at("close", default: false)) {
vertices.push(curve.close(mode: "straight"))
}

if type(drawable.stroke) == dictionary and "thickness" in drawable.stroke and type(drawable.stroke.thickness) != std.length {
drawable.stroke.thickness *= length
}
path(
std.curve(
stroke: drawable.stroke,
fill: drawable.fill,
fill-rule: drawable.at("fill-rule", default: "non-zero"),
closed: drawable.at("close", default: false),
..vertices,
)
} else if drawable.type == "content" {
let (width, height) = std.measure(drawable.body)
move(
dx: (drawable.pos.at(0) - bounds.low.at(0)) * length - width / 2,
dy: (drawable.pos.at(1) - bounds.low.at(1)) * length - height / 2,
dx: (drawable.pos.at(0) - offset-x) * length - width / 2,
dy: (drawable.pos.at(1) - offset-y) * length - height / 2,
drawable.body,
)
}, dx: x * length, dy: y * length)
}, dx: segment-x * length, dy: segment-y * length)
}
}))
})}
2 changes: 1 addition & 1 deletion src/coordinate.typ
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
// dictionary of numbers
return vector.scale(
c.bary.pairs().fold(
vector.new(3),
(0, 0, 0),
(vec, (k, v)) => {
vector.add(
vec,
Expand Down
2 changes: 2 additions & 0 deletions src/draw/transformations.typ
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@
/// circle((0,0))
/// ```
///
/// Note that content like text does not scale automatically. See `auto-scale` styling of content for that.
///
/// - ..args (float, ratio): A single value to scale the transformation matrix by or per axis
/// scaling factors. Accepts a single float or ratio value or any combination of the named arguments
/// `x`, `y` and `z` to set per axis scaling factors. A ratio of 100% is the same as the value $1$.
Expand Down
22 changes: 12 additions & 10 deletions src/matrix.typ
Original file line number Diff line number Diff line change
Expand Up @@ -248,15 +248,17 @@
/// -> vector
#let mul4x4-vec3(mat, vec, w: 1) = {
assert(vec.len() <= 4)
let out = (0, 0, 0)
for m in range(0, 3) {
let v = (mat.at(m).at(0) * vec.at(0, default: 0)
+ mat.at(m).at(1) * vec.at(1, default: 0)
+ mat.at(m).at(2) * vec.at(2, default: 0)
+ mat.at(m).at(3) * vec.at(3, default: w))
out.at(m) = v
}
return out

let x = vec.at(0)
let y = vec.at(1)
let z = vec.at(2, default: 0)
let w = vec.at(3, default: w)

let ((a1,a2,a3,a4), (b1,b2,b3,b4), (c1,c2,c3,c4), _) = mat
return (
a1 * x + a2 * y + a3 * z + a4 * w,
b1 * x + b2 * y + b3 * z + b4 * w,
c1 * x + c2 * y + c3 * z + c4 * w)
}

// Multiply matrix with vector
Expand Down Expand Up @@ -319,7 +321,7 @@
return inverted
}

/// Swaps the ath column with the bth column.
/// Swaps the a-th column with the b-th column.
///
/// - mat (matrix): Matrix
/// - a (int): The index of column a.
Expand Down
64 changes: 20 additions & 44 deletions src/vector.typ
Original file line number Diff line number Diff line change
@@ -1,25 +1,3 @@
/// Returns a new vector of dimension `dim` with all fields set to `init` (defaults to 0).
///
/// - dim (int): Vector dimension
/// - init (float): Initial value of all fields
/// -> vector
#let new(dim, init: 0) = {
return range(0, dim).map(x => init)
}

/// Returns the dimension of a vector.
///
/// - v (vector): The vector to find the dimension of.
/// -> int
#let dim(v) = {
assert(
type(v) == array,
message: "Expected vector to be of array type, got: " + repr(v)
)
return v.len()
}


/// Converts a vector to a row or column matrix.
///
/// - v (vector): The vector to convert.
Expand All @@ -35,13 +13,13 @@
}
}

/// 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`.
/// 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`.
///
/// - v (vector): The vector to ensure.
/// - init (vector): The vector to check the dimension against.
/// -> vector
#let as-vec(v, init: (0, 0, 0)) = {
for i in range(0, calc.min(dim(v), dim(init))) {
for i in range(0, calc.min(v.len(), init.len())) {
init.at(i) = v.at(i)
}
return init
Expand All @@ -62,12 +40,9 @@
/// - v2 (vector): The vector on the right hand side.
/// -> vector
#let add(v1, v2) = {
if dim(v1) != dim(v2) {
v1 = as-vec(v1)
v2 = as-vec(v2)
}
assert(dim(v1) == dim(v2), message: "Cannot add vectors, " + repr(v1) + " and " + repr(v2) + " are not of the same dimensions.")
return v1.zip(v2).map(((a, b)) => a + b)
range(0, calc.max(v1.len(), v2.len())).map(i => {
v1.at(i, default: 0) + v2.at(i, default: 0)
})
}

/// Subtracts two vectors of the same dimension
Expand All @@ -76,12 +51,9 @@
/// - v2 (vector): The vector on the right hand side.
/// -> vector
#let sub(v1, v2) = {
if dim(v1) != dim(v2) {
v1 = as-vec(v1)
v2 = as-vec(v2)
}
assert(dim(v1) == dim(v2), message: "Cannot subtract vectors, " + repr(v1) + " and " + repr(v2) + " are not of the same dimensions.")
return v1.zip(v2).map(((a, b)) => a - b)
range(0, calc.max(v1.len(), v2.len())).map(i => {
v1.at(i, default: 0) - v2.at(i, default: 0)
})
}

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

Expand All @@ -131,11 +103,14 @@
/// - v2 (vector): The vector on the right hand side.
/// -> vector
#let cross(v1, v2) = {
assert(dim(v1) == 3 and dim(v2) == 3)
let x = v1.at(1) * v2.at(2) - v1.at(2) * v2.at(1)
let y = v1.at(2) * v2.at(0) - v1.at(0) * v2.at(2)
let z = v1.at(0) * v2.at(1) - v1.at(1) * v2.at(0)
return (x, y, z)
assert(v1.len() == 3 and v2.len() == 3)

let (x1, y1, z1) = v1
let (x2, y2, z2) = v2

return (y1 * z2 - z1 * y2,
z1 * x2 - x1 * z2,
x1 * y2 - y1 * x2)
}

/// Calculates the angle between two vectors and the x-axis in 2d space
Expand All @@ -152,8 +127,9 @@
/// - c (vector): The vector to measure the angle at.
/// - v2 (vector): The vector to measure the angle to.
#let angle(v1, c, v2) = {
assert(dim(v1) == dim(v2), message: "Vectors " + repr(v1) + " and " + repr(v2) + " do not have the same dimensions.")
if dim(v1) == 2 or dim(v1) == 3 {
assert(v1.len() == v2.len(),
message: "Vectors " + repr(v1) + " and " + repr(v2) + " do not have the same dimensions.")
if v1.len() == 2 or v1.len() == 3 {
v1 = sub(v1, c)
v2 = sub(v2, c)
return calc.acos(dot(norm(v1), norm(v2)))
Expand Down
Binary file modified tests/angle/ref/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 2ef2efd

Please sign in to comment.