diff --git a/doc/style.typ b/doc/style.typ index 7ca077dc3..fc4a83b4c 100644 --- a/doc/style.typ +++ b/doc/style.typ @@ -1,4 +1,5 @@ #import "example.typ": example +#import "/src/lib.typ" #import "@preview/tidy:0.1.0" #import "@preview/t4t:0.3.2": is @@ -95,7 +96,8 @@ read(path), scope: ( example: example, - show-parameter-block: show-parameter-block + show-parameter-block: show-parameter-block, + cetz: lib ) ), show-outline: false, diff --git a/manual.pdf b/manual.pdf index c1f771a26..0abe03f89 100644 Binary files a/manual.pdf and b/manual.pdf differ diff --git a/manual.typ b/manual.typ index fe627268e..d4d80547b 100644 --- a/manual.typ +++ b/manual.typ @@ -658,7 +658,6 @@ Supported charts are: #doc-style.parse-show-module("/src/lib/chart/columnchart.typ") === Examples -- Bar Chart - ```example-vertical import cetz.chart // Left - Basic @@ -806,7 +805,7 @@ let my-star(center, name: none, ..style) = { let def-style = (n: 5, inner-radius: .5, radius: 1) // Resolve the current style ("star") - let style = cetz.styles.resolve(ctx.style, style.named(), + let style = cetz.styles.resolve(ctx.style, merge: style.named(), base: def-style, root: "star") // Compute the corner coordinates @@ -830,6 +829,7 @@ set-style(star: (fill: yellow)) // set-style works, too! my-star((0,6), inner-radius: .3) ``` + = Internals == Context @@ -880,13 +880,13 @@ import cetz.vector let my-star(center, ..style) = { (ctx => { // Define a default style - let def-style = (n: 5, inner-radius: .5, radius: 1) + let def-style = (n: 5, inner-radius: .5, radius: 1, stroke: auto, fill: auto) // Resolve center to a vector let (ctx, center) = cetz.coordinate.resolve(ctx, center) // Resolve the current style ("star") - let style = cetz.styles.resolve(ctx.style, style.named(), + let style = cetz.styles.resolve(ctx.style, merge: style.named(), base: def-style, root: "star") // Compute the corner coordinates diff --git a/src/canvas.typ b/src/canvas.typ index 0312c6aee..adf835edf 100644 --- a/src/canvas.typ +++ b/src/canvas.typ @@ -42,7 +42,7 @@ // Previous element position & bbox prev: (pt: (0, 0, 0)), em-size: measure(box(width: 1em, height: 1em), st), - style: (:), + style: styles.default, // Current transform transform: matrix.mul-mat( matrix.transform-shear-z(.5), diff --git a/src/draw/grouping.typ b/src/draw/grouping.typ index d4bfb15f2..1f6246ac0 100644 --- a/src/draw/grouping.typ +++ b/src/draw/grouping.typ @@ -118,7 +118,7 @@ message: "Unexpected positional arguments: " + repr(style.pos()), ) (ctx => { - let style = styles.resolve(ctx, style.named(), root: "group") + let style = styles.resolve(ctx.style, merge: style.named(), root: "group") let bounds = none let drawables = () @@ -447,7 +447,7 @@ end: path-util.point-on-path(path.segments, 1) ) - let style = styles.resolve(ctx.style, style, root: "mark") + let style = styles.resolve(ctx.style, merge: style, root: "mark") for mark in marks { let (pos, dir) = path-util.direction(path.segments, mark.pos) diff --git a/src/draw/shapes.typ b/src/draw/shapes.typ index aeead32d6..92281b24e 100644 --- a/src/draw/shapes.typ +++ b/src/draw/shapes.typ @@ -51,7 +51,7 @@ (ctx => { let (ctx, pos) = coordinate.resolve(ctx, position) - let style = styles.resolve(ctx.style, style, root: "circle") + let style = styles.resolve(ctx.style, merge: style, root: "circle") let (rx, ry) = util.resolve-radius(style.radius).map(util.resolve-number.with(ctx)) let (cx, cy, cz) = pos let (ox, oy) = (calc.cos(45deg) * rx, calc.sin(45deg) * ry) @@ -143,7 +143,7 @@ let center = util.calculate-circle-center-3pt(a, b, c) - let style = styles.resolve(ctx.style, style, root: "circle") + let style = styles.resolve(ctx.style, merge: style, root: "circle") let (cx, cy, cz) = center let r = vector.dist(a, (cx, cy)) let (ox, oy) = (calc.cos(45deg) * r, calc.sin(45deg) * r) @@ -266,7 +266,7 @@ assert.ne(start-angle, stop-angle, message: "Angle must be greater than 0deg") return (ctx => { - let style = styles.resolve(ctx.style, style, root: "arc") + let style = styles.resolve(ctx.style, merge: style, root: "arc") assert(style.mode in ("OPEN", "PIE", "CLOSE")) let (ctx, arc-start) = coordinate.resolve(ctx, position) @@ -540,7 +540,7 @@ return (ctx => { let (ctx, ..pts) = coordinate.resolve(ctx, from, to) - let style = styles.resolve(ctx.style, style, root: "mark") + let style = styles.resolve(ctx.style, merge: style, root: "mark") return (ctx: ctx, drawables: drawable.mark( ..pts, @@ -585,7 +585,7 @@ return (ctx => { let (ctx, ..pts) = coordinate.resolve(ctx, ..pts) - let style = styles.resolve(ctx.style, style, root: "line") + let style = styles.resolve(ctx.style, merge: style, root: "line") let (transform, anchors) = anchor_.setup( (anchor) => { ( @@ -642,16 +642,17 @@ /// /// = Styling /// *Root:* `grid` +/// / step: TODO +/// / help-lines: TODO /// /// = Anchors /// Supports compass anchors. /// /// - from (coordinate): The top left of the grid /// - to (coordinate): The bottom right of the grid -/// - step (number): Grid spacing. /// - name (none,string): /// - ..style (style): -#let grid(from, to, step: 1, name: none, help-lines: false, ..style) = { +#let grid(from, to, name: none, ..style) = { (from, to).map(coordinate.resolve-system) assert.eq(style.pos(), (), message: "Unexpected positional arguments: " + repr(style.pos())) @@ -665,17 +666,21 @@ (calc.max(from.at(0), to.at(0)), calc.max(from.at(1), to.at(1))) ) - let style = styles.resolve(ctx.style, style) - if help-lines { + let style = styles.resolve(ctx.style, merge: style, root: "grid", base: ( + step: 1, + stroke: auto, + help-lines: false, + )) + if style.help-lines { style.stroke = 0.2pt + gray } - let (x-step, y-step) = if type(step) == dictionary { - (step.at("x", default: 1), step.at("y", default: 1)) - } else if type(step) == array { - step + let (x-step, y-step) = if type(style.step) == dictionary { + (style.step.at("x", default: 1), style.step.at("y", default: 1)) + } else if type(style.step) == array { + style.step } else { - (step, step) + (style.step, style.step) }.map(util.resolve-number.with(ctx)) let drawables = { @@ -685,7 +690,6 @@ x += from.at(0) drawable.path( path-util.line-segment(((x, from.at(1)), (x, to.at(1)))), - fill: style.fill, stroke: style.stroke ) }) @@ -698,7 +702,6 @@ y += from.at(1) drawable.path( path-util.line-segment(((from.at(0), y), (to.at(1), y))), - fill: style.fill, stroke: style.stroke ) }) @@ -816,7 +819,7 @@ } return (ctx => { - let style = styles.resolve(ctx.style, style, root: "content") + let style = styles.resolve(ctx.style, merge: style, root: "content") let padding = util.as-padding-dict(style.padding) for (k, v) in padding { padding.insert(k, util.resolve-number(ctx, v)) @@ -1032,7 +1035,7 @@ transform: ctx.transform ) - let style = styles.resolve(ctx.style, style, root: "rect") + let style = styles.resolve(ctx.style, merge: style, root: "rect") let (x1, y1, z1) = a let (x2, y2, z2) = b let drawables = drawable.path( @@ -1118,7 +1121,7 @@ transform: ctx.transform ) - let style = styles.resolve(ctx.style, style, root: "bezier") + let style = styles.resolve(ctx.style, merge: style, root: "bezier") let curve = (start, end, ..ctrl) let (marks, curve) = if style.mark != none { @@ -1231,7 +1234,7 @@ ) } - let style = styles.resolve(ctx.style, style, root: "catmull") + let style = styles.resolve(ctx.style, merge: style, root: "catmull") let curves = bezier_.catmull-to-cubic( pts, style.tension, @@ -1320,7 +1323,7 @@ ) } - let style = styles.resolve(ctx.style, style, root: "hobby") + let style = styles.resolve(ctx.style, merge: style, root: "hobby") let curves = hobby_.hobby-to-cubic( pts, ta: ta, @@ -1407,7 +1410,7 @@ } } - let style = styles.resolve(ctx.style, style) + let style = styles.resolve(ctx.style, merge: style) let (transform, anchors) = anchor_.setup( anchor => { diff --git a/src/lib/angle.typ b/src/lib/angle.typ index 76eda9122..3c3e4357d 100644 --- a/src/lib/angle.typ +++ b/src/lib/angle.typ @@ -12,7 +12,7 @@ stroke: auto, radius: .5, label-radius: 50%, - mark: styles.default.mark, + mark: auto, ) /// Draw an angle between `a` and `b` through origin `origin` @@ -62,7 +62,7 @@ name: none, ..style ) = draw.group(name: name, ctx => { - let style = styles.resolve(ctx.style, style.named(), base: default-style, root: "angle") + let style = styles.resolve(ctx.style, merge: style.named(), base: default-style, root: "angle") let (ctx, origin, a, b) = coordinate.resolve(ctx, origin, a, b) assert(origin.at(2) == a.at(2) and a.at(2) == b.at(2), diff --git a/src/lib/axes.typ b/src/lib/axes.typ index 36d66a8ba..7b8c6e758 100644 --- a/src/lib/axes.typ +++ b/src/lib/axes.typ @@ -30,6 +30,18 @@ stroke: (paint: gray, dash: "dotted"), fill: none ), + x: ( + fill: auto, + stroke: auto, + mark: auto, + tick: auto + ), + y: ( + fill: auto, + stroke: auto, + mark: auto, + tick: auto + ) ) #let default-style-schoolbook = util.merge-dictionary(default-style, ( @@ -313,7 +325,7 @@ anchor("data-top-right", (w, h)) let style = style.named() - style = styles.resolve(ctx.style, style, root: "axes", + style = styles.resolve(ctx.style, merge: style, root: "axes", base: default-style) let padding = ( @@ -324,6 +336,7 @@ ) let axis-settings = ( + // (axis, side, anchor, placement, tic-dir, name) (left, "west", "east", (0, auto), ( 1, 0), "left"), (right, "east", "west", (w, auto), (-1, 0), "right"), (bottom, "south", "north", (auto, 0), (0, 1), "bottom"), @@ -339,7 +352,10 @@ for (axis, _, anchor, placement, tic-dir, name) in axis-settings { let style = style if name in style { - style = util.merge-dictionary(style, style.at(name)) + style = styles.resolve(style, root: name, base: ( + tick: auto, + grid: auto + )) } if axis != none { @@ -467,7 +483,7 @@ let style = style.named() style = styles.resolve( ctx.style, - style, + merge: style, root: "axes", base: default-style-schoolbook ) @@ -492,9 +508,7 @@ (y-axis, "east", (y-x, auto), (1, 0), "y"), ) - line((-padding.left, x-y), (w + padding.right, x-y), - ..util.merge-dictionary(style, style.at("x", default: (:))), - name: "x-axis") + line((-padding.left, x-y), (w + padding.right, x-y), ..style.x, name: "x-axis") if "label" in x-axis and x-axis.label != none { let anchor = style.label.anchor if style.label.anchor == auto { @@ -504,9 +518,7 @@ anchor: anchor, par(justify: false, x-axis.label)) } - line((y-x, -padding.bottom), (y-x, h + padding.top), - ..util.merge-dictionary(style, style.at("y", default: (:))), - name: "y-axis") + line((y-x, -padding.bottom), (y-x, h + padding.top), ..style.y, name: "y-axis") if "label" in y-axis and y-axis.label != none { let anchor = style.label.anchor if style.label.anchor == auto { diff --git a/src/lib/chart/barchart.typ b/src/lib/chart/barchart.typ index 645b651af..743093085 100644 --- a/src/lib/chart/barchart.typ +++ b/src/lib/chart/barchart.typ @@ -174,8 +174,7 @@ ) group(ctx => { - let style = util.merge-dictionary(barchart-default-style, - styles.resolve(ctx.style, (:), root: "barchart")) + let style = styles.resolve(ctx.style, root: "barchart", base: barchart-default-style) axes.scientific( size: size, diff --git a/src/lib/chart/columnchart.typ b/src/lib/chart/columnchart.typ index 293dbf985..a583bb272 100644 --- a/src/lib/chart/columnchart.typ +++ b/src/lib/chart/columnchart.typ @@ -169,8 +169,8 @@ ) group(ctx => { - let style = util.merge-dictionary(columnchart-default-style, - styles.resolve(ctx.style, (:), root: "columnchart")) + let style = styles.resolve(ctx.style, root: "barchart", base: columnchart-default-style) + axes.scientific(size: size, left: y, diff --git a/src/lib/decorations.typ b/src/lib/decorations.typ index fc548002c..3e1f3bf48 100644 --- a/src/lib/decorations.typ +++ b/src/lib/decorations.typ @@ -73,7 +73,7 @@ group(name: name, ctx => { // Get styles and validate types and values - let style = styles.resolve(ctx.style, style.named(), + let style = styles.resolve(ctx.style, merge: style.named(), root: "brace", base: brace-default-style) let amplitude = style.amplitude @@ -259,7 +259,7 @@ group(name: name, ctx => { // Get styles and validate their types and values - let style = styles.resolve(ctx.style, style.named(), + let style = styles.resolve(ctx.style, merge: style.named(), root: "flat-brace", base: flat-brace-default-style) let amplitude = style.amplitude diff --git a/src/lib/plot.typ b/src/lib/plot.typ index 8b5bbe6a0..696751ba6 100644 --- a/src/lib/plot.typ +++ b/src/lib/plot.typ @@ -310,8 +310,12 @@ if type(data.at(i).mark-style) == function { data.at(i).mark-style = (data.at(i).mark-style)(i) } - data.at(i).mark-style = util.merge-dictionary( - mark-style-base, data.at(i).mark-style) + if type(data.at(i).mark-style) == dictionary { + data.at(i).mark-style = util.merge-dictionary( + mark-style-base, + data.at(i).mark-style + ) + } } } diff --git a/src/lib/plot/legend.typ b/src/lib/plot/legend.typ index df8edcf16..7c7e22852 100644 --- a/src/lib/plot/legend.typ +++ b/src/lib/plot/legend.typ @@ -87,7 +87,7 @@ // Draw a legend box at position relative to anchor of plot-element #let draw-legend(ctx, style, items, size, plot, position, anchor) = { let style = styles.resolve( - ctx.style, style, base: default-style, root: "legend") + ctx.style, merge: style, base: default-style, root: "legend") assert(style.orientation in (ttb, ltr), message: "Unsupported legend orientation.") diff --git a/src/styles.typ b/src/styles.typ index 03a83f0fc..8df0af371 100644 --- a/src/styles.typ +++ b/src/styles.typ @@ -1,86 +1,79 @@ #import "util.typ" -// Default mark style -// -// Assuming a mark ">" is pointing directly to the right: -// - length Sets the length of the mark along its direction (in this case, its horizontal size) -// - width Sets the size of the mark along the normal of its direction -// - inset Sets the inner length of triangular shaped marks -// - scale A factor that is applied to all of the three attributes above -// - sep Is the distance between multiple marks along their path -// -// If a mark is pointing to positive or negative z, the mark will be drawn -// with width on the axis perpendicular to its direction and the styles `z-up` -// vector. -#let _default-mark = ( - scale: 1, // Scaling factor - length: .2, // Length - width: 0.15, // Width - inset: .05, // Arrow mark base inset - sep: .1, // Extra distance between marks - z-up: (0,1,0),// Z-Axis upwards vector - start: none, // Mark start symbol(s) - end: none, // Mark end symbol(s) - stroke: auto, - fill: none, -) - #let default = ( - root: ( - fill: none, - stroke: black + 1pt, - radius: 1, + fill: none, + stroke: black + 1pt, + radius: 1, + /// Bezier shortening mode: + /// - "LINEAR" Moving the affected point and it's next control point (like TikZ "quick" key) + /// - "CURVED" Preserving the bezier curve by calculating new control points + shorten: "LINEAR", + + // Allowed values: + // - none + // - Number + // - Array: (y, x), (top, y, bottom), (top, right, bottom, left) + // - Dictionary: (top:, right:, bottom:, left:) + padding: none, + mark: ( + scale: 1, // A factor that is applied to length, widht, and inset. + length: .2, // The size of the mark along its direction + width: 0.15, // The size of the mark along the normal of its direction + inset: .05, // The inner length of triangular shaped marks + sep: .1, // The distance between multiple marks along their path + z-up: (0,1,0),// If a mark is pointing to +ve or -ve z, the mark will be drawn with width on the axis perpendicular to its direction and this vector. + start: none, // Mark start symbol(s) + end: none, // Mark end symbol(s) + stroke: auto, + fill: auto, + /// If true, the mark points in the direction of the secant from + /// its base to its tip. If false, the tangent at the marks tip is used. + flex: true, + /// Max. number of samples to use for calculating curve positions + /// a higher number gives better results but may slow down compilation. + position-samples: 30 + ), + circle: ( + radius: auto, + stroke: auto, + fill: auto + ), + rect: ( + stroke: auto, + fill: auto, ), - mark: _default-mark, group: ( - padding: none, + padding: auto, + fill: auto, + stroke: auto ), line: ( - mark: _default-mark, + mark: auto, + fill: auto, + stroke: auto, ), bezier: ( - mark: ( - .._default-mark, - /// If true, the mark points in the direction of the secant from - /// its base to its tip. If false, the tangent at the marks tip is used. - flex: true, - /// Max. number of samples to use for calculating curve positions - /// a higher number gives better results but may slow down compilation. - position-samples: 30, - ), - /// Bezier shortening mode: - /// - "LINEAR" Moving the affected point and it's next control point (like TikZ "quick" key) - /// - "CURVED" Preserving the bezier curve by calculating new control points - shorten: "LINEAR", + stroke: auto, + fill: auto, + mark: auto, + shorten: auto, ), catmull: ( tension: .5, - mark: ( - .._default-mark, - /// If true, the mark points in the direction of the secant from - /// its base to its tip. If false, the tangent at the marks tip is used. - flex: true, - /// Max. number of samples to use for calculating curve positions - /// a higher number gives better results but may slow down compilation. - position-samples: 30, - ), - shorten: "LINEAR", + mark: auto, + shorten: auto, + stroke: auto, + fill: auto ), hobby: ( /// Curve start and end omega (curlyness) omega: (1,1), /// Rho function, see /src/hobby.typ for details rho: auto, - mark: ( - .._default-mark, - /// If true, the mark points in the direction of the secant from - /// its base to its tip. If false, the tangent at the marks tip is used. - flex: true, - /// Max. number of samples to use for calculating curve positions - /// a higher number gives better results but may slow down compilation. - position-samples: 30, - ), - shorten: "LINEAR", + mark: auto, + shorten: auto, + stroke: auto, + fill: auto ), arc: ( // Supported values: @@ -88,16 +81,14 @@ // - "CLOSE" // - "PIE" mode: "OPEN", - mark: _default-mark, update-position: true, + mark: auto, + stroke: auto, + fill: auto, + radius: auto ), content: ( - // Allowed values: - // - none - // - Number - // - Array: (y, x), (top, y, bottom), (top, right, bottom, left) - // - Dictionary: (top:, right:, bottom:, left:) - padding: 0, + padding: auto, // Supported values // - none // - "rect" @@ -108,68 +99,96 @@ ), ) -/// Resolve the current style root +/// You can use this to combine the style in `ctx`, the style given by a user for a single element and an element's default style. +/// +/// `base` is first merged onto `dict` without overwriting existing values, and if `root` is given it is merged onto that key of `dict`. `merge` is then merged onto `dict` but does overwrite existing entries, if `root` is given it is merged onto that key of `dict`. Then entries in `dict` that are `` inherit values from their nearest ancestor and entries of type `` are merged with their closest ancestor. +/// ```typ +/// #let dict = ( +/// stroke: "black", +/// fill: none, +/// mark: (stroke: auto, fill: "blue"), +/// line: (stroke: auto, mark: auto, fill: "red") +/// ) +/// #styles.resolve(dict, merge: (mark: (stroke: "yellow")), root: "line") +/// ``` +/// #let dict = ( +/// stroke: "black", +/// fill: none, +/// mark: (stroke: auto, fill: "blue"), +/// line: (stroke: auto, mark: auto, fill: "red") +/// ) +/// #cetz.styles.resolve(dict, merge: (mark: (stroke: "yellow")), root: "line") +/// The following is a more detailed explanation of how the algorithm works to use as a reference if needed. It should be updated whenever changes are made. +/// Remember that dictionaries are recursivley merged, if an entry it is any other type it is simply updated. (dict + dict = merged dict, value + dict = dict, dict + value = value) +/// First if `base` is given, it will be merged without overwriting values onto `dict`. If `root` is given it will be merged onto that key of `dict`. +/// Each level of `dict` is then processed with these steps. If `root` is given the level with that key will be the first, otherwise the whole of `dict` is processed. +/// + Values on the corresponding level of `merge` are inserted into the level if the key does not exist on the level or if they are not both dictionaries. If they are both dictionaries their values will be inserted in the same stage at a lower level. +/// + If an entry is `auto` or a dictionary, the tree is travelled back up until an entry with the same key is found. If the current entry is `auto` the value of the ancestor's entry is copied. Or if the current entry and ancestor entry is a dictionary, they are merged with the current entry overwriting any values in it's ancestors. +/// + Each entry that is a dictionary is then resolved from step 1. /// /// #example(``` /// get-ctx(ctx => { /// // Get the current "mark" style -/// content((0,0), [#cetz.styles.resolve(ctx.style, (:), root: "mark")]) +/// content((0,0), [#cetz.styles.resolve(ctx.style, root: "mark")]) /// }) /// ```) /// -/// - current (style): Current context style (`ctx.style`). -/// - new (style): Style values overwriting the current style (or an empty dict). -/// I.e. inline styles passed with an element: `line(.., stroke: red)`. +/// - dict (style): Current context style (`ctx.style`). +/// - merge (style): Style values overwriting the current style. I.e. inline styles passed with an element: `line(.., stroke: red)`. /// - root (none, str): Style root element name. -/// - base (none, style): Base style. For use with custom elements, see `lib/angle.typ` as an example. -#let resolve(current, new, root: none, base: none) = { - if base != none { - if root != none { - let default = default - default.insert(root, base) - base = default - } else { - base = util.merge-dictionary(default, base) +/// - base (none, style): Style values to merge into `dict` without overwriting it. +#let resolve(dict, root: none, merge: (:), base: (:)) = { + let resolve(dict, ancestors, merge) = { + // Merge. If both values are dictionaries, merge's values will be inserted at a lower level in this step. + for (k, v) in merge { + if k not in dict or not (type(v) == dictionary and type(dict.at(k)) == dictionary) { + dict.insert(k, v) + } } - } else { - base = default - } - let resolve-auto(hier, dict) = { - if type(dict) != dictionary { return dict } + // For each entry that is a dictionary or `auto`, travel back up the tree until it finds an entry with the same key. for (k, v) in dict { - if v == auto { - for i in range(0, hier.len()) { - let parent = hier.at(i) - if k in parent { - v = parent.at(k) - if v != auto { - dict.insert(k, v) - break + let is-dict = type(v) == dictionary + if is-dict or v == auto { + for ancestor in ancestors { + if k in ancestor { + // If v is auto and the ancestor's value is not auto, update v. + if ancestor.at(k) != auto and v == auto { + v = ancestor.at(k) + // If both values are dictionaries, merge them. Values in v overwrite its ancestor's value. + } else if is-dict and type(ancestor.at(k)) == dictionary { + v = util.merge-dictionary(ancestor.at(k), v) } + // Retain the updated value. Because all of the ancestors have already been processed even if a v is still auto that just means the key at the highest level either is auto or doesn't exist. + dict.insert(k, v) + break } } } + } + + // Record history here so it doesn't change. + ancestors = (dict,) + ancestors + // Because only keys on this level have been processed, process all children of this level. + for (k, v) in dict { if type(v) == dictionary { - dict.insert(k, resolve-auto((dict,) + hier, v)) + dict.insert(k, resolve(v, ancestors, merge.at(k, default: (:)))) } } return dict } - let s = base.root - if root != none and root in base { - s = util.merge-dictionary(s, base.at(root)) - } else { - s = util.merge-dictionary(s, base) - } - if root != none and root in current { - s = util.merge-dictionary(s, current.at(root)) - } else { - s = util.merge-dictionary(s, current) + if base != (:) { + if root != none { + let a = (:) + a.insert(root, base) + base = a + } + dict = util.merge-dictionary(dict, base, overwrite: false) } - - s = util.merge-dictionary(s, new) - s = resolve-auto((current, s, base.root), s) - return s + return resolve( + if root != none { dict.at(root) } else { dict }, + if root != none {(dict,)} else {()}, + merge + ) } diff --git a/src/util.typ b/src/util.typ index fed80e9c4..bd02d1bbc 100644 --- a/src/util.typ +++ b/src/util.typ @@ -172,19 +172,14 @@ /// - b (dictionary): Dictionary b /// -> dictionary #let merge-dictionary(a, b, overwrite: true) = { - if type(a) == dictionary and type(b) == dictionary { - let c = a - for (k, v) in b { - if not k in c { - c.insert(k, v) - } else { - c.at(k) = merge-dictionary(a.at(k), v, overwrite: overwrite) - } + for (k, v) in b { + if type(a) == dictionary and k in a and type(v) == dictionary and type(a.at(k)) == dictionary { + a.insert(k, merge-dictionary(a.at(k), v, overwrite: overwrite)) + } else if overwrite or k not in a { + a.insert(k, v) } - return c - } else { - return if overwrite {b} else {a} } + return a } // Measure content in canvas coordinates diff --git a/tests/arc/ref.png b/tests/arc/ref.png index d206565e2..f97542d47 100644 Binary files a/tests/arc/ref.png and b/tests/arc/ref.png differ diff --git a/tests/axes/test.typ b/tests/axes/test.typ index ad7dff042..4c713a842 100644 --- a/tests/axes/test.typ +++ b/tests/axes/test.typ @@ -1,14 +1,16 @@ #set page(width: auto, height: auto) -#import "../../src/lib.typ": * +#import "/src/lib.typ": * // Schoolbook Axis Styling #box(stroke: 2pt + red, canvas({ import draw: * - set-style(axes: (stroke: blue)) - set-style(axes: (padding: .75)) - set-style(axes: (x: (stroke: red))) - set-style(axes: (y: (stroke: green, tick: (stroke: blue, length: .3)))) + set-style(axes: ( + stroke: blue, + padding: .75, + x: (stroke: red), + y: (stroke: green, tick: (stroke: blue, length: .3)) + )) axes.school-book(size: (6, 6), axes.axis(min: -1, max: 1, ticks: (step: 1, minor-step: auto, grid: "both")),