Skip to content

Commit

Permalink
Support Content Intersections (#386)
Browse files Browse the repository at this point in the history
- Adds intersection checking for `content` elements by setting their
`segments` to their frame (defaulting to a rectangle)
- Fixes a bug in `line` intersection checking, taking the furthest
intersection for one side and the nearest for the other

This changes `intersection` to _not_ emit anchors for intersections of
one element's drawables with each other (e.g. an element with 2
drawables or marks did emit self intersections prior to this change!).
See the grid intersection example (ref image), which should _not_ emit
intersections.
  • Loading branch information
johannes-wolf authored Dec 21, 2023
1 parent 2abf154 commit b31d6af
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 51 deletions.
49 changes: 30 additions & 19 deletions src/draw/grouping.typ
Original file line number Diff line number Diff line change
Expand Up @@ -97,34 +97,45 @@
return (ctx => {
let ctx = ctx

let named-drawables = () // List of elements to calc intersections for
let drawables = () // List of elements to draw + calc intersections for
// List of drawables to calc intersections for;
// grouped by element.
let named-drawables = ()
// List of drawables passed as elements to calc intersections for;
// grouped by element.
let drawables = ()

for elem in elements.pos() {
if type(elem) == str {
assert(elem in ctx.nodes,
message: "No such element '" + elem + "' in elements " + repr(ctx.nodes.keys()))
named-drawables += ctx.nodes.at(elem).drawables
named-drawables.push(ctx.nodes.at(elem).drawables)
} else {
let new-drawables = ()
(ctx: ctx, drawables: new-drawables, ..) = process.many(ctx, elem)
drawables += new-drawables
for sub in elem {
let sub-drawables = ()
(ctx: ctx, drawables: sub-drawables, ..) = process.element(ctx, sub)
if sub-drawables != none and sub-drawables != () {
drawables.push(sub-drawables)
}
}
}
}

// Filter out elements that can not intersect
let paths = (named-drawables + drawables).filter(d => d.type == "path")

let elems = named-drawables + drawables
let pts = ()
if paths.len() > 1 {
for (i, path-1) in paths.enumerate() {
for path-2 in paths.slice(i+1) {
for pt in intersection.path-path(
path-1,
path-2,
samples: samples
) {
if pt not in pts { pts.push(pt) }
if elems.len() > 1 {
for (i, elem-1) in elems.enumerate() {
for j in range(i + 1, elems.len()) {
let elem-2 = elems.at(j)
for path-1 in elem-1 {
for path-2 in elem-2 {
for pt in intersection.path-path(
path-1,
path-2,
samples: samples
) {
if pt not in pts { pts.push(pt) }
}
}
}
}
}
Expand All @@ -145,7 +156,7 @@
transform: none,
name: name
).last(),
drawables: drawables
drawables: drawables.flatten()
)
},)
}
Expand Down
56 changes: 29 additions & 27 deletions src/draw/shapes.typ
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@
a
} else {
// Find the nearest point
let pt = util.sort-points-by-distance(b, pts).first()
let pt = util.sort-points-by-distance(tb, pts).first()

// Reverse the transformation
return util.revert-transform(ctx.transform, pt)
Expand All @@ -630,7 +630,7 @@
}
if pts-system.last() == "element" {
let elem = ctx.nodes.at(last-elem)
pts.last() = element-line-intersection(ctx, elem, ..pts.slice(-2))
pts.last() = element-line-intersection(ctx, elem, ..pts.slice(-2).rev())
}

let style = styles.resolve(ctx.style, merge: style, root: "line")
Expand Down Expand Up @@ -935,43 +935,45 @@
)
}

let drawables = ()
if style.frame in ("rect", "circle") {
drawables.push(
if style.frame == "rect" {
drawable.path(
path-util.line-segment((
anchors.north-west,
anchors.north-east,
anchors.south-east,
anchors.south-west
)),
close: true,
stroke: style.stroke,
fill: style.fill
)
} else if style.frame == "circle" {
let (x, y, z) = util.calculate-circle-center-3pt(anchors.north-west, anchors.south-west, anchors.south-east)
let r = vector.dist((x, y, z), anchors.north-west)
drawable.ellipse(
x, y, z,
r, r,
stroke: style.stroke,
fill: style.fill
)
}
let border = if style.frame in (none, "rect") {
drawable.path(
path-util.line-segment((
anchors.north-west,
anchors.north-east,
anchors.south-east,
anchors.south-west
)),
close: true,
stroke: style.stroke,
fill: style.fill)
} else if style.frame == "circle" {
let (x, y, z) = util.calculate-circle-center-3pt(anchors.north-west, anchors.south-west, anchors.south-east)
let r = vector.dist((x, y, z), anchors.north-west)
drawable.ellipse(
x, y, z,
r, r,
stroke: style.stroke,
fill: style.fill
)
}

let (aabb-width, aabb-height, ..) = aabb.size(aabb.aabb(
(anchors.north-west, anchors.north-east,
anchors.south-west, anchors.south-east)))

let corners = (anchors.north-east, anchors.north-west,
anchors.south-west, anchors.south-east)

let drawables = ()
if style.frame != none {
drawables.push(border)
}
drawables.push(
drawable.content(
anchors.center,
aabb-width,
aabb-height,
border.segments,
typst-rotate(angle,
block(
width: width * ctx.length,
Expand Down
4 changes: 3 additions & 1 deletion src/drawable.typ
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#import "path-util.typ"
#import "vector.typ"
#import "util.typ"
#import "path-util.typ"

#let apply-transform(transform, drawables) = {
if type(drawables) == dictionary {
Expand Down Expand Up @@ -46,12 +47,13 @@
)
}

#let content(pos, width, height, body) = {
#let content(pos, width, height, border, body) = {
return (
type: "content",
pos: pos,
width: width,
height: height,
segments: border,
body: body,
hidden: false,
)
Expand Down
Binary file added tests/content/intersection/ref.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions tests/content/intersection/test.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#set page(width: auto, height: auto)
#import "/src/lib.typ": *
#import "/tests/helper.typ": *

#test-case({
import draw: *
set-style(fill: gray, stroke: gray)

intersections("i", {
content((0,0), [Text])
on-layer(-1, {
line((1,1), (-1,-1))
bezier((-1,0), (1,0), (-.5,.5), (.5,-.5), fill: none)
})
})
on-layer(-1, {
for-each-anchor("i", n => {
circle("i." + n, radius: .05)
})
})
})
Binary file modified tests/intersection/ref.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 6 additions & 2 deletions tests/intersection/test.typ
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,18 @@
rotate(45deg)
line((0,0), (calc.sqrt(2*calc.pow(2,2)),0))
})
test({
// The marks must not generate intersections with the line!
line((0,0), (2,2), mark: (start: ">", end: ">"))
})
})

#box(stroke: 2pt + red, canvas({
import draw: *

intersections("i", {
content((0, 0), [Das ist\ ein Text!], frame: "circle", name: "a")
content((2, 1), [Hallo!], frame: "circle", name: "b")
content((0, 0), [This is\ Text!], frame: "circle", name: "a")
content((2, 1), [Hello!], frame: "circle", name: "b")
// Invisible intersection line
line("a.default", "b.default", stroke: none)
})
Expand Down
Binary file modified tests/line/element-element/ref.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 10 additions & 2 deletions tests/line/element-element/test.typ
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,16 @@
import draw: *

set-style(content: (padding: .1))
test(content((0,0), [Text], frame: "rect", name: "a"),
content((1,1), [Text], frame: "rect", name: "b"))
test(content((0,0), [Text], name: "a"),
content((1,1), [Text], name: "b"))
}))

#box(stroke: 2pt + red, canvas({
import draw: *

set-style(content: (padding: .1))
test(content((0,0), [Text], frame: "circle", name: "a"),
content((1,1), [Text], frame: "circle", name: "b"))
}))

#box(stroke: 2pt + red, canvas({
Expand Down

0 comments on commit b31d6af

Please sign in to comment.