Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

shapes: Use decoration style #541

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/canvas.typ
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#import "styles.typ"
#import "process.typ"
#import "version.typ"
#import "modifiers.typ"

#import util: typst-length

Expand Down Expand Up @@ -40,6 +41,8 @@
assert(length / 1cm != 0,
message: "Canvas length must be != 0!")

import "/src/lib/decorations.typ": wave, zigzag, coil

let ctx = (
version: version.version,
length: length,
Expand All @@ -57,8 +60,16 @@
(0, 0, .0, 1)),
// Nodes, stores anchors and paths
nodes: (:),
// group stack
// Group stack
groups: (),
// Registeretd modifier functions
path-modifiers: (
linearize: modifiers.linearize,
wave: modifiers.wave,
//zigzag: modifiers.zigzag,
//coil: modifiers.coil,
ticks: modifiers.ticks,
),
)

let (ctx, bounds, drawables) = process.many(ctx, body)
Expand Down
2 changes: 1 addition & 1 deletion src/draw.typ
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#import "draw/grouping.typ": intersections, group, anchor, copy-anchors, place-anchors, set-ctx, get-ctx, for-each-anchor, on-layer, place-marks, hide, floating
#import "draw/grouping.typ": intersections, group, anchor, copy-anchors, place-anchors, set-ctx, get-ctx, for-each-anchor, on-layer, place-marks, hide, floating, apply-modifier
#import "draw/transformations.typ": set-transform, rotate, translate, scale, set-origin, move-to, set-viewport
#import "draw/styling.typ": set-style, fill, stroke
#import "draw/shapes.typ": circle, circle-through, arc, arc-through, mark, line, grid, content, rect, bezier, bezier-through, catmull, hobby, merge-path
Expand Down
29 changes: 29 additions & 0 deletions src/draw/grouping.typ
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#import "/src/anchor.typ" as anchor_
#import "/src/matrix.typ"
#import "/src/deps.typ"
#import "/src/modifier.typ": apply-path-modifier
#import deps.oxifmt: strfmt

#import "transformations.typ": move-to
Expand Down Expand Up @@ -565,6 +566,34 @@
},)
}

/// Apply one or more element modifiers
///
/// - modifier (string,function): Modifier name or function
/// - body (element):
/// - ..style (style):
#let apply-modifier(modifier, body, close: false, ..style) = {
assert.eq(style.pos(), (),
message: "Unexpected positional argumnets.")

if type(modifier) != array {
modifier = (modifier,)
}

(ctx => {
let (ctx, drawables, ..) = process.many(ctx, util.resolve-body(ctx, body))

let style = styles.resolve(ctx.style, merge: style.named())
style.modifier = modifier

drawables = apply-path-modifier(ctx, style, drawables, close)

return (
ctx: ctx,
drawables: drawables,
)
},)
}

// DEPRECATED TODO: Remove
#let place-anchors(path, name, ..anchors) = {
panic("place-anchors got removed. Use path anchors `(name: <element>, anchor: <number, ratio>)` instead.")
Expand Down
43 changes: 43 additions & 0 deletions src/draw/shapes.typ
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,34 @@
#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": *
#import "grouping.typ": *


// Apply path decoration function fn to drawables
// and return a single drawable.
#let _apply-decoration-fn(ctx, style, drawables, fn) = {
if fn != none {
ctx.transform = matrix.ident()
let (drawables: drawables, ..) = process.many(ctx, (fn)((ctx => {
return (ctx: ctx, drawables: if type(drawables) != array { (drawables,) } else { drawables })
},), ..style, decoration: none))

assert.eq(drawables.len(), 1,
message: "Path decoration function must return a single element")
return drawables.first()
}
return drawables
}

// Apply path decoration to drawables
#let _apply-decoration(ctx, style, drawables, close) = {
return modifier.apply-path-modifier(ctx, style, drawables, close)
}

/// Draws a circle or ellipse.
///
/// #example(```
Expand Down Expand Up @@ -63,6 +86,8 @@
stroke: style.stroke
)

drawables = _apply-decoration(ctx, style, drawables, true)

let (transform, anchors) = anchor_.setup(
(_) => pos,
("center",),
Expand Down Expand Up @@ -136,6 +161,8 @@
stroke: style.stroke
)

drawables = _apply-decoration(ctx, style, drawables, true)

let (transform, anchors) = anchor_.setup(
(anchor) => (
center: center,
Expand Down Expand Up @@ -253,6 +280,8 @@
mode: style.mode
)

drawables = _apply-decoration(ctx, style, drawables, false)

let sector-center = (
x - rx * calc.cos(start-angle),
y - ry * calc.sin(start-angle),
Expand Down Expand Up @@ -566,6 +595,9 @@
close: close
)

// Apply decoration
drawables = _apply-decoration(ctx, style, drawables, close)

// Get bounds
let (transform, anchors) = anchor_.setup(
auto,
Expand Down Expand Up @@ -881,6 +913,7 @@

let drawables = ()
if style.frame != none {
border = _apply-decoration(ctx, style, border, true)
drawables.push(border)
}

Expand Down Expand Up @@ -1110,6 +1143,8 @@
drawable.path(segments, fill: style.fill, stroke: style.stroke, close: true)
}

drawables = _apply-decoration(ctx, style, drawables, true)

// Calculate border anchors
let center = vector.lerp(a, b, .5)
let (width, height, ..) = size
Expand Down Expand Up @@ -1193,6 +1228,8 @@
stroke: style.stroke,
)

drawables = _apply-decoration(ctx, style, drawables, false)

let (transform, anchors) = anchor_.setup(
anchor => (
ctrl-0: ctrl.at(0),
Expand Down Expand Up @@ -1296,6 +1333,8 @@
stroke: style.stroke,
close: close)

drawables = _apply-decoration(ctx, style, drawables, close)

let (transform, anchors) = {
let a = for (i, pt) in pts.enumerate() {
(("pt-" + str(i)): pt)
Expand Down Expand Up @@ -1375,6 +1414,8 @@
stroke: style.stroke,
close: close)

drawables = _apply-decoration(ctx, style, drawables, close)

let (transform, anchors) = {
let a = for (i, pt) in pts.enumerate() {
(("pt-" + str(i)): pt)
Expand Down Expand Up @@ -1465,6 +1506,8 @@
let style = styles.resolve(ctx.style, merge: style)
let drawables = drawable.path(fill: style.fill, stroke: style.stroke, close: close, segments)

drawables = _apply-decoration(ctx, style, drawables, close)

let (transform, anchors) = anchor_.setup(
auto,
(),
Expand Down
1 change: 1 addition & 0 deletions src/draw/transformations.typ
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#import "/src/vector.typ"
#import "/src/util.typ"


// Utility for applying translation to and from
// the origin to apply a transformation matrix to.
//
Expand Down
149 changes: 149 additions & 0 deletions src/modifier.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#import "/src/path-util.typ"

/// A path modifier is a function that accepts a contex, style and
/// 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, close) => {
/// // ... modify the drawable ...
/// return (replacement: ..., decoration: ...)
/// }
/// ```

// Function for slicing a path into three parts,
// a head, a mid section and a tail.
#let slice-segments(segments, start, end) = {
let len = path-util.length(segments)
if type(start) == ratio {
start = len * start / 100%
}
if type(end) == ratio {
end = len * end / 100%
}

let (head, mid, tail) = ((), segments, ())

if start != 0 or end != len {
mid = path-util.shorten-path(segments, start, end)
}

if start != 0 {
head = path-util.shorten-path(segments, 0, len - start)
}

if end != len {
tail = path-util.shorten-path(segments, len - end, 0)
}

return (head, mid, tail)
}

/// Apply a path modifier to a list of drawables
///
/// - 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 == ()
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 new-elements
}

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

let fns = if type(style.modifier) == array {
style.modifier
} else {
(style.modifier,)
}.map(n => {
let name = if type(n) == dictionary {
n.at("name", default: none)
} else {
n
}

let extra-style = if type(n) == dictionary {
n
} else {
(:)
}

let fn = if type(name) == str {
assert(name in ctx.path-modifiers,
message: "Unknown path-modifier: " + repr(n))
ctx.path-modifiers.at(name)
} else {
name
}

(fn: fn, style: style + extra-style)
})

// Unset modifiers to prevent unwanted recursion
style.modifier = ()

// Apply function on all drawables
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)
}
drawables = new
}

return drawables
}
Loading
Loading