cetz-package / cetz

CeTZ: ein Typst Zeichenpaket - A library for drawing stuff with Typst.
https://cetz-package.github.io
GNU Lesser General Public License v3.0
893 stars 36 forks source link

Drawing well-centered lines between elements #234

Closed TimotheAlbouy closed 10 months ago

TimotheAlbouy commented 1 year ago

Currently, there is no simple way to draw a "well-centered line" between 2 elements in CeTZ.

I call a line between two elements "well-centered" if it follows the same path as the infinite line that passes through the centers of both elements, but it just touches the edges of the 2 elements instead. I know that this may not be very clear, so this is the kind of things that I want to achieve (this was produced in TikZ):

The solution should also be element- and position-agnostic, so no cheating like for instance assuming that the elements are circles of a given radius, or assuming their position and inferring a given line angle.

Doing this in TikZ is very easy:

\node[draw, circle] (A) at (0,0) {arbitrary text};
\node[draw, align=left] (B) at (2,3) {arbitrary\\text};
\draw[<->] (A) -- (B);

So the solution in CeTZ should be as intuitive as the TikZ solution. Something like:

content((0,0), [arbitrary text], name: "A", frame: "circle")
content((2,3), [arbitrary\ text], name: "B", frame: "rect")
line("A", "B", mark: (end: ">", start: ">"))

Such a feature could be very helpful for a variety of things, such as drawing pretty box-and-arrow diagrams. It could also be useful for the tree CeTZ library (see this other issue) to replicate the behaviour of the tree TikZ library (the lines between the elements are "well-centered", and not drawn between the top and bottom anchors of the elements).

See this Discord thread for more info.

TimotheAlbouy commented 1 year ago

So now, let's talk about how this feature could be implemented.

Currently, line("A", "B", mark: (end: ">", start: ">")) resolves the "A" and "B" parameters of the line() function as the "A.default" and "B.default" anchors, which respectively correspond to the centers of the circle and the rectangle. So this code would produce a line that goes from the center of the circle to the center of the rectangle, which is not what we want. This current behaviour of CeTZ could be redefined to implement the feature of this post.

However, this is easier said than done, because in the current codebase, "A" and "B" get resolved independently of each other, but we can see that, to resolve "A" into a coordinate, we need to know the position of the center of "B", and we can make a symmetric argument for "B". So we cannot resolve the different coordinates one by one, like what is done in the coordinate.typ file right now.

So we could add a new function in the coordinate.typ file, for example called resolve-element-line-intersect(), that would define a new resolution rule resolving the coordinates of the point at the edge of element "A" based on the direction of the other point (the center of element "B" in my example). We would then repeat the operation on the second point "B". This function would take in parameters the name of the first element for which we must touch the edge, and the second coordinate towards which the line is oriented. This second coordinate could be expressed in any format that is currently resolvable in the coordinate.typ file. This way, it would be possible to have "well-centered" lines between an element name (e.g., "A") and a classic coordinate (e.g., (2,3)) or an anchor (e.g., "B.bottom") for example, like what is already possible in TikZ. However, to get the simple line("A", "B") syntax that I gave above, I think that it's not possible to do everything inside the resolution rules, and some logic specific to the "well-centered line" rule will have to be added to the line() function.

Next, we have to work out how this resolve-element-line-intersect() function would work. The most natural way to implement this feature may be to leverage the intersections() function. However, I think that the current issues with the intersections() function (and especially this one) will have to be fixed first.

I will try to write the resolve-element-line-intersect() function later here.

johannes-wolf commented 10 months ago

line(..) now accepts element names as first and/or last coordinates to calculate border intersections.