Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

canvas: Use new curve API (Typst 0.13) #814

Merged
merged 7 commits into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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