Skip to content

Commit b845272

Browse files
committed
modifier: Support decorating modifiers
1 parent 23f8c6a commit b845272

File tree

2 files changed

+94
-9
lines changed

2 files changed

+94
-9
lines changed

src/modifier.typ

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
#import "/src/path-util.typ"
22

33
/// A path modifier is a function that accepts a contex, style and
4-
/// a single drawable and returns a single (modified) drawable.
4+
/// a single drawable and returns either a single replacement drawable,
5+
/// or an dictionary with the keys `replacement` (single drawable) and `decoration` (list of drawables)
6+
/// that contain a replacement and/or additional drawable to render.
7+
///
8+
/// Arguments:
9+
/// - ctx (context):
10+
/// - style (styles):
11+
/// - drawable (drawable): Single drawable to modify/decorate
12+
/// - close (bool): Boolean if the drawable is closed
513
///
614
/// Example:
715
/// ```typ
8-
/// (ctx, style, drawable) => {
16+
/// (ctx, style, drawable, close) => {
917
/// // ... modify the drawable ...
10-
/// return drawable
18+
/// return (replacement: ..., decoration: ...)
1119
/// }
1220
/// ```
1321

@@ -48,16 +56,39 @@
4856
assert(type(fn) == function,
4957
message: "Path modifier must be of type function.")
5058

59+
let new-elements = ()
5160
if "segments" in elem {
5261
let begin = style.at("begin", default: 0%)
5362
let end = style.at("end", default: 0%)
5463

5564
let (head, mid, tail) = slice-segments(elem.segments, begin, end)
5665
let close = close and head == () and tail == ()
57-
elem.segments = head + (fn)(ctx, style, mid, close) + tail
66+
let result = (fn)(ctx, style, mid, close)
67+
if type(result) != dictionary {
68+
result = (replace: result)
69+
} else {
70+
new-elements += result.at("decoration", default: ())
71+
}
72+
73+
let replace = result.at("replacement", default: none)
74+
if replace != none {
75+
let replacement-elem = elem
76+
replacement-elem.segments = head + replace + tail
77+
78+
new-elements.insert(0, replacement-elem)
79+
} else {
80+
let head-elem = elem
81+
head-elem.segments = head
82+
83+
let tail-elem = elem
84+
tail-elem.segments = tail
85+
86+
new-elements.insert(0, head-elem)
87+
new-elements.push(tail-elem)
88+
}
5889
}
5990

60-
return elem
91+
return new-elements
6192
}
6293

6394
/// Apply a path modifier to a list of drawables
@@ -98,10 +129,12 @@
98129
style.modifier = ()
99130

100131
// Apply function on all drawables
101-
return drawables.map(d => {
132+
let new = ()
133+
for i in range(0, drawables.len()) {
102134
for fn in fns.filter(v => v.fn != none) {
103-
d = apply-modifier-fn(ctx, fn.style, d, fn.fn, close)
135+
new += apply-modifier-fn(ctx, fn.style, drawables.at(i), fn.fn, close)
104136
}
105-
return d
106-
})
137+
}
138+
139+
return new
107140
}

src/modifiers.typ

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#import "/src/path-util.typ"
33
#import "/src/bezier.typ" as bezier_
44
#import "/src/vector.typ"
5+
#import "/src/drawable.typ"
6+
#import "/src/util.typ"
57

68
// Call callback `fn` for each decoration segment
79
// on path `segments`.
@@ -114,3 +116,53 @@
114116
path-util.cubic-segment(..c)
115117
})
116118
}
119+
120+
121+
122+
#let ticks-default-style = (
123+
step: 10%,
124+
length: 1,
125+
origin: 50%,
126+
replace: false,
127+
)
128+
129+
// Draw tick-marks along a path
130+
#let ticks(ctx, style, segments, close) = {
131+
let style = styles.resolve(ctx.style, merge: style,
132+
base: ticks-default-style)
133+
134+
let length = path-util.length(segments)
135+
let step = if type(style.step) == ratio {
136+
length * style.step / 100%
137+
} else {
138+
util.resolve-number(ctx, style.step)
139+
}
140+
141+
assert(step > 1e-6,
142+
message: "Tick step must be > 0!")
143+
144+
let tick-segments = ()
145+
146+
let distance = 0
147+
while distance <= length {
148+
let (pt, dir) = path-util.direction(segments, distance)
149+
150+
// Compute the tangent normal
151+
let norm = vector.scale(vector.norm((-dir.at(1), dir.at(0), dir.at(2))),
152+
style.length)
153+
154+
// Compute points
155+
let p0 = vector.add(pt, vector.scale(norm, -style.origin / 100%))
156+
let p1 = vector.add(pt, vector.scale(norm, (100% - style.origin) / 100%))
157+
158+
tick-segments.push(path-util.line-segment((p0, p1)))
159+
160+
distance += step
161+
}
162+
163+
return (
164+
replacement: if style.replace { none } else { segments },
165+
decoration: tick-segments.map(s => {
166+
drawable.path(s, stroke: style.stroke)
167+
}))
168+
}

0 commit comments

Comments
 (0)