Skip to content

PoC: Element Backtrace #726

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

Open
wants to merge 5 commits into
base: ctx-coordinate-resolve
Choose a base branch
from
Open
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
2 changes: 0 additions & 2 deletions src/anchor.typ
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
#import deps.oxifmt: strfmt

#import "util.typ"
#import util: typst-length

#import "intersection.typ"
#import "drawable.typ"
#import "path-util.typ"
Expand Down
14 changes: 8 additions & 6 deletions src/canvas.typ
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
#import "process.typ"
#import "version.typ"

#import util: typst-length

/// Sets up a canvas for drawing on.
///
/// - length (length, ratio): Used to specify what 1 coordinate unit is. If given a ratio, that ratio is relative to the containing elements width!
Expand All @@ -26,7 +24,7 @@
message: "Incorrect type for body: " + repr(type(body)),
)

assert(type(length) in (typst-length, ratio), message: "Expected `length` to be of type length or ratio, got " + repr(length))
assert(type(length) in (std.length, ratio), message: "Expected `length` to be of type length or ratio, got " + repr(length))
let length = if type(length) == ratio {
length * ly.width
} else {
Expand All @@ -39,6 +37,8 @@
version: version.version,
length: length,
debug: debug,
// Backtrace info (list of strings)
backtrace: (),
// Previous element position & bbox
prev: (pt: (0, 0, 0)),
style: styles.default,
Expand All @@ -58,7 +58,9 @@
marks: (
mnemonics: (:),
marks: (:),
)
),
// coordinate resolver
resolve-coordinate: none,
)

let (ctx, bounds, drawables) = process.many(ctx, body)
Expand Down Expand Up @@ -120,7 +122,7 @@
vertices += pts
}
}
if type(drawable.stroke) == dictionary and "thickness" in drawable.stroke and type(drawable.stroke.thickness) != typst-length {
if type(drawable.stroke) == dictionary and "thickness" in drawable.stroke and type(drawable.stroke.thickness) != std.length {
drawable.stroke.thickness *= length
}
path(
Expand All @@ -130,7 +132,7 @@
..vertices,
)
} else if drawable.type == "content" {
let (width, height) = util.typst-measure(drawable.body)
let (width, height) = std.measure(drawable.body)
move(
dx: (drawable.pos.at(0) - bounds.low.at(0)) * length - width / 2,
dy: (drawable.pos.at(1) - bounds.low.at(1)) * length - height / 2,
Expand Down
30 changes: 21 additions & 9 deletions src/coordinate.typ
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#import "vector.typ"
#import "util.typ"
#import "error.typ"
#import "deps.typ"
#import deps.oxifmt: strfmt

Expand Down Expand Up @@ -68,7 +69,7 @@
}

// Check if node is known
assert(name in ctx.nodes,
error.assert(ctx, name in ctx.nodes,
message: "Unknown element '" + name + "' in elements " + repr(ctx.nodes.keys()))

// Resolve length anchors
Expand Down Expand Up @@ -137,7 +138,7 @@
// Distance between C and P
let pc = vector.len(D)
if pc < r {
panic("No tangent solution for element " + c.element + " and point " + repr(c.point))
error.panic(ctx, "No tangent solution for element " + c.element + " and point " + repr(c.point))
}
// Distance between P and X0
let d = r*r / pc
Expand Down Expand Up @@ -251,7 +252,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()
Expand Down Expand Up @@ -297,7 +298,7 @@
}

if t == none {
panic("Failed to resolve coordinate: " + repr(c))
error.panic(ctx, "Failed to resolve coordinate: " + repr(c))
}
return t
}
Expand All @@ -319,10 +320,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
Expand All @@ -344,13 +355,14 @@
} else if t == "function" {
resolve-function(resolve, ctx, c)
} else {
panic("Failed to resolve coordinate of format: " + repr(c))
error.panic(ctx, "Failed to resolve coordinate of format: " + repr(c))
}.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)
Expand Down
2 changes: 1 addition & 1 deletion src/draw.typ
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 0 additions & 1 deletion src/draw/grouping.typ
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
43 changes: 6 additions & 37 deletions src/draw/shapes.typ
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
#let typst-angle = angle
#let typst-rotate = rotate

#import "/src/coordinate.typ"
#import "/src/drawable.typ"
#import "/src/styles.typ"
Expand All @@ -16,6 +13,7 @@
#import "/src/mark-shapes.typ" as mark-shapes_
#import "/src/polygon.typ"
#import "/src/aabb.typ"
#import "/src/error.typ"

#import "transformations.typ": *
#import "styling.typ": *
Expand Down Expand Up @@ -52,6 +50,8 @@
let style = style.named()

(ctx => {
ctx = error.add-element-backtrace(ctx, "circle", name)

let (ctx, pos) = coordinate.resolve(ctx, position)
let style = styles.resolve(ctx.style, merge: style, root: "circle")
let (rx, ry) = util.resolve-radius(style.radius).map(util.resolve-number.with(ctx))
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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) = {
Expand All @@ -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
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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)
Expand All @@ -794,7 +773,7 @@
(ctx, b) = coordinate.resolve(ctx, b)
}

let angle = if type(angle) != typst-angle {
let angle = if type(angle) != std.angle {
let c
(ctx, c) = coordinate.resolve(ctx, angle)
vector.angle2(a, c)
Expand Down Expand Up @@ -930,7 +909,7 @@
aabb-width,
aabb-height,
border.segments,
typst-rotate(angle,
std.rotate(angle,
reflow: true,
origin: center + horizon,
block(
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
4 changes: 0 additions & 4 deletions src/draw/transformations.typ
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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))

Expand Down
38 changes: 38 additions & 0 deletions src/draw/util.typ
Original file line number Diff line number Diff line change
Expand Up @@ -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)
},)
}
Loading
Loading