├── LICENSE ├── README.md ├── examples ├── asymptotic.png ├── equation-desc.png ├── equation-desc.typ ├── example-pages.png ├── example.pdf ├── example.png ├── example.typ ├── fletcher.png ├── fletcher.typ ├── pinit-for-raw.png ├── pinit-for-raw.typ ├── simple-demo.png └── simple-demo2.png ├── lib.typ ├── pinit-core.typ ├── pinit-fletcher.typ ├── simple-arrow.typ └── typst.toml /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 OrangeX4 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pinit 2 | 3 | Relative positioning by pins, especially useful for making slides in typst. 4 | 5 | ## Example 6 | 7 | ### Pin things as you like 8 | 9 | Have a look at the source [here](./examples/example.typ). 10 | 11 | ![Example](./examples/example.png) 12 | 13 | ### Dynamic Slides 14 | 15 | Pinit works with [Touying](https://github.com/touying-typ/touying) or [Polylux](https://github.com/andreasKroepelin/polylux) animations. 16 | 17 | Have a look at the pdf file [here](https://github.com/OrangeX4/typst-pinit/blob/main/examples/example.pdf). 18 | 19 | ![Example Pages](./examples/example-pages.png) 20 | 21 | 22 | ## Usage 23 | 24 | ### Examples 25 | 26 | 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. 27 | 28 | For example, we can highlight text and add a tip by pins simply: 29 | 30 | ```typ 31 | #import "@preview/pinit:0.2.2": * 32 | 33 | #set text(size: 24pt) 34 | 35 | A simple #pin(1)highlighted text#pin(2). 36 | 37 | #pinit-highlight(1, 2) 38 | 39 | #pinit-point-from(2)[It is simple.] 40 | ``` 41 | 42 | ![simple-demo](./examples/simple-demo.png) 43 | 44 | If you want to place the content relative to the center of some pins, you use a array of pins: 45 | 46 | ```typ 47 | #import "@preview/pinit:0.2.2": * 48 | 49 | #set text(size: 12pt) 50 | 51 | A simple #pin(1)highlighted text#pin(2). 52 | 53 | #pinit-highlight(1, 2) 54 | 55 | #pinit-point-from((1, 2))[It is simple.] 56 | ``` 57 | 58 | ![simple-demo2](./examples/simple-demo2.png) 59 | 60 | A more complex example, Have a look at the source [here](./examples/equation-desc.typ). 61 | 62 | ![equation-desc](./examples/equation-desc.png) 63 | 64 | 65 | ### Fletcher edge support 66 | 67 | [Fletcher](https://github.com/Jollywatt/typst-fletcher) is a powerful Typst package for drawing diagrams with arrows. We can use fletcher to draw more complex arrows. 68 | 69 | [`pinit-fletcher-edge`](#pinit-fletcher-edge) 70 | 71 | ```typst 72 | #import "@preview/pinit:0.2.2": * 73 | #import "@preview/fletcher:0.5.1" 74 | 75 | Con#pin(1)#h(4em)#pin(2)nect 76 | 77 | #pinit-fletcher-edge( 78 | fletcher, 1, end: 2, (1, 0), [bend], bend: -20deg, "<->", 79 | decorations: fletcher.cetz.decorations.wave.with(amplitude: .1), 80 | ) 81 | ``` 82 | 83 | ![fletcher](./examples/fletcher.png) 84 | 85 | 86 | ### Pinit for raw 87 | 88 | In the code block, we need to use a regex trick to get pinit to work, for example 89 | 90 | ```typst 91 | #show raw: it => { 92 | show regex("pin\d"): it => pin(eval(it.text.slice(3))) 93 | it 94 | } 95 | 96 | `print(pin1"hello, world"pin2)` 97 | 98 | #pinit-highlight(1, 2) 99 | ``` 100 | 101 | ![equation-desc](./examples/pinit-for-raw.png) 102 | 103 | Note that typst's code highlighting breaks up the text, causing overly complex regular expressions such as '#pin\(.*?\)' to not work properly. 104 | 105 | However, you may want to consider putting it in a comment to avoid highlighting the text and breaking it up. 106 | 107 | 108 | ## Notice 109 | 110 | **Since Typst does not provide a reliable `absolute-place` function, you may consider taking the following steps if a MISALIGNMENT occurs:** 111 | 112 | 1. **You could try to add a `#box()` after the `#pinit-xxx` function call, like `#pinit-xxx()#box()`.** 113 | 2. **You should add a blank line before the `#pinit-xxx` function call, otherwise it will cause misalignment.** 114 | 3. **You can try moving `#pinit-xxx()` in front of or behind `#pin()`, or otherwhere, in short, try more.** 115 | 4. **Try to add a offset to the `dx` or `dy` argument of `#pinit-xxx` function by yourself.** 116 | 5. **Open an issue if you have any questions you can't solve.** 117 | 118 | 119 | ## Outline 120 | 121 | - [Pinit](#pinit) 122 | - [Example](#example) 123 | - [Pin things as you like](#pin-things-as-you-like) 124 | - [Dynamic Slides](#dynamic-slides) 125 | - [Usage](#usage) 126 | - [Examples](#examples) 127 | - [Fletcher edge support](#fletcher-edge-support) 128 | - [Pinit for raw](#pinit-for-raw) 129 | - [Notice](#notice) 130 | - [Outline](#outline) 131 | - [Reference](#reference) 132 | - [`pin`](#pin) 133 | - [`pinit`](#pinit-1) 134 | - [`absolute-place`](#absolute-place) 135 | - [`pinit-place`](#pinit-place) 136 | - [`pinit-rect`](#pinit-rect) 137 | - [`pinit-highlight`](#pinit-highlight) 138 | - [`pinit-line`](#pinit-line) 139 | - [`pinit-line-to`](#pinit-line-to) 140 | - [`pinit-arrow`](#pinit-arrow) 141 | - [`pinit-double-arrow`](#pinit-double-arrow) 142 | - [`pinit-point-to`](#pinit-point-to) 143 | - [`pinit-point-from`](#pinit-point-from) 144 | - [`simple-arrow`](#simple-arrow) 145 | - [`double-arrow`](#double-arrow) 146 | - [`pinit-fletcher-edge`](#pinit-fletcher-edge) 147 | - [Changelog](#changelog) 148 | - [0.2.2](#022) 149 | - [0.2.1](#021) 150 | - [0.2.0](#020) 151 | - [0.1.4](#014) 152 | - [0.1.3](#013) 153 | - [0.1.2](#012) 154 | - [0.1.1](#011) 155 | - [0.1.0](#010) 156 | - [Acknowledgements](#acknowledgements) 157 | - [License](#license) 158 | 159 | 160 | ## Reference 161 | 162 | ### `pin` 163 | 164 | Pinning a pin in text, the pin is supposed to be unique in one page. 165 | 166 | ```typ 167 | #let pin(name) = { .. } 168 | ``` 169 | 170 | **Arguments:** 171 | 172 | - `name`: [`int` or `str` or `any`] — Name of pin, which can be any types with unique `repr()` return value, such as integer and string. 173 | 174 | ### `pinit` 175 | 176 | Query positions of pins in the same page, then call the callback function `callback`. 177 | 178 | ```typ 179 | #let pinit(callback: none, ..pins) = { .. } 180 | ``` 181 | 182 | **Arguments:** 183 | 184 | - `..pins`: [`pin`] — Names of pins you want to query. It is supposed to be arguments of pin or a group of pins. 185 | - `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 the `absolute-place` function in this callback function to display something around the pins. 186 | 187 | 188 | ### `absolute-place` 189 | 190 | Place content at a specific location on the page relative to the top left corner of the page, regardless of margins, current containers, etc. 191 | 192 | > This function comes from [typst-drafting](https://github.com/ntjess/typst-drafting). 193 | 194 | ```typ 195 | #let absolute-place( 196 | dx: 0em, 197 | dy: 0em, 198 | body, 199 | ) = { .. } 200 | ``` 201 | 202 | **Arguments:** 203 | 204 | - `dx`: [`length`] — Length in the x-axis relative to the left edge of the page. 205 | - `dy`: [`length`] — Length in the y-axis relative to the top edge of the page. 206 | - `content`: [`content`] — The content you want to place. 207 | 208 | 209 | ### `pinit-place` 210 | 211 | Place content at a specific location on the page relative to the pin. 212 | 213 | ```typ 214 | #let pinit-place( 215 | dx: 0pt, 216 | dy: 0pt, 217 | pin-name, 218 | body, 219 | ) = { .. } 220 | ``` 221 | 222 | **Arguments:** 223 | 224 | - `dx`: [`length`] — Offset X relative to the pin. 225 | - `dy`: [`length`] — Offset Y relative to the pin. 226 | - `pin-name`: [`pin`] — Name of the pin to which you want to locate. 227 | - `body`: [`content`] — The content you want to place. 228 | 229 | 230 | ### `pinit-rect` 231 | 232 | Draw a rectangular shape on the page **containing all pins** with optional extended width and height. 233 | 234 | ```typ 235 | #let pinit-rect( 236 | dx: 0em, 237 | dy: -1em, 238 | extended-width: 0em, 239 | extended-height: 1.4em, 240 | pin1, 241 | pin2, 242 | pin3, // Optional 243 | ..pinX, 244 | ..args, 245 | ) = { .. } 246 | ``` 247 | 248 | **Arguments:** 249 | 250 | - `dx`: [`length`] — Offset X relative to the min-left of pins. 251 | - `dy`: [`length`] — Offset Y relative to the min-top of pins. 252 | - `extended-width`: [`length`] — Optional extended width of the rectangular shape. 253 | - `extended-height`: [`length`] — Optional extended height of the rectangular shape. 254 | - `pin1`: [`pin`] — One of these pins. 255 | - `pin2`: [`pin`] — One of these pins. 256 | - `pin3`: [`pin`] — One of these pins, optionally. 257 | - `...args`: Additional named arguments or settings for [`rect`](https://typst.app/docs/reference/visualize/rect/), like `fill`, `stroke` and `radius`. 258 | 259 | 260 | ### `pinit-highlight` 261 | 262 | Highlight a specific area on the page with a filled color and optional radius and stroke. It is just a simply styled `pinit-rect`. 263 | 264 | ```typ 265 | #let pinit-highlight( 266 | fill: rgb(255, 0, 0, 20), 267 | radius: 5pt, 268 | stroke: 0pt, 269 | dx: 0em, 270 | dy: -1em, 271 | extended-width: 0em, 272 | extended-height: 1.4em, 273 | pin1, 274 | pin2, 275 | pin3, // Optional 276 | ..pinX, 277 | ...args, 278 | ) = { .. } 279 | ``` 280 | 281 | **Arguments:** 282 | 283 | - `fill`: [`color`] — The fill color for the highlighted area. 284 | - `radius`: [`length`] — Optional radius for the highlight. 285 | - `stroke`: [`stroke`] — Optional stroke width for the highlight. 286 | - `dx`: [`length`] — Offset X relative to the min-left of pins. 287 | - `dy`: [`length`] — Offset Y relative to the min-top of pins. 288 | - `extended-width`: [`length`] — Optional extended width of the rectangular shape. 289 | - `extended-height`: [`length`] — Optional extended height of the rectangular shape. 290 | - `pin1`: [`pin`] — One of these pins. 291 | - `pin2`: [`pin`] — One of these pins. 292 | - `pin3`: [`pin`] — One of these pins, optionally. 293 | - `...args`: Additional arguments or settings for [`pinit-rect`](#pinit-rect). 294 | 295 | 296 | ### `pinit-line` 297 | 298 | Draw a line on the page between two specified pins with an optional stroke. 299 | 300 | ```typ 301 | #let pinit-line( 302 | stroke: 1pt, 303 | start-dx: 0pt, 304 | start-dy: 0pt, 305 | end-dx: 0pt, 306 | end-dy: 0pt, 307 | start, 308 | end, 309 | ) = { ... } 310 | ``` 311 | 312 | **Arguments:** 313 | 314 | - `stroke`: [`stroke`] — The stroke for the line. 315 | - `start-dx`: [`length`] — Offset X relative to the start pin. 316 | - `start-dy`: [`length`] — Offset Y relative to the start pin. 317 | - `end-dx`: [`length`] — Offset X relative to the end pin. 318 | - `end-dy`: [`length`] — Offset Y relative to the end pin. 319 | - `start`: [`pin`] — The start pin. 320 | - `end`: [`pin`] — The end pin. 321 | 322 | 323 | ### `pinit-line-to` 324 | 325 | Draw an line from a specified pin to a point on the page with optional settings. 326 | 327 | ```typ 328 | #let pinit-line-to( 329 | stroke: 1pt, 330 | pin-dx: 5pt, 331 | pin-dy: 5pt, 332 | body-dx: 5pt, 333 | body-dy: 5pt, 334 | offset-dx: 35pt, 335 | offset-dy: 35pt, 336 | pin-name, 337 | body, 338 | ) = { ... } 339 | ``` 340 | 341 | **Arguments:** 342 | 343 | - `stroke`: [`stroke`] — The stroke for the line. 344 | - `pin-dx`: [`length`] — Offset X of arrow start relative to the pin. 345 | - `pin-dy`: [`length`] — Offset Y of arrow start relative to the pin. 346 | - `body-dx`: [`length`] — Offset X of arrow end relative to the body. 347 | - `body-dy`: [`length`] — Offset Y of arrow end relative to the body. 348 | - `offset-dx`: [`length`] — Offset X relative to the pin. 349 | - `offset-dy`: [`length`] — Offset Y relative to the pin. 350 | - `pin-name`: [`pin`] — The name of the pin to start from. 351 | - `body`: [`content`] — The content to draw the arrow to. 352 | 353 | 354 | ### `pinit-arrow` 355 | 356 | Draw an arrow between two specified pins with optional settings. 357 | 358 | ```typ 359 | #let pinit-arrow( 360 | start-dx: 0pt, 361 | start-dy: 0pt, 362 | end-dx: 0pt, 363 | end-dy: 0pt, 364 | start, 365 | end, 366 | ..args, 367 | ) = { ... } 368 | ``` 369 | 370 | **Arguments:** 371 | 372 | - `start-dx`: [`length`] — Offset X relative to the start pin. 373 | - `start-dy`: [`length`] — Offset Y relative to the start pin. 374 | - `end-dx`: [`length`] — Offset X relative to the end pin. 375 | - `end-dy`: [`length`] — Offset Y relative to the end pin. 376 | - `start`: [`pin`] — The start pin. 377 | - `end`: [`pin`] — The end pin. 378 | - `...args`: Additional arguments or settings for [`simple-arrow`](#simple-arrow), like `fill`, `stroke` and `thickness`. 379 | 380 | 381 | ### `pinit-double-arrow` 382 | 383 | Draw an double arrow between two specified pins with optional settings. 384 | 385 | ```typ 386 | #let pinit-double-arrow( 387 | start-dx: 0pt, 388 | start-dy: 0pt, 389 | end-dx: 0pt, 390 | end-dy: 0pt, 391 | start, 392 | end, 393 | ..args, 394 | ) = { ... } 395 | ``` 396 | 397 | **Arguments:** 398 | 399 | - `start-dx`: [`length`] — Offset X relative to the start pin. 400 | - `start-dy`: [`length`] — Offset Y relative to the start pin. 401 | - `end-dx`: [`length`] — Offset X relative to the end pin. 402 | - `end-dy`: [`length`] — Offset Y relative to the end pin. 403 | - `start`: [`pin`] — The start pin. 404 | - `end`: [`pin`] — The end pin. 405 | - `...args`: Additional arguments or settings for [`double-arrow`](#double-arrow), like `fill`, `stroke` and `thickness`. 406 | 407 | 408 | ### `pinit-point-to` 409 | 410 | Draw an arrow from a specified pin to a point on the page with optional settings. 411 | 412 | ```typ 413 | #let pinit-point-to( 414 | pin-dx: 5pt, 415 | pin-dy: 5pt, 416 | body-dx: 5pt, 417 | body-dy: 5pt, 418 | offset-dx: 35pt, 419 | offset-dy: 35pt, 420 | double: false, 421 | pin-name, 422 | body, 423 | ..args, 424 | ) = { ... } 425 | ``` 426 | 427 | **Arguments:** 428 | 429 | - `pin-dx`: [`length`] — Offset X of arrow start relative to the pin. 430 | - `pin-dy`: [`length`] — Offset Y of arrow start relative to the pin. 431 | - `body-dx`: [`length`] — Offset X of arrow end relative to the body. 432 | - `body-dy`: [`length`] — Offset Y of arrow end relative to the body. 433 | - `offset-dx`: [`length`] — Offset X relative to the pin. 434 | - `offset-dy`: [`length`] — Offset Y relative to the pin. 435 | - `double`: [`bool`] — Draw a double arrow, default is `false`. 436 | - `pin-name`: [`pin`] — The name of the pin to start from. 437 | - `body`: [`content`] — The content to draw the arrow to. 438 | - `...args`: Additional arguments or settings for [`simple-arrow`](#simple-arrow), like `fill`, `stroke` and `thickness`. 439 | 440 | 441 | ### `pinit-point-from` 442 | 443 | Draw an arrow from a point on the page to a specified pin with optional settings. 444 | 445 | ```typ 446 | #let pinit-point-from( 447 | pin-dx: 5pt, 448 | pin-dy: 5pt, 449 | body-dx: 5pt, 450 | body-dy: 5pt, 451 | offset-dx: 35pt, 452 | offset-dy: 35pt, 453 | double: false, 454 | pin-name, 455 | body, 456 | ..args, 457 | ) = { ... } 458 | ``` 459 | 460 | **Arguments:** 461 | 462 | - `pin-dx`: [`length`] — Offset X relative to the pin. 463 | - `pin-dy`: [`length`] — Offset Y relative to the pin. 464 | - `body-dx`: [`length`] — Offset X relative to the body. 465 | - `body-dy`: [`length`] — Offset Y relative to the body. 466 | - `offset-dx`: [`length`] — Offset X relative to the left edge of the page. 467 | - `offset-dy`: [`length`] — Offset Y relative to the top edge of the page. 468 | - `double`: [`bool`] — Draw a double arrow, default is `false`. 469 | - `pin-name`: [`pin`] — The name of the pin that the arrow to. 470 | - `body`: [`content`] — The content to draw the arrow from. 471 | - `...args`: Additional arguments or settings for [`simple-arrow`](#simple-arrow), like `fill`, `stroke` and `thickness`. 472 | 473 | 474 | ### `simple-arrow` 475 | 476 | Draw a simple arrow on the page with optional settings, implemented by [`polygon`](https://typst.app/docs/reference/visualize/polygon/). 477 | 478 | ```typ 479 | #let simple-arrow( 480 | fill: black, 481 | stroke: 0pt, 482 | start: (0pt, 0pt), 483 | end: (30pt, 0pt), 484 | thickness: 2pt, 485 | arrow-width: 4, 486 | arrow-height: 4, 487 | inset: 0.5, 488 | tail: (), 489 | ) = { ... } 490 | ``` 491 | 492 | **Arguments:** 493 | 494 | - `fill`: [`color`] — The fill color for the arrow. 495 | - `stroke`: [`stroke`] — The stroke for the arrow. 496 | - `start`: [`point`] — The starting point of the arrow. 497 | - `end`: [`point`] — The ending point of the arrow. 498 | - `thickness`: [`length`] — The thickness of the arrow. 499 | - `arrow-width`: [`int` or `float`] — The width of the arrowhead relative to thickness. 500 | - `arrow-height`: [`int` or `float`] — The height of the arrowhead relative to thickness. 501 | - `inset`: [`int` or `float`] — The inset value for the arrowhead relative to thickness. 502 | - `tail`: [`array`] — The tail settings for the arrow. 503 | 504 | 505 | ### `double-arrow` 506 | 507 | Draw a double arrow on the page with optional settings, implemented by [`polygon`](https://typst.app/docs/reference/visualize/polygon/). 508 | 509 | ```typ 510 | #let double-arrow( 511 | fill: black, 512 | stroke: 0pt, 513 | start: (0pt, 0pt), 514 | end: (30pt, 0pt), 515 | thickness: 2pt, 516 | arrow-width: 4, 517 | arrow-height: 4, 518 | inset: 0.5, 519 | tail: (), 520 | ) = { ... } 521 | ``` 522 | 523 | **Arguments:** 524 | 525 | - `fill`: [`color`] — The fill color for the arrow. 526 | - `stroke`: [`stroke`] — The stroke for the arrow. 527 | - `start`: [`point`] — The starting point of the arrow. 528 | - `end`: [`point`] — The ending point of the arrow. 529 | - `thickness`: [`length`] — The thickness of the arrow. 530 | - `arrow-width`: [`int` or `float`] — The width of the arrowhead relative to thickness. 531 | - `arrow-height`: [`int` or `float`] — The height of the arrowhead relative to thickness. 532 | - `inset`: [`int` or `float`] — The inset value for the arrowhead relative to thickness. 533 | - `tail`: [`array`] — The tail settings for the arrow. 534 | 535 | 536 | 537 | ### `pinit-fletcher-edge` 538 | 539 | Draw a connecting line or arc in an fletcher arrow diagram. 540 | 541 | ```typ 542 | #let pinit-fletcher-edge( 543 | fletcher, 544 | start, 545 | end: none, 546 | start-dx: 0pt, 547 | start-dy: 0pt, 548 | end-dx: 0pt, 549 | end-dy: 0pt, 550 | width-scale: 100%, 551 | height-scale: 100%, 552 | default-width: 30pt, 553 | default-height: 30pt, 554 | ..args, 555 | ) = { ... } 556 | ``` 557 | 558 | **Arguments:** 559 | 560 | - `fletcher` (module): The Fletcher module. You can import it with something like `#import "@preview/fletcher:0.5.1"` 561 | - `start` (pin): The starting pin of the edge. It is assumed that the pin is at the *origin point (0, 0)* of the edge. 562 | - `end` (pin): The ending pin of the edge. If not provided, the edge will use default values for the width and height. 563 | - `start-dx` (length): The x-offset of the starting pin. You should use pt units. 564 | - `start-dy` (length): The y-offset of the starting pin. You should use pt units. 565 | - `end-dx` (length): The x-offset of the ending pin. You should use pt units. 566 | - `end-dy` (length): The y-offset of the ending pin. You should use pt units. 567 | - `width-scale` (percent): The width scale of the edge. The default value is 100%. 568 | 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"`. 569 | - `height-scale` (percent): The height scale of the edge. The default value is 100%. 570 | - `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. 571 | - `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. 572 | - `..args` (any): An edge's positional arguments may specify: 573 | - the edge's #param[edge][vertices], each specified with a CeTZ-style coordinate 574 | - the #param[edge][label] content 575 | - arrow #param[edge][marks], like `"=>"` or `"<<-|-o"` 576 | - other style flags, like `"double"` or `"wave"` 577 | 578 | 579 | 580 | ## Changelog 581 | 582 | ### 0.2.2 583 | 584 | - Fix bugs. 585 | 586 | 587 | ### 0.2.1 588 | 589 | - To be compatible with Typst 0.12. 590 | 591 | 592 | ### 0.2.0 593 | 594 | - **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. 595 | - **Migration**: you need to use a named argument `callback: (..positions) => { .. }` to specify the callback function. 596 | - **Migration**: you cannot use a array as a pin name. Now `#pinit((pin1, pin2), callback: func)` means that we use `pin1` and `pin2` as a group of pins, and the callback function will receive **a single position** (the center of the bounding box of `pin1` and `pin2`). 597 | - **Benefit**: you can use `#pinit(pin1, pin2, callback: func)` to query the positions of `pin1` and `pin2` separately, and `#pinit((pin1, pin2), callback: func)` to query the position of the center of the bounding box of `pin1` and `pin2`. 598 | - Add `pinit-fletcher-edge` function to draw a connecting line or arc in an fletcher arrow diagram. 599 | - Add `double-arrow` function and `pinit-double-arrow` function. 600 | - Add `double` argument for `pinit-point-to` and `pinit-point-from` functions. 601 | - Better comments and documentation. 602 | 603 | 604 | ### 0.1.4 605 | 606 | - Update documentation. 607 | 608 | 609 | ### 0.1.3 610 | 611 | - Add `pinit-line-to` function. 612 | 613 | 614 | ### 0.1.2 615 | 616 | - Add em unit support for `simple-arrow`. 617 | 618 | 619 | ### 0.1.1 620 | 621 | - Fix some bugs. 622 | 623 | 624 | ### 0.1.0 625 | 626 | - Initial release. 627 | 628 | 629 | ## Acknowledgements 630 | 631 | - Some of the inspirations and codes comes from [typst-drafting](https://github.com/ntjess/typst-drafting). 632 | - The concise and aesthetic example slide style come from course *Data Structures and Algorithms* of [Chaodong ZHENG](https://chaodong.me/). 633 | - Thank [PaulS](https://github.com/psads-git) for double arrow feature. 634 | - Thank [Jollywatt](https://github.com/Jollywatt) for fletcher package. 635 | 636 | 637 | ## License 638 | 639 | This project is licensed under the MIT License. 640 | -------------------------------------------------------------------------------- /examples/asymptotic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OrangeX4/typst-pinit/b12b9bebd1d2c0cde9a493049a29a123867aa485/examples/asymptotic.png -------------------------------------------------------------------------------- /examples/equation-desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OrangeX4/typst-pinit/b12b9bebd1d2c0cde9a493049a29a123867aa485/examples/equation-desc.png -------------------------------------------------------------------------------- /examples/equation-desc.typ: -------------------------------------------------------------------------------- 1 | // Example adapted from: @Matt https://discord.com/channels/1054443721975922748/1088371919725793360/1166508915572351067 2 | 3 | #import "../lib.typ": * 4 | #set page(width: 700pt, height: auto, margin: 30pt) 5 | #set text(size: 20pt) 6 | #set math.equation(numbering: "(1)") 7 | 8 | #let pinit-highlight-equation-from(height: 2em, pos: bottom, fill: rgb(0, 180, 255), highlight-pins, point-pin, body) = { 9 | pinit-highlight(..highlight-pins, dy: -0.9em, fill: rgb(..fill.components().slice(0, -1), 40)) 10 | pinit-point-from( 11 | fill: fill, pin-dx: 0em, pin-dy: if pos == bottom { 0.5em } else { -0.9em }, body-dx: 0pt, body-dy: if pos == bottom { -1.7em } else { -1.6em }, offset-dx: 0em, offset-dy: if pos == bottom { 0.8em + height } else { -0.6em - height }, 12 | point-pin, 13 | rect( 14 | inset: 0.5em, 15 | stroke: (bottom: 0.12em + fill), 16 | { 17 | set text(fill: fill) 18 | body 19 | } 20 | ) 21 | ) 22 | } 23 | 24 | Equation written out directly (for comparison): 25 | 26 | $ (q_T^* p_T)/p_E p_E^* >= (c + q_T^* p_T^*)(1+r^*)^(2N) $ 27 | 28 | Laid out with pinit: 29 | 30 | #v(3.5em) 31 | 32 | $ (#pin(1)q_T^* p_T#pin(2))/(#pin(3)p_E#pin(4))#pin(5)p_E^*#pin(6) >= (c + q_T^* p_T^*)(1+r^*)^(2N) $ 33 | 34 | #v(5em) 35 | 36 | #pinit-highlight-equation-from((1, 2, 3, 4), (3, 4), height: 3.5em, pos: bottom, fill: rgb(0, 180, 255))[ 37 | quantity of Terran goods 38 | ] 39 | 40 | #pinit-highlight-equation-from((5, 6), (5, 6), height: 2.5em, pos: top, fill: rgb(150, 90, 170))[ 41 | price of Terran goods, on Trantor 42 | ] 43 | 44 | Paragraph after the equation. 45 | -------------------------------------------------------------------------------- /examples/example-pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OrangeX4/typst-pinit/b12b9bebd1d2c0cde9a493049a29a123867aa485/examples/example-pages.png -------------------------------------------------------------------------------- /examples/example.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OrangeX4/typst-pinit/b12b9bebd1d2c0cde9a493049a29a123867aa485/examples/example.pdf -------------------------------------------------------------------------------- /examples/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OrangeX4/typst-pinit/b12b9bebd1d2c0cde9a493049a29a123867aa485/examples/example.png -------------------------------------------------------------------------------- /examples/example.typ: -------------------------------------------------------------------------------- 1 | #import "../lib.typ": * 2 | #import "@preview/touying:0.5.3": * 3 | #import themes.default: * 4 | 5 | #set text(size: 20pt, font: "Calibri", ligatures: false) 6 | #show heading: set text(weight: "regular") 7 | #show heading: set block(above: 1.4em, below: 1em) 8 | #show heading.where(level: 1): set text(size: 1.5em) 9 | 10 | // Useful functions 11 | #let crimson = rgb("#c00000") 12 | #let greybox(..args, body) = rect(fill: luma(95%), stroke: 0.5pt, inset: 0pt, outset: 10pt, ..args, body) 13 | #let redbold(body) = { 14 | set text(fill: crimson, weight: "bold") 15 | body 16 | } 17 | #let blueit(body) = { 18 | set text(fill: blue) 19 | body 20 | } 21 | 22 | #show: default-theme.with(aspect-ratio: "4-3") 23 | 24 | // Main body 25 | #slide[ 26 | = Asymptotic Notation: $O$ 27 | 28 | Use #pin("h1")asymptotic notations#pin("h2") to describe asymptotic efficiency of algorithms. 29 | (Ignore constant coefficients and lower-order terms.) 30 | 31 | #pause 32 | 33 | #greybox[ 34 | Given a function $g(n)$, we denote by $O(g(n))$ the following *set of functions*: 35 | #redbold(${f(n): "exists" c > 0 "and" n_0 > 0, "such that" f(n) <= c dot g(n) "for all" n >= n_0}$) 36 | ] 37 | 38 | #pinit-highlight("h1", "h2") 39 | 40 | #pause 41 | 42 | $f(n) = O(g(n))$: #pin(1)$f(n)$ is *asymptotically smaller* than $g(n)$.#pin(2) 43 | 44 | #absolute-place(dx: 550pt, dy: 300pt, image(width: 25%, "asymptotic.png")) 45 | 46 | #pause 47 | 48 | $f(n) redbold(in) O(g(n))$: $f(n)$ is *asymptotically* #redbold[at most] $g(n)$. 49 | 50 | #pinit-line(stroke: 3pt + crimson, start-dy: -0.25em, end-dy: -0.25em, 1, 2) 51 | 52 | #pause 53 | 54 | #block[Insertion Sort as an #pin("r1")example#pin("r2"):] 55 | 56 | - Best Case: $T(n) approx c n + c' n - c''$ #pin(3) 57 | - Worst case: $T(n) approx c n + (c' \/ 2) n^2 - c''$ #pin(4) 58 | 59 | #pinit-rect("r1", "r2") 60 | 61 | #pause 62 | 63 | #pinit-place(3, dx: 15pt, dy: -15pt)[#redbold[$T(n) = O(n)$]] 64 | #pinit-place(4, dx: 15pt, dy: -15pt)[#redbold[$T(n) = O(n)$]] 65 | 66 | #pause 67 | 68 | #blueit[Q: Is $n^(3) = O(n^2)$#pin("que")? How to prove your answer#pin("ans")?] 69 | 70 | #pause 71 | 72 | #pinit-point-to("que", fill: crimson, redbold[No.]) 73 | #pinit-point-from("ans", body-dx: -150pt)[ 74 | Show that the equation $(3/2)^n >= c$ \ 75 | has infinitely many solutions for $n$. 76 | ] 77 | ] 78 | -------------------------------------------------------------------------------- /examples/fletcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OrangeX4/typst-pinit/b12b9bebd1d2c0cde9a493049a29a123867aa485/examples/fletcher.png -------------------------------------------------------------------------------- /examples/fletcher.typ: -------------------------------------------------------------------------------- 1 | #import "../lib.typ": * 2 | #import "@preview/fletcher:0.5.1" 3 | 4 | Con#pin(1)#h(4em)#pin(2)nect 5 | 6 | #pinit-fletcher-edge( 7 | fletcher, 1, end: 2, (1, 0), [bend], bend: -20deg, "<->", 8 | decorations: fletcher.cetz.decorations.wave.with(amplitude: .1), 9 | ) -------------------------------------------------------------------------------- /examples/pinit-for-raw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OrangeX4/typst-pinit/b12b9bebd1d2c0cde9a493049a29a123867aa485/examples/pinit-for-raw.png -------------------------------------------------------------------------------- /examples/pinit-for-raw.typ: -------------------------------------------------------------------------------- 1 | #import "../lib.typ": * 2 | 3 | #set text(size: 24pt) 4 | 5 | #show raw: it => { 6 | show regex("pin\d"): it => pin(eval(it.text.slice(3))) 7 | it 8 | } 9 | 10 | `print(pin1"hello, world"pin2)` 11 | 12 | #pinit-highlight(1, 2) -------------------------------------------------------------------------------- /examples/simple-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OrangeX4/typst-pinit/b12b9bebd1d2c0cde9a493049a29a123867aa485/examples/simple-demo.png -------------------------------------------------------------------------------- /examples/simple-demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OrangeX4/typst-pinit/b12b9bebd1d2c0cde9a493049a29a123867aa485/examples/simple-demo2.png -------------------------------------------------------------------------------- /lib.typ: -------------------------------------------------------------------------------- 1 | #import "simple-arrow.typ": simple-arrow, double-arrow 2 | #import "pinit-fletcher.typ": pinit-fletcher-edge 3 | #import "pinit-core.typ": * 4 | 5 | // ----------------------------------------------- 6 | // Libs 7 | // ----------------------------------------------- 8 | 9 | /// Draw a rectangular shape on the page **containing all pins** with optional extended width and height. 10 | /// 11 | /// - `dx`: [`length`] — Offset X relative to the min-left of pins. 12 | /// - `dy`: [`length`] — Offset Y relative to the min-top of pins. 13 | /// - `extended-width`: [`length`] — Optional extended width of the rectangular shape. 14 | /// - `extended-height`: [`length`] — Optional extended height of the rectangular shape. 15 | /// - `pin1`: [`pin`] — One of these pins. 16 | /// - `pin2`: [`pin`] — One of these pins. 17 | /// - `pin3`: [`pin`] — One of these pins, optionally. 18 | /// - `...args`: Additional named arguments or settings for [`rect`](https://typst.app/docs/reference/visualize/rect/), like `fill`, `stroke` and `radius`. 19 | #let pinit-rect( 20 | dx: 0em, 21 | dy: -1em, 22 | extended-width: 0em, 23 | extended-height: 1.4em, 24 | ..args, 25 | ) = { 26 | pinit( 27 | ..args.pos(), 28 | callback: (..positions) => { 29 | positions = positions.pos() 30 | let min-x = calc.min(..positions.map(loc => loc.x)) 31 | let max-x = calc.max(..positions.map(loc => loc.x)) 32 | let min-y = calc.min(..positions.map(loc => loc.y)) 33 | let max-y = calc.max(..positions.map(loc => loc.y)) 34 | absolute-place( 35 | dx: min-x + dx, 36 | dy: min-y + dy, 37 | rect( 38 | width: max-x - min-x + extended-width, 39 | height: max-y - min-y + extended-height, 40 | ..args.named(), 41 | ), 42 | ) 43 | }, 44 | ) 45 | } 46 | 47 | /// Highlight a specific area on the page with a filled color and optional radius and stroke. It is just a simply styled `pinit-rect`. 48 | /// 49 | // - `fill`: [`color`] — The fill color for the highlighted area. 50 | // - `radius`: [`length`] — Optional radius for the highlight. 51 | // - `stroke`: [`stroke`] — Optional stroke width for the highlight. 52 | // - `dx`: [`length`] — Offset X relative to the min-left of pins. 53 | // - `dy`: [`length`] — Offset Y relative to the min-top of pins. 54 | // - `extended-width`: [`length`] — Optional extended width of the rectangular shape. 55 | // - `extended-height`: [`length`] — Optional extended height of the rectangular shape. 56 | // - `pin1`: [`pin`] — One of these pins. 57 | // - `pin2`: [`pin`] — One of these pins. 58 | // - `pin3`: [`pin`] — One of these pins, optionally. 59 | // - `...args`: Additional arguments or settings for [`pinit-rect`](#pinit-rect). 60 | #let pinit-highlight( 61 | fill: rgb(255, 0, 0, 20), 62 | radius: 5pt, 63 | stroke: 0pt, 64 | dx: 0em, 65 | dy: -1em, 66 | extended-width: 0em, 67 | extended-height: 1.4em, 68 | ..args, 69 | ) = { 70 | pinit-rect( 71 | fill: fill, 72 | radius: radius, 73 | stroke: stroke, 74 | dx: dx, 75 | dy: dy, 76 | extended-width: extended-width, 77 | extended-height: extended-height, 78 | ..args, 79 | ) 80 | } 81 | 82 | /// Draw a line on the page between two specified pins with an optional stroke. 83 | /// 84 | /// - `stroke`: [`stroke`] — The stroke for the line. 85 | /// - `start-dx`: [`length`] — Offset X relative to the start pin. 86 | /// - `start-dy`: [`length`] — Offset Y relative to the start pin. 87 | /// - `end-dx`: [`length`] — Offset X relative to the end pin. 88 | /// - `end-dy`: [`length`] — Offset Y relative to the end pin. 89 | /// - `start`: [`pin`] — The start pin. 90 | /// - `end`: [`pin`] — The end pin. 91 | #let pinit-line( 92 | stroke: 1pt, 93 | start-dx: 0pt, 94 | start-dy: 0pt, 95 | end-dx: 0pt, 96 | end-dy: 0pt, 97 | start, 98 | end, 99 | ) = { 100 | pinit( 101 | start, 102 | end, 103 | callback: (start-pos, end-pos) => { 104 | absolute-place( 105 | line( 106 | stroke: stroke, 107 | start: ( 108 | start-pos.x + start-dx, 109 | start-pos.y + start-dy, 110 | ), 111 | end: ( 112 | end-pos.x + end-dx, 113 | end-pos.y + end-dy, 114 | ), 115 | ), 116 | ) 117 | }, 118 | ) 119 | } 120 | 121 | /// Draw an line from a specified pin to a point on the page with optional settings. 122 | /// 123 | /// - `stroke`: [`stroke`] — The stroke for the line. 124 | /// - `pin-dx`: [`length`] — Offset X of arrow start relative to the pin. 125 | /// - `pin-dy`: [`length`] — Offset Y of arrow start relative to the pin. 126 | /// - `body-dx`: [`length`] — Offset X of arrow end relative to the body. 127 | /// - `body-dy`: [`length`] — Offset Y of arrow end relative to the body. 128 | /// - `offset-dx`: [`length`] — Offset X relative to the pin. 129 | /// - `offset-dy`: [`length`] — Offset Y relative to the pin. 130 | /// - `pin-name`: [`pin`] — The name of the pin to start from. 131 | /// - `body`: [`content`] — The content to draw the arrow to. 132 | #let pinit-line-to( 133 | pin-dx: 5pt, 134 | pin-dy: 5pt, 135 | body-dx: 5pt, 136 | body-dy: 5pt, 137 | offset-dx: 35pt, 138 | offset-dy: 35pt, 139 | pin-name, 140 | body, 141 | ..args, 142 | ) = { 143 | pinit-line(pin-name, pin-name, start-dx: pin-dx, start-dy: pin-dy, end-dx: offset-dx, end-dy: offset-dy, ..args) 144 | pinit-place(pin-name, body, dx: offset-dx + body-dx, dy: offset-dy + body-dy) 145 | } 146 | 147 | /// Draw an arrow between two specified pins with optional settings. 148 | /// 149 | /// - `start-dx`: [`length`] — Offset X relative to the start pin. 150 | /// - `start-dy`: [`length`] — Offset Y relative to the start pin. 151 | /// - `end-dx`: [`length`] — Offset X relative to the end pin. 152 | /// - `end-dy`: [`length`] — Offset Y relative to the end pin. 153 | /// - `start`: [`pin`] — The start pin. 154 | /// - `end`: [`pin`] — The end pin. 155 | /// - `...args`: Additional arguments or settings for [`simple-arrow`](#simple-arrow), like `fill`, `stroke` and `thickness`. 156 | #let pinit-arrow( 157 | start-dx: 0pt, 158 | start-dy: 0pt, 159 | end-dx: 0pt, 160 | end-dy: 0pt, 161 | start, 162 | end, 163 | ..args, 164 | ) = { 165 | pinit( 166 | start, 167 | end, 168 | callback: (start-pos, end-pos) => { 169 | absolute-place( 170 | dx: start-pos.x + start-dx, 171 | dy: start-pos.y + start-dy, 172 | simple-arrow( 173 | start: ( 174 | 0em, 175 | 0em, 176 | ), 177 | end: ( 178 | end-pos.x + end-dx - (start-pos.x + start-dx), 179 | end-pos.y + end-dy - (start-pos.y + start-dy), 180 | ), 181 | ..args, 182 | ), 183 | ) 184 | }, 185 | ) 186 | } 187 | 188 | /// Draw an double arrow between two specified pins with optional settings. 189 | /// 190 | /// - `start-dx`: [`length`] — Offset X relative to the start pin. 191 | /// - `start-dy`: [`length`] — Offset Y relative to the start pin. 192 | /// - `end-dx`: [`length`] — Offset X relative to the end pin. 193 | /// - `end-dy`: [`length`] — Offset Y relative to the end pin. 194 | /// - `start`: [`pin`] — The start pin. 195 | /// - `end`: [`pin`] — The end pin. 196 | /// - `...args`: Additional arguments or settings for [`double-arrow`](#double-arrow), like `fill`, `stroke` and `thickness`. 197 | #let pinit-double-arrow( 198 | start-dx: 0pt, 199 | start-dy: 0pt, 200 | end-dx: 0pt, 201 | end-dy: 0pt, 202 | start, 203 | end, 204 | ..args, 205 | ) = { 206 | pinit( 207 | start, 208 | end, 209 | callback: (start-pos, end-pos) => { 210 | absolute-place( 211 | dx: start-pos.x + start-dx, 212 | dy: start-pos.y + start-dy, 213 | double-arrow( 214 | start: ( 215 | 0em, 216 | 0em, 217 | ), 218 | end: ( 219 | end-pos.x + end-dx - (start-pos.x + start-dx), 220 | end-pos.y + end-dy - (start-pos.y + start-dy), 221 | ), 222 | ..args, 223 | ), 224 | ) 225 | }, 226 | ) 227 | } 228 | 229 | /// Draw an arrow from a specified pin to a point on the page with optional settings. 230 | /// - `pin-dx`: [`length`] — Offset X of arrow start relative to the pin. 231 | /// - `pin-dy`: [`length`] — Offset Y of arrow start relative to the pin. 232 | /// - `body-dx`: [`length`] — Offset X of arrow end relative to the body. 233 | /// - `body-dy`: [`length`] — Offset Y of arrow end relative to the body. 234 | /// - `offset-dx`: [`length`] — Offset X relative to the pin. 235 | /// - `offset-dy`: [`length`] — Offset Y relative to the pin. 236 | /// - `double`: [`bool`] — Draw a double arrow, default is `false`. 237 | /// - `pin-name`: [`pin`] — The name of the pin to start from. 238 | /// - `body`: [`content`] — The content to draw the arrow to. 239 | /// - `...args`: Additional arguments or settings for [`simple-arrow`](#simple-arrow), like `fill`, `stroke` and `thickness`. 240 | #let pinit-point-to( 241 | pin-dx: 5pt, 242 | pin-dy: 5pt, 243 | body-dx: 5pt, 244 | body-dy: 5pt, 245 | offset-dx: 35pt, 246 | offset-dy: 35pt, 247 | double: false, 248 | pin-name, 249 | body, 250 | ..args, 251 | ) = { 252 | let arrow-fn = if double { 253 | pinit-double-arrow 254 | } else { 255 | pinit-arrow 256 | } 257 | arrow-fn(pin-name, pin-name, start-dx: pin-dx, start-dy: pin-dy, end-dx: offset-dx, end-dy: offset-dy, ..args) 258 | pinit-place(pin-name, body, dx: offset-dx + body-dx, dy: offset-dy + body-dy) 259 | } 260 | 261 | /// Draw an arrow from a point on the page to a specified pin with optional settings. 262 | /// 263 | /// - `pin-dx`: [`length`] — Offset X relative to the pin. 264 | /// - `pin-dy`: [`length`] — Offset Y relative to the pin. 265 | /// - `body-dx`: [`length`] — Offset X relative to the body. 266 | /// - `body-dy`: [`length`] — Offset Y relative to the body. 267 | /// - `offset-dx`: [`length`] — Offset X relative to the left edge of the page. 268 | /// - `offset-dy`: [`length`] — Offset Y relative to the top edge of the page. 269 | /// - `double`: [`bool`] — Draw a double arrow, default is `false`. 270 | /// - `pin-name`: [`pin`] — The name of the pin that the arrow to. 271 | /// - `body`: [`content`] — The content to draw the arrow from. 272 | /// - `...args`: Additional arguments or settings for [`simple-arrow`](#simple-arrow), like `fill`, `stroke` and `thickness`. 273 | #let pinit-point-from( 274 | pin-dx: 5pt, 275 | pin-dy: 5pt, 276 | body-dx: 5pt, 277 | body-dy: 5pt, 278 | offset-dx: 35pt, 279 | offset-dy: 35pt, 280 | double: false, 281 | pin-name, 282 | body, 283 | ..args, 284 | ) = { 285 | let arrow-fn = if double { 286 | pinit-double-arrow 287 | } else { 288 | pinit-arrow 289 | } 290 | arrow-fn(pin-name, pin-name, start-dx: offset-dx, start-dy: offset-dy, end-dx: pin-dx, end-dy: pin-dy, ..args) 291 | pinit-place(pin-name, body, dx: offset-dx + body-dx, dy: offset-dy + body-dy) 292 | } 293 | -------------------------------------------------------------------------------- /pinit-core.typ: -------------------------------------------------------------------------------- 1 | #let empty-box = box(height: 0pt, width: 0pt, outset: 0pt, inset: 0pt) 2 | 3 | // ----------------------------------------------- 4 | // Code from https://github.com/ntjess/typst-drafting 5 | // ----------------------------------------------- 6 | #let _run-func-on-first-loc(func, label-name: "loc-tracker") = { 7 | // Some placements are determined by locations relative to a fixed point. However, typst 8 | // will automatically re-evaluate that computation several times, since the usage 9 | // of that computation will change where an element is placed (and therefore update its 10 | // location, and so on). Get around this with a state that only checks for the first 11 | // update, then ignores all subsequent updates 12 | let lbl = label(label-name) 13 | [#metadata(label-name)#lbl] 14 | context { 15 | let use-loc = query(selector(lbl).before(here())).last().location() 16 | func(use-loc) 17 | } 18 | } 19 | 20 | /// Place content at a specific location on the page relative to the top left corner 21 | /// of the page, regardless of margins, current container, etc. 22 | /// 23 | /// > This function comes from [typst-drafting](https://github.com/ntjess/typst-drafting), Thanks to [ntjess]((https://github.com/ntjess/). 24 | /// 25 | /// - `dx`: [`length`] — Length in the x-axis relative to the left edge of the page. 26 | /// - `dy`: [`length`] — Length in the y-axis relative to the top edge of the page. 27 | /// - `body`: [`content`] — The content you want to place. 28 | #let absolute-place(dx: 0em, dy: 0em, body) = { 29 | _run-func-on-first-loc(loc => { 30 | let pos = loc.position() 31 | place(dx: -pos.x + dx, dy: -pos.y + dy, body) 32 | }) 33 | } 34 | 35 | // ----------------------------------------------- 36 | // Core 37 | // ----------------------------------------------- 38 | 39 | #let _pin-label(loc, name) = label("page-" + str(loc.position().page) + ":" + repr(name)) 40 | 41 | /// Pinning a pin in text, the pin is supposed to be unique in one page. 42 | /// 43 | /// - `name`: [`integer` or `string` or `any`] — Name of pin, which can be any types with unique `repr()` return value, such as integer and string. 44 | #let pin(name) = { 45 | _run-func-on-first-loc(loc => { 46 | [#empty-box#_pin-label(loc, name)] 47 | }) 48 | } 49 | 50 | /// Query positions of pins in the same page, then call the callback function `func`. 51 | /// 52 | /// - `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 the `absolute-place` function in this callback function to display something around the pins. 53 | /// - `..pins`: [`pin`] — Names of pins you want to query. It is supposed to be arguments composed with pin or a group of pins. 54 | #let pinit(callback: none, ..pins) = { 55 | assert(callback != none, message: "The callback function is required.") 56 | assert(pins.named().len() == 0, message: "The pin names should not be named.") 57 | pins = pins.pos() 58 | _run-func-on-first-loc(loc => { 59 | let positions = () 60 | for pin-group in pins { 61 | let poss = () 62 | if type(pin-group) != array { 63 | pin-group = (pin-group,) 64 | } 65 | for pin-name in pin-group { 66 | let elems = query( 67 | selector(_pin-label(loc, pin-name)), 68 | ) 69 | assert(elems.len() > 0, message: "Pin not found: " + repr(pin-name)) 70 | poss.push(elems.at(0).location().position()) 71 | } 72 | if poss.len() == 1 { 73 | positions.push(poss.at(0)) 74 | } else { 75 | positions.push(( 76 | page: poss.at(0).page, 77 | x: poss.map(p => p.x).sum() / poss.len(), 78 | y: poss.map(p => p.y).sum() / poss.len(), 79 | )) 80 | } 81 | } 82 | callback(..positions) 83 | }) 84 | } 85 | 86 | /// Place content at a specific location on the page relative to the pin. 87 | /// 88 | /// - `pin-name`: [`pin`] — Name of the pin to which you want to locate. 89 | /// - `body`: [`content`] — The content you want to place. 90 | /// - `dx`: [`length`] — Offset X relative to the pin. 91 | /// - `dy`: [`length`] — Offset Y relative to the pin. 92 | #let pinit-place( 93 | pin-name, 94 | body, 95 | dx: 0pt, 96 | dy: 0pt, 97 | ) = { 98 | pinit( 99 | pin-name, 100 | callback: pos => { 101 | absolute-place(dx: pos.x + dx, dy: pos.y + dy, body) 102 | }, 103 | ) 104 | } 105 | 106 | -------------------------------------------------------------------------------- /pinit-fletcher.typ: -------------------------------------------------------------------------------- 1 | #import "pinit-core.typ": * 2 | 3 | /// Draw a connecting line or arc in an arrow diagram. 4 | /// 5 | /// Example: 6 | /// 7 | /// ```typc 8 | /// #import "@preview/fletcher:0.5.1" 9 | /// Con#pin(1)#h(4em)#pin(2)nect 10 | /// 11 | /// #pinit-fletcher-edge( 12 | /// fletcher, 1, end: 2, (1, 0), [bend], bend: -20deg, "<->", 13 | /// decorations: fletcher.cetz.decorations.wave.with(amplitude: .1), 14 | /// ) 15 | /// ``` 16 | /// 17 | /// - fletcher (module): The Fletcher module. You can import it with something like `#import "@preview/fletcher:0.5.1"` 18 | /// 19 | /// - start (pin): The starting pin of the edge. It is assumed that the pin is at the *origin point (0, 0)* of the edge. 20 | /// 21 | /// - end (pin): The ending pin of the edge. If not provided, the edge will use default values for the width and height. 22 | /// 23 | /// - start-dx (length): The x-offset of the starting pin. You should use pt units. 24 | /// 25 | /// - start-dy (length): The y-offset of the starting pin. You should use pt units. 26 | /// 27 | /// - end-dx (length): The x-offset of the ending pin. You should use pt units. 28 | /// 29 | /// - end-dy (length): The y-offset of the ending pin. You should use pt units. 30 | /// 31 | /// - width-scale (percent): The width scale of the edge. The default value is 100%. 32 | /// 33 | /// 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"`. 34 | /// 35 | /// - height-scale (percent): The height scale of the edge. The default value is 100%. 36 | /// 37 | /// - 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. 38 | /// 39 | /// - 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. 40 | /// 41 | /// 42 | /// ====================================================================== 43 | /// 44 | /// The following are the options for the `fletcher.edge` function. Source: [Jollywatt/typst-fletcher](https://github.com/Jollywatt/typst-fletcher) 45 | /// 46 | /// ====================================================================== 47 | /// 48 | /// - ..args (any): An edge's positional arguments may specify: 49 | /// - the edge's #param[edge][vertices], each specified with a CeTZ-style coordinate 50 | /// - the #param[edge][label] content 51 | /// - arrow #param[edge][marks], like `"=>"` or `"<<-|-o"` 52 | /// - other style flags, like `"double"` or `"wave"` 53 | /// 54 | /// Vertex coordinates must come first, and are optional: 55 | /// 56 | /// ```typc 57 | /// edge(from, to, ..) // explicit start and end nodes 58 | /// edge(to, ..) == edge(auto, to, ..) // start snaps to previous node 59 | /// edge(..) == edge(auto, auto, ..) // snaps to previous and next nodes 60 | /// edge(from, v1, v2, ..vs, to, ..) // a multi-segmented edge 61 | /// edge(from, "->", to) // for two vertices, the marks style can come in between 62 | /// ``` 63 | /// 64 | /// All vertices except the start point can be shorthand relative coordinate 65 | /// string containing the characters 66 | /// ${#"lrudtbnesw".clusters().map(raw).join($, $)}$ or commas. 67 | /// 68 | /// If given as positional arguments, an edge's #param[edge][marks] and 69 | /// #param[edge][label] are disambiguated by guessing based on the types. For 70 | /// example, the following are equivalent: 71 | /// 72 | /// ```typc 73 | /// edge((0,0), (1,0), $f$, "->") 74 | /// edge((0,0), (1,0), "->", $f$) 75 | /// edge((0,0), (1,0), $f$, marks: "->") 76 | /// edge((0,0), (1,0), "->", label: $f$) 77 | /// edge((0,0), (1,0), label: $f$, marks: "->") 78 | /// ``` 79 | /// 80 | /// Additionally, some common options are given flags that may be given as 81 | /// string positional arguments. These are 82 | /// #fletcher.EDGE_FLAGS.keys().map(repr).map(raw).join([, ], last: [, and ]). 83 | /// For example, the following are equivalent: 84 | /// 85 | /// ```typc 86 | /// edge((0,0), (1,0), $f$, "wave", "crossing") 87 | /// edge((0,0), (1,0), $f$, decorations: "wave", crossing: true) 88 | /// ``` 89 | /// 90 | /// - vertices (array): Array of (at least two) coordinates for the edge. 91 | /// 92 | /// Vertices can also be specified as leading positional arguments, but if so, 93 | /// the `vertices` option must be empty. If the number of vertices is greater 94 | /// than two, #param[edge][kind] defaults to `"poly"`. 95 | /// 96 | /// - kind (string): The kind of the edge, one of `"line"`, `"arc"`, or `"poly"`. 97 | /// This is chosen automatically based on the presence of other options 98 | /// (#param[edge][bend] implies `"arc"`, #param[edge][corner] or additional 99 | /// vertices implies `"poly"`). 100 | /// 101 | /// - corner (none, left, right): Whether to create a right-angled corner, 102 | /// turning `left` or `right`. 103 | /// (Bending right means the corner sticks out to the left, and vice versa.) 104 | /// 105 | /// #diagram( 106 | /// node((0,1), `from`), 107 | /// node((1,0), `to`), 108 | /// edge((0,1), (1,0), `right`, "->", corner: right), 109 | /// edge((0,1), (1,0), `left`, "->", corner: left), 110 | /// ) 111 | /// 112 | /// - bend (angle): Edge curvature. If `0deg`, the connector is a straight line; 113 | /// positive angles bend clockwise. 114 | /// 115 | /// #diagram(debug: 0, { 116 | /// node((0,0), $A$) 117 | /// node((1,1), $B$) 118 | /// let N = 4 119 | /// range(N + 1) 120 | /// .map(x => (x/N - 0.5)*2*100deg) 121 | /// .map(θ => edge((0,0), (1,1), θ, bend: θ, ">->", label-side: center)) 122 | /// .join() 123 | /// }) 124 | /// 125 | /// - label (content): Content for the edge label. See the 126 | /// #param[edge][label-pos] and #param[edge][label-side] options to control 127 | /// the position (and #param[edge][label-sep] and #param[edge][label-anchor] 128 | /// for finer control). 129 | /// 130 | /// - label-side (left, right, center): Which side of the edge to place the 131 | /// label on, viewed as you walk along it from base to tip. 132 | /// 133 | /// If `center`, then the label is placed directly on the edge and 134 | /// #param[edge][label-fill] defaults to `true`. When `auto`, a value of 135 | /// `left` or `right` is automatically chosen so that the label is: 136 | /// - roughly above the connector, in the case of straight lines; or 137 | /// - on the outside of the curve, in the case of arcs. 138 | /// 139 | /// - label-pos (number): Position of the label along the connector, from the 140 | /// start to end (from `0` to `1`). 141 | /// 142 | /// #stack( 143 | /// dir: ltr, 144 | /// spacing: 1fr, 145 | /// ..(0, 0.25, 0.5, 0.75, 1).map(p => fletcher.diagram( 146 | /// cell-size: 1cm, 147 | /// edge((0,0), (1,0), p, "->", label-pos: p)) 148 | /// ), 149 | /// ) 150 | /// 151 | /// - label-sep (length): Separation between the connector and the label anchor. 152 | /// 153 | /// With the default anchor (automatically set to `"south"` in this case): 154 | /// 155 | /// #diagram( 156 | /// debug: 2, 157 | /// cell-size: 8mm, 158 | /// { 159 | /// for (i, s) in (-5pt, 0pt, .4em, .8em).enumerate() { 160 | /// edge((2*i,0), (2*i + 1,0), s, "->", label-sep: s) 161 | /// } 162 | /// }) 163 | /// 164 | /// With #param[edge][label-anchor] set to `"center"`: 165 | /// 166 | /// #diagram( 167 | /// debug: 2, 168 | /// cell-size: 8mm, 169 | /// { 170 | /// for (i, s) in (-5pt, 0pt, .4em, .8em).enumerate() { 171 | /// edge((2*i,0), (2*i + 1,0), s, "->", label-sep: s, label-anchor: "center") 172 | /// } 173 | /// }) 174 | /// 175 | /// Set #param[diagram][debug] to `2` or higher to see label anchors and 176 | /// outlines as seen here. 177 | /// 178 | /// Default: #the-param[diagram][label-sep] 179 | /// 180 | /// - label-angle (angle, left, right, top, bottom, auto): Angle to rotate the 181 | /// label (counterclockwise). 182 | /// 183 | /// If a direction is given, the label is rotated so that the edge travels in 184 | /// that direction relative to the label. If `auto`, the best of `right` or 185 | /// `left` is chosen. 186 | /// 187 | /// #for angle in (0deg, 90deg, auto, right, top, left) { 188 | /// diagram(edge((0,1), (2,0), "->", [#angle], label-angle: angle)) 189 | /// } 190 | /// 191 | /// - label-anchor (anchor): The CeTZ-style anchor point of the label to use for 192 | /// placement (e.g., `"north-east"` or `"center"`). If `auto`, the best anchor 193 | /// is chosen based on #param[edge][label-side], #param[edge][label-angle], 194 | /// and the edge's direction. 195 | /// 196 | /// - label-fill (bool, paint): The background fill for the label. If `true`, 197 | /// defaults to the value of #param[edge][crossing-fill]. If `false` or 198 | /// `none`, no fill is used. If `auto`, then defaults to `true` if the label 199 | /// is covering the edge (#param[edge][label-side]`: center`). 200 | /// 201 | /// - label-size (auto, length): The default text size to apply to edge labels. 202 | /// 203 | /// Default: #the-param[diagram][label-size] 204 | /// 205 | /// - label-wrapper (auto, function): Callback function accepting a node 206 | /// dictionary and returning the label content. This is used to add a label 207 | /// background (see #param[edge][crossing-fill]), and can be used to adjust 208 | /// the label's padding, outline, and so on. 209 | /// 210 | /// #example(``` 211 | /// diagram(edge($f$, label-wrapper: e => 212 | /// circle(e.label, fill: e.label-fill))) 213 | /// ```) 214 | /// 215 | /// Default: #the-param[diagram][label-wrapper] 216 | /// 217 | /// - stroke (stroke): Stroke style of the edge. Arrows/marks scale with the 218 | /// stroke thickness (and with #param[edge][mark-scale]). 219 | /// 220 | /// - dash (string): The stroke's dash style. This is also set by some mark 221 | /// styles. For example, setting `marks: "<..>"` applies `dash: "dotted"`. 222 | /// 223 | /// - decorations (none, string, function): Apply a CeTZ path decoration to the 224 | /// stroke. Preset options are `"wave"`, `"zigzag"`, and `"coil"` (which may 225 | /// also be passed as convenience positional arguments), but a decoration 226 | /// function may also be specified. 227 | /// 228 | /// #example(``` 229 | /// diagram( 230 | /// $ 231 | /// A edge("wave") & 232 | /// B edge("zigzag") & 233 | /// C edge("coil") & D \ 234 | /// alpha &&& omega 235 | /// $, 236 | /// edge((0,1), (3,1), "<->", decorations: 237 | /// cetz.decorations.wave 238 | /// .with(amplitude: .4) 239 | /// ) 240 | /// ) 241 | /// ```) 242 | /// 243 | /// - marks (array): The marks (arrowheads) to draw along an edge's stroke. This 244 | /// may be: 245 | /// 246 | /// - A shorthand string such as `"->"` or `"hook'-/->>"`. Specifically, 247 | /// shorthand strings are of the form $M_1 L M_2$ or $M_1 L M_2 L M_3$, etc, 248 | /// where 249 | /// 250 | /// $ M_i in #`fletcher.MARKS` = #context math.mat(..fletcher.MARKS.get().keys().map(i => $#raw(lang: none, i),$).chunks(6), delim: "{") $ 251 | /// is a mark name and 252 | /// $ L in #`fletcher.LINE_ALIASES` = {#fletcher.LINE_ALIASES.keys().map(raw.with(lang: none)).join($,$)} $ 253 | /// is the line style. 254 | /// 255 | /// - An array of marks, where each mark is specified by name of as a _mark 256 | /// object_ (dictionary of parameters with a `draw` entry). 257 | /// 258 | /// Shorthands are expanded into other arguments. For example, 259 | /// `edge(p1, p2, "=>")` is short for `edge(p1, p2, marks: (none, "head"), "double")`, or more precisely, the result of `edge(p1, p2, ..fletcher.interpret-marks-arg("=>"))`. 260 | /// 261 | /// #table( 262 | /// columns: (1fr, 4fr), 263 | /// align: (center + horizon, horizon), 264 | /// [Result], [Value of `marks`], 265 | /// ..( 266 | /// "->", 267 | /// ">>-->", 268 | /// "<=>", 269 | /// "==>", 270 | /// "->>-", 271 | /// "x-/-@", 272 | /// "|..|", 273 | /// "hook->>", 274 | /// "hook'->>", 275 | /// "||-*-harpoon'", 276 | /// ("X", (inherit: "head", size: 15, sharpness: 40deg),), ((inherit: 277 | /// "circle", pos: 0.5, fill: auto),), 278 | /// ).map(arg => ( 279 | /// fletcher.diagram(edge((0,0), (1,0), marks: arg, stroke: 0.8pt)), 280 | /// raw(repr(arg)), 281 | /// )).join() 282 | /// ) 283 | /// 284 | /// - mark-scale (percent): Scale factor for marks or arrowheads, relative to 285 | /// the #param[edge][stroke] thickness. See also #the-param[diagram][mark-scale]. 286 | /// 287 | /// #diagram( 288 | /// label-sep: 10pt, 289 | /// edge-stroke: 1pt, 290 | /// for i in range(3) { 291 | /// let s = (1 + i/2)*100% 292 | /// edge((2*i,0), (2*i + 1,0), label: s, "->", mark-scale: s) 293 | /// } 294 | /// ) 295 | /// 296 | /// Note that the default arrowheads scale automatically with double and 297 | /// triple strokes: 298 | /// 299 | /// #diagram( 300 | /// label-sep: 10pt, 301 | /// edge-stroke: 1pt, 302 | /// for (i, s) in ("->", "=>", "==>").enumerate() { 303 | /// edge((2*i,0), (2*i + 1,0), s, label: raw(s, lang: none)) 304 | /// } 305 | /// ) 306 | /// 307 | /// - extrude (array): Draw a separate stroke for each extrusion offset to 308 | /// obtain a multi-stroke effect. Offsets may be numbers (specifying multiples 309 | /// of the stroke's thickness) or lengths. 310 | /// 311 | /// #diagram({ 312 | /// ( 313 | /// (0,), 314 | /// (-1.5,+1.5), 315 | /// (-2,0,+2), 316 | /// (-.5em,), 317 | /// (0, 5pt,), 318 | /// ).enumerate().map(((i, e)) => { 319 | /// edge( 320 | /// (2*i, 0), (2*i + 1, 0), [#e], "|->", 321 | /// extrude: e, stroke: 1pt, label-sep: 1em) 322 | /// }).join() 323 | /// }) 324 | /// 325 | /// Notice how the ends of the line need to shift a little depending on the 326 | /// mark. This offset is computed with `cap-offset()`. 327 | /// 328 | /// See also #the-param[node][extrude]. 329 | /// 330 | /// - crossing (bool): If `true`, draws a backdrop of color 331 | /// #param[edge][crossing-fill] to give the illusion of lines crossing each 332 | /// other. 333 | /// 334 | /// #diagram({ 335 | /// edge((0,1), (1,0), stroke: 1pt) 336 | /// edge((0,0), (1,1), stroke: 1pt) 337 | /// edge((2,1), (3,0), stroke: 1pt) 338 | /// edge((2,0), (3,1), stroke: 1pt, crossing: true) 339 | /// }) 340 | /// 341 | /// You can also pass `"crossing"` as a positional argument as a shorthand for 342 | /// `crossing: true`. 343 | /// 344 | /// - crossing-thickness (number): Thickness of the "crossing" background stroke 345 | /// (applicable if #param[edge][crossing] is `true`) in multiples of the 346 | /// normal stroke's thickness. 347 | /// 348 | /// #diagram({ 349 | /// (1, 2, 4, 8).enumerate().map(((i, x)) => { 350 | /// edge((2*i, 1), (2*i + 1, 0), stroke: 1pt, label-sep: 1em) 351 | /// edge((2*i, 0), (2*i + 1, 1), raw(str(x)), stroke: 1pt, label-sep: 352 | /// 2pt, label-pos: 0.3, crossing: true, crossing-thickness: x) 353 | /// }).join() 354 | /// }) 355 | /// 356 | /// Default: #the-param[diagram][crossing-thickness] 357 | /// 358 | /// - crossing-fill (paint): Color to use behind connectors or labels to give 359 | /// the illusion of crossing over other objects. 360 | /// 361 | /// #let cross(x, fill) = { 362 | /// edge((2*x + 0,1), (2*x + 1,0), stroke: 1pt) 363 | /// edge((2*x + 0,0), (2*x + 1,1), $f$, stroke: 1pt, crossing: true, crossing-fill: fill, label-fill: true) 364 | /// } 365 | /// #diagram(crossing-thickness: 5, { 366 | /// cross(0, white) 367 | /// cross(1, blue.lighten(50%)) 368 | /// }) 369 | /// 370 | /// Default: #the-param[diagram][crossing-fill] 371 | /// 372 | /// - corner-radius (length, none): Radius of rounded corners for edges with 373 | /// multiple segments. Note that `none` is distinct from `0pt`. 374 | /// 375 | /// #for (i, r) in (none, 0pt, 5pt).enumerate() { 376 | /// if i > 0 { h(1fr) } 377 | /// fletcher.diagram( 378 | /// edge-stroke: 1pt, 379 | /// edge((3*i, 0), "r,t,rd,r", "=>", raw(repr(r)), label-pos: 0.6, corner-radius: r) 380 | /// ) 381 | /// } 382 | /// 383 | /// This length specifies the corner radius for right-angled bends. The actual 384 | /// radius is smaller for acute angles and larger for obtuse angles to balance 385 | /// things visually. (Trust me, it looks naff otherwise!) 386 | /// 387 | /// Default: #the-param[diagram][edge-corner-radius] 388 | /// 389 | /// - shift (length, number, pair): Amount to shift the edge sideways by, 390 | /// perpendicular to its direction. A pair `(from, to)` controls the shifts at 391 | /// each end of the edge independently, and a single shift `s` is short for 392 | /// `(s, s)`. Shifts can absolute lengths (e.g., `5pt`) or coordinate 393 | /// differences (e.g., `0.1`). 394 | /// 395 | /// #diagram( 396 | /// node((0,0), $A$), node((1,0), $B$), 397 | /// edge((0,0), (1,0), "->", `3pt`, shift: 3pt), 398 | /// edge((0,0), (1,0), "->", `-3pt`, shift: -3pt, label-side: right), 399 | /// ) 400 | /// 401 | /// If an edge has many vertices, the shifts only affect the first and last 402 | /// segments of the edge. 403 | /// 404 | /// #example(``` 405 | /// diagram( 406 | /// node-fill: luma(70%), 407 | /// node((0,0), [Hello]), 408 | /// edge("u,r,d", "->"), 409 | /// edge("u,r,d", "-->", shift: 8pt), 410 | /// node((1,0), [World]), 411 | /// ) 412 | /// ```) 413 | /// 414 | /// - snap-to (pair): The nodes the start and end of an edge should snap to. 415 | /// Each node can be a position or node #param[node][name], or `none` to disable 416 | /// snapping. See also #the-param[node][snap]. 417 | /// 418 | /// By default, an edge's first and last #param[edge][vertices] snap to nearby 419 | /// nodes. This option can be used in case automatic snapping fails (if there 420 | /// are many nodes close together, for example.) 421 | /// 422 | /// - layer (number): Layer on which to draw the edge. 423 | /// 424 | /// Objects on a higher `layer` are drawn on top of objects on a lower 425 | /// `layer`. Objects on the same layer are drawn in the order they are passed 426 | /// to `diagram()`. 427 | /// 428 | /// - post (function): Callback function to intercept `cetz` objects before they 429 | /// are drawn to the canvas. 430 | /// 431 | /// This can be used to hide elements without affecting layout (for use with 432 | /// #link("https://github.com/touying-typ/touying")[Touying], for example). 433 | /// The `hide()` function also helps for this purpose. 434 | /// 435 | #let pinit-fletcher-edge( 436 | fletcher, 437 | start, 438 | end: none, 439 | start-dx: 0pt, 440 | start-dy: 0pt, 441 | end-dx: 0pt, 442 | end-dy: 0pt, 443 | width-scale: 100%, 444 | height-scale: 100%, 445 | default-width: 30pt, 446 | default-height: 30pt, 447 | // fletcher.edge arguments 448 | ..args, 449 | vertices: (), 450 | label: none, 451 | label-side: auto, 452 | label-pos: 0.5, 453 | label-sep: auto, 454 | label-angle: 0deg, 455 | label-anchor: auto, 456 | label-fill: auto, 457 | label-size: auto, 458 | label-wrapper: auto, 459 | stroke: auto, 460 | dash: none, 461 | decorations: none, 462 | extrude: (0,), 463 | shift: 0pt, 464 | kind: auto, 465 | bend: 0deg, 466 | corner: none, 467 | corner-radius: auto, 468 | marks: (), 469 | mark-scale: 100%, 470 | crossing: false, 471 | crossing-thickness: auto, 472 | crossing-fill: auto, 473 | snap-to: (auto, auto), 474 | layer: 0, 475 | post: x => x, 476 | ) = { 477 | // fletcher.edge arguments 478 | let fletcher-edge-args = ( 479 | vertices: vertices, 480 | label: label, 481 | label-side: label-side, 482 | label-pos: label-pos, 483 | label-sep: label-sep, 484 | label-angle: label-angle, 485 | label-anchor: label-anchor, 486 | label-fill: label-fill, 487 | label-size: label-size, 488 | label-wrapper: label-wrapper, 489 | stroke: stroke, 490 | dash: dash, 491 | decorations: decorations, 492 | extrude: extrude, 493 | shift: shift, 494 | kind: kind, 495 | bend: bend, 496 | corner: corner, 497 | corner-radius: corner-radius, 498 | marks: marks, 499 | mark-scale: mark-scale, 500 | crossing: crossing, 501 | crossing-thickness: crossing-thickness, 502 | crossing-fill: crossing-fill, 503 | snap-to: snap-to, 504 | layer: layer, 505 | post: post, 506 | ) 507 | pinit( 508 | start, 509 | ..( 510 | if end != none { 511 | (end,) 512 | } else { 513 | () 514 | } 515 | ), 516 | callback: (start-pos, ..other-pos) => { 517 | // calculate width and height 518 | let width = default-width 519 | let height = default-height 520 | let start-x = start-pos.x + start-dx 521 | let start-y = start-pos.y + start-dy 522 | if other-pos.pos().len() > 0 { 523 | let end-pos = other-pos.pos().at(0) 524 | let end-x = end-pos.x + end-dx 525 | let end-y = end-pos.y + end-dy 526 | width = calc.abs(start-x - end-x) 527 | height = calc.abs(start-y - end-y) 528 | width *= width-scale 529 | height *= height-scale 530 | if width == 0pt or width == 0em { 531 | width = default-width 532 | } 533 | if height == 0pt or height == 0em { 534 | height = default-height 535 | } 536 | } 537 | 538 | let origin-id = repr(start) + repr(start-dx) + repr(start-dy) + "-origin" 539 | 540 | // place the diagram directly to get the origin offset 541 | absolute-place( 542 | dx: start-x, 543 | dy: start-y, 544 | hide( 545 | fletcher.diagram( 546 | spacing: (width, height), 547 | node-inset: 0pt, 548 | fletcher.node((0, 0), pin(origin-id)), 549 | fletcher.edge(..args, ..fletcher-edge-args), 550 | ), 551 | ), 552 | ) 553 | 554 | // place the diagram again with the correct offset 555 | pinit( 556 | origin-id, 557 | callback: origin-pos => { 558 | absolute-place( 559 | dx: 2 * start-x - origin-pos.x, 560 | dy: 2 * start-y - origin-pos.y, 561 | fletcher.diagram( 562 | spacing: (width, height), 563 | node-inset: 0pt, 564 | fletcher.node((0, 0), none), 565 | fletcher.edge(..args, ..fletcher-edge-args), 566 | ), 567 | ) 568 | }, 569 | ) 570 | }, 571 | ) 572 | } -------------------------------------------------------------------------------- /simple-arrow.typ: -------------------------------------------------------------------------------- 1 | /// Draw a simple arrow on the page with optional settings, implemented by [`polygon`](https://typst.app/docs/reference/visualize/polygon/). 2 | /// 3 | /// - `fill`: [`color`] — The fill color for the arrow. 4 | /// - `stroke`: [`stroke`] — The stroke for the arrow. 5 | /// - `start`: [`point`] — The starting point of the arrow. 6 | /// - `end`: [`point`] — The ending point of the arrow. 7 | /// - `thickness`: [`length`] — The thickness of the arrow. 8 | /// - `arrow-width`: [`int` or `float`] — The width of the arrowhead relative to thickness. 9 | /// - `arrow-height`: [`int` or `float`] — The height of the arrowhead relative to thickness. 10 | /// - `inset`: [`int` or `float`] — The inset value for the arrowhead relative to thickness. 11 | /// - `tail`: [`array`] — The tail settings for the arrow. 12 | #let simple-arrow( 13 | fill: black, 14 | stroke: 0em, 15 | start: (0em, 0em), 16 | end: (3em, 0em), 17 | thickness: 0.12em, 18 | arrow-width: 4, 19 | arrow-height: 4, 20 | inset: 0.5, 21 | tail: (), 22 | ) = { 23 | let _arrow-width = arrow-width * thickness 24 | let _arrow-height = arrow-height * thickness 25 | let _inset = inset * thickness 26 | let start-x = start.at(0) 27 | let start-y = start.at(1) 28 | let end-x = end.at(0) 29 | let end-y = end.at(1) 30 | let _dx = end-x - start-x 31 | if _dx.em != 0 and (_dx - _dx.em * 1em).pt() != 0 { 32 | panic("The x-coordinate must only use pt units or only em units.") 33 | } 34 | let _dy = end-y - start-y 35 | if _dy.em != 0 and (_dy - _dy.em * 1em).pt() != 0 { 36 | panic("The y-coordinate must only use pt units or only em units.") 37 | } 38 | let _angle = if _dx.em != 0 or _dy.em != 0 { 39 | calc.atan2(_dx.em, _dy.em) 40 | } else { 41 | calc.atan2(_dx.pt(), _dy.pt()) 42 | } 43 | let _ht = 0.5 * thickness 44 | let _hw = 0.5 * _arrow-width 45 | let _c = calc.cos(_angle) 46 | let _s = calc.sin(_angle) 47 | 48 | polygon( 49 | fill: fill, 50 | stroke: stroke, 51 | ..tail, 52 | (start-x - _s * _ht, start-y + _c * _ht), 53 | ( 54 | start-x + _dx - _s * _ht - _c * (_arrow-height - _inset), 55 | start-y + _dy + _c * _ht - _s * (_arrow-height - _inset), 56 | ), 57 | (start-x + _dx - _s * _hw - _c * _arrow-height, start-y + _dy + _c * _hw - _s * _arrow-height), 58 | (end-x, end-y), 59 | (start-x + _dx + _s * _hw - _c * _arrow-height, start-y + _dy - _c * _hw - _s * _arrow-height), 60 | ( 61 | start-x + _dx + _s * _ht - _c * (_arrow-height - _inset), 62 | start-y + _dy - _c * _ht - _s * (_arrow-height - _inset), 63 | ), 64 | (start-x + _s * _ht, start-y - _c * _ht), 65 | ) 66 | } 67 | 68 | /// Draw a double arrow on the page with optional settings, implemented by [`polygon`](https://typst.app/docs/reference/visualize/polygon/). 69 | /// 70 | /// Author: [PaulS](https://github.com/psads-git) 71 | /// 72 | /// - `fill`: [`color`] — The fill color for the arrow. 73 | /// - `stroke`: [`stroke`] — The stroke for the arrow. 74 | /// - `start`: [`point`] — The starting point of the arrow. 75 | /// - `end`: [`point`] — The ending point of the arrow. 76 | /// - `thickness`: [`length`] — The thickness of the arrow. 77 | /// - `arrow-width`: [`int` or `float`] — The width of the arrowhead relative to thickness. 78 | /// - `arrow-height`: [`int` or `float`] — The height of the arrowhead relative to thickness. 79 | /// - `inset`: [`int` or `float`] — The inset value for the arrowhead relative to thickness. 80 | /// - `tail`: [`array`] — The tail settings for the arrow. 81 | #let double-arrow( 82 | fill: black, 83 | stroke: 0em, 84 | start: (0em, 0em), 85 | end: (3em, 0em), 86 | thickness: 0.12em, 87 | arrow-width: 4, 88 | arrow-height: 4, 89 | inset: 0.5, 90 | tail: (), 91 | ) = { 92 | let _arrow-width = arrow-width * thickness 93 | let _arrow-height = arrow-height * thickness 94 | let _inset = inset * thickness 95 | let start-x = start.at(0) 96 | let start-y = start.at(1) 97 | let end-x = end.at(0) 98 | let end-y = end.at(1) 99 | let _dx = end-x - start-x 100 | if _dx.em != 0 and (_dx - _dx.em * 1em).pt() != 0 { 101 | panic("The x-coordinate must only use pt units or only em units.") 102 | } 103 | let _dy = end-y - start-y 104 | if _dy.em != 0 and (_dy - _dy.em * 1em).pt() != 0 { 105 | panic("The y-coordinate must only use pt units or only em units.") 106 | } 107 | let _angle = if _dx.em != 0 or _dy.em != 0 { 108 | calc.atan2(_dx.em, _dy.em) 109 | } else { 110 | calc.atan2(_dx.pt(), _dy.pt()) 111 | } 112 | let _ht = 0.5 * thickness 113 | let _hw = 0.5 * _arrow-width 114 | let _c = calc.cos(_angle) 115 | let _s = calc.sin(_angle) 116 | 117 | polygon( 118 | fill: fill, 119 | stroke: stroke, 120 | ..tail, 121 | // First arrowhead points 122 | (start-x + _s * _ht + _c * (_arrow-height - _inset), start-y - _c * _ht + _s * (_arrow-height - _inset)), 123 | (start-x + _s * _hw + _c * _arrow-height, start-y - _c * _hw + _s * _arrow-height), 124 | (start-x, start-y), 125 | (start-x - _s * _hw + _c * _arrow-height, start-y + _c * _hw + _s * _arrow-height), 126 | (start-x - _s * _ht + _c * (_arrow-height - _inset), start-y + _c * _ht + _s * (_arrow-height - _inset)), 127 | // Shaft points 128 | (start-x + _dx - _s * _ht - _c * (_arrow-height - _inset), start-y + _dy + _c * _ht - _s * (_arrow-height - _inset)), 129 | // Second arrowhead points 130 | (start-x + _dx - _s * _hw - _c * _arrow-height, start-y + _dy + _c * _hw - _s * _arrow-height), 131 | (end-x, end-y), 132 | (start-x + _dx + _s * _hw - _c * _arrow-height, start-y + _dy - _c * _hw - _s * _arrow-height), 133 | (start-x + _dx + _s * _ht - _c * (_arrow-height - _inset), start-y + _dy - _c * _ht - _s * (_arrow-height - _inset)), 134 | // Close the polygon back to the shaft 135 | (start-x + _s * _ht, start-y - _c * _ht), 136 | ) 137 | } -------------------------------------------------------------------------------- /typst.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pinit" 3 | version = "0.2.2" 4 | authors = ["OrangeX4"] 5 | entrypoint = "lib.typ" 6 | exclude = ["./examples"] 7 | keywords = ["layout", "positioning", "pinit", "pin", "slide", "presentation", "arrow", "draw"] 8 | license = "MIT" 9 | repository = "https://github.com/OrangeX4/typst-pinit" 10 | description = "Relative positioning by pins, especially useful for making slides in typst." 11 | categories = ["layout", "utility"] --------------------------------------------------------------------------------