Skip to content

Commit

Permalink
Merge pull request #238 from RubixDev/flat-brace
Browse files Browse the repository at this point in the history
feat(decorations): add flat-brace
  • Loading branch information
johannes-wolf authored Oct 3, 2023
2 parents 51fa85f + 8ceb95d commit cea2081
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 0 deletions.
52 changes: 52 additions & 0 deletions manual.typ
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,58 @@ content((rel: (.3, .1), to: "hill.center"), text[*εїз*])
==== Default `brace` Style
#decorations.brace-default-style

#show-module-fn(decorations-module, "flat-brace")
```example
import cetz.decorations: flat-brace
flat-brace((), (x: 5))
flat-brace((0, 0), (5, 0), flip: true, aspect: .3)
flat-brace((), (rel: (-2, -1)), name: "a")
flat-brace((), (0, 0), amplitude: 1, curves: 1.5, outer-curves: .5)
content("a.content", [$P_2$])
flat-brace((0, -3), (5, -3), debug: true, amplitude: 1, aspect: .4, curves: (1.5, .9, 1, .1), outer-curves: (1, .3, .1, .7))
// triangle and square braces
flat-brace((0, -4), (2.4, -4), curves: (auto, 0, 0, 0))
flat-brace((2.6, -4), (5, -4), curves: 0)
merge-path(close: true, fill: white, {
move-to((.5, -6))
flat-brace((), (rel: (1, 1)))
flat-brace((), (rel: (2, 0)), flip: true, name: "top")
flat-brace((), (rel: (1, -1)))
flat-brace((), (rel: (-1, -1)))
flat-brace((), (rel: (-2, 0)), flip: true, name: "bottom")
flat-brace((), (rel: (-1, 1)))
})
content(("top.spike", .5, "bottom.spike"), [Hello, World!])
```

#STYLING

#def-arg("amplitude", `<number>`, default: decorations.flat-brace-default-style.amplitude, [Determines how much the brace rises above the base line.])
#def-arg("aspect", `<number>`, default: decorations.flat-brace-default-style.aspect, [Determines the fraction of the total length where the spike will be placed.])
#block(breakable: false, def-arg("curves", `<array> or <number>`, default: decorations.flat-brace-default-style.curves, [
Customizes the control points of the curved parts.
Setting a single number is the same as setting ```typc (num, auto, auto, auto)```.
Setting any item to #auto will use its default value.
The first item specifies the curve widths as a fraction of the amplitude.
The second item specifies the length of the green and blue debug lines as a fraction of the curve's width.
The third item specifies the vertical offset of the red and purple debug lines as a fraction of the curve's height.
The fourth item specifies the horizontal offset of the red and purple debug lines as a fraction of the curve's width.
]))
#def-arg("outer-curves", `<array> or <number> or <auto>`, default: decorations.flat-brace-default-style.outer-curves, [
Customizes the control points of just the outer two curves (just the blue and purple debug lines).
Overrides settings from `curves`.
Setting the entire value or individual items to #auto uses the values from `curves` as fallbacks.
])
#def-arg("content-offset", `<number>`, default: decorations.flat-brace-default-style.content-offset, [Offset of the `content` anchor from the spike.])
#def-arg("debug-text-size", `<length>`, default: decorations.flat-brace-default-style.debug-text-size, [Font size of displayed debug points when `debug` is #true.])

==== Default `flat-brace` Style
#decorations.flat-brace-default-style

= Advanced Functions

== Coordinate
Expand Down
184 changes: 184 additions & 0 deletions src/lib/decorations.typ
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,187 @@
// move to end point so the current position after this is the end position
move-to(end)
}

#let flat-brace-default-style = (
amplitude: .3,
aspect: .5,
curves: (1, .5, .6, .15),
outer-curves: auto,
content-offset: .3,
debug-text-size: 6pt,
)

/// Draw a flat curly brace between two points.
///
/// This mimics the braces from TikZ's `decorations.pathreplacing` library#footnote[https://github.com/pgf-tikz/pgf/blob/6e5fd71581ab04351a89553a259b57988bc28140/tex/generic/pgf/libraries/decorations/pgflibrarydecorations.pathreplacing.code.tex#L136-L185].
/// In contrast to @@brace(), these braces use straight line segments, resulting
/// in better looks for long braces with a small amplitude.
///
/// *Style root:* `flat-brace`.
///
/// *Anchors:*
/// / start: Where the brace starts, same as the `start` parameter.
/// / end: Where the brace end, same as the `end` parameter.
/// / spike: Point of the spike's top.
/// / content: Point to place content/text at, in front of the spike.
/// / center: Center of the enclosing rectangle.
/// / (a-h): Debug points `a` through `h`.
///
/// - start (coordinate): Start point
/// - end (coordinate): End point
/// - flip (bool): Flip the brace around
/// - debug (bool): Show debug lines and points
/// - name (string, none): Element name
/// - ..style (style): Style attributes
#let flat-brace(
start,
end,
flip: false,
debug: false,
name: none,
..style,
) = {
// validate coordinates
let t = (start, end).map(coordinate.resolve-system)

group(name: name, ctx => {
// get styles and validate their types and values
let style = util.merge-dictionary(flat-brace-default-style,
styles.resolve(ctx.style, style.named(), root: "flat-brace"))

let amplitude = style.amplitude
assert(
type(amplitude) in (int, float),
message: "amplitude must be a number, got " + repr(amplitude),
)
// we achieve flipping by inverting the amplitude
if flip { amplitude *= -1 }

let aspect = style.aspect
assert(
type(aspect) in (int, float)
and aspect >= 0 and aspect <= 1,
message: "aspect must be a factor between 0 and 1, got " + repr(aspect),
)

let inner-curves = style.curves
assert(
type(inner-curves) in (int, float)
or type(inner-curves) == array
and inner-curves.all(v => type(v) in (int, float, type(auto))),
message: "curves must be a number, or an array of numbers or auto, got " + repr(inner-curves),
)
if type(inner-curves) in (int, float) { inner-curves = (inner-curves,) }
while inner-curves.len() < flat-brace-default-style.curves.len() {
inner-curves.push(auto)
}
inner-curves = inner-curves.enumerate().map(((idx, v)) => if v == auto {
flat-brace-default-style.curves.at(idx)
} else { v })

let outer-curves = style.outer-curves
assert(
type(outer-curves) in (int, float, type(auto))
or type(outer-curves) == array
and outer-curves.all(v => type(v) in (int, float, type(auto))),
message: "outer-curves must be auto, a number, or an array of numbers or auto, got " + repr(outer-curves),
)
if outer-curves == auto {
outer-curves = inner-curves
} else {
if type(outer-curves) in (int, float) { outer-curves = (outer-curves,) }
while outer-curves.len() < inner-curves.len() { outer-curves.push(auto) }
outer-curves = outer-curves.enumerate()
.map(((idx, v)) => if v == auto { inner-curves.at(idx) } else { v })
}

let content-offset = style.content-offset
assert(
type(content-offset) in (int, float),
message: "content-offset must be a number, got " + repr(content-offset),
)

// all the following code assumes the brace to start at (0, 0), growing to the right,
// pointing upwards, so we set the origin and rotate the entire group accordingly
let start = coordinate.resolve(ctx, start)
let end = coordinate.resolve(ctx, end)
set-origin(start)
rotate(vector.angle2(start, end))

let length = vector.dist(start, end)
let middle = aspect * length
let horizon = amplitude / 2

let normal-outer = calc.abs(amplitude * outer-curves.at(0))
let normal-inner = calc.abs(amplitude * inner-curves.at(0))
let length-left = middle
let length-right = length - middle

// width of left-outer, left-inner, right-inner, right-outer curve segments
let lo = if 2 * normal-outer > length-left { length-left / 2 } else { normal-outer }
let li = if 2 * normal-inner > length-left { length-left / 2 } else { normal-inner }
let ri = if 2 * normal-inner > length-right { length-right / 2 } else { normal-inner }
let ro = if 2 * normal-outer > length-right { length-right / 2 } else { normal-outer }

// 'a' and 'b' are start and end
let a = ( 0, 0)
let b = (length, 0)
// 'c' is the spike's top
let c = (middle, amplitude)
// 'de' is the left line, 'fg' is the right line
let d = ( lo, horizon)
let e = (middle - li, horizon)
let f = (middle + ri, horizon)
let g = (length - ro, horizon)
// 'h' is where to place content, above the spike
let h = (middle, amplitude + content-offset)

// list of all named points to show in debug mode
let points = (a: a, b: b, c: c, d: d, e: e, f: f, g: g, h: h)

// bezier control points: in 'dlc' 'd' stands for the point 'd' where the control point is used,
// 'l' stands for left of spike, 'c' stands for control point
let dlc = ( (1 - outer-curves.at(1)) * lo, horizon)
let elc = (middle - (1 - inner-curves.at(1)) * li, horizon)
let frc = (middle + (1 - inner-curves.at(1)) * ri, horizon)
let grc = (length - (1 - outer-curves.at(1)) * ro, horizon)
let alc = ( outer-curves.at(3) * lo, outer-curves.at(2) / 2 * amplitude)
let clc = (middle - inner-curves.at(3) * li, (1 - inner-curves.at(2) / 2) * amplitude)
let crc = (middle + inner-curves.at(3) * ri, (1 - inner-curves.at(2) / 2) * amplitude)
let brc = (length - outer-curves.at(3) * ro, outer-curves.at(2) / 2 * amplitude)

merge-path({
bezier(a, d, alc, dlc)
bezier(e, c, elc, clc)
bezier(c, f, crc, frc)
bezier(g, b, grc, brc)
})
// define some named anchors
anchor("spike", c)
anchor("content", h)
anchor("start", a)
anchor("end", b)
anchor("center", (d, .5, g))
// define anchors for all points
for (name, point) in points {
anchor(name, point)
}
if debug {
// show bezier control points using colored lines
line(stroke: purple, a, alc)
line(stroke: blue, d, dlc)
line(stroke: olive, e, elc)
line(stroke: red, c, clc)
line(stroke: red, c, crc)
line(stroke: olive, f, frc)
line(stroke: blue, g, grc)
line(stroke: purple, b, brc)
// show all named points
for (name, point) in points {
content(point, box(fill: luma(240), inset: .5pt, text(style.debug-text-size, raw(name))))
}
}
})
// move to end point so the current position after this is the end position
move-to(end)
}
Binary file modified tests/decorations/ref.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions tests/decorations/test.typ
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,12 @@
decorations.brace((0, -4), (3, -4), pointiness: 1)
decorations.brace((0, -5), (3, -5), pointiness: 45deg)
}))

#box(stroke: 2pt + red, canvas({
decorations.flat-brace((0, 0), (3, 0))
decorations.flat-brace((0, 0), (3, 0), flip: true)
decorations.flat-brace((0, -1), (3, -1), debug: true, debug-text-size: 4pt)
decorations.flat-brace((0, -2), (3, -2), amplitude: .7)
decorations.flat-brace((0, -2.5), (3, -2.5), curves: (.5, 0, 0, 0), outer-curves: 1)
decorations.flat-brace((0, -3), (3, -3), aspect: .3)
}))

0 comments on commit cea2081

Please sign in to comment.