diff --git a/src/canvas.typ b/src/canvas.typ index 57c7df76..0c6fe55b 100644 --- a/src/canvas.typ +++ b/src/canvas.typ @@ -58,7 +58,9 @@ marks: ( mnemonics: (:), marks: (:), - ) + ), + // coordinate resolver + resolve-coordinate: none, ) let (ctx, bounds, drawables) = process.many(ctx, body) diff --git a/src/coordinate.typ b/src/coordinate.typ index 7bd23113..2fe937e5 100644 --- a/src/coordinate.typ +++ b/src/coordinate.typ @@ -251,7 +251,7 @@ /// Figures out what system a coordinate belongs to and returns the corresponding string. /// - c (coordinate): The coordinate to find the system of. /// -> str -#let resolve-system(c) = { +#let resolve-system(ctx, c) = { let t = if type(c) == dictionary { let keys = c.keys() let len = c.len() @@ -319,10 +319,20 @@ /// - update (bool): Update the context's last position /// -> array #let resolve(ctx, ..coordinates, update: true) = { + let resolvers = if type(ctx.resolve-coordinate) == array { + ctx.resolve-coordinate + } else { + () + } + let result = () for c in coordinates.pos() { - let t = resolve-system(c) - let out = if t == "xyz" { + for resolver in resolvers.rev() { + c = resolver(ctx, c) + } + + let t = resolve-system(ctx, c) + c = if t == "xyz" { resolve-xyz(c) } else if t == "previous" { ctx.prev.pt @@ -348,9 +358,10 @@ }.map(util.resolve-number.with(ctx)) if update { - ctx.prev.pt = out + ctx.prev.pt = c } - result.push(out) + + result.push(c) } return (ctx, ..result) diff --git a/src/draw.typ b/src/draw.typ index e46b87de..cef4e6d2 100644 --- a/src/draw.typ +++ b/src/draw.typ @@ -3,4 +3,4 @@ #import "draw/styling.typ": set-style, fill, stroke, register-mark #import "draw/shapes.typ": circle, circle-through, arc, arc-through, mark, line, grid, content, rect, bezier, bezier-through, catmull, hobby, merge-path #import "draw/projection.typ": ortho, on-xy, on-xz, on-yz -#import "draw/util.typ": assert-version +#import "draw/util.typ": assert-version, register-coordinate-resolver diff --git a/src/draw/grouping.typ b/src/draw/grouping.typ index d3dc791f..c2038201 100644 --- a/src/draw/grouping.typ +++ b/src/draw/grouping.typ @@ -368,7 +368,6 @@ assert(name != none and name != "" and not name.starts-with("."), message: "Anchors must not be none, \"\" or start with \".\"!") - coordinate.resolve-system(position) return (ctx => { let (ctx, position) = coordinate.resolve(ctx, position) position = util.apply-transform(ctx.transform, position) diff --git a/src/draw/shapes.typ b/src/draw/shapes.typ index f52779cb..7052a7a8 100644 --- a/src/draw/shapes.typ +++ b/src/draw/shapes.typ @@ -116,8 +116,6 @@ assert.eq(style.pos(), (), message: "Unexpected positional arguments: " + repr(style.pos())) style = style.named() - (a, b, c).map(coordinate.resolve-system) - return (ctx => { let (ctx, a, b, c) = coordinate.resolve(ctx, a, b, c) @@ -220,9 +218,6 @@ ) let style = style.named() - // Coordinate check - let t = coordinate.resolve-system(position) - let start-angle = if start == auto { stop - delta } else { start } let stop-angle = if stop == auto { start + delta } else { stop } // Border angles can break if the angle is 0. @@ -445,8 +440,6 @@ to = ((rel: (to, 1), to: from)) } - (from, to).map(coordinate.resolve-system) - return (ctx => { let (ctx, ..pts) = coordinate.resolve(ctx, from, to) let style = styles.resolve(ctx.style, merge: style, root: "mark") @@ -506,9 +499,6 @@ assert(pts.len() >= 2, message: "Line must have a minimum of two points") - // Coordinate check - let pts-system = pts.map(coordinate.resolve-system) - // Find the intersection between line a-b next to b // if no intersection could be found, return a. let element-line-intersection(ctx, elem, a, b) = { @@ -534,6 +524,7 @@ return (ctx => { let first-elem = pts.first() let last-elem = pts.last() + let pts-system = pts.map(coordinate.resolve-system.with(ctx)) let (ctx, ..pts) = coordinate.resolve(ctx, ..pts) // If the first/last element, test for intersection @@ -610,8 +601,6 @@ /// ## Anchors /// Supports border anchors. #let grid(from, to, name: none, ..style) = { - (from, to).map(coordinate.resolve-system) - assert.eq(style.pos(), (), message: "Unexpected positional arguments: " + repr(style.pos())) style = style.named() @@ -770,16 +759,6 @@ panic("Expected 2 or 3 positional arguments, got " + str(args.len())) } - coordinate.resolve-system(a) - - if b != auto { - coordinate.resolve-system(b) - } - - if type(angle) != typst-angle { - coordinate.resolve-system(angle) - } - return (ctx => { let style = styles.resolve(ctx.style, merge: style, root: "content") let padding = util.as-padding-dict(style.padding) @@ -1016,9 +995,6 @@ /// Supports border and path anchors. It's default is the `"center"` anchor. /// #let rect(a, b, name: none, anchor: none, ..style) = { - // Coordinate check - let t = (a, b).map(coordinate.resolve-system) - // No extra positional arguments from the style sink assert.eq( style.pos(), @@ -1208,9 +1184,6 @@ ) let coordinates = (start, ..ctrl, end) - // Coordinates check - let t = coordinates.map(coordinate.resolve-system) - return ( ctx => { let (ctx, start, ..ctrl, end) = coordinate.resolve(ctx, ..coordinates) @@ -1310,8 +1283,6 @@ assert(pts.len() >= 2, message: "Catmull-rom curve requires at least two points. Got " + repr(pts.len()) + "instead.") - pts.map(coordinate.resolve-system) - return (ctx => { let (ctx, ..pts) = coordinate.resolve(ctx, ..pts) let style = styles.resolve(ctx.style, merge: style, root: "catmull") @@ -1385,8 +1356,6 @@ assert(pts.len() >= 2, message: "Hobby curve requires at least two points. Got " + repr(pts.len()) + "instead.") - pts.map(coordinate.resolve-system) - return (ctx => { let (ctx, ..pts) = coordinate.resolve(ctx, ..pts) let style = styles.resolve(ctx.style, merge: style, root: "hobby") diff --git a/src/draw/transformations.typ b/src/draw/transformations.typ index b8c07f02..7c9c748d 100644 --- a/src/draw/transformations.typ +++ b/src/draw/transformations.typ @@ -223,8 +223,6 @@ /// /// - pt (coordinate): The coordinate to move to. #let move-to(pt) = { - let t = coordinate.resolve-system(pt) - return (ctx => { let (ctx, pt) = coordinate.resolve(ctx, pt) return (ctx: ctx) @@ -244,8 +242,6 @@ /// - bounds (vector): Viewport bounds vector that describes the inner width, /// height and depth of the viewport #let set-viewport(from, to, bounds: (1, 1, 1)) = { - (from, to).map(coordinate.resolve-system) - return (ctx => { let bounds = vector.as-vec(bounds, init: (1, 1, 1)) diff --git a/src/draw/util.typ b/src/draw/util.typ index 3f2c0d1d..3d4a99cb 100644 --- a/src/draw/util.typ +++ b/src/draw/util.typ @@ -18,3 +18,41 @@ return (ctx: ctx) },) } + +/// Push a custom coordinate resolve function to the list of coordinate +/// resolvers. This resolver is scoped to the current context scope! +/// +/// A coordinate resolver must be a function of the format `(context, coordinate) => coordinate`. And must _always_ return a valid coordinate or panic, in case of an error. +/// +/// If multiple resolvers are registered, coordinates get passed through all +/// resolvers in reverse registering order. All coordinates get paased to cetz' +/// default coordinate resolvers. +/// +/// ```typc example +/// register-coordinate-resolver((ctx, c) => { +/// if type(c) == dictionary and "log" in c { +/// c = c.log.map(n => calc.log(n, base: 10)) +/// } +/// return c +/// }) +/// +/// circle((log: (10, 0)), radius: .25) +/// circle((log: (100, 0)), radius: .25) +/// circle((log: (1000, 0)), radius: .25) +/// ``` +/// +/// - resolver (function): The resolver function, taking a context and a single coordinate and returning a single coordinate +#let register-coordinate-resolver(resolver) = { + assert.eq(type(resolver), function, + message: "Coordinate resolver must be of type function (ctx, coordinate) => coordinate.") + + return (ctx => { + if type(ctx.resolve-coordinate) == array { + ctx.resolve-coordinate.push(resolver) + } else { + ctx.resolve-coordinate = (resolver,) + } + + return (ctx: ctx) + },) +} diff --git a/src/lib/decorations/brace.typ b/src/lib/decorations/brace.typ index 5571d38f..4b976529 100644 --- a/src/lib/decorations/brace.typ +++ b/src/lib/decorations/brace.typ @@ -62,9 +62,6 @@ assert.eq(style.pos().len(), 0, message: "Brace takes no additional positional arugments.") - // Validate coordinates - let _ = (start, end).map(coordinate.resolve-system) - group(name: name, ctx => { // Resolve all coordinates let (ctx, start, end) = coordinate.resolve(ctx, start, end) @@ -195,9 +192,6 @@ name: none, ..style, ) = { - // Validate coordinates - let _ = (start, end).map(coordinate.resolve-system) - group(name: name, ctx => { // Get styles and validate their types and values let style = styles.resolve(ctx.style, merge: style.named(), diff --git a/tests/coordinate/custom/ref/1.png b/tests/coordinate/custom/ref/1.png new file mode 100644 index 00000000..10c1cd30 Binary files /dev/null and b/tests/coordinate/custom/ref/1.png differ diff --git a/tests/coordinate/custom/test.typ b/tests/coordinate/custom/test.typ new file mode 100644 index 00000000..0c213794 --- /dev/null +++ b/tests/coordinate/custom/test.typ @@ -0,0 +1,26 @@ +#set page(width: auto, height: auto) +#import "/src/lib.typ": * +#import "/tests/helper.typ": * + +#test-case({ + import draw: * + grid((-2,-1), (7,1), stroke: gray) + + let log-resolver(ctx, coordinate) = { + if type(coordinate) == dictionary and "log" in coordinate { + coordinate = coordinate.log + coordinate = coordinate.map(n => calc.log(calc.max(n, util.float-epsilon), base: 10)) + } + + return coordinate + } + + register-coordinate-resolver(log-resolver) + + set-style(circle: (radius: .1)) + for i in (.1, 1, 10, 100, 1000, 10000) { + let pt = (log: (i * 1, 1)) + circle(pt) + content(pt, repr(i), anchor: "north", padding: (top: .5)) + } +})