Skip to content
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

line: Draw line between element borders #378

Merged
merged 3 commits into from
Dec 9, 2023
Merged
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
5 changes: 3 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ CeTZ 0.2.0 requires Typst 0.10.0
- **BREAKING** Changed the behaviour of `translate` by changing the transformation order, changed arguments of `scale` and `translate`
- Content padding has been improved to be configurable per side
- Groups support same padding options as content
- Mark offsetting has been fixed and improved
- Catmull-Rom curves, Hobby curves and arcs now can have marks
- Fixed mark offsetting
- Fixed and improved intersection calculation
- Fixed marks pointing to +/- z
- Fixed and improved the styling algorithm
- Catmull-Rom curves, Hobby curves and arcs now can have marks
- Line elements now use border intersection coordinates if first and/or last coordinate is an element name with a "default" anchor
- Added element `arc-through` to draw an arc through three points
- Added Hobby curves (`hobby`) in addition to catmull (thanks to @Enivex)
- Added `radius` style to `rect` for drawing rounded rects
Expand Down
12 changes: 6 additions & 6 deletions src/anchor.typ
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,12 @@
continue
}
pts += intersection.line-path(..test-line, drawable)
}

// We only want one point
if pts.len() > 0 {
break
}
if pts.len() == 1 {
return pts.first()
}
assert(pts.len() > 0, message: strfmt("{} {} {}", test-line, drawables, angle))
return pts.first()

// Find the furthest intersection point from center
return util.sort-points-by-distance(center, pts).last()
}
18 changes: 6 additions & 12 deletions src/coordinate.typ
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,6 @@
let (func, ..c) = c
(ctx, ..c) = resolve(ctx, ..c)
func(..c)
// (c.first())()
}

#let resolve-pos(ctx, c) = {
Expand Down Expand Up @@ -298,7 +297,11 @@
"function"
}
} else if type(c) == str {
"anchor"
if c.contains(".") {
"anchor"
} else {
"element"
}
}

if t == none {
Expand Down Expand Up @@ -336,7 +339,7 @@
resolve-polar(c)
} else if t == "barycentric" {
resolve-barycentric(ctx, c)
} else if t == "anchor" {
} else if t in ("element", "anchor") {
resolve-anchor(ctx, c)
} else if t == "tangent" {
resolve-tangent(resolve, ctx, c)
Expand All @@ -361,12 +364,3 @@

return (ctx, ..result)
}

// #let resolve-many(ctx, ..coordinates) = {
// let out = ()
// for c in coordinates.pos() {
// (ctx, c) = resolve(ctx, c)
// out.push(c)
// }
// return (ctx, ..out)
// }
54 changes: 51 additions & 3 deletions src/draw/shapes.typ
Original file line number Diff line number Diff line change
Expand Up @@ -556,8 +556,19 @@
/// line((-1.5, 0), (1.5, 0))
/// line((0, -1.5), (0, 1.5))
/// line((-1, -1), (-0.5, 0.5), (0.5, 0.5), (1, -1), close: true)
/// ```)
///
/// ```)
///
/// If the first or last coordinates are given as the name of an element,
/// that has a `"default"` anchor, the intersection of that element's border
/// and a line from the first or last two coordinates given is used as coordinate.
/// This is useful to span a line between the borders of two elements.
///
/// #example(```
/// circle((1,2), radius: .5, name: "a")
/// rect((2,1), (rel: (1,1)), name: "b")
/// line("a", "b")
/// ```)
///
/// = parameters
///
/// = Styling
Expand All @@ -581,10 +592,47 @@
assert(pts.len() >= 2, message: "Line must have a minimum of two points")

// Coordinate check
pts.map(coordinate.resolve-system)
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) = {
// Vectors a and b are not transformed yet, but the vectors of the
// drawable are.
let (ta, tb) = util.apply-transform(ctx.transform, a, b)

let pts = ()
for drawable in elem.at("drawables", default: ()) {
pts += intersection.line-path(ta, tb, drawable)
}
johannes-wolf marked this conversation as resolved.
Show resolved Hide resolved
return if pts == () {
a
} else {
// Find the nearest point
let pt = util.sort-points-by-distance(b, pts).first()

// Reverse the transformation
return util.revert-transform(ctx.transform, pt)
}
}

return (ctx => {
let first-elem = pts.first()
let last-elem = pts.last()
let (ctx, ..pts) = coordinate.resolve(ctx, ..pts)

// If the first/last element, test for intersection
// of that element and a line from the two first/last coordinates of this
// line strip.
if pts-system.first() == "element" {
let elem = ctx.nodes.at(first-elem)
pts.first() = element-line-intersection(ctx, elem, ..pts.slice(0, 2))
}
if pts-system.last() == "element" {
let elem = ctx.nodes.at(last-elem)
pts.last() = element-line-intersection(ctx, elem, ..pts.slice(-2))
}

let style = styles.resolve(ctx.style, merge: style, root: "line")
let (transform, anchors) = anchor_.setup(
(anchor) => {
Expand Down
4 changes: 2 additions & 2 deletions src/intersection.typ
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
}

let pts = ()
for s in path.segments {
for s in path.at("segments", default: ()) {
pts += segment(s)
}
return pts
Expand Down Expand Up @@ -101,7 +101,7 @@
}

let pts = ()
for s in a.segments {
for s in a.at("segments", default: ()) {
let sv = linearize-segment(s)
for ai in range(0, sv.len() - 1) {
pts += line-path(sv.at(ai), sv.at(ai + 1), b)
Expand Down
2 changes: 1 addition & 1 deletion src/process.typ
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
}
}
if "name" in element and type(element.name) == "string" and "anchors" in element {
ctx.nodes.insert(element.name, (anchors: element.anchors))
ctx.nodes.insert(element.name, element)
}

if ctx.debug and bounds != none {
Expand Down
18 changes: 18 additions & 0 deletions src/util.typ
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,21 @@
south-east: se,
)
}

/// Sort list of points/vectors by distance to base
/// - base (vector): Vector to measure distance to
/// - pts (array of vector): List of points
/// -> array Sorted list of points
#let sort-points-by-distance(base, pts) = {
if pts.len() == 1 {
return pts
}

// Sort by transforming points into tuples of (point, distance),
// sorting them by key 1 and then transforming them back to points.
return pts.map(p => {
return (p, vector.dist(p, base))
})
.sorted(key: t => t.at(1))
.map(t => t.at(0))
}
2 changes: 1 addition & 1 deletion tests/intersection/test.typ
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
content((0, 0), [Das ist\ ein Text!], frame: "circle", name: "a")
content((2, 1), [Hallo!], frame: "circle", name: "b")
// Invisible intersection line
line("a", "b", stroke: none)
line("a.default", "b.default", stroke: none)
fenjalien marked this conversation as resolved.
Show resolved Hide resolved
})
line("i.0", "i.1", mark: (end: ">"))
}))
Expand Down
Binary file added 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.
56 changes: 56 additions & 0 deletions tests/line/element-element/test.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#set page(width: auto, height: auto)
#import "/src/lib.typ": *

#let test(a, b, ..line-args) = {
import draw: *

a; b;
line("a", "b", ..line-args)
}

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

test(rect((0,-.5), (rel: (1,1)), name: "a"),
circle((3,0), name: "b"))
}))

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

test(rect((0,-1), (rel: (1,1)), name: "a"),
circle((2,1), name: "b"))
}))

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

test(rect((0,-2), (rel: (1,1)), name: "a"),
circle((2,2), name: "b"))
}))

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

test(rect((0,0), (rel: (1,1)), name: "a"),
rect((0,0), (rel: (1,1)), name: "b"))
}))

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

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

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

set-style(content: (padding: .1))
test(rect((0,0), (rel: (1,1)), name: "a"),
group({
line((2,2), (3,1), (rel: (0,2)), (rel: (-.1, -1.6)), close: true)
anchor("center", (5,3))
}, name: "b"))
}))
Loading