Skip to content

Commit

Permalink
WIP modifiers II
Browse files Browse the repository at this point in the history
  • Loading branch information
johannes-wolf committed Apr 4, 2024
1 parent e05ebcb commit 340ad25
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 30 deletions.
4 changes: 4 additions & 0 deletions src/canvas.typ
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#import "aabb.typ"
#import "styles.typ"
#import "process.typ"
#import "modifiers.typ"

#import util: typst-length

Expand Down Expand Up @@ -65,6 +66,9 @@
zigzag: zigzag,
coil: coil,
),
path-modifiers: (
linearize: modifiers.linearize,
),
)

let (ctx, bounds, drawables) = process.many(ctx, body)
Expand Down
20 changes: 2 additions & 18 deletions src/draw/shapes.typ
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#import "/src/mark.typ" as mark_
#import "/src/mark-shapes.typ" as mark-shapes_
#import "/src/aabb.typ"
#import "/src/modifier.typ"

#import "transformations.typ": *
#import "styling.typ": *
Expand All @@ -39,24 +40,7 @@

// Apply path decoration to drawables
#let _apply-decoration(ctx, style, drawables) = {
let deco = style.at("decoration", default: none)

if deco != none {
import "/src/lib/decorations.typ": wave, zigzag, coil
assert(type(deco) in (str, function),
message: "Decoration must be of type string or function")

let fn = if type(deco) == str {
ctx.decorations.at(deco)
} else if type(deco) == function {
deco
}

assert(fn != none,
message: "Decoration function is none")
return _apply-decoration-fn(ctx, style, drawables, fn)
}
return drawables
return modifier.apply-path-modifier(ctx, style, drawables).first()
}

/// Draws a circle or ellipse.
Expand Down
50 changes: 42 additions & 8 deletions src/modifier.typ
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
#import "/src/path-util.typ"

/// A path modifier is a function that accepts a contex, style and
/// a single drawable and returns a single (modifierd) drawable.

/// Apply a path modifier to a list of drawables
///
/// - ctx (context):
/// - style (style):
/// - drawables (array): List of drawables (paths)
#let apply-modifier-fn(ctx, style, drawables, fn) = {
/// - elem (element): Single element
#let apply-modifier-fn(ctx, style, elem, name-or-fn) = {
let fn = if type(name-or-fn) == function {
name-or-fn
} else {
Expand All @@ -16,14 +18,46 @@
assert(type(fn) == function,
message: "Path modifier must be of type function.")

return drawables.map(d => {
// ...
return d
}).filter(none)
if "segments" in elem {
elem.segments = fn(ctx, style, elem.segments)
}

return elem
}

/// Apply a path modifier to a list of drawables
#let apply-path-modifier(ctx, style, drawables) = {
if type(drawables) != array {
drawables = (drawables,)
}

ctx.path-modifiers.insert("-|", (ctx, style, segments, mode: "-|") => {
let x = if mode == "-|" { 0 } else { -1 }
let y = if mode == "-|" { -1 } else { 0 }

let new = ()
for s in segments {
let (kind, ..pts) = s
if kind == "line" {
new.push(pts.first())
for i in range(1, pts.len()) {
new.push((pts.at(i + x).at(0),
pts.at(i + y).at(1),
(pts.at(i - 1).at(2) + pts.at(i).at(2)) / 2))
new.push(pts.at(i))
}
} else {
new.push(pts.at(0))
new.push((pts.at(1 + x).at(0),
pts.at(1 + y).at(1),
(pts.at(0).at(2) + pts.at(1).at(2)) / 2))
new.push(pts.at(1))
}
}

return (path-util.line-segment(new),)
})

let fns = if type(style.decoration) == array {
style.decoration
} else {
Expand All @@ -43,8 +77,8 @@

// Apply function on all drawables
return drawables.map(d => {
for fn in fns {
d = apply-modifier-fn(ctx, style, fn)
for fn in fns.filter(v => v != none) {
d = apply-modifier-fn(ctx, style, d, fn)
}
return d
})
Expand Down
30 changes: 30 additions & 0 deletions src/modifiers.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#import "/src/styles.typ"
#import "/src/path-util.typ"
#import "/src/bezier.typ" as bezier_

#let linearize-default-style = (linearize: (
samples: 3,
))

/// Path modifier that linearizes bezier segments
/// by sampling n points along the curve.
#let linearize(ctx, style, segments) = {
let style = styles.resolve(ctx.style, merge: style,
base: linearize-default-style).linearize
let samples = calc.max(2, int(style.samples))

let new = ()
for s in segments {
let kind = s.first()
if kind == "cubic" {
let pts = s.slice(1)
new.push(path-util.line-segment(range(0, samples).map(i => {
let t = calc.min(1, 1 / (samples - 1) * i)
bezier_.cubic-point(..pts, t)
})))
} else {
new.push(s)
}
}
return new
}
16 changes: 12 additions & 4 deletions src/styles.typ
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
circle: (
radius: auto,
stroke: auto,
fill: auto
fill: auto,
decoration: auto,
),
group: (
padding: auto,
Expand All @@ -69,27 +70,31 @@
mark: auto,
fill: auto,
stroke: auto,
decoration: auto,
),
bezier: (
stroke: auto,
fill: auto,
mark: auto,
shorten: auto,
decoration: auto,
),
catmull: (
tension: .5,
mark: auto,
shorten: auto,
stroke: auto,
fill: auto
fill: auto,
decoration: auto,
),
hobby: (
/// Curve start and end omega (curlyness)
omega: (1,1),
mark: auto,
shorten: auto,
stroke: auto,
fill: auto
fill: auto,
decoration: auto,
),
rect: (
/// Rect corner radius that supports the following types:
Expand All @@ -105,6 +110,7 @@
radius: 0,
stroke: auto,
fill: auto,
decoration: auto,
),
arc: (
// Supported values:
Expand All @@ -116,7 +122,8 @@
mark: auto,
stroke: auto,
fill: auto,
radius: auto
radius: auto,
decoration: auto,
),
content: (
padding: auto,
Expand All @@ -127,6 +134,7 @@
frame: none,
fill: auto,
stroke: auto,
decoration: auto,
),
)

Expand Down

0 comments on commit 340ad25

Please sign in to comment.