Skip to content

Commit f62917b

Browse files
committed
shapes: Add svg-path element
1 parent 6960d42 commit f62917b

File tree

3 files changed

+115
-1
lines changed

3 files changed

+115
-1
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- Changed the internal path model to support multiple subpaths
66
- Braces are now drawn tapered by default (#828)
77
- Brace styling changed, see the documention of `decorations.brace`
8+
- Added a new element `svg-path` that accepts a list of a subset of SVG commands to construct paths
89

910
# 0.3.4
1011
- 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, multi-path
4+
#import "draw/shapes.typ": circle, circle-through, arc, arc-through, mark, line, grid, content, rect, bezier, bezier-through, catmull, hobby, merge-path, polygon, multi-path, svg-path
55
#import "draw/projection.typ": ortho, on-xy, on-xz, on-yz
66
#import "draw/util.typ": assert-version

src/draw/shapes.typ

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,6 +1551,119 @@
15511551
},)
15521552
}
15531553

1554+
/// Create a new path from a SVG-like list of commands.
1555+
///
1556+
/// The following commands are supported (uppercase command names use absolute coordinates, lowercase use relative coordinates)
1557+
/// - `("l", pt)` line to `pt`
1558+
/// - `("h", num)` Horizontal line
1559+
/// - `("v", num)` Vertical line
1560+
/// - `("m", pt)` Move to `pt`
1561+
/// - `("c", ctrl-1, ctrl-2, pt)` Cubic bezier curve to `pt` with control points `ctrl-1` and `ctrl-2`
1562+
/// - `("q", ctrl, pt)` Quadratiuc bezier curve
1563+
/// - `("z")` Close the current path
1564+
#let svg-path(name: none, anchor: none, ..commands-style) = {
1565+
let style = commands-style.named()
1566+
let commands = commands-style.pos().map(cmd => {
1567+
if type(cmd) == str {
1568+
(cmd,)
1569+
} else {
1570+
cmd
1571+
}
1572+
})
1573+
return (ctx => {
1574+
let paths = ()
1575+
1576+
let origin = (0, 0, 0)
1577+
let current = ()
1578+
1579+
for ((cmd, ..args)) in commands {
1580+
assert(cmd in ("m", "M", "l", "L", "c", "C", "h", "H", "v", "V", "z", "Z", "q", "Q"),
1581+
message: "Unknown svg-path command: " + repr(cmd))
1582+
1583+
let is-relative = cmd in ("m", "l", "c", "h", "v")
1584+
let wrap-coordinate = if is-relative {
1585+
x => (rel: x)
1586+
} else {
1587+
x => x
1588+
}
1589+
1590+
if cmd in ("h", "H") {
1591+
assert.eq(args.len(), 1)
1592+
let (x, ..rest) = args
1593+
args = ((x, 0.0, 0.0),)
1594+
cmd = if cmd == "h" { "l" } else { "L" }
1595+
} else if cmd in ("v", "V") {
1596+
assert.eq(args.len(), 1)
1597+
let (y, ..rest) = args
1598+
args = ((0.0, y, 0.0),)
1599+
cmd = if cmd == "v" { "l" } else { "L" }
1600+
}
1601+
1602+
(ctx, ..args) = coordinate.resolve(ctx, ..args.map(wrap-coordinate))
1603+
1604+
if cmd in ("z", "Z") {
1605+
assert.eq(args.len(), 0)
1606+
if current != () {
1607+
paths.push(path-util.make-subpath(origin, current, closed: cmd == "z"))
1608+
}
1609+
1610+
current = ()
1611+
}
1612+
1613+
if cmd in ("m", "M", "l", "L") {
1614+
if cmd in ("m", "M") {
1615+
assert.eq(args.len(), 1)
1616+
origin = args.at(0, default: (0, 0, 0))
1617+
args.pop()
1618+
}
1619+
1620+
current += args.map(pt => ("l", pt))
1621+
} else if cmd in ("c", "C") {
1622+
assert.eq(args.len(), 3)
1623+
let (c1, c2, pt) = args
1624+
current.push(("c", c1, c2, pt))
1625+
} else if cmd in ("q", "Q") {
1626+
assert.eq(args.len(), 2)
1627+
let (c1, pt) = args
1628+
let (_, pt, c1, c2) = bezier_.quadratic-to-cubic(ctx.prev.pt, pt, c1)
1629+
current.push(("c", c1, c2, pt))
1630+
}
1631+
}
1632+
1633+
if current != () {
1634+
paths.push(path-util.make-subpath(origin, current, closed: false))
1635+
}
1636+
1637+
let style = styles.resolve(ctx.style, merge: style)
1638+
let drawables = drawable.path(paths, stroke: style.stroke, fill: style.fill, fill-rule: style.fill-rule)
1639+
1640+
let (transform, anchors) = anchor_.setup(
1641+
(_) => none,
1642+
(),
1643+
default: none,
1644+
name: name,
1645+
offset-anchor: anchor,
1646+
transform: ctx.transform,
1647+
//border-anchors: true,
1648+
path-anchors: true,
1649+
path: drawables,
1650+
)
1651+
1652+
if mark_.check-mark(style.mark) {
1653+
drawables = mark_.place-marks-along-path(ctx, style.mark, transform, drawables)
1654+
} else {
1655+
drawables = drawable.apply-transform(transform, drawables)
1656+
}
1657+
1658+
return (
1659+
ctx: ctx,
1660+
name: name,
1661+
anchors: anchors,
1662+
drawables: drawables,
1663+
)
1664+
},)
1665+
}
1666+
15541667
/// Create a new path with one or more elments used as sub-paths.
15551668
/// This can be used to create paths with holes.
15561669
///

0 commit comments

Comments
 (0)