From ee459508c5e8bcf317fcfe0d12759e251e68e139 Mon Sep 17 00:00:00 2001 From: RubixDev Date: Sun, 1 Oct 2023 17:08:20 +0800 Subject: [PATCH 1/6] feat(decorations): add `outer-pointiness` style key --- manual.typ | 22 +++++++++++++---- src/lib/decorations.typ | 52 ++++++++++++++++++++++++++++------------- 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/manual.typ b/manual.typ index 974b1919..9d3dd49d 100644 --- a/manual.typ +++ b/manual.typ @@ -1063,23 +1063,35 @@ Various pre-made shapes and lines. #show-module-fn(decorations-module, "brace") ```example import cetz.decorations: brace -brace((0, 0), (4, -.5), pointiness: 25deg, amplitude: .8, debug: true) +let text = text.with(size: 12pt, font: "Linux Libertine") + +brace((0, 0), (4, -.5), pointiness: 25deg, outer-pointiness: auto, amplitude: .8, debug: true) brace((0, -.5), (0, -3.5), name: "brace") content("brace.content", [$P_1$]) // styling can be passed to the underlying `merge-path` call -brace((1.5, -2), (4.5, -2), amplitude: 1, pointiness: .5, stroke: orange + 2pt, fill: maroon, close: true, name: "saloon") -content((rel: (0, -.15), to: "saloon.center"), text(12pt, fill: orange, font: "Linux Libertine", smallcaps[*Saloon*])) +brace((1, -3), (4, -3), amplitude: 1, pointiness: .5, stroke: orange + 2pt, fill: maroon, close: true, name: "saloon") +content((rel: (0, -.15), to: "saloon.center"), text(fill: orange, smallcaps[*Saloon*])) // as part of another path -set-origin((3, -3)) +set-origin((2, -5)) merge-path({ brace((+1, .5), (+1, -.5), amplitude: .3, pointiness: .5) brace((-1, .5), (-1, -.5), amplitude: .3, pointiness: .5, flip: true) }, fill: white, close: true) -content((0, 0), text(.8em)[Hello, World!]) +content((0, 0), text(size: 10pt)[Hello, World!]) + +brace((-1.5, -2.5), (2, -2.5), pointiness: 1, outer-pointiness: 1, stroke: olive, fill: green, name: "hill") +content((rel: (.3, .1), to: "hill.center"), text[εїз]) ``` +#STYLING + +#def-arg("amplitude", ``, default: .7, [Determines how much the brace rises above the base line.]) +#def-arg("pointiness", ` or `, default: 15deg, [How pointy the spike should be. #0deg or `0` for maximum pointiness, #90deg or `1` for minimum.]) +#def-arg("outer-pointiness", ` or or `, default: 0, [How pointy the outer edges should be. #0deg or `0` for maximum pointiness (allowing for a smooth transition to a straight line), #90deg or `1` for minimum. Setting this to #auto will use the value set for `pointiness`.]) +#def-arg("content-offset", ``, default: .3, [Offset of the `content` anchor from the spike.]) + ==== Default `brace` Style #decorations.brace-default-style diff --git a/src/lib/decorations.typ b/src/lib/decorations.typ index efb96d09..b30cc38a 100644 --- a/src/lib/decorations.typ +++ b/src/lib/decorations.typ @@ -18,6 +18,7 @@ #let brace-default-style = ( amplitude: .7, pointiness: 15deg, + outer-pointiness: 0, content-offset: .3, ) @@ -25,12 +26,6 @@ /// /// *Style root:* `brace`. /// -/// *Additional styles keys:* -/// / amplitude (number): Determines how much the brace rises above the base line. -/// / pointiness (angle): How pointy the spike should be. -/// #0deg or #0 for maximum pointiness, #90deg or #1 for minimum. -/// / content-offset (number): Offset of the `content` anchor from the spike. -/// /// *Anchors:* /// / start: Where the brace starts, same as the `start` parameter. /// / end: Where the brace end, same as the `end` parameter. @@ -38,7 +33,7 @@ /// by `amplitude` towards the pointing direction. /// / content: Point to place content/text at, in front of the spike. /// / center: Center of the enclosing rectangle. -/// / (a-i): Debug points `a` through `i`. +/// / (a-k): Debug points `a` through `k`. /// /// - start (coordinate): Start point /// - end (coordinate): End point @@ -63,6 +58,7 @@ } group(name: name, ctx => { + // get styles and validate types and values let style = util.merge-dictionary(brace-default-style, styles.resolve(ctx.style, style.named(), root: "brace")) @@ -72,17 +68,33 @@ message: "amplitude must be a number", ) - // get pointiness from styles let pointiness = style.pointiness assert( - (type(pointiness) in (int, float) + type(pointiness) in (int, float) and pointiness >= 0 and pointiness <= 1 or type(pointiness) == angle - and pointiness >= 0deg and pointiness <= 90deg), + and pointiness >= 0deg and pointiness <= 90deg, message: "pointiness must be a factor between 0 and 1 or an angle between 0deg and 90deg", ) let pointiness = if type(pointiness) == angle { pointiness } else { pointiness * 90deg } + let outer-pointiness = style.outer-pointiness + assert( + outer-pointiness == auto + or type(outer-pointiness) in (int, float) + and outer-pointiness >= 0 and outer-pointiness <= 1 + or type(outer-pointiness) == angle + and outer-pointiness >= 0deg and outer-pointiness <= 90deg, + message: "outer-pointiness must be a factor between 0 and 1 or an angle between 0deg and 90deg or auto", + ) + let outer-pointiness = if outer-pointiness == auto { + pointiness + } else if type(outer-pointiness) == angle { + outer-pointiness + } else { + outer-pointiness * 90deg + } + let content-offset = style.content-offset assert( type(content-offset) in (int, float), @@ -116,23 +128,31 @@ line(f, h, stroke: orange) } - // 'i' is the point where the content should be placed. It is offset from the spike (point 'f') + // 'i' and 'j' are the control points for the outer ends + let i = (_rotate-around.with(angle: -outer-pointiness), a, d) + let j = (_rotate-around.with(angle: +outer-pointiness), b, c) + if debug { + line(a, i, stroke: purple) + line(b, j, stroke: orange) + } + + // 'k' is the point where the content should be placed. It is offset from the spike (point 'f') // by 'content-offset' in the direction the spike is pointing - let i = ((a, b) => { + let k = ((a, b) => { let rel = vector.sub(b, a) let scaled = vector.scale(vector.norm(rel), vector.len(rel) + content-offset) return vector.add(a, scaled) }, e, f) - let points = (a: a, b: b, c: c, d: d, e: e, f: f, g: g, h: h, i: i) + let points = (a: a, b: b, c: c, d: d, e: e, f: f, g: g, h: h, i: i, j: j, k: k) // combine the two bezier curves using 'merge-path' and apply styling merge-path({ - bezier(a, f, d, g) - bezier(f, b, h, c) + bezier(a, f, i, g) + bezier(f, b, h, j) }, ..style) // define some named anchors anchor("spike", f) - anchor("content", i) + anchor("content", k) anchor("start", a) anchor("end", b) anchor("center", (e, .5, f)) From 16f4f0f27bd70ee7f532478a4b68f27ba71b05c8 Mon Sep 17 00:00:00 2001 From: RubixDev Date: Sun, 1 Oct 2023 18:31:00 +0800 Subject: [PATCH 2/6] fix(decorations): current pos after flipped brace is the start not end --- src/lib/decorations.typ | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/decorations.typ b/src/lib/decorations.typ index b30cc38a..a6b5f462 100644 --- a/src/lib/decorations.typ +++ b/src/lib/decorations.typ @@ -169,5 +169,5 @@ } }) // move to end point so the current position after this is the end position - move-to(end) + move-to(if flip { start } else { end }) } From 9f43698f86ac7852444a545d561ab322152b92a8 Mon Sep 17 00:00:00 2001 From: RubixDev Date: Sun, 1 Oct 2023 19:49:38 +0800 Subject: [PATCH 3/6] fix(decorations): flipped braces misbehaving inside `merge-path` --- manual.typ | 4 ++-- src/lib/decorations.typ | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/manual.typ b/manual.typ index 9d3dd49d..5156ce71 100644 --- a/manual.typ +++ b/manual.typ @@ -1077,12 +1077,12 @@ content((rel: (0, -.15), to: "saloon.center"), text(fill: orange, smallcaps[*Sal set-origin((2, -5)) merge-path({ brace((+1, .5), (+1, -.5), amplitude: .3, pointiness: .5) - brace((-1, .5), (-1, -.5), amplitude: .3, pointiness: .5, flip: true) + brace((-1, -.5), (-1, .5), amplitude: .3, pointiness: .5) }, fill: white, close: true) content((0, 0), text(size: 10pt)[Hello, World!]) brace((-1.5, -2.5), (2, -2.5), pointiness: 1, outer-pointiness: 1, stroke: olive, fill: green, name: "hill") -content((rel: (.3, .1), to: "hill.center"), text[εїз]) +content((rel: (.3, .1), to: "hill.center"), text[*εїз*]) ``` #STYLING diff --git a/src/lib/decorations.typ b/src/lib/decorations.typ index a6b5f462..b82ebfd4 100644 --- a/src/lib/decorations.typ +++ b/src/lib/decorations.typ @@ -37,7 +37,7 @@ /// /// - start (coordinate): Start point /// - end (coordinate): End point -/// - flip (bool): Flip the brace around, same as swapping the start and end points +/// - flip (bool): Flip the brace around /// - debug (bool): Show debug lines and points /// - name (string, none): Element name /// - ..style (style): Style attributes @@ -52,11 +52,6 @@ // validate coordinates let t = (start, end).map(coordinate.resolve-system) - // flipping is achieved by swapping the start and end points, the parameter is just for convenience - if flip { - (start, end) = (end, start) - } - group(name: name, ctx => { // get styles and validate types and values let style = util.merge-dictionary(brace-default-style, @@ -101,6 +96,13 @@ message: "content-offset must be a number", ) + // we flip the brace by inverting the amplitude and pointiness values + if flip { + amplitude *= -1 + pointiness *= -1 + outer-pointiness *= -1 + } + // 'abcd' is a rectangle with the base line 'ab' and the height 'amplitude' let a = start let b = end @@ -169,5 +171,5 @@ } }) // move to end point so the current position after this is the end position - move-to(if flip { start } else { end }) + move-to(end) } From 87f59623f51557c8ec67f77759b8291732e75268 Mon Sep 17 00:00:00 2001 From: RubixDev Date: Sun, 1 Oct 2023 19:54:44 +0800 Subject: [PATCH 4/6] chore(decorations): show the incorrect value on failed assertions --- src/lib/decorations.typ | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/decorations.typ b/src/lib/decorations.typ index b82ebfd4..5a476360 100644 --- a/src/lib/decorations.typ +++ b/src/lib/decorations.typ @@ -60,7 +60,7 @@ let amplitude = style.amplitude assert( type(amplitude) in (int, float), - message: "amplitude must be a number", + message: "amplitude must be a number, got " + repr(amplitude), ) let pointiness = style.pointiness @@ -69,7 +69,7 @@ and pointiness >= 0 and pointiness <= 1 or type(pointiness) == angle and pointiness >= 0deg and pointiness <= 90deg, - message: "pointiness must be a factor between 0 and 1 or an angle between 0deg and 90deg", + message: "pointiness must be a factor between 0 and 1 or an angle between 0deg and 90deg, got " + repr(pointiness), ) let pointiness = if type(pointiness) == angle { pointiness } else { pointiness * 90deg } @@ -80,7 +80,7 @@ and outer-pointiness >= 0 and outer-pointiness <= 1 or type(outer-pointiness) == angle and outer-pointiness >= 0deg and outer-pointiness <= 90deg, - message: "outer-pointiness must be a factor between 0 and 1 or an angle between 0deg and 90deg or auto", + message: "outer-pointiness must be a factor between 0 and 1 or an angle between 0deg and 90deg or auto, got " + repr(outer-pointiness), ) let outer-pointiness = if outer-pointiness == auto { pointiness @@ -93,7 +93,7 @@ let content-offset = style.content-offset assert( type(content-offset) in (int, float), - message: "content-offset must be a number", + message: "content-offset must be a number, got " + repr(content-offset), ) // we flip the brace by inverting the amplitude and pointiness values From be61268351198e89ea260891f242f3e422226f6e Mon Sep 17 00:00:00 2001 From: RubixDev Date: Sun, 1 Oct 2023 19:56:55 +0800 Subject: [PATCH 5/6] feat(decorations): allow setting font size of brace debug text --- src/lib/decorations.typ | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/decorations.typ b/src/lib/decorations.typ index 5a476360..68a69a9b 100644 --- a/src/lib/decorations.typ +++ b/src/lib/decorations.typ @@ -20,6 +20,7 @@ pointiness: 15deg, outer-pointiness: 0, content-offset: .3, + debug-text-size: 6pt, ) /// Draw a curly brace between two points. @@ -166,7 +167,7 @@ // label all points in debug mode if debug { for (name, point) in points { - content(point, box(fill: luma(240), inset: .5pt, text(6pt, raw(name)))) + content(point, box(fill: luma(240), inset: .5pt, text(style.debug-text-size, raw(name)))) } } }) From cd99a32fe8beed3416fa9aac1e92d4bc9abfb0a3 Mon Sep 17 00:00:00 2001 From: RubixDev Date: Sun, 1 Oct 2023 19:59:15 +0800 Subject: [PATCH 6/6] docs: document brace's `debug-text-size` style --- manual.typ | 1 + 1 file changed, 1 insertion(+) diff --git a/manual.typ b/manual.typ index 5156ce71..675d568e 100644 --- a/manual.typ +++ b/manual.typ @@ -1091,6 +1091,7 @@ content((rel: (.3, .1), to: "hill.center"), text[*εїз*]) #def-arg("pointiness", ` or `, default: 15deg, [How pointy the spike should be. #0deg or `0` for maximum pointiness, #90deg or `1` for minimum.]) #def-arg("outer-pointiness", ` or or `, default: 0, [How pointy the outer edges should be. #0deg or `0` for maximum pointiness (allowing for a smooth transition to a straight line), #90deg or `1` for minimum. Setting this to #auto will use the value set for `pointiness`.]) #def-arg("content-offset", ``, default: .3, [Offset of the `content` anchor from the spike.]) +#def-arg("debug-text-size", ``, default: 6pt, [Font size of displayed debug points when `debug` is #true.]) ==== Default `brace` Style #decorations.brace-default-style