├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── ci.yaml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── README.src.md ├── docs ├── gallery │ ├── 01-commutative.svg │ ├── 01-commutative.typ │ ├── 02-algebra-cube.svg │ ├── 02-algebra-cube.typ │ ├── 03-ml-architecture.svg │ ├── 03-ml-architecture.typ │ ├── 04-io-flowchart.svg │ ├── 04-io-flowchart.typ │ ├── 05-digraph.svg │ ├── 05-digraph.typ │ ├── 06-node-groups.svg │ ├── 06-node-groups.typ │ ├── 07-uml-diagram.svg │ ├── 07-uml-diagram.typ │ ├── 08-tree.svg │ ├── 08-tree.typ │ ├── 09-feynman-diagram.svg │ ├── 09-feynman-diagram.typ │ ├── 10-category-theory.svg │ └── 10-category-theory.typ ├── manual.pdf ├── manual.typ ├── readme-examples │ ├── 1-first-isomorphism-theorem-dark.svg │ ├── 1-first-isomorphism-theorem-light.svg │ ├── 1-first-isomorphism-theorem.typ │ ├── 2-flowchart-trap-dark.svg │ ├── 2-flowchart-trap-light.svg │ ├── 2-flowchart-trap.typ │ ├── 3-state-machine-dark.svg │ ├── 3-state-machine-light.svg │ ├── 3-state-machine.typ │ ├── 4-feynman-diagram-dark.svg │ ├── 4-feynman-diagram-light.svg │ └── 4-feynman-diagram.typ └── style.typ ├── package-excludes ├── pixi.lock ├── pixi.toml ├── scripts ├── check.nu ├── compile.nu ├── install.nu ├── readme.nu └── typos.toml ├── src ├── coords.typ ├── default-marks.typ ├── deps.typ ├── diagram.typ ├── draw.typ ├── edge.typ ├── exports.typ ├── marks.typ ├── node.typ ├── shapes.typ └── utils.typ ├── tests ├── .gitignore ├── anchors │ ├── ref │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ └── 6.png │ └── test.typ ├── cetz-integration │ ├── ref │ │ └── 1.png │ └── test.typ ├── coords │ ├── ref │ │ └── 1.png │ └── test.typ ├── debug │ ├── ref │ │ └── 1.png │ └── test.typ ├── diagram │ ├── axes │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ ├── 4.png │ │ │ ├── 5.png │ │ │ ├── 6.png │ │ │ ├── 7.png │ │ │ ├── 8.png │ │ │ └── 9.png │ │ └── test.typ │ ├── bounding-box │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ └── 4.png │ │ └── test.typ │ ├── cetz-coords │ │ ├── ref │ │ │ ├── 1.png │ │ │ └── 2.png │ │ └── test.typ │ ├── implicit-coords │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ └── 3.png │ │ └── test.typ │ ├── inline │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── math-mode │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ └── 4.png │ │ └── test.typ │ └── options │ │ ├── ref │ │ └── 1.png │ │ └── test.typ ├── edge │ ├── arguments │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── bend-marks │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── bend │ │ ├── ref │ │ │ ├── 1.png │ │ │ └── 2.png │ │ └── test.typ │ ├── calculations │ │ └── test.typ │ ├── corner │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── crossing │ │ ├── ref │ │ │ ├── 1.png │ │ │ └── 2.png │ │ └── test.typ │ ├── decorations │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── loops │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── mark-shorthands │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── options │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ └── 3.png │ │ └── test.typ │ ├── poly │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ └── 4.png │ │ └── test.typ │ ├── shift │ │ ├── ref │ │ │ ├── 1.png │ │ │ └── 2.png │ │ └── test.typ │ └── snap-to │ │ ├── ref │ │ ├── 1.png │ │ ├── 2.png │ │ └── 3.png │ │ └── test.typ ├── gallery │ ├── ref │ │ ├── 1.png │ │ ├── 10.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png │ └── test.typ ├── hiding │ ├── ref │ │ └── 1.png │ └── test.typ ├── issues │ ├── ref │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png │ └── test.typ ├── label │ ├── angle │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ └── 3.png │ │ └── test.typ │ ├── fill │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── pos-with-segment │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 10.png │ │ │ ├── 11.png │ │ │ ├── 12.png │ │ │ ├── 13.png │ │ │ ├── 14.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ ├── 4.png │ │ │ ├── 5.png │ │ │ ├── 6.png │ │ │ ├── 7.png │ │ │ ├── 8.png │ │ │ └── 9.png │ │ └── test.typ │ ├── pos │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ ├── 4.png │ │ │ ├── 5.png │ │ │ └── 6.png │ │ └── test.typ │ ├── side-auto │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── side │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── size │ │ ├── ref │ │ │ ├── 1.png │ │ │ └── 2.png │ │ └── test.typ │ └── wrapper │ │ ├── ref │ │ └── 1.png │ │ └── test.typ ├── mark │ ├── cap-offsets │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── debug │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 10.png │ │ │ ├── 11.png │ │ │ ├── 12.png │ │ │ ├── 13.png │ │ │ ├── 14.png │ │ │ ├── 15.png │ │ │ ├── 16.png │ │ │ ├── 17.png │ │ │ ├── 18.png │ │ │ ├── 19.png │ │ │ ├── 2.png │ │ │ ├── 20.png │ │ │ ├── 21.png │ │ │ ├── 22.png │ │ │ ├── 23.png │ │ │ ├── 24.png │ │ │ ├── 25.png │ │ │ ├── 26.png │ │ │ ├── 27.png │ │ │ ├── 28.png │ │ │ ├── 29.png │ │ │ ├── 3.png │ │ │ ├── 30.png │ │ │ ├── 31.png │ │ │ ├── 32.png │ │ │ ├── 33.png │ │ │ ├── 34.png │ │ │ ├── 35.png │ │ │ ├── 36.png │ │ │ ├── 37.png │ │ │ ├── 38.png │ │ │ ├── 39.png │ │ │ ├── 4.png │ │ │ ├── 40.png │ │ │ ├── 41.png │ │ │ ├── 42.png │ │ │ ├── 43.png │ │ │ ├── 44.png │ │ │ ├── 45.png │ │ │ ├── 46.png │ │ │ ├── 47.png │ │ │ ├── 48.png │ │ │ ├── 49.png │ │ │ ├── 5.png │ │ │ ├── 50.png │ │ │ ├── 51.png │ │ │ ├── 52.png │ │ │ ├── 6.png │ │ │ ├── 7.png │ │ │ ├── 8.png │ │ │ └── 9.png │ │ └── test.typ │ ├── gallery │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── math-matching │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── math-mode │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── scale │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ └── 3.png │ │ └── test.typ │ ├── state │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ └── stealth │ │ ├── ref │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ └── 7.png │ │ └── test.typ ├── node │ ├── defocus │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── enclose │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ ├── 4.png │ │ │ ├── 5.png │ │ │ ├── 6.png │ │ │ ├── 7.png │ │ │ ├── 8.png │ │ │ └── 9.png │ │ └── test.typ │ ├── extrude │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── label-align │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ └── 4.png │ │ └── test.typ │ ├── layer │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── name │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── shapes │ │ ├── ref │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ ├── 4.png │ │ │ ├── 5.png │ │ │ ├── 6.png │ │ │ ├── 7.png │ │ │ ├── 8.png │ │ │ └── 9.png │ │ └── test.typ │ └── size-inset-outset │ │ ├── ref │ │ ├── 1.png │ │ ├── 2.png │ │ └── 3.png │ │ └── test.typ ├── readme-examples │ ├── ref │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ └── 8.png │ └── test.typ ├── template.typ └── utils │ ├── ref │ └── 1.png │ └── test.typ └── typst.toml /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Minimal working example: 15 | 16 | ```typ 17 | #import "@preview/fletcher:x.y.z" as fletcher: diagram, node, edge 18 | 19 | #diagram() 20 | ``` 21 | 22 | **Expected behavior** 23 | A clear and concise description of what you expected to happen. 24 | 25 | **Version information** 26 | - Typst compiler version 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What kind of diagram are you wanting to make?** 11 | Use screenshots or working Typst code if you can. 12 | 13 | **Idea or feature request** 14 | Describe your idea or feature request and how it might work. 15 | 16 | **Alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | branches: [ main ] 7 | 8 | jobs: 9 | tests: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | 15 | - name: Probe runner package cache 16 | uses: awalsh128/cache-apt-pkgs-action@latest 17 | with: 18 | packages: cargo 19 | version: 1.0 20 | 21 | - name: Install tytanic from crates.io 22 | uses: baptiste0928/cargo-install@v3.0.0 23 | with: 24 | crate: tytanic 25 | version: '0.2.1' 26 | 27 | - name: Run test suite 28 | run: tt run 29 | 30 | - name: Archive artifacts 31 | uses: actions/upload-artifact@v4 32 | if: always() 33 | with: 34 | name: artifacts 35 | path: | 36 | tests/**/diff/*.png 37 | tests/**/out/*.png 38 | tests/**/ref/*.png 39 | retention-days: 5 40 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to fletcher 2 | 3 | Thank you for your interest in contributing to fletcher! This guide explains how to install Pixi, run Tytanic tests, update the README, build the manual, and edit docstrings. 4 | 5 | ## Installing Pixi 6 | 7 | Fletcher uses [Pixi](https://pixi.sh/) to run project commands and reproducibly install development tools such as [Tytanic](https://github.com/tytanic/tytanic) for testing, [Nushell](https://www.nushell.sh) for scripting, [Typos](https://crates.io/crates/typos) and also Typst itself. 8 | 9 | To install Pixi, run: 10 | 11 | ```sh 12 | curl -fsSL https://pixi.sh/install.sh | bash 13 | ``` 14 | 15 | After installation, you should have access to all the development tools via `pixi run ` or by activating the development environment with 16 | 17 | ```sh 18 | pixi shell 19 | ``` 20 | 21 | which makes the tools available directly from the command line. 22 | 23 | 24 | ## Running Tytanic Tests 25 | 26 | Fletcher uses [Tytanic](https://github.com/tytanic/tytanic) for testing. To run all the tests: 27 | 28 | ```sh 29 | pixi run test 30 | ``` 31 | 32 | Alternatively, enter the development environment (`pixi shell`) and use `tt` (if Tytanic is not already on your system). 33 | 34 | Using Tytanic (see the [official docs](https://tingerrr.github.io/tytanic/)): 35 | 36 | - `tt run ` to run all tests 37 | - `tt update ` to update test reference images 38 | - Test sets can be specified like so: 39 | - All tests, e.g., `tt run`. 40 | - A single test, e.g., `tt run node/shapes` 41 | - Tests matching glob pattern, e.g., `tt run -e g:node/*` 42 | 43 | ## Updating the README 44 | 45 | The main `README.md` file is generated from a template `README.src.md`. 46 | To update the README, edit the template file and run 47 | ```sh 48 | pixi run readme 49 | ``` 50 | to generate the main file. Don't edit `README.md` directly. 51 | 52 | The template is automatically populated with two kinds of example images: 53 | 54 | 1. `docs/readme-examples/`: Short examples with light/dark modes displayed alongside the source code. 55 | To compile these to light and dark SVGs, use `pixi run compile examples [pattern]`. 56 | They are _not_ standalone Typst documents; more like snippets with a special syntax for switching between light/dark themes (see examples). 57 | 2. `docs/gallery/`: Complex examples displayed in a table of images linking to source files. 58 | These should be standalone documents. 59 | Make sure they import the correct fletcher version! 60 | Compile these with `pixi compile gallery [pattern]`. 61 | 62 | The optional `[pattern]` argument is a substring to filter filenames by. 63 | 64 | 65 | ## Building the Manual 66 | 67 | To build the manual PDF (takes a while) and exit: 68 | 69 | ```sh 70 | pixi run compile manual 71 | ``` 72 | 73 | You can also watch for changes and continuously edit with: 74 | 75 | ```sh 76 | pixi run manual 77 | ``` 78 | 79 | ## Editing Docstrings 80 | 81 | The manual uses [Tidy](https://typst.app/universe/package/tidy/) to parse docstrings and populate the manual. 82 | 83 | To create a clickable reference to a documented argument of a function, use `#param[fn][arg]` (which renders as `` `arg` ``) or `#the-param[fn][arg]` (which renders as ``the `arg` option of `fn` ``). 84 | 85 | ## Additional Notes 86 | 87 | - Please ensure all tests pass before submitting a pull request. 88 | - Thank you! 🎈 89 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Joseph Wilson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.src.md: -------------------------------------------------------------------------------- 1 | ![Version](https://img.shields.io/badge/version-{VERSION}-green) 2 | [![Typst Universe](https://img.shields.io/badge/Typst-Universe-239dad)](https://typst.app/universe/package/fletcher/) 3 | [![Repo](https://img.shields.io/badge/GitHub-repo-444)](https://github.com/Jollywatt/typst-fletcher) 4 | [![Development version](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fgithub.com%2FJollywatt%2Ftypst-fletcher%2Fraw%2Fmain%2Ftypst.toml&query=package.version&label=main&color=444)](https://github.com/Jollywatt/typst-fletcher/tree/main) 5 | 6 | # fletcher 7 | 8 | _**fletcher** (noun) a maker of arrows_ 9 | 10 | A [Typst](https://typst.app/) package for drawing diagrams with arrows, 11 | built on top of [CeTZ](https://github.com/johannes-wolf/cetz). 12 | 13 | See the [manual](docs/manual.pdf?raw=true) for documentation or ask the community for help. 14 | 15 | [![Manual](https://img.shields.io/badge/docs-manual.pdf-orange)](docs/manual.pdf?raw=true) 16 | [![Ask on Discord](https://img.shields.io/badge/ask-on%20Typst%20forum-239dad 17 | )](https://forum.typst.app) 18 | [![Ask on Discord](https://img.shields.io/badge/ask-on%20Discord-2a4d7e 19 | )](https://discord.com/channels/1054443721975922748/1260973351900414102) 20 | 21 | 22 | ```typ 23 | #import "@preview/fletcher:{VERSION}" as fletcher: diagram, node, edge 24 | ``` 25 | 26 | {README_EXAMPLES} 27 | 28 | 29 | 30 | # More examples 31 | Pull requests are most welcome! 32 | 33 | {GALLERY} 34 | 35 | 36 | 37 | ## Change log 38 | 39 | ### 0.5.8 40 | 41 | - Added bracket and parenthesis mark types, with shorthands `"["`, `"]"`, `"("`, `")"`, `"bracket"`, `"parenthesis"` (#102). 42 | - Added `cylinder`, `brace`, `bracket`, `paren` and `stretched-glyph` node shapes (#99, #109). 43 | - Allow specifying label positions as `(segment, position)` (#107, #112, [@deffi](https://github.com/deffi)). 44 | - Fix bugs with anchors of absolutely-positioned enclose nodes (#95, #113). 45 | - Fix some instabilities with edges related to division by zero (#100, #105). 46 | 47 | ### 0.5.7 48 | 49 | - Update `cetz` dependency to `0.3.4` fixing bugs with `typst` version `0.13.1` (#89, #90, #91). 50 | 51 | ### 0.5.6 52 | 53 | - **Require `typst` version `>=0.13.0`.** 54 | - Update `cetz` dependency to `0.3.3`. 55 | - Support CeTZ anchors on nodes which dynamically enclose other nodes (#81). 56 | - Allow nested enclosing of nodes (#45). 57 | - Add `floating` option to `edge()` to make certain objects not affect the diagram's bounds (#38). 58 | - Make `debug` annotations "floating" so they don't affect diagram size and layout. 59 | 60 | ### 0.5.5 61 | 62 | - Update `cetz` dependency to `0.3.2`. 63 | - Fix deprecated type comparisons (#75). 64 | - Add space ` ` line style for empty stroke (#70). 65 | - Fix bug with `label-side` for corner edges (#74). 66 | 67 | ### 0.5.4 68 | 69 | - Allow relative lengths for the `label-pos` option of `edge()` (#61). 70 | - Fix layout bugs for diagrams with non-default `axes` options (#62, #66). 71 | - Fix a line breaking issue with justified text in nodes (#64). 72 | 73 | ### 0.5.3 74 | 75 | - Support CeTZ anchors in edge coordinates, e.g., `edge(, ..)`. 76 | - Fix crash when `stroke: none` set on polyline edges (#60, [@SillyFreak](https://github.com/SillyFreak!)). 77 | - Fix bug with crossing edges in math mode (#54). 78 | - Fix layout bugs for diagrams with non-default `axes` options (#62, #66). 79 | 80 | ### 0.5.2 81 | 82 | - **Require `typst` version `>=0.12.0`.** 83 | - Update `cetz` dependency to `0.3.1`. **Note:** This may slightly change edge label positions. 84 | - Add `loop-angle` option to `edge()` (#36). 85 | 86 | ### 0.5.1 87 | 88 | - Fix nodes which `enclose` absolute coordinates. 89 | - Allow CeTZ-style coordinate expressions in node `enclose` option. 90 | - Fix crash with polar coordinates. 91 | - Fix edges which bend at 0deg or 180deg (e.g., `edge("r,r")` or `edge("r,l")`) and enhance the way the corner radius adapts to the bend angle. **Note:** This may change diagram layout from previous versions. 92 | - Improve error messages. 93 | - Add marks for crow's foot notation: `n` (many), `n?` (zero or more), `n!` (one or more), `1` (one), `1?` (zero or one), `1!` (exactly one). 94 | - Add `node-shape` option to `diagram()`. 95 | 96 | ### 0.5.0 97 | 98 | - Greatly enhance coordinate system. 99 | - Support CeTZ-style coordinate expressions (relative, polar, interpolating, named coordinates, etc). 100 | - Absolute coordinates (physical lengths) can be used alongside "elastic" coordinates (row/column positions). 101 | - Add `label-angle` option to `edge()`. 102 | - Add `label-wrapper` option to allow changing the label inset, outline stroke, and so on (#26). 103 | - Add `label-size` option to control default edge label text size (#35) 104 | - Add `trapezium` node shape. 105 | - Disallow string labels to be passed as positional arguments to `edge()` (to eliminate ambiguity). Used named argument or pass content instead. 106 | 107 | ### 0.4.5 108 | 109 | - Add isosceles triangle node shape (#31). 110 | - Add `fit` and `dir` options to various node shapes to adjust sizing and orientation. 111 | - Allow more than one consecutive edge to have an implicit end vertex. 112 | - Allow `snap-to` to be `none` to disable edge snapping (#32). 113 | 114 | ### 0.4.4 115 | 116 | - Support fully customisable marks/arrowheads! 117 | - Added new mark styles and tweaked some existing defaults. **Note.** This may change the micro-typography of diagrams from previous versions. 118 | - Add node groups via the `enclose` option of `node()`. 119 | - Node labels can be aligned inside the node with `align()`. 120 | - Node labels wrap naturally when label text is wider than the node. **Note:** This may change diagram layout from previous versions. 121 | - Add a `layer` option to nodes and edges to control drawing order. 122 | - Add node shapes: `ellipse`, `octagon`. 123 | 124 | ### 0.4.3 125 | 126 | - Fixed edge crossing backgrounds being drawn above nodes (#14). 127 | - Add `fletcher.hide()` to hide elements with/without affecting layout, useful for incremental diagrams in slides (#15). 128 | - Support `shift`ing edges by coordinate deltas as well as absolute lengths (#13). 129 | - Support node names (#8). 130 | 131 | ### 0.4.2 132 | 133 | - Improve edge-to-node snapping. Edges can terminate anywhere near a node (not just at its center) and will automatically snap to the node outline. Added `snap-to` option to `edge()`. 134 | - Fix node `inset` being half the amount specified. If upgrading from previous version, you will need to divide node `inset` values by two to preserve diagram layout. 135 | - Add `decorations` option to `edge()` for CeTZ path decorations (`"wave"`, `"zigzag"`, and `"coil"`, also accepted as positional string arguments). 136 | 137 | ### 0.4.1 138 | 139 | - Support custom node shapes! Edges connect to node outlines automatically. 140 | - New `shapes` submodule, containing `diamond`, `pill`, `parallelogram`, `hexagon`, and other node shapes. 141 | - Allow edges to have multiple segments. 142 | - Add `vertices` an `corner-radius` options to `edge()`. 143 | - Relative coordinate shorthands may be comma separated to signify multiple segments, e.g., `"r,u,ll"`. 144 | - Add `dodge` option to `edge()` to adjust end points. 145 | - Support `cetz:0.2.0`. 146 | 147 | ### 0.4.0 148 | 149 | - Add ability to specify diagrams in math-mode, using `&` to separate nodes. 150 | - Allow implicit and relative edge coordinates, e.g., `edge("d")` becomes `edge(prev-node, (0, 1))`. 151 | - Add ability to place marks anywhere along an edge. Shorthands now accept an optional middle mark, for example `|->-|` and `hook-/->>`. 152 | - Add “hanging tail” correction to marks on curved edges. Marks now rotate a bit to fit more comfortably along tightly curving arcs. 153 | - Add more arrowheads for the sake of it: `}>`, `<{`, `/`, `\`, `x`, `X`, `*` (solid dot), `@` (solid circle). 154 | - Add `axes` option to `diagram()` to control the direction of each axis in the diagram's coordinate system. 155 | - Add `width`, `height` and `radius` options to `node()` for explicit control over size. 156 | - Add `corner-radius` option to `node()`. 157 | - Add `stroke` option to `edge()` replacing `thickness` and `paint` options. 158 | - Add `edge-stroke` option to `diagram()` replacing `edge-thickness`. 159 | 160 | ### 0.3.0 161 | 162 | - Make round-style arrow heads better approximate the default math font. 163 | - Add solid arrow heads with shorthand `<|-`, `-|>` and double-bar `||-`, `-||`. 164 | - Add an `extrude` option to `node()` which duplicates and extrudes the node's stroke, enabling double stroke effects. 165 | 166 | ### 0.2.0 167 | 168 | - Experimental support for customising arrowheads. 169 | - Add right-angled edges with `edge(..., corner: left/right)`. 170 | 171 | ## Star History 172 | 173 | 174 | 175 | 176 | 177 | Star History Chart 178 | 179 | 180 | -------------------------------------------------------------------------------- /docs/gallery/01-commutative.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/fletcher:0.5.9" as fletcher: diagram, node, edge 2 | #set page(width: auto, height: auto, margin: 5mm, fill: white) 3 | 4 | #diagram( 5 | spacing: (1em, 3em), 6 | $ 7 | & tau^* (bold(A B)^n R slash.double R^times) edge(->) & bold(B)^n R slash.double R^times \ 8 | X edge("ur", "-->") edge("=") & X edge(->, tau) edge("u", <-) & bold(B) R^times edge("u", <-) 9 | $, 10 | edge((2,1), "d,ll,u", "->>", text(blue, $Gamma^*_R$), stroke: blue, label-side: center) 11 | ) -------------------------------------------------------------------------------- /docs/gallery/02-algebra-cube.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/fletcher:0.5.9" as fletcher: diagram, node, edge 2 | #set page(width: auto, height: auto, margin: 5mm, fill: white) 3 | 4 | #diagram( 5 | node-defocus: 0, 6 | spacing: (1cm, 2cm), 7 | edge-stroke: 1pt, 8 | crossing-thickness: 5, 9 | mark-scale: 70%, 10 | node-fill: luma(97%), 11 | node-outset: 3pt, 12 | node((0,0), "magma"), 13 | 14 | node((-1,1), "semigroup"), 15 | node(( 0,1), "unital magma"), 16 | node((+1,1), "quasigroup"), 17 | 18 | node((-1,2), "monoid"), 19 | node(( 0,2), "inverse semigroup"), 20 | node((+1,2), "loop"), 21 | 22 | node(( 0,3), "group"), 23 | 24 | { 25 | let quad(a, b, label, paint, ..args) = { 26 | paint = paint.darken(25%) 27 | edge(a, b, text(paint, label), "-|>", stroke: paint, label-side: center, ..args) 28 | } 29 | 30 | quad((0,0), (-1,1), "Assoc", blue) 31 | quad((0,1), (-1,2), "Assoc", blue, label-pos: 0.3) 32 | quad((1,2), (0,3), "Assoc", blue) 33 | 34 | quad((0,0), (0,1), "Id", red) 35 | quad((-1,1), (-1,2), "Id", red, label-pos: 0.3) 36 | quad((+1,1), (+1,2), "Id", red, label-pos: 0.3) 37 | quad((0,2), (0,3), "Id", red) 38 | 39 | quad((0,0), (1,1), "Div", yellow) 40 | quad((-1,1), (0,2), "Div", yellow, label-pos: 0.3, "crossing") 41 | 42 | quad((-1,2), (0,3), "Inv", green) 43 | quad((0,1), (+1,2), "Inv", green, label-pos: 0.3) 44 | 45 | quad((1,1), (0,2), "Assoc", blue, label-pos: 0.3, "crossing") 46 | }, 47 | ) 48 | -------------------------------------------------------------------------------- /docs/gallery/03-ml-architecture.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/fletcher:0.5.9" as fletcher: diagram, node, edge 2 | #import fletcher.shapes: house, hexagon 3 | #set page(width: auto, height: auto, margin: 5mm, fill: white) 4 | #set text(font: "New Computer Modern") 5 | 6 | #let blob(pos, label, tint: white, ..args) = node( 7 | pos, align(center, label), 8 | width: 28mm, 9 | fill: tint.lighten(60%), 10 | stroke: 1pt + tint.darken(20%), 11 | corner-radius: 5pt, 12 | ..args, 13 | ) 14 | 15 | #diagram( 16 | spacing: 8pt, 17 | cell-size: (8mm, 10mm), 18 | edge-stroke: 1pt, 19 | edge-corner-radius: 5pt, 20 | mark-scale: 70%, 21 | 22 | blob((0,1), [Add & Norm], tint: yellow, shape: hexagon), 23 | edge(), 24 | blob((0,2), [Multi-Head\ Attention], tint: orange), 25 | blob((0,4), [Input], shape: house.with(angle: 30deg), 26 | width: auto, tint: red), 27 | 28 | for x in (-.3, -.1, +.1, +.3) { 29 | edge((0,2.8), (x,2.8), (x,2), "-|>") 30 | }, 31 | edge((0,2.8), (0,4)), 32 | 33 | edge((0,3), "l,uu,r", "--|>"), 34 | edge((0,1), (0, 0.35), "r", (1,3), "r,u", "-|>"), 35 | edge((1,2), "d,rr,uu,l", "--|>"), 36 | 37 | blob((2,0), [Softmax], tint: green), 38 | edge("<|-"), 39 | blob((2,1), [Add & Norm], tint: yellow, shape: hexagon), 40 | edge(), 41 | blob((2,2), [Feed\ Forward], tint: blue), 42 | ) 43 | -------------------------------------------------------------------------------- /docs/gallery/04-io-flowchart.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/fletcher:0.5.9" as fletcher: diagram, node, edge 2 | #set page(width: auto, height: auto, margin: 5mm, fill: white) 3 | #set text(white, font: "New Computer Modern") 4 | #let colors = (maroon, olive, eastern) 5 | 6 | #diagram( 7 | edge-stroke: 1pt, 8 | node-corner-radius: 5pt, 9 | edge-corner-radius: 8pt, 10 | mark-scale: 80%, 11 | 12 | node((0,0), [input], fill: colors.at(0)), 13 | node((2,+1), [memory unit (MU)], fill: colors.at(1)), 14 | node((2, 0), align(center)[arithmetic & logic \ unit (ALU)], fill: colors.at(1)), 15 | node((2,-1), [control unit (CU)], fill: colors.at(1)), 16 | node((4,0), [output], fill: colors.at(2), shape: fletcher.shapes.hexagon), 17 | 18 | edge((0,0), "r,u,r", "-}>"), 19 | edge((2,-1), "r,d,r", "-}>"), 20 | edge((2,-1), "r,dd,l", "--}>"), 21 | edge((2,1), "l", (1,-.5), marks: ((inherit: "}>", pos: 0.65, rev: false),)), 22 | 23 | for i in range(-1, 2) { 24 | edge((2,0), (2,1), "<{-}>", shift: i*5mm, bend: i*20deg) 25 | }, 26 | 27 | edge((2,-1), (2,0), "<{-}>"), 28 | ) -------------------------------------------------------------------------------- /docs/gallery/05-digraph.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/fletcher:0.5.9" as fletcher: diagram, node, edge, shapes 2 | #set page(width: auto, height: auto, margin: 5mm, fill: white) 3 | 4 | #let nodes = ("A", "B", "C", "D", "E", "F", "G") 5 | #let edges = ( 6 | (3, 2), 7 | (4, 1), 8 | (1, 4), 9 | (0, 4), 10 | (3, 0), 11 | (5, 6), 12 | (6, 5), 13 | ) 14 | 15 | #diagram({ 16 | for (i, n) in nodes.enumerate() { 17 | let θ = 90deg - i*360deg/nodes.len() 18 | node((θ, 18mm), n, stroke: 0.5pt, name: str(i)) 19 | } 20 | for (from, to) in edges { 21 | let bend = if (to, from) in edges { 10deg } else { 0deg } 22 | // refer to nodes by label, e.g., <1> 23 | edge(label(str(from)), label(str(to)), "-|>", bend: bend) 24 | } 25 | }) -------------------------------------------------------------------------------- /docs/gallery/06-node-groups.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/fletcher:0.5.9" as fletcher: diagram, node, edge 2 | #set page(width: auto, height: auto, margin: 5mm, fill: white) 3 | 4 | 5 | #diagram( 6 | node-corner-radius: 4pt, 7 | node((0,0), $S a$), 8 | node((1,0), $T b$), 9 | node((0,1), $S a'$), 10 | node((1,1), $T b'$), 11 | edge((0,0), (1,0), "->", $f$), 12 | edge((0,1), (1,1), "->", $f'$), 13 | edge((0,0), (0,1), "->", $alpha$), 14 | edge((1,0), (1,1), "->", $beta$), 15 | 16 | node((2,0), $(a, b, f)$), 17 | edge("->", text(0.8em, $(alpha, beta)$)), 18 | node((2,1), $(a', b', f')$), 19 | 20 | node((0,2), $S a$), 21 | edge("->", $f$), 22 | node((1,2), $T b$), 23 | 24 | node((2,2), $(a, b, f)$), 25 | 26 | { 27 | let tint(c) = (stroke: c, fill: rgb(..c.components().slice(0,3), 5%), inset: 8pt) 28 | node(enclose: ((0,0), (1,1)), ..tint(teal), name: ) 29 | node(enclose: ((2,0), (2,1)), ..tint(teal), name: ) 30 | node(enclose: ((0,2), (1,2)), ..tint(green), name: ) 31 | node(enclose: ((2,2),), ..tint(green), name: ) 32 | }, 33 | 34 | edge(, , "<==>", stroke: teal + .75pt), 35 | edge(, , "<==>", stroke: green + .75pt), 36 | edge(, , "<=>", stroke: .75pt), 37 | edge(, , "<=>", stroke: .75pt), 38 | ) -------------------------------------------------------------------------------- /docs/gallery/07-uml-diagram.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/fletcher:0.5.9" as fletcher: diagram, node, edge 2 | #set page(width: auto, height: auto, margin: 5mm, fill: white) 3 | 4 | #diagram( 5 | spacing: (18mm, 10mm), 6 | node-stroke: luma(80%), 7 | node((0.5,0), [*Diagram*], name: ), 8 | node((0,1), [*Node*], name: ), 9 | node((1,1), [*Edge*], name: ), 10 | 11 | edge(, ((), "|-", (0,0.5)), ((), "-|", ), , "1!-n!"), 12 | edge(, ((), "|-", (0,0.5)), ((), "-|", ), , "1!-n?"), 13 | 14 | 15 | edge("1!-n?"), 16 | 17 | node((1,2), [*Mark*], name: ), 18 | 19 | edge(, "-|>", , stroke: teal, label: text(teal)[snap], left), 20 | 21 | edge((rel: (-15pt, 0pt), to: ), , "-|>", bend: 40deg, stroke: orange, text(orange)[layout], label-angle: auto) 22 | ) -------------------------------------------------------------------------------- /docs/gallery/08-tree.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/fletcher:0.5.9" as fletcher: diagram, node, edge 2 | #set page(width: auto, height: auto, margin: 5mm, fill: white) 3 | 4 | #let bent-edge(from, to, ..args) = { 5 | let midpoint = (from, 50%, to) 6 | let vertices = ( 7 | from, 8 | (from, "|-", midpoint), 9 | (midpoint, "-|", to), 10 | to, 11 | ) 12 | edge(..vertices, "-|>", ..args) 13 | } 14 | 15 | #diagram( 16 | node-stroke: luma(80%), 17 | edge-corner-radius: none, 18 | spacing: (10pt, 20pt), 19 | 20 | // Nodes 21 | node((1.5,0), [*Entrate\ pubblicitarie*], name: ), 22 | node((0.5,1), [*Numero di\ impression*], name: ), 23 | node((2.5,1), [*Guadagno medio\ per impression*], name: ), 24 | 25 | node((0,2), [*Traffico\ sul sito*], name: ), 26 | node((1,2), [*Impression/\ visitatori*], name: ), 27 | 28 | node((2,2), [*Modalità\ di vendita*], name: ), 29 | node((3,2), [*Tipo di\ posizionamento*], name: ), 30 | 31 | // Edges 32 | bent-edge(, ), 33 | bent-edge(, ), 34 | bent-edge(, ), 35 | bent-edge(, ), 36 | bent-edge(, ), 37 | bent-edge(, ), 38 | ) -------------------------------------------------------------------------------- /docs/gallery/09-feynman-diagram.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/fletcher:0.5.9" as fletcher: diagram, node, edge 2 | #set page(width: auto, height: auto, margin: 5mm, fill: white) 3 | 4 | #diagram( 5 | mark-scale:130%, 6 | $ 7 | edge("rdr", overline(q), "-<|-") 8 | edge(#(4, 0), #(3.5, 0.5), b, "-<|-") 9 | edge(#(4, 1), #(3.5, 0.5), overline(b), "-<|-", label-side:#left) \ 10 | & & edge("d", "-<|-") & & edge(#(3.5, 0.5), #(2, 1), Z', "wave") \ 11 | & & edge(#(3.5, 2.5), #(2, 2), gamma, "wave") \ 12 | edge("rru", q, "-|>-") & \ 13 | $) -------------------------------------------------------------------------------- /docs/gallery/10-category-theory.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/fletcher:0.5.9" as fletcher: diagram, node, edge 2 | #set page(width: auto, height: auto, margin: 5mm, fill: white) 3 | 4 | #let member(..args) = edge(..args, " ", label: $in$, label-side: center, label-angle: right) 5 | 6 | #diagram( 7 | spacing: 7mm, 8 | node-inset: 7pt, 9 | $ 10 | id_S member() edge("d", |->) & 11 | "Hom"_cal(C)(S, S) edge(->, script(phi.alt_S)) edge("d", ->, script(f^*), #right) & 12 | A(S) edge("d", ->, script(A(f)), #left) & 13 | u_S member("l") edge("d", |->) \ 14 | f member() & 15 | "Hom"_cal(C)(T, S)) edge(->, script(phi.alt_T), #right) & 16 | A(T) & 17 | phi.alt_T (f) member("l") \ 18 | $) -------------------------------------------------------------------------------- /docs/manual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/docs/manual.pdf -------------------------------------------------------------------------------- /docs/readme-examples/1-first-isomorphism-theorem.typ: -------------------------------------------------------------------------------- 1 | #diagram(cell-size: 15mm,/*darkmode*/ 2 | edge-stroke: white, 3 | crossing-fill: none,/*end*/ $ 4 | G edge(f, ->) edge("d", pi, ->>) & im(f) \ 5 | G slash ker(f) edge("ur", tilde(f), "hook-->") 6 | $) -------------------------------------------------------------------------------- /docs/readme-examples/2-flowchart-trap.typ: -------------------------------------------------------------------------------- 1 | // https://xkcd.com/1195/ 2 | #import fletcher.shapes: diamond 3 | #set text(font: "Comic Neue", weight: 600) // testing: omit 4 | 5 | #diagram( 6 | node-stroke: 1pt/*darkmode*/ + white/*end*/, 7 | edge-stroke: 1pt/*darkmode*/ + white/*end*/, 8 | crossing-fill: white, // darkmode 9 | node((0,0), [Start], corner-radius: 2pt, extrude: (0, 3)), 10 | edge("-|>"), 11 | node((0,1), align(center)[ 12 | Hey, wait,\ this flowchart\ is a trap! 13 | ], shape: diamond), 14 | edge("d,r,u,l", "-|>", [Yes], label-pos: 0.1) 15 | ) -------------------------------------------------------------------------------- /docs/readme-examples/3-state-machine.typ: -------------------------------------------------------------------------------- 1 | #set text(10pt) 2 | #diagram( 3 | node-stroke: .1em/*darkmode*/ + white/*end*/, 4 | edge-stroke: white, // darkmode 5 | crossing-fill: black , // darkmode 6 | node-fill: gradient.radial(blue.lighten(80%), blue, center: (30%, 20%), radius: 80%), 7 | spacing: 4em, 8 | edge((-1,0), "r", "-|>", `open(path)`, label-pos: 0, label-side: center), 9 | node((0,0), `reading`, radius: 2em), 10 | edge(`read()`, "-|>"), 11 | node((1,0), `eof`, radius: 2em), 12 | edge(`close()`, "-|>"), 13 | node((2,0), `closed`, radius: 2em, extrude: (-2.5, 0)), 14 | edge((0,0), (0,0), `read()`, "--|>", bend: 130deg), 15 | edge((0,0), (2,0), `close()`, "-|>", bend: -40deg), 16 | ) -------------------------------------------------------------------------------- /docs/readme-examples/4-feynman-diagram.typ: -------------------------------------------------------------------------------- 1 | #diagram(/*darkmode*/edge-stroke: white,/*end*/$ 2 | e^- edge("rd", "-<|-") & & & edge("ld", "-|>-") e^+ \ 3 | & edge(gamma, "wave") \ 4 | e^+ edge("ru", "-|>-") & & & edge("lu", "-<|-") e^- \ 5 | $) -------------------------------------------------------------------------------- /docs/style.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/tidy:0.4.1" 2 | 3 | #let function-name-color = tidy.styles.default.function-name-color 4 | #let rainbow-map = tidy.styles.default.rainbow-map 5 | #let gradient-for-color-types = tidy.styles.default.gradient-for-color-types 6 | #let default-type-color = tidy.styles.default.default-type-color 7 | #let colors = tidy.styles.default.colors 8 | #let colors-dark = tidy.styles.default.colors-dark 9 | 10 | #let show-outline(module-doc, style-args: (:)) = box({ 11 | for fn in module-doc.functions [ 12 | - #link(label(fn.name + "()"), raw(fn.name + "()")) 13 | ] 14 | v(2em) 15 | }) 16 | 17 | #let show-type(type, style-args: (:)) = { 18 | let c-type = if type.starts-with("pair of") { 19 | type.slice(8, -1) 20 | } else { type } 21 | let c = colors.at(c-type, default: colors.at("default")) 22 | box(outset: 2pt, fill: c, radius: 2pt, raw(type)) 23 | } 24 | 25 | 26 | #let fn-label(fn-name) = label(fn-name + "()") 27 | #let fn-param-label(fn-name, arg-name) = label(fn-name + "." + arg-name) 28 | 29 | 30 | 31 | 32 | #let show-parameter-list(fn) = { 33 | set text(font: "Cascadia Mono", size: 0.8em, weight: 340) 34 | 35 | text(fn.name, fill: colors.at("signature-func-name", default: rgb("#4b69c6"))) 36 | "(" 37 | 38 | let inline = fn.args.len() <= 2 39 | if not inline { "\n " } 40 | 41 | let items = fn.args.pairs().map(((arg-name, info)) => { 42 | 43 | if info.at("description", default: "") == "" { 44 | arg-name 45 | } else { 46 | link(fn-param-label(fn.name, arg-name), arg-name) 47 | } 48 | 49 | if "types" in info { 50 | ": " + info.types.map(show-type).join(" ") 51 | } 52 | }) 53 | 54 | items.join( if inline {", "} else { ",\n "}) 55 | if not inline { ",\n" } + ")" 56 | 57 | if fn.return-types != none { 58 | " -> " 59 | fn.return-types.map(show-type).join(" ") 60 | } 61 | } 62 | 63 | 64 | 65 | // Create a parameter description block, containing name, type, description and optionally the default value. 66 | #let show-parameter-block( 67 | fn, name, types, content, 68 | show-default: false, 69 | default: none, 70 | fn-name: none, 71 | is-long: false 72 | ) = { 73 | let sep(it) = box(inset: (x: 5pt), text(0.8em, it)) 74 | let type-pills = types.map(show-type).join(sep[or]) 75 | block( 76 | inset: 10pt, 77 | breakable: is-long, 78 | { 79 | let default-multiline = type(default) == str and "\n" in default 80 | block( 81 | outset: 10pt, 82 | radius: 10pt, 83 | stroke: (top: .6pt + gray), 84 | )[ 85 | #text(1em, { 86 | strong(raw(name)) 87 | h(1em) 88 | type-pills 89 | if show-default and not default-multiline { 90 | sep[default] 91 | show-type(default) 92 | } 93 | h(1fr) 94 | text(gray, link(fn-label(fn.name), $arrow.tl$)) 95 | }) 96 | #fn-param-label(fn.name, name) 97 | ] 98 | 99 | content 100 | 101 | if show-default and default-multiline [ 102 | #parbreak() 103 | Default: #raw(default) 104 | ] 105 | } 106 | ) 107 | } 108 | 109 | 110 | #let show-function(fn, style-args) = { 111 | 112 | if style-args.colors == auto { style-args.colors = colors } 113 | 114 | block(breakable: false)[ 115 | #text(1.2em)[ 116 | #heading(raw(fn.name + "()"), level: style-args.first-heading-level + 1) 117 | #fn-label(fn.name) 118 | ] 119 | 120 | #tidy.utilities.eval-docstring(fn.description, style-args) 121 | 122 | #show-parameter-list(fn) 123 | ] 124 | 125 | 126 | for (name, info) in fn.args { 127 | let types = info.at("types", default: ()) 128 | let description = info.at("description", default: "") 129 | if description == "" { continue } 130 | 131 | show-parameter-block( 132 | fn, 133 | name, 134 | types, 135 | tidy.utilities.eval-docstring(description, style-args), 136 | is-long: description.len() > 500, // approximate 137 | show-default: "default" in info and "Default:" not in description, 138 | default: info.at("default", default: none), 139 | ) 140 | } 141 | v(3em, weak: true) 142 | } 143 | 144 | 145 | 146 | #let show-variable( 147 | var, style-args, 148 | ) = { 149 | if style-args.colors == auto { style-args.colors = colors } 150 | let type = if "type" not in var { none } 151 | else { show-type(var.type, style-args: style-args) } 152 | 153 | stack(dir: ltr, spacing: 1.2em, 154 | [ 155 | #heading(var.name, level: style-args.first-heading-level + 1) 156 | #label(style-args.label-prefix + var.name) 157 | ], 158 | type 159 | ) 160 | 161 | eval-docstring(var.description, style-args) 162 | v(4.8em, weak: true) 163 | } 164 | 165 | 166 | #let show-reference(label, name, style-args: none) = { 167 | link(label, raw(name)) 168 | } 169 | 170 | -------------------------------------------------------------------------------- /package-excludes: -------------------------------------------------------------------------------- 1 | # this file contains paths that should not be included when submitting this package to https://github.com/typst/packages 2 | package-excludes 3 | tests/ 4 | scripts/ 5 | pixi.lock 6 | pixi.toml 7 | README.src.md 8 | .git 9 | .github 10 | .gitattributes 11 | .jj 12 | # plus anything in .gitignore 13 | -------------------------------------------------------------------------------- /pixi.lock: -------------------------------------------------------------------------------- 1 | version: 6 2 | environments: 3 | default: 4 | channels: 5 | - url: https://conda.anaconda.org/conda-forge/ 6 | packages: 7 | linux-64: 8 | - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 9 | - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 10 | - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2025.1.31-hbcca054_0.conda 11 | - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h767d61c_2.conda 12 | - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_2.conda 13 | - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h767d61c_2.conda 14 | - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-h8f9b012_2.conda 15 | - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_2.conda 16 | - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda 17 | - conda: https://conda.anaconda.org/conda-forge/linux-64/nushell-0.95.0-hffdb0bd_0.conda 18 | - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.1-h7b32b05_0.conda 19 | - conda: https://conda.anaconda.org/conda-forge/linux-64/typos-1.30.0-h8fae777_0.conda 20 | - conda: https://conda.anaconda.org/conda-forge/linux-64/typst-0.13.1-h53e704d_0.conda 21 | - conda: https://conda.anaconda.org/conda-forge/linux-64/tytanic-0.2.1-h53e704d_1.conda 22 | osx-arm64: 23 | - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ca-certificates-2025.1.31-hf0a4a13_0.conda 24 | - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-19.1.7-ha82da77_0.conda 25 | - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda 26 | - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nushell-0.95.0-h783d0cf_0.conda 27 | - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.4.1-h81ee809_0.conda 28 | - conda: https://conda.anaconda.org/conda-forge/osx-arm64/typos-1.30.0-h0716509_0.conda 29 | - conda: https://conda.anaconda.org/conda-forge/osx-arm64/typst-0.13.1-h0716509_0.conda 30 | - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tytanic-0.2.1-h0716509_1.conda 31 | packages: 32 | - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 33 | sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 34 | md5: d7c89558ba9fa0495403155b64376d81 35 | license: None 36 | size: 2562 37 | timestamp: 1578324546067 38 | - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 39 | build_number: 16 40 | sha256: fbe2c5e56a653bebb982eda4876a9178aedfc2b545f25d0ce9c4c0b508253d22 41 | md5: 73aaf86a425cc6e73fcf236a5a46396d 42 | depends: 43 | - _libgcc_mutex 0.1 conda_forge 44 | - libgomp >=7.5.0 45 | constrains: 46 | - openmp_impl 9999 47 | license: BSD-3-Clause 48 | license_family: BSD 49 | size: 23621 50 | timestamp: 1650670423406 51 | - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2025.1.31-hbcca054_0.conda 52 | sha256: bf832198976d559ab44d6cdb315642655547e26d826e34da67cbee6624cda189 53 | md5: 19f3a56f68d2fd06c516076bff482c52 54 | license: ISC 55 | size: 158144 56 | timestamp: 1738298224464 57 | - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ca-certificates-2025.1.31-hf0a4a13_0.conda 58 | sha256: 7e12816618173fe70f5c638b72adf4bfd4ddabf27794369bb17871c5bb75b9f9 59 | md5: 3569d6a9141adc64d2fe4797f3289e06 60 | license: ISC 61 | size: 158425 62 | timestamp: 1738298167688 63 | - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-19.1.7-ha82da77_0.conda 64 | sha256: 776092346da87a2a23502e14d91eb0c32699c4a1522b7331537bd1c3751dcff5 65 | md5: 5b3e1610ff8bd5443476b91d618f5b77 66 | depends: 67 | - __osx >=11.0 68 | license: Apache-2.0 WITH LLVM-exception 69 | license_family: Apache 70 | size: 523505 71 | timestamp: 1736877862502 72 | - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h767d61c_2.conda 73 | sha256: 3a572d031cb86deb541d15c1875aaa097baefc0c580b54dc61f5edab99215792 74 | md5: ef504d1acbd74b7cc6849ef8af47dd03 75 | depends: 76 | - __glibc >=2.17,<3.0.a0 77 | - _openmp_mutex >=4.5 78 | constrains: 79 | - libgomp 14.2.0 h767d61c_2 80 | - libgcc-ng ==14.2.0=*_2 81 | license: GPL-3.0-only WITH GCC-exception-3.1 82 | license_family: GPL 83 | size: 847885 84 | timestamp: 1740240653082 85 | - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_2.conda 86 | sha256: fb7558c328b38b2f9d2e412c48da7890e7721ba018d733ebdfea57280df01904 87 | md5: a2222a6ada71fb478682efe483ce0f92 88 | depends: 89 | - libgcc 14.2.0 h767d61c_2 90 | license: GPL-3.0-only WITH GCC-exception-3.1 91 | license_family: GPL 92 | size: 53758 93 | timestamp: 1740240660904 94 | - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h767d61c_2.conda 95 | sha256: 1a3130e0b9267e781b89399580f3163632d59fe5b0142900d63052ab1a53490e 96 | md5: 06d02030237f4d5b3d9a7e7d348fe3c6 97 | depends: 98 | - __glibc >=2.17,<3.0.a0 99 | license: GPL-3.0-only WITH GCC-exception-3.1 100 | license_family: GPL 101 | size: 459862 102 | timestamp: 1740240588123 103 | - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-h8f9b012_2.conda 104 | sha256: 8f5bd92e4a24e1d35ba015c5252e8f818898478cb3bc50bd8b12ab54707dc4da 105 | md5: a78c856b6dc6bf4ea8daeb9beaaa3fb0 106 | depends: 107 | - __glibc >=2.17,<3.0.a0 108 | - libgcc 14.2.0 h767d61c_2 109 | license: GPL-3.0-only WITH GCC-exception-3.1 110 | license_family: GPL 111 | size: 3884556 112 | timestamp: 1740240685253 113 | - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_2.conda 114 | sha256: e86f38b007cf97cc2c67cd519f2de12a313c4ee3f5ef11652ad08932a5e34189 115 | md5: c75da67f045c2627f59e6fcb5f4e3a9b 116 | depends: 117 | - libstdcxx 14.2.0 h8f9b012_2 118 | license: GPL-3.0-only WITH GCC-exception-3.1 119 | license_family: GPL 120 | size: 53830 121 | timestamp: 1740240722530 122 | - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda 123 | sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 124 | md5: edb0dca6bc32e4f4789199455a1dbeb8 125 | depends: 126 | - __glibc >=2.17,<3.0.a0 127 | - libgcc >=13 128 | constrains: 129 | - zlib 1.3.1 *_2 130 | license: Zlib 131 | license_family: Other 132 | size: 60963 133 | timestamp: 1727963148474 134 | - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda 135 | sha256: ce34669eadaba351cd54910743e6a2261b67009624dbc7daeeafdef93616711b 136 | md5: 369964e85dc26bfe78f41399b366c435 137 | depends: 138 | - __osx >=11.0 139 | constrains: 140 | - zlib 1.3.1 *_2 141 | license: Zlib 142 | license_family: Other 143 | size: 46438 144 | timestamp: 1727963202283 145 | - conda: https://conda.anaconda.org/conda-forge/linux-64/nushell-0.95.0-hffdb0bd_0.conda 146 | sha256: 8fd13f9ff69b65271b798cd078038147838f74e53edb4d28f796236098b45dd4 147 | md5: 726b85d32cb9d4750bd1b2755f35e7c0 148 | depends: 149 | - libgcc-ng >=12 150 | - libstdcxx-ng >=12 151 | - libzlib >=1.3.1,<2.0a0 152 | - openssl >=3.3.1,<4.0a0 153 | license: MIT 154 | license_family: MIT 155 | size: 8130300 156 | timestamp: 1719358596211 157 | - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nushell-0.95.0-h783d0cf_0.conda 158 | sha256: 2b2ade438bf3bc561b498946eb4fb1ab1fdafe58f7fc5da371db97b875b0de61 159 | md5: b8a4474a347cc5d26384c5e644f461ea 160 | depends: 161 | - __osx >=11.0 162 | - libcxx >=16 163 | - libzlib >=1.3.1,<2.0a0 164 | - openssl >=3.3.1,<4.0a0 165 | constrains: 166 | - __osx >=11.0 167 | license: MIT 168 | license_family: MIT 169 | size: 7686021 170 | timestamp: 1719358942667 171 | - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.1-h7b32b05_0.conda 172 | sha256: cbf62df3c79a5c2d113247ddea5658e9ff3697b6e741c210656e239ecaf1768f 173 | md5: 41adf927e746dc75ecf0ef841c454e48 174 | depends: 175 | - __glibc >=2.17,<3.0.a0 176 | - ca-certificates 177 | - libgcc >=13 178 | license: Apache-2.0 179 | license_family: Apache 180 | size: 2939306 181 | timestamp: 1739301879343 182 | - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.4.1-h81ee809_0.conda 183 | sha256: 4f8e2389e1b711b44182a075516d02c80fa7a3a7e25a71ff1b5ace9eae57a17a 184 | md5: 75f9f0c7b1740017e2db83a53ab9a28e 185 | depends: 186 | - __osx >=11.0 187 | - ca-certificates 188 | license: Apache-2.0 189 | license_family: Apache 190 | size: 2934522 191 | timestamp: 1739301896733 192 | - conda: https://conda.anaconda.org/conda-forge/linux-64/typos-1.30.0-h8fae777_0.conda 193 | sha256: c9caa8b1c26aa935103cefb17c235c1d5c50554a532d15feef93fab57703bde0 194 | md5: a316b816c0557c963ad8dd3137e1fcce 195 | depends: 196 | - __glibc >=2.17,<3.0.a0 197 | - libgcc >=13 198 | constrains: 199 | - __glibc >=2.17 200 | license: MIT 201 | license_family: MIT 202 | size: 3450558 203 | timestamp: 1740827978586 204 | - conda: https://conda.anaconda.org/conda-forge/osx-arm64/typos-1.30.0-h0716509_0.conda 205 | sha256: e8104dee7991c6b68e041f30b95a744e01110bb4ca13fdfaffa6769f34d91ecf 206 | md5: a8d7da311c13b07f701b3052701108f7 207 | depends: 208 | - __osx >=11.0 209 | constrains: 210 | - __osx >=11.0 211 | license: MIT 212 | license_family: MIT 213 | size: 2705083 214 | timestamp: 1740828253407 215 | - conda: https://conda.anaconda.org/conda-forge/linux-64/typst-0.13.1-h53e704d_0.conda 216 | sha256: e863d9e1581a31064c2f12d71f8e27058d16ec5a897c2d469b6a24665c2689e4 217 | md5: 2a61bbad3fdbc1675419d36695f716f2 218 | depends: 219 | - __glibc >=2.17,<3.0.a0 220 | - libgcc >=13 221 | - openssl >=3.4.1,<4.0a0 222 | constrains: 223 | - __glibc >=2.17 224 | arch: x86_64 225 | platform: linux 226 | license: Apache-2.0 227 | license_family: Apache 228 | size: 13178185 229 | timestamp: 1741356392643 230 | - conda: https://conda.anaconda.org/conda-forge/osx-arm64/typst-0.13.1-h0716509_0.conda 231 | sha256: 84268cfb391e34e34c455c781728cc298c06bef41987d876aaea55b3509d707c 232 | md5: 81b207d1e979d5318596a7916450fe8e 233 | depends: 234 | - __osx >=11.0 235 | constrains: 236 | - __osx >=11.0 237 | arch: arm64 238 | platform: osx 239 | license: Apache-2.0 240 | license_family: Apache 241 | size: 12551990 242 | timestamp: 1741356577909 243 | - conda: https://conda.anaconda.org/conda-forge/linux-64/tytanic-0.2.1-h53e704d_1.conda 244 | sha256: baa000322b2d0660c00f8eb04734187497b756091e3e4014416ec0381abee4a7 245 | md5: 1e57cb72222b68be15242563c1a79319 246 | depends: 247 | - __glibc >=2.17,<3.0.a0 248 | - libgcc >=13 249 | - openssl >=3.4.1,<4.0a0 250 | constrains: 251 | - __glibc >=2.17 252 | license: MIT 253 | license_family: MIT 254 | size: 13389544 255 | timestamp: 1740740425587 256 | - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tytanic-0.2.1-h0716509_1.conda 257 | sha256: bbfce50876fb37a0d36c91c1255fe7e949beae07b03de270034a09ec88f0f0b8 258 | md5: 134be97496a4dc7ddcd0ac57f2eb32b8 259 | depends: 260 | - __osx >=11.0 261 | constrains: 262 | - __osx >=11.0 263 | license: MIT 264 | license_family: MIT 265 | size: 12844081 266 | timestamp: 1740741010226 267 | -------------------------------------------------------------------------------- /pixi.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "fletcher" 3 | authors = ["jollywatt "] 4 | channels = ["conda-forge"] 5 | platforms = ["osx-arm64", "linux-64"] 6 | 7 | [tasks] 8 | readme = "scripts/readme.nu" 9 | compile = "scripts/compile.nu" 10 | test = "tt run" 11 | fix = "tt update" 12 | manual = "typst watch --root . docs/manual.typ" 13 | install = "scripts/install.nu" 14 | check = "scripts/check.nu" 15 | 16 | [dependencies] 17 | nushell = ">=0.95.0,<0.96" 18 | typos = "*" 19 | typst = "==0.13.1" 20 | tytanic = ">=0.2.1,<0.3" 21 | -------------------------------------------------------------------------------- /scripts/check.nu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nu 2 | 3 | # Check for possible typos or incorrect version numbers in files 4 | def main [ 5 | --typos (-t) # Run a typo check on all files 6 | --versions (-v) # Search files for version numbers of the form `x.y.z` 7 | ] { 8 | let all = not ($typos or $versions) 9 | 10 | if $typos or $all { 11 | print (typos) 12 | } 13 | if $versions or $all { 14 | print (versions) 15 | } 16 | } 17 | 18 | def typos [] { 19 | print "Possible typos:" 20 | ^typos --config scripts/typos.toml --format brief | lines 21 | } 22 | 23 | def versions [] { 24 | print "Version numbers in files to check:" 25 | ls **/*.typ **/*.toml | get name | each { |file| 26 | let matches = open $file --raw | lines | find -r '\d\.\d\.\d' 27 | $matches | wrap match | insert file $file 28 | } | flatten | select file match 29 | } 30 | -------------------------------------------------------------------------------- /scripts/compile.nu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nu 2 | 3 | # This script is used to compile example diagrams in the readme and gallery. 4 | # 5 | 6 | 7 | const TYP_TEMPLATE = ' 8 | #import "/src/exports.typ" as fletcher: diagram, node, edge 9 | #set page(width: auto, height: auto, margin: 1em, fill: none) 10 | #set text(fill: white) // darkmode 11 | 12 | // Not sure how to scale SVGs output, so just do this 13 | #show: it => context { 14 | let factor = 1.8 15 | let m = measure(it) 16 | box( 17 | inset: ( 18 | x: m.width/factor, 19 | y: m.height/factor, 20 | ), 21 | scale(factor*100%, it), 22 | ) 23 | } 24 | 25 | ' 26 | 27 | const EXAMPLES_PATH = "docs/readme-examples" 28 | 29 | def apply_example_template [path, --darkmode(-d)] { 30 | let src = $TYP_TEMPLATE + (open $path) 31 | 32 | let src = if $darkmode { 33 | $src 34 | } else { 35 | $src | 36 | str replace -ra '.*//\s*darkmode\s*\n' '' | 37 | str replace -ra '/\*darkmode\*/[\s\S]*/\*end\*/' '' 38 | } 39 | 40 | $src 41 | } 42 | 43 | def compile_example [path, --darkmode(-d)] { 44 | let src = apply_example_template $path --darkmode=$darkmode 45 | let destpath = $path | path parse | 46 | update stem { $"($in)-(if $darkmode {'dark'} else {'light'})" } | 47 | update extension svg | 48 | path join 49 | 50 | $src | save temp.typ --force 51 | typst compile --root . temp.typ $destpath --format svg 52 | rm temp.typ 53 | } 54 | 55 | def compile_message [path, desc=''] { 56 | print -n (ansi green_bold) 'Compiling' (ansi w) $": ($path) " (ansi reset) $"($desc)\n" 57 | } 58 | 59 | # Compile light and dark versions of README example diagrams. 60 | # 61 | # For each file in /docs/readme-examples/*.typ, two images are produced: 62 | # 1. /docs/readme-examples/*-light.svg for light backgrounds 63 | # 2. /docs/readme-examples/*-dark.svg for dark backgrounds 64 | def "main examples" [ 65 | pattern: string = '' # Filter to examples with name matching regex 66 | ] { 67 | # navigate to repository root 68 | # cd $env.FILE_PWD 69 | # cd ../ 70 | 71 | ls docs/readme-examples/*.typ | 72 | insert basename { $in.name | path basename } | 73 | where basename =~ $pattern | 74 | each { |it| 75 | compile_message $it.name '(light mode)' 76 | compile_example $it.name 77 | compile_message $it.name '(dark mode)' 78 | compile_example $it.name --darkmode 79 | } 80 | 81 | null 82 | } 83 | 84 | 85 | 86 | # Compile gallery examples to SVGs 87 | def "main gallery" [ 88 | pattern : string = "" # Filter to filenames matching regex 89 | ] { 90 | ls docs/gallery/*.typ | 91 | insert parts {|it| $it.name | path parse} | 92 | where parts.stem =~ $pattern | 93 | insert dest {|it| $it.parts | update extension svg | path join } | 94 | each { |it| 95 | 96 | let src = open $it.name | 97 | str replace --regex '#import "@preview/fletcher:\d+.\d+.\d+' '#import "/src/exports.typ' 98 | 99 | $src | save temp.typ --force 100 | print -n (ansi green_bold) 'Compiling' (ansi w) $": ($it.name) " (ansi reset) "\n" 101 | typst compile --root . temp.typ $it.dest 102 | rm temp.typ 103 | } 104 | null 105 | } 106 | 107 | def "main manual" [] { 108 | let path = 'docs/manual.typ' 109 | compile_message $path 110 | typst compile --root . $path 111 | } 112 | 113 | def main [] { 114 | main examples 115 | main gallery 116 | main manual 117 | } 118 | -------------------------------------------------------------------------------- /scripts/install.nu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nu 2 | 3 | # Locally install a typst package under the given namespace 4 | # so it is installable with `import "@namespace/package:version"`. 5 | def main [ 6 | path : path = . # Location of typst package 7 | --namespace (-n) : string = "local" # namespace to install to, e.g., 'preview' 8 | --symlink (-s) # Install by symlinking instead of copying 9 | --delete (-d) # Uninstall by deleting installed directory 10 | ] { 11 | let path = realpath $path 12 | let info = open typst.toml | get package 13 | let packages_dir = [(DATA_DIR) "typst" "packages"] | path join 14 | 15 | cd $packages_dir 16 | mkdir $namespace 17 | cd $namespace 18 | mkdir $info.name 19 | cd $info.name 20 | if ($info.version | path exists) { rm $info.version } 21 | 22 | if $delete { 23 | if ($info.version | path exists) { 24 | rm $info.version 25 | } 26 | } else { 27 | if $symlink { 28 | print $"Linking (pwd)" 29 | ln -s ($path) ($info.version) 30 | } else { 31 | error make { msg: "copying not implemented", help: "pass --symlink to symlink instead of copy" } 32 | } 33 | print $'Installed package locally as "@($namespace)/($info.name):($info.version)".' 34 | } 35 | } 36 | 37 | 38 | # Get the OS-specific data directory 39 | def DATA_DIR [] { 40 | match $nu.os-info.name { 41 | "macos" => { $"~/Library/Application Support" }, 42 | "linux" => { $env.XDG_DATA_HOME? | default $"~/.local/share" }, 43 | "windows" => { $env.APPDATA }, 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /scripts/readme.nu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nu 2 | 3 | # Generate README.md from template file README.src.md. 4 | # Inserts version number, readme examples and gallery 5 | 6 | def get_version [] { 7 | open typst.toml | get package.version 8 | } 9 | 10 | const README_EXAMPLE = ' 11 | 12 | 13 | 14 | 15 | 16 | ```typ 17 | {src} 18 | ``` 19 | ' 20 | 21 | def clean_readme_example [path: path] { 22 | let parts = $path | path parse 23 | $README_EXAMPLE | 24 | str replace -a '{url}' $"($parts.parent)/($parts.stem)" | 25 | str replace '{src}' (open $path) | 26 | str replace -ra '.*//\s*darkmode\s*\n' '' | 27 | str replace -ra '/\*darkmode\*/[\s\S]*/\*end\*/' '' 28 | } 29 | 30 | def examples [] { 31 | ls docs/readme-examples/*.typ | get name | each {|it| 32 | clean_readme_example $it 33 | } | str join 34 | } 35 | 36 | def gallery [] { 37 | let cells = ls docs/gallery/*.typ | each {|it| 38 | let img = $it.name | path parse | update extension svg | path join 39 | {tag: td, attributes: {style: 'background: white;'} content: [{ 40 | tag: a 41 | attributes: {href: $it.name} 42 | content: [{tag: center, content: [{ 43 | tag: img 44 | attributes: {src: $img width: '100%'} 45 | }]}] 46 | }]} 47 | } 48 | let N = $cells | length 49 | let cols = 2 50 | let rows = 0..($N / $cols - 1 | math ceil) | each {|n| 51 | let row = $n * $cols 52 | { 53 | tag: tr 54 | content: ($cells | range $row..($row + $cols - 1)) 55 | } 56 | } 57 | {tag: table, content: $rows} | to xml --indent 2 --self-closed 58 | } 59 | 60 | def main [] { 61 | open README.src.md | 62 | str replace -a '{VERSION}' (get_version) | 63 | str replace '{README_EXAMPLES}' (examples) | 64 | str replace '{GALLERY}' (gallery) | 65 | save README.md --force 66 | print "Wrote to README.md" 67 | } 68 | -------------------------------------------------------------------------------- /scripts/typos.toml: -------------------------------------------------------------------------------- 1 | # don't spellcheck the following files 2 | [files] 3 | extend-exclude = [ 4 | "*.svg", 5 | "*.sublime-workspace", 6 | "*.sublime-project", 7 | ] 8 | 9 | [default.extend-words] 10 | typ="typ" 11 | -------------------------------------------------------------------------------- /src/coords.typ: -------------------------------------------------------------------------------- 1 | #import "utils.typ": * 2 | #import "deps.typ": cetz 3 | 4 | /// Convert from elastic to absolute coordinates, $(u, v) |-> (x, y)$. 5 | /// 6 | /// _Elastic_ coordinates are specific to the diagram and adapt to row/column 7 | /// sizes; _absolute_ coordinates are the final, physical lengths which are 8 | /// passed to `cetz`. 9 | /// 10 | /// - grid (dictionary): Representation of the grid layout, including: 11 | /// - `origin` 12 | /// - `centers` 13 | /// - `spacing` 14 | /// - `flip` 15 | /// The `grid` is passed to #the-param[diagram][render]. 16 | /// - uv (array): Elastic coordinate, `(float, float)`. 17 | #let uv-to-xy(grid, uv) = { 18 | let (i, j) = vector.sub(vector-2d(uv), grid.origin) 19 | 20 | let (n-x, n-y) = grid.centers.map(array.len) 21 | if grid.flip.xy { (n-x, n-y) = (n-y, n-x) } 22 | if grid.flip.x { i = (n-x - 1) - i } 23 | if grid.flip.y { j = (n-y - 1) - j } 24 | if grid.flip.xy { (i, j) = (j, i) } 25 | 26 | (i, j).zip(grid.centers, grid.spacing) 27 | .map(((t, c, s)) => interp(c, t, spacing: s)) 28 | } 29 | 30 | /// Convert from absolute to elastic coordinates, $(x, y) |-> (u, v)$. 31 | /// 32 | /// Inverse of `uv-to-xy()`. 33 | #let xy-to-uv(grid, xy) = { 34 | let (i, j) = xy.zip(grid.centers, grid.spacing) 35 | .map(((x, c, s)) => interp-inv(c, x, spacing: s)) 36 | 37 | let (n-x, n-y) = grid.centers.map(array.len) 38 | if grid.flip.xy { (n-x, n-y) = (n-y, n-x) } 39 | if grid.flip.xy { (i, j) = (j, i) } 40 | if grid.flip.x { i = (n-x - 1) - i } 41 | if grid.flip.y { j = (n-y - 1) - j } 42 | 43 | vector.add((i, j), grid.origin) 44 | } 45 | 46 | /// Jacobian of the coordinate map `uv-to-xy()`. 47 | /// 48 | /// Used to convert a "nudge" in $u v$ coordinates to a "nudge" in $x y$ 49 | /// coordinates. This is needed because $u v$ coordinates are non-linear 50 | /// (they're elastic). Uses a balanced finite differences approximation. 51 | /// 52 | /// - grid (dictionary): Representation of the grid layout. 53 | /// The `grid` is passed to #the-param[diagram][render]. 54 | /// - uv (array): The point `(float, float)` in the $u v$-manifold where the 55 | /// shift tangent vector is rooted. 56 | /// - duv (array): The shift tangent vector `(float, float)` in $u v$ coordinates. 57 | #let duv-to-dxy(grid, uv, duv) = { 58 | let duv = vector.scale(duv, 0.5) 59 | vector.sub( 60 | uv-to-xy(grid, vector.add(uv, duv)), 61 | uv-to-xy(grid, vector.sub(uv, duv)), 62 | ) 63 | } 64 | 65 | /// Jacobian of the coordinate map `xy-to-uv()`. 66 | #let dxy-to-duv(grid, xy, dxy) = { 67 | let dxy = vector.scale(dxy, 0.5) 68 | vector.sub( 69 | xy-to-uv(grid, vector.add(xy, dxy)), 70 | xy-to-uv(grid, vector.sub(xy, dxy)), 71 | ) 72 | } 73 | 74 | /// Return a vector rooted at a $x y$ coordinate with a given angle $θ$ in $x 75 | /// y$-space but with a length specified in either $x y$-space or $u v$-space. 76 | #let vector-polar-with-xy-or-uv-length(grid, xy, target-length, θ) = { 77 | if type(target-length) == length { 78 | vector-polar(target-length, θ) 79 | } else { 80 | let unit = vector-polar(1pt, θ) 81 | let det = vector.len(dxy-to-duv(grid, xy, unit)) 82 | vector.scale(unit, target-length/det) 83 | } 84 | } 85 | 86 | 87 | 88 | #let NAN_COORD = (float.nan, float.nan) 89 | 90 | #let default-ctx = ( 91 | prev: (pt: (0, 0)), 92 | 93 | // cetz anchors assume y axis going up. 94 | // see lines ending with the comment 95 | // CETZ Y AXIS 96 | transform: 97 | ((1, 0, 0, 0), 98 | (0,-1, 0, 0), 99 | (0, 0, 1, 0), 100 | (0, 0, 0, 1)), 101 | 102 | nodes: (:), 103 | length: 1cm, 104 | em-size: (width: 11pt, height: 11pt), 105 | style: cetz.styles.default, 106 | groups: (), 107 | debug: false, 108 | ) 109 | 110 | 111 | #let resolve-system(coord) = { 112 | if type(coord) == dictionary and ("u", "v").all(k => k in coord) { 113 | return "uv" 114 | } else if type(coord) == label { 115 | return "element" 116 | } 117 | 118 | let cetz-system = cetz.coordinate.resolve-system(coord) 119 | if cetz-system == "xyz" and coord.len() == 2 { 120 | if coord.all(x => type(x) == length) { 121 | "xyz" 122 | } else if coord.all(x => type(x) in (int, float)) { 123 | "uv" 124 | } else { 125 | error("Coordinates must be two numbers (for elastic coordinates) or two lengths (for physical coordinates); got #0.", coord) 126 | } 127 | } else { 128 | cetz-system 129 | } 130 | } 131 | 132 | #let resolve-anchor(ctx, c) = { 133 | // (name: , anchor: or ) 134 | // "name.anchor" 135 | // "name" 136 | if type(c) == label { c = str(c) } 137 | 138 | let (name, anchor) = if type(c) == str { 139 | let (name, ..anchor) = c.split(".") 140 | if anchor.len() == 0 { 141 | anchor = "default" 142 | } 143 | (name, anchor) 144 | } else { 145 | (str(c.name), c.at("anchor", default: "default")) 146 | } 147 | 148 | if name not in ctx.nodes { 149 | error("Node #0 not found. Named nodes are: #..1.", name, ctx.nodes.keys()) 150 | } 151 | 152 | // Resolve length anchors 153 | if type(anchor) == length { 154 | anchor = util.resolve-number(ctx, anchor) 155 | } 156 | 157 | let calculate-anchors = ctx.nodes.at(name).anchors 158 | 159 | (calculate-anchors)(anchor) 160 | } 161 | 162 | 163 | 164 | #let resolve-relative(resolve, ctx, c) = { 165 | // (rel: , update: or , to: ) 166 | let update = c.at("update", default: true) 167 | 168 | let target-system = ctx.target-system 169 | let sub-ctx = ctx + (target-system: auto) 170 | 171 | let (ctx, rel) = resolve(sub-ctx, c.rel, update: false) 172 | 173 | ctx.target-system = target-system 174 | let (ctx, to) = if "to" in c { 175 | resolve(ctx, c.to, update: false) 176 | } else { 177 | (ctx, ctx.prev.pt) 178 | } 179 | 180 | if is-nan-vector(to) { 181 | return (coord: to, update: update) 182 | } 183 | 184 | 185 | let is-xy(coord) = coord.any(x => type(x) == length) 186 | let is-uv(coord) = not is-xy(coord) 187 | 188 | let error-value = (coord: NAN_COORD, update: update) 189 | 190 | if is-xy(rel) and is-uv(to) { 191 | if "grid" not in ctx { return error-value } 192 | to = uv-to-xy(ctx.grid, to) 193 | } else if is-uv(rel) and is-xy(to) { 194 | if "grid" not in ctx { return error-value } 195 | to = xy-to-uv(ctx.grid, to) 196 | } 197 | 198 | c = vector.add(rel, to) 199 | 200 | if ctx.target-system == "xyz" and is-uv(c) { 201 | if "grid" not in ctx { return error-value } 202 | c = uv-to-xy(ctx.grid, c) 203 | } else if ctx.target-system == "uv" and is-xy(c) { 204 | if "grid" not in ctx { return error-value } 205 | c = xy-to-uv(ctx.grid, c) 206 | } 207 | 208 | (coord: c, update: update) 209 | } 210 | 211 | 212 | /// Resolve CeTZ-style coordinate expressions to absolute vectors. 213 | /// 214 | /// This is an drop-in replacement of `cetz.coordinate.resolve()` but extended 215 | /// to handle fletcher's elastic $u v$ coordinates alongside CeTZ' physical $x 216 | /// y$ coordinates. The target coordinate system must be specified in the 217 | /// context object `ctx`. 218 | /// 219 | /// Resolving $u v$ coordinates to or from $x y$ coordinates requires the 220 | /// diagram's `grid`, which defines the non-linear maps `uv-to-xy()` and 221 | /// `xy-to-uv()`. The `grid` may be supplied in the context object `ctx`. 222 | /// 223 | /// If `grid` is not supplied, *coordinate resolution may fail*, in which case 224 | /// the vector #fletcher.NAN_COORD is returned. 225 | /// 226 | /// - ctx (dictionary): CeTZ canvas context object, additionally containing: 227 | /// - `target-system`: the target coordinate system to resolve to, one of 228 | /// `"uv"` or `"xyz"`. 229 | /// - `grid` (optional): the diagram's grid specification, defining the 230 | /// coordinate maps $u v <-> x y$. If not given, coordinates requiring this 231 | /// map resolve to #fletcher.NAN_COORD. 232 | /// 233 | /// - ..coordinates (coordinate): CeTZ-style coordinate expression(s), e.g., 234 | /// `(1, 2)`, `(45deg, 2cm)`, or `(rel: (+1, 0), to: "name")`. 235 | #let resolve(ctx, ..coordinates, update: true) = { 236 | assert(ctx.target-system in (auto, "uv", "xyz")) 237 | 238 | let result = () 239 | for c in coordinates.pos() { 240 | let t = resolve-system(c) 241 | let out = if t == "uv" { 242 | if ctx.target-system in (auto, "uv") { 243 | let (u, v) = c // also works for dictionaries 244 | (u, v) 245 | } else if ctx.target-system == "xyz" { 246 | if "grid" in ctx { uv-to-xy(ctx.grid, c) } 247 | else { NAN_COORD } 248 | } 249 | } else if t == "xyz" { 250 | let c = cetz.coordinate.resolve-xyz(c) 251 | c = vector-2d(c).map(x => x.abs + x.em*ctx.em-size.width) 252 | if ctx.target-system in (auto, "xyz") { 253 | c 254 | } else if ctx.target-system == "uv" { 255 | if "grid" in ctx { xy-to-uv(ctx.grid, c) } 256 | else { NAN_COORD } 257 | } 258 | } else if t == "previous" { 259 | (ctx, c) = resolve(ctx, ctx.prev.pt) 260 | c 261 | } else if t == "polar" { 262 | c = vector-2d(cetz.coordinate.resolve-polar(c)) 263 | resolve(ctx, c).at(1) // ensure uv <-> xyz conversion 264 | } else if t == "barycentric" { 265 | cetz.coordinate.resolve-barycentric(ctx, c) 266 | } else if t in ("element", "anchor") { 267 | resolve-anchor(ctx, c) 268 | } else if t == "tangent" { 269 | cetz.coordinate.resolve-tangent(resolve, ctx, c) 270 | } else if t == "perpendicular" { 271 | cetz.coordinate.resolve-perpendicular(resolve, ctx, c) 272 | } else if t == "relative" { 273 | let result = resolve-relative(resolve, ctx, c) 274 | update = result.update 275 | result.coord 276 | } else if t == "lerp" { 277 | cetz.coordinate.resolve-lerp(resolve, ctx, c) 278 | } else if t == "function" { 279 | cetz.coordinate.resolve-function(resolve, ctx, c) 280 | } else { 281 | error("Failed to resolve coordinate #0.", c) 282 | } 283 | 284 | out = vector-2d(out) 285 | 286 | if update { ctx.prev.pt = out } 287 | result.push(out) 288 | } 289 | 290 | assert(result.all(c => c.len() == 2)) 291 | 292 | return (ctx, ..result) 293 | } 294 | 295 | 296 | #let is-grid-independent-uv-coordinate(coord) = { 297 | let ctx = default-ctx + (target-system: "uv") 298 | (ctx, coord) = resolve(ctx, coord) 299 | not is-nan-vector(coord) 300 | } 301 | 302 | -------------------------------------------------------------------------------- /src/default-marks.typ: -------------------------------------------------------------------------------- 1 | #import "deps.typ" 2 | #import deps.cetz.draw 3 | 4 | #let DEFAULT_MARKS = ( 5 | // all numbers are interpreted as multiples of stroke thickness 6 | 7 | head: ( 8 | size: 7, // radius of curvature 9 | sharpness: 24.7deg, // angle at vertex between central line and arrow's edge 10 | delta: 53.5deg, // angle spanned by arc of curved arrow edge 11 | 12 | tip-origin: 0.5, 13 | tail-end: mark => calc.min(..mark.extrude), 14 | tail-origin: mark => { 15 | let dx = calc.cos(mark.sharpness) + calc.cos(mark.sharpness + mark.delta) 16 | mark.tail-end - mark.size*mark.delta/1.8rad*dx 17 | }, 18 | 19 | stroke: (cap: "round"), 20 | 21 | draw: mark => { 22 | for flip in (+1, -1) { 23 | draw.arc( 24 | (0, 0), 25 | radius: mark.size, 26 | start: flip*(90deg + mark.sharpness), 27 | delta: flip*mark.delta, 28 | fill: none, 29 | ) 30 | } 31 | }, 32 | 33 | cap-offset: (mark, y) => { 34 | import calc: sin, sqrt, pow, cos, abs, max 35 | let r = mark.size 36 | let θ = mark.sharpness 37 | r*(sin(θ) - sqrt(max(0, 1 - pow(cos(θ) - abs(y)/r, 2)))) 38 | }, 39 | 40 | ), 41 | 42 | doublehead: ( 43 | inherit: "head", 44 | size: 10.56, 45 | sharpness: 19.4deg, 46 | delta: 43.5deg, 47 | ), 48 | 49 | triplehead: ( 50 | inherit: "head", 51 | size: 13.5, 52 | sharpness: 25.5deg, 53 | delta: 42.6deg, 54 | ), 55 | 56 | harpoon: ( 57 | inherit: "head", 58 | draw: mark => { 59 | draw.arc( 60 | (0, 0), 61 | radius: mark.size, 62 | start: -(90deg + mark.sharpness), 63 | delta: -mark.delta, 64 | fill: none, 65 | ) 66 | }, 67 | ), 68 | 69 | straight: ( 70 | size: 10, 71 | sharpness: 20deg, 72 | 73 | tip-origin: mark => 0.5/calc.sin(mark.sharpness), 74 | tail-origin: mark => -mark.size*calc.cos(mark.sharpness), 75 | 76 | fill: none, 77 | 78 | draw: mark => { 79 | draw.line( 80 | (180deg + mark.sharpness, mark.size), 81 | (0, 0), 82 | (180deg - mark.sharpness, mark.size), 83 | ) 84 | }, 85 | 86 | cap-offset: (mark, y) => calc.tan(mark.sharpness + 90deg)*calc.abs(y), 87 | ), 88 | 89 | solid: ( 90 | inherit: "straight", 91 | 92 | tip-origin: 0, 93 | tip-end: mark => -0.5/calc.sin(mark.sharpness), 94 | tail-end: mark => -0.5/calc.sin(mark.sharpness), 95 | 96 | stroke: none, 97 | fill: auto, 98 | ), 99 | 100 | stealth: ( 101 | size: 6, 102 | stealth: 0.3, 103 | angle: 25deg, 104 | rear-angle: mark => calc.atan2(mark.stealth, calc.tan(mark.angle)), 105 | 106 | tip-origin: mark => 0.5/calc.sin(mark.angle), 107 | tip-end: mark => mark.size*(mark.stealth - 1)*calc.cos(mark.angle), 108 | tail-origin: mark => { 109 | if mark.stealth > 0 { 110 | let wing-angle = (mark.rear-angle - mark.angle)/2 111 | 112 | let miter-limit = if mark.stroke == none { 0 } 113 | else { stroke(mark.stroke).miter-limit } 114 | 115 | let miter-length = 1/calc.sin(wing-angle) 116 | // stealth arrows with sharp wings look bigger due to long miter lengths 117 | let extra-size = if miter-length < miter-limit { 118 | // so account for extra apparent size 119 | 0.4*miter-length 120 | } else { 121 | // unless over miter limit, since wings get clipped 122 | 0 123 | } 124 | 125 | -(mark.size + extra-size)*calc.cos(mark.angle) 126 | } else { 127 | // negative stealth looks like a diamond 128 | mark.tip-end - 0.5/calc.sin(mark.rear-angle) 129 | } 130 | }, 131 | 132 | stroke: (miter-limit: 20), 133 | 134 | draw: mark => { 135 | draw.line( 136 | (0,0), 137 | (180deg + mark.angle, mark.size), 138 | (mark.tip-end, 0), 139 | (180deg - mark.angle, mark.size), 140 | close: true, 141 | ) 142 | }, 143 | 144 | cap-offset: (mark, y) => if mark.tip { 145 | -mark.stealth/calc.tan(mark.angle)*calc.abs(y) 146 | } else { 147 | calc.tan(mark.angle + 90deg)*calc.abs(y) 148 | }, 149 | ), 150 | 151 | latex: ( 152 | size: 23, // radius of curvature 153 | sharpness: 10deg, // angle at vertex between central line and arrow's edge 154 | delta: 20deg, // angle spanned by arc of curved arrow edge 155 | 156 | tip-end: mark => mark.size*(calc.sin(mark.sharpness) - calc.sin(mark.sharpness + mark.delta)), 157 | tail-end: mark => mark.tip-end/2, 158 | tail-origin: mark => mark.tip-end, 159 | 160 | fill: auto, 161 | stroke: none, 162 | draw: mark => { 163 | for flip in (+1, -1) { 164 | draw.merge-path({ 165 | draw.arc( 166 | (0, 0), 167 | radius: mark.size, 168 | start: flip*(90deg + mark.sharpness), 169 | delta: flip*mark.delta, 170 | fill: none, 171 | ) 172 | draw.line((), ((), "|-", (0, flip*1e-1))) 173 | }) 174 | } 175 | } 176 | ), 177 | 178 | cone: ( 179 | size: 8, 180 | radius: 6, 181 | angle: 30deg, 182 | 183 | tip-end: mark => -mark.size, 184 | tail-end: mark => mark.tip-end/2, 185 | tail-origin: mark => mark.tip-end, 186 | 187 | stroke: none, 188 | draw: mark => { 189 | for flip in (+1, -1) { 190 | draw.merge-path({ 191 | draw.arc( 192 | (-mark.size, -flip*1e-1), 193 | radius: mark.radius, 194 | start: 0deg, 195 | stop: flip*mark.angle, 196 | ) 197 | draw.line((), (0, 0)) 198 | }) 199 | } 200 | } 201 | ), 202 | 203 | circle: ( 204 | size: 2, 205 | 206 | tip-end: mark => -mark.size, 207 | tail-end: mark => mark.size, 208 | tip-origin: mark => mark.size + 0.5, 209 | tail-origin: mark => -(mark.size + 0.5), 210 | 211 | fill: none, 212 | 213 | draw: mark => draw.circle((0,0), radius: mark.size, fill: mark.fill), 214 | 215 | cap-offset: (mark, y) => { 216 | let r = mark.size 217 | let o = r - calc.sqrt(calc.max(0, r*r - y*y)) 218 | if not mark.tip { o *= -1 } 219 | o 220 | }, 221 | ), 222 | 223 | square: ( 224 | size: 2, 225 | angle: 0deg, 226 | fill: none, 227 | tip-origin: mark => +(mark.size + 0.5)/calc.cos(mark.angle), 228 | tail-origin: mark => -(mark.size + 0.5)/calc.cos(mark.angle), 229 | tip-end: mark => -mark.size/calc.cos(mark.angle), 230 | tail-end: mark => +mark.size/calc.cos(mark.angle), 231 | draw: mark => { 232 | let x = mark.size 233 | draw.rotate(mark.angle) 234 | draw.rect( 235 | (-x, -x), (+x, +x), 236 | ) 237 | } 238 | ), 239 | 240 | diamond: ( 241 | inherit: "stealth", 242 | size: 4, 243 | angle: 45deg, 244 | stealth: -1, 245 | fill: none, 246 | ), 247 | 248 | bar: ( 249 | size: 4.9, 250 | angle: 90deg, 251 | 252 | tail-origin: mark => calc.min(..mark.extrude), 253 | 254 | draw: mark => draw.line( 255 | (mark.angle, -mark.size), 256 | (mark.angle, +mark.size), 257 | ), 258 | cap-offset: (mark, y) => { 259 | let o = y*calc.tan(mark.angle - 90deg) 260 | // if mark.tip { o *= -1 } 261 | -o 262 | }, 263 | ), 264 | 265 | cross: ( 266 | size: 4, 267 | angle: 45deg, 268 | draw: mark => { 269 | draw.line((+mark.angle, -mark.size), (+mark.angle, +mark.size)) 270 | draw.line((-mark.angle, -mark.size), (-mark.angle, +mark.size)) 271 | }, 272 | 273 | cap-offset: (mark, y) => calc.tan(mark.angle + 90deg)*calc.abs(y), 274 | ), 275 | 276 | bracket: ( 277 | size: 6, 278 | depth: 3, 279 | draw: mark => draw.line( 280 | (-mark.depth, mark.size), 281 | (0, mark.size), 282 | (0, -mark.size), 283 | (-mark.depth, -mark.size), 284 | fill: none, 285 | ), 286 | tail-origin: mark => -mark.depth, 287 | ), 288 | 289 | parenthesis: ( 290 | size: 8, 291 | angle: 50deg, 292 | draw: mark => draw.arc( 293 | (0, 0), 294 | start: -mark.angle, 295 | stop: mark.angle, 296 | radius: mark.size, 297 | anchor: "origin", 298 | fill: none, 299 | ), 300 | tail-end: mark => mark.size, 301 | tip-end: mark => mark.size, 302 | tip-origin: mark => mark.size, 303 | tail-origin: mark => mark.size*calc.cos(mark.angle), 304 | cap-offset: (mark, y) => mark.size*(calc.sqrt(1 - calc.pow(y/mark.size, 2)) - 1), 305 | ), 306 | 307 | hook: ( 308 | size: 2.88, 309 | rim: 0.85, 310 | 311 | tip-origin: mark => mark.size + 0.5, 312 | 313 | stroke: (cap: "round"), 314 | 315 | draw: mark => { 316 | draw.arc( 317 | (0,0), 318 | start: -90deg, 319 | stop: +90deg, 320 | radius: mark.size, 321 | fill: none, 322 | ) 323 | draw.line((), (rel: (-mark.rim, 0))) 324 | }, 325 | ), 326 | 327 | hooks: ( 328 | inherit: "hook", 329 | draw: mark => { 330 | for flip in (-1, +1) { 331 | draw.arc( 332 | (0,0), 333 | start: -flip*90deg, 334 | stop: +flip*90deg, 335 | radius: mark.size, 336 | fill: none, 337 | ) 338 | } 339 | }, 340 | ), 341 | 342 | ">": (inherit: "head", rev: false), 343 | "<": (inherit: "head", rev: true), 344 | 345 | ">>": (inherit: "head", extrude: (-2.88, 0), rev: false), 346 | "<<": (inherit: "head", extrude: (-2.88, 0), rev: true), 347 | 348 | ">>>": (inherit: "head", extrude: (-6, -3, 0), rev: false), 349 | "<<<": (inherit: "head", extrude: (-6, -3, 0), rev: true), 350 | 351 | "|>": (inherit: "solid", rev: false), 352 | "<|": (inherit: "solid", rev: true), 353 | 354 | "}>": (inherit: "stealth", rev: false), 355 | "<{": (inherit: "stealth", rev: true), 356 | 357 | "|": (inherit: "bar"), 358 | "||": (inherit: "bar", extrude: (-3, 0)), 359 | "|||": (inherit: "bar", extrude: (-6, -3, 0)), 360 | 361 | "/": (inherit: "bar", angle: +60deg, rev: false), 362 | "\\": (inherit: "bar", angle: -60deg, rev: false), 363 | 364 | "x": (inherit: "cross"), 365 | "X": (inherit: "cross", size: 7), 366 | 367 | "o": (inherit: "circle"), 368 | "O": (inherit: "circle", size: 4), 369 | "*": (inherit: "circle", fill: auto), 370 | "@": (inherit: "circle", size: 4, fill: auto), 371 | 372 | "[]": (inherit: "square"), 373 | "<>": (inherit: "diamond"), 374 | 375 | "]": (inherit: "bracket", rev: false), 376 | "[": (inherit: "bracket", rev: true), 377 | 378 | ")": (inherit: "parenthesis", rev: false), 379 | "(": (inherit: "parenthesis", rev: true), 380 | 381 | 382 | 383 | // crow's foot notation 384 | crowfoot: ( 385 | many-width: 5, 386 | many-length: 8, 387 | one-width: 5, 388 | zero-width: 3.5, 389 | gap: 3, 390 | first-gap: 5, 391 | many: true, 392 | one: true, 393 | zero: true, 394 | tail-origin: mark => -mark.many-length, 395 | zero-fill: white, 396 | fill: none, 397 | draw: mark => { 398 | let x = 0 399 | if mark.many { 400 | draw.line((0, mark.many-width), (-mark.many-length - .5, 0), (0, -mark.many-width)) 401 | x -= mark.many-length 402 | } 403 | if mark.one { 404 | x -= mark.gap 405 | x = calc.min(x, -mark.first-gap) 406 | draw.line((x, mark.one-width), (x, -mark.one-width)) 407 | } 408 | if mark.zero { 409 | x -= mark.gap 410 | draw.circle((x - mark.zero-width, 0), radius: mark.zero-width, fill: mark.zero-fill) 411 | } 412 | } 413 | ), 414 | "n": (inherit: "crowfoot", zero: false, one: false, many: true), 415 | "n!": (inherit: "crowfoot", zero: false, one: true, many: true), 416 | "n?": (inherit: "crowfoot", zero: true, one: false, many: true), 417 | "1": (inherit: "crowfoot", zero: false, one: true, many: false), 418 | "1!": (inherit: "crowfoot", zero: false, one: true, many: false, extrude: mark => (0, -calc.max(4, mark.gap))), 419 | "1?": (inherit: "crowfoot", zero: true, one: true, many: false), 420 | 421 | ) 422 | 423 | #let MARKS = state("fletcher-marks", DEFAULT_MARKS) 424 | -------------------------------------------------------------------------------- /src/deps.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/cetz:0.3.4" 2 | -------------------------------------------------------------------------------- /src/exports.typ: -------------------------------------------------------------------------------- 1 | #import "deps.typ": cetz 2 | 3 | #import "marks.typ": * 4 | #import "draw.typ": * 5 | #import "shapes.typ" 6 | #import "node.typ": * 7 | #import "edge.typ": * 8 | #import "diagram.typ": * 9 | #import "coords.typ": * 10 | #import "utils.typ" 11 | -------------------------------------------------------------------------------- /src/utils.typ: -------------------------------------------------------------------------------- 1 | #import "deps.typ": cetz 2 | #import cetz: vector 3 | 4 | #let error(message, ..args) = { 5 | let pairs = args.pos().enumerate() + args.named().pairs() 6 | let ticks(x) = "`" + if type(x) == str { x } else { repr(x) } + "`" 7 | for (k, v) in pairs { 8 | if type(v) == array { 9 | let replacement = if v.len() > 0 { 10 | v.map(ticks).join(", ") 11 | } else { "()" } 12 | message = message.replace("#.." + str(k), replacement) 13 | } 14 | if type(v) != str { v = repr(v) } 15 | message = message.replace("#" + str(k), ticks(v)) 16 | } 17 | assert(false, message: message) 18 | } 19 | 20 | 21 | // Replace `auto` with a value 22 | #let map-auto(value, fallback) = if value == auto { fallback } else { value } 23 | 24 | // Make a function propagate `auto` 25 | #let pass-auto(f) = x => if x == auto { x } else { f(x) } 26 | 27 | // Make a function propagage `none` 28 | #let pass-none(f) = x => if x == none { x } else { f(x) } 29 | 30 | #let as-bool(obj, message: "Expected boolean") = { 31 | if type(obj) == bool { obj } 32 | else { error(message + "; got #0.", repr(obj)) } 33 | } 34 | 35 | // for when `stroke` is already in namespace 36 | #let as-stroke(x) = stroke(x) 37 | 38 | #let as-label(x) = { 39 | if type(x) == label { x } 40 | else if type(x) == str { label(x) } 41 | else { error("Expected label or string; got #0.", repr(x)) } 42 | } 43 | 44 | #let as-pair(obj) = { 45 | if type(obj) == array { 46 | if obj.len() == 2 { obj } 47 | else { error("Expected a pair (array of length 2); got #0.", repr(obj))} 48 | } else { (obj, obj) } 49 | } 50 | 51 | #let as-array(obj) = if type(obj) == array { obj } else { (obj,) } 52 | 53 | #let as-number-or-length(obj, message: "Expected a number or length") = { 54 | if type(obj) in (int, float, length) { obj } 55 | else { error(message + "; got #0.", repr(obj)) } 56 | } 57 | 58 | #let as-relative(obj, message: "Expected float or relative length") = { 59 | if type(obj) == relative { obj } 60 | else if type(obj) in (int, float) { obj*100% + 0pt } 61 | else if type(obj) in (ratio, length) { obj + 0% + 0pt } 62 | else { error(message + "; got #0.", repr(obj)) } 63 | } 64 | 65 | #let relative-to-float(t, len: float("inf")*1pt) = { 66 | len = len.to-absolute() 67 | if type(t) in (int, float, ratio) { float(t) } 68 | else if type(t) == length { t.to-absolute()/len } 69 | else if type(t) == relative { float(t.ratio) + t.length.to-absolute()/len } 70 | else { error("Cannot convert #0 to float.", t) } 71 | } 72 | 73 | 74 | #let as-length(obj, message: "Expected a length") = { 75 | if type(obj) == length { obj } 76 | else { error(message + "; got #0.", repr(obj)) } 77 | } 78 | 79 | #let as-angle(obj, message: "Expected an angle") = { 80 | if type(obj) == angle { obj } 81 | else { error(message + "; got #0.", repr(obj)) } 82 | } 83 | 84 | #let stroke-to-dict(s) = { 85 | let s = as-stroke(s) 86 | let d = ( 87 | paint: s.paint, 88 | thickness: s.thickness, 89 | cap: s.cap, 90 | join: s.join, 91 | dash: s.dash, 92 | miter-limit: s.miter-limit, 93 | ) 94 | 95 | // remove auto entries to allow folding strokes by joining dicts 96 | for (key, value) in d { 97 | if value == auto { 98 | let _ = d.remove(key) 99 | } 100 | } 101 | 102 | d 103 | } 104 | 105 | 106 | #let min-max(array) = (calc.min(..array), calc.max(..array)) 107 | #let cumsum(array) = { 108 | let sum = array.at(0) 109 | for i in range(1, array.len()) { 110 | sum += array.at(i) 111 | array.at(i) = sum 112 | } 113 | array 114 | } 115 | 116 | #let vector-len((x, y)) = 1pt*calc.sqrt((x/1pt)*(x/1pt) + (y/1pt)*(y/1pt)) 117 | #let vector-set-len(len, v) = vector.scale(v, len/vector-len(v)) 118 | #let vector-unitless(v) = v.map(x => if type(x) == length { x.pt() } else { x }) 119 | #let vector-2d((x, y, ..z)) = (x, y) 120 | #let vector-max(a, b) = array.zip(a, b).map(vals => calc.max(..vals)) 121 | 122 | #let vector-polar(r, θ) = (r*calc.cos(θ), r*calc.sin(θ)) 123 | #let vector-angle(v) = calc.atan2(..vector-unitless(v)) 124 | #let angle-between(from, to) = vector-angle(vector.sub(to, from)) 125 | 126 | // Ensure angle is in range 0deg <= θ < 360deg 127 | #let wrap-angle-360(θ) = calc.rem-euclid(θ/360deg, 1)*360deg 128 | 129 | // Ensure angle is in range -180deg <= θ <= 180deg 130 | #let wrap-angle-180(θ) = (θ/360deg - calc.round(θ/360deg))*360deg 131 | 132 | #let dir-to-anchor(dir) = { 133 | ( 134 | repr(top): "north", 135 | repr(top + right): "north-east", 136 | repr(right): "east", 137 | repr(bottom + right): "south-east", 138 | repr(bottom): "south", 139 | repr(bottom + left): "south-west", 140 | repr(left): "west", 141 | repr(top + left): "north-west", 142 | ).at(repr(dir)) 143 | } 144 | 145 | #let angle-to-anchor(θ) = { 146 | let i = calc.rem(8*θ/1rad/calc.tau, 8) 147 | ( 148 | "east", 149 | "north-east", 150 | "north", 151 | "north-west", 152 | "west", 153 | "south-west", 154 | "south", 155 | "south-east", 156 | ).at(int(calc.round(i))) 157 | } 158 | 159 | 160 | #let is-length-vector(v) = v.all(x => type(x) == length) 161 | #let is-number-vector(v) = v.all(x => type(x) in (int, float)) 162 | #let is-nan-vector(v) = is-number-vector(v) and v.any(x => float(x).is-nan()) 163 | 164 | 165 | #let lerp(a, b, t) = a*(1 - t) + b*t 166 | 167 | /// Linearly interpolate an array with linear behaviour outside bounds 168 | /// 169 | /// - values (array): Array of lengths defining interpolation function. 170 | /// - index (int, float): Index-coordinate to sample. 171 | /// - spacing (length): Gradient for linear extrapolation beyond array bounds. 172 | #let interp(values, index, spacing: 0pt) = { 173 | let max-index = values.len() - 1 174 | if index < 0 { 175 | values.at(0) + spacing*index 176 | } else if index > max-index { 177 | values.at(-1) + spacing*(index - max-index) 178 | } else { 179 | lerp( 180 | values.at(calc.floor(index)), 181 | values.at(calc.ceil(index)), 182 | calc.fract(index), 183 | ) 184 | } 185 | } 186 | 187 | 188 | /// Inverse of `interp()`. 189 | /// 190 | /// - values (array): Array of lengths defining interpolation function. 191 | /// - value: Value to find the interpolated index of. 192 | /// - spacing (length): Gradient for linear extrapolation beyond array bounds. 193 | #let interp-inv(values, value, spacing: 0pt) = { 194 | let i = 0 195 | while i < values.len() { 196 | if values.at(i) >= value { break } 197 | i += 1 198 | } 199 | let (first, last) = (values.at(0), values.at(-1)) 200 | 201 | // avoids division by zero when numerator and denominator both vanish 202 | let div(a, b) = if calc.abs(a) < 1e-3pt { 0 } else { a/b } 203 | 204 | if value < first { 205 | div(value - first, spacing) 206 | } else if value >= last { 207 | values.len() - 1 + div(value - last, spacing) 208 | } else { 209 | let (prev, nearest) = (values.at(i - 1), values.at(i)) 210 | i - 1 + div(value - prev, nearest - prev) 211 | } 212 | } 213 | 214 | 215 | #let rect-at(center, size) = (-1, +1).map(dir => { 216 | vector.add(center, vector.scale(size, dir/2)) 217 | }) 218 | 219 | #let point-is-in-rect(point, (center, size)) = { 220 | point.zip(center, size).all(((x, o, s)) => { 221 | calc.abs(x - o) <= s/2 222 | }) 223 | } 224 | 225 | #let bounding-rect(points) = { 226 | let (xs, ys) = array.zip(..points) 227 | let p1 = (calc.min(..xs), calc.min(..ys)) 228 | let p2 = (calc.max(..xs), calc.max(..ys)) 229 | ( 230 | center: vector.scale(vector.add(p1, p2), 0.5), 231 | size: vector.sub(p2, p1) 232 | ) 233 | } 234 | 235 | 236 | /// Determine arc between two points with a given bend angle 237 | /// 238 | /// The bend angle is the angle between chord of the arc (line connecting the 239 | /// points) and the tangent to the arc and the first point. 240 | /// 241 | /// Returns a dictionary containing: 242 | /// - `center`: the center of the arc's curvature 243 | /// - `radius` 244 | /// - `start`: the start angle of the arc 245 | /// - `stop`: the end angle of the arc 246 | /// 247 | /// - from (point): 2D vector of initial point. 248 | /// - to (point): 2D vector of final point. 249 | /// - angle (angle): The bend angle between chord of the arc (line connecting the 250 | /// points) and the tangent to the arc and the first point. 251 | /// -> dictionary 252 | /// 253 | /// #diagram(spacing: 2cm, { 254 | /// for (i, θ) in (0deg, 45deg, -90deg).enumerate() { 255 | /// edge((2*i, 0), (2*i + 1, 0), marks: (none, "head"), bend: θ) 256 | /// edge((2*i, 0), (2*i + 1, 0), [#θ], label-side: center, dash: 257 | /// "dotted") 258 | /// } 259 | /// }) 260 | #let get-arc-connecting-points(from, to, angle) = { 261 | // TODO: properly handle trivial arcs 262 | if from == to { to = vector.add(to, (0pt, 1e-4pt)) } 263 | 264 | let mid = vector.scale(vector.add(from, to), 0.5) 265 | let (dx, dy) = vector.sub(to, from) 266 | let perp = (dy, -dx) 267 | 268 | let center = vector.add(mid, vector.scale(perp, 0.5/calc.tan(angle))) 269 | 270 | let radius = vector-len(vector.sub(to, center)) 271 | 272 | let start = angle-between(center, from) 273 | let stop = angle-between(center, to) 274 | 275 | if start < stop and angle > 0deg { start += 360deg } 276 | if start > stop and angle < 0deg { start -= 360deg } 277 | 278 | (center: center, radius: radius, start: start, stop: stop) 279 | } 280 | 281 | /// Return true if a content element is a space or sequence of spaces 282 | #let is-space(el) = { 283 | if el == none { return true } 284 | if repr(el.func()) == "space" { return true } 285 | if repr(el.func()) == "sequence" { return el.children.all(is-space) } 286 | return false 287 | } 288 | 289 | #let is-sequence(it) = { 290 | type(it) == content and repr(it.func()) == "sequence" 291 | } 292 | 293 | #let flatten-sequence-to-array(it) = { 294 | if is-sequence(it) { 295 | it.children.map(flatten-sequence-to-array).join() + () 296 | } else { (it,) } 297 | } 298 | 299 | 300 | // find a node near a given uv coordinate 301 | #let find-node-at(nodes, uv, snap: true) = { 302 | nodes.filter(node => { 303 | if is-nan-vector(node.pos.uv) { return false } 304 | 305 | if snap { 306 | // node must be within a one-unit block around pos 307 | vector.sub(node.pos.uv, uv).all(Δ => calc.abs(Δ) < 0.1) 308 | } else { 309 | node.pos.uv == uv 310 | } 311 | }) 312 | .sorted(key: node => vector.len(vector.sub(node.pos.uv, uv))) 313 | .at(0, default: none) 314 | } 315 | 316 | #let find-node(nodes, key, snap: true) = { 317 | if type(key) == label { 318 | let node = nodes.find(node => node.name == key) 319 | assert(node != none, message: "Couldn't find node with name " + repr(key)) 320 | node 321 | } else if type(key) == array and is-number-vector(key) { 322 | find-node-at(nodes, key, snap: snap) 323 | } else { 324 | none 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | **/out/* 2 | **/diff/* -------------------------------------------------------------------------------- /tests/anchors/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/anchors/ref/1.png -------------------------------------------------------------------------------- /tests/anchors/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/anchors/ref/2.png -------------------------------------------------------------------------------- /tests/anchors/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/anchors/ref/3.png -------------------------------------------------------------------------------- /tests/anchors/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/anchors/ref/4.png -------------------------------------------------------------------------------- /tests/anchors/ref/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/anchors/ref/5.png -------------------------------------------------------------------------------- /tests/anchors/ref/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/anchors/ref/6.png -------------------------------------------------------------------------------- /tests/anchors/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram( 5 | node-stroke: blue, 6 | node((0,0), name: "foo")[Foo], 7 | node((rel: (2,-1), to: ), name: "bar")[Bar], 8 | node(enclose: (, ), stroke: red), 9 | 10 | edge(, , stroke: blue + 1pt), 11 | edge(, "latex-latex", , bend: 45deg), 12 | edge( 13 | (name: "foo", anchor: -45deg), 14 | "x-->", 15 | , 16 | corner: left, 17 | ) 18 | ) 19 | 20 | #pagebreak() 21 | 22 | #diagram( 23 | node-stroke: yellow, 24 | node((1,1), `45deg`, shape: circle, name: "a"), 25 | edge((name: "a", anchor: 45deg), "u", "->", bend: -20deg), 26 | node((0,1), `"north"`, shape: fletcher.shapes.diamond, name: ), 27 | node((1,0), `"south-west"`, shape: fletcher.shapes.triangle, name: ), 28 | edge(, "..", ), 29 | 30 | ) 31 | 32 | #pagebreak() 33 | 34 | #let a = 0deg 35 | #for (name, shape) in fletcher.shapes.ALL_SHAPES { 36 | show raw: set text(0.6em) 37 | diagram( 38 | node((0,0), name, shape: shape, fill: color.hsv(a, 50%, 100%), name: ), 39 | node(, `N`), 40 | node(, `NE`), 41 | node(, `E`), 42 | node(, `SE`), 43 | node(, `S`), 44 | node(, `SW`), 45 | node(, `W`), 46 | node(, `NW`), 47 | ) 48 | linebreak() 49 | a += 27deg 50 | } 51 | 52 | #pagebreak() 53 | 54 | #diagram( 55 | node-stroke: yellow, 56 | node((1,1), `45deg`, shape: circle, name: "a"), 57 | edge((name: "a", anchor: 45deg), "u", "->", bend: -20deg), 58 | node((0,1), `"north"`, shape: fletcher.shapes.diamond, name: ), 59 | node((1,0), `"south-west"`, shape: fletcher.shapes.triangle, name: ), 60 | edge(, "..", ), 61 | 62 | ) 63 | 64 | 65 | #pagebreak() 66 | 67 | Node positions depending on other nodes' anchors 68 | 69 | #diagram( 70 | node((0,0), [Alpha], name: , fill: green), 71 | node((1,1), [Beta], name: , fill: yellow), 72 | node(, $ times.circle $), 73 | edge(, "->", auto), 74 | node((rel: (0pt, -20pt), to: ), $ plus.circle $, inset: 0pt, name: ), 75 | node((rel: (10pt, 0pt), to: ), $ f $), 76 | edge( 77 | , 78 | ((), "-|", (, 50%, )), 79 | ((), "|-", ), 80 | , 81 | "..>", 82 | ) 83 | ) 84 | 85 | #pagebreak() 86 | 87 | Diagram requiring two coordinate resolution passes 88 | 89 | #diagram({ 90 | node((0,0), $A$, name: , stroke: 1pt) 91 | node((1,1), $B$, name: , stroke: 1pt) 92 | node(enclose: (, ), stroke: yellow, name: , text(yellow)[enclose node\ with anchor]) 93 | 94 | // node that depends on the anchors of an enclosing node 95 | node(, $ plus.circle $, name: , stroke: teal) 96 | 97 | // edge depending on 98 | edge(, "->", (rel: (2cm,0cm)), stroke: 1pt + teal) 99 | }) 100 | -------------------------------------------------------------------------------- /tests/cetz-integration/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/cetz-integration/ref/1.png -------------------------------------------------------------------------------- /tests/cetz-integration/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram( 5 | node((0,1), $A$, stroke: 1pt), 6 | node((2,0), [Bézier], stroke: 1pt, shape: fletcher.shapes.diamond), 7 | render: (grid, nodes, edges, options) => { 8 | fletcher.cetz.canvas({ 9 | fletcher.draw-diagram(grid, nodes, edges, debug: options.debug) 10 | 11 | let n1 = fletcher.find-node-at(nodes, (0,1)) 12 | let n2 = fletcher.find-node-at(nodes, (2,0)) 13 | 14 | let θ1 = 0deg 15 | let θ2 = -90deg 16 | 17 | fletcher.get-node-anchor(n1, θ1, p1 => { 18 | fletcher.get-node-anchor(n2, θ2, p2 => { 19 | let c1 = (rel: (θ1, 30pt), to: p1) 20 | let c2 = (rel: (θ2, 70pt), to: p2) 21 | fletcher.cetz.draw.bezier(p1, p2, c1, c2) 22 | fletcher.draw-mark("head", origin: p1, angle: 180deg, stroke: 1pt) 23 | }) 24 | }) 25 | 26 | }) 27 | } 28 | ) 29 | -------------------------------------------------------------------------------- /tests/coords/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/coords/ref/1.png -------------------------------------------------------------------------------- /tests/coords/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | #import "/src/diagram.typ": compute-cell-centers, interpret-axes 4 | 5 | #import "/src/utils.typ": vector-2d, interp, interp-inv 6 | #import "/src/coords.typ": * 7 | 8 | // this test contains no visual output 9 | #show: none 10 | 11 | 12 | = Test `interp()` and `interp-inv()` are inverses 13 | 14 | #let values = (1pt, 2pt, 4pt) 15 | #let indices = (-1, 0, 0.5, 1, 1.75, 3, 4) 16 | 17 | #for i in indices { 18 | let value = interp(values, i, spacing: 10pt) 19 | let i2 = interp-inv(values, value, spacing: 10pt) 20 | assert(i == i2) 21 | } 22 | 23 | #for v in values { 24 | let index = interp-inv(values, v , spacing: 10pt) 25 | let v2 = interp(values, index, spacing: 10pt) 26 | assert(v == v2) 27 | } 28 | 29 | 30 | = Test `uv-to-xy()` and `xy-to-uv()` are inverses 31 | 32 | #for grid in ( 33 | ( 34 | origin: (0, 0), 35 | axes: (ltr, ttb), 36 | ), 37 | ( 38 | origin: (1, -1), 39 | axes: (ttb, rtl), 40 | ), 41 | ) { 42 | let grid = grid + ( 43 | cell-sizes: ( 44 | (36pt, 72pt, 24pt), 45 | (12pt, 48pt) 46 | ), 47 | spacing: (12pt, 48pt), 48 | ) 49 | grid += fletcher.interpret-axes(grid.axes) 50 | grid += fletcher.compute-cell-centers(grid) 51 | 52 | for uv in ((0,0), (1,2), (-5.5, 0.75), (3,1.125)) { 53 | let xy = uv-to-xy(grid, uv) 54 | assert(uv == xy-to-uv(grid, xy)) 55 | assert(xy == uv-to-xy(grid, uv)) 56 | 57 | } 58 | } 59 | 60 | 61 | 62 | 63 | 64 | #let assert-resolve(ctx, lhs, rhs) = { 65 | lhs = vector-2d(resolve(ctx, lhs).at(1)) 66 | rhs = vector-2d(resolve(ctx, rhs).at(1)) 67 | assert(lhs == rhs, message: repr((lhs, rhs))) 68 | } 69 | 70 | = Resolving $u v$ coordinates independently of grid 71 | 72 | #let ctx = default-ctx + ( 73 | target-system: "uv", 74 | transform: cetz.matrix.ident(4), 75 | nodes: ( 76 | "a": (anchors: _ => (100, 100)), 77 | "b": (anchors: _ => (20, 50)), 78 | "o": (anchors: _ => (0, 0)), 79 | ) 80 | ) 81 | 82 | #assert-resolve(ctx, (rel: (2, 3), to: (10, 10)), (12, 13)) 83 | #assert-resolve(ctx, (rel: (v: 3, u: 4), to: "a"), (104, 103)) 84 | #assert-resolve(ctx, (bary: (a: 1, b: 3)), ("a", 75%, "b")) 85 | 86 | #assert(is-nan-vector(resolve(ctx, (45deg, 1cm)).at(1))) 87 | 88 | 89 | 90 | == Polar coordinates 91 | #assert-resolve(ctx, (90deg, 1), (calc.cos(90deg), 1)) 92 | 93 | 94 | 95 | = Resolving $x y$ coordinates 96 | 97 | #let grid = { 98 | let g = ( 99 | origin: (0, 0), 100 | axes: (ltr, btt), 101 | cell-sizes: ( 102 | (36pt, 72pt, 24pt), 103 | (12pt, 48pt) 104 | ), 105 | spacing: (12pt, 48pt), 106 | ) 107 | g += interpret-axes(g.axes) 108 | g += compute-cell-centers(g) 109 | g 110 | } 111 | 112 | #assert(grid.centers == ((18pt, 84pt, 144pt), (6pt, 84pt))) 113 | 114 | #let ctx = default-ctx + ( 115 | target-system: "xyz", 116 | grid: grid, 117 | nodes: ( 118 | "a": (anchors: a => (100, 100)), 119 | "b": (anchors: a => (20, 50)), 120 | "o": (anchors: a => (0, 0)), 121 | ) 122 | ) 123 | 124 | #assert-resolve(ctx, (0, 0), (18pt, 6pt)) 125 | #assert-resolve(ctx, (0, 1), (18pt, 84pt)) 126 | 127 | #assert-resolve(ctx, (rel: (1, 0), to: (0, 2)), (1, 2)) 128 | 129 | 130 | #assert-resolve(ctx, 131 | (rel: (1pt, 1pt), to: (1, 2)), 132 | (rel: (45deg, calc.sqrt(2)*1pt), to: (1, 2)), 133 | ) 134 | 135 | #assert( 136 | resolve(ctx, (-1, 0), (rel: (1, 0))).slice(1) == 137 | resolve(ctx, (-1, 0), (0, 0)).slice(1) 138 | ) 139 | 140 | = Going from $x y$ coordinates to $u v$ 141 | 142 | #let ctx = ctx + (target-system: "uv") 143 | #assert-resolve(ctx, (18pt, 6pt), (0, 0)) 144 | #assert-resolve(ctx, (18pt, 84pt), (0, 1)) 145 | 146 | = Testing grid-dependence of coordinates 147 | 148 | If a grid isn't provided, $x y$-derived coordinates should resolve to #NAN_COORD. 149 | 150 | #let ctx = default-ctx + ( 151 | target-system: "xyz", 152 | ) 153 | 154 | #assert(is-nan-vector(resolve(ctx, (1, 2)).at(1))) 155 | #assert(not is-nan-vector(resolve(ctx, (1pt, 2pt)).at(1))) 156 | 157 | #assert( 158 | resolve(ctx, (1pt, 2pt), (rel: (0deg, 1pt))).slice(1) == 159 | resolve(ctx, (1pt, 2pt), (2pt, 2pt)).slice(1) 160 | ) 161 | 162 | #assert(is-nan-vector(resolve(ctx, (rel: (45deg, 2), to: (1pt, 2pt))).at(1))) 163 | 164 | #assert(resolve(ctx, (1, 2), (rel: (45deg, 2pt))).slice(1).all(is-nan-vector)) 165 | 166 | 167 | #assert(is-grid-independent-uv-coordinate((1,2))) 168 | #assert(not is-grid-independent-uv-coordinate((1pt,2pt))) 169 | 170 | #let uv-coord-is-grid-independent(coord) = { 171 | let ctx = default-ctx + ( 172 | target-system: "uv", 173 | ) 174 | (ctx, coord) = resolve(ctx, coord) 175 | not coord.all(x => type(x) == float and x.is-nan()) 176 | } 177 | 178 | #assert(uv-coord-is-grid-independent((1, 2))) 179 | #assert(uv-coord-is-grid-independent((rel: (+10, 0), to: (1, 2)))) 180 | #assert(not uv-coord-is-grid-independent((1pt, 2pt))) 181 | #assert(not uv-coord-is-grid-independent((rel: (+10pt, 0pt), to: (1, 2)))) 182 | 183 | 184 | = Previous coordinate 185 | 186 | #let ctx = default-ctx + ( 187 | target-system: "xyz", 188 | grid: grid, 189 | prev: (pt: (4, 5)) 190 | ) 191 | #assert-resolve(ctx, (), (4, 5)) 192 | 193 | 194 | = `em` coordinates 195 | 196 | #let ctx = default-ctx + ( 197 | target-system: "uv", 198 | grid: grid, 199 | ) 200 | 201 | #assert-resolve(ctx, 202 | (rel: (0pt, 1em), to: (0pt, 1pt)), 203 | (0pt, 1em + 1pt), 204 | ) 205 | 206 | #assert-resolve(ctx + (em-size: (width: 1cm)), 207 | (0deg, 1pt + 1em), 208 | (0deg, 1pt + 1cm), 209 | ) 210 | -------------------------------------------------------------------------------- /tests/debug/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/debug/ref/1.png -------------------------------------------------------------------------------- /tests/debug/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #block(inset: (y: 3mm))[ 5 | #for debug in range(4) [ 6 | This is an inline #diagram($A edge("->") & B$, debug: debug) diagram. 7 | 8 | ] 9 | ] -------------------------------------------------------------------------------- /tests/diagram/axes/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/axes/ref/1.png -------------------------------------------------------------------------------- /tests/diagram/axes/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/axes/ref/2.png -------------------------------------------------------------------------------- /tests/diagram/axes/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/axes/ref/3.png -------------------------------------------------------------------------------- /tests/diagram/axes/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/axes/ref/4.png -------------------------------------------------------------------------------- /tests/diagram/axes/ref/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/axes/ref/5.png -------------------------------------------------------------------------------- /tests/diagram/axes/ref/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/axes/ref/6.png -------------------------------------------------------------------------------- /tests/diagram/axes/ref/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/axes/ref/7.png -------------------------------------------------------------------------------- /tests/diagram/axes/ref/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/axes/ref/8.png -------------------------------------------------------------------------------- /tests/diagram/axes/ref/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/axes/ref/9.png -------------------------------------------------------------------------------- /tests/diagram/axes/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge, cetz 3 | #import cetz.draw 4 | 5 | #let grid = ( 6 | origin: (0, -1), 7 | axes: (ltr, btt), 8 | centers: ( 9 | (0cm, 1cm, 2cm, 4cm, 5cm), 10 | (0cm, 1cm, 2cm), 11 | ), 12 | cell-sizes: ( 13 | (5mm, 10mm, 0mm, 5mm, 5mm), 14 | (5mm, 10mm, 0mm), 15 | ), 16 | spacing: (1cm, 1cm), 17 | ) 18 | 19 | #let pip(coord, fill) = draw.circle( 20 | coord, 21 | radius: 2pt, 22 | stroke: none, 23 | fill: fill, 24 | ) 25 | 26 | #((ltr, btt), (ltr, ttb), (rtl, ttb), (rtl, btt)).map(axes => { 27 | (axes, axes.rev()).map(axes => [ 28 | #let grid = grid + (axes: axes) + fletcher.interpret-axes(axes) 29 | 30 | #grid.axes 31 | 32 | #cetz.canvas({ 33 | fletcher.draw-debug-axes(grid, floating: false) 34 | pip(fletcher.uv-to-xy(grid, (0,0)), red) 35 | pip(fletcher.uv-to-xy(grid, (1,0)), green) 36 | pip(fletcher.uv-to-xy(grid, (1,.5)), blue) 37 | }) 38 | ]) 39 | }).flatten().join(pagebreak()) 40 | 41 | #pagebreak() 42 | 43 | #( 44 | (ltr, ttb), 45 | (ttb, ltr), 46 | ).map(axes => diagram( 47 | debug: 1, 48 | axes: axes, 49 | node((0,0), $A$), 50 | node((1,0), $B$), 51 | node(enclose: ((0,0), (1,0)), fill: white), 52 | )).join(linebreak()) 53 | -------------------------------------------------------------------------------- /tests/diagram/bounding-box/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/bounding-box/ref/1.png -------------------------------------------------------------------------------- /tests/diagram/bounding-box/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/bounding-box/ref/2.png -------------------------------------------------------------------------------- /tests/diagram/bounding-box/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/bounding-box/ref/3.png -------------------------------------------------------------------------------- /tests/diagram/bounding-box/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/bounding-box/ref/4.png -------------------------------------------------------------------------------- /tests/diagram/bounding-box/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | 5 | #for axes in ( 6 | (ltr, btt), 7 | (rtl, ttb), 8 | (ttb, ltr), 9 | (btt, rtl), 10 | ) { 11 | for coords in ( 12 | ((0, 0), (0, 1)), 13 | ((0, 0), (1, 0)), 14 | ((0, 0), (0.5, 1), (1, 0)), 15 | ((0, 0), (0.5, 0.5), (0, 1)), 16 | ) { 17 | box(rect(diagram( 18 | debug: 1, 19 | axes: axes, 20 | for coord in coords { 21 | node(coord, raw(repr(coord)), stroke: 1pt) 22 | } 23 | ))) 24 | } 25 | pagebreak(weak: true) 26 | } 27 | -------------------------------------------------------------------------------- /tests/diagram/cetz-coords/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/cetz-coords/ref/1.png -------------------------------------------------------------------------------- /tests/diagram/cetz-coords/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/cetz-coords/ref/2.png -------------------------------------------------------------------------------- /tests/diagram/cetz-coords/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | 5 | Polar coordinates 6 | 7 | #diagram( 8 | node-fill: teal.transparentize(60%), 9 | { 10 | node((0,0), [hello], name:
) 11 | let n = 16 12 | for i in range(0, n) { 13 | node((rel: (i*360deg/n, 15mm), to:
), $ ast $, fill: none, inset: 0pt) 14 | edge(
, "<-") 15 | } 16 | } 17 | ) 18 | 19 | #pagebreak() 20 | 21 | Perpendicular coordinates 22 | 23 | #diagram( 24 | node-defocus: 0, 25 | node((100,100), $A$, name: ), 26 | edge("-"), 27 | node((rel: (1,1)), $B$, name: ), 28 | node((, "|-", ), $A tack B$, name: ), 29 | edge(".."), 30 | node((, "-|", ), $A tack.l B$, name: ), 31 | ) 32 | -------------------------------------------------------------------------------- /tests/diagram/implicit-coords/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/implicit-coords/ref/1.png -------------------------------------------------------------------------------- /tests/diagram/implicit-coords/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/implicit-coords/ref/2.png -------------------------------------------------------------------------------- /tests/diagram/implicit-coords/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/implicit-coords/ref/3.png -------------------------------------------------------------------------------- /tests/diagram/implicit-coords/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram(edge((0,0), (1,0), [label], "->")) 5 | #diagram(edge((1,0), [label], "->")) 6 | #diagram(edge([label], "->")) 7 | 8 | #diagram( 9 | node((1,2), [prev]), 10 | edge("->", bend: 45deg), 11 | node((2,1), [next]), 12 | edge((1,2), ".."), 13 | ) 14 | 15 | #pagebreak() 16 | 17 | #diagram( 18 | edge((0,0), "nw", "->"), 19 | node((0,0), [London]), 20 | edge("..|>", bend: 20deg), 21 | edge("<|..", bend: -20deg), 22 | node((1,1), [Paris]), 23 | edge("e", "->"), 24 | edge("s", "->"), 25 | edge("se", "->"), 26 | ) 27 | 28 | #pagebreak() 29 | 30 | #diagram({ 31 | edge((1,0), auto, "..>") 32 | node(name: )[Hello] // first coord is (0,0) 33 | edge("->") 34 | node(name: , (rel: (1,1)))[Bye] 35 | }) 36 | -------------------------------------------------------------------------------- /tests/diagram/inline/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/inline/ref/1.png -------------------------------------------------------------------------------- /tests/diagram/inline/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #for (i, a) in ("->", "=>", "==>").enumerate() [ 5 | Diagram #diagram( 6 | node-inset: 2.5pt, 7 | label-sep: 1pt + i*1pt, 8 | node((0, -i), $A$), 9 | edge((0, -i), (1, -i), text(0.6em, $f$), a), 10 | node((1, -i), $B$), 11 | ) and equation #($A -> B$, $A => B$, $A arrow.triple B$).at(i). \ 12 | ] 13 | 14 | 15 | The formula is #diagram($#[Hello] edge(->) & #[World]$)! 16 | -------------------------------------------------------------------------------- /tests/diagram/math-mode/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/math-mode/ref/1.png -------------------------------------------------------------------------------- /tests/diagram/math-mode/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/math-mode/ref/2.png -------------------------------------------------------------------------------- /tests/diagram/math-mode/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/math-mode/ref/3.png -------------------------------------------------------------------------------- /tests/diagram/math-mode/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/math-mode/ref/4.png -------------------------------------------------------------------------------- /tests/diagram/math-mode/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | = Diagrams in math mode 5 | 6 | The following diagrams should be identical: 7 | 8 | #diagram($ 9 | G edge(f, ->) edge(#(0,1), pi, ->>) & im(f) \ 10 | G slash ker(f) edge(#(1,0), tilde(f), "hook-->") 11 | $) 12 | 13 | #diagram( 14 | node((0,0), $G$), 15 | edge((0,0), (1,0), $f$, "->"), 16 | edge((0,0), (0,1), $pi$, "->>"), 17 | node((1,0), $im(f)$), 18 | node((0,1), $G slash ker(f)$), 19 | edge((0,1), (1,0), $tilde(f)$, "hook-->") 20 | ) 21 | 22 | 23 | #pagebreak() 24 | 25 | = Explicit nodes in math mode 26 | 27 | #diagram( 28 | node-outset: 2pt, 29 | node-corner-radius: 2pt, 30 | $ 31 | A edge(->) & node(sqrt(B), fill: #blue.lighten(70%), inset: #5pt) \ 32 | node(C, stroke: #(red + .3pt), radius: #1em) edge("u", "=") 33 | edge(#(1,0), "..||..") 34 | $, 35 | ) 36 | 37 | #diagram( 38 | node-stroke: 1pt, 39 | $ node(A B C, extrude: #(0,2)) edge(->) & pi r^2 $ 40 | ) 41 | 42 | 43 | #pagebreak() 44 | 45 | = Relative coordinates in math mode 46 | 47 | The following diagrams should be identical: 48 | 49 | #diagram($ 50 | (0,0) edge(#(0,1), #(rel: (0, -1)), ->) & // first non-relative coordinate becomes `from`... 51 | (1,0) edge(#(0,1), "=>") \ // ...unless it is the only coordinate, in which case it becomes `to` 52 | (0,1) edge(#(0,0), "dr", "..>") & 53 | (1,1) edge("u", "-->") // if a single relative coordinate is given, set `from: auto` 54 | $) 55 | 56 | #diagram( 57 | node((0,0), $(0,0)$), 58 | node((1,0), $(1,0)$), 59 | node((0,1), $(0,1)$), 60 | node((1,1), $(1,1)$), 61 | 62 | edge((0,1), (0,0), "->"), 63 | edge((1,0), (0,1), "=>"), 64 | edge((0,0), (1,1), "..>"), 65 | edge((1,1), (1,0), "-->"), 66 | ) 67 | 68 | 69 | #pagebreak() 70 | 71 | = Label side in math mode 72 | 73 | #diagram(spacing: (1cm, 3mm), $ 74 | A edge(f, #left, "->") & B \ 75 | A edge(#center, f, "->") & B \ 76 | A edge(f, "->", #right) & B \ 77 | $) 78 | -------------------------------------------------------------------------------- /tests/diagram/options/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/diagram/options/ref/1.png -------------------------------------------------------------------------------- /tests/diagram/options/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram( 5 | node-stroke: gray.darken(50%) + 1pt, 6 | edge-stroke: green.darken(40%) + .6pt, 7 | node-fill: green.lighten(80%), 8 | node-outset: 2pt, 9 | label-sep: 0pt, 10 | node((0,1), $A$), 11 | node((1,0), $sin compose cos compose tan$, fill: none), 12 | node((2,1), $C$), 13 | edge("o-o", stroke: orange), 14 | node((3,1), $D$, shape: "rect"), 15 | edge((0,1), (1,0), $sigma$, "-}>", bend: -45deg), 16 | edge((2,1), (1,0), $f$, "<{-"), 17 | ) 18 | -------------------------------------------------------------------------------- /tests/edge/arguments/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/arguments/ref/1.png -------------------------------------------------------------------------------- /tests/edge/arguments/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | // this test contains no visual output 5 | #show: none 6 | 7 | = `auto` vertices 8 | 9 | #assert(edge(, ).value.vertices == (, )) 10 | #assert(edge(, auto).value.vertices == (, auto)) 11 | #assert(edge(auto, auto).value.vertices == (auto, auto)) 12 | #assert(edge().value.vertices == (auto, auto)) 13 | #assert(edge().value.vertices == (auto, )) 14 | 15 | = Coordinate vertices 16 | 17 | #assert(edge((1, 2), (3, 4)).value.vertices == ((1, 2), (3, 4))) 18 | #assert(edge((1, 2), "r").value.vertices == ((1, 2), (rel: (1, 0)))) 19 | #assert(edge((1, 2), (rel: (1, 0))).value.vertices == ((1, 2), (rel: (1, 0)))) 20 | 21 | #assert(edge("r") == edge(auto, "r")) 22 | #assert(edge("r,u") == edge(auto, "r", "u")) 23 | #assert(edge((), (, 50%, )).value.vertices == ((), (, 50%, ))) 24 | 25 | = Vertices and marks 26 | #assert(edge(, , "->") == edge(vertices: (, ), marks: "->")) 27 | #assert(edge(, "->", ) == edge(vertices: (, ), marks: "->")) 28 | #assert(edge(, "->") == edge(vertices: (auto, ), marks: "->")) 29 | #assert(edge("->", ) == edge(vertices: (auto, ), marks: "->")) 30 | 31 | = Marks and labels 32 | 33 | #assert(edge() == edge(marks: (), label: none)) 34 | #assert(edge([Hi]) == edge(marks: (), label: [Hi])) 35 | #assert(edge("->", [Hi]) == edge(marks: "->", label: [Hi])) 36 | #assert(edge([Hi], "->") == edge(marks: "->", label: [Hi])) 37 | 38 | = Error messages 39 | // #edge((), "->", (), "=>") 40 | // #edge((), "r", vertices: (,)) 41 | // #edge(2, 3, 4) 42 | // #edge(right, label-side: left) 43 | // #edge([], label: (,)) 44 | -------------------------------------------------------------------------------- /tests/edge/bend-marks/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/bend-marks/ref/1.png -------------------------------------------------------------------------------- /tests/edge/bend-marks/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram( 5 | spacing: (10mm, 6mm), 6 | for (i, bend) in (0deg, 40deg, 80deg, -90deg).enumerate() { 7 | let x = 2*i 8 | ( 9 | (">->->->",), 10 | ("<<->>",), 11 | (">>-<<",), 12 | (marks: ((inherit: "hook", rev: true), "head")), 13 | (marks: ((inherit: "hook", rev: true), "hook'")), 14 | (marks: ("bar", "bar", "bar")), 15 | (marks: ("||", "||")), 16 | ("<=>",), 17 | ("triple",), 18 | (marks: ("o", "O")), 19 | (marks: ((inherit: "solid", rev: true), "solid")), 20 | ).enumerate().map(((i, args)) => { 21 | edge((x, i), (x + 1, i), ..args, bend: bend) 22 | }).join() 23 | 24 | } 25 | ) 26 | -------------------------------------------------------------------------------- /tests/edge/bend/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/bend/ref/1.png -------------------------------------------------------------------------------- /tests/edge/bend/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/bend/ref/2.png -------------------------------------------------------------------------------- /tests/edge/bend/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram( 5 | cell-size: 3cm, 6 | node((0,0), "from"), 7 | node((1,0), "to"), 8 | for θ in (0deg, 20deg, -50deg) { 9 | edge((0,0), (1,0), $#θ$, bend: θ, marks: (none, "head"), label-side: center) 10 | } 11 | ) 12 | 13 | #pagebreak() 14 | 15 | #for (i, to) in ((0,1), (1,0), (calc.sqrt(1/2),-calc.sqrt(1/2))).enumerate() { 16 | diagram(debug: 0, { 17 | node((0,0), $A$) 18 | node(to, $B$) 19 | let N = 6 20 | range(N + 1).map(x => (x/N - 0.5)*2*120deg).map(θ => edge((0,0), to, bend: θ, marks: ">->")).join() 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /tests/edge/calculations/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/edge.typ": normalize-label-pos 3 | 4 | // this test contains no visual output 5 | #show: none 6 | 7 | #let assert-position-almost-equal(a, b) = { 8 | a.position = a.position + 0pt + 0% 9 | b.position = b.position + 0pt + 0% 10 | 11 | let dl = calc.abs(a.position.length - b.position.length) 12 | let dr = calc.abs(a.position.ratio - b.position.ratio) 13 | 14 | assert(a.segment == b.segment 15 | and dl < 1e-3pt 16 | and dr < 1e-3% 17 | , message: repr(a) + " != " + repr(b)) 18 | } 19 | 20 | = `normalize-label-pos` 21 | 22 | == Odd number of segments 23 | 24 | #assert-position-almost-equal(normalize-label-pos(-10%, 5), (segment: 0, position: -50%)) 25 | #assert-position-almost-equal(normalize-label-pos(0%, 5), (segment: 0, position: 0%)) 26 | #assert-position-almost-equal(normalize-label-pos(10%, 5), (segment: 0, position: 50%)) 27 | #assert-position-almost-equal(normalize-label-pos(20%, 5), (segment: 0, position: 100%)) 28 | #assert-position-almost-equal(normalize-label-pos(30%, 5), (segment: 1, position: 50%)) 29 | #assert-position-almost-equal(normalize-label-pos(40%, 5), (segment: 1, position: 100%)) 30 | #assert-position-almost-equal(normalize-label-pos(50%, 5), (segment: 2, position: 50%)) 31 | #assert-position-almost-equal(normalize-label-pos(60%, 5), (segment: 2, position: 100%)) 32 | #assert-position-almost-equal(normalize-label-pos(70%, 5), (segment: 3, position: 50%)) 33 | #assert-position-almost-equal(normalize-label-pos(80%, 5), (segment: 3, position: 100%)) 34 | #assert-position-almost-equal(normalize-label-pos(90%, 5), (segment: 4, position: 50%)) 35 | #assert-position-almost-equal(normalize-label-pos(100%, 5), (segment: 4, position: 100%)) 36 | #assert-position-almost-equal(normalize-label-pos(110%, 5), (segment: 4, position: 150%)) 37 | 38 | == Even number of segments 39 | 40 | #assert-position-almost-equal(normalize-label-pos(-10%, 4), (segment: 0, position: -40%)) 41 | #assert-position-almost-equal(normalize-label-pos(0%, 4), (segment: 0, position: 0%)) 42 | #assert-position-almost-equal(normalize-label-pos(10%, 4), (segment: 0, position: 40%)) 43 | #assert-position-almost-equal(normalize-label-pos(20%, 4), (segment: 0, position: 80%)) 44 | #assert-position-almost-equal(normalize-label-pos(30%, 4), (segment: 1, position: 20%)) 45 | #assert-position-almost-equal(normalize-label-pos(40%, 4), (segment: 1, position: 60%)) 46 | #assert-position-almost-equal(normalize-label-pos(50%, 4), (segment: 1, position: 100%)) 47 | #assert-position-almost-equal(normalize-label-pos(60%, 4), (segment: 2, position: 40%)) 48 | #assert-position-almost-equal(normalize-label-pos(70%, 4), (segment: 2, position: 80%)) 49 | #assert-position-almost-equal(normalize-label-pos(80%, 4), (segment: 3, position: 20%)) 50 | #assert-position-almost-equal(normalize-label-pos(90%, 4), (segment: 3, position: 60%)) 51 | #assert-position-almost-equal(normalize-label-pos(100%, 4), (segment: 3, position: 100%)) 52 | #assert-position-almost-equal(normalize-label-pos(110%, 4), (segment: 3, position: 140%)) 53 | 54 | == Different inputs 55 | 56 | #assert-position-almost-equal(normalize-label-pos(100%, 5), (segment: 4, position: 100%)) 57 | #assert-position-almost-equal(normalize-label-pos(1, 5), (segment: 4, position: 100%)) 58 | #assert-position-almost-equal(normalize-label-pos(1.0, 5), (segment: 4, position: 100%)) 59 | 60 | 61 | == With length 62 | 63 | #assert-position-almost-equal(normalize-label-pos(100% + 10pt, 5), (segment: 4, position: 100% + 10pt)) 64 | #assert-position-almost-equal(normalize-label-pos(-10pt, 5), (segment: 0, position: 0% - 10pt)) 65 | 66 | #assert-position-almost-equal(normalize-label-pos(1em, 5), (segment: 0, position: 0% + 1em)) 67 | #assert-position-almost-equal(normalize-label-pos(10pt - 1em, 5), (segment: 0, position: 0% + 10pt - 1em)) 68 | #assert-position-almost-equal(normalize-label-pos(10% + 10pt - 1em, 5), (segment: 0, position: 50% + 10pt - 1em)) 69 | 70 | == With segment 71 | 72 | #assert-position-almost-equal(normalize-label-pos((3, 27%), 5), (segment: 3, position: 27%)) 73 | // Segment out of range 74 | // #assert-position-almost-equal(normalize-label-pos((5, 27%), 3), (segment: 5, position: 27%)) 75 | -------------------------------------------------------------------------------- /tests/edge/corner/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/corner/ref/1.png -------------------------------------------------------------------------------- /tests/edge/corner/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #let around = ( 5 | (-1,+1), (+1,+1), 6 | (-1,-1), (+1,-1), 7 | ) 8 | 9 | #for dir in (left, right) { 10 | pad(1mm, diagram( 11 | spacing: 1cm, 12 | node((0,0), [#dir]), 13 | { 14 | for c in around { 15 | node(c, $#c$) 16 | edge((0,0), c, $f$, marks: ( 17 | (inherit: "head", rev: false, pos: 0), 18 | (inherit: "head", rev: false, pos: 0.33), 19 | (inherit: "head", rev: false, pos: 0.66), 20 | (inherit: "head", rev: false, pos: 1), 21 | ), "double", corner: dir) 22 | } 23 | } 24 | )) 25 | } 26 | 27 | #for dir in (left, right) { 28 | pad(1mm, diagram( 29 | // debug: 4, 30 | spacing: 1cm, 31 | axes: (ltr, btt), 32 | node((0,0), [#dir]), 33 | { 34 | for c in around { 35 | node(c, $#c$) 36 | edge((0,0), c, $f$, marks: ( 37 | (inherit: "head", rev: false, pos: 0), 38 | (inherit: "head", rev: false, pos: 0.33), 39 | (inherit: "head", rev: false, pos: 0.66), 40 | (inherit: "head", rev: false, pos: 1), 41 | ), "double", corner: dir) 42 | } 43 | } 44 | )) 45 | } 46 | -------------------------------------------------------------------------------- /tests/edge/crossing/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/crossing/ref/1.png -------------------------------------------------------------------------------- /tests/edge/crossing/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/crossing/ref/2.png -------------------------------------------------------------------------------- /tests/edge/crossing/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram({ 5 | edge((0,1), (1,0)) 6 | edge((0,0), (1,1), "crossing") 7 | edge((2,1), (3,0), "|-|", bend: -20deg) 8 | edge((2,0), (3,1), "<=>", crossing: true, bend: 20deg) 9 | }) 10 | 11 | #pagebreak() 12 | 13 | #diagram($ 14 | edge("dr", "-", "crossing") & 15 | edge("dl", "-", "crossing") 16 | $) 17 | 18 | #diagram($$) // invisible, but shouldn't crash 19 | -------------------------------------------------------------------------------- /tests/edge/decorations/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/decorations/ref/1.png -------------------------------------------------------------------------------- /tests/edge/decorations/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram(spacing: 4em, $ 5 | A edge(<->, "wave") & B edge(<->, "zigzag") & C edge(<->, "coil") 6 | $) 7 | 8 | #diagram(spacing: (2em, 3em), $ 9 | e^- edge("dr", "-<|-") & & & & & edge("dl", "-|>-") e^+ \ 10 | & edge("wave") & edge(gamma, "wave", bend: #80deg) edge("wave", bend: #(-80deg)) & edge("wave") \ 11 | e^+ edge("ur", "-|>-") & & & & & edge("ul", "-<|-") e^- \ 12 | $) 13 | 14 | 15 | #diagram(spacing: (1cm, 0mm), $ 16 | A edge(~>) & B \ 17 | A edge(<~) & B \ 18 | A edge("<~>") & B \ 19 | A edge(">~<") & B \ 20 | $) -------------------------------------------------------------------------------- /tests/edge/loops/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/loops/ref/1.png -------------------------------------------------------------------------------- /tests/edge/loops/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram( 5 | node((0,0), [Loop], shape: circle, fill: eastern), 6 | edge((0,0), "->", (0,0), bend: 120deg, layer: 2, loop-angle: 0deg), 7 | edge((0,0), "->", (0,0), bend: 120deg, layer: 2, loop-angle: 90deg), 8 | edge((0,0), "->", (0,0), bend: 120deg, layer: 2, loop-angle: 180deg), 9 | edge((0,0), "->", (0,0), bend: 120deg, layer: 2, loop-angle: 270deg), 10 | node((1,0), [Boop], shape: circle, fill: orange.mix(red)), 11 | edge((), (), "->", bend: 120deg, layer: 2, loop-angle: -135deg), 12 | edge((), (), "->", bend: 120deg, layer: 2, loop-angle: -45deg), 13 | edge((), (), "->", bend: 120deg, layer: 2, loop-angle: +45deg), 14 | edge((), (), "->", bend: 120deg, layer: 2, loop-angle: +135deg), 15 | ) 16 | -------------------------------------------------------------------------------- /tests/edge/mark-shorthands/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/mark-shorthands/ref/1.png -------------------------------------------------------------------------------- /tests/edge/mark-shorthands/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | 5 | $ 6 | #for i in ( 7 | "->", 8 | "<-", 9 | ">-<", 10 | "<->", 11 | "<=>", 12 | "<==>", 13 | ">==<", 14 | "|->", 15 | "|=>", 16 | ">->", 17 | "<<->>", 18 | ">>-<<", 19 | ">>>-}>", 20 | "hook-hook", 21 | "hook'--hook'", 22 | "|=|", 23 | "||-||", 24 | "|||-|||", 25 | "/--\\", 26 | "\\=\\", 27 | "/=/", 28 | "x-X", 29 | ">>-<<", 30 | "harpoon-harpoon", 31 | "harpoon'-<<", 32 | "<--hook'", 33 | "|..|", 34 | "hooks--hooks", 35 | "o-o", 36 | "O-o", 37 | "*-@", 38 | "o==O", 39 | "||->>", 40 | "<|-|>", 41 | "|>-<|", 42 | "-|-", 43 | "hook-/->", 44 | "<{-}>", 45 | ) { 46 | $ #block(inset: 2pt, fill: white.darken(5%), raw(repr(i))) 47 | &= #align(center, box(width: 15mm, diagram(edge((0,0), (1,0), marks: i), debug: 0))) \ $ 48 | } 49 | $ 50 | 51 | -------------------------------------------------------------------------------- /tests/edge/options/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/options/ref/1.png -------------------------------------------------------------------------------- /tests/edge/options/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/options/ref/2.png -------------------------------------------------------------------------------- /tests/edge/options/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/options/ref/3.png -------------------------------------------------------------------------------- /tests/edge/options/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | Explicit named arguments versus implicit positional arguments. 5 | 6 | Each row should be the same thing repeated. 7 | 8 | #let ab = node((0,0), $A$) + node((1,0), $B$) 9 | #grid( 10 | columns: (3cm,)*3, 11 | 12 | diagram(ab, edge((0,0), (1,0), marks: "->")), 13 | diagram(ab, edge((0,0), (1,0), "->")), 14 | diagram($A edge(->) & B$), 15 | 16 | diagram(ab, edge((0,0), (1,0), label: $pi$)), 17 | diagram(ab, edge((0,0), (1,0), $pi$)), 18 | diagram($A edge(pi) & B$), 19 | 20 | diagram(ab, edge((0,0), (1,0), marks: "|->", label: $tau$)), 21 | diagram(ab, edge((0,0), (1,0), "|->", $tau$)), 22 | diagram($A edge(tau, |->) & B$), 23 | 24 | diagram(ab, edge((0,0), (1,0), marks: "->>", label: $+$)), 25 | diagram(ab, edge((0,0), (1,0), "->>", $+$)), 26 | diagram($A edge(->>, +) & B$), 27 | ) 28 | 29 | #pagebreak() 30 | 31 | #diagram( 32 | axes: (ltr, btt), 33 | edge((0,0), (1,1), "->", "double", bend: 45deg), 34 | edge((1,0), (0,1), "->>", "crossing"), 35 | edge((1,1), (2,1), $f$, "|->"), 36 | edge((0,0), (1,0), "-", "dashed"), 37 | ) 38 | 39 | #pagebreak() 40 | 41 | Diagram and edge stroke options. 42 | 43 | $ 44 | 45 | #block(diagram( 46 | edge-stroke: red, 47 | edge(stroke: 2pt), 48 | )) 49 | &equiv 50 | #line(stroke: (paint: red, thickness: 2pt, cap: "round")) 51 | \ 52 | #block(diagram( 53 | edge-stroke: 2pt, 54 | edge(stroke: (cap: "butt")) 55 | )) 56 | &equiv 57 | #line(stroke: 2pt) 58 | \ 59 | #block(diagram( 60 | edge-stroke: 1pt + green, 61 | edge(dash: "dashed") 62 | )) 63 | &equiv 64 | #line(stroke: (paint: green, dash: "dashed", cap: "round")) 65 | \ 66 | #block(diagram( 67 | edge(stroke: none) 68 | )) 69 | &equiv & "(none)" 70 | 71 | $ 72 | 73 | -------------------------------------------------------------------------------- /tests/edge/poly/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/poly/ref/1.png -------------------------------------------------------------------------------- /tests/edge/poly/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/poly/ref/2.png -------------------------------------------------------------------------------- /tests/edge/poly/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/poly/ref/3.png -------------------------------------------------------------------------------- /tests/edge/poly/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/poly/ref/4.png -------------------------------------------------------------------------------- /tests/edge/poly/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #for radius in (none, 0pt, 10pt) { 5 | diagram( 6 | mark-scale: 150%, 7 | node((0,0), $A$), 8 | edge(">->", vertices: ( 9 | (0,0), 10 | (1.1,0), 11 | (1,1), 12 | (3,2), 13 | (4,1), 14 | (1.3,2), 15 | (2,0), 16 | (3,0), 17 | (2,1), 18 | ), kind: "poly", 19 | corner-radius: radius, 20 | extrude: (4, 0, -4) 21 | ), 22 | node((2,1), $B$), 23 | ) 24 | } 25 | 26 | #pagebreak() 27 | 28 | Rounded corners without stroke 29 | 30 | #for radius in (none, 0pt, 10pt) { 31 | diagram( 32 | mark-scale: 150%, 33 | node((0,0), $A$), 34 | edge(">->", vertices: ( 35 | (0,0), 36 | (1.1,0), 37 | (1,1), 38 | (3,2), 39 | (4,1), 40 | (1.3,2), 41 | (2,0), 42 | (3,0), 43 | (2,1), 44 | ), kind: "poly", 45 | stroke: none, 46 | corner-radius: radius, 47 | extrude: (4, 0, -4) 48 | ), 49 | node((2,1), $B$), 50 | ) 51 | } 52 | 53 | #pagebreak() 54 | 55 | #for dash in ("dashed", "loosely-dashed", "dotted") { 56 | diagram( 57 | edge( 58 | "r,ddd,r,u,ll,u,rr", 59 | "<->", 60 | corner-radius: 5mm, 61 | stroke: (dash: dash) 62 | ) 63 | ) 64 | } 65 | 66 | #pagebreak() 67 | 68 | Dynamic corner radius 69 | 70 | #let c = gradient.linear(..color.map.rainbow) 71 | #diagram( 72 | edge-corner-radius: 6pt, 73 | for t in range(13).map(i => i/12) { 74 | let a = t*180deg 75 | edge("r", (rel: (-a, 1)), "->", stroke: c.sample(t*100%)) 76 | } 77 | ) -------------------------------------------------------------------------------- /tests/edge/shift/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/shift/ref/1.png -------------------------------------------------------------------------------- /tests/edge/shift/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/shift/ref/2.png -------------------------------------------------------------------------------- /tests/edge/shift/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: 5cm, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | 5 | #(3.4pt, 0.1).map(shift => [ 6 | Edge shift by #type(shift): 7 | 8 | #diagram( 9 | node((0,0), $A$), 10 | edge((0,0), (1,0), "->", shift: +shift), 11 | edge((0,0), (1,0), "<-", shift: -shift), 12 | node((1,0), $B$), 13 | ) 14 | 15 | #diagram( 16 | node((0,0), $A$), 17 | edge((0,0), (1,0), "->", shift: +shift, bend: 40deg), 18 | edge((0,0), (1,0), "<-", shift: -shift, bend: 40deg), 19 | node((1,0), $B$), 20 | ) 21 | 22 | #diagram( 23 | node-stroke: 1pt, 24 | node((0,0), $A$), 25 | edge((0,0), (1,0), (1,1), "->", shift: +shift), 26 | edge((0,0), (1,0), (1,1), "->", shift: -shift), 27 | edge((0,0), (1,1), "->", corner: left, shift: +shift), 28 | edge((0,0), (1,1), "->", corner: left, shift: -shift), 29 | node((1,1), $A B C$), 30 | ) 31 | 32 | ]).join(pagebreak()) 33 | 34 | -------------------------------------------------------------------------------- /tests/edge/snap-to/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/snap-to/ref/1.png -------------------------------------------------------------------------------- /tests/edge/snap-to/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/snap-to/ref/2.png -------------------------------------------------------------------------------- /tests/edge/snap-to/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/edge/snap-to/ref/3.png -------------------------------------------------------------------------------- /tests/edge/snap-to/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #let label = table( 5 | columns: (12mm, 4mm, 4mm, 4mm), 6 | `Node`, none, `4`, none, 7 | stroke: (x, y) => if 0 < x and x < 3 { (x: 1pt) }, 8 | ) 9 | 10 | Allow `snap-to` to be `none`. 11 | 12 | #diagram( 13 | node-stroke: 1pt, 14 | edge-stroke: 1pt, 15 | mark-scale: 50%, 16 | node((0,0), label, inset: 0pt, corner-radius: 3pt), 17 | edge((0.09,0), (0,1), "*-straight", snap-to: (none, auto)), 18 | edge((0.41,0), (1,1), "*-straight", snap-to: none), 19 | node((0,1), `Subnode`), 20 | ) 21 | 22 | #pagebreak() 23 | 24 | #diagram( 25 | node(enclose: ((0,0), (0,3)), fill: yellow, snap: false), 26 | for i in range(4) { 27 | node((0,i), [#i], fill: white) 28 | edge((0,i), (1,i), "<->") 29 | 30 | }, 31 | node([B], enclose: ((1,0), (1,3)), fill: yellow), 32 | for i in range(3) { 33 | edge((0,i), (0,i + 1), "o..o") 34 | }, 35 | ) 36 | 37 | #pagebreak() 38 | 39 | #diagram( 40 | node-stroke: 0.6pt, 41 | node-fill: white, 42 | node((0,1), [X]), 43 | edge("->-", bend: 40deg), 44 | node((1,0), [Y], name: ), 45 | node($Sigma$, enclose: ((0,1), ), 46 | stroke: teal, fill: teal.lighten(90%), 47 | snap: -1, // prioritise other nodes when auto-snapping 48 | name: ), 49 | edge(, , "->"), 50 | node((2.5,0.5), [Z], name: ), 51 | ) -------------------------------------------------------------------------------- /tests/gallery/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/gallery/ref/1.png -------------------------------------------------------------------------------- /tests/gallery/ref/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/gallery/ref/10.png -------------------------------------------------------------------------------- /tests/gallery/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/gallery/ref/2.png -------------------------------------------------------------------------------- /tests/gallery/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/gallery/ref/3.png -------------------------------------------------------------------------------- /tests/gallery/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/gallery/ref/4.png -------------------------------------------------------------------------------- /tests/gallery/ref/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/gallery/ref/5.png -------------------------------------------------------------------------------- /tests/gallery/ref/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/gallery/ref/6.png -------------------------------------------------------------------------------- /tests/gallery/ref/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/gallery/ref/7.png -------------------------------------------------------------------------------- /tests/gallery/ref/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/gallery/ref/8.png -------------------------------------------------------------------------------- /tests/gallery/ref/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/gallery/ref/9.png -------------------------------------------------------------------------------- /tests/gallery/test.typ: -------------------------------------------------------------------------------- 1 | #let render-example(name) = { 2 | let src = read("/docs/gallery/" + name + ".typ") 3 | .replace(regex("@preview/fletcher:\d+\.\d+.\d+"), "/src/exports.typ") 4 | eval(src, mode: "markup") 5 | } 6 | 7 | #( 8 | "01-commutative", 9 | "02-algebra-cube", 10 | "03-ml-architecture", 11 | "04-io-flowchart", 12 | "05-digraph", 13 | "06-node-groups", 14 | "07-uml-diagram", 15 | "08-tree", 16 | "09-feynman-diagram", 17 | "10-category-theory", 18 | ).map(render-example).join() -------------------------------------------------------------------------------- /tests/hiding/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/hiding/ref/1.png -------------------------------------------------------------------------------- /tests/hiding/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | = Hiding 5 | 6 | #rect(inset: 0pt, diagram({ 7 | node((0,0), [Can't see me]) 8 | edge("->", bend: 20deg) 9 | node((1,1), [Can see me]) 10 | })) 11 | 12 | #rect(inset: 0pt, diagram({ 13 | fletcher.hide({ 14 | node((0,0), [Can't see me]) 15 | edge("->", bend: 20deg) 16 | }) 17 | node((1,1), [Can see me]) 18 | })) 19 | -------------------------------------------------------------------------------- /tests/issues/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/issues/ref/1.png -------------------------------------------------------------------------------- /tests/issues/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/issues/ref/2.png -------------------------------------------------------------------------------- /tests/issues/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/issues/ref/3.png -------------------------------------------------------------------------------- /tests/issues/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/issues/ref/4.png -------------------------------------------------------------------------------- /tests/issues/ref/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/issues/ref/5.png -------------------------------------------------------------------------------- /tests/issues/ref/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/issues/ref/6.png -------------------------------------------------------------------------------- /tests/issues/ref/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/issues/ref/7.png -------------------------------------------------------------------------------- /tests/issues/ref/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/issues/ref/8.png -------------------------------------------------------------------------------- /tests/issues/ref/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/issues/ref/9.png -------------------------------------------------------------------------------- /tests/issues/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #show link: it => { 5 | pagebreak(weak: true) 6 | underline(it) 7 | } 8 | 9 | https://github.com/Jollywatt/typst-fletcher/issues/64 10 | 11 | #diagram($A times B$) 12 | #par(justify: true, diagram($A times B$)) 13 | 14 | 15 | https://github.com/Jollywatt/typst-fletcher/issues/74 16 | 17 | #for bend in (left, right) { 18 | diagram( 19 | node((0, 0), [A]), 20 | for side in (left, right) { 21 | edge( 22 | "->", 23 | corner: bend, 24 | label: side, 25 | label-pos: 1em, 26 | label-side: side, 27 | ) 28 | }, 29 | node((1, 1), [B]), 30 | ) 31 | } 32 | 33 | https://github.com/Jollywatt/typst-fletcher/issues/81 34 | 35 | #diagram( 36 | debug: 3, 37 | node((0,0), [1], fill: red, name: <1>), 38 | node((1,0), [2], fill: blue, name: <2>), 39 | node([],enclose: (<1>,<2>), name: <3>, stroke: black), 40 | edge(<1.south>, "d"), 41 | edge(<3.south>,"d") 42 | ) 43 | 44 | https://github.com/Jollywatt/typst-fletcher/issues/38 45 | 46 | #align(center, diagram( 47 | node((0, 0), $1$), 48 | edge("->", [this is a very long label], floating: true), 49 | node((0, 1), $2$), 50 | )) 51 | #align(center, diagram( 52 | node((0, 0), $1$), 53 | edge("->"), 54 | node((0, 1), $2$), 55 | )) 56 | 57 | https://github.com/Jollywatt/typst-fletcher/issues/89 58 | 59 | #import fletcher.shapes 60 | 61 | #diagram( 62 | node-stroke: 1pt, 63 | node((0,0), shape: shapes.ellipse, [Test]) 64 | ) 65 | 66 | https://github.com/Jollywatt/typst-fletcher/issues/93 67 | 68 | #box(width: 9cm)[ 69 | Test anchors on enclose nodes whose position is specified with absolute coordinates. 70 | ] 71 | 72 | #diagram( 73 | 74 | node((0, 0), [Origin], name: ), 75 | node((rel: (2cm, 1cm), to: ), $+1$, name:

), 76 | node((rel: (2cm, 0cm), to: ), $0$, name: ), 77 | node((rel: (2cm, -1cm), to: ), $-1$, name: ), 78 | 79 | edge(, auto, "->"), 80 | node(enclose: (

, , ), name: , stroke: black), 81 | 82 | for anchor in ( 83 | "north", 84 | "north-east", 85 | "east", 86 | "south-east", 87 | "south", 88 | "south-west", 89 | "west", 90 | "north-west", 91 | "center", 92 | ) { 93 | let pos = (name: , anchor: anchor) 94 | node(pos, text(red, { 95 | $ dot.circle $ 96 | place(text(0.4em, raw(anchor))) 97 | })) 98 | } 99 | ) 100 | 101 | https://github.com/Jollywatt/typst-fletcher/issues/100 102 | 103 | #table( 104 | columns: 3, 105 | ..range(1, 10).map(x => { 106 | diagram( 107 | spacing: (2mm, 6mm), 108 | 109 | node-stroke: 1pt, 110 | 111 | node((0, 0), "aa" , shape: circle, name: ), 112 | node((1, 1), "d" * x, shape: circle), 113 | node((0, 2), "bb" , shape: circle, name: ), 114 | 115 | edge(, "r,r,u,u", ), 116 | ) 117 | }) 118 | ) 119 | 120 | https://github.com/Jollywatt/typst-fletcher/issues/105 121 | 122 | #diagram( 123 | node-stroke: 0.2pt, 124 | edge-stroke: 1pt, 125 | node-corner-radius: 5pt, 126 | { 127 | node((1, 1), [x], shape: rect, name: ) 128 | node((, "-|", ), radius: 1pt, fill: blue, stroke: none, name: ) 129 | for x in range(30) { 130 | edge((, "-|", ), (rel: (0pt, x*1pt)), [xedge]) // caused division by zero 131 | } 132 | } 133 | ) 134 | 135 | https://github.com/Jollywatt/typst-fletcher/issues/113 136 | 137 | #diagram( 138 | node-stroke: 1pt, 139 | cell-size: 3cm, 140 | node((0, 0), [A], shape: circle, name: ), 141 | node(enclose: (,), [], inset: 1cm, fill: blue.lighten(50%), name: ), 142 | node((0, 1), [B], name: ), 143 | node((, 50%, ), $ + $) 144 | ) 145 | -------------------------------------------------------------------------------- /tests/label/angle/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/angle/ref/1.png -------------------------------------------------------------------------------- /tests/label/angle/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/angle/ref/2.png -------------------------------------------------------------------------------- /tests/label/angle/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/angle/ref/3.png -------------------------------------------------------------------------------- /tests/label/angle/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #for bend in (0deg, 30deg) { 5 | [`label-angle` shown in center] 6 | grid( 7 | columns: 2, 8 | gutter: 5mm, 9 | ..(left, right, top, bottom, auto).map(angle => diagram( 10 | // debug: 2, 11 | spacing: 2cm, 12 | node((0,0))[#angle], 13 | ( 14 | "r", 15 | "ru", 16 | "u", 17 | "ul", 18 | "l", 19 | "ld", 20 | "d", 21 | "dr", 22 | ).map(to => { 23 | edge( 24 | to, 25 | "-|>", 26 | $ pi r^2 $, 27 | label-angle: angle, 28 | label-side: center, 29 | bend: bend, 30 | label-pos: 0.3, 31 | ) 32 | for side in (left, right) { 33 | edge( 34 | to, 35 | stroke: none, 36 | text(0.8em)[#side], 37 | label-angle: angle, 38 | label-side: side, 39 | bend: bend, 40 | label-pos: 0.8, 41 | ) 42 | } 43 | }) 44 | )), 45 | ) 46 | pagebreak() 47 | } 48 | 49 | #diagram( 50 | spacing: 2cm, 51 | node((0, 0), [#auto]), 52 | ( 53 | "rr", 54 | "rrd", 55 | "rdd", 56 | "dd", 57 | "ldd", 58 | "lld", 59 | "ll", 60 | "llu", 61 | "uul", 62 | "uur", 63 | "uu", 64 | "urr", 65 | ).map(d => edge(d, "->", $sqrt(a^2 + b^2)$, label-angle: auto, center)) 66 | ) 67 | -------------------------------------------------------------------------------- /tests/label/fill/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/fill/ref/1.png -------------------------------------------------------------------------------- /tests/label/fill/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram( 5 | crossing-fill: gray, 6 | edge((0,0), (1,1), "->", [no fill], label-side: right), 7 | edge((1,0), (2,1), "->", [fill], label-side: center), 8 | edge((2,0), (3,1), "->", [fill], label-fill: true), 9 | ) 10 | \ 11 | #diagram( 12 | crossing-fill: gray, 13 | edge((0,0), (1,1), "->", [blue fill], label-side: right, label-fill: blue), 14 | edge((1,0), (2,1), "->", [no fill], label-side: center, label-fill: false), 15 | edge((2,0), (3,1), "->", [no fill]), 16 | ) 17 | -------------------------------------------------------------------------------- /tests/label/pos-with-segment/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos-with-segment/ref/1.png -------------------------------------------------------------------------------- /tests/label/pos-with-segment/ref/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos-with-segment/ref/10.png -------------------------------------------------------------------------------- /tests/label/pos-with-segment/ref/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos-with-segment/ref/11.png -------------------------------------------------------------------------------- /tests/label/pos-with-segment/ref/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos-with-segment/ref/12.png -------------------------------------------------------------------------------- /tests/label/pos-with-segment/ref/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos-with-segment/ref/13.png -------------------------------------------------------------------------------- /tests/label/pos-with-segment/ref/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos-with-segment/ref/14.png -------------------------------------------------------------------------------- /tests/label/pos-with-segment/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos-with-segment/ref/2.png -------------------------------------------------------------------------------- /tests/label/pos-with-segment/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos-with-segment/ref/3.png -------------------------------------------------------------------------------- /tests/label/pos-with-segment/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos-with-segment/ref/4.png -------------------------------------------------------------------------------- /tests/label/pos-with-segment/ref/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos-with-segment/ref/5.png -------------------------------------------------------------------------------- /tests/label/pos-with-segment/ref/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos-with-segment/ref/6.png -------------------------------------------------------------------------------- /tests/label/pos-with-segment/ref/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos-with-segment/ref/7.png -------------------------------------------------------------------------------- /tests/label/pos-with-segment/ref/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos-with-segment/ref/8.png -------------------------------------------------------------------------------- /tests/label/pos-with-segment/ref/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos-with-segment/ref/9.png -------------------------------------------------------------------------------- /tests/label/pos-with-segment/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | 5 | 6 | #let make-diagram(label-pos) = diagram( 7 | edge-corner-radius: 10pt, 8 | edge((0, 0), "d,drr,ddd", "-}>", 9 | [#label-pos], 10 | label-side: center, 11 | label-pos: label-pos, 12 | ) 13 | ) 14 | 15 | #for i in ( 16 | (0, -50%), 17 | (0, 0%), 18 | (0, 50%), 19 | (0, 100%), 20 | (0, 150%), 21 | (1, 50%), 22 | (2, -0.5), 23 | (2, 0), 24 | (2, 12pt), 25 | (2, 0.25), 26 | (2, 0.5), 27 | (2, 0.75), 28 | (2, 100%-6pt), 29 | (2, 100%+6pt), 30 | ) { 31 | make-diagram(i) 32 | pagebreak(weak: true) 33 | } 34 | 35 | #for i in ( 36 | (-1, 50%), // Segment must be non-negative 37 | (3, 50%), // Segment out of range 38 | ) { 39 | assert-panic(() => make-diagram(i)) 40 | } 41 | -------------------------------------------------------------------------------- /tests/label/pos/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos/ref/1.png -------------------------------------------------------------------------------- /tests/label/pos/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos/ref/2.png -------------------------------------------------------------------------------- /tests/label/pos/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos/ref/3.png -------------------------------------------------------------------------------- /tests/label/pos/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos/ref/4.png -------------------------------------------------------------------------------- /tests/label/pos/ref/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos/ref/5.png -------------------------------------------------------------------------------- /tests/label/pos/ref/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/pos/ref/6.png -------------------------------------------------------------------------------- /tests/label/pos/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #for pos in (0.2, 20%, 20pt, 2em, 100% - 10pt, (0, 50%)) [ 5 | #raw("label-pos: " + repr(pos)) 6 | 7 | #for w in (20mm, 40mm) { 8 | diagram(spacing: (w, 10mm), { 9 | node((0,0), [A]) 10 | edge([X], label-pos: pos, label-side: center) 11 | edge([X], label-pos: pos, label-side: center, bend: -40deg) 12 | edge((0,0), (20mm, 15mm), ((), "-|", (1,0)), (1,0), [X], label-pos: pos, label-side: center) 13 | node((1,0), [B]) 14 | }) 15 | linebreak() 16 | } 17 | #pagebreak(weak: true) 18 | ] -------------------------------------------------------------------------------- /tests/label/side-auto/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/side-auto/ref/1.png -------------------------------------------------------------------------------- /tests/label/side-auto/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | Default placement should be above the line. 5 | 6 | #let around = ( 7 | (-1,+1), ( 0,+1), (+1,+1), 8 | (-1, 0), (+1, 0), 9 | (-1,-1), ( 0,-1), (+1,-1), 10 | ) 11 | 12 | #diagram( 13 | spacing: 2cm, 14 | axes: (ltr, ttb), 15 | for p in around { 16 | edge(p, (0,0), $f$) 17 | }, 18 | ) 19 | 20 | Reversed $y$-axis: 21 | 22 | #diagram( 23 | spacing: 2cm, 24 | axes: (ltr, btt), 25 | for p in around { 26 | edge(p, (0,0), $f$) 27 | }, 28 | ) -------------------------------------------------------------------------------- /tests/label/side/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/side/ref/1.png -------------------------------------------------------------------------------- /tests/label/side/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram(spacing: (3cm, 1cm), { 5 | for (i, a) in (left, center, right).enumerate() { 6 | for (j, θ) in (-30deg, 0deg, 50deg).enumerate() { 7 | edge((j, 2*i), (j, 2*i - 1), label: a, "->", label-side: a, bend: θ) 8 | } 9 | } 10 | }) 11 | 12 | #diagram(spacing: 1.5cm, { 13 | for (i, a) in (left, center, right).enumerate() { 14 | for (j, θ) in (-30deg, 0deg, 50deg).enumerate() { 15 | edge((2*i, j), (2*i + 1, j), label: a, "->", label-side: a, bend: θ) 16 | } 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /tests/label/size/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/size/ref/1.png -------------------------------------------------------------------------------- /tests/label/size/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/size/ref/2.png -------------------------------------------------------------------------------- /tests/label/size/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | 5 | #diagram( 6 | label-size: 0.8em, 7 | label-sep: 2pt, 8 | node((0, 0), [A]), 9 | edge("rd", $h$, right), 10 | edge($f$), 11 | node((1, 0), [B]), 12 | edge($g$, left), 13 | node((1, 1), [C]), 14 | ) 15 | 16 | #pagebreak() 17 | 18 | 19 | #diagram(label-size: 0.8em, $ 20 | f edge("d", ->) edge(->, #$ f $) & f \ 21 | f edge("ur", ->) 22 | $) -------------------------------------------------------------------------------- /tests/label/wrapper/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/label/wrapper/ref/1.png -------------------------------------------------------------------------------- /tests/label/wrapper/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram( 5 | crossing-fill: blue.lighten(80%), 6 | edge($ f $, label-wrapper: it => rect( 7 | it.label, fill: it.label-fill, inset: 0pt, stroke: .1pt + blue, 8 | )) 9 | ) 10 | 11 | #diagram( 12 | label-wrapper: it => circle( 13 | align(center + horizon, $ #it.label $), fill: it.label-fill, inset: 1pt, stroke: blue, 14 | radius: 7pt, 15 | ), 16 | $ 17 | A edge(->, f) & B edge("d", ->, g, #right) \ 18 | C edge("u", ->, i) & D edge("l", ->, j) 19 | $ 20 | ) 21 | -------------------------------------------------------------------------------- /tests/mark/cap-offsets/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/cap-offsets/ref/1.png -------------------------------------------------------------------------------- /tests/mark/cap-offsets/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | #import "/src/marks.typ": * 4 | 5 | 6 | #context align(center, MARKS.get().values().map(mark => { 7 | let e = 2.5 8 | diagram(edge( 9 | (0,0), (1,0), 10 | marks: (mark, mark), 11 | extrude: (-e,0,+e) 12 | )) 13 | }).join(linebreak())) 14 | -------------------------------------------------------------------------------- /tests/mark/debug/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/1.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/10.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/11.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/12.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/13.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/14.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/15.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/16.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/17.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/18.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/19.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/2.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/20.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/21.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/22.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/23.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/24.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/25.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/26.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/27.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/28.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/29.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/3.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/30.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/31.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/32.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/33.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/34.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/35.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/36.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/37.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/38.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/39.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/4.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/40.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/41.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/42.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/43.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/43.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/44.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/45.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/46.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/46.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/47.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/48.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/49.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/49.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/5.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/50.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/51.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/51.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/52.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/6.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/7.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/8.png -------------------------------------------------------------------------------- /tests/mark/debug/ref/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/debug/ref/9.png -------------------------------------------------------------------------------- /tests/mark/debug/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher 3 | 4 | 5 | #context fletcher.MARKS.get().pairs().map(((key, mark)) => [ 6 | #raw(key) 7 | #set align(center) 8 | #fletcher.mark-debug(mark, show-offsets: true) 9 | #fletcher.mark-demo(mark) 10 | ]).join(pagebreak()) 11 | -------------------------------------------------------------------------------- /tests/mark/gallery/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/gallery/ref/1.png -------------------------------------------------------------------------------- /tests/mark/gallery/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: 15cm, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #context table( 5 | columns: (1fr,)*6, 6 | stroke: none, 7 | ..fletcher.MARKS.get().pairs().map(((k, v)) => [ 8 | #set align(center) 9 | #raw(k) \ 10 | #diagram(spacing: 18mm, edge(stroke: 1pt, marks: (v, v))) 11 | ]), 12 | ) -------------------------------------------------------------------------------- /tests/mark/math-matching/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/math-matching/ref/1.png -------------------------------------------------------------------------------- /tests/mark/math-matching/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: 6cm, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | Compare to $->$, $=>$, $arrow.triple$, $arrow.twohead$, $arrow.hook$, $|->$. 5 | 6 | #let (result-color, target-color) = (rgb("f066"), rgb("0bf5")) 7 | 8 | #text(result-color)[Our output] versus #text(target-color)[reference symbol] in default math font. 9 | \ 10 | #set text(10em) 11 | #diagram( 12 | spacing: 0.825em, 13 | crossing-fill: none, 14 | label-sep: 0.0915em, 15 | edge-stroke: result-color, 16 | for (i, a) in ( 17 | ("->", $->$, 18 | 0em, 0.029), 19 | ("=>", $=>$, 20 | 0em, 0.02), 21 | ("==>", $arrow.triple$, 22 | 0em, 0.048), 23 | ("->>", $->>$, 24 | 0em, 0.053), 25 | ("hook->", $arrow.hook$, 26 | 0.024em, 0.057), 27 | ("|->", $|->$, 28 | 0em, 0.004), 29 | ).enumerate() { 30 | let (marks, label, δl, δr) = a 31 | edge( 32 | (0, i), (1 + δr,i), 33 | align(horizon, box(height: 0em, move(dx: δl - 0.28em, text(target-color, label)))), 34 | marks: marks, 35 | label-anchor: "west", 36 | label-pos: 0, 37 | ) 38 | }, 39 | ) -------------------------------------------------------------------------------- /tests/mark/math-mode/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/math-mode/ref/1.png -------------------------------------------------------------------------------- /tests/mark/math-mode/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #table( 5 | columns: 3, 6 | align: horizon, 7 | [Math], [Mark], [Diagram], 8 | ..( 9 | $->$, $-->$, $<-$, $<->$, $<-->$, 10 | $->>$, $<<-$, 11 | $>->$, $<-<$, 12 | $=>$, $==>$, $<==$, $<=>$, $<==>$, 13 | $|->$, $|=>$, 14 | $~>$, $<~$, 15 | $arrow.hook$, $arrow.hook.l$, 16 | ).map(x => { 17 | let unicode = x.body.text 18 | (x,) 19 | if unicode in fletcher.MARK_SYMBOL_ALIASES { 20 | let marks = fletcher.MARK_SYMBOL_ALIASES.at(unicode) 21 | (raw(marks), diagram(edge((0,0), (1,0), marks: marks))) 22 | } else { 23 | (text(red)[none!],) * 2 24 | } 25 | }).flatten() 26 | ) 27 | -------------------------------------------------------------------------------- /tests/mark/scale/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/scale/ref/1.png -------------------------------------------------------------------------------- /tests/mark/scale/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/scale/ref/2.png -------------------------------------------------------------------------------- /tests/mark/scale/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/scale/ref/3.png -------------------------------------------------------------------------------- /tests/mark/scale/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | #import "/src/marks.typ": * 4 | 5 | #context for scale in (100%, 200%) [ 6 | #pagebreak(weak: true) 7 | 8 | #let mark = fletcher.MARKS.get().head 9 | 10 | #mark-debug(mark + (scale: scale)) 11 | #mark-demo(mark + (scale: scale)) 12 | 13 | #diagram(edge(marks: (mark + (scale: scale), mark + (scale: scale)))) 14 | #diagram(edge(marks: (mark, mark), mark-scale: scale)) 15 | #diagram(edge(marks: (mark, mark)), mark-scale: scale) 16 | 17 | #diagram(edge("triple", marks: (mark + (scale: scale), mark + (scale: scale)))) 18 | #diagram(edge("triple", marks: (mark, mark), mark-scale: scale)) 19 | #diagram(edge("triple", marks: (mark, mark)), mark-scale: scale) 20 | 21 | ] 22 | 23 | #pagebreak() 24 | 25 | #diagram(mark-scale: 100%, edge("cone-latex")) \ 26 | #diagram(mark-scale: 50%, edge("cone-latex")) -------------------------------------------------------------------------------- /tests/mark/state/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/state/ref/1.png -------------------------------------------------------------------------------- /tests/mark/state/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram(edge("<->")) 5 | 6 | #fletcher.MARKS.update(m => m + ( 7 | "<": (inherit: "stealth", rev: true), 8 | ">": (inherit: "stealth", rev: false), 9 | )) 10 | 11 | #diagram(edge("<->")) 12 | -------------------------------------------------------------------------------- /tests/mark/stealth/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/stealth/ref/1.png -------------------------------------------------------------------------------- /tests/mark/stealth/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/stealth/ref/2.png -------------------------------------------------------------------------------- /tests/mark/stealth/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/stealth/ref/3.png -------------------------------------------------------------------------------- /tests/mark/stealth/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/stealth/ref/4.png -------------------------------------------------------------------------------- /tests/mark/stealth/ref/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/stealth/ref/5.png -------------------------------------------------------------------------------- /tests/mark/stealth/ref/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/stealth/ref/6.png -------------------------------------------------------------------------------- /tests/mark/stealth/ref/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/mark/stealth/ref/7.png -------------------------------------------------------------------------------- /tests/mark/stealth/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | #import "/src/marks.typ": * 4 | 5 | #context for stealth in (0.8, 0.5, 0.3, 0, -0.5, -1, -1.5) [ 6 | #pagebreak(weak: true) 7 | 8 | #let mark = fletcher.MARKS.get().stealth + (stealth: stealth) 9 | 10 | #mark-debug(mark + (stealth: stealth)) 11 | #mark-demo(mark + (stealth: stealth)) 12 | 13 | #diagram(edge(marks: (mark, mark))) 14 | #diagram(edge(bend: 60deg, marks: (mark, mark))) 15 | #diagram(edge(bend: 60deg, marks: (mark + (rev: false), mark + (rev: true)))) 16 | ] 17 | -------------------------------------------------------------------------------- /tests/node/defocus/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/defocus/ref/1.png -------------------------------------------------------------------------------- /tests/node/defocus/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #let around = ( 5 | (-1,+1), ( 0,+1), (+1,+1), 6 | (-1, 0), (+1, 0), 7 | (-1,-1), ( 0,-1), (+1,-1), 8 | ) 9 | 10 | #grid( 11 | columns: 2, 12 | ..(-10, -1, -.25, 0, +.25, +1, +10).map(defocus => { 13 | ((7em, 3em), (3em, 7em)).map(((w, h)) => { 14 | align(center + horizon, diagram( 15 | node-defocus: defocus, 16 | node-inset: 0pt, 17 | { 18 | node((0,0), rect(width: w, height: h, inset: 0pt, align(center + horizon)[#defocus])) 19 | for p in around { 20 | edge(p, (0,0)) 21 | } 22 | })) 23 | }) 24 | }).join() 25 | ) 26 | 27 | -------------------------------------------------------------------------------- /tests/node/enclose/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/enclose/ref/1.png -------------------------------------------------------------------------------- /tests/node/enclose/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/enclose/ref/2.png -------------------------------------------------------------------------------- /tests/node/enclose/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/enclose/ref/3.png -------------------------------------------------------------------------------- /tests/node/enclose/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/enclose/ref/4.png -------------------------------------------------------------------------------- /tests/node/enclose/ref/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/enclose/ref/5.png -------------------------------------------------------------------------------- /tests/node/enclose/ref/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/enclose/ref/6.png -------------------------------------------------------------------------------- /tests/node/enclose/ref/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/enclose/ref/7.png -------------------------------------------------------------------------------- /tests/node/enclose/ref/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/enclose/ref/8.png -------------------------------------------------------------------------------- /tests/node/enclose/ref/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/enclose/ref/9.png -------------------------------------------------------------------------------- /tests/node/enclose/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram( 5 | node-stroke: 1pt, 6 | { 7 | node((0,0), [Hello], name: ) 8 | node((2,0), [Bonjour], name: ) 9 | node((1,1), [Quack], name: ) 10 | 11 | let a = (inset: 5pt, corner-radius: 5pt) 12 | node(enclose: (, ), ..a, stroke: teal, name: ) 13 | node((0,0), enclose: (, ), ..a, stroke: orange, name: ) 14 | edge(, , stroke: teal, "->") 15 | edge(, , stroke: orange, "->") 16 | }, 17 | ) 18 | 19 | #pagebreak() 20 | 21 | #diagram( 22 | node-stroke: .7pt, 23 | edge-stroke: .7pt, 24 | spacing: 10pt, 25 | 26 | node((0,1), [volume]), 27 | node((0,2), [gain]), 28 | node((0,3), [fine]), 29 | 30 | edge((0,1), "r", "->", snap-to: (auto, )), 31 | edge((0,2), "r", "->", snap-to: (auto, )), 32 | edge((0,3), "r", "->", snap-to: (auto, )), 33 | 34 | // a node that encloses/spans multiple grid points, 35 | node($Sigma$, enclose: ((1,1), (1,3)), inset: 10pt, name: ), 36 | 37 | edge((1,1), "r,u", "->", snap-to: (, auto)), 38 | node((2,0), $ times $, radius: 8pt), 39 | ) 40 | 41 | #pagebreak() 42 | 43 | #diagram({ 44 | let c = rgb(..orange.components().slice(0,3), 50%) 45 | edge("l", "o-o") 46 | node((0,0), `R1`, radius: 5mm, fill: c) 47 | edge("o-o") 48 | node((1,0), `R2`, radius: 5mm, fill: c) 49 | edge("u", "o-o") 50 | edge("r", "o-o") 51 | node(`L7`, enclose: ((0,0), (1,0)), stroke: red + 0.5pt, 52 | extrude: (0,2), snap: false) 53 | }) 54 | 55 | #pagebreak() 56 | 57 | #diagram( 58 | node-stroke: .7pt, 59 | edge-stroke: .7pt, 60 | node((0,1), $ a $, radius: 10pt), 61 | node((0,2), $ b $, radius: 10pt), 62 | edge((0,1), "r", "->", snap-to: (auto, )), 63 | edge((0,2), "r", "->", snap-to: (auto, )), 64 | node($ Sigma $, enclose: ((1,1), (1,2)), name: ), 65 | edge((1,1), "r", "->", snap-to: (, auto)), 66 | edge((1,2), "r", "->", snap-to: (, auto)), 67 | node((2,1), $ x $, radius: 10pt), 68 | node((2,2), $ y $, radius: 10pt), 69 | ) 70 | 71 | #pagebreak() 72 | 73 | Enclosing absolutely positioned nodes 74 | 75 | #diagram( 76 | node-inset: 0pt, 77 | for i in range(7) { 78 | let a = 30deg*i 79 | node((a, 1cm), [#i], name: str(i)) 80 | 81 | let labels = range(i + 1).map(str).map(label) 82 | node(enclose: labels, fill: blue.transparentize(70%)) 83 | }, 84 | ) 85 | 86 | 87 | #pagebreak() 88 | 89 | Enclosing CeTZ coordinates 90 | 91 | #diagram({ 92 | node((0,0), [1], name: <1>) 93 | node((1,1), [2], name: <2>) 94 | node(enclose: ((0,0), <2>), fill: teal, inset: 0pt) 95 | node(enclose: ((<1>, 50%, <2>), (rel: (0pt, 0pt), to: <2>)), fill: yellow, inset: 0pt) 96 | }) 97 | 98 | #pagebreak() 99 | 100 | Nested enclose nodes 101 | 102 | #diagram(node-inset: 2mm, { 103 | node((0,0), circle(fill: red)) 104 | node((rel: (15mm, 8mm)), circle(fill: blue, radius: 2mm), name: <1>) 105 | node(enclose: ((0,0), <1>), stroke: 1pt, name: , corner-radius: 2mm) 106 | node((1,.7), circle(fill: green), name: <2>) 107 | node(enclose: (, <2>), stroke: 1pt, corner-radius: 4mm, name: ) 108 | node((-1,0), circle(fill: yellow, radius: 2mm), name: <3>) 109 | node(enclose: (, <3>), stroke: 1pt, corner-radius: 6mm, name: ) 110 | }) 111 | 112 | #pagebreak() 113 | 114 | #import fletcher.shapes 115 | #diagram( 116 | node-inset: 4pt, 117 | node-fill: teal, 118 | node((0,0), $A$, name: ), 119 | node((2,0), $B$, name: ), 120 | node((2,1), $C$, name: ), 121 | node(enclose: (, ), shape: shapes.brace.with(label: $oo$)), 122 | node(enclose: (, ), shape: shapes.bracket.with(dir: right, sep: 1em, label: [label])), 123 | node(enclose: (, , ), shape: shapes.stretched-glyph.with(glyph: $integral$, dir: left)), 124 | node(enclose: , shape: shapes.paren.with(dir: left, label: [X], label-sep: 0mm)) 125 | ) 126 | 127 | #pagebreak() 128 | #import fletcher.shapes 129 | 130 | #diagram( 131 | node-fill: blue.transparentize(80%), 132 | node((0,0), [1], name: <1>), 133 | edge("=>"), 134 | node((1,0.0), [2], name: <2>, shape: shapes.diamond), 135 | node(enclose: (<1>, <2>), shape: shapes.brace.with(dir: top, label: $f$, fill: olive)), 136 | ) -------------------------------------------------------------------------------- /tests/node/extrude/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/extrude/ref/1.png -------------------------------------------------------------------------------- /tests/node/extrude/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram( 5 | node-outset: 4pt, 6 | spacing: (15mm, 8mm), 7 | node-stroke: black + 0.5pt, 8 | node((0, 0), $s_1$, ), 9 | node((1, 0), $s_2$, extrude: (-1.5, 1.5), fill: blue.lighten(70%)), 10 | edge((0, 0), (1, 0), "->", label: $a$, bend: 20deg), 11 | edge((0, 0), (0, 0), "->", label: $b$, bend: 120deg), 12 | edge((1, 0), (0, 0), "->", label: $b$, bend: 20deg), 13 | edge((1, 0), (1, 0), "->", label: $a$, bend: 120deg), 14 | edge((1,0), (2,0), "->>"), 15 | node((2,0), $s_3$, extrude: (+1, -1), stroke: 1pt, fill: red.lighten(70%)), 16 | ) 17 | 18 | Extrusion by multiples of stroke thickness: 19 | 20 | #diagram( 21 | node((0,0), `outer`, stroke: 1pt, extrude: (-1, +1), fill: green), 22 | node((1,0), `inner`, stroke: 1pt, extrude: (+1, -1), fill: green), 23 | node((2,0), `middle`, stroke: 1pt, extrude: (0, +2, -2), fill: green), 24 | ) 25 | 26 | Extrusion by absolute lengths: 27 | 28 | #diagram( 29 | node((0,0), `outer`, stroke: 1pt, extrude: (-1mm, 0pt), fill: green), 30 | node((1,0), `inner`, stroke: 1pt, extrude: (0, +.5em, -2pt), fill: green), 31 | ) 32 | -------------------------------------------------------------------------------- /tests/node/label-align/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/label-align/ref/1.png -------------------------------------------------------------------------------- /tests/node/label-align/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/label-align/ref/2.png -------------------------------------------------------------------------------- /tests/node/label-align/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/label-align/ref/3.png -------------------------------------------------------------------------------- /tests/node/label-align/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/label-align/ref/4.png -------------------------------------------------------------------------------- /tests/node/label-align/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram( 5 | spacing: 5pt, 6 | node((0,0), [Hi], height: 15mm, stroke: 1pt + red), 7 | node((1,0), align(top)[Hi], height: 15mm, stroke: 1pt + green), 8 | node((2,0), align(bottom)[Hi], height: 15mm, stroke: 1pt + blue), 9 | node((0,1), [Hi], stroke: 1pt + red), 10 | node((1,1), align(left)[Hi], stroke: 1pt + green), 11 | node((2,1), align(right)[Hi], stroke: 1pt + blue), 12 | node((0,2), [Hi], width: 15mm, stroke: 1pt + red), 13 | node((1,2), align(left)[Hi], width: 15mm, stroke: 1pt + green), 14 | node((2,2), align(right)[Hi], width: 15mm, stroke: 1pt + blue), 15 | ) 16 | 17 | #pagebreak() 18 | 19 | #diagram( 20 | spacing: 5pt, 21 | node-fill: yellow, 22 | node((0,0), [Automatic width]), 23 | node((0,1), align(left)[Explicit width causes wrapping], width: 35mm), 24 | ) 25 | 26 | #pagebreak() 27 | 28 | #diagram( 29 | node(align(top + left, box(fill: orange)[aligned \ content]), enclose: ((0,1), (1,0)), fill: yellow, inset: 0pt), 30 | node((1,0), [node], fill: green), 31 | node((0,1), [x \ y], fill: green), 32 | ) 33 | 34 | #pagebreak() 35 | 36 | The baseline #diagram($A edge(->) & B$) should align. -------------------------------------------------------------------------------- /tests/node/layer/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/layer/ref/1.png -------------------------------------------------------------------------------- /tests/node/layer/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram( 5 | spacing: (10mm, 5mm), // wide columns, narrow rows 6 | node-stroke: 1pt, // outline node shapes 7 | edge-stroke: 1pt, // make lines thicker 8 | mark-scale: 60%, // make arrowheads smaller 9 | edge((-2,0), "r,u,r", "..|>", $f$, label-side: left, layer: -2), 10 | edge((-2,0), "r,d,r", "-|>", $g$), 11 | node((0,-1), $F(s)$, fill: white, layer: -2), 12 | node((0,+1), $G(s)$, fill: white), 13 | edge((0,+1), (1,0), "-|>", corner: left), 14 | edge((0,-1), (1,0), "..|>", corner: right, layer: -2), 15 | node((1,0), text(white, $ plus.circle $), inset: 2pt, fill: black), 16 | edge("-|>"), 17 | node( 18 | enclose: ((0,+1), (0,-1)), 19 | fill: rgb("fa6c"), 20 | stroke: none, 21 | inset: 10pt, 22 | snap: false, 23 | ), 24 | ) -------------------------------------------------------------------------------- /tests/node/name/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/name/ref/1.png -------------------------------------------------------------------------------- /tests/node/name/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #diagram( 5 | node((0,0), [A], name: ), 6 | node((2,0), [B], name: "b"), 7 | edge(, , [by label], ">>-}>"), 8 | edge(, "rrr", "--", snap-to: (, auto)) 9 | ) 10 | -------------------------------------------------------------------------------- /tests/node/shapes/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/shapes/ref/1.png -------------------------------------------------------------------------------- /tests/node/shapes/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/shapes/ref/2.png -------------------------------------------------------------------------------- /tests/node/shapes/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/shapes/ref/3.png -------------------------------------------------------------------------------- /tests/node/shapes/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/shapes/ref/4.png -------------------------------------------------------------------------------- /tests/node/shapes/ref/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/shapes/ref/5.png -------------------------------------------------------------------------------- /tests/node/shapes/ref/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/shapes/ref/6.png -------------------------------------------------------------------------------- /tests/node/shapes/ref/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/shapes/ref/7.png -------------------------------------------------------------------------------- /tests/node/shapes/ref/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/shapes/ref/8.png -------------------------------------------------------------------------------- /tests/node/shapes/ref/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/shapes/ref/9.png -------------------------------------------------------------------------------- /tests/node/shapes/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge, shapes 3 | 4 | #diagram( 5 | node-stroke: 1pt, 6 | node-outset: 5pt, 7 | axes: (ltr, ttb), 8 | node((0,0), $A$, radius: 5mm), 9 | edge("->"), 10 | node((1,1), [crowded], shape: shapes.house, fill: blue.lighten(90%)), 11 | edge("..>", bend: 30deg), 12 | node((0,2), $B$, shape: shapes.diamond), 13 | edge((0,0), "d,ru,d", "=>"), 14 | 15 | edge((1,1), "rd", bend: -40deg), 16 | node((2,2), `cool`, shape: shapes.pill), 17 | edge("->"), 18 | node((1,3), [_amazing_], shape: shapes.parallelogram), 19 | 20 | node((2,0), [robots], shape: shapes.hexagon), 21 | node((2,3), [squashed], shape: shapes.ellipse), 22 | edge("u", "->", bend: -30deg), 23 | 24 | ) 25 | #pagebreak() 26 | 27 | Diagram `node-shape` option 28 | 29 | #diagram( 30 | node-shape: circle, 31 | node-fill: yellow, 32 | 33 | node((0,0), [A]), 34 | node((1,0), [A B C]), 35 | ) 36 | 37 | #diagram( 38 | node-shape: rect, 39 | node-fill: orange, 40 | 41 | node((0,0), [A]), 42 | node((1,0), [A B C]), 43 | ) 44 | 45 | 46 | #pagebreak() 47 | 48 | #set align(center) 49 | 50 | 51 | #for (name, shape) in shapes.ALL_SHAPES { 52 | if type(shape) != function { continue } 53 | diagram(debug: 0, node((0, 0), name, shape: shape, stroke: 1pt, extrude: (0, 2))) 54 | linebreak() 55 | } 56 | 57 | #pagebreak() 58 | 59 | #diagram( 60 | node-stroke: 1pt, 61 | spacing: 10pt, 62 | node((0,0), [STOP], shape: shapes.octagon.with(truncate: 0)), 63 | node((1,0), [STOP], shape: shapes.octagon.with(truncate: 0.5)), 64 | node((2,0), [STOP], shape: shapes.octagon.with(truncate: 1)), 65 | node((0,1), [STOP], shape: shapes.octagon.with(truncate: 2pt)), 66 | node((1,1), [STOP], shape: shapes.octagon.with(truncate: 5pt)), 67 | node((2,1), [STOP], shape: shapes.octagon.with(truncate: 8pt)), 68 | ) 69 | 70 | 71 | #pagebreak() 72 | 73 | Direction 74 | 75 | #diagram( 76 | for (i, shape) in ( 77 | shapes.trapezium, 78 | shapes.triangle, 79 | shapes.house, 80 | shapes.chevron, 81 | ).enumerate() { 82 | for (j, dir) in (top, bottom, left, right).enumerate() { 83 | node( 84 | (j, i), 85 | [#dir], 86 | fill: orange.transparentize(40%), 87 | shape: shape.with(dir: dir), 88 | ) 89 | } 90 | } 91 | ) 92 | 93 | 94 | #pagebreak() 95 | 96 | Flip 97 | 98 | #diagram( 99 | for (i, flip) in (false, true).enumerate() { 100 | node( 101 | (i, 0), 102 | [#flip], 103 | fill: teal.transparentize(40%), 104 | shape: shapes.parallelogram.with(flip: flip), 105 | ) 106 | } 107 | ) 108 | 109 | #pagebreak() 110 | 111 | Fit factor 112 | 113 | #diagram( 114 | node-inset: 0pt, 115 | for (i, shape) in ( 116 | shapes.parallelogram, 117 | shapes.trapezium, 118 | shapes.diamond, 119 | shapes.triangle, 120 | shapes.chevron, 121 | shapes.hexagon, 122 | ).enumerate() { 123 | for (j, fit) in (0, 0.5, 1).enumerate() { 124 | node( 125 | (j, i), 126 | box(fill: blue.transparentize(60%), inset: 10pt, raw("fit: " + repr(fit))), 127 | fill: green.transparentize(20%), 128 | shape: shape.with(fit: fit), 129 | ) 130 | } 131 | } 132 | ) 133 | 134 | #pagebreak() 135 | 136 | #diagram( 137 | axes: (ttb, ltr), 138 | for (j, shape) in (shapes.brace, shapes.bracket, shapes.paren).enumerate() { 139 | for (i, dir) in (left, top, right, bottom).enumerate() { 140 | node((i,j), [#dir], shape: shape.with(dir: dir)) 141 | } 142 | }, 143 | ) 144 | 145 | #pagebreak() 146 | 147 | #for tilt in (-5deg, 0deg, 5deg, 10deg, 20deg) { 148 | diagram( 149 | node-stroke: green, 150 | node-fill: green.lighten(90%), 151 | node-outset: 5pt, 152 | node((0,0), [Local], shape: shapes.cylinder.with(tilt: tilt), name: ), 153 | node((2,0), [Remote], shape: shapes.cylinder.with(tilt: tilt, rings: 2pt), name: ), 154 | edge(, , ">->", [Backup], center) 155 | ) 156 | linebreak() 157 | } 158 | -------------------------------------------------------------------------------- /tests/node/size-inset-outset/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/size-inset-outset/ref/1.png -------------------------------------------------------------------------------- /tests/node/size-inset-outset/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/size-inset-outset/ref/2.png -------------------------------------------------------------------------------- /tests/node/size-inset-outset/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/node/size-inset-outset/ref/3.png -------------------------------------------------------------------------------- /tests/node/size-inset-outset/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #let cm-square = box(width: 5mm, height: 5mm, fill: black) 5 | 6 | What `5mm` inset should look like: 7 | 8 | #rect(fill: green, inset: 5mm, cm-square) 9 | 10 | A diagram node with `5mm` inset: 11 | 12 | #diagram(node((0,0), cm-square, shape: rect, inset: 5mm, fill: green)) 13 | 14 | A diagram node with `5mm` outset: 15 | 16 | #diagram( 17 | spacing: 1cm, 18 | node((0,0), cm-square, shape: rect, inset: 0pt, outset: 5mm, fill: blue), 19 | edge("->"), 20 | ) 21 | 22 | #pagebreak() 23 | 24 | Circular insets: 25 | 26 | #diagram( 27 | node-stroke: 1pt, 28 | node((0,0), $A$, inset: 0pt, shape: rect), 29 | node((1,0), $A$, inset: 0pt), 30 | node((0,1), $A$, shape: rect), 31 | node((1,1), $A$), 32 | node((0,2), $A B C D E$, shape: rect), 33 | node((1,2), $A B C D E$, shape: circle), 34 | ) 35 | 36 | #pagebreak() 37 | 38 | Explicit node size: 39 | 40 | #circle(radius: 1cm, align(center + horizon, `1cm`)) 41 | #diagram( 42 | node((0,0), `1cm`, stroke: 1pt, radius: 1cm, inset: 1cm, shape: "circle"), 43 | node((0,1), [width], stroke: 1pt, width: 2cm), 44 | node((1,1), [height], height: 4em, inset: 0pt, fill: blue.lighten(50%)), 45 | node((2,1), [both], width: 5em, height: 5em, stroke: 2pt), 46 | ) 47 | -------------------------------------------------------------------------------- /tests/readme-examples/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/readme-examples/ref/1.png -------------------------------------------------------------------------------- /tests/readme-examples/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/readme-examples/ref/2.png -------------------------------------------------------------------------------- /tests/readme-examples/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/readme-examples/ref/3.png -------------------------------------------------------------------------------- /tests/readme-examples/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/readme-examples/ref/4.png -------------------------------------------------------------------------------- /tests/readme-examples/ref/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/readme-examples/ref/5.png -------------------------------------------------------------------------------- /tests/readme-examples/ref/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/readme-examples/ref/6.png -------------------------------------------------------------------------------- /tests/readme-examples/ref/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/readme-examples/ref/7.png -------------------------------------------------------------------------------- /tests/readme-examples/ref/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/readme-examples/ref/8.png -------------------------------------------------------------------------------- /tests/readme-examples/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em, fill: none) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | 4 | #let render-example(name, darkmode: false) = { 5 | let src = read("/docs/readme-examples/" + name + ".typ") 6 | src = src.replace(regex("\n.*// testing: omit"), "") 7 | if not darkmode { 8 | src = src.replace(regex("/\*darkmode\*/[\s\S]*/\*end\*/"), "") 9 | src = src.replace(regex("\n.*// darkmode"), "") 10 | } 11 | let out = eval(src, mode: "markup", scope: ( 12 | fletcher: fletcher, 13 | diagram: diagram, 14 | node: node, 15 | edge: edge, 16 | )) 17 | if darkmode [ 18 | #set page(fill: black) 19 | #set text(fill: white) 20 | #out 21 | ] else { 22 | out 23 | } 24 | } 25 | 26 | #( 27 | "1-first-isomorphism-theorem", 28 | "2-flowchart-trap", 29 | "3-state-machine", 30 | "4-feynman-diagram", 31 | ).map(name => { 32 | render-example(name, darkmode: false) 33 | render-example(name, darkmode: true) 34 | }).join() 35 | -------------------------------------------------------------------------------- /tests/template.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/exports.typ" as fletcher: diagram, node, edge 3 | -------------------------------------------------------------------------------- /tests/utils/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jollywatt/typst-fletcher/6482bd75828937ecfc0f7efbdf73cad4a508b65f/tests/utils/ref/1.png -------------------------------------------------------------------------------- /tests/utils/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #import "/src/utils.typ": * 3 | 4 | // this test contains no visual output 5 | #show: none 6 | 7 | = Points in rect 8 | 9 | #assert(point-is-in-rect((1, 2), (center: (1, 2), size: (0,0)))) 10 | #assert(not point-is-in-rect((1, 11), (center: (0, 0), size: (1,10)))) 11 | 12 | #let points = ( 13 | (0pt,0pt), 14 | (-2pt,80pt), 15 | (-1cm,5mm), 16 | ) 17 | #assert(points.all(point => point-is-in-rect(point, bounding-rect(points)))) 18 | 19 | 20 | = Array interpolation 21 | 22 | #let a = (22pt,) 23 | #assert(interp(a, 0) == 22pt) 24 | #assert(interp-inv(a, 22pt) == 0) 25 | 26 | #let a = (10pt, 20pt, 25pt) 27 | #assert(range(3).map(interp.with(a)) == a) 28 | #assert(interp(a, 0.5) == 15pt) 29 | 30 | Outside bounds 31 | 32 | #assert(interp(a, 3) == interp(a, a.len() - 1)) 33 | #assert(interp(a, -1) == interp(a, 0)) 34 | 35 | #assert(interp(a, -1, spacing: 100pt) == a.at(0) - 100pt) 36 | -------------------------------------------------------------------------------- /typst.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fletcher" 3 | version = "0.5.9" 4 | compiler = "0.13.0" 5 | entrypoint = "src/exports.typ" 6 | authors = ["Joseph Wilson (Jollywatt)"] 7 | license = "MIT" 8 | description = "Draw diagrams with nodes and arrows." 9 | repository = "https://github.com/Jollywatt/typst-fletcher" 10 | categories = ["visualization", "components"] 11 | keywords = [ 12 | "commutative", 13 | "commuting", 14 | "commute", 15 | "diagram", 16 | "category", 17 | "flowchart", 18 | "DAG", 19 | "graph", 20 | "finite state", 21 | "network", 22 | "node", 23 | "arrow", 24 | ] 25 | exclude = ["docs/", "tests/"] 26 | --------------------------------------------------------------------------------