Skip to content

Commit

Permalink
Path Decorations (#329)
Browse files Browse the repository at this point in the history
Adds some path replacing decorations:
- Zig-zag (sawtooth and triangle waves)
- Wave (wave drawn using a catmull curve)
- Coil/Loops

**TODOs**
- [x] Add documentation
- [x] Fix 3D paths (add an up-vector)
  • Loading branch information
johannes-wolf authored Jan 15, 2024
1 parent dd25eea commit a1012c8
Show file tree
Hide file tree
Showing 47 changed files with 1,087 additions and 461 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ CeTZ 0.2.0 requires Typst 0.10.0
- **BREAKING** Removed the `shadow` function
- **BREAKING** Changed the behaviour of `mark`
- **BREAKING** Changed the behaviour of `translate` by changing the transformation order, changed arguments of `scale` and `translate`
- **BREAKING** LERP coordinates now use ratio instead of float for relative interpolation.
- Content padding has been improved to be configurable per side
- Groups support same padding options as content
- Fixed mark offsetting
Expand Down Expand Up @@ -40,6 +41,9 @@ CeTZ 0.2.0 requires Typst 0.10.0
- Added `piechart` for drawing pie- and donut charts
- Added `boxwhisker` for drawing boxwhisker charts

### Decorations
- New path decorations `zigzag`, `wave` and `coil`

# 0.1.2
CeTZ requires Typst 0.8.0.

Expand Down
4 changes: 2 additions & 2 deletions gallery/karls-picture.typ
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@
set-style(stroke: (thickness: 1.2pt))

line((30deg, 1), ((), "|-", (0,0)), stroke: (paint: red), name: "sin")
content(("sin.start", .5, "sin.end"), text(red)[$ sin alpha $])
content(("sin.start", 50%, "sin.end"), text(red)[$ sin alpha $])
line("sin.end", (0,0), stroke: (paint: blue), name: "cos")
content(("cos.start", .5, "cos.end"), text(blue)[$ cos alpha $], anchor: "north")
content(("cos.start", 50%, "cos.end"), text(blue)[$ cos alpha $], anchor: "north")
line((1, 0), (1, calc.tan(30deg)), name: "tan", stroke: (paint: orange))
content("tan.end", $ text(#orange, tan alpha) = text(#red, sin alpha) / text(#blue, cos alpha) $, anchor: "west")
})
53 changes: 38 additions & 15 deletions manual.typ
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ Some elements support compass anchors. TODO
for-each-anchor("group", n => {
if n != "center" {
content(
(rel: ("group.center", .75, "group." + n),
(rel: ("group.center", 75%, "group." + n),
to: "group." + n), n)
} else {
content((rel: (0, .5), to: "group.center"), n)
Expand Down Expand Up @@ -397,28 +397,25 @@ An angle can also be given for the general meaning: "First consider the line fro

#def-arg("a", `<coordinate>`, [The coordinate to interpolate from.])
#def-arg("b", `<coordinate>`, [The coordinate to interpolate to.])
#def-arg("number", [`<number>` or `<length>`], [
#def-arg("number", [`<number>` or `<ratio>`], [
The factor to interpolate by or the distance away from `a` towards `b`.
])
#def-arg("angle", `<angle>`, default: 0deg, "")
#def-arg("abs", `<bool>`, default: false, [
Interpret `number` as absolute distance, instead of a factor.
])

Can be used implicitly as an array in the form `(a, number, b)` or `(a, number, angle, b)`.

```example
grid((0,0), (3,3), help-lines: true)
line((0,0), (2,2))
for i in (0, 0.2, 0.5, 0.8, 1, 1.5) { /* Relative distance */
content(((0,0), i, (2,2)),
line((0,0), (2,2), name: "a")
for i in (0%, 20%, 50%, 80%, 100%, 125%) { /* Relative distance */
content(("a.start", i, "a.end"),
box(fill: white, inset: 1pt, [#i]))
}
line((1,0), (3,2))
line((1,0), (3,2), name: "b")
for i in (0, 0.5, 1, 2) { /* Absolute distance */
content((a: (1,0), number: i, abs: true, b: (3,2)),
content(("b.start", i, "b.end"),
box(fill: white, inset: 1pt, text(red, [#i])))
}
```
Expand All @@ -429,7 +426,7 @@ line((1,0), (3,2))
line((1,0), ((1, 0), 1, 10deg, (3,2)))
fill(red)
stroke(none)
circle(((1, 0), 0.5, 10deg, (3, 2)), radius: 2pt)
circle(((1, 0), 50%, 10deg, (3, 2)), radius: 2pt)
```

```example
Expand All @@ -454,7 +451,7 @@ fill(red)
stroke(none)
circle(
( // a
(((0, 0), 0.3, (3, 2))),
(((0, 0), .3, (3, 2))),
0.7,
(3,0)
),
Expand Down Expand Up @@ -785,9 +782,35 @@ The `angle` function of the angle module allows drawing angles with an optional

== Decorations <decorations>

Various pre-made shapes and lines.

#doc-style.parse-show-module("/src/lib/decorations.typ")
Various pre-made shapes and path modifications.

=== Braces
#doc-style.parse-show-module("/src/lib/decorations/brace.typ")

=== Path Decorations
Path decorations are elements that accept a path as input and generate
one or more shapes that follow that path.

All path decoration functions support the following style keys:
#def-arg("start", [`<ratio>` or `<length>`], default: 0%,
[Absolute or relative start of the decoration on the path.])
#def-arg("stop", [`<ratio>` or `<length>`], default: 100%,
[Absolute or relative end of the decoration on the path.])
#def-arg("rest", [`<string>`], default: "LINE",
[If set to `"LINE"`, generate lines between the paths start/end and
the decorations start/end if the path is _not closed_.])
#def-arg("width", [`<number>`], default: 1,
[Width or thickness of the decoration.])
#def-arg("segments", [`<int>`], default: 10,
[Number of repetitions/phases to generate.
This key is ignored if `segment-length` is set != `none`.])
#def-arg("segment-length", [`none` or `<number>`], default: none,
[Length of one repetition/phase of the decoration.])
#def-arg("align", [`"START"`, `"MID"`, `"END"`], default: "START",
[Alignment of the decoration on the path _if `segment-length` is set_ and
the decoration does not fill up the full range between start and stop.])

#doc-style.parse-show-module("/src/lib/decorations/path.typ")

==== Styling

Expand Down
52 changes: 17 additions & 35 deletions src/coordinate.typ
Original file line number Diff line number Diff line change
Expand Up @@ -185,32 +185,27 @@
}

#let resolve-lerp(resolve, ctx, c) = {
// (a: <coordinate>, number: <number>,
// abs?: <bool>, angle?: <angle>, b: <coordinate>)
// (a, number, b)
// (a, number, angle, b)
// (a: <coordinate>, number: <number,ratio>,
// angle?: <angle>, b: <coordinate>)
// (a, <number, ratio>, b)
// (a, <number, ratio>, angle, b)

let (a, number, angle, b, abs) = if type(c) == array {
let (a, number, angle, b) = if type(c) == array {
if c.len() == 3 {
(
..c.slice(0, 2),
none, // angle
c.last(),
false,
)
} else {
(
..c,
false
)
c
}
} else {
(
c.a,
c.number,
c.at("angle", default: 0deg),
c.b,
c.at("abs", default: false),
c.b
)
}

Expand All @@ -228,34 +223,21 @@
)
}

if type(number) == length {
let dist = vector.dist(a, b)
number = if dist != 0 {
util.resolve-number(ctx, number) / dist
} else {
0
}
}
let ab = vector.sub(b, a)

if abs {
let dist = vector.dist(a, b)
number = if dist != 0 {
number / dist
let is-absolute = type(number) != ratio
let distance = if is-absolute {
let dist = vector.len(ab)
if dist != 0 {
util.resolve-number(ctx, number) / dist
} else {
0
}
} else {
number / 100%
}

return vector.add(
vector.scale(
a,
(1 - number)
),
vector.scale(
b,
number
)
)
return vector.add(a, vector.scale(ab, distance))
}

#let resolve-function(resolve, ctx, c) = {
Expand Down Expand Up @@ -301,7 +283,7 @@
"polar"
} else if len == 3 and c.at(1) in ("-|", "|-") {
"perpendicular"
} else if len in (3, 4) and types.at(1) in ("integer", "float", "length") and (len == 3 or (len == 4 and types.at(2) == "angle")) {
} else if len in (3, 4) and types.at(1) in ("integer", "float", "length", "ratio") and (len == 3 or (len == 4 and types.at(2) == "angle")) {
"lerp"
} else if len >= 2 and types.first() == "function" {
"function"
Expand Down
1 change: 1 addition & 0 deletions src/drawable.typ
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
),
stroke: stroke,
fill: fill,
close: true,
)
}

Expand Down
Loading

0 comments on commit a1012c8

Please sign in to comment.