Skip to content

Commit f4cf9a0

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

File tree

2 files changed

+111
-15
lines changed

2 files changed

+111
-15
lines changed

src/modifier.typ

+53-11
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

@@ -44,20 +52,51 @@
4452
/// - ctx (context):
4553
/// - style (style):
4654
/// - elem (element): Single element
55+
/// -> List of elements
4756
#let apply-modifier-fn(ctx, style, elem, fn, close) = {
4857
assert(type(fn) == function,
4958
message: "Path modifier must be of type function.")
5059

60+
let new-elements = ()
5161
if "segments" in elem {
5262
let begin = style.at("begin", default: 0%)
5363
let end = style.at("end", default: 0%)
5464

5565
let (head, mid, tail) = slice-segments(elem.segments, begin, end)
5666
let close = close and head == () and tail == ()
57-
elem.segments = head + (fn)(ctx, style, mid, close) + tail
67+
let result = (fn)(ctx, style, mid, close)
68+
if type(result) != dictionary {
69+
result = (replacement: result)
70+
} else {
71+
new-elements += result.at("decoration", default: ())
72+
}
73+
74+
let replace = result.at("replacement", default: none)
75+
if replace != none {
76+
let replacement-elem = elem
77+
replacement-elem.segments = head + replace + tail
78+
79+
if replacement-elem.segments != () {
80+
new-elements.insert(0, replacement-elem)
81+
}
82+
} else {
83+
if head != () {
84+
let head-elem = elem
85+
head-elem.segments = head
86+
87+
new-elements.insert(0, head-elem)
88+
}
89+
90+
if tail != () {
91+
let tail-elem = elem
92+
tail-elem.segments = tail
93+
94+
new-elements.push(tail-elem)
95+
}
96+
}
5897
}
5998

60-
return elem
99+
return new-elements
61100
}
62101

63102
/// Apply a path modifier to a list of drawables
@@ -72,7 +111,7 @@
72111
(style.modifier,)
73112
}.map(n => {
74113
let name = if type(n) == dictionary {
75-
n.name
114+
n.at("name", default: none)
76115
} else {
77116
n
78117
}
@@ -98,10 +137,13 @@
98137
style.modifier = ()
99138

100139
// Apply function on all drawables
101-
return drawables.map(d => {
102-
for fn in fns.filter(v => v.fn != none) {
103-
d = apply-modifier-fn(ctx, fn.style, d, fn.fn, close)
140+
for fn in fns.filter(v => v.fn != none) {
141+
let new = ()
142+
for i in range(0, drawables.len()) {
143+
new += apply-modifier-fn(ctx, fn.style, drawables.at(i), fn.fn, close)
104144
}
105-
return d
106-
})
145+
drawables = new
146+
}
147+
148+
return drawables
107149
}

src/modifiers.typ

+58-4
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`.
@@ -64,7 +66,7 @@
6466
new.push(s)
6567
}
6668
}
67-
return new
69+
return (replacement: new)
6870
}
6971

7072

@@ -110,7 +112,59 @@
110112
}
111113

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

0 commit comments

Comments
 (0)