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

Suggestions for improvements to core drawing functionality #4123

Open
liuguangxi opened this issue May 13, 2024 · 2 comments
Open

Suggestions for improvements to core drawing functionality #4123

liuguangxi opened this issue May 13, 2024 · 2 comments
Labels
proposal You think that something is a good idea or should work differently. visualize Related to drawing and visualizations.

Comments

@liuguangxi
Copy link

liuguangxi commented May 13, 2024

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 (vec2 type). Create a straight line segment with previous vertex.
  • An array of three points (array of vec2 type), 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.

tst

@liuguangxi liuguangxi added the feature request New feature or request label May 13, 2024
@laurmaedje laurmaedje added visualize Related to drawing and visualizations. proposal You think that something is a good idea or should work differently. and removed feature request New feature or request labels May 13, 2024
@bluebear94
Copy link
Contributor

Note that circles and ellipses can’t be reproduced exactly with Bézier curves, but Typst already approximates them anyway.

@liuguangxi
Copy link
Author

liuguangxi commented May 14, 2024

Indeed, circles, ellipses, arcs, and elliptical arcs can all be approximated using no more than 4 cubic Bézier curves. Further, general curves (not ill-conditioned) can also be approximated using a series of straight and cubic Bézier curve segments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal You think that something is a good idea or should work differently. visualize Related to drawing and visualizations.
Projects
None yet
Development

No branches or pull requests

3 participants