diff --git a/CHANGES.md b/CHANGES.md
index 8d301aa75..3113ca363 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,9 @@
+# 0.5.3
+- Fixed mark `inset:` style getting applied twice (#1099)
+- Fixes `matrix.diag` function typo
+- Added a new `make-ctx` function for creating a new canvas context without a canvas
+- Fixed a bug with hobby and boolean rejecting two component vectors
+
# 0.5.1
- Added the `boolean` draw function for path boolean operations.
- Fixed border-anchor calculation for small rounded rects (#1083)
diff --git a/README.md b/README.md
index 6e7c604b3..e1e3e5d01 100644
--- a/README.md
+++ b/README.md
@@ -79,7 +79,7 @@ For information, see the [API Reference (PDF)](./manual.pdf?raw=true) or the [on
To use this package, simply add the following code to your document:
```typ
-#import "@preview/cetz:0.5.2"
+#import "@preview/cetz:0.5.3"
#cetz.canvas({
import cetz.draw: *
@@ -127,7 +127,7 @@ just install
The installed version can be imported by prefixing the package name with `@local`.
```typ
-#import "@local/cetz:0.5.2"
+#import "@local/cetz:0.5.3"
#cetz.canvas({
import cetz.draw: *
diff --git a/docs/basics/canvas.md b/docs/basics/canvas.md
index 1837b1b0d..e36bae91a 100644
--- a/docs/basics/canvas.md
+++ b/docs/basics/canvas.md
@@ -15,4 +15,4 @@ You can now call the draw functions within the body and they'll produce some gra
The canvas does not have typical `width` and `height` parameters. Instead its size will grow and shrink to fit the drawn graphic.
-By default 1 [coordinate](/basics/coordinate-systems) unit is `1cm`, this can be changed by setting the `length` parameter. If a ratio is given, the length will be the size of the canvas' parent's width!
+By default 1 [coordinate](/basics/coordinate-systems) unit is `1cm`, this can be changed by setting the `length` parameter.
diff --git a/docs/basics/coordinate-systems.mdx b/docs/basics/coordinate-systems.mdx
index 83f2444d3..3c46e34b8 100644
--- a/docs/basics/coordinate-systems.mdx
+++ b/docs/basics/coordinate-systems.mdx
@@ -7,6 +7,8 @@ import Type from "@site/src/components/Type";
A coordinate is a position on the canvas on which the picture is drawn. They take the form of dictionaries and the following subsections define the key-value pairs for each system. Some systems have a more implicit form as an array of values and CeTZ attempts to infer the system based on the element types.
+By default 1 coordinate unit is `1cm`, this can be changed by setting the `length` parameter of [`canvas`](/api/internal/canvas).
+
## XYZ
Defines a point `x` units right, `y` units upward and `z` units away.
diff --git a/docs/genhtml.py b/docs/genhtml.py
index 7f2f17385..8d1c0bf8d 100755
--- a/docs/genhtml.py
+++ b/docs/genhtml.py
@@ -10,7 +10,7 @@
from pathlib import Path
-DEFAULT_CETZ_VERSION="@preview/cetz:0.5.2"
+DEFAULT_CETZ_VERSION="@preview/cetz:0.5.3"
def typst_template(code, cetz_path):
diff --git a/docs/getting-started.mdx b/docs/getting-started.mdx
index 8031ae364..efeac7b46 100644
--- a/docs/getting-started.mdx
+++ b/docs/getting-started.mdx
@@ -8,7 +8,7 @@ sidebar_position: 1
This is the minimal starting point in a `.typ` file:
```typ
-#import "@preview/cetz:0.5.2"
+#import "@preview/cetz:0.5.3"
#cetz.canvas({
import cetz.draw: *
...
diff --git a/docs/tutorials/karl.mdx b/docs/tutorials/karl.mdx
index f9a619564..66e1a2d43 100644
--- a/docs/tutorials/karl.mdx
+++ b/docs/tutorials/karl.mdx
@@ -20,7 +20,7 @@ In CeTZ, to draw a picture, two imports and a function call is all you need. Kar
```typ
#set page(width: auto, height: auto)
-#import "@preview/cetz:0.5.2"
+#import "@preview/cetz:0.5.3"
We are working on
#cetz.canvas({
diff --git a/gallery/karls-picture.png b/gallery/karls-picture.png
index b3a602eb3..a9bfe226f 100644
Binary files a/gallery/karls-picture.png and b/gallery/karls-picture.png differ
diff --git a/gallery/karls-picture.typ b/gallery/karls-picture.typ
index b86771bb2..3525cf6ca 100644
--- a/gallery/karls-picture.typ
+++ b/gallery/karls-picture.typ
@@ -1,12 +1,15 @@
-#import "@preview/cetz:0.5.2"
+#import "@preview/cetz:0.5.3"
#set page(width: auto, height: auto, margin: .5cm)
-#show math.equation: block.with(fill: white, inset: 1pt)
+#show math.equation: block.with(fill: white.transparentize(30%), inset: 1pt, radius: 2pt)
// Create a new canvas to draw on
#cetz.canvas(length: 3cm, {
import cetz.draw: *
+ // Control the angle
+ let angle = 212deg
+
// Change the design for all elements after it
set-style(
// Design of arrow tips at the end of lines
@@ -18,14 +21,15 @@
radius: 0.3,
label-radius: .22,
fill: green.lighten(80%),
- stroke: (paint: green.darken(50%))
+ stroke: green.darken(50%)
),
// Design of all text elements with an anchor
content: (padding: 1pt)
)
- // Draws the grid behind the circle
- grid((-1.5, -1.5), (1.4, 1.4), step: 0.5, stroke: gray + 0.2pt)
+ // Draw the grids behind the circle
+ grid((-1.5, -1.5), (1.5, 1.5), step: 0.25, stroke: gray + 0.1pt)
+ grid((-1.5, -1.5), (1.5, 1.5), step: 0.5, stroke: gray + 0.2pt)
// Draw the unit circle
circle((0,0), radius: 1)
@@ -48,29 +52,35 @@
content((), anchor: "east", ct)
}
+ // Position on the unit circle (in polar coordinates)
+ let pos = (angle, 1)
+
// Draw the green angle
- cetz.angle.angle((0,0), (1,0), (1, calc.tan(30deg)),
- label: text(green, [#sym.alpha]))
+ cetz.angle.angle((0,0), (1,0), pos,
+ label: text(green, $alpha = angle.deg() degree$))
+
+ // Draw the hypotenuse of the triangle
+ line(pos, (1, calc.tan(angle)))
+ line(pos, (0, 0))
- // Draw the hypothenuse of the triangle
- line((0,0), (1, calc.tan(30deg)))
// Change the stroke for all upcoming elements
set-style(stroke: (thickness: 1.2pt))
// Draw the inner opposite leg of the triangle:
// "The intersection of a vertical line (|-) through (30deg, 1) and a horizontal line through (0, 0)"
- line((30deg, 1), ((), "|-", (0,0)), stroke: (paint: red), name: "sin")
+ line(pos, ((), "|-", (0,0)), stroke: red, name: "sin")
// Place the text halfway through on the opposite leg
content(("sin.start", 50%, "sin.end"), text(red)[$ sin alpha $])
-
+
// Draw the adjacent leg of the triangle
- line("sin.end", (0,0), stroke: (paint: blue), name: "cos")
+ line("sin.end", (0,0), stroke: blue, name: "cos")
// Place the text halfway and position it below the line
content(("cos.start", 50%, "cos.end"), text(blue)[$ cos alpha $], anchor: "north")
// Draw the outer opposite leg of the triangle
- line((1, 0), (1, calc.tan(30deg)), name: "tan", stroke: (paint: orange))
- // Draw the tangent equasion at the top and to the right of the line
+ line((1, 0), (1, calc.tan(angle)), name: "tan", stroke: orange)
+
+ // Draw the tangent equation at the top and to the right of the line
content("tan.end", $ text(#orange, tan alpha) = text(#red, sin alpha) / text(#blue, cos alpha) $, anchor: "west")
})
diff --git a/gallery/paciolis.typ b/gallery/paciolis.typ
index 812f68064..ada2de405 100644
--- a/gallery/paciolis.typ
+++ b/gallery/paciolis.typ
@@ -1,5 +1,5 @@
// Example by @samuelireson
-#import "@preview/cetz:0.5.2": canvas, draw
+#import "@preview/cetz:0.5.3": canvas, draw
#set page(width: auto, height: auto, margin: .5cm)
diff --git a/gallery/periodic-table.typ b/gallery/periodic-table.typ
index de6660da2..9eeff68ba 100644
--- a/gallery/periodic-table.typ
+++ b/gallery/periodic-table.typ
@@ -1,5 +1,5 @@
// Copied from https://github.com/janosh/tikz/blob/da1b4582/assets/periodic-table/periodic-table.typ
-#import "@preview/cetz:0.5.2": canvas, draw
+#import "@preview/cetz:0.5.3": canvas, draw
#set page(width: auto, height: auto, margin: 15pt)
diff --git a/gallery/plate-capacitor.typ b/gallery/plate-capacitor.typ
index 66aafb088..278bc7e00 100644
--- a/gallery/plate-capacitor.typ
+++ b/gallery/plate-capacitor.typ
@@ -1,5 +1,5 @@
// Copied from https://github.com/janosh/tikz/blob/87754ea/assets/plate-capacitor/plate-capacitor.typ
-#import "@preview/cetz:0.5.2": canvas, draw
+#import "@preview/cetz:0.5.3": canvas, draw
#import draw: line, rect, content, bezier, group, anchor
#set page(width: auto, height: auto, margin: 5pt)
diff --git a/gallery/torus.typ b/gallery/torus.typ
index ffba55e84..585e27c86 100644
--- a/gallery/torus.typ
+++ b/gallery/torus.typ
@@ -1,6 +1,6 @@
// From https://forum.typst.app/t/how-to-best-draw-a-3d-torus/4744/4
// Note: current settings use about 2 GiB of RAM and 20 s of compilation time.
-#import "@preview/cetz:0.5.2"
+#import "@preview/cetz:0.5.3"
#set page(width: auto, height: auto, margin: 0.6pt)
diff --git a/gallery/tree.typ b/gallery/tree.typ
index 9e9a3c317..235f2cf97 100644
--- a/gallery/tree.typ
+++ b/gallery/tree.typ
@@ -1,4 +1,4 @@
-#import "@preview/cetz:0.5.2": canvas, draw, tree
+#import "@preview/cetz:0.5.3": canvas, draw, tree
#set page(width: auto, height: auto, margin: .5cm)
diff --git a/gallery/waves.typ b/gallery/waves.typ
index 956a42af9..0895afcb6 100644
--- a/gallery/waves.typ
+++ b/gallery/waves.typ
@@ -1,4 +1,4 @@
-#import "@preview/cetz:0.5.2": canvas, draw
+#import "@preview/cetz:0.5.3": canvas, draw
#set page(width: auto, height: auto, margin: .5cm)
diff --git a/manual.typ b/manual.typ
index f50ce400e..0e87bcd3b 100644
--- a/manual.typ
+++ b/manual.typ
@@ -93,7 +93,7 @@ automatically resize. And remember: up is positive!
== Usage
This is the minimal starting point in a `.typ` file:
```typst
-#import "@preview/cetz:0.5.2"
+#import "@preview/cetz:0.5.3"
#cetz.canvas({
import cetz.draw: *
...
diff --git a/src/canvas.typ b/src/canvas.typ
index 84d685cf6..69e8afbd6 100644
--- a/src/canvas.typ
+++ b/src/canvas.typ
@@ -9,33 +9,25 @@
#import "process.typ"
#import "coordinate.typ"
-/// Sets up a canvas for drawing on.
+/// Returns an empty context object usable for calling cetz internal functions
+/// outsides a canvas.
///
-/// - length (length): Used to specify what 1 coordinate unit is. Note that ratios are no longer supported! You can wrap the canvas into a `layout(ly => canvas(length: ly.width * , ...))`.
-/// - baseline (none,number,coordinate): Specifies the coordinate to use as the baseline. Setting this the canvas behaves like a `box` element instead of a `block`.
-/// - body (none, array, element): A code block in which functions from the `draw` module have been called.
-/// - background (none, color): A color to be used for the background of the canvas.
-/// - stroke (none, stroke): Stroke style to apply to the canvas top-level element (box or block)
-/// - padding (none, number, array, dictionary) = none: How much padding to add to the canvas. `none` applies no padding. A number applies padding to all sides equally. A dictionary applies padding following Typst's `pad` function: https://typst.app/docs/reference/layout/pad/. An array follows CSS like padding: `(y, x)`, `(top, x, bottom)` or `(top, right, bottom, left)`.
+/// - length (length): Used to specify what 1 coordinate unit is.
/// - x (number, vector) = 1.0: Sets up the x vector of the coordinate system to `(x, 0, 0)` or to the given vector.
/// - y (number, vector) = 1.0: Sets up the y vector of the coordinate system to `(0, y, 0)` or to the given vector.
/// - z (number, vector) = 1.0: Sets up the z vector of the coordinate system to `(0, 0, z)` or to the given vector.
-/// - debug (bool): Shows the bounding boxes of each element when `true`.
-/// -> content
-#let canvas(length: 1cm, x: 1.0, y: 1.0, z: 1.0, baseline: none, debug: false, background: none, stroke: none, padding: none, body) = context {
- if body == none {
- return []
- }
- if type(body) != array {
- panic("Incorrect type for body: " + repr(type(body)))
- }
+/// -> context
+#let make-ctx(length: 1cm, x: 1.0, y: 1.0, z: 1.0) = {
if type(length) != std.length {
panic("Expected `length` to be of type length, got " + repr(length))
}
- let length = length.to-absolute()
- assert(length / 1cm != 0,
- message: "Canvas length must be != 0!")
+ let length = if length.em != 0 {
+ length.to-absolute()
+ } else {
+ length
+ }
+ assert(length / 1cm != 0, message: "Canvas length must be != 0!")
// Prepare the coordinate system
let resolve-number(x) = {
@@ -49,11 +41,11 @@
let y = (if type(y) != array { (0.0, y, 0.0) } else { y }).map(resolve-number)
let z = (if type(z) != array { (0.0, 0.0, z) } else { z }).map(resolve-number)
- let ctx = (
+ return (
version: version.version,
length: length,
- debug: debug,
- background: background,
+ debug: false,
+ background: none,
// Previous element position & bbox
prev: (pt: (0.0, 0.0, 0.0)),
style: styles.default,
@@ -78,6 +70,32 @@
// CeTZ itself does not use this dictionary for data.
shared-state: (:),
)
+}
+
+/// Sets up a canvas for drawing on.
+///
+/// - length (length): Used to specify what 1 coordinate unit is. Note that ratios are no longer supported! You can wrap the canvas into a `layout(ly => canvas(length: ly.width * , ...))`.
+/// - baseline (none,number,coordinate): Specifies the coordinate to use as the baseline. Setting this the canvas behaves like a `box` element instead of a `block`.
+/// - body (none, array, element): A code block in which functions from the `draw` module have been called.
+/// - background (none, color): A color to be used for the background of the canvas.
+/// - stroke (none, stroke): Stroke style to apply to the canvas top-level element (box or block)
+/// - padding (none, number, array, dictionary) = none: How much padding to add to the canvas. `none` applies no padding. A number applies padding to all sides equally. A dictionary applies padding following Typst's `pad` function: https://typst.app/docs/reference/layout/pad/. An array follows CSS like padding: `(y, x)`, `(top, x, bottom)` or `(top, right, bottom, left)`.
+/// - x (number, vector) = 1.0: Sets up the x vector of the coordinate system to `(x, 0, 0)` or to the given vector.
+/// - y (number, vector) = 1.0: Sets up the y vector of the coordinate system to `(0, y, 0)` or to the given vector.
+/// - z (number, vector) = 1.0: Sets up the z vector of the coordinate system to `(0, 0, z)` or to the given vector.
+/// - debug (bool): Shows the bounding boxes of each element when `true`.
+/// -> content
+#let canvas(length: 1cm, x: 1.0, y: 1.0, z: 1.0, baseline: none, debug: false, background: none, stroke: none, padding: none, body) = context {
+ if body == none {
+ return []
+ }
+ if type(body) != array {
+ panic("Incorrect type for body: " + repr(type(body)))
+ }
+
+ let ctx = make-ctx(length: length, x: x, y: y, z: z)
+ ctx.debug = debug
+ ctx.background = background
let (ctx, bounds, drawables) = process.many(ctx, body)
if bounds == none {
diff --git a/src/draw/boolean.typ b/src/draw/boolean.typ
index 44f54b6dc..d550720bc 100644
--- a/src/draw/boolean.typ
+++ b/src/draw/boolean.typ
@@ -75,12 +75,12 @@
// Projects a CeTZ 3D path to a 2D wire path, asserting all vertices share the
// same z-plane (within `tol`) and all subpaths are closed.
-#let _path3d-to-wire2d(path3d, tol: 1e-6) = {
+#let _path3d-to-wire2d(path3d, eps: 1e-6) = {
if path3d.len() == 0 {
return ((subpaths: ()), 0.0)
}
- let (z0, same-z) = path-util.same-z-plane(path3d, tol: tol)
+ let (z0, same-z) = path-util.same-z-plane(path3d, eps: eps)
assert(same-z, message: "boolean: all input vertices must lie in a single z-plane.")
let drop-z(v) = (v.at(0), v.at(1))
diff --git a/src/draw/projection.typ b/src/draw/projection.typ
index 4b4694400..17fe3b162 100644
--- a/src/draw/projection.typ
+++ b/src/draw/projection.typ
@@ -193,11 +193,14 @@
drawables = _sort-by-distance(drawables)
}
- ctx.transform = transform
if perspective-mode {
- drawable.apply-transform(projection-matrix, drawables)
+ drawables = drawable.apply-transform(projection-matrix, drawables)
ctx._perspective-projection = previous-perspective-mode
}
+
+ // Restore the old transformation
+ ctx.transform = transform
+
if not reset-transform {
drawables = drawable.apply-transform(ctx.transform, drawables)
}
diff --git a/src/draw/shapes.typ b/src/draw/shapes.typ
index ad75690c4..563fca707 100644
--- a/src/draw/shapes.typ
+++ b/src/draw/shapes.typ
@@ -1136,7 +1136,8 @@
let angle = if type(angle) != std.angle {
let (_, c) = coordinate.resolve(ctx, angle)
- vector.angle2(a, util.apply-transform(ctx.transform, c))
+ vector.angle2(util.apply-transform(ctx.transform, a),
+ util.apply-transform(ctx.transform, c))
} else {
angle
}
@@ -1692,7 +1693,7 @@
#let catmull(..pts-style, close: false, name: none) = {
let (pts, style) = (pts-style.pos(), pts-style.named())
- assert(pts.len() >= 2, message: "Catmull-rom curve requires at least two points. Got " + repr(pts.len()) + "instead.")
+ assert(pts.len() >= 2, message: "Catmull-rom curve requires at least two points. Got " + repr(pts.len()) + " instead.")
return (ctx => {
let (ctx, ..pts) = coordinate.resolve(ctx, ..pts)
@@ -1764,7 +1765,7 @@
#let hobby(..pts-style, ta: auto, tb: auto, close: false, name: none) = {
let (pts, style) = (pts-style.pos(), pts-style.named())
- assert(pts.len() >= 2, message: "Hobby curve requires at least two points. Got " + repr(pts.len()) + "instead.")
+ assert(pts.len() >= 2, message: "Hobby curve requires at least two points. Got " + repr(pts.len()) + " instead.")
return (ctx => {
let (ctx, ..pts) = coordinate.resolve(ctx, ..pts)
diff --git a/src/lib.typ b/src/lib.typ
index aa00d816a..233631fb3 100644
--- a/src/lib.typ
+++ b/src/lib.typ
@@ -1,6 +1,8 @@
+// expose name
+#let name = "cetz"
#import "version.typ": version
-#import "canvas.typ": canvas
+#import "canvas.typ": canvas, make-ctx
#import "draw.typ"
// Expose utilities
@@ -22,6 +24,3 @@
#import "lib/angle.typ"
#import "lib/tree.typ"
#import "lib/decorations.typ"
-
-// expose name
-#let name = "cetz"
\ No newline at end of file
diff --git a/src/mark-shapes.typ b/src/mark-shapes.typ
index b9c29adfb..d3a4ff7d6 100644
--- a/src/mark-shapes.typ
+++ b/src/mark-shapes.typ
@@ -205,8 +205,9 @@
fast-line(..pts, stroke: style.stroke, fill: style.fill)
let offset = style.canvas-thickness / 2
- create-tip-and-base-anchor(style, (-offset, 0), (offset, 0))
anchor("center", (0, 0))
+ anchor("tip", (offset, 0))
+ anchor("base", (-offset, 0))
},
ellipse: (style) => {
import "/src/draw.typ": arc, circle
diff --git a/src/mark.typ b/src/mark.typ
index be7e5dd6b..2b14df388 100644
--- a/src/mark.typ
+++ b/src/mark.typ
@@ -278,14 +278,11 @@
style.mark = none
let mark = _eval-mark-shape-and-anchors(ctx, mark-fn(style), style)
- let offset = style.at("offset", default: 0)
- let inset = style.at("inset", default: 0)
-
let mark-tip-info = path-util.point-at(
segments, distance, reverse: is-end)
let mark-base-info = if mark.length != 0 {
path-util.point-at(
- segments, distance + mark.length - inset, reverse: is-end)
+ segments, distance + mark.length, reverse: is-end)
} else {
mark-tip-info
}
diff --git a/src/matrix.typ b/src/matrix.typ
index 1cc8d2fc9..26946676f 100644
--- a/src/matrix.typ
+++ b/src/matrix.typ
@@ -19,7 +19,7 @@
// List of identity matrices of dimension 1 x 1 to 4 x 4
#let _ident = (
- ((1.0),),
+ ((1.0,),),
((1.0, 0.0), (0.0, 1.0)),
((1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)),
((1.0, 0.0, 0.0, 0.0), (0.0, 1.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, 0.0, 0.0, 1.0)),
@@ -31,10 +31,12 @@
/// -> matrix
#let ident(size) = {
assert(size >= 1, message: "Invalid dimension")
- return _ident.at(size - 1, default:
- range(0, size).map(j => range(0, size).map(k => {
- if j == k { 1.0 } else { 0.0 }
- })))
+ if _ident.len() >= size {
+ return _ident.at(size - 1)
+ }
+ return range(0, size).map(j => range(0, size).map(k => {
+ if j == k { 1.0 } else { 0.0 }
+ }))
}
/// Create a square matrix with the diagonal set to the
@@ -69,176 +71,12 @@
/// Rounds each value in the matrix to a precision.
/// - mat (matrix): Input matrix
-/// - precision (int) = 8: Rounding precision (digits)
+/// - precision (int) = 10: Rounding precision (digits)
/// -> matrix
#let round(mat, precision: precision) = {
mat.map(r => r.map(v => _round(v, digits: precision)))
}
-/// Returns a $4 times 4$ translation matrix
-/// - x (float): The translation in the $x$ direction.
-/// - y (float): The translation in the $y$ direction.
-/// - z (float): The translation in the $x$ direction.
-/// -> matrix
-#let transform-translate(x, y, z) = {
- ((1, 0, 0, x), (0, 1, 0, y), (0, 0, 1, z), (0, 0, 0, 1))
-}
-
-/// Returns a $4 times 4$ x-shear matrix
-/// - factor (float): The shear in the $x$ direction.
-/// -> matrix
-#let transform-shear-x(factor) = {
- ((1, factor, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1))
-}
-
-
-/// Returns a $4 times 4$ z-shear matrix
-/// - factor (float): The shear in the $z$ direction.
-/// -> matrix
-#let transform-shear-z(factor) = {
- ((1, 0, factor, 0), (0, 1, -factor, 0), (0, 0, 1, 0), (0, 0, 0, 1))
-}
-
-/// Returns a $4 times 4$ scale matrix
-/// - f (float,array,dictionary): The scale factor(s) of the matrix. An {{array}} of at least 3 {{float}}s sets the x, y and z scale factors. A {{dictionary}} sets the scale in the direction of the corresponding x, y and z keys. A single {{float}} sets the scale for all directions.
-/// -> matrix
-#let transform-scale(f) = {
- let (x, y, z) = if type(f) == array {
- vector.as-vec(f, init: (1, 1, 1))
- } else if type(f) == dictionary {
- (f.at("x", default: 1), f.at("y", default: 1), f.at("z", default: 1))
- } else {
- (f, f, f)
- }
- return (
- (x, 0, 0, 0),
- (0, y, 0, 0),
- (0, 0, z, 0),
- (0, 0, 0, 1),
- )
-}
-
-/// Returns a $4 times 4$ rotation xyz matrix for a direction and up vector
-/// - dir (vector): idk
-/// - up (vector): idk
-/// -> matrix
-#let transform-rotate-dir(dir, up) = {
- dir = vector.norm(dir.slice(0, 3))
- up = vector.norm(up.slice(0, 3))
-
- let (dx, dy, dz) = dir
- let (ux, uy, uz) = up
- let (rx, ry, rz) = vector.norm(vector.cross(dir, up))
-
- ((rx, dx, ux, 0), (ry, dy, uy, 0), (rz, dz, uz, 0), (0, 0, 0, 1))
-}
-
-// Return 4x4 rotate x matrix
-/// Returns a $4 times 4$ $x$ rotation matrix
-/// - angle (angle): The angle to rotate around the $x$ axis
-/// -> matrix
-#let transform-rotate-x(angle) = {
- ((1, 0, 0, 0), (0, cos(angle), -sin(angle), 0), (0, sin(angle), cos(angle), 0), (0, 0, 0, 1))
-}
-
-// Return 4x4 rotate y matrix
-/// Returns a $4 times 4$ $y$ rotation matrix
-/// - angle (angle): The angle to rotate around the $y$ axis
-/// -> matrix
-#let transform-rotate-y(angle) = {
- ((cos(angle), 0, -sin(angle), 0), (0, 1, 0, 0), (sin(angle), 0, cos(angle), 0), (0, 0, 0, 1))
-}
-
-// Return 4x4 rotate z matrix
-/// Returns a $4 times 4$ $z$ rotation matrix
-/// - angle (angle): The angle to rotate around the $z$ axis
-/// -> matrix
-#let transform-rotate-z(angle) = {
- ((cos(angle), -sin(angle), 0, 0), (sin(angle), cos(angle), 0, 0), (0, 0, 1, 0), (0, 0, 0, 1))
-}
-
-// 3D rotation matrix around an arbitrary axis (ax, ay, az).
-#let _rotate-axis-angle(ax, ay, az, angle) = {
- let c = cos(angle)
- let s = sin(angle)
- (
- (ax * ax * (1 - c) + c, ax * ay * (1 - c) - az * s, ax * az * (1 - c) + ay * s, 0),
- (ay * ax * (1 - c) + az * s, ay * ay * (1 - c) + c, ay * az * (1 - c) - ax * s, 0),
- (az * ax * (1 - c) - ay * s, az * ay * (1 - c) + ax * s, az * az * (1 - c) + c, 0),
- (0, 0, 0, 1),
- )
-}
-
-/// Returns a $4 times 4$ rotation matrix from azimuth/elevation/roll.
-/// Assumes the viewing convention where $z$ points up and $x$ points toward the viewer.
-/// - azimuth (angle): Rotation around z.
-/// - elevation (angle): Tilt above the xy plane.
-/// - roll (angle): Rotation around the current viewing axis.
-/// -> matrix
-#let transform-rotate-aer(azimuth, elevation, roll: 0deg) = {
- let rotate-z-up = transform-rotate-x(-90deg)
- let rotate-azimuth = transform-rotate-z(-90deg - azimuth)
- let (ax, ay, az) = (-sin(azimuth), cos(azimuth), 0)
- let rotate-elevation = _rotate-axis-angle(ax, ay, az, elevation)
- let base = mul-mat(rotate-z-up, rotate-azimuth, rotate-elevation)
-
- if roll == 0deg {
- return base
- }
-
- // Roll around the current viewing axis after azimuth/elevation.
- let (vx, vy, vz) = vector.norm(mul4x4-vec3(base, (1, 0, 0), w: 0))
- let rotate-roll = _rotate-axis-angle(vx, vy, vz, roll)
- mul-mat(rotate-roll, base)
-}
-
-// Return 4x4 rotate xz matrix
-/// Returns a $4 times 4$ $x z$ rotation matrix
-/// - x (angle): The angle to rotate around the $x$ axis
-/// - z (angle): The angle to rotate around the $z$ axis
-/// -> matrix
-#let transform-rotate-xz(x, z) = {
- (
- (cos(z), sin(z), 0, 0),
- (-cos(x) * sin(z), cos(x) * cos(z), -sin(x), 0),
- (sin(x) * sin(z), -sin(x) * cos(z), cos(x), 1),
- (0, 0, 0, 1),
- )
-}
-
-/// Returns a $4 times 4$ rotation matrix - yaw-pitch-roll
-///
-/// - a (angle): Yaw
-/// - b (angle): Pitch
-/// - c (angle): Roll
-/// -> matrix
-#let transform-rotate-ypr(a, b, c) = {
- (
- (cos(a) * cos(b), cos(a) * sin(b) * sin(c) - sin(a) * cos(c), cos(a) * sin(b) * cos(c) + sin(a) * sin(c), 0),
- (sin(a) * cos(b), sin(a) * sin(b) * sin(c) + cos(a) * cos(c), sin(a) * sin(b) * cos(c) - cos(a) * sin(c), 0),
- (-sin(b), cos(b) * sin(c), cos(b) * cos(c), 1),
- (0, 0, 0, 1),
- )
-}
-
-/// Returns a $4 times 4$ rotation matrix - euler angles
-///
-/// Calculates the product of the three rotation matrices
-/// $R = R_z(z) R_y(y) R_x(x)$
-///
-/// - x (angle): Rotation about x
-/// - y (angle): Rotation about y
-/// - z (angle): Rotation about z
-/// -> matrix
-#let transform-rotate-xyz(x, y, z) = {
- (
- (cos(y) * cos(z), sin(x) * sin(y) * cos(z) - cos(x) * sin(z), cos(x) * sin(y) * cos(z) + sin(x) * sin(z), 0),
- (cos(y) * sin(z), sin(x) * sin(y) * sin(z) + cos(x) * cos(z), cos(x) * sin(y) * sin(z) - sin(x) * cos(z), 0),
- (-sin(y), sin(x) * cos(y), cos(x) * cos(y), 0),
- (0, 0, 0, 1),
- )
-}
-
/// Multiplies matrices on top of each other.
/// - ..matrices (matrix): The matrices to multiply from left to right.
/// -> matrix
@@ -464,3 +302,167 @@
return inverted
}
+
+/// Returns a $4 times 4$ translation matrix
+/// - x (float): The translation in the $x$ direction.
+/// - y (float): The translation in the $y$ direction.
+/// - z (float): The translation in the $x$ direction.
+/// -> matrix
+#let transform-translate(x, y, z) = {
+ ((1, 0, 0, x), (0, 1, 0, y), (0, 0, 1, z), (0, 0, 0, 1))
+}
+
+/// Returns a $4 times 4$ x-shear matrix
+/// - factor (float): The shear in the $x$ direction.
+/// -> matrix
+#let transform-shear-x(factor) = {
+ ((1, factor, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1))
+}
+
+
+/// Returns a $4 times 4$ z-shear matrix
+/// - factor (float): The shear in the $z$ direction.
+/// -> matrix
+#let transform-shear-z(factor) = {
+ ((1, 0, factor, 0), (0, 1, -factor, 0), (0, 0, 1, 0), (0, 0, 0, 1))
+}
+
+/// Returns a $4 times 4$ scale matrix
+/// - f (float,array,dictionary): The scale factor(s) of the matrix. An {{array}} of at least 3 {{float}}s sets the x, y and z scale factors. A {{dictionary}} sets the scale in the direction of the corresponding x, y and z keys. A single {{float}} sets the scale for all directions.
+/// -> matrix
+#let transform-scale(f) = {
+ let (x, y, z) = if type(f) == array {
+ vector.as-vec(f, init: (1, 1, 1))
+ } else if type(f) == dictionary {
+ (f.at("x", default: 1), f.at("y", default: 1), f.at("z", default: 1))
+ } else {
+ (f, f, f)
+ }
+ return (
+ (x, 0, 0, 0),
+ (0, y, 0, 0),
+ (0, 0, z, 0),
+ (0, 0, 0, 1),
+ )
+}
+
+/// Returns a $4 times 4$ rotation xyz matrix for a direction and up vector
+/// - dir (vector): idk
+/// - up (vector): idk
+/// -> matrix
+#let transform-rotate-dir(dir, up) = {
+ dir = vector.norm(dir.slice(0, 3))
+ up = vector.norm(up.slice(0, 3))
+
+ let (dx, dy, dz) = dir
+ let (ux, uy, uz) = up
+ let (rx, ry, rz) = vector.norm(vector.cross(dir, up))
+
+ ((rx, dx, ux, 0), (ry, dy, uy, 0), (rz, dz, uz, 0), (0, 0, 0, 1))
+}
+
+// Return 4x4 rotate x matrix
+/// Returns a $4 times 4$ $x$ rotation matrix
+/// - angle (angle): The angle to rotate around the $x$ axis
+/// -> matrix
+#let transform-rotate-x(angle) = {
+ ((1, 0, 0, 0), (0, cos(angle), -sin(angle), 0), (0, sin(angle), cos(angle), 0), (0, 0, 0, 1))
+}
+
+// Return 4x4 rotate y matrix
+/// Returns a $4 times 4$ $y$ rotation matrix
+/// - angle (angle): The angle to rotate around the $y$ axis
+/// -> matrix
+#let transform-rotate-y(angle) = {
+ ((cos(angle), 0, -sin(angle), 0), (0, 1, 0, 0), (sin(angle), 0, cos(angle), 0), (0, 0, 0, 1))
+}
+
+// Return 4x4 rotate z matrix
+/// Returns a $4 times 4$ $z$ rotation matrix
+/// - angle (angle): The angle to rotate around the $z$ axis
+/// -> matrix
+#let transform-rotate-z(angle) = {
+ ((cos(angle), -sin(angle), 0, 0), (sin(angle), cos(angle), 0, 0), (0, 0, 1, 0), (0, 0, 0, 1))
+}
+
+// 3D rotation matrix around an arbitrary axis (ax, ay, az).
+#let _rotate-axis-angle(ax, ay, az, angle) = {
+ let c = cos(angle)
+ let s = sin(angle)
+ (
+ (ax * ax * (1 - c) + c, ax * ay * (1 - c) - az * s, ax * az * (1 - c) + ay * s, 0),
+ (ay * ax * (1 - c) + az * s, ay * ay * (1 - c) + c, ay * az * (1 - c) - ax * s, 0),
+ (az * ax * (1 - c) - ay * s, az * ay * (1 - c) + ax * s, az * az * (1 - c) + c, 0),
+ (0, 0, 0, 1),
+ )
+}
+
+/// Returns a $4 times 4$ rotation matrix from azimuth/elevation/roll.
+/// Assumes the viewing convention where $z$ points up and $x$ points toward the viewer.
+/// - azimuth (angle): Rotation around z.
+/// - elevation (angle): Tilt above the xy plane.
+/// - roll (angle): Rotation around the current viewing axis.
+/// -> matrix
+#let transform-rotate-aer(azimuth, elevation, roll: 0deg) = {
+ let rotate-z-up = transform-rotate-x(-90deg)
+ let rotate-azimuth = transform-rotate-z(-90deg - azimuth)
+ let (ax, ay, az) = (-sin(azimuth), cos(azimuth), 0)
+ let rotate-elevation = _rotate-axis-angle(ax, ay, az, elevation)
+ let base = mul-mat(rotate-z-up, rotate-azimuth, rotate-elevation)
+
+ if roll == 0deg {
+ return base
+ }
+
+ // Roll around the current viewing axis after azimuth/elevation.
+ let (vx, vy, vz) = vector.norm(mul4x4-vec3(base, (1, 0, 0), w: 0))
+ let rotate-roll = _rotate-axis-angle(vx, vy, vz, roll)
+ mul-mat(rotate-roll, base)
+}
+
+// Return 4x4 rotate xz matrix
+/// Returns a $4 times 4$ $x z$ rotation matrix
+/// - x (angle): The angle to rotate around the $x$ axis
+/// - z (angle): The angle to rotate around the $z$ axis
+/// -> matrix
+#let transform-rotate-xz(x, z) = {
+ (
+ (cos(z), sin(z), 0, 0),
+ (-cos(x) * sin(z), cos(x) * cos(z), -sin(x), 0),
+ (sin(x) * sin(z), -sin(x) * cos(z), cos(x), 0),
+ (0, 0, 0, 1),
+ )
+}
+
+/// Returns a $4 times 4$ rotation matrix - yaw-pitch-roll
+///
+/// - a (angle): Yaw
+/// - b (angle): Pitch
+/// - c (angle): Roll
+/// -> matrix
+#let transform-rotate-ypr(a, b, c) = {
+ (
+ (cos(a) * cos(b), cos(a) * sin(b) * sin(c) - sin(a) * cos(c), cos(a) * sin(b) * cos(c) + sin(a) * sin(c), 0),
+ (sin(a) * cos(b), sin(a) * sin(b) * sin(c) + cos(a) * cos(c), sin(a) * sin(b) * cos(c) - cos(a) * sin(c), 0),
+ (-sin(b), cos(b) * sin(c), cos(b) * cos(c), 0),
+ (0, 0, 0, 1),
+ )
+}
+
+/// Returns a $4 times 4$ rotation matrix - euler angles
+///
+/// Calculates the product of the three rotation matrices
+/// $R = R_z(z) R_y(y) R_x(x)$
+///
+/// - x (angle): Rotation about x
+/// - y (angle): Rotation about y
+/// - z (angle): Rotation about z
+/// -> matrix
+#let transform-rotate-xyz(x, y, z) = {
+ (
+ (cos(y) * cos(z), sin(x) * sin(y) * cos(z) - cos(x) * sin(z), cos(x) * sin(y) * cos(z) + sin(x) * sin(z), 0),
+ (cos(y) * sin(z), sin(x) * sin(y) * sin(z) + cos(x) * cos(z), cos(x) * sin(y) * sin(z) - sin(x) * cos(z), 0),
+ (-sin(y), sin(x) * cos(y), cos(x) * cos(y), 0),
+ (0, 0, 0, 1),
+ )
+}
diff --git a/src/path-util.typ b/src/path-util.typ
index d601a37ca..4e3c2d27a 100644
--- a/src/path-util.typ
+++ b/src/path-util.typ
@@ -93,19 +93,19 @@
/// `true` iff every other vertex is within `tol` of that z-coordinate.
///
/// - path (path): Input path; must be non-empty
-/// - tol (float): Absolute z tolerance
+/// - eps (float): Absolute z tolerance
/// -> array Tuple of the form (z, same-z)
-#let same-z-plane(path, tol: 1e-6) = {
+#let same-z-plane(path, eps: 1e-6) = {
assert(
path.len() > 0,
message: "Cannot determine z-plane of an empty path",
)
- let z0 = path.first().at(0).at(2)
+ let z0 = path.first().at(0).at(2, default: 0.0)
for (origin, _, segments) in path {
- if calc.abs(origin.at(2) - z0) > tol { return (z0, false) }
+ if calc.abs(origin.at(2, default: 0.0) - z0) > eps { return (z0, false) }
for (kind, ..args) in segments {
for v in args {
- if calc.abs(v.at(2) - z0) > tol { return (z0, false) }
+ if calc.abs(v.at(2, default: 0.0) - z0) > eps { return (z0, false) }
}
}
}
diff --git a/src/version.typ b/src/version.typ
index da052ac53..f28a4ad5d 100644
--- a/src/version.typ
+++ b/src/version.typ
@@ -1 +1 @@
-#let version = version(0,5,2)
+#let version = version(0,5,3)
diff --git a/tests/angle/ref/1.png b/tests/angle/ref/1.png
index bf4ecac56..6e9eaa7f7 100644
Binary files a/tests/angle/ref/1.png and b/tests/angle/ref/1.png differ
diff --git a/tests/canvas/baseline/ref/1.png b/tests/canvas/baseline/ref/1.png
index f026ae3d1..7ad1829a6 100644
Binary files a/tests/canvas/baseline/ref/1.png and b/tests/canvas/baseline/ref/1.png differ
diff --git a/tests/canvas/make-ctx/test.typ b/tests/canvas/make-ctx/test.typ
new file mode 100644
index 000000000..6e37062eb
--- /dev/null
+++ b/tests/canvas/make-ctx/test.typ
@@ -0,0 +1,9 @@
+#import "/src/lib.typ" as cetz
+
+assert.ne(cetz.make-ctx(), none)
+
+assert.eq(cetz.make-ctx().transform, cetz.matrix.ident(4))
+assert.eq(cetz.make-ctx(x: 2, y: 2, z: 2).transform,
+ ((2, 0, 0, 0), (0, 2, 0, 0), (0, 0, 2, 0), (0, 0, 0, 1)))
+assert.eq(cetz.make-ctx(x: (1, 2, 3), y: (2, 3, 4), z: (3, 4, 5)).transform,
+ ((1, 2, 3, 0), (2, 3, 4, 0), (3, 4, 5, 0), (0, 0, 0, 1)))
diff --git a/tests/content/angle/ref/1.png b/tests/content/angle/ref/1.png
new file mode 100644
index 000000000..9795ac164
Binary files /dev/null and b/tests/content/angle/ref/1.png differ
diff --git a/tests/content/angle/test.typ b/tests/content/angle/test.typ
new file mode 100644
index 000000000..9fbb0c7b9
--- /dev/null
+++ b/tests/content/angle/test.typ
@@ -0,0 +1,17 @@
+#import "/src/lib.typ": *
+#import "/tests/helper.typ": *
+#set page(width: auto, height: auto)
+
+// #1032
+#test-case({
+ import draw: *
+ set-style(padding: 5pt)
+
+ line((0, 0), (5, 5), name: "line1")
+ content("line1.50%", angle: "line1.end", anchor: "south", [Content])
+
+ rotate(15deg)
+
+ line((5, 0), (10, 5), name: "line2")
+ content("line2.50%", angle: "line2.end", anchor: "south", [Content])
+})
diff --git a/tests/custom/mark/ref/1.png b/tests/custom/mark/ref/1.png
index 11ec3460e..0262b1bc3 100644
Binary files a/tests/custom/mark/ref/1.png and b/tests/custom/mark/ref/1.png differ
diff --git a/tests/docs/torus/ref/1.png b/tests/docs/torus/ref/1.png
new file mode 100644
index 000000000..d4e08a3a0
Binary files /dev/null and b/tests/docs/torus/ref/1.png differ
diff --git a/tests/docs/torus/test.typ b/tests/docs/torus/test.typ
new file mode 100644
index 000000000..aa681010b
--- /dev/null
+++ b/tests/docs/torus/test.typ
@@ -0,0 +1,62 @@
+#set page(width: auto, height: auto)
+#import "/src/lib.typ" as cetz
+#import "/tests/helper.typ": *
+
+#let torus(pos, angle: 60deg, cap-angle: 5deg, radius: 1, thickness: 0.4, fill: yellow, stroke: black) = {
+ import cetz.draw: scope, compound-path, circle, arc, hide, intersections, arc-through, merge-path
+
+ let cap-angle = cap-angle * calc.sin(angle)
+
+ scope({
+ let radius = (radius, radius * calc.cos(angle))
+ let inner-north = pos.at(1) + radius.at(1) - thickness
+ let show-hole = inner-north > pos.at(1)
+
+ hide({
+ circle(pos, radius: cetz.vector.add(radius, (thickness, thickness)), name: "outer")
+ circle(pos, radius: radius, name: "mid")
+ })
+
+ intersections("inner", {
+ arc-through(
+ ((name: "outer", anchor: 0deg - cap-angle), 200%, (name: "mid", anchor: 0deg)),
+ ((name: "outer", anchor: 270deg), 200%, (name: "mid", anchor: 270deg)),
+ ((name: "outer", anchor: 180deg + cap-angle), 200%, (name: "mid", anchor: 180deg)),)
+ arc-through(
+ ((name: "outer", anchor: 180deg - cap-angle), 200%, (name: "mid", anchor: 180deg)),
+ ((name: "outer", anchor: 90deg), 200%, (name: "mid", anchor: 90deg)),
+ ((name: "outer", anchor: 0deg + cap-angle), 200%, (name: "mid", anchor: 0deg)),)
+ })
+
+ compound-path({
+ circle(pos, radius: cetz.vector.add(radius, (thickness, thickness)), name: "outer")
+
+ if show-hole {
+ if cap-angle > 0deg {
+ merge-path({
+ arc-through("inner.0",
+ ((name: "outer", anchor: 90deg), 200%, (name: "mid", anchor: 90deg)),
+ "inner.1")
+ arc-through("inner.1",
+ ((name: "outer", anchor: 270deg), 200%, (name: "mid", anchor: 270deg)),
+ "inner.0")
+ }, close: true)
+ } else {
+ circle(pos, radius: cetz.vector.sub(radius, (thickness, thickness)))
+ }
+ }
+ }, fill-rule: "even-odd", fill: fill, stroke: stroke)
+
+ if cap-angle > 0deg and angle not in (0deg, 90deg, 180deg, 270deg) {
+ arc-through(((name: "outer", anchor: 0deg - cap-angle), 200%, (name: "mid", anchor: 0deg)),
+ ((name: "outer", anchor: 270deg), 200%, (name: "mid", anchor: 270deg)),
+ ((name: "outer", anchor: 180deg + cap-angle), 200%, (name: "mid", anchor: 180deg)), fill: none, stroke: stroke)
+ }
+ })
+}
+
+#cetz.canvas({
+ import cetz.draw: *
+
+ torus((0, 0))
+})
diff --git a/tests/mark/anchors/ref/1.png b/tests/mark/anchors/ref/1.png
index 1b35f8693..d4757d103 100644
Binary files a/tests/mark/anchors/ref/1.png and b/tests/mark/anchors/ref/1.png differ
diff --git a/tests/mark/auto/offset/ref/1.png b/tests/mark/auto/offset/ref/1.png
index 43dc826b1..24529b08c 100644
Binary files a/tests/mark/auto/offset/ref/1.png and b/tests/mark/auto/offset/ref/1.png differ
diff --git a/tests/mark/offset/ref/1.png b/tests/mark/offset/ref/1.png
new file mode 100644
index 000000000..0b727b01f
Binary files /dev/null and b/tests/mark/offset/ref/1.png differ
diff --git a/tests/mark/offset/test.typ b/tests/mark/offset/test.typ
new file mode 100644
index 000000000..6a47450b9
--- /dev/null
+++ b/tests/mark/offset/test.typ
@@ -0,0 +1,25 @@
+#set page(width: auto, height: auto)
+#import "/src/lib.typ": *
+#import "/tests/helper.typ": *
+
+// Bug #1099 - Offsetting "|" introduced a litte gap
+#test-case({
+ import cetz.draw: *
+
+ line((0, 0), (1, 0), stroke: blue)
+ line((0, 0), (1, 0), mark: (start: "|", end: "|", offset: 10%, stroke: black.transparentize(50%)))
+})
+
+#test-case({
+ import cetz.draw: *
+
+ line((0, 0), (1, 0), stroke: blue)
+ line((0, 0), (1, 0), mark: (reverse: true, start: "|", end: "|", offset: 10%, stroke: black.transparentize(50%)))
+})
+
+#test-case({
+ import cetz.draw: *
+
+ line((0, 0), (1, 0), stroke: blue)
+ line((0, 0), (1, 0), mark: (start: ">>", end: ">>", offset: 10%, stroke: black.transparentize(50%)))
+})
diff --git a/tests/matrix/ref/1.png b/tests/matrix/ref/1.png
deleted file mode 100644
index 797166170..000000000
Binary files a/tests/matrix/ref/1.png and /dev/null differ
diff --git a/tests/matrix/test.typ b/tests/matrix/test.typ
index ab10be7b9..2f131c4c0 100644
--- a/tests/matrix/test.typ
+++ b/tests/matrix/test.typ
@@ -1,17 +1,40 @@
#set page(width: auto, height: auto)
#import "/src/lib.typ": matrix
-#{
- let m = (
- (3, 2, 1),
- (1, 0, 2),
- )
- let v = (
- 1, 0, 4
- )
- let r = (
- 7, 9
- )
-
- assert(matrix.mul-vec(m, v) == r)
+// matrix.ident
+#assert.eq(matrix.ident(1), ((1.0,),))
+#assert.eq(matrix.ident(2), ((1.0, 0), (0, 1.0)))
+#assert.eq(matrix.ident(3), ((1.0, 0, 0), (0, 1.0, 0), (0, 0, 1.0)))
+#assert.eq(matrix.ident(5), ((1.0, 0, 0, 0, 0), (0, 1.0, 0, 0, 0), (0, 0, 1.0, 0, 0), (0, 0, 0, 1.0, 0), (0, 0, 0, 0, 1.0)))
+
+// matrix.is-identity
+#for i in range(1, 8) {
+ assert(matrix.is-identity(matrix.ident(i)))
}
+#assert(not matrix.is-identity(((1.1,),)))
+#assert(not matrix.is-identity(((1, 0), (1, 2))))
+
+// matrix.diag
+#assert.eq(matrix.diag(1), ((1,),))
+#assert.eq(matrix.diag(1, 2), ((1, 0), (0, 2)))
+#assert.eq(matrix.diag(1, 2, 3), ((1, 0, 0), (0, 2, 0), (0, 0, 3)))
+
+// matrix.dim
+#assert.eq(matrix.dim(((1,),)), (1, 1))
+#assert.eq(matrix.dim(((1, 2),)), (1, 2))
+#assert.eq(matrix.dim(((1,), (2,))), (2, 1))
+
+// matrix.column
+#assert.eq(matrix.column(matrix.ident(3), 1), (0.0, 1.0, 0.0))
+
+// matrix.inverse
+#assert.eq(matrix.inverse(matrix.ident(4)), matrix.ident(4))
+#assert.eq(matrix.inverse(((2, 1), (6, 4))), ((2, -1/2), (-3, 1)))
+
+// matrix.mul-vec
+#assert.eq(matrix.mul-vec(matrix.ident(3), (1, 0, 4)), (1, 0, 4))
+#assert.eq(matrix.mul-vec(((3, 2, 1), (1, 0, 2)), (1, 0, 4)), (7, 9))
+
+// matrix.mul-mat
+#assert.eq(matrix.mul-mat(((3, 2, 1), (1, 0, 2)), ((1, 2), (0, 1), (4, 0))), ((7, 8), (9, 2)))
+#assert.eq(matrix.mul-mat(matrix.inverse(((2, 1), (6, 4))), ((2, 1), (6, 4))), matrix.ident(2)) // A A^(-1) = I
diff --git a/tests/merge/ref/1.png b/tests/merge/ref/1.png
index 7ade42916..929750dc6 100644
Binary files a/tests/merge/ref/1.png and b/tests/merge/ref/1.png differ
diff --git a/tests/multiple/marks/ref/1.png b/tests/multiple/marks/ref/1.png
index 9b1294e86..cb7c81ee2 100644
Binary files a/tests/multiple/marks/ref/1.png and b/tests/multiple/marks/ref/1.png differ
diff --git a/tests/projection/perspective/ref/1.png b/tests/projection/perspective/ref/1.png
index 0aef1aec2..1f94979ea 100644
Binary files a/tests/projection/perspective/ref/1.png and b/tests/projection/perspective/ref/1.png differ
diff --git a/tests/rect/around/base/ref/1.png b/tests/rect/around/base/ref/1.png
index 78aa8902c..bf3e8449b 100644
Binary files a/tests/rect/around/base/ref/1.png and b/tests/rect/around/base/ref/1.png differ
diff --git a/tests/style/fold-stroke/ref/1.png b/tests/style/fold-stroke/ref/1.png
index 42bef5fc9..4b226ad06 100644
Binary files a/tests/style/fold-stroke/ref/1.png and b/tests/style/fold-stroke/ref/1.png differ
diff --git a/tests/transform/transform/ref/1.png b/tests/transform/transform/ref/1.png
index fb0a4adfd..3e1d68018 100644
Binary files a/tests/transform/transform/ref/1.png and b/tests/transform/transform/ref/1.png differ
diff --git a/tests/transform/transform/test.typ b/tests/transform/transform/test.typ
index 50ead3e8b..65094ad96 100644
--- a/tests/transform/transform/test.typ
+++ b/tests/transform/transform/test.typ
@@ -19,3 +19,10 @@
transform(matrix.transform-scale((1, 0.5, 1)))
arrow
})
+
+#test-case({
+ import draw: *
+
+ transform(matrix.transform-rotate-aer(10deg, 20deg, roll: 30deg))
+ arrow
+})
diff --git a/typst.toml b/typst.toml
index 472ef21e7..a5fdbffbd 100644
--- a/typst.toml
+++ b/typst.toml
@@ -1,6 +1,6 @@
[package]
name = "cetz"
-version = "0.5.2"
+version = "0.5.3"
compiler = "0.14.0"
repository = "https://github.com/cetz-package/cetz"
homepage = "https://cetz-package.github.io/"