Skip to content
Draft

0.5.3 #1101

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c43d604
version: Bump to 0.5.3
johannes-wolf May 6, 2026
572b6d5
tests: Add Torus Drawing
johannes-wolf May 6, 2026
bd2b1e7
Fix Mark Inset
johannes-wolf May 12, 2026
d7855dd
Update Reference Images
johannes-wolf May 12, 2026
0bd7c7b
Fix Double Mark Inset (#1102)
johannes-wolf May 12, 2026
09d790d
Update Changelog
johannes-wolf May 12, 2026
eb4749e
Improve Matrix Tests
johannes-wolf May 12, 2026
a3741c5
Provide a make-ctx Function
johannes-wolf May 13, 2026
414c1d4
Provide a make-ctx Function (#1106)
johannes-wolf May 13, 2026
8436fa4
Fix Perspective Projection
johannes-wolf May 22, 2026
5000453
Fix Perspective Projection (#1110)
johannes-wolf May 22, 2026
1de9173
Fix assertion messages for Catmull-rom and Hobby curves
AlexanderHD27 May 25, 2026
e9cb6ab
Fix small typo/formatting issue in assertion in hobby/catmul (#1111)
johannes-wolf May 25, 2026
dde8df8
path-util: Fix same-z-plane for 2D vectors
johannes-wolf May 26, 2026
dc1e4fe
path-util: Fix same-z-plane for 2D Vectors (#1112)
johannes-wolf May 26, 2026
1dc0edc
matrix: move transform functions to bottom of file
sijow May 28, 2026
c002e52
Fix typos in rotation transforms
sijow May 29, 2026
6db281f
matrix: move transform functions to bottom of file (#1113)
johannes-wolf May 29, 2026
011e4a4
docs(coordinate-systems): Coordinate length can be changed by canvas
TheJanzap Jun 26, 2026
e1f90c2
docs(canvas): Remove deprecated length-as-ratio mention
TheJanzap Jun 26, 2026
e2158f3
docs: Update coordinate length handling (#1115)
johannes-wolf Jun 26, 2026
fec0a37
Improve karls-picture.typ
yerlotic Jul 8, 2026
482095f
Use polar coordinates
yerlotic Jul 12, 2026
5dd425f
Update gallery/karls-picture.typ
yerlotic Jul 12, 2026
e65ccb4
Improve even more
yerlotic Jul 12, 2026
7765767
Update Gallery Image
johannes-wolf Jul 13, 2026
cdf3da8
Improve Karl's Picture (#1120)
johannes-wolf Jul 13, 2026
6ec8d2a
Fix Content Angle Calculation From Two Coordinates
johannes-wolf Jul 13, 2026
4655ed6
Update Test Ref Images for Tytanic 0.4
johannes-wolf Jul 13, 2026
19d8981
Fix Content Angle Calculation From Two Coordinates (#1119)
johannes-wolf Jul 13, 2026
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
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: *
Expand Down Expand Up @@ -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: *
Expand Down
2 changes: 1 addition & 1 deletion docs/basics/canvas.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 2 additions & 0 deletions docs/basics/coordinate-systems.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import Type from "@site/src/components/Type";

A <Type>coordinate</Type> 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.
Expand Down
2 changes: 1 addition & 1 deletion docs/genhtml.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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: *
...
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/karl.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Binary file modified gallery/karls-picture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 24 additions & 14 deletions gallery/karls-picture.typ
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
Expand All @@ -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")
})
2 changes: 1 addition & 1 deletion gallery/paciolis.typ
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
2 changes: 1 addition & 1 deletion gallery/periodic-table.typ
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
2 changes: 1 addition & 1 deletion gallery/plate-capacitor.typ
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 1 addition & 1 deletion gallery/torus.typ
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
2 changes: 1 addition & 1 deletion gallery/tree.typ
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
2 changes: 1 addition & 1 deletion gallery/waves.typ
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
2 changes: 1 addition & 1 deletion manual.typ
Original file line number Diff line number Diff line change
Expand Up @@ -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: *
...
Expand Down
62 changes: 40 additions & 22 deletions src/canvas.typ
Original file line number Diff line number Diff line change
Expand Up @@ -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 * <ratio>, ...))`.
/// - 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) = {
Expand All @@ -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,
Expand All @@ -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 * <ratio>, ...))`.
/// - 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 {
Expand Down
4 changes: 2 additions & 2 deletions src/draw/boolean.typ
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
7 changes: 5 additions & 2 deletions src/draw/projection.typ
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
7 changes: 4 additions & 3 deletions src/draw/shapes.typ
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
7 changes: 3 additions & 4 deletions src/lib.typ
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -22,6 +24,3 @@
#import "lib/angle.typ"
#import "lib/tree.typ"
#import "lib/decorations.typ"

// expose name
#let name = "cetz"
3 changes: 2 additions & 1 deletion src/mark-shapes.typ
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 1 addition & 4 deletions src/mark.typ
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Loading