|
1551 | 1551 | },) |
1552 | 1552 | } |
1553 | 1553 |
|
| 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 | + |
1554 | 1667 | /// Create a new path with one or more elments used as sub-paths. |
1555 | 1668 | /// This can be used to create paths with holes. |
1556 | 1669 | /// |
|
0 commit comments