Suggestions for improvements to core drawing functionality
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 56.1k
- Forks
- 1.7k
- Avg merge
- 3d 10h
- Merged PRs (30d)
- 22
Description
Description
With the long-term use of the Typst drawing function, some functional or performance shortcomings gradually appeared. The famous drawing libraries TikZ and PSTricks in the LaTeX world, are good reference. Suggested improvements to the core drawing functionality are detailed below. I expect some of the functionality to be difficult to implement or cause breaking changes in interfaces, but they are worth considering as a direction for future improvements.
Introduce new data types and related functions
vec2
vec2 is a pair of real numbers, which can be seen as a point or a vector on a plane, or as a complex number. The vec2 type and related operations are frequently used in various graphics processing, so the built-in implementation of them can help improve performance. Typical usage examples might be as follows:
#let v = vec2(1.5, 3.0) // create a `vec2` object
#(vec2(1, 2) + vec2(3, 4)) // addition
#(vec2(1, 2) * 4.5) // scale
#vec2(3.0, 4.0).length() // length of vector
#vec2(3.0, 4.0).angle() // angle of vector
#vec2(3.0, 4.0).dot(v) // dot of two vectors
#v.min() // minimum of two elements in vector
#v.max() // maximum of two elements in vector
transform
transform implement affine transform in 2D geometry. Internally, it is denoted by t = (t.x, t.y, t.xx, t.xy, t.yx, t.yy). A point (x, y) is transformed by the transform t to (x', y'), where
x' = t.x + t.xx * x + t.xy * y
y' = t.y + t.yx * x + t.yy * y
Transforms can be applied to pairs, paths and graphics by multiplication on the left. And they can also be composed with one another and inverted.
Constructor for transform object may be:
#let transform.identity() -> transform // identity transform
#let transform.translate(dx, dy) -> transform // translate transform
#let transform.scale(sx, sy) -> transform // scale transform
#let transform.rotate(angle, pt) -> transform // rotate transform by angle about point pt
#let transform.matrix(x, y, xx, xy, yx, yy) -> transform // general transform
The functions for transform object might be as follows (suppose t1 and t2 are transform objects):
#t1.mat() // internal matrix elements of transform
#t1.lmul(t2) // transform multiplication
#t1.inv() // inverse of transform
#vec2(1.5, 2.0).lmul(t1) // transform vector using t1
path
Currently Typst does not have an explicit path object for user, instead the path objects is tightly coupled to the drawing function (implemented as path function). This approach is not flexible enough and introduce some troubles for future extention. Based on the idea that path definition is separated from drawing, as in mainstream drawing library. A dedicated path type could be defined.
A path is specified as a list of points interconnected with straight line segments or cubic spline segments, and it can be closed or open. Constructor function for path object may be:
#let path(closed: [true | false], vertices) -> path
Where vertices is array of vertices of the path. Each vertex can be defined in 2 ways:
- A regular point (
vec2type). Create a straight line segment with previous vertex. - An array of three points (array of
vec2type), the first two being the control points, and the last one being the vertex. Create a cubic spline segment with previous vertex.
Several paths can be group into a path array, i.e. array of path, as a compound path. For example, a rectangle with a hole in it.
#let p-square = path(...) // path of square
#let p-circle = path(...) // path of circle
#let ps = (p-square, p-circle) // compound path
The functions for path object might be as follows:
#let p1 = path(
closed: true,
( vec2(0, 0), (vec2(4, 2), vec2(6, 2), vec2(10, 0)) )
) // create a closed path
#p1.closed() // whether path is closed
#p1.vertices() // array of vertices of the path
#p1.size() // the number of nodes in the path
#p1.reverse() // return reversed path
#p1.min() // pair (left,bottom) for the bounding box of path
#p1.max() // pair (right,top) for the bounding box of path
#p1.lmul(t1) // return transformed path using t1
#p1.sub-path(a, b) // return the subpath of p1 running from node a to node b
#path.merge-path(p1, p2, p3) // return merged path p1, p2, p3
Path transformation operations are computationally intensive and frequently present in graphics processing, so the built-in implementation of them can help improve performance.
More path-related functions can be implemented by the extension library.
Refine drawing functions
The following drawing functions need to be provided. They are all path-based.
#let draw(draw-pen: [`color` | ...], [`path` | array of `path`])
#let fill(fill-pen: [`color` | ...], fill-rule: ["nonzero" | "evenodd"], [`path` | array of `path`])
#let filldraw(fill-pen: [`color` | ...], draw-pen: [`color` | ...], fill-rule: ["nonzero" | "evenodd"], [`path` | array of `path`])
#let clip(clip-rule: ["nonzero" | "evenodd"], [`path` | array of `path`])
The first three functions stroke and/or fill path(compound path) using predefined color, gradient, etc. Note that both nonzero and evenodd fill rules should be supported (except in function draw).
The last function clip implements cliping the current drawing contents to the region bounded by the path(compound path), where both nonzero and evenodd clip rules are supported. This function may be complicated to implement, and the scope of the clipping operation needs to be more sensibly defined.
These drawing functions are already available in the library TikZ, PSTricks and in SVG specification, so Typst should consider refine these in the future.
Use Case
The following graphics would be relatively easy to draw if Typst supports the previously mentioned improvements.
A path of a circle with 5 holes in it fills the gradient, and lay on top of the texts.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
No files, tests, or entry points are named. Begin by mapping the existing core drawing, path, and transformation implementation and its tests, then clarify which single capability is in scope; done cannot be defined until the proposed API and compatibility boundaries are agreed.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- computer-graphics
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100