Skip to content

Commit

Permalink
modifier: Support decorating modifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
johannes-wolf committed Jun 29, 2024
1 parent 23f8c6a commit f4cf9a0
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 15 deletions.
64 changes: 53 additions & 11 deletions src/modifier.typ
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
#import "/src/path-util.typ"

/// A path modifier is a function that accepts a contex, style and
/// a single drawable and returns a single (modified) drawable.
/// a single drawable and returns either a single replacement drawable,
/// or an dictionary with the keys `replacement` (single drawable) and `decoration` (list of drawables)
/// that contain a replacement and/or additional drawable to render.
///
/// Arguments:
/// - ctx (context):
/// - style (styles):
/// - drawable (drawable): Single drawable to modify/decorate
/// - close (bool): Boolean if the drawable is closed
///
/// Example:
/// ```typ
/// (ctx, style, drawable) => {
/// (ctx, style, drawable, close) => {
/// // ... modify the drawable ...
/// return drawable
/// return (replacement: ..., decoration: ...)
/// }
/// ```
Expand Down Expand Up @@ -44,20 +52,51 @@
/// - ctx (context):
/// - style (style):
/// - elem (element): Single element
/// -> List of elements
#let apply-modifier-fn(ctx, style, elem, fn, close) = {
assert(type(fn) == function,
message: "Path modifier must be of type function.")

let new-elements = ()
if "segments" in elem {
let begin = style.at("begin", default: 0%)
let end = style.at("end", default: 0%)

let (head, mid, tail) = slice-segments(elem.segments, begin, end)
let close = close and head == () and tail == ()
elem.segments = head + (fn)(ctx, style, mid, close) + tail
let result = (fn)(ctx, style, mid, close)
if type(result) != dictionary {
result = (replacement: result)
} else {
new-elements += result.at("decoration", default: ())
}

let replace = result.at("replacement", default: none)
if replace != none {
let replacement-elem = elem
replacement-elem.segments = head + replace + tail

if replacement-elem.segments != () {
new-elements.insert(0, replacement-elem)
}
} else {
if head != () {
let head-elem = elem
head-elem.segments = head

new-elements.insert(0, head-elem)
}

if tail != () {
let tail-elem = elem
tail-elem.segments = tail

new-elements.push(tail-elem)
}
}
}

return elem
return new-elements
}

/// Apply a path modifier to a list of drawables
Expand All @@ -72,7 +111,7 @@
(style.modifier,)
}.map(n => {
let name = if type(n) == dictionary {
n.name
n.at("name", default: none)
} else {
n
}
Expand All @@ -98,10 +137,13 @@
style.modifier = ()

// Apply function on all drawables
return drawables.map(d => {
for fn in fns.filter(v => v.fn != none) {
d = apply-modifier-fn(ctx, fn.style, d, fn.fn, close)
for fn in fns.filter(v => v.fn != none) {
let new = ()
for i in range(0, drawables.len()) {
new += apply-modifier-fn(ctx, fn.style, drawables.at(i), fn.fn, close)
}
return d
})
drawables = new
}

return drawables
}
62 changes: 58 additions & 4 deletions src/modifiers.typ
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#import "/src/path-util.typ"
#import "/src/bezier.typ" as bezier_
#import "/src/vector.typ"
#import "/src/drawable.typ"
#import "/src/util.typ"

// Call callback `fn` for each decoration segment
// on path `segments`.
Expand Down Expand Up @@ -64,7 +66,7 @@
new.push(s)
}
}
return new
return (replacement: new)
}


Expand Down Expand Up @@ -110,7 +112,59 @@
}

let pts = _n-segment-effect(ctx, segments, fn, style, close: close)
return bezier_.catmull-to-cubic(pts, style.tension, close: close).map(c => {
path-util.cubic-segment(..c)
})
return (
replacement: bezier_.catmull-to-cubic(pts, style.tension, close: close).map(c => {
path-util.cubic-segment(..c)
})
)
}



#let ticks-default-style = (
step: 10%,
length: 1,
origin: 50%,
replace: false,
)

// Draw tick-marks along a path
#let ticks(ctx, style, segments, close) = {
let style = styles.resolve(ctx.style, merge: style,
base: ticks-default-style)

let length = path-util.length(segments)
let step = if type(style.step) == ratio {
length * style.step / 100%
} else {
util.resolve-number(ctx, style.step)
}

assert(step > 1e-6,
message: "Tick step must be > 0!")

let tick-segments = ()

let distance = 0
while distance <= length {
let (pt, dir) = path-util.direction(segments, distance)

// Compute the tangent normal
let norm = vector.scale(vector.norm((-dir.at(1), dir.at(0), dir.at(2))),
style.length)

// Compute points
let p0 = vector.add(pt, vector.scale(norm, -style.origin / 100%))
let p1 = vector.add(pt, vector.scale(norm, (100% - style.origin) / 100%))

tick-segments.push(path-util.line-segment((p0, p1)))

distance += step
}

return (
replacement: if style.replace { none } else { segments },
decoration: tick-segments.map(s => {
drawable.path(s, stroke: style.stroke)
}))
}

0 comments on commit f4cf9a0

Please sign in to comment.