Relative positioning by pins, especially useful for making slides in typst.
Have a look at the source here.
Pinit works with Touying or Polylux animations.
Have a look at the pdf file here.
The idea of pinit is pinning pins on the normal flow of the text, and then placing the content on the page by absolute-place function.
For example, we can highlight text and add a tip by pins simply:
#import "@preview/pinit:0.2.2": *
#set text(size: 24pt)
A simple #pin(1)highlighted text#pin(2).
#pinit-highlight(1, 2)
#pinit-point-from(2)[It is simple.]If you want to place the content relative to the center of some pins, you use a array of pins:
#import "@preview/pinit:0.2.2": *
#set text(size: 12pt)
A simple #pin(1)highlighted text#pin(2).
#pinit-highlight(1, 2)
#pinit-point-from((1, 2))[It is simple.]A more complex example, Have a look at the source here.
Fletcher is a powerful Typst package for drawing diagrams with arrows. We can use fletcher to draw more complex arrows.
#import "@preview/pinit:0.2.2": *
#import "@preview/fletcher:0.5.1"
Con#pin(1)#h(4em)#pin(2)nect
#pinit-fletcher-edge(
fletcher, 1, end: 2, (1, 0), [bend], bend: -20deg, "<->",
decorations: fletcher.cetz.decorations.wave.with(amplitude: .1),
)In the code block, we need to use a regex trick to get pinit to work, for example
#show raw: it => {
show regex("pin\d"): it => pin(eval(it.text.slice(3)))
it
}
`print(pin1"hello, world"pin2)`
#pinit-highlight(1, 2)Note that typst's code highlighting breaks up the text, causing overly complex regular expressions such as '#pin(.*?)' to not work properly.
However, you may want to consider putting it in a comment to avoid highlighting the text and breaking it up.
Since Typst does not provide a reliable absolute-place function, you may consider taking the following steps if a MISALIGNMENT occurs:
- You could try to add a
#box()after the#pinit-xxxfunction call, like#pinit-xxx()#box(). - You should add a blank line before the
#pinit-xxxfunction call, otherwise it will cause misalignment. - You can try moving
#pinit-xxx()in front of or behind#pin(), or otherwhere, in short, try more. - Try to add a offset to the
dxordyargument of#pinit-xxxfunction by yourself. - Open an issue if you have any questions you can't solve.
Pinning a pin in text, the pin is supposed to be unique in one page.
#let pin(name) = { .. }Arguments:
name: [intorstrorany] — Name of pin, which can be any types with uniquerepr()return value, such as integer and string.
Query positions of pins in the same page, then call the callback function callback.
#let pinit(callback: none, ..pins) = { .. }Arguments:
..pins: [pin] — Names of pins you want to query. It is supposed to be arguments of pin or a group of pins.callback: [(..positions) => { .. }] — A callback function accepting an array of positions (or a single position) as a parameter. Each position is a dictionary like(page: 1, x: 319.97pt, y: 86.66pt). You can use theabsolute-placefunction in this callback function to display something around the pins.
Place content at a specific location on the page relative to the top left corner of the page, regardless of margins, current containers, etc.
This function comes from typst-drafting.
#let absolute-place(
dx: 0em,
dy: 0em,
body,
) = { .. }Arguments:
dx: [length] — Length in the x-axis relative to the left edge of the page.dy: [length] — Length in the y-axis relative to the top edge of the page.content: [content] — The content you want to place.
Place content at a specific location on the page relative to the pin.
#let pinit-place(
dx: 0pt,
dy: 0pt,
pin-name,
body,
) = { .. }Arguments:
dx: [length] — Offset X relative to the pin.dy: [length] — Offset Y relative to the pin.pin-name: [pin] — Name of the pin to which you want to locate.body: [content] — The content you want to place.
Draw a rectangular shape on the page containing all pins with optional extended width and height.
#let pinit-rect(
dx: 0em,
dy: -1em,
extended-width: 0em,
extended-height: 1.4em,
pin1,
pin2,
pin3, // Optional
..pinX,
..args,
) = { .. }Arguments:
dx: [length] — Offset X relative to the min-left of pins.dy: [length] — Offset Y relative to the min-top of pins.extended-width: [length] — Optional extended width of the rectangular shape.extended-height: [length] — Optional extended height of the rectangular shape.pin1: [pin] — One of these pins.pin2: [pin] — One of these pins.pin3: [pin] — One of these pins, optionally....args: Additional named arguments or settings forrect, likefill,strokeandradius.
Highlight a specific area on the page with a filled color and optional radius and stroke. It is just a simply styled pinit-rect.
#let pinit-highlight(
fill: rgb(255, 0, 0, 20),
radius: 5pt,
stroke: 0pt,
dx: 0em,
dy: -1em,
extended-width: 0em,
extended-height: 1.4em,
pin1,
pin2,
pin3, // Optional
..pinX,
...args,
) = { .. }Arguments:
fill: [color] — The fill color for the highlighted area.radius: [length] — Optional radius for the highlight.stroke: [stroke] — Optional stroke width for the highlight.dx: [length] — Offset X relative to the min-left of pins.dy: [length] — Offset Y relative to the min-top of pins.extended-width: [length] — Optional extended width of the rectangular shape.extended-height: [length] — Optional extended height of the rectangular shape.pin1: [pin] — One of these pins.pin2: [pin] — One of these pins.pin3: [pin] — One of these pins, optionally....args: Additional arguments or settings forpinit-rect.
Draw a line on the page between two specified pins with an optional stroke.
#let pinit-line(
stroke: 1pt,
start-dx: 0pt,
start-dy: 0pt,
end-dx: 0pt,
end-dy: 0pt,
start,
end,
) = { ... }Arguments:
stroke: [stroke] — The stroke for the line.start-dx: [length] — Offset X relative to the start pin.start-dy: [length] — Offset Y relative to the start pin.end-dx: [length] — Offset X relative to the end pin.end-dy: [length] — Offset Y relative to the end pin.start: [pin] — The start pin.end: [pin] — The end pin.
Draw an line from a specified pin to a point on the page with optional settings.
#let pinit-line-to(
stroke: 1pt,
pin-dx: 5pt,
pin-dy: 5pt,
body-dx: 5pt,
body-dy: 5pt,
offset-dx: 35pt,
offset-dy: 35pt,
pin-name,
body,
) = { ... }Arguments:
stroke: [stroke] — The stroke for the line.pin-dx: [length] — Offset X of arrow start relative to the pin.pin-dy: [length] — Offset Y of arrow start relative to the pin.body-dx: [length] — Offset X of arrow end relative to the body.body-dy: [length] — Offset Y of arrow end relative to the body.offset-dx: [length] — Offset X relative to the pin.offset-dy: [length] — Offset Y relative to the pin.pin-name: [pin] — The name of the pin to start from.body: [content] — The content to draw the arrow to.
Draw an arrow between two specified pins with optional settings.
#let pinit-arrow(
start-dx: 0pt,
start-dy: 0pt,
end-dx: 0pt,
end-dy: 0pt,
start,
end,
..args,
) = { ... }Arguments:
start-dx: [length] — Offset X relative to the start pin.start-dy: [length] — Offset Y relative to the start pin.end-dx: [length] — Offset X relative to the end pin.end-dy: [length] — Offset Y relative to the end pin.start: [pin] — The start pin.end: [pin] — The end pin....args: Additional arguments or settings forsimple-arrow, likefill,strokeandthickness.
Draw an double arrow between two specified pins with optional settings.
#let pinit-double-arrow(
start-dx: 0pt,
start-dy: 0pt,
end-dx: 0pt,
end-dy: 0pt,
start,
end,
..args,
) = { ... }Arguments:
start-dx: [length] — Offset X relative to the start pin.start-dy: [length] — Offset Y relative to the start pin.end-dx: [length] — Offset X relative to the end pin.end-dy: [length] — Offset Y relative to the end pin.start: [pin] — The start pin.end: [pin] — The end pin....args: Additional arguments or settings fordouble-arrow, likefill,strokeandthickness.
Draw an arrow from a specified pin to a point on the page with optional settings.
#let pinit-point-to(
pin-dx: 5pt,
pin-dy: 5pt,
body-dx: 5pt,
body-dy: 5pt,
offset-dx: 35pt,
offset-dy: 35pt,
double: false,
pin-name,
body,
..args,
) = { ... }Arguments:
pin-dx: [length] — Offset X of arrow start relative to the pin.pin-dy: [length] — Offset Y of arrow start relative to the pin.body-dx: [length] — Offset X of arrow end relative to the body.body-dy: [length] — Offset Y of arrow end relative to the body.offset-dx: [length] — Offset X relative to the pin.offset-dy: [length] — Offset Y relative to the pin.double: [bool] — Draw a double arrow, default isfalse.pin-name: [pin] — The name of the pin to start from.body: [content] — The content to draw the arrow to....args: Additional arguments or settings forsimple-arrow, likefill,strokeandthickness.
Draw an arrow from a point on the page to a specified pin with optional settings.
#let pinit-point-from(
pin-dx: 5pt,
pin-dy: 5pt,
body-dx: 5pt,
body-dy: 5pt,
offset-dx: 35pt,
offset-dy: 35pt,
double: false,
pin-name,
body,
..args,
) = { ... }Arguments:
pin-dx: [length] — Offset X relative to the pin.pin-dy: [length] — Offset Y relative to the pin.body-dx: [length] — Offset X relative to the body.body-dy: [length] — Offset Y relative to the body.offset-dx: [length] — Offset X relative to the left edge of the page.offset-dy: [length] — Offset Y relative to the top edge of the page.double: [bool] — Draw a double arrow, default isfalse.pin-name: [pin] — The name of the pin that the arrow to.body: [content] — The content to draw the arrow from....args: Additional arguments or settings forsimple-arrow, likefill,strokeandthickness.
Draw a simple arrow on the page with optional settings, implemented by polygon.
#let simple-arrow(
fill: black,
stroke: 0pt,
start: (0pt, 0pt),
end: (30pt, 0pt),
thickness: 2pt,
arrow-width: 4,
arrow-height: 4,
inset: 0.5,
tail: (),
) = { ... }Arguments:
fill: [color] — The fill color for the arrow.stroke: [stroke] — The stroke for the arrow.start: [point] — The starting point of the arrow.end: [point] — The ending point of the arrow.thickness: [length] — The thickness of the arrow.arrow-width: [intorfloat] — The width of the arrowhead relative to thickness.arrow-height: [intorfloat] — The height of the arrowhead relative to thickness.inset: [intorfloat] — The inset value for the arrowhead relative to thickness.tail: [array] — The tail settings for the arrow.
Draw a double arrow on the page with optional settings, implemented by polygon.
#let double-arrow(
fill: black,
stroke: 0pt,
start: (0pt, 0pt),
end: (30pt, 0pt),
thickness: 2pt,
arrow-width: 4,
arrow-height: 4,
inset: 0.5,
tail: (),
) = { ... }Arguments:
fill: [color] — The fill color for the arrow.stroke: [stroke] — The stroke for the arrow.start: [point] — The starting point of the arrow.end: [point] — The ending point of the arrow.thickness: [length] — The thickness of the arrow.arrow-width: [intorfloat] — The width of the arrowhead relative to thickness.arrow-height: [intorfloat] — The height of the arrowhead relative to thickness.inset: [intorfloat] — The inset value for the arrowhead relative to thickness.tail: [array] — The tail settings for the arrow.
Draw a connecting line or arc in an fletcher arrow diagram.
#let pinit-fletcher-edge(
fletcher,
start,
end: none,
start-dx: 0pt,
start-dy: 0pt,
end-dx: 0pt,
end-dy: 0pt,
width-scale: 100%,
height-scale: 100%,
default-width: 30pt,
default-height: 30pt,
..args,
) = { ... }Arguments:
fletcher(module): The Fletcher module. You can import it with something like#import "@preview/fletcher:0.5.1"start(pin): The starting pin of the edge. It is assumed that the pin is at the origin point (0, 0) of the edge.end(pin): The ending pin of the edge. If not provided, the edge will use default values for the width and height.start-dx(length): The x-offset of the starting pin. You should use pt units.start-dy(length): The y-offset of the starting pin. You should use pt units.end-dx(length): The x-offset of the ending pin. You should use pt units.end-dy(length): The y-offset of the ending pin. You should use pt units.width-scale(percent): The width scale of the edge. The default value is 100%. If you set the width scale to 50%, the width of the edge will be half of the default width. Then you can use"r,r"which is equivalent to single"r".height-scale(percent): The height scale of the edge. The default value is 100%.default-width(length): The default width of the edge. The default value is 30pt, which will only be used if the end pin is not provided or the width is 0pt or 0em.default-height(length): The default height of the edge. The default value is 30pt, which will only be used if the end pin is not provided or the height is 0pt or 0em...args(any): An edge's positional arguments may specify:- the edge's #param[edge][vertices], each specified with a CeTZ-style coordinate
- the #param[edge][label] content
- arrow #param[edge][marks], like
"=>"or"<<-|-o" - other style flags, like
"double"or"wave"
- Fix bugs.
- To be compatible with Typst 0.12.
- Breaking changes:
#pinit(pins, func)is replaced by#pinit(callback: none, ..pins)and the callback argument will receive an(..positions) => { .. }function instead of a(positions) => { .. }function.- Migration: you need to use a named argument
callback: (..positions) => { .. }to specify the callback function. - Migration: you cannot use a array as a pin name. Now
#pinit((pin1, pin2), callback: func)means that we usepin1andpin2as a group of pins, and the callback function will receive a single position (the center of the bounding box ofpin1andpin2). - Benefit: you can use
#pinit(pin1, pin2, callback: func)to query the positions ofpin1andpin2separately, and#pinit((pin1, pin2), callback: func)to query the position of the center of the bounding box ofpin1andpin2.
- Migration: you need to use a named argument
- Add
pinit-fletcher-edgefunction to draw a connecting line or arc in an fletcher arrow diagram. - Add
double-arrowfunction andpinit-double-arrowfunction. - Add
doubleargument forpinit-point-toandpinit-point-fromfunctions. - Better comments and documentation.
- Update documentation.
- Add
pinit-line-tofunction.
- Add em unit support for
simple-arrow.
- Fix some bugs.
- Initial release.
- Some of the inspirations and codes comes from typst-drafting.
- The concise and aesthetic example slide style come from course Data Structures and Algorithms of Chaodong ZHENG.
- Thank PaulS for double arrow feature.
- Thank Jollywatt for fletcher package.
This project is licensed under the MIT License.