From 23819f4d1b296d1b1798c4332f41dd0982ecf6a0 Mon Sep 17 00:00:00 2001 From: Jan Meyer Date: Thu, 16 Apr 2026 12:34:05 +0200 Subject: [PATCH] vault backup: 2026-04-16 12:34:05 --- .../caches/preview_elembic_1.1.1.cache | Bin 0 -> 298087 bytes .../caches/preview_lilaq_0.5.0.cache | Bin 0 -> 346687 bytes .../caches/preview_mannot_0.3.1.cache | Bin 0 -> 43740 bytes .../caches/preview_quick-maths_0.2.1.cache | Bin 0 -> 9228 bytes .../caches/preview_tiptoe_0.3.1.cache | Bin 0 -> 56514 bytes .../caches/preview_tiptoe_0.3.2.cache | Bin 0 -> 57392 bytes .../caches/preview_zero_0.5.0.cache | Bin 0 -> 75489 bytes .../packages/preview/elembic/1.1.1/LICENSE | 1 + .../preview/elembic/1.1.1/LICENSE-APACHE | 201 + .../preview/elembic/1.1.1/LICENSE-MIT | 21 + .../packages/preview/elembic/1.1.1/README.md | 114 + .../preview/elembic/1.1.1/src/data.typ | 584 +++ .../preview/elembic/1.1.1/src/element.typ | 3693 +++++++++++++++++ .../preview/elembic/1.1.1/src/fields.typ | 366 ++ .../preview/elembic/1.1.1/src/filters.typ | 430 ++ .../preview/elembic/1.1.1/src/lib.typ | 12 + .../preview/elembic/1.1.1/src/pub/data.typ | 1 + .../preview/elembic/1.1.1/src/pub/element.typ | 2 + .../preview/elembic/1.1.1/src/pub/filters.typ | 1 + .../1.1.1/src/pub/internal-constants.typ | 1 + .../elembic/1.1.1/src/pub/internal.typ | 2 + .../preview/elembic/1.1.1/src/pub/leaky.typ | 8 + .../preview/elembic/1.1.1/src/pub/native.typ | 2 + .../preview/elembic/1.1.1/src/pub/parsing.typ | 1 + .../preview/elembic/1.1.1/src/pub/result.typ | 1 + .../elembic/1.1.1/src/pub/stateful.typ | 8 + .../preview/elembic/1.1.1/src/pub/types.typ | 5 + .../preview/elembic/1.1.1/src/types/base.typ | 694 ++++ .../elembic/1.1.1/src/types/custom.typ | 428 ++ .../elembic/1.1.1/src/types/native.typ | 349 ++ .../preview/elembic/1.1.1/src/types/types.typ | 434 ++ .../packages/preview/elembic/1.1.1/typst.toml | 15 + .../packages/preview/lilaq/0.5.0/LICENSE | 21 + .../packages/preview/lilaq/0.5.0/README.md | 181 + .../src/algorithm/bezier-interpolation.typ | 79 + .../lilaq/0.5.0/src/algorithm/boxplot.typ | 44 + .../lilaq/0.5.0/src/algorithm/contour.typ | 818 ++++ .../0.5.0/src/algorithm/gaussian-kde.typ | 9 + .../lilaq/0.5.0/src/algorithm/interpolate.typ | 36 + .../0.5.0/src/algorithm/linear-system.typ | 59 + .../preview/lilaq/0.5.0/src/assertations.typ | 154 + .../preview/lilaq/0.5.0/src/bounds.typ | 190 + .../preview/lilaq/0.5.0/src/lilaq.typ | 51 + .../preview/lilaq/0.5.0/src/loading/txt.typ | 163 + .../preview/lilaq/0.5.0/src/logic/limits.typ | 28 + .../0.5.0/src/logic/process-coordinates.typ | 182 + .../lilaq/0.5.0/src/logic/sample-colors.typ | 58 + .../preview/lilaq/0.5.0/src/logic/scale.typ | 174 + .../preview/lilaq/0.5.0/src/logic/symlog.typ | 14 + .../lilaq/0.5.0/src/logic/tick-format.typ | 618 +++ .../lilaq/0.5.0/src/logic/tick-locate.typ | 1235 ++++++ .../preview/lilaq/0.5.0/src/logic/time.typ | 135 + .../lilaq/0.5.0/src/logic/transform.typ | 44 + .../packages/preview/lilaq/0.5.0/src/math.typ | 332 ++ .../preview/lilaq/0.5.0/src/model/axis.typ | 933 +++++ .../lilaq/0.5.0/src/model/colorbar.typ | 123 + .../preview/lilaq/0.5.0/src/model/diagram.typ | 696 ++++ .../lilaq/0.5.0/src/model/errorbar.typ | 94 + .../preview/lilaq/0.5.0/src/model/grid.typ | 113 + .../preview/lilaq/0.5.0/src/model/label.typ | 79 + .../preview/lilaq/0.5.0/src/model/legend.typ | 172 + .../preview/lilaq/0.5.0/src/model/mark.typ | 190 + .../preview/lilaq/0.5.0/src/model/spine.typ | 52 + .../preview/lilaq/0.5.0/src/model/tick.typ | 206 + .../preview/lilaq/0.5.0/src/model/title.typ | 94 + .../preview/lilaq/0.5.0/src/place-anchor.typ | 15 + .../preview/lilaq/0.5.0/src/plot/bar.typ | 314 ++ .../preview/lilaq/0.5.0/src/plot/boxplot.typ | 344 ++ .../lilaq/0.5.0/src/plot/colormesh.typ | 259 ++ .../preview/lilaq/0.5.0/src/plot/contour.typ | 222 + .../preview/lilaq/0.5.0/src/plot/ellipse.typ | 124 + .../lilaq/0.5.0/src/plot/fill-between.typ | 138 + .../preview/lilaq/0.5.0/src/plot/hbar.typ | 202 + .../preview/lilaq/0.5.0/src/plot/hboxplot.typ | 286 ++ .../preview/lilaq/0.5.0/src/plot/hlines.typ | 113 + .../preview/lilaq/0.5.0/src/plot/hstem.typ | 157 + .../preview/lilaq/0.5.0/src/plot/line.typ | 119 + .../preview/lilaq/0.5.0/src/plot/path.typ | 204 + .../preview/lilaq/0.5.0/src/plot/place.typ | 114 + .../preview/lilaq/0.5.0/src/plot/plot.typ | 495 +++ .../preview/lilaq/0.5.0/src/plot/quiver.typ | 303 ++ .../preview/lilaq/0.5.0/src/plot/rect.typ | 130 + .../preview/lilaq/0.5.0/src/plot/scatter.typ | 226 + .../preview/lilaq/0.5.0/src/plot/stem.typ | 173 + .../preview/lilaq/0.5.0/src/plot/vlines.typ | 106 + .../lilaq/0.5.0/src/process-styles.typ | 158 + .../preview/lilaq/0.5.0/src/style/color.typ | 122 + .../preview/lilaq/0.5.0/src/style/cycle.typ | 65 + .../preview/lilaq/0.5.0/src/style/map.typ | 481 +++ .../preview/lilaq/0.5.0/src/style/styling.typ | 292 ++ .../preview/lilaq/0.5.0/src/style/tilings.typ | 30 + .../preview/lilaq/0.5.0/src/theme/misty.typ | 32 + .../preview/lilaq/0.5.0/src/theme/moon.typ | 23 + .../preview/lilaq/0.5.0/src/theme/ocean.typ | 24 + .../lilaq/0.5.0/src/theme/schoolbook.typ | 26 + .../preview/lilaq/0.5.0/src/theme/skyline.typ | 22 + .../preview/lilaq/0.5.0/src/theme/theme.typ | 5 + .../preview/lilaq/0.5.0/src/typing.typ | 21 + .../preview/lilaq/0.5.0/src/utility.typ | 73 + .../packages/preview/lilaq/0.5.0/src/vec.typ | 66 + .../packages/preview/lilaq/0.5.0/typst.toml | 13 + .../packages/preview/mannot/0.3.1/LICENSE | 21 + .../packages/preview/mannot/0.3.1/README.md | 322 ++ .../preview/mannot/0.3.1/src/annot.typ | 496 +++ .../packages/preview/mannot/0.3.1/src/lib.typ | 2 + .../preview/mannot/0.3.1/src/mark.typ | 498 +++ .../preview/mannot/0.3.1/src/util.typ | 39 + .../packages/preview/mannot/0.3.1/typst.toml | 12 + .../preview/quick-maths/0.2.1/LICENSE | 22 + .../preview/quick-maths/0.2.1/README.md | 43 + .../preview/quick-maths/0.2.1/src/lib.typ | 1 + .../quick-maths/0.2.1/src/quick-maths.typ | 193 + .../preview/quick-maths/0.2.1/typst.toml | 10 + .../packages/preview/tiptoe/0.3.1/LICENSE.txt | 21 + .../packages/preview/tiptoe/0.3.1/README.md | 337 ++ .../preview/tiptoe/0.3.1/src/arc-impl.typ | 125 + .../packages/preview/tiptoe/0.3.1/src/arc.typ | 46 + .../preview/tiptoe/0.3.1/src/assert.typ | 20 + .../preview/tiptoe/0.3.1/src/curve.typ | 429 ++ .../preview/tiptoe/0.3.1/src/line.typ | 71 + .../preview/tiptoe/0.3.1/src/marks.typ | 547 +++ .../tiptoe/0.3.1/src/path-to-curve.typ | 80 + .../preview/tiptoe/0.3.1/src/path.typ | 118 + .../preview/tiptoe/0.3.1/src/tiptoe.typ | 6 + .../preview/tiptoe/0.3.1/src/utility.typ | 67 + .../packages/preview/tiptoe/0.3.1/typst.toml | 13 + .../packages/preview/tiptoe/0.3.2/LICENSE.txt | 21 + .../packages/preview/tiptoe/0.3.2/README.md | 341 ++ .../preview/tiptoe/0.3.2/src/arc-impl.typ | 125 + .../packages/preview/tiptoe/0.3.2/src/arc.typ | 46 + .../preview/tiptoe/0.3.2/src/assert.typ | 20 + .../preview/tiptoe/0.3.2/src/curve.typ | 429 ++ .../preview/tiptoe/0.3.2/src/line.typ | 118 + .../preview/tiptoe/0.3.2/src/marks.typ | 547 +++ .../tiptoe/0.3.2/src/path-to-curve.typ | 80 + .../preview/tiptoe/0.3.2/src/path.typ | 118 + .../preview/tiptoe/0.3.2/src/tiptoe.typ | 6 + .../preview/tiptoe/0.3.2/src/utility.typ | 67 + .../packages/preview/tiptoe/0.3.2/typst.toml | 13 + .../packages/preview/zero/0.5.0/LICENSE | 21 + .../packages/preview/zero/0.5.0/README.md | 473 +++ .../preview/zero/0.5.0/src/align-column.typ | 31 + .../preview/zero/0.5.0/src/assertations.typ | 46 + .../preview/zero/0.5.0/src/formatting.typ | 321 ++ .../packages/preview/zero/0.5.0/src/impl.typ | 5 + .../packages/preview/zero/0.5.0/src/num.typ | 243 ++ .../preview/zero/0.5.0/src/parsing.typ | 249 ++ .../preview/zero/0.5.0/src/rounding.typ | 209 + .../packages/preview/zero/0.5.0/src/state.typ | 41 + .../packages/preview/zero/0.5.0/src/units.typ | 283 ++ .../preview/zero/0.5.0/src/utility.typ | 31 + .../packages/preview/zero/0.5.0/src/zero.typ | 7 + .../packages/preview/zero/0.5.0/src/zi.typ | 154 + .../preview/zero/0.5.0/src/ztable.typ | 87 + .../packages/preview/zero/0.5.0/typst.toml | 13 + 11 Homework/Mathe II/Homework 1.typ | 2 + .../Mathe II/_Math Homework Template.typ | 14 + 157 files changed, 27948 insertions(+) create mode 100644 .obsidian/plugins/typst-mate/caches/preview_elembic_1.1.1.cache create mode 100644 .obsidian/plugins/typst-mate/caches/preview_lilaq_0.5.0.cache create mode 100644 .obsidian/plugins/typst-mate/caches/preview_mannot_0.3.1.cache create mode 100644 .obsidian/plugins/typst-mate/caches/preview_quick-maths_0.2.1.cache create mode 100644 .obsidian/plugins/typst-mate/caches/preview_tiptoe_0.3.1.cache create mode 100644 .obsidian/plugins/typst-mate/caches/preview_tiptoe_0.3.2.cache create mode 100644 .obsidian/plugins/typst-mate/caches/preview_zero_0.5.0.cache create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/LICENSE create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/LICENSE-APACHE create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/LICENSE-MIT create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/README.md create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/data.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/element.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/fields.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/filters.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/lib.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/pub/data.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/pub/element.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/pub/filters.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/pub/internal-constants.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/pub/internal.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/pub/leaky.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/pub/native.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/pub/parsing.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/pub/result.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/pub/stateful.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/pub/types.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/types/base.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/types/custom.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/types/native.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/types/types.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/typst.toml create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/LICENSE create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/README.md create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/algorithm/bezier-interpolation.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/algorithm/boxplot.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/algorithm/contour.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/algorithm/gaussian-kde.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/algorithm/interpolate.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/algorithm/linear-system.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/assertations.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/bounds.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/lilaq.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/loading/txt.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/logic/limits.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/logic/process-coordinates.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/logic/sample-colors.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/logic/scale.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/logic/symlog.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/logic/tick-format.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/logic/tick-locate.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/logic/time.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/logic/transform.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/math.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/model/axis.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/model/colorbar.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/model/diagram.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/model/errorbar.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/model/grid.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/model/label.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/model/legend.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/model/mark.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/model/spine.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/model/tick.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/model/title.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/place-anchor.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/bar.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/boxplot.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/colormesh.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/contour.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/ellipse.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/fill-between.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/hbar.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/hboxplot.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/hlines.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/hstem.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/line.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/path.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/place.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/plot.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/quiver.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/rect.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/scatter.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/stem.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/plot/vlines.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/process-styles.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/style/color.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/style/cycle.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/style/map.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/style/styling.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/style/tilings.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/theme/misty.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/theme/moon.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/theme/ocean.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/theme/schoolbook.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/theme/skyline.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/theme/theme.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/typing.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/utility.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/src/vec.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/lilaq/0.5.0/typst.toml create mode 100644 .obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/LICENSE create mode 100644 .obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/README.md create mode 100644 .obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/src/annot.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/src/lib.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/src/mark.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/src/util.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/typst.toml create mode 100644 .obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/LICENSE create mode 100644 .obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/README.md create mode 100644 .obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/src/lib.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/src/quick-maths.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/typst.toml create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/LICENSE.txt create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/README.md create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/arc-impl.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/arc.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/assert.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/curve.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/line.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/marks.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/path-to-curve.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/path.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/tiptoe.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/utility.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/typst.toml create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/LICENSE.txt create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/README.md create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/arc-impl.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/arc.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/assert.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/curve.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/line.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/marks.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/path-to-curve.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/path.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/tiptoe.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/utility.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/typst.toml create mode 100644 .obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/LICENSE create mode 100644 .obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/README.md create mode 100644 .obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/align-column.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/assertations.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/formatting.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/impl.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/num.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/parsing.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/rounding.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/state.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/units.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/utility.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/zero.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/zi.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/ztable.typ create mode 100644 .obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/typst.toml create mode 100644 11 Homework/Mathe II/Homework 1.typ create mode 100644 11 Homework/Mathe II/_Math Homework Template.typ diff --git a/.obsidian/plugins/typst-mate/caches/preview_elembic_1.1.1.cache b/.obsidian/plugins/typst-mate/caches/preview_elembic_1.1.1.cache new file mode 100644 index 0000000000000000000000000000000000000000..cfbc9ab95d23b92a8e7a9952cf317ce74df0493f GIT binary patch literal 298087 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 literal 0 HcmV?d00001 diff --git a/.obsidian/plugins/typst-mate/caches/preview_lilaq_0.5.0.cache b/.obsidian/plugins/typst-mate/caches/preview_lilaq_0.5.0.cache new file mode 100644 index 0000000000000000000000000000000000000000..2800639554798fd2cf08be04db0c34f638db6a08 GIT binary patch literal 346687 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/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 WYGtD{Y-by5e7-SBvkCwH_WuG3jKCoP literal 0 HcmV?d00001 diff --git a/.obsidian/plugins/typst-mate/caches/preview_zero_0.5.0.cache b/.obsidian/plugins/typst-mate/caches/preview_zero_0.5.0.cache new file mode 100644 index 0000000000000000000000000000000000000000..add45d8175bdb85e00c3c852e859fb7c0d993165 GIT binary patch literal 75489 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 Yt*ADMDuCwopbr6|Nz3TiEotTd1E!;^6aWAK literal 0 HcmV?d00001 diff --git a/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/LICENSE b/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/LICENSE new file mode 100644 index 0000000..03f646f --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/LICENSE @@ -0,0 +1 @@ +MIT OR Apache-2.0 diff --git a/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/LICENSE-APACHE b/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/LICENSE-APACHE new file mode 100644 index 0000000..71b8799 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 PgBiel + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/LICENSE-MIT b/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/LICENSE-MIT new file mode 100644 index 0000000..869be70 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 PgBiel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/README.md b/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/README.md new file mode 100644 index 0000000..b4f8429 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/README.md @@ -0,0 +1,114 @@ +# elembic +Framework for custom elements and types in Typst. **Supports Typst 0.11.0 or later.** + +**Read the book:** https://pgbiel.github.io/elembic + +**Mirrors:** [GitHub (pgbiel/elembic)](https://github.com/PgBiel/elembic); [Codeberg (pgbiel/elembic)](https://codeberg.org/PgBiel/elembic) + +## About + +Elembic lets you create [**custom elements,**](https://pgbiel.github.io/elembic/elements/creating/index.html) which are **reusable and customizable document components,** with support for **typechecked fields, [show and set rules](https://pgbiel.github.io/elembic/elements/styling/index.html)** (without using any `state` by default!), **[reference and outline support,](https://pgbiel.github.io/elembic/elements/creating/labels-refs.html)** as well as features not present in Typst elements today such as **[revokable rules](https://pgbiel.github.io/elembic/elements/styling/revoke.html)** and **[nested element selectors](https://pgbiel.github.io/elembic/elements/filters/within.html)**. In addition, Elembic lets you create [**custom types,**](https://pgbiel.github.io/elembic/types/custom-types/index.html) which also support **typechecked fields** but also [**custom casting**](https://pgbiel.github.io/elembic/types/custom-types/casts.html), and can be used in element fields or by themselves in arbitrary Typst code. + +Elembic's name comes from "element" + ["alembic"](https://en.wikipedia.org/wiki/Alembic), to indicate that one of Elembic's goals is to experiment with different approaches for this functionality, and to help shape a future update to Typst that includes native custom elements, which will eventually remove the need for using this package. + +Its documentation is at the Elembic Handbook: [https://pgbiel.github.io/elembic](https://pgbiel.github.io/elembic) + +This library has a few limitations which are [appropriately noted in the book.](https://pgbiel.github.io/elembic/about/limitations.html) + +## Who is elembic for? + +Elembic is especially suited for: +1. **Packages:** elembic's elements allows creating reusable components which can be freely customized by your package's end users. With typechecking and other features, elembic's got you covered in terms of flexibility. See the ["Simple Theorems" example](https://pgbiel.github.io/elembic/examples/simple-theorems.html) for a sample. +2. **Templates:** elembic's elements can be used for fine-grained configuration of parts of your template. See the ["Simple Thesis" example](https://pgbiel.github.io/elembic/examples/simple-thesis.html) for a sample. + +## Installation + +Just import the latest elembic version from the package manager and you're ready to go! + +```typ +#import "@preview/elembic:1.1.1" as e + +// See the full example below +#show: e.set_(element, stroke: red) +// ... +``` + +## Example + +### Custom element + +```typ +#import "@preview/elembic:1.1.1" as e: field, types + +#let bigbox = e.element.declare( + "bigbox", + prefix: "@preview/my-package,v1", + doc: "A fancy and customizable box.", + display: it => block(fill: it.fill, stroke: it.stroke, inset: 5pt, it.body), + fields: ( + field("body", types.option(content), doc: "Box contents", required: true), + field("fill", types.option(types.paint), doc: "Box fill"), + field("stroke", types.option(stroke), doc: "Box border", default: red) + ) +) + +#bigbox[abc] + +#show: e.set_(bigbox, fill: red) + +#bigbox(stroke: blue + 2pt)[def] +``` + +!['abc' with no fill and a thin red stroke, followed by 'def' with red fill and blue thicker stroke](https://github.com/user-attachments/assets/c852cfcd-c0de-446a-999b-5ecaa44809b7) + +### Custom type + +```typ +#import "@preview/elembic:1.1.1" as e: field, types + +#let person = e.types.declare( + "person", + prefix: "@preview/my-package,v1", + doc: "Relevant data for a person.", + fields: ( + field("name", str, doc: "Person's name", required: true, named: true), + field("age", int, doc: "Person's age", default: 40), + field("preference", types.any, doc: "Anything the person likes", default: none) + ), + casts: ( + (from: dictionary), + (from: str, with: person => name => person(name: name)), + ) +) + +#assert.eq( + e.repr(person(name: "John", age: 50, preference: "soup")), + "person(age: 50, name: \"John\", preference: \"soup\")" +) + +// Manually invoke typechecking and cast +#assert.eq( + types.cast((name: "abc", age: 99), person), + (true, person(name: "abc", age: 99)) +) + +#assert.eq( + types.cast("abc", person), + (true, person(name: "abc")) +) + +// Can then use 'person' as the type of an element's field, for example +``` + +## Source structure + +- `pub/`: Contains public re-exports of each module (so we can keep some things private) +- `data`: Functions and constants related to extracting data from elements and types +- `element`: Functions related to creating and using elements and their rules (set rules, revoke rules and so on) +- `filters`: Functions to create and match element filters +- `types`: Functions and constants related to Elembic's custom type system +- `fields`: Functions related to element and type field parsing + +## License + +Licensed under MIT or Apache-2.0, at your option. diff --git a/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/data.typ b/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/data.typ new file mode 100644 index 0000000..ee7361e --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/data.typ @@ -0,0 +1,584 @@ +// Functions to extract data from custom elements and types, as well as associated constants. + +// Type constants: + +// Used by typeinfos +#let type-key = "__elembic_type" +// To be used by any custom type instances +#let custom-type-key = "__elembic_custom_type" +// Used by custom types themselves +#let custom-type-data-key = "__elembic_custom_type_data" + +// Versions: +#let element-version = 5 // v1 = alphas 1 and 2, v2 = alpha 3-v1.0.0-rc2, v3 = v1.0.0, v4 = v1.1.0, v5 = v1.1.1+ +#let type-version = 4 // v1 = alphas to v1.0.0-rc2, v2 = v1.0.0, v3 = v1.1.0, v4 = v1.1.1+ +#let custom-type-version = 5 // v1 = alphas 1 and 2, v2 = alpha 3-v1.0.0-rc2, v3 = v1.0.0, v4 = v1.1.0, v5 = v1.1.1+ +#let current-field-version = 4 // v1 = alphas 1 and 2, v2 = alpha 3-v1.0.0-rc2, v3 = v1.0.0-v1.1.0, v4 = v1.1.1+ + +// Potential modes for configuration of styles. +// This defines how we declare a set rule (or similar) +// within a certain scope. +#let style-modes = ( + // Normal mode: we store metadata in a bibliography.title set rule. + // + // Before doing so, we retrieve the original value for bibliography.title, + // allowing us to restore it later. The effect is that the library is + // fully hygienic, that is, the change to bibliography.title is not perceptible. + // + // The downside is that retrieving the original value for bibliography.title costs + // an additional nested context { } call, of which there is a limit of 64. This means + // that, in this mode, you can have up to 32 non-consecutive set rules. + normal: 0, + + // leaky mode: similar to normal mode, but we don't try to preserve the value of bibliography.title + // after applying our changes to the document. This doubles the limit to up to 64 non-consecutive + // set rules since we no longer have an extra step to retrieve the old value, but, as a downside, + // we lose the original value of bibliography.title. While, in a future change, we might be able to + // preserve the FIRST known value, we can't generally preserve its value at later points, so the + // value of bibliography.title is effectively frozen before the first custom set rule. + // + // This mode should be used by package authors which know there won't be a bibliography (or, really, + // any custom user input) at some point to avoid consuming the set rule cost. End users can also use + // this mode if they hit a "max show rule depth exceeded" error. + // + // Note that this mode can only be enabled on individual set rules. + leaky: 1, + + // Stateful mode: this is entirely different from the other modes and should only be set by the end + // user (not by packages). This stores the style chain - and, thus, set rules' updated fields - in + // a 'state()'. This is more likely to be slower and lead to trouble as it triggers at least one + // document relayout. However, **this mode does not have a set rule limit.** Therefore, it can be + // used as a last resort by the end user if they can't fix the "max show rule depth exceeded error". + // + // Enabling this mode is as simple as using `#show: e.stateful.toggle(true)` at the beginning of the + // document. This will trigger a compatibility behavior where existing set rules will push to the + // state, even if they're not in the stateful mode. It will also push existing set rule data into + // the style 'state()'. Therefore, existing set rules are compatible with stateful mode, but this + // only effectively fixes the error if the set rules are individually switched to stateful mode + // with `e.stateful.set_` instead of `e.set_`. + stateful: 2 +) + + +// When on stateful mode, this state holds the sequence of 'data' for each scope. +// The last element on the list is the "current" data. +#let style-state-key = "__elembic_element_state" +#let style-state = state(style-state-key, ()) + +// Element constants: + +// Prefix for the labels added to shown elements. +#let lbl-show-head = "__elembic_element_shown_" + +// Prefix for the labels added to the metadata of each element. +// Used for querying. +#let lbl-meta-head = "__elembic_element_meta_" + +// Prefix for the labels added outside shown elements. +// This is used to be able to effectively apply show-set rules to them. +#let lbl-outer-head = "__elembic_element_outer_" + +// Prefix for counters of elements. +// This is only used if the element isn't 'refable'. +#let lbl-counter-head = "__elembic_element_counter_" + +// Prefix for labels for 'context' which should panic with 'missing prepare(elem)'. +#let lbl-elem-prepare-check-head = "__elembic_elem_prepare_check_" + +// Kind of the special figure used by a labelable element. +#let labelable-elem-figure-kind = "__elembic_element_labelable_figure" + +// Prefix for the figure kind used by 'refable' elements. +// This is not to be confused with figures containing the elements. +// This is the kind for a hidden figure used for ref purposes. +#let lbl-ref-figure-kind-head = "__elembic_element_refable_" + +// Custom label applied to the hidden reference figure when the user specifies their own label. +#let lbl-ref-figure-label-head = "__elembic_element_ref_figure_label_" + +// Label for a context which should panic with 'missing prepare()'... +#let lbl-empty-prepare-check = <__elembic_empty_prepare_check> + +// Label for the hidden figure used for references. +#let lbl-ref-figure = <__elembic_element_ref_figure> + +// Label for context blocks which have access to the virtual stylechain. +#let lbl-get = <__elembic_element_get> + +// Label for metadata indicating an element's initial properties post-construction. +#let lbl-tag = <__elembic_element_tag> + +// Label for metadata indicating a rule's parameters. +#let lbl-rule-tag = <__elembic_element_rule_v2> + +// 'lbl-rule-tag' from older Elembic versions. +#let lbl-old-rule-tag = <__elembic_element_rule> + +// Label for other functions which access or modify the style chain, namely +// 'get', 'select', 'debug-get', 'stateful.toggle'. +// +// This is attached to metadata and the 'special-rule-key' property +// indicates which kind of rule this is. +#let lbl-special-rule-tag = <__elembic_element_special_rule> + +// Label for metadata which stores the global data. +// In practice, this label is never present in the document +// unless one accidentally leaks the 'bibliography.title' +// override from our workaround. +#let lbl-data-metadata = <__elembic_element_global_data_metadata> + +#let lbl-stateful-mode = <__elembic_element_stateful_mode> +#let lbl-normal-mode = <__elembic_element_normal_mode> +#let lbl-leaky-mode = <__elembic_element_leaky_mode> +#let lbl-auto-mode = <__elembic_element_auto_mode> + +// Prefix for labels added by 'select' to matched elements. +// These labels are not specific to eids. +#let lbl-global-select-head = "__elembic_element_global_select_" + +// Special dictionary key to indicate this is a prepared rule. +#let prepared-rule-key = "__elembic-prepared-rule" + +// Special dictionary key to indicate this is a "special rule" +// ('get', 'select', 'debug-get', 'stateful.toggle'). +#let special-rule-key = "__elembic-special-rule" + +// Special dictionary key which stores element context and other data. +#let stored-data-key = "__elembic_stored_element_data" + +// Special dictionary key to indicate this is query metadata for an element. +#let element-meta-key = "__elembic_element_meta" + +#let element-key = "__elembic_element" +#let element-data-key = "__elembic_element_data" +#let global-data-key = "__elembic_element_global_data" +#let filter-key = "__elembic_element_filter" + +#let sequence = [].func() +#let styled = { set text(red); [a] }.func() +#let elem-funcs = (sequence, styled, figure) + +// Special values that can be passed to a type or element's constructor to retrieve some data or show +// some behavior. +#let special-data-values = ( + // Indicate that the constructor should return the type or element's data. + get-data: 0, + // Indicate that the constructor should return an element filter. + get-where: 1, +) + +// Extract data from a type's or element's constructor, as well as convert +// a custom type or element instance into a dictionary with keys such as body (for elements only), +// fields and func, allowing you to access its fields and information when given content (for elements) +// or the type instance (for types). +// +// When given content that is not a custom element, 'body' will be the given value, +// 'fields' will be 'body.fields()' and 'func' will be 'body.func()'. +// +// The returned 'data-kind' indicates which kind of data was retrieved. +#let data(it) = { + if type(it) == function { + it(__elembic_data: special-data-values.get-data) + } else if type(it) == dictionary { + if element-key in it { + (data-kind: "element", ..it) + } else if custom-type-data-key in it { + (data-kind: "custom-type-data", ..it) + } else if custom-type-key in it { + it.at(custom-type-key) + } else if stored-data-key in it { + it.at(stored-data-key) + } else { + (data-kind: "unknown", body: it, fields: (:), func: none, eid: none, fields-known: false, valid: false) + } + } else if type(it) != content { + (data-kind: "unknown", body: it, fields: (:), func: none, eid: none, fields-known: false, valid: false) + } else if ( + it.func() == styled + and it.has("label") + and it.child.func() == sequence + and it.child.children.len() >= 2 + and it.child.children.last().at("label", default: none) == lbl-tag + ) { + // Any labeled elements need to be wrapped in a 'styled' to avoid + // interference when joining with the element in a show rule on the + // label. + it.child.children.last().value + } else if it.func() == sequence and it.children.len() >= 2 { + let last = it.children.last() + if ( + last.at("label", default: none) == lbl-tag + // Workaround for 0.11.0 weirdly placing some 'meta' element sometimes + or sys.version < version(0, 12, 0) and { + last = it.children.at(it.children.len() - 2) + last.at("label", default: none) == lbl-tag + } + // Pre-1.0 elembic versions didn't add a label to metadata + or str(it.at("label", default: "")).starts-with(lbl-show-head) and last.func() == metadata + ) { + // Decomposing a recently-constructed (but not placed) element + last.value + } else { + (data-kind: "content", body: it, fields: it.fields(), func: it.func(), eid: none, fields-known: false, valid: false) + } + } else if it.func() == figure and it.has("kind") and it.kind == labelable-elem-figure-kind { + let last + if it.body.func() == sequence and it.body.children.len() >= 2 and { + last = it.body.children.last() + ( + last.at("label", default: none) == lbl-tag + // Workaround for 0.11.0 weirdly placing some 'meta' element sometimes + or sys.version < version(0, 12, 0) and { + last = it.children.at(it.children.len() - 2) + last.at("label", default: none) == lbl-tag + } + ) + } { + last.value + } else { + (data-kind: "content", body: it, fields: it.fields(), func: it.func(), eid: none, fields-known: false, valid: false) + } + } else if ( + it.has("label") + and str(it.label).starts-with(lbl-outer-head) + ) { + (data-kind: "incomplete-element-instance", body: it, fields: (:), func: (:), eid: str(it.label).slice(lbl-outer-head.len()), fields-known: false, valid: false) + } else { + (data-kind: "content", body: it, fields: it.fields(), func: it.func(), eid: none, fields-known: false, valid: false) + } +} + +// Obtain the fields of a type instance or element instance (custom or not). +// +// SAMPLE USAGE: +// +// #show e.selector(elem): it => { +// let fields = e.fields(it) +// [Hello #fields.name!] +// } +#let fields(it) = { + let info = data(it) + + if type(info) == dictionary and "data-kind" in info { + if info.data-kind in ("content", "element-instance", "type-instance") { + return info.fields + } + } + + (:) +} + +// Obtain context at an element's site. +// +// SAMPLE USAGE: +// +// 1. In show rules: +// +// #show e.selector(elem): it => { +// let (get, ..) = e.ctx(it) +// let other-elem-ctx = get(other-elem) +// [The other element field was set to #other-elem-ctx.field at that point!] +// } +// +// 2. In element declarations: +// +// #e.element.declare( +// ... +// synthesize: it => { +// // Get context for other element +// it.some-field = (e.ctx(it).get)(other-elem).field +// }, +// ... +// ) +#let ctx(it) = { + let info = data(it) + if type(info) == dictionary and "ctx" in info { + info.ctx + } else { + none + } +} + +// Obtain an element's or type's scope (usually a module with important definitions). +// +// SAMPLE USAGE: +// +// #let subelem = e.scope(elem).subelem +#let scope(it) = { + let info = data(it) + if type(info) == dictionary and "scope" in info { + info.scope + } else { + none + } +} + +/// Obtain an element's or custom type's constructor function. +/// For native elements, this will be equivalent to `it.func()`. +/// +/// Useful in custom element show rules, for example. +/// +/// This is equivalent to `e.data(it).func`. +/// +/// SAMPLE USAGE: +/// +/// ```typ +/// #show selector.or(e.selector(elem1), e.selector(elem2)): it => { +/// // Will be either elem1 or elem2 +/// let elem = e.func(it) +/// // 'elem == elem1' works, but comparing 'eid's is recommended +/// if e.eid(elem) == e.eid(elem1) { +/// [This is elem1] +/// } else { +/// [This is elem2] +/// } +/// } +/// ``` +/// +/// - it (any): element/custom type instance (or element/custom type itself) to get the constructor from +/// -> function | none +#let func(it) = { + let info = data(it) + if type(info) == dictionary and "func" in info { + info.func + } else { + none + } +} + +/// Obtain an element's eid. It uniquely distinguishes this element from others, +/// even if they have the same name, by including both its prefix and name. +/// +/// This is equivalent to `e.data(elem).eid`. +/// +/// - elem (any): custom element (or an instance of it) to get 'eid' from +/// -> function | none +#let eid(it) = { + let info = data(it) + if type(info) == dictionary and "eid" in info { + info.eid + } else { + none + } +} + +/// Obtain a custom type's tid. It uniquely distinguishes a custom type from +/// others, even if they have the same name, by including both its prefix and +/// name. Returns `none` on invalid input. +/// +/// This is equivalent to `e.data(typ).tid`. +/// +/// - typ (any): custom type (or an instance of it) to get 'tid' from +/// -> function | none +#let tid(it) = { + let info = data(it) + if type(info) == dictionary and "tid" in info { + info.tid + } else { + none + } +} + +// Obtain an element's counter. +// +// USAGE: +// +// #context { +// [The element counter value is #e.counter(elem).get().first()] +// } +#let counter_(elem) = { + let info = data(elem) + + if type(info) == dictionary and "data-kind" in info and (info.data-kind == "element" or info.data-kind == "element-instance") { + info.counter + } else { + assert(false, message: "elembic: e.counter: this is not an element") + } +} + +/// Get the name of a content's constructor function as a string. +/// +/// Returns 'none' on invalid input. +/// +/// USAGE: +/// +/// ```typ +/// assert.eq(func-name(my-elem()), "my-elem") +/// assert.eq(func-name([= abc]), "heading") +/// ``` +/// +/// - c (content): content to get the constructor function of +/// -> function | none +#let func-name(c) = { + if type(c) == function { + let func-data = data(c) + return if "name" in func-data { + func-data.name + } else { + none + } + } else if type(c) != content { + return none + } + let name = repr(c.func()) + if c.func() in elem-funcs { + let element-data = data(c) + if "eid" in element-data and element-data.eid != none { + name = if "name" in element-data and type(element-data.name) == str { element-data.name } else { "unknown custom element" } + } + } + name +} + +#let _letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-" + +/// This is used to obtain a debug representation of custom elements and types. +/// +/// Also supports native types (just calls `repr()` for them). +/// +/// - value (any): value to represent +/// - depth (int): current depth (must start at 0, conservative limit of 10 for now) +/// -> str +#let repr_(value, depth: 0) = { + if depth >= 10 { + return repr(value) + } + let typename = "" + let value-type = type(value) + if value-type == content and value.func() in elem-funcs { + let value-data = data(value) + if "eid" in value-data and value-data.eid != none { + value = value-data.fields + value-type = dictionary + typename = if "name" in value-data and type(value-data.name) == str { + value-data.name + } else { + "unknown-element" + } + } + } + + if value-type == dictionary { + let pairs = if typename != "" { + // Element fields => sort + value.pairs().sorted(key: ((k, _)) => k) + } else if custom-type-key in value { + let type-data = value.at(custom-type-key) + + let id = type-data.id + + typename = if "name" in id { + id.name + } else if id == "custom type" { + return if custom-type-data-key in value { + "custom-type(name: " + repr(value.name) + ", tid: " + repr(value.tid) + ")" + } else { + "custom-type()" + } + } else { + str(id) + } + + type-data.fields.pairs().sorted(key: ((k, _)) => k) + } else { + value.pairs() + } + + typename + "(" + pairs.map(((k, v)) => { + if k.codepoints().all(c => c in _letters) { + k + } else { + repr(k) + } + + ": " + + repr_(v, depth: depth + 1) + }).join(", ") + ")" + } else if value-type == array { + "(" + value.map(repr_.with(depth: depth + 1)).join(", ") + ")" + } else { + repr(value) + } +} + +/// Performs deep equality of values. +/// +/// This is necessary to reliably compare instances of the same element or +/// custom type, as well as data structures containing them such as arrays +/// or dictionary, between different versions of the same element or type, +/// by recursively comparing `eid(a) == eid(b) and fields(a) == fields(b)`. +/// However, this is notably slower than Typst's built-in equality check. +/// +/// - a (any): First value to compare. +/// - b (any): Second value to compare. +/// -> bool +#let eq(a, b) = { + if a == b { + return true + } + if type(a) != type(b) or type(a) not in (content, dictionary, array) { + return false + } + + // Recursively compare until we find a 'false' + let stack = ((a, b),) + let fuel = 3000 + while stack != () { + let (a, b) = stack.pop() + if a == b { + // Good! + continue + } + fuel -= 1 + if fuel == 0 { + return false + } + // Of course, the types must match + let a-type = type(a) + let b-type = type(b) + if a-type != b-type { + return false + } + + if a-type == array { + if a.len() != b.len() { + return false + } + stack += array.zip(a, b) + + // Only have special checks for composed types and custom types and elements + // of same type + } else if (a-type == content or a-type == dictionary) and eid(a) == eid(b) and tid(a) == tid(b) { + if eid(a) != none or tid(a) != none or a-type == content and a.func() == b.func() { + // Same element id, compare their fields + a = fields(a) + b = fields(b) + a-type = type(a) + if a-type != type(b) { + return false + } + } + if a-type != dictionary or a.len() != b.len() { + // Fields were invalid, or content didn't have the same func + return false + } + for (key, a-val) in a { + if key not in b { + return false + } + stack.push((a-val, b.at(key),)) + } + } else { + return false + } + } + + // No checks failed + true +} diff --git a/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/element.typ b/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/element.typ new file mode 100644 index 0000000..35af18e --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/elembic/1.1.1/src/element.typ @@ -0,0 +1,3693 @@ +#import "data.typ": data, lbl-show-head, lbl-meta-head, lbl-outer-head, lbl-counter-head, lbl-elem-prepare-check-head, lbl-empty-prepare-check, labelable-elem-figure-kind, lbl-ref-figure-kind-head, lbl-ref-figure-label-head, lbl-ref-figure, lbl-get, lbl-tag, lbl-rule-tag, lbl-old-rule-tag, lbl-special-rule-tag, lbl-data-metadata, lbl-stateful-mode, lbl-leaky-mode, lbl-normal-mode, lbl-auto-mode, lbl-global-select-head, prepared-rule-key, stored-data-key, element-key, element-data-key, element-meta-key, global-data-key, filter-key, special-rule-key, special-data-values, custom-type-key, custom-type-data-key, type-key, element-version, style-modes, style-state +#import "fields.typ" as field-internals +#import "filters.typ": verify-filter +#import "types/base.typ" +#import "types/types.typ" + +// Basic elements for our document tree analysis +#let sequence = [].func() +#let space = [ ].func() +#let styled = { set text(red); [a] }.func() +#let state-update-func = state(".").update(1).func() +#let counter-update-func = counter(".").update(1).func() + +// Default library-wide data. +#let default-global-data = ( + (global-data-key): true, + + // Keep track of versions in case we need some backwards-compatibility behavior + // in the future. + version: element-version, + + // If the style state should be read by set rules as the user has + // enabled stateful mode with `#show: e.stateful.enable()`. + stateful: false, + + // First known bib title. + // This is used by leaky mode to attempt to preserve the correct bibliography.title + // property. Evidently, it's not perfect, and leaky mode should be avoided. + first-bib-title: (), + + // Identical to 'global.select-count', this is only here for compatibility + // with older elements. + where-rule-count: 0, + + // Some global settings changeable through set rules. + settings: ( + // Whether non-stateful rules should default to leaky mode. + prefer-leaky: false, + + // Additional elements for which ancestry should be tracked. + // Setting this to 'any' will enable ancestry tracking for all elements + // (POTENTIALLY SLOW!). + track-ancestry: (:), + + // Additional elements which should support ancestry-related filters when + // queried. + store-ancestry: (:), + ), + + // Shared state between elements. + // Differently from settings, this is not meant to be configurable by users. + global: ( + // Version that created the default global data. + version: element-version, + + // Amount of select rules in the style chain so far. + // Used to apply a unique label. + select-count: 0, + + // Current element ancestors, from outermost to innermost. + ancestry-chain: (), + ), + + // Per-element data (set rules and other style chain info). + elements: (:), +) + +// Default per-element data. +#let default-data = ( + (element-data-key): true, + + version: element-version, + + // Chain for foldable fields, that is, fields which have special behavior + // when changed through more than one set rule. By default, specifying the + // same field in two subsequent set rules will have the innermost set rule + // override the value from the previous one, but this can be overridden + // for certain types where it makes sense to combine the two values in + // some way instead. For example, stroke fields have custom folding: if + // you specify 4pt for a stroke field in one set rule and orange in another, + // the final stroke will be 4pt + orange, not orange. + // + // This data structure has an entry for each changed foldable field, laid out as follows: + // ( + // foldable-field-name: ( + // folder: auto or (outer, inner) => combined value // how to combine two values, auto = simple sum, equivalent to (a, b) => a + b + // default: stroke(), // default value for this field to begin folding. This is 'field.default' unless 'required = true'. + // // Then, it is the type's default. + // values: (4pt, orange, ...) // list of all set values for this field (length = amount of times this field was changed) + // // only 'values' is used if possible, for efficiency. E.g.: values.sum(default: stroke()) + // data: ( // list to associate each value with the real style chain index and name. + // (index: 3, name: none, names: (), value: 4pt), // If 'revoke' or 'reset' are used, this list is used instead + // (index: 5, name: none, names: (), value: orange), // so we can know which values were revoked. + // ... + // ) + // ), + // ... + // ) + // + // The final argument passed to the constructor, if any, also has to be folded with the latest folded value, + // or with the field's default value if nothing was changed. However, that step is done separately. So, if + // no set rules change a particular foldable field, it is not present in this dictionary at all. + fold-chain: (:), + + // The current accumulated styles (overridden values for arguments) for the element. + chain: (), + + // Maps each style in the chain to some data. + // This is used to assign names to styles, so they can be revoked later. + data-chain: (), + + // All known names, so we can be aware of invalid revoke rules. + names: (:), + + // List of active revokes, of the form: + // (index: last-chain-index, revoking: name-revoked, name: none / name of the revoke itself) + revoke-chain: (), + + // This is set to true when a rule with '.within(eid)' is used. + track-ancestry: false, + + // Data for filtering. + filters: ( + // Filters applying to this element. Each filter is associated with a rule below. + // While some filters might trivially apply to all instances of an element, + // others might require specific field values to match, for example. + all: (), + + // This is an array of rules to apply for each filter in the same index. + // These rules are applied whenever an element matching the given filter + // is found. + rules: (), + + // Data associated with each filter, such as its name(s). + // Format: + // ((names: ("name1", "name2", ...)), ...) + data: (), + ), + + // Conditional set rules, which only apply to matching instances of an + // element. + cond-sets: ( + // Filters to apply to each set rule. + filters: (), + // For each filter above, the associated set rule args with the changed + // fields. + args: (), + // Data associated with each conditional set rule. + // Format: + // ((names: ("name1", "name2", ...), index: 0), ...) + data: (), + ), + + // Show rules that might apply to this element. + show-rules: ( + // Filters for each show rule. + filters: (), + // For each filter above, the associated show rule function. + callbacks: (), + // Data associated with each show rule. + // Format: + // ((names: ("name1", "name2", ...), index: 0), ...) + data: (), + ), + + // Applied selectors (filters for labels). + selects: ( + filters: (), + labels: (), + data: (), + ) +) + +/// This is meant to be used in a show rule of the form `#show ref: e.ref` to ensure +/// references to custom elements work properly. +/// +/// Please use [`e.prepare`](#eprepare) as it does that automatically, and more if +/// necessary. +/// +/// - args (arguments): ref and extra arguments +/// -> content +#let ref_(..args) = { + assert(args.pos().len() > 0, message: "elembic: element.ref: expected at least one positional argument (reference or label)") + let first-arg = args.pos().first() + + set ref(..args.named()) + + show ref: it => { + let element = if it.has("element") { + it.element + } else { + query(it.target).at(0, default: none) + } + if ( + element == none + or element.has("label") and str(element.label).starts-with(lbl-ref-figure-label-head) + or type(it.target) != label + ) { + // This is known to be a reference to a custom element + // (or the target is not something we can deal with, i.e. not a label) + return it + } + + let info = data(element) + if type(info) == dictionary and "data-kind" in info and info.data-kind == "element-instance" { + if "__future-ref" in info and element-version <= info.__future-ref.max-version { + return (info.__future-ref.call)(target: it.target, supplement: it.at("supplement", default: none), ref-instance: it, __future-version: element-version) + } + + let supplement = if it.has("supplement") and it.supplement != none { + (supplement: it.supplement) + } else { + (:) + } + + // Convert into a reference towards the reference figure + let converted-label = label(lbl-ref-figure-label-head + str(it.target)) + let reference = ref(converted-label, ..supplement) + + if "custom-ref" in info and info.custom-ref != none { + show ref.where(target: converted-label): [#info.custom-ref] + + reference + } else { + reference + } + } else { + it + } + } + + if type(first-arg) == content and first-arg.func() == ref { + first-arg + } else { + ref(..args) + } +} + +// Changes stateful mode settings within a certain scope. +// This function will sync all data between all modes (data from normal mode +// goes to state and data from stateful mode goes to normal mode). +// +// Setting it to 'true' tells all set rules to update the state, and also ensures +// getters retrieve the value from the state, even if not explicitly aware of +// stateful mode. +// +// By default, this function will not trigger any changes if one attempts to +// change the stateful mode to its current value. This behavior can be disabled +// with 'force: true', though that is not expected to make a difference in any way. +#let toggle-stateful-mode(enable, force: false) = doc => { + context { + let previous-bib-title = bibliography.title + [#context { + let (global-data, was-first-bib-title) = if ( + type(bibliography.title) == content + and bibliography.title.func() == metadata + and bibliography.title.at("label", default: none) == lbl-data-metadata + ) { + (bibliography.title.value, false) + } else { + ((..default-global-data, first-bib-title: previous-bib-title), true) + } + + set bibliography(title: previous-bib-title) + + if global-data.stateful != enable or force { + if not enable { + // Enabling stateful mode => use data from the style chain + // + // Disabling stateful mode => need to sync stateful with non-stateful, + // so we use data from the state + let chain = style-state.get() + global-data = if chain == () { + default-global-data + } else { + chain.last() + } + + // Store the first known bib title in the state as well + if global-data.first-bib-title == () and was-first-bib-title { + global-data.first-bib-title = previous-bib-title + } + } + + // Notify both modes about it (non-stateful and stateful) + global-data.stateful = enable + + let (show-normal, show-stateful) = if enable { + // TODO: Have a way to keep track of previous toggles and undo them + (none, it => it.value.body) + } else { + (it => it.value.body, none) + } + + show lbl-auto-mode: none + show lbl-normal-mode: show-normal + show lbl-stateful-mode: show-stateful + + // Sync data with style chain for non-stateful modes + show lbl-get: set bibliography(title: [#metadata(global-data)#lbl-data-metadata]) + + // Sync data with state for stateful mode + // Push at the start of the scope, pop at the end + [#style-state.update(chain => { + chain.push(global-data) + chain + })#doc#style-state.update(chain => { + _ = chain.pop() + chain + })] + } else { + // Nothing to do: it is already toggled to this value + doc + } + }#lbl-get] + } + + [#metadata(((special-rule-key): "toggle-stateful-mode", data-kind: "special-rule", kind: "toggle-stateful-mode", version: element-version, enable: enable, force: force))#lbl-special-rule-tag] +} + +#let request-ancestry-tracking(elements, requests) = { + for (eid, elem-data) in requests { + if eid not in elements { + elements.insert(eid, elem-data.default-data) + } + elements.at(eid).track-ancestry = true + } + + elements +} + +// Apply set and revoke rules to the current per-element data. +#let apply-rules(rules, elements: none, settings: (:), global: (:), extra-output: (:)) = { + for rule in rules { + if "__future" in rule and element-version <= rule.__future.max-version { + let output = (rule.__future.call)(rule, elements: elements, settings: settings, global: global, extra-output: extra-output, __future-version: element-version) + extra-output += output + if "elements" in output { + elements = output.elements + } + if "settings" in output { + settings = output.settings + } + if "global" in output { + global = output.global + } + continue + } + + let kind = rule.kind + if kind == "settings" { + let (write, transform) = rule + if write != none { + settings += write + } + if transform != none { + settings = transform(settings) + } + } else if kind == "set" { + let (element, args) = rule + let (eid, default-data, fields) = element + + // Forward-compatibility with newer elements + if ( + "__future-rules" in default-data + and "set" in default-data.__future-rules + and element-version <= default-data.__future-rules.set.max-version + ) { + let output = (default-data.__future-rules.set.call)(rule, elements: elements, settings: settings, global: global, extra-output: extra-output, __future-version: element-version) + extra-output += output + if "elements" in output { + elements = output.elements + } + if "settings" in output { + settings = output.settings + } + if "global" in output { + global = output.global + } + continue + } + + if eid in elements { + elements.at(eid).chain.push(args) + } else { + elements.insert(eid, (..default-data, chain: (args,))) + } + + let names = if "names" in rule { rule.names } else if "name" in rule and rule.name != none { (rule.name,) } else { () } + let compat-name = none + if names != () { + let element-data = elements.at(eid) + let index = element-data.chain.len() - 1 + compat-name = names.last() + + // Lazily fill the data chain with 'none' + // Add 'name' for compatibility with older elembic versions + elements.at(eid).data-chain += (none,) * (index - element-data.data-chain.len()) + elements.at(eid).data-chain.push((kind: "set", name: compat-name, names: names)) + + for rule-name in names { + elements.at(eid).names.insert(rule-name, true) + } + } + + if fields.foldable-fields != (:) and args.keys().any(n => n in fields.foldable-fields) { + // A foldable field was specified in this set rule, so we need to record the fold + // data in the corresponding data structures separately for later. + let element-data = elements.at(eid) + let index = element-data.chain.len() - 1 + for (field-name, fold-data) in fields.foldable-fields { + if field-name in args { + let value = args.at(field-name) + let value-data = (index: index, name: compat-name, names: names, value: value) + if field-name in element-data.fold-chain { + elements.at(eid).fold-chain.at(field-name).values.push(value) + elements.at(eid).fold-chain.at(field-name).data.push(value-data) + } else { + elements.at(eid).fold-chain.insert( + field-name, + ( + folder: fold-data.folder, + default: fold-data.default, + values: (value,), + data: (value-data,) + ) + ) + } + } + } + } + } else if kind == "revoke" { + let rule-names = if "names" in rule { rule.names } else if "name" in rule and rule.name != none { (rule.name,) } else { () } + let compat-name = if rule-names == () { + none + } else { + rule-names.last() + } + + for (name, element-data) in elements { + // Forward-compatibility with newer elements + if ( + "__future-rules" in element-data + and "revoke" in element-data.__future-rules + and element-version <= element-data.__future-rules.revoke.max-version + ) { + let output = (element-data.__future-rules.revoke.call)(rule, elements: elements, settings: settings, global: global, extra-output: extra-output, __future-version: element-version) + extra-output += output + if "elements" in output { + elements = output.elements + } + if "settings" in output { + settings = output.settings + } + if "global" in output { + global = output.global + } + continue + } + + // Can only revoke what's before us. + // If this element has no rules with this name, there is nothing to revoke; + // we shouldn't revoke names that come after us (inner rules). + // Note that this potentially includes named revokes as well. + if rule.revoking in element-data.names { + elements.at(name).revoke-chain.push((kind: "revoke", name: compat-name, names: rule-names, index: element-data.chain.len(), revoking: rule.revoking)) + + if rule-names != () { + for rule-name in rule-names { + elements.at(name).names.insert(rule-name, true) + } + } + } + } + } else if kind == "reset" { + // Whether the list of elements that this reset applies to is restricted. + let filtering = rule.eids != () + let rule-names = if "names" in rule { rule.names } else if "name" in rule and rule.name != none { (rule.name,) } else { () } + let compat-name = if rule-names == () { + none + } else { + rule-names.last() + } + + for (name, element-data) in elements { + // Forward-compatibility with newer elements + if ( + "__future-rules" in element-data + and "reset" in element-data.__future-rules + and element-version <= element-data.__future-rules.reset.max-version + ) { + let output = (element-data.__future-rules.reset.call)(rule, elements: elements, settings: settings, global: global, extra-output: extra-output, __future-version: element-version) + extra-output += output + if "elements" in output { + elements = output.elements + } + if "settings" in output { + settings = output.settings + } + if "global" in output { + global = output.global + } + continue + } + + // Can only revoke what's before us. + // If this element has no rules, no need to add a reset. + if (not filtering or name in rule.eids) and element-data.chain != () { + elements.at(name).revoke-chain.push((kind: "reset", name: compat-name, names: rule-names, index: element-data.chain.len())) + + if rule-names != () { + for rule-name in rule-names { + elements.at(name).names.insert(rule-name, true) + } + } + } + } + } else if kind == "filtered" { + let (filter, rule: inner-rule, names) = rule + if type(filter) != dictionary or "elements" not in filter or "kind" not in filter { + assert(false, message: "elembic: element.filtered: invalid filter found while applying rule: " + repr(filter) + "\nPlease use 'elem.with(field: value, ...)' to create a filter.\n\nhint: it might come from a package's element made with an outdated elembic version. Please update your packages.") + } + let target-elements = filter.elements + if target-elements == none { + assert(false, message: "elembic: element.filtered: this filter appears to apply to any element (e.g. it's a 'not' or 'custom' filter). It must match only within a certain set of elements. Consider using an 'and' filter, e.g. 'e.filters.and(wibble, e.not(wibble.with(a: 10)))' instead of just 'e.not(wibble.with(a: 10))', to restrict it.") + } + let base-data = (names: names) + + if "ancestry-elements" in filter and filter.ancestry-elements not in (none, (:)) { + elements = request-ancestry-tracking(elements, filter.ancestry-elements) + } + + for (eid, all-elem-data) in target-elements { + // Forward-compatibility with newer elements + if ( + "__future-rules" in all-elem-data.default-data + and "filtered" in all-elem-data.default-data.__future-rules + and element-version <= all-elem-data.default-data.__future-rules.filtered.max-version + ) { + let output = (all-elem-data.default-data.__future-rules.filtered.call)(rule, elements: elements, settings: settings, global: global, extra-output: extra-output, __future-version: element-version) + extra-output += output + if "elements" in output { + elements = output.elements + } + if "settings" in output { + settings = output.settings + } + if "global" in output { + global = output.global + } + continue + } + + if eid not in elements { + elements.insert(eid, all-elem-data.default-data) + } + if "filters" not in elements.at(eid) { + // Old version + elements.at(eid).filters = default-data.filters + } + + let index = elements.at(eid).chain.len() + let data = (..base-data, index: index) + + elements.at(eid).filters.all.push(filter) + elements.at(eid).filters.rules.push(inner-rule) + elements.at(eid).filters.data.push(data) + + // Push an entry to the data chain so we have an index to assign to + // this filter rule. This allows us to reset() it later. + elements.at(eid).chain.push((:)) + + // Lazily fill the data chain with 'none' + elements.at(eid).data-chain += (none,) * (index - elements.at(eid).data-chain.len()) + + // Keep "name" for some compatibility with older versions... + elements.at(eid).data-chain.push( + (kind: "filtered", name: if data.names == () { none } else { data.names.last() }, names: data.names) + ) + + for name in data.names { + // Ensure the name is registered so revoke rules on this name are + // treated as valid. + elements.at(eid).names.insert(name, true) + } + } + } else if kind == "show" { + let (filter, callback, names) = rule + if type(filter) != dictionary or "elements" not in filter or "kind" not in filter { + assert(false, message: "elembic: element.show_: invalid filter found while applying rule: " + repr(filter) + "\nPlease use 'elem.with(field: value, ...)' to create a filter.\n\nhint: it might come from a package's element made with an outdated elembic version. Please update your packages.") + } + let target-elements = filter.elements + if target-elements == none { + assert(false, message: "elembic: element.show_: this filter appears to apply to any element (e.g. it's a 'not' or 'custom' filter). It must match only within a certain set of elements. Consider using an 'and' filter, e.g. 'e.filters.and(wibble, e.not(wibble.with(a: 10)))' instead of just 'e.not(wibble.with(a: 10))', to restrict it.") + } + let base-data = (names: names) + + if "ancestry-elements" in filter and filter.ancestry-elements not in (none, (:)) { + elements = request-ancestry-tracking(elements, filter.ancestry-elements) + } + + for (eid, all-elem-data) in target-elements { + // Forward-compatibility with newer elements + if ( + "__future-rules" in all-elem-data.default-data + and "show" in all-elem-data.default-data.__future-rules + and element-version <= all-elem-data.default-data.__future-rules.show.max-version + ) { + let output = (all-elem-data.default-data.__future-rules.show.call)(rule, elements: elements, settings: settings, global: global, extra-output: extra-output, __future-version: element-version) + extra-output += output + if "elements" in output { + elements = output.elements + } + if "settings" in output { + settings = output.settings + } + if "global" in output { + global = output.global + } + continue + } + + if eid not in elements { + elements.insert(eid, all-elem-data.default-data) + } + if "show-rules" not in elements.at(eid) { + // Old version + elements.at(eid).show-rules = default-data.show-rules + } + + let index = elements.at(eid).chain.len() + let data = (..base-data, index: index) + + elements.at(eid).show-rules.filters.push(filter) + elements.at(eid).show-rules.callbacks.push(callback) + elements.at(eid).show-rules.data.push(data) + + // Push an entry to the data chain so we have an index to assign to + // this show rule. This allows us to reset() it later. + elements.at(eid).chain.push((:)) + + // Lazily fill the data chain with 'none' + elements.at(eid).data-chain += (none,) * (index - elements.at(eid).data-chain.len()) + + // Keep "name" for some compatibility with older versions... + elements.at(eid).data-chain.push( + (kind: "show", name: if data.names == () { none } else { data.names.last() }, names: data.names) + ) + + for name in data.names { + // Ensure the name is registered so revoke rules on this name are + // treated as valid. + elements.at(eid).names.insert(name, true) + } + } + } else if kind == "cond-set" { + let (filter, args, names, element) = rule + if type(filter) != dictionary or "elements" not in filter or "kind" not in filter { + assert(false, message: "elembic: element.cond-set: invalid filter found while applying rule: " + repr(filter) + "\nPlease use 'elem.with(field: value, ...)' to create a filter.\n\nhint: it might come from a package's element made with an outdated elembic version. Please update your packages.") + } + + if "ancestry-elements" in filter and filter.ancestry-elements not in (none, (:)) { + elements = request-ancestry-tracking(elements, filter.ancestry-elements) + } + + let (eid,) = element + + // Forward-compatibility with newer elements + if ( + "__future-rules" in element.default-data + and "cond-set" in element.default-data.__future-rules + and element-version <= element.default-data.__future-rules.cond-set.max-version + ) { + let output = (element.default-data.__future-rules.cond-set.call)(rule, elements: elements, settings: settings, global: global, extra-output: extra-output, __future-version: element-version) + extra-output += output + if "elements" in output { + elements = output.elements + } + if "settings" in output { + settings = output.settings + } + if "global" in output { + global = output.global + } + continue + } + + if eid not in elements { + elements.insert(eid, element.default-data) + } + if "cond-sets" not in elements.at(eid) { + // Old version + elements.at(eid).cond-sets = default-data.cond-sets + } + + let index = elements.at(eid).chain.len() + let data = (names: names, index: index) + elements.at(eid).cond-sets.filters.push(filter) + elements.at(eid).cond-sets.args.push(args) + elements.at(eid).cond-sets.data.push(data) + + // Push an entry to the data chain so we have an index to assign to + // this filter rule. This allows us to reset() it later. + elements.at(eid).chain.push((:)) + + // Lazily fill the data chain with 'none' + elements.at(eid).data-chain += (none,) * (index - elements.at(eid).data-chain.len()) + + // Keep "name" for some compatibility with older versions... + elements.at(eid).data-chain.push( + (kind: "cond-set", name: if data.names == () { none } else { data.names.last() }, names: data.names) + ) + + for name in data.names { + // Ensure the name is registered so revoke rules on this name are + // treated as valid. + elements.at(eid).names.insert(name, true) + } + } else if kind == "select" { + let (element-data: target-elements, names) = rule + let base-data = (names: names) + + for (eid, elem-data) in target-elements { + if "filters" not in elem-data or "labels" not in elem-data { + assert(false, message: "elembic: element.select: missing filters or labels for element " + repr(eid)) + } + let (filters, labels) = elem-data + assert(filters.len() == labels.len(), message: "elembic: element.select: differing lengths for filters and labels found (this is an internal error)") + if filters == () { + continue + } + + let sample-filter = filters.first() + assert( + type(sample-filter) == dictionary + and "kind" in sample-filter + and "elements" in sample-filter + and type(sample-filter.elements) == dictionary + and eid in sample-filter.elements, + message: "elembic: element.select: invalid filter found for element " + repr(eid) + ", it must contain the element's data.\nPlease use 'elem.with(field: value, ...)' to create a filter.\n\nhint: it might come from a package's element made with an outdated elembic version. Please update your packages." + ) + + let all-elem-data = sample-filter.elements.at(eid) + + // Forward-compatibility with newer elements + if ( + "__future-rules" in all-elem-data.default-data + and "select" in all-elem-data.default-data.__future-rules + and element-version <= all-elem-data.default-data.__future-rules.select.max-version + ) { + let output = (all-elem-data.default-data.__future-rules.select.call)(rule, elements: elements, settings: settings, global: global, extra-output: extra-output, __future-version: element-version) + extra-output += output + if "elements" in output { + elements = output.elements + } + if "settings" in output { + settings = output.settings + } + if "global" in output { + global = output.global + } + continue + } + + for filter in filters { + assert( + type(filter) == dictionary + and "kind" in filter + and "elements" in filter, + message: "elembic: element.select: invalid filter found for element " + repr(eid) + "\nPlease use 'elem.with(field: value, ...)' to create a filter.\n\nhint: it might come from a package's element made with an outdated elembic version. Please update your packages." + ) + if "ancestry-elements" in filter and filter.ancestry-elements not in (none, (:)) { + elements = request-ancestry-tracking(elements, filter.ancestry-elements) + } + } + + if eid not in elements { + elements.insert(eid, all-elem-data.default-data) + } + if "selects" not in elements.at(eid) { + // Old version + elements.at(eid).selects = default-data.selects + } + + let index = elements.at(eid).chain.len() + let data = (..base-data, index: index) + + elements.at(eid).selects.filters += filters + elements.at(eid).selects.labels += labels + elements.at(eid).selects.data += (data,) * filters.len() + + // Push an entry to the data chain so we have an index to assign to + // this filter rule. This allows us to reset() it later. + elements.at(eid).chain += (((:),) * filters.len()) + + // Lazily fill the data chain with 'none' + elements.at(eid).data-chain += (none,) * (index - elements.at(eid).data-chain.len()) + + // Keep "name" for some compatibility with older versions... + elements.at(eid).data-chain.push( + (kind: "select", name: if data.names == () { none } else { data.names.last() }, names: data.names) + ) + + for name in data.names { + // Ensure the name is registered so revoke rules on this name are + // treated as valid. + elements.at(eid).names.insert(name, true) + } + } + } else if kind == "apply" { + // Mostly a fallback in case the rule is accidentally passed here... + let output = apply-rules(rule.rules, elements: elements, settings: settings, global: global, extra-output: extra-output) + extra-output += output + if "elements" in output { + elements = output.elements + } + if "settings" in output { + settings = output.settings + } + if "global" in output { + global = output.global + } + } else { + assert(false, message: "elembic: element: invalid rule kind '" + rule.kind + "'\n\nhint: this might mean you're using packages depending on conflicting elembic versions. Please ensure your dependencies are up-to-date.") + } + } + + (..extra-output, elements: elements, settings: settings) +} + +// Prepare rule(s), returning a function `doc => ...` to be used in +// `#show: rule`. The rule is attached as metadata to the returned +// content so it can still be accessed outside of a show rule. +// +// This is where we execute our main machinery to apply rules to the +// document, that is, modifications to the global data of custom +// elements. This is done in different ways depending on the mode: +// +// - In normal mode, we create 'get rule' points by annotating +// context blocks with `#lbl-get`. Any modifications to the global +// data are stored as 'set bibliography(title: metadata with data)' +// scoped to context blocks with that label. Therefore, we can access +// the data by retrieving bibliography.title inside those blocks. +// +// The downside is that the entire document is wrapped in context, +// so 'max show rule depth exceeded' errors can occur. +// +// - In leaky mode, it is similar, but we reset bibliography.title +// to an arbitrary value instead of having two context blocks to +// ensure it remains unchanged. +// +// - In stateful mode, we don't wrap anything around the document, +// removing the 'max show rule depth exceeded' problem. Rather, we +// place a state update at the start and another at the end of the +// scope, respectively updating the global data and then undoing +// the update, ensuring it only applies to that scope. +// +// The downside is that this uses 'state()', which can lead to +// relayouts (slower) and even diverging layout. +#let prepare-rule(rule) = { + let rules = if rule.kind == "apply" { rule.rules } else { (rule,) } + + doc => { + let rule = rule + let rules = rules + let mode = rule.mode + + // If there are two 'show:' in a row, flatten into a single set of rules + // instead of running this function multiple times, reducing the + // probability of accidental nested function limit errors. + // + // Note that all rules replace the document with + // [#context { ... doc .. }[#metadata(doc: doc, rule: rule)#lbl-rule-tag]] + // We get the second child to extract the original rule information. + // If 'doc' has the form above, this means the user wrote + // #show: rule1 + // #show: rule2 + // which we want to unify. So we check children len == 2 and unify if the tag is there. + // + // But we also want to accept some parbreaks before, i.e. + // + // #show: rule1 + // + // #show: rule2 + // + // This generates a doc of the form + // [#parbreak()[#context { ... doc .. }[#metadata(doc: doc, rule: rule)#lbl-rule-tag]]] + // So we also check for children len >= 2 (although == 2 is enough in that case) and + // strip any leading parbreaks / spaces / linebreaks, moving them to the new 'doc' (they + // now receive the rules, which is technically incorrect, but in practice is only a problem + // if you have a show rule on parbreak or space or something, which is odd). + // + // Note also that + // #show: rule1 + // + // // hello world! + // // hello world! + // // hello world! + // + // #show: rule2 + // + // produces + // [#parbreak()#space()#space()#parbreak()[... rule substructure with metadata... ]] + // which makes the need for stripping multiple kinds of whitespace explicit. + // We limit at 100 to prevent unbounded search. + // + // We also need to consider the case with + // #show: rule1 + // #set native(field: value) + // #show: rule2 + // + // in which case the document structure (from rule1's view) is + // + // styled(child: [... rule2 ...], styles: ..) + // + // Worse, there could be parbreaks around the set rule: + // + // #show: rule1 + // + // #set native(field: value) + // + // #show: rule2 + // + // leading to + // + // sequence(parbreak(), styled(child: sequence(parbreak(), [ ... rule2 ... ]), styles: ..)) + // + // so we need to perform a document tree walk to lift rule2 and transform this into + // + // #show: apply( + // rule1 + // rule2 + // ) + // + // #set native(field: value) + // ... + // + // Tree walk is performed as follows: + // + // this rule + // \ + // sequence + // \ space parbreak ... sequence + // \ space parbreak ... styled (styles = S) + // \ sequence + // \ space parbreak ... inner rule! + // \ (rule.doc, rule.rule) + // We store each tree level in 'wrappers' so we can reconstruct this document structure without 'rule!'. + // In the case above, that would correspond to + // wrappers = ((sequence, (space, parbreak, ...)), (sequence, space, parbreak, ...), (styled, S), (sequence, space, parbreak, ...)) + // and 'rule' would become 'potential-doc'. + // + // We would then wrap 'rule.doc' in reverse order, adding after the sequence prefix or + // making it the styled child, producing + // + // this rule + inner rule + // \ + // (sequence, apply(this rule, inner rule)) + // \ space parbreak ... sequence + // \ space parbreak ... styled (styles = S) + // \ sequence + // \ space parbreak ... rule.doc + // + // as desired. That is, we move the inner rule up into this rule in order to only consume 1 from + // the rule limit, which is valid since the rule won't apply to spaces, parbreaks, and styled. + // Of course, there could be show rules towards a different structure, but we assume that the user + // understands that show rules on spacing may cause unexpected behavior. + let potential-doc = [#doc] + let wrappers = () + let max-depth = 150 + // Acceptable content types for set rule lifting. + // These are content types that are leaves and we usually don't expect them to + // be replaced in a show rule by an actual custom element. + // If we find something that isn't here, e.g. a block, we stop searching as we can't lift any further rules. + // We also exclude anything with a label since that indicates there might be a show rule application incoming. + let whitespace-funcs = (parbreak, space, linebreak, h, v, state-update-func, counter-update-func) + // Content types we can peek at. + let recursing-funcs = (styled, sequence) + let loop-prefix = none + let loop-children = () + let loop-last = none + + while max-depth > 0 { + // Child is #{ + // set something(abc: def) + // show something: else + // [some stuff] + // } + if potential-doc.func() == styled { + max-depth -= 1 + wrappers.push((styled, potential-doc.styles)) + + // 'Recursively' check the child + potential-doc = [#potential-doc.child] + } else if ( + // Child is #[ + // (parbreak) + // (space) + // #[ sequence, rule or more styles ] + // ] + potential-doc.func() == sequence + and { loop-children = potential-doc.children; loop-children.len() >= 2 } // something like 'if let Sequence(children) = potential-doc { ... }' + and { loop-last = loop-children.last(); loop-last.func() in recursing-funcs } + and max-depth - loop-children.len() > 0 + and { + loop-prefix = loop-children.slice(0, -1); + loop-prefix.all(t => (t.func() in whitespace-funcs or t == []) and t.at("label", default: none) == none) + } + ) { + max-depth -= loop-children.len() + wrappers.push((sequence, loop-prefix)) + + // 'Recursively' check the last child + potential-doc = loop-last + } else { + break + } + } + + // Merge with the closest rule application below us, "moving" it upwards + // and reducing the rule count by 1 + let last-label = none + while ( + potential-doc.func() == sequence + and potential-doc.children.len() == 2 + and { + last-label = potential-doc.children.last().at("label", default: none) + last-label == lbl-rule-tag or last-label == lbl-old-rule-tag + } + ) { + let last = potential-doc.children.last() + let inner-rule = last.value.rule + + // Process all rules below us together with this one + if inner-rule.kind == "apply" { + // Note: apply should automatically distribute modes across its children, + // so it's okay if we don't inherit its own mode here. + rules += inner-rule.rules + } else { + rules.push(inner-rule) + } + + // We assume 'apply' already checked its own rules. + // Therefore, we only need to fold a single time. + // Don't check all rules every time again. + if ( + inner-rule.mode == style-modes.stateful + or mode != style-modes.stateful and inner-rule.mode == style-modes.leaky + or mode == auto + ) { + // Prioritize more explicit modes: + // stateful > leaky > normal + mode = inner-rule.mode + } + + // Convert this into an 'apply' rule + rule = ((prepared-rule-key): true, version: element-version, kind: "apply", rules: rules, mode: mode, name: none, names: ()) + + // Place what's inside, don't place the context block that would run our code again + doc = last.value.doc + + // Reconstruct the document structure. + // Must be in reverse (innermost wrapper to outermost). + for (func, data) in wrappers.rev() { + if func == styled { + doc = styled(doc, data) + } else { + // (sequence, prefix) + // Re-add stripped whitespace and stuff + doc = data.join() + doc + } + } + + if "__future" in last.value and element-version <= last.value.__future.max-version { + let res = (last.value.__future.call)(rule, doc, __future-version: element-version) + + if "doc" in res { + return res.doc + } + } + + if last-label == lbl-old-rule-tag { + // If we're merging with an older rule version, we may have to merge a + // newer version again + potential-doc = last.value.doc + } else { + break + } + } + + // Stateful mode: no context, just push in a state at the start of the scope + // and pop to previous data at the end. + let stateful = { + style-state.update(chain => { + let global-data = if chain == () { + default-global-data + } else { + chain.last() + } + + assert( + global-data.stateful, + message: "elembic: element rule: cannot use a stateful rule without enabling the global stateful toggle\n hint: if you don't mind the performance hit, write '#show: e.stateful.enable()' somewhere above this rule, or at the top of the document to apply to all" + ) + + if "settings" not in global-data { + global-data.settings = default-global-data.settings + } + + if "global" not in global-data { + global-data.global = default-global-data.global + } + + global-data += apply-rules(rules, elements: global-data.elements, settings: global-data.settings, global: global-data.global) + + chain.push(global-data) + chain + }) + doc + style-state.update(chain => { + _ = chain.pop() + chain + }) + } + + // Leaky mode: one context resetting bibliography.title. + let leaky = [#context { + let global-data = if ( + type(bibliography.title) == content + and bibliography.title.func() == metadata + and bibliography.title.at("label", default: none) == lbl-data-metadata + ) { + bibliography.title.value + } else { + // Bibliography title wasn't overridden, so we can use it + (..default-global-data, first-bib-title: bibliography.title) + } + + if mode == auto and ("settings" not in global-data or "prefer-leaky" not in global-data.settings or not global-data.settings.prefer-leaky) { + // User didn't want leaky. + return none + } + + let first-bib-title = global-data.first-bib-title + if first-bib-title == () { + // Nobody has seen the bibliography title (bug?) + first-bib-title = auto + } + + if global-data.stateful { + if mode == auto { + // User chose something else. + // Don't even place anything. + return none + } else { + // Use state instead! + return { + set bibliography(title: first-bib-title) + stateful + } + } + } + + if "settings" not in global-data { + global-data.settings = default-global-data.settings + } + + if "global" not in global-data { + global-data.global = default-global-data.global + } + + global-data += apply-rules(rules, elements: global-data.elements, settings: global-data.settings, global: global-data.global) + + set bibliography(title: first-bib-title) + show lbl-get: set bibliography(title: [#metadata(global-data)#lbl-data-metadata]) + doc + }#lbl-get] + + // Normal mode: two nested contexts: one retrieves the current bibliography title, + // and the other retrieves the title with metadata and restores the current title. + let normal = context { + let previous-bib-title = bibliography.title + [#context { + let global-data = if ( + type(bibliography.title) == content + and bibliography.title.func() == metadata + and bibliography.title.at("label", default: none) == lbl-data-metadata + ) { + bibliography.title.value + } else { + (..default-global-data, first-bib-title: previous-bib-title) + } + + if mode == auto and "settings" in global-data and "prefer-leaky" in global-data.settings and global-data.settings.prefer-leaky { + // User wants leaky. + return none + } + + if global-data.stateful { + if mode == auto { + // User chose something else. + // Don't even place anything. + return none + } else { + // Use state instead! + return { + set bibliography(title: previous-bib-title) + stateful + } + } + } + + if "settings" not in global-data { + global-data.settings = default-global-data.settings + } + + if "global" not in global-data { + global-data.global = default-global-data.global + } + + global-data += apply-rules(rules, elements: global-data.elements, settings: global-data.settings, global: global-data.global) + + set bibliography(title: previous-bib-title) + show lbl-get: set bibliography(title: [#metadata(global-data)#lbl-data-metadata]) + doc + }#lbl-get] + } + + let body = if mode == auto { + // Allow user to pick the mode through show rules. + [#metadata((body: stateful))#lbl-stateful-mode] + [#metadata((body: normal))#lbl-normal-mode] + [#leaky] + [#normal#lbl-auto-mode] + } else if mode == style-modes.normal { + normal + } else if mode == style-modes.leaky { + leaky + } else if mode == style-modes.stateful { + [#context { + let global-data = if ( + type(bibliography.title) == content + and bibliography.title.func() == metadata + and bibliography.title.at("label", default: none) == lbl-data-metadata + ) { + bibliography.title.value + } else { + default-global-data + } + + if not global-data.stateful { + let only-rule = if rules.len() == 1 { rules.first() } else { (kind: "apply", rules: rules, name: none, names: (), mode: auto) } + let named = if "names" in only-rule and only-rule.names != () { + " named " + only-rule.names.map(repr).join(", ") + } else if "name" in only-rule and only-rule.name != none { + " named " + repr(only-rule.name) + } else { + "" + } + + let extra = if only-rule.kind == "revoke" { + " revoking " + repr(only-rule.revoking) + } else { + "" + } + + assert( + false, + message: ( + "elembic: element rule: cannot use a stateful rule without enabling the global stateful toggle" + + "\n hint: if you don't mind the performance hit, write '#show: e.stateful.enable()' somewhere above this rule, or at the top of the document to apply to all" + + if only-rule.kind != "apply" { "\n help: this was triggered by a " + repr(only-rule.kind) + " rule" + named + extra } + ) + ) + } + }#lbl-get] + + stateful + } else { + panic("element rule: unknown mode: " + repr(mode)) + } + + // Add the rule tag after each rule application. + // This allows extracting information about the rule before it is applied. + // It also allows combining the rule with an outer rule before application, + // as we do earlier. + [#body#metadata((data-kind: "prepared-rule", version: element-version, routines: (prepare-rule: prepare-rule, apply-rules: apply-rules), doc: doc, rule: rule))#lbl-rule-tag] + } +} + +/// Apply a set rule to a custom element. Check out the Styling guide for more information. +/// +/// Note that this function only accepts non-required fields (that have a `default`). +/// Any required fields must always be specified at call site and, as such, are always +/// be prioritized, so it is pointless to have set rules for those. +/// +/// Keep in mind the limitations when using set rules, as well as revoke, reset and +/// apply rules. +/// +/// As such, when applying many set rules at once, please use `e.apply` instead +/// (or specify them consecutively so `elembic` does that automatically). +/// +/// USAGE: +/// +/// ```typ +/// #show: e.set_(superbox, fill: red) +/// #show: e.set_(superbox, optional-pos-arg1, optional-pos-arg2) +/// +/// // This call will be equivalent to: +/// // #superbox(required-arg, optional-pos-arg1, optional-pos-arg2, fill: red) +/// #superbox(required-arg) +/// ``` +/// +/// - elem (function): element to apply the set rule on +/// - fields (arguments): optional fields to set (positionally or named, depending on the field) +/// -> function +#let set_(elem, ..fields) = { + if type(elem) == function { + elem = data(elem) + } + assert(type(elem) == dictionary, message: "elembic: element.set_: please specify the element's constructor or data in the first parameter") + let (res, args) = (elem.parse-args)(fields, include-required: false) + if not res { + assert(false, message: args) + } + + prepare-rule( + ((prepared-rule-key): true, version: element-version, kind: "set", name: none, names: (), mode: auto, element: (eid: elem.eid, default-data: elem.default-data, fields: elem.fields), args: args) + ) +} + +/// Prepare a selector similar to 'element.where(..args)' +/// which can be used in "show sel: set". Receives a filter +/// generated by 'element.with(fields)' or '(element-data.where)(fields)'. +/// +/// This works by checking the filter within all element instances and, +/// if they match, they receive a unique label to be matched +/// by that selector. The label is then provided to the callback function +/// as the selector. +/// +/// Each requested selector is passed as a separate parameter to the callback. +/// You must wrap the remainder of the document that depends on those selectors +/// in this callback. +/// +/// USAGE: +/// +/// ```typ +/// #e.select(superbox.with(fill: red), prefix: "my first select", superbox.with(width: auto), (red-superbox, auto-superbox) => { +/// // Hide superboxes with red fill or auto width +/// show red-superbox: none +/// show auto-superbox: none +/// +/// // This one is hidden +/// #superbox(fill: red) +/// +/// // This one is hidden +/// #superbox(width: auto) +/// +/// // This one is kept +/// #superbox(fill: green, width: 5pt) +/// }) +/// ``` +/// +/// - args (function): filters in the format 'element.with(field-a: a, field-b: b)'. Note that you must write fields' names even if they are positional. +/// - receiver (function): receives one requested selector per filter as separate arguments, must return content. +/// - prefix (str): a unique prefix for selectors generated by this 'selector' to disambiguate from other calls to this function. +/// -> content +#let select(..args, receiver, prefix: 0) = { + assert(type(prefix) == str, message: "elembic: element.select: please pick a unique string 'prefix:' argument for the selectors generated by this call to 'select' to ensure they don't clash with other calls to 'select'.") + assert(args.named() == (:), message: "elembic: element.select: unexpected named arguments") + assert(type(receiver) == function, message: "elembic: element.select: last argument must be a function receiving each prepared selector as a separate argument") + + let filters = args.pos() + + // (eid: ((index, filter), ...)) + // The idea is to apply all filters for a given eid at once + let filters-by-eid = (:) + // (eid: sel) + let labels-by-eid = (:) + // Elements which still require explicit show rules. + let old-elements = (:) + let ordered-eids = () + + let i = 0 + for filter in filters { + if type(filter) == function { + filter = filter(__elembic_data: special-data-values.get-where) + } + + if type(filter) != dictionary or filter-key not in filter { + if type(filter) == selector { + assert(false, message: "elembic: element.select: Typst-native selectors cannot be specified here, only those of custom elements") + } + assert(false, message: "elembic: element.select: expected a valid filter, such as 'custom-element' or 'custom-element.with(field-name: value, ...)', got " + base.typename(filter)) + } + + if "elements" not in filter { + assert(false, message: "elembic: element.select: invalid filter found while applying rule, as it did not have an 'elements' field: " + repr(filter) + "\nPlease use 'elem.with(field: value, ...)' to create a filter.\n\nhint: it might come from a package's element made with an outdated elembic version. Please update your packages.") + } + + for (eid, elem-data) in filter.elements { + if "sel" not in elem-data { + assert(false, message: "elembic: element.select: filter did not have the element's selector") + } + if elem-data.eid in labels-by-eid and labels-by-eid.at(elem-data.eid) != elem-data.sel { + assert(false, message: "elembic: element.select: filter had a different selector from the others for the same element ID, check if you're not using conflicting library versions (could also be a bug)") + } + + if elem-data.eid not in labels-by-eid { + labels-by-eid.insert(elem-data.eid, elem-data.sel) + } + + if elem-data.eid in filters-by-eid { + filters-by-eid.at(elem-data.eid).push((i, filter)) + } else { + filters-by-eid.insert(elem-data.eid, ((i, filter),)) + ordered-eids.push(elem-data.eid) + } + + if ("version" not in elem-data or elem-data.version <= 1) and ("default-data" not in elem-data or "selects" not in elem-data.default-data) { + old-elements.insert(elem-data.eid, true) + } + } + i += 1 + } + + context { + let previous-bib-title = bibliography.title + [#context { + let global-data = if ( + type(bibliography.title) == content + and bibliography.title.func() == metadata + and bibliography.title.at("label", default: none) == lbl-data-metadata + ) { + bibliography.title.value + } else { + (..default-global-data, first-bib-title: previous-bib-title) + } + + if global-data.stateful { + let chain = style-state.get() + global-data = if chain == () { + default-global-data + } else { + chain.last() + } + } + + if "global" not in global-data { + global-data.global = default-global-data.global + } + + // Amount of 'select rules' so far, so we can + // assign a unique number to each query + let rule-counter = global-data.global.at("select-count", default: 0) + + // Generate labels by counting up, and update counter + let matching-labels = range(0, filters.len()).map(i => label(lbl-global-select-head + prefix + str(rule-counter + i))) + rule-counter += matching-labels.len() + global-data.global.select-count = rule-counter + + // Provide labels to the body, one per filter + // These labels only match the shown bodies of + // elements with matching field values + let body = receiver(..matching-labels) + + // Apply show rules to the body to add labels to matching elements + let styled-body = ordered-eids.filter(e => e in old-elements).fold(body, (acc, eid) => { + let filters = filters-by-eid.at(eid) + show labels-by-eid.at(eid): it => { + let data = data(it) + let tag = [#metadata(data)#lbl-tag] + let fields = data.fields + + let labeled-it = it + for (i, filter) in filters { + // Check if all positional and named arguments match + // Note: no ancestry support since newer elements don't run this + // code, they use 'select' rules instead + if verify-filter(fields, eid: eid, filter: filter, ancestry: ()) { + // Add corresponding label and preserve tag so 'data(it)' still works + labeled-it = [#[#labeled-it#tag]#matching-labels.at(i)] + } + } + + labeled-it + } + + acc + }) + + set bibliography(title: previous-bib-title) + + let pairs-by-eid = (:) + for eid in ordered-eids { + if eid in old-elements or filters-by-eid.at(eid, default: ()) == () { + continue + } + let pairs = filters-by-eid.at(eid).map(((i, f)) => (f, matching-labels.at(i))) + let (filters, labels) = array.zip(..pairs) + pairs-by-eid.insert(eid, (filters: filters, labels: labels)) + } + + if pairs-by-eid != (:) { + let select-rule = ( + ((prepared-rule-key): true, + version: element-version, + kind: "select", + name: none, + names: (), + mode: auto, + element-data: pairs-by-eid, + ) + ) + global-data += apply-rules( + (select-rule,), + elements: global-data.elements, + settings: global-data.at("settings", default: default-global-data.settings), + global: global-data.at("global", default: default-global-data.global) + ) + } + + // Increase select rule counter for further select rules + if global-data.stateful { + style-state.update(chain => { + chain.push(global-data) + chain + }) + + styled-body + + style-state.update(chain => { + _ = chain.pop() + chain + }) + } else { + show lbl-get: set bibliography(title: [#metadata(global-data)#lbl-data-metadata]) + styled-body + } + }#lbl-get] + } + + [#metadata( + ( + (special-rule-key): "select", + data-kind: "special-rule", + kind: "select", + version: element-version, + filters: filters, + computed: (filters-by-eid: filters-by-eid, labels-by-eid: labels-by-eid, ordered-eids: ordered-eids), + receiver: receiver, + prefix: prefix + ) + )#lbl-special-rule-tag] +} + +/// Apply filtered rules to a custom element's descendants +/// (but not to itself; for that use `cond-set`). +/// +/// USAGE: +/// +/// ```typ +/// #show: e.filtered( +/// elem, +/// e.set_(elem3, fields: ...) +/// ) +/// ``` +/// +/// When applying many set rules at once, use 'apply' instead of 'set' on the last parameter. +/// +/// - filter (filter): filter specifying which element instances should create this set rule +/// for their children. +/// - rule (rule): which rule to create under matched elements. +/// -> function +#let filtered(filter, rule) = { + if type(filter) == function { + filter = filter(__elembic_data: special-data-values.get-where) + } + assert(type(filter) == dictionary and filter-key in filter, message: "elembic: element.filtered: invalid filter, please use 'custom-element.with(...)' to generate a filter.") + assert(type(rule) == function, message: "elembic: element.filtered: this is not a valid rule (not a function), please use functions such as 'set_' to create one.") + assert("elements" in filter, message: "elembic: element.filtered: this filter is missing the 'elements' field; this indicates it comes from an element generated with an outdated elembic version. Please use an element made with an up-to-date elembic version.") + assert(filter.elements != (:), message: "elembic: element.filtered: this filter appears to not be restricted to any elements and is thus impossible to match. It must apply to exactly one element (the one receiving the set rule). Consider using a different filter.") + assert(filter.elements != none, message: "elembic: element.filtered: this filter appears to apply to any element (e.g. it's a 'not' or 'custom' filter). It must match only within a certain set of elements. Consider using an 'and' filter, e.g. 'e.filters.and(wibble, e.not(wibble.with(a: 10)))' instead of just 'e.not(wibble.with(a: 10))', to restrict it.") + + let rule = rule([]).children.last().value.rule + let filtered-rule = ((prepared-rule-key): true, version: element-version, kind: "filtered", filter: filter, rule: rule, name: none, names: (), mode: rule.at("mode", default: auto)) + if rule.kind == "apply" { + // Transpose filtered(filter, apply(a, b, c)) into apply(filtered(filter, a), filtered(filter, b), filtered(filter, c)) + let i = 0 + for inner-rule in rule.rules { + assert(inner-rule.kind in ("show", "set", "revoke", "reset", "cond-set", "filtered"), message: "elembic: element.filtered: can only filter apply, show, set, revoke, reset, filtered and cond-set rules at this moment, not '" + inner-rule.kind + "'") + + rule.rules.at(i) = (..filtered-rule, rule: inner-rule, mode: inner-rule.at("mode", default: auto)) + + i += 1 + } + + // Keep the apply but with everything filtered. + prepare-rule(rule) + } else { + assert(rule.kind in ("show", "set", "revoke", "reset", "cond-set", "filtered"), message: "elembic: element.filtered: can only filter apply, show, set, revoke, reset, filtered and cond-set rules at this moment, not '" + rule.kind + "'") + + prepare-rule(filtered-rule) + } +} + +/// Apply a conditional set rule to a custom element. The set rule is only applied if +/// the given filter matches for that element. +/// +/// Check out the Styling guide for more information. +/// +/// Note that this function only accepts non-required fields (that have a `default`). +/// Any required fields must always be specified at call site and, as such, are always +/// going to be prioritized, so it is pointless to have set rules for those. +/// +/// Keep in mind the limitations when using set rules, as well as revoke, reset and +/// apply rules. +/// +/// As such, when applying many set rules at once, please use `e.apply` instead +/// (or specify them consecutively so `elembic` does that automatically). +/// +/// USAGE: +/// +/// ```typ +/// #show: e.set_(superbox, fill: red) +/// #show: e.cond-set(superbox.with(data: 10), fill: blue) +/// +/// #superbox(data: 5) // this will have red fill +/// #superbox(data: 10) // this will have blue fill +/// ``` +/// +/// - filter (filter): filter specifying which element instances should receive this set rule. +/// - fields (arguments): optional fields to set (positionally or named, depending on the field) +/// -> function +#let cond-set(filter, ..fields) = { + if type(filter) == function { + filter = filter(__elembic_data: special-data-values.get-where) + } + assert(type(filter) == dictionary and filter-key in filter, message: "elembic: element.cond-set: invalid filter, please pass just 'custom-element' or use 'custom-element.with(...)' to generate a filter.") + assert("elements" in filter, message: "elembic: element.cond-set: this filter is missing the 'elements' field; this indicates it comes from an element generated with an outdated elembic version. Please use an element made with an up-to-date elembic version.") + assert(filter.elements != (:), message: "elembic: element.cond-set: this filter appears to not be restricted to any elements and is thus impossible to match. It must apply to exactly one element (the one receiving the set rule). Consider using a different filter.") + assert(filter.elements != none, message: "elembic: element.cond-set: this filter appears to apply to any element. It must apply to exactly one element (the one receiving the set rule). Consider using an 'and' filter, e.g. 'e.filters.and(wibble, e.not(wibble.with(a: 10)))' instead of just 'e.not(wibble.with(a: 10))', to restrict it.") + assert(filter.elements.len() == 1, message: "elembic: element.cond-set: this filter appears to apply to more than one element. It must apply to exactly one element (the one receiving the set rule).") + let (eid, elem) = filter.elements.pairs().first() + + let (res, args) = (elem.parse-args)(fields, include-required: false) + if not res { + assert(false, message: args) + } + + prepare-rule( + ((prepared-rule-key): true, version: element-version, kind: "cond-set", name: none, names: (), mode: auto, filter: filter, element: (eid: elem.eid, default-data: elem.default-data, fields: elem.fields), args: args) + ) +} + +/// Applies a show rule through the elembic stylechain, thus making it +/// revokable and also allowing easy usage of filters. +/// +/// Show rules allow you to transform all occurrences of one or more elements, +/// replacing them with arbitrary document content. +/// +/// For example: +/// +/// ```typ +/// #show: e.show_(elem.with(fill: blue), it => [Hello *#it*!]) +/// +/// #elem(fill: red)[First] +/// #elem(fill: blue)[Second] // displays as "Hello *Second*!" +/// ``` +/// +/// - filter (filter): which element(s) to apply the rule to, with which fields etc. +/// - callback (function | content | str | none): replacement content or transformation function (content -> content) +/// receiving any matched elements and returning what to replace it with. +/// -> function +#let show_(filter, replacement, mode: auto) = { + if type(filter) == function { + filter = filter(__elembic_data: special-data-values.get-where) + } + assert(type(filter) == dictionary and filter-key in filter, message: "elembic: element.show_: invalid filter, please use 'custom-element.with(...)' to generate a filter.") + assert(replacement == none or type(replacement) in (function, str, content), message: "elembic: element.show_: second parameter is not a valid show rule replacement or callback. Must be either a function 'it => content', or the content to unconditionally replace by (if it does not depend on the matched element). For example, you can write 'show: e.show_(elem, it => [*#it*])' to make an element bold, or 'show: e.show_(elem, [Hi])' to always replace it with the word 'Hi'.") + + let callback = replacement + if type(replacement) != function { + replacement = [#replacement] + callback = _ => replacement + } + + prepare-rule(((prepared-rule-key): true, version: element-version, kind: "show", filter: filter, callback: callback, name: none, names: (), mode: mode)) +} + +/// Apply multiple rules (set rules, etc.) at once. +/// +/// These rules do not count towards the "set rule limit" observed in 'Limitations'; +/// `apply` itself will always count as a single rule regardless of the amount of rules +/// inside it (be it 5, 50, or 500). Therefore, +/// **it is recommended to group rules together under `apply` whenever possible.** +/// +/// Note that Elembic will automatically wrap consecutive rules (only whitespace +/// or native set/show rules inbetween) into a single `apply`, bringing the same benefit. +/// +/// USAGE: +/// +/// ```typ +/// #show: e.apply( +/// set_(elem, fields), +/// set_(elem, fields) +/// ) +/// ``` +/// +/// - mode (int): style mode given by the `style-modes` dictionary +/// - args (arguments): rules to apply +/// -> function +#let apply(mode: auto, ..args) = { + assert(args.named() == (:), message: "elembic: element.apply: unexpected named arguments") + assert(mode == auto or mode == style-modes.normal or mode == style-modes.leaky or mode == style-modes.stateful, message: "elembic: element.apply: invalid mode, must be auto or e.style-modes.(normal / leaky / stateful)") + + let rules = args.pos().map( + rule => { + assert(type(rule) == function, message: "elembic: element.apply: invalid rule of type " + str(type(rule)) + ", please use 'set_' or some other function from this library to generate it") + + // Call it as if it we were in a show rule. + // It will have some trailing metadata indicating its arguments. + let inner = rule([]) + let rule-data = inner.children.last().value.rule + + if rule-data.kind == "apply" { + // Flatten 'apply' + rule-data.rules + } else { + (rule-data,) + } + } + ).sum(default: ()) + + if mode == auto { + mode = rules.fold(auto, (mode, rule) => { + if ( + rule.mode == style-modes.stateful + or mode != style-modes.stateful and rule.mode == style-modes.leaky + or mode == auto + ) { + // Prioritize more explicit modes: + // stateful > leaky > normal + rule.mode + } else { + mode + } + }) + } + + if mode != auto { + rules = rules.map(r => r + (mode: mode)) + } + + // Set this apply rule's mode as an optimization, but note that we have forcefully altered + // its children's modes above. + prepare-rule(((prepared-rule-key): true, version: element-version, kind: "apply", rules: rules, mode: mode)) +} + +#let settings(..args, mode: auto) = { + assert(args.pos() == (), message: "elembic: element.settings: unexpected positional args") + let args = args.named() + assert(args != (:), message: "elembic: element.settings: please specify some setting, e.g. e.settings(prefer-leaky: true)") + + let write = (:) + let transform = () + for (key, val) in args { + if key not in default-global-data.settings { + assert(false, message: "elembic: element.settings: invalid setting '" + key + "', valid keys are " + default-global-data.settings.keys().map(repr).join(", ")) + } + + let default-setting = default-global-data.settings.at(key) + if key in ("track-ancestry", "store-ancestry") and val != "any" { + if type(val) == array { + let new-elements = (:) + for elem in val { + if type(elem) == function { + elem = data(elem) + } + if type(elem) != dictionary or "eid" not in elem { + assert(false, message: "elembic: element.settings: expected array of elements or literal \"any\" (apply to any element) for setting '" + key + "', got array of '" + str(type(elem)) + "'") + } + + new-elements.insert(elem.eid, elem) + } + + if new-elements != (:) { + transform.push(s => { + let existing = if s != none and key in s { s.at(key) } else { (:) } + if existing == "any" { + // Nothing to change, already applies to all elements + s + } else { + (:..s, (key): (:) + existing + new-elements) + } + }) + } + } else { + assert(false, message: "elembic: element.settings: expected array of elements or literal \"any\" (apply to any element) for setting '" + key + "', got '" + str(type(val)) + "'") + } + } else if key == "prefer-leaky" and type(val) != type(default-setting) { + assert(false, message: "elembic: element.settings: expected type of '" + str(type(default-setting)) + "' for setting '" + key + "', got '" + str(type(val)) + "'") + } else { + write.insert(key, val) + } + } + + let transform = if transform == () { + none + } else if transform.len() == 1 { + transform.first() + } else { + s => transform.fold(s, (acc, fun) => fun(acc)) + } + + prepare-rule(((prepared-rule-key): true, version: element-version, kind: "settings", write: write, transform: transform, mode: mode)) +} + +/// Name a certain rule. Use `e.apply` to name a group of rules. +/// This is used to be able to revoke the rule later with `e.revoke`. +/// +/// Please note that, at the moment, each rule can only have +/// one name. This means that applying multiple `named` on +/// the same set of rules will simply replace the previous +/// names. +/// +/// However, more than one rule can have the same name, allowing both to be +/// revoked at once if needed. +/// +/// USAGE: +/// +/// ```typ +/// #show: e.named( +/// "cool set", +/// e.set_(elem, fields) +/// ) +/// ``` +/// +/// - name (str): The name to give to the rule. +/// - rule (function): The rule to apply this name to. +/// -> function +#let named(..names, rule) = { + assert(names.named() == (:), message: "elembic: element.named: unexpected named arguments") + let names = names.pos() + assert(names != (), message: "elembic: element.named: expected at least two arguments (one or more names and a rule)") + assert(type(rule) == function, message: "elembic: element.named: last parameter is not a valid rule (not a function), please use functions such as 'set_' to create one.") + for name in names { + assert(type(name) == str, message: "elembic: element.named: rule name must be a string, not " + str(type(name))) + assert(name != "", message: "elembic: element.named: name must not be empty") + } + + // For backwards compatibility when only one name was possible + let compat-name = names.last() + let rule = rule([]).children.last().value.rule + if rule.kind == "apply" { + let i = 0 + for inner-rule in rule.rules { + assert(inner-rule.kind in ("show", "set", "revoke", "reset", "filtered", "cond-set"), message: "elembic: element.named: can only name show, set, revoke, reset, filtered and cond-set rules at this moment, not '" + inner-rule.kind + "'") + + rule.rules.at(i).name = compat-name + + if "names" in inner-rule { + rule.rules.at(i).names += names + } else { + rule.rules.at(i).names = names + } + + i += 1 + } + } else { + assert(rule.kind in ("show", "set", "revoke", "reset", "filtered", "cond-set"), message: "elembic: element.named: can only name show, set, revoke, reset, filtered and cond-set rules at this moment, not '" + rule.kind + "'") + rule.name = compat-name + + if "names" in rule { + rule.names += names + } else { + rule.names = names + } + } + + // Re-prepare the rule + prepare-rule(rule) +} + +/// Revoke all rules with a certain name. +/// +/// This is intended to be used in a specific scope, +/// and temporary. This means you are supposed to only revoke the rule +/// for a short portion of the document. If you wish to do the opposite, +/// that is, only apply the rule for a short portion for the document +/// (and have it never apply again afterwards), then please just scope +/// the set rule itself instead. +/// +/// USAGE: +/// +/// ```typ +/// #show: e.named("name", set_(element, fields)) +/// ... +/// #[ +/// #show: e.revoke("name") +/// // rule 'name' doesn't apply here +/// ... +/// ] +/// +/// // Applies here again +/// ... +/// ``` +/// +/// - name (str): name of rules to be revoked +/// - mode (int): style mode given by the `style-modes` dictionary +/// -> function +#let revoke(name, mode: auto) = { + assert(type(name) == str, message: "elembic: element.revoke: rule name must be a string, not " + str(type(name))) + assert(mode == auto or mode == style-modes.normal or mode == style-modes.leaky or mode == style-modes.stateful, message: "elembic: element.revoke: invalid mode, must be auto or e.style-modes.(normal / leaky / stateful)") + + prepare-rule(((prepared-rule-key): true, version: element-version, kind: "revoke", revoking: name, name: none, names: (), mode: mode)) +} + +/// Temporarily revoke all active set rules for certain elements (or even all elements if none are specified). +/// Applies only to the current scope, like other rules. +/// +/// USAGE: +/// +/// ```typ +/// #show: e.set_(element, fill: red) +/// #[ +/// // Revoke all previous set rules on 'element' for this scope +/// #show: e.reset(element) +/// #element[This is using the default fill (not red)] +/// ] +/// +/// // Rules not revoked outside the scope +/// #element[This is using red fill] +/// ``` +/// +/// - args (arguments): elements whose rules should be reset, or none to reset all rules +/// - mode (int): style mode given by the `style-modes` dictionary +/// -> function +#let reset(..args, mode: auto) = { + assert(args.named() == (:), message: "elembic: element.reset: unexpected named arguments") + assert(mode == auto or mode == style-modes.normal or mode == style-modes.leaky or mode == style-modes.stateful, message: "elembic: element.reset: invalid mode, must be auto or e.style-modes.(normal / leaky / stateful)") + + let filters = args.pos().map(it => if type(it) == function { data(it) } else { it }) + assert(filters.all(x => type(x) == dictionary and "eid" in x), message: "elembic: element.reset: invalid arguments, please provide a function or element data with at least an 'eid'") + + prepare-rule(((prepared-rule-key): true, version: element-version, kind: "reset", eids: filters.map(x => x.eid), name: none, names: (), mode: mode)) +} + +// Stateful variants +#let stateful-set(..args) = { + apply(set_(..args), mode: style-modes.stateful) +} +#let stateful-cond-set(..args) = { + apply(cond-set(..args), mode: style-modes.stateful) +} +#let stateful-settings = settings.with(mode: style-modes.stateful) +#let stateful-apply = apply.with(mode: style-modes.stateful) +#let stateful-show = show_.with(mode: style-modes.stateful) +#let stateful-revoke = revoke.with(mode: style-modes.stateful) +#let stateful-reset = reset.with(mode: style-modes.stateful) + +// Leaky variants +#let leaky-set(..args) = { + apply(set_(..args), mode: style-modes.leaky) +} +#let leaky-cond-set(..args) = { + apply(cond-set(..args), mode: style-modes.leaky) +} +#let leaky-settings = settings.with(mode: style-modes.leaky) +#let leaky-apply = apply.with(mode: style-modes.leaky) +#let leaky-show = show_.with(mode: style-modes.leaky) +#let leaky-revoke = revoke.with(mode: style-modes.leaky) +#let leaky-reset = reset.with(mode: style-modes.leaky) + +#let leaky-toggle(enable) = leaky-settings(prefer-leaky: enable) + +// Apply revokes and other modifications to the chain and generate a final set +// of fields. +#let fold-styles(chain, data-chain, revoke-chain, fold-chain) = { + // Map name -> up to which index (exclusive) it is revoked. + // + // Importantly, a revoke at index B will apply to + // all rules with the revoked name before that index. + // If that revoke rule is, itself, revoked, that either + // completely eliminates the name from being revoked, + // or it simply leads the name to be revoked up to + // an index A < B. That, or it was also being revoked + // by another unrevoked revoke rule at index C > B, + // in which case the name is still revoked up to C. + // In all cases, the name is always revoked from the + // start until some end index. Otherwise, it isn't + // revoked at all (end index 0). + let active-revokes = (:) + + let first-active-index = 0 + + // Revoke revoked revokes by analyzing revokes in reverse + // order: a revoke that came later always takes priority. + for revoke in revoke-chain.rev() { + // This revoke will revoke rules named 'revoking' up to 'index' in the chain, which + // automatically revokes revoke rules before it as well, since they were added when + // the chain length was smaller (or the same), and 'index' is always the chain length + // at the moment the revoke rule was added. + // + // We don't explicitly add revoke rules to the chain as their order in the revoke-chain + // list is enough to know which revoke rules can revoke others, and the index indicates + // which set rules are revoked. + // + // Regarding the first part of the AND, note that, if a name is already revoked up to + // index C from a later revoke (since we're going in reverse, so this one appears earlier + // than the previous ones), then revoking it up to index B <= C for this revoke is + // unnecessary since the index interval [0, B) is already contained in [0, C). + // + // In other words, only the last revoke for a particular name matters, which is the + // first one we find in this loop. + // + // (As you can see, we assume above that, if revoke 1 comes before revoke 2 in the revoke-chain + // (before reversing), with revoke 1 applying up to chain index B and revoke 2 up to index C, + // then B <= C. This is enforced in 'prepare-rules' as we analyze revokes and push their + // information to the chain in order (outer to inner / earlier to later).) + let was-not-revoked = ( + ( + "names" not in revoke or revoke.names.all(n => n not in active-revokes) + ) + and ( + "names" in revoke or "name" not in revoke or revoke.name == none or revoke.name not in active-revokes + ) + ) + + if revoke.kind == "revoke" and revoke.revoking not in active-revokes and was-not-revoked { + active-revokes.insert(revoke.revoking, revoke.index) + } else if revoke.kind == "reset" and was-not-revoked { + // Applying a reset, so we delete everything before this index and stop revoking since + // any revokes before this reset won't count anymore. + first-active-index = revoke.index + + chain = if chain.len() <= first-active-index { + () + } else { + chain.slice(first-active-index) + } + + data-chain = if data-chain.len() <= first-active-index { + () + } else { + data-chain.slice(first-active-index) + } + + for (field-name, fold-data) in fold-chain { + let first-fold-index = fold-data.data.position(d => d.index >= first-active-index) + if first-fold-index == none { + // All folded values removed. + // The caller will be responsible for joining the default value with the + // final arguments (without any chain values inbetween) if that's necessary. + _ = fold-chain.remove(field-name) + } else { + fold-chain.at(field-name).values = fold-data.values.slice(first-fold-index) + fold-chain.at(field-name).data = fold-data.data.slice(first-fold-index) + } + } + + // No need to analyze any further revoke rules since everything was reset. + break + } + } + + if active-revokes != (:) { + let i = first-active-index + for data in data-chain { + if data != none and ( + "names" in data and data.names.any(n => n in active-revokes and i < active-revokes.at(n)) + or "names" not in data and "name" in data and data.name in active-revokes and i < active-revokes.at(data.name) + ) { + // Nullify changes at this stage + chain.at(i) = (:) + } + + i += 1 + } + + for (field-name, fold-data) in fold-chain { + let filtered-data = fold-data.data.filter(d => ( + // Only keep data without a name in the revoked name map, or, if the + // name is there, then data that came after the name was revoked. + ("names" not in d or d.names.all(n => n not in active-revokes or d.index >= active-revokes.at(n))) + and ("names" in d or "name" not in d or (d.name == none or d.name not in active-revokes or d.index >= active-revokes.at(d.name))) + )) + if filtered-data == () { + _ = fold-chain.remove(field-name) + } else { + fold-chain.at(field-name).data = filtered-data + fold-chain.at(field-name).values = filtered-data.map(d => d.value) + } + } + } + + let final-values = chain.sum(default: (:)) + + // Apply folds separately (their fields' values are meaningless in the above dict) + for (field-name, fold-data) in fold-chain { + final-values.at(field-name) = if fold-data.values == () { + fold-data.default + } else if fold-data.folder == auto { + fold-data.default + fold-data.values.sum() + } else { + fold-data.values.fold(fold-data.default, fold-data.folder) + } + } + + (folded: final-values, active-revokes: active-revokes, first-active-index: first-active-index) +} + +// Retrieves the final chain data for an element, after applying all set rules so far. +#let get-styles(element, elements: (:), use-routine: false) = { + if type(element) == function { + element = data(element) + } + let (eid, default-fields) = if type(element) == dictionary and "eid" in element and "default-fields" in element { + (element.eid, element.default-fields) + } else { + assert(false, message: "elembic: element.get: expected element (function / data dictionary), received " + str(type(element))) + } + + if ( + use-routine + and ("version" not in element or element.version != element-version) + and "routines" in element + and "get-styles" in element.routines + and type(element.routines.get-styles) == function + ) { + // Use the element's own "get styles". + return (element.routines.get-styles)(element, elements: elements) + } + + let element-data = elements.at(eid, default: default-data) + let folded-chain = if element-data.revoke-chain == default-data.revoke-chain and element-data.fold-chain == default-data.fold-chain { + element-data.chain.sum(default: (:)) + } else { + fold-styles(element-data.chain, element-data.data-chain, element-data.revoke-chain, element-data.fold-chain).folded + } + + // No need to do extra folding like in constructor: + // if a foldable field hasn't been specified, it is either equal to + // its default, or it is a required field which has no default and + // thus it is not returned here since it can't be set. + default-fields + folded-chain +} + +/// Reads the current values of element fields after applying set rules. +/// Must be in a context block. +/// +/// This is a stateful version, which doesn't require a callback, but only +/// works on stateful mode (less performant). +/// +/// USAGE: +/// ```typ +/// #show: e.set_(elem, fill: green) +/// // ... +/// #context { +/// // OK +/// assert(e.stateful.get(elem).fill == green) +/// } +/// ``` +/// +/// - receiver (function): function ('get' function) -> content +/// -> content +#let stateful-get(element) = { + let chain = style-state.get() + let global-data = if chain == () { + default-global-data + } else { + chain.last() + } + + assert( + global-data.stateful, + message: "elembic: stateful.get: cannot use this function without enabling the global stateful toggle\n hint: if you don't mind the performance hit, write '#show: e.stateful.enable()' somewhere above the 'context {}' in which this call happens, or at the top of the document to apply to all rules as well" + ) + + get-styles(element, elements: global-data.elements, use-routine: true) +} + +/// Used for debugging elembic. Stateful version of `debug-get`. +#let stateful-debug-get() = { + let chain = style-state.get() + let global-data = if chain == () { + default-global-data + } else { + chain.last() + } + + assert( + global-data.stateful, + message: "elembic: stateful.debug-get: cannot use this function without enabling the global stateful toggle\n hint: if you don't mind the performance hit, write '#show: e.stateful.enable()' somewhere above the 'context {}' in which this call happens, or at the top of the document to apply to all rules as well" + ) + + let getter = get-styles.with(elements: global-data.elements, use-routine: true) + (:..global-data, ctx: (get: getter)) +} + +#let _is-get(body) = ( + type(body) == content + and body.func() == sequence + and body.children.len() == 2 // [#context{...}#metadata(get-meta)#lbl-special-rule-tag] + and body.children.last().func() == metadata + and body.at("label", default: none) == none + and body.children.last().at("label", default: none) == lbl-special-rule-tag + and body.children.last().value.kind == "get" +) + +#let _recurse-get(body, elements: none) = { + let get-meta = body.children.last().value + + if "__future" in get-meta and element-version <= get-meta.__future.max-version { + let res = (get-meta.__future.call)(body, __future-version: element-version) + + if "doc" in res { + res.doc + } + } else if "receiver" in get-meta and type(get-meta.receiver) == function { + // Pick up updates from filtered rules + let getter = get-styles.with(elements: elements, use-routine: true) + (get-meta.receiver)(getter) + } +} + +#let prepare-ctx(receiver, include-global: false) = context { + let previous-bib-title = bibliography.title + [#context { + let global-data = if ( + type(bibliography.title) == content + and bibliography.title.func() == metadata + and bibliography.title.at("label", default: none) == lbl-data-metadata + ) { + bibliography.title.value + } else { + (..default-global-data, first-bib-title: previous-bib-title) + } + + if global-data.stateful { + let chain = style-state.get() + global-data = if chain == () { + default-global-data + } else { + chain.last() + } + } + + set bibliography(title: previous-bib-title) + + let getter = get-styles.with(elements: global-data.elements, use-routine: true) + let body = if include-global { + receiver((:..global-data, ctx: (get: getter))) + } else { + receiver(getter) + } + + // Optimization: flatten 'get' + while _is-get(body) { body = _recurse-get(body, elements: global-data.elements) } + body + }#lbl-get] +} + +/// Reads the current values of element fields after applying set rules. +/// +/// The callback receives a 'get' function which can be used to read the +/// values for a given element. The content returned by the function, which +/// depends on those values, is then placed into the document. +/// +/// USAGE: +/// ```typ +/// #show: e.set_(elem, fill: green) +/// // ... +/// #e.get(get => { +/// // OK +/// assert(get(elem).fill == green) +/// }) +/// ``` +/// +/// - receiver (function): function ('get' function) -> content +/// -> content +#let prepare-get(receiver) = { + let output = prepare-ctx(include-global: false, receiver) + [#output#metadata(((special-rule-key): "get", data-kind: "special-rule", kind: "get", version: element-version, receiver: receiver))#lbl-special-rule-tag] +} + +/// Used for debugging elembic. Passes the internal style chain information. +#let prepare-debug(receiver) = { + let output = prepare-ctx(include-global: true, receiver) + [#output#metadata(((special-rule-key): "debug-get", data-kind: "special-rule", kind: "debug-get", version: element-version, receiver: receiver))#lbl-special-rule-tag] +} + +// Obtain a Typst selector to use to match this element in show rules or in the outline. +// Specify 'meta: true' to match this element in a query, as that selector is +// generated once regardless of show rules. +#let elem-selector(elem, outline: false, outer: false, meta: false) = { + if outline { + assert(not outer, message: "elembic: element.selector: cannot have 'outline: true' and 'outer: true' at the same time, please pick one selector") + assert(not meta, message: "elembic: element.selector: cannot have 'outline: true' and 'meta: true' at the same time, please pick one selector") + let elem-data = data(elem) + assert("outline-sel" in elem-data, message: "elembic: element.selector: this isn't a valid element") + assert(elem-data.outline-sel != none, message: "elembic: element.selector: this element isn't outlinable\n hint: try asking its author to define it as such with 'outline: auto', 'outline: (caption: [...])' or 'outline: (caption: it => ...)'") + elem-data.outline-sel + } else if outer { + assert(not meta, message: "elembic: element.selector: cannot have 'outer: true' and 'meta: true' at the same time, please pick one selector") + data(elem).outer-sel + } else if meta { + let elem-data = data(elem) + elem-data.at("meta-sel", default: elem-data.sel) + } else { + data(elem).sel + } +} + +#let elem-query(filter, before: none, after: none) = { + if type(filter) == function { + filter = filter(__elembic_data: special-data-values.get-where) + } + + if type(filter) != dictionary or filter-key not in filter { + if type(filter) == selector { + assert(false, message: "elembic: element.query: Typst-native selectors cannot be specified here, only those of custom elements") + } + assert(false, message: "elembic: element.query: expected a valid filter, such as 'custom-element' or 'custom-element.with(field-name: value, ...)', got " + base.typename(filter)) + } + + assert("elements" in filter, message: "elembic: element.query: this filter is missing the 'elements' field; this indicates it comes from an element generated with an outdated elembic version. Please use an element made with an up-to-date elembic version.") + assert(filter.elements != none, message: "elembic: element.query: this filter appears to apply to any element (e.g. it's a 'not' or 'custom' filter). It must match only within a certain set of elements. Consider using an 'and' filter, e.g. 'e.filters.and(wibble, e.not(wibble.with(a: 10)))' instead of just 'e.not(wibble.with(a: 10))', to restrict it.") + + let results = () + for (eid, elem-data) in filter.elements { + if "meta-sel" in elem-data { + let sel = elem-data.meta-sel + if before != none { + sel = selector(sel).before(before) + } + if after != none { + sel = selector(sel).after(after) + } + + results += query(sel).filter( + instance => ( + instance.func() == metadata + and { + let meta = data(instance.value) + + verify-filter( + meta.at("fields", default: (:)), + eid: eid, + filter: filter, + ancestry: if "may-need-ancestry" in filter and filter.may-need-ancestry and meta.at("ctx", default: none) != none and "ancestry" in meta.ctx { + meta.ctx.ancestry + } else { + () + } + ) and "rendered" in instance.value + } + ) + ).map( + instance => instance.value.rendered + ) + } else if "sel" in elem-data { + let sel = elem-data.sel + if before != none { + sel = selector(sel).before(before) + } + if after != none { + sel = selector(sel).after(after) + } + // This element is probably too outdated to have ancestry checks anyway, so we don't bother + results += query(sel).filter(instance => verify-filter(data(instance).at("fields", default: (:)), eid: eid, filter: filter, ancestry: ())) + } else { + assert(false, message: "elembic: element.query: filter did not have the element's meta selector") + } + } + + results +} + +#let default-prepare-rules(doc) = { + // Custom references + show ref: ref_ + + // Fix labelable elements + show figure.where(kind: labelable-elem-figure-kind): set align(start) + show figure.where(kind: labelable-elem-figure-kind): fig => { + let fig-label = fig.at("label", default: none) + if fig-label == none { + fig.body + } else { + let fig-data = data(fig) + if "instance-internal-args" in fig-data and "default-constructor" in fig-data { + let named-args = fig-data.instance-internal-args.named() + let given-label = named-args.at("label", default: none) + + if "label" in named-args and (given-label == none or type(given-label) == label and given-label != fig-label) { + // Don't override label specified via argument, unless it's equal to + // the figure's label, then we should avoid a double label + fig.body + } else { + (fig-data.default-constructor)(..fig-data.instance-internal-args, label: fig-label, __elembic_outer_label: true) + } + } else { + fig.body + } + } + } + + // Remove error about missing preparation + show lbl-empty-prepare-check: none + + doc +} + +/// Applies necessary show rules to the entire document so that custom elements behave +/// properly. This is usually only needed for elements which have custom references, +/// since, in that case, the document-wide rule `#show ref: e.ref` is required. +/// In addition, labelable elements (supporting outer labels) also need at least +/// `#show: e.prepare()` to work properly. +/// **It is recommended to always use `e.prepare` when using Elembic.** +/// +/// However, **some custom elements also have their own `prepare` functions.** (Read +/// their documentation to know if that's the case.) Then, you may specify their functions +/// as parameters to this function, and this function will run the `prepare` function of +/// each element. Not specifying any elements will just run the default rules, which may +/// still be important. +/// +/// As an example, an element may use its own `prepare` function to apply some special +/// behavior to its `outline`. +/// +/// USAGE: +/// ```rs +/// // Apply default rules + special rules for these elements (if they need it) +/// #show: e.prepare(elemA, elemB) +/// +/// // Apply default rules only +/// #show: e.prepare() +/// ``` +/// - args (arguments): element functions which need special preparation, or none to just apply default rules +/// -> function +#let prepare( + ..args +) = { + assert(args.named() == (:), message: "elembic: element.prepare: unexpected named arguments") + + if args.pos() == () { + return default-prepare-rules + } + + let elems = args.pos().map(data) + + if elems.len() == 1 and type(args.pos().first()) == content { + assert(false, message: "elembic: element.prepare: expected (optional) element functions as arguments, not the document\n hint: write '#show: e.prepare()', not '#show: e.prepare' - note the parentheses") + } + + assert(elems.all(it => it.data-kind == "element"), message: "elembic: element.prepare: positional arguments must be elements") + let prepares = elems.filter(elem => "prepare" in elem and elem.prepare != none).map(elem => { + if "lbl-elem-prepare-check" in elem { + doc => { + // Remove error about missing element preparation + show elem.lbl-elem-prepare-check: none + + (elem.prepare)(elem.func, doc) + } + } else { + elem.prepare.with(elem.func) + } + }) + + doc => { + show: default-prepare-rules + prepares.fold(doc, (acc, prepare) => prepare(acc)) + } +} + +/// Creates a new element, returning its constructor. Read the "Creating custom elements" +/// chapter for more information. +/// +/// USAGE: +/// +/// ```typ +/// #import "@preview/elembic:X.X.X" as e: field +/// +/// // For references to apply +/// #show: e.prepare() +/// +/// #let elem = e.element.declare( +/// "elem", +/// prefix: "@preview/my-package,v1", +/// display: it => { +/// [== #it.title] +/// block(fill: it.fill)[#it.inner] +/// }, +/// fields: ( +/// field("fill", e.types.option(e.types.paint)), +/// field("inner", content, default: [Hello!]), +/// field("title", content, default: [Hello!]), +/// ), +/// reference: ( +/// supplement: [Elem], +/// numbering: "1" +/// ), +/// outline: (caption: it => it.title), +/// ) +/// +/// #outline(target: e.selector(elem, outline: true)) +/// +/// #elem() +/// #elem(title: [abc], label: ) +/// @abc +/// ``` +/// +/// - name (str): The element's name. +/// - prefix (str): The element's prefix, used to distinguish it from elements with the same name. This is usually your package's name alongside a (major) version. +/// - doc (none | str): The element's documentation, if any. +/// - display (function): Function `fields => content` to display the element. +/// - fields (array): Array with this element's fields. +/// - parse-args (auto | function): Optional override for the built-in argument parser +/// (or `auto` to keep as is). Must be in the form +/// `function(args, include-required: bool) => dictionary`, where `include-required: true` +/// means required fields are enforced (constructor), while `include-required: false` means +/// they are forbidden (set rules). +/// - typecheck (bool): Set to `false` to disable field typechecking. +/// - allow-unknown-fields (bool): Set to `true` to allow users to specify unknown +/// fields to your element. They are not typechecked and are simply forwarded to +/// the element's fields by the argument parser. +/// - template (none | function): Optional function displayed element => content to define overridable default set rules for your elements, such as paragraph settings. Users can override these settings with show-set rules on elements. +/// - prepare (none | function): Optional function (element, document) => content +/// to define show and set rules that should be applied to the whole document for your +/// element to properly function. +/// - construct (none | function): Optional function that overrides the default +/// element constructor, returning arbitrary content. This should be used over +/// manually wrapping the returned constructor as it ensures set rules and data +/// extraction from the constructor still work. +/// - scope (none | dictionary | module): Optional scope with associated data for your +/// element. This could be a module with constructors for associated elements, for +/// instance. This value can be accessed with `e.scope(elem)`, e.g. +/// `#import e.scope(elem): sub-elem`. +/// - count (none | function): Optional function `counter => (content | function fields => content)` +/// which inserts a counter step before the element. Ensures the element's display function has +/// updated context to get the latest counter value (after the step / update) with +/// `e.counter(it).get()`. Defaults to `counter.step` to step the counter once before +/// each element placed. +/// - labelable (auto|bool): Set this to `true` to support outer label syntax: `#elem(...) `. +/// The downsides are that `#show: e.prepare()` becomes required to use the element, the element can no longer be inline, +/// and show rules on the individual labels no longer have access to final fields. +/// Defaults to `auto`, which still allows labeling without those downsides by specifying `#element(label: )`, +/// ensuring show rules on that label work and have access to the element's final fields. +/// In both cases, also allows referring to labeled elements with `@chosen-label` (requires `#show: e.prepare()` to work), +/// but the element may not have its own settable field named `label`. +/// When `false`, the element may have a field named `label` instead, but it won't have any of those effects. +/// - reference (none | (supplement: none | str | content | function fields => str | content, numbering: none | str | function fields => str | function, custom: none | function fields => content)): +/// When not `none`, allows referring to the new element with Typst's built-in +/// `@ref` syntax. Requires the user to execute `#show: e.prepare()` at the top +/// of their document (it is part of the default rules, so `prepare` needs no +/// arguments there). Specify either a `supplement` and `numbering` for references +/// looking like "Name 2", and/or `custom` to show some fully customized content +/// for the reference instead. +/// - outline (none | auto | dictionary): +/// Accepts either `auto` or a dictionary of the form +/// `(caption: str | content | function fields => content)`. +/// When not `none`, allows creating an outline for the element's appearances +/// with `#outline(target: e.selector(elem, outline: true))`. When set to `auto`, +/// the entries will display "Name 2" based on reference information. When a caption +/// is specified, it will display as "Name 2: caption", unless supplement and numbering +/// for reference are both none. +/// - synthesize (none | function): Can be set to a function `fields => fields` to +/// override final values of fields, or create new fields based on final values of +/// fields, before the first show rule. When computing new fields based on other +/// fields, please specify those new fields in the fields array with +/// `synthesized: true`. This forbids the user from specifying them manually, +/// but allows them to filter based on that field. +/// - contextual (bool): When set to `true`, functions `fields => something` for +/// other options, including `display`, will be able to access the current +/// values of set rules with `(e.ctx(fields).get)(other-elem)`. In addition, +/// an additional context block is created, so that you may access the correct +/// values for `native-elem.field` in the context. In practice, this is a bit +/// expensive, and so this option shouldn't be enabled unless you need precisely +/// `bibliography.title`, or you really need to get set rule information from +/// other elements within functions such as `synthesize` or `display`. +/// -> function +#let declare( + name, + display: none, + fields: none, + prefix: none, + doc: none, + parse-args: auto, + typecheck: true, + allow-unknown-fields: false, + template: none, + prepare: none, + construct: none, + scope: none, + count: counter.step, + labelable: auto, + reference: none, + outline: none, + synthesize: none, + contextual: false, +) = { + assert(type(display) == function, message: "elembic: element.declare: please specify a show rule in 'display:' to determine how your element is displayed.") + + let fields-hint = if type(fields) == dictionary { "\n hint: check if you didn't forget to add a trailing comma for a single field: write 'fields: (field,)', not 'fields: (field)'" } else { "" } + assert(type(fields) == array, message: "elembic: element.declare: please specify an array of fields, creating each field with the 'field' function. It can be empty with '()'." + fields-hint) + assert(doc == none or type(doc) == str, message: "elembic: element.declare: 'doc' must be none or a string (add documentation)") + assert(prefix != none, message: "elembic: element.declare: please specify a 'prefix: ...' for your type, to distinguish it from types with the same name. If you are writing a package or template to be used by others, please do not use an empty prefix.") + assert(type(prefix) == str, message: "elembic: element.declare: the prefix must be a string, not '" + str(type(prefix)) + "'") + assert(parse-args == auto or type(parse-args) == function, message: "elembic: element.declare: 'parse-args' must be either 'auto' (use built-in parser) or a function (default arg parser, fields: dictionary, typecheck: bool) => (user arguments, include-required: true (required fields must be specified - in constructor) / false (required fields must be omitted - in set rules)) => (bool (true on success, false on error), dictionary with parsed fields (or error message string if the bool is false)).") + assert(type(typecheck) == bool, message: "elembic: element.declare: the 'typecheck' argument must be a boolean (true to enable typechecking, false to disable).") + assert(type(allow-unknown-fields) == bool, message: "elembic: element.declare: the 'allow-unknown-fields' argument must be a boolean.") + assert(template == none or type(template) == function, message: "elembic: element.declare: 'template' must be 'none' or a function displayed element => content (usually set rules applied on the displayed element). This is used to add a set of overridable set rules to the element, such as paragraph settings.") + assert(prepare == none or type(prepare) == function, message: "elembic: element.declare: 'prepare' must be 'none' or a function (element, document) => styled document (used to apply show and set rules to the document).") + assert(count == none or type(count) == function, message: "elembic: element.declare: 'count' must be 'none', a function counter => counter step/update element, or a function counter => final fields => counter step/update element.") + assert(synthesize == none or type(synthesize) == function, message: "elembic: element.declare: 'synthesize' must be 'none' or a function element fields => element fields.") + assert(contextual == auto or type(contextual) == bool, message: "elembic: element.declare: 'contextual' must be 'auto' (true if using a contextual feature) or a boolean (true to wrap the output in a 'context { ... }', false to not).") + assert(construct == none or type(construct) == function, message: "elembic: element.declare: 'construct' must be 'none' (use default constructor) or a function receiving the original constructor and returning the new constructor.") + assert(scope == none or type(scope) in (dictionary, module), message: "elembic: element.declare: 'scope' must be either 'none', a dictionary or a module") + assert(labelable == auto or type(labelable) == bool, message: "elembic: element.declare: 'labelable' must be auto (only adds the 'label' constructor argument with 'elem(label: + + + +A [Typst](https://typst.app/) package for marking and annotating elements within math blocks. + +For comprehensive documentation, please refer to [manual](docs/doc.pdf). + + +## Features +* Visually marks content within math blocks without affecting the layout. +* Enables subsequent annotation of marked content. +* Supports annotations using CeTZ canvas for advanced graphical annotations. + + +## Examples +### Simple Example +```typst +$ + markhl(x) + markhl(y, color: #blue, tag: #) + + #annot()[Annotation] +$ +``` + +![Simple example image](examples/sample1.svg) + +### More Complex Example +![Example1](examples/showcase1.svg) + +
Source code + +```typst +#set page(width: auto, height: auto, margin: (left: 4cm, top: 2cm, rest: 1cm), fill: white) +#set text(24pt) + +$ + markul(p_i, tag: #

) = markrect( + exp(- mark(beta, tag: #, color: #red) mark(E_i, tag: #, color: #green)), + tag: #, color: #blue, + ) / markhl(sum_j exp(- beta E_j), tag: #) + + #annot(

, pos: bottom + left)[Probability of \ state $i$] + #annot(, pos: top + left, dy: -1.5em, leader-connect: "elbow")[Inverse temperature] + #annot(, pos: top + right, dy: -1em)[Energy] + #annot(, pos: top + left)[Boltzmann factor] + #annot()[Partition function] +$ +``` + +

+ +### Annotations using CeTZ +![Example3](examples/showcase3.svg) + +
Source code + +```typst +#import "@preview/cetz:0.4.2" + +#set page(width: auto, height: auto, margin: (y: 2cm, bottom: 1cm), fill: white) +#set text(24pt) + +#let rmark = mark.with(color: red) +#let bmark = mark.with(color: blue) +#let pmark = mark.with(color: purple) + +$ + ( rmark(a x, tag: #) + bmark(b, tag: #) ) + ( rmark(c x, tag: #) + bmark(d, tag: #) ) + = rmark(a c x^2) + pmark((a d + b c) x) + bmark(b d) +$ + +#annot-cetz( + (, , , ), + cetz, + { + import cetz.draw: * + set-style(mark: (end: "straight")) + bezier-through("ax.south", (rel: (x: 1, y: -.5)), "cx.south", stroke: red) + bezier-through("ax.south", (rel: (x: 1, y: -1)), "d.south", stroke: purple) + bezier-through("b.north", (rel: (x: .6, y: .5)), "cx.north", stroke: purple) + bezier-through("b.north", (rel: (x: .6, y: 1)), "d.north", stroke: blue) + }, +) +``` + +
+ + +### Customized Markings and Annotations +![Example2](examples/showcase2.svg) + +
Source code + +```typst +#import "@preview/cetz:0.4.2" + +#set page(width: auto, height: auto, margin: (x: 4cm, top: 2cm, bottom: 1cm), fill: white) +#set text(24pt) + +#let markhl = markhl.with(stroke: 1pt) + +$ + markhl(1 mark(., tag: #) 23, tag: #, color: #red) + mark( + mark(times, tag: #) + mark(10, tag: #)^mark(4, tag: #), + tag: #, + ) +$ + +#{ + let annot = annot.with(leader-tip: tiptoe.triangle, leader-toe: none) + annot(, pos: left, dx: -.5em, dy: -1em, annot-text-props: (size: .9em))[mantissa] + + let annot = annot.with(leader-stroke: .03em, leader-tip: none, leader-toe: none) + annot(, pos: bottom + left, dx: -.5em)[decimal \ separator] + annot(, pos: top, dx: -1em, dy: -1.2em)[product] + annot(, pos: top, dy: -1em)[base] + annot(, pos: top + right, dx: 1em)[exponent] + + annot-cetz( + , + cetz, + { + import cetz.draw: * + cetz.decorations.flat-brace( + "pow.south-west", + "pow.south-east", + flip: true, + name: "brace", + stroke: blue, + ) + content("brace.south", anchor: "north", text(blue, .9em)[power]) + }, + ) +} +``` + +
+ + +## Usage +Import the package `mannot` on the top of your document: +```typst +#import "@preview/mannot:0.3.1": * +``` + +### Marking +To decorate content within math blocks, use the following marking functions: +- `mark`: Changes the text color. +- `markhl`: Highlights the content. +- `markrect`: Draws a rectangle around the content. +- `markul`: Underlines the content. +```typst +$ + mark(x, color: #red) + markhl(f(x)) + markrect(e^x) + markul(x + 1) +$ +``` +![Usage1](examples/usage1.svg) + +You can customize the marking color and other styles: +```typst +$ + mark(x, color: #green) + + markhl(f(x), color: #purple, stroke: #1pt, radius: #10%) + + markrect(e^x, color: #red, fill: #blue, outset: #.2em) + + markul(x + 1, color: #gray, stroke: #2pt) +$ +``` +![Usage2](examples/usage2.svg) + +### Annotations +After marking content with a tag, +you can later annotate it using the `annot` function: +```typst +$ + mark(x, tag: #<1>, color: #green) + + markhl(f(x), tag: #<2>, color: #purple, stroke: #1pt, radius: #10%) + + markrect(e^x, tag: #<3>, color: #red, fill: #blue, outset: #.2em) + + markul(x + 1, tag: #<4>, color: #gray, stroke: #2pt) + + #annot(<1>)[Annotation] + #annot(<3>, pos: top)[Another annotation] +$ +``` +![Usage3](examples/usage3.svg) + +Markings and annotations do not affect the layout, +so you might sometimes need to manually insert spacing before and after the equations to achieve the desired visual appearance: +```typst +Text text text text text: +#v(1em) // <- Spacing +$ + mark(x, tag: #<1>, color: #green) + #annot(<1>, pos: top + right)[Annotation] + #annot(<1>, dy: 1em)[Annotation] +$ +#v(2em) // <- Spacing +text text text text text. +``` +![Usage4](examples/usage4.svg) + +#### Annotation Positioning +The `annot` function offers the following arguments to control annotation placement: +* `pos`: Where to place the annotation relative to the marked content. + This can be: + - A single alignment for simple relative positioning. + - A pair of alignments, where the first defines the anchor point on the marked content, + and the second defines the anchor point on the annotation. + + ![Usage of pos](examples/usage-pos.svg) + +* `dx`, `dy`: The horizontal and vertical displacement of the annotation's anchor + from the marked content's anchor. + + ![Usage of dx, dy](examples/usage-dxdy.svg) + +#### Annotation Leader Line +When the annotation is far from the marked content, a leader line is drawn by default. +You can customize its appearance using the following `annot` arguments: +* `leader`: Whether to draw a leader line. Set to `auto` to enable automatic drawing based on distance. +* `leader-stroke`: How to stroke the leader line e.g., `1pt + red`. +* `leader-tip`, `leader-toe`: Define the end and start markers of the leader line. + Leader lines are drawn by the [tiptoe](https://typst.app/universe/package/tiptoe/0.3.0) package. + You can specify markers or `none`: + ```typst + $ + markhl(x, tag: #<1>) + + #annot( + <1>, pos: bottom + right, dy: 1em, + leader-tip: tiptoe.circle, + leader-toe: tiptoe.stealth.with(length: 1000%), + )[annotaition] + $ + ``` + + ![Usage of leader-tiptoe](examples/usage-leader-tiptoe.svg) + + For more options, see the [tiptoe page](https://typst.app/universe/package/tiptoe/0.3.0). + +* `leader-connect`: How the leader line connects to the marked content and the annotation. + This can be: + - A pair of alignments defining the connection points on the marked content and the annotation. + - "elbow" to create an elbow-shaped leader line. + + ![Usage of leader-connect](examples/usage-leader-connect.svg) + + +### Annotating Multiple Elements +You can also annotate multiple marked elements simultaneously +by passing an array of their tags to the `annot` function. +```typst +$ + mark(x, tag: #<1>, color: #green) + + markhl(f(x), tag: #<2>, color: #purple, stroke: #1pt, radius: #10%) + + markrect(e^x, tag: #<3>, color: #red, fill: #blue, outset: #.2em) + + markul(x + 1, tag: #<4>, color: #gray, stroke: #2pt) + + #annot((<1>, <2>), dy: 1em)[Annotation] + #annot((<3>, <2>, <4>), pos: top, dy: -1em, leader-connect: "elbow")[Another annotation] +$ +``` +![Usage5](examples/usage5.svg) + +### Annotations using CeTZ +To create annotations using the CeTZ canvas, use the `annot-cetz` function. +This allows you to embed a CeTZ canvas directly onto previously marked content. +Within the CeTZ canvas code block, +you can reference the position and dimensions of the marked content using an anchor with the same name as its tag. +For elements marked with multiple tags, corresponding anchors will be available. +```typst +#import "@preview/cetz:0.4.2" + +$ + mark(x, tag: #) + mark(y, tag: #) + + #annot-cetz((, ), cetz, { + import cetz.draw: * + content((0, -1), [CeTZ], anchor: "north-west", name: "a") + line("x", "a") // You can refer the marked content. + line("y", "a") + }) +$ +``` +![Usage of annot-cetz](examples/usage-annot-cetz.svg) + + +## Tips +### Q. How to override default options? +Use the `with` function to create new functions with modified default arguments. +For example, to always use elbow-shaped leader lines for annotations: +```typst +#let annot = annot.with(leader-connect: "elbow") +``` + + +## Changelog +* v0.3.1: + - Added support for Typst v0.14. +* v0.3.0: + - Renamed some marking functions for clarity: + - `mark` is now `markhl` (for highlight). + - `marktc` is now `mark` (for text color change). + - Added direct leader line functionality to the `annot` function. + - Added support for multi-element annotations. + - Introduced support for annotations using CeTZ canvas. +* v0.2.3: + - Added support for RTL documents. + - Replaced deprecated `path` functions with `curve`. +* v0.2.2: + - Improved performance by removing counters. +* v0.2.1: + - Fixed layout issues with underlays/overlays in marking functions when custom math fonts are used. +* v0.2.0: + - Removed the `mannot-init` function. + - Introduced support for placing underlays beneath math content during marking. + - Added new marking functions: markrect, markul, marktc. +* v0.1.0: Initial release. diff --git a/.obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/src/annot.typ b/.obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/src/annot.typ new file mode 100644 index 0000000..73fa8ad --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/src/annot.typ @@ -0,0 +1,496 @@ +#import "util.typ": copy-stroke, default-stroke + +#import "@preview/tiptoe:0.3.2" + + +#let _coerce-anchor(anchor) = { + assert(type(anchor) == alignment, message: "`anchor` must be `alignment`") + if anchor.x == none { + anchor += center + } + if anchor.y == none { + anchor += horizon + } + return anchor +} + +#let _coerce-pos(pos) = { + if type(pos) == alignment { + pos = _coerce-anchor(pos) + pos = if pos.y == horizon { + (pos, pos.inv()) + } else { + (pos.y + center, pos.inv()) + } + } else if type(pos) == array { + assert(pos.len() == 2, message: "`pos` must be `anchor` or `(anchor, anchor)`") + pos = (_coerce-anchor(pos.at(0)), _coerce-anchor(pos.at(1))) + } else { + panic("`pos` must be alignment or array") + } + + return pos +} + +#let _coerce-connect(connect) = { + if type(connect) == array { + assert(connect.len() == 2, message: "`connect` must be `(anchor, anchor)` or \"elbow\"") + + connect = ( + _coerce-anchor(connect.at(0)), + _coerce-anchor(connect.at(1)), + ) + } else if connect != "elbow" { + panic("`connect` must be `(anchor, anchor)` or \"elbow\"") + } + + return connect +} + + +/// Places a custom annotation on content (or contents) within a math block that was previously marked. +/// +/// This function generates a custom annotation by applying an overlay based on +/// metadata associated with content marked with a specific tag using a marking function. +/// +/// Use this function as a foundation for defining custom annotation functions. +/// +/// *Example* +/// #example(```typ +/// #let myannot(tag, annotation) = { +/// let a = rect(annotation) +/// let overlay(markers) = { +/// let m = markers.first() +/// place(dx: m.x, dy: m.y + m.height, a) +/// } +/// return core-annot(tag, overlay) +/// } +/// +/// $ +/// mark(x, tag: #) +/// #myannot()[This is x.] +/// $ +/// ```, preview-inset: 20pt) +/// +/// -> content +#let core-annot( + /// The tag (or an array of tags) associated with the content to annotate. + /// This tag must match the tag previously used in a marking function. + /// -> label | array + tag, + /// The function to create the custom annotation overlay. + /// This function receives a list of metadata for the marked contents, + /// and should return content to be placed relative to the top-left corner of the page. + /// -> function + overlay, +) = { + if type(tag) == label { + tag = (tag,) + } + + context { + let markers = tag.map(tag => query(selector(tag).before(here())).last().value) + let hpos = here().position() + math.attach(box(place( + dx: -hpos.x, + dy: -hpos.y, + float: false, + left + top, + overlay(markers), + ))) + } +} + + +/// Places an annotation on content (or contents) within a math block that was previously marked. +/// +/// *Example* +/// ```example +/// #v(2em) +/// $ +/// markhl(x, tag: #) +/// #annot()[Annotation] +/// #annot(, pos: top + right, dy: -1em)[Another annotation] +/// $ +/// #v(1em) +/// ``` +/// +/// #example(```typ +/// $ +/// markrect(integral x dif x, tag: #<0>, color: #blue) +/// + markul(x, tag: #<1>, color: #blue) +/// +/// #annot((<0>, <1>), pos: top, dx: 4em)[Multi] +/// #annot((<0>, <1>), pos: bottom + left, dx: -1em, dy: 1em, leader-connect: "elbow")[Elbow] +/// $ +/// #v(1em) +/// ```, preview-inset: 20pt) +/// +/// -> content +#let annot( + /// The tag (or an array of tags) associated with the content to annotate. + /// This tag must match the tag previously used in a marking function. + /// -> label | array + tag, + /// Content of the annotation. -> content + annotation, + /// Where to place the annotation relative to the marked content. + /// This can be: + /// - A single alignment for the relative position to the marked content. + /// - A pair of alignments. The first one describes the anchor of the marked content, + /// and the second is the anchor of the annotation. + /// -> alignment | (alignment, alignment) + pos: bottom, + /// The horizontal displacement of the annotation. + /// -> auto | length + dx: auto, + /// The vertical displacement of the annotation. + /// -> auto | length + dy: auto, + /// Whether to draw a leader line from the marked content to the annotation. + /// If set to `auto`, it depends on the distance between the marked content and the annotation. + /// -> auto | bool + leader: auto, + /// How to stroke the leader line. + /// If its `paint` is set to `auto`, it will be set to the marking color. + /// If its `thickness` is set to `auto`, it defaults to `.048em`. + leader-stroke: .048em, + /// How to end the leader line. + /// See #link("https://typst.app/universe/package/tiptoe")[tiptoe]. + leader-tip: none, + /// How to start the leader line. + /// See #link("https://typst.app/universe/package/tiptoe")[tiptoe]. + leader-toe: tiptoe.straight.with(length: 600%), + /// How to connect the leader line. This can be: + /// - A pair of alignments describing the start anchor of the marked content + /// and the end anchor of the annotation. + /// - "elbow" for an elbow-shaped leader line + leader-connect: (center + horizon, center + horizon), + /// How much to pad the annotation content. + /// -> length | dictionary + annot-inset: (x: .08em, y: .16em), + /// How to align the annotation text. + /// -> auto | alignment + annot-alignment: auto, + /// Properties for the annotation text. + /// If the `fill` property is not specified, it defaults to the marking's color. + /// -> dictionary + annot-text-props: (size: .7em), + /// Properties for the annotation paragraph. + /// -> dictionary + annot-par-props: (leading: .4em), +) = { + pos = _coerce-pos(pos) + + if dx == auto { + dx = if pos.at(0).x == left and pos.at(1).x == right { + -.2em + } else if pos.at(0).x == right and pos.at(1).x == left { + .2em + } else { + 0em + } + } + if dy == auto { + dy = if pos.at(0).y == top and pos.at(1).y == bottom { + -.2em + } else if pos.at(0).y == bottom and pos.at(1).y == top { + .2em + } else { + 0em + } + } + + if type(annot-inset) != dictionary { + annot-inset = (annot-inset,) + } + + annotation = { + show: pad.with(..annot-inset) + set par(..annot-par-props) + text(..annot-text-props, annotation) + } + + context { + let dx = dx.to-absolute() + let dy = dy.to-absolute() + let annot-size = measure(annotation) + let aw = annot-size.width + let ah = annot-size.height + + let overlay(markers) = { + let x = markers.first().x + let y = markers.first().y + let w = markers.first().width + let h = markers.first().height + let c = markers.first().color + + let leader-stroke = default-stroke(leader-stroke, paint: c, thickness: .048em) + leader-stroke = copy-stroke(leader-stroke, thickness: leader-stroke.thickness.to-absolute()) + + let ax = if pos.at(0).x == left { x } else if pos.at(0).x == right { x + w } else { x + w / 2 } + ax -= if pos.at(1).x == left { 0pt } else if pos.at(1).x == right { aw } else { aw / 2 } + ax += dx + + let ay = if pos.at(0).y == top { y } else if pos.at(0).y == bottom { y + h } else { y + h / 2 } + ay -= if pos.at(1).y == top { 0pt } else if pos.at(1).y == bottom { ah } else { ah / 2 } + ay += dy + + let annot-text-fill = annot-text-props.at("fill", default: c) + let annot-alignment = if annot-alignment == auto { + if ax + aw / 2 < x + w / 2 { right } else { left } + } else { + annot-alignment + } + let annotation = { + show: box.with(width: aw, height: ah) + set align(annot-alignment) + set text(annot-text-fill) + annotation + } + + place(dx: ax, dy: ay, float: false, left + top, annotation) + + if leader != false { + for info in markers { + let x = info.x + let y = info.y + let w = info.width + let h = info.height + + if leader == auto { + let dst = calc.max( + ax - x - w, + x - ax - aw, + ay - y - h, + y - ay - ah, + ) + if dst <= .3em.to-absolute() { + continue + } + } + + if type(leader-connect) == array { + let c0x = if leader-connect.at(0).x == left { + x + } else if leader-connect.at(0).x == right { + x + w + } else { + x + w / 2 + } + let c0y = if leader-connect.at(0).y == top { + y + } else if leader-connect.at(0).y == bottom { + y + h + } else { + y + h / 2 + } + let c1x = if leader-connect.at(1).x == left { + ax + } else if leader-connect.at(1).x == right { + ax + aw + } else { + ax + aw / 2 + } + let c1y = if leader-connect.at(1).y == top { + ay + } else if leader-connect.at(1).y == bottom { + ay + ah + } else { + ay + ah / 2 + } + let cdx = c1x - c0x + let cdy = c1y - c0y + + let l0x = c0x + let l0y = c0y + let l1x = c1x + let l1y = c1y + + if leader-connect.at(0) == center + horizon { + if calc.abs(cdx.pt()) * h < calc.abs(cdy.pt()) * w { + if cdy > 0pt { + l0x = c0x + h / 2 / cdy * cdx + l0y = y + h + } else { + l0x = c0x - h / 2 / cdy * cdx + l0y = y + } + } else { + if cdx > 0pt { + l0x = x + w + l0y = c0y + w / 2 / cdx * cdy + } else { + l0x = x + l0y = c0y - w / 2 / cdx * cdy + } + } + } + + if leader-connect.at(1) == center + horizon { + if calc.abs(cdx.pt()) * ah < calc.abs(cdy.pt()) * aw { + if cdy > 0pt { + l1x = c1x - ah / 2 / cdy * cdx + l1y = ay + } else { + l1x = c1x + ah / 2 / cdy * cdx + l1y = ay + ah + } + } else { + if cdx > 0pt { + l1x = ax + l1y = c1y - aw / 2 / cdx * cdy + } else { + l1x = ax + aw + l1y = c1y + aw / 2 / cdx * cdy + } + } + } + + // if leader == auto { + // let leader-len = calc.norm((l1x - l0x).pt(), (l1y - l0y).pt()) + // if leader-len <= .4em.to-absolute().pt() { + // continue + // } + // } + + { + set place(left + top, float: false) // For RTL document. + tiptoe.curve( + stroke: leader-stroke, + tip: leader-tip, + toe: leader-toe, + curve.move((l0x, l0y)), + curve.line((l1x, l1y)), + ) + } + } else { + let corner = .2em.to-absolute() + let components = if ax > x + corner or ax + aw < x + w - corner { + if ax + aw / 2 < x + w / 2 { + if ay + ah < y or ay + ah > y + h { + let l0x = calc.max(x + corner, ax + aw) + let l0y = if ay + ah < y { y } else if y + h < ay + ah { y + h } + ( + curve.move((l0x, l0y)), + curve.line((l0x, ay + ah)), + curve.line((ax, ay + ah)), + ) + } else { + ( + curve.move((x, ay + ah)), + curve.line((ax, ay + ah)), + ) + } + } else { + if ay + ah < y or ay + ah > y + h { + let l0x = calc.min(x + w - corner, ax) + let l0y = if ay + ah < y { y } else if y + h < ay + ah { y + h } + ( + curve.move((l0x, l0y)), + curve.line((l0x, ay + ah)), + curve.line((ax + aw, ay + ah)), + ) + } else { + ( + curve.move((x + w, ay + ah)), + curve.line((ax + aw, ay + ah)), + ) + } + } + } else { + if ay > y + h { + ( + curve.move((x + w / 2, y + h)), + curve.line((x + w / 2, ay)), + ) + } else { + ( + curve.move((x + w / 2, y)), + curve.line((x + w / 2, ay + ah)), + ) + } + } + + { + set place(left + top, float: false) // For RTL document. + tiptoe.curve( + stroke: leader-stroke, + tip: leader-tip, + toe: leader-toe, + ..components, + ) + } + } + } + } + } + + return core-annot(tag, overlay) + } +} + + +/// Places a CeTZ canvas annotation on content (or contecnts) within a math block that was previously marked. +/// +/// Within the CeTZ canvas code block, you can refer to the position of the marked content +/// using an anchor with the same name as the tag. +/// For multiple tags, you'll have multiple corresponding anchors. +/// +/// *Example* +/// ```example +/// #import "@preview/cetz:0.4.2" +/// +/// $ +/// mark(x, tag: #<0>) + mark(y, tag: #<1>) +/// +/// #annot-cetz((<0>, <1>), cetz, { +/// import cetz.draw: * +/// content((0, -.6), [Annotation], anchor: "north-west", name: "a") +/// set-style(stroke: .7pt, mark: (start: "straight", scale: 0.6)) +/// line("0", "a") +/// line("1", "a") +/// }) +/// $ +/// #v(1em) +/// ``` +/// +/// -> content +#let annot-cetz( + /// The tag (or an array of tags) associated with the content to annotate. + /// This tag must match the tag previously used in a marking function. + /// -> label | array + tag, + /// A CeTZ module. + /// -> module + cetz, + /// A code block containing CeTZ drawing commands. + /// -> array + drawable, +) = { + let overlay(markers) = { + let origin = markers.first() + let preamble = markers + .map(info => { + cetz.draw.rect( + (info.x - origin.x, -(info.y - origin.y)), + (info.x + info.width - origin.x, -(info.y + info.height - origin.y)), + name: str(info.tag), + stroke: none, + fill: none, + ) + }) + .sum() + let loc-lab = <_mannot-annot-cetz-loc> + let loc-lab-content = cetz.draw.content((0, 0), [#none#loc-lab]) + + place(hide(cetz.canvas(loc-lab-content + preamble + drawable))) + + context { + let cpos = query(selector(loc-lab).before(here())).last().location().position() + place(dx: origin.x - cpos.x, dy: origin.y - cpos.y, cetz.canvas(preamble + drawable)) + } + } + + core-annot(tag, overlay) +} diff --git a/.obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/src/lib.typ b/.obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/src/lib.typ new file mode 100644 index 0000000..cf42851 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/src/lib.typ @@ -0,0 +1,2 @@ +#import "mark.typ": core-mark, mark, markhl, markrect, markul +#import "annot.typ": core-annot, annot, annot-cetz, tiptoe diff --git a/.obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/src/mark.typ b/.obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/src/mark.typ new file mode 100644 index 0000000..430d6ea --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/src/mark.typ @@ -0,0 +1,498 @@ +#import "util.typ": default-stroke + + +#let _sequence-func = ([x] + [y]).func() +#let _align-point-func = $&$.body.func() + +#let _remove-leading-h(body) = { + if type(body) == content { + if body.func() == math.equation { + return _remove-leading-h(body.body) + } else if body.func() == _sequence-func { + let children = body.children + if children.len() == 0 { + return (none, none) + } + let rest + let remove + let leadingCount = 0 + for c in children { + if c == [ ] { + leadingCount += 1 + continue + } + let (crest, cremove) = _remove-leading-h(c) + rest = crest + remove += cremove + if rest != none { + break + } + leadingCount += 1 + } + children = children.slice(leadingCount) + children.first() = rest + let rest = _sequence-func(children) + return (rest, remove) + } else if body.func() == h { + return (none, body) + } + } + return (body, none) +} + +#let _remove-trailing-h(body) = { + if type(body) == content { + if body.func() == math.equation { + return _remove-trailing-h(body.body) + } else if body.func() == _sequence-func { + let children = body.children + if children.len() == 0 { + return (none, none) + } + let rest + let remove + let trailingCount = 0 + for c in children.rev() { + if c == [ ] { + trailingCount += 1 + continue + } + let (crest, cremove) = _remove-leading-h(c) + rest = crest + remove += cremove + if rest != none { + break + } + trailingCount += 1 + } + children = children.slice(0, children.len() - trailingCount) + children.last() = rest + let rest = _sequence-func(children) + return (rest, remove) + } else if body.func() == h { + return (none, body) + } + } + return (body, none) +} + +#let _label-each-child(body, label) = { + if type(body) == content { + if body.func() == math.equation { + let body = _label-each-child(body.body, label) + return body + } else if body.func() == _sequence-func { + // If `content` is the sequence of contents, + // then put `label` on each child. + let children = body.children.map(c => { + _label-each-child(c, label) + }) + return body.func()(children) + } else if body == [ ] or body.func() == _align-point-func or body.func() == h or body.func() == v { + // Do not put `label` on layout contents such as [ ], align-point(), + // h(), or v(), in order to avoid broken layout. + return body + } + } + return math.attach(math.limits(body), t: pad([#none#label], -1em), b: pad([#none#label], -1em)) +} + + +/// Marks content within a math block with a custom underlay or overlay. +/// +/// This function measures the position and size of the marked content, +/// applies a custom underlay or overlay, and generates metadata associated with a given tag. +/// The metadata includes the content's position (`x`, `y`), dimensions (`width`, `height`), +/// and the color used for the marking. This metadata can be later used for annotations. +/// +/// Use this function as a foundation for defining custom marking functions. +/// +/// *Example:* +/// ```example +/// #let mymark(body, tag: none) = { +/// let overlay(width, height, color) = { +/// rect(width: width, height: height, stroke: color) +/// } +/// return core-mark(body, tag: tag, color: red, overlay: overlay, outset: (y: .1em)) +/// } +/// +/// $ +/// mymark(x, tag: #) +/// +/// #context { +/// let info = query().last() +/// sym.wj +/// box(place(repr(info.value), dx: -5em)) +/// } +/// $ +/// #v(12em) +/// ``` +/// +/// -> content +#let core-mark( + /// The content to be marked within a math block. -> content + body, + /// An optional tag to associate with the metadata. + /// This tag can be used to query the marked element. + /// -> none | label + tag: none, + /// The color used for marking underlay/overlay and later annotations. + /// If set to `auto`, it defaults to the text fill color. + /// -> auto | color + color: auto, + /// An optional function to create a custom underlay. + /// This function receives the marked content's width and height (including outset) + /// and marking color, and should return content to be placed *under* the marked content. + /// The signature is `underlay(width, height, color)`. + /// -> none | function + underlay: none, + /// An optional function to create a custom overlay. + /// This function receives the marked content's width and height (including outset) + /// and marking color, and should return content to be placed *over* the marked content. + /// The signature is `overlay(width, height, color)`. + /// -> none | function + overlay: none, + /// How much to expand the marking box size without affecting the layout. + /// This can be specified as a single `length` value, which applies to all sides, + /// or as a `dictionary` of `length` with keys `left`, `right`, `top`, `bottom`, `x`, `y`, or `rest`. + /// -> none | length | dictionary + outset: (:), +) = { + // Extract leading/trailing horizontal spaces from body. + let (body, leading-h) = _remove-leading-h(body) + let (body, trailing-h) = _remove-trailing-h(body) + leading-h + + context { + let y-lab = label("_mannot-mark-y") + let dy-lab = label("_mannot-mark-dy") + let info-lab = if type(tag) == label { + tag + } else { + label("_mannot-mark-info") + } + + let color = color + if color == auto { + color = text.fill + } + + let begin-loc = here() + + // Place `underlay(width, height, color)` under the `body`. + if underlay != none { + sym.wj + context { + let elems = query(selector(info-lab).after(begin-loc)) + if elems.len() > 0 { + let info + for e in elems { + info = e.value + // Find the corresponding info if nesting. + if info.begin-loc == begin-loc { + break + } + } + + let hpos = here().position() + let dx = info.x - hpos.x + let dy = info.y - hpos.y + let width = info.width + let height = info.height + let color = info.color + math.attach(box(place( + dx: dx, + dy: dy, + float: false, + left + top, + underlay(width, height, color), + ))) + } + } + } + + // Put the labeled body for measuring its position and size. + let labeled-body = _label-each-child(body, y-lab) + sym.wj + labeled-body + sym.wj + math.attach( + math.limits([#none#dy-lab]), + t: pad(-1em, [#none#dy-lab]), + b: pad(-1em, [#none#dy-lab]), + ) + + // Measure the position and size, expose metadata and place the overlay. + context { + let end-loc = here() + + let y-array = query(selector(y-lab).after(begin-loc).before(end-loc)).map(e => e.location().position().y) + let min-y = calc.min(..y-array) + let max-y = calc.max(..y-array) + + let dy-array = query(selector(dy-lab).after(begin-loc).before(end-loc)).map(e => e.location().position().y) + let top-dy = dy-array.at(0) - dy-array.at(1) + let bottom-dy = dy-array.at(0) - dy-array.at(2) + // let place-dy = dy-array.at(0) - dy-array.at(3) + // let place-dy = 0pt + let top-y = min-y + top-dy + let bottom-y = max-y + bottom-dy + + let left-x = begin-loc.position().x + let right-x = query(selector(dy-lab).after(begin-loc).before(end-loc)).last().location().position().x + + let outset = if outset == none { + (left: 0pt, right: 0pt, top: 0pt, bottom: 0pt) + } else if type(outset) == length { + let outset = outset.to-absolute() + (left: outset, right: outset, top: outset, bottom: outset) + } else if type(outset) == dictionary { + let rest = outset.at("rest", default: 0pt).to-absolute() + let x = outset.at("x", default: rest).to-absolute() + let left = outset.at("left", default: x).to-absolute() + let right = outset.at("right", default: x).to-absolute() + let y = outset.at("y", default: rest).to-absolute() + let top = outset.at("top", default: y).to-absolute() + let bottom = outset.at("bottom", default: y).to-absolute() + (left: left, right: right, top: top, bottom: bottom) + } + + let x = left-x - outset.left + let y = top-y - outset.top + let width = right-x + outset.right - x + let height = bottom-y - top-y + outset.top + outset.bottom + + // Expose the metadata. + let info = (body: body, x: x, y: y, width: width, height: height, color: color, tag: tag, begin-loc: begin-loc) + sym.wj + [#metadata(info)#info-lab] + + // Place `overlay(width, height, color)` over the `body`. + if overlay != none { + let hpos = here().position() + let dx = x - hpos.x + let dy = y - hpos.y + sym.wj + math.attach(box(place( + dx: dx, + dy: dy, + float: false, + left + top, + overlay(width, height, color), + ))) + } + } + } + + sym.wj + trailing-h +} + + +/// Marks an annotation target within a math block. +/// +/// If you mark content with a tag, you can annotate it using the `annot` function. +/// +/// If the `color` argument is provided, it will change the color of the marked text. +/// +/// *Example* +/// ```example +/// $ mark(x, color: #red) $ +/// ``` +/// +/// -> content +#let mark( + /// The content to be marked within a math block. -> content + body, + /// An optional tag used to identify the marked content for later annotations. + /// -> none | label + tag: none, + /// An optional color for the text and later annotations. + /// If set to `auto`, it defaults to the text fill color. + /// -> auto | color + color: auto, + /// How much to expand the marking box size without affecting the layout. + /// This can be specified as a single `length` value which applies to all sides, + /// or as a `dictionary` of `length` with keys `left`, `right`, `top`, `bottom`, `x`, `y`, or `rest`. + /// -> none | length | dictionary + outset: (y: .1em), +) = { + if color != auto { + body = text(fill: color, body) + return core-mark(body, tag: tag, color: color, outset: outset) + } else { + return context { + let color = text.fill + core-mark(body, tag: tag, color: color, outset: outset) + } + } +} + + +/// Marks and highlights content within a math block. +/// +/// If you mark content with a tag, you can annotate it using the `annot` function. +/// +/// *Example* +/// ```example +/// $ markhl(x) $ +/// ``` +/// +/// -> content +#let markhl( + /// The content to be highlighted within a math block. -> content + body, + /// An optional tag used to identify the marked content for later annotations. + /// -> none | label + tag: none, + /// The color used for the highlight and later annotations. + /// If both `color` and `fill` are set to `auto`, `color` defaults to `orange`. + /// Otherwise, if only `color` is `auto`, it defaults to the text fill color. + /// -> auto | color + color: auto, + /// How to fill the highlight rectangle. + /// If set to `auto`, `fill` will be set to `color.transparentize(60%)`. + /// -> auto | none | color | gradient | pattern + fill: auto, + /// How to stroke the highlight rectangle. + /// If its `paint` is set to `auto`, it will be set to the `color`. + /// If its `thickness` is set to `auto`, it defaults to `.048em`. + /// -> none | auto | length | color | gradient | stroke | pattern | dictionary + stroke: none, + /// How much to round the highlight rectangle's corner. + /// -> relative | dictionary + radius: (:), + /// How much to expand the highlight box size without affecting the layout. + /// This can be specified as a single `length` value which applies to all sides, + /// or as a `dictionary` of `length` with keys `left`, `right`, `top`, `bottom`, `x`, `y`, or `rest`. + /// -> none | length | dictionary + outset: (y: .1em), +) = { + if fill == auto { + if color == auto { + color = orange + } + fill = color.transparentize(60%) + } + if stroke != none { + stroke = default-stroke(stroke, paint: color, thickness: .048em) + } + + let underlay = if fill == none and stroke == none { none } else { + (width, height, _) => { + rect( + width: width, + height: height, + fill: fill, + stroke: stroke, + radius: radius, + ) + } + } + + return core-mark(body, tag: tag, color: color, underlay: underlay, outset: outset) +} + + +/// Marks and boxes around content within a math block. +/// +/// If you mark content with a tag, you can annotate it using the `annot` function. +/// +/// *Example* +/// ```example +/// $ markrect(x + y) $ +/// ``` +/// +/// -> content +#let markrect( + /// The content to be boxed around within a math block. -> content + body, + /// An optional tag used to identify the content for later annotations. + /// -> none | label + tag: none, + /// The color used for the rectangle's stroke and later annotations. + /// If set to `auto`, it defaults to the text fill color. + /// -> color + color: auto, + /// How to fill the rectangle. + /// -> none | color | gradient | pattern + fill: none, + /// How to stroke the rectangle. + /// If its `paint` is set to `auto`, it will be set to the `color`. + /// If its `thickness` is set to `auto`, it defaults to `.048em`. + /// -> none | length | color | gradient | stroke | pattern | dictionary + stroke: .048em, + /// How much to round the highlight rectangle's corner. + /// -> relative | dictionary + radius: (:), + /// How much to expand the rectangle size without affecting the layout. + /// This can be specified as a single `length` value which applies to all sides, + /// or as a `dictionary` of `length` with keys `left`, `right`, `top`, `bottom`, `x`, `y`, or `rest`. + /// -> none | length | dictionary + outset: (y: .1em), +) = { + let underlay = if fill == none and stroke == none { none } else { + (width, height, color) => { + let stroke = stroke + if stroke != none { + stroke = default-stroke(stroke, paint: color, thickness: .048em) + } + + rect( + width: width, + height: height, + fill: fill, + stroke: stroke, + radius: radius, + ) + } + } + + return core-mark(body, tag: tag, color: color, underlay: underlay, outset: outset) +} + + +/// Marks and underlines content within a math block. +/// +/// If you mark content with a tag, you can annotate it using the `annot` function. +/// +/// *Example* +/// ```example +/// $ markul(x + y) $ +/// ``` +/// +/// -> content +#let markul( + /// The content to be underlined within a math block. -> content + body, + /// An optional tag used to identify the content for later annotations. + /// -> none | label + tag: none, + /// The color used for the underline and later annotations. + /// If set to `auto`, it defaults to the text fill color. + /// -> auto | color + color: auto, + /// How to stroke the underline. + /// If its `paint` is set to `auto`, it will be set to the `color`. + /// If its `thickness` is set to `auto`, it defaults to `.048em`. + /// -> none | length | color | gradient | stroke | pattern | dictionary + stroke: .048em, + /// How much to expand the marking box size without affecting the layout. + /// This can be specified as a single `length` value which applies to all sides, + /// or as a `dictionary` of `length` with keys `left`, `right`, `top`, `bottom`, `x`, `y`, or `rest`. + /// -> none | length | dictionary + outset: (top: .1em, bottom: .144em), +) = { + let overlay = if stroke == none { none } else { + (width, height, color) => { + let stroke = stroke + if stroke != none { + stroke = default-stroke(stroke, paint: color, thickness: .048em) + } + + line(start: (0pt, height), end: (width, height), stroke: stroke) + } + } + + return core-mark(body, tag: tag, color: color, overlay: overlay, outset: outset) +} diff --git a/.obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/src/util.typ b/.obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/src/util.typ new file mode 100644 index 0000000..ad93286 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/src/util.typ @@ -0,0 +1,39 @@ +#let default-stroke( + stroke_like, + paint: auto, + thickness: auto, + cap: auto, + join: auto, + dash: auto, + miter-limit: auto, +) = { + let s = stroke(stroke_like) + return stroke( + paint: if s.paint == auto { paint } else { s.paint }, + thickness: if s.thickness == auto { thickness } else { s.thickness }, + cap: if s.cap == auto { cap } else { s.cap }, + join: if s.join == auto { join } else { s.join }, + dash: if s.dash == auto { dash } else { s.dash }, + miter-limit: if s.miter-limit == auto { miter-limit } else { s.miter-limit }, + ) +} + +#let copy-stroke( + stroke_like, + paint: auto, + thickness: auto, + cap: auto, + join: auto, + dash: auto, + miter-limit: auto, +) = { + let s = stroke(stroke_like) + return stroke( + paint: if paint == auto { s.paint } else { paint }, + thickness: if thickness == auto { s.thickness } else { thickness }, + cap: if cap == auto { s.cap } else { cap }, + join: if join == auto { s.join } else { join }, + dash: if dash == auto { s.dash } else { dash }, + miter-limit: if miter-limit == auto { s.miter-limit } else { miter-limit }, + ) +} diff --git a/.obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/typst.toml b/.obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/typst.toml new file mode 100644 index 0000000..714bf0c --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/mannot/0.3.1/typst.toml @@ -0,0 +1,12 @@ +[package] +name = "mannot" +version = "0.3.1" +entrypoint = "src/lib.typ" +authors = ["ryuryu-ymj"] +license = "MIT" +description = "A package for marking and annotating in math blocks." +repository = "https://github.com/ryuryu-ymj/mannot" +keywords = ["mark", "highlight", "annotation", "math"] +categories = ["visualization", "layout"] +compiler = "0.14.0" +exclude = ["docs/*", "tests/*", "examples/*"] diff --git a/.obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/LICENSE b/.obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/LICENSE new file mode 100644 index 0000000..c55e98f --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2023-2025 Eric Biedert + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/README.md b/.obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/README.md new file mode 100644 index 0000000..b013864 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/README.md @@ -0,0 +1,43 @@ +# quick-maths + +A package for creating custom shorthands for math equations. + +## Usage + +The package comes with a single template function `shorthands` that takes one or more tuples of the form `(shorthand, replacement)`, where `shorthand` can be a string or content. + +There are some small quality of life features for interaction of shorthands with fractions and attachments: + +- If the right-most symbol of a shorthand has any attachments, they are moved to the shorthand's replacement. +- If a shorthand ends in the numerator of a fraction, the whole replacement is placed in the numerator. +- If a shorthand starts in the denominator of a fraction, the whole replacement is placed in the denominator. + +As the implementation of these features is quite hacky, you may encounter some edge cases, where the use of explicit parentheses hopefully saves you. + +## Notes + +- Shorthands are parsed in the order they are given, so if you have a shorthand that is a prefix of another shorthand, you should put the longer shorthand first. + +- The content of an equation is traversed from left to right, so the left-most matching shorthand will be replaced first. + +- Shorthands consisting of only a single character or element may be applied using show rules, so that they can affect non-sequence elements. This may lead to different behavior than multi-character shorthands. + +- If the replacement of a shorthand contains a shorthand itself, there are no protections against infinite recursion or overflows. + +## Example + +```typ +#import "@preview/quick-maths:0.2.1": shorthands + +#show: shorthands.with( + ($+-$, $plus.minus$), + ($|-$, math.tack), + ($<=$, math.arrow.l.double) // Replaces '≤' +) + +$ x^2 = 9 quad <==> quad x = +-3 $ +$ A or B |- A $ +$ x <= y $ +``` + +![Result of example code.](./assets/example.svg) diff --git a/.obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/src/lib.typ b/.obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/src/lib.typ new file mode 100644 index 0000000..e2d2c65 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/src/lib.typ @@ -0,0 +1 @@ +#import "quick-maths.typ": shorthands diff --git a/.obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/src/quick-maths.typ b/.obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/src/quick-maths.typ new file mode 100644 index 0000000..bc4ecf8 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/src/quick-maths.typ @@ -0,0 +1,193 @@ +// Element function for sequences +#let sequence = [].func() + +// Convert content to an array of its children +#let to-children(content) = { + if type(content) in (str, symbol) { + str(content).clusters().map(char => [#char]) + } else if content.func() == sequence { + content.children + } else if content.func() == text { + to-children(content.text) + } else if content.func() == math.equation { + to-children(content.body) + } else { + (content,) + } +} + +// Convert shorthands in the given sequence to their respective replacements. +#let convert-sequence(seq, shorthands) = { + let is-sequence = type(seq) == content and seq.func() == sequence + if not is-sequence or seq.children.len() == 0 { + return seq + } + + let children = seq.children + for (shorthand, replacement) in shorthands { + let components = to-children(shorthand) + let start = 0 + + while start < children.len() { + let pos = children.slice(start).position(c => { + c == components.first() or [#c].func() == math.frac + }) + + if pos == none { + break + } + + pos = start + pos // Position of first matching character + start = pos + 1 // Start index for finding next match + + // Check whether all components of the shorthand match + let matches = true + let attachments = none + let frac-fields = none + for i in range(components.len()) { + let child = children.at(pos+i, default: none) + if child == components.at(i) { + continue + } + + let is-last = i == components.len() - 1 + + // Try last one with only numerator. + if is-last and child != none and [#child].func() == math.frac { + let (num, denom) = child.fields() + child = num + frac-fields = (denom: denom) + } + + if child == components.at(i) { + // Next match could start in the denominator of the same fraction, so + // revert the start index to include the current index. + start -= 1 + continue + } + + // Try first one with only denominator. + if i == 0 and child != none and [#child].func() == math.frac { + let (num, denom) = child.fields() + child = denom + frac-fields = (num: num) + } + + if child == components.at(i) { + continue + } + + // Try last one without attachments. + if is-last and child != none and [#child].func() == math.attach { + let fields = child.fields() + let base = fields.remove("base") + child = child.base + attachments = fields + } + + if child != components.at(i) { + matches = false + break + } + } + + if matches { + // Remove shorthand and insert replacement + for i in range(components.len()) { + let _ = children.remove(pos) + } + let replacement = replacement + + // Add back attachments. + if attachments != none { + replacement = math.attach(replacement, ..attachments) + } + + // Put back in denominator or numerator if needed. + if frac-fields != none { + replacement = if "num" in frac-fields { + math.frac(frac-fields.num, replacement) + } else { + math.frac(replacement, frac-fields.denom) + } + } + + children.insert(pos, replacement) + } + } + } + + return children.join() +} + +// A template that converts the given shorthands to their respective replacement. +// +// Parameters: +// - shorthands: One or more tuples of the form `(shorthand, replacement)`. +// - body: The body to apply the template on. +// +// Returns: The body with evaluated shorthands. +#let shorthands(..shorthands, body) = { + let shorthands = shorthands.pos() + + show math.equation: eq => { + show sequence: seq => { + let new = convert-sequence(seq, shorthands) + if new != seq { new } else { seq } + } + + // Apply single-element shorthands as show rules, so that they are also + // applied to non-sequence elements. + shorthands.fold(eq, (acc, (shorthand, replacement)) => { + let string = if type(shorthand) in (str, symbol) { + str(shorthand) + } else if type(shorthand) != content { + return acc + } else if shorthand.func() == text { + shorthand.text + } else if shorthand.func() == math.equation and shorthand.body.func() == text { + if shorthand.body.text.len() > 1 or sys.version < version(0, 13) { + shorthand.body.text + } + } else if shorthand.func() == sequence { + return acc + } + + if string == none { + // Shorthand cannot be converted to a string, so try to match it directly. + if shorthand.func() == math.equation and shorthand.body.func() != sequence { + let func = shorthand.body.func() + let filter = shorthand.body.fields() + + show func.where(..filter): it => { + // If the element contains more content that was needed for the + // match (e.g. attachments), it should be added back to the + // replacement. + let fields = it.fields() + let remaining = fields.pairs() + .filter(((key, val)) => key not in filter and type(val) == content) + .fold((:), (acc, (key, val)) => acc + ((key): val)) + + if remaining != (:) { + func(replacement, ..remaining) + } else { + replacement + } + } + + acc + } else { + acc + } + } else { + // Escape regex special characters. + string = string.replace(regex("[-\[\]{}()*+?.,\\\\^$|#\\s]"), it => "\\" + it.text) + + show regex("^" + string + "$"): replacement + acc + } + }) + } + + body +} diff --git a/.obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/typst.toml b/.obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/typst.toml new file mode 100644 index 0000000..8d88a58 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/quick-maths/0.2.1/typst.toml @@ -0,0 +1,10 @@ +[package] +name = "quick-maths" +version = "0.2.1" +entrypoint = "src/lib.typ" +authors = ["Eric Biedert"] +license = "MIT" +description = "Custom shorthands for math equations." +repository = "https://github.com/EpicEricEE/typst-quick-maths" +exclude = ["assets"] +categories = ["utility"] diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/LICENSE.txt b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/LICENSE.txt new file mode 100644 index 0000000..5e8cd93 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024-2025 Mc-Zen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/README.md b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/README.md new file mode 100644 index 0000000..6231c83 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/README.md @@ -0,0 +1,337 @@ +

+ + + + Logo + +

+ +_Arrows for [Typst][typst] paths and other stories._ + +[![Typst Package](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FMc-Zen%2Ftiptoe%2Fv0.3.1%2Ftypst.toml&query=%24.package.version&prefix=v&logo=typst&label=package&color=239DAD)](https://typst.app/universe/package/tiptoe) +[![Test Status](https://github.com/Mc-Zen/tiptoe/actions/workflows/run_tests.yml/badge.svg)](https://github.com/Mc-Zen/tiptoe/actions/workflows/run_tests.yml) +[![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/Mc-Zen/tiptoe/blob/main/LICENSE) + +--- + +*Tiptoe* adds configurable arrow tips (and toes) to the functions `line()` and `curve()`, and `path()`. Moreover, it adds the geometric primitive `arc()`. + +- [Tiptoe vs. Fletcher](#tiptoe-vs-fletcher) +- [Available marks](#available-marks) +- [Mark sizing and styling](#mark-sizing-and-styling) +- [Mark alignment](#mark-alignment) +- [Path shortening](#path-shortening) +- [Combining marks](#combining-marks) +- [Defining custom marks](#defining-custom-marks) +- [Arc](#arc) +- [Difference between built in and tiptoe curve](#difference-between-built-in-and-tiptoe-curve) + + +The functions `tiptoe.line()`, `tiptoe.curve()`, and `tiptoe.path()` act as a drop-in replacement (except that [they are placed by default](#difference-between-built-in-and-tiptoe-path)) for the built-in counterparts − but they are enhanced by additional `tip` and `toe` (you have read the title, what did you expect??) arguments. + +Let us consider a simple example to start off. +```typ +#import "@preview/tiptoe:0.3.1": * + +#line(tip: stealth, toe: stealth.with(rev: true)) +#curve( + tip: triangle, toe: bar, + std.curve.cubic((10pt, 0pt), (20pt, 0pt), (20pt, 10pt)), + std.curve.cubic(auto, none, (0pt, 20pt)), +)) +``` +

+ + + + Basic introductory example + +

+ + + +## Tiptoe vs. [Fletcher][fletcher] + +_Before going into the details:_ There exists another awesome package that provides great support for arrows and marks: [Fletcher][fletcher] by [Jollywatt][jollywatt]. If you wonder which package to use, the decision is easy because their use-cases are almost complementary. +- Fletcher works with (and needs) [CeTZ][cetz] while +- Tiptoe does not need (and does not really work with) CeTZ. + +So, if you want to create CeTZ graphics − use Fletcher! If you don't want to use CeTZ − maybe because you just need a single arrow, can't use a canvas, or develop a package that provides graphics utilities − stay here 😉. + +Also note, that the tip sizing and configuration mechanism works quite differently. + + +## Available marks + +Tiptoe comes with a collection of predefined marks, listed below. In [Defining custom marks](#defining-custom-marks), you can learn how to define your own marks. +

+ + + + Available predefined marks + +

+ + +## Mark sizing and styling + +All predefined marks can be configured through `.with()` calls. Some options (like the `inset` of a stealth arrow) are mark-specific. + +*Be Typst!* If you use a configured mark more than once, define an alias for it. Typst makes it incredibly easy to define variables: +```typ +#let my-mark = stealth.with(rev: true, inset: 20%) +#line(tip: my-mark) +``` + +### Sizing + +The size of most arrows is primarily defined by their length and secondarily by their width (exceptions are `bar`, `barb`, `hooks`, and `tikz` which only have a width). Both width and length can be set using +- a `length` value, such as `10pt`, +- or a ratio which is measured relative to the thickness of the line (e.g., `500%` corresponds to 5 times the line thickness), +- or a combination of both, e.g., `3pt + 450%` (this is by the way the default for the `stealth` mark). +This makes it possible to fine-tune the sizing behavior of a mark. By default (`width: auto`) and for most marks the width is defined in terms of the length via some predefined ratio. + + +With the predefined marks, the length/width encompasses the *full* length/width of the mark, independent of the stroke thickness that is used. This is demonstrated below, where the fill is removed through `stealth.with(fill: none)`. + +

+ + + + Arrow sizing + +

+ +### Colors! + +Usually, a mark inherits color and stroke thickness (but not other stroke attributes like `join` or `cap`) from the line that the mark is attached to. In order to override the color, the thickness or both, all marks feature a `stroke` parameter. Additionally, all solid marks feature a `fill` parameter that defaults (when set to `auto`) to the stroke color. + +Below are some examples for mark styling. + +

+ + + + Examples for styling marks + +

+ +## More styling + +Apart from `length`, `width`, `fill`, and `stroke`, many marks possess additional styling parameters, such as +- the `inset` for the arrows `stealth` and `round`, +- an arc angle parameter for the marks `barb` and `hooks`, + +- and of course the `rev` parameter that allows for reversing all marks where this makes sense. + +The figure below shows the additional parameters that each mark supports. The red line indicates how much the underlying path is shortened (see [Path shortening](#path-shortening)). + +

+ + + + Parameters of the predefined marks + +

+ + +## Mark alignment + +Most marks are aligned such that they point _right onto the end_ (or start) of the path. However, for some marks it is more desirable to have them _centered_ at the end (or start). This is for example the case for the `square` and `circle` marker. All markers that are by default centered on the path end have an `align` parameter that can be set either to `center` or `end` to configure this behavior. + +The mark alignment for the built-in marks is summarized in the table below. + +

+ + + + Alignment of marks + +

+ + + +## Path shortening + +In order to make room for the mark, the path needs to be shortened by some amount. This is trivial for straight segments but not for curved paths. + +The issue is demonstrated in the figure below. In all cases, the arrow is tangent to the curve at its end. In the left panel of the figure, the curve does not enter the arrow in the middle but rather from the side which definitely wouldn't make you look like a good designer when handing in professional work ;) + +To compensate this issue, the path is _transformed_, i.e., shortened by some amount to make it seem nicer. This happens at the cost of the path being not quite the same as before; but it yields a much prettier result. + + +

+ + + + Path shortening + +

+ + + +Not always is it desirable to shorten the path all the way (hey, a little asymmetry simply belongs in life). For this purpose, `curve`, `path`, and `arc` have a parameter `shorten` which takes ratios between `0%` and `100%` (default). + + + +## Combining marks + +The function `combine()` makes it easy to combine multiple marks into a single new one. It accepts any number of marks and can even process combined marks recursively. + +```typ +#line(tip: combine(bar, stealth)) +``` +

+ + + + How to combine marks + +

+ + + +The combined marks are automatically lined up one after the other; always the next one where the previous one ended. In order to introduce or increase the space between two marks, you may use length values (like `10pt`) or even better ratios (which are measured relative to the line thickness of the curve). Negative values are allowed! + + + +

+ + + + Examples for combined arrows and marks + +

+ +By default, the path is shortened until the last mark. This behaviour can be overriden by adding an `end` element somewhere in the mark list. The position of the `end` element between the marks defines where the line or path should end. + + +## Defining custom marks + +A mark is just a function that accepts a named `line` argument and that returns a dictionary `(mark: .., end: ..)` where `mark` holds the rendered mark and `end` is a length that specifies the amount by which the line or path needs to be shortened. + +As an example, let us look at a simplified definition of the `bar` mark. +```typ +#import tiptoe.utility + +#let bar( + // mandatory, will be set by line(), path() and arc() + line: stroke() + // optional configuration parameters + width: 2.4pt + 360%, + stroke: auto, +) = { + stroke = utility.process-stroke(line, stroke) + let (width,) = utility.process-dims( + line, width: width + ) + + ( + mark: place(path( + (0pt, -width / 2), + (0pt, width / 2), + stroke: stroke + )), + end: 0pt + ) +} +``` + +Let us first explain the difference between the parameters `line` and `stroke`: +- `line` is sort of a _private_ parameter. It is set by the functions `tiptoe.path()`, `tiptoe.line()` and `tiptoe.arc()`, when the mark is realized and contains the stroke used for drawing the path/line/arc. We use it often to make the mark inherit color and thickness. +- `stroke` is totally optional for your mark, but all built-in marks have it. It allows the user of the mark to customize its stroke, overriding the stroke inherited from `line`. + +You can add an arbitrary number of other configuration parameters to your mark. + +The module `tiptoe.utility` provides two very useful helpers. The function `process-stroke()` takes the `line` and `stroke` parameter and returns a merged stroke to be used for drawing the mark. The merging obeys the following rules +- Only `thickness` and `paint` are inherited from `line`. +- Thickness and paint are both (independently) only inherited if they are set to `auto` in `stroke`. This makes it for example possible to configure only the color of a mark without changing the thickness. +- If `stroke` is `none`, nothing is inherited and `none` is returned. +- The merged stroke is guaranteed to have a `thickness` that is not `auto`. + + + + + +The other helper function `process-dims()` is useful for processing length and/or width of the mark which may be (see the [section about sizing](#sizing)) a `ratio` (in terms of the line thickness), a `length`, or a combination thereof. It takes the `line` and optional `width` and `length` parameters and returns a dictionary with the evaluated `width` and `length` (if they were given). In addition, it can process a width set to `auto` in terms of the length with a coefficient that can be specified with the parameter `default-ratio`. As an example, the `stealth` mark has an automatic width by default which is then 80% of the length. + +Hints for rendering the mark: +- You only need to take care of the case where the mark is used as a `tip`. The `toe` case is handled automatically. +- The path goes from left to right and ends at `(0pt, 0pt)`. The stealth arrow, e.g., points exactly to this coordinate. +- The rotation of the mark is fully handled by tiptoe. + +## Arc + +Many have noted that (as of now) Typst does not feature a function to draw arcs. This is sometimes unfortunate since circular arcs are not at all trivial to approximate with Bézier curves. + +Until a built-in arc function makes it into the core of Typst, enjoy this one: + +```typ +#let arc( + origin: (0pt, 0pt), // Origin coordinates + angle: 0deg, // Start angle + arc: 45deg, // Arc angle + radius: 1cm, // The radius of the full circle + width: auto, // The width of the full ellipse + height: auto, // The height of the full ellipse + stroke: 1pt + black, + fill: none, + closed: false, // false, "segment" or "sector" + tip: none, // Mark placed at the start + toe: none, // Mark placed at the toe + shorten: 100% // Path shortening +) +``` + + + +

+ + + + Usage of the arc() primitive + +

+ + + + + + + +## Difference between built-in and tiptoe `curve` + + +While the built-in [`curve`][typst-curve] function returns a block-level element with a size that measures from `(0pt, 0pt)` to the largest (positive) coordinate, the corresponding tiptoe function returns placed content (with zero-width and -height). + +The reasons are +- It is hard to measure the bounding box properly including the marks. +- The behavior of the built-in functions is not particularly useful since they measure only in the positive direction. I suspect that most packages using the drawing primitives wrap them with `place()` anyway. + +Currently, there are some additional limitations that might be fixed in a future release. +- Values of type `relative` or `ratio` are not supported as coordinates in curve elements (to be precise, the `ratio` part is dismissed as of now). +- When using coordinates with `relative: true`, some edge cases might not work as expected. +- Tips are not supported on a `curve.close` element. +- Toes don't work when the curve starts with multiple subsequent `curve.move` elements. You should just merge them into one. + +## Changelog + +### 0.3.1 +- Fixed tiptoe for right-to-left text flow. +- Internally changed the implementation of `arc` to use `curve` instead of `path`. Consequently, the default of `arc.shorten` is now `100%` which looks better with the new implementation. + +### 0.3.0 +- Added `curve` function in analogy to `std.curve`. + +### 0.2.0 +- Added support for `relative` inputs for `line`. + +### 0.1.0 +Initial release + + +[fletcher]: https://github.com/Jollywatt/typst-fletcher +[cetz]: https://github.com/cetz-package/cetz +[jollywatt]: https://github.com/Jollywatt/ +[typst-curve]: https://typst.app/docs/reference/visualize/curve/ +[typst]: https://typst.app diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/arc-impl.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/arc-impl.typ new file mode 100644 index 0000000..65dc8a0 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/arc-impl.typ @@ -0,0 +1,125 @@ +#let sub(p, q) = p.zip(q).map(((a, b)) => a - b) +#import "assert.typ": assert-dict-keys + +#let bezier-arc( + origin: (0pt, 0pt), + angle: 0deg, + arc: 45deg, + radius: 1cm, + width: auto, + height: auto +) = { + + if arc == 0deg { return () } + if width == auto { width = 2 * radius } + if height == auto { height = 2 * radius } + if calc.abs(arc) > 360deg { arc = 360deg } + let num-curves = int(calc.ceil(calc.abs(arc/90deg))) + let a = arc / num-curves + + let y0 = calc.sin(a / 2) + let x0 = calc.cos(a / 2) + let tx = (1 - x0) * 4/3 + let ty = y0 - tx*x0 / (y0 + 0.0001) + let px = (x0, x0 + tx, x0 + tx, x0) + let py = (-y0, -ty, ty, y0) + + let sn = calc.sin(angle + a / 2) + let cs = calc.cos(angle + a / 2) + let points = () + points.push(( + origin.at(0) + 0.5 * width * (px.at(0)*cs - py.at(0)*sn), + origin.at(1) + 0.5 * height * (px.at(0)*sn + py.at(0)*cs) + )) + + for i in range(num-curves) { + let astart = angle + a * i + let sn = calc.sin(astart + a / 2) + let cs = calc.cos(astart + a / 2) + for j in range(1, 4) { + points.push(( + origin.at(0) + 0.5 * width * (px.at(j)*cs - py.at(j)*sn), + origin.at(1) + 0.5 * height * (px.at(j)*sn + py.at(j)*cs) + )) + } + } + let coords = ( + (points.at(0), (0pt, 0pt), sub(points.at(1), points.at(0))), ..points.slice(1).chunks(3).map( + ((a, b, c)) => { + (c, sub(b, c)) + } + )) + coords.last().push((0pt, 0pt)) + coords +} + +#let bezier-arc2( + origin: (0pt, 0pt), + angle: 0deg, + arc: 45deg, + radius: 1cm, + width: auto, + height: auto +) = { + + if arc == 0deg { return () } + if width == auto { width = 2 * radius } + if height == auto { height = 2 * radius } + if calc.abs(arc) > 360deg { arc = 360deg } + let num-curves = int(calc.ceil(calc.abs(arc/90deg))) + let a = arc / num-curves + + let y0 = calc.sin(a / 2) + let x0 = calc.cos(a / 2) + let tx = (1 - x0) * 4/3 + let ty = y0 - tx*x0 / (y0 + 0.0001) + let px = (x0, x0 + tx, x0 + tx, x0) + let py = (-y0, -ty, ty, y0) + + let sn = calc.sin(angle + a / 2) + let cs = calc.cos(angle + a / 2) + let points = () + points.push(( + origin.at(0) + 0.5 * width * (px.at(0)*cs - py.at(0)*sn), + origin.at(1) + 0.5 * height * (px.at(0)*sn + py.at(0)*cs) + )) + + for i in range(num-curves) { + let astart = angle + a * i + let sn = calc.sin(astart + a / 2) + let cs = calc.cos(astart + a / 2) + for j in range(1, 4) { + points.push(( + origin.at(0) + 0.5 * width * (px.at(j)*cs - py.at(j)*sn), + origin.at(1) + 0.5 * height * (px.at(j)*sn + py.at(j)*cs) + )) + } + } + let coords = ( + curve.move(points.at(0)), + ..points.slice(1).chunks(3).map(x => curve.cubic(..x, relative: false)) + ) + + coords +} + + +#let arc-impl( + origin: (0pt, 0pt), + angle: 0deg, + arc: 6rad, + radius: 1cm, + stroke: 1pt + black +) = { + curve( + stroke: stroke, + ..bezier-arc2( + origin: origin, + angle: angle, + arc: arc, + radius: radius + ) + ) +} + + diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/arc.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/arc.typ new file mode 100644 index 0000000..83a9c26 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/arc.typ @@ -0,0 +1,46 @@ +#import "arc-impl.typ": bezier-arc2 +#import "curve.typ": curve + +#let arc( + origin: (0pt, 0pt), + angle: 0deg, + arc: 45deg, + radius: 1cm, + width: auto, + height: auto, + stroke: 1pt + black, + fill: none, + closed: false, + tip: none, + toe: none, + shorten: 100% +) = { + let coords = bezier-arc2( + origin: origin, + angle: angle, + arc: arc, + radius: radius, + width: width, + height: height, + ) + + if closed == "sector" { + coords.push(std.curve.line(origin)) + } + if closed in ("sector", "segment") { + coords.push(std.curve.close(mode: "straight")) + closed = true + } else if closed == true { + coords.push(std.curve.close()) + } + + curve( + stroke: stroke, + tip: tip, + toe: toe, + shorten: shorten, + fill: fill, + ..coords + ) + +} \ No newline at end of file diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/assert.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/assert.typ new file mode 100644 index 0000000..c56fa41 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/assert.typ @@ -0,0 +1,20 @@ +#let assert-mark(mark, kind: "tip") = { + assert(type(mark) == function, message: "Invalid arrow " + kind + ", expected a function but got a " + str(type(mark)) + ". Ensure that you use e.g. `stealth.with(..)` instead of directly writing `stealth(..)`") +} + +#let assert-dict-keys(dict, required: (), optional: ()) = { + let keys = dict.keys() + + for key in required { + if key not in keys { + assert(false, message: "") + } + } + let possible-keys = required + optional.dict + for key in keys { + if key not in possible-keys { + assert(false, message: "Unexpected key \"" + key + "\"") + } + } + assert(array) +} \ No newline at end of file diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/curve.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/curve.typ new file mode 100644 index 0000000..c4b0acd --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/curve.typ @@ -0,0 +1,429 @@ +#import "marks.typ": * +#import "assert.typ": assert-mark +#import "path-to-curve.typ": path-to-curve + + +#let first-not-none-or-auto(..args) = args.pos().find(x => x != none and x != auto) + +#let add(p, q) = p.zip(q).map(array.sum) +#let sub(p, q) = p.zip(q).map(((a, b)) => a - b) +#let add-if-array(p, q) = if type(p) == array { add(p, q) } else { p } + +// Mirrors control around origin +#let mirror(control, origin) = add(origin, sub(origin, control)) + + + +// When encountering a final curve element with an `auto` control, +// we can use this function to compute this control based on the +// previous curve element. +#let get-prev-mirrored-control(segments) = { + let p = segments.at(-2, default: std.curve.move((0pt, 0pt))) + if p.func() == std.curve.close { p = std.curve.move((0pt, 0pt)) } + + if p.func() == std.curve.move { p.start } + else if p.func() == std.curve.line { p.end } + else if p.func() == std.curve.quad { + if p.control == none {p.end} + else { mirror(p.control, p.end) } + } + else if p.func() == std.curve.cubic { + if p.control-end == none {p.end} + else { mirror(p.control-end, p.end) } + } +} + +// Test whether a curve segment is relative (either explicitly or by a set rule) +#let is-relative(segment) = { + if segment.has("relative") { return segment.relative } + return segment.func().relative +} + + +#let curve-to-absolute-polygon(segments) = { + let base = (0pt, 0pt) + let index-last-absolute-segment = -1 + + for i in range(1, segments.len() + 1) { + let index = segments.len() - i + let segment = segments.at(index) + + if segment.func() == std.curve.move { + index-last-absolute-segment = index + base = segment.start + break + } else if not is-relative(segment) { + index-last-absolute-segment = index + base = segment.end + break + } + } + let polygon = (base, ) + + for (i, segment) in segments.enumerate().slice(index-last-absolute-segment + 1) { + if segment.func() == std.curve.line { + base = add(base, segment.end) + polygon.push(base) + } else if segment.func() == std.curve.quad { + if i == segments.len() - 1 { + polygon.push(add(base, segment.control)) + } + base = add(base, segment.end) + polygon.push(base) + } else if segment.func() == std.curve.cubic { + if i == segments.len() - 1 { + polygon.push(add(base, segment.control-start)) + polygon.push(add(base, segment.control-end)) + } + base = add(base, segment.end) + polygon.push(base) + } + + } + polygon +} + + +#assert.eq(curve-to-absolute-polygon(( + std.curve.move((10pt, 10pt)), + std.curve.line((10pt, 10pt), relative: true), + )), + ((10pt, 10pt), (20pt, 20pt)) +) +#assert.eq(curve-to-absolute-polygon(( + std.curve.move((10pt, 10pt)), + std.curve.line((0pt, 10pt), relative: true), + std.curve.line((10pt, 0pt), relative: true), + )), + ((10pt, 10pt), (10pt, 20pt), (20pt, 20pt)) +) +#assert.eq(curve-to-absolute-polygon(( + std.curve.move((10pt, 10pt)), + std.curve.quad((0pt, 10pt), (10pt, 10pt), relative: true), + std.curve.line((10pt, 0pt), relative: true), + )), + ((10pt, 10pt), (20pt, 20pt), (30pt, 20pt)) +) + +#let resolve-relative(segments) = { + let base = (0pt, 0pt) + for segment in segments.rev() { + if segment.func() == std.curve.move { + base = add(base, segment.start) + break + } else { + base = add(base, segment.end) + if not is-relative(segment) { + break + } + } + } + let prev + if segments.last().func() == std.curve.move { + prev = sub(base, segments.last().start) + } else { + prev = sub(base, segments.last().end) + } + (prev, base) +} + + +#assert.eq(resolve-relative(( + std.curve.move((10pt, 10pt)), + std.curve.line((10pt, 10pt), relative: true), + )), + ((10pt, 10pt), (20pt, 20pt)) +) +#assert.eq(resolve-relative(( + std.curve.move((10pt, 10pt)), + std.curve.line((0pt, 10pt), relative: true), + std.curve.line((10pt, 0pt), relative: true), + )), + ((10pt, 20pt), (20pt, 20pt)) +) +#assert.eq(resolve-relative(( + std.curve.move((10pt, 10pt)), + std.curve.line((0pt, 10pt), relative: true), + std.curve.line((0pt, 10pt), relative: true), + std.curve.line((0pt, 10pt), relative: false), + std.curve.line((10pt, 0pt), relative: true), + )), + ((0pt, 10pt), (10pt, 10pt)) +) + +#assert.eq(resolve-relative(( + std.curve.move((10pt, 10pt)), + std.curve.line((10pt, 10pt), relative: true), + )), + ((10pt, 10pt), (20pt, 20pt)) +) + + +#let treat-tip(mark, segments, inner, shorten: 100%) = { + let final-segment = segments.last() + let args = (:) + let end = final-segment.end + + if is-relative(final-segment) { + if final-segment.has("relative") { + args.relative = true + } + (inner, end) = resolve-relative(segments) + + (.., inner, end) = curve-to-absolute-polygon(segments) + } + + let dx = (end.at(0) - inner.at(0)).length.to-absolute() / 1pt + let dy = (end.at(1) - inner.at(1)).length.to-absolute() / 1pt + let angle = calc.atan2(dx, dy) + let mark-content = place( + dx: end.at(0), dy: end.at(1), + rotate(angle, mark.mark) + ) + end.at(0) -= calc.cos(angle) * mark.end * shorten + end.at(1) -= calc.sin(angle) * mark.end * shorten + (mark-content, end, args) +} + + + +#let treat-toe(mark, vertex0, vertex1, shorten: 100%) = { + let dx = (vertex0.at(0) - vertex1.at(0)).length.to-absolute() / 1pt + let dy = (vertex0.at(1) - vertex1.at(1)).length.to-absolute() / 1pt + let angle = calc.atan2(dx, dy) + + let mark-content = place( + dx: vertex0.at(0), dy: vertex0.at(1), + rotate(angle, mark.mark) + ) + + vertex0.at(0) -= calc.cos(angle) * mark.end * shorten + vertex0.at(1) -= calc.sin(angle) * mark.end * shorten + + (mark-content, vertex0) +} + + + + +#let curve( + ..segments, + fill: none, + fill-rule: auto, + stroke: 1pt, + tip: none, + toe: none, + shorten: 100%, +) = { + if segments.named().len() != 0 { + assert(false, message: "Unexpected named argument \"" + segments.named().keys().first() + "\"") + } + + set place(left) + + stroke = std.stroke(stroke) + + assert( + type(shorten) in (ratio, dictionary), + message: "Expected ratio or dictionary for parameter `shorten`, found " + str(type(shorten)) + ) + if type(shorten) == ratio { + shorten = (start: shorten, end: shorten) + } else if type(shorten) == dictionary { + assert( + shorten.keys().sorted() == ("end", "start"), + message: "Unexpected key, valid keys are \"start\" and \"end\"" + ) + } + + + context { + let segments = segments.pos() + let marks + + if toe != none and segments.len() >= 1 { + + assert-mark(toe, kind: "toe") + let toe = toe(line: stroke) + let first-segment = segments.first() + + if first-segment.func() == std.curve.move and segments.len() >= 2 { + + let vertex0 = first-segment.start + let relative = is-relative(segments.at(1)) + + // Obtain next vertex: either an end point or the next control point + let vertex1 = { + let se = segments.at(1) + if se.func() == std.curve.line { + if relative { + // We will change the first curve.move, so this second segment cannot be relative + segments.at(1) = std.curve.line(add(vertex0, se.end), relative: false) + } + se.end + } else if se.func() == std.curve.quad { + if relative { + // We will change the first curve.move, so this second segment cannot be relative + segments.at(1) = std.curve.quad( + add-if-array(se.control, vertex0), + add(se.end, vertex0), + relative: false + ) + } + first-not-none-or-auto(se.control, se.end) + } else if se.func() == std.curve.cubic { + if relative { + // We will change the first curve.move, so this second segment cannot be relative + segments.at(1) = std.curve.cubic( + add-if-array(se.control-start, vertex0), + add-if-array(se.control-end, vertex0), + add(se.end, vertex0), + relative: false + ) + } + first-not-none-or-auto(se.control-start, se.control-end, se.end) + } + } + + if relative { + vertex1 = add(vertex0, vertex1) + } + + let (mark, new-vertex0) = treat-toe( + toe, vertex0, vertex1, + shorten: shorten.start + ) + marks += mark + segments.first() = std.curve.move(new-vertex0) + + } else if first-segment.func() == std.curve.line { + + let (mark, new-vertex0) = treat-toe( + toe, (0pt, 0pt), first-segment.end, + shorten: shorten.start + ) + marks += mark + segments.first() = std.curve.line(first-segment.end, relative: false) + segments.insert(0, std.curve.move(new-vertex0)) + + } else if first-segment.func() == std.curve.quad { + + let vertex1 = first-not-none-or-auto(first-segment.control, first-segment.end) + let (mark, new-vertex0) = treat-toe( + toe, (0pt, 0pt), vertex1, + shorten: shorten.start + ) + marks += mark + segments.first() = std.curve.quad( + first-segment.control, + first-segment.end, + relative: false + ) + segments.insert(0, std.curve.move(new-vertex0)) + + } else if first-segment.func() == std.curve.cubic { + + let vertex1 = first-not-none-or-auto( + first-segment.control-start, first-segment.control-end, first-segment.end + ) + let (mark, new-vertex0) = treat-toe( + toe, (0pt, 0pt), vertex1, + shorten: shorten.start + ) + marks += mark + segments.first() = std.curve.cubic( + first-segment.control-start, + first-segment.control-end, + first-segment.end, + relative: false + ) + segments.insert(0, std.curve.move(new-vertex0)) + + } + + } + + + + if tip != none and segments.len() >= 1 { + + assert-mark(tip, kind: "tip") + let tip = tip(line: stroke) + let final-segment = segments.last() + + // Obtain previous vertex: either an end point or the last control point + let vertex-n-1 = { + let p = segments.at(-2, default: std.curve.move((0pt, 0pt))) + if p.func() == std.curve.close { p = std.curve.move((0pt, 0pt)) } + if p.func() == std.curve.move { p.start } + else { p.end } + } + + if final-segment.func() == std.curve.close { + assert(false, message: "Tips are not supported on the `curve.close` element") + + } else if final-segment.func() == std.curve.line { + + let (mark, new-vertex-n, args) = treat-tip( + tip, + segments, vertex-n-1, + shorten: shorten.end + ) + marks += mark + segments.last() = std.curve.line(new-vertex-n, relative: false) + + } else if final-segment.func() == std.curve.quad { + + if final-segment.control != none { + if final-segment.control == auto { + vertex-n-1 = get-prev-mirrored-control(segments) + } else { + vertex-n-1 = final-segment.control + } + } + + let (mark, new-vertex-n, args) = treat-tip( + tip, + segments, vertex-n-1, + shorten: shorten.end + ) + + marks += mark + segments.last() = std.curve.quad( + final-segment.control, new-vertex-n, relative: false + ) + + } else if final-segment.func() == std.curve.cubic { + + if final-segment.control-end != none { + vertex-n-1 = final-segment.control-end + } else if final-segment.control-start == auto { + vertex-n-1 = get-prev-mirrored-control(segments) + } else if final-segment.control-start != none { + vertex-n-1 = final-segment.control-start + } + + let (mark, new-vertex-n, args) = treat-tip( + tip, + segments, vertex-n-1, + shorten: shorten.end + ) + + marks += mark + segments.last() = std.curve.cubic( + final-segment.control-start, + final-segment.control-end, + new-vertex-n, relative: false + ) + + } + } + + place(std.curve( + ..segments, + stroke: stroke, + fill: fill, + fill-rule: utility.if-auto(fill-rule, std.curve.fill-rule) + )) + marks + } +} diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/line.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/line.typ new file mode 100644 index 0000000..1d89c66 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/line.typ @@ -0,0 +1,71 @@ +#import "marks.typ": * +#import "assert.typ": assert-mark + +#let line( + start: (0pt, 0pt), + end: none, + length: 30pt, + angle: 0deg, + stroke: 1pt, + tip: none, + toe: none +) = { + stroke = std.stroke(stroke) + if tip != none { + assert-mark(tip, kind: "tip") + tip = tip(line: stroke) + } + if toe != none { + assert-mark(toe, kind: "toe") + toe = toe(line: stroke) + } + set place(left) + + context box({ + let end = end + let start = start + let angle = angle + let length = length + if end == none { + end = start.zip((length*calc.cos(angle), length*calc.sin(angle))).map(array.sum) + if type(length) == ratio { + assert(angle in (0deg, 90deg), message: "When `length` is a ratio, the angle can only be 0deg or 90deg, found " + repr(angle)) + } else if length.to-absolute() < 0pt { + length *= -1 + angle += 180deg + } + } else { + let dx = (end.at(0) - start.at(0)).to-absolute() / 1pt + let dy = (end.at(1) - start.at(1)).to-absolute() / 1pt + angle = calc.atan2(dx, dy) + length = 1pt * calc.sqrt(dx*dx + dy*dy) + } + let original-line = std.line(start: start, angle: angle, length: length, stroke: stroke) + + let toe-pos = start + if toe != none { + start.at(0) += calc.cos(angle) * toe.end + start.at(1) += calc.sin(angle) * toe.end + length -= toe.end + } + if tip != none { + length -= tip.end + } + + place(std.line(start: start, angle: angle, length: length, stroke: stroke)) + + if tip != none { + place( + dx: end.at(0), dy: end.at(1), + rotate(angle, tip.mark) + ) + } + if toe != none { + place( + dx: toe-pos.at(0), dy: toe-pos.at(1), + rotate(angle, scale(x: -100%, toe.mark)) + ) + } + hide(original-line) + }) +} diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/marks.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/marks.typ new file mode 100644 index 0000000..fe34e4a --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/marks.typ @@ -0,0 +1,547 @@ +#import "arc-impl.typ": arc-impl +#import "utility.typ" + + + +#let bar( + width: 2.4pt + 360%, + stroke: auto, + align: center, + line: stroke() +) = { + stroke = utility.process-stroke(line, stroke) + let (width,) = utility.process-dims( + line, width: width, + ) + + assert(align in (center, end)) + let offset = if align == end { stroke.thickness / 2 } else { 0pt } + + ( + mark: place(std.line( + start: (-offset, -width / 2), + end: (-offset, width / 2), + stroke: stroke + )), + end: offset + ) +} + + +#let bracket( + width: 2.4pt + 360%, + length: auto, + stroke: auto, + rev: false, + line: stroke() +) = { + stroke = utility.process-stroke(line, stroke) + let (width, length) = utility.process-dims( + line, length: width, width: length, + default-ratio: .3 + ) + (width, length) = (length, width) + let s = stroke.thickness / 2 + let y = width / 2 - s + let mark = place(curve( + curve.move((-length, -y)), + curve.line((-s, -y), relative: false), + curve.line((-s, y), relative: false), + curve.line((-length, y), relative: false), + stroke: stroke + )) + if rev { + mark = scale(x: -100%, place(mark, dx: length)) + } + ( + mark: mark, + end: if rev { length } else { s } + ) +} + + +#let stealth( + length: 3pt + 450%, + width: auto, + inset: 40%, + fill: auto, + rev: false, + stroke: auto, + line: stroke() +) = { + let is-auto-stroke = stroke == auto + stroke = utility.process-stroke(line, stroke) + let (width, length) = utility.process-dims( + line, length: length, width: width, + default-ratio: .8 + ) + + let linewidth = stroke.thickness + let Δl = length * inset + let dhalf = 0.5 * width + let tip-length + + let path = if fill == auto and is-auto-stroke { + // very common optimization for filled arrows + polygon( + (0pt, 0pt), + (-length, dhalf), + (-length + Δl, 0pt), + (-length, -dhalf), + stroke: none, + fill: utility.if-auto(stroke.paint, black) + ) + tip-length = length / 2 + } else { + let tanα = dhalf / length + let α = calc.atan(tanα) + let sinα  = calc.sin(α) + let x3 = 0.5 * linewidth / sinα + + let (x, x4, y) = if inset == 0% { + let x = length - linewidth/2 + (x, x, tanα * (x - x3)) + } else { + let tanβ = dhalf / Δl + let β = calc.atan(tanβ) + let x1 = 0.5 * linewidth / calc.sin(β) + let x2 = length - Δl - x1 - x3 + let b = -x2 * tanα + let x = b / (tanβ - tanα) + let y = tanβ * x + let x4 = length - Δl - x1 + (length - Δl - x1 - x, x4, y) + } + tip-length = x3 + polygon( + (-x3, 0pt), + (-x, y), + (-x4, 0pt), + (-x, -y), + stroke: std.stroke( + thickness: linewidth, + paint: utility.if-auto(stroke.paint, black), + dash: stroke.dash, + miter-limit: 7, + join: "miter" + ), + fill: utility.chained-if-auto(fill, stroke.paint, black) + ) + } + + + let mark = place(path) + if rev { + mark = scale(x: -100%, place(mark, dx: length)) + } + ( + mark: mark, + end: if rev { length - tip-length } else { length - Δl} + ) +} + + +#let triangle = stealth.with(inset: 0%) + +#let round( + length: 3pt + 450%, + width: auto, + inset: 40%, + rev: false, + stroke: auto, + fill: auto, + line: stroke() +) = { + stroke = utility.process-stroke(line, stroke) + let (width, length) = utility.process-dims( + line, length: length, width: width, + default-ratio: .8 + ) + + let s = 0.5 * stroke.thickness + + if inset >= 100% { + inset = length - stroke.thickness + } else { + inset = length * inset + } + + let mark = place(polygon( + (-s, 0pt), + (-length + s, 0.5 * width - s), + (-length + inset + s, 0pt), + (-length + s, -0.5 * width + s), + fill: utility.chained-if-auto(fill, stroke.paint, black), + stroke: std.stroke( + thickness: stroke.thickness, + paint: utility.if-auto(stroke.paint, black), + dash: stroke.dash, + miter-limit: 7, + join: "round" + ) + )) + + let end = length - inset - s + + if rev { + mark = scale(x: -100%, place(mark, dx: length)) + end = length - s + } + + ( + mark: mark, + end: end + ) +} + + +#let straight( + length: 3pt + 450%, + width: auto, + rev: false, + stroke: auto, + line: stroke() +) = { + stroke = utility.process-stroke(line, stroke) + let (width, length) = utility.process-dims( + line, length: length, width: width, + default-ratio: .8 + ) + + let s = stroke.thickness / 2 + let α = calc.atan(0.5 * width / length) + let tip-length = 0.5 * stroke.thickness / calc.sin(α) + + let mark = place(curve( + curve.move((-length + s, 0.5 * width - s)), + curve.line((-tip-length, 0pt), relative: false), + curve.line((-length + s, -0.5 * width + s), relative: false), + stroke: std.stroke( + thickness: stroke.thickness, + paint: utility.if-auto(stroke.paint, black), + dash: stroke.dash, + miter-limit: 7, + cap: "round" + ) + )) + + + if rev { + mark = scale(x: -100%, place(mark, dx: length)) + } + + ( + mark: mark, + end: if rev { length - tip-length } else { tip-length } + ) +} + +#let diamond( + length: 565.69%, + width: auto, + fill: auto, + stroke: auto, + align: center, + line: stroke() +) = { + stroke = utility.process-stroke(line, stroke) + let (width, length) = utility.process-dims( + line, length: length, width: width, + default-ratio: 1 + ) + let dhalf = 0.5 * width + let tip-length + + let tanα = dhalf / length * 2 + let α = calc.atan(tanα) + let tip-length = 0.5 * stroke.thickness / calc.sin(α) + let top-length = 0.5 * stroke.thickness / calc.cos(α) + + assert(align in (center, end)) + let offset = if align == center { length / 2 } else { 0pt } + + let mark = place(dx: offset, polygon( + (-length / 2, dhalf - top-length), + (-tip-length, 0pt), + (-length / 2, -dhalf + top-length), + (-length + tip-length, 0pt), + stroke: stroke, + fill: utility.chained-if-auto(fill, stroke.paint, black) + )) + + ( + mark: mark, + end: length - tip-length - offset + ) +} + +#let square( + length: 400%, + width: auto, + fill: auto, + stroke: auto, + align: center, + line: stroke() +) = { + stroke = utility.process-stroke(line, stroke) + let (width, length) = utility.process-dims( + line, length: length, width: width, + default-ratio: 1 + ) + assert(align in (center, end)) + let offset = if align == center { length / 2 } else { 0pt } + let s = stroke.thickness / 2 + let y = width/2 - s + let mark = place(dx: offset, + polygon( + (-s, y), + (-length + s, y), + (-length + s, -y), + (-s, -y), + stroke: stroke, + fill: utility.chained-if-auto(fill, stroke.paint, black), + ) + ) + + ( + mark: mark, + end: length - s - offset + ) +} + + + +#let circle( + length: 400%, + width: auto, + fill: auto, + stroke: auto, + align: center, + line: stroke() +) = { + stroke = utility.process-stroke(line, stroke) + let (width, length) = utility.process-dims( + line, length: length, width: width, + default-ratio: 1 + ) + let s = stroke.thickness / 2 + let y = width / 2 - s + + assert(align in (center, end)) + let offset = if align == center { length / 2 } else { 0pt } + + let mark = place(dx: -length + s + offset, dy: -y, + ellipse( + width: length - 2 * s, + height: width - 2 * s, + stroke: stroke, + fill: utility.chained-if-auto(fill, stroke.paint, black), + ) + ) + + ( + mark: mark, + end: length - s - offset + ) +} + + +#let rays( + n: 4, + length: 280%, + phase: auto, + stroke: auto, + align: center, + line: stroke() +) = { + stroke = utility.process-stroke(line, stroke) + let (length,) = utility.process-dims( + line, length: length + ) + if phase == auto { + if n == 4 { phase = 45deg } + else { phase = -90deg } + } + + assert(align in (center, end)) + let offset = if align == end { -length } else { 0pt } + + let mark = for i in range(n) { + place(dx: offset, std.line(length: length, angle: phase + 360deg*i/n, stroke: stroke)) + } + + ( + mark: mark, + end: -offset + ) +} + + + +#let barb( + width: 3pt + 450%, + arc: 180deg, + stroke: auto, + rev: false, + line: stroke(), +) = { + stroke = utility.process-stroke(line, stroke) + let (width,) = utility.process-dims( + line, width: width + ) + + let s = stroke.thickness / 2 + let radius = width / 2 - s + + let mark = place( + arc-impl( + origin: (-width / 2, 0pt), + angle: -arc / 2, + arc: arc, + radius: radius, + stroke: stroke + ) + ) + let end = s + + if rev { + mark = scale(x: -100%, place(mark, dx: width / 2)) + end = radius + } + + ( + mark: mark, + end: end + ) +} + + +#let hooks( + width: 3pt + 450%, + arc: 180deg, + stroke: auto, + rev: false, + line: stroke(), +) = { + stroke = utility.process-stroke(line, stroke) + let (width,) = utility.process-dims( + line, width: width + ) + + let s = stroke.thickness / 2 + let r = (width / 2 - s) / 2 + + let mark = { + place( + arc-impl( + origin: (-r - s, r), + angle: -90deg, + arc: arc, + radius: r, + stroke: stroke + ) + ) + place( + arc-impl( + origin: (-r - s, -r), + angle: 90deg, + arc: -arc, + radius: r, + stroke: stroke + ) + ) + } + let end = r - s + + if rev { + mark = scale(x: -100%, place(mark, dx: r + s)) + end = 0pt + } + + ( + mark: mark, + end: end + s + ) +} + + + +#let tikz( + width: 3pt + 450%, + arc: 180deg, + stroke: auto, + line: stroke(), +) = { + let sharp = false + stroke = utility.process-stroke(line, stroke) + let (width,) = utility.process-dims( + line, width: width + ) + + let s = stroke.thickness / 2 + stroke = if sharp { + (thickness: stroke.thickness, paint: stroke.paint, join: "bevel", cap: "butt") + } else { + (thickness: stroke.thickness, paint: stroke.paint, join: "round", cap: "round") + } + let x0 = if sharp { stroke.thickness/width*10pt } else { -s } + let mark = place( + curve( + curve.move((-width * 0.42, -width / 2 + s)), + curve.cubic( + (-width * 0.32, -width * 0.1 + s), + none, + (x0, 0pt), + relative: false + ), + curve.cubic( + none, + (-width * 0.32, width * 0.1 - s), + (-width * 0.42, width / 2 - s), + relative: false + ), + stroke: stroke + ) + ) + + ( + mark: mark, + end: stroke.thickness + ) +} + + +#let combine( + line: stroke(), + ..marks +) = (line: stroke()) => { + let marks = marks.pos() + let linewidth = utility.if-auto(line.thickness, 1pt) + + let combined-mark + let pos = 0pt + let end + + for mark in marks { + if mark == std.end { + end = pos + } else if type(mark) == length { + pos += mark + } else if type(mark) == ratio { + pos += mark * linewidth + } else if type(mark) == function { + mark = mark(line: line) + combined-mark += place(dx: -pos, mark.mark) + pos += mark.end + } + } + if end == none { + end = pos + } + + ( + mark: combined-mark, + end: end + ) +} + diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/path-to-curve.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/path-to-curve.typ new file mode 100644 index 0000000..1cc0c52 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/path-to-curve.typ @@ -0,0 +1,80 @@ + + + +#let transform(a, b, mapper) = array.zip(a, b, exact: true).map(mapper) +#let add(a, b) = transform(a, b, ((x,y)) => x + y) +#let subtract(a, b) = transform(a, b, ((x,y)) => x - y) +#let multiply(a, c) = a.map(x => x * c) + +#let path-to-curve( + ..vertices, + stroke: stroke(), + fill: none, + fill-rule: "even-odd", + closed: false +) = { + // place(std.path(..vertices, stroke: stroke, fill: fill, closed: closed)) + vertices = vertices.pos() + if vertices.len() == 0 { return } + + let is-vertex(v) = (type(v) == array and type(v.first()) != array) or v.len() == 1 + let extract-vertex(v) = { + if is-vertex(v) { + if v.len() == 2 {v} + else { v.first() } + } else { v.first() } + } + + let curve-elements = () + let start-in = none + let out = none + for vertex in vertices { + let v = extract-vertex(vertex) + + if is-vertex(vertex) { + if out == none { + curve-elements.push((v,)) + } else { + curve-elements.push((out, none, v)) + out = none + } + } else { + if vertex.len() == 2 { + vertex.push(multiply(vertex.at(1), -1)) + // now its definitely a "cubic"! + } + if curve-elements.len() == 0 { + curve-elements.push((v,)) + start-in = add(v, vertex.at(1)) + } else if out == none { + curve-elements.push((auto, add(v, vertex.at(1)), v)) + out = auto + } else { + curve-elements.push((out, add(v, vertex.at(1)), v)) + } + out = add(v, vertex.at(2)) + } + } + + let to-curve-element(x) = { + if x.len() == 1 { curve.line(..x) } + else if x.len() == 2 { curve.quad(..x) } + else if x.len() == 3 { curve.cubic(..x) } + } + curve-elements = curve-elements.map(to-curve-element) + let start = extract-vertex(vertices.first()) + if closed { + if out != none or start-in != none { + curve-elements.push(curve.cubic(out, start-in, start)) + } + curve-elements.push(curve.close(mode: "straight")) + } + + std.curve( + curve.move(start), + fill: fill, stroke: stroke, + ..curve-elements, + fill-rule: fill-rule + // stroke: yellow + .5pt + ) +} diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/path.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/path.typ new file mode 100644 index 0000000..f69b864 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/path.typ @@ -0,0 +1,118 @@ +#import "marks.typ": * +#import "assert.typ": assert-mark +#import "path-to-curve.typ": path-to-curve + + +#let add(p, q) = p.zip(q).map(array.sum) +#let sub(p, q) = p.zip(q).map(((a, b)) => a - b) +#let addorsub(p, q, f) = p.zip(q).map(((a, b)) => a + f*b) + +#let extract-bezier-polygon(vertex1, vertex2, rev: false) = { + let polygon = () + if type(vertex1.at(0)) == array { + let v = vertex1.at(0) + if vertex1.len() == 1 { polygon.push(v) } + else if vertex1.len() == 2 { + polygon += (v, sub(v, vertex1.at(1))).dedup() + } else if vertex1.len() == 3 { + polygon += (v, add(v, vertex1.at(2))).dedup() + } + } else { + polygon.push(vertex1) + } + if type(vertex2.at(0)) == array { + let v = vertex2.at(0) + if vertex2.len() == 1 { polygon.push(v) } + else { + polygon += (add(v, vertex2.at(1)), v).dedup() + } + } else { + polygon.push(vertex2) + } + + return polygon +} + + + +#let path( + ..args, + fill: none, + fill-rule: auto, + stroke: 1pt, + closed: false, + tip: none, + toe: none, + shorten: 100%, +) = { + if args.named().len() != 0 { + assert(false, message: "Unexpected named argument \"" + args.named().keys().first() + "\"") + } + + set place(left) + + stroke = std.stroke(stroke) + + assert( + type(shorten) in (ratio, dictionary), + message: "Expected ratio or dictionary for parameter `shorten`, found " + str(type(shorten)) + ) + if type(shorten) == ratio { + shorten = (start: shorten, end: shorten) + } else if type(shorten) == dictionary { + assert( + shorten.keys().sorted() == ("end", "start"), + message: "Unexpected key, valid keys are \"start\" and \"end\"" + ) + } + + + context { + let points = args.pos() + let original-points = points + + let treat-mark(mark, i1, i2, pos: start, shorten: 100%) = { + mark = mark(line: stroke) + + let polygon = extract-bezier-polygon(points.at(i1), points.at(i2)) + if pos == end { polygon = polygon.rev() } + let inner = polygon.at(1) + let outer = polygon.at(0) + + let dx = (outer.at(0) - inner.at(0)).to-absolute() / 1pt + let dy = (outer.at(1) - inner.at(1)).to-absolute() / 1pt + let angle = calc.atan2(dx, dy) + + let mark-content = place( + dx: outer.at(0), dy: outer.at(1), + rotate(angle, mark.mark) + ) + outer.at(0) -= calc.cos(angle) * mark.end * shorten + outer.at(1) -= calc.sin(angle) * mark.end * shorten + return (mark-content, outer) + } + + let marks + if toe != none and points.len() >= 2 { + assert-mark(toe, kind: "toe") + let (mark, end) = treat-mark(toe, 0, 1, pos: start, shorten: shorten.start) + if type(points.first().at(0)) == array { points.first().at(0) = end } + else { points.first() = end } + marks += mark + } + if tip != none and points.len() >= 2 { + assert-mark(tip, kind: "tip") + let (mark, end) = treat-mark(tip, -2, -1, pos: end, shorten: shorten.end) + if type(points.last().at(0)) == array { points.last().at(0) = end } + else { points.last() = end } + marks += mark + } + + let fill-rule = fill-rule + if fill-rule == auto { + fill-rule = std.curve.fill-rule + } + + place(path-to-curve(..points, stroke: stroke, fill: fill, closed: closed, fill-rule: fill-rule)) + marks + } +} \ No newline at end of file diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/tiptoe.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/tiptoe.typ new file mode 100644 index 0000000..2404c90 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/tiptoe.typ @@ -0,0 +1,6 @@ +#import "line.typ": line +#import "path.typ": path +#import "curve.typ": curve +#import "arc.typ": arc +#import "utility.typ" +#import "marks.typ": * \ No newline at end of file diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/utility.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/utility.typ new file mode 100644 index 0000000..5185141 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/src/utility.typ @@ -0,0 +1,67 @@ +#let if-auto(a, b) = if a == auto { b } else { a } +#let if-none(a, b) = if a == none { b } else { a } +#let chained-if-auto(..x) = x.pos().find(it => it != auto) + + + +#let extract-thickness-and-paint(stroke) = { + if-auto(stroke.thickness, 1pt) + if-auto(stroke.paint, black) +} + +#let inherit-thickness-and-paint(stroke, parent: 2) = { + if stroke == none { return none } // this is always stronger + + // strokes might be length, color or dictionary: + let parent = std.stroke(parent) + let stroke = std.stroke(stroke) + return std.stroke( + paint: if-auto(stroke.paint, parent.paint), + thickness: chained-if-auto(stroke.thickness, parent.thickness, 1pt), + cap: stroke.cap, + join: stroke.join, + dash: stroke.dash, + miter-limit: stroke.miter-limit + ) +} + + +#let process-stroke(line, stroke) = { + if stroke == auto { + return extract-thickness-and-paint(line) + } + return inherit-thickness-and-paint(stroke, parent: line) +} + + +#let process-dims( + line, + length: none, + width: none, + default-ratio: .8 +) = { + let result = (:) + let linewidth = if-auto(line.thickness, 1pt) + + if length != none { + result.length = if type(length) == ratio { + linewidth * length + } else if type(length) == relative { + length.length + linewidth * length.ratio + } else { + length + } + } + + if width != none { + result.width = if width == auto { + result.length * default-ratio + } else if type(width) == ratio { + linewidth * width + } else if type(width) == relative { + width.length + linewidth * width.ratio + } else { + width + } + } + return result +} \ No newline at end of file diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/typst.toml b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/typst.toml new file mode 100644 index 0000000..e3cdd06 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.1/typst.toml @@ -0,0 +1,13 @@ +[package] +name = "tiptoe" +version = "0.3.1" +entrypoint = "src/tiptoe.typ" +authors = ["Mc-Zen "] +license = "MIT" +description = "Arrows and other marks for lines and paths." +compiler = "0.13.0" + +repository = "https://github.com/Mc-Zen/tiptoe" +keywords = ["arrows", "marks", "primitives", "line", "path", "tip", "arc"] +categories = ["visualization", "layout"] +exclude = ["/docs/*", "/tests/*"] \ No newline at end of file diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/LICENSE.txt b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/LICENSE.txt new file mode 100644 index 0000000..5e8cd93 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024-2025 Mc-Zen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/README.md b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/README.md new file mode 100644 index 0000000..451e137 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/README.md @@ -0,0 +1,341 @@ +

+ + + + Logo + +

+ +_Arrows for [Typst][typst] paths and other stories._ + +[![Typst Package](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FMc-Zen%2Ftiptoe%2Fv0.3.2%2Ftypst.toml&query=%24.package.version&prefix=v&logo=typst&label=package&color=239DAD)](https://typst.app/universe/package/tiptoe) +[![Test Status](https://github.com/Mc-Zen/tiptoe/actions/workflows/run_tests.yml/badge.svg)](https://github.com/Mc-Zen/tiptoe/actions/workflows/run_tests.yml) +[![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/Mc-Zen/tiptoe/blob/main/LICENSE) + +--- + +*Tiptoe* adds configurable arrow tips (and toes) to the functions `line()` and `curve()`, and `path()`. Moreover, it adds the geometric primitive `arc()`. + +- [Tiptoe vs. Fletcher](#tiptoe-vs-fletcher) +- [Available marks](#available-marks) +- [Mark sizing and styling](#mark-sizing-and-styling) +- [Mark alignment](#mark-alignment) +- [Path shortening](#path-shortening) +- [Combining marks](#combining-marks) +- [Defining custom marks](#defining-custom-marks) +- [Arc](#arc) +- [Difference between built in and tiptoe curve](#difference-between-built-in-and-tiptoe-curve) + + +The functions `tiptoe.line()`, `tiptoe.curve()`, and `tiptoe.path()` act as a drop-in replacement (except that [they are placed by default](#difference-between-built-in-and-tiptoe-path)) for the built-in counterparts − but they are enhanced by additional `tip` and `toe` (you have read the title, what did you expect??) arguments. + +Let us consider a simple example to start off. +```typ +#import "@preview/tiptoe:0.3.2": * + +#line(tip: stealth, toe: stealth.with(rev: true)) +#curve( + tip: triangle, toe: bar, + std.curve.cubic((10pt, 0pt), (20pt, 0pt), (20pt, 10pt)), + std.curve.cubic(auto, none, (0pt, 20pt)), +)) +``` +

+ + + + Basic introductory example + +

+ + + +## Tiptoe vs. [Fletcher][fletcher] + +_Before going into the details:_ There exists another awesome package that provides great support for arrows and marks: [Fletcher][fletcher] by [Jollywatt][jollywatt]. If you wonder which package to use, the decision is easy because their use-cases are almost complementary. +- Fletcher works with (and needs) [CeTZ][cetz] while +- Tiptoe does not need (and does not really work with) CeTZ. + +So, if you want to create CeTZ graphics − use Fletcher! If you don't want to use CeTZ − maybe because you just need a single arrow, can't use a canvas, or develop a package that provides graphics utilities − stay here 😉. + +Also note, that the tip sizing and configuration mechanism works quite differently. + + +## Available marks + +Tiptoe comes with a collection of predefined marks, listed below. In [Defining custom marks](#defining-custom-marks), you can learn how to define your own marks. +

+ + + + Available predefined marks + +

+ + +## Mark sizing and styling + +All predefined marks can be configured through `.with()` calls. Some options (like the `inset` of a stealth arrow) are mark-specific. + +*Be Typst!* If you use a configured mark more than once, define an alias for it. Typst makes it incredibly easy to define variables: +```typ +#let my-mark = stealth.with(rev: true, inset: 20%) +#line(tip: my-mark) +``` + +### Sizing + +The size of most arrows is primarily defined by their length and secondarily by their width (exceptions are `bar`, `barb`, `hooks`, and `tikz` which only have a width). Both width and length can be set using +- a `length` value, such as `10pt`, +- or a ratio which is measured relative to the thickness of the line (e.g., `500%` corresponds to 5 times the line thickness), +- or a combination of both, e.g., `3pt + 450%` (this is by the way the default for the `stealth` mark). +This makes it possible to fine-tune the sizing behavior of a mark. By default (`width: auto`) and for most marks the width is defined in terms of the length via some predefined ratio. + + +With the predefined marks, the length/width encompasses the *full* length/width of the mark, independent of the stroke thickness that is used. This is demonstrated below, where the fill is removed through `stealth.with(fill: none)`. + +

+ + + + Arrow sizing + +

+ +### Colors! + +Usually, a mark inherits color and stroke thickness (but not other stroke attributes like `join` or `cap`) from the line that the mark is attached to. In order to override the color, the thickness or both, all marks feature a `stroke` parameter. Additionally, all solid marks feature a `fill` parameter that defaults (when set to `auto`) to the stroke color. + +Below are some examples for mark styling. + +

+ + + + Examples for styling marks + +

+ +## More styling + +Apart from `length`, `width`, `fill`, and `stroke`, many marks possess additional styling parameters, such as +- the `inset` for the arrows `stealth` and `round`, +- an arc angle parameter for the marks `barb` and `hooks`, + +- and of course the `rev` parameter that allows for reversing all marks where this makes sense. + +The figure below shows the additional parameters that each mark supports. The red line indicates how much the underlying path is shortened (see [Path shortening](#path-shortening)). + +

+ + + + Parameters of the predefined marks + +

+ + + +## Mark alignment + +Most marks are aligned such that they point _right onto the end_ (or start) of the path. However, for some marks it is more desirable to have them _centered_ at the end (or start). This is for example the case for the `square` and `circle` marker. All markers that are by default centered on the path end have an `align` parameter that can be set either to `center` or `end` to configure this behavior. + +The mark alignment for the built-in marks is summarized in the table below. + +

+ + + + Alignment of marks + +

+ + + +## Path shortening + +In order to make room for the mark, the path needs to be shortened by some amount. This is trivial for straight segments but not for curved paths. + +The issue is demonstrated in the figure below. In all cases, the arrow is tangent to the curve at its end. In the left panel of the figure, the curve does not enter the arrow in the middle but rather from the side which definitely wouldn't make you look like a good designer when handing in professional work ;) + +To compensate this issue, the path is _transformed_, i.e., shortened by some amount to make it seem nicer. This happens at the cost of the path being not quite the same as before; but it yields a much prettier result. + + +

+ + + + Path shortening + +

+ + + +Not always is it desirable to shorten the path all the way (hey, a little asymmetry simply belongs in life). For this purpose, `curve`, `path`, and `arc` have a parameter `shorten` which takes ratios between `0%` and `100%` (default). + + + +## Combining marks + +The function `combine()` makes it easy to combine multiple marks into a single new one. It accepts any number of marks and can even process combined marks recursively. + +```typ +#line(tip: combine(bar, stealth)) +``` +

+ + + + How to combine marks + +

+ + + +The combined marks are automatically lined up one after the other; always the next one where the previous one ended. In order to introduce or increase the space between two marks, you may use length values (like `10pt`) or even better ratios (which are measured relative to the line thickness of the curve). Negative values are allowed! + + + +

+ + + + Examples for combined arrows and marks + +

+ +By default, the path is shortened until the last mark. This behaviour can be overriden by adding an `end` element somewhere in the mark list. The position of the `end` element between the marks defines where the line or path should end. + + +## Defining custom marks + +A mark is just a function that accepts a named `line` argument and that returns a dictionary `(mark: .., end: ..)` where `mark` holds the rendered mark and `end` is a length that specifies the amount by which the line or path needs to be shortened. + +As an example, let us look at a simplified definition of the `bar` mark. +```typ +#import tiptoe.utility + +#let bar( + // mandatory, will be set by line(), path() and arc() + line: stroke(), + // optional configuration parameters + width: 2.4pt + 360%, + stroke: auto, +) = { + stroke = utility.process-stroke(line, stroke) + let (width,) = utility.process-dims( + line, width: width + ) + + ( + mark: place(path( + (0pt, -width / 2), + (0pt, width / 2), + stroke: stroke + )), + end: 0pt + ) +} +``` + +Let us first explain the difference between the parameters `line` and `stroke`: +- `line` is sort of a _private_ parameter. It is set by the functions `tiptoe.path()`, `tiptoe.line()` and `tiptoe.arc()`, when the mark is realized and contains the stroke used for drawing the path/line/arc. We use it often to make the mark inherit color and thickness. +- `stroke` is totally optional for your mark, but all built-in marks have it. It allows the user of the mark to customize its stroke, overriding the stroke inherited from `line`. + +You can add an arbitrary number of other configuration parameters to your mark. + +The module `tiptoe.utility` provides two very useful helpers. The function `process-stroke()` takes the `line` and `stroke` parameter and returns a merged stroke to be used for drawing the mark. The merging obeys the following rules +- Only `thickness` and `paint` are inherited from `line`. +- Thickness and paint are both (independently) only inherited if they are set to `auto` in `stroke`. This makes it for example possible to configure only the color of a mark without changing the thickness. +- If `stroke` is `none`, nothing is inherited and `none` is returned. +- The merged stroke is guaranteed to have a `thickness` that is not `auto`. + + + + + +The other helper function `process-dims()` is useful for processing length and/or width of the mark which may be (see the [section about sizing](#sizing)) a `ratio` (in terms of the line thickness), a `length`, or a combination thereof. It takes the `line` and optional `width` and `length` parameters and returns a dictionary with the evaluated `width` and `length` (if they were given). In addition, it can process a width set to `auto` in terms of the length with a coefficient that can be specified with the parameter `default-ratio`. As an example, the `stealth` mark has an automatic width by default which is then 80% of the length. + +Hints for rendering the mark: +- You only need to take care of the case where the mark is used as a `tip`. The `toe` case is handled automatically. +- The path goes from left to right and ends at `(0pt, 0pt)`. The stealth arrow, e.g., points exactly to this coordinate. +- The rotation of the mark is fully handled by tiptoe. + +## Arc + +Many have noted that (as of now) Typst does not feature a function to draw arcs. This is sometimes unfortunate since circular arcs are not at all trivial to approximate with Bézier curves. + +Until a built-in arc function makes it into the core of Typst, enjoy this one: + +```typ +#let arc( + origin: (0pt, 0pt), // Origin coordinates + angle: 0deg, // Start angle + arc: 45deg, // Arc angle + radius: 1cm, // The radius of the full circle + width: auto, // The width of the full ellipse + height: auto, // The height of the full ellipse + stroke: 1pt + black, + fill: none, + closed: false, // false, "segment" or "sector" + tip: none, // Mark placed at the start + toe: none, // Mark placed at the toe + shorten: 100% // Path shortening +) +``` + + + +

+ + + + Usage of the arc() primitive + +

+ + + + + + + +## Difference between built-in and tiptoe `curve` + + +While the built-in [`curve`][typst-curve] function returns a block-level element with a size that measures from `(0pt, 0pt)` to the largest (positive) coordinate, the corresponding tiptoe function returns placed content (with zero-width and -height). + +The reasons are +- It is hard to measure the bounding box properly including the marks. +- The behavior of the built-in functions is not particularly useful since they measure only in the positive direction. I suspect that most packages using the drawing primitives wrap them with `place()` anyway. + +Currently, there are some additional limitations that might be fixed in a future release. +- Values of type `relative` or `ratio` are not supported as coordinates in curve elements (to be precise, the `ratio` part is dismissed as of now). +- When using coordinates with `relative: true`, some edge cases might not work as expected. +- Tips are not supported on a `curve.close` element. +- Toes don't work when the curve starts with multiple subsequent `curve.move` elements. You should just merge them into one. + +## Changelog + +### 0.3.2 +- Added support for `ratio` and `relative` values in the start and end coordinates. + +### 0.3.1 +- Fixed tiptoe for right-to-left text flow. +- Internally changed the implementation of `arc` to use `curve` instead of `path`. Consequently, the default of `arc.shorten` is now `100%` which looks better with the new implementation. + +### 0.3.0 +- Added `curve` function in analogy to `std.curve`. + +### 0.2.0 +- Added support for `relative` inputs for `line`. + +### 0.1.0 +Initial release + + +[fletcher]: https://github.com/Jollywatt/typst-fletcher +[cetz]: https://github.com/cetz-package/cetz +[jollywatt]: https://github.com/Jollywatt/ +[typst-curve]: https://typst.app/docs/reference/visualize/curve/ +[typst]: https://typst.app diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/arc-impl.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/arc-impl.typ new file mode 100644 index 0000000..65dc8a0 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/arc-impl.typ @@ -0,0 +1,125 @@ +#let sub(p, q) = p.zip(q).map(((a, b)) => a - b) +#import "assert.typ": assert-dict-keys + +#let bezier-arc( + origin: (0pt, 0pt), + angle: 0deg, + arc: 45deg, + radius: 1cm, + width: auto, + height: auto +) = { + + if arc == 0deg { return () } + if width == auto { width = 2 * radius } + if height == auto { height = 2 * radius } + if calc.abs(arc) > 360deg { arc = 360deg } + let num-curves = int(calc.ceil(calc.abs(arc/90deg))) + let a = arc / num-curves + + let y0 = calc.sin(a / 2) + let x0 = calc.cos(a / 2) + let tx = (1 - x0) * 4/3 + let ty = y0 - tx*x0 / (y0 + 0.0001) + let px = (x0, x0 + tx, x0 + tx, x0) + let py = (-y0, -ty, ty, y0) + + let sn = calc.sin(angle + a / 2) + let cs = calc.cos(angle + a / 2) + let points = () + points.push(( + origin.at(0) + 0.5 * width * (px.at(0)*cs - py.at(0)*sn), + origin.at(1) + 0.5 * height * (px.at(0)*sn + py.at(0)*cs) + )) + + for i in range(num-curves) { + let astart = angle + a * i + let sn = calc.sin(astart + a / 2) + let cs = calc.cos(astart + a / 2) + for j in range(1, 4) { + points.push(( + origin.at(0) + 0.5 * width * (px.at(j)*cs - py.at(j)*sn), + origin.at(1) + 0.5 * height * (px.at(j)*sn + py.at(j)*cs) + )) + } + } + let coords = ( + (points.at(0), (0pt, 0pt), sub(points.at(1), points.at(0))), ..points.slice(1).chunks(3).map( + ((a, b, c)) => { + (c, sub(b, c)) + } + )) + coords.last().push((0pt, 0pt)) + coords +} + +#let bezier-arc2( + origin: (0pt, 0pt), + angle: 0deg, + arc: 45deg, + radius: 1cm, + width: auto, + height: auto +) = { + + if arc == 0deg { return () } + if width == auto { width = 2 * radius } + if height == auto { height = 2 * radius } + if calc.abs(arc) > 360deg { arc = 360deg } + let num-curves = int(calc.ceil(calc.abs(arc/90deg))) + let a = arc / num-curves + + let y0 = calc.sin(a / 2) + let x0 = calc.cos(a / 2) + let tx = (1 - x0) * 4/3 + let ty = y0 - tx*x0 / (y0 + 0.0001) + let px = (x0, x0 + tx, x0 + tx, x0) + let py = (-y0, -ty, ty, y0) + + let sn = calc.sin(angle + a / 2) + let cs = calc.cos(angle + a / 2) + let points = () + points.push(( + origin.at(0) + 0.5 * width * (px.at(0)*cs - py.at(0)*sn), + origin.at(1) + 0.5 * height * (px.at(0)*sn + py.at(0)*cs) + )) + + for i in range(num-curves) { + let astart = angle + a * i + let sn = calc.sin(astart + a / 2) + let cs = calc.cos(astart + a / 2) + for j in range(1, 4) { + points.push(( + origin.at(0) + 0.5 * width * (px.at(j)*cs - py.at(j)*sn), + origin.at(1) + 0.5 * height * (px.at(j)*sn + py.at(j)*cs) + )) + } + } + let coords = ( + curve.move(points.at(0)), + ..points.slice(1).chunks(3).map(x => curve.cubic(..x, relative: false)) + ) + + coords +} + + +#let arc-impl( + origin: (0pt, 0pt), + angle: 0deg, + arc: 6rad, + radius: 1cm, + stroke: 1pt + black +) = { + curve( + stroke: stroke, + ..bezier-arc2( + origin: origin, + angle: angle, + arc: arc, + radius: radius + ) + ) +} + + diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/arc.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/arc.typ new file mode 100644 index 0000000..83a9c26 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/arc.typ @@ -0,0 +1,46 @@ +#import "arc-impl.typ": bezier-arc2 +#import "curve.typ": curve + +#let arc( + origin: (0pt, 0pt), + angle: 0deg, + arc: 45deg, + radius: 1cm, + width: auto, + height: auto, + stroke: 1pt + black, + fill: none, + closed: false, + tip: none, + toe: none, + shorten: 100% +) = { + let coords = bezier-arc2( + origin: origin, + angle: angle, + arc: arc, + radius: radius, + width: width, + height: height, + ) + + if closed == "sector" { + coords.push(std.curve.line(origin)) + } + if closed in ("sector", "segment") { + coords.push(std.curve.close(mode: "straight")) + closed = true + } else if closed == true { + coords.push(std.curve.close()) + } + + curve( + stroke: stroke, + tip: tip, + toe: toe, + shorten: shorten, + fill: fill, + ..coords + ) + +} \ No newline at end of file diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/assert.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/assert.typ new file mode 100644 index 0000000..c56fa41 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/assert.typ @@ -0,0 +1,20 @@ +#let assert-mark(mark, kind: "tip") = { + assert(type(mark) == function, message: "Invalid arrow " + kind + ", expected a function but got a " + str(type(mark)) + ". Ensure that you use e.g. `stealth.with(..)` instead of directly writing `stealth(..)`") +} + +#let assert-dict-keys(dict, required: (), optional: ()) = { + let keys = dict.keys() + + for key in required { + if key not in keys { + assert(false, message: "") + } + } + let possible-keys = required + optional.dict + for key in keys { + if key not in possible-keys { + assert(false, message: "Unexpected key \"" + key + "\"") + } + } + assert(array) +} \ No newline at end of file diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/curve.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/curve.typ new file mode 100644 index 0000000..c4b0acd --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/curve.typ @@ -0,0 +1,429 @@ +#import "marks.typ": * +#import "assert.typ": assert-mark +#import "path-to-curve.typ": path-to-curve + + +#let first-not-none-or-auto(..args) = args.pos().find(x => x != none and x != auto) + +#let add(p, q) = p.zip(q).map(array.sum) +#let sub(p, q) = p.zip(q).map(((a, b)) => a - b) +#let add-if-array(p, q) = if type(p) == array { add(p, q) } else { p } + +// Mirrors control around origin +#let mirror(control, origin) = add(origin, sub(origin, control)) + + + +// When encountering a final curve element with an `auto` control, +// we can use this function to compute this control based on the +// previous curve element. +#let get-prev-mirrored-control(segments) = { + let p = segments.at(-2, default: std.curve.move((0pt, 0pt))) + if p.func() == std.curve.close { p = std.curve.move((0pt, 0pt)) } + + if p.func() == std.curve.move { p.start } + else if p.func() == std.curve.line { p.end } + else if p.func() == std.curve.quad { + if p.control == none {p.end} + else { mirror(p.control, p.end) } + } + else if p.func() == std.curve.cubic { + if p.control-end == none {p.end} + else { mirror(p.control-end, p.end) } + } +} + +// Test whether a curve segment is relative (either explicitly or by a set rule) +#let is-relative(segment) = { + if segment.has("relative") { return segment.relative } + return segment.func().relative +} + + +#let curve-to-absolute-polygon(segments) = { + let base = (0pt, 0pt) + let index-last-absolute-segment = -1 + + for i in range(1, segments.len() + 1) { + let index = segments.len() - i + let segment = segments.at(index) + + if segment.func() == std.curve.move { + index-last-absolute-segment = index + base = segment.start + break + } else if not is-relative(segment) { + index-last-absolute-segment = index + base = segment.end + break + } + } + let polygon = (base, ) + + for (i, segment) in segments.enumerate().slice(index-last-absolute-segment + 1) { + if segment.func() == std.curve.line { + base = add(base, segment.end) + polygon.push(base) + } else if segment.func() == std.curve.quad { + if i == segments.len() - 1 { + polygon.push(add(base, segment.control)) + } + base = add(base, segment.end) + polygon.push(base) + } else if segment.func() == std.curve.cubic { + if i == segments.len() - 1 { + polygon.push(add(base, segment.control-start)) + polygon.push(add(base, segment.control-end)) + } + base = add(base, segment.end) + polygon.push(base) + } + + } + polygon +} + + +#assert.eq(curve-to-absolute-polygon(( + std.curve.move((10pt, 10pt)), + std.curve.line((10pt, 10pt), relative: true), + )), + ((10pt, 10pt), (20pt, 20pt)) +) +#assert.eq(curve-to-absolute-polygon(( + std.curve.move((10pt, 10pt)), + std.curve.line((0pt, 10pt), relative: true), + std.curve.line((10pt, 0pt), relative: true), + )), + ((10pt, 10pt), (10pt, 20pt), (20pt, 20pt)) +) +#assert.eq(curve-to-absolute-polygon(( + std.curve.move((10pt, 10pt)), + std.curve.quad((0pt, 10pt), (10pt, 10pt), relative: true), + std.curve.line((10pt, 0pt), relative: true), + )), + ((10pt, 10pt), (20pt, 20pt), (30pt, 20pt)) +) + +#let resolve-relative(segments) = { + let base = (0pt, 0pt) + for segment in segments.rev() { + if segment.func() == std.curve.move { + base = add(base, segment.start) + break + } else { + base = add(base, segment.end) + if not is-relative(segment) { + break + } + } + } + let prev + if segments.last().func() == std.curve.move { + prev = sub(base, segments.last().start) + } else { + prev = sub(base, segments.last().end) + } + (prev, base) +} + + +#assert.eq(resolve-relative(( + std.curve.move((10pt, 10pt)), + std.curve.line((10pt, 10pt), relative: true), + )), + ((10pt, 10pt), (20pt, 20pt)) +) +#assert.eq(resolve-relative(( + std.curve.move((10pt, 10pt)), + std.curve.line((0pt, 10pt), relative: true), + std.curve.line((10pt, 0pt), relative: true), + )), + ((10pt, 20pt), (20pt, 20pt)) +) +#assert.eq(resolve-relative(( + std.curve.move((10pt, 10pt)), + std.curve.line((0pt, 10pt), relative: true), + std.curve.line((0pt, 10pt), relative: true), + std.curve.line((0pt, 10pt), relative: false), + std.curve.line((10pt, 0pt), relative: true), + )), + ((0pt, 10pt), (10pt, 10pt)) +) + +#assert.eq(resolve-relative(( + std.curve.move((10pt, 10pt)), + std.curve.line((10pt, 10pt), relative: true), + )), + ((10pt, 10pt), (20pt, 20pt)) +) + + +#let treat-tip(mark, segments, inner, shorten: 100%) = { + let final-segment = segments.last() + let args = (:) + let end = final-segment.end + + if is-relative(final-segment) { + if final-segment.has("relative") { + args.relative = true + } + (inner, end) = resolve-relative(segments) + + (.., inner, end) = curve-to-absolute-polygon(segments) + } + + let dx = (end.at(0) - inner.at(0)).length.to-absolute() / 1pt + let dy = (end.at(1) - inner.at(1)).length.to-absolute() / 1pt + let angle = calc.atan2(dx, dy) + let mark-content = place( + dx: end.at(0), dy: end.at(1), + rotate(angle, mark.mark) + ) + end.at(0) -= calc.cos(angle) * mark.end * shorten + end.at(1) -= calc.sin(angle) * mark.end * shorten + (mark-content, end, args) +} + + + +#let treat-toe(mark, vertex0, vertex1, shorten: 100%) = { + let dx = (vertex0.at(0) - vertex1.at(0)).length.to-absolute() / 1pt + let dy = (vertex0.at(1) - vertex1.at(1)).length.to-absolute() / 1pt + let angle = calc.atan2(dx, dy) + + let mark-content = place( + dx: vertex0.at(0), dy: vertex0.at(1), + rotate(angle, mark.mark) + ) + + vertex0.at(0) -= calc.cos(angle) * mark.end * shorten + vertex0.at(1) -= calc.sin(angle) * mark.end * shorten + + (mark-content, vertex0) +} + + + + +#let curve( + ..segments, + fill: none, + fill-rule: auto, + stroke: 1pt, + tip: none, + toe: none, + shorten: 100%, +) = { + if segments.named().len() != 0 { + assert(false, message: "Unexpected named argument \"" + segments.named().keys().first() + "\"") + } + + set place(left) + + stroke = std.stroke(stroke) + + assert( + type(shorten) in (ratio, dictionary), + message: "Expected ratio or dictionary for parameter `shorten`, found " + str(type(shorten)) + ) + if type(shorten) == ratio { + shorten = (start: shorten, end: shorten) + } else if type(shorten) == dictionary { + assert( + shorten.keys().sorted() == ("end", "start"), + message: "Unexpected key, valid keys are \"start\" and \"end\"" + ) + } + + + context { + let segments = segments.pos() + let marks + + if toe != none and segments.len() >= 1 { + + assert-mark(toe, kind: "toe") + let toe = toe(line: stroke) + let first-segment = segments.first() + + if first-segment.func() == std.curve.move and segments.len() >= 2 { + + let vertex0 = first-segment.start + let relative = is-relative(segments.at(1)) + + // Obtain next vertex: either an end point or the next control point + let vertex1 = { + let se = segments.at(1) + if se.func() == std.curve.line { + if relative { + // We will change the first curve.move, so this second segment cannot be relative + segments.at(1) = std.curve.line(add(vertex0, se.end), relative: false) + } + se.end + } else if se.func() == std.curve.quad { + if relative { + // We will change the first curve.move, so this second segment cannot be relative + segments.at(1) = std.curve.quad( + add-if-array(se.control, vertex0), + add(se.end, vertex0), + relative: false + ) + } + first-not-none-or-auto(se.control, se.end) + } else if se.func() == std.curve.cubic { + if relative { + // We will change the first curve.move, so this second segment cannot be relative + segments.at(1) = std.curve.cubic( + add-if-array(se.control-start, vertex0), + add-if-array(se.control-end, vertex0), + add(se.end, vertex0), + relative: false + ) + } + first-not-none-or-auto(se.control-start, se.control-end, se.end) + } + } + + if relative { + vertex1 = add(vertex0, vertex1) + } + + let (mark, new-vertex0) = treat-toe( + toe, vertex0, vertex1, + shorten: shorten.start + ) + marks += mark + segments.first() = std.curve.move(new-vertex0) + + } else if first-segment.func() == std.curve.line { + + let (mark, new-vertex0) = treat-toe( + toe, (0pt, 0pt), first-segment.end, + shorten: shorten.start + ) + marks += mark + segments.first() = std.curve.line(first-segment.end, relative: false) + segments.insert(0, std.curve.move(new-vertex0)) + + } else if first-segment.func() == std.curve.quad { + + let vertex1 = first-not-none-or-auto(first-segment.control, first-segment.end) + let (mark, new-vertex0) = treat-toe( + toe, (0pt, 0pt), vertex1, + shorten: shorten.start + ) + marks += mark + segments.first() = std.curve.quad( + first-segment.control, + first-segment.end, + relative: false + ) + segments.insert(0, std.curve.move(new-vertex0)) + + } else if first-segment.func() == std.curve.cubic { + + let vertex1 = first-not-none-or-auto( + first-segment.control-start, first-segment.control-end, first-segment.end + ) + let (mark, new-vertex0) = treat-toe( + toe, (0pt, 0pt), vertex1, + shorten: shorten.start + ) + marks += mark + segments.first() = std.curve.cubic( + first-segment.control-start, + first-segment.control-end, + first-segment.end, + relative: false + ) + segments.insert(0, std.curve.move(new-vertex0)) + + } + + } + + + + if tip != none and segments.len() >= 1 { + + assert-mark(tip, kind: "tip") + let tip = tip(line: stroke) + let final-segment = segments.last() + + // Obtain previous vertex: either an end point or the last control point + let vertex-n-1 = { + let p = segments.at(-2, default: std.curve.move((0pt, 0pt))) + if p.func() == std.curve.close { p = std.curve.move((0pt, 0pt)) } + if p.func() == std.curve.move { p.start } + else { p.end } + } + + if final-segment.func() == std.curve.close { + assert(false, message: "Tips are not supported on the `curve.close` element") + + } else if final-segment.func() == std.curve.line { + + let (mark, new-vertex-n, args) = treat-tip( + tip, + segments, vertex-n-1, + shorten: shorten.end + ) + marks += mark + segments.last() = std.curve.line(new-vertex-n, relative: false) + + } else if final-segment.func() == std.curve.quad { + + if final-segment.control != none { + if final-segment.control == auto { + vertex-n-1 = get-prev-mirrored-control(segments) + } else { + vertex-n-1 = final-segment.control + } + } + + let (mark, new-vertex-n, args) = treat-tip( + tip, + segments, vertex-n-1, + shorten: shorten.end + ) + + marks += mark + segments.last() = std.curve.quad( + final-segment.control, new-vertex-n, relative: false + ) + + } else if final-segment.func() == std.curve.cubic { + + if final-segment.control-end != none { + vertex-n-1 = final-segment.control-end + } else if final-segment.control-start == auto { + vertex-n-1 = get-prev-mirrored-control(segments) + } else if final-segment.control-start != none { + vertex-n-1 = final-segment.control-start + } + + let (mark, new-vertex-n, args) = treat-tip( + tip, + segments, vertex-n-1, + shorten: shorten.end + ) + + marks += mark + segments.last() = std.curve.cubic( + final-segment.control-start, + final-segment.control-end, + new-vertex-n, relative: false + ) + + } + } + + place(std.curve( + ..segments, + stroke: stroke, + fill: fill, + fill-rule: utility.if-auto(fill-rule, std.curve.fill-rule) + )) + marks + } +} diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/line.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/line.typ new file mode 100644 index 0000000..8939e29 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/line.typ @@ -0,0 +1,118 @@ +#import "marks.typ": * +#import "assert.typ": assert-mark + +#let resolve-relative(x, y, size) = { + let resolve-coordinate(x, length) = { + + if type(x) == std.length { x } + else if type(x) == ratio { length * x } + else if type(x) == relative { x.length + length * x.ratio } + } + + ( + resolve-coordinate(x, size.width), + resolve-coordinate(y, size.height), + ) +} + +#let line-impl( + start: (0pt, 0pt), + end: none, + length: 30pt, + angle: 0deg, + stroke: 1pt, + tip: none, + toe: none +) = box({ + + if end == none { + // using length and angle + end = start.zip((length*calc.cos(angle), length*calc.sin(angle))).map(array.sum) + if type(length) == ratio { + assert(angle in (0deg, 90deg), message: "When `length` is a ratio, the angle can only be 0deg or 90deg, found " + repr(angle)) + } else if length.to-absolute() < 0pt { + length *= -1 + angle += 180deg + } + } else { + // using start and end + let dx = (end.at(0) - start.at(0)).to-absolute() / 1pt + let dy = (end.at(1) - start.at(1)).to-absolute() / 1pt + angle = calc.atan2(dx, dy) + length = 1pt * calc.sqrt(dx*dx + dy*dy) + } + + let original-line = std.line(start: start, angle: angle, length: length, stroke: stroke) + + // Apply path shortening + let toe-pos = start + if toe != none { + start.at(0) += calc.cos(angle) * toe.end + start.at(1) += calc.sin(angle) * toe.end + length -= toe.end + } + if tip != none { + length -= tip.end + } + + place(std.line(start: start, angle: angle, length: length, stroke: stroke)) + + if tip != none { + place( + dx: end.at(0), dy: end.at(1), + rotate(angle, tip.mark) + ) + } + if toe != none { + place( + dx: toe-pos.at(0), dy: toe-pos.at(1), + rotate(angle, scale(x: -100%, toe.mark)) + ) + } + hide(original-line) +}) + + + +#let line( + start: (0pt, 0pt), + end: none, + length: 30pt, + angle: 0deg, + stroke: 1pt, + tip: none, + toe: none +) = { + stroke = std.stroke(stroke) + if tip != none { + assert-mark(tip, kind: "tip") + tip = tip(line: stroke) + } + if toe != none { + assert-mark(toe, kind: "toe") + toe = toe(line: stroke) + } + set place(left) + + let line-impl = line-impl.with( + start: start, + end: end, + length: length, + angle: angle, + stroke: stroke, + tip: tip, + toe: toe + ) + + if end != none and (start + end).map(type).any(x => x in (ratio, relative)) { + + context layout(size => line-impl( + start:resolve-relative(..start, size) , + end: resolve-relative(..end, size) + ) + ) + + } else { + context line-impl() + } +} diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/marks.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/marks.typ new file mode 100644 index 0000000..fe34e4a --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/marks.typ @@ -0,0 +1,547 @@ +#import "arc-impl.typ": arc-impl +#import "utility.typ" + + + +#let bar( + width: 2.4pt + 360%, + stroke: auto, + align: center, + line: stroke() +) = { + stroke = utility.process-stroke(line, stroke) + let (width,) = utility.process-dims( + line, width: width, + ) + + assert(align in (center, end)) + let offset = if align == end { stroke.thickness / 2 } else { 0pt } + + ( + mark: place(std.line( + start: (-offset, -width / 2), + end: (-offset, width / 2), + stroke: stroke + )), + end: offset + ) +} + + +#let bracket( + width: 2.4pt + 360%, + length: auto, + stroke: auto, + rev: false, + line: stroke() +) = { + stroke = utility.process-stroke(line, stroke) + let (width, length) = utility.process-dims( + line, length: width, width: length, + default-ratio: .3 + ) + (width, length) = (length, width) + let s = stroke.thickness / 2 + let y = width / 2 - s + let mark = place(curve( + curve.move((-length, -y)), + curve.line((-s, -y), relative: false), + curve.line((-s, y), relative: false), + curve.line((-length, y), relative: false), + stroke: stroke + )) + if rev { + mark = scale(x: -100%, place(mark, dx: length)) + } + ( + mark: mark, + end: if rev { length } else { s } + ) +} + + +#let stealth( + length: 3pt + 450%, + width: auto, + inset: 40%, + fill: auto, + rev: false, + stroke: auto, + line: stroke() +) = { + let is-auto-stroke = stroke == auto + stroke = utility.process-stroke(line, stroke) + let (width, length) = utility.process-dims( + line, length: length, width: width, + default-ratio: .8 + ) + + let linewidth = stroke.thickness + let Δl = length * inset + let dhalf = 0.5 * width + let tip-length + + let path = if fill == auto and is-auto-stroke { + // very common optimization for filled arrows + polygon( + (0pt, 0pt), + (-length, dhalf), + (-length + Δl, 0pt), + (-length, -dhalf), + stroke: none, + fill: utility.if-auto(stroke.paint, black) + ) + tip-length = length / 2 + } else { + let tanα = dhalf / length + let α = calc.atan(tanα) + let sinα  = calc.sin(α) + let x3 = 0.5 * linewidth / sinα + + let (x, x4, y) = if inset == 0% { + let x = length - linewidth/2 + (x, x, tanα * (x - x3)) + } else { + let tanβ = dhalf / Δl + let β = calc.atan(tanβ) + let x1 = 0.5 * linewidth / calc.sin(β) + let x2 = length - Δl - x1 - x3 + let b = -x2 * tanα + let x = b / (tanβ - tanα) + let y = tanβ * x + let x4 = length - Δl - x1 + (length - Δl - x1 - x, x4, y) + } + tip-length = x3 + polygon( + (-x3, 0pt), + (-x, y), + (-x4, 0pt), + (-x, -y), + stroke: std.stroke( + thickness: linewidth, + paint: utility.if-auto(stroke.paint, black), + dash: stroke.dash, + miter-limit: 7, + join: "miter" + ), + fill: utility.chained-if-auto(fill, stroke.paint, black) + ) + } + + + let mark = place(path) + if rev { + mark = scale(x: -100%, place(mark, dx: length)) + } + ( + mark: mark, + end: if rev { length - tip-length } else { length - Δl} + ) +} + + +#let triangle = stealth.with(inset: 0%) + +#let round( + length: 3pt + 450%, + width: auto, + inset: 40%, + rev: false, + stroke: auto, + fill: auto, + line: stroke() +) = { + stroke = utility.process-stroke(line, stroke) + let (width, length) = utility.process-dims( + line, length: length, width: width, + default-ratio: .8 + ) + + let s = 0.5 * stroke.thickness + + if inset >= 100% { + inset = length - stroke.thickness + } else { + inset = length * inset + } + + let mark = place(polygon( + (-s, 0pt), + (-length + s, 0.5 * width - s), + (-length + inset + s, 0pt), + (-length + s, -0.5 * width + s), + fill: utility.chained-if-auto(fill, stroke.paint, black), + stroke: std.stroke( + thickness: stroke.thickness, + paint: utility.if-auto(stroke.paint, black), + dash: stroke.dash, + miter-limit: 7, + join: "round" + ) + )) + + let end = length - inset - s + + if rev { + mark = scale(x: -100%, place(mark, dx: length)) + end = length - s + } + + ( + mark: mark, + end: end + ) +} + + +#let straight( + length: 3pt + 450%, + width: auto, + rev: false, + stroke: auto, + line: stroke() +) = { + stroke = utility.process-stroke(line, stroke) + let (width, length) = utility.process-dims( + line, length: length, width: width, + default-ratio: .8 + ) + + let s = stroke.thickness / 2 + let α = calc.atan(0.5 * width / length) + let tip-length = 0.5 * stroke.thickness / calc.sin(α) + + let mark = place(curve( + curve.move((-length + s, 0.5 * width - s)), + curve.line((-tip-length, 0pt), relative: false), + curve.line((-length + s, -0.5 * width + s), relative: false), + stroke: std.stroke( + thickness: stroke.thickness, + paint: utility.if-auto(stroke.paint, black), + dash: stroke.dash, + miter-limit: 7, + cap: "round" + ) + )) + + + if rev { + mark = scale(x: -100%, place(mark, dx: length)) + } + + ( + mark: mark, + end: if rev { length - tip-length } else { tip-length } + ) +} + +#let diamond( + length: 565.69%, + width: auto, + fill: auto, + stroke: auto, + align: center, + line: stroke() +) = { + stroke = utility.process-stroke(line, stroke) + let (width, length) = utility.process-dims( + line, length: length, width: width, + default-ratio: 1 + ) + let dhalf = 0.5 * width + let tip-length + + let tanα = dhalf / length * 2 + let α = calc.atan(tanα) + let tip-length = 0.5 * stroke.thickness / calc.sin(α) + let top-length = 0.5 * stroke.thickness / calc.cos(α) + + assert(align in (center, end)) + let offset = if align == center { length / 2 } else { 0pt } + + let mark = place(dx: offset, polygon( + (-length / 2, dhalf - top-length), + (-tip-length, 0pt), + (-length / 2, -dhalf + top-length), + (-length + tip-length, 0pt), + stroke: stroke, + fill: utility.chained-if-auto(fill, stroke.paint, black) + )) + + ( + mark: mark, + end: length - tip-length - offset + ) +} + +#let square( + length: 400%, + width: auto, + fill: auto, + stroke: auto, + align: center, + line: stroke() +) = { + stroke = utility.process-stroke(line, stroke) + let (width, length) = utility.process-dims( + line, length: length, width: width, + default-ratio: 1 + ) + assert(align in (center, end)) + let offset = if align == center { length / 2 } else { 0pt } + let s = stroke.thickness / 2 + let y = width/2 - s + let mark = place(dx: offset, + polygon( + (-s, y), + (-length + s, y), + (-length + s, -y), + (-s, -y), + stroke: stroke, + fill: utility.chained-if-auto(fill, stroke.paint, black), + ) + ) + + ( + mark: mark, + end: length - s - offset + ) +} + + + +#let circle( + length: 400%, + width: auto, + fill: auto, + stroke: auto, + align: center, + line: stroke() +) = { + stroke = utility.process-stroke(line, stroke) + let (width, length) = utility.process-dims( + line, length: length, width: width, + default-ratio: 1 + ) + let s = stroke.thickness / 2 + let y = width / 2 - s + + assert(align in (center, end)) + let offset = if align == center { length / 2 } else { 0pt } + + let mark = place(dx: -length + s + offset, dy: -y, + ellipse( + width: length - 2 * s, + height: width - 2 * s, + stroke: stroke, + fill: utility.chained-if-auto(fill, stroke.paint, black), + ) + ) + + ( + mark: mark, + end: length - s - offset + ) +} + + +#let rays( + n: 4, + length: 280%, + phase: auto, + stroke: auto, + align: center, + line: stroke() +) = { + stroke = utility.process-stroke(line, stroke) + let (length,) = utility.process-dims( + line, length: length + ) + if phase == auto { + if n == 4 { phase = 45deg } + else { phase = -90deg } + } + + assert(align in (center, end)) + let offset = if align == end { -length } else { 0pt } + + let mark = for i in range(n) { + place(dx: offset, std.line(length: length, angle: phase + 360deg*i/n, stroke: stroke)) + } + + ( + mark: mark, + end: -offset + ) +} + + + +#let barb( + width: 3pt + 450%, + arc: 180deg, + stroke: auto, + rev: false, + line: stroke(), +) = { + stroke = utility.process-stroke(line, stroke) + let (width,) = utility.process-dims( + line, width: width + ) + + let s = stroke.thickness / 2 + let radius = width / 2 - s + + let mark = place( + arc-impl( + origin: (-width / 2, 0pt), + angle: -arc / 2, + arc: arc, + radius: radius, + stroke: stroke + ) + ) + let end = s + + if rev { + mark = scale(x: -100%, place(mark, dx: width / 2)) + end = radius + } + + ( + mark: mark, + end: end + ) +} + + +#let hooks( + width: 3pt + 450%, + arc: 180deg, + stroke: auto, + rev: false, + line: stroke(), +) = { + stroke = utility.process-stroke(line, stroke) + let (width,) = utility.process-dims( + line, width: width + ) + + let s = stroke.thickness / 2 + let r = (width / 2 - s) / 2 + + let mark = { + place( + arc-impl( + origin: (-r - s, r), + angle: -90deg, + arc: arc, + radius: r, + stroke: stroke + ) + ) + place( + arc-impl( + origin: (-r - s, -r), + angle: 90deg, + arc: -arc, + radius: r, + stroke: stroke + ) + ) + } + let end = r - s + + if rev { + mark = scale(x: -100%, place(mark, dx: r + s)) + end = 0pt + } + + ( + mark: mark, + end: end + s + ) +} + + + +#let tikz( + width: 3pt + 450%, + arc: 180deg, + stroke: auto, + line: stroke(), +) = { + let sharp = false + stroke = utility.process-stroke(line, stroke) + let (width,) = utility.process-dims( + line, width: width + ) + + let s = stroke.thickness / 2 + stroke = if sharp { + (thickness: stroke.thickness, paint: stroke.paint, join: "bevel", cap: "butt") + } else { + (thickness: stroke.thickness, paint: stroke.paint, join: "round", cap: "round") + } + let x0 = if sharp { stroke.thickness/width*10pt } else { -s } + let mark = place( + curve( + curve.move((-width * 0.42, -width / 2 + s)), + curve.cubic( + (-width * 0.32, -width * 0.1 + s), + none, + (x0, 0pt), + relative: false + ), + curve.cubic( + none, + (-width * 0.32, width * 0.1 - s), + (-width * 0.42, width / 2 - s), + relative: false + ), + stroke: stroke + ) + ) + + ( + mark: mark, + end: stroke.thickness + ) +} + + +#let combine( + line: stroke(), + ..marks +) = (line: stroke()) => { + let marks = marks.pos() + let linewidth = utility.if-auto(line.thickness, 1pt) + + let combined-mark + let pos = 0pt + let end + + for mark in marks { + if mark == std.end { + end = pos + } else if type(mark) == length { + pos += mark + } else if type(mark) == ratio { + pos += mark * linewidth + } else if type(mark) == function { + mark = mark(line: line) + combined-mark += place(dx: -pos, mark.mark) + pos += mark.end + } + } + if end == none { + end = pos + } + + ( + mark: combined-mark, + end: end + ) +} + diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/path-to-curve.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/path-to-curve.typ new file mode 100644 index 0000000..1cc0c52 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/path-to-curve.typ @@ -0,0 +1,80 @@ + + + +#let transform(a, b, mapper) = array.zip(a, b, exact: true).map(mapper) +#let add(a, b) = transform(a, b, ((x,y)) => x + y) +#let subtract(a, b) = transform(a, b, ((x,y)) => x - y) +#let multiply(a, c) = a.map(x => x * c) + +#let path-to-curve( + ..vertices, + stroke: stroke(), + fill: none, + fill-rule: "even-odd", + closed: false +) = { + // place(std.path(..vertices, stroke: stroke, fill: fill, closed: closed)) + vertices = vertices.pos() + if vertices.len() == 0 { return } + + let is-vertex(v) = (type(v) == array and type(v.first()) != array) or v.len() == 1 + let extract-vertex(v) = { + if is-vertex(v) { + if v.len() == 2 {v} + else { v.first() } + } else { v.first() } + } + + let curve-elements = () + let start-in = none + let out = none + for vertex in vertices { + let v = extract-vertex(vertex) + + if is-vertex(vertex) { + if out == none { + curve-elements.push((v,)) + } else { + curve-elements.push((out, none, v)) + out = none + } + } else { + if vertex.len() == 2 { + vertex.push(multiply(vertex.at(1), -1)) + // now its definitely a "cubic"! + } + if curve-elements.len() == 0 { + curve-elements.push((v,)) + start-in = add(v, vertex.at(1)) + } else if out == none { + curve-elements.push((auto, add(v, vertex.at(1)), v)) + out = auto + } else { + curve-elements.push((out, add(v, vertex.at(1)), v)) + } + out = add(v, vertex.at(2)) + } + } + + let to-curve-element(x) = { + if x.len() == 1 { curve.line(..x) } + else if x.len() == 2 { curve.quad(..x) } + else if x.len() == 3 { curve.cubic(..x) } + } + curve-elements = curve-elements.map(to-curve-element) + let start = extract-vertex(vertices.first()) + if closed { + if out != none or start-in != none { + curve-elements.push(curve.cubic(out, start-in, start)) + } + curve-elements.push(curve.close(mode: "straight")) + } + + std.curve( + curve.move(start), + fill: fill, stroke: stroke, + ..curve-elements, + fill-rule: fill-rule + // stroke: yellow + .5pt + ) +} diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/path.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/path.typ new file mode 100644 index 0000000..f69b864 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/path.typ @@ -0,0 +1,118 @@ +#import "marks.typ": * +#import "assert.typ": assert-mark +#import "path-to-curve.typ": path-to-curve + + +#let add(p, q) = p.zip(q).map(array.sum) +#let sub(p, q) = p.zip(q).map(((a, b)) => a - b) +#let addorsub(p, q, f) = p.zip(q).map(((a, b)) => a + f*b) + +#let extract-bezier-polygon(vertex1, vertex2, rev: false) = { + let polygon = () + if type(vertex1.at(0)) == array { + let v = vertex1.at(0) + if vertex1.len() == 1 { polygon.push(v) } + else if vertex1.len() == 2 { + polygon += (v, sub(v, vertex1.at(1))).dedup() + } else if vertex1.len() == 3 { + polygon += (v, add(v, vertex1.at(2))).dedup() + } + } else { + polygon.push(vertex1) + } + if type(vertex2.at(0)) == array { + let v = vertex2.at(0) + if vertex2.len() == 1 { polygon.push(v) } + else { + polygon += (add(v, vertex2.at(1)), v).dedup() + } + } else { + polygon.push(vertex2) + } + + return polygon +} + + + +#let path( + ..args, + fill: none, + fill-rule: auto, + stroke: 1pt, + closed: false, + tip: none, + toe: none, + shorten: 100%, +) = { + if args.named().len() != 0 { + assert(false, message: "Unexpected named argument \"" + args.named().keys().first() + "\"") + } + + set place(left) + + stroke = std.stroke(stroke) + + assert( + type(shorten) in (ratio, dictionary), + message: "Expected ratio or dictionary for parameter `shorten`, found " + str(type(shorten)) + ) + if type(shorten) == ratio { + shorten = (start: shorten, end: shorten) + } else if type(shorten) == dictionary { + assert( + shorten.keys().sorted() == ("end", "start"), + message: "Unexpected key, valid keys are \"start\" and \"end\"" + ) + } + + + context { + let points = args.pos() + let original-points = points + + let treat-mark(mark, i1, i2, pos: start, shorten: 100%) = { + mark = mark(line: stroke) + + let polygon = extract-bezier-polygon(points.at(i1), points.at(i2)) + if pos == end { polygon = polygon.rev() } + let inner = polygon.at(1) + let outer = polygon.at(0) + + let dx = (outer.at(0) - inner.at(0)).to-absolute() / 1pt + let dy = (outer.at(1) - inner.at(1)).to-absolute() / 1pt + let angle = calc.atan2(dx, dy) + + let mark-content = place( + dx: outer.at(0), dy: outer.at(1), + rotate(angle, mark.mark) + ) + outer.at(0) -= calc.cos(angle) * mark.end * shorten + outer.at(1) -= calc.sin(angle) * mark.end * shorten + return (mark-content, outer) + } + + let marks + if toe != none and points.len() >= 2 { + assert-mark(toe, kind: "toe") + let (mark, end) = treat-mark(toe, 0, 1, pos: start, shorten: shorten.start) + if type(points.first().at(0)) == array { points.first().at(0) = end } + else { points.first() = end } + marks += mark + } + if tip != none and points.len() >= 2 { + assert-mark(tip, kind: "tip") + let (mark, end) = treat-mark(tip, -2, -1, pos: end, shorten: shorten.end) + if type(points.last().at(0)) == array { points.last().at(0) = end } + else { points.last() = end } + marks += mark + } + + let fill-rule = fill-rule + if fill-rule == auto { + fill-rule = std.curve.fill-rule + } + + place(path-to-curve(..points, stroke: stroke, fill: fill, closed: closed, fill-rule: fill-rule)) + marks + } +} \ No newline at end of file diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/tiptoe.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/tiptoe.typ new file mode 100644 index 0000000..2404c90 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/tiptoe.typ @@ -0,0 +1,6 @@ +#import "line.typ": line +#import "path.typ": path +#import "curve.typ": curve +#import "arc.typ": arc +#import "utility.typ" +#import "marks.typ": * \ No newline at end of file diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/utility.typ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/utility.typ new file mode 100644 index 0000000..5185141 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/src/utility.typ @@ -0,0 +1,67 @@ +#let if-auto(a, b) = if a == auto { b } else { a } +#let if-none(a, b) = if a == none { b } else { a } +#let chained-if-auto(..x) = x.pos().find(it => it != auto) + + + +#let extract-thickness-and-paint(stroke) = { + if-auto(stroke.thickness, 1pt) + if-auto(stroke.paint, black) +} + +#let inherit-thickness-and-paint(stroke, parent: 2) = { + if stroke == none { return none } // this is always stronger + + // strokes might be length, color or dictionary: + let parent = std.stroke(parent) + let stroke = std.stroke(stroke) + return std.stroke( + paint: if-auto(stroke.paint, parent.paint), + thickness: chained-if-auto(stroke.thickness, parent.thickness, 1pt), + cap: stroke.cap, + join: stroke.join, + dash: stroke.dash, + miter-limit: stroke.miter-limit + ) +} + + +#let process-stroke(line, stroke) = { + if stroke == auto { + return extract-thickness-and-paint(line) + } + return inherit-thickness-and-paint(stroke, parent: line) +} + + +#let process-dims( + line, + length: none, + width: none, + default-ratio: .8 +) = { + let result = (:) + let linewidth = if-auto(line.thickness, 1pt) + + if length != none { + result.length = if type(length) == ratio { + linewidth * length + } else if type(length) == relative { + length.length + linewidth * length.ratio + } else { + length + } + } + + if width != none { + result.width = if width == auto { + result.length * default-ratio + } else if type(width) == ratio { + linewidth * width + } else if type(width) == relative { + width.length + linewidth * width.ratio + } else { + width + } + } + return result +} \ No newline at end of file diff --git a/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/typst.toml b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/typst.toml new file mode 100644 index 0000000..843123c --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/tiptoe/0.3.2/typst.toml @@ -0,0 +1,13 @@ +[package] +name = "tiptoe" +version = "0.3.2" +entrypoint = "src/tiptoe.typ" +authors = ["Mc-Zen "] +license = "MIT" +description = "Arrows and other marks for lines and paths." +compiler = "0.13.0" + +repository = "https://github.com/Mc-Zen/tiptoe" +keywords = ["arrows", "marks", "primitives", "line", "path", "tip", "arc"] +categories = ["visualization", "layout"] +exclude = ["/docs/*", "/tests/*"] \ No newline at end of file diff --git a/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/LICENSE b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/LICENSE new file mode 100644 index 0000000..5e8cd93 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024-2025 Mc-Zen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/README.md b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/README.md new file mode 100644 index 0000000..2dcd159 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/README.md @@ -0,0 +1,473 @@ +# $Z\cdot e^{ro}$ + +_Advanced scientific number formatting for Typst._ + +[![Typst Package](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FMc-Zen%2Fzero%2Fv0.5.0%2Ftypst.toml&query=%24.package.version&prefix=v&logo=typst&label=package&color=239DAD)](https://typst.app/universe/package/zero) +[![Test Status](https://github.com/Mc-Zen/zero/actions/workflows/run_tests.yml/badge.svg)](https://github.com/Mc-Zen/zero/actions/workflows/run_tests.yml) +[![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/Mc-Zen/zero/blob/main/LICENSE) + + +- [Introduction](#introduction) +- [Quick Demo](#quick-demo) +- [Number Formatting](#number-formatting) +- [Table Alignment](#table-alignment) +- [Units and Quantities](#units-and-quantities) +- [Zero for Third-Party Packages](#zero-for-third-party-packages) +- [Changelog](#changelog) + +--- + +## Introduction + +Proper number formatting is essential for clear and readable scientific documents. **Zero** provides tools for consistent formatting and simplifies adherence to established publication standards. Key features include: + +- **Standardized** formatting +- Digit [**grouping**](#grouping), e.g., 299 792 458 instead of 299792458 +- **Plug-and-play** number [**alignment in tables**](#table-alignment) +- Quick scientific notation, e.g., `"2e4"` becomes 2×10⁴ +- Symmetric and asymmetric [**uncertainties**](#specifying-uncertainties) +- [**Rounding**](#rounding) in various modes +- [**Unit and quantity formatting**](#units-and-quantities) +- Helpers for package authors + +A number in scientific notation consists of three parts: the _mantissa_, an optional _uncertainty_, and an optional _power_ (exponent). The following figure illustrates the anatomy of a formatted number: + +

+ + + + Anatomy of a formatted number + +

+ + + +--- + +## Demo + +```typ +#import "@preview/zero:0.5.0": num, format-table, zi + +Physicists estimate a number of #num[1e80] particles in the observable universe. + +#figure({ + show: format-table(none, auto) + table( + columns: 2, + [1], [1.2], + [2], [2], + [3], [300] + ) +}) + +#let Js = zi.declare("J s") +Plancks constant is roughly #Js[6.626e-34]. +``` + +

+ + + + Quick demo + +

+ +See below for a few examples of the syntax that can be used with `num`. + +

+ + + + Quick demo + +

+ +--- + +## Number Formatting + +- [Function `num`](#num) +- [Grouping](#grouping) +- [Rounding](#rounding) +- [Uncertainties](#specifying-uncertainties) + +### `num` + +Zero's core is the `num()` function, which provides flexible number formatting. Its defaults can be configured via `set-num()`. + +```typ +#num( + number: str | content | int | float | dictionary | array, + digits: auto | int = auto, + fixed: none | int = none, + + decimal-separator: str = ".", + product: content = sym.times, + tight: bool = false, + math: bool = true, + omit-unity-mantissa: bool = true, + positive-sign: bool = false, + positive-sign-exponent: bool = false, + base: int | content = 10, + uncertainty-mode: str = "separate", + round: dictionary, + group: dictionary, +) +``` +- `number: str | content | int | float | array` : Number input; `str` is preferred. If the input is `content`, it may only contain text nodes. Numeric types `int` and `float` are supported but not encouraged because of information loss (e.g., the number of trailing "0" digits or the exponent). The remaining types `dictionary` and `array` are intended for advanced use, see [below](#zero-for-third-party-packages). +- `digits: auto | int = auto` : Truncates the number at a given (positive) number of decimal places or pads the number with zeros if necessary. This is independent of [rounding](#rounding). +- `fixed: none | int = none` : If not `none`, forces a fixed exponent. Additional exponents given in the number input are taken into account. +- `decimal-separator: str = "."` : Specifies the marker that is used for separating integer and decimal part. +- `product: content = sym.times` : Specifies the multiplication symbol used for scientific notation. +- `tight: bool = false` : If true, tight spacing is applied between operands (applies to × and ±). +- `math: bool = true` : If set to `false`, the parts of the number won't be wrapped in a `math.equation`. This makes it possible to use `num()` with non-math fonts. +- `omit-unity-mantissa: bool = false` : Determines whether a mantissa of 1 is omitted in scientific notation, e.g., 10⁴ instead of 1·10⁴. +- `positive-sign: bool = false` : If set to `true`, positive coefficients are shown with a + sign. +- `positive-sign-exponent: bool = false` : If set to `true`, positive exponents are shown with a + sign. +- `base: int | content = 10` : The base used for scientific power notation. +- `uncertainty-mode: str = "separate"` : Selects one of the modes `"separate"`, `"compact"`, or `"compact-separator"` for displaying uncertainties. The different behaviors are shown below: + +

+ + + + Uncertainty modes + +

+ +- `round: dictionary` : You can provide one or more rounding options in a dictionary. Also see [rounding](#rounding). +- `group: dictionary` : You can provide one or more grouping options in a dictionary. Also see [grouping](#grouping). + +Configuration example: +```typ +#set-num(product: math.dot, tight: true) +``` + +### Grouping + + +Digit grouping is important for keeping large figures readable. It is customary to separate thousands with a thin space, a period, comma, or an apostrophe (however, we discourage using a period or a comma to avoid confusion since both are used for decimal separators in various countries). + + +

+ + + + Digit grouping + +

+ + +Digit grouping can be configured with the `set-group()` function. + + +```typ +#set-group( + size: int = 3, + separator: content = sym.space.thin, + threshold: int | dictionary = 5 +) +``` +- `size: int = 3` : Determines the size of the groups. +- `separator: content = sym.space.thin` : Separator between groups. +- `threshold: int | dictionary = 5` : Necessary number of digits needed for digit grouping to kick in. Four-digit numbers for example are usually not grouped at all since they can still be read easily. This parameter also accepts dictionary arguments of the form `(integer: int, fractional: int)` to allow turning on grouping for only the integer or fractional part, for example `(integer: 5, fractional: calc.inf)`. + + + +Configuration example: +```typ +#set-group(separator: "'", threshold: 4) +``` + +Set `threshold: calc.inf` to disable grouping. + + + +### Rounding + +Rounding can be configured with the `set-round()` function. + +```typ +#set-round( + mode: str = "places", + precision: int | none = 2, + pad: bool = true, + direction: str = "nearest", +) +``` +- `mode: str = "places"` : Sets the rounding mode. The possible options are + - `none` : Rounding is turned off. + - `"places"` : The number is rounded to the number of decimal places given by the `precision` parameter. + - `"figures"` : The number is rounded to a number of significant figures given by the `precision` parameter. + - `"uncertainty"` : Requires giving an uncertainty value. The uncertainty is + rounded to significant figures according to the `precision` argument and + then the number is rounded to the same number of decimal places as the + uncertainty. +- `precision: int | none = 2` : The precision to round to. Also see parameter `mode`. When set to `none`, no rounding is applied. +- `pad: bool = true` : Whether to pad the number with zeros if the + number has fewer digits than the rounding precision. +- `direction: str = "nearest"` : Sets the rounding direction. + - `"nearest"`: Rounding takes place in the usual fashion, rounding to the nearer + number, e.g., 2.34 → 2.3 and 2.36 → 2.4. + - `"down"`: Always rounds down, e.g., 2.38 → 2.3 and 2.30 → 2.3. + - `"up"`: Always rounds up, e.g., 2.32 → 2.4 and 2.30 → 2.3. + + + +### Specifying Uncertainties + +There are two ways of specifying uncertainties: +- Applying an uncertainty to the least significant digits using parentheses, e.g., `2.3(4)`, +- Denoting an absolute uncertainty, e.g., `2.3+-0.4` becomes 2.3±0.4. + +Zero supports both and can convert between these two, so that you can pick the displayed style (configured via `uncertainty-mode`, see above) independently of the input style. + +How do uncertainties interplay with exponents? The uncertainty needs to come first, and the exponent applies to both the mantissa and the uncertainty, e.g., `num("1.23+-.04e2")` becomes + + (1.23±0.04)×10². + +Note that the mantissa is now put in parentheses to disambiguate the application of the power. + +In some cases, the uncertainty is asymmetric which can be expressed via `num("1.23+0.02-0.01")` + +$$ 1.23^{+0.02}_{-0.01}. $$ + +--- + +## Table Alignment + +In scientific publication, presenting many numbers in a readable fashion can be a difficult discipline. A good starting point is to align numbers in a table at the decimal separator. With Zero, this can be easily accomplished by simply applying a show-rule to `table`. + + +```typ +#{ + show table: zero.format-table(none, auto, auto) + table( + columns: 3, + align: center, + $n$, $α$, $β$, + [1], [3.45], [-11.1], + .. + ) +} +``` + +

+ + + + Number alignment in tables + +

+ +Arguments to `format-table` are `none`, `auto`, or dictionaries (see [below](#advanced-table-options)) that turn on or off number alignment for individual columns. In the example above, we activate number alignment for the second and third column. + +Be careful to scope the show-rule with curly `{}` or square `[]` brackets to avoid applying the rule twice when changing format for the next table. You can use this in your figures in the following way: +```typ +#figure( + { + show table: zero.format-table(..) + table(..) + }, + caption: [] +) +``` +Through this show-rule, Zero can interoperate seamlessly with many other table packages for Typst. + +Nevertheless, Zero also provides the function `ztable` that you can use as a drop-in replacement for the standard table function. It features an additional parameter `format` which takes an array of `none`, `auto`, or `dictionary` values to turn on number alignment for specific columns. With `ztable` the above example can be recreated like this: +```typ +#ztable( + columns: 3, + align: center, + format: (none, auto, auto), + $n$, $α$, $β$, + [1], [3.45], [-11.1], + .. +) +``` + +### Protect Non-Numerical Content + +Non-number entries (e.g., in the header) are automatically recognized in some cases and will not be aligned. In ambiguous cases, adding a leading or trailing space tells Zero not to apply alignment to this cell, e.g., `[Angle ]` instead of `[Angle]`. + + +In addition, you can prefix or suffix a numeral with content wrapped by the function `nonum[]` to mark it as _not belonging to the number_. The remaining content may still be recognized as a number and formatted/aligned accordingly. +```typ +#ztable( + format: (auto,), + [#nonum[€]123.0#nonum(footnote[A special number])], + [12.111], +) +``` + +

+ + + + Avoid number recognition in tables + +

+ + +### Advanced Table Options + +Zero not only aligns numbers at the decimal point but also at the uncertainty and exponent part. Moreover, by passing a `dictionary` instead of `auto`, a set of `num()` arguments to apply to all numbers in a column can be specified. + +```typ +#ztable( + columns: 4, + align: center, + format: (none, auto, auto, (digits: 1)), + $n$, $α$, $β$, $γ$, + [1], [3.45e2], [-11.1+-3], [0], + .. +) +``` + +

+ + + + Advanced number alignment in tables + +

+ +--- + +## Units and Quantities + +Numbers are frequently displayed together with a (physical) unit forming a so-called _quantity_. Zero has built-in support for formatting quantities through the `zi` module. + +Zero takes a different approach to units than other packages: In order to avoid repetition ([DRY principle](https://de.wikipedia.org/wiki/Don%E2%80%99t_repeat_yourself)) and to avoid accidental errors, every unit is +- first _declared_ (or already predefined) +- and then used as a function to produce a quantity. + +Take a look at the example below: +```typ +#import "@preview/zero:0.5.0": zi + +#let kgm-s2 = zi.declare("kg m/s^2") + +- The current world record for the 100 metres is held by Usain Bolt with #zi.s[9.58]. +- The velocity of light is #zi.m-s[299792458]. +- A Newton is defined as #kgm-s2[1]. +- The unit of a frequency is #zi.Hz(). +``` +

+ + + + Units and quantities + +

+ + +### Declaring a New Unit + +All common single units as well as a few frequent combinations have been predefined in the `zi` module. + +You can create a new unit through the `zi.declare` function. We recommend the following naming convention to uniquely assign a variable name to the unit. + +

+ + + + Declaring new units + +

+ +For most units, it will suffice to use the string unit syntax shown above because mostly latin letters are used. For the µ symbol, you can write "mu", as in `zi.declare("mus")` for microseconds. + +If you need to build more complex units, consisting of symbols, math, or other content, you can use the alternative construction method. Here, each base unit is passed as a positional argument to `zi.declare`: either just the content if (the exponent is 1) or a pair of content and exponent. +```typ +#zi.declare($M_dot.circle$, ("s", -2)) +``` + + +### Configuring Units +The appearance of units can be configured via `set-unit`: +```typ +#set-unit( + unit-separator: content = sym.space.thin, + fraction: str = "power", + breakable: bool = false +) +``` +- `unit-separator: content` : Configures the separator between consecutive unit parts in a composite unit. +- `fraction: str` : Configures the appearance of fractions when there are units present in the denominator. Possible options are + - `"power"` : Units with negative exponents are shown as powers. + - `"fraction"` : When units with negative exponents are present, a fraction is created and the concerned units are put in the denominator. + - `"inline"` : An inline fraction is created. +- `breakable: bool` : Whether units and quantities can be broken across paragraph lines. + +These options are also available when instancing a quantity, e.g., `#zi.m(fraction: "inline")[2.5]`. + +Note that the configuration made through `set-num` also affects the numeral of a quantity. + + +--- + +## Zero for Third-party Packages + +This package provides some useful extras for third-party packages that generate formatted numbers (for example graphics libraries). + +Instead of passing a `str` to `num()`, it is also possible to pass a dictionary of the form +```typ +( + mantissa: str | int | float, + e: none | str, + pm: none | array +) +``` +This way, parsing the number can be avoided which makes especially sense for packages that generate numbers (e.g., tick labels for a diagram axis) with independent mantissa and exponent. + +Furthermore, `num()` also allows `array` arguments for `number` which allows for more efficient batch-processing of numbers with the same setup. In this case, the caller of the function needs to provide `context`. + +Lastly, the function `align-columns` can be used to format and align an array of numerals into a single column. The returned array of items can be used to fill a column of a `table` or `stack`. Also here, the caller of the function needs to provide `context`. + + +## Changelog + +### Version 0.5.0 +- Adds a new unit construction method that allows for more complex units involving math, symbols or basically anything. +- ⚠️ Breaking Change: The rounding setup has been streamlined. The `mode` can no longer be `none`, instead it defaults to `"places"` while the default precision is `none` meaning that no rounding is applied. This is more convenient when rounding single numbers (e.g., `num(round: (precision: 2))[9.80665]`) because the mode does not have to be set repeatedly. + + +### Version 0.4.0 +_Units and quantities_ +- Adds the `zi` module for unit and quantity formatting. +- Adds new way of applying table alignment via show-rules for seamless interoperability with other table packages. +- Adds option to configure the group threshold individually for the integer and fractional part. +- Fixes numbers in RTL direction context. +- Fixes `figure.kind` detection of`ztable`. +- Fixes direct usage of `nonum` in `ztable`. +- Fixes uncertainties in combination with a `fixed` exponent. + +### Version 0.3.3 +_Fix_ +- Fixes an issue with negative numbers in parentheses due to a change in Typst 0.13. + +### Version 0.3.2 +_Fixes and more helpers for third-party package developers_ +- Adds `align-columns` for package developers. +- Fixes issues arising for Typst 0.13. + +### Version 0.3.1 +_Improvements for tables and math-less mode_ +- Fixes `show` rules with `table.cell` for number-aligned cells. +- Improves `math: false` mode: Formatting can now be handled entirely without equations which makes it possible to use Zero with fonts without math support. +- Improves number recognition in tables. A number now needs to start with one of `0123456789+-,.`. This gets rid of many false positives (mostly encountered in header cells). + +### Version 0.3.0 +_Support for non-numerical content in number cells_ +- Adds `nonum[]` function that can be used to mark content in cells as _not belonging to the number_. The remaining content may still be recognized as a number and formatted/aligned accordingly. The content wrapped by `nonum[]` is preserved. +- Fixes number alignment tables with new version Typst 0.12. + +### Version 0.2.0 +_Performance and math-less mode_ +- Adds support for using non-math fonts for `num` via the option `math`. This can be activated by calling `#set-num(math: false)`. +- Performance improvements for both `num()` and `ztable(9)` + +### Version 0.1.0 +_Initial release_ diff --git a/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/align-column.typ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/align-column.typ new file mode 100644 index 0000000..b2b5288 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/align-column.typ @@ -0,0 +1,31 @@ + +#import "num.typ": num +#import "state.typ": num-state + +/// Turns a series of numeral strings into an array of aligned numbers. +/// This can be used to generate cells for a column in a `table` or `stack`. +/// +/// This function requires context from the caller. +/// +/// -> array +#let align-column( + /// Numerals and named options to pass on to `num`. + /// -> str | int | float | content + ..args +) = { + let state = num-state.get() + let numbers = args.pos().map(num.with(align: "components", state: state, ..args.named())) + let widths = numbers.map(components => + components.map(x => if x == none { 0pt } else { measure(x).width }) + ) + let max-widths = array.zip(..widths).map(col => calc.max(..col)) + // return args.pos().map( + // num.with(align: (col-widths: max-widths, col: 0), state: state, ..args.named()) + // ) + return numbers.map(components => + components + .zip(max-widths, (right, left, left, left)) + .map(((body, width, alignment)) => box(width: width, align(alignment, body))) + .join() + ) +} \ No newline at end of file diff --git a/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/assertations.typ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/assertations.typ new file mode 100644 index 0000000..b2fd811 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/assertations.typ @@ -0,0 +1,46 @@ + +/// Check that a given value is one of the given string options +/// and prints a suitable error message suggesting the possible +/// values. +#let assert-option( + /// Value to check -> any + value, + /// Name of the parameter -> str + name, + /// Possible options -> array + options +) = { + if value not in options { + options = options.map(x => "\"" + x + "\"") + if options.len() == 2 { + options = options.join(" or ") + } else { + options = options.slice(0, -1).join(", ") + ", or " + options.last() + } + assert( + false, message: "Expected " + options + " for `" + name + "`, got " + "\"" + value + "\"" + ) + } +} + + +/// Checks that a given set of `arguments` contains only named +/// arguments contained in `dict`. Otherwise, the function panicks +/// with a suitable error message. +#let assert-settable-args(args, dict, name: none) = { + if args.pos().len() != 0 { + let message = "Unexpected argument: " + repr(args.pos().first()) + if name != none { + message += " in `" + name + "`" + } + assert(false, message: message) + } + for (arg, _) in args.named() { + if arg in dict { continue } + let message = "Unexpected argument: " + arg + if name != none { + message += " in `" + name + "`" + } + assert(false, message: message) + } +} diff --git a/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/formatting.typ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/formatting.typ new file mode 100644 index 0000000..c56aa04 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/formatting.typ @@ -0,0 +1,321 @@ +#import "state.typ": num-state +#import "parsing.typ": * + + +#let sequence-constructor = $$.body.func() + +/// Creates an equation from a sequence. This function leaves the +/// `block` attribute unset. +#let make-equation(sequence) = { + math.equation(sequence-constructor(sequence)) +} + +#assert.eq(make-equation((sym.minus, [2])).body, $-2$.body) + + + +/// Formats a sign. If the sign is the ASCII character "-", the minus +/// unicode symbol "−" is returned. Otherwise, "+" is returned but only +/// if `positive-sign` is set to true. In all other cases, the result is +/// `none`. +#let format-sign(sign, positive-sign: false) = { + if sign == "-" { return "−" } + else if sign == "+" and positive-sign { return "+" } +} + +#assert.eq(format-sign("-", positive-sign: false), "−") +#assert.eq(format-sign("+", positive-sign: false), none) +#assert.eq(format-sign("-", positive-sign: true), "−") +#assert.eq(format-sign("+", positive-sign: true), "+") +#assert.eq(format-sign(none, positive-sign: true), none) + + + +/// Inserts group separators (e.g., thousand separators if `group-size` is 3) +/// into a sequence of digits. +/// - x (str): Input sequence. +/// - invert (bool): If `false`, the separators are inserted counting from +/// right-to-left (as customary for integers), if `true`, they are inserted +/// from left-to-right (for fractionals). +#let insert-group-separators( + x, + invert: false, + threshold: 5, + size: 3, + separator: sym.space.thin +) = { + if type(threshold) == dictionary { + assert( + threshold.keys().sorted() == ("fractional", "integer"), + message: "group.threshold expects either an int or a dictionary with the keys \"fractional\" and \"integer\"" + ) + if invert { + threshold = threshold.fractional + } else { + threshold = threshold.integer + } + } + if x.len() < threshold { return x } + + if not invert { x = x.rev() } + let chunks = x.codepoints().chunks(size) + if not invert { chunks = chunks.rev().map(array.rev) } + return chunks.intersperse(separator).flatten().join() +} + +#assert.eq(insert-group-separators("123"), "123") +#assert.eq(insert-group-separators("1234"), "1234") +#assert.eq(insert-group-separators("12345", separator: " "), "12 345") +#assert.eq(insert-group-separators("123456", separator: " "), "123 456") +#assert.eq(insert-group-separators("1234567", separator: " "), "1 234 567") +#assert.eq(insert-group-separators("12345678", separator: " "), "12 345 678") +#assert.eq(insert-group-separators("12345678", separator: " ", size: 2), "12 34 56 78") +#assert.eq(insert-group-separators("1234", separator: " ", threshold: 3), "1 234") + +#assert.eq(insert-group-separators("1234", separator: " ", threshold: 3, invert: true), "123 4") +#assert.eq(insert-group-separators("1234567", separator: " ", threshold: 3, invert: true), "123 456 7") +#assert.eq(insert-group-separators("1234567", separator: " ", size: 2, threshold: 3, invert: true), "12 34 56 7") + + + +#let contextual-group(x, invert: false) = { + insert-group-separators(x, invert: invert) +} + + +/// Attaches sub-/super-script values using text mode, while allowing for +/// stacking of a combined sub-script and super-script. +/// - body (content): Content to attach to +/// - t (content): Superscript value +/// - b (content): Subscript value +#let non-math-attach(body, t: none, b: none) = { + t = if t != none { super(typographic: false, t) } + b = if b != none { sub(typographic: false, b) } + if t != none and b != none { + let width = calc.max(measure(t).width, measure(b).width) + body + box(width: width, sym.zws + place(top, sym.zws + t) + place(top, sym.zws + b)) + } else { + body + t + b + } +} + + +/// Takes a sequence of digits and returns a new sequence of length `digits`. +/// If the input sequence is too short, a corresponding number of trailing +/// zeros is appended. Exceeding inputs are truncated. +#let fit-decimals(x, digits) = { + let len = x.len() + if len == digits or digits == auto { return x } + if len < digits { return x + "0" * (digits - len) } + if len > digits { return x.slice(0, digits) } +} + +#assert.eq(fit-decimals("345", 3), "345") +#assert.eq(fit-decimals("345", 4), "3450") +#assert.eq(fit-decimals("345", 2), "34") + + + + +#let format-integer = it => { + // int, group + if type(it.group) == dictionary and it.int != none { it.int = insert-group-separators(it.int, ..it.group) } + if it.int == "" { it.int = "0" } + it.int +} + + + +#let format-fractional = it => { + // frac, group, digits, decimal-separator? + let frac = fit-decimals(it.frac, it.digits) + if frac.len() == 0 { return none } + if type(it.group) == dictionary { frac = insert-group-separators(frac, invert: true, ..it.group) } + it.decimal-separator + frac +} + + + +#let format-comma-number = it => { + // sign, int, frac, digits, group, positive-sign + let frac = format-fractional((frac: it.frac, group: it.group, digits: it.digits, decimal-separator: it.decimal-separator)) + + return format-sign(it.sign, positive-sign: it.positive-sign) + format-integer((int: it.int, group: it.group)) + frac +} + + + +#let format-uncertainty = it => { + /// pm, digits, mode, concise, tight, math + let pm = it.pm + if pm == none { return () } + let is-symmetric = type(pm.first()) != array + if is-symmetric { pm = (pm,) } + + if it.concise { + let compact-pm = ( + it.mode == "compact" or + (it.mode == "compact-separator" and pm.map(x => x.first().trim("0")).all(x => x.len() == 0)) + ) + + if compact-pm { + pm = pm.map(x => utility.shift-decimal-left(..x, -it.digits)) + it.digits = auto + } + } + + pm = pm.map(((int, frac)) => + format-comma-number(( + sign: none, int: int, frac: frac, digits: it.digits, group: false, positive-sign: false, decimal-separator: it.decimal-separator + )) + ) + if is-symmetric { + if it.concise { ("(", pm.first(), ")") } + else if it.math { + ( + math.class("normal", none), + math.class(if it.tight {"normal"} else {"binary"}, sym.plus.minus), + pm.first() + ) + } else { + let space = if not it.tight { sym.space.thin } + ( + space, sym.plus.minus, space, + pm.first() + ) + } + } else if it.math { + ( + math.attach( + none, + t: $+#pm.at(0)$, + b: $-#pm.at(1)$ + ), + ) + } else { + ( + non-math-attach( + none, + t: "+" + pm.at(0), + b: "−" + pm.at(1) + ), + ) + } +} + + + +#let format-power = it => { + /// x, base, product, positive-sign-exponent, tight, math + if it.exponent == none { return () } + + let (sign, integer, fractional) = decompose-signed-float-string(it.exponent) + let exponent = format-comma-number((sign: sign, int: integer, frac: fractional, digits: auto, group: false, positive-sign: it.positive-sign-exponent, decimal-separator: it.decimal-separator)) + + if it.math { + let power = math.attach([#it.base], t: [#exponent]) + if it.product == none { (power,) } + else { + ( + box(), + math.class(if it.tight {"normal"} else {"binary"}, it.product), + power + ) + } + } else { + let power = non-math-attach([#it.base], t: [#exponent]) + if it.product == none { (power,) } + else { + let space = if not it.tight { sym.space.thin } + ( + box(), + space, it.product, space, + power + ) + } + } +} + + + +#let show-num-impl = it => { + /// sign, int, frac, e, pm, + /// digits + /// omit-unity-mantissa, uncertainty-mode, positive-sign + + let omit-mantissa = ( + it.omit-unity-mantissa and it.int == "1" and + it.frac == "" and it.e != none and it.pm == none and it.digits == 0 + ) + + assert( + it.uncertainty-mode in ("separate", "compact", "compact-separator"), + message: "The uncertainty-mode can be one of \"separate\", \"compact\", \"compact-separator\", got " + repr(it.uncertainty-mode) + ) + let concise-uncertainty = it.uncertainty-mode != "separate" + + + + + let integer = ( + sign: it.sign, + int: if omit-mantissa { none } else { it.int }, + decimal-separator: it.decimal-separator, + group: it.group + ) + + + let uncertainty = ( + pm: it.pm, + digits: it.digits, + concise: concise-uncertainty, + tight: it.tight, + math: it.math, + mode: it.uncertainty-mode, + decimal-separator: it.decimal-separator + ) + + + let power = ( + exponent: it.e, + base: it.base, + product: if omit-mantissa {none} else {it.product}, + positive-sign-exponent: it.positive-sign-exponent, + tight: it.tight, + math: it.math, + decimal-separator: it.decimal-separator + ) + + let integer-part = ( + format-sign(it.sign, positive-sign: it.positive-sign), + format-integer(integer), + ) + + let fractional-part = ( + format-fractional((frac: it.frac, group: it.group, digits: it.digits, decimal-separator: it.decimal-separator)), + ) + + let uncertainty-part = format-uncertainty(uncertainty) + + if concise-uncertainty { + fractional-part += uncertainty-part + uncertainty-part = () + } + + + if it.pm != none and (it.e != none or it.fpau) and not concise-uncertainty { + integer-part = ("(",) + integer-part + uncertainty-part.push(")") + } + + let result = ( + integer-part, + fractional-part, + uncertainty-part, + format-power(power), + ) + return result +} + + + diff --git a/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/impl.typ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/impl.typ new file mode 100644 index 0000000..6ac57ce --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/impl.typ @@ -0,0 +1,5 @@ +// Access to implementation for third-party packages. Note: this API may be unstable. +#import "formatting.typ" +#import "parsing.typ" +#import "utility.typ" +#import "rounding.typ" \ No newline at end of file diff --git a/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/num.typ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/num.typ new file mode 100644 index 0000000..4305856 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/num.typ @@ -0,0 +1,243 @@ +#import "state.typ": num-state, update-num-state +#import "formatting.typ": * +#import "rounding.typ": * +#import "assertations.typ": * +#import "parsing.typ" as parsing: nonum + +#let update-state(state, args, name: none) = { + state.update(s => { + assert-settable-args(args, s, name: name) + s + args.named() + }) +} + + +#let set-num(..args) = update-state(num-state, args, name: "set-num") + +#let set-group(..args) = { + num-state.update(s => { + assert-settable-args(args, s.group, name: "set-group") + s.group += args.named() + s + }) +} + +#let set-round(..args) = { + num-state.update(s => { + assert-settable-args(args, s.round, name: "set-round") + s.round += args.named() + s + }) +} + + +#let contextual-round(int, frac, pm, round-state) = { + round( + int, frac, + mode: round-state.mode, + precision: round-state.precision, + direction: round-state.direction, + pad: round-state.pad, + pm: pm + ) +} + + + +#let show-num = it => { + + // Process input + let info + if type(it.number) == dictionary { + info = it.number + if "mantissa" in info { + let mantissa = info.mantissa + if type(mantissa) in (int, float) { mantissa = str(mantissa).replace("−", "-") } + let (sign, int, frac) = parsing.decompose-signed-float-string(mantissa) + info += (sign: sign, int: int, frac: frac) + } + if "sign" not in info {info.sign = "" } + } else { + let num-str = number-to-string(it.number) + if num-str == none { + assert(false, message: "Cannot parse the number `" + repr(it.number) + "`") + } + info = decompose-normalized-number-string(num-str) + } + + /// Maybe shift exponent + if it.fixed != none { + let e = if info.e == none { 0 } else { int(info.e) } + let shift(int, frac) = utility.shift-decimal-left(int, frac, it.fixed - e) + (info.int, info.frac) = shift(info.int, info.frac) + + if info.pm != none { + if type(info.pm.first()) != array { + info.pm = shift(..info.pm) + } else { + info.pm = pm.map(x => shift(..x)) + } + } + + info.e = str(it.fixed).replace("−", "-") + } + + /// Round number and uncertainty + if it.round.precision != none { + (info.int, info.frac, info.pm) = contextual-round(info.int, info.frac, info.pm, it.round) + } + + let digits = if it.digits == auto { info.frac.len() } else { it.digits } + if digits < 0 { assert(false, message: "`digits` needs to be positive, got " + str(digits)) } + + if info.pm != none { + let pm = info.pm + if type(pm.first()) != array { pm = (pm,) } + digits = calc.max(digits, ..pm.map(array.last).map(str.len)) + } + + // info.digits = digits + it.digits = digits + + // Format number + let components = show-num-impl(info + it) + let collect = if it.math { make-equation } else { it => it.join() } + + if it.align == none { + set text(dir: ltr) + it.prefix + collect(components.join()) + it.suffix + } else if it.align == "components" { + components.map(c => { set text(dir: ltr); collect(c) }) + } else { + set text(dir: ltr) + + let (col-widths, col) = it.align + let components = components.map(x => if x == () { none } else { collect(x) }) + components.at(0) = it.prefix + components.at(0) + if it.suffix != none { + if components.at(2) == none and components.at(3) == none { + components.at(1) += it.suffix + } else { + components.at(3) += it.suffix + } + } + let widths = components.map(x => if x == none { 0pt } else { measure(x).width }) + + if col-widths != auto { + for i in range(4) { + let alignment = if i == 0 { right } else { left } + let content = align(alignment, components.at(i)) + components.at(i) = box(width: col-widths.at(i), content) + } + } + + [#components.join()#metadata((col,) + widths)<__pillar-num__>] + } +} + + +#let num( + number, + align: none, + state: auto, + prefix: none, + suffix: none, + force-parentheses-around-uncertainty: false, + ..args +) = { + let inline-args = ( + align: align, + prefix: prefix, + suffix: suffix, + fpau: force-parentheses-around-uncertainty, + ) + + if type(number) == array { + let named = args.named() + let num-state = if state == auto { num-state.get() } else { state } + let it = num-state + inline-args + args.named() + return number.map(n => show-num(it + (number: n))) + } + + if state != auto { + let it = update-num-state(state, args.named()) + inline-args + (number: number) + return show-num(it) + } + context { + let it = update-num-state(num-state.get(), args.named()) + inline-args + (number: number) + show-num(it) + } +} + + + + + + + + + +/* + +- Mantissa (value or uncertainty): Doesn't really need a show rule? +- Power: A rule would be beneficial. Does the multiplier need to be included? + + +Question: How to pass values on to the nested types + +#set num.power(base: [5]) +and then +#num(power: (base: [4]), "1.23e4") + + +show num.power: it => { + - it.exponent [2.3] + - it.base [10] + - it.multiplier [×] ?? + + math.attach([#it.base], t: [#it.exponent]) + + or + + ( + box(), + it.multiplier, + math.attach([#it.base], t: [#it.exponent]) + ) +} + + +show num.exponent: it => { + - it.sign + - it.integer + - it.fractional + + it.sign + format-integer(it.integer) + format-fractional(it.fractional).join() +} + + +show num.uncertainty: it => { + - it.value ([0.2, 0.4]) + - it.mode + - it.symmetric false + if it.mode == ... + + if it.symmetric { + return ( + math.class("normal", none), + math.class(if state.tight {"normal"} else {"binary"}, sym.plus.minus), + it.value + ) + } else { + math.attach(none, t: sym.plus + it.value.at(0), b: sym.minus + it.value.at(1)), + } +} + +show num.num: it => { + +} + + + +*/ + diff --git a/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/parsing.typ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/parsing.typ new file mode 100644 index 0000000..90c9c86 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/parsing.typ @@ -0,0 +1,249 @@ +#import "utility.typ" + +/// Converts a content value into a string if it contains only text nodes. +/// Otherwise, `none` is returned. +#let content-to-string(x) = { + if x.has("text") { return x.text } + if x.has("children") and x.children.len() != 0 and x.children.all(x => x.has("text")) { + return x.children.map(x => x.text).join() + } + return none +} + +#assert.eq(content-to-string([123]), "123") +#assert.eq(content-to-string([123 231.]), "123 231.") +#assert.eq(content-to-string([123] + [34]), "12334") +#assert.eq(content-to-string([a $a$]), none) + + +/// Converts a content value into a string if it contains only text nodes. +/// Otherwise, `none` is returned. +#let content-to-string-table(x) = { + let prefix = none + let suffix = none + if x.has("text") { return (x.text, prefix, suffix) } + if x.has("children") and x.children.len() != 0 { + if x.children.all(x => x.has("text")){ + return (x.children.map(x => x.text).join(), prefix, suffix) + } + let main = none + for child in x.children { + if child.has("text") { main += child.text } + else if child.func() == highlight { + if main == none { prefix = child.body } + else { suffix = child.body } + } + else { return none } + } + return (main, prefix, suffix) + } + return none +} + +#let nonum = highlight +#assert.eq(content-to-string-table[alpha ], none) +#assert.eq(content-to-string-table[#nonum[€]12], ("12", [€], none)) +#assert.eq(content-to-string-table[#nonum[€]12.43#nonum[#footnote[1]]], ("12.43", [€], footnote[1])) + +/// Converts a number into a string if the input is either +/// - an integer or a float, +/// - a string, +/// - or a content value that contains only text nodes but does not start +/// or end with a space. +/// In case of a failure, `none` is returned. +/// The output is normalized, meaning that both decimals separators "," and +/// "." are unified to "." and the minus symbol "−" is replaced by the +/// ASCII "-" character. +#let number-to-string(number) = { + let result + if type(number) == str { result = number } + else if type(number) in (int, float) { result = str(number) } + else if type(number) == content { result = content-to-string(number) } + else { result = none } + if result == none { return none } + return result.replace(",", ".").replace("−", "-") +} + +#let number-to-string-table(number) = { + let result + if type(number) == str { result = number } + else if type(number) in (int, float) { result = str(number) } + else if type(number) == content { result = content-to-string-table(number) } + else { result = none } + if result == none { return none } + if type(result) != array { result = (result, none, none) } + result.at(0) = result.at(0).replace(",", ".").replace("−", "-") + if result.len() == 0 or result.at(0).at(0) not in "0123456789+-." { + return none + } + return result +} + +#assert.eq(number-to-string("123"), "123") +#assert.eq(number-to-string("-2.0"), "-2.0") +#assert.eq(number-to-string("−" + "2,0"), "-2.0") +#assert.eq(number-to-string[2], "2") +#assert.eq(number-to-string[-2.1], "-2.1") +#assert.eq(number-to-string[-2.], "-2.") +#assert.eq(number-to-string[2.], "2.") +#assert.eq(number-to-string[-.1], "-.1") +#assert.eq(number-to-string(100), "100") +#assert.eq(number-to-string(-101.24), "-101.24") + +// unparsable inputs +#assert.eq(number-to-string[], none) +#assert.eq(number-to-string[$a + b$], none) +#assert.eq(number-to-string[2 ], none) +#assert.eq(number-to-string[ 2], none) +#assert.eq(number-to-string[ 2343.23 ], none) +#assert.eq(str(sym.plus), "+") + + + +/// Decomposes a string representing an unsigned floating point into +/// integer and fractional part. If either part is not present, it is +/// returned as an empty string. Returns `(integer, fractional)`. +/// Expects a normalized input string (see @number-to-string). +/// +/// *Example:* +/// #example(`decompose-unsigned-float-string("9.81")` +#let decompose-unsigned-float-string(x) = { + let components = x.split(".") + if components.len() == 1 { components.push("") } + else if components.len() > 2 {assert(false, message: "weird number `" + x + "`")} + components +} + +#assert.eq(decompose-unsigned-float-string("23.2"), ("23", "2")) +#assert.eq(decompose-unsigned-float-string("23."), ("23", "")) +#assert.eq(decompose-unsigned-float-string("23"), ("23", "")) +#assert.eq(decompose-unsigned-float-string(".34"), ("", "34")) + + + +/// Decomposes a string representing a (possibly signed) floating point +/// into integer and fractional part. If either part is not present, it +/// is returned as an empty string. Returns `(sign, integer, fractional)` +/// where the sign is either `"+"` or `"-"`. +/// Expects a normalized input string (see @number-to-string). +/// +/// *Example:* +/// #example(`decompose-signed-float-string("-9.81")` +#let decompose-signed-float-string(x) = { + let sign = "+" + if x.starts-with("-") { + sign = "-" + x = x.slice(1) + } else if x.starts-with("+") { x = x.slice(1) } + return (sign, ) + decompose-unsigned-float-string(x) +} + +#assert.eq(decompose-signed-float-string("23.2"), ("+", "23", "2")) +#assert.eq(decompose-signed-float-string("+23."), ("+", "23", "")) +#assert.eq(decompose-signed-float-string("-23"), ("-", "23", "")) +#assert.eq(decompose-signed-float-string("-.34"), ("-", "", "34")) +#assert.eq(decompose-signed-float-string(sym.plus + ".34"), ("+", "", "34")) + + + +/// Decomposes a normalized number string into sign, integer, fractional, +/// uncertainty and exponent. Here, normalized means that the decimal separator +/// is `"."`, and `"+"`, `"-"` is used for all signs (as opposed to +/// `"−"`). +/// +/// Sign, integer and fractional part are guaranteed to be valid (however, +/// the latter two may be empty strings) while the uncertainty and the +/// exponent may be none if not present. +/// +/// +/// *Example:* +/// #example(`decompose-normalized-number-string("-10.2+-.3e3")`) +#let decompose-normalized-number-string(x) = { + let original-number = x + let e + let pm + let sign = "+" + if "e" in x { + let components = x.split("e") + if components.len() > 2 { + assert(false, message: "Error while parsing `" + original-number + "`: Asymmetric uncertainties must be specified on both sides") + } + (x, e) = components + } + if x.starts-with("-") { + sign = "-" + x = x.slice(1) + } else if x.starts-with("+") { x = x.slice(1) } + + let normalize-pm = false + let pm-count = int("+" in x) + int("-" in x) + if pm-count == 2 { + if "+-" in x { (x, pm) = x.split("+-") } + else { + let p + let m + (x, m) = x.split("-") + assert("+" in x, message: "Error while parsing `" + original-number + "`: Asymmetric uncertainties must start with the positive component") + (x, p) = x.split("+") + pm = (p, m) + } + } else if pm-count == 1 { + assert(false, message: "Error while parsing `" + original-number + "`: Asymmetric uncertainties must be specified on both sides") + } else if "(" in x { + (x, pm) = x.split("(") + assert(pm.ends-with(")"), message: "Error while parsing `" + original-number + "`: Unclosed parenthesized uncertainty") + pm = pm.trim(")") + normalize-pm = true + } + let (integer, fractional) = decompose-unsigned-float-string(x) + if pm != none { + if type(pm) == array { + pm = pm.map(decompose-unsigned-float-string) + } else { + pm = decompose-unsigned-float-string(pm) + } + + if normalize-pm { + pm = utility.shift-decimal-left(..pm, fractional.len()) + } + } + return (int: integer, frac: fractional, sign: sign, pm: pm, e: e) +} + + +#assert.eq( + decompose-normalized-number-string("-10e3"), + (sign: "-", int: "10", frac: "", pm: none, e: "3") +) +#assert.eq( + decompose-normalized-number-string("+2.4+-0.1"), + (sign: "+", int: "2", frac: "4", pm: ("0", "1"), e: none) +) +#assert.eq( + decompose-normalized-number-string("+.4+0.1-0.2e-10"), + (sign: "+", int: "", frac: "4", pm: (("0", "1"), ("0", "2")), e: "-10") +) +#assert.eq( + decompose-normalized-number-string(".4(2)"), + (sign: "+", int: "", frac: "4", pm: ("", "2"), e: none) +) +#assert.eq( + decompose-normalized-number-string(".4333(2)"), + (sign: "+", int: "", frac: "4333", pm: ("", "0002"), e: none) +) +#assert.eq( + decompose-normalized-number-string(".4333(200)"), + (sign: "+", int: "", frac: "4333", pm: ("", "0200"), e: none) +) +#assert.eq( + decompose-normalized-number-string(".43(200)"), + (sign: "+", int: "", frac: "43", pm: ("2", "00"), e: none) +) +#assert.eq( + decompose-normalized-number-string("2(2)"), + (sign: "+", int: "2", frac: "", pm: ("2", ""), e: none) +) +#assert.eq( + decompose-normalized-number-string("2.3(2.9)"), + (sign: "+", int: "2", frac: "3", pm: ("", "29"), e: none) +) diff --git a/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/rounding.typ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/rounding.typ new file mode 100644 index 0000000..1f0fc2c --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/rounding.typ @@ -0,0 +1,209 @@ +#import "assertations.typ": * + +#let count-leading-zeros(integer-string) = { + integer-string.len() - integer-string.trim("0", at: start).len() +} + + +/// Rounds an integer given as a string of digits to a given digit place. +/// The rounding direction may be `"nearest"`, `"up"`, or `"down"`. +#let round-integer(num-string, digit, dir: "nearest") = { + if digit == 0 { return "" } + if dir == "down" { + return num-string.slice(0, digit) + } else if dir == "up" { + let x = float(num-string.slice(0, digit) + "." + num-string.slice(digit)) + num-string = str(int(calc.ceil(x))) + } else if dir == "nearest" { + let x = float(num-string.slice(0, digit) + "." + num-string.slice(digit)) + num-string = str(int(calc.round(x))) + } + if digit > num-string.len() { + num-string = "0" * (digit - num-string.len()) + num-string + } + return num-string +} + +#assert.eq(round-integer("123", 2, dir: "down"), "12") +#assert.eq(round-integer("123", 1, dir: "down"), "1") +#assert.eq(round-integer("9989823", 7, dir: "down"), "9989823") +#assert.eq(round-integer("123", 0, dir: "down"), "") + +#assert.eq(round-integer("120", 2, dir: "up"), "12") +#assert.eq(round-integer("123", 2, dir: "up"), "13") +#assert.eq(round-integer("1200000000002", 2, dir: "up"), "13") +#assert.eq(round-integer("999000003", 3, dir: "up"), "1000") + +#assert.eq(round-integer("2234", 1, dir: "nearest"), "2") +#assert.eq(round-integer("2234", 0, dir: "nearest"), "") +#assert.eq(round-integer("0022", 3, dir: "nearest"), "002") +#assert.eq(round-integer("0395", 3, dir: "nearest"), "040") +#assert.eq(round-integer("999", 2, dir: "nearest"), "100") + + + +/// Rounds or pads a number given by an integer part and a fractional part +/// to a given number of total digits (including the integer digits). The +/// rounding direction may be `"nearest"`, `"up"`, or `"down"`. +/// The number `total-digits` cannot be negative. If it exceeds the number +/// of available digits and `pad` is set to `true`, the number is padded +/// with zeros. +#let round-or-pad(int, frac, total-digits, dir: "nearest", pad: true) = { + total-digits = calc.max(0, total-digits) + let number = int + frac + if total-digits < number.len() { + number = round-integer(number, total-digits, dir: dir) + let new-int-digits = int.len() + number.len() - total-digits + if total-digits < int.len() { + number += "0" * (int.len() - total-digits) + } + + int = number.slice(0, new-int-digits) + frac = number.slice(new-int-digits) + } else if pad { + frac += "0" * (total-digits - number.len()) + } + return (int, frac) +} + + + +/// Rounds (or pads) a number given by an integer part and a fractional part. +/// Different modes are supported. +#let round( + /// Integer part. -> str + int, + /// Fractional part. -> str + frac, + /// Rounding mode. + /// - `"places"`: The number is rounded to the number of places after the + /// decimal point given by the `precision` parameter. + /// - `"figures"`: The number is rounded to a number of significant figures. + /// - `"uncertainty"`: Requires giving the uncertainty. The uncertainty is rounded + /// to significant figures given by the `precision` argument and then the number + /// is rounded to the same number of places as the uncertainty. + mode: none, + /// The precision to round to. See parameter `mode` for the different modes. -> int + precision: 2, + /// Rounding direction. + /// - `"nearest"`: Rounding takes place in the usual fashion, rounding to the nearer + /// number, e.g., 2.34 -> 2.3 and 2.36 -> 2.4. + /// - `"down"`: Always rounds down, e.g., 2.38 -> 2.3, 2.30 -> 2.3. + /// - `"up"`: Always rounds up, e.g., 2.32 -> 2.4, 2.30 -> 2.3. + /// -> str + direction: "nearest", + /// Determines whether the number should be padded with zeros if the number has less + /// digits than the rounding precision. + /// -> bool + pad: true, + /// Uncertainty + pm: none +) = { + if mode == none { return (int, frac, pm) } + if mode == "uncertainty" and pm == none { return (int, frac, pm) } + + + + assert-option(mode, "round-mode", ("places", "figures", "uncertainty")) + assert-option(direction, "round-direction", ("nearest", "up", "down")) + + let round-digit = precision + if mode == "places" { + round-digit += int.len() + } else if mode == "figures" { + let full-number = int + frac + let leading-zeros = full-number.len() - full-number.trim("0", at: start).len() + round-digit += leading-zeros + } + + if mode == "uncertainty" { + let round-digit-pm + let is-symmetric = type(pm.first()) != array + if is-symmetric { + round-digit-pm = count-leading-zeros(pm.join()) + precision + pm = round-or-pad(..pm, round-digit-pm, dir: direction, pad: true) + round-digit = round-digit-pm + int.len() - pm.first().len() + } else { + let place = calc.max( + ..pm.map(u => count-leading-zeros(u.join()) + precision - u.first().len()) + ) + round-digit = place + int.len() + pm = pm.map(u => round-or-pad(..u, place + u.first().len(), dir: direction, pad: true)) + } + } + + return (..round-or-pad(int, frac, round-digit, dir: direction, pad: pad), pm) +} + + +#let round-places = round.with(mode: "places") +#let round-figures = round.with(mode: "figures") + +#assert.eq(round("23", "5", mode: none), ("23", "5", none)) + +#assert.eq(round-places("1", "234", precision: 3), ("1", "234", none)) +#assert.eq(round-places("1", "234", precision: 2), ("1", "23", none)) +#assert.eq(round-places("1", "234", precision: 1), ("1", "2", none)) +#assert.eq(round-places("1", "234", precision: 0), ("1", "", none)) +#assert.eq(round-places("23", "534", precision: -1), ("20", "", none)) +#assert.eq(round-places("12345", "534", precision: -3), ("12000", "", none)) +#assert.eq(round-places("2", "234", precision: -3), ("0", "", none)) +#assert.eq(round-places("", "0022", precision: 3), ("", "002", none)) + +#assert.eq(round-places("1", "1", precision: 0), ("1", "", none)) +#assert.eq(round-places("1", "1", precision: 3), ("1", "100", none)) +#assert.eq(round-places("1", "1", precision: 5), ("1", "10000", none)) +#assert.eq(round-places("1", "1", precision: 5), ("1", "10000", none)) +#assert.eq(round-places("1", "1", precision: 5, pad: false), ("1", "1", none)) + + +#assert.eq(round-figures("1", "234", precision: 4), ("1", "234", none)) +#assert.eq(round-figures("1", "234", precision: 3), ("1", "23", none)) +#assert.eq(round-figures("1", "234", precision: 2), ("1", "2", none)) +#assert.eq(round-figures("1", "234", precision: 1), ("1", "", none)) +#assert.eq(round-figures("1", "234", precision: 0), ("0", "", none)) +#assert.eq(round-figures("1", "234", precision: -1), ("0", "", none)) + +#assert.eq(round-figures("1", "2", precision: 4), ("1", "200", none)) +#assert.eq(round-figures("1", "2", precision: 4, pad: false), ("1", "2", none)) + +#assert.eq(round-figures("0", "00126", precision: 2), ("0", "0013", none)) +#assert.eq(round-figures("0", "000126", precision: 3), ("0", "000126", none)) + + + +#assert.eq(round-places("99", "92", precision: 2), ("99", "92", none)) +#assert.eq(round-places("99", "92", precision: 0), ("100", "", none)) +#assert.eq(round-places("99", "99", precision: 1), ("100", "0", none)) +#assert.eq(round-places("99", "99", precision: -1), ("100", "", none)) +#assert.eq(round-places("1", "299995", precision: 5), ("1", "30000", none)) +#assert.eq(round-places("1", "299994", precision: 5), ("1", "29999", none)) +#assert.eq(round-places("523", "", precision: -2), ("500", "", none)) + + +#assert.eq(round("42", "3734", pm: ("", "0025"), precision: 2, mode: "uncertainty"), ("42", "3734", ("", "0025"))) +#assert.eq(round("42", "3734", pm: ("", "0025"), precision: 1, mode: "uncertainty"), ("42", "373", ("", "003"))) +#assert.eq(round("42", "3734", pm: ("2", "2"), precision: 1, mode: "uncertainty"), ("42", "", ("2", ""))) +#assert.eq(round("42", "3734", pm: ("2", "2"), precision: 2, mode: "uncertainty"), ("42", "4", ("2", "2"))) +#assert.eq(round("42", "3734", pm: ("2", "2"), precision: 3, mode: "uncertainty"), ("42", "37", ("2", "20"))) + +#assert.eq(round("4211", "3734", pm: ("230", "2"), precision: 1, mode: "uncertainty"), ("4200", "", ("200", ""))) + +#assert.eq(round("1", "23", pm: ("0", "2"), precision: 1, mode: "uncertainty"), ("1", "2", ("0", "2"))) +#assert.eq(round("123", "9", pm: ("020", ""), precision: 1, mode: "uncertainty"), ("120", "", ("020", ""))) +#assert.eq( + round("1", "23", pm: (("0", "2"), ("0", "3")), precision: 1, mode: "uncertainty"), + ("1", "2", (("0", "2"), ("0", "3"))) +) +#assert.eq( + round("123", "9", pm: (("020", ""), ("30", "")), precision: 1, mode: "uncertainty"), + ("120", "", (("020", ""), ("30", ""))) +) +#assert.eq( + round("1", "23", pm: (("0", "24"), ("0", "3")), precision: 1, mode: "uncertainty"), + ("1", "2", (("0", "2"), ("0", "3"))) +) +#assert.eq( + round("1", "23", pm: (("0", "04"), ("0", "3")), precision: 1, mode: "uncertainty"), + ("1", "23", (("0", "04"), ("0", "30"))) +) diff --git a/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/state.typ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/state.typ new file mode 100644 index 0000000..4cfb4d0 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/state.typ @@ -0,0 +1,41 @@ +#let default-state = ( + digits: auto, + fixed: none, + product: sym.times, + decimal-separator: ".", + tight: false, + omit-unity-mantissa: false, + positive-sign: false, + positive-sign-exponent: false, + base: 10, + uncertainty-mode: "separate", + math: true, + group: ( + size: 3, + separator: sym.space.thin, + threshold: 5 + ), + round: ( + mode: "places", + precision: none, + pad: true, + direction: "nearest", + ), + unit: ( + unit-separator: sym.space.thin, + fraction: "power", + breakable: false, + use-sqrt: true + ) +) +#let num-state = state("num-state", default-state) + + + + +#let update-num-state(state, args) = { + if "round" in args { state.round += args.round; args.remove("round") } + if "group" in args { state.group += args.group; args.remove("group") } + if "unit" in args { state.unit += args.unit; args.remove("unit") } + return state + args +} \ No newline at end of file diff --git a/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/units.typ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/units.typ new file mode 100644 index 0000000..07660b6 --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/units.typ @@ -0,0 +1,283 @@ +#import "num.typ": num +#import "state.typ": num-state, update-num-state +#import "assertations.typ": assert-settable-args + + +/// [internal function] +/// Parse a text-based unit specification. +/// - Consecutive units can be separated by a space. +/// (Why is this necessary? in "kg m" the "kg" needs to be close +/// but distinguishable from the "m") +/// - Exponents are allowed as in "m^2" +/// - A unit in the fraction can be specified either with a negative +/// exponent "s^-1" or by adding a slash before "/s" +/// - Prefixes are allowed and should be prepended to the base unit without +/// a space in between. Example: `"/mm^2"`. Occurrences of "mu" will be replaced +/// by the greek mu symbol. +/// Returns: a dictionary with the keys "numerator" and "denominator", +/// both containing a list where each entry is a tuple with the unit symbol +/// as the first element and the exponent as the second element. The exponent +/// is always positive. +#let parse-unit-str(str) = { + str += " " + str = str.replace("mu", "µ") + + let numerator = () + let denominator = () + let unit = "" + let per = false + + let get-symbol-and-exponent(str, per) = { + let pow-index = str.position("^") + if pow-index == none { return (str, "1") } + + let exponent = str.slice(pow-index + 1) + assert(exponent.len() > 0, message: "Invalid unit: missing exponent after \"^\"") + exponent = exponent.trim("(").trim(")") + let symbol = str.slice(0, pow-index) + assert(symbol.len() != 0, message: "Invalid unit: an exponent needs to be preceded by a unit") + return (symbol, exponent) + } + + for c in str { + if c in "/ " { // both "/" and " " terminate the current unit + if unit.len() == 0 { + if c == "/" { per = true } + continue + } + + let (symbol, exponent) = get-symbol-and-exponent(unit, per) + if exponent.starts-with("-") { + per = not per + exponent = exponent.slice(1) + } + exponent = [#exponent] + if unit != "1" { // make calls like "1/s" possible in addition to "/s" + if per { denominator.push((symbol, exponent)) } + else { numerator.push((symbol, exponent)) } + } + per = false + unit = "" + + if c == "/" { per = true } + } else { + unit += c + } + } + + (numerator: numerator, denominator: denominator) +} + +#let parse-unit(..children) = { + children = children.pos() + if children.len() == 1 and type(children.first()) == str { + return parse-unit-str(children.first()) + } + + let numerator = () + let denominator = () + + for child in children { + if type(child) in (str, content, symbol) { + numerator.push((child, [1])) + } else if type(child) == array { + assert( + child.len() == 2, + message: "Unit entries need to be a tuple of a unit and an exponent, got " + repr(child) + ) + let (unit, exponent) = child + assert( + type(exponent) in (int, float, str), + message: "The exponent of a unit entry needs to be an int, float, or str, got " + repr(exponent) + ) + if type(exponent) in (int, float) { + exponent = str(exponent).replace("−", "-") + } + if exponent.starts-with("-") { + exponent = exponent.slice(1) + denominator.push((unit, [#exponent])) + } else { + numerator.push((unit, [#exponent])) + } + } else { + assert( + false, + message: "Expected str, content, symbol or a unit-exponent pair, got " + repr(child) + ) + } + } + + (numerator: numerator, denominator: denominator) +} + +#let format-unit-power(unit, exponent, math: true) = { + if math { + if type(exponent) in (int, float) { + exponent = str(exponent) + } + if exponent in (1, [1]) { unit } + else { std.math.attach(unit, t: exponent) } + + } else { + + if exponent in (1, [1]) { unit } + else { unit + super(typographic: false, exponent) } + + } +} + +#let fold-units( + ..units, + exp-multiplier, + math: true, + unit-separator: sym.space.thin, + use-sqrt: true +) = { + let units = units.pos().map(((unit, exponent)) => { + if exp-multiplier == -1 { + exponent = sym.minus + exponent + } + if use-sqrt and exponent == [0.5] and math { + return std.math.sqrt(unit) + } + format-unit-power(unit, exponent, math: math) + }) + + let folded-units = units.join(unit-separator) + if math { + std.math.upright(folded-units) + } else { + folded-units + } +} + +/// Show a unit that has been parsed with @parse-unit-str. +/// - fraction (string): Mode for displaying the units in the fraction. +/// Options are "power", "fraction" and "inline" like in siunitx +/// - unit-separator (content): Symbol to use between base units. +#let show-unit( + unit-spec, + fraction: "power", + math: true, + unit-separator: sym.space.thin, + use-sqrt: true, + ..args +) = { + let fold-units = fold-units.with( + unit-separator: unit-separator, + math: math, + use-sqrt: use-sqrt + ) + + + let numerator = fold-units(..unit-spec.numerator, 1) + if unit-spec.denominator.len() == 0 { + return if math { $numerator$ } else { numerator } + } + + let denom-exp-multiplier = if fraction == "power" { -1 } else { 1 } + let denominator = fold-units(..unit-spec.denominator, denom-exp-multiplier) + + if fraction == "power" { + // numerator may be empty! + let result = denominator + if unit-spec.numerator.len() != 0 { + result = numerator + unit-separator + result + } + return if math { $result$ } else { result } + } + + // for the two fractional modes the numerator should not be empty + if unit-spec.numerator.len() == 0 { numerator = $1$ } + + if fraction == "fraction" { + if not math { + assert(false, "`math: false` cannot be used together with `fraction: \"fraction\"`") + } + return $numerator/denominator$ + } else if fraction == "inline" { + if unit-spec.denominator.len() > 1 { + denominator = "(" + denominator + ")" + } + + if math { + $#numerator#h(0pt)\/#h(0pt)#denominator$ + } else { + numerator + "/" + denominator + } + } else { + assert( + false, + message: "Invalid fraction: " + fraction + ". Expected \"power\", \"fraction\", or \"symbol\"" + ) + } +} + +#let unit( + unit, + ..args +) = context { + + let num-state = update-num-state(num-state.get(), (unit: args.named())) + + let result = show-unit( + unit, + ..num-state.unit, + math: num-state.math + ) + if not num-state.unit.breakable { + if num-state.math { + result = $result$ + } else { + result = box(result) + } + } + result +} + + + + +#let qty( + value, + unit, + ..args +) = context { + + let num-state = update-num-state(num-state.get(), (unit: args.named()) + args.named()) + + let separator = sym.space.thin + if num-state.math { + separator = $separator$ + } + + let result = { + num(value, state: num-state, force-parentheses-around-uncertainty: true) // force parens around numbers with uncertainty + separator + show-unit( + unit, + fraction: num-state.unit.fraction, + unit-separator: num-state.unit.unit-separator, + math: num-state.math + ) + } + + if not num-state.unit.breakable { + if num-state.math { + result = $result$ + } else { + result = box(result) + } + } + result +} + + +#let set-unit(..args) = { + num-state.update(s => { + assert-settable-args(args, s.unit, name: "set-unit") + s.unit += args.named() + s + }) +} + diff --git a/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/utility.typ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/utility.typ new file mode 100644 index 0000000..4bd798c --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/utility.typ @@ -0,0 +1,31 @@ + +/// Takes a number by integer and fractional part and +/// shifts specified number of digits left. Negative values +/// for `digits` produce a right-shift. Numbers are automatically +/// padded with zeros but both integer and fractional parts +/// may become "empty" when they are zero. +#let shift-decimal-left(integer, fractional, digits) = { + if digits < 0 { + let available-digits = calc.min(-digits, fractional.len()) + integer += fractional.slice(0, available-digits) + integer += "0" * (-digits - available-digits) + fractional = fractional.slice(available-digits) + if integer.starts-with("0") { + integer = (integer + ";").trim("0").slice(0,-1) + } + } else { + let available-digits = calc.min(digits, integer.len()) + fractional = integer.slice(integer.len() - available-digits) + fractional + fractional = "0" * (digits - available-digits) + fractional + integer = integer.slice(0, integer.len() - available-digits) + } + return (integer, fractional) +} + +#assert.eq(shift-decimal-left("123", "456", 0), ("123", "456")) +#assert.eq(shift-decimal-left("123", "456", 2), ("1", "23456")) +#assert.eq(shift-decimal-left("123", "456", 5), ("", "00123456")) +#assert.eq(shift-decimal-left("123", "456", -2), ("12345", "6")) +#assert.eq(shift-decimal-left("123", "456", -5), ("12345600", "")) +#assert.eq(shift-decimal-left("0", "0012", -4), ("12", "")) +#assert.eq(shift-decimal-left("0", "0012", -2), ("", "12")) diff --git a/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/zero.typ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/zero.typ new file mode 100644 index 0000000..b323fad --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/zero.typ @@ -0,0 +1,7 @@ +#import "impl.typ" +#import "num.typ": num, nonum, set-num, set-group, set-round +#import "state.typ": default-state, num-state +#import "ztable.typ": ztable, format-table +#import "align-column.typ": align-column +#import "zi.typ" +#import "units.typ": set-unit diff --git a/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/zi.typ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/zi.typ new file mode 100644 index 0000000..ece1b0d --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/zi.typ @@ -0,0 +1,154 @@ +#import "units.typ" + +#let declare(..unit) = { + assert(unit.named().len() == 0, ) + + (..value) => if value.pos().len() == 0 { + units.unit(units.parse-unit(..unit.pos()), ..value) + } else { + units.qty(..value, units.parse-unit(..unit.pos())) + } +} + +// SI base units .. +#let ampere = declare("A") +#let candela = declare("cd") +#let kelvin = declare("K") +#let kilogram = declare("kg") +#let metre = declare("m") +#let meter = metre +#let mole = declare("mol") +#let second = declare("s") + +// .. and shorthands +#let A = ampere +#let cd = candela +#let K = kelvin +#let kg = kilogram +#let m = metre +#let mol = mole +#let s = second + + +// Derived units .. +#let becquerel = declare("Bq") +#let degreeCelsius = declare(sym.degree + "C") +#let coulomb = declare("C") +#let farad = declare("F") +#let gray = declare("Gy") +#let hertz = declare("Hz") +#let henry = declare("H") +#let joule = declare("J") +#let lumen = declare("lm") +#let katal = declare("kat") +#let lux = declare("lx") +#let newton = declare("N") +#let ohm = declare(sym.Omega) +#let pascal = declare("Pa") +#let radian = declare("rad") +#let siemens = declare("S") +#let sievert = declare("Sv") +#let steradian = declare("sr") +#let tesla = declare("T") +#let volt = declare("V") +#let watt = declare("W") +#let weber = declare("Wb") + +// .. and shorthands +#let Bq = becquerel +#let C = coulomb +#let F = farad +#let Gy = gray +#let Hz = hertz +#let H = henry +#let J = joule +#let lm = lumen +#let kat = katal +#let lx = lux +#let N = newton +#let Ω = ohm +#let Pa = pascal +#let rad = radian +#let S = siemens +#let Sv = sievert +#let sr = steradian +#let T = tesla +#let V = volt +#let W = watt +#let Wb = weber + + +#let astronomicalunit = declare("au") +#let bel = declare("B") +#let dalton = declare("Da") +#let day = declare("d") +#let decibel = declare("dB") +#let degree = declare(sym.degree) +#let electronvolt = declare("eV") +#let hectare = declare("ha") +#let hour = declare("h") +#let litre = declare("L") +#let liter = declare("L") +#let arcminute = declare(sym.prime) +#let minute = declare("min") +#let arcsecond = declare(sym.prime.double) +#let neper = declare("Np") +#let tonne = declare("t") +#let gram = declare("g") + +// Common units with prefixes +#let mm = declare("mm") +#let km = declare("km") +#let cm = declare("cm") +#let µm = declare("µm") +#let nm = declare("nm") +#let pm = declare("pm") +#let ms = declare("ms") +#let µs = declare("µs") +#let ns = declare("ns") +#let ps = declare("ps") +#let mK = declare("mK") +#let nK = declare("nK") +#let mF = declare("mF") +#let µF = declare("µF") +#let nF = declare("nF") +#let pF = declare("pF") +#let mH = declare("mH") +#let THz = declare("THz") +#let GHz = declare("GHz") +#let MHz = declare("MHz") +#let kHz = declare("kHz") +#let MJ = declare("MJ") +#let kJ = declare("kJ") +#let mJ = declare("mJ") +#let GPa = declare("GPa") +#let MPa = declare("MPa") +#let kPa = declare("kPa") +#let kPa = declare("kPa") +#let MN = declare("MN") +#let kN = declare("kN") +#let mN = declare("mN") +#let kT = declare("kT") +#let mT = declare("mT") +#let µT = declare("µT") +#let nT = declare("nT") +#let GV = declare("GV") +#let MV = declare("MV") +#let kV = declare("kV") +#let mV = declare("mV") +#let µV = declare("µV") +#let nV = declare("nV") +#let kA = declare("kA") +#let mA = declare("mA") +#let µA = declare("µA") +#let nA = declare("nA") +#let TW = declare("TW") +#let GW = declare("GW") +#let kW = declare("kW") +#let mW = declare("mW") +#let mSv = declare("mSv") + + +// combined units +#let m-s = declare("m/s") +#let km-h = declare("km/h") diff --git a/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/ztable.typ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/ztable.typ new file mode 100644 index 0000000..f1dac9b --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/src/ztable.typ @@ -0,0 +1,87 @@ +#import "num.typ": num, number-to-string-table +#import "state.typ": num-state +#import "parsing.typ": nonum + +// #let ptable-counter = counter("__pillar-table__") + +#let is-normal-cell(cell, format, default: none) = { + (format.at(cell.x, default: default) == none or number-to-string-table(cell.body) == none) and cell.body.func() != nonum +} + +#let call-num(cell, format, col-widths: auto, default: none, state: auto) = context{ + if cell.body.func() == nonum { return cell.body.body } + let (numeral, prefix, suffix) = number-to-string-table(cell.body) + let cell-fmt = format.at(cell.x, default: default) + let args = if type(cell-fmt) == dictionary { cell-fmt } else { () } + num(numeral, prefix: prefix, suffix: suffix, state: state, align: (col-widths: col-widths, col: cell.x), ..args) +} + + + + + +#let show-ztable(it, format: none) = { + if format == none or it.children.len() == 0 { return it } + assert.eq(type(format), array, message: "The parameter `format` requires an array argument, got " + repr(format)) + + + + + let table = context { + let state = num-state.get() + + let table-end = query(selector(<__pillar-table__>).after(here())).first().location() + + let number-infos = query(selector(<__pillar-num__>) + .after(here()) + .before(table-end)) + .map(x => x.value) + + + // let debug-info = (counter: ptable-counter.get(), d: number-infos) + + + if number-infos.len() == 0 { // first layout pass + return { + show table.cell: it => { + if is-normal-cell(it, format) { it } + else { call-num(it, format, state: state) } + } + it + } + } + + // second layout pass + let aligned-columns = range(format.len()).filter(x => format.at(x) != none) + let col-widths = (none,) * format.len() + for col in aligned-columns { + let filtered-cells = number-infos.filter(x => x.at(0) == col) + col-widths.at(col) = range(1, 5) + .map(i => calc.max(0pt, ..filtered-cells.map(x => x.at(i)))) + } + + show table.cell: it => { + if is-normal-cell(it, format) { it } + else { + table.cell( + call-num(it, format, col-widths: col-widths.at(it.x), state: state), + align: it.align, + x: it.x, y: it.y, + inset: it.inset, + ) + } + } + it + } + table + [#metadata(none)<__pillar-table__>] + place(hide(std.table())) +} + + +#let ztable(..children, format: none) = { + show-ztable(table(..children), format: format) +} + + +#let format-table(..format) = { + show-ztable.with(format: format.pos()) +} diff --git a/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/typst.toml b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/typst.toml new file mode 100644 index 0000000..1bbf73d --- /dev/null +++ b/.obsidian/plugins/typst-mate/packages/preview/zero/0.5.0/typst.toml @@ -0,0 +1,13 @@ +[package] +name = "zero" +version = "0.5.0" +entrypoint = "src/zero.typ" +authors = ["Mc-Zen "] +license = "MIT" +description = "Advanced scientific number formatting." +compiler = "0.11.0" + +repository = "https://github.com/Mc-Zen/zero" +keywords = ["number", "table", "siunitx", "numeral", "alignment", "formatting", "num", "ztable"] +categories = ["visualization", "layout"] +disciplines = ["physics", "chemistry", "engineering", "mathematics", "economics", "computer-science"] diff --git a/11 Homework/Mathe II/Homework 1.typ b/11 Homework/Mathe II/Homework 1.typ new file mode 100644 index 0000000..3292bf6 --- /dev/null +++ b/11 Homework/Mathe II/Homework 1.typ @@ -0,0 +1,2 @@ +#import "_Math Homework Template.typ": assignment_header +#show: assignment_header("Mathe II", 1) diff --git a/11 Homework/Mathe II/_Math Homework Template.typ b/11 Homework/Mathe II/_Math Homework Template.typ new file mode 100644 index 0000000..96ebd5f --- /dev/null +++ b/11 Homework/Mathe II/_Math Homework Template.typ @@ -0,0 +1,14 @@ +#let assignment_header(course, index) = { + set page( + header: align(right, [ + #set text(size: 9pt) + #set par(leading: .3em) + #pad(y: -.5cm, [ + #course \ + Hausaufgabe #index \ + Jan Meyer \ + 664237 + ]) + ]) + ) +} \ No newline at end of file