Skip to content

Commit 4fa41cc

Browse files
committed
shapes: Add svg-path element
1 parent f22e375 commit 4fa41cc

File tree

3 files changed

+118
-2
lines changed

3 files changed

+118
-2
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
leaks elements (like `scope` does) (#1004)
1515
- Fixed a bug that prevented the leakage of elements of `scope` elements inside
1616
a `group` (#930)
17+
- Added a new element `svg-path` that accepts a list of a subset of SVG
18+
commands to construct paths
1719

1820
# 0.4.2
1921
- The `tree` element now has a `anchor:` argument to position the tree (#929)
@@ -65,6 +67,7 @@
6567
- Added support for user-defined coordinate systems. See `register-coordinate-resolver(<callback>)`
6668
- The default style value for `mark.transform-shape` changed to `false`
6769
- Harpoon marks changed sides (on a line from (0, 0) to (1, 0), the end mark now appears on the left side/top)
70+
- Brace styling changed, see the documention of `decorations.brace`
6871

6972
# 0.3.4
7073
- Fixed a bug with rendering curves with Typst 0.13.1

src/draw.typ

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#import "draw/grouping.typ": intersections, group, scope, anchor, copy-anchors, set-ctx, get-ctx, for-each-anchor, on-layer, hide, floating
22
#import "draw/transformations.typ": set-transform, rotate, translate, scale, set-origin, move-to, set-viewport
33
#import "draw/styling.typ": set-style, fill, stroke, register-mark
4-
#import "draw/shapes.typ": circle, circle-through, arc, arc-through, mark, line, grid, content, rect, bezier, bezier-through, catmull, hobby, merge-path, polygon, compound-path, n-star, rect-around
4+
#import "draw/shapes.typ": circle, circle-through, arc, arc-through, mark, line, grid, content, rect, bezier, bezier-through, catmull, hobby, merge-path, polygon, compound-path, n-star, rect-around, svg-path
55
#import "draw/projection.typ": ortho, on-xy, on-xz, on-yz
66
#import "draw/util.typ": assert-version, register-coordinate-resolver

src/draw/shapes.typ

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -917,7 +917,7 @@
917917

918918
let (from-x, from-y, ..) = from
919919
let (to-x, to-y, ..) = to
920-
920+
921921
// Resolve shift parameter
922922
let shift = style.at("shift", default: 0)
923923
if type(shift) == dictionary {
@@ -2022,3 +2022,116 @@
20222022
})
20232023
return ctx
20242024
}
2025+
2026+
/// Create a new path from a SVG-like list of commands.
2027+
///
2028+
/// The following commands are supported (uppercase command names use absolute coordinates, lowercase use relative coordinates)
2029+
/// - `("l", pt)` line to `pt`
2030+
/// - `("h", num)` Horizontal line
2031+
/// - `("v", num)` Vertical line
2032+
/// - `("m", pt)` Move to `pt`
2033+
/// - `("c", ctrl-1, ctrl-2, pt)` Cubic bezier curve to `pt` with control points `ctrl-1` and `ctrl-2`
2034+
/// - `("q", ctrl, pt)` Quadratiuc bezier curve
2035+
/// - `("z")` Close the current path
2036+
#let svg-path(name: none, anchor: none, ..commands-style) = {
2037+
let style = commands-style.named()
2038+
let commands = commands-style.pos().map(cmd => {
2039+
if type(cmd) == str {
2040+
(cmd,)
2041+
} else {
2042+
cmd
2043+
}
2044+
})
2045+
return (ctx => {
2046+
let paths = ()
2047+
2048+
let origin = (0, 0, 0)
2049+
let current = ()
2050+
2051+
for ((cmd, ..args)) in commands {
2052+
assert(cmd in ("m", "M", "l", "L", "c", "C", "h", "H", "v", "V", "z", "Z", "q", "Q"),
2053+
message: "Unknown svg-path command: " + repr(cmd))
2054+
2055+
let is-relative = cmd in ("m", "l", "c", "h", "v")
2056+
let wrap-coordinate = if is-relative {
2057+
x => (rel: x)
2058+
} else {
2059+
x => x
2060+
}
2061+
2062+
if cmd in ("h", "H") {
2063+
assert.eq(args.len(), 1)
2064+
let (x, ..rest) = args
2065+
args = ((x, 0.0, 0.0),)
2066+
cmd = if cmd == "h" { "l" } else { "L" }
2067+
} else if cmd in ("v", "V") {
2068+
assert.eq(args.len(), 1)
2069+
let (y, ..rest) = args
2070+
args = ((0.0, y, 0.0),)
2071+
cmd = if cmd == "v" { "l" } else { "L" }
2072+
}
2073+
2074+
(ctx, ..args) = coordinate.resolve(ctx, ..args.map(wrap-coordinate))
2075+
2076+
if cmd in ("z", "Z") {
2077+
assert.eq(args.len(), 0)
2078+
if current != () {
2079+
paths.push(path-util.make-subpath(origin, current, closed: cmd == "z"))
2080+
}
2081+
2082+
current = ()
2083+
}
2084+
2085+
if cmd in ("m", "M", "l", "L") {
2086+
if cmd in ("m", "M") {
2087+
assert.eq(args.len(), 1)
2088+
origin = args.at(0, default: (0, 0, 0))
2089+
args.pop()
2090+
}
2091+
2092+
current += args.map(pt => ("l", pt))
2093+
} else if cmd in ("c", "C") {
2094+
assert.eq(args.len(), 3)
2095+
let (c1, c2, pt) = args
2096+
current.push(("c", c1, c2, pt))
2097+
} else if cmd in ("q", "Q") {
2098+
assert.eq(args.len(), 2)
2099+
let (c1, pt) = args
2100+
let (_, pt, c1, c2) = bezier_.quadratic-to-cubic(ctx.prev.pt, pt, c1)
2101+
current.push(("c", c1, c2, pt))
2102+
}
2103+
}
2104+
2105+
if current != () {
2106+
paths.push(path-util.make-subpath(origin, current, closed: false))
2107+
}
2108+
2109+
let style = styles.resolve(ctx.style, merge: style)
2110+
let drawables = drawable.path(paths, stroke: style.stroke, fill: style.fill, fill-rule: style.fill-rule)
2111+
2112+
let (transform, anchors) = anchor_.setup(
2113+
(_) => none,
2114+
(),
2115+
default: none,
2116+
name: name,
2117+
offset-anchor: anchor,
2118+
transform: ctx.transform,
2119+
//border-anchors: true,
2120+
path-anchors: true,
2121+
path: drawables,
2122+
)
2123+
2124+
if mark_.check-mark(style.mark) {
2125+
drawables = mark_.place-marks-along-path(ctx, style.mark, transform, drawables)
2126+
} else {
2127+
drawables = drawable.apply-transform(transform, drawables)
2128+
}
2129+
2130+
return (
2131+
ctx: ctx,
2132+
name: name,
2133+
anchors: anchors,
2134+
drawables: drawables,
2135+
)
2136+
},)
2137+
}

0 commit comments

Comments
 (0)