From 7eebafa7837ec173a7b2064ae60fd45b5413d17c Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 23 Nov 2023 16:25:49 +0100 Subject: Merge `typst` and `typst-library` --- Cargo.lock | 61 +- Cargo.toml | 3 +- crates/typst-cli/Cargo.toml | 1 - crates/typst-cli/src/args.rs | 3 +- crates/typst-cli/src/compile.rs | 8 +- crates/typst-cli/src/fonts.rs | 4 +- crates/typst-cli/src/query.rs | 9 +- crates/typst-cli/src/tracing.rs | 5 +- crates/typst-cli/src/update.rs | 5 +- crates/typst-cli/src/watch.rs | 2 +- crates/typst-cli/src/world.rs | 11 +- crates/typst-docs/Cargo.toml | 1 - crates/typst-docs/src/contribs.rs | 2 +- crates/typst-docs/src/html.rs | 13 +- crates/typst-docs/src/lib.rs | 252 +-- crates/typst-docs/src/link.rs | 30 +- crates/typst-docs/src/model.rs | 6 +- crates/typst-ide/src/analyze.rs | 17 +- crates/typst-ide/src/complete.rs | 26 +- crates/typst-ide/src/jump.rs | 7 +- crates/typst-ide/src/lib.rs | 4 +- crates/typst-ide/src/tooltip.rs | 21 +- crates/typst-library/Cargo.toml | 53 - .../assets/cj_linebreak_data.postcard | Bin 18848 -> 0 bytes crates/typst-library/assets/icudata.postcard | Bin 352005 -> 0 bytes crates/typst-library/assets/syntect.bin | Bin 687378 -> 0 bytes crates/typst-library/src/compute/calc.rs | 949 ---------- crates/typst-library/src/compute/data.rs | 609 ------ crates/typst-library/src/compute/foundations.rs | 236 --- crates/typst-library/src/compute/mod.rs | 20 - crates/typst-library/src/compute/sys.rs | 24 - crates/typst-library/src/layout/align.rs | 46 - crates/typst-library/src/layout/columns.rs | 169 -- crates/typst-library/src/layout/container.rs | 502 ----- crates/typst-library/src/layout/enum.rs | 319 ---- crates/typst-library/src/layout/flow.rs | 714 ------- crates/typst-library/src/layout/fragment.rs | 87 - crates/typst-library/src/layout/grid.rs | 734 -------- crates/typst-library/src/layout/hide.rs | 29 - crates/typst-library/src/layout/list.rs | 238 --- crates/typst-library/src/layout/measure.rs | 53 - crates/typst-library/src/layout/mod.rs | 786 -------- crates/typst-library/src/layout/pad.rs | 121 -- crates/typst-library/src/layout/page.rs | 949 ---------- crates/typst-library/src/layout/par.rs | 1520 --------------- crates/typst-library/src/layout/place.rs | 124 -- crates/typst-library/src/layout/regions.rs | 144 -- crates/typst-library/src/layout/repeat.rs | 75 - crates/typst-library/src/layout/spacing.rs | 262 --- crates/typst-library/src/layout/stack.rs | 345 ---- crates/typst-library/src/layout/table.rs | 361 ---- crates/typst-library/src/layout/terms.rs | 162 -- crates/typst-library/src/layout/transform.rs | 187 -- crates/typst-library/src/lib.rs | 170 -- crates/typst-library/src/math/accent.rs | 137 -- crates/typst-library/src/math/align.rs | 60 - crates/typst-library/src/math/attach.rs | 440 ----- crates/typst-library/src/math/cancel.rs | 230 --- crates/typst-library/src/math/class.rs | 38 - crates/typst-library/src/math/ctx.rs | 335 ---- crates/typst-library/src/math/frac.rs | 157 -- crates/typst-library/src/math/fragment.rs | 513 ----- crates/typst-library/src/math/lr.rs | 195 -- crates/typst-library/src/math/matrix.rs | 655 ------- crates/typst-library/src/math/mod.rs | 500 ----- crates/typst-library/src/math/op.rs | 115 -- crates/typst-library/src/math/root.rs | 137 -- crates/typst-library/src/math/row.rs | 261 --- crates/typst-library/src/math/spacing.rs | 63 - crates/typst-library/src/math/stretch.rs | 199 -- crates/typst-library/src/math/style.rs | 574 ------ crates/typst-library/src/math/underover.rs | 315 ---- crates/typst-library/src/meta/bibliography.rs | 1038 ---------- crates/typst-library/src/meta/cite.rs | 156 -- crates/typst-library/src/meta/context.rs | 169 -- crates/typst-library/src/meta/counter.rs | 722 ------- crates/typst-library/src/meta/document.rs | 124 -- crates/typst-library/src/meta/figure.rs | 588 ------ crates/typst-library/src/meta/footnote.rs | 305 --- crates/typst-library/src/meta/heading.rs | 269 --- crates/typst-library/src/meta/link.rs | 133 -- crates/typst-library/src/meta/metadata.rs | 40 - crates/typst-library/src/meta/mod.rs | 76 - crates/typst-library/src/meta/numbering.rs | 567 ------ crates/typst-library/src/meta/outline.rs | 525 ------ crates/typst-library/src/meta/query.rs | 157 -- crates/typst-library/src/meta/reference.rs | 283 --- crates/typst-library/src/meta/state.rs | 413 ---- crates/typst-library/src/prelude.rs | 42 - crates/typst-library/src/shared/behave.rs | 114 -- crates/typst-library/src/shared/ext.rs | 92 - crates/typst-library/src/shared/mod.rs | 7 - crates/typst-library/src/symbols/emoji.rs | 1356 ------------- crates/typst-library/src/symbols/mod.rs | 17 - crates/typst-library/src/symbols/sym.rs | 873 --------- crates/typst-library/src/text/deco.rs | 566 ------ crates/typst-library/src/text/linebreak.rs | 262 --- crates/typst-library/src/text/misc.rs | 315 ---- crates/typst-library/src/text/mod.rs | 992 ---------- crates/typst-library/src/text/quote.rs | 208 -- crates/typst-library/src/text/quotes.rs | 369 ---- crates/typst-library/src/text/raw.rs | 835 -------- crates/typst-library/src/text/shaping.rs | 1123 ----------- crates/typst-library/src/text/shift.rs | 223 --- crates/typst-library/src/visualize/image.rs | 271 --- crates/typst-library/src/visualize/line.rs | 86 - crates/typst-library/src/visualize/mod.rs | 31 - crates/typst-library/src/visualize/path.rs | 208 -- crates/typst-library/src/visualize/polygon.rs | 164 -- crates/typst-library/src/visualize/shape.rs | 547 ------ crates/typst-macros/src/cast.rs | 59 +- crates/typst-macros/src/category.rs | 57 + crates/typst-macros/src/elem.rs | 234 ++- crates/typst-macros/src/func.rs | 50 +- crates/typst-macros/src/lib.rs | 23 +- crates/typst-macros/src/scope.rs | 18 +- crates/typst-macros/src/symbols.rs | 11 +- crates/typst-macros/src/ty.rs | 30 +- crates/typst-macros/src/util.rs | 39 +- crates/typst-pdf/src/color.rs | 2 +- crates/typst-pdf/src/font.rs | 2 +- crates/typst-pdf/src/gradient.rs | 15 +- crates/typst-pdf/src/image.rs | 5 +- crates/typst-pdf/src/lib.rs | 12 +- crates/typst-pdf/src/outline.rs | 8 +- crates/typst-pdf/src/page.rs | 29 +- crates/typst-render/src/lib.rs | 32 +- crates/typst-svg/src/lib.rs | 32 +- crates/typst-syntax/src/reparser.rs | 2 +- crates/typst-syntax/src/span.rs | 4 +- crates/typst/Cargo.toml | 18 + crates/typst/assets/cj_linebreak_data.postcard | Bin 0 -> 18848 bytes crates/typst/assets/icudata.postcard | Bin 0 -> 352005 bytes crates/typst/assets/syntect.bin | Bin 0 -> 687378 bytes crates/typst/src/diag.rs | 29 +- crates/typst/src/doc.rs | 893 --------- crates/typst/src/eval/access.rs | 99 + crates/typst/src/eval/args.rs | 322 ---- crates/typst/src/eval/array.rs | 964 ---------- crates/typst/src/eval/auto.rs | 238 --- crates/typst/src/eval/binding.rs | 179 ++ crates/typst/src/eval/bool.rs | 26 - crates/typst/src/eval/bytes.rs | 263 --- crates/typst/src/eval/call.rs | 587 ++++++ crates/typst/src/eval/cast.rs | 445 ----- crates/typst/src/eval/code.rs | 317 ++++ crates/typst/src/eval/datetime.rs | 590 ------ crates/typst/src/eval/dict.rs | 337 ---- crates/typst/src/eval/duration.rs | 215 --- crates/typst/src/eval/fields.rs | 88 - crates/typst/src/eval/float.rs | 75 - crates/typst/src/eval/flow.rs | 227 +++ crates/typst/src/eval/func.rs | 870 --------- crates/typst/src/eval/import.rs | 227 +++ crates/typst/src/eval/int.rs | 160 -- crates/typst/src/eval/library.rs | 179 -- crates/typst/src/eval/markup.rs | 272 +++ crates/typst/src/eval/math.rs | 113 ++ crates/typst/src/eval/methods.rs | 110 -- crates/typst/src/eval/mod.rs | 1988 +------------------- crates/typst/src/eval/module.rs | 123 -- crates/typst/src/eval/none.rs | 112 -- crates/typst/src/eval/ops.rs | 145 +- crates/typst/src/eval/plugin.rs | 357 ---- crates/typst/src/eval/repr.rs | 165 -- crates/typst/src/eval/rules.rs | 51 + crates/typst/src/eval/scope.rs | 270 --- crates/typst/src/eval/str.rs | 960 ---------- crates/typst/src/eval/symbol.rs | 338 ---- crates/typst/src/eval/tracer.rs | 4 +- crates/typst/src/eval/ty.rs | 209 -- crates/typst/src/eval/value.rs | 697 ------- crates/typst/src/eval/version.rs | 200 -- crates/typst/src/eval/vm.rs | 127 ++ crates/typst/src/font/book.rs | 561 ------ crates/typst/src/font/mod.rs | 248 --- crates/typst/src/font/variant.rs | 296 --- crates/typst/src/foundations/args.rs | 322 ++++ crates/typst/src/foundations/array.rs | 962 ++++++++++ crates/typst/src/foundations/auto.rs | 240 +++ crates/typst/src/foundations/bool.rs | 26 + crates/typst/src/foundations/bytes.rs | 263 +++ crates/typst/src/foundations/calc.rs | 944 ++++++++++ crates/typst/src/foundations/cast.rs | 446 +++++ crates/typst/src/foundations/content.rs | 795 ++++++++ crates/typst/src/foundations/datetime.rs | 557 ++++++ crates/typst/src/foundations/dict.rs | 337 ++++ crates/typst/src/foundations/duration.rs | 215 +++ crates/typst/src/foundations/element.rs | 382 ++++ crates/typst/src/foundations/fields.rs | 91 + crates/typst/src/foundations/float.rs | 75 + crates/typst/src/foundations/func.rs | 528 ++++++ crates/typst/src/foundations/int.rs | 160 ++ crates/typst/src/foundations/label.rs | 78 + crates/typst/src/foundations/methods.rs | 110 ++ crates/typst/src/foundations/mod.rs | 293 +++ crates/typst/src/foundations/module.rs | 123 ++ crates/typst/src/foundations/none.rs | 114 ++ crates/typst/src/foundations/plugin.rs | 358 ++++ crates/typst/src/foundations/repr.rs | 194 ++ crates/typst/src/foundations/scope.rs | 315 ++++ crates/typst/src/foundations/selector.rs | 432 +++++ crates/typst/src/foundations/str.rs | 960 ++++++++++ crates/typst/src/foundations/styles.rs | 931 +++++++++ crates/typst/src/foundations/sys.rs | 17 + crates/typst/src/foundations/ty.rs | 209 ++ crates/typst/src/foundations/value.rs | 703 +++++++ crates/typst/src/foundations/version.rs | 200 ++ crates/typst/src/geom/abs.rs | 261 --- crates/typst/src/geom/align.rs | 369 ---- crates/typst/src/geom/angle.rs | 236 --- crates/typst/src/geom/axes.rs | 322 ---- crates/typst/src/geom/color.rs | 1892 ------------------- crates/typst/src/geom/corners.rs | 281 --- crates/typst/src/geom/dir.rs | 133 -- crates/typst/src/geom/ellipse.rs | 22 - crates/typst/src/geom/em.rs | 159 -- crates/typst/src/geom/fr.rs | 137 -- crates/typst/src/geom/gradient.rs | 1243 ------------ crates/typst/src/geom/length.rs | 236 --- crates/typst/src/geom/macros.rs | 47 - crates/typst/src/geom/mod.rs | 124 -- crates/typst/src/geom/paint.rs | 73 - crates/typst/src/geom/path.rs | 102 - crates/typst/src/geom/point.rs | 146 -- crates/typst/src/geom/ratio.rs | 147 -- crates/typst/src/geom/rect.rs | 599 ------ crates/typst/src/geom/rel.rs | 273 --- crates/typst/src/geom/scalar.rs | 198 -- crates/typst/src/geom/shape.rs | 44 - crates/typst/src/geom/sides.rs | 312 --- crates/typst/src/geom/size.rs | 83 - crates/typst/src/geom/stroke.rs | 604 ------ crates/typst/src/geom/transform.rs | 126 -- crates/typst/src/image/mod.rs | 175 -- crates/typst/src/image/raster.rs | 139 -- crates/typst/src/image/svg.rs | 263 --- crates/typst/src/introspection/counter.rs | 731 +++++++ crates/typst/src/introspection/introspector.rs | 242 +++ crates/typst/src/introspection/locate.rs | 47 + crates/typst/src/introspection/location.rs | 87 + crates/typst/src/introspection/locator.rs | 117 ++ crates/typst/src/introspection/metadata.rs | 43 + crates/typst/src/introspection/mod.rs | 109 ++ crates/typst/src/introspection/query.rs | 159 ++ crates/typst/src/introspection/state.rs | 418 ++++ crates/typst/src/layout/abs.rs | 268 +++ crates/typst/src/layout/align.rs | 423 +++++ crates/typst/src/layout/angle.rs | 244 +++ crates/typst/src/layout/axes.rs | 326 ++++ crates/typst/src/layout/columns.rs | 176 ++ crates/typst/src/layout/container.rs | 508 +++++ crates/typst/src/layout/corners.rs | 287 +++ crates/typst/src/layout/dir.rs | 136 ++ crates/typst/src/layout/em.rs | 168 ++ crates/typst/src/layout/flow.rs | 717 +++++++ crates/typst/src/layout/fr.rs | 145 ++ crates/typst/src/layout/fragment.rs | 89 + crates/typst/src/layout/frame.rs | 587 ++++++ crates/typst/src/layout/grid.rs | 744 ++++++++ crates/typst/src/layout/hide.rs | 32 + crates/typst/src/layout/inline/linebreak.rs | 261 +++ crates/typst/src/layout/inline/mod.rs | 1382 ++++++++++++++ crates/typst/src/layout/inline/shaping.rs | 1022 ++++++++++ crates/typst/src/layout/layout.rs | 84 + crates/typst/src/layout/length.rs | 244 +++ crates/typst/src/layout/measure.rs | 56 + crates/typst/src/layout/mod.rs | 256 +++ crates/typst/src/layout/pad.rs | 125 ++ crates/typst/src/layout/page.rs | 960 ++++++++++ crates/typst/src/layout/place.rs | 130 ++ crates/typst/src/layout/point.rs | 150 ++ crates/typst/src/layout/ratio.rs | 153 ++ crates/typst/src/layout/regions.rs | 144 ++ crates/typst/src/layout/rel.rs | 281 +++ crates/typst/src/layout/repeat.rs | 79 + crates/typst/src/layout/sides.rs | 317 ++++ crates/typst/src/layout/size.rs | 86 + crates/typst/src/layout/spacing.rs | 264 +++ crates/typst/src/layout/stack.rs | 352 ++++ crates/typst/src/layout/transform.rs | 315 ++++ crates/typst/src/layout/vt.rs | 43 + crates/typst/src/lib.rs | 246 ++- crates/typst/src/loading/cbor.rs | 57 + crates/typst/src/loading/csv.rs | 118 ++ crates/typst/src/loading/json.rs | 94 + crates/typst/src/loading/mod.rs | 82 + crates/typst/src/loading/read.rs | 57 + crates/typst/src/loading/toml.rs | 90 + crates/typst/src/loading/xml.rs | 116 ++ crates/typst/src/loading/yaml.rs | 78 + crates/typst/src/math/accent.rs | 147 ++ crates/typst/src/math/align.rs | 63 + crates/typst/src/math/attach.rs | 448 +++++ crates/typst/src/math/cancel.rs | 240 +++ crates/typst/src/math/class.rs | 42 + crates/typst/src/math/ctx.rs | 397 ++++ crates/typst/src/math/equation.rs | 309 +++ crates/typst/src/math/frac.rs | 166 ++ crates/typst/src/math/fragment.rs | 472 +++++ crates/typst/src/math/lr.rs | 201 ++ crates/typst/src/math/matrix.rs | 671 +++++++ crates/typst/src/math/mod.rs | 311 +++ crates/typst/src/math/op.rs | 122 ++ crates/typst/src/math/root.rs | 145 ++ crates/typst/src/math/row.rs | 267 +++ crates/typst/src/math/spacing.rs | 67 + crates/typst/src/math/stretch.rs | 200 ++ crates/typst/src/math/style.rs | 578 ++++++ crates/typst/src/math/underover.rs | 326 ++++ crates/typst/src/model/bibliography.rs | 1050 +++++++++++ crates/typst/src/model/cite.rs | 161 ++ crates/typst/src/model/content.rs | 821 -------- crates/typst/src/model/document.rs | 154 ++ crates/typst/src/model/element.rs | 315 ---- crates/typst/src/model/emph.rs | 41 + crates/typst/src/model/enum.rs | 325 ++++ crates/typst/src/model/figure.rs | 597 ++++++ crates/typst/src/model/footnote.rs | 313 +++ crates/typst/src/model/heading.rs | 273 +++ crates/typst/src/model/introspect.rs | 350 ---- crates/typst/src/model/label.rs | 80 - crates/typst/src/model/link.rs | 169 ++ crates/typst/src/model/list.rs | 246 +++ crates/typst/src/model/location.rs | 82 - crates/typst/src/model/mod.rs | 228 +-- crates/typst/src/model/numbering.rs | 569 ++++++ crates/typst/src/model/outline.rs | 526 ++++++ crates/typst/src/model/par.rs | 180 ++ crates/typst/src/model/quote.rs | 213 +++ crates/typst/src/model/realize.rs | 242 --- crates/typst/src/model/reference.rs | 292 +++ crates/typst/src/model/selector.rs | 432 ----- crates/typst/src/model/strong.rs | 48 + crates/typst/src/model/styles.rs | 874 --------- crates/typst/src/model/table.rs | 368 ++++ crates/typst/src/model/terms.rs | 169 ++ crates/typst/src/realize/behave.rs | 111 ++ crates/typst/src/realize/mod.rs | 752 ++++++++ crates/typst/src/symbols/emoji.rs | 1357 +++++++++++++ crates/typst/src/symbols/mod.rs | 27 + crates/typst/src/symbols/sym.rs | 874 +++++++++ crates/typst/src/symbols/symbol.rs | 338 ++++ crates/typst/src/text/case.rs | 79 + crates/typst/src/text/deco.rs | 576 ++++++ crates/typst/src/text/font/book.rs | 561 ++++++ crates/typst/src/text/font/mod.rs | 248 +++ crates/typst/src/text/font/variant.rs | 296 +++ crates/typst/src/text/item.rs | 63 + crates/typst/src/text/lang.rs | 182 ++ crates/typst/src/text/linebreak.rs | 43 + crates/typst/src/text/lorem.rs | 24 + crates/typst/src/text/mod.rs | 1156 ++++++++++++ crates/typst/src/text/raw.rs | 841 +++++++++ crates/typst/src/text/shift.rs | 228 +++ crates/typst/src/text/smallcaps.rs | 32 + crates/typst/src/text/smartquote.rs | 373 ++++ crates/typst/src/text/space.rs | 26 + crates/typst/src/util/deferred.rs | 11 +- crates/typst/src/util/fat.rs | 55 + crates/typst/src/util/macros.rs | 47 + crates/typst/src/util/mod.rs | 63 +- crates/typst/src/util/pico.rs | 3 +- crates/typst/src/util/scalar.rs | 202 ++ crates/typst/src/visualize/color.rs | 1899 +++++++++++++++++++ crates/typst/src/visualize/gradient.rs | 1255 ++++++++++++ crates/typst/src/visualize/image/mod.rs | 450 +++++ crates/typst/src/visualize/image/raster.rs | 138 ++ crates/typst/src/visualize/image/svg.rs | 263 +++ crates/typst/src/visualize/line.rs | 93 + crates/typst/src/visualize/mod.rs | 47 + crates/typst/src/visualize/paint.rs | 78 + crates/typst/src/visualize/path.rs | 314 ++++ crates/typst/src/visualize/polygon.rs | 173 ++ crates/typst/src/visualize/shape.rs | 1221 ++++++++++++ crates/typst/src/visualize/stroke.rs | 611 ++++++ docs/dev/architecture.md | 23 +- docs/reference/categories.yml | 178 -- docs/reference/groups.yml | 82 +- docs/reference/packages.md | 6 + tests/Cargo.toml | 1 - tests/src/benches.rs | 27 +- tests/src/tests.rs | 28 +- 383 files changed, 54941 insertions(+), 54402 deletions(-) delete mode 100644 crates/typst-library/Cargo.toml delete mode 100644 crates/typst-library/assets/cj_linebreak_data.postcard delete mode 100644 crates/typst-library/assets/icudata.postcard delete mode 100644 crates/typst-library/assets/syntect.bin delete mode 100644 crates/typst-library/src/compute/calc.rs delete mode 100644 crates/typst-library/src/compute/data.rs delete mode 100644 crates/typst-library/src/compute/foundations.rs delete mode 100644 crates/typst-library/src/compute/mod.rs delete mode 100644 crates/typst-library/src/compute/sys.rs delete mode 100644 crates/typst-library/src/layout/align.rs delete mode 100644 crates/typst-library/src/layout/columns.rs delete mode 100644 crates/typst-library/src/layout/container.rs delete mode 100644 crates/typst-library/src/layout/enum.rs delete mode 100644 crates/typst-library/src/layout/flow.rs delete mode 100644 crates/typst-library/src/layout/fragment.rs delete mode 100644 crates/typst-library/src/layout/grid.rs delete mode 100644 crates/typst-library/src/layout/hide.rs delete mode 100644 crates/typst-library/src/layout/list.rs delete mode 100644 crates/typst-library/src/layout/measure.rs delete mode 100644 crates/typst-library/src/layout/mod.rs delete mode 100644 crates/typst-library/src/layout/pad.rs delete mode 100644 crates/typst-library/src/layout/page.rs delete mode 100644 crates/typst-library/src/layout/par.rs delete mode 100644 crates/typst-library/src/layout/place.rs delete mode 100644 crates/typst-library/src/layout/regions.rs delete mode 100644 crates/typst-library/src/layout/repeat.rs delete mode 100644 crates/typst-library/src/layout/spacing.rs delete mode 100644 crates/typst-library/src/layout/stack.rs delete mode 100644 crates/typst-library/src/layout/table.rs delete mode 100644 crates/typst-library/src/layout/terms.rs delete mode 100644 crates/typst-library/src/layout/transform.rs delete mode 100644 crates/typst-library/src/lib.rs delete mode 100644 crates/typst-library/src/math/accent.rs delete mode 100644 crates/typst-library/src/math/align.rs delete mode 100644 crates/typst-library/src/math/attach.rs delete mode 100644 crates/typst-library/src/math/cancel.rs delete mode 100644 crates/typst-library/src/math/class.rs delete mode 100644 crates/typst-library/src/math/ctx.rs delete mode 100644 crates/typst-library/src/math/frac.rs delete mode 100644 crates/typst-library/src/math/fragment.rs delete mode 100644 crates/typst-library/src/math/lr.rs delete mode 100644 crates/typst-library/src/math/matrix.rs delete mode 100644 crates/typst-library/src/math/mod.rs delete mode 100644 crates/typst-library/src/math/op.rs delete mode 100644 crates/typst-library/src/math/root.rs delete mode 100644 crates/typst-library/src/math/row.rs delete mode 100644 crates/typst-library/src/math/spacing.rs delete mode 100644 crates/typst-library/src/math/stretch.rs delete mode 100644 crates/typst-library/src/math/style.rs delete mode 100644 crates/typst-library/src/math/underover.rs delete mode 100644 crates/typst-library/src/meta/bibliography.rs delete mode 100644 crates/typst-library/src/meta/cite.rs delete mode 100644 crates/typst-library/src/meta/context.rs delete mode 100644 crates/typst-library/src/meta/counter.rs delete mode 100644 crates/typst-library/src/meta/document.rs delete mode 100644 crates/typst-library/src/meta/figure.rs delete mode 100644 crates/typst-library/src/meta/footnote.rs delete mode 100644 crates/typst-library/src/meta/heading.rs delete mode 100644 crates/typst-library/src/meta/link.rs delete mode 100644 crates/typst-library/src/meta/metadata.rs delete mode 100644 crates/typst-library/src/meta/mod.rs delete mode 100644 crates/typst-library/src/meta/numbering.rs delete mode 100644 crates/typst-library/src/meta/outline.rs delete mode 100644 crates/typst-library/src/meta/query.rs delete mode 100644 crates/typst-library/src/meta/reference.rs delete mode 100644 crates/typst-library/src/meta/state.rs delete mode 100644 crates/typst-library/src/prelude.rs delete mode 100644 crates/typst-library/src/shared/behave.rs delete mode 100644 crates/typst-library/src/shared/ext.rs delete mode 100644 crates/typst-library/src/shared/mod.rs delete mode 100644 crates/typst-library/src/symbols/emoji.rs delete mode 100644 crates/typst-library/src/symbols/mod.rs delete mode 100644 crates/typst-library/src/symbols/sym.rs delete mode 100644 crates/typst-library/src/text/deco.rs delete mode 100644 crates/typst-library/src/text/linebreak.rs delete mode 100644 crates/typst-library/src/text/misc.rs delete mode 100644 crates/typst-library/src/text/mod.rs delete mode 100644 crates/typst-library/src/text/quote.rs delete mode 100644 crates/typst-library/src/text/quotes.rs delete mode 100644 crates/typst-library/src/text/raw.rs delete mode 100644 crates/typst-library/src/text/shaping.rs delete mode 100644 crates/typst-library/src/text/shift.rs delete mode 100644 crates/typst-library/src/visualize/image.rs delete mode 100644 crates/typst-library/src/visualize/line.rs delete mode 100644 crates/typst-library/src/visualize/mod.rs delete mode 100644 crates/typst-library/src/visualize/path.rs delete mode 100644 crates/typst-library/src/visualize/polygon.rs delete mode 100644 crates/typst-library/src/visualize/shape.rs create mode 100644 crates/typst-macros/src/category.rs create mode 100644 crates/typst/assets/cj_linebreak_data.postcard create mode 100644 crates/typst/assets/icudata.postcard create mode 100644 crates/typst/assets/syntect.bin delete mode 100644 crates/typst/src/doc.rs create mode 100644 crates/typst/src/eval/access.rs delete mode 100644 crates/typst/src/eval/args.rs delete mode 100644 crates/typst/src/eval/array.rs delete mode 100644 crates/typst/src/eval/auto.rs create mode 100644 crates/typst/src/eval/binding.rs delete mode 100644 crates/typst/src/eval/bool.rs delete mode 100644 crates/typst/src/eval/bytes.rs create mode 100644 crates/typst/src/eval/call.rs delete mode 100644 crates/typst/src/eval/cast.rs create mode 100644 crates/typst/src/eval/code.rs delete mode 100644 crates/typst/src/eval/datetime.rs delete mode 100644 crates/typst/src/eval/dict.rs delete mode 100644 crates/typst/src/eval/duration.rs delete mode 100644 crates/typst/src/eval/fields.rs delete mode 100644 crates/typst/src/eval/float.rs create mode 100644 crates/typst/src/eval/flow.rs delete mode 100644 crates/typst/src/eval/func.rs create mode 100644 crates/typst/src/eval/import.rs delete mode 100644 crates/typst/src/eval/int.rs delete mode 100644 crates/typst/src/eval/library.rs create mode 100644 crates/typst/src/eval/markup.rs create mode 100644 crates/typst/src/eval/math.rs delete mode 100644 crates/typst/src/eval/methods.rs delete mode 100644 crates/typst/src/eval/module.rs delete mode 100644 crates/typst/src/eval/none.rs delete mode 100644 crates/typst/src/eval/plugin.rs delete mode 100644 crates/typst/src/eval/repr.rs create mode 100644 crates/typst/src/eval/rules.rs delete mode 100644 crates/typst/src/eval/scope.rs delete mode 100644 crates/typst/src/eval/str.rs delete mode 100644 crates/typst/src/eval/symbol.rs delete mode 100644 crates/typst/src/eval/ty.rs delete mode 100644 crates/typst/src/eval/value.rs delete mode 100644 crates/typst/src/eval/version.rs create mode 100644 crates/typst/src/eval/vm.rs delete mode 100644 crates/typst/src/font/book.rs delete mode 100644 crates/typst/src/font/mod.rs delete mode 100644 crates/typst/src/font/variant.rs create mode 100644 crates/typst/src/foundations/args.rs create mode 100644 crates/typst/src/foundations/array.rs create mode 100644 crates/typst/src/foundations/auto.rs create mode 100644 crates/typst/src/foundations/bool.rs create mode 100644 crates/typst/src/foundations/bytes.rs create mode 100644 crates/typst/src/foundations/calc.rs create mode 100644 crates/typst/src/foundations/cast.rs create mode 100644 crates/typst/src/foundations/content.rs create mode 100644 crates/typst/src/foundations/datetime.rs create mode 100644 crates/typst/src/foundations/dict.rs create mode 100644 crates/typst/src/foundations/duration.rs create mode 100644 crates/typst/src/foundations/element.rs create mode 100644 crates/typst/src/foundations/fields.rs create mode 100644 crates/typst/src/foundations/float.rs create mode 100644 crates/typst/src/foundations/func.rs create mode 100644 crates/typst/src/foundations/int.rs create mode 100644 crates/typst/src/foundations/label.rs create mode 100644 crates/typst/src/foundations/methods.rs create mode 100644 crates/typst/src/foundations/mod.rs create mode 100644 crates/typst/src/foundations/module.rs create mode 100644 crates/typst/src/foundations/none.rs create mode 100644 crates/typst/src/foundations/plugin.rs create mode 100644 crates/typst/src/foundations/repr.rs create mode 100644 crates/typst/src/foundations/scope.rs create mode 100644 crates/typst/src/foundations/selector.rs create mode 100644 crates/typst/src/foundations/str.rs create mode 100644 crates/typst/src/foundations/styles.rs create mode 100644 crates/typst/src/foundations/sys.rs create mode 100644 crates/typst/src/foundations/ty.rs create mode 100644 crates/typst/src/foundations/value.rs create mode 100644 crates/typst/src/foundations/version.rs delete mode 100644 crates/typst/src/geom/abs.rs delete mode 100644 crates/typst/src/geom/align.rs delete mode 100644 crates/typst/src/geom/angle.rs delete mode 100644 crates/typst/src/geom/axes.rs delete mode 100644 crates/typst/src/geom/color.rs delete mode 100644 crates/typst/src/geom/corners.rs delete mode 100644 crates/typst/src/geom/dir.rs delete mode 100644 crates/typst/src/geom/ellipse.rs delete mode 100644 crates/typst/src/geom/em.rs delete mode 100644 crates/typst/src/geom/fr.rs delete mode 100644 crates/typst/src/geom/gradient.rs delete mode 100644 crates/typst/src/geom/length.rs delete mode 100644 crates/typst/src/geom/macros.rs delete mode 100644 crates/typst/src/geom/mod.rs delete mode 100644 crates/typst/src/geom/paint.rs delete mode 100644 crates/typst/src/geom/path.rs delete mode 100644 crates/typst/src/geom/point.rs delete mode 100644 crates/typst/src/geom/ratio.rs delete mode 100644 crates/typst/src/geom/rect.rs delete mode 100644 crates/typst/src/geom/rel.rs delete mode 100644 crates/typst/src/geom/scalar.rs delete mode 100644 crates/typst/src/geom/shape.rs delete mode 100644 crates/typst/src/geom/sides.rs delete mode 100644 crates/typst/src/geom/size.rs delete mode 100644 crates/typst/src/geom/stroke.rs delete mode 100644 crates/typst/src/geom/transform.rs delete mode 100644 crates/typst/src/image/mod.rs delete mode 100644 crates/typst/src/image/raster.rs delete mode 100644 crates/typst/src/image/svg.rs create mode 100644 crates/typst/src/introspection/counter.rs create mode 100644 crates/typst/src/introspection/introspector.rs create mode 100644 crates/typst/src/introspection/locate.rs create mode 100644 crates/typst/src/introspection/location.rs create mode 100644 crates/typst/src/introspection/locator.rs create mode 100644 crates/typst/src/introspection/metadata.rs create mode 100644 crates/typst/src/introspection/mod.rs create mode 100644 crates/typst/src/introspection/query.rs create mode 100644 crates/typst/src/introspection/state.rs create mode 100644 crates/typst/src/layout/abs.rs create mode 100644 crates/typst/src/layout/align.rs create mode 100644 crates/typst/src/layout/angle.rs create mode 100644 crates/typst/src/layout/axes.rs create mode 100644 crates/typst/src/layout/columns.rs create mode 100644 crates/typst/src/layout/container.rs create mode 100644 crates/typst/src/layout/corners.rs create mode 100644 crates/typst/src/layout/dir.rs create mode 100644 crates/typst/src/layout/em.rs create mode 100644 crates/typst/src/layout/flow.rs create mode 100644 crates/typst/src/layout/fr.rs create mode 100644 crates/typst/src/layout/fragment.rs create mode 100644 crates/typst/src/layout/frame.rs create mode 100644 crates/typst/src/layout/grid.rs create mode 100644 crates/typst/src/layout/hide.rs create mode 100644 crates/typst/src/layout/inline/linebreak.rs create mode 100644 crates/typst/src/layout/inline/mod.rs create mode 100644 crates/typst/src/layout/inline/shaping.rs create mode 100644 crates/typst/src/layout/layout.rs create mode 100644 crates/typst/src/layout/length.rs create mode 100644 crates/typst/src/layout/measure.rs create mode 100644 crates/typst/src/layout/mod.rs create mode 100644 crates/typst/src/layout/pad.rs create mode 100644 crates/typst/src/layout/page.rs create mode 100644 crates/typst/src/layout/place.rs create mode 100644 crates/typst/src/layout/point.rs create mode 100644 crates/typst/src/layout/ratio.rs create mode 100644 crates/typst/src/layout/regions.rs create mode 100644 crates/typst/src/layout/rel.rs create mode 100644 crates/typst/src/layout/repeat.rs create mode 100644 crates/typst/src/layout/sides.rs create mode 100644 crates/typst/src/layout/size.rs create mode 100644 crates/typst/src/layout/spacing.rs create mode 100644 crates/typst/src/layout/stack.rs create mode 100644 crates/typst/src/layout/transform.rs create mode 100644 crates/typst/src/layout/vt.rs create mode 100644 crates/typst/src/loading/cbor.rs create mode 100644 crates/typst/src/loading/csv.rs create mode 100644 crates/typst/src/loading/json.rs create mode 100644 crates/typst/src/loading/mod.rs create mode 100644 crates/typst/src/loading/read.rs create mode 100644 crates/typst/src/loading/toml.rs create mode 100644 crates/typst/src/loading/xml.rs create mode 100644 crates/typst/src/loading/yaml.rs create mode 100644 crates/typst/src/math/accent.rs create mode 100644 crates/typst/src/math/align.rs create mode 100644 crates/typst/src/math/attach.rs create mode 100644 crates/typst/src/math/cancel.rs create mode 100644 crates/typst/src/math/class.rs create mode 100644 crates/typst/src/math/ctx.rs create mode 100644 crates/typst/src/math/equation.rs create mode 100644 crates/typst/src/math/frac.rs create mode 100644 crates/typst/src/math/fragment.rs create mode 100644 crates/typst/src/math/lr.rs create mode 100644 crates/typst/src/math/matrix.rs create mode 100644 crates/typst/src/math/mod.rs create mode 100644 crates/typst/src/math/op.rs create mode 100644 crates/typst/src/math/root.rs create mode 100644 crates/typst/src/math/row.rs create mode 100644 crates/typst/src/math/spacing.rs create mode 100644 crates/typst/src/math/stretch.rs create mode 100644 crates/typst/src/math/style.rs create mode 100644 crates/typst/src/math/underover.rs create mode 100644 crates/typst/src/model/bibliography.rs create mode 100644 crates/typst/src/model/cite.rs delete mode 100644 crates/typst/src/model/content.rs create mode 100644 crates/typst/src/model/document.rs delete mode 100644 crates/typst/src/model/element.rs create mode 100644 crates/typst/src/model/emph.rs create mode 100644 crates/typst/src/model/enum.rs create mode 100644 crates/typst/src/model/figure.rs create mode 100644 crates/typst/src/model/footnote.rs create mode 100644 crates/typst/src/model/heading.rs delete mode 100644 crates/typst/src/model/introspect.rs delete mode 100644 crates/typst/src/model/label.rs create mode 100644 crates/typst/src/model/link.rs create mode 100644 crates/typst/src/model/list.rs delete mode 100644 crates/typst/src/model/location.rs create mode 100644 crates/typst/src/model/numbering.rs create mode 100644 crates/typst/src/model/outline.rs create mode 100644 crates/typst/src/model/par.rs create mode 100644 crates/typst/src/model/quote.rs delete mode 100644 crates/typst/src/model/realize.rs create mode 100644 crates/typst/src/model/reference.rs delete mode 100644 crates/typst/src/model/selector.rs create mode 100644 crates/typst/src/model/strong.rs delete mode 100644 crates/typst/src/model/styles.rs create mode 100644 crates/typst/src/model/table.rs create mode 100644 crates/typst/src/model/terms.rs create mode 100644 crates/typst/src/realize/behave.rs create mode 100644 crates/typst/src/realize/mod.rs create mode 100644 crates/typst/src/symbols/emoji.rs create mode 100644 crates/typst/src/symbols/mod.rs create mode 100644 crates/typst/src/symbols/sym.rs create mode 100644 crates/typst/src/symbols/symbol.rs create mode 100644 crates/typst/src/text/case.rs create mode 100644 crates/typst/src/text/deco.rs create mode 100644 crates/typst/src/text/font/book.rs create mode 100644 crates/typst/src/text/font/mod.rs create mode 100644 crates/typst/src/text/font/variant.rs create mode 100644 crates/typst/src/text/item.rs create mode 100644 crates/typst/src/text/lang.rs create mode 100644 crates/typst/src/text/linebreak.rs create mode 100644 crates/typst/src/text/lorem.rs create mode 100644 crates/typst/src/text/mod.rs create mode 100644 crates/typst/src/text/raw.rs create mode 100644 crates/typst/src/text/shift.rs create mode 100644 crates/typst/src/text/smallcaps.rs create mode 100644 crates/typst/src/text/smartquote.rs create mode 100644 crates/typst/src/text/space.rs create mode 100644 crates/typst/src/util/fat.rs create mode 100644 crates/typst/src/util/macros.rs create mode 100644 crates/typst/src/util/scalar.rs create mode 100644 crates/typst/src/visualize/color.rs create mode 100644 crates/typst/src/visualize/gradient.rs create mode 100644 crates/typst/src/visualize/image/mod.rs create mode 100644 crates/typst/src/visualize/image/raster.rs create mode 100644 crates/typst/src/visualize/image/svg.rs create mode 100644 crates/typst/src/visualize/line.rs create mode 100644 crates/typst/src/visualize/mod.rs create mode 100644 crates/typst/src/visualize/paint.rs create mode 100644 crates/typst/src/visualize/path.rs create mode 100644 crates/typst/src/visualize/polygon.rs create mode 100644 crates/typst/src/visualize/shape.rs create mode 100644 crates/typst/src/visualize/stroke.rs delete mode 100644 docs/reference/categories.yml create mode 100644 docs/reference/packages.md diff --git a/Cargo.lock b/Cargo.lock index 2bd65a40..8279a484 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2704,14 +2704,26 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" name = "typst" version = "0.9.0" dependencies = [ + "az", "bitflags 2.4.1", + "chinese-number", + "ciborium", "comemo", + "csv", "ecow", "fontdb", + "hayagriva", + "hypher", + "icu_properties", + "icu_provider", + "icu_provider_adapters", + "icu_provider_blob", + "icu_segmenter", "image", "indexmap 2.0.2", "kurbo", "lasso", + "lipsum", "log", "once_cell", "palette", @@ -2720,16 +2732,22 @@ dependencies = [ "roxmltree", "rustybuzz", "serde", + "serde_json", + "serde_yaml 0.9.27", "siphasher", "smallvec", "stacker", + "syntect", "time", "toml", "tracing", "ttf-parser", + "typed-arena", "typst-macros", "typst-syntax", + "unicode-bidi", "unicode-math-class", + "unicode-script", "unicode-segmentation", "usvg", "wasmi", @@ -2771,7 +2789,6 @@ dependencies = [ "tracing-flame", "tracing-subscriber", "typst", - "typst-library", "typst-pdf", "typst-render", "typst-svg", @@ -2795,7 +2812,6 @@ dependencies = [ "syntect", "typed-arena", "typst", - "typst-library", "unicode_names2", "unscanny", "yaml-front-matter", @@ -2814,46 +2830,6 @@ dependencies = [ "unscanny", ] -[[package]] -name = "typst-library" -version = "0.9.0" -dependencies = [ - "az", - "chinese-number", - "ciborium", - "comemo", - "csv", - "ecow", - "hayagriva", - "hypher", - "icu_properties", - "icu_provider", - "icu_provider_adapters", - "icu_provider_blob", - "icu_segmenter", - "indexmap 2.0.2", - "kurbo", - "lipsum", - "log", - "once_cell", - "roxmltree", - "rustybuzz", - "serde_json", - "serde_yaml 0.9.27", - "smallvec", - "syntect", - "time", - "toml", - "tracing", - "ttf-parser", - "typed-arena", - "typst", - "unicode-bidi", - "unicode-math-class", - "unicode-script", - "unicode-segmentation", -] - [[package]] name = "typst-macros" version = "0.9.0" @@ -2948,7 +2924,6 @@ dependencies = [ "tiny-skia", "ttf-parser", "typst", - "typst-library", "typst-pdf", "typst-render", "typst-svg", diff --git a/Cargo.toml b/Cargo.toml index 57c869cb..3edde258 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,6 @@ typst = { path = "crates/typst" } typst-cli = { path = "crates/typst-cli" } typst-docs = { path = "crates/typst-docs" } typst-ide = { path = "crates/typst-ide" } -typst-library = { path = "crates/typst-library" } typst-macros = { path = "crates/typst-macros" } typst-pdf = { path = "crates/typst-pdf" } typst-render = { path = "crates/typst-render" } @@ -98,7 +97,7 @@ tar = "0.4" tempfile = "3.7.0" time = { version = "0.3.20", features = ["formatting", "macros", "parsing"] } tiny-skia = "0.11" -toml = { version = "0.8", default-features = false, features = ["parse"] } +toml = { version = "0.8", default-features = false, features = ["parse", "display"] } tracing = "0.1.37" tracing-error = "0.2" tracing-flame = "0.2.0" diff --git a/crates/typst-cli/Cargo.toml b/crates/typst-cli/Cargo.toml index 7ac4ee5e..3819469e 100644 --- a/crates/typst-cli/Cargo.toml +++ b/crates/typst-cli/Cargo.toml @@ -21,7 +21,6 @@ doc = false [dependencies] typst = { workspace = true } -typst-library = { workspace = true } typst-pdf = { workspace = true } typst-render = { workspace = true } typst-svg = { workspace = true } diff --git a/crates/typst-cli/src/args.rs b/crates/typst-cli/src/args.rs index cfd1ae08..075412cd 100644 --- a/crates/typst-cli/src/args.rs +++ b/crates/typst-cli/src/args.rs @@ -1,9 +1,8 @@ use std::fmt::{self, Display, Formatter}; use std::path::PathBuf; -use semver::Version; - use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum}; +use semver::Version; /// The character typically used to separate path components /// in environment variables. diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index 48d6401c..73115924 100644 --- a/crates/typst-cli/src/compile.rs +++ b/crates/typst-cli/src/compile.rs @@ -4,12 +4,14 @@ use std::path::{Path, PathBuf}; use chrono::{Datelike, Timelike}; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::term::{self, termcolor}; +use ecow::eco_format; use termcolor::{ColorChoice, StandardStream}; use typst::diag::{bail, Severity, SourceDiagnostic, StrResult}; -use typst::doc::Document; -use typst::eval::{eco_format, Datetime, Tracer}; -use typst::geom::Color; +use typst::eval::Tracer; +use typst::foundations::Datetime; +use typst::model::Document; use typst::syntax::{FileId, Source, Span}; +use typst::visualize::Color; use typst::{World, WorldExt}; use crate::args::{CompileCommand, DiagnosticFormat, OutputFormat}; diff --git a/crates/typst-cli/src/fonts.rs b/crates/typst-cli/src/fonts.rs index 7c785716..f4711b82 100644 --- a/crates/typst-cli/src/fonts.rs +++ b/crates/typst-cli/src/fonts.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; use fontdb::{Database, Source}; use typst::diag::StrResult; -use typst::font::{Font, FontBook, FontInfo, FontVariant}; +use typst::text::{Font, FontBook, FontInfo, FontVariant}; use crate::args::FontsCommand; @@ -106,7 +106,7 @@ impl FontSearcher { #[cfg(feature = "embed-fonts")] fn add_embedded(&mut self) { let mut process = |bytes: &'static [u8]| { - let buffer = typst::eval::Bytes::from_static(bytes); + let buffer = typst::foundations::Bytes::from_static(bytes); for (i, font) in Font::iter(buffer).enumerate() { self.book.push(font.info().clone()); self.fonts.push(FontSlot { diff --git a/crates/typst-cli/src/query.rs b/crates/typst-cli/src/query.rs index cc9cfc23..a84cef79 100644 --- a/crates/typst-cli/src/query.rs +++ b/crates/typst-cli/src/query.rs @@ -1,10 +1,13 @@ use comemo::Track; +use ecow::{eco_format, EcoString}; use serde::Serialize; use typst::diag::{bail, StrResult}; use typst::eval::{eval_string, EvalMode, Tracer}; -use typst::model::Introspector; +use typst::foundations::{Content, IntoValue, LocatableSelector, Scope}; +use typst::introspection::Introspector; +use typst::model::Document; +use typst::syntax::Span; use typst::World; -use typst_library::prelude::*; use crate::args::{QueryCommand, SerializationFormat}; use crate::compile::print_diagnostics; @@ -95,7 +98,7 @@ fn format(elements: Vec, command: &QueryCommand) -> StrResult { .collect(); if command.one { - let Some(value) = mapped.get(0) else { + let Some(value) = mapped.first() else { bail!("no such field found for element"); }; serialize(value, command.format) diff --git a/crates/typst-cli/src/tracing.rs b/crates/typst-cli/src/tracing.rs index c01efd6d..331c6327 100644 --- a/crates/typst-cli/src/tracing.rs +++ b/crates/typst-cli/src/tracing.rs @@ -6,8 +6,9 @@ use inferno::flamegraph::Options; use tracing::metadata::LevelFilter; use tracing_error::ErrorLayer; use tracing_flame::{FlameLayer, FlushGuard}; -use tracing_subscriber::fmt; -use tracing_subscriber::prelude::*; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::{fmt, Layer}; use crate::args::{CliArguments, Command}; diff --git a/crates/typst-cli/src/update.rs b/crates/typst-cli/src/update.rs index c94515b6..3cd65c9d 100644 --- a/crates/typst-cli/src/update.rs +++ b/crates/typst-cli/src/update.rs @@ -1,13 +1,12 @@ -use std::env; -use std::fs; use std::io::{Cursor, Read, Write}; use std::path::PathBuf; +use std::{env, fs}; +use ecow::eco_format; use semver::Version; use serde::Deserialize; use tempfile::NamedTempFile; use typst::diag::{bail, StrResult}; -use typst::eval::eco_format; use xz2::bufread::XzDecoder; use zip::ZipArchive; diff --git a/crates/typst-cli/src/watch.rs b/crates/typst-cli/src/watch.rs index 138f473a..aea3ca48 100644 --- a/crates/typst-cli/src/watch.rs +++ b/crates/typst-cli/src/watch.rs @@ -3,11 +3,11 @@ use std::io::{self, IsTerminal, Write}; use std::path::{Path, PathBuf}; use codespan_reporting::term::{self, termcolor}; +use ecow::eco_format; use notify::{RecommendedWatcher, RecursiveMode, Watcher}; use same_file::is_same_file; use termcolor::WriteColor; use typst::diag::StrResult; -use typst::eval::eco_format; use crate::args::CompileCommand; use crate::color_stream; diff --git a/crates/typst-cli/src/world.rs b/crates/typst-cli/src/world.rs index 3b774f1d..f375c648 100644 --- a/crates/typst-cli/src/world.rs +++ b/crates/typst-cli/src/world.rs @@ -5,13 +5,14 @@ use std::path::{Path, PathBuf}; use chrono::{DateTime, Datelike, Local}; use comemo::Prehashed; +use ecow::eco_format; use typst::diag::{FileError, FileResult, StrResult}; -use typst::doc::Frame; -use typst::eval::{eco_format, Bytes, Datetime, Library}; -use typst::font::{Font, FontBook}; +use typst::foundations::{Bytes, Datetime}; +use typst::layout::Frame; use typst::syntax::{FileId, Source, VirtualPath}; +use typst::text::{Font, FontBook}; use typst::util::hash128; -use typst::World; +use typst::{Library, World}; use crate::args::SharedArgs; use crate::fonts::{FontSearcher, FontSlot}; @@ -75,7 +76,7 @@ impl SystemWorld { input, root, main: FileId::new(None, main_path), - library: Prehashed::new(typst_library::build()), + library: Prehashed::new(Library::build()), book: Prehashed::new(searcher.book), fonts: searcher.fonts, slots: RefCell::default(), diff --git a/crates/typst-docs/Cargo.toml b/crates/typst-docs/Cargo.toml index b2e82e43..152f5e79 100644 --- a/crates/typst-docs/Cargo.toml +++ b/crates/typst-docs/Cargo.toml @@ -12,7 +12,6 @@ bench = false [dependencies] typst = { workspace = true } -typst-library = { workspace = true } comemo = { workspace = true } ecow = { workspace = true } heck = { workspace = true } diff --git a/crates/typst-docs/src/contribs.rs b/crates/typst-docs/src/contribs.rs index cbd87dc6..58a730e2 100644 --- a/crates/typst-docs/src/contribs.rs +++ b/crates/typst-docs/src/contribs.rs @@ -4,7 +4,7 @@ use std::fmt::Write; use serde::{Deserialize, Serialize}; -use super::{Html, Resolver}; +use crate::{Html, Resolver}; /// Build HTML detailing the contributors between two tags. pub fn contributors(resolver: &dyn Resolver, from: &str, to: &str) -> Option { diff --git a/crates/typst-docs/src/html.rs b/crates/typst-docs/src/html.rs index 1210545f..7481b050 100644 --- a/crates/typst-docs/src/html.rs +++ b/crates/typst-docs/src/html.rs @@ -8,11 +8,12 @@ use pulldown_cmark as md; use serde::{Deserialize, Serialize}; use typed_arena::Arena; use typst::diag::{FileResult, StrResult}; -use typst::eval::{Bytes, Datetime, Library, Tracer}; -use typst::font::{Font, FontBook}; -use typst::geom::{Abs, Point, Size}; +use typst::eval::Tracer; +use typst::foundations::{Bytes, Datetime}; +use typst::layout::{Abs, Point, Size}; use typst::syntax::{FileId, Source, VirtualPath}; -use typst::World; +use typst::text::{Font, FontBook}; +use typst::{Library, World}; use unscanny::Scanner; use yaml_front_matter::YamlFrontMatter; @@ -360,13 +361,13 @@ fn code_block(resolver: &dyn Resolver, lang: &str, text: &str) -> Html { buf.push_str(""); return Html::new(buf); } else if !matches!(lang, "example" | "typ" | "preview") { - let set = &*typst_library::text::SYNTAXES; + let set = &*typst::text::RAW_SYNTAXES; let buf = syntect::html::highlighted_html_for_string( &display, set, set.find_syntax_by_token(lang) .unwrap_or_else(|| panic!("unsupported highlighting language: {lang}")), - &typst_library::text::THEME, + &typst::text::RAW_THEME, ) .expect("failed to highlight code"); return Html::new(buf); diff --git a/crates/typst-docs/src/lib.rs b/crates/typst-docs/src/lib.rs index d58734c6..444dda32 100644 --- a/crates/typst-docs/src/lib.rs +++ b/crates/typst-docs/src/lib.rs @@ -5,9 +5,9 @@ mod html; mod link; mod model; -pub use contribs::{contributors, Author, Commit}; -pub use html::Html; -pub use model::*; +pub use self::contribs::*; +pub use self::html::*; +pub use self::model::*; use std::path::Path; @@ -20,30 +20,48 @@ use serde::de::DeserializeOwned; use serde::Deserialize; use serde_yaml as yaml; use typst::diag::{bail, StrResult}; -use typst::doc::Frame; -use typst::eval::{ - CastInfo, Func, Library, Module, ParamInfo, Repr, Scope, Smart, Type, Value, +use typst::foundations::{ + CastInfo, Category, Func, Module, ParamInfo, Repr, Scope, Smart, Type, Value, + FOUNDATIONS, }; -use typst::font::{Font, FontBook}; -use typst::geom::Abs; -use typst_library::layout::{Margin, PageElem}; +use typst::introspection::INTROSPECTION; +use typst::layout::{Abs, Frame, Margin, PageElem, LAYOUT}; +use typst::loading::DATA_LOADING; +use typst::math::MATH; +use typst::model::MODEL; +use typst::symbols::SYMBOLS; +use typst::text::{Font, FontBook, TEXT}; +use typst::visualize::VISUALIZE; +use typst::Library; static DOCS_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../../docs"); static FILE_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../../assets/files"); static FONT_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../../assets/fonts"); -static CATEGORIES: Lazy = Lazy::new(|| yaml("reference/categories.yml")); -static GROUPS: Lazy> = Lazy::new(|| yaml("reference/groups.yml")); +static GROUPS: Lazy> = Lazy::new(|| { + let mut groups: Vec = yaml("reference/groups.yml"); + for group in &mut groups { + if group.filter.is_empty() { + group.filter = group + .module() + .scope() + .iter() + .filter(|(_, v)| matches!(v, Value::Func(_))) + .map(|(k, _)| k.clone()) + .collect(); + } + } + groups +}); static LIBRARY: Lazy> = Lazy::new(|| { - let mut lib = typst_library::build(); + let mut lib = Library::build(); lib.styles .set(PageElem::set_width(Smart::Custom(Abs::pt(240.0).into()))); lib.styles.set(PageElem::set_height(Smart::Auto)); lib.styles.set(PageElem::set_margin(Margin::splat(Some(Smart::Custom( Abs::pt(15.0).into(), ))))); - typst::eval::set_lang_items(lib.items.clone()); Prehashed::new(lib) }); @@ -128,14 +146,15 @@ fn reference_pages(resolver: &dyn Resolver) -> PageModel { .with_part("Language"), markdown_page(resolver, "/docs/reference/", "reference/styling.md"), markdown_page(resolver, "/docs/reference/", "reference/scripting.md"), - category_page(resolver, "foundations").with_part("Library"), - category_page(resolver, "text"), - category_page(resolver, "math"), - category_page(resolver, "layout"), - category_page(resolver, "visualize"), - category_page(resolver, "meta"), - category_page(resolver, "symbols"), - category_page(resolver, "data-loading"), + category_page(resolver, FOUNDATIONS).with_part("Library"), + category_page(resolver, MODEL), + category_page(resolver, TEXT), + category_page(resolver, MATH), + category_page(resolver, SYMBOLS), + category_page(resolver, LAYOUT), + category_page(resolver, VISUALIZE), + category_page(resolver, INTROSPECTION), + category_page(resolver, DATA_LOADING), ]; page } @@ -152,50 +171,73 @@ fn guide_pages(resolver: &dyn Resolver) -> PageModel { /// Build the packages section. fn packages_page(resolver: &dyn Resolver) -> PageModel { + let md = DOCS_DIR + .get_file("reference/packages.md") + .unwrap() + .contents_utf8() + .unwrap(); PageModel { route: "/docs/packages/".into(), title: "Packages".into(), description: "Packages for Typst.".into(), part: None, outline: vec![], - body: BodyModel::Packages(Html::markdown( - resolver, - category_details("packages"), - Some(1), - )), + body: BodyModel::Packages(Html::markdown(resolver, md, Some(1))), children: vec![], } } /// Create a page for a category. #[track_caller] -fn category_page(resolver: &dyn Resolver, category: &str) -> PageModel { - let route = eco_format!("/docs/reference/{category}/"); +fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel { + let route = eco_format!("/docs/reference/{}/", category.name()); let mut children = vec![]; let mut items = vec![]; + let mut shorthands = None; + let mut markup = vec![]; + let mut math = vec![]; - let (module, path): (&Module, &[&str]) = match category { - "math" => (&LIBRARY.math, &["math"]), - _ => (&LIBRARY.global, &[]), + let (module, path): (&Module, &[&str]) = if category == MATH { + (&LIBRARY.math, &["math"]) + } else { + (&LIBRARY.global, &[]) }; // Add groups. - for mut group in GROUPS.iter().filter(|g| g.category == category).cloned() { - let mut focus = module; - if matches!(group.name.as_str(), "calc" | "sys") { - focus = get_module(focus, &group.name).unwrap(); - group.functions = focus - .scope() - .iter() - .filter(|(_, v)| matches!(v, Value::Func(_))) - .map(|(k, _)| k.clone()) - .collect(); + for group in GROUPS.iter().filter(|g| g.category == category.name()).cloned() { + if matches!(group.name.as_str(), "sym" | "emoji") { + let subpage = symbols_page(resolver, &route, &group); + let BodyModel::Symbols(model) = &subpage.body else { continue }; + let list = &model.list; + markup.extend( + list.iter() + .filter(|symbol| symbol.markup_shorthand.is_some()) + .cloned(), + ); + math.extend( + list.iter().filter(|symbol| symbol.math_shorthand.is_some()).cloned(), + ); + + items.push(CategoryItem { + name: group.name.clone(), + route: subpage.route.clone(), + oneliner: oneliner(category.docs()).into(), + code: true, + }); + children.push(subpage); + continue; } - let (child, item) = group_page(resolver, &route, &group, focus.scope()); + + let (child, item) = group_page(resolver, &route, &group); children.push(child); items.push(item); } + // Add symbol pages. These are ordered manually. + if category == SYMBOLS { + shorthands = Some(ShorthandsModel { markup, math }); + } + // Add functions. let scope = module.scope(); for (name, value) in scope.iter() { @@ -203,9 +245,9 @@ fn category_page(resolver: &dyn Resolver, category: &str) -> PageModel { continue; } - if category == "math" { + if category == MATH { // Skip grouped functions. - if GROUPS.iter().flat_map(|group| &group.functions).any(|f| f == name) { + if GROUPS.iter().flat_map(|group| &group.filter).any(|f| f == name) { continue; } @@ -242,54 +284,35 @@ fn category_page(resolver: &dyn Resolver, category: &str) -> PageModel { } } - children.sort_by_cached_key(|child| child.title.clone()); - items.sort_by_cached_key(|item| item.name.clone()); - - // Add symbol pages. These are ordered manually. - let mut shorthands = None; - if category == "symbols" { - let mut markup = vec![]; - let mut math = vec![]; - for module in ["sym", "emoji"] { - let subpage = symbols_page(resolver, &route, module); - let BodyModel::Symbols(model) = &subpage.body else { continue }; - let list = &model.list; - markup.extend( - list.iter() - .filter(|symbol| symbol.markup_shorthand.is_some()) - .cloned(), - ); - math.extend( - list.iter().filter(|symbol| symbol.math_shorthand.is_some()).cloned(), - ); - - items.push(CategoryItem { - name: module.into(), - route: subpage.route.clone(), - oneliner: oneliner(category_details(module)).into(), - code: true, - }); - children.push(subpage); - } - shorthands = Some(ShorthandsModel { markup, math }); + if category != SYMBOLS { + children.sort_by_cached_key(|child| child.title.clone()); + items.sort_by_cached_key(|item| item.name.clone()); } - let name: EcoString = category.to_title_case().into(); - - let details = Html::markdown(resolver, category_details(category), Some(1)); + let name = category.title(); + let details = Html::markdown(resolver, category.docs(), Some(1)); let mut outline = vec![OutlineItem::from_name("Summary")]; outline.extend(details.outline()); outline.push(OutlineItem::from_name("Definitions")); + if shorthands.is_some() { + outline.push(OutlineItem::from_name("Shorthands")); + } PageModel { route, - title: name.clone(), + title: name.into(), description: eco_format!( "Documentation for functions related to {name} in Typst." ), part: None, outline, - body: BodyModel::Category(CategoryModel { name, details, items, shorthands }), + body: BodyModel::Category(CategoryModel { + name: category.name(), + title: category.title(), + details, + items, + shorthands, + }), children, } } @@ -498,18 +521,17 @@ fn group_page( resolver: &dyn Resolver, parent: &str, group: &GroupData, - scope: &Scope, ) -> (PageModel, CategoryItem) { let mut functions = vec![]; let mut outline = vec![OutlineItem::from_name("Summary")]; let path: Vec<_> = group.path.iter().map(|s| s.as_str()).collect(); - let details = Html::markdown(resolver, &group.description, Some(1)); + let details = Html::markdown(resolver, &group.details, Some(1)); outline.extend(details.outline()); let mut outline_items = vec![]; - for name in &group.functions { - let value = scope.get(name).unwrap(); + for name in &group.filter { + let value = group.module().scope().get(name).unwrap(); let Value::Func(func) = value else { panic!("not a function") }; let func = func_model(resolver, func, &path, true); let id_base = urlify(&eco_format!("functions-{}", func.name)); @@ -530,13 +552,13 @@ fn group_page( let model = PageModel { route: eco_format!("{parent}{}", group.name), - title: group.display.clone(), + title: group.title.clone(), description: eco_format!("Documentation for the {} functions.", group.name), part: None, outline, body: BodyModel::Group(GroupModel { name: group.name.clone(), - title: group.display.clone(), + title: group.title.clone(), details, functions, }), @@ -546,7 +568,7 @@ fn group_page( let item = CategoryItem { name: group.name.clone(), route: model.route.clone(), - oneliner: oneliner(&group.description).into(), + oneliner: oneliner(&group.details).into(), code: false, }; @@ -601,19 +623,12 @@ fn type_outline(model: &TypeModel) -> Vec { } /// Create a page for symbols. -fn symbols_page(resolver: &dyn Resolver, parent: &str, name: &str) -> PageModel { - let module = get_module(&LIBRARY.global, name).unwrap(); - let title = match name { - "sym" => "General", - "emoji" => "Emoji", - _ => unreachable!(), - }; - - let model = symbols_model(resolver, name, title, module.scope()); +fn symbols_page(resolver: &dyn Resolver, parent: &str, group: &GroupData) -> PageModel { + let model = symbols_model(resolver, group); PageModel { - route: eco_format!("{parent}{name}/"), - title: title.into(), - description: eco_format!("Documentation for the `{name}` module."), + route: eco_format!("{parent}{}/", group.name), + title: group.title.clone(), + description: eco_format!("Documentation for the `{}` module.", group.name), part: None, outline: vec![], body: BodyModel::Symbols(model), @@ -622,14 +637,9 @@ fn symbols_page(resolver: &dyn Resolver, parent: &str, name: &str) -> PageModel } /// Produce a symbol list's model. -fn symbols_model( - resolver: &dyn Resolver, - name: &str, - title: &'static str, - scope: &Scope, -) -> SymbolsModel { +fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel { let mut list = vec![]; - for (name, value) in scope.iter() { + for (name, value) in group.module().scope().iter() { let Value::Symbol(symbol) = value else { continue }; let complete = |variant: &str| { if variant.is_empty() { @@ -649,7 +659,7 @@ fn symbols_model( markup_shorthand: shorthand(typst::syntax::ast::Shorthand::MARKUP_LIST), math_shorthand: shorthand(typst::syntax::ast::Shorthand::MATH_LIST), codepoint: c as u32, - accent: typst::eval::Symbol::combining_accent(c).is_some(), + accent: typst::symbols::Symbol::combining_accent(c).is_some(), unicode_name: unicode_names2::name(c) .map(|s| s.to_string().to_title_case().into()), alternates: symbol @@ -662,8 +672,9 @@ fn symbols_model( } SymbolsModel { - name: title, - details: Html::markdown(resolver, category_details(name), Some(1)), + name: group.name.clone(), + title: group.title.clone(), + details: Html::markdown(resolver, &group.details, Some(1)), list, } } @@ -684,15 +695,6 @@ fn yaml(path: &str) -> T { yaml::from_slice(file.contents()).unwrap() } -/// Load details for an identifying key. -#[track_caller] -fn category_details(key: &str) -> &str { - CATEGORIES - .get(&yaml::Value::String(key.into())) - .and_then(|value| value.as_str()) - .unwrap_or_else(|| panic!("missing details for {key}")) -} - /// Turn a title into an URL fragment. pub fn urlify(title: &str) -> EcoString { title @@ -752,13 +754,23 @@ const TYPE_ORDER: &[&str] = &[ #[derive(Debug, Clone, Deserialize)] struct GroupData { name: EcoString, + title: EcoString, category: EcoString, - display: EcoString, #[serde(default)] path: Vec, #[serde(default)] - functions: Vec, - description: EcoString, + filter: Vec, + details: EcoString, +} + +impl GroupData { + fn module(&self) -> &'static Module { + let mut focus = &LIBRARY.global; + for path in &self.path { + focus = get_module(focus, path).unwrap(); + } + focus + } } #[cfg(test)] diff --git a/crates/typst-docs/src/link.rs b/crates/typst-docs/src/link.rs index 38730c32..e2721b95 100644 --- a/crates/typst-docs/src/link.rs +++ b/crates/typst-docs/src/link.rs @@ -1,5 +1,5 @@ use typst::diag::{bail, StrResult}; -use typst::eval::Func; +use typst::foundations::Func; use crate::{get_module, GROUPS, LIBRARY}; @@ -55,6 +55,15 @@ fn resolve_known(head: &str) -> Option<&'static str> { fn resolve_definition(head: &str) -> StrResult { let mut parts = head.trim_start_matches('$').split('.').peekable(); let mut focus = &LIBRARY.global; + + let Some(name) = parts.peek() else { + bail!("missing first link component"); + }; + + let Some(category) = focus.scope().get_category(name) else { + bail!("{name} has no category"); + }; + while let Some(m) = parts.peek().and_then(|&name| get_module(focus, name).ok()) { focus = m; parts.next(); @@ -62,18 +71,15 @@ fn resolve_definition(head: &str) -> StrResult { let name = parts.next().ok_or("link is missing first part")?; let value = focus.field(name)?; - let Some(category) = focus.scope().get_category(name) else { - bail!("{name} has no category"); - }; // Handle grouped functions. - if let Some(group) = GROUPS - .iter() - .filter(|_| category == "math") - .find(|group| group.functions.iter().any(|func| func == name)) - { - let mut route = - format!("/docs/reference/math/{}/#functions-{}", group.name, name); + if let Some(group) = GROUPS.iter().find(|group| { + group.category == category.name() && group.filter.iter().any(|func| func == name) + }) { + let mut route = format!( + "/docs/reference/{}/{}/#functions-{}", + group.category, group.name, name + ); if let Some(param) = parts.next() { route.push('-'); route.push_str(param); @@ -81,7 +87,7 @@ fn resolve_definition(head: &str) -> StrResult { return Ok(route); } - let mut route = format!("/docs/reference/{category}/{name}/"); + let mut route = format!("/docs/reference/{}/{name}/", category.name()); if let Some(next) = parts.next() { if value.field(next).is_ok() { route.push_str("#definitions-"); diff --git a/crates/typst-docs/src/model.rs b/crates/typst-docs/src/model.rs index 580ae0d3..93742825 100644 --- a/crates/typst-docs/src/model.rs +++ b/crates/typst-docs/src/model.rs @@ -62,7 +62,8 @@ pub enum BodyModel { /// Details about a category. #[derive(Debug, Serialize)] pub struct CategoryModel { - pub name: EcoString, + pub name: &'static str, + pub title: &'static str, pub details: Html, pub items: Vec, pub shorthands: Option, @@ -144,7 +145,8 @@ pub struct TypeModel { /// A collection of symbols. #[derive(Debug, Serialize)] pub struct SymbolsModel { - pub name: &'static str, + pub name: EcoString, + pub title: EcoString, pub details: Html, pub list: Vec, } diff --git a/crates/typst-ide/src/analyze.rs b/crates/typst-ide/src/analyze.rs index 4d12e1c5..2d48039b 100644 --- a/crates/typst-ide/src/analyze.rs +++ b/crates/typst-ide/src/analyze.rs @@ -1,8 +1,11 @@ use comemo::Track; use ecow::{eco_vec, EcoString, EcoVec}; -use typst::doc::Frame; -use typst::eval::{Route, Scopes, Tracer, Value, Vm}; -use typst::model::{DelayedErrors, Introspector, Label, Locator, Vt}; +use typst::diag::DelayedErrors; +use typst::eval::{Route, Tracer, Vm}; +use typst::foundations::{Label, Scopes, Value}; +use typst::introspection::{Introspector, Locator}; +use typst::layout::{Frame, Vt}; +use typst::model::BibliographyElem; use typst::syntax::{ast, LinkedNode, Span, SyntaxKind}; use typst::World; @@ -75,13 +78,9 @@ pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option { /// - All labels and descriptions for them, if available /// - A split offset: All labels before this offset belong to nodes, all after /// belong to a bibliography. -pub fn analyze_labels( - world: &dyn World, - frames: &[Frame], -) -> (Vec<(Label, Option)>, usize) { +pub fn analyze_labels(frames: &[Frame]) -> (Vec<(Label, Option)>, usize) { let mut output = vec![]; let introspector = Introspector::new(frames); - let items = &world.library().items; // Labels in the document. for elem in introspector.all() { @@ -102,7 +101,7 @@ pub fn analyze_labels( let split = output.len(); // Bibliography keys. - for (key, detail) in (items.bibliography_keys)(introspector.track()) { + for (key, detail) in BibliographyElem::keys(introspector.track()) { output.push((Label::new(&key), detail)); } diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index 71ae95db..5cff5a81 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -4,21 +4,21 @@ use std::collections::{BTreeSet, HashSet}; use ecow::{eco_format, EcoString}; use if_chain::if_chain; use serde::{Deserialize, Serialize}; -use typst::doc::Frame; -use typst::eval::{ - format_str, repr, AutoValue, CastInfo, Func, Library, NoneValue, Repr, Scope, Type, - Value, +use typst::foundations::{ + fields_on, format_str, mutable_methods_on, repr, AutoValue, CastInfo, Func, Label, + NoneValue, Repr, Scope, Type, Value, }; -use typst::geom::Color; -use typst::model::Label; +use typst::layout::Frame; use typst::syntax::{ ast, is_id_continue, is_id_start, is_ident, LinkedNode, Source, SyntaxKind, }; +use typst::text::RawElem; +use typst::visualize::Color; use typst::World; use unscanny::Scanner; -use crate::analyze::analyze_labels; -use crate::{analyze_expr, analyze_import, plain_docs_sentence, summarize_font_family}; +use crate::analyze::{analyze_expr, analyze_import, analyze_labels}; +use crate::{plain_docs_sentence, summarize_font_family}; /// Autocomplete a cursor position in a source file. /// @@ -367,7 +367,7 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) { } } - for &(method, args) in typst::eval::mutable_methods_on(value.ty()) { + for &(method, args) in mutable_methods_on(value.ty()) { ctx.completions.push(Completion { kind: CompletionKind::Func, label: method.into(), @@ -380,7 +380,7 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) { }) } - for &field in typst::eval::fields_on(value.ty()) { + for &field in fields_on(value.ty()) { // Complete the field name along with its value. Notes: // 1. No parentheses since function fields cannot currently be called // with method syntax; @@ -967,7 +967,6 @@ fn code_completions(ctx: &mut CompletionContext, hash: bool) { struct CompletionContext<'a> { world: &'a (dyn World + 'a), frames: &'a [Frame], - library: &'a Library, global: &'a Scope, math: &'a Scope, text: &'a str, @@ -996,7 +995,6 @@ impl<'a> CompletionContext<'a> { Some(Self { world, frames, - library, global: library.global.scope(), math: library.math.scope(), text, @@ -1074,7 +1072,7 @@ impl<'a> CompletionContext<'a> { /// Add completions for raw block tags. fn raw_completions(&mut self) { - for (name, mut tags) in (self.library.items.raw_languages)() { + for (name, mut tags) in RawElem::languages() { let lower = name.to_lowercase(); if !tags.contains(&lower.as_str()) { tags.push(lower.as_str()); @@ -1096,7 +1094,7 @@ impl<'a> CompletionContext<'a> { /// Add completions for labels and references. fn label_completions(&mut self) { - let (labels, split) = analyze_labels(self.world, self.frames); + let (labels, split) = analyze_labels(self.frames); let head = &self.text[..self.from]; let at = head.ends_with('@'); diff --git a/crates/typst-ide/src/jump.rs b/crates/typst-ide/src/jump.rs index a33e743c..700f475f 100644 --- a/crates/typst-ide/src/jump.rs +++ b/crates/typst-ide/src/jump.rs @@ -1,10 +1,11 @@ use std::num::NonZeroUsize; use ecow::EcoString; -use typst::doc::{Destination, Frame, FrameItem, Meta, Position}; -use typst::geom::{Geometry, Point, Size}; -use typst::model::Introspector; +use typst::introspection::{Introspector, Meta}; +use typst::layout::{Frame, FrameItem, Point, Position, Size}; +use typst::model::Destination; use typst::syntax::{FileId, LinkedNode, Source, Span, SyntaxKind}; +use typst::visualize::Geometry; use typst::World; /// Where to [jump](jump_from_click) to. diff --git a/crates/typst-ide/src/lib.rs b/crates/typst-ide/src/lib.rs index 3ab367dc..173a4264 100644 --- a/crates/typst-ide/src/lib.rs +++ b/crates/typst-ide/src/lib.rs @@ -13,9 +13,7 @@ pub use self::tooltip::{tooltip, Tooltip}; use std::fmt::Write; use ecow::{eco_format, EcoString}; -use typst::font::{FontInfo, FontStyle}; - -use self::analyze::*; +use typst::text::{FontInfo, FontStyle}; /// Extract the first sentence of plain text of a piece of documentation. /// diff --git a/crates/typst-ide/src/tooltip.rs b/crates/typst-ide/src/tooltip.rs index d3f040e7..4f079166 100644 --- a/crates/typst-ide/src/tooltip.rs +++ b/crates/typst-ide/src/tooltip.rs @@ -2,14 +2,15 @@ use std::fmt::Write; use ecow::{eco_format, EcoString}; use if_chain::if_chain; -use typst::doc::Frame; -use typst::eval::{repr, CapturesVisitor, CastInfo, Repr, Tracer, Value}; -use typst::geom::{round_2, Length, Numeric}; +use typst::eval::{CapturesVisitor, Tracer}; +use typst::foundations::{repr, CastInfo, Repr, Value}; +use typst::layout::{Frame, Length}; use typst::syntax::{ast, LinkedNode, Source, SyntaxKind}; +use typst::util::{round_2, Numeric}; use typst::World; -use crate::analyze::analyze_labels; -use crate::{analyze_expr, plain_docs_sentence, summarize_font_family}; +use crate::analyze::{analyze_expr, analyze_labels}; +use crate::{plain_docs_sentence, summarize_font_family}; /// Describe the item under the cursor. pub fn tooltip( @@ -25,7 +26,7 @@ pub fn tooltip( named_param_tooltip(world, &leaf) .or_else(|| font_tooltip(world, &leaf)) - .or_else(|| label_tooltip(world, frames, &leaf)) + .or_else(|| label_tooltip(frames, &leaf)) .or_else(|| expr_tooltip(world, &leaf)) .or_else(|| closure_tooltip(&leaf)) } @@ -144,18 +145,14 @@ fn length_tooltip(length: Length) -> Option { } /// Tooltip for a hovered reference or label. -fn label_tooltip( - world: &dyn World, - frames: &[Frame], - leaf: &LinkedNode, -) -> Option { +fn label_tooltip(frames: &[Frame], leaf: &LinkedNode) -> Option { let target = match leaf.kind() { SyntaxKind::RefMarker => leaf.text().trim_start_matches('@'), SyntaxKind::Label => leaf.text().trim_start_matches('<').trim_end_matches('>'), _ => return None, }; - for (label, detail) in analyze_labels(world, frames).0 { + for (label, detail) in analyze_labels(frames).0 { if label.as_str() == target { return Some(Tooltip::Text(detail?)); } diff --git a/crates/typst-library/Cargo.toml b/crates/typst-library/Cargo.toml deleted file mode 100644 index 426c56f3..00000000 --- a/crates/typst-library/Cargo.toml +++ /dev/null @@ -1,53 +0,0 @@ -[package] -name = "typst-library" -description = "The standard library for Typst." -version.workspace = true -rust-version.workspace = true -authors.workspace = true -edition.workspace = true -homepage.workspace = true -repository.workspace = true -license.workspace = true -categories.workspace = true -keywords.workspace = true - -[lib] -test = false -doctest = false -bench = false - -[dependencies] -typst = { workspace = true } -az = { workspace = true } -chinese-number = { workspace = true } -ciborium = { workspace = true } -comemo = { workspace = true } -csv = { workspace = true } -ecow = { workspace = true } -hayagriva = { workspace = true } -hypher = { workspace = true } -icu_properties = { workspace = true } -icu_provider = { workspace = true } -icu_provider_adapters = { workspace = true } -icu_provider_blob = { workspace = true } -icu_segmenter = { workspace = true } -indexmap = { workspace = true } -kurbo = { workspace = true } -lipsum = { workspace = true } -log = { workspace = true } -once_cell = { workspace = true } -roxmltree = { workspace = true } -rustybuzz = { workspace = true } -serde_json = { workspace = true } -serde_yaml = { workspace = true } -smallvec = { workspace = true } -syntect = { workspace = true } -time = { workspace = true } -toml = { workspace = true, features = ["display"] } -tracing = { workspace = true } -ttf-parser = { workspace = true } -typed-arena = { workspace = true } -unicode-bidi = { workspace = true } -unicode-math-class = { workspace = true } -unicode-script = { workspace = true } -unicode-segmentation = { workspace = true } diff --git a/crates/typst-library/assets/cj_linebreak_data.postcard b/crates/typst-library/assets/cj_linebreak_data.postcard deleted file mode 100644 index 910dd167..00000000 Binary files a/crates/typst-library/assets/cj_linebreak_data.postcard and /dev/null differ diff --git a/crates/typst-library/assets/icudata.postcard b/crates/typst-library/assets/icudata.postcard deleted file mode 100644 index a1fdbd48..00000000 Binary files a/crates/typst-library/assets/icudata.postcard and /dev/null differ diff --git a/crates/typst-library/assets/syntect.bin b/crates/typst-library/assets/syntect.bin deleted file mode 100644 index 043602a4..00000000 Binary files a/crates/typst-library/assets/syntect.bin and /dev/null differ diff --git a/crates/typst-library/src/compute/calc.rs b/crates/typst-library/src/compute/calc.rs deleted file mode 100644 index 6276905c..00000000 --- a/crates/typst-library/src/compute/calc.rs +++ /dev/null @@ -1,949 +0,0 @@ -//! Calculations and processing of numeric values. - -use std::cmp; -use std::cmp::Ordering; -use std::ops::{Div, Rem}; - -use typst::eval::{Module, Scope}; - -use crate::prelude::*; - -/// Hook up all calculation definitions. -pub(super) fn define(global: &mut Scope) { - global.category("calculate"); - global.define_module(module()); -} - -/// A module with calculation definitions. -fn module() -> Module { - let mut scope = Scope::new(); - scope.category("calculate"); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define_func::(); - scope.define("inf", f64::INFINITY); - scope.define("nan", f64::NAN); - scope.define("pi", std::f64::consts::PI); - scope.define("tau", std::f64::consts::TAU); - scope.define("e", std::f64::consts::E); - Module::new("calc", scope) -} - -/// Calculates the absolute value of a numeric value. -/// -/// ```example -/// #calc.abs(-5) \ -/// #calc.abs(5pt - 2cm) \ -/// #calc.abs(2fr) -/// ``` -#[func(title = "Absolute")] -pub fn abs( - /// The value whose absolute value to calculate. - value: ToAbs, -) -> Value { - value.0 -} - -/// A value of which the absolute value can be taken. -pub struct ToAbs(Value); - -cast! { - ToAbs, - v: i64 => Self(v.abs().into_value()), - v: f64 => Self(v.abs().into_value()), - v: Length => Self(Value::Length(v.try_abs() - .ok_or("cannot take absolute value of this length")?)), - v: Angle => Self(Value::Angle(v.abs())), - v: Ratio => Self(Value::Ratio(v.abs())), - v: Fr => Self(Value::Fraction(v.abs())), -} - -/// Raises a value to some exponent. -/// -/// ```example -/// #calc.pow(2, 3) -/// ``` -#[func(title = "Power")] -pub fn pow( - /// The callsite span. - span: Span, - /// The base of the power. - base: Num, - /// The exponent of the power. - exponent: Spanned, -) -> SourceResult { - match exponent.v { - _ if exponent.v.float() == 0.0 && base.float() == 0.0 => { - bail!(span, "zero to the power of zero is undefined") - } - Num::Int(i) if i32::try_from(i).is_err() => { - bail!(exponent.span, "exponent is too large") - } - Num::Float(f) if !f.is_normal() && f != 0.0 => { - bail!(exponent.span, "exponent may not be infinite, subnormal, or NaN") - } - _ => {} - }; - - let result = match (base, exponent.v) { - (Num::Int(a), Num::Int(b)) if b >= 0 => a - .checked_pow(b as u32) - .map(Num::Int) - .ok_or("the result is too large") - .at(span)?, - (a, b) => Num::Float(if a.float() == std::f64::consts::E { - b.float().exp() - } else if a.float() == 2.0 { - b.float().exp2() - } else if let Num::Int(b) = b { - a.float().powi(b as i32) - } else { - a.float().powf(b.float()) - }), - }; - - if result.float().is_nan() { - bail!(span, "the result is not a real number") - } - - Ok(result) -} - -/// Raises a value to some exponent of e. -/// -/// ```example -/// #calc.exp(1) -/// ``` -#[func(title = "Exponential")] -pub fn exp( - /// The callsite span. - span: Span, - /// The exponent of the power. - exponent: Spanned, -) -> SourceResult { - match exponent.v { - Num::Int(i) if i32::try_from(i).is_err() => { - bail!(exponent.span, "exponent is too large") - } - Num::Float(f) if !f.is_normal() && f != 0.0 => { - bail!(exponent.span, "exponent may not be infinite, subnormal, or NaN") - } - _ => {} - }; - - let result = exponent.v.float().exp(); - if result.is_nan() { - bail!(span, "the result is not a real number") - } - - Ok(result) -} - -/// Calculates the square root of a number. -/// -/// ```example -/// #calc.sqrt(16) \ -/// #calc.sqrt(2.5) -/// ``` -#[func(title = "Square Root")] -pub fn sqrt( - /// The number whose square root to calculate. Must be non-negative. - value: Spanned, -) -> SourceResult { - if value.v.float() < 0.0 { - bail!(value.span, "cannot take square root of negative number"); - } - Ok(value.v.float().sqrt()) -} - -/// Calculates the sine of an angle. -/// -/// When called with an integer or a float, they will be interpreted as -/// radians. -/// -/// ```example -/// #assert(calc.sin(90deg) == calc.sin(-270deg)) -/// #calc.sin(1.5) \ -/// #calc.sin(90deg) -/// ``` -#[func(title = "Sine")] -pub fn sin( - /// The angle whose sine to calculate. - angle: AngleLike, -) -> f64 { - match angle { - AngleLike::Angle(a) => a.sin(), - AngleLike::Int(n) => (n as f64).sin(), - AngleLike::Float(n) => n.sin(), - } -} - -/// Calculates the cosine of an angle. -/// -/// When called with an integer or a float, they will be interpreted as -/// radians. -/// -/// ```example -/// #calc.cos(90deg) \ -/// #calc.cos(1.5) \ -/// #calc.cos(90deg) -/// ``` -#[func(title = "Cosine")] -pub fn cos( - /// The angle whose cosine to calculate. - angle: AngleLike, -) -> f64 { - match angle { - AngleLike::Angle(a) => a.cos(), - AngleLike::Int(n) => (n as f64).cos(), - AngleLike::Float(n) => n.cos(), - } -} - -/// Calculates the tangent of an angle. -/// -/// When called with an integer or a float, they will be interpreted as -/// radians. -/// -/// ```example -/// #calc.tan(1.5) \ -/// #calc.tan(90deg) -/// ``` -#[func(title = "Tangent")] -pub fn tan( - /// The angle whose tangent to calculate. - angle: AngleLike, -) -> f64 { - match angle { - AngleLike::Angle(a) => a.tan(), - AngleLike::Int(n) => (n as f64).tan(), - AngleLike::Float(n) => n.tan(), - } -} - -/// Calculates the arcsine of a number. -/// -/// ```example -/// #calc.asin(0) \ -/// #calc.asin(1) -/// ``` -#[func(title = "Arcsine")] -pub fn asin( - /// The number whose arcsine to calculate. Must be between -1 and 1. - value: Spanned, -) -> SourceResult { - let val = value.v.float(); - if val < -1.0 || val > 1.0 { - bail!(value.span, "value must be between -1 and 1"); - } - Ok(Angle::rad(val.asin())) -} - -/// Calculates the arccosine of a number. -/// -/// ```example -/// #calc.acos(0) \ -/// #calc.acos(1) -/// ``` -#[func(title = "Arccosine")] -pub fn acos( - /// The number whose arcsine to calculate. Must be between -1 and 1. - value: Spanned, -) -> SourceResult { - let val = value.v.float(); - if val < -1.0 || val > 1.0 { - bail!(value.span, "value must be between -1 and 1"); - } - Ok(Angle::rad(val.acos())) -} - -/// Calculates the arctangent of a number. -/// -/// ```example -/// #calc.atan(0) \ -/// #calc.atan(1) -/// ``` -#[func(title = "Arctangent")] -pub fn atan( - /// The number whose arctangent to calculate. - value: Num, -) -> Angle { - Angle::rad(value.float().atan()) -} - -/// Calculates the four-quadrant arctangent of a coordinate. -/// -/// The arguments are `(x, y)`, not `(y, x)`. -/// -/// ```example -/// #calc.atan2(1, 1) \ -/// #calc.atan2(-2, -3) -/// ``` -#[func(title = "Four-quadrant Arctangent")] -pub fn atan2( - /// The X coordinate. - x: Num, - /// The Y coordinate. - y: Num, -) -> Angle { - Angle::rad(f64::atan2(y.float(), x.float())) -} - -/// Calculates the hyperbolic sine of a hyperbolic angle. -/// -/// ```example -/// #calc.sinh(0) \ -/// #calc.sinh(1.5) -/// ``` -#[func(title = "Hyperbolic Sine")] -pub fn sinh( - /// The hyperbolic angle whose hyperbolic sine to calculate. - value: f64, -) -> f64 { - value.sinh() -} - -/// Calculates the hyperbolic cosine of a hyperbolic angle. -/// -/// ```example -/// #calc.cosh(0) \ -/// #calc.cosh(1.5) -/// ``` -#[func(title = "Hyperbolic Cosine")] -pub fn cosh( - /// The hyperbolic angle whose hyperbolic cosine to calculate. - value: f64, -) -> f64 { - value.cosh() -} - -/// Calculates the hyperbolic tangent of an hyperbolic angle. -/// -/// ```example -/// #calc.tanh(0) \ -/// #calc.tanh(1.5) -/// ``` -#[func(title = "Hyperbolic Tangent")] -pub fn tanh( - /// The hyperbolic angle whose hyperbolic tangent to calculate. - value: f64, -) -> f64 { - value.tanh() -} - -/// Calculates the logarithm of a number. -/// -/// If the base is not specified, the logarithm is calculated in base 10. -/// -/// ```example -/// #calc.log(100) -/// ``` -#[func(title = "Logarithm")] -pub fn log( - /// The callsite span. - span: Span, - /// The number whose logarithm to calculate. Must be strictly positive. - value: Spanned, - /// The base of the logarithm. May not be zero. - #[named] - #[default(Spanned::new(10.0, Span::detached()))] - base: Spanned, -) -> SourceResult { - let number = value.v.float(); - if number <= 0.0 { - bail!(value.span, "value must be strictly positive") - } - - if !base.v.is_normal() { - bail!(base.span, "base may not be zero, NaN, infinite, or subnormal") - } - - let result = if base.v == std::f64::consts::E { - number.ln() - } else if base.v == 2.0 { - number.log2() - } else if base.v == 10.0 { - number.log10() - } else { - number.log(base.v) - }; - - if result.is_infinite() || result.is_nan() { - bail!(span, "the result is not a real number") - } - - Ok(result) -} - -/// Calculates the natural logarithm of a number. -/// -/// ```example -/// #calc.ln(calc.e) -/// ``` -#[func(title = "Natural Logarithm")] -pub fn ln( - /// The callsite span. - span: Span, - /// The number whose logarithm to calculate. Must be strictly positive. - value: Spanned, -) -> SourceResult { - let number = value.v.float(); - if number <= 0.0 { - bail!(value.span, "value must be strictly positive") - } - - let result = number.ln(); - if result.is_infinite() { - bail!(span, "result close to -inf") - } - - Ok(result) -} - -/// Calculates the factorial of a number. -/// -/// ```example -/// #calc.fact(5) -/// ``` -#[func(title = "Factorial")] -pub fn fact( - /// The number whose factorial to calculate. Must be non-negative. - number: u64, -) -> StrResult { - Ok(fact_impl(1, number).ok_or("the result is too large")?) -} - -/// Calculates a permutation. -/// -/// Returns the `k`-permutation of `n`, or the number of ways to choose `k` -/// items from a set of `n` with regard to order. -/// -/// ```example -/// $ "perm"(n, k) &= n!/((n - k)!) \ -/// "perm"(5, 3) &= #calc.perm(5, 3) $ -/// ``` -#[func(title = "Permutation")] -pub fn perm( - /// The base number. Must be non-negative. - base: u64, - /// The number of permutations. Must be non-negative. - numbers: u64, -) -> StrResult { - // By convention. - if base < numbers { - return Ok(0); - } - - Ok(fact_impl(base - numbers + 1, base).ok_or("the result is too large")?) -} - -/// Calculates the product of a range of numbers. Used to calculate -/// permutations. Returns None if the result is larger than `i64::MAX` -fn fact_impl(start: u64, end: u64) -> Option { - // By convention - if end + 1 < start { - return Some(0); - } - - let real_start: u64 = cmp::max(1, start); - let mut count: u64 = 1; - for i in real_start..=end { - count = count.checked_mul(i)?; - } - - count.try_into().ok() -} - -/// Calculates a binomial coefficient. -/// -/// Returns the `k`-combination of `n`, or the number of ways to choose `k` -/// items from a set of `n` without regard to order. -/// -/// ```example -/// #calc.binom(10, 5) -/// ``` -#[func(title = "Binomial")] -pub fn binom( - /// The upper coefficient. Must be non-negative. - n: u64, - /// The lower coefficient. Must be non-negative. - k: u64, -) -> StrResult { - Ok(binom_impl(n, k).ok_or("the result is too large")?) -} - -/// Calculates a binomial coefficient, with `n` the upper coefficient and `k` -/// the lower coefficient. Returns `None` if the result is larger than -/// `i64::MAX` -fn binom_impl(n: u64, k: u64) -> Option { - if k > n { - return Some(0); - } - - // By symmetry - let real_k = cmp::min(n - k, k); - if real_k == 0 { - return Some(1); - } - - let mut result: u64 = 1; - for i in 0..real_k { - result = result.checked_mul(n - i)?.checked_div(i + 1)?; - } - - result.try_into().ok() -} - -/// Calculates the greatest common divisor of two integers. -/// -/// ```example -/// #calc.gcd(7, 42) -/// ``` -#[func(title = "Greatest Common Divisor")] -pub fn gcd( - /// The first integer. - a: i64, - /// The second integer. - b: i64, -) -> i64 { - let (mut a, mut b) = (a, b); - while b != 0 { - let temp = b; - b = a % b; - a = temp; - } - - a.abs() -} - -/// Calculates the least common multiple of two integers. -/// -/// ```example -/// #calc.lcm(96, 13) -/// ``` -#[func(title = "Least Common Multiple")] -pub fn lcm( - /// The first integer. - a: i64, - /// The second integer. - b: i64, -) -> StrResult { - if a == b { - return Ok(a.abs()); - } - - Ok(a.checked_div(gcd(a, b)) - .and_then(|gcd| gcd.checked_mul(b)) - .map(|v| v.abs()) - .ok_or("the return value is too large")?) -} - -/// Rounds a number down to the nearest integer. -/// -/// If the number is already an integer, it is returned unchanged. -/// -/// ```example -/// #assert(calc.floor(3.14) == 3) -/// #assert(calc.floor(3) == 3) -/// #calc.floor(500.1) -/// ``` -#[func] -pub fn floor( - /// The number to round down. - value: Num, -) -> i64 { - match value { - Num::Int(n) => n, - Num::Float(n) => n.floor() as i64, - } -} - -/// Rounds a number up to the nearest integer. -/// -/// If the number is already an integer, it is returned unchanged. -/// -/// ```example -/// #assert(calc.ceil(3.14) == 4) -/// #assert(calc.ceil(3) == 3) -/// #calc.ceil(500.1) -/// ``` -#[func] -pub fn ceil( - /// The number to round up. - value: Num, -) -> i64 { - match value { - Num::Int(n) => n, - Num::Float(n) => n.ceil() as i64, - } -} - -/// Returns the integer part of a number. -/// -/// If the number is already an integer, it is returned unchanged. -/// -/// ```example -/// #assert(calc.trunc(3) == 3) -/// #assert(calc.trunc(-3.7) == -3) -/// #calc.trunc(15.9) -/// ``` -#[func(title = "Truncate")] -pub fn trunc( - /// The number to truncate. - value: Num, -) -> i64 { - match value { - Num::Int(n) => n, - Num::Float(n) => n.trunc() as i64, - } -} - -/// Returns the fractional part of a number. -/// -/// If the number is an integer, returns `0`. -/// -/// ```example -/// #assert(calc.fract(3) == 0) -/// #calc.fract(-3.1) -/// ``` -#[func(title = "Fractional")] -pub fn fract( - /// The number to truncate. - value: Num, -) -> Num { - match value { - Num::Int(_) => Num::Int(0), - Num::Float(n) => Num::Float(n.fract()), - } -} - -/// Rounds a number to the nearest integer. -/// -/// Optionally, a number of decimal places can be specified. -/// -/// ```example -/// #assert(calc.round(3.14) == 3) -/// #assert(calc.round(3.5) == 4) -/// #calc.round(3.1415, digits: 2) -/// ``` -#[func] -pub fn round( - /// The number to round. - value: Num, - /// The number of decimal places. - #[named] - #[default(0)] - digits: i64, -) -> Num { - match value { - Num::Int(n) if digits == 0 => Num::Int(n), - _ => { - let n = value.float(); - let factor = 10.0_f64.powi(digits as i32); - Num::Float((n * factor).round() / factor) - } - } -} - -/// Clamps a number between a minimum and maximum value. -/// -/// ```example -/// #assert(calc.clamp(5, 0, 10) == 5) -/// #assert(calc.clamp(5, 6, 10) == 6) -/// #calc.clamp(5, 0, 4) -/// ``` -#[func] -pub fn clamp( - /// The number to clamp. - value: Num, - /// The inclusive minimum value. - min: Num, - /// The inclusive maximum value. - max: Spanned, -) -> SourceResult { - if max.v.float() < min.float() { - bail!(max.span, "max must be greater than or equal to min") - } - Ok(value.apply3(min, max.v, i64::clamp, f64::clamp)) -} - -/// Determines the minimum of a sequence of values. -/// -/// ```example -/// #calc.min(1, -3, -5, 20, 3, 6) \ -/// #calc.min("typst", "in", "beta") -/// ``` -#[func(title = "Minimum")] -pub fn min( - /// The callsite span. - span: Span, - /// The sequence of values from which to extract the minimum. - /// Must not be empty. - #[variadic] - values: Vec>, -) -> SourceResult { - minmax(span, values, Ordering::Less) -} - -/// Determines the maximum of a sequence of values. -/// -/// ```example -/// #calc.max(1, -3, -5, 20, 3, 6) \ -/// #calc.max("typst", "in", "beta") -/// ``` -#[func(title = "Maximum")] -pub fn max( - /// The callsite span. - span: Span, - /// The sequence of values from which to extract the maximum. - /// Must not be empty. - #[variadic] - values: Vec>, -) -> SourceResult { - minmax(span, values, Ordering::Greater) -} - -/// Find the minimum or maximum of a sequence of values. -fn minmax( - span: Span, - values: Vec>, - goal: Ordering, -) -> SourceResult { - let mut iter = values.into_iter(); - let Some(Spanned { v: mut extremum, .. }) = iter.next() else { - bail!(span, "expected at least one value"); - }; - - for Spanned { v, span } in iter { - let ordering = typst::eval::ops::compare(&v, &extremum).at(span)?; - if ordering == goal { - extremum = v; - } - } - - Ok(extremum) -} - -/// Determines whether an integer is even. -/// -/// ```example -/// #calc.even(4) \ -/// #calc.even(5) \ -/// #range(10).filter(calc.even) -/// ``` -#[func] -pub fn even( - /// The number to check for evenness. - value: i64, -) -> bool { - value % 2 == 0 -} - -/// Determines whether an integer is odd. -/// -/// ```example -/// #calc.odd(4) \ -/// #calc.odd(5) \ -/// #range(10).filter(calc.odd) -/// ``` -#[func] -pub fn odd( - /// The number to check for oddness. - value: i64, -) -> bool { - value % 2 != 0 -} - -/// Calculates the remainder of two numbers. -/// -/// The value `calc.rem(x, y)` always has the same sign as `x`, and is smaller -/// in magnitude than `y`. -/// -/// ```example -/// #calc.rem(7, 3) \ -/// #calc.rem(7, -3) \ -/// #calc.rem(-7, 3) \ -/// #calc.rem(-7, -3) \ -/// #calc.rem(1.75, 0.5) -/// ``` -#[func(title = "Remainder")] -pub fn rem( - /// The dividend of the remainder. - dividend: Num, - /// The divisor of the remainder. - divisor: Spanned, -) -> SourceResult { - if divisor.v.float() == 0.0 { - bail!(divisor.span, "divisor must not be zero"); - } - Ok(dividend.apply2(divisor.v, Rem::rem, Rem::rem)) -} - -/// Performs euclidean division of two numbers. -/// -/// The result of this computation is that of a division rounded to the integer -/// `{n}` such that the dividend is greater than or equal to `{n}` times the divisor. -/// -/// ```example -/// #calc.div-euclid(7, 3) \ -/// #calc.div-euclid(7, -3) \ -/// #calc.div-euclid(-7, 3) \ -/// #calc.div-euclid(-7, -3) \ -/// #calc.div-euclid(1.75, 0.5) -/// ``` -#[func(title = "Euclidean Division")] -pub fn div_euclid( - /// The dividend of the division. - dividend: Num, - /// The divisor of the division. - divisor: Spanned, -) -> SourceResult { - if divisor.v.float() == 0.0 { - bail!(divisor.span, "divisor must not be zero"); - } - Ok(dividend.apply2(divisor.v, i64::div_euclid, f64::div_euclid)) -} - -/// This calculates the least nonnegative remainder of a division. -/// -/// Warning: Due to a floating point round-off error, the remainder may equal the absolute -/// value of the divisor if the dividend is much smaller in magnitude than the divisor -/// and the dividend is negative. This only applies for floating point inputs. -/// -/// ```example -/// #calc.rem-euclid(7, 3) \ -/// #calc.rem-euclid(7, -3) \ -/// #calc.rem-euclid(-7, 3) \ -/// #calc.rem-euclid(-7, -3) \ -/// #calc.rem(1.75, 0.5) -/// ``` -#[func(title = "Euclidean Remainder")] -pub fn rem_euclid( - /// The dividend of the remainder. - dividend: Num, - /// The divisor of the remainder. - divisor: Spanned, -) -> SourceResult { - if divisor.v.float() == 0.0 { - bail!(divisor.span, "divisor must not be zero"); - } - Ok(dividend.apply2(divisor.v, i64::rem_euclid, f64::rem_euclid)) -} - -/// Calculates the quotient (floored division) of two numbers. -/// -/// ```example -/// $ "quo"(a, b) &= floor(a/b) \ -/// "quo"(14, 5) &= #calc.quo(14, 5) \ -/// "quo"(3.46, 0.5) &= #calc.quo(3.46, 0.5) $ -/// ``` -#[func(title = "Quotient")] -pub fn quo( - /// The dividend of the quotient. - dividend: Num, - /// The divisor of the quotient. - divisor: Spanned, -) -> SourceResult { - if divisor.v.float() == 0.0 { - bail!(divisor.span, "divisor must not be zero"); - } - - Ok(floor(dividend.apply2(divisor.v, Div::div, Div::div))) -} - -/// A value which can be passed to functions that work with integers and floats. -#[derive(Debug, Copy, Clone)] -pub enum Num { - Int(i64), - Float(f64), -} - -impl Num { - fn apply2( - self, - other: Self, - int: impl FnOnce(i64, i64) -> i64, - float: impl FnOnce(f64, f64) -> f64, - ) -> Num { - match (self, other) { - (Self::Int(a), Self::Int(b)) => Num::Int(int(a, b)), - (a, b) => Num::Float(float(a.float(), b.float())), - } - } - - fn apply3( - self, - other: Self, - third: Self, - int: impl FnOnce(i64, i64, i64) -> i64, - float: impl FnOnce(f64, f64, f64) -> f64, - ) -> Num { - match (self, other, third) { - (Self::Int(a), Self::Int(b), Self::Int(c)) => Num::Int(int(a, b, c)), - (a, b, c) => Num::Float(float(a.float(), b.float(), c.float())), - } - } - - fn float(self) -> f64 { - match self { - Self::Int(v) => v as f64, - Self::Float(v) => v, - } - } -} - -cast! { - Num, - self => match self { - Self::Int(v) => v.into_value(), - Self::Float(v) => v.into_value(), - }, - v: i64 => Self::Int(v), - v: f64 => Self::Float(v), -} - -/// A value that can be passed to a trigonometric function. -pub enum AngleLike { - Int(i64), - Float(f64), - Angle(Angle), -} - -cast! { - AngleLike, - v: i64 => Self::Int(v), - v: f64 => Self::Float(v), - v: Angle => Self::Angle(v), -} diff --git a/crates/typst-library/src/compute/data.rs b/crates/typst-library/src/compute/data.rs deleted file mode 100644 index e4767ebf..00000000 --- a/crates/typst-library/src/compute/data.rs +++ /dev/null @@ -1,609 +0,0 @@ -use typst::diag::{format_xml_like_error, FileError}; -use typst::eval::Bytes; -use typst::syntax::is_newline; - -use crate::prelude::*; - -/// Hook up all data loading definitions. -pub(super) fn define(global: &mut Scope) { - global.category("data-loading"); - global.define_func::(); - global.define_func::(); - global.define_func::(); - global.define_func::(); - global.define_func::(); - global.define_func::(); - global.define_func::(); -} - -/// Reads plain text or data from a file. -/// -/// By default, the file will be read as UTF-8 and returned as a [string]($str). -/// -/// If you specify `{encoding: none}`, this returns raw [bytes]($bytes) instead. -/// -/// # Example -/// ```example -/// An example for a HTML file: \ -/// #let text = read("data.html") -/// #raw(text, lang: "html") -/// -/// Raw bytes: -/// #read("tiger.jpg", encoding: none) -/// ``` -#[func] -pub fn read( - /// The virtual machine. - vm: &mut Vm, - /// Path to a file. - path: Spanned, - /// The encoding to read the file with. - /// - /// If set to `{none}`, this function returns raw bytes. - #[named] - #[default(Some(Encoding::Utf8))] - encoding: Option, -) -> SourceResult { - let Spanned { v: path, span } = path; - let id = vm.resolve_path(&path).at(span)?; - let data = vm.world().file(id).at(span)?; - Ok(match encoding { - None => Readable::Bytes(data), - Some(Encoding::Utf8) => Readable::Str( - std::str::from_utf8(&data) - .map_err(|_| "file is not valid utf-8") - .at(span)? - .into(), - ), - }) -} - -/// An encoding of a file. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] -pub enum Encoding { - /// The Unicode UTF-8 encoding. - Utf8, -} - -/// A value that can be read from a file. -#[derive(Debug, Clone, PartialEq, Hash)] -pub enum Readable { - /// A decoded string. - Str(Str), - /// Raw bytes. - Bytes(Bytes), -} - -impl Readable { - fn as_slice(&self) -> &[u8] { - match self { - Readable::Bytes(v) => v, - Readable::Str(v) => v.as_bytes(), - } - } -} - -cast! { - Readable, - self => match self { - Self::Str(v) => v.into_value(), - Self::Bytes(v) => v.into_value(), - }, - v: Str => Self::Str(v), - v: Bytes => Self::Bytes(v), -} - -impl From for Bytes { - fn from(value: Readable) -> Self { - match value { - Readable::Bytes(v) => v, - Readable::Str(v) => v.as_bytes().into(), - } - } -} - -/// Reads structured data from a CSV file. -/// -/// The CSV file will be read and parsed into a 2-dimensional array of strings: -/// Each row in the CSV file will be represented as an array of strings, and all -/// rows will be collected into a single array. Header rows will not be -/// stripped. -/// -/// # Example -/// ```example -/// #let results = csv("data.csv") -/// -/// #table( -/// columns: 2, -/// [*Condition*], [*Result*], -/// ..results.flatten(), -/// ) -/// ``` -#[func(scope, title = "CSV")] -pub fn csv( - /// The virtual machine. - vm: &mut Vm, - /// Path to a CSV file. - path: Spanned, - /// The delimiter that separates columns in the CSV file. - /// Must be a single ASCII character. - #[named] - #[default] - delimiter: Delimiter, -) -> SourceResult { - let Spanned { v: path, span } = path; - let id = vm.resolve_path(&path).at(span)?; - let data = vm.world().file(id).at(span)?; - self::csv::decode(Spanned::new(Readable::Bytes(data), span), delimiter) -} - -#[scope] -impl csv { - /// Reads structured data from a CSV string/bytes. - #[func(title = "Decode CSV")] - pub fn decode( - /// CSV data. - data: Spanned, - /// The delimiter that separates columns in the CSV file. - /// Must be a single ASCII character. - #[named] - #[default] - delimiter: Delimiter, - ) -> SourceResult { - let Spanned { v: data, span } = data; - let mut builder = ::csv::ReaderBuilder::new(); - builder.has_headers(false); - builder.delimiter(delimiter.0 as u8); - let mut reader = builder.from_reader(data.as_slice()); - let mut array = Array::new(); - - for (line, result) in reader.records().enumerate() { - // Original solution use line from error, but that is incorrect with - // `has_headers` set to `false`. See issue: - // https://github.com/BurntSushi/rust-csv/issues/184 - let line = line + 1; // Counting lines from 1 - let row = result.map_err(|err| format_csv_error(err, line)).at(span)?; - let sub = row.into_iter().map(|field| field.into_value()).collect(); - array.push(Value::Array(sub)) - } - - Ok(array) - } -} - -/// The delimiter to use when parsing CSV files. -pub struct Delimiter(char); - -impl Default for Delimiter { - fn default() -> Self { - Self(',') - } -} - -cast! { - Delimiter, - self => self.0.into_value(), - v: EcoString => { - let mut chars = v.chars(); - let first = chars.next().ok_or("delimiter must not be empty")?; - if chars.next().is_some() { - bail!("delimiter must be a single character"); - } - - if !first.is_ascii() { - bail!("delimiter must be an ASCII character"); - } - - Self(first) - }, -} - -/// Format the user-facing CSV error message. -fn format_csv_error(err: ::csv::Error, line: usize) -> EcoString { - match err.kind() { - ::csv::ErrorKind::Utf8 { .. } => "file is not valid utf-8".into(), - ::csv::ErrorKind::UnequalLengths { expected_len, len, .. } => { - eco_format!( - "failed to parse CSV (found {len} instead of \ - {expected_len} fields in line {line})" - ) - } - _ => eco_format!("failed to parse CSV ({err})"), - } -} - -/// Reads structured data from a JSON file. -/// -/// The file must contain a valid JSON object or array. JSON objects will be -/// converted into Typst dictionaries, and JSON arrays will be converted into -/// Typst arrays. Strings and booleans will be converted into the Typst -/// equivalents, `null` will be converted into `{none}`, and numbers will be -/// converted to floats or integers depending on whether they are whole numbers. -/// -/// The function returns a dictionary or an array, depending on the JSON file. -/// -/// The JSON files in the example contain objects with the keys `temperature`, -/// `unit`, and `weather`. -/// -/// # Example -/// ```example -/// #let forecast(day) = block[ -/// #box(square( -/// width: 2cm, -/// inset: 8pt, -/// fill: if day.weather == "sunny" { -/// yellow -/// } else { -/// aqua -/// }, -/// align( -/// bottom + right, -/// strong(day.weather), -/// ), -/// )) -/// #h(6pt) -/// #set text(22pt, baseline: -8pt) -/// #day.temperature °#day.unit -/// ] -/// -/// #forecast(json("monday.json")) -/// #forecast(json("tuesday.json")) -/// ``` -#[func(scope, title = "JSON")] -pub fn json( - /// The virtual machine. - vm: &mut Vm, - /// Path to a JSON file. - path: Spanned, -) -> SourceResult { - let Spanned { v: path, span } = path; - let id = vm.resolve_path(&path).at(span)?; - let data = vm.world().file(id).at(span)?; - json::decode(Spanned::new(Readable::Bytes(data), span)) -} - -#[scope] -impl json { - /// Reads structured data from a JSON string/bytes. - #[func(title = "Decode JSON")] - pub fn decode( - /// JSON data. - data: Spanned, - ) -> SourceResult { - let Spanned { v: data, span } = data; - serde_json::from_slice(data.as_slice()) - .map_err(|err| eco_format!("failed to parse JSON ({err})")) - .at(span) - } - - /// Encodes structured data into a JSON string. - #[func(title = "Encode JSON")] - pub fn encode( - /// Value to be encoded. - value: Spanned, - /// Whether to pretty print the JSON with newlines and indentation. - #[named] - #[default(true)] - pretty: bool, - ) -> SourceResult { - let Spanned { v: value, span } = value; - if pretty { - serde_json::to_string_pretty(&value) - } else { - serde_json::to_string(&value) - } - .map(|v| v.into()) - .map_err(|err| eco_format!("failed to encode value as JSON ({err})")) - .at(span) - } -} - -/// Reads structured data from a TOML file. -/// -/// The file must contain a valid TOML table. TOML tables will be converted into -/// Typst dictionaries, and TOML arrays will be converted into Typst arrays. -/// Strings, booleans and datetimes will be converted into the Typst equivalents -/// and numbers will be converted to floats or integers depending on whether -/// they are whole numbers. -/// -/// The TOML file in the example consists of a table with the keys `title`, -/// `version`, and `authors`. -/// -/// # Example -/// ```example -/// #let details = toml("details.toml") -/// -/// Title: #details.title \ -/// Version: #details.version \ -/// Authors: #(details.authors -/// .join(", ", last: " and ")) -/// ``` -#[func(scope, title = "TOML")] -pub fn toml( - /// The virtual machine. - vm: &mut Vm, - /// Path to a TOML file. - path: Spanned, -) -> SourceResult { - let Spanned { v: path, span } = path; - let id = vm.resolve_path(&path).at(span)?; - let data = vm.world().file(id).at(span)?; - toml::decode(Spanned::new(Readable::Bytes(data), span)) -} - -#[scope] -impl toml { - /// Reads structured data from a TOML string/bytes. - #[func(title = "Decode TOML")] - pub fn decode( - /// TOML data. - data: Spanned, - ) -> SourceResult { - let Spanned { v: data, span } = data; - let raw = std::str::from_utf8(data.as_slice()) - .map_err(|_| "file is not valid utf-8") - .at(span)?; - ::toml::from_str(raw) - .map_err(|err| format_toml_error(err, raw)) - .at(span) - } - - /// Encodes structured data into a TOML string. - #[func(title = "Encode TOML")] - pub fn encode( - /// Value to be encoded. - value: Spanned, - /// Whether to pretty-print the resulting TOML. - #[named] - #[default(true)] - pretty: bool, - ) -> SourceResult { - let Spanned { v: value, span } = value; - if pretty { ::toml::to_string_pretty(&value) } else { ::toml::to_string(&value) } - .map(|v| v.into()) - .map_err(|err| eco_format!("failed to encode value as TOML ({err})")) - .at(span) - } -} - -/// Format the user-facing TOML error message. -fn format_toml_error(error: ::toml::de::Error, raw: &str) -> EcoString { - if let Some(head) = error.span().and_then(|range| raw.get(..range.start)) { - let line = head.lines().count(); - let column = 1 + head.chars().rev().take_while(|&c| !is_newline(c)).count(); - eco_format!( - "failed to parse TOML ({} at line {line} column {column})", - error.message(), - ) - } else { - eco_format!("failed to parse TOML ({})", error.message()) - } -} - -/// Reads structured data from a YAML file. -/// -/// The file must contain a valid YAML object or array. YAML mappings will be -/// converted into Typst dictionaries, and YAML sequences will be converted into -/// Typst arrays. Strings and booleans will be converted into the Typst -/// equivalents, null-values (`null`, `~` or empty ``) will be converted into -/// `{none}`, and numbers will be converted to floats or integers depending on -/// whether they are whole numbers. Custom YAML tags are ignored, though the -/// loaded value will still be present. -/// -/// The YAML files in the example contain objects with authors as keys, -/// each with a sequence of their own submapping with the keys -/// "title" and "published" -/// -/// # Example -/// ```example -/// #let bookshelf(contents) = { -/// for (author, works) in contents { -/// author -/// for work in works [ -/// - #work.title (#work.published) -/// ] -/// } -/// } -/// -/// #bookshelf( -/// yaml("scifi-authors.yaml") -/// ) -/// ``` -#[func(scope, title = "YAML")] -pub fn yaml( - /// The virtual machine. - vm: &mut Vm, - /// Path to a YAML file. - path: Spanned, -) -> SourceResult { - let Spanned { v: path, span } = path; - let id = vm.resolve_path(&path).at(span)?; - let data = vm.world().file(id).at(span)?; - yaml::decode(Spanned::new(Readable::Bytes(data), span)) -} - -#[scope] -impl yaml { - /// Reads structured data from a YAML string/bytes. - #[func(title = "Decode YAML")] - pub fn decode( - /// YAML data. - data: Spanned, - ) -> SourceResult { - let Spanned { v: data, span } = data; - serde_yaml::from_slice(data.as_slice()) - .map_err(|err| eco_format!("failed to parse YAML ({err})")) - .at(span) - } - - /// Encode structured data into a YAML string. - #[func(title = "Encode YAML")] - pub fn encode( - /// Value to be encoded. - value: Spanned, - ) -> SourceResult { - let Spanned { v: value, span } = value; - serde_yaml::to_string(&value) - .map(|v| v.into()) - .map_err(|err| eco_format!("failed to encode value as YAML ({err})")) - .at(span) - } -} - -/// Reads structured data from a CBOR file. -/// -/// The file must contain a valid cbor serialization. Mappings will be -/// converted into Typst dictionaries, and sequences will be converted into -/// Typst arrays. Strings and booleans will be converted into the Typst -/// equivalents, null-values (`null`, `~` or empty ``) will be converted into -/// `{none}`, and numbers will be converted to floats or integers depending on -/// whether they are whole numbers. -#[func(scope, title = "CBOR")] -pub fn cbor( - /// The virtual machine. - vm: &mut Vm, - /// Path to a CBOR file. - path: Spanned, -) -> SourceResult { - let Spanned { v: path, span } = path; - let id = vm.resolve_path(&path).at(span)?; - let data = vm.world().file(id).at(span)?; - cbor::decode(Spanned::new(data, span)) -} - -#[scope] -impl cbor { - /// Reads structured data from CBOR bytes. - #[func(title = "Decode CBOR")] - pub fn decode( - /// cbor data. - data: Spanned, - ) -> SourceResult { - let Spanned { v: data, span } = data; - ciborium::from_reader(data.as_slice()) - .map_err(|err| eco_format!("failed to parse CBOR ({err})")) - .at(span) - } - - /// Encode structured data into CBOR bytes. - #[func(title = "Encode CBOR")] - pub fn encode( - /// Value to be encoded. - value: Spanned, - ) -> SourceResult { - let Spanned { v: value, span } = value; - let mut res = Vec::new(); - ciborium::into_writer(&value, &mut res) - .map(|_| res.into()) - .map_err(|err| eco_format!("failed to encode value as CBOR ({err})")) - .at(span) - } -} - -/// Reads structured data from an XML file. -/// -/// The XML file is parsed into an array of dictionaries and strings. XML nodes -/// can be elements or strings. Elements are represented as dictionaries with -/// the the following keys: -/// -/// - `tag`: The name of the element as a string. -/// - `attrs`: A dictionary of the element's attributes as strings. -/// - `children`: An array of the element's child nodes. -/// -/// The XML file in the example contains a root `news` tag with multiple -/// `article` tags. Each article has a `title`, `author`, and `content` tag. The -/// `content` tag contains one or more paragraphs, which are represented as `p` -/// tags. -/// -/// # Example -/// ```example -/// #let find-child(elem, tag) = { -/// elem.children -/// .find(e => "tag" in e and e.tag == tag) -/// } -/// -/// #let article(elem) = { -/// let title = find-child(elem, "title") -/// let author = find-child(elem, "author") -/// let pars = find-child(elem, "content") -/// -/// heading(title.children.first()) -/// text(10pt, weight: "medium")[ -/// Published by -/// #author.children.first() -/// ] -/// -/// for p in pars.children { -/// if (type(p) == "dictionary") { -/// parbreak() -/// p.children.first() -/// } -/// } -/// } -/// -/// #let data = xml("example.xml") -/// #for elem in data.first().children { -/// if (type(elem) == "dictionary") { -/// article(elem) -/// } -/// } -/// ``` -#[func(scope, title = "XML")] -pub fn xml( - /// The virtual machine. - vm: &mut Vm, - /// Path to an XML file. - path: Spanned, -) -> SourceResult { - let Spanned { v: path, span } = path; - let id = vm.resolve_path(&path).at(span)?; - let data = vm.world().file(id).at(span)?; - xml::decode(Spanned::new(Readable::Bytes(data), span)) -} - -#[scope] -impl xml { - /// Reads structured data from an XML string/bytes. - #[func(title = "Decode XML")] - pub fn decode( - /// XML data. - data: Spanned, - ) -> SourceResult { - let Spanned { v: data, span } = data; - let text = std::str::from_utf8(data.as_slice()) - .map_err(FileError::from) - .at(span)?; - let document = - roxmltree::Document::parse(text).map_err(format_xml_error).at(span)?; - Ok(convert_xml(document.root())) - } -} - -/// Convert an XML node to a Typst value. -fn convert_xml(node: roxmltree::Node) -> Value { - if node.is_text() { - return node.text().unwrap_or_default().into_value(); - } - - let children: Array = node.children().map(convert_xml).collect(); - if node.is_root() { - return Value::Array(children); - } - - let tag: Str = node.tag_name().name().into(); - let attrs: Dict = node - .attributes() - .map(|attr| (attr.name().into(), attr.value().into_value())) - .collect(); - - Value::Dict(dict! { - "tag" => tag, - "attrs" => attrs, - "children" => children, - }) -} - -/// Format the user-facing XML error message. -fn format_xml_error(error: roxmltree::Error) -> EcoString { - format_xml_like_error("XML", error) -} diff --git a/crates/typst-library/src/compute/foundations.rs b/crates/typst-library/src/compute/foundations.rs deleted file mode 100644 index fef00269..00000000 --- a/crates/typst-library/src/compute/foundations.rs +++ /dev/null @@ -1,236 +0,0 @@ -use typst::eval::{ - Datetime, Duration, EvalMode, Module, Never, NoneValue, Plugin, Regex, Repr, Version, -}; - -use crate::prelude::*; - -/// Hook up all foundational definitions. -pub(super) fn define(global: &mut Scope) { - global.category("foundations"); - global.define_type::(); - global.define_type::(); - global.define_type::(); - global.define_type::(); - global.define_type::(); - global.define_type::(); - global.define_type::(); - global.define_type::(); - global.define_type::(); - global.define_type::(); - global.define_type::(); - global.define_type::(); - global.define_type::(); - global.define_type::(); - global.define_type::(); - global.define_type::(); - global.define_type::(); - global.define_func::(); - global.define_func::(); - global.define_func::(); - global.define_func::(); -} - -/// Returns the string representation of a value. -/// -/// When inserted into content, most values are displayed as this representation -/// in monospace with syntax-highlighting. The exceptions are `{none}`, -/// integers, floats, strings, content, and functions. -/// -/// **Note:** This function is for debugging purposes. Its output should not be -/// considered stable and may change at any time! -/// -/// # Example -/// ```example -/// #none vs #repr(none) \ -/// #"hello" vs #repr("hello") \ -/// #(1, 2) vs #repr((1, 2)) \ -/// #[*Hi*] vs #repr([*Hi*]) -/// ``` -#[func(title = "Representation")] -pub fn repr( - /// The value whose string representation to produce. - value: Value, -) -> Str { - value.repr().into() -} - -/// Fails with an error. -/// -/// Arguments are displayed to the user (not rendered in the document) as -/// strings, converting with `repr` if necessary. -/// -/// # Example -/// The code below produces the error `panicked with: "this is wrong"`. -/// ```typ -/// #panic("this is wrong") -/// ``` -#[func(keywords = ["error"])] -pub fn panic( - /// The values to panic with and display to the user. - #[variadic] - values: Vec, -) -> StrResult { - let mut msg = EcoString::from("panicked"); - if !values.is_empty() { - msg.push_str(" with: "); - for (i, value) in values.iter().enumerate() { - if i > 0 { - msg.push_str(", "); - } - msg.push_str(&value.repr()); - } - } - Err(msg) -} - -/// Ensures that a condition is fulfilled. -/// -/// Fails with an error if the condition is not fulfilled. Does not -/// produce any output in the document. -/// -/// If you wish to test equality between two values, see -/// [`assert.eq`]($assert.eq) and [`assert.ne`]($assert.ne). -/// -/// # Example -/// ```typ -/// #assert(1 < 2, message: "math broke") -/// ``` -#[func(scope)] -pub fn assert( - /// The condition that must be true for the assertion to pass. - condition: bool, - /// The error message when the assertion fails. - #[named] - message: Option, -) -> StrResult { - if !condition { - if let Some(message) = message { - bail!("assertion failed: {message}"); - } else { - bail!("assertion failed"); - } - } - Ok(NoneValue) -} - -#[scope] -impl assert { - /// Ensures that two values are equal. - /// - /// Fails with an error if the first value is not equal to the second. Does not - /// produce any output in the document. - /// - /// ```typ - /// #assert.eq(10, 10) - /// ``` - #[func(title = "Assert Equal")] - pub fn eq( - /// The first value to compare. - left: Value, - /// The second value to compare. - right: Value, - /// An optional message to display on error instead of the representations - /// of the compared values. - #[named] - message: Option, - ) -> StrResult { - if left != right { - if let Some(message) = message { - bail!("equality assertion failed: {message}"); - } else { - bail!( - "equality assertion failed: value {} was not equal to {}", - left.repr(), - right.repr() - ); - } - } - Ok(NoneValue) - } - - /// Ensures that two values are not equal. - /// - /// Fails with an error if the first value is equal to the second. Does not - /// produce any output in the document. - /// - /// ```typ - /// #assert.ne(3, 4) - /// ``` - #[func(title = "Assert Not Equal")] - pub fn ne( - /// The first value to compare. - left: Value, - /// The second value to compare. - right: Value, - /// An optional message to display on error instead of the representations - /// of the compared values. - #[named] - message: Option, - ) -> StrResult { - if left == right { - if let Some(message) = message { - bail!("inequality assertion failed: {message}"); - } else { - bail!( - "inequality assertion failed: value {} was equal to {}", - left.repr(), - right.repr() - ); - } - } - Ok(NoneValue) - } -} - -/// Evaluates a string as Typst code. -/// -/// This function should only be used as a last resort. -/// -/// # Example -/// ```example -/// #eval("1 + 1") \ -/// #eval("(1, 2, 3, 4)").len() \ -/// #eval("*Markup!*", mode: "markup") \ -/// ``` -#[func(title = "Evaluate")] -pub fn eval( - /// The virtual machine. - vm: &mut Vm, - /// A string of Typst code to evaluate. - /// - /// The code in the string cannot interact with the file system. - source: Spanned, - /// The syntactical mode in which the string is parsed. - /// - /// ```example - /// #eval("= Heading", mode: "markup") - /// #eval("1_2^3", mode: "math") - /// ``` - #[named] - #[default(EvalMode::Code)] - mode: EvalMode, - /// A scope of definitions that are made available. - /// - /// ```example - /// #eval("x + 1", scope: (x: 2)) \ - /// #eval( - /// "abc/xyz", - /// mode: "math", - /// scope: ( - /// abc: $a + b + c$, - /// xyz: $x + y + z$, - /// ), - /// ) - /// ``` - #[named] - #[default] - scope: Dict, -) -> SourceResult { - let Spanned { v: text, span } = source; - let dict = scope; - let mut scope = Scope::new(); - for (key, value) in dict { - scope.define(key, value); - } - typst::eval::eval_string(vm.world(), &text, span, mode, scope) -} diff --git a/crates/typst-library/src/compute/mod.rs b/crates/typst-library/src/compute/mod.rs deleted file mode 100644 index f1af24c5..00000000 --- a/crates/typst-library/src/compute/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Computational functions. - -pub mod calc; -pub mod sys; - -mod data; -mod foundations; - -pub use self::data::*; -pub use self::foundations::*; - -use crate::prelude::*; - -/// Hook up all compute definitions. -pub(super) fn define(global: &mut Scope) { - self::foundations::define(global); - self::data::define(global); - self::calc::define(global); - self::sys::define(global); -} diff --git a/crates/typst-library/src/compute/sys.rs b/crates/typst-library/src/compute/sys.rs deleted file mode 100644 index 6404e625..00000000 --- a/crates/typst-library/src/compute/sys.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! System-related things. - -use typst::eval::{Module, Scope, Version}; - -/// Hook up all calculation definitions. -pub(super) fn define(global: &mut Scope) { - global.category("sys"); - global.define_module(module()); -} - -/// A module with system-related things. -fn module() -> Module { - let mut scope = Scope::deduplicating(); - scope.category("sys"); - scope.define( - "version", - Version::from_iter([ - env!("CARGO_PKG_VERSION_MAJOR").parse::().unwrap(), - env!("CARGO_PKG_VERSION_MINOR").parse::().unwrap(), - env!("CARGO_PKG_VERSION_PATCH").parse::().unwrap(), - ]), - ); - Module::new("sys", scope) -} diff --git a/crates/typst-library/src/layout/align.rs b/crates/typst-library/src/layout/align.rs deleted file mode 100644 index 9c18266d..00000000 --- a/crates/typst-library/src/layout/align.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::prelude::*; - -/// Aligns content horizontally and vertically. -/// -/// # Example -/// ```example -/// #set align(center) -/// -/// Centered text, a sight to see \ -/// In perfect balance, visually \ -/// Not left nor right, it stands alone \ -/// A work of art, a visual throne -/// ``` -#[elem(Show)] -pub struct AlignElem { - /// The [alignment]($alignment) along both axes. - /// - /// ```example - /// #set page(height: 6cm) - /// #set text(lang: "ar") - /// - /// مثال - /// #align( - /// end + horizon, - /// rect(inset: 12pt)[ركن] - /// ) - /// ``` - #[positional] - #[fold] - #[default] - pub alignment: Align, - - /// The content to align. - #[required] - pub body: Content, -} - -impl Show for AlignElem { - #[tracing::instrument(name = "AlignElem::show", skip_all)] - fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { - Ok(self - .body() - .clone() - .styled(Self::set_alignment(self.alignment(styles)))) - } -} diff --git a/crates/typst-library/src/layout/columns.rs b/crates/typst-library/src/layout/columns.rs deleted file mode 100644 index bf111506..00000000 --- a/crates/typst-library/src/layout/columns.rs +++ /dev/null @@ -1,169 +0,0 @@ -use crate::prelude::*; -use crate::text::TextElem; - -/// Separates a region into multiple equally sized columns. -/// -/// The `column` function allows to separate the interior of any container into -/// multiple columns. It will not equalize the height of the columns, instead, -/// the columns will take up the height of their container or the remaining -/// height on the page. The columns function can break across pages if -/// necessary. -/// -/// If you need to insert columns across your whole document, you can use the -/// [`{page}` function's `columns` parameter]($page.columns) instead. -/// -/// # Example -/// ```example -/// = Towards Advanced Deep Learning -/// -/// #box(height: 68pt, -/// columns(2, gutter: 11pt)[ -/// #set par(justify: true) -/// This research was funded by the -/// National Academy of Sciences. -/// NAoS provided support for field -/// tests and interviews with a -/// grant of up to USD 40.000 for a -/// period of 6 months. -/// ] -/// ) -/// -/// In recent years, deep learning has -/// increasingly been used to solve a -/// variety of problems. -/// ``` -#[elem(Layout)] -pub struct ColumnsElem { - /// The number of columns. - #[positional] - #[default(NonZeroUsize::new(2).unwrap())] - pub count: NonZeroUsize, - - /// The size of the gutter space between each column. - #[resolve] - #[default(Ratio::new(0.04).into())] - pub gutter: Rel, - - /// The content that should be layouted into the columns. - #[required] - pub body: Content, -} - -impl Layout for ColumnsElem { - #[tracing::instrument(name = "ColumnsElem::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - let body = self.body(); - - // Separating the infinite space into infinite columns does not make - // much sense. - if !regions.size.x.is_finite() { - return body.layout(vt, styles, regions); - } - - // Determine the width of the gutter and each column. - let columns = self.count(styles).get(); - let gutter = self.gutter(styles).relative_to(regions.base().x); - let width = (regions.size.x - gutter * (columns - 1) as f64) / columns as f64; - - let backlog: Vec<_> = std::iter::once(®ions.size.y) - .chain(regions.backlog) - .flat_map(|&height| std::iter::repeat(height).take(columns)) - .skip(1) - .collect(); - - // Create the pod regions. - let pod = Regions { - size: Size::new(width, regions.size.y), - full: regions.full, - backlog: &backlog, - last: regions.last, - expand: Axes::new(true, regions.expand.y), - root: regions.root, - }; - - // Layout the children. - let mut frames = body.layout(vt, styles, pod)?.into_iter(); - let mut finished = vec![]; - - let dir = TextElem::dir_in(styles); - let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize; - - // Stitch together the columns for each region. - for region in regions.iter().take(total_regions) { - // The height should be the parent height if we should expand. - // Otherwise its the maximum column height for the frame. In that - // case, the frame is first created with zero height and then - // resized. - let height = if regions.expand.y { region.y } else { Abs::zero() }; - let mut output = Frame::hard(Size::new(regions.size.x, height)); - let mut cursor = Abs::zero(); - - for _ in 0..columns { - let Some(frame) = frames.next() else { break }; - if !regions.expand.y { - output.size_mut().y.set_max(frame.height()); - } - - let width = frame.width(); - let x = if dir == Dir::LTR { - cursor - } else { - regions.size.x - cursor - width - }; - - output.push_frame(Point::with_x(x), frame); - cursor += width + gutter; - } - - finished.push(output); - } - - Ok(Fragment::frames(finished)) - } -} - -/// Forces a column break. -/// -/// The function will behave like a [page break]($pagebreak) when used in a -/// single column layout or the last column on a page. Otherwise, content after -/// the column break will be placed in the next column. -/// -/// # Example -/// ```example -/// #set page(columns: 2) -/// Preliminary findings from our -/// ongoing research project have -/// revealed a hitherto unknown -/// phenomenon of extraordinary -/// significance. -/// -/// #colbreak() -/// Through rigorous experimentation -/// and analysis, we have discovered -/// a hitherto uncharacterized process -/// that defies our current -/// understanding of the fundamental -/// laws of nature. -/// ``` -#[elem(title = "Column Break", Behave)] -pub struct ColbreakElem { - /// If `{true}`, the column break is skipped if the current column is - /// already empty. - #[default(false)] - pub weak: bool, -} - -impl Behave for ColbreakElem { - fn behaviour(&self) -> Behaviour { - if self.weak(StyleChain::default()) { - Behaviour::Weak(1) - } else { - Behaviour::Destructive - } - } -} diff --git a/crates/typst-library/src/layout/container.rs b/crates/typst-library/src/layout/container.rs deleted file mode 100644 index 9268f8df..00000000 --- a/crates/typst-library/src/layout/container.rs +++ /dev/null @@ -1,502 +0,0 @@ -use typst::eval::AutoValue; - -use crate::layout::{Spacing, VElem}; -use crate::prelude::*; - -/// An inline-level container that sizes content. -/// -/// All elements except inline math, text, and boxes are block-level and cannot -/// occur inside of a paragraph. The box function can be used to integrate such -/// elements into a paragraph. Boxes take the size of their contents by default -/// but can also be sized explicitly. -/// -/// # Example -/// ```example -/// Refer to the docs -/// #box( -/// height: 9pt, -/// image("docs.svg") -/// ) -/// for more information. -/// ``` -#[elem(Layout)] -pub struct BoxElem { - /// The width of the box. - /// - /// Boxes can have [fractional]($fraction) widths, as the example below - /// demonstrates. - /// - /// _Note:_ Currently, only boxes and only their widths might be fractionally - /// sized within paragraphs. Support for fractionally sized images, shapes, - /// and more might be added in the future. - /// - /// ```example - /// Line in #box(width: 1fr, line(length: 100%)) between. - /// ``` - pub width: Sizing, - - /// The height of the box. - pub height: Smart>, - - /// An amount to shift the box's baseline by. - /// - /// ```example - /// Image: #box(baseline: 40%, image("tiger.jpg", width: 2cm)). - /// ``` - #[resolve] - pub baseline: Rel, - - /// The box's background color. See the - /// [rectangle's documentation]($rect.fill) for more details. - pub fill: Option, - - /// The box's border color. See the - /// [rectangle's documentation]($rect.stroke) for more details. - #[resolve] - #[fold] - pub stroke: Sides>>, - - /// How much to round the box's corners. See the - /// [rectangle's documentation]($rect.radius) for more details. - #[resolve] - #[fold] - pub radius: Corners>>, - - /// How much to pad the box's content. - /// - /// _Note:_ When the box contains text, its exact size depends on the - /// current [text edges]($text.top-edge). - /// - /// ```example - /// #rect(inset: 0pt)[Tight] - /// ``` - #[resolve] - #[fold] - pub inset: Sides>>, - - /// How much to expand the box's size without affecting the layout. - /// - /// This is useful to prevent padding from affecting line layout. For a - /// generalized version of the example below, see the documentation for the - /// [raw text's block parameter]($raw.block). - /// - /// ```example - /// An inline - /// #box( - /// fill: luma(235), - /// inset: (x: 3pt, y: 0pt), - /// outset: (y: 3pt), - /// radius: 2pt, - /// )[rectangle]. - /// ``` - #[resolve] - #[fold] - pub outset: Sides>>, - - /// Whether to clip the content inside the box. - #[default(false)] - pub clip: bool, - - /// The contents of the box. - #[positional] - pub body: Option, -} - -impl Layout for BoxElem { - #[tracing::instrument(name = "BoxElem::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - let width = match self.width(styles) { - Sizing::Auto => Smart::Auto, - Sizing::Rel(rel) => Smart::Custom(rel), - Sizing::Fr(_) => Smart::Custom(Ratio::one().into()), - }; - - // Resolve the sizing to a concrete size. - let sizing = Axes::new(width, self.height(styles)); - let expand = sizing.as_ref().map(Smart::is_custom); - let size = sizing - .resolve(styles) - .zip_map(regions.base(), |s, b| s.map(|v| v.relative_to(b))) - .unwrap_or(regions.base()); - - // Apply inset. - let mut body = self.body(styles).unwrap_or_default(); - let inset = self.inset(styles); - if inset.iter().any(|v| !v.is_zero()) { - body = body.padded(inset.map(|side| side.map(Length::from))); - } - - // Select the appropriate base and expansion for the child depending - // on whether it is automatically or relatively sized. - let pod = Regions::one(size, expand); - let mut frame = body.layout(vt, styles, pod)?.into_frame(); - - // Enforce correct size. - *frame.size_mut() = expand.select(size, frame.size()); - - // Apply baseline shift. - let shift = self.baseline(styles).relative_to(frame.height()); - if !shift.is_zero() { - frame.set_baseline(frame.baseline() - shift); - } - - // Prepare fill and stroke. - let fill = self.fill(styles); - let stroke = self.stroke(styles).map(|s| s.map(Stroke::unwrap_or_default)); - - // Clip the contents - if self.clip(styles) { - let outset = self.outset(styles).relative_to(frame.size()); - let size = frame.size() + outset.sum_by_axis(); - let radius = self.radius(styles); - frame.clip(clip_rect(size, radius, &stroke)); - } - - // Add fill and/or stroke. - if fill.is_some() || stroke.iter().any(Option::is_some) { - let outset = self.outset(styles); - let radius = self.radius(styles); - frame.fill_and_stroke(fill, stroke, outset, radius, self.span()); - } - - // Apply metadata. - frame.meta(styles, false); - frame.set_kind(FrameKind::Hard); - - Ok(Fragment::frame(frame)) - } -} - -/// A block-level container. -/// -/// Such a container can be used to separate content, size it, and give it a -/// background or border. -/// -/// # Examples -/// With a block, you can give a background to content while still allowing it -/// to break across multiple pages. -/// ```example -/// #set page(height: 100pt) -/// #block( -/// fill: luma(230), -/// inset: 8pt, -/// radius: 4pt, -/// lorem(30), -/// ) -/// ``` -/// -/// Blocks are also useful to force elements that would otherwise be inline to -/// become block-level, especially when writing show rules. -/// ```example -/// #show heading: it => it.body -/// = Blockless -/// More text. -/// -/// #show heading: it => block(it.body) -/// = Blocky -/// More text. -/// ``` -#[elem(Layout)] -pub struct BlockElem { - /// The block's width. - /// - /// ```example - /// #set align(center) - /// #block( - /// width: 60%, - /// inset: 8pt, - /// fill: silver, - /// lorem(10), - /// ) - /// ``` - pub width: Smart>, - - /// The block's height. When the height is larger than the remaining space - /// on a page and [`breakable`]($block.breakable) is `{true}`, the - /// block will continue on the next page with the remaining height. - /// - /// ```example - /// #set page(height: 80pt) - /// #set align(center) - /// #block( - /// width: 80%, - /// height: 150%, - /// fill: aqua, - /// ) - /// ``` - pub height: Smart>, - - /// Whether the block can be broken and continue on the next page. - /// - /// ```example - /// #set page(height: 80pt) - /// The following block will - /// jump to its own page. - /// #block( - /// breakable: false, - /// lorem(15), - /// ) - /// ``` - #[default(true)] - pub breakable: bool, - - /// The block's background color. See the - /// [rectangle's documentation]($rect.fill) for more details. - pub fill: Option, - - /// The block's border color. See the - /// [rectangle's documentation]($rect.stroke) for more details. - #[resolve] - #[fold] - pub stroke: Sides>>, - - /// How much to round the block's corners. See the - /// [rectangle's documentation]($rect.radius) for more details. - #[resolve] - #[fold] - pub radius: Corners>>, - - /// How much to pad the block's content. See the - /// [box's documentation]($box.inset) for more details. - #[resolve] - #[fold] - pub inset: Sides>>, - - /// How much to expand the block's size without affecting the layout. See - /// the [box's documentation]($box.outset) for more details. - #[resolve] - #[fold] - pub outset: Sides>>, - - /// The spacing around this block. This is shorthand to set `above` and - /// `below` to the same value. - /// - /// ```example - /// #set align(center) - /// #show math.equation: set block(above: 8pt, below: 16pt) - /// - /// This sum of $x$ and $y$: - /// $ x + y = z $ - /// A second paragraph. - /// ``` - #[external] - #[default(Em::new(1.2).into())] - pub spacing: Spacing, - - /// The spacing between this block and its predecessor. Takes precedence - /// over `spacing`. Can be used in combination with a show rule to adjust - /// the spacing around arbitrary block-level elements. - #[external] - #[default(Em::new(1.2).into())] - pub above: Spacing, - #[internal] - #[parse( - let spacing = args.named("spacing")?; - args.named("above")? - .map(VElem::block_around) - .or_else(|| spacing.map(VElem::block_spacing)) - )] - #[default(VElem::block_spacing(Em::new(1.2).into()))] - pub above: VElem, - - /// The spacing between this block and its successor. Takes precedence - /// over `spacing`. - #[external] - #[default(Em::new(1.2).into())] - pub below: Spacing, - #[internal] - #[parse( - args.named("below")? - .map(VElem::block_around) - .or_else(|| spacing.map(VElem::block_spacing)) - )] - #[default(VElem::block_spacing(Em::new(1.2).into()))] - pub below: VElem, - - /// Whether to clip the content inside the block. - #[default(false)] - pub clip: bool, - - /// The contents of the block. - #[positional] - pub body: Option, - - /// Whether this block must stick to the following one. - /// - /// Use this to prevent page breaks between e.g. a heading and its body. - #[internal] - #[default(false)] - pub sticky: bool, -} - -impl Layout for BlockElem { - #[tracing::instrument(name = "BlockElem::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - // Apply inset. - let mut body = self.body(styles).unwrap_or_default(); - let inset = self.inset(styles); - if inset.iter().any(|v| !v.is_zero()) { - body = body.clone().padded(inset.map(|side| side.map(Length::from))); - } - - // Resolve the sizing to a concrete size. - let sizing = Axes::new(self.width(styles), self.height(styles)); - let mut expand = sizing.as_ref().map(Smart::is_custom); - let mut size = sizing - .resolve(styles) - .zip_map(regions.base(), |s, b| s.map(|v| v.relative_to(b))) - .unwrap_or(regions.base()); - - // Layout the child. - let mut frames = if self.breakable(styles) { - // Measure to ensure frames for all regions have the same width. - if sizing.x == Smart::Auto { - let pod = Regions::one(size, Axes::splat(false)); - let frame = body.measure(vt, styles, pod)?.into_frame(); - size.x = frame.width(); - expand.x = true; - } - - let mut pod = regions; - pod.size.x = size.x; - pod.expand = expand; - - if expand.y { - pod.full = size.y; - } - - // Generate backlog for fixed height. - let mut heights = vec![]; - if sizing.y.is_custom() { - let mut remaining = size.y; - for region in regions.iter() { - let limited = region.y.min(remaining); - heights.push(limited); - remaining -= limited; - if Abs::zero().fits(remaining) { - break; - } - } - - if let Some(last) = heights.last_mut() { - *last += remaining; - } - - pod.size.y = heights[0]; - pod.backlog = &heights[1..]; - pod.last = None; - } - - let mut frames = body.layout(vt, styles, pod)?.into_frames(); - for (frame, &height) in frames.iter_mut().zip(&heights) { - *frame.size_mut() = - expand.select(Size::new(size.x, height), frame.size()); - } - frames - } else { - let pod = Regions::one(size, expand); - let mut frames = body.layout(vt, styles, pod)?.into_frames(); - *frames[0].size_mut() = expand.select(size, frames[0].size()); - frames - }; - - // Prepare fill and stroke. - let fill = self.fill(styles); - let stroke = self.stroke(styles).map(|s| s.map(Stroke::unwrap_or_default)); - - // Clip the contents - if self.clip(styles) { - for frame in frames.iter_mut() { - let outset = self.outset(styles).relative_to(frame.size()); - let size = frame.size() + outset.sum_by_axis(); - let radius = self.radius(styles); - frame.clip(clip_rect(size, radius, &stroke)); - } - } - - // Add fill and/or stroke. - if fill.is_some() || stroke.iter().any(Option::is_some) { - let mut skip = false; - if let [first, rest @ ..] = frames.as_slice() { - skip = first.is_empty() && rest.iter().any(|frame| !frame.is_empty()); - } - - let outset = self.outset(styles); - let radius = self.radius(styles); - for frame in frames.iter_mut().skip(skip as usize) { - frame.fill_and_stroke( - fill.clone(), - stroke.clone(), - outset, - radius, - self.span(), - ); - } - } - - // Apply metadata. - for frame in &mut frames { - frame.set_kind(FrameKind::Hard); - frame.meta(styles, false); - } - - Ok(Fragment::frames(frames)) - } -} - -/// Defines how to size a grid cell along an axis. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Sizing { - /// A track that fits its cell's contents. - Auto, - /// A track size specified in absolute terms and relative to the parent's - /// size. - Rel(Rel), - /// A track size specified as a fraction of the remaining free space in the - /// parent. - Fr(Fr), -} - -impl Sizing { - /// Whether this is fractional sizing. - pub fn is_fractional(self) -> bool { - matches!(self, Self::Fr(_)) - } -} - -impl Default for Sizing { - fn default() -> Self { - Self::Auto - } -} - -impl> From for Sizing { - fn from(spacing: T) -> Self { - match spacing.into() { - Spacing::Rel(rel) => Self::Rel(rel), - Spacing::Fr(fr) => Self::Fr(fr), - } - } -} - -cast! { - Sizing, - self => match self { - Self::Auto => Value::Auto, - Self::Rel(rel) => rel.into_value(), - Self::Fr(fr) => fr.into_value(), - }, - _: AutoValue => Self::Auto, - v: Rel => Self::Rel(v), - v: Fr => Self::Fr(v), -} diff --git a/crates/typst-library/src/layout/enum.rs b/crates/typst-library/src/layout/enum.rs deleted file mode 100644 index 0c98c18a..00000000 --- a/crates/typst-library/src/layout/enum.rs +++ /dev/null @@ -1,319 +0,0 @@ -use std::str::FromStr; - -use crate::layout::{BlockElem, GridLayouter, ParElem, Sizing, Spacing}; -use crate::meta::{Numbering, NumberingPattern}; -use crate::prelude::*; -use crate::text::TextElem; - -/// A numbered list. -/// -/// Displays a sequence of items vertically and numbers them consecutively. -/// -/// # Example -/// ```example -/// Automatically numbered: -/// + Preparations -/// + Analysis -/// + Conclusions -/// -/// Manually numbered: -/// 2. What is the first step? -/// 5. I am confused. -/// + Moving on ... -/// -/// Multiple lines: -/// + This enum item has multiple -/// lines because the next line -/// is indented. -/// -/// Function call. -/// #enum[First][Second] -/// ``` -/// -/// You can easily switch all your enumerations to a different numbering style -/// with a set rule. -/// ```example -/// #set enum(numbering: "a)") -/// -/// + Starting off ... -/// + Don't forget step two -/// ``` -/// -/// You can also use [`enum.item`]($enum.item) to programmatically customize the -/// number of each item in the enumeration: -/// -/// ```example -/// #enum( -/// enum.item(1)[First step], -/// enum.item(5)[Fifth step], -/// enum.item(10)[Tenth step] -/// ) -/// ``` -/// -/// # Syntax -/// This functions also has dedicated syntax: -/// -/// - Starting a line with a plus sign creates an automatically numbered -/// enumeration item. -/// - Starting a line with a number followed by a dot creates an explicitly -/// numbered enumeration item. -/// -/// Enumeration items can contain multiple paragraphs and other block-level -/// content. All content that is indented more than an item's marker becomes -/// part of that item. -#[elem(scope, title = "Numbered List", Layout)] -pub struct EnumElem { - /// If this is `{false}`, the items are spaced apart with - /// [enum spacing]($enum.spacing). If it is `{true}`, they use normal - /// [leading]($par.leading) instead. This makes the enumeration more - /// compact, which can look better if the items are short. - /// - /// In markup mode, the value of this parameter is determined based on - /// whether items are separated with a blank line. If items directly follow - /// each other, this is set to `{true}`; if items are separated by a blank - /// line, this is set to `{false}`. - /// - /// ```example - /// + If an enum has a lot of text, and - /// maybe other inline content, it - /// should not be tight anymore. - /// - /// + To make an enum wide, simply - /// insert a blank line between the - /// items. - /// ``` - #[default(true)] - pub tight: bool, - - /// How to number the enumeration. Accepts a - /// [numbering pattern or function]($numbering). - /// - /// If the numbering pattern contains multiple counting symbols, they apply - /// to nested enums. If given a function, the function receives one argument - /// if `full` is `{false}` and multiple arguments if `full` is `{true}`. - /// - /// ```example - /// #set enum(numbering: "1.a)") - /// + Different - /// + Numbering - /// + Nested - /// + Items - /// + Style - /// - /// #set enum(numbering: n => super[#n]) - /// + Superscript - /// + Numbering! - /// ``` - #[default(Numbering::Pattern(NumberingPattern::from_str("1.").unwrap()))] - #[borrowed] - pub numbering: Numbering, - - /// Which number to start the enumeration with. - /// - /// ```example - /// #enum( - /// start: 3, - /// [Skipping], - /// [Ahead], - /// ) - /// ``` - #[default(1)] - pub start: usize, - - /// Whether to display the full numbering, including the numbers of - /// all parent enumerations. - /// - /// - /// ```example - /// #set enum(numbering: "1.a)", full: true) - /// + Cook - /// + Heat water - /// + Add integredients - /// + Eat - /// ``` - #[default(false)] - pub full: bool, - - /// The indentation of each item. - #[resolve] - pub indent: Length, - - /// The space between the numbering and the body of each item. - #[resolve] - #[default(Em::new(0.5).into())] - pub body_indent: Length, - - /// The spacing between the items of a wide (non-tight) enumeration. - /// - /// If set to `{auto}`, uses the spacing [below blocks]($block.below). - pub spacing: Smart, - - /// The horizontal alignment that enum numbers should have. - /// - /// By default, this is set to `{end}`, which aligns enum numbers - /// towards end of the current text direction (in left-to-right script, - /// for example, this is the same as `{right}`). The choice of `{end}` - /// for horizontal alignment of enum numbers is usually preferred over - /// `{start}`, as numbers then grow away from the text instead of towards - /// it, avoiding certain visual issues. This option lets you override this - /// behavior, however. - /// - /// ````example - /// #set enum(number-align: start) - /// - /// Here are some powers of two: - /// 1. One - /// 2. Two - /// 4. Four - /// 8. Eight - /// 16. Sixteen - /// 32. Thirty two - /// ```` - #[default(HAlign::End)] - pub number_align: HAlign, - - /// The numbered list's items. - /// - /// When using the enum syntax, adjacent items are automatically collected - /// into enumerations, even through constructs like for loops. - /// - /// ```example - /// #for phase in ( - /// "Launch", - /// "Orbit", - /// "Descent", - /// ) [+ #phase] - /// ``` - #[variadic] - pub children: Vec, - - /// The numbers of parent items. - #[internal] - #[fold] - parents: Parent, -} - -#[scope] -impl EnumElem { - #[elem] - type EnumItem; -} - -impl Layout for EnumElem { - #[tracing::instrument(name = "EnumElem::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - let numbering = self.numbering(styles); - let indent = self.indent(styles); - let body_indent = self.body_indent(styles); - let gutter = if self.tight(styles) { - ParElem::leading_in(styles).into() - } else { - self.spacing(styles) - .unwrap_or_else(|| *BlockElem::below_in(styles).amount()) - }; - - let mut cells = vec![]; - let mut number = self.start(styles); - let mut parents = self.parents(styles); - let full = self.full(styles); - - // Horizontally align based on the given respective parameter. - // Vertically align to the top to avoid inheriting `horizon` or `bottom` - // alignment from the context and having the number be displaced in - // relation to the item it refers to. - let number_align = self.number_align(styles) + VAlign::Top; - - for item in self.children() { - number = item.number(styles).unwrap_or(number); - - let resolved = if full { - parents.push(number); - let content = numbering.apply_vt(vt, &parents)?.display(); - parents.pop(); - content - } else { - match numbering { - Numbering::Pattern(pattern) => { - TextElem::packed(pattern.apply_kth(parents.len(), number)) - } - other => other.apply_vt(vt, &[number])?.display(), - } - }; - - // Disable overhang as a workaround to end-aligned dots glitching - // and decreasing spacing between numbers and items. - let resolved = - resolved.aligned(number_align).styled(TextElem::set_overhang(false)); - - cells.push(Content::empty()); - cells.push(resolved); - cells.push(Content::empty()); - cells.push(item.body().clone().styled(Self::set_parents(Parent(number)))); - number = number.saturating_add(1); - } - - let layouter = GridLayouter::new( - Axes::with_x(&[ - Sizing::Rel(indent.into()), - Sizing::Auto, - Sizing::Rel(body_indent.into()), - Sizing::Auto, - ]), - Axes::with_y(&[gutter.into()]), - &cells, - regions, - styles, - self.span(), - ); - - Ok(layouter.layout(vt)?.fragment) - } -} - -/// An enumeration item. -#[elem(name = "item", title = "Numbered List Item")] -pub struct EnumItem { - /// The item's number. - #[positional] - pub number: Option, - - /// The item's body. - #[required] - pub body: Content, -} - -cast! { - EnumItem, - array: Array => { - let mut iter = array.into_iter(); - let (number, body) = match (iter.next(), iter.next(), iter.next()) { - (Some(a), Some(b), None) => (a.cast()?, b.cast()?), - _ => bail!("array must contain exactly two entries"), - }; - Self::new(body).with_number(number) - }, - v: Content => v.to::().cloned().unwrap_or_else(|| Self::new(v.clone())), -} - -#[derive(Debug, Clone, Copy, PartialEq, Hash)] -struct Parent(usize); - -cast! { - Parent, - self => self.0.into_value(), - v: usize => Self(v), -} - -impl Fold for Parent { - type Output = Vec; - - fn fold(self, mut outer: Self::Output) -> Self::Output { - outer.push(self.0); - outer - } -} diff --git a/crates/typst-library/src/layout/flow.rs b/crates/typst-library/src/layout/flow.rs deleted file mode 100644 index 17a39254..00000000 --- a/crates/typst-library/src/layout/flow.rs +++ /dev/null @@ -1,714 +0,0 @@ -use std::mem; - -use comemo::Prehashed; - -use crate::layout::{ - AlignElem, BlockElem, ColbreakElem, ColumnsElem, ParElem, PlaceElem, Spacing, VElem, -}; -use crate::meta::{FootnoteElem, FootnoteEntry}; -use crate::prelude::*; -use crate::visualize::{ - CircleElem, EllipseElem, ImageElem, LineElem, PathElem, PolygonElem, RectElem, - SquareElem, -}; - -/// Arranges spacing, paragraphs and block-level elements into a flow. -/// -/// This element is responsible for layouting both the top-level content flow -/// and the contents of boxes. -#[elem(Layout)] -pub struct FlowElem { - /// The children that will be arranges into a flow. - #[variadic] - pub children: Vec>, -} - -impl Layout for FlowElem { - #[tracing::instrument(name = "FlowElem::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - if !regions.size.x.is_finite() && regions.expand.x { - bail!(error!(self.span(), "cannot expand into infinite width")); - } - if !regions.size.y.is_finite() && regions.expand.y { - bail!(error!(self.span(), "cannot expand into infinite height")); - } - let mut layouter = FlowLayouter::new(regions, styles); - - for mut child in self.children().iter().map(|c| &**c) { - let outer = styles; - let mut styles = styles; - if let Some((elem, map)) = child.to_styled() { - child = elem; - styles = outer.chain(map); - } - - if let Some(elem) = child.to::() { - layouter.layout_spacing(vt, elem, styles)?; - } else if let Some(elem) = child.to::() { - layouter.layout_par(vt, elem, styles)?; - } else if child.is::() - || child.is::() - || child.is::() - || child.is::() - || child.is::() - || child.is::() - || child.is::() - || child.is::() - { - let layoutable = child.with::().unwrap(); - layouter.layout_single(vt, layoutable, styles)?; - } else if child.is::() { - let mut frame = Frame::soft(Size::zero()); - frame.meta(styles, true); - layouter.items.push(FlowItem::Frame { - frame, - align: Axes::splat(FixedAlign::Start), - sticky: true, - movable: false, - }); - } else if let Some(placed) = child.to::() { - layouter.layout_placed(vt, placed, styles)?; - } else if child.can::() { - layouter.layout_multiple(vt, child, styles)?; - } else if child.is::() { - if !layouter.regions.backlog.is_empty() || layouter.regions.last.is_some() - { - layouter.finish_region(vt)?; - } - } else { - bail!(child.span(), "unexpected flow child"); - } - } - - layouter.finish(vt) - } -} - -/// Performs flow layout. -struct FlowLayouter<'a> { - /// Whether this is the root flow. - root: bool, - /// The regions to layout children into. - regions: Regions<'a>, - /// The shared styles. - styles: StyleChain<'a>, - /// Whether the flow should expand to fill the region. - expand: Axes, - /// The initial size of `regions.size` that was available before we started - /// subtracting. - initial: Size, - /// Whether the last block was a paragraph. - last_was_par: bool, - /// Spacing and layouted blocks for the current region. - items: Vec, - /// A queue of floats. - pending_floats: Vec, - /// Whether we have any footnotes in the current region. - has_footnotes: bool, - /// Footnote configuration. - footnote_config: FootnoteConfig, - /// Finished frames for previous regions. - finished: Vec, -} - -/// Cached footnote configuration. -struct FootnoteConfig { - separator: Content, - clearance: Abs, - gap: Abs, -} - -/// A prepared item in a flow layout. -#[derive(Debug)] -enum FlowItem { - /// Spacing between other items and whether it is weak. - Absolute(Abs, bool), - /// Fractional spacing between other items. - Fractional(Fr), - /// A frame for a layouted block, how to align it, whether it sticks to the - /// item after it (for orphan prevention), and whether it is movable - /// (to keep it together with its footnotes). - Frame { frame: Frame, align: Axes, sticky: bool, movable: bool }, - /// An absolutely placed frame. - Placed { - frame: Frame, - x_align: FixedAlign, - y_align: Smart>, - delta: Axes>, - float: bool, - clearance: Abs, - }, - /// A footnote frame (can also be the separator). - Footnote(Frame), -} - -impl FlowItem { - /// The inherent height of the item. - fn height(&self) -> Abs { - match self { - Self::Absolute(v, _) => *v, - Self::Fractional(_) | Self::Placed { .. } => Abs::zero(), - Self::Frame { frame, .. } | Self::Footnote(frame) => frame.height(), - } - } -} - -impl<'a> FlowLayouter<'a> { - /// Create a new flow layouter. - fn new(mut regions: Regions<'a>, styles: StyleChain<'a>) -> Self { - let expand = regions.expand; - - // Disable vertical expansion & root for children. - regions.expand.y = false; - let root = mem::replace(&mut regions.root, false); - - Self { - root, - regions, - styles, - expand, - initial: regions.size, - last_was_par: false, - items: vec![], - pending_floats: vec![], - has_footnotes: false, - footnote_config: FootnoteConfig { - separator: FootnoteEntry::separator_in(styles), - clearance: FootnoteEntry::clearance_in(styles), - gap: FootnoteEntry::gap_in(styles), - }, - finished: vec![], - } - } - - /// Layout vertical spacing. - #[tracing::instrument(name = "FlowLayouter::layout_spacing", skip_all)] - fn layout_spacing( - &mut self, - vt: &mut Vt, - v: &VElem, - styles: StyleChain, - ) -> SourceResult<()> { - self.layout_item( - vt, - match v.amount() { - Spacing::Rel(rel) => FlowItem::Absolute( - rel.resolve(styles).relative_to(self.initial.y), - v.weakness(styles) > 0, - ), - Spacing::Fr(fr) => FlowItem::Fractional(*fr), - }, - ) - } - - /// Layout a paragraph. - #[tracing::instrument(name = "FlowLayouter::layout_par", skip_all)] - fn layout_par( - &mut self, - vt: &mut Vt, - par: &ParElem, - styles: StyleChain, - ) -> SourceResult<()> { - let align = AlignElem::alignment_in(styles).resolve(styles); - let leading = ParElem::leading_in(styles); - let consecutive = self.last_was_par; - let lines = par - .layout(vt, styles, consecutive, self.regions.base(), self.regions.expand.x)? - .into_frames(); - - let mut sticky = self.items.len(); - for (i, item) in self.items.iter().enumerate().rev() { - match *item { - FlowItem::Absolute(_, _) => {} - FlowItem::Frame { sticky: true, .. } => sticky = i, - _ => break, - } - } - - if let Some(first) = lines.first() { - if !self.regions.size.y.fits(first.height()) && !self.regions.in_last() { - let carry: Vec<_> = self.items.drain(sticky..).collect(); - self.finish_region(vt)?; - for item in carry { - self.layout_item(vt, item)?; - } - } - } - - for (i, frame) in lines.into_iter().enumerate() { - if i > 0 { - self.layout_item(vt, FlowItem::Absolute(leading, true))?; - } - - self.layout_item( - vt, - FlowItem::Frame { frame, align, sticky: false, movable: true }, - )?; - } - - self.last_was_par = true; - Ok(()) - } - - /// Layout into a single region. - #[tracing::instrument(name = "FlowLayouter::layout_single", skip_all)] - fn layout_single( - &mut self, - vt: &mut Vt, - content: &dyn Layout, - styles: StyleChain, - ) -> SourceResult<()> { - let align = AlignElem::alignment_in(styles).resolve(styles); - let sticky = BlockElem::sticky_in(styles); - let pod = Regions::one(self.regions.base(), Axes::splat(false)); - let frame = content.layout(vt, styles, pod)?.into_frame(); - self.layout_item(vt, FlowItem::Frame { frame, align, sticky, movable: true })?; - self.last_was_par = false; - Ok(()) - } - - /// Layout a placed element. - fn layout_placed( - &mut self, - vt: &mut Vt, - placed: &PlaceElem, - styles: StyleChain, - ) -> SourceResult<()> { - let float = placed.float(styles); - let clearance = placed.clearance(styles); - let alignment = placed.alignment(styles); - let delta = Axes::new(placed.dx(styles), placed.dy(styles)).resolve(styles); - let x_align = alignment.map_or(FixedAlign::Center, |align| { - align.x().unwrap_or_default().resolve(styles) - }); - let y_align = alignment.map(|align| align.y().map(VAlign::fix)); - let frame = placed.layout(vt, styles, self.regions)?.into_frame(); - let item = FlowItem::Placed { frame, x_align, y_align, delta, float, clearance }; - self.layout_item(vt, item) - } - - /// Layout into multiple regions. - fn layout_multiple( - &mut self, - vt: &mut Vt, - block: &Content, - styles: StyleChain, - ) -> SourceResult<()> { - // Temporarily delegerate rootness to the columns. - let is_root = self.root; - if is_root && block.is::() { - self.root = false; - self.regions.root = true; - } - - let mut notes = Vec::new(); - - if self.regions.is_full() { - // Skip directly if region is already full. - self.finish_region(vt)?; - } - - // How to align the block. - let align = if let Some(align) = block.to::() { - align.alignment(styles) - } else if let Some((_, local)) = block.to_styled() { - AlignElem::alignment_in(styles.chain(local)) - } else { - AlignElem::alignment_in(styles) - } - .resolve(styles); - - // Layout the block itself. - let sticky = BlockElem::sticky_in(styles); - let fragment = block.layout(vt, styles, self.regions)?; - - for (i, frame) in fragment.into_iter().enumerate() { - // Find footnotes in the frame. - if self.root { - find_footnotes(&mut notes, &frame); - } - - if i > 0 { - self.finish_region(vt)?; - } - - let item = FlowItem::Frame { frame, align, sticky, movable: false }; - self.layout_item(vt, item)?; - } - - self.try_handle_footnotes(vt, notes)?; - - self.root = is_root; - self.regions.root = false; - self.last_was_par = false; - - Ok(()) - } - - /// Layout a finished frame. - #[tracing::instrument(name = "FlowLayouter::layout_item", skip_all)] - fn layout_item(&mut self, vt: &mut Vt, mut item: FlowItem) -> SourceResult<()> { - match item { - FlowItem::Absolute(v, weak) => { - if weak - && !self - .items - .iter() - .any(|item| matches!(item, FlowItem::Frame { .. })) - { - return Ok(()); - } - self.regions.size.y -= v - } - FlowItem::Fractional(_) => {} - FlowItem::Frame { ref frame, movable, .. } => { - let height = frame.height(); - if !self.regions.size.y.fits(height) && !self.regions.in_last() { - self.finish_region(vt)?; - } - - self.regions.size.y -= height; - if self.root && movable { - let mut notes = Vec::new(); - find_footnotes(&mut notes, frame); - self.items.push(item); - if !self.handle_footnotes(vt, &mut notes, true, false)? { - let item = self.items.pop(); - self.finish_region(vt)?; - self.items.extend(item); - self.regions.size.y -= height; - self.handle_footnotes(vt, &mut notes, true, true)?; - } - return Ok(()); - } - } - FlowItem::Placed { float: false, .. } => {} - FlowItem::Placed { - ref mut frame, - ref mut y_align, - float: true, - clearance, - .. - } => { - // If the float doesn't fit, queue it for the next region. - if !self.regions.size.y.fits(frame.height() + clearance) - && !self.regions.in_last() - { - self.pending_floats.push(item); - return Ok(()); - } - - // Select the closer placement, top or bottom. - if y_align.is_auto() { - let ratio = (self.regions.size.y - - (frame.height() + clearance) / 2.0) - / self.regions.full; - let better_align = - if ratio <= 0.5 { FixedAlign::End } else { FixedAlign::Start }; - *y_align = Smart::Custom(Some(better_align)); - } - - // Add some clearance so that the float doesn't touch the main - // content. - frame.size_mut().y += clearance; - if *y_align == Smart::Custom(Some(FixedAlign::End)) { - frame.translate(Point::with_y(clearance)); - } - - self.regions.size.y -= frame.height(); - - // Find footnotes in the frame. - if self.root { - let mut notes = vec![]; - find_footnotes(&mut notes, frame); - self.try_handle_footnotes(vt, notes)?; - } - } - FlowItem::Footnote(_) => {} - } - - self.items.push(item); - Ok(()) - } - - /// Finish the frame for one region. - fn finish_region(&mut self, vt: &mut Vt) -> SourceResult<()> { - // Trim weak spacing. - while self - .items - .last() - .map_or(false, |item| matches!(item, FlowItem::Absolute(_, true))) - { - self.items.pop(); - } - - // Determine the used size. - let mut fr = Fr::zero(); - let mut used = Size::zero(); - let mut footnote_height = Abs::zero(); - let mut float_top_height = Abs::zero(); - let mut float_bottom_height = Abs::zero(); - let mut first_footnote = true; - for item in &self.items { - match item { - FlowItem::Absolute(v, _) => used.y += *v, - FlowItem::Fractional(v) => fr += *v, - FlowItem::Frame { frame, .. } => { - used.y += frame.height(); - used.x.set_max(frame.width()); - } - FlowItem::Placed { float: false, .. } => {} - FlowItem::Placed { frame, float: true, y_align, .. } => match y_align { - Smart::Custom(Some(FixedAlign::Start)) => { - float_top_height += frame.height() - } - Smart::Custom(Some(FixedAlign::End)) => { - float_bottom_height += frame.height() - } - _ => {} - }, - FlowItem::Footnote(frame) => { - footnote_height += frame.height(); - if !first_footnote { - footnote_height += self.footnote_config.gap; - } - first_footnote = false; - used.x.set_max(frame.width()); - } - } - } - used.y += footnote_height + float_top_height + float_bottom_height; - - // Determine the size of the flow in this region depending on whether - // the region expands. Also account for fractional spacing and - // footnotes. - let mut size = self.expand.select(self.initial, used).min(self.initial); - if (fr.get() > 0.0 || self.has_footnotes) && self.initial.y.is_finite() { - size.y = self.initial.y; - } - - let mut output = Frame::soft(size); - let mut ruler = FixedAlign::Start; - let mut float_top_offset = Abs::zero(); - let mut offset = float_top_height; - let mut float_bottom_offset = Abs::zero(); - let mut footnote_offset = Abs::zero(); - - // Place all frames. - for item in self.items.drain(..) { - match item { - FlowItem::Absolute(v, _) => { - offset += v; - } - FlowItem::Fractional(v) => { - let remaining = self.initial.y - used.y; - offset += v.share(fr, remaining); - } - FlowItem::Frame { frame, align, .. } => { - ruler = ruler.max(align.y); - let x = align.x.position(size.x - frame.width()); - let y = offset + ruler.position(size.y - used.y); - let pos = Point::new(x, y); - offset += frame.height(); - output.push_frame(pos, frame); - } - FlowItem::Placed { frame, x_align, y_align, delta, float, .. } => { - let x = x_align.position(size.x - frame.width()); - let y = if float { - match y_align { - Smart::Custom(Some(FixedAlign::Start)) => { - let y = float_top_offset; - float_top_offset += frame.height(); - y - } - Smart::Custom(Some(FixedAlign::End)) => { - let y = size.y - footnote_height - float_bottom_height - + float_bottom_offset; - float_bottom_offset += frame.height(); - y - } - _ => unreachable!("float must be y aligned"), - } - } else { - match y_align { - Smart::Custom(Some(align)) => { - align.position(size.y - frame.height()) - } - _ => offset + ruler.position(size.y - used.y), - } - }; - - let pos = Point::new(x, y) - + delta.zip_map(size, Rel::relative_to).to_point(); - - output.push_frame(pos, frame); - } - FlowItem::Footnote(frame) => { - let y = size.y - footnote_height + footnote_offset; - footnote_offset += frame.height() + self.footnote_config.gap; - output.push_frame(Point::with_y(y), frame); - } - } - } - - // Advance to the next region. - self.finished.push(output); - self.regions.next(); - self.initial = self.regions.size; - self.has_footnotes = false; - - // Try to place floats. - for item in mem::take(&mut self.pending_floats) { - self.layout_item(vt, item)?; - } - - Ok(()) - } - - /// Finish layouting and return the resulting fragment. - fn finish(mut self, vt: &mut Vt) -> SourceResult { - if self.expand.y { - while !self.regions.backlog.is_empty() { - self.finish_region(vt)?; - } - } - - self.finish_region(vt)?; - while !self.items.is_empty() { - self.finish_region(vt)?; - } - - Ok(Fragment::frames(self.finished)) - } -} - -impl FlowLayouter<'_> { - fn try_handle_footnotes( - &mut self, - vt: &mut Vt, - mut notes: Vec, - ) -> SourceResult<()> { - if self.root && !self.handle_footnotes(vt, &mut notes, false, false)? { - self.finish_region(vt)?; - self.handle_footnotes(vt, &mut notes, false, true)?; - } - Ok(()) - } - - /// Processes all footnotes in the frame. - #[tracing::instrument(skip_all)] - fn handle_footnotes( - &mut self, - vt: &mut Vt, - notes: &mut Vec, - movable: bool, - force: bool, - ) -> SourceResult { - let items_len = self.items.len(); - let notes_len = notes.len(); - - // Process footnotes one at a time. - let mut k = 0; - while k < notes.len() { - if notes[k].is_ref() { - k += 1; - continue; - } - - if !self.has_footnotes { - self.layout_footnote_separator(vt)?; - } - - self.regions.size.y -= self.footnote_config.gap; - let checkpoint = vt.locator.clone(); - let frames = FootnoteEntry::new(notes[k].clone()) - .pack() - .layout(vt, self.styles, self.regions.with_root(false))? - .into_frames(); - - // If the entries didn't fit, abort (to keep footnote and entry - // together). - if !force - && (k == 0 || movable) - && frames.first().map_or(false, Frame::is_empty) - { - // Remove existing footnotes attempts because we need to - // move the item to the next page. - notes.truncate(notes_len); - - // Undo region modifications. - for item in self.items.drain(items_len..) { - self.regions.size.y -= item.height(); - } - - // Undo Vt modifications. - *vt.locator = checkpoint; - - return Ok(false); - } - - let prev = notes.len(); - for (i, frame) in frames.into_iter().enumerate() { - find_footnotes(notes, &frame); - if i > 0 { - self.finish_region(vt)?; - self.layout_footnote_separator(vt)?; - self.regions.size.y -= self.footnote_config.gap; - } - self.regions.size.y -= frame.height(); - self.items.push(FlowItem::Footnote(frame)); - } - - k += 1; - - // Process the nested notes before dealing with further top-level - // notes. - let nested = notes.len() - prev; - if nested > 0 { - notes[k..].rotate_right(nested); - } - } - - Ok(true) - } - - /// Layout and save the footnote separator, typically a line. - #[tracing::instrument(skip_all)] - fn layout_footnote_separator(&mut self, vt: &mut Vt) -> SourceResult<()> { - let expand = Axes::new(self.regions.expand.x, false); - let pod = Regions::one(self.regions.base(), expand); - let separator = &self.footnote_config.separator; - - let mut frame = separator.layout(vt, self.styles, pod)?.into_frame(); - frame.size_mut().y += self.footnote_config.clearance; - frame.translate(Point::with_y(self.footnote_config.clearance)); - - self.has_footnotes = true; - self.regions.size.y -= frame.height(); - self.items.push(FlowItem::Footnote(frame)); - - Ok(()) - } -} - -/// Finds all footnotes in the frame. -#[tracing::instrument(skip_all)] -fn find_footnotes(notes: &mut Vec, frame: &Frame) { - for (_, item) in frame.items() { - match item { - FrameItem::Group(group) => find_footnotes(notes, &group.frame), - FrameItem::Meta(Meta::Elem(content), _) - if !notes.iter().any(|note| note.location() == content.location()) => - { - let Some(footnote) = content.to::() else { continue }; - notes.push(footnote.clone()); - } - _ => {} - } - } -} diff --git a/crates/typst-library/src/layout/fragment.rs b/crates/typst-library/src/layout/fragment.rs deleted file mode 100644 index 3550df2a..00000000 --- a/crates/typst-library/src/layout/fragment.rs +++ /dev/null @@ -1,87 +0,0 @@ -use crate::prelude::*; - -/// A partial layout result. -#[derive(Clone)] -pub struct Fragment(Vec); - -impl Fragment { - /// Create a fragment from a single frame. - pub fn frame(frame: Frame) -> Self { - Self(vec![frame]) - } - - /// Create a fragment from multiple frames. - pub fn frames(frames: Vec) -> Self { - Self(frames) - } - - /// Return `true` if the length is 0. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// The number of frames in the fragment. - pub fn len(&self) -> usize { - self.0.len() - } - - /// Extract the first and only frame. - /// - /// Panics if there are multiple frames. - #[track_caller] - pub fn into_frame(self) -> Frame { - assert_eq!(self.0.len(), 1, "expected exactly one frame"); - self.0.into_iter().next().unwrap() - } - - /// Extract the frames. - pub fn into_frames(self) -> Vec { - self.0 - } - - /// Iterate over the contained frames. - pub fn iter(&self) -> std::slice::Iter { - self.0.iter() - } - - /// Iterate over the contained frames. - pub fn iter_mut(&mut self) -> std::slice::IterMut { - self.0.iter_mut() - } -} - -impl Debug for Fragment { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self.0.as_slice() { - [frame] => frame.fmt(f), - frames => frames.fmt(f), - } - } -} - -impl IntoIterator for Fragment { - type Item = Frame; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl<'a> IntoIterator for &'a Fragment { - type Item = &'a Frame; - type IntoIter = std::slice::Iter<'a, Frame>; - - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl<'a> IntoIterator for &'a mut Fragment { - type Item = &'a mut Frame; - type IntoIter = std::slice::IterMut<'a, Frame>; - - fn into_iter(self) -> Self::IntoIter { - self.0.iter_mut() - } -} diff --git a/crates/typst-library/src/layout/grid.rs b/crates/typst-library/src/layout/grid.rs deleted file mode 100644 index 1cde6564..00000000 --- a/crates/typst-library/src/layout/grid.rs +++ /dev/null @@ -1,734 +0,0 @@ -use smallvec::{smallvec, SmallVec}; - -use crate::layout::Sizing; -use crate::prelude::*; -use crate::text::TextElem; - -/// Arranges content in a grid. -/// -/// The grid element allows you to arrange content in a grid. You can define the -/// number of rows and columns, as well as the size of the gutters between them. -/// There are multiple sizing modes for columns and rows that can be used to -/// create complex layouts. -/// -/// The sizing of the grid is determined by the track sizes specified in the -/// arguments. Because each of the sizing parameters accepts the same values, we -/// will explain them just once, here. Each sizing argument accepts an array of -/// individual track sizes. A track size is either: -/// -/// - `{auto}`: The track will be sized to fit its contents. It will be at most -/// as large as the remaining space. If there is more than one `{auto}` track -/// which, and together they claim more than the available space, the `{auto}` -/// tracks will fairly distribute the available space among themselves. -/// -/// - A fixed or relative length (e.g. `{10pt}` or `{20% - 1cm}`): The track -/// will be exactly of this size. -/// -/// - A fractional length (e.g. `{1fr}`): Once all other tracks have been sized, -/// the remaining space will be divided among the fractional tracks according -/// to their fractions. For example, if there are two fractional tracks, each -/// with a fraction of `{1fr}`, they will each take up half of the remaining -/// space. -/// -/// To specify a single track, the array can be omitted in favor of a single -/// value. To specify multiple `{auto}` tracks, enter the number of tracks -/// instead of an array. For example, `columns:` `{3}` is equivalent to -/// `columns:` `{(auto, auto, auto)}`. -/// -/// # Examples -/// The example below demonstrates the different track sizing options. -/// -/// ```example -/// // We use `rect` to emphasize the -/// // area of cells. -/// #set rect( -/// inset: 8pt, -/// fill: rgb("e4e5ea"), -/// width: 100%, -/// ) -/// -/// #grid( -/// columns: (60pt, 1fr, 2fr), -/// rows: (auto, 60pt), -/// gutter: 3pt, -/// rect[Fixed width, auto height], -/// rect[1/3 of the remains], -/// rect[2/3 of the remains], -/// rect(height: 100%)[Fixed height], -/// image("tiger.jpg", height: 100%), -/// image("tiger.jpg", height: 100%), -/// ) -/// ``` -/// -/// You can also [spread]($arguments/#spreading) an array of strings or content -/// into a grid to populate its cells. -/// -/// ```example -/// #grid( -/// columns: 5, -/// gutter: 5pt, -/// ..range(25).map(str) -/// ) -/// ``` -#[elem(Layout)] -pub struct GridElem { - /// The column sizes. - /// - /// Either specify a track size array or provide an integer to create a grid - /// with that many `{auto}`-sized columns. Note that opposed to rows and - /// gutters, providing a single track size will only ever create a single - /// column. - #[borrowed] - pub columns: TrackSizings, - - /// The row sizes. - /// - /// If there are more cells than fit the defined rows, the last row is - /// repeated until there are no more cells. - #[borrowed] - pub rows: TrackSizings, - - /// The gaps between rows & columns. - /// - /// If there are more gutters than defined sizes, the last gutter is repeated. - #[external] - pub gutter: TrackSizings, - - /// The gaps between columns. Takes precedence over `gutter`. - #[parse( - let gutter = args.named("gutter")?; - args.named("column-gutter")?.or_else(|| gutter.clone()) - )] - #[borrowed] - pub column_gutter: TrackSizings, - - /// The gaps between rows. Takes precedence over `gutter`. - #[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))] - #[borrowed] - pub row_gutter: TrackSizings, - - /// The contents of the grid cells. - /// - /// The cells are populated in row-major order. - #[variadic] - pub children: Vec, -} - -impl Layout for GridElem { - #[tracing::instrument(name = "GridElem::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - let columns = self.columns(styles); - let rows = self.rows(styles); - let column_gutter = self.column_gutter(styles); - let row_gutter = self.row_gutter(styles); - - // Prepare grid layout by unifying content and gutter tracks. - let layouter = GridLayouter::new( - Axes::new(&columns.0, &rows.0), - Axes::new(&column_gutter.0, &row_gutter.0), - &self.children, - regions, - styles, - self.span(), - ); - - // Measure the columns and layout the grid row-by-row. - Ok(layouter.layout(vt)?.fragment) - } -} - -/// Track sizing definitions. -#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] -pub struct TrackSizings(pub SmallVec<[Sizing; 4]>); - -cast! { - TrackSizings, - self => self.0.into_value(), - sizing: Sizing => Self(smallvec![sizing]), - count: NonZeroUsize => Self(smallvec![Sizing::Auto; count.get()]), - values: Array => Self(values.into_iter().map(Value::cast).collect::>()?), -} - -/// Performs grid layout. -pub struct GridLayouter<'a> { - /// The grid cells. - cells: &'a [Content], - /// Whether this is an RTL grid. - is_rtl: bool, - /// Whether this grid has gutters. - has_gutter: bool, - /// The column tracks including gutter tracks. - cols: Vec, - /// The row tracks including gutter tracks. - rows: Vec, - /// The regions to layout children into. - regions: Regions<'a>, - /// The inherited styles. - styles: StyleChain<'a>, - /// Resolved column sizes. - rcols: Vec, - /// The sum of `rcols`. - width: Abs, - /// Resolve row sizes, by region. - rrows: Vec>, - /// Rows in the current region. - lrows: Vec, - /// The initial size of the current region before we started subtracting. - initial: Size, - /// Frames for finished regions. - finished: Vec, - /// The span of the grid element. - span: Span, -} - -/// The resulting sizes of columns and rows in a grid. -#[derive(Debug)] -pub struct GridLayout { - /// The fragment. - pub fragment: Fragment, - /// The column widths. - pub cols: Vec, - /// The heights of the resulting rows segments, by region. - pub rows: Vec>, -} - -/// Details about a resulting row piece. -#[derive(Debug)] -pub struct RowPiece { - /// The height of the segment. - pub height: Abs, - /// The index of the row. - pub y: usize, -} - -/// Produced by initial row layout, auto and relative rows are already finished, -/// fractional rows not yet. -enum Row { - /// Finished row frame of auto or relative row with y index. - Frame(Frame, usize), - /// Fractional row with y index. - Fr(Fr, usize), -} - -impl<'a> GridLayouter<'a> { - /// Create a new grid layouter. - /// - /// This prepares grid layout by unifying content and gutter tracks. - pub fn new( - tracks: Axes<&[Sizing]>, - gutter: Axes<&[Sizing]>, - cells: &'a [Content], - regions: Regions<'a>, - styles: StyleChain<'a>, - span: Span, - ) -> Self { - let mut cols = vec![]; - let mut rows = vec![]; - - // Number of content columns: Always at least one. - let c = tracks.x.len().max(1); - - // Number of content rows: At least as many as given, but also at least - // as many as needed to place each item. - let r = { - let len = cells.len(); - let given = tracks.y.len(); - let needed = len / c + (len % c).clamp(0, 1); - given.max(needed) - }; - - let has_gutter = gutter.any(|tracks| !tracks.is_empty()); - let auto = Sizing::Auto; - let zero = Sizing::Rel(Rel::zero()); - let get_or = |tracks: &[_], idx, default| { - tracks.get(idx).or(tracks.last()).copied().unwrap_or(default) - }; - - // Collect content and gutter columns. - for x in 0..c { - cols.push(get_or(tracks.x, x, auto)); - if has_gutter { - cols.push(get_or(gutter.x, x, zero)); - } - } - - // Collect content and gutter rows. - for y in 0..r { - rows.push(get_or(tracks.y, y, auto)); - if has_gutter { - rows.push(get_or(gutter.y, y, zero)); - } - } - - // Remove superfluous gutter tracks. - if has_gutter { - cols.pop(); - rows.pop(); - } - - // Reverse for RTL. - let is_rtl = TextElem::dir_in(styles) == Dir::RTL; - if is_rtl { - cols.reverse(); - } - - // We use these regions for auto row measurement. Since at that moment, - // columns are already sized, we can enable horizontal expansion. - let mut regions = regions; - regions.expand = Axes::new(true, false); - - Self { - cells, - is_rtl, - has_gutter, - rows, - regions, - styles, - rcols: vec![Abs::zero(); cols.len()], - cols, - width: Abs::zero(), - rrows: vec![], - lrows: vec![], - initial: regions.size, - finished: vec![], - span, - } - } - - /// Determines the columns sizes and then layouts the grid row-by-row. - pub fn layout(mut self, vt: &mut Vt) -> SourceResult { - self.measure_columns(vt)?; - - for y in 0..self.rows.len() { - // Skip to next region if current one is full, but only for content - // rows, not for gutter rows. - if self.regions.is_full() && (!self.has_gutter || y % 2 == 0) { - self.finish_region(vt)?; - } - - match self.rows[y] { - Sizing::Auto => self.layout_auto_row(vt, y)?, - Sizing::Rel(v) => self.layout_relative_row(vt, v, y)?, - Sizing::Fr(v) => self.lrows.push(Row::Fr(v, y)), - } - } - - self.finish_region(vt)?; - - Ok(GridLayout { - fragment: Fragment::frames(self.finished), - cols: self.rcols, - rows: self.rrows, - }) - } - - /// Determine all column sizes. - #[tracing::instrument(name = "GridLayouter::measure_columns", skip_all)] - fn measure_columns(&mut self, vt: &mut Vt) -> SourceResult<()> { - // Sum of sizes of resolved relative tracks. - let mut rel = Abs::zero(); - - // Sum of fractions of all fractional tracks. - let mut fr = Fr::zero(); - - // Resolve the size of all relative columns and compute the sum of all - // fractional tracks. - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - match col { - Sizing::Auto => {} - Sizing::Rel(v) => { - let resolved = - v.resolve(self.styles).relative_to(self.regions.base().x); - *rcol = resolved; - rel += resolved; - } - Sizing::Fr(v) => fr += v, - } - } - - // Size that is not used by fixed-size columns. - let available = self.regions.size.x - rel; - if available >= Abs::zero() { - // Determine size of auto columns. - let (auto, count) = self.measure_auto_columns(vt, available)?; - - // If there is remaining space, distribute it to fractional columns, - // otherwise shrink auto columns. - let remaining = available - auto; - if remaining >= Abs::zero() { - self.grow_fractional_columns(remaining, fr); - } else { - self.shrink_auto_columns(available, count); - } - } - - // Sum up the resolved column sizes once here. - self.width = self.rcols.iter().sum(); - - Ok(()) - } - - /// Measure the size that is available to auto columns. - fn measure_auto_columns( - &mut self, - vt: &mut Vt, - available: Abs, - ) -> SourceResult<(Abs, usize)> { - let mut auto = Abs::zero(); - let mut count = 0; - - // Determine size of auto columns by laying out all cells in those - // columns, measuring them and finding the largest one. - for (x, &col) in self.cols.iter().enumerate() { - if col != Sizing::Auto { - continue; - } - - let mut resolved = Abs::zero(); - for y in 0..self.rows.len() { - if let Some(cell) = self.cell(x, y) { - // For relative rows, we can already resolve the correct - // base and for auto and fr we could only guess anyway. - let height = match self.rows[y] { - Sizing::Rel(v) => { - v.resolve(self.styles).relative_to(self.regions.base().y) - } - _ => self.regions.base().y, - }; - - let size = Size::new(available, height); - let pod = Regions::one(size, Axes::splat(false)); - let frame = cell.measure(vt, self.styles, pod)?.into_frame(); - resolved.set_max(frame.width()); - } - } - - self.rcols[x] = resolved; - auto += resolved; - count += 1; - } - - Ok((auto, count)) - } - - /// Distribute remaining space to fractional columns. - fn grow_fractional_columns(&mut self, remaining: Abs, fr: Fr) { - if fr.is_zero() { - return; - } - - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - if let Sizing::Fr(v) = col { - *rcol = v.share(fr, remaining); - } - } - } - - /// Redistribute space to auto columns so that each gets a fair share. - fn shrink_auto_columns(&mut self, available: Abs, count: usize) { - let mut last; - let mut fair = -Abs::inf(); - let mut redistribute = available; - let mut overlarge = count; - let mut changed = true; - - // Iteratively remove columns that don't need to be shrunk. - while changed && overlarge > 0 { - changed = false; - last = fair; - fair = redistribute / (overlarge as f64); - - for (&col, &rcol) in self.cols.iter().zip(&self.rcols) { - // Remove an auto column if it is not overlarge (rcol <= fair), - // but also hasn't already been removed (rcol > last). - if col == Sizing::Auto && rcol <= fair && rcol > last { - redistribute -= rcol; - overlarge -= 1; - changed = true; - } - } - } - - // Redistribute space fairly among overlarge columns. - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - if col == Sizing::Auto && *rcol > fair { - *rcol = fair; - } - } - } - - /// Layout a row with automatic height. Such a row may break across multiple - /// regions. - fn layout_auto_row(&mut self, vt: &mut Vt, y: usize) -> SourceResult<()> { - // Determine the size for each region of the row. If the first region - // ends up empty for some column, skip the region and remeasure. - let mut resolved = match self.measure_auto_row(vt, y, true)? { - Some(resolved) => resolved, - None => { - self.finish_region(vt)?; - self.measure_auto_row(vt, y, false)?.unwrap() - } - }; - - // Nothing to layout. - if resolved.is_empty() { - return Ok(()); - } - - // Layout into a single region. - if let &[first] = resolved.as_slice() { - let frame = self.layout_single_row(vt, first, y)?; - self.push_row(frame, y); - return Ok(()); - } - - // Expand all but the last region. - // Skip the first region if the space is eaten up by an fr row. - let len = resolved.len(); - for (region, target) in self - .regions - .iter() - .zip(&mut resolved[..len - 1]) - .skip(self.lrows.iter().any(|row| matches!(row, Row::Fr(..))) as usize) - { - target.set_max(region.y); - } - - // Layout into multiple regions. - let fragment = self.layout_multi_row(vt, &resolved, y)?; - let len = fragment.len(); - for (i, frame) in fragment.into_iter().enumerate() { - self.push_row(frame, y); - if i + 1 < len { - self.finish_region(vt)?; - } - } - - Ok(()) - } - - /// Measure the regions sizes of an auto row. The option is always `Some(_)` - /// if `can_skip` is false. - fn measure_auto_row( - &mut self, - vt: &mut Vt, - y: usize, - can_skip: bool, - ) -> SourceResult>> { - let mut resolved: Vec = vec![]; - - for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(cell) = self.cell(x, y) { - let mut pod = self.regions; - pod.size.x = rcol; - - let frames = cell.measure(vt, self.styles, pod)?.into_frames(); - - // Skip the first region if one cell in it is empty. Then, - // remeasure. - if let [first, rest @ ..] = frames.as_slice() { - if can_skip - && first.is_empty() - && rest.iter().any(|frame| !frame.is_empty()) - { - return Ok(None); - } - } - - let mut sizes = frames.iter().map(|frame| frame.height()); - for (target, size) in resolved.iter_mut().zip(&mut sizes) { - target.set_max(size); - } - - // New heights are maximal by virtue of being new. Note that - // this extend only uses the rest of the sizes iterator. - resolved.extend(sizes); - } - } - - Ok(Some(resolved)) - } - - /// Layout a row with relative height. Such a row cannot break across - /// multiple regions, but it may force a region break. - fn layout_relative_row( - &mut self, - vt: &mut Vt, - v: Rel, - y: usize, - ) -> SourceResult<()> { - let resolved = v.resolve(self.styles).relative_to(self.regions.base().y); - let frame = self.layout_single_row(vt, resolved, y)?; - - // Skip to fitting region. - let height = frame.height(); - while !self.regions.size.y.fits(height) && !self.regions.in_last() { - self.finish_region(vt)?; - - // Don't skip multiple regions for gutter and don't push a row. - if self.has_gutter && y % 2 == 1 { - return Ok(()); - } - } - - self.push_row(frame, y); - - Ok(()) - } - - /// Layout a row with fixed height and return its frame. - fn layout_single_row( - &mut self, - vt: &mut Vt, - height: Abs, - y: usize, - ) -> SourceResult { - if !height.is_finite() { - bail!(error!(self.span, "cannot create grid with infinite height")); - } - - let mut output = Frame::soft(Size::new(self.width, height)); - let mut pos = Point::zero(); - - for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(cell) = self.cell(x, y) { - let size = Size::new(rcol, height); - let mut pod = Regions::one(size, Axes::splat(true)); - if self.rows[y] == Sizing::Auto { - pod.full = self.regions.full; - } - let frame = cell.layout(vt, self.styles, pod)?.into_frame(); - output.push_frame(pos, frame); - } - - pos.x += rcol; - } - - Ok(output) - } - - /// Layout a row spanning multiple regions. - fn layout_multi_row( - &mut self, - vt: &mut Vt, - heights: &[Abs], - y: usize, - ) -> SourceResult { - // Prepare frames. - let mut outputs: Vec<_> = heights - .iter() - .map(|&h| Frame::soft(Size::new(self.width, h))) - .collect(); - - // Prepare regions. - let size = Size::new(self.width, heights[0]); - let mut pod = Regions::one(size, Axes::splat(true)); - pod.full = self.regions.full; - pod.backlog = &heights[1..]; - - // Layout the row. - let mut pos = Point::zero(); - for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(cell) = self.cell(x, y) { - pod.size.x = rcol; - - // Push the layouted frames into the individual output frames. - let fragment = cell.layout(vt, self.styles, pod)?; - for (output, frame) in outputs.iter_mut().zip(fragment) { - output.push_frame(pos, frame); - } - } - - pos.x += rcol; - } - - Ok(Fragment::frames(outputs)) - } - - /// Push a row frame into the current region. - fn push_row(&mut self, frame: Frame, y: usize) { - self.regions.size.y -= frame.height(); - self.lrows.push(Row::Frame(frame, y)); - } - - /// Finish rows for one region. - fn finish_region(&mut self, vt: &mut Vt) -> SourceResult<()> { - // Determine the height of existing rows in the region. - let mut used = Abs::zero(); - let mut fr = Fr::zero(); - for row in &self.lrows { - match row { - Row::Frame(frame, _) => used += frame.height(), - Row::Fr(v, _) => fr += *v, - } - } - - // Determine the size of the grid in this region, expanding fully if - // there are fr rows. - let mut size = Size::new(self.width, used).min(self.initial); - if fr.get() > 0.0 && self.initial.y.is_finite() { - size.y = self.initial.y; - } - - // The frame for the region. - let mut output = Frame::soft(size); - let mut pos = Point::zero(); - let mut rrows = vec![]; - - // Place finished rows and layout fractional rows. - for row in std::mem::take(&mut self.lrows) { - let (frame, y) = match row { - Row::Frame(frame, y) => (frame, y), - Row::Fr(v, y) => { - let remaining = self.regions.full - used; - let height = v.share(fr, remaining); - (self.layout_single_row(vt, height, y)?, y) - } - }; - - let height = frame.height(); - output.push_frame(pos, frame); - rrows.push(RowPiece { height, y }); - pos.y += height; - } - - self.finished.push(output); - self.rrows.push(rrows); - self.regions.next(); - self.initial = self.regions.size; - - Ok(()) - } - - /// Get the content of the cell in column `x` and row `y`. - /// - /// Returns `None` if it's a gutter cell. - #[track_caller] - fn cell(&self, mut x: usize, y: usize) -> Option<&'a Content> { - assert!(x < self.cols.len()); - assert!(y < self.rows.len()); - - // Columns are reorder, but the cell slice is not. - if self.is_rtl { - x = self.cols.len() - 1 - x; - } - - if self.has_gutter { - // Even columns and rows are children, odd ones are gutter. - if x % 2 == 0 && y % 2 == 0 { - let c = 1 + self.cols.len() / 2; - self.cells.get((y / 2) * c + x / 2) - } else { - None - } - } else { - let c = self.cols.len(); - self.cells.get(y * c + x) - } - } -} diff --git a/crates/typst-library/src/layout/hide.rs b/crates/typst-library/src/layout/hide.rs deleted file mode 100644 index af3d0631..00000000 --- a/crates/typst-library/src/layout/hide.rs +++ /dev/null @@ -1,29 +0,0 @@ -use smallvec::smallvec; - -use crate::prelude::*; - -/// Hides content without affecting layout. -/// -/// The `hide` function allows you to hide content while the layout still 'sees' -/// it. This is useful to create whitespace that is exactly as large as some -/// content. It may also be useful to redact content because its arguments are -/// not included in the output. -/// -/// # Example -/// ```example -/// Hello Jane \ -/// #hide[Hello] Joe -/// ``` -#[elem(Show)] -pub struct HideElem { - /// The content to hide. - #[required] - pub body: Content, -} - -impl Show for HideElem { - #[tracing::instrument(name = "HideElem::show", skip(self))] - fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult { - Ok(self.body().clone().styled(MetaElem::set_data(smallvec![Meta::Hide]))) - } -} diff --git a/crates/typst-library/src/layout/list.rs b/crates/typst-library/src/layout/list.rs deleted file mode 100644 index 7c089760..00000000 --- a/crates/typst-library/src/layout/list.rs +++ /dev/null @@ -1,238 +0,0 @@ -use crate::layout::{BlockElem, GridLayouter, ParElem, Sizing, Spacing}; -use crate::prelude::*; -use crate::text::TextElem; - -/// A bullet list. -/// -/// Displays a sequence of items vertically, with each item introduced by a -/// marker. -/// -/// # Example -/// ```example -/// Normal list. -/// - Text -/// - Math -/// - Layout -/// - ... -/// -/// Multiple lines. -/// - This list item spans multiple -/// lines because it is indented. -/// -/// Function call. -/// #list( -/// [Foundations], -/// [Calculate], -/// [Construct], -/// [Data Loading], -/// ) -/// ``` -/// -/// # Syntax -/// This functions also has dedicated syntax: Start a line with a hyphen, -/// followed by a space to create a list item. A list item can contain multiple -/// paragraphs and other block-level content. All content that is indented -/// more than an item's marker becomes part of that item. -#[elem(scope, title = "Bullet List", Layout)] -pub struct ListElem { - /// If this is `{false}`, the items are spaced apart with - /// [list spacing]($list.spacing). If it is `{true}`, they use normal - /// [leading]($par.leading) instead. This makes the list more compact, which - /// can look better if the items are short. - /// - /// In markup mode, the value of this parameter is determined based on - /// whether items are separated with a blank line. If items directly follow - /// each other, this is set to `{true}`; if items are separated by a blank - /// line, this is set to `{false}`. - /// - /// ```example - /// - If a list has a lot of text, and - /// maybe other inline content, it - /// should not be tight anymore. - /// - /// - To make a list wide, simply insert - /// a blank line between the items. - /// ``` - #[default(true)] - pub tight: bool, - - /// The marker which introduces each item. - /// - /// Instead of plain content, you can also pass an array with multiple - /// markers that should be used for nested lists. If the list nesting depth - /// exceeds the number of markers, the last one is repeated. For total - /// control, you may pass a function that maps the list's nesting depth - /// (starting from `{0}`) to a desired marker. - /// - /// ```example - /// #set list(marker: [--]) - /// - A more classic list - /// - With en-dashes - /// - /// #set list(marker: ([•], [--])) - /// - Top-level - /// - Nested - /// - Items - /// - Items - /// ``` - #[borrowed] - #[default(ListMarker::Content(vec![TextElem::packed('•')]))] - pub marker: ListMarker, - - /// The indent of each item. - #[resolve] - pub indent: Length, - - /// The spacing between the marker and the body of each item. - #[resolve] - #[default(Em::new(0.5).into())] - pub body_indent: Length, - - /// The spacing between the items of a wide (non-tight) list. - /// - /// If set to `{auto}`, uses the spacing [below blocks]($block.below). - pub spacing: Smart, - - /// The bullet list's children. - /// - /// When using the list syntax, adjacent items are automatically collected - /// into lists, even through constructs like for loops. - /// - /// ```example - /// #for letter in "ABC" [ - /// - Letter #letter - /// ] - /// ``` - #[variadic] - pub children: Vec, - - /// The nesting depth. - #[internal] - #[fold] - depth: Depth, -} - -#[scope] -impl ListElem { - #[elem] - type ListItem; -} - -impl Layout for ListElem { - #[tracing::instrument(name = "ListElem::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - let indent = self.indent(styles); - let body_indent = self.body_indent(styles); - let gutter = if self.tight(styles) { - ParElem::leading_in(styles).into() - } else { - self.spacing(styles) - .unwrap_or_else(|| *BlockElem::below_in(styles).amount()) - }; - - let depth = self.depth(styles); - let marker = self - .marker(styles) - .resolve(vt, depth)? - // avoid '#set align' interference with the list - .aligned(HAlign::Start + VAlign::Top); - - let mut cells = vec![]; - for item in self.children() { - cells.push(Content::empty()); - cells.push(marker.clone()); - cells.push(Content::empty()); - cells.push(item.body().clone().styled(Self::set_depth(Depth))); - } - - let layouter = GridLayouter::new( - Axes::with_x(&[ - Sizing::Rel(indent.into()), - Sizing::Auto, - Sizing::Rel(body_indent.into()), - Sizing::Auto, - ]), - Axes::with_y(&[gutter.into()]), - &cells, - regions, - styles, - self.span(), - ); - - Ok(layouter.layout(vt)?.fragment) - } -} - -/// A bullet list item. -#[elem(name = "item", title = "Bullet List Item")] -pub struct ListItem { - /// The item's body. - #[required] - pub body: Content, -} - -cast! { - ListItem, - v: Content => v.to::().cloned().unwrap_or_else(|| Self::new(v.clone())), -} - -/// A list's marker. -#[derive(Debug, Clone, PartialEq, Hash)] -pub enum ListMarker { - Content(Vec), - Func(Func), -} - -impl ListMarker { - /// Resolve the marker for the given depth. - fn resolve(&self, vt: &mut Vt, depth: usize) -> SourceResult { - Ok(match self { - Self::Content(list) => { - list.get(depth).or(list.last()).cloned().unwrap_or_default() - } - Self::Func(func) => func.call_vt(vt, [depth])?.display(), - }) - } -} - -cast! { - ListMarker, - self => match self { - Self::Content(vec) => if vec.len() == 1 { - vec.into_iter().next().unwrap().into_value() - } else { - vec.into_value() - }, - Self::Func(func) => func.into_value(), - }, - v: Content => Self::Content(vec![v]), - array: Array => { - if array.is_empty() { - bail!("array must contain at least one marker"); - } - Self::Content(array.into_iter().map(Value::display).collect()) - }, - v: Func => Self::Func(v), -} - -#[derive(Debug, Clone, Copy, PartialEq, Hash)] -struct Depth; - -cast! { - Depth, - self => Value::None, - _: Value => Self, -} - -impl Fold for Depth { - type Output = usize; - - fn fold(self, outer: Self::Output) -> Self::Output { - outer + 1 - } -} diff --git a/crates/typst-library/src/layout/measure.rs b/crates/typst-library/src/layout/measure.rs deleted file mode 100644 index d41b7f95..00000000 --- a/crates/typst-library/src/layout/measure.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::prelude::*; - -/// Measures the layouted size of content. -/// -/// The `measure` function lets you determine the layouted size of content. Note -/// that an infinite space is assumed, therefore the measured height/width may -/// not necessarily match the final height/width of the measured content. If you -/// want to measure in the current layout dimensions, you can combine `measure` -/// and [`layout`]($layout). -/// -/// # Example -/// The same content can have a different size depending on the styles that -/// are active when it is layouted. For example, in the example below -/// `[#content]` is of course bigger when we increase the font size. -/// -/// ```example -/// #let content = [Hello!] -/// #content -/// #set text(14pt) -/// #content -/// ``` -/// -/// To do a meaningful measurement, you therefore first need to retrieve the -/// active styles with the [`style`]($style) function. You can then pass them to -/// the `measure` function. -/// -/// ```example -/// #let thing(body) = style(styles => { -/// let size = measure(body, styles) -/// [Width of "#body" is #size.width] -/// }) -/// -/// #thing[Hey] \ -/// #thing[Welcome] -/// ``` -/// -/// The measure function returns a dictionary with the entries `width` and -/// `height`, both of type [`length`]($length). -#[func] -pub fn measure( - /// The virtual machine. - vm: &mut Vm, - /// The content whose size to measure. - content: Content, - /// The styles with which to layout the content. - styles: Styles, -) -> SourceResult { - let pod = Regions::one(Axes::splat(Abs::inf()), Axes::splat(false)); - let styles = StyleChain::new(&styles); - let frame = content.measure(&mut vm.vt, styles, pod)?.into_frame(); - let Size { x, y } = frame.size(); - Ok(dict! { "width" => x, "height" => y }) -} diff --git a/crates/typst-library/src/layout/mod.rs b/crates/typst-library/src/layout/mod.rs deleted file mode 100644 index bcbe3b2b..00000000 --- a/crates/typst-library/src/layout/mod.rs +++ /dev/null @@ -1,786 +0,0 @@ -//! Composable layouts. - -mod align; -mod columns; -mod container; -#[path = "enum.rs"] -mod enum_; -mod flow; -mod fragment; -mod grid; -mod hide; -mod list; -#[path = "measure.rs"] -mod measure_; -mod pad; -mod page; -mod par; -mod place; -mod regions; -mod repeat; -mod spacing; -mod stack; -mod table; -mod terms; -mod transform; - -pub use self::align::*; -pub use self::columns::*; -pub use self::container::*; -pub use self::enum_::*; -pub use self::flow::*; -pub use self::fragment::*; -pub use self::grid::*; -pub use self::hide::*; -pub use self::list::*; -pub use self::measure_::*; -pub use self::pad::*; -pub use self::page::*; -pub use self::par::*; -pub use self::place::*; -pub use self::regions::*; -pub use self::repeat::*; -pub use self::spacing::*; -pub use self::stack::*; -pub use self::table::*; -pub use self::terms::*; -pub use self::transform::*; - -use std::borrow::Cow; -use std::mem; - -use typed_arena::Arena; -use typst::diag::SourceResult; -use typst::eval::Tracer; -use typst::model::{applicable, realize, DelayedErrors, StyleVecBuilder}; - -use crate::math::{EquationElem, LayoutMath}; -use crate::meta::{CiteElem, CiteGroup, DocumentElem}; -use crate::prelude::*; -use crate::shared::BehavedBuilder; -use crate::text::{LinebreakElem, SmartquoteElem, SpaceElem, TextElem}; -use crate::visualize::{ - CircleElem, EllipseElem, ImageElem, LineElem, PathElem, PolygonElem, RectElem, - SquareElem, -}; - -/// Hook up all layout definitions. -pub(super) fn define(global: &mut Scope) { - global.category("layout"); - global.define_type::(); - global.define_type::(); - global.define_type::(); - global.define_type::>(); - global.define_type::(); - global.define_type::(); - global.define_type::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_elem::(); - global.define_func::(); -} - -/// Root-level layout. -pub trait LayoutRoot { - /// Layout into one frame per page. - fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult; -} - -impl LayoutRoot for Content { - #[tracing::instrument(name = "Content::layout_root", skip_all)] - fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { - #[comemo::memoize] - fn cached( - content: &Content, - world: Tracked, - introspector: Tracked, - locator: Tracked, - delayed: TrackedMut, - tracer: TrackedMut, - styles: StyleChain, - ) -> SourceResult { - let mut locator = Locator::chained(locator); - let mut vt = Vt { - world, - introspector, - locator: &mut locator, - delayed, - tracer, - }; - let scratch = Scratch::default(); - let (realized, styles) = realize_root(&mut vt, &scratch, content, styles)?; - realized - .with::() - .unwrap() - .layout_root(&mut vt, styles) - } - - tracing::info!("Starting layout"); - cached( - self, - vt.world, - vt.introspector, - vt.locator.track(), - TrackedMut::reborrow_mut(&mut vt.delayed), - TrackedMut::reborrow_mut(&mut vt.tracer), - styles, - ) - } -} - -/// Layout into regions. -pub trait Layout { - /// Layout into one frame per region. - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult; - - /// Layout without side effects. - /// - /// This element must be layouted again in the same order for the results to - /// be valid. - #[tracing::instrument(name = "Layout::measure", skip_all)] - fn measure( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - let mut locator = Locator::chained(vt.locator.track()); - let mut vt = Vt { - world: vt.world, - introspector: vt.introspector, - locator: &mut locator, - tracer: TrackedMut::reborrow_mut(&mut vt.tracer), - delayed: TrackedMut::reborrow_mut(&mut vt.delayed), - }; - self.layout(&mut vt, styles, regions) - } -} - -impl Layout for Content { - #[tracing::instrument(name = "Content::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - #[allow(clippy::too_many_arguments)] - #[comemo::memoize] - fn cached( - content: &Content, - world: Tracked, - introspector: Tracked, - locator: Tracked, - delayed: TrackedMut, - tracer: TrackedMut, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - let mut locator = Locator::chained(locator); - let mut vt = Vt { - world, - introspector, - locator: &mut locator, - delayed, - tracer, - }; - let scratch = Scratch::default(); - let (realized, styles) = realize_block(&mut vt, &scratch, content, styles)?; - realized - .with::() - .unwrap() - .layout(&mut vt, styles, regions) - } - - tracing::info!("Layouting `Content`"); - - let fragment = cached( - self, - vt.world, - vt.introspector, - vt.locator.track(), - TrackedMut::reborrow_mut(&mut vt.delayed), - TrackedMut::reborrow_mut(&mut vt.tracer), - styles, - regions, - )?; - - vt.locator.visit_frames(&fragment); - Ok(fragment) - } -} - -/// Realize into an element that is capable of root-level layout. -#[tracing::instrument(skip_all)] -fn realize_root<'a>( - vt: &mut Vt, - scratch: &'a Scratch<'a>, - content: &'a Content, - styles: StyleChain<'a>, -) -> SourceResult<(Cow<'a, Content>, StyleChain<'a>)> { - if content.can::() && !applicable(content, styles) { - return Ok((Cow::Borrowed(content), styles)); - } - - let mut builder = Builder::new(vt, scratch, true); - builder.accept(content, styles)?; - builder.interrupt_page(Some(styles), true)?; - let (pages, shared) = builder.doc.unwrap().pages.finish(); - Ok((Cow::Owned(DocumentElem::new(pages.to_vec()).pack()), shared)) -} - -/// Realize into an element that is capable of block-level layout. -#[tracing::instrument(skip_all)] -fn realize_block<'a>( - vt: &mut Vt, - scratch: &'a Scratch<'a>, - content: &'a Content, - styles: StyleChain<'a>, -) -> SourceResult<(Cow<'a, Content>, StyleChain<'a>)> { - // These elements implement `Layout` but still require a flow for - // proper layout. - if content.can::() - && !content.is::() - && !content.is::() - && !content.is::() - && !content.is::() - && !content.is::() - && !content.is::() - && !content.is::() - && !content.is::() - && !content.is::() - && !content.is::() - && !applicable(content, styles) - { - return Ok((Cow::Borrowed(content), styles)); - } - - let mut builder = Builder::new(vt, scratch, false); - builder.accept(content, styles)?; - builder.interrupt_par()?; - let (children, shared) = builder.flow.0.finish(); - Ok((Cow::Owned(FlowElem::new(children.to_vec()).pack()), shared)) -} - -/// Builds a document or a flow element from content. -struct Builder<'a, 'v, 't> { - /// The virtual typesetter. - vt: &'v mut Vt<'t>, - /// Scratch arenas for building. - scratch: &'a Scratch<'a>, - /// The current document building state. - doc: Option>, - /// The current flow building state. - flow: FlowBuilder<'a>, - /// The current paragraph building state. - par: ParBuilder<'a>, - /// The current list building state. - list: ListBuilder<'a>, - /// The current citation grouping state. - cites: CiteGroupBuilder<'a>, -} - -/// Temporary storage arenas for building. -#[derive(Default)] -struct Scratch<'a> { - /// An arena where intermediate style chains are stored. - styles: Arena>, - /// An arena where intermediate content resulting from show rules is stored. - content: Arena, -} - -impl<'a, 'v, 't> Builder<'a, 'v, 't> { - fn new(vt: &'v mut Vt<'t>, scratch: &'a Scratch<'a>, top: bool) -> Self { - Self { - vt, - scratch, - doc: top.then(DocBuilder::default), - flow: FlowBuilder::default(), - par: ParBuilder::default(), - list: ListBuilder::default(), - cites: CiteGroupBuilder::default(), - } - } - - fn accept( - &mut self, - mut content: &'a Content, - styles: StyleChain<'a>, - ) -> SourceResult<()> { - if content.can::() && !content.is::() { - content = - self.scratch.content.alloc(EquationElem::new(content.clone()).pack()); - } - - if let Some(realized) = realize(self.vt, content, styles)? { - let stored = self.scratch.content.alloc(realized); - return self.accept(stored, styles); - } - - if let Some((elem, local)) = content.to_styled() { - return self.styled(elem, local, styles); - } - - if let Some(children) = content.to_sequence() { - for elem in children { - self.accept(elem, styles)?; - } - return Ok(()); - } - - if self.cites.accept(content, styles) { - return Ok(()); - } - - self.interrupt_cites()?; - - if self.list.accept(content, styles) { - return Ok(()); - } - - self.interrupt_list()?; - - if self.list.accept(content, styles) { - return Ok(()); - } - - if self.par.accept(content, styles) { - return Ok(()); - } - - self.interrupt_par()?; - - if self.flow.accept(content, styles) { - return Ok(()); - } - - let keep = content - .to::() - .map_or(false, |pagebreak| !pagebreak.weak(styles)); - - self.interrupt_page(keep.then_some(styles), false)?; - - if let Some(doc) = &mut self.doc { - if doc.accept(content, styles) { - return Ok(()); - } - } - - if content.is::() { - bail!(content.span(), "pagebreaks are not allowed inside of containers"); - } else { - bail!(content.span(), "{} is not allowed here", content.func().name()); - } - } - - fn styled( - &mut self, - elem: &'a Content, - map: &'a Styles, - styles: StyleChain<'a>, - ) -> SourceResult<()> { - let stored = self.scratch.styles.alloc(styles); - let styles = stored.chain(map); - self.interrupt_style(map, None)?; - self.accept(elem, styles)?; - self.interrupt_style(map, Some(styles))?; - Ok(()) - } - - fn interrupt_style( - &mut self, - local: &Styles, - outer: Option>, - ) -> SourceResult<()> { - if let Some(Some(span)) = local.interruption::() { - if self.doc.is_none() { - bail!(span, "document set rules are not allowed inside of containers"); - } - if outer.is_none() - && (!self.flow.0.is_empty() - || !self.par.0.is_empty() - || !self.list.items.is_empty()) - { - bail!(span, "document set rules must appear before any content"); - } - } else if let Some(Some(span)) = local.interruption::() { - if self.doc.is_none() { - bail!(span, "page configuration is not allowed inside of containers"); - } - self.interrupt_page(outer, false)?; - } else if local.interruption::().is_some() - || local.interruption::().is_some() - { - self.interrupt_par()?; - } else if local.interruption::().is_some() - || local.interruption::().is_some() - || local.interruption::().is_some() - { - self.interrupt_list()?; - } - Ok(()) - } - - fn interrupt_cites(&mut self) -> SourceResult<()> { - if !self.cites.items.is_empty() { - let staged = mem::take(&mut self.cites.staged); - let (group, styles) = mem::take(&mut self.cites).finish(); - let stored = self.scratch.content.alloc(group); - self.accept(stored, styles)?; - for (content, styles) in staged { - self.accept(content, styles)?; - } - } - Ok(()) - } - - fn interrupt_list(&mut self) -> SourceResult<()> { - self.interrupt_cites()?; - if !self.list.items.is_empty() { - let staged = mem::take(&mut self.list.staged); - let (list, styles) = mem::take(&mut self.list).finish(); - let stored = self.scratch.content.alloc(list); - self.accept(stored, styles)?; - for (content, styles) in staged { - self.accept(content, styles)?; - } - } - Ok(()) - } - - fn interrupt_par(&mut self) -> SourceResult<()> { - self.interrupt_list()?; - if !self.par.0.is_empty() { - let (par, styles) = mem::take(&mut self.par).finish(); - let stored = self.scratch.content.alloc(par); - self.accept(stored, styles)?; - } - - Ok(()) - } - - fn interrupt_page( - &mut self, - styles: Option>, - last: bool, - ) -> SourceResult<()> { - self.interrupt_par()?; - let Some(doc) = &mut self.doc else { return Ok(()) }; - if (doc.keep_next && styles.is_some()) || self.flow.0.has_strong_elements(last) { - let (flow, shared) = mem::take(&mut self.flow).0.finish(); - let styles = if shared == StyleChain::default() { - styles.unwrap_or_default() - } else { - shared - }; - let page = PageElem::new(FlowElem::new(flow.to_vec()).pack()); - let stored = self.scratch.content.alloc(page.pack()); - self.accept(stored, styles)?; - } - Ok(()) - } -} - -/// Accepts pagebreaks and pages. -struct DocBuilder<'a> { - /// The page runs built so far. - pages: StyleVecBuilder<'a, Cow<'a, Content>>, - /// Whether to keep a following page even if it is empty. - keep_next: bool, - /// Whether the next page should be cleared to an even or odd number. - clear_next: Option, -} - -impl<'a> DocBuilder<'a> { - fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { - if let Some(pagebreak) = content.to::() { - self.keep_next = !pagebreak.weak(styles); - self.clear_next = pagebreak.to(styles); - return true; - } - - if let Some(page) = content.to::() { - let elem = if let Some(clear_to) = self.clear_next.take() { - let mut page = page.clone(); - page.push_clear_to(Some(clear_to)); - Cow::Owned(page.pack()) - } else { - Cow::Borrowed(content) - }; - - self.pages.push(elem, styles); - self.keep_next = false; - return true; - } - - false - } -} - -impl Default for DocBuilder<'_> { - fn default() -> Self { - Self { - pages: StyleVecBuilder::new(), - keep_next: true, - clear_next: None, - } - } -} - -/// Accepts flow content. -#[derive(Default)] -struct FlowBuilder<'a>(BehavedBuilder<'a>, bool); - -impl<'a> FlowBuilder<'a> { - fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { - if content.is::() { - self.1 = true; - return true; - } - - let last_was_parbreak = self.1; - self.1 = false; - - if content.is::() - || content.is::() - || content.is::() - || content.is::() - { - self.0.push(Cow::Borrowed(content), styles); - return true; - } - - if content.can::() || content.is::() { - let is_tight_list = if let Some(elem) = content.to::() { - elem.tight(styles) - } else if let Some(elem) = content.to::() { - elem.tight(styles) - } else if let Some(elem) = content.to::() { - elem.tight(styles) - } else { - false - }; - - if !last_was_parbreak && is_tight_list { - let leading = ParElem::leading_in(styles); - let spacing = VElem::list_attach(leading.into()); - self.0.push(Cow::Owned(spacing.pack()), styles); - } - - let (above, below) = if let Some(block) = content.to::() { - (block.above(styles), block.below(styles)) - } else { - (BlockElem::above_in(styles), BlockElem::below_in(styles)) - }; - - self.0.push(Cow::Owned(above.pack()), styles); - self.0.push(Cow::Borrowed(content), styles); - self.0.push(Cow::Owned(below.pack()), styles); - return true; - } - - false - } -} - -/// Accepts paragraph content. -#[derive(Default)] -struct ParBuilder<'a>(BehavedBuilder<'a>); - -impl<'a> ParBuilder<'a> { - fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { - if content.is::() { - if self.0.has_strong_elements(false) { - self.0.push(Cow::Borrowed(content), styles); - return true; - } - } else if content.is::() - || content.is::() - || content.is::() - || content.is::() - || content.is::() - || content.to::().map_or(false, |elem| !elem.block(styles)) - || content.is::() - { - self.0.push(Cow::Borrowed(content), styles); - return true; - } - - false - } - - fn finish(self) -> (Content, StyleChain<'a>) { - let (children, shared) = self.0.finish(); - - // Find the first span that isn't detached. - let span = children - .iter() - .map(|(cnt, _)| cnt.span()) - .find(|span| !span.is_detached()) - .unwrap_or_else(Span::detached); - - (ParElem::new(children.to_vec()).spanned(span).pack(), shared) - } -} - -/// Accepts list / enum items, spaces, paragraph breaks. -struct ListBuilder<'a> { - /// The list items collected so far. - items: StyleVecBuilder<'a, Cow<'a, Content>>, - /// Whether the list contains no paragraph breaks. - tight: bool, - /// Trailing content for which it is unclear whether it is part of the list. - staged: Vec<(&'a Content, StyleChain<'a>)>, -} - -impl<'a> ListBuilder<'a> { - fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { - if !self.items.is_empty() - && (content.is::() || content.is::()) - { - self.staged.push((content, styles)); - return true; - } - - if (content.is::() - || content.is::() - || content.is::()) - && self - .items - .elems() - .next() - .map_or(true, |first| first.func() == content.func()) - { - self.items.push(Cow::Borrowed(content), styles); - self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::()); - return true; - } - - false - } - - fn finish(self) -> (Content, StyleChain<'a>) { - let (items, shared) = self.items.finish(); - let item = items.items().next().unwrap(); - let output = if item.is::() { - ListElem::new( - items - .iter() - .map(|(item, local)| { - let item = item.to::().unwrap(); - item.clone() - .with_body(item.body().clone().styled_with_map(local.clone())) - }) - .collect::>(), - ) - .with_tight(self.tight) - .pack() - } else if item.is::() { - EnumElem::new( - items - .iter() - .map(|(item, local)| { - let item = item.to::().unwrap(); - item.clone() - .with_body(item.body().clone().styled_with_map(local.clone())) - }) - .collect::>(), - ) - .with_tight(self.tight) - .pack() - } else if item.is::() { - TermsElem::new( - items - .iter() - .map(|(item, local)| { - let item = item.to::().unwrap(); - item.clone() - .with_term(item.term().clone().styled_with_map(local.clone())) - .with_description( - item.description().clone().styled_with_map(local.clone()), - ) - }) - .collect::>(), - ) - .with_tight(self.tight) - .pack() - } else { - unreachable!() - }; - (output, shared) - } -} - -impl Default for ListBuilder<'_> { - fn default() -> Self { - Self { - items: StyleVecBuilder::default(), - tight: true, - staged: vec![], - } - } -} - -/// Accepts citations. -#[derive(Default)] -struct CiteGroupBuilder<'a> { - /// The styles. - styles: StyleChain<'a>, - /// The citations. - items: Vec, - /// Trailing content for which it is unclear whether it is part of the list. - staged: Vec<(&'a Content, StyleChain<'a>)>, -} - -impl<'a> CiteGroupBuilder<'a> { - fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { - if !self.items.is_empty() - && (content.is::() || content.is::()) - { - self.staged.push((content, styles)); - return true; - } - - if let Some(citation) = content.to::() { - if self.items.is_empty() { - self.styles = styles; - } - self.staged.retain(|(elem, _)| !elem.is::()); - self.items.push(citation.clone()); - return true; - } - - false - } - - fn finish(self) -> (Content, StyleChain<'a>) { - let span = self.items.first().map(|cite| cite.span()).unwrap_or(Span::detached()); - (CiteGroup::new(self.items).pack().spanned(span), self.styles) - } -} diff --git a/crates/typst-library/src/layout/pad.rs b/crates/typst-library/src/layout/pad.rs deleted file mode 100644 index d1b0cb1f..00000000 --- a/crates/typst-library/src/layout/pad.rs +++ /dev/null @@ -1,121 +0,0 @@ -use crate::prelude::*; - -/// Adds spacing around content. -/// -/// The spacing can be specified for each side individually, or for all sides at -/// once by specifying a positional argument. -/// -/// # Example -/// ```example -/// #set align(center) -/// -/// #pad(x: 16pt, image("typing.jpg")) -/// _Typing speeds can be -/// measured in words per minute._ -/// ``` -#[elem(title = "Padding", Layout)] -pub struct PadElem { - /// The padding at the left side. - #[parse( - let all = args.named("rest")?.or(args.find()?); - let x = args.named("x")?.or(all); - let y = args.named("y")?.or(all); - args.named("left")?.or(x) - )] - pub left: Rel, - - /// The padding at the top side. - #[parse(args.named("top")?.or(y))] - pub top: Rel, - - /// The padding at the right side. - #[parse(args.named("right")?.or(x))] - pub right: Rel, - - /// The padding at the bottom side. - #[parse(args.named("bottom")?.or(y))] - pub bottom: Rel, - - /// The horizontal padding. Both `left` and `right` take precedence over - /// this. - #[external] - pub x: Rel, - - /// The vertical padding. Both `top` and `bottom` take precedence over this. - #[external] - pub y: Rel, - - /// The padding for all sides. All other parameters take precedence over - /// this. - #[external] - pub rest: Rel, - - /// The content to pad at the sides. - #[required] - pub body: Content, -} - -impl Layout for PadElem { - #[tracing::instrument(name = "PadElem::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - let sides = Sides::new( - self.left(styles), - self.top(styles), - self.right(styles), - self.bottom(styles), - ); - - // Layout child into padded regions. - let mut backlog = vec![]; - let padding = sides.resolve(styles); - let pod = regions.map(&mut backlog, |size| shrink(size, padding)); - let mut fragment = self.body().layout(vt, styles, pod)?; - - for frame in &mut fragment { - // Apply the padding inversely such that the grown size padded - // yields the frame's size. - let padded = grow(frame.size(), padding); - let padding = padding.relative_to(padded); - let offset = Point::new(padding.left, padding.top); - - // Grow the frame and translate everything in the frame inwards. - frame.set_size(padded); - frame.translate(offset); - } - - Ok(fragment) - } -} - -/// Shrink a size by padding relative to the size itself. -fn shrink(size: Size, padding: Sides>) -> Size { - size - padding.relative_to(size).sum_by_axis() -} - -/// Grow a size by padding relative to the grown size. -/// This is the inverse operation to `shrink()`. -/// -/// For the horizontal axis the derivation looks as follows. -/// (Vertical axis is analogous.) -/// -/// Let w be the grown target width, -/// s be the given width, -/// l be the left padding, -/// r be the right padding, -/// p = l + r. -/// -/// We want that: w - l.resolve(w) - r.resolve(w) = s -/// -/// Thus: w - l.resolve(w) - r.resolve(w) = s -/// <=> w - p.resolve(w) = s -/// <=> w - p.rel * w - p.abs = s -/// <=> (1 - p.rel) * w = s + p.abs -/// <=> w = (s + p.abs) / (1 - p.rel) -fn grow(size: Size, padding: Sides>) -> Size { - size.zip_map(padding.sum_by_axis(), |s, p| (s + p.abs).safe_div(1.0 - p.rel.get())) -} diff --git a/crates/typst-library/src/layout/page.rs b/crates/typst-library/src/layout/page.rs deleted file mode 100644 index 53646c7d..00000000 --- a/crates/typst-library/src/layout/page.rs +++ /dev/null @@ -1,949 +0,0 @@ -use std::borrow::Cow; -use std::ptr; -use std::str::FromStr; - -use typst::eval::AutoValue; - -use crate::layout::{AlignElem, ColumnsElem}; -use crate::meta::{Counter, CounterKey, ManualPageCounter, Numbering}; -use crate::prelude::*; -use crate::text::TextElem; - -/// Layouts its child onto one or multiple pages. -/// -/// Although this function is primarily used in set rules to affect page -/// properties, it can also be used to explicitly render its argument onto -/// a set of pages of its own. -/// -/// Pages can be set to use `{auto}` as their width or height. In this case, the -/// pages will grow to fit their content on the respective axis. -/// -/// The [Guide for Page Setup]($guides/page-setup-guide) explains how to use -/// this and related functions to set up a document with many examples. -/// -/// # Example -/// ```example -/// >>> #set page(margin: auto) -/// #set page("us-letter") -/// -/// There you go, US friends! -/// ``` -#[elem] -pub struct PageElem { - /// A standard paper size to set width and height. - #[external] - #[default(Paper::A4)] - pub paper: Paper, - - /// The width of the page. - /// - /// ```example - /// #set page( - /// width: 3cm, - /// margin: (x: 0cm), - /// ) - /// - /// #for i in range(3) { - /// box(square(width: 1cm)) - /// } - /// ``` - #[resolve] - #[parse( - let paper = args.named_or_find::("paper")?; - args.named("width")? - .or_else(|| paper.map(|paper| Smart::Custom(paper.width().into()))) - )] - #[default(Smart::Custom(Paper::A4.width().into()))] - pub width: Smart, - - /// The height of the page. - /// - /// If this is set to `{auto}`, page breaks can only be triggered manually - /// by inserting a [page break]($pagebreak). Most examples throughout this - /// documentation use `{auto}` for the height of the page to dynamically - /// grow and shrink to fit their content. - #[resolve] - #[parse( - args.named("height")? - .or_else(|| paper.map(|paper| Smart::Custom(paper.height().into()))) - )] - #[default(Smart::Custom(Paper::A4.height().into()))] - pub height: Smart, - - /// Whether the page is flipped into landscape orientation. - /// - /// ```example - /// #set page( - /// "us-business-card", - /// flipped: true, - /// fill: rgb("f2e5dd"), - /// ) - /// - /// #set align(bottom + end) - /// #text(14pt)[*Sam H. Richards*] \ - /// _Procurement Manager_ - /// - /// #set text(10pt) - /// 17 Main Street \ - /// New York, NY 10001 \ - /// +1 555 555 5555 - /// ``` - #[default(false)] - pub flipped: bool, - - /// The page's margins. - /// - /// - `{auto}`: The margins are set automatically to 2.5/21 times the smaller - /// dimension of the page. This results in 2.5cm margins for an A4 page. - /// - A single length: The same margin on all sides. - /// - A dictionary: With a dictionary, the margins can be set individually. - /// The dictionary can contain the following keys in order of precedence: - /// - `top`: The top margin. - /// - `right`: The right margin. - /// - `bottom`: The bottom margin. - /// - `left`: The left margin. - /// - `inside`: The margin at the inner side of the page (where the - /// [binding]($page.binding) is). - /// - `outside`: The margin at the outer side of the page (opposite to the - /// [binding]($page.binding)). - /// - `x`: The horizontal margins. - /// - `y`: The vertical margins. - /// - `rest`: The margins on all sides except those for which the - /// dictionary explicitly sets a size. - /// - /// The values for `left` and `right` are mutually exclusive with - /// the values for `inside` and `outside`. - /// - /// ```example - /// #set page( - /// width: 3cm, - /// height: 4cm, - /// margin: (x: 8pt, y: 4pt), - /// ) - /// - /// #rect( - /// width: 100%, - /// height: 100%, - /// fill: aqua, - /// ) - /// ``` - #[fold] - pub margin: Margin, - - /// On which side the pages will be bound. - /// - /// - `{auto}`: Equivalent to `left` if the [text direction]($text.dir) - /// is left-to-right and `right` if it is right-to-left. - /// - `left`: Bound on the left side. - /// - `right`: Bound on the right side. - /// - /// This affects the meaning of the `inside` and `outside` options for - /// margins. - pub binding: Smart, - - /// How many columns the page has. - /// - /// If you need to insert columns into a page or other container, you can - /// also use the [`columns` function]($columns). - /// - /// ```example:single - /// #set page(columns: 2, height: 4.8cm) - /// Climate change is one of the most - /// pressing issues of our time, with - /// the potential to devastate - /// communities, ecosystems, and - /// economies around the world. It's - /// clear that we need to take urgent - /// action to reduce our carbon - /// emissions and mitigate the impacts - /// of a rapidly changing climate. - /// ``` - #[default(NonZeroUsize::ONE)] - pub columns: NonZeroUsize, - - /// The page's background color. - /// - /// This instructs the printer to color the complete page with the given - /// color. If you are considering larger production runs, it may be more - /// environmentally friendly and cost-effective to source pre-dyed pages and - /// not set this property. - /// - /// ```example - /// #set page(fill: rgb("444352")) - /// #set text(fill: rgb("fdfdfd")) - /// *Dark mode enabled.* - /// ``` - #[borrowed] - pub fill: Option, - - /// How to [number]($numbering) the pages. - /// - /// If an explicit `footer` (or `header` for top-aligned numbering) is - /// given, the numbering is ignored. - /// - /// ```example - /// #set page( - /// height: 100pt, - /// margin: (top: 16pt, bottom: 24pt), - /// numbering: "1 / 1", - /// ) - /// - /// #lorem(48) - /// ``` - #[borrowed] - pub numbering: Option, - - /// The alignment of the page numbering. - /// - /// If the vertical component is `top`, the numbering is placed into the - /// header and if it is `bottom`, it is placed in the footer. Horizon - /// alignment is forbidden. If an explicit matching `header` or `footer` is - /// given, the numbering is ignored. - /// - /// ```example - /// #set page( - /// margin: (top: 16pt, bottom: 24pt), - /// numbering: "1", - /// number-align: right, - /// ) - /// - /// #lorem(30) - /// ``` - #[default(HAlign::Center + VAlign::Bottom)] - #[parse({ - let option: Option> = args.named("number-align")?; - if let Some(Spanned { v: align, span }) = option { - if align.y() == Some(VAlign::Horizon) { - bail!(span, "page number cannot be `horizon`-aligned"); - } - } - option.map(|spanned| spanned.v) - })] - pub number_align: Align, - - /// The page's header. Fills the top margin of each page. - /// - /// ```example - /// #set par(justify: true) - /// #set page( - /// margin: (top: 32pt, bottom: 20pt), - /// header: [ - /// #set text(8pt) - /// #smallcaps[Typst Academcy] - /// #h(1fr) _Exercise Sheet 3_ - /// ], - /// ) - /// - /// #lorem(19) - /// ``` - #[borrowed] - pub header: Option, - - /// The amount the header is raised into the top margin. - #[resolve] - #[default(Ratio::new(0.3).into())] - pub header_ascent: Rel, - - /// The page's footer. Fills the bottom margin of each page. - /// - /// For just a page number, the `numbering` property, typically suffices. If - /// you want to create a custom footer, but still display the page number, - /// you can directly access the [page counter]($counter). - /// - /// ```example - /// #set par(justify: true) - /// #set page( - /// height: 100pt, - /// margin: 20pt, - /// footer: [ - /// #set align(right) - /// #set text(8pt) - /// #counter(page).display( - /// "1 of I", - /// both: true, - /// ) - /// ] - /// ) - /// - /// #lorem(48) - /// ``` - #[borrowed] - pub footer: Option, - - /// The amount the footer is lowered into the bottom margin. - #[resolve] - #[default(Ratio::new(0.3).into())] - pub footer_descent: Rel, - - /// Content in the page's background. - /// - /// This content will be placed behind the page's body. It can be - /// used to place a background image or a watermark. - /// - /// ```example - /// #set page(background: rotate(24deg, - /// text(18pt, fill: rgb("FFCBC4"))[ - /// *CONFIDENTIAL* - /// ] - /// )) - /// - /// = Typst's secret plans - /// In the year 2023, we plan to take - /// over the world (of typesetting). - /// ``` - #[borrowed] - pub background: Option, - - /// Content in the page's foreground. - /// - /// This content will overlay the page's body. - /// - /// ```example - /// #set page(foreground: text(24pt)[🥸]) - /// - /// Reviewer 2 has marked our paper - /// "Weak Reject" because they did - /// not understand our approach... - /// ``` - #[borrowed] - pub foreground: Option, - - /// The contents of the page(s). - /// - /// Multiple pages will be created if the content does not fit on a single - /// page. A new page with the page properties prior to the function invocation - /// will be created after the body has been typeset. - #[required] - pub body: Content, - - /// Whether the page should be aligned to an even or odd page. - #[internal] - pub clear_to: Option, -} - -impl PageElem { - /// A document can consist of multiple `PageElem`s, one per run of pages - /// with equal properties (not one per actual output page!). The `number` is - /// the physical page number of the first page of this run. It is mutated - /// while we post-process the pages in this function. This function returns - /// a fragment consisting of multiple frames, one per output page of this - /// page run. - #[tracing::instrument(skip_all)] - pub fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - page_counter: &mut ManualPageCounter, - extend_to: Option, - ) -> SourceResult { - tracing::info!("Page layout"); - - // When one of the lengths is infinite the page fits its content along - // that axis. - let width = self.width(styles).unwrap_or(Abs::inf()); - let height = self.height(styles).unwrap_or(Abs::inf()); - let mut size = Size::new(width, height); - if self.flipped(styles) { - std::mem::swap(&mut size.x, &mut size.y); - } - - let mut min = width.min(height); - if !min.is_finite() { - min = Paper::A4.width(); - } - - // Determine the margins. - let default = Rel::::from((2.5 / 21.0) * min); - let margin = self.margin(styles); - let two_sided = margin.two_sided.unwrap_or(false); - let margin = margin - .sides - .map(|side| side.and_then(Smart::as_custom).unwrap_or(default)) - .resolve(styles) - .relative_to(size); - - // Determine the binding. - let binding = - self.binding(styles) - .unwrap_or_else(|| match TextElem::dir_in(styles) { - Dir::LTR => Binding::Left, - _ => Binding::Right, - }); - - // Realize columns. - let mut child = self.body().clone(); - let columns = self.columns(styles); - if columns.get() > 1 { - child = ColumnsElem::new(child).with_count(columns).pack(); - } - - let area = size - margin.sum_by_axis(); - let mut regions = Regions::repeat(area, area.map(Abs::is_finite)); - regions.root = true; - - // Layout the child. - let mut frames = child.layout(vt, styles, regions)?.into_frames(); - - // Align the child to the pagebreak's parity. - // Check for page count after adding the pending frames - if extend_to - .is_some_and(|p| !p.matches(page_counter.physical().get() + frames.len())) - { - // Insert empty page after the current pages. - let size = area.map(Abs::is_finite).select(area, Size::zero()); - frames.push(Frame::hard(size)); - } - - let fill = self.fill(styles); - let foreground = Cow::Borrowed(self.foreground(styles)); - let background = Cow::Borrowed(self.background(styles)); - let header_ascent = self.header_ascent(styles); - let footer_descent = self.footer_descent(styles); - let numbering = self.numbering(styles); - let numbering_meta = Meta::PageNumbering(numbering.clone().into_value()); - let number_align = self.number_align(styles); - let mut header = Cow::Borrowed(self.header(styles)); - let mut footer = Cow::Borrowed(self.footer(styles)); - - // Construct the numbering (for header or footer). - let numbering_marginal = Cow::Owned(numbering.as_ref().map(|numbering| { - let both = match numbering { - Numbering::Pattern(pattern) => pattern.pieces() >= 2, - Numbering::Func(_) => true, - }; - - let mut counter = - Counter::new(CounterKey::Page).display(Some(numbering.clone()), both); - - // We interpret the Y alignment as selecting header or footer - // and then ignore it for aligning the actual number. - if let Some(x) = number_align.x() { - counter = counter.aligned(x.into()); - } - - counter - })); - - if matches!(number_align.y(), Some(VAlign::Top)) { - header = if header.is_some() { header } else { numbering_marginal }; - } else { - footer = if footer.is_some() { footer } else { numbering_marginal }; - } - - // Post-process pages. - for frame in frames.iter_mut() { - tracing::info!("Layouting page #{}", page_counter.physical()); - - // The padded width of the page's content without margins. - let pw = frame.width(); - - // If two sided, left becomes inside and right becomes outside. - // Thus, for left-bound pages, we want to swap on even pages and - // for right-bound pages, we want to swap on odd pages. - let mut margin = margin; - if two_sided && binding.swap(page_counter.physical()) { - std::mem::swap(&mut margin.left, &mut margin.right); - } - - // Realize margins. - frame.set_size(frame.size() + margin.sum_by_axis()); - frame.translate(Point::new(margin.left, margin.top)); - frame.push_positionless_meta(numbering_meta.clone()); - - // The page size with margins. - let size = frame.size(); - - // Realize overlays. - for (name, marginal) in [ - ("header", &header), - ("footer", &footer), - ("background", &background), - ("foreground", &foreground), - ] { - tracing::info!("Layouting {name}"); - - let Some(content) = &**marginal else { continue }; - - let (pos, area, align); - if ptr::eq(marginal, &header) { - let ascent = header_ascent.relative_to(margin.top); - pos = Point::with_x(margin.left); - area = Size::new(pw, margin.top - ascent); - align = Align::BOTTOM; - } else if ptr::eq(marginal, &footer) { - let descent = footer_descent.relative_to(margin.bottom); - pos = Point::new(margin.left, size.y - margin.bottom + descent); - area = Size::new(pw, margin.bottom - descent); - align = Align::TOP; - } else { - pos = Point::zero(); - area = size; - align = HAlign::Center + VAlign::Horizon; - }; - - let pod = Regions::one(area, Axes::splat(true)); - let sub = content - .clone() - .styled(AlignElem::set_alignment(align)) - .layout(vt, styles, pod)? - .into_frame(); - - if ptr::eq(marginal, &header) || ptr::eq(marginal, &background) { - frame.prepend_frame(pos, sub); - } else { - frame.push_frame(pos, sub); - } - } - - if let Some(fill) = fill { - frame.fill(fill.clone()); - } - - page_counter.visit(vt, frame)?; - - // Add a PDF page label if there is a numbering. - if let Some(num) = numbering { - if let Some(page_label) = num.apply_pdf(page_counter.logical()) { - frame.push_positionless_meta(Meta::PdfPageLabel(page_label)); - } - } - - page_counter.step(); - } - - Ok(Fragment::frames(frames)) - } -} - -/// Specification of the page's margins. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Margin { - /// The margins for each side. - pub sides: Sides>>>, - /// Whether to swap `left` and `right` to make them `inside` and `outside` - /// (when to swap depends on the binding). - pub two_sided: Option, -} - -impl Margin { - /// Create an instance with four equal components. - pub fn splat(value: Option>>) -> Self { - Self { sides: Sides::splat(value), two_sided: None } - } -} - -impl Fold for Margin { - type Output = Margin; - - fn fold(self, outer: Self::Output) -> Self::Output { - let sides = - self.sides - .zip(outer.sides) - .map(|(inner, outer)| match (inner, outer) { - (Some(value), Some(outer)) => Some(value.fold(outer)), - _ => inner.or(outer), - }); - let two_sided = self.two_sided.or(outer.two_sided); - Margin { sides, two_sided } - } -} - -cast! { - Margin, - self => { - let mut dict = Dict::new(); - let mut handle = |key: &str, component: Value| { - let value = component.into_value(); - if value != Value::None { - dict.insert(key.into(), value); - } - }; - - handle("top", self.sides.top.into_value()); - handle("bottom", self.sides.bottom.into_value()); - if self.two_sided.unwrap_or(false) { - handle("inside", self.sides.left.into_value()); - handle("outside", self.sides.right.into_value()); - } else { - handle("left", self.sides.left.into_value()); - handle("right", self.sides.right.into_value()); - } - - Value::Dict(dict) - }, - _: AutoValue => Self::splat(Some(Smart::Auto)), - v: Rel => Self::splat(Some(Smart::Custom(v))), - mut dict: Dict => { - let mut take = |key| dict.take(key).ok().map(Value::cast).transpose(); - - let rest = take("rest")?; - let x = take("x")?.or(rest); - let y = take("y")?.or(rest); - let top = take("top")?.or(y); - let bottom = take("bottom")?.or(y); - let outside = take("outside")?; - let inside = take("inside")?; - let left = take("left")?; - let right = take("right")?; - - let implicitly_two_sided = outside.is_some() || inside.is_some(); - let implicitly_not_two_sided = left.is_some() || right.is_some(); - if implicitly_two_sided && implicitly_not_two_sided { - bail!("`inside` and `outside` are mutually exclusive with `left` and `right`"); - } - - // - If 'implicitly_two_sided' is false here, then - // 'implicitly_not_two_sided' will be guaranteed to be true - // due to the previous two 'if' conditions. - // - If both are false, this means that this margin change does not - // affect lateral margins, and thus shouldn't make a difference on - // the 'two_sided' attribute of this margin. - let two_sided = (implicitly_two_sided || implicitly_not_two_sided) - .then_some(implicitly_two_sided); - - dict.finish(&[ - "left", "top", "right", "bottom", "outside", "inside", "x", "y", "rest", - ])?; - - Margin { - sides: Sides { - left: inside.or(left).or(x), - top, - right: outside.or(right).or(x), - bottom, - }, - two_sided, - } - } -} - -/// Specification of the page's binding. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Binding { - /// Bound on the left, as customary in LTR languages. - Left, - /// Bound on the right, as customary in RTL languages. - Right, -} - -impl Binding { - /// Whether to swap left and right margin for the page with this number. - fn swap(self, number: NonZeroUsize) -> bool { - match self { - // Left-bound must swap on even pages - // (because it is correct on the first page). - Self::Left => number.get() % 2 == 0, - // Right-bound must swap on odd pages - // (because it is wrong on the first page). - Self::Right => number.get() % 2 == 1, - } - } -} - -cast! { - Binding, - self => match self { - Self::Left => Align::LEFT.into_value(), - Self::Right => Align::RIGHT.into_value(), - }, - v: Align => match v { - Align::LEFT => Self::Left, - Align::RIGHT => Self::Right, - _ => bail!("must be `left` or `right`"), - }, -} - -/// A header, footer, foreground or background definition. -#[derive(Debug, Clone, Hash)] -pub enum Marginal { - /// Bare content. - Content(Content), - /// A closure mapping from a page number to content. - Func(Func), -} - -impl Marginal { - /// Resolve the marginal based on the page number. - pub fn resolve(&self, vt: &mut Vt, page: usize) -> SourceResult> { - Ok(match self { - Self::Content(content) => Cow::Borrowed(content), - Self::Func(func) => Cow::Owned(func.call_vt(vt, [page])?.display()), - }) - } -} - -cast! { - Marginal, - self => match self { - Self::Content(v) => v.into_value(), - Self::Func(v) => v.into_value(), - }, - v: Content => Self::Content(v), - v: Func => Self::Func(v), -} - -/// A manual page break. -/// -/// Must not be used inside any containers. -/// -/// # Example -/// ```example -/// The next page contains -/// more details on compound theory. -/// #pagebreak() -/// -/// == Compound Theory -/// In 1984, the first ... -/// ``` -#[elem(title = "Page Break")] -pub struct PagebreakElem { - /// If `{true}`, the page break is skipped if the current page is already - /// empty. - #[default(false)] - pub weak: bool, - - /// If given, ensures that the next page will be an even/odd page, with an - /// empty page in between if necessary. - /// - /// ```example - /// #set page(height: 30pt) - /// - /// First. - /// #pagebreak(to: "odd") - /// Third. - /// ``` - pub to: Option, -} - -/// Whether something should be even or odd. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] -pub enum Parity { - /// Next page will be an even page. - Even, - /// Next page will be an odd page. - Odd, -} - -impl Parity { - /// Whether the given number matches the parity. - fn matches(self, number: usize) -> bool { - match self { - Self::Even => number % 2 == 0, - Self::Odd => number % 2 == 1, - } - } -} - -/// Specification of a paper. -#[derive(Debug, Copy, Clone, Hash)] -pub struct Paper { - /// The name of the paper. - name: &'static str, - /// The width of the paper in millimeters. - width: Scalar, - /// The height of the paper in millimeters. - height: Scalar, -} - -impl Paper { - /// The width of the paper. - pub fn width(self) -> Abs { - Abs::mm(self.width.get()) - } - - /// The height of the paper. - pub fn height(self) -> Abs { - Abs::mm(self.height.get()) - } -} - -/// Defines paper constants and a paper parsing implementation. -macro_rules! papers { - ($(($var:ident: $width:expr, $height: expr, $name:literal))*) => { - /// Predefined papers. - /// - /// Each paper is parsable from its name in kebab-case. - impl Paper { - $(pub const $var: Self = Self { - name: $name, - width: Scalar::new($width), - height: Scalar::new($height), - };)* - } - - impl FromStr for Paper { - type Err = &'static str; - - fn from_str(name: &str) -> Result { - match name.to_lowercase().as_str() { - $($name => Ok(Self::$var),)* - _ => Err("unknown paper size"), - } - } - } - - cast! { - Paper, - self => self.name.into_value(), - $( - /// Produces a paper of the respective size. - $name => Self::$var, - )* - } - }; -} - -// All paper sizes in mm. -// -// Resources: -// - https://papersizes.io/ -// - https://en.wikipedia.org/wiki/Paper_size -// - https://www.theedkins.co.uk/jo/units/oldunits/print.htm -// - https://vintagepaper.co/blogs/news/traditional-paper-sizes -papers! { - // ---------------------------------------------------------------------- // - // ISO 216 A Series - (A0: 841.0, 1189.0, "a0") - (A1: 594.0, 841.0, "a1") - (A2: 420.0, 594.0, "a2") - (A3: 297.0, 420.0, "a3") - (A4: 210.0, 297.0, "a4") - (A5: 148.0, 210.0, "a5") - (A6: 105.0, 148.0, "a6") - (A7: 74.0, 105.0, "a7") - (A8: 52.0, 74.0, "a8") - (A9: 37.0, 52.0, "a9") - (A10: 26.0, 37.0, "a10") - (A11: 18.0, 26.0, "a11") - - // ISO 216 B Series - (ISO_B1: 707.0, 1000.0, "iso-b1") - (ISO_B2: 500.0, 707.0, "iso-b2") - (ISO_B3: 353.0, 500.0, "iso-b3") - (ISO_B4: 250.0, 353.0, "iso-b4") - (ISO_B5: 176.0, 250.0, "iso-b5") - (ISO_B6: 125.0, 176.0, "iso-b6") - (ISO_B7: 88.0, 125.0, "iso-b7") - (ISO_B8: 62.0, 88.0, "iso-b8") - - // ISO 216 C Series - (ISO_C3: 324.0, 458.0, "iso-c3") - (ISO_C4: 229.0, 324.0, "iso-c4") - (ISO_C5: 162.0, 229.0, "iso-c5") - (ISO_C6: 114.0, 162.0, "iso-c6") - (ISO_C7: 81.0, 114.0, "iso-c7") - (ISO_C8: 57.0, 81.0, "iso-c8") - - // DIN D Series (extension to ISO) - (DIN_D3: 272.0, 385.0, "din-d3") - (DIN_D4: 192.0, 272.0, "din-d4") - (DIN_D5: 136.0, 192.0, "din-d5") - (DIN_D6: 96.0, 136.0, "din-d6") - (DIN_D7: 68.0, 96.0, "din-d7") - (DIN_D8: 48.0, 68.0, "din-d8") - - // SIS (used in academia) - (SIS_G5: 169.0, 239.0, "sis-g5") - (SIS_E5: 115.0, 220.0, "sis-e5") - - // ANSI Extensions - (ANSI_A: 216.0, 279.0, "ansi-a") - (ANSI_B: 279.0, 432.0, "ansi-b") - (ANSI_C: 432.0, 559.0, "ansi-c") - (ANSI_D: 559.0, 864.0, "ansi-d") - (ANSI_E: 864.0, 1118.0, "ansi-e") - - // ANSI Architectural Paper - (ARCH_A: 229.0, 305.0, "arch-a") - (ARCH_B: 305.0, 457.0, "arch-b") - (ARCH_C: 457.0, 610.0, "arch-c") - (ARCH_D: 610.0, 914.0, "arch-d") - (ARCH_E1: 762.0, 1067.0, "arch-e1") - (ARCH_E: 914.0, 1219.0, "arch-e") - - // JIS B Series - (JIS_B0: 1030.0, 1456.0, "jis-b0") - (JIS_B1: 728.0, 1030.0, "jis-b1") - (JIS_B2: 515.0, 728.0, "jis-b2") - (JIS_B3: 364.0, 515.0, "jis-b3") - (JIS_B4: 257.0, 364.0, "jis-b4") - (JIS_B5: 182.0, 257.0, "jis-b5") - (JIS_B6: 128.0, 182.0, "jis-b6") - (JIS_B7: 91.0, 128.0, "jis-b7") - (JIS_B8: 64.0, 91.0, "jis-b8") - (JIS_B9: 45.0, 64.0, "jis-b9") - (JIS_B10: 32.0, 45.0, "jis-b10") - (JIS_B11: 22.0, 32.0, "jis-b11") - - // SAC D Series - (SAC_D0: 764.0, 1064.0, "sac-d0") - (SAC_D1: 532.0, 760.0, "sac-d1") - (SAC_D2: 380.0, 528.0, "sac-d2") - (SAC_D3: 264.0, 376.0, "sac-d3") - (SAC_D4: 188.0, 260.0, "sac-d4") - (SAC_D5: 130.0, 184.0, "sac-d5") - (SAC_D6: 92.0, 126.0, "sac-d6") - - // ISO 7810 ID - (ISO_ID_1: 85.6, 53.98, "iso-id-1") - (ISO_ID_2: 74.0, 105.0, "iso-id-2") - (ISO_ID_3: 88.0, 125.0, "iso-id-3") - - // ---------------------------------------------------------------------- // - // Asia - (ASIA_F4: 210.0, 330.0, "asia-f4") - - // Japan - (JP_SHIROKU_BAN_4: 264.0, 379.0, "jp-shiroku-ban-4") - (JP_SHIROKU_BAN_5: 189.0, 262.0, "jp-shiroku-ban-5") - (JP_SHIROKU_BAN_6: 127.0, 188.0, "jp-shiroku-ban-6") - (JP_KIKU_4: 227.0, 306.0, "jp-kiku-4") - (JP_KIKU_5: 151.0, 227.0, "jp-kiku-5") - (JP_BUSINESS_CARD: 91.0, 55.0, "jp-business-card") - - // China - (CN_BUSINESS_CARD: 90.0, 54.0, "cn-business-card") - - // Europe - (EU_BUSINESS_CARD: 85.0, 55.0, "eu-business-card") - - // French Traditional (AFNOR) - (FR_TELLIERE: 340.0, 440.0, "fr-tellière") - (FR_COURONNE_ECRITURE: 360.0, 460.0, "fr-couronne-écriture") - (FR_COURONNE_EDITION: 370.0, 470.0, "fr-couronne-édition") - (FR_RAISIN: 500.0, 650.0, "fr-raisin") - (FR_CARRE: 450.0, 560.0, "fr-carré") - (FR_JESUS: 560.0, 760.0, "fr-jésus") - - // United Kingdom Imperial - (UK_BRIEF: 406.4, 342.9, "uk-brief") - (UK_DRAFT: 254.0, 406.4, "uk-draft") - (UK_FOOLSCAP: 203.2, 330.2, "uk-foolscap") - (UK_QUARTO: 203.2, 254.0, "uk-quarto") - (UK_CROWN: 508.0, 381.0, "uk-crown") - (UK_BOOK_A: 111.0, 178.0, "uk-book-a") - (UK_BOOK_B: 129.0, 198.0, "uk-book-b") - - // Unites States - (US_LETTER: 215.9, 279.4, "us-letter") - (US_LEGAL: 215.9, 355.6, "us-legal") - (US_TABLOID: 279.4, 431.8, "us-tabloid") - (US_EXECUTIVE: 84.15, 266.7, "us-executive") - (US_FOOLSCAP_FOLIO: 215.9, 342.9, "us-foolscap-folio") - (US_STATEMENT: 139.7, 215.9, "us-statement") - (US_LEDGER: 431.8, 279.4, "us-ledger") - (US_OFICIO: 215.9, 340.36, "us-oficio") - (US_GOV_LETTER: 203.2, 266.7, "us-gov-letter") - (US_GOV_LEGAL: 215.9, 330.2, "us-gov-legal") - (US_BUSINESS_CARD: 88.9, 50.8, "us-business-card") - (US_DIGEST: 139.7, 215.9, "us-digest") - (US_TRADE: 152.4, 228.6, "us-trade") - - // ---------------------------------------------------------------------- // - // Other - (NEWSPAPER_COMPACT: 280.0, 430.0, "newspaper-compact") - (NEWSPAPER_BERLINER: 315.0, 470.0, "newspaper-berliner") - (NEWSPAPER_BROADSHEET: 381.0, 578.0, "newspaper-broadsheet") - (PRESENTATION_16_9: 297.0, 167.0625, "presentation-16-9") - (PRESENTATION_4_3: 280.0, 210.0, "presentation-4-3") -} diff --git a/crates/typst-library/src/layout/par.rs b/crates/typst-library/src/layout/par.rs deleted file mode 100644 index 2d7c9080..00000000 --- a/crates/typst-library/src/layout/par.rs +++ /dev/null @@ -1,1520 +0,0 @@ -use comemo::Prehashed; -use typst::eval::Tracer; -use typst::model::DelayedErrors; -use unicode_bidi::{BidiInfo, Level as BidiLevel}; -use unicode_script::{Script, UnicodeScript}; - -use crate::layout::{AlignElem, BoxElem, HElem, Sizing, Spacing}; -use crate::math::EquationElem; -use crate::prelude::*; -use crate::text::{ - breakpoints, char_is_cjk_script, is_gb_style, shape, Breakpoint, LinebreakElem, - Quoter, Quotes, ShapedGlyph, ShapedText, SmartquoteElem, SpaceElem, TextElem, - BEGIN_PUNCT_PAT, END_PUNCT_PAT, -}; - -/// Arranges text, spacing and inline-level elements into a paragraph. -/// -/// Although this function is primarily used in set rules to affect paragraph -/// properties, it can also be used to explicitly render its argument onto a -/// paragraph of its own. -/// -/// # Example -/// ```example -/// #show par: set block(spacing: 0.65em) -/// #set par( -/// first-line-indent: 1em, -/// justify: true, -/// ) -/// -/// We proceed by contradiction. -/// Suppose that there exists a set -/// of positive integers $a$, $b$, and -/// $c$ that satisfies the equation -/// $a^n + b^n = c^n$ for some -/// integer value of $n > 2$. -/// -/// Without loss of generality, -/// let $a$ be the smallest of the -/// three integers. Then, we ... -/// ``` -#[elem(title = "Paragraph", Construct)] -pub struct ParElem { - /// The spacing between lines. - #[resolve] - #[ghost] - #[default(Em::new(0.65).into())] - pub leading: Length, - - /// Whether to justify text in its line. - /// - /// Hyphenation will be enabled for justified paragraphs if the - /// [text function's `hyphenate` property]($text.hyphenate) is set to - /// `{auto}` and the current language is known. - /// - /// Note that the current [alignment]($align) still has an effect on the - /// placement of the last line except if it ends with a - /// [justified line break]($linebreak.justify). - #[ghost] - #[default(false)] - pub justify: bool, - - /// How to determine line breaks. - /// - /// When this property is set to `{auto}`, its default value, optimized line - /// breaks will be used for justified paragraphs. Enabling optimized line - /// breaks for ragged paragraphs may also be worthwhile to improve the - /// appearance of the text. - /// - /// ```example - /// #set page(width: 207pt) - /// #set par(linebreaks: "simple") - /// Some texts feature many longer - /// words. Those are often exceedingly - /// challenging to break in a visually - /// pleasing way. - /// - /// #set par(linebreaks: "optimized") - /// Some texts feature many longer - /// words. Those are often exceedingly - /// challenging to break in a visually - /// pleasing way. - /// ``` - #[ghost] - pub linebreaks: Smart, - - /// The indent the first line of a paragraph should have. - /// - /// Only the first line of a consecutive paragraph will be indented (not - /// the first one in a block or on the page). - /// - /// By typographic convention, paragraph breaks are indicated either by some - /// space between paragraphs or by indented first lines. Consider reducing - /// the [paragraph spacing]($block.spacing) to the [`leading`] when - /// using this property (e.g. using - /// `[#show par: set block(spacing: 0.65em)]`). - #[ghost] - pub first_line_indent: Length, - - /// The indent all but the first line of a paragraph should have. - #[ghost] - #[resolve] - pub hanging_indent: Length, - - /// The contents of the paragraph. - #[external] - #[required] - pub body: Content, - - /// The paragraph's children. - #[internal] - #[variadic] - pub children: Vec>, -} - -impl Construct for ParElem { - fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult { - // The paragraph constructor is special: It doesn't create a paragraph - // element. Instead, it just ensures that the passed content lives in a - // separate paragraph and styles it. - let styles = Self::set(vm, args)?; - let body = args.expect::("body")?; - Ok(Content::sequence([ - ParbreakElem::new().pack(), - body.styled_with_map(styles), - ParbreakElem::new().pack(), - ])) - } -} - -impl ParElem { - /// Layout the paragraph into a collection of lines. - #[tracing::instrument(name = "ParElement::layout", skip_all)] - pub fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - consecutive: bool, - region: Size, - expand: bool, - ) -> SourceResult { - #[comemo::memoize] - #[allow(clippy::too_many_arguments)] - fn cached( - par: &ParElem, - world: Tracked, - introspector: Tracked, - locator: Tracked, - delayed: TrackedMut, - tracer: TrackedMut, - styles: StyleChain, - consecutive: bool, - region: Size, - expand: bool, - ) -> SourceResult { - let mut locator = Locator::chained(locator); - let mut vt = Vt { - world, - introspector, - locator: &mut locator, - delayed, - tracer, - }; - let children = par.children(); - - // Collect all text into one string for BiDi analysis. - let (text, segments, spans) = collect(children, &styles, consecutive)?; - - // Perform BiDi analysis and then prepare paragraph layout by building a - // representation on which we can do line breaking without layouting - // each and every line from scratch. - let p = prepare(&mut vt, children, &text, segments, spans, styles, region)?; - - // Break the paragraph into lines. - let lines = linebreak(&vt, &p, region.x - p.hang); - - // Stack the lines into one frame per region. - finalize(&mut vt, &p, &lines, region, expand) - } - - let fragment = cached( - self, - vt.world, - vt.introspector, - vt.locator.track(), - TrackedMut::reborrow_mut(&mut vt.delayed), - TrackedMut::reborrow_mut(&mut vt.tracer), - styles, - consecutive, - region, - expand, - )?; - - vt.locator.visit_frames(&fragment); - Ok(fragment) - } -} - -/// How to determine line breaks in a paragraph. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] -pub enum Linebreaks { - /// Determine the line breaks in a simple first-fit style. - Simple, - /// Optimize the line breaks for the whole paragraph. - /// - /// Typst will try to produce more evenly filled lines of text by - /// considering the whole paragraph when calculating line breaks. - Optimized, -} - -/// A paragraph break. -/// -/// This starts a new paragraph. Especially useful when used within code like -/// [for loops]($scripting/#loops). Multiple consecutive -/// paragraph breaks collapse into a single one. -/// -/// # Example -/// ```example -/// #for i in range(3) { -/// [Blind text #i: ] -/// lorem(5) -/// parbreak() -/// } -/// ``` -/// -/// # Syntax -/// Instead of calling this function, you can insert a blank line into your -/// markup to create a paragraph break. -#[elem(title = "Paragraph Break", Unlabellable)] -pub struct ParbreakElem {} - -impl Unlabellable for ParbreakElem {} - -/// Range of a substring of text. -type Range = std::ops::Range; - -// The characters by which spacing, inline content and pins are replaced in the -// paragraph's full text. -const SPACING_REPLACE: char = ' '; // Space -const OBJ_REPLACE: char = '\u{FFFC}'; // Object Replacement Character - -/// A paragraph representation in which children are already layouted and text -/// is already preshaped. -/// -/// In many cases, we can directly reuse these results when constructing a line. -/// Only when a line break falls onto a text index that is not safe-to-break per -/// rustybuzz, we have to reshape that portion. -pub(crate) struct Preparation<'a> { - /// Bidirectional text embedding levels for the paragraph. - pub bidi: BidiInfo<'a>, - /// Text runs, spacing and layouted elements. - pub items: Vec>, - /// The span mapper. - pub spans: SpanMapper, - /// Whether to hyphenate if it's the same for all children. - pub hyphenate: Option, - /// The text language if it's the same for all children. - pub lang: Option, - /// The paragraph's resolved horizontal alignment. - pub align: FixedAlign, - /// Whether to justify the paragraph. - pub justify: bool, - /// The paragraph's hanging indent. - pub hang: Abs, - /// Whether to add spacing between CJK and Latin characters. - pub cjk_latin_spacing: bool, - /// Whether font fallback is enabled for this paragraph. - pub fallback: bool, - /// The leading of the paragraph. - pub leading: Abs, - /// How to determine line breaks. - pub linebreaks: Smart, - /// The text size. - pub size: Abs, -} - -impl<'a> Preparation<'a> { - /// Find the item that contains the given `text_offset`. - pub fn find(&self, text_offset: usize) -> Option<&Item<'a>> { - let mut cursor = 0; - for item in &self.items { - let end = cursor + item.len(); - if (cursor..end).contains(&text_offset) { - return Some(item); - } - cursor = end; - } - None - } - - /// Return the items that intersect the given `text_range`. - /// - /// Returns the expanded range around the items and the items. - pub fn slice(&self, text_range: Range) -> (Range, &[Item<'a>]) { - let mut cursor = 0; - let mut start = 0; - let mut end = 0; - let mut expanded = text_range.clone(); - - for (i, item) in self.items.iter().enumerate() { - if cursor <= text_range.start { - start = i; - expanded.start = cursor; - } - - let len = item.len(); - if cursor < text_range.end || cursor + len <= text_range.end { - end = i + 1; - expanded.end = cursor + len; - } else { - break; - } - - cursor += len; - } - - (expanded, &self.items[start..end]) - } -} - -/// A segment of one or multiple collapsed children. -#[derive(Debug, Copy, Clone)] -enum Segment<'a> { - /// One or multiple collapsed text or text-equivalent children. Stores how - /// long the segment is (in bytes of the full text string). - Text(usize), - /// Horizontal spacing between other segments. - Spacing(Spacing), - /// A mathematical equation. - Equation(&'a EquationElem), - /// A box with arbitrary content. - Box(&'a BoxElem, bool), - /// Metadata. - Meta, -} - -impl Segment<'_> { - /// The text length of the item. - fn len(&self) -> usize { - match *self { - Self::Text(len) => len, - Self::Spacing(_) => SPACING_REPLACE.len_utf8(), - Self::Box(_, true) => SPACING_REPLACE.len_utf8(), - Self::Equation(_) | Self::Box(_, _) => OBJ_REPLACE.len_utf8(), - Self::Meta => 0, - } - } -} - -/// A prepared item in a paragraph layout. -#[derive(Debug)] -pub(crate) enum Item<'a> { - /// A shaped text run with consistent style and direction. - Text(ShapedText<'a>), - /// Absolute spacing between other items. - Absolute(Abs), - /// Fractional spacing between other items. - Fractional(Fr, Option<(&'a BoxElem, StyleChain<'a>)>), - /// Layouted inline-level content. - Frame(Frame), - /// Metadata. - Meta(Frame), -} - -impl<'a> Item<'a> { - /// If this a text item, return it. - pub fn text(&self) -> Option<&ShapedText<'a>> { - match self { - Self::Text(shaped) => Some(shaped), - _ => None, - } - } - - pub fn text_mut(&mut self) -> Option<&mut ShapedText<'a>> { - match self { - Self::Text(shaped) => Some(shaped), - _ => None, - } - } - - /// The text length of the item. - pub fn len(&self) -> usize { - match self { - Self::Text(shaped) => shaped.text.len(), - Self::Absolute(_) | Self::Fractional(_, _) => SPACING_REPLACE.len_utf8(), - Self::Frame(_) => OBJ_REPLACE.len_utf8(), - Self::Meta(_) => 0, - } - } - - /// The natural layouted width of the item. - pub fn width(&self) -> Abs { - match self { - Self::Text(shaped) => shaped.width, - Self::Absolute(v) => *v, - Self::Frame(frame) => frame.width(), - Self::Fractional(_, _) | Self::Meta(_) => Abs::zero(), - } - } -} - -/// Maps byte offsets back to spans. -#[derive(Default)] -pub struct SpanMapper(Vec<(usize, Span)>); - -impl SpanMapper { - /// Create a new span mapper. - pub fn new() -> Self { - Self::default() - } - - /// Push a span for a segment with the given length. - pub fn push(&mut self, len: usize, span: Span) { - self.0.push((len, span)); - } - - /// Determine the span at the given byte offset. - /// - /// May return a detached span. - pub fn span_at(&self, offset: usize) -> (Span, u16) { - let mut cursor = 0; - for &(len, span) in &self.0 { - if (cursor..=cursor + len).contains(&offset) { - return (span, u16::try_from(offset - cursor).unwrap_or(0)); - } - cursor += len; - } - (Span::detached(), 0) - } -} - -/// A layouted line, consisting of a sequence of layouted paragraph items that -/// are mostly borrowed from the preparation phase. This type enables you to -/// measure the size of a line in a range before committing to building the -/// line's frame. -/// -/// At most two paragraph items must be created individually for this line: The -/// first and last one since they may be broken apart by the start or end of the -/// line, respectively. But even those can partially reuse previous results when -/// the break index is safe-to-break per rustybuzz. -struct Line<'a> { - /// Bidi information about the paragraph. - bidi: &'a BidiInfo<'a>, - /// The trimmed range the line spans in the paragraph. - trimmed: Range, - /// The untrimmed end where the line ends. - end: usize, - /// A reshaped text item if the line sliced up a text item at the start. - first: Option>, - /// Inner items which don't need to be reprocessed. - inner: &'a [Item<'a>], - /// A reshaped text item if the line sliced up a text item at the end. If - /// there is only one text item, this takes precedence over `first`. - last: Option>, - /// The width of the line. - width: Abs, - /// Whether the line should be justified. - justify: bool, - /// Whether the line ends with a hyphen or dash, either naturally or through - /// hyphenation. - dash: bool, -} - -impl<'a> Line<'a> { - /// Iterate over the line's items. - fn items(&self) -> impl Iterator> { - self.first.iter().chain(self.inner).chain(&self.last) - } - - /// Return items that intersect the given `text_range`. - fn slice(&self, text_range: Range) -> impl Iterator> { - let mut cursor = self.trimmed.start; - let mut start = 0; - let mut end = 0; - - for (i, item) in self.items().enumerate() { - if cursor <= text_range.start { - start = i; - } - - let len = item.len(); - if cursor < text_range.end || cursor + len <= text_range.end { - end = i + 1; - } else { - break; - } - - cursor += len; - } - - self.items().skip(start).take(end - start) - } - - /// How many glyphs are in the text where we can insert additional - /// space when encountering underfull lines. - fn justifiables(&self) -> usize { - let mut count = 0; - for shaped in self.items().filter_map(Item::text) { - count += shaped.justifiables(); - } - // CJK character at line end should not be adjusted. - if self - .items() - .last() - .and_then(Item::text) - .map(|s| s.cjk_justifiable_at_last()) - .unwrap_or(false) - { - count -= 1; - } - - count - } - - /// How much can the line stretch - fn stretchability(&self) -> Abs { - self.items().filter_map(Item::text).map(|s| s.stretchability()).sum() - } - - /// How much can the line shrink - fn shrinkability(&self) -> Abs { - self.items().filter_map(Item::text).map(|s| s.shrinkability()).sum() - } - - /// The sum of fractions in the line. - fn fr(&self) -> Fr { - self.items() - .filter_map(|item| match item { - Item::Fractional(fr, _) => Some(*fr), - _ => None, - }) - .sum() - } -} - -/// Collect all text of the paragraph into one string. This also performs -/// string-level preprocessing like case transformations. -#[allow(clippy::type_complexity)] -fn collect<'a>( - children: &'a [Prehashed], - styles: &'a StyleChain<'a>, - consecutive: bool, -) -> SourceResult<(String, Vec<(Segment<'a>, StyleChain<'a>)>, SpanMapper)> { - let mut full = String::new(); - let mut quoter = Quoter::new(); - let mut segments = Vec::with_capacity(2 + children.len()); - let mut spans = SpanMapper::new(); - let mut iter = children.iter().map(|c| &**c).peekable(); - - let first_line_indent = ParElem::first_line_indent_in(*styles); - if !first_line_indent.is_zero() - && consecutive - && AlignElem::alignment_in(*styles).resolve(*styles).x - == TextElem::dir_in(*styles).start().into() - { - full.push(SPACING_REPLACE); - segments.push((Segment::Spacing(first_line_indent.into()), *styles)); - } - - let hang = ParElem::hanging_indent_in(*styles); - if !hang.is_zero() { - full.push(SPACING_REPLACE); - segments.push((Segment::Spacing((-hang).into()), *styles)); - } - - while let Some(mut child) = iter.next() { - let outer = styles; - let mut styles = *styles; - if let Some((elem, local)) = child.to_styled() { - child = elem; - styles = outer.chain(local); - } - - let segment = if child.is::() { - full.push(' '); - Segment::Text(1) - } else if let Some(elem) = child.to::() { - let prev = full.len(); - if let Some(case) = TextElem::case_in(styles) { - full.push_str(&case.apply(elem.text())); - } else { - full.push_str(elem.text()); - } - Segment::Text(full.len() - prev) - } else if let Some(elem) = child.to::() { - if elem.amount().is_zero() { - continue; - } - - full.push(SPACING_REPLACE); - Segment::Spacing(*elem.amount()) - } else if let Some(elem) = child.to::() { - let c = if elem.justify(styles) { '\u{2028}' } else { '\n' }; - full.push(c); - Segment::Text(c.len_utf8()) - } else if let Some(elem) = child.to::() { - let prev = full.len(); - if SmartquoteElem::enabled_in(styles) { - let quotes = SmartquoteElem::quotes_in(styles); - let lang = TextElem::lang_in(styles); - let region = TextElem::region_in(styles); - let quotes = Quotes::new( - quotes, - lang, - region, - SmartquoteElem::alternative_in(styles), - ); - let peeked = iter.peek().and_then(|child| { - let child = if let Some((child, _)) = child.to_styled() { - child - } else { - child - }; - if let Some(elem) = child.to::() { - elem.text().chars().next() - } else if child.is::() { - Some('"') - } else if child.is::() - || child.is::() - || child.is::() - { - Some(SPACING_REPLACE) - } else { - Some(OBJ_REPLACE) - } - }); - - full.push_str(quoter.quote("es, elem.double(styles), peeked)); - } else { - full.push(if elem.double(styles) { '"' } else { '\'' }); - } - Segment::Text(full.len() - prev) - } else if let Some(elem) = child.to::() { - full.push(OBJ_REPLACE); - Segment::Equation(elem) - } else if let Some(elem) = child.to::() { - let frac = elem.width(styles).is_fractional(); - full.push(if frac { SPACING_REPLACE } else { OBJ_REPLACE }); - Segment::Box(elem, frac) - } else if child.is::() { - Segment::Meta - } else { - bail!(child.span(), "unexpected paragraph child"); - }; - - if let Some(last) = full.chars().last() { - quoter.last(last, child.is::()); - } - - spans.push(segment.len(), child.span()); - - if let (Some((Segment::Text(last_len), last_styles)), Segment::Text(len)) = - (segments.last_mut(), segment) - { - if *last_styles == styles { - *last_len += len; - continue; - } - } - - segments.push((segment, styles)); - } - - Ok((full, segments, spans)) -} - -/// Prepare paragraph layout by shaping the whole paragraph and layouting all -/// contained inline-level content. -fn prepare<'a>( - vt: &mut Vt, - children: &'a [Prehashed], - text: &'a str, - segments: Vec<(Segment<'a>, StyleChain<'a>)>, - spans: SpanMapper, - styles: StyleChain<'a>, - region: Size, -) -> SourceResult> { - let dir = TextElem::dir_in(styles); - let bidi = BidiInfo::new( - text, - match dir { - Dir::LTR => Some(BidiLevel::ltr()), - Dir::RTL => Some(BidiLevel::rtl()), - _ => None, - }, - ); - - let mut cursor = 0; - let mut items = Vec::with_capacity(segments.len()); - - // Shape / layout the children and collect them into items. - for (segment, styles) in segments { - let end = cursor + segment.len(); - match segment { - Segment::Text(_) => { - shape_range(&mut items, vt, &bidi, cursor..end, &spans, styles); - } - Segment::Spacing(spacing) => match spacing { - Spacing::Rel(v) => { - let resolved = v.resolve(styles).relative_to(region.x); - items.push(Item::Absolute(resolved)); - } - Spacing::Fr(v) => { - items.push(Item::Fractional(v, None)); - } - }, - Segment::Equation(equation) => { - let pod = Regions::one(region, Axes::splat(false)); - let mut frame = equation.layout(vt, styles, pod)?.into_frame(); - frame.translate(Point::with_y(TextElem::baseline_in(styles))); - items.push(Item::Frame(frame)); - } - Segment::Box(elem, _) => { - if let Sizing::Fr(v) = elem.width(styles) { - items.push(Item::Fractional(v, Some((elem, styles)))); - } else { - let pod = Regions::one(region, Axes::splat(false)); - let mut frame = elem.layout(vt, styles, pod)?.into_frame(); - frame.translate(Point::with_y(TextElem::baseline_in(styles))); - items.push(Item::Frame(frame)); - } - } - Segment::Meta => { - let mut frame = Frame::soft(Size::zero()); - frame.meta(styles, true); - items.push(Item::Meta(frame)); - } - } - - cursor = end; - } - - let cjk_latin_spacing = TextElem::cjk_latin_spacing_in(styles).is_auto(); - if cjk_latin_spacing { - add_cjk_latin_spacing(&mut items); - } - - Ok(Preparation { - bidi, - items, - spans, - hyphenate: shared_get(styles, children, TextElem::hyphenate_in), - lang: shared_get(styles, children, TextElem::lang_in), - align: AlignElem::alignment_in(styles).resolve(styles).x, - justify: ParElem::justify_in(styles), - hang: ParElem::hanging_indent_in(styles), - cjk_latin_spacing, - fallback: TextElem::fallback_in(styles), - leading: ParElem::leading_in(styles), - linebreaks: ParElem::linebreaks_in(styles), - size: TextElem::size_in(styles), - }) -} - -/// Add some spacing between Han characters and western characters. -/// See Requirements for Chinese Text Layout, Section 3.2.2 Mixed Text Composition in Horizontal -/// Written Mode -fn add_cjk_latin_spacing(items: &mut [Item]) { - let mut items = items.iter_mut().filter(|x| !matches!(x, Item::Meta(_))).peekable(); - let mut prev: Option<&ShapedGlyph> = None; - while let Some(item) = items.next() { - let Some(text) = item.text_mut() else { - prev = None; - continue; - }; - - // Since we only call this function in [`prepare`], we can assume - // that the Cow is owned, and `to_mut` can be called without overhead. - debug_assert!(matches!(text.glyphs, std::borrow::Cow::Owned(_))); - let mut glyphs = text.glyphs.to_mut().iter_mut().peekable(); - - while let Some(glyph) = glyphs.next() { - let next = glyphs.peek().map(|n| n as _).or_else(|| { - items - .peek() - .and_then(|i| i.text()) - .and_then(|shaped| shaped.glyphs.first()) - }); - - // Case 1: CJK followed by a Latin character - if glyph.is_cjk_script() && next.map_or(false, |g| g.is_letter_or_number()) { - // The spacing is default to 1/4 em, and can be shrunk to 1/8 em. - glyph.x_advance += Em::new(0.25); - glyph.adjustability.shrinkability.1 += Em::new(0.125); - text.width += Em::new(0.25).at(text.size); - } - - // Case 2: Latin followed by a CJK character - if glyph.is_cjk_script() && prev.map_or(false, |g| g.is_letter_or_number()) { - glyph.x_advance += Em::new(0.25); - glyph.x_offset += Em::new(0.25); - glyph.adjustability.shrinkability.0 += Em::new(0.125); - text.width += Em::new(0.25).at(text.size); - } - - prev = Some(glyph); - } - } -} - -/// Group a range of text by BiDi level and script, shape the runs and generate -/// items for them. -fn shape_range<'a>( - items: &mut Vec>, - vt: &Vt, - bidi: &BidiInfo<'a>, - range: Range, - spans: &SpanMapper, - styles: StyleChain<'a>, -) { - let script = TextElem::script_in(styles); - let lang = TextElem::lang_in(styles); - let region = TextElem::region_in(styles); - let mut process = |range: Range, level: BidiLevel| { - let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL }; - let shaped = - shape(vt, range.start, &bidi.text[range], spans, styles, dir, lang, region); - items.push(Item::Text(shaped)); - }; - - let mut prev_level = BidiLevel::ltr(); - let mut prev_script = Script::Unknown; - let mut cursor = range.start; - - // Group by embedding level and script. If the text's script is explicitly - // set (rather than inferred from the glyphs), we keep the script at an - // unchanging `Script::Unknown` so that only level changes cause breaks. - for i in range.clone() { - if !bidi.text.is_char_boundary(i) { - continue; - } - - let level = bidi.levels[i]; - let curr_script = match script { - Smart::Auto => { - bidi.text[i..].chars().next().map_or(Script::Unknown, |c| c.script()) - } - Smart::Custom(_) => Script::Unknown, - }; - - if level != prev_level || !is_compatible(curr_script, prev_script) { - if cursor < i { - process(cursor..i, prev_level); - } - cursor = i; - prev_level = level; - prev_script = curr_script; - } else if is_generic_script(prev_script) { - prev_script = curr_script; - } - } - - process(cursor..range.end, prev_level); -} - -/// Whether this is not a specific script. -fn is_generic_script(script: Script) -> bool { - matches!(script, Script::Unknown | Script::Common | Script::Inherited) -} - -/// Whether these script can be part of the same shape run. -fn is_compatible(a: Script, b: Script) -> bool { - is_generic_script(a) || is_generic_script(b) || a == b -} - -/// Get a style property, but only if it is the same for all children of the -/// paragraph. -fn shared_get( - styles: StyleChain<'_>, - children: &[Prehashed], - getter: fn(StyleChain) -> T, -) -> Option { - let value = getter(styles); - children - .iter() - .filter_map(|child| child.to_styled()) - .all(|(_, local)| getter(styles.chain(local)) == value) - .then_some(value) -} - -/// Find suitable linebreaks. -fn linebreak<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec> { - let linebreaks = p.linebreaks.unwrap_or_else(|| { - if p.justify { - Linebreaks::Optimized - } else { - Linebreaks::Simple - } - }); - - match linebreaks { - Linebreaks::Simple => linebreak_simple(vt, p, width), - Linebreaks::Optimized => linebreak_optimized(vt, p, width), - } -} - -/// Perform line breaking in simple first-fit style. This means that we build -/// lines greedily, always taking the longest possible line. This may lead to -/// very unbalanced line, but is fast and simple. -fn linebreak_simple<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec> { - let mut lines = Vec::with_capacity(16); - let mut start = 0; - let mut last = None; - - breakpoints(p, |end, breakpoint| { - // Compute the line and its size. - let mut attempt = line(vt, p, start..end, breakpoint); - - // If the line doesn't fit anymore, we push the last fitting attempt - // into the stack and rebuild the line from the attempt's end. The - // resulting line cannot be broken up further. - if !width.fits(attempt.width) { - if let Some((last_attempt, last_end)) = last.take() { - lines.push(last_attempt); - start = last_end; - attempt = line(vt, p, start..end, breakpoint); - } - } - - // Finish the current line if there is a mandatory line break (i.e. - // due to "\n") or if the line doesn't fit horizontally already - // since then no shorter line will be possible. - if breakpoint == Breakpoint::Mandatory || !width.fits(attempt.width) { - lines.push(attempt); - start = end; - last = None; - } else { - last = Some((attempt, end)); - } - }); - - if let Some((line, _)) = last { - lines.push(line); - } - - lines -} - -/// Perform line breaking in optimized Knuth-Plass style. Here, we use more -/// context to determine the line breaks than in the simple first-fit style. For -/// example, we might choose to cut a line short even though there is still a -/// bit of space to improve the fit of one of the following lines. The -/// Knuth-Plass algorithm is based on the idea of "cost". A line which has a -/// very tight or very loose fit has a higher cost than one that is just right. -/// Ending a line with a hyphen incurs extra cost and endings two successive -/// lines with hyphens even more. -/// -/// To find the layout with the minimal total cost the algorithm uses dynamic -/// programming: For each possible breakpoint it determines the optimal -/// paragraph layout _up to that point_. It walks over all possible start points -/// for a line ending at that point and finds the one for which the cost of the -/// line plus the cost of the optimal paragraph up to the start point (already -/// computed and stored in dynamic programming table) is minimal. The final -/// result is simply the layout determined for the last breakpoint at the end of -/// text. -fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec> { - /// The cost of a line or paragraph layout. - type Cost = f64; - - /// An entry in the dynamic programming table. - struct Entry<'a> { - pred: usize, - total: Cost, - line: Line<'a>, - } - - // Cost parameters. - const HYPH_COST: Cost = 0.5; - const RUNT_COST: Cost = 0.5; - const CONSECUTIVE_DASH_COST: Cost = 0.3; - const MAX_COST: Cost = 1_000_000.0; - const MIN_RATIO: f64 = -1.0; - - // Dynamic programming table. - let mut active = 0; - let mut table = vec![Entry { - pred: 0, - total: 0.0, - line: line(vt, p, 0..0, Breakpoint::Mandatory), - }]; - - let em = p.size; - let mut lines = Vec::with_capacity(16); - breakpoints(p, |end, breakpoint| { - let k = table.len(); - let eof = end == p.bidi.text.len(); - let mut best: Option = None; - - // Find the optimal predecessor. - for (i, pred) in table.iter().enumerate().skip(active) { - // Layout the line. - let start = pred.line.end; - - let attempt = line(vt, p, start..end, breakpoint); - - // Determine how much the line's spaces would need to be stretched - // to make it the desired width. - let delta = width - attempt.width; - // Determine how much stretch are permitted. - let adjust = if delta >= Abs::zero() { - attempt.stretchability() - } else { - attempt.shrinkability() - }; - // Ideally, the ratio should between -1.0 and 1.0, but sometimes a value above 1.0 - // is possible, in which case the line is underfull. - let mut ratio = delta / adjust; - if ratio.is_nan() { - // The line is not stretchable, but it just fits. - // This often happens with monospace fonts and CJK texts. - ratio = 0.0; - } - if ratio > 1.0 { - // We should stretch the line above its stretchability. Now - // calculate the extra amount. Also, don't divide by zero. - let extra_stretch = - (delta - adjust) / attempt.justifiables().max(1) as f64; - // Normalize the amount by half Em size. - ratio = 1.0 + extra_stretch / (em / 2.0); - } - - // Determine the cost of the line. - let min_ratio = if p.justify { MIN_RATIO } else { 0.0 }; - let mut cost = if ratio < min_ratio { - // The line is overfull. This is the case if - // - justification is on, but we'd need to shrink too much - // - justification is off and the line just doesn't fit - // - // If this is the earliest breakpoint in the active set - // (active == i), remove it from the active set. If there is an - // earlier one (active < i), then the logically shorter line was - // in fact longer (can happen with negative spacing) and we - // can't trim the active set just yet. - if active == i { - active += 1; - } - MAX_COST - } else if breakpoint == Breakpoint::Mandatory || eof { - // This is a mandatory break and the line is not overfull, so - // all breakpoints before this one become inactive since no line - // can span above the mandatory break. - active = k; - // If ratio > 0, we need to stretch the line only when justify is needed. - // If ratio < 0, we always need to shrink the line. - if (ratio > 0.0 && attempt.justify) || ratio < 0.0 { - ratio.powi(3).abs() - } else { - 0.0 - } - } else { - // Normal line with cost of |ratio^3|. - ratio.powi(3).abs() - }; - - // Penalize runts. - if k == i + 1 && eof { - cost += RUNT_COST; - } - - // Penalize hyphens. - if breakpoint == Breakpoint::Hyphen { - cost += HYPH_COST; - } - - // In Knuth paper, cost = (1 + 100|r|^3 + p)^2 + a, - // where r is the ratio, p=50 is the penalty, and a=3000 is consecutive the penalty. - // We divide the whole formula by 10, resulting (0.01 + |r|^3 + p)^2 + a, - // where p=0.5 and a=0.3 - cost = (0.01 + cost).powi(2); - - // Penalize two consecutive dashes (not necessarily hyphens) extra. - if attempt.dash && pred.line.dash { - cost += CONSECUTIVE_DASH_COST; - } - - // The total cost of this line and its chain of predecessors. - let total = pred.total + cost; - - // If this attempt is better than what we had before, take it! - if best.as_ref().map_or(true, |best| best.total >= total) { - best = Some(Entry { pred: i, total, line: attempt }); - } - } - - table.push(best.unwrap()); - }); - - // Retrace the best path. - let mut idx = table.len() - 1; - while idx != 0 { - table.truncate(idx + 1); - let entry = table.pop().unwrap(); - lines.push(entry.line); - idx = entry.pred; - } - - lines.reverse(); - lines -} - -/// Create a line which spans the given range. -fn line<'a>( - vt: &Vt, - p: &'a Preparation, - mut range: Range, - breakpoint: Breakpoint, -) -> Line<'a> { - let end = range.end; - let mut justify = - p.justify && end < p.bidi.text.len() && breakpoint != Breakpoint::Mandatory; - - if range.is_empty() { - return Line { - bidi: &p.bidi, - end, - trimmed: range, - first: None, - inner: &[], - last: None, - width: Abs::zero(), - justify, - dash: false, - }; - } - - // Slice out the relevant items. - let (expanded, mut inner) = p.slice(range.clone()); - let mut width = Abs::zero(); - - // Reshape the last item if it's split in half or hyphenated. - let mut last = None; - let mut dash = false; - if let Some((Item::Text(shaped), before)) = inner.split_last() { - // Compute the range we want to shape, trimming whitespace at the - // end of the line. - let base = expanded.end - shaped.text.len(); - let start = range.start.max(base); - let text = &p.bidi.text[start..range.end]; - // U+200B ZERO WIDTH SPACE is used to provide a line break opportunity, - // we want to trim it too. - let trimmed = text.trim_end().trim_end_matches('\u{200B}'); - range.end = start + trimmed.len(); - - // Deal with hyphens, dashes and justification. - let shy = trimmed.ends_with('\u{ad}'); - let hyphen = breakpoint == Breakpoint::Hyphen; - dash = hyphen || shy || trimmed.ends_with(['-', '–', '—']); - justify |= text.ends_with('\u{2028}'); - - // Deal with CJK punctuation at line ends. - let gb_style = is_gb_style(shaped.lang, shaped.region); - let maybe_adjust_last_glyph = trimmed.ends_with(END_PUNCT_PAT) - || (p.cjk_latin_spacing && trimmed.ends_with(char_is_cjk_script)); - - // Usually, we don't want to shape an empty string because: - // - We don't want the height of trimmed whitespace in a different - // font to be considered for the line height. - // - Even if it's in the same font, its unnecessary. - // - // There is one exception though. When the whole line is empty, we - // need the shaped empty string to make the line the appropriate - // height. That is the case exactly if the string is empty and there - // are no other items in the line. - if hyphen || start + shaped.text.len() > range.end || maybe_adjust_last_glyph { - if hyphen || start < range.end || before.is_empty() { - let mut reshaped = shaped.reshape(vt, &p.spans, start..range.end); - if hyphen || shy { - reshaped.push_hyphen(vt, p.fallback); - } - - if let Some(last_glyph) = reshaped.glyphs.last() { - if last_glyph.is_cjk_left_aligned_punctuation(gb_style) { - // If the last glyph is a CJK punctuation, we want to shrink it. - // See Requirements for Chinese Text Layout, Section 3.1.6.3 - // Compression of punctuation marks at line start or line end - let shrink_amount = last_glyph.shrinkability().1; - let punct = reshaped.glyphs.to_mut().last_mut().unwrap(); - punct.shrink_right(shrink_amount); - reshaped.width -= shrink_amount.at(reshaped.size); - } else if p.cjk_latin_spacing - && last_glyph.is_cjk_script() - && (last_glyph.x_advance - last_glyph.x_offset) > Em::one() - { - // If the last glyph is a CJK character adjusted by [`add_cjk_latin_spacing`], - // restore the original width. - let shrink_amount = - last_glyph.x_advance - last_glyph.x_offset - Em::one(); - let glyph = reshaped.glyphs.to_mut().last_mut().unwrap(); - glyph.x_advance -= shrink_amount; - glyph.adjustability.shrinkability.1 = Em::zero(); - reshaped.width -= shrink_amount.at(reshaped.size); - } - } - - width += reshaped.width; - last = Some(Item::Text(reshaped)); - } - - inner = before; - } - } - - // Deal with CJK characters at line starts. - let text = &p.bidi.text[range.start..end]; - let maybe_adjust_first_glyph = text.starts_with(BEGIN_PUNCT_PAT) - || (p.cjk_latin_spacing && text.starts_with(char_is_cjk_script)); - - // Reshape the start item if it's split in half. - let mut first = None; - if let Some((Item::Text(shaped), after)) = inner.split_first() { - // Compute the range we want to shape. - let base = expanded.start; - let end = range.end.min(base + shaped.text.len()); - - // Reshape if necessary. - if range.start + shaped.text.len() > end || maybe_adjust_first_glyph { - // If the range is empty, we don't want to push an empty text item. - if range.start < end { - let reshaped = shaped.reshape(vt, &p.spans, range.start..end); - width += reshaped.width; - first = Some(Item::Text(reshaped)); - } - - inner = after; - } - } - - if maybe_adjust_first_glyph { - let reshaped = first.as_mut().or(last.as_mut()).and_then(Item::text_mut); - if let Some(reshaped) = reshaped { - if let Some(first_glyph) = reshaped.glyphs.first() { - if first_glyph.is_cjk_right_aligned_punctuation() { - // If the first glyph is a CJK punctuation, we want to shrink it. - let shrink_amount = first_glyph.shrinkability().0; - let glyph = reshaped.glyphs.to_mut().first_mut().unwrap(); - glyph.shrink_left(shrink_amount); - let amount_abs = shrink_amount.at(reshaped.size); - reshaped.width -= amount_abs; - width -= amount_abs; - } else if p.cjk_latin_spacing - && first_glyph.is_cjk_script() - && first_glyph.x_offset > Em::zero() - { - // If the first glyph is a CJK character adjusted by [`add_cjk_latin_spacing`], - // restore the original width. - let shrink_amount = first_glyph.x_offset; - let glyph = reshaped.glyphs.to_mut().first_mut().unwrap(); - glyph.x_advance -= shrink_amount; - glyph.x_offset = Em::zero(); - glyph.adjustability.shrinkability.0 = Em::zero(); - let amount_abs = shrink_amount.at(reshaped.size); - reshaped.width -= amount_abs; - width -= amount_abs; - } - } - } - } - - // Measure the inner items. - for item in inner { - width += item.width(); - } - - Line { - bidi: &p.bidi, - trimmed: range, - end, - first, - inner, - last, - width, - justify, - dash, - } -} - -/// Combine layouted lines into one frame per region. -fn finalize( - vt: &mut Vt, - p: &Preparation, - lines: &[Line], - region: Size, - expand: bool, -) -> SourceResult { - // Determine the paragraph's width: Full width of the region if we - // should expand or there's fractional spacing, fit-to-width otherwise. - let width = if !region.x.is_finite() - || (!expand && lines.iter().all(|line| line.fr().is_zero())) - { - region - .x - .min(p.hang + lines.iter().map(|line| line.width).max().unwrap_or_default()) - } else { - region.x - }; - - // Stack the lines into one frame per region. - let mut frames: Vec = lines - .iter() - .map(|line| commit(vt, p, line, width, region.y)) - .collect::>()?; - - // Prevent orphans. - if frames.len() >= 2 && !frames[1].is_empty() { - let second = frames.remove(1); - let first = &mut frames[0]; - merge(first, second, p.leading); - } - - // Prevent widows. - let len = frames.len(); - if len >= 2 && !frames[len - 2].is_empty() { - let second = frames.pop().unwrap(); - let first = frames.last_mut().unwrap(); - merge(first, second, p.leading); - } - - Ok(Fragment::frames(frames)) -} - -/// Merge two line frames -fn merge(first: &mut Frame, second: Frame, leading: Abs) { - let offset = first.height() + leading; - let total = offset + second.height(); - first.push_frame(Point::with_y(offset), second); - first.size_mut().y = total; -} - -/// Commit to a line and build its frame. -fn commit( - vt: &mut Vt, - p: &Preparation, - line: &Line, - width: Abs, - full: Abs, -) -> SourceResult { - let mut remaining = width - line.width - p.hang; - let mut offset = Abs::zero(); - - // Reorder the line from logical to visual order. - let (reordered, starts_rtl) = reorder(line); - if !starts_rtl { - offset += p.hang; - } - - // Handle hanging punctuation to the left. - if let Some(Item::Text(text)) = reordered.first() { - if let Some(glyph) = text.glyphs.first() { - if !text.dir.is_positive() - && TextElem::overhang_in(text.styles) - && (reordered.len() > 1 || text.glyphs.len() > 1) - { - let amount = overhang(glyph.c) * glyph.x_advance.at(text.size); - offset -= amount; - remaining += amount; - } - } - } - - // Handle hanging punctuation to the right. - if let Some(Item::Text(text)) = reordered.last() { - if let Some(glyph) = text.glyphs.last() { - if text.dir.is_positive() - && TextElem::overhang_in(text.styles) - && (reordered.len() > 1 || text.glyphs.len() > 1) - { - let amount = overhang(glyph.c) * glyph.x_advance.at(text.size); - remaining += amount; - } - } - } - - // Determine how much additional space is needed. - // The justicication_ratio is for the first step justification, - // extra_justification is for the last step. - // For more info on multi-step justification, see Procedures for Inter- - // Character Space Expansion in W3C document Chinese Layout Requirements. - let fr = line.fr(); - let mut justification_ratio = 0.0; - let mut extra_justification = Abs::zero(); - - let shrink = line.shrinkability(); - let stretch = line.stretchability(); - if remaining < Abs::zero() && shrink > Abs::zero() { - // Attempt to reduce the length of the line, using shrinkability. - justification_ratio = (remaining / shrink).max(-1.0); - remaining = (remaining + shrink).min(Abs::zero()); - } else if line.justify && fr.is_zero() { - // Attempt to increase the length of the line, using stretchability. - if stretch > Abs::zero() { - justification_ratio = (remaining / stretch).min(1.0); - remaining = (remaining - stretch).max(Abs::zero()); - } - - let justifiables = line.justifiables(); - if justifiables > 0 && remaining > Abs::zero() { - // Underfull line, distribute the extra space. - extra_justification = remaining / justifiables as f64; - remaining = Abs::zero(); - } - } - - let mut top = Abs::zero(); - let mut bottom = Abs::zero(); - - // Build the frames and determine the height and baseline. - let mut frames = vec![]; - for item in reordered { - let mut push = |offset: &mut Abs, frame: Frame| { - let width = frame.width(); - top.set_max(frame.baseline()); - bottom.set_max(frame.size().y - frame.baseline()); - frames.push((*offset, frame)); - *offset += width; - }; - - match item { - Item::Absolute(v) => { - offset += *v; - } - Item::Fractional(v, elem) => { - let amount = v.share(fr, remaining); - if let Some((elem, styles)) = elem { - let region = Size::new(amount, full); - let pod = Regions::one(region, Axes::new(true, false)); - let mut frame = elem.layout(vt, *styles, pod)?.into_frame(); - frame.translate(Point::with_y(TextElem::baseline_in(*styles))); - push(&mut offset, frame); - } else { - offset += amount; - } - } - Item::Text(shaped) => { - let frame = shaped.build(vt, justification_ratio, extra_justification); - push(&mut offset, frame); - } - Item::Frame(frame) | Item::Meta(frame) => { - push(&mut offset, frame.clone()); - } - } - } - - // Remaining space is distributed now. - if !fr.is_zero() { - remaining = Abs::zero(); - } - - let size = Size::new(width, top + bottom); - let mut output = Frame::soft(size); - output.set_baseline(top); - - // Construct the line's frame. - for (offset, frame) in frames { - let x = offset + p.align.position(remaining); - let y = top - frame.baseline(); - output.push_frame(Point::new(x, y), frame); - } - - Ok(output) -} - -/// Return a line's items in visual order. -fn reorder<'a>(line: &'a Line<'a>) -> (Vec<&Item<'a>>, bool) { - let mut reordered = vec![]; - - // The bidi crate doesn't like empty lines. - if line.trimmed.is_empty() { - return (line.slice(line.trimmed.clone()).collect(), false); - } - - // Find the paragraph that contains the line. - let para = line - .bidi - .paragraphs - .iter() - .find(|para| para.range.contains(&line.trimmed.start)) - .unwrap(); - - // Compute the reordered ranges in visual order (left to right). - let (levels, runs) = line.bidi.visual_runs(para, line.trimmed.clone()); - let starts_rtl = levels.first().map_or(false, |level| level.is_rtl()); - - // Collect the reordered items. - for run in runs { - // Skip reset L1 runs because handling them would require reshaping - // again in some cases. - if line.bidi.levels[run.start] != levels[run.start] { - continue; - } - - let prev = reordered.len(); - reordered.extend(line.slice(run.clone())); - - if levels[run.start].is_rtl() { - reordered[prev..].reverse(); - } - } - - (reordered, starts_rtl) -} - -/// How much a character should hang into the end margin. -/// -/// For more discussion, see: -/// https://recoveringphysicist.com/21/ -fn overhang(c: char) -> f64 { - match c { - // Dashes. - '–' | '—' => 0.2, - '-' => 0.55, - - // Punctuation. - '.' | ',' => 0.8, - ':' | ';' => 0.3, - - // Arabic - '\u{60C}' | '\u{6D4}' => 0.4, - - _ => 0.0, - } -} diff --git a/crates/typst-library/src/layout/place.rs b/crates/typst-library/src/layout/place.rs deleted file mode 100644 index c8e83383..00000000 --- a/crates/typst-library/src/layout/place.rs +++ /dev/null @@ -1,124 +0,0 @@ -use crate::prelude::*; - -/// Places content at an absolute position. -/// -/// Placed content will not affect the position of other content. Place is -/// always relative to its parent container and will be in the foreground of all -/// other content in the container. Page margins will be respected. -/// -/// -/// # Example -/// ```example -/// #set page(height: 60pt) -/// Hello, world! -/// -/// #place( -/// top + right, -/// square( -/// width: 20pt, -/// stroke: 2pt + blue -/// ), -/// ) -/// ``` -#[elem(Layout, Behave)] -pub struct PlaceElem { - /// Relative to which position in the parent container to place the content. - /// - /// Cannot be `{auto}` if `float` is `{false}` and must be either - /// `{auto}`, `{top}`, or `{bottom}` if `float` is `{true}`. - /// - /// When an axis of the page is `{auto}` sized, all alignments relative to - /// that axis will be ignored, instead, the item will be placed in the - /// origin of the axis. - #[positional] - #[default(Smart::Custom(Align::START))] - pub alignment: Smart, - - /// Whether the placed element has floating layout. - /// - /// Floating elements are positioned at the top or bottom of the page, - /// displacing in-flow content. - /// - /// ```example - /// #set page(height: 150pt) - /// #let note(where, body) = place( - /// center + where, - /// float: true, - /// clearance: 6pt, - /// rect(body), - /// ) - /// - /// #lorem(10) - /// #note(bottom)[Bottom 1] - /// #note(bottom)[Bottom 2] - /// #lorem(40) - /// #note(top)[Top] - /// #lorem(10) - /// ``` - pub float: bool, - - /// The amount of clearance the placed element has in a floating layout. - #[default(Em::new(1.5).into())] - #[resolve] - pub clearance: Length, - - /// The horizontal displacement of the placed content. - /// - /// ```example - /// #set page(height: 100pt) - /// #for i in range(16) { - /// let amount = i * 4pt - /// place(center, dx: amount - 32pt, dy: amount)[A] - /// } - /// ``` - pub dx: Rel, - - /// The vertical displacement of the placed content. - pub dy: Rel, - - /// The content to place. - #[required] - pub body: Content, -} - -impl Layout for PlaceElem { - #[tracing::instrument(name = "PlaceElem::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - // The pod is the base area of the region because for absolute - // placement we don't really care about the already used area. - let base = regions.base(); - let float = self.float(styles); - let alignment = self.alignment(styles); - - if float - && alignment - .map_or(false, |align| matches!(align.y(), None | Some(VAlign::Horizon))) - { - bail!(self.span(), "floating placement must be `auto`, `top`, or `bottom`"); - } else if !float && alignment.is_auto() { - return Err("automatic positioning is only available for floating placement") - .hint("you can enable floating placement with `place(float: true, ..)`") - .at(self.span()); - } - - let child = self - .body() - .clone() - .aligned(alignment.unwrap_or_else(|| Align::CENTER)); - - let pod = Regions::one(base, Axes::splat(false)); - let frame = child.layout(vt, styles, pod)?.into_frame(); - Ok(Fragment::frame(frame)) - } -} - -impl Behave for PlaceElem { - fn behaviour(&self) -> Behaviour { - Behaviour::Ignorant - } -} diff --git a/crates/typst-library/src/layout/regions.rs b/crates/typst-library/src/layout/regions.rs deleted file mode 100644 index 6dd549b0..00000000 --- a/crates/typst-library/src/layout/regions.rs +++ /dev/null @@ -1,144 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; - -use typst::geom::{Abs, Axes, Size}; - -/// A sequence of regions to layout into. -#[derive(Copy, Clone, Hash)] -pub struct Regions<'a> { - /// The remaining size of the first region. - pub size: Size, - /// The full height of the region for relative sizing. - pub full: Abs, - /// The height of followup regions. The width is the same for all regions. - pub backlog: &'a [Abs], - /// The height of the final region that is repeated once the backlog is - /// drained. The width is the same for all regions. - pub last: Option, - /// Whether elements should expand to fill the regions instead of shrinking - /// to fit the content. - pub expand: Axes, - /// Whether these are the root regions or direct descendants. - /// - /// True for the padded page regions and columns directly in the page, - /// false otherwise. - pub root: bool, -} - -impl Regions<'_> { - /// Create a new region sequence with exactly one region. - pub fn one(size: Size, expand: Axes) -> Self { - Self { - size, - full: size.y, - backlog: &[], - last: None, - expand, - root: false, - } - } - - /// Create a new sequence of same-size regions that repeats indefinitely. - pub fn repeat(size: Size, expand: Axes) -> Self { - Self { - size, - full: size.y, - backlog: &[], - last: Some(size.y), - expand, - root: false, - } - } - - /// The base size, which doesn't take into account that the regions is - /// already partially used up. - /// - /// This is also used for relative sizing. - pub fn base(&self) -> Size { - Size::new(self.size.x, self.full) - } - - /// Create new regions where all sizes are mapped with `f`. - /// - /// Note that since all regions must have the same width, the width returned - /// by `f` is ignored for the backlog and the final region. - pub fn map<'v, F>(&self, backlog: &'v mut Vec, mut f: F) -> Regions<'v> - where - F: FnMut(Size) -> Size, - { - let x = self.size.x; - backlog.clear(); - backlog.extend(self.backlog.iter().map(|&y| f(Size::new(x, y)).y)); - Regions { - size: f(self.size), - full: f(Size::new(x, self.full)).y, - backlog, - last: self.last.map(|y| f(Size::new(x, y)).y), - expand: self.expand, - root: false, - } - } - - /// Whether the first region is full and a region break is called for. - pub fn is_full(&self) -> bool { - Abs::zero().fits(self.size.y) && !self.in_last() - } - - /// Whether the first region is the last usable region. - /// - /// If this is true, calling `next()` will have no effect. - pub fn in_last(&self) -> bool { - self.backlog.is_empty() && self.last.map_or(true, |height| self.size.y == height) - } - - /// The same regions, but with different `root` configuration. - pub fn with_root(self, root: bool) -> Self { - Self { root, ..self } - } - - /// Advance to the next region if there is any. - pub fn next(&mut self) { - if let Some(height) = self - .backlog - .split_first() - .map(|(first, tail)| { - self.backlog = tail; - *first - }) - .or(self.last) - { - self.size.y = height; - self.full = height; - } - } - - /// An iterator that returns the sizes of the first and all following - /// regions, equivalently to what would be produced by calling - /// [`next()`](Self::next) repeatedly until all regions are exhausted. - /// This iterator may be infinite. - pub fn iter(&self) -> impl Iterator + '_ { - let first = std::iter::once(self.size); - let backlog = self.backlog.iter(); - let last = self.last.iter().cycle(); - first.chain(backlog.chain(last).map(|&h| Size::new(self.size.x, h))) - } -} - -impl Debug for Regions<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str("Regions ")?; - let mut list = f.debug_list(); - let mut prev = self.size.y; - list.entry(&self.size); - for &height in self.backlog { - list.entry(&Size::new(self.size.x, height)); - prev = height; - } - if let Some(last) = self.last { - if last != prev { - list.entry(&Size::new(self.size.x, last)); - } - list.entry(&(..)); - } - list.finish() - } -} diff --git a/crates/typst-library/src/layout/repeat.rs b/crates/typst-library/src/layout/repeat.rs deleted file mode 100644 index ce31164e..00000000 --- a/crates/typst-library/src/layout/repeat.rs +++ /dev/null @@ -1,75 +0,0 @@ -use crate::layout::AlignElem; -use crate::prelude::*; - -/// Repeats content to the available space. -/// -/// This can be useful when implementing a custom index, reference, or outline. -/// -/// Space may be inserted between the instances of the body parameter, so be -/// sure to include negative space if you need the instances to overlap. -/// -/// Errors if there no bounds on the available space, as it would create -/// infinite content. -/// -/// # Example -/// ```example -/// Sign on the dotted line: -/// #box(width: 1fr, repeat[.]) -/// -/// #set text(10pt) -/// #v(8pt, weak: true) -/// #align(right)[ -/// Berlin, the 22nd of December, 2022 -/// ] -/// ``` -#[elem(Layout)] -pub struct RepeatElem { - /// The content to repeat. - #[required] - pub body: Content, -} - -impl Layout for RepeatElem { - #[tracing::instrument(name = "RepeatElem::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - let pod = Regions::one(regions.size, Axes::new(false, false)); - let piece = self.body().layout(vt, styles, pod)?.into_frame(); - let align = AlignElem::alignment_in(styles).resolve(styles); - - let fill = regions.size.x; - let width = piece.width(); - let count = (fill / width).floor(); - let remaining = fill % width; - let apart = remaining / (count - 1.0); - - let size = Size::new(regions.size.x, piece.height()); - - if !size.is_finite() { - bail!(self.span(), "repeat with no size restrictions"); - } - - let mut frame = Frame::soft(size); - if piece.has_baseline() { - frame.set_baseline(piece.baseline()); - } - - let mut offset = Abs::zero(); - if count == 1.0 { - offset += align.x.position(remaining); - } - - if width > Abs::zero() { - for _ in 0..(count as usize).min(1000) { - frame.push_frame(Point::with_x(offset), piece.clone()); - offset += piece.width() + apart; - } - } - - Ok(Fragment::frame(frame)) - } -} diff --git a/crates/typst-library/src/layout/spacing.rs b/crates/typst-library/src/layout/spacing.rs deleted file mode 100644 index 88b6e2cd..00000000 --- a/crates/typst-library/src/layout/spacing.rs +++ /dev/null @@ -1,262 +0,0 @@ -use std::borrow::Cow; - -use crate::prelude::*; - -/// Inserts horizontal spacing into a paragraph. -/// -/// The spacing can be absolute, relative, or fractional. In the last case, the -/// remaining space on the line is distributed among all fractional spacings -/// according to their relative fractions. -/// -/// # Example -/// ```example -/// First #h(1cm) Second \ -/// First #h(30%) Second \ -/// First #h(2fr) Second #h(1fr) Third -/// ``` -/// -/// # Mathematical Spacing { #math-spacing } -/// In [mathematical formulas]($category/math), you can additionally use these -/// constants to add spacing between elements: `thin`, `med`, `thick`, `quad`, `wide`. -#[elem(title = "Spacing (H)", Behave)] -pub struct HElem { - /// How much spacing to insert. - #[required] - pub amount: Spacing, - - /// If `{true}`, the spacing collapses at the start or end of a paragraph. - /// Moreover, from multiple adjacent weak spacings all but the largest one - /// collapse. - /// - /// Weak spacing in markup also causes all adjacent markup spaces to be - /// removed, regardless of the amount of spacing inserted. To force a space - /// next to weak spacing, you can explicitly write `[#" "]` (for a normal - /// space) or `[~]` (for a non-breaking space). The latter can be useful to - /// create a construct that always attaches to the preceding word with one - /// non-breaking space, independently of whether a markup space existed in - /// front or not. - /// - /// ```example - /// #h(1cm, weak: true) - /// We identified a group of _weak_ - /// specimens that fail to manifest - /// in most cases. However, when - /// #h(8pt, weak: true) supported - /// #h(8pt, weak: true) on both sides, - /// they do show up. - /// - /// Further #h(0pt, weak: true) more, - /// even the smallest of them swallow - /// adjacent markup spaces. - /// ``` - #[default(false)] - pub weak: bool, -} - -impl HElem { - /// Zero-width horizontal weak spacing that eats surrounding spaces. - pub fn hole() -> Self { - Self::new(Abs::zero().into()).with_weak(true) - } -} - -impl Behave for HElem { - fn behaviour(&self) -> Behaviour { - if self.amount().is_fractional() { - Behaviour::Destructive - } else if self.weak(StyleChain::default()) { - Behaviour::Weak(1) - } else { - Behaviour::Invisible - } - } - - fn larger( - &self, - prev: &(Cow, Behaviour, StyleChain), - styles: StyleChain, - ) -> bool { - let Some(other) = prev.0.to::() else { return false }; - match (self.amount(), other.amount()) { - (Spacing::Fr(this), Spacing::Fr(other)) => this > other, - (Spacing::Rel(this), Spacing::Rel(other)) => { - this.resolve(styles) > other.resolve(prev.2) - } - _ => false, - } - } -} - -/// Inserts vertical spacing into a flow of blocks. -/// -/// The spacing can be absolute, relative, or fractional. In the last case, -/// the remaining space on the page is distributed among all fractional spacings -/// according to their relative fractions. -/// -/// # Example -/// ```example -/// #grid( -/// rows: 3cm, -/// columns: 6, -/// gutter: 1fr, -/// [A #parbreak() B], -/// [A #v(0pt) B], -/// [A #v(10pt) B], -/// [A #v(0pt, weak: true) B], -/// [A #v(40%, weak: true) B], -/// [A #v(1fr) B], -/// ) -/// ``` -#[elem(title = "Spacing (V)", Behave)] -pub struct VElem { - /// How much spacing to insert. - #[required] - pub amount: Spacing, - - /// If `{true}`, the spacing collapses at the start or end of a flow. - /// Moreover, from multiple adjacent weak spacings all but the largest one - /// collapse. Weak spacings will always collapse adjacent paragraph spacing, - /// even if the paragraph spacing is larger. - /// - /// ```example - /// The following theorem is - /// foundational to the field: - /// #v(4pt, weak: true) - /// $ x^2 + y^2 = r^2 $ - /// #v(4pt, weak: true) - /// The proof is simple: - /// ``` - #[external] - pub weak: bool, - - /// The element's weakness level, see also [`Behaviour`]. - #[internal] - #[parse(args.named("weak")?.map(|v: bool| v as usize))] - pub weakness: usize, -} - -impl VElem { - /// Normal strong spacing. - pub fn strong(amount: Spacing) -> Self { - Self::new(amount).with_weakness(0) - } - - /// User-created weak spacing. - pub fn weak(amount: Spacing) -> Self { - Self::new(amount).with_weakness(1) - } - - /// Weak spacing with list attach weakness. - pub fn list_attach(amount: Spacing) -> Self { - Self::new(amount).with_weakness(2) - } - - /// Weak spacing with BlockElem::ABOVE/BELOW weakness. - pub fn block_around(amount: Spacing) -> Self { - Self::new(amount).with_weakness(3) - } - - /// Weak spacing with BlockElem::SPACING weakness. - pub fn block_spacing(amount: Spacing) -> Self { - Self::new(amount).with_weakness(4) - } -} - -impl Behave for VElem { - fn behaviour(&self) -> Behaviour { - if self.amount().is_fractional() { - Behaviour::Destructive - } else if self.weakness(StyleChain::default()) > 0 { - Behaviour::Weak(self.weakness(StyleChain::default())) - } else { - Behaviour::Invisible - } - } - - fn larger( - &self, - prev: &(Cow, Behaviour, StyleChain), - styles: StyleChain, - ) -> bool { - let Some(other) = prev.0.to::() else { return false }; - match (self.amount(), other.amount()) { - (Spacing::Fr(this), Spacing::Fr(other)) => this > other, - (Spacing::Rel(this), Spacing::Rel(other)) => { - this.resolve(styles) > other.resolve(prev.2) - } - _ => false, - } - } -} - -cast! { - VElem, - v: Content => v.to::().cloned().ok_or("expected `v` element")?, -} - -/// Kinds of spacing. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Spacing { - /// Spacing specified in absolute terms and relative to the parent's size. - Rel(Rel), - /// Spacing specified as a fraction of the remaining free space in the - /// parent. - Fr(Fr), -} - -impl Spacing { - /// Whether this is fractional spacing. - pub fn is_fractional(self) -> bool { - matches!(self, Self::Fr(_)) - } - - /// Whether the spacing is actually no spacing. - pub fn is_zero(&self) -> bool { - match self { - Self::Rel(rel) => rel.is_zero(), - Self::Fr(fr) => fr.is_zero(), - } - } -} - -impl From for Spacing { - fn from(abs: Abs) -> Self { - Self::Rel(abs.into()) - } -} - -impl From for Spacing { - fn from(em: Em) -> Self { - Self::Rel(Rel::new(Ratio::zero(), em.into())) - } -} - -impl From for Spacing { - fn from(length: Length) -> Self { - Self::Rel(length.into()) - } -} - -impl From for Spacing { - fn from(fr: Fr) -> Self { - Self::Fr(fr) - } -} - -cast! { - Spacing, - self => match self { - Self::Rel(rel) => { - if rel.rel.is_zero() { - rel.abs.into_value() - } else if rel.abs.is_zero() { - rel.rel.into_value() - } else { - rel.into_value() - } - } - Self::Fr(fr) => fr.into_value(), - }, - v: Rel => Self::Rel(v), - v: Fr => Self::Fr(v), -} diff --git a/crates/typst-library/src/layout/stack.rs b/crates/typst-library/src/layout/stack.rs deleted file mode 100644 index c12d2048..00000000 --- a/crates/typst-library/src/layout/stack.rs +++ /dev/null @@ -1,345 +0,0 @@ -use crate::layout::{AlignElem, Spacing}; -use crate::prelude::*; - -/// Arranges content and spacing horizontally or vertically. -/// -/// The stack places a list of items along an axis, with optional spacing -/// between each item. -/// -/// # Example -/// ```example -/// #stack( -/// dir: ttb, -/// rect(width: 40pt), -/// rect(width: 120pt), -/// rect(width: 90pt), -/// ) -/// ``` -#[elem(Layout)] -pub struct StackElem { - /// The direction along which the items are stacked. Possible values are: - /// - /// - `{ltr}`: Left to right. - /// - `{rtl}`: Right to left. - /// - `{ttb}`: Top to bottom. - /// - `{btt}`: Bottom to top. - /// - /// You can use the `start` and `end` methods to obtain the initial and - /// final points (respectively) of a direction, as `alignment`. You can also - /// use the `axis` method to determine whether a direction is - /// `{"horizontal"}` or `{"vertical"}`. The `inv` method returns a - /// direction's inverse direction. - /// - /// For example, `{ttb.start()}` is `top`, `{ttb.end()}` is `bottom`, - /// `{ttb.axis()}` is `{"vertical"}` and `{ttb.inv()}` is equal to `btt`. - #[default(Dir::TTB)] - pub dir: Dir, - - /// Spacing to insert between items where no explicit spacing was provided. - pub spacing: Option, - - /// The children to stack along the axis. - #[variadic] - pub children: Vec, -} - -impl Layout for StackElem { - #[tracing::instrument(name = "StackElem::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - let mut layouter = StackLayouter::new(self.dir(styles), regions, styles); - - // Spacing to insert before the next block. - let spacing = self.spacing(styles); - let mut deferred = None; - - for child in self.children() { - match child { - StackChild::Spacing(kind) => { - layouter.layout_spacing(*kind); - deferred = None; - } - StackChild::Block(block) => { - if let Some(kind) = deferred { - layouter.layout_spacing(kind); - } - - layouter.layout_block(vt, block, styles)?; - deferred = spacing; - } - } - } - - Ok(layouter.finish()) - } -} - -/// A child of a stack element. -#[derive(Clone, PartialEq, Hash)] -pub enum StackChild { - /// Spacing between other children. - Spacing(Spacing), - /// Arbitrary block-level content. - Block(Content), -} - -impl Debug for StackChild { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Spacing(kind) => kind.fmt(f), - Self::Block(block) => block.fmt(f), - } - } -} - -cast! { - StackChild, - self => match self { - Self::Spacing(spacing) => spacing.into_value(), - Self::Block(content) => content.into_value(), - }, - v: Spacing => Self::Spacing(v), - v: Content => Self::Block(v), -} - -/// Performs stack layout. -struct StackLayouter<'a> { - /// The stacking direction. - dir: Dir, - /// The axis of the stacking direction. - axis: Axis, - /// The regions to layout children into. - regions: Regions<'a>, - /// The inherited styles. - styles: StyleChain<'a>, - /// Whether the stack itself should expand to fill the region. - expand: Axes, - /// The initial size of the current region before we started subtracting. - initial: Size, - /// The generic size used by the frames for the current region. - used: Gen, - /// The sum of fractions in the current region. - fr: Fr, - /// Already layouted items whose exact positions are not yet known due to - /// fractional spacing. - items: Vec, - /// Finished frames for previous regions. - finished: Vec, -} - -/// A prepared item in a stack layout. -enum StackItem { - /// Absolute spacing between other items. - Absolute(Abs), - /// Fractional spacing between other items. - Fractional(Fr), - /// A frame for a layouted block. - Frame(Frame, Axes), -} - -impl<'a> StackLayouter<'a> { - /// Create a new stack layouter. - fn new(dir: Dir, mut regions: Regions<'a>, styles: StyleChain<'a>) -> Self { - let axis = dir.axis(); - let expand = regions.expand; - - // Disable expansion along the block axis for children. - regions.expand.set(axis, false); - - Self { - dir, - axis, - regions, - styles, - expand, - initial: regions.size, - used: Gen::zero(), - fr: Fr::zero(), - items: vec![], - finished: vec![], - } - } - - /// Add spacing along the spacing direction. - #[tracing::instrument(name = "StackLayouter::layout_spacing", skip_all)] - fn layout_spacing(&mut self, spacing: Spacing) { - match spacing { - Spacing::Rel(v) => { - // Resolve the spacing and limit it to the remaining space. - let resolved = v - .resolve(self.styles) - .relative_to(self.regions.base().get(self.axis)); - let remaining = self.regions.size.get_mut(self.axis); - let limited = resolved.min(*remaining); - if self.dir.axis() == Axis::Y { - *remaining -= limited; - } - self.used.main += limited; - self.items.push(StackItem::Absolute(resolved)); - } - Spacing::Fr(v) => { - self.fr += v; - self.items.push(StackItem::Fractional(v)); - } - } - } - - /// Layout an arbitrary block. - #[tracing::instrument(name = "StackLayouter::layout_block", skip_all)] - fn layout_block( - &mut self, - vt: &mut Vt, - block: &Content, - styles: StyleChain, - ) -> SourceResult<()> { - if self.regions.is_full() { - self.finish_region(); - } - - // Block-axis alignment of the `AlignElement` is respected by stacks. - let align = if let Some(align) = block.to::() { - align.alignment(styles) - } else if let Some((_, local)) = block.to_styled() { - AlignElem::alignment_in(styles.chain(local)) - } else { - AlignElem::alignment_in(styles) - } - .resolve(styles); - - let fragment = block.layout(vt, styles, self.regions)?; - let len = fragment.len(); - for (i, frame) in fragment.into_iter().enumerate() { - // Grow our size, shrink the region and save the frame for later. - let size = frame.size(); - if self.dir.axis() == Axis::Y { - self.regions.size.y -= size.y; - } - - let gen = match self.axis { - Axis::X => Gen::new(size.y, size.x), - Axis::Y => Gen::new(size.x, size.y), - }; - - self.used.main += gen.main; - self.used.cross.set_max(gen.cross); - - self.items.push(StackItem::Frame(frame, align)); - - if i + 1 < len { - self.finish_region(); - } - } - - Ok(()) - } - - /// Advance to the next region. - fn finish_region(&mut self) { - // Determine the size of the stack in this region depending on whether - // the region expands. - let mut size = self - .expand - .select(self.initial, self.used.into_axes(self.axis)) - .min(self.initial); - - // Expand fully if there are fr spacings. - let full = self.initial.get(self.axis); - let remaining = full - self.used.main; - if self.fr.get() > 0.0 && full.is_finite() { - self.used.main = full; - size.set(self.axis, full); - } - - let mut output = Frame::hard(size); - let mut cursor = Abs::zero(); - let mut ruler: FixedAlign = self.dir.start().into(); - - // Place all frames. - for item in self.items.drain(..) { - match item { - StackItem::Absolute(v) => cursor += v, - StackItem::Fractional(v) => cursor += v.share(self.fr, remaining), - StackItem::Frame(frame, align) => { - if self.dir.is_positive() { - ruler = ruler.max(align.get(self.axis)); - } else { - ruler = ruler.min(align.get(self.axis)); - } - - // Align along the main axis. - let parent = size.get(self.axis); - let child = frame.size().get(self.axis); - let main = ruler.position(parent - self.used.main) - + if self.dir.is_positive() { - cursor - } else { - self.used.main - child - cursor - }; - - // Align along the cross axis. - let other = self.axis.other(); - let cross = align - .get(other) - .position(size.get(other) - frame.size().get(other)); - - let pos = Gen::new(cross, main).to_point(self.axis); - cursor += child; - output.push_frame(pos, frame); - } - } - } - - // Advance to the next region. - self.regions.next(); - self.initial = self.regions.size; - self.used = Gen::zero(); - self.fr = Fr::zero(); - self.finished.push(output); - } - - /// Finish layouting and return the resulting frames. - fn finish(mut self) -> Fragment { - self.finish_region(); - Fragment::frames(self.finished) - } -} - -/// A container with a main and cross component. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -struct Gen { - /// The main component. - pub cross: T, - /// The cross component. - pub main: T, -} - -impl Gen { - /// Create a new instance from the two components. - const fn new(cross: T, main: T) -> Self { - Self { cross, main } - } - - /// Convert to the specific representation, given the current main axis. - fn into_axes(self, main: Axis) -> Axes { - match main { - Axis::X => Axes::new(self.main, self.cross), - Axis::Y => Axes::new(self.cross, self.main), - } - } -} - -impl Gen { - /// The zero value. - fn zero() -> Self { - Self { cross: Abs::zero(), main: Abs::zero() } - } - - /// Convert to a point. - fn to_point(self, main: Axis) -> Point { - self.into_axes(main).to_point() - } -} diff --git a/crates/typst-library/src/layout/table.rs b/crates/typst-library/src/layout/table.rs deleted file mode 100644 index 0eff7a44..00000000 --- a/crates/typst-library/src/layout/table.rs +++ /dev/null @@ -1,361 +0,0 @@ -use typst::eval::{CastInfo, Reflect}; - -use crate::layout::{AlignElem, GridLayouter, TrackSizings}; -use crate::meta::Figurable; -use crate::prelude::*; - -/// A table of items. -/// -/// Tables are used to arrange content in cells. Cells can contain arbitrary -/// content, including multiple paragraphs and are specified in row-major order. -/// Because tables are just grids with configurable cell properties, refer to -/// the [grid documentation]($grid) for more information on how to size the -/// table tracks. -/// -/// To give a table a caption and make it [referenceable]($ref), put it into a -/// [figure]($figure). -/// -/// # Example -/// ```example -/// #table( -/// columns: (1fr, auto, auto), -/// inset: 10pt, -/// align: horizon, -/// [], [*Area*], [*Parameters*], -/// image("cylinder.svg"), -/// $ pi h (D^2 - d^2) / 4 $, -/// [ -/// $h$: height \ -/// $D$: outer radius \ -/// $d$: inner radius -/// ], -/// image("tetrahedron.svg"), -/// $ sqrt(2) / 12 a^3 $, -/// [$a$: edge length] -/// ) -/// ``` -#[elem(Layout, LocalName, Figurable)] -pub struct TableElem { - /// The column sizes. See the [grid documentation]($grid) for more - /// information on track sizing. - #[borrowed] - pub columns: TrackSizings, - - /// The row sizes. See the [grid documentation]($grid) for more information - /// on track sizing. - #[borrowed] - pub rows: TrackSizings, - - /// The gaps between rows & columns. See the [grid documentation]($grid) for - /// more information on gutters. - #[external] - pub gutter: TrackSizings, - - /// The gaps between columns. Takes precedence over `gutter`. See the - /// [grid documentation]($grid) for more information on gutters. - #[borrowed] - #[parse( - let gutter = args.named("gutter")?; - args.named("column-gutter")?.or_else(|| gutter.clone()) - )] - pub column_gutter: TrackSizings, - - /// The gaps between rows. Takes precedence over `gutter`. See the - /// [grid documentation]($grid) for more information on gutters. - #[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))] - #[borrowed] - pub row_gutter: TrackSizings, - - /// How to fill the cells. - /// - /// This can be a color or a function that returns a color. The function is - /// passed the cells' column and row index, starting at zero. This can be - /// used to implement striped tables. - /// - /// ```example - /// #table( - /// fill: (col, _) => if calc.odd(col) { luma(240) } else { white }, - /// align: (col, row) => - /// if row == 0 { center } - /// else if col == 0 { left } - /// else { right }, - /// columns: 4, - /// [], [*Q1*], [*Q2*], [*Q3*], - /// [Revenue:], [1000 €], [2000 €], [3000 €], - /// [Expenses:], [500 €], [1000 €], [1500 €], - /// [Profit:], [500 €], [1000 €], [1500 €], - /// ) - /// ``` - #[borrowed] - pub fill: Celled>, - - /// How to align the cells' content. - /// - /// This can either be a single alignment, an array of alignments - /// (corresponding to each column) or a function that returns an alignment. - /// The function is passed the cells' column and row index, starting at zero. - /// If set to `{auto}`, the outer alignment is used. - /// - /// ```example - /// #table( - /// columns: 3, - /// align: (x, y) => (left, center, right).at(x), - /// [Hello], [Hello], [Hello], - /// [A], [B], [C], - /// ) - /// ``` - #[borrowed] - pub align: Celled>, - - /// How to [stroke]($stroke) the cells. - /// - /// Strokes can be disabled by setting this to `{none}`. - /// - /// _Note:_ Richer stroke customization for individual cells is not yet - /// implemented, but will be in the future. In the meantime, you can use the - /// third-party [tablex library](https://github.com/PgBiel/typst-tablex/). - #[resolve] - #[fold] - #[default(Some(Stroke::default()))] - pub stroke: Option, - - /// How much to pad the cells' content. - /// - /// ```example - /// #table( - /// inset: 10pt, - /// [Hello], - /// [World], - /// ) - /// - /// #table( - /// columns: 2, - /// inset: ( - /// x: 20pt, - /// y: 10pt, - /// ), - /// [Hello], - /// [World], - /// ) - /// ``` - #[fold] - #[default(Sides::splat(Abs::pt(5.0).into()))] - pub inset: Sides>>, - - /// The contents of the table cells. - #[variadic] - pub children: Vec, -} - -impl Layout for TableElem { - #[tracing::instrument(name = "TableElem::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - let inset = self.inset(styles); - let align = self.align(styles); - let columns = self.columns(styles); - let rows = self.rows(styles); - let column_gutter = self.column_gutter(styles); - let row_gutter = self.row_gutter(styles); - - let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice()); - let gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice()); - let cols = tracks.x.len().max(1); - let cells: Vec<_> = self - .children() - .iter() - .enumerate() - .map(|(i, child)| { - let mut child = child.clone().padded(inset); - - let x = i % cols; - let y = i / cols; - if let Smart::Custom(alignment) = align.resolve(vt, x, y)? { - child = child.styled(AlignElem::set_alignment(alignment)); - } - - Ok(child) - }) - .collect::>()?; - - let fill = self.fill(styles); - let stroke = self.stroke(styles).map(Stroke::unwrap_or_default); - - // Prepare grid layout by unifying content and gutter tracks. - let layouter = - GridLayouter::new(tracks, gutter, &cells, regions, styles, self.span()); - - // Measure the columns and layout the grid row-by-row. - let mut layout = layouter.layout(vt)?; - - // Add lines and backgrounds. - for (frame, rows) in layout.fragment.iter_mut().zip(&layout.rows) { - if layout.cols.is_empty() || rows.is_empty() { - continue; - } - - // Render table lines. - if let Some(stroke) = &stroke { - let thickness = stroke.thickness; - let half = thickness / 2.0; - - // Render horizontal lines. - for offset in points(rows.iter().map(|piece| piece.height)) { - let target = Point::with_x(frame.width() + thickness); - let hline = Geometry::Line(target).stroked(stroke.clone()); - frame.prepend( - Point::new(-half, offset), - FrameItem::Shape(hline, self.span()), - ); - } - - // Render vertical lines. - for offset in points(layout.cols.iter().copied()) { - let target = Point::with_y(frame.height() + thickness); - let vline = Geometry::Line(target).stroked(stroke.clone()); - frame.prepend( - Point::new(offset, -half), - FrameItem::Shape(vline, self.span()), - ); - } - } - - // Render cell backgrounds. - let mut dx = Abs::zero(); - for (x, &col) in layout.cols.iter().enumerate() { - let mut dy = Abs::zero(); - for row in rows { - if let Some(fill) = fill.resolve(vt, x, row.y)? { - let pos = Point::new(dx, dy); - let size = Size::new(col, row.height); - let rect = Geometry::Rect(size).filled(fill); - frame.prepend(pos, FrameItem::Shape(rect, self.span())); - } - dy += row.height; - } - dx += col; - } - } - - Ok(layout.fragment) - } -} - -/// Turn an iterator of extents into an iterator of offsets before, in between, -/// and after the extents, e.g. [10mm, 5mm] -> [0mm, 10mm, 15mm]. -fn points(extents: impl IntoIterator) -> impl Iterator { - let mut offset = Abs::zero(); - std::iter::once(Abs::zero()).chain(extents).map(move |extent| { - offset += extent; - offset - }) -} - -/// A value that can be configured per cell. -#[derive(Debug, Clone, PartialEq, Hash)] -pub enum Celled { - /// A bare value, the same for all cells. - Value(T), - /// A closure mapping from cell coordinates to a value. - Func(Func), - /// An array of alignment values corresponding to each column. - Array(Vec), -} - -impl Celled { - /// Resolve the value based on the cell position. - pub fn resolve(&self, vt: &mut Vt, x: usize, y: usize) -> SourceResult { - Ok(match self { - Self::Value(value) => value.clone(), - Self::Func(func) => func.call_vt(vt, [x, y])?.cast().at(func.span())?, - Self::Array(array) => x - .checked_rem(array.len()) - .and_then(|i| array.get(i)) - .cloned() - .unwrap_or_default(), - }) - } -} - -impl Default for Celled { - fn default() -> Self { - Self::Value(T::default()) - } -} - -impl Reflect for Celled { - fn input() -> CastInfo { - T::input() + Array::input() + Func::input() - } - - fn output() -> CastInfo { - T::output() + Array::output() + Func::output() - } - - fn castable(value: &Value) -> bool { - Array::castable(value) || Func::castable(value) || T::castable(value) - } -} - -impl IntoValue for Celled { - fn into_value(self) -> Value { - match self { - Self::Value(value) => value.into_value(), - Self::Func(func) => func.into_value(), - Self::Array(arr) => arr.into_value(), - } - } -} - -impl FromValue for Celled { - fn from_value(value: Value) -> StrResult { - match value { - Value::Func(v) => Ok(Self::Func(v)), - Value::Array(array) => Ok(Self::Array( - array.into_iter().map(T::from_value).collect::>()?, - )), - v if T::castable(&v) => Ok(Self::Value(T::from_value(v)?)), - v => Err(Self::error(&v)), - } - } -} - -impl LocalName for TableElem { - fn local_name(lang: Lang, _: Option) -> &'static str { - match lang { - Lang::ALBANIAN => "Tabel", - Lang::ARABIC => "جدول", - Lang::BOKMÅL => "Tabell", - Lang::CHINESE => "表", - Lang::CZECH => "Tabulka", - Lang::DANISH => "Tabel", - Lang::DUTCH => "Tabel", - Lang::FILIPINO => "Talaan", - Lang::FINNISH => "Taulukko", - Lang::FRENCH => "Tableau", - Lang::GERMAN => "Tabelle", - Lang::GREEK => "Πίνακας", - Lang::HUNGARIAN => "Táblázat", - Lang::ITALIAN => "Tabella", - Lang::NYNORSK => "Tabell", - Lang::POLISH => "Tabela", - Lang::PORTUGUESE => "Tabela", - Lang::ROMANIAN => "Tabelul", - Lang::RUSSIAN => "Таблица", - Lang::SLOVENIAN => "Tabela", - Lang::SPANISH => "Tabla", - Lang::SWEDISH => "Tabell", - Lang::TURKISH => "Tablo", - Lang::UKRAINIAN => "Таблиця", - Lang::VIETNAMESE => "Bảng", - Lang::JAPANESE => "表", - Lang::ENGLISH | _ => "Table", - } - } -} - -impl Figurable for TableElem {} diff --git a/crates/typst-library/src/layout/terms.rs b/crates/typst-library/src/layout/terms.rs deleted file mode 100644 index d373768d..00000000 --- a/crates/typst-library/src/layout/terms.rs +++ /dev/null @@ -1,162 +0,0 @@ -use crate::layout::{BlockElem, HElem, ParElem, Spacing, VElem}; -use crate::prelude::*; - -/// A list of terms and their descriptions. -/// -/// Displays a sequence of terms and their descriptions vertically. When the -/// descriptions span over multiple lines, they use hanging indent to -/// communicate the visual hierarchy. -/// -/// # Example -/// ```example -/// / Ligature: A merged glyph. -/// / Kerning: A spacing adjustment -/// between two adjacent letters. -/// ``` -/// -/// # Syntax -/// This function also has dedicated syntax: Starting a line with a slash, -/// followed by a term, a colon and a description creates a term list item. -#[elem(scope, title = "Term List", Layout)] -pub struct TermsElem { - /// If this is `{false}`, the items are spaced apart with - /// [term list spacing]($terms.spacing). If it is `{true}`, they use normal - /// [leading]($par.leading) instead. This makes the term list more compact, - /// which can look better if the items are short. - /// - /// In markup mode, the value of this parameter is determined based on - /// whether items are separated with a blank line. If items directly follow - /// each other, this is set to `{true}`; if items are separated by a blank - /// line, this is set to `{false}`. - /// - /// ```example - /// / Fact: If a term list has a lot - /// of text, and maybe other inline - /// content, it should not be tight - /// anymore. - /// - /// / Tip: To make it wide, simply - /// insert a blank line between the - /// items. - /// ``` - #[default(true)] - pub tight: bool, - - /// The separator between the item and the description. - /// - /// If you want to just separate them with a certain amount of space, use - /// `{h(2cm, weak: true)}` as the separator and replace `{2cm}` with your - /// desired amount of space. - /// - /// ```example - /// #set terms(separator: [: ]) - /// - /// / Colon: A nice separator symbol. - /// ``` - #[default(HElem::new(Em::new(0.6).into()).with_weak(true).pack())] - #[borrowed] - pub separator: Content, - - /// The indentation of each item. - pub indent: Length, - - /// The hanging indent of the description. - /// - /// This is in addition to the whole item's `indent`. - /// - /// ```example - /// #set terms(hanging-indent: 0pt) - /// / Term: This term list does not - /// make use of hanging indents. - /// ``` - #[default(Em::new(2.0).into())] - pub hanging_indent: Length, - - /// The spacing between the items of a wide (non-tight) term list. - /// - /// If set to `{auto}`, uses the spacing [below blocks]($block.below). - pub spacing: Smart, - - /// The term list's children. - /// - /// When using the term list syntax, adjacent items are automatically - /// collected into term lists, even through constructs like for loops. - /// - /// ```example - /// #for (year, product) in ( - /// "1978": "TeX", - /// "1984": "LaTeX", - /// "2019": "Typst", - /// ) [/ #product: Born in #year.] - /// ``` - #[variadic] - pub children: Vec, -} - -#[scope] -impl TermsElem { - #[elem] - type TermItem; -} - -impl Layout for TermsElem { - #[tracing::instrument(name = "TermsElem::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - let separator = self.separator(styles); - let indent = self.indent(styles); - let hanging_indent = self.hanging_indent(styles); - let gutter = if self.tight(styles) { - ParElem::leading_in(styles).into() - } else { - self.spacing(styles) - .unwrap_or_else(|| *BlockElem::below_in(styles).amount()) - }; - - let mut seq = vec![]; - for (i, child) in self.children().iter().enumerate() { - if i > 0 { - seq.push(VElem::new(gutter).with_weakness(1).pack()); - } - if !indent.is_zero() { - seq.push(HElem::new(indent.into()).pack()); - } - seq.push(child.term().clone().strong()); - seq.push((*separator).clone()); - seq.push(child.description().clone()); - } - - Content::sequence(seq) - .styled(ParElem::set_hanging_indent(hanging_indent + indent)) - .layout(vt, styles, regions) - } -} - -/// A term list item. -#[elem(name = "item", title = "Term List Item")] -pub struct TermItem { - /// The term described by the list item. - #[required] - pub term: Content, - - /// The description of the term. - #[required] - pub description: Content, -} - -cast! { - TermItem, - array: Array => { - let mut iter = array.into_iter(); - let (term, description) = match (iter.next(), iter.next(), iter.next()) { - (Some(a), Some(b), None) => (a.cast()?, b.cast()?), - _ => bail!("array must contain exactly two entries"), - }; - Self::new(term, description) - }, - v: Content => v.to::().cloned().ok_or("expected term item or array")?, -} diff --git a/crates/typst-library/src/layout/transform.rs b/crates/typst-library/src/layout/transform.rs deleted file mode 100644 index 012a146d..00000000 --- a/crates/typst-library/src/layout/transform.rs +++ /dev/null @@ -1,187 +0,0 @@ -use typst::geom::Transform; - -use crate::prelude::*; - -/// Moves content without affecting layout. -/// -/// The `move` function allows you to move content while the layout still 'sees' -/// it at the original positions. Containers will still be sized as if the -/// content was not moved. -/// -/// # Example -/// ```example -/// #rect(inset: 0pt, move( -/// dx: 6pt, dy: 6pt, -/// rect( -/// inset: 8pt, -/// fill: white, -/// stroke: black, -/// [Abra cadabra] -/// ) -/// )) -/// ``` -#[elem(Layout)] -pub struct MoveElem { - /// The horizontal displacement of the content. - pub dx: Rel, - - /// The vertical displacement of the content. - pub dy: Rel, - - /// The content to move. - #[required] - pub body: Content, -} - -impl Layout for MoveElem { - #[tracing::instrument(name = "MoveElem::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - let pod = Regions::one(regions.base(), Axes::splat(false)); - let mut frame = self.body().layout(vt, styles, pod)?.into_frame(); - let delta = Axes::new(self.dx(styles), self.dy(styles)).resolve(styles); - let delta = delta.zip_map(regions.base(), Rel::relative_to); - frame.translate(delta.to_point()); - Ok(Fragment::frame(frame)) - } -} - -/// Rotates content without affecting layout. -/// -/// Rotates an element by a given angle. The layout will act as if the element -/// was not rotated. -/// -/// # Example -/// ```example -/// #stack( -/// dir: ltr, -/// spacing: 1fr, -/// ..range(16) -/// .map(i => rotate(24deg * i)[X]), -/// ) -/// ``` -#[elem(Layout)] -pub struct RotateElem { - /// The amount of rotation. - /// - /// ```example - /// #rotate(-1.571rad)[Space!] - /// ``` - /// - #[positional] - pub angle: Angle, - - /// The origin of the rotation. - /// - /// If, for instance, you wanted the bottom left corner of the rotated - /// element to stay aligned with the baseline, you would set it to `bottom + - /// left` instead. - /// - /// ```example - /// #set text(spacing: 8pt) - /// #let square = square.with(width: 8pt) - /// - /// #box(square()) - /// #box(rotate(30deg, origin: center, square())) - /// #box(rotate(30deg, origin: top + left, square())) - /// #box(rotate(30deg, origin: bottom + right, square())) - /// ``` - #[fold] - #[default(HAlign::Center + VAlign::Horizon)] - pub origin: Align, - - /// The content to rotate. - #[required] - pub body: Content, -} - -impl Layout for RotateElem { - #[tracing::instrument(name = "RotateElem::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - let pod = Regions::one(regions.base(), Axes::splat(false)); - let mut frame = self.body().layout(vt, styles, pod)?.into_frame(); - let Axes { x, y } = self - .origin(styles) - .resolve(styles) - .zip_map(frame.size(), FixedAlign::position); - let ts = Transform::translate(x, y) - .pre_concat(Transform::rotate(self.angle(styles))) - .pre_concat(Transform::translate(-x, -y)); - frame.transform(ts); - Ok(Fragment::frame(frame)) - } -} - -/// Scales content without affecting layout. -/// -/// Lets you mirror content by specifying a negative scale on a single axis. -/// -/// # Example -/// ```example -/// #set align(center) -/// #scale(x: -100%)[This is mirrored.] -/// ``` -#[elem(Layout)] -pub struct ScaleElem { - /// The horizontal scaling factor. - /// - /// The body will be mirrored horizontally if the parameter is negative. - #[parse( - let all = args.find()?; - args.named("x")?.or(all) - )] - #[default(Ratio::one())] - pub x: Ratio, - - /// The vertical scaling factor. - /// - /// The body will be mirrored vertically if the parameter is negative. - #[parse(args.named("y")?.or(all))] - #[default(Ratio::one())] - pub y: Ratio, - - /// The origin of the transformation. - /// - /// ```example - /// A#box(scale(75%)[A])A \ - /// B#box(scale(75%, origin: bottom + left)[B])B - /// ``` - #[fold] - #[default(HAlign::Center + VAlign::Horizon)] - pub origin: Align, - - /// The content to scale. - #[required] - pub body: Content, -} - -impl Layout for ScaleElem { - #[tracing::instrument(name = "ScaleElem::layout", skip_all)] - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - let pod = Regions::one(regions.base(), Axes::splat(false)); - let mut frame = self.body().layout(vt, styles, pod)?.into_frame(); - let Axes { x, y } = self - .origin(styles) - .resolve(styles) - .zip_map(frame.size(), FixedAlign::position); - let transform = Transform::translate(x, y) - .pre_concat(Transform::scale(self.x(styles), self.y(styles))) - .pre_concat(Transform::translate(-x, -y)); - frame.transform(transform); - Ok(Fragment::frame(frame)) - } -} diff --git a/crates/typst-library/src/lib.rs b/crates/typst-library/src/lib.rs deleted file mode 100644 index 212debb0..00000000 --- a/crates/typst-library/src/lib.rs +++ /dev/null @@ -1,170 +0,0 @@ -//! Typst's standard library. - -#![allow(clippy::wildcard_in_or_patterns)] -#![allow(clippy::manual_range_contains)] -#![allow(clippy::comparison_chain)] - -pub mod compute; -pub mod layout; -pub mod math; -pub mod meta; -pub mod prelude; -pub mod shared; -pub mod symbols; -pub mod text; -pub mod visualize; - -use typst::eval::{Array, LangItems, Library, Module, Scope, Smart}; -use typst::geom::{Align, Color, Dir}; -use typst::model::{NativeElement, Styles}; - -use self::layout::LayoutRoot; - -/// Construct the standard library. -pub fn build() -> Library { - let math = math::module(); - let global = global(math.clone()); - Library { global, math, styles: styles(), items: items() } -} - -/// Construct the module with global definitions. -#[tracing::instrument(skip_all)] -fn global(math: Module) -> Module { - let mut global = Scope::deduplicating(); - text::define(&mut global); - global.define_module(math); - layout::define(&mut global); - visualize::define(&mut global); - meta::define(&mut global); - symbols::define(&mut global); - compute::define(&mut global); - prelude(&mut global); - Module::new("global", global) -} - -/// Defines scoped values that are globally available, too. -fn prelude(global: &mut Scope) { - global.reset_category(); - global.define("black", Color::BLACK); - global.define("gray", Color::GRAY); - global.define("silver", Color::SILVER); - global.define("white", Color::WHITE); - global.define("navy", Color::NAVY); - global.define("blue", Color::BLUE); - global.define("aqua", Color::AQUA); - global.define("teal", Color::TEAL); - global.define("eastern", Color::EASTERN); - global.define("purple", Color::PURPLE); - global.define("fuchsia", Color::FUCHSIA); - global.define("maroon", Color::MAROON); - global.define("red", Color::RED); - global.define("orange", Color::ORANGE); - global.define("yellow", Color::YELLOW); - global.define("olive", Color::OLIVE); - global.define("green", Color::GREEN); - global.define("lime", Color::LIME); - global.define("luma", Color::luma_data()); - global.define("oklab", Color::oklab_data()); - global.define("oklch", Color::oklch_data()); - global.define("rgb", Color::rgb_data()); - global.define("cmyk", Color::cmyk_data()); - global.define("range", Array::range_data()); - global.define("ltr", Dir::LTR); - global.define("rtl", Dir::RTL); - global.define("ttb", Dir::TTB); - global.define("btt", Dir::BTT); - global.define("start", Align::START); - global.define("left", Align::LEFT); - global.define("center", Align::CENTER); - global.define("right", Align::RIGHT); - global.define("end", Align::END); - global.define("top", Align::TOP); - global.define("horizon", Align::HORIZON); - global.define("bottom", Align::BOTTOM); -} - -/// Construct the standard style map. -fn styles() -> Styles { - Styles::new() -} - -/// Construct the standard lang item mapping. -fn items() -> LangItems { - LangItems { - layout: |world, content, styles| content.layout_root(world, styles), - em: text::TextElem::size_in, - dir: text::TextElem::dir_in, - space: || text::SpaceElem::new().pack(), - linebreak: || text::LinebreakElem::new().pack(), - text: |text| text::TextElem::new(text).pack(), - text_elem: text::TextElem::elem(), - text_str: |content| Some(content.to::()?.text()), - smart_quote: |double| text::SmartquoteElem::new().with_double(double).pack(), - parbreak: || layout::ParbreakElem::new().pack(), - strong: |body| text::StrongElem::new(body).pack(), - emph: |body| text::EmphElem::new(body).pack(), - raw: |text, lang, block| { - let mut elem = text::RawElem::new(text).with_block(block); - if let Some(lang) = lang { - elem.push_lang(Some(lang)); - } - elem.pack() - }, - raw_languages: text::RawElem::languages, - link: |url| meta::LinkElem::from_url(url).pack(), - reference: |target, supplement| { - let mut elem = meta::RefElem::new(target); - if let Some(supplement) = supplement { - elem.push_supplement(Smart::Custom(Some(meta::Supplement::Content( - supplement, - )))); - } - elem.pack() - }, - bibliography_keys: meta::BibliographyElem::keys, - heading: |level, title| meta::HeadingElem::new(title).with_level(level).pack(), - heading_elem: meta::HeadingElem::elem(), - list_item: |body| layout::ListItem::new(body).pack(), - enum_item: |number, body| { - let mut elem = layout::EnumItem::new(body); - if let Some(number) = number { - elem.push_number(Some(number)); - } - elem.pack() - }, - term_item: |term, description| layout::TermItem::new(term, description).pack(), - equation: |body, block| math::EquationElem::new(body).with_block(block).pack(), - math_align_point: || math::AlignPointElem::new().pack(), - math_delimited: |open, body, close| math::LrElem::new(open + body + close).pack(), - math_attach: |base, t, b, tl, bl, tr, br| { - let mut elem = math::AttachElem::new(base); - if let Some(t) = t { - elem.push_t(Some(t)); - } - if let Some(b) = b { - elem.push_b(Some(b)); - } - if let Some(tl) = tl { - elem.push_tl(Some(tl)); - } - if let Some(bl) = bl { - elem.push_bl(Some(bl)); - } - if let Some(tr) = tr { - elem.push_tr(Some(tr)); - } - if let Some(br) = br { - elem.push_br(Some(br)); - } - elem.pack() - }, - math_primes: |count| math::PrimesElem::new(count).pack(), - math_accent: |base, accent| { - math::AccentElem::new(base, math::Accent::new(accent)).pack() - }, - math_frac: |num, denom| math::FracElem::new(num, denom).pack(), - math_root: |index, radicand| { - math::RootElem::new(radicand).with_index(index).pack() - }, - } -} diff --git a/crates/typst-library/src/math/accent.rs b/crates/typst-library/src/math/accent.rs deleted file mode 100644 index 1b2d4793..00000000 --- a/crates/typst-library/src/math/accent.rs +++ /dev/null @@ -1,137 +0,0 @@ -use crate::math::*; - -/// How much the accent can be shorter than the base. -const ACCENT_SHORT_FALL: Em = Em::new(0.5); - -/// Attaches an accent to a base. -/// -/// # Example -/// ```example -/// $grave(a) = accent(a, `)$ \ -/// $arrow(a) = accent(a, arrow)$ \ -/// $tilde(a) = accent(a, \u{0303})$ -/// ``` -#[elem(LayoutMath)] -pub struct AccentElem { - /// The base to which the accent is applied. - /// May consist of multiple letters. - /// - /// ```example - /// $arrow(A B C)$ - /// ``` - #[required] - pub base: Content, - - /// The accent to apply to the base. - /// - /// Supported accents include: - /// - /// | Accent | Name | Codepoint | - /// | ------------- | --------------- | --------- | - /// | Grave | `grave` | ` | - /// | Acute | `acute` | `´` | - /// | Circumflex | `hat` | `^` | - /// | Tilde | `tilde` | `~` | - /// | Macron | `macron` | `¯` | - /// | Breve | `breve` | `˘` | - /// | Dot | `dot` | `.` | - /// | Double dot | `dot.double` | `¨` | - /// | Triple dot | `dot.triple` | | - /// | Quadruple dot | `dot.quad` | | - /// | Diaeresis | `diaer` | `¨` | - /// | Circle | `circle` | `∘` | - /// | Double acute | `acute.double` | `˝` | - /// | Caron | `caron` | `ˇ` | - /// | Right arrow | `arrow`, `->` | `→` | - /// | Left arrow | `arrow.l`, `<-` | `←` | - #[required] - pub accent: Accent, -} - -impl LayoutMath for AccentElem { - #[tracing::instrument(skip(ctx))] - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - ctx.style(ctx.style.with_cramped(true)); - let base = ctx.layout_fragment(self.base())?; - ctx.unstyle(); - - // Preserve class to preserve automatic spacing. - let base_class = base.class().unwrap_or(MathClass::Normal); - let base_attach = match &base { - MathFragment::Glyph(base) => { - attachment(ctx, base.id, base.italics_correction) - } - _ => (base.width() + base.italics_correction()) / 2.0, - }; - - // Forcing the accent to be at least as large as the base makes it too - // wide in many case. - let Accent(c) = self.accent(); - let glyph = GlyphFragment::new(ctx, *c, self.span()); - let short_fall = ACCENT_SHORT_FALL.scaled(ctx); - let variant = glyph.stretch_horizontal(ctx, base.width(), short_fall); - let accent = variant.frame; - let accent_attach = match variant.id { - Some(id) => attachment(ctx, id, variant.italics_correction), - None => accent.width() / 2.0, - }; - - // Descent is negative because the accent's ink bottom is above the - // baseline. Therefore, the default gap is the accent's negated descent - // minus the accent base height. Only if the base is very small, we need - // a larger gap so that the accent doesn't move too low. - let accent_base_height = scaled!(ctx, accent_base_height); - let gap = -accent.descent() - base.height().min(accent_base_height); - let size = Size::new(base.width(), accent.height() + gap + base.height()); - let accent_pos = Point::with_x(base_attach - accent_attach); - let base_pos = Point::with_y(accent.height() + gap); - let base_ascent = base.ascent(); - let baseline = base_pos.y + base.ascent(); - - let mut frame = Frame::soft(size); - frame.set_baseline(baseline); - frame.push_frame(accent_pos, accent); - frame.push_frame(base_pos, base.into_frame()); - ctx.push( - FrameFragment::new(ctx, frame) - .with_class(base_class) - .with_base_ascent(base_ascent), - ); - - Ok(()) - } -} - -/// The horizontal attachment position for the given glyph. -fn attachment(ctx: &MathContext, id: GlyphId, italics_correction: Abs) -> Abs { - ctx.table - .glyph_info - .and_then(|info| info.top_accent_attachments) - .and_then(|attachments| attachments.get(id)) - .map(|record| record.value.scaled(ctx)) - .unwrap_or_else(|| { - let advance = ctx.ttf.glyph_hor_advance(id).unwrap_or_default(); - (advance.scaled(ctx) + italics_correction) / 2.0 - }) -} - -/// An accent character. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Accent(char); - -impl Accent { - /// Normalize a character into an accent. - pub fn new(c: char) -> Self { - Self(Symbol::combining_accent(c).unwrap_or(c)) - } -} - -cast! { - Accent, - self => self.0.into_value(), - v: char => Self::new(v), - v: Content => match v.to::() { - Some(elem) => Value::Str(elem.text().clone().into()).cast()?, - None => bail!("expected text"), - }, -} diff --git a/crates/typst-library/src/math/align.rs b/crates/typst-library/src/math/align.rs deleted file mode 100644 index 4192e97b..00000000 --- a/crates/typst-library/src/math/align.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::math::*; - -/// A math alignment point: `&`, `&&`. -#[elem(title = "Alignment Point", LayoutMath)] -pub struct AlignPointElem {} - -impl LayoutMath for AlignPointElem { - #[tracing::instrument(skip(ctx))] - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - ctx.push(MathFragment::Align); - Ok(()) - } -} - -pub(super) struct AlignmentResult { - pub points: Vec, - pub width: Abs, -} - -/// Determine the position of the alignment points. -pub(super) fn alignments(rows: &[MathRow]) -> AlignmentResult { - let mut widths = Vec::::new(); - - let mut pending_width = Abs::zero(); - for row in rows { - let mut width = Abs::zero(); - let mut alignment_index = 0; - - for fragment in row.iter() { - if matches!(fragment, MathFragment::Align) { - if alignment_index < widths.len() { - widths[alignment_index].set_max(width); - } else { - widths.push(width.max(pending_width)); - } - width = Abs::zero(); - alignment_index += 1; - } else { - width += fragment.width(); - } - } - if widths.is_empty() { - pending_width.set_max(width); - } else if alignment_index < widths.len() { - widths[alignment_index].set_max(width); - } else { - widths.push(width.max(pending_width)); - } - } - - let mut points = widths; - for i in 1..points.len() { - let prev = points[i - 1]; - points[i] += prev; - } - AlignmentResult { - width: points.last().copied().unwrap_or(pending_width), - points, - } -} diff --git a/crates/typst-library/src/math/attach.rs b/crates/typst-library/src/math/attach.rs deleted file mode 100644 index 3e6b69f2..00000000 --- a/crates/typst-library/src/math/attach.rs +++ /dev/null @@ -1,440 +0,0 @@ -use super::*; - -/// A base with optional attachments. -/// -/// ```example -/// $ attach( -/// Pi, t: alpha, b: beta, -/// tl: 1, tr: 2+3, bl: 4+5, br: 6, -/// ) $ -/// ``` -#[elem(LayoutMath)] -pub struct AttachElem { - /// The base to which things are attached. - #[required] - pub base: Content, - - /// The top attachment, smartly positioned at top-right or above the base. - /// - /// You can wrap the base in `{limits()}` or `{scripts()}` to override the - /// smart positioning. - pub t: Option, - - /// The bottom attachment, smartly positioned at the bottom-right or below - /// the base. - /// - /// You can wrap the base in `{limits()}` or `{scripts()}` to override the - /// smart positioning. - pub b: Option, - - /// The top-left attachment (before the base). - pub tl: Option, - - /// The bottom-left attachment (before base). - pub bl: Option, - - /// The top-right attachment (after the base). - pub tr: Option, - - /// The bottom-right attachment (after the base). - pub br: Option, -} - -impl LayoutMath for AttachElem { - #[tracing::instrument(skip(ctx))] - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - type GetAttachment = fn(&AttachElem, styles: StyleChain) -> Option; - let layout_attachment = |ctx: &mut MathContext, getter: GetAttachment| { - getter(self, ctx.styles()) - .map(|elem| ctx.layout_fragment(&elem)) - .transpose() - }; - - let base = ctx.layout_fragment(self.base())?; - - ctx.style(ctx.style.for_superscript()); - let tl = layout_attachment(ctx, Self::tl)?; - let tr = layout_attachment(ctx, Self::tr)?; - let t = layout_attachment(ctx, Self::t)?; - ctx.unstyle(); - - ctx.style(ctx.style.for_subscript()); - let bl = layout_attachment(ctx, Self::bl)?; - let br = layout_attachment(ctx, Self::br)?; - let b = layout_attachment(ctx, Self::b)?; - ctx.unstyle(); - - let limits = base.limits().active(ctx); - let (t, tr) = if limits || tr.is_some() { (t, tr) } else { (None, t) }; - let (b, br) = if limits || br.is_some() { (b, br) } else { (None, b) }; - layout_attachments(ctx, base, [tl, t, tr, bl, b, br]) - } -} - -/// Grouped primes. -/// -/// ```example -/// $ a'''_b = a^'''_b $ -/// ``` -/// -/// # Syntax -/// This function has dedicated syntax: use apostrophes instead of primes. They -/// will automatically attach to the previous element, moving superscripts to -/// the next level. -#[elem(LayoutMath)] -pub struct PrimesElem { - /// The number of grouped primes. - #[required] - pub count: usize, -} - -impl LayoutMath for PrimesElem { - #[tracing::instrument(skip(ctx))] - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - match *self.count() { - count @ 1..=4 => { - let f = ctx.layout_fragment(&TextElem::packed(match count { - 1 => '′', - 2 => '″', - 3 => '‴', - 4 => '⁗', - _ => unreachable!(), - }))?; - ctx.push(f); - } - count => { - // Custom amount of primes - let prime = ctx.layout_fragment(&TextElem::packed('′'))?.into_frame(); - let width = prime.width() * (count + 1) as f64 / 2.0; - let mut frame = Frame::soft(Size::new(width, prime.height())); - frame.set_baseline(prime.ascent()); - - for i in 0..count { - frame.push_frame( - Point::new(prime.width() * (i as f64 / 2.0), Abs::zero()), - prime.clone(), - ) - } - ctx.push(FrameFragment::new(ctx, frame)); - } - } - Ok(()) - } -} - -/// Forces a base to display attachments as scripts. -/// -/// ```example -/// $ scripts(sum)_1^2 != sum_1^2 $ -/// ``` -#[elem(LayoutMath)] -pub struct ScriptsElem { - /// The base to attach the scripts to. - #[required] - pub body: Content, -} - -impl LayoutMath for ScriptsElem { - #[tracing::instrument(skip(ctx))] - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let mut fragment = ctx.layout_fragment(self.body())?; - fragment.set_limits(Limits::Never); - ctx.push(fragment); - Ok(()) - } -} - -/// Forces a base to display attachments as limits. -/// -/// ```example -/// $ limits(A)_1^2 != A_1^2 $ -/// ``` -#[elem(LayoutMath)] -pub struct LimitsElem { - /// The base to attach the limits to. - #[required] - pub body: Content, - - /// Whether to also force limits in inline equations. - /// - /// When applying limits globally (e.g., through a show rule), it is - /// typically a good idea to disable this. - #[default(true)] - pub inline: bool, -} - -impl LayoutMath for LimitsElem { - #[tracing::instrument(skip(ctx))] - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let mut fragment = ctx.layout_fragment(self.body())?; - fragment.set_limits(if self.inline(ctx.styles()) { - Limits::Always - } else { - Limits::Display - }); - ctx.push(fragment); - Ok(()) - } -} - -/// Describes in which situation a frame should use limits for attachments. -#[derive(Debug, Copy, Clone)] -pub enum Limits { - /// Always scripts. - Never, - /// Display limits only in `display` math. - Display, - /// Always limits. - Always, -} - -impl Limits { - /// The default limit configuration if the given character is the base. - pub fn for_char(c: char) -> Self { - match unicode_math_class::class(c) { - Some(MathClass::Large) => { - if is_integral_char(c) { - Limits::Never - } else { - Limits::Display - } - } - Some(MathClass::Relation) => Limits::Always, - _ => Limits::Never, - } - } - - /// Whether limits should be displayed in this context - pub fn active(&self, ctx: &MathContext) -> bool { - match self { - Self::Always => true, - Self::Display => ctx.style.size == MathSize::Display, - Self::Never => false, - } - } -} - -macro_rules! measure { - ($e: ident, $attr: ident) => { - $e.as_ref().map(|e| e.$attr()).unwrap_or_default() - }; -} - -/// Layout the attachments. -fn layout_attachments( - ctx: &mut MathContext, - base: MathFragment, - [tl, t, tr, bl, b, br]: [Option; 6], -) -> SourceResult<()> { - let (shift_up, shift_down) = - compute_shifts_up_and_down(ctx, &base, [&tl, &tr, &bl, &br]); - - let sup_delta = Abs::zero(); - let sub_delta = -base.italics_correction(); - let (base_width, base_ascent, base_descent) = - (base.width(), base.ascent(), base.descent()); - let base_class = base.class().unwrap_or(MathClass::Normal); - - let ascent = base_ascent - .max(shift_up + measure!(tr, ascent)) - .max(shift_up + measure!(tl, ascent)) - .max(shift_up + measure!(t, height)); - - let descent = base_descent - .max(shift_down + measure!(br, descent)) - .max(shift_down + measure!(bl, descent)) - .max(shift_down + measure!(b, height)); - - let pre_sup_width = measure!(tl, width); - let pre_sub_width = measure!(bl, width); - let pre_width_dif = pre_sup_width - pre_sub_width; // Could be negative. - let pre_width_max = pre_sup_width.max(pre_sub_width); - let post_max_width = - (sup_delta + measure!(tr, width)).max(sub_delta + measure!(br, width)); - - let (center_frame, base_offset) = attach_top_and_bottom(ctx, base, t, b); - let base_pos = - Point::new(sup_delta + pre_width_max, ascent - base_ascent - base_offset); - if [&tl, &bl, &tr, &br].iter().all(|&e| e.is_none()) { - ctx.push(FrameFragment::new(ctx, center_frame).with_class(base_class)); - return Ok(()); - } - - let mut frame = Frame::soft(Size::new( - pre_width_max + base_width + post_max_width + scaled!(ctx, space_after_script), - ascent + descent, - )); - frame.set_baseline(ascent); - frame.push_frame(base_pos, center_frame); - - if let Some(tl) = tl { - let pos = - Point::new(-pre_width_dif.min(Abs::zero()), ascent - shift_up - tl.ascent()); - frame.push_frame(pos, tl.into_frame()); - } - - if let Some(bl) = bl { - let pos = - Point::new(pre_width_dif.max(Abs::zero()), ascent + shift_down - bl.ascent()); - frame.push_frame(pos, bl.into_frame()); - } - - if let Some(tr) = tr { - let pos = Point::new( - sup_delta + pre_width_max + base_width, - ascent - shift_up - tr.ascent(), - ); - frame.push_frame(pos, tr.into_frame()); - } - - if let Some(br) = br { - let pos = Point::new( - sub_delta + pre_width_max + base_width, - ascent + shift_down - br.ascent(), - ); - frame.push_frame(pos, br.into_frame()); - } - - ctx.push(FrameFragment::new(ctx, frame).with_class(base_class)); - - Ok(()) -} - -fn attach_top_and_bottom( - ctx: &mut MathContext, - base: MathFragment, - t: Option, - b: Option, -) -> (Frame, Abs) { - let upper_gap_min = scaled!(ctx, upper_limit_gap_min); - let upper_rise_min = scaled!(ctx, upper_limit_baseline_rise_min); - let lower_gap_min = scaled!(ctx, lower_limit_gap_min); - let lower_drop_min = scaled!(ctx, lower_limit_baseline_drop_min); - - let mut base_offset = Abs::zero(); - let mut width = base.width(); - let mut height = base.height(); - - if let Some(t) = &t { - let top_gap = upper_gap_min.max(upper_rise_min - t.descent()); - width.set_max(t.width()); - height += t.height() + top_gap; - base_offset = top_gap + t.height(); - } - - if let Some(b) = &b { - let bottom_gap = lower_gap_min.max(lower_drop_min - b.ascent()); - width.set_max(b.width()); - height += b.height() + bottom_gap; - } - - let base_pos = Point::new((width - base.width()) / 2.0, base_offset); - let delta = base.italics_correction() / 2.0; - - let mut frame = Frame::soft(Size::new(width, height)); - frame.set_baseline(base_pos.y + base.ascent()); - frame.push_frame(base_pos, base.into_frame()); - - if let Some(t) = t { - let top_pos = Point::with_x((width - t.width()) / 2.0 + delta); - frame.push_frame(top_pos, t.into_frame()); - } - - if let Some(b) = b { - let bottom_pos = - Point::new((width - b.width()) / 2.0 - delta, height - b.height()); - frame.push_frame(bottom_pos, b.into_frame()); - } - - (frame, base_offset) -} - -fn compute_shifts_up_and_down( - ctx: &MathContext, - base: &MathFragment, - [tl, tr, bl, br]: [&Option; 4], -) -> (Abs, Abs) { - let sup_shift_up = if ctx.style.cramped { - scaled!(ctx, superscript_shift_up_cramped) - } else { - scaled!(ctx, superscript_shift_up) - }; - - let sup_bottom_min = scaled!(ctx, superscript_bottom_min); - let sup_bottom_max_with_sub = scaled!(ctx, superscript_bottom_max_with_subscript); - let sup_drop_max = scaled!(ctx, superscript_baseline_drop_max); - let gap_min = scaled!(ctx, sub_superscript_gap_min); - let sub_shift_down = scaled!(ctx, subscript_shift_down); - let sub_top_max = scaled!(ctx, subscript_top_max); - let sub_drop_min = scaled!(ctx, subscript_baseline_drop_min); - - let mut shift_up = Abs::zero(); - let mut shift_down = Abs::zero(); - let is_char_box = is_character_box(base); - - if tl.is_some() || tr.is_some() { - let ascent = match &base { - MathFragment::Frame(frame) => frame.base_ascent, - _ => base.ascent(), - }; - shift_up = shift_up - .max(sup_shift_up) - .max(if is_char_box { Abs::zero() } else { ascent - sup_drop_max }) - .max(sup_bottom_min + measure!(tl, descent)) - .max(sup_bottom_min + measure!(tr, descent)); - } - - if bl.is_some() || br.is_some() { - shift_down = shift_down - .max(sub_shift_down) - .max(if is_char_box { Abs::zero() } else { base.descent() + sub_drop_min }) - .max(measure!(bl, ascent) - sub_top_max) - .max(measure!(br, ascent) - sub_top_max); - } - - for (sup, sub) in [(tl, bl), (tr, br)] { - if let (Some(sup), Some(sub)) = (&sup, &sub) { - let sup_bottom = shift_up - sup.descent(); - let sub_top = sub.ascent() - shift_down; - let gap = sup_bottom - sub_top; - if gap >= gap_min { - continue; - } - - let increase = gap_min - gap; - let sup_only = - (sup_bottom_max_with_sub - sup_bottom).clamp(Abs::zero(), increase); - let rest = (increase - sup_only) / 2.0; - shift_up += sup_only + rest; - shift_down += rest; - } - } - - (shift_up, shift_down) -} - -/// Determines if the character is one of a variety of integral signs -fn is_integral_char(c: char) -> bool { - ('∫'..='∳').contains(&c) || ('⨋'..='⨜').contains(&c) -} - -/// Whether the fragment consists of a single character or atomic piece of text. -fn is_character_box(fragment: &MathFragment) -> bool { - match fragment { - MathFragment::Glyph(_) | MathFragment::Variant(_) => { - fragment.class() != Some(MathClass::Large) - } - MathFragment::Frame(fragment) => is_atomic_text_frame(&fragment.frame), - _ => false, - } -} - -/// Handles e.g. "sin", "log", "exp", "CustomOperator". -fn is_atomic_text_frame(frame: &Frame) -> bool { - // Meta information isn't visible or renderable, so we exclude it. - let mut iter = frame - .items() - .map(|(_, item)| item) - .filter(|item| !matches!(item, FrameItem::Meta(_, _))); - matches!(iter.next(), Some(FrameItem::Text(_))) && iter.next().is_none() -} diff --git a/crates/typst-library/src/math/cancel.rs b/crates/typst-library/src/math/cancel.rs deleted file mode 100644 index 455750f7..00000000 --- a/crates/typst-library/src/math/cancel.rs +++ /dev/null @@ -1,230 +0,0 @@ -use super::*; - -/// Displays a diagonal line over a part of an equation. -/// -/// This is commonly used to show the elimination of a term. -/// -/// # Example -/// ```example -/// >>> #set page(width: 140pt) -/// Here, we can simplify: -/// $ (a dot b dot cancel(x)) / -/// cancel(x) $ -/// ``` -#[elem(LayoutMath)] -pub struct CancelElem { - /// The content over which the line should be placed. - #[required] - pub body: Content, - - /// The length of the line, relative to the length of the diagonal spanning - /// the whole element being "cancelled". A value of `{100%}` would then have - /// the line span precisely the element's diagonal. - /// - /// ```example - /// >>> #set page(width: 140pt) - /// $ a + cancel(x, length: #200%) - /// - cancel(x, length: #200%) $ - /// ``` - #[default(Rel::new(Ratio::one(), Abs::pt(3.0).into()))] - pub length: Rel, - - /// Whether the cancel line should be inverted (flipped along the y-axis). - /// For the default angle setting, inverted means the cancel line - /// points to the top left instead of top right. - /// - /// ```example - /// >>> #set page(width: 140pt) - /// $ (a cancel((b + c), inverted: #true)) / - /// cancel(b + c, inverted: #true) $ - /// ``` - #[default(false)] - pub inverted: bool, - - /// Whether two opposing cancel lines should be drawn, forming a cross over - /// the element. Overrides `inverted`. - /// - /// ```example - /// >>> #set page(width: 140pt) - /// $ cancel(Pi, cross: #true) $ - /// ``` - #[default(false)] - pub cross: bool, - - /// How much to rotate the cancel line. - /// - /// - If `{auto}`, the line assumes the default angle; that is, along the - /// diagonal line of the content box. - /// - If given an angle, the line is rotated by that angle clockwise w.r.t - /// the y-axis. - /// - If given a function `angle => angle`, the line is rotated by the angle - /// returned by that function. The function receives the default angle as - /// its input. - /// - /// ```example - /// >>> #set page(width: 140pt) - /// $ cancel(Pi) - /// cancel(Pi, angle: #0deg) - /// cancel(Pi, angle: #45deg) - /// cancel(Pi, angle: #90deg) - /// cancel(1/(1+x), angle: #(a => a + 45deg)) - /// cancel(1/(1+x), angle: #(a => a + 90deg)) $ - /// ``` - pub angle: Smart, - - /// How to [stroke]($stroke) the cancel line. - /// - /// ```example - /// >>> #set page(width: 140pt) - /// $ cancel( - /// sum x, - /// stroke: #( - /// paint: red, - /// thickness: 1.5pt, - /// dash: "dashed", - /// ), - /// ) $ - /// ``` - #[resolve] - #[fold] - #[default(Stroke { - // Default stroke has 0.5pt for better visuals. - thickness: Smart::Custom(Abs::pt(0.5)), - ..Default::default() - })] - pub stroke: Stroke, -} - -impl LayoutMath for CancelElem { - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let body = ctx.layout_fragment(self.body())?; - // Use the same math class as the body, in order to preserve automatic spacing around it. - let body_class = body.class().unwrap_or(MathClass::Special); - let mut body = body.into_frame(); - - let styles = ctx.styles(); - let body_size = body.size(); - let span = self.span(); - let length = self.length(styles).resolve(styles); - - let stroke = self.stroke(styles).unwrap_or(FixedStroke { - paint: TextElem::fill_in(styles).as_decoration(), - ..Default::default() - }); - - let invert = self.inverted(styles); - let cross = self.cross(styles); - let angle = self.angle(styles); - - let invert_first_line = !cross && invert; - let first_line = draw_cancel_line( - ctx, - length, - stroke.clone(), - invert_first_line, - &angle, - body_size, - span, - )?; - - // The origin of our line is the very middle of the element. - let center = body_size.to_point() / 2.0; - body.push_frame(center, first_line); - - if cross { - // Draw the second line. - let second_line = - draw_cancel_line(ctx, length, stroke, true, &angle, body_size, span)?; - - body.push_frame(center, second_line); - } - - ctx.push(FrameFragment::new(ctx, body).with_class(body_class)); - - Ok(()) - } -} - -/// Defines the cancel line. -#[derive(Debug, Clone, PartialEq, Hash)] -pub enum CancelAngle { - Angle(Angle), - Func(Func), -} - -cast! { - CancelAngle, - self => match self { - Self::Angle(v) => v.into_value(), - Self::Func(v) => v.into_value() - }, - v: Angle => CancelAngle::Angle(v), - v: Func => CancelAngle::Func(v), -} - -/// Draws a cancel line. -fn draw_cancel_line( - ctx: &mut MathContext, - length_scale: Rel, - stroke: FixedStroke, - invert: bool, - angle: &Smart, - body_size: Size, - span: Span, -) -> SourceResult { - let default = default_angle(body_size); - let mut angle = match angle { - // Non specified angle defaults to the diagonal - Smart::Auto => default, - Smart::Custom(angle) => match angle { - // This specifies the absolute angle w.r.t y-axis clockwise. - CancelAngle::Angle(v) => *v, - // This specifies a function that takes the default angle as input. - CancelAngle::Func(func) => { - func.call_vt(ctx.vt, [default])?.cast().at(span)? - } - }, - }; - - // invert means flipping along the y-axis - if invert { - angle *= -1.0; - } - - // same as above, the default length is the diagonal of the body box. - let default_length = body_size.to_point().hypot(); - let length = length_scale.relative_to(default_length); - - // Draw a vertical line of length and rotate it by angle - let start = Point::new(Abs::zero(), length / 2.0); - let delta = Point::new(Abs::zero(), -length); - - let mut frame = Frame::soft(body_size); - frame.push(start, FrameItem::Shape(Geometry::Line(delta).stroked(stroke), span)); - - // Having the middle of the line at the origin is convenient here. - frame.transform(Transform::rotate(angle)); - Ok(frame) -} - -/// The default line angle for a body of the given size. -fn default_angle(body: Size) -> Angle { - // The default cancel line is the diagonal. - // We infer the default angle from - // the diagonal w.r.t to the body box. - // - // The returned angle is in the range of [0, Pi/2] - // - // Note that the angle is computed w.r.t to the y-axis - // - // B - // /| - // diagonal / | height - // / | - // / | - // O ---- - // width - let (width, height) = (body.x, body.y); - let default_angle = (width / height).atan(); // arctangent (in the range [0, Pi/2]) - Angle::rad(default_angle) -} diff --git a/crates/typst-library/src/math/class.rs b/crates/typst-library/src/math/class.rs deleted file mode 100644 index d2c5192d..00000000 --- a/crates/typst-library/src/math/class.rs +++ /dev/null @@ -1,38 +0,0 @@ -use super::*; - -/// Forced use of a certain math class. -/// -/// This is useful to treat certain symbols as if they were of a different -/// class, e.g. to make a symbol behave like a relation. -/// -/// # Example -/// ```example -/// #let loves = math.class( -/// "relation", -/// sym.suit.heart, -/// ) -/// -/// $x loves y and y loves 5$ -/// ``` -#[elem(LayoutMath)] -pub struct ClassElem { - /// The class to apply to the content. - #[required] - pub class: MathClass, - - /// The content to which the class is applied. - #[required] - pub body: Content, -} - -impl LayoutMath for ClassElem { - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - ctx.style(ctx.style.with_class(*self.class())); - let mut fragment = ctx.layout_fragment(self.body())?; - ctx.unstyle(); - - fragment.set_class(*self.class()); - ctx.push(fragment); - Ok(()) - } -} diff --git a/crates/typst-library/src/math/ctx.rs b/crates/typst-library/src/math/ctx.rs deleted file mode 100644 index 789bd332..00000000 --- a/crates/typst-library/src/math/ctx.rs +++ /dev/null @@ -1,335 +0,0 @@ -use comemo::Prehashed; -use ttf_parser::gsub::SubstitutionSubtable; -use ttf_parser::math::MathValue; -use typst::font::{FontStyle, FontWeight}; -use typst::model::realize; -use typst::syntax::is_newline; -use unicode_segmentation::UnicodeSegmentation; - -use super::*; -use crate::text::{tags, BottomEdge, BottomEdgeMetric, TopEdge, TopEdgeMetric}; - -macro_rules! scaled { - ($ctx:expr, text: $text:ident, display: $display:ident $(,)?) => { - match $ctx.style.size { - MathSize::Display => scaled!($ctx, $display), - _ => scaled!($ctx, $text), - } - }; - ($ctx:expr, $name:ident) => { - $ctx.constants.$name().scaled($ctx) - }; -} - -macro_rules! percent { - ($ctx:expr, $name:ident) => { - $ctx.constants.$name() as f64 / 100.0 - }; -} - -/// The context for math layout. -pub struct MathContext<'a, 'b, 'v> { - pub vt: &'v mut Vt<'b>, - pub regions: Regions<'static>, - pub font: &'a Font, - pub ttf: &'a ttf_parser::Face<'a>, - pub table: ttf_parser::math::Table<'a>, - pub constants: ttf_parser::math::Constants<'a>, - pub ssty_table: Option>, - pub glyphwise_tables: Option>>, - pub space_width: Em, - pub fragments: Vec, - pub local: Styles, - pub style: MathStyle, - pub size: Abs, - outer: StyleChain<'a>, - style_stack: Vec<(MathStyle, Abs)>, -} - -impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { - pub fn new( - vt: &'v mut Vt<'b>, - styles: StyleChain<'a>, - regions: Regions, - font: &'a Font, - block: bool, - ) -> Self { - let math_table = font.ttf().tables().math.unwrap(); - let gsub_table = font.ttf().tables().gsub; - let constants = math_table.constants.unwrap(); - - let ssty_table = gsub_table - .and_then(|gsub| { - gsub.features - .find(ttf_parser::Tag::from_bytes(b"ssty")) - .and_then(|feature| feature.lookup_indices.get(0)) - .and_then(|index| gsub.lookups.get(index)) - }) - .and_then(|ssty| ssty.subtables.get::(0)) - .and_then(|ssty| match ssty { - SubstitutionSubtable::Alternate(alt_glyphs) => Some(alt_glyphs), - _ => None, - }); - - let features = tags(styles); - let glyphwise_tables = gsub_table.map(|gsub| { - features - .into_iter() - .filter_map(|feature| GlyphwiseSubsts::new(gsub, feature)) - .collect() - }); - - let size = TextElem::size_in(styles); - let ttf = font.ttf(); - let space_width = ttf - .glyph_index(' ') - .and_then(|id| ttf.glyph_hor_advance(id)) - .map(|advance| font.to_em(advance)) - .unwrap_or(THICK); - - let variant = variant(styles); - Self { - vt, - regions: Regions::one(regions.base(), Axes::splat(false)), - font, - ttf: font.ttf(), - table: math_table, - constants, - ssty_table, - glyphwise_tables, - space_width, - fragments: vec![], - local: Styles::new(), - style: MathStyle { - variant: MathVariant::Serif, - size: if block { MathSize::Display } else { MathSize::Text }, - class: Smart::Auto, - cramped: false, - bold: variant.weight >= FontWeight::BOLD, - italic: match variant.style { - FontStyle::Normal => Smart::Auto, - FontStyle::Italic | FontStyle::Oblique => Smart::Custom(true), - }, - }, - size, - outer: styles, - style_stack: vec![], - } - } - - pub fn push(&mut self, fragment: impl Into) { - self.fragments.push(fragment.into()); - } - - pub fn extend(&mut self, fragments: Vec) { - self.fragments.extend(fragments); - } - - pub fn layout_fragment( - &mut self, - elem: &dyn LayoutMath, - ) -> SourceResult { - let row = self.layout_fragments(elem)?; - Ok(MathRow::new(row).into_fragment(self)) - } - - pub fn layout_fragments( - &mut self, - elem: &dyn LayoutMath, - ) -> SourceResult> { - let prev = std::mem::take(&mut self.fragments); - elem.layout_math(self)?; - Ok(std::mem::replace(&mut self.fragments, prev)) - } - - pub fn layout_row(&mut self, elem: &dyn LayoutMath) -> SourceResult { - let fragments = self.layout_fragments(elem)?; - Ok(MathRow::new(fragments)) - } - - pub fn layout_frame(&mut self, elem: &dyn LayoutMath) -> SourceResult { - Ok(self.layout_fragment(elem)?.into_frame()) - } - - pub fn layout_box(&mut self, boxed: &BoxElem) -> SourceResult { - Ok(boxed - .layout(self.vt, self.outer.chain(&self.local), self.regions)? - .into_frame()) - } - - pub fn layout_content(&mut self, content: &Content) -> SourceResult { - Ok(content - .layout(self.vt, self.outer.chain(&self.local), self.regions)? - .into_frame()) - } - - pub fn layout_text(&mut self, elem: &TextElem) -> SourceResult { - let text = elem.text(); - let span = elem.span(); - let mut chars = text.chars(); - let fragment = if let Some(mut glyph) = chars - .next() - .filter(|_| chars.next().is_none()) - .map(|c| self.style.styled_char(c)) - .and_then(|c| GlyphFragment::try_new(self, c, span)) - { - // A single letter that is available in the math font. - match self.style.size { - MathSize::Script => { - glyph.make_scriptsize(self); - } - MathSize::ScriptScript => { - glyph.make_scriptscriptsize(self); - } - _ => (), - } - - let class = self.style.class.as_custom().or(glyph.class); - if class == Some(MathClass::Large) { - let mut variant = if self.style.size == MathSize::Display { - let height = scaled!(self, display_operator_min_height); - glyph.stretch_vertical(self, height, Abs::zero()) - } else { - glyph.into_variant() - }; - // TeXbook p 155. Large operators are always vertically centered on the axis. - variant.center_on_axis(self); - variant.into() - } else { - glyph.into() - } - } else if text.chars().all(|c| c.is_ascii_digit() || c == '.') { - // Numbers aren't that difficult. - let mut fragments = vec![]; - for c in text.chars() { - let c = self.style.styled_char(c); - fragments.push(GlyphFragment::new(self, c, span).into()); - } - let frame = MathRow::new(fragments).into_frame(self); - FrameFragment::new(self, frame).into() - } else { - // Anything else is handled by Typst's standard text layout. - let mut style = self.style; - if self.style.italic == Smart::Auto { - style = style.with_italic(false); - } - let text: EcoString = text.chars().map(|c| style.styled_char(c)).collect(); - if text.contains(is_newline) { - let mut fragments = vec![]; - for (i, piece) in text.split(is_newline).enumerate() { - if i != 0 { - fragments.push(MathFragment::Linebreak); - } - if !piece.is_empty() { - fragments.push(self.layout_complex_text(piece, span)?.into()); - } - } - let mut frame = MathRow::new(fragments).into_frame(self); - let axis = scaled!(self, axis_height); - frame.set_baseline(frame.height() / 2.0 + axis); - FrameFragment::new(self, frame).into() - } else { - self.layout_complex_text(&text, span)?.into() - } - }; - Ok(fragment) - } - - pub fn layout_complex_text( - &mut self, - text: &str, - span: Span, - ) -> SourceResult { - let spaced = text.graphemes(true).nth(1).is_some(); - let elem = TextElem::packed(text) - .styled(TextElem::set_top_edge(TopEdge::Metric(TopEdgeMetric::Bounds))) - .styled(TextElem::set_bottom_edge(BottomEdge::Metric( - BottomEdgeMetric::Bounds, - ))) - .spanned(span); - - // There isn't a natural width for a paragraph in a math environment; - // because it will be placed somewhere probably not at the left margin - // it will overflow. So emulate an `hbox` instead and allow the paragraph - // to extend as far as needed. - let span = elem.span(); - let frame = ParElem::new(vec![Prehashed::new(elem)]) - .spanned(span) - .layout( - self.vt, - self.outer.chain(&self.local), - false, - Size::splat(Abs::inf()), - false, - )? - .into_frame(); - - Ok(FrameFragment::new(self, frame) - .with_class(MathClass::Alphabetic) - .with_spaced(spaced)) - } - - pub fn styles(&self) -> StyleChain { - self.outer.chain(&self.local) - } - - pub fn realize(&mut self, content: &Content) -> SourceResult> { - realize(self.vt, content, self.outer.chain(&self.local)) - } - - pub fn style(&mut self, style: MathStyle) { - self.style_stack.push((self.style, self.size)); - let base_size = TextElem::size_in(self.styles()) / self.style.size.factor(self); - self.size = base_size * style.size.factor(self); - self.local.set(TextElem::set_size(TextSize(self.size.into()))); - self.local - .set(TextElem::set_style(if style.italic == Smart::Custom(true) { - FontStyle::Italic - } else { - FontStyle::Normal - })); - self.local.set(TextElem::set_weight(if style.bold { - FontWeight::BOLD - } else { - // The normal weight is what we started with. - // It's 400 for CM Regular, 450 for CM Book. - self.font.info().variant.weight - })); - self.style = style; - } - - pub fn unstyle(&mut self) { - (self.style, self.size) = self.style_stack.pop().unwrap(); - self.local.unset(); - self.local.unset(); - self.local.unset(); - } -} - -pub(super) trait Scaled { - fn scaled(self, ctx: &MathContext) -> Abs; -} - -impl Scaled for i16 { - fn scaled(self, ctx: &MathContext) -> Abs { - ctx.font.to_em(self).scaled(ctx) - } -} - -impl Scaled for u16 { - fn scaled(self, ctx: &MathContext) -> Abs { - ctx.font.to_em(self).scaled(ctx) - } -} - -impl Scaled for Em { - fn scaled(self, ctx: &MathContext) -> Abs { - self.at(ctx.size) - } -} - -impl Scaled for MathValue<'_> { - fn scaled(self, ctx: &MathContext) -> Abs { - self.value.scaled(ctx) - } -} diff --git a/crates/typst-library/src/math/frac.rs b/crates/typst-library/src/math/frac.rs deleted file mode 100644 index c3014178..00000000 --- a/crates/typst-library/src/math/frac.rs +++ /dev/null @@ -1,157 +0,0 @@ -use super::*; - -const FRAC_AROUND: Em = Em::new(0.1); - -/// A mathematical fraction. -/// -/// # Example -/// ```example -/// $ 1/2 < (x+1)/2 $ -/// $ ((x+1)) / 2 = frac(a, b) $ -/// ``` -/// -/// # Syntax -/// This function also has dedicated syntax: Use a slash to turn neighbouring -/// expressions into a fraction. Multiple atoms can be grouped into a single -/// expression using round grouping parenthesis. Such parentheses are removed -/// from the output, but you can nest multiple to force them. -#[elem(title = "Fraction", LayoutMath)] -pub struct FracElem { - /// The fraction's numerator. - #[required] - pub num: Content, - - /// The fraction's denominator. - #[required] - pub denom: Content, -} - -impl LayoutMath for FracElem { - #[tracing::instrument(skip(ctx))] - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - layout(ctx, self.num(), std::slice::from_ref(self.denom()), false, self.span()) - } -} - -/// A binomial expression. -/// -/// # Example -/// ```example -/// $ binom(n, k) $ -/// $ binom(n, k_1, k_2, k_3, ..., k_m) $ -/// ``` -#[elem(title = "Binomial", LayoutMath)] -pub struct BinomElem { - /// The binomial's upper index. - #[required] - pub upper: Content, - - /// The binomial's lower index. - #[required] - #[variadic] - #[parse( - let values = args.all::>()?; - if values.is_empty() { - // Prevents one element binomials - bail!(args.span, "missing argument: lower"); - } - values.into_iter().map(|spanned| spanned.v.display()).collect() - )] - pub lower: Vec, -} - -impl LayoutMath for BinomElem { - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - layout(ctx, self.upper(), self.lower(), true, self.span()) - } -} - -/// Layout a fraction or binomial. -fn layout( - ctx: &mut MathContext, - num: &Content, - denom: &[Content], - binom: bool, - span: Span, -) -> SourceResult<()> { - let short_fall = DELIM_SHORT_FALL.scaled(ctx); - let axis = scaled!(ctx, axis_height); - let thickness = scaled!(ctx, fraction_rule_thickness); - let shift_up = scaled!( - ctx, - text: fraction_numerator_shift_up, - display: fraction_numerator_display_style_shift_up, - ); - let shift_down = scaled!( - ctx, - text: fraction_denominator_shift_down, - display: fraction_denominator_display_style_shift_down, - ); - let num_min = scaled!( - ctx, - text: fraction_numerator_gap_min, - display: fraction_num_display_style_gap_min, - ); - let denom_min = scaled!( - ctx, - text: fraction_denominator_gap_min, - display: fraction_denom_display_style_gap_min, - ); - - ctx.style(ctx.style.for_numerator()); - let num = ctx.layout_frame(num)?; - ctx.unstyle(); - - ctx.style(ctx.style.for_denominator()); - let denom = ctx.layout_frame(&Content::sequence( - // Add a comma between each element. - denom.iter().flat_map(|a| [TextElem::packed(','), a.clone()]).skip(1), - ))?; - ctx.unstyle(); - - let around = FRAC_AROUND.scaled(ctx); - let num_gap = (shift_up - axis - num.descent()).max(num_min + thickness / 2.0); - let denom_gap = (shift_down + axis - denom.ascent()).max(denom_min + thickness / 2.0); - - let line_width = num.width().max(denom.width()); - let width = line_width + 2.0 * around; - let height = num.height() + num_gap + thickness + denom_gap + denom.height(); - let size = Size::new(width, height); - let num_pos = Point::with_x((width - num.width()) / 2.0); - let line_pos = - Point::new((width - line_width) / 2.0, num.height() + num_gap + thickness / 2.0); - let denom_pos = Point::new((width - denom.width()) / 2.0, height - denom.height()); - let baseline = line_pos.y + axis; - - let mut frame = Frame::soft(size); - frame.set_baseline(baseline); - frame.push_frame(num_pos, num); - frame.push_frame(denom_pos, denom); - - if binom { - let mut left = - GlyphFragment::new(ctx, '(', span).stretch_vertical(ctx, height, short_fall); - left.center_on_axis(ctx); - ctx.push(left); - ctx.push(FrameFragment::new(ctx, frame)); - let mut right = - GlyphFragment::new(ctx, ')', span).stretch_vertical(ctx, height, short_fall); - right.center_on_axis(ctx); - ctx.push(right); - } else { - frame.push( - line_pos, - FrameItem::Shape( - Geometry::Line(Point::with_x(line_width)).stroked(FixedStroke { - paint: TextElem::fill_in(ctx.styles()).as_decoration(), - thickness, - ..FixedStroke::default() - }), - span, - ), - ); - ctx.push(FrameFragment::new(ctx, frame)); - } - - Ok(()) -} diff --git a/crates/typst-library/src/math/fragment.rs b/crates/typst-library/src/math/fragment.rs deleted file mode 100644 index 76ee2512..00000000 --- a/crates/typst-library/src/math/fragment.rs +++ /dev/null @@ -1,513 +0,0 @@ -use rustybuzz::Feature; -use ttf_parser::gsub::{ - AlternateSet, AlternateSubstitution, SingleSubstitution, SubstitutionSubtable, -}; -use ttf_parser::opentype_layout::LayoutTable; - -use super::*; - -#[derive(Debug, Clone)] -pub enum MathFragment { - Glyph(GlyphFragment), - Variant(VariantFragment), - Frame(FrameFragment), - Spacing(Abs), - Space(Abs), - Linebreak, - Align, -} - -impl MathFragment { - pub fn size(&self) -> Size { - Size::new(self.width(), self.height()) - } - - pub fn width(&self) -> Abs { - match self { - Self::Glyph(glyph) => glyph.width, - Self::Variant(variant) => variant.frame.width(), - Self::Frame(fragment) => fragment.frame.width(), - Self::Spacing(amount) => *amount, - Self::Space(amount) => *amount, - _ => Abs::zero(), - } - } - - pub fn height(&self) -> Abs { - match self { - Self::Glyph(glyph) => glyph.height(), - Self::Variant(variant) => variant.frame.height(), - Self::Frame(fragment) => fragment.frame.height(), - _ => Abs::zero(), - } - } - - pub fn ascent(&self) -> Abs { - match self { - Self::Glyph(glyph) => glyph.ascent, - Self::Variant(variant) => variant.frame.ascent(), - Self::Frame(fragment) => fragment.frame.baseline(), - _ => Abs::zero(), - } - } - - pub fn descent(&self) -> Abs { - match self { - Self::Glyph(glyph) => glyph.descent, - Self::Variant(variant) => variant.frame.descent(), - Self::Frame(fragment) => fragment.frame.descent(), - _ => Abs::zero(), - } - } - - pub fn class(&self) -> Option { - self.style().and_then(|style| style.class.as_custom()).or(match self { - Self::Glyph(glyph) => glyph.class, - Self::Variant(variant) => variant.class, - Self::Frame(fragment) => Some(fragment.class), - _ => None, - }) - } - - pub fn style(&self) -> Option { - match self { - Self::Glyph(glyph) => Some(glyph.style), - Self::Variant(variant) => Some(variant.style), - Self::Frame(fragment) => Some(fragment.style), - _ => None, - } - } - - pub fn font_size(&self) -> Option { - match self { - Self::Glyph(glyph) => Some(glyph.font_size), - Self::Variant(variant) => Some(variant.font_size), - Self::Frame(fragment) => Some(fragment.font_size), - _ => None, - } - } - - pub fn set_class(&mut self, class: MathClass) { - macro_rules! set_style_class { - ($fragment:ident) => { - if $fragment.style.class.is_custom() { - $fragment.style.class = Smart::Custom(class); - } - }; - } - - match self { - Self::Glyph(glyph) => { - glyph.class = Some(class); - set_style_class!(glyph); - } - Self::Variant(variant) => { - variant.class = Some(class); - set_style_class!(variant); - } - Self::Frame(fragment) => { - fragment.class = class; - set_style_class!(fragment); - } - _ => {} - } - } - - pub fn set_limits(&mut self, limits: Limits) { - match self { - Self::Glyph(glyph) => glyph.limits = limits, - Self::Variant(variant) => variant.limits = limits, - Self::Frame(fragment) => fragment.limits = limits, - _ => {} - } - } - - pub fn is_spaced(&self) -> bool { - match self { - MathFragment::Frame(frame) => { - match self.style().and_then(|style| style.class.as_custom()) { - Some(MathClass::Fence) => true, - Some(_) => false, - None => frame.spaced, - } - } - _ => self.class() == Some(MathClass::Fence), - } - } - - pub fn italics_correction(&self) -> Abs { - match self { - Self::Glyph(glyph) => glyph.italics_correction, - Self::Variant(variant) => variant.italics_correction, - _ => Abs::zero(), - } - } - - pub fn into_frame(self) -> Frame { - match self { - Self::Glyph(glyph) => glyph.into_frame(), - Self::Variant(variant) => variant.frame, - Self::Frame(fragment) => fragment.frame, - _ => Frame::soft(self.size()), - } - } - - pub fn limits(&self) -> Limits { - match self { - MathFragment::Glyph(glyph) => glyph.limits, - MathFragment::Variant(variant) => variant.limits, - MathFragment::Frame(fragment) => fragment.limits, - _ => Limits::Never, - } - } -} - -impl From for MathFragment { - fn from(glyph: GlyphFragment) -> Self { - Self::Glyph(glyph) - } -} - -impl From for MathFragment { - fn from(variant: VariantFragment) -> Self { - Self::Variant(variant) - } -} - -impl From for MathFragment { - fn from(fragment: FrameFragment) -> Self { - Self::Frame(fragment) - } -} - -#[derive(Clone)] -pub struct GlyphFragment { - pub id: GlyphId, - pub c: char, - pub font: Font, - pub lang: Lang, - pub fill: Paint, - pub shift: Abs, - pub width: Abs, - pub ascent: Abs, - pub descent: Abs, - pub italics_correction: Abs, - pub style: MathStyle, - pub font_size: Abs, - pub class: Option, - pub span: Span, - pub meta: SmallVec<[Meta; 1]>, - pub limits: Limits, -} - -impl GlyphFragment { - pub fn new(ctx: &MathContext, c: char, span: Span) -> Self { - let id = ctx.ttf.glyph_index(c).unwrap_or_default(); - let id = Self::adjust_glyph_index(ctx, id); - Self::with_id(ctx, c, id, span) - } - - pub fn try_new(ctx: &MathContext, c: char, span: Span) -> Option { - let c = ctx.style.styled_char(c); - let id = ctx.ttf.glyph_index(c)?; - let id = Self::adjust_glyph_index(ctx, id); - Some(Self::with_id(ctx, c, id, span)) - } - - pub fn with_id(ctx: &MathContext, c: char, id: GlyphId, span: Span) -> Self { - let class = match c { - ':' => Some(MathClass::Relation), - '.' | '/' | '⋯' | '⋱' | '⋰' | '⋮' => Some(MathClass::Normal), - _ => unicode_math_class::class(c), - }; - let mut fragment = Self { - id, - c, - font: ctx.font.clone(), - lang: TextElem::lang_in(ctx.styles()), - fill: TextElem::fill_in(ctx.styles()).as_decoration(), - shift: TextElem::baseline_in(ctx.styles()), - style: ctx.style, - font_size: ctx.size, - width: Abs::zero(), - ascent: Abs::zero(), - descent: Abs::zero(), - limits: Limits::for_char(c), - italics_correction: Abs::zero(), - class, - span, - meta: MetaElem::data_in(ctx.styles()), - }; - fragment.set_id(ctx, id); - fragment - } - - /// Apply GSUB substitutions. - fn adjust_glyph_index(ctx: &MathContext, id: GlyphId) -> GlyphId { - if let Some(glyphwise_tables) = &ctx.glyphwise_tables { - glyphwise_tables.iter().fold(id, |id, table| table.apply(id)) - } else { - id - } - } - - /// Sets element id and boxes in appropriate way without changing other - /// styles. This is used to replace the glyph with a stretch variant. - pub fn set_id(&mut self, ctx: &MathContext, id: GlyphId) { - let advance = ctx.ttf.glyph_hor_advance(id).unwrap_or_default(); - let italics = italics_correction(ctx, id).unwrap_or_default(); - let bbox = ctx.ttf.glyph_bounding_box(id).unwrap_or(Rect { - x_min: 0, - y_min: 0, - x_max: 0, - y_max: 0, - }); - - let mut width = advance.scaled(ctx); - if !is_extended_shape(ctx, id) { - width += italics; - } - - self.id = id; - self.width = width; - self.ascent = bbox.y_max.scaled(ctx); - self.descent = -bbox.y_min.scaled(ctx); - self.italics_correction = italics; - } - - pub fn height(&self) -> Abs { - self.ascent + self.descent - } - - pub fn into_variant(self) -> VariantFragment { - VariantFragment { - c: self.c, - id: Some(self.id), - style: self.style, - font_size: self.font_size, - italics_correction: self.italics_correction, - class: self.class, - span: self.span, - limits: self.limits, - frame: self.into_frame(), - } - } - - pub fn into_frame(self) -> Frame { - let item = TextItem { - font: self.font.clone(), - size: self.font_size, - fill: self.fill, - lang: self.lang, - text: self.c.into(), - glyphs: vec![Glyph { - id: self.id.0, - x_advance: Em::from_length(self.width, self.font_size), - x_offset: Em::zero(), - range: 0..self.c.len_utf8() as u16, - span: (self.span, 0), - }], - }; - let size = Size::new(self.width, self.ascent + self.descent); - let mut frame = Frame::soft(size); - frame.set_baseline(self.ascent); - frame.push(Point::with_y(self.ascent + self.shift), FrameItem::Text(item)); - frame.meta_iter(self.meta); - frame - } - - pub fn make_scriptsize(&mut self, ctx: &MathContext) { - let alt_id = - script_alternatives(ctx, self.id).and_then(|alts| alts.alternates.get(0)); - - if let Some(alt_id) = alt_id { - self.set_id(ctx, alt_id); - } - } - - pub fn make_scriptscriptsize(&mut self, ctx: &MathContext) { - let alts = script_alternatives(ctx, self.id); - let alt_id = alts - .and_then(|alts| alts.alternates.get(1).or_else(|| alts.alternates.get(0))); - - if let Some(alt_id) = alt_id { - self.set_id(ctx, alt_id); - } - } -} - -impl Debug for GlyphFragment { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "GlyphFragment({:?})", self.c) - } -} - -#[derive(Clone)] -pub struct VariantFragment { - pub c: char, - pub id: Option, - pub italics_correction: Abs, - pub frame: Frame, - pub style: MathStyle, - pub font_size: Abs, - pub class: Option, - pub span: Span, - pub limits: Limits, -} - -impl VariantFragment { - /// Vertically adjust the fragment's frame so that it is centered - /// on the axis. - pub fn center_on_axis(&mut self, ctx: &MathContext) { - let h = self.frame.height(); - self.frame.set_baseline(h / 2.0 + scaled!(ctx, axis_height)); - } -} - -impl Debug for VariantFragment { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "VariantFragment({:?})", self.c) - } -} - -#[derive(Debug, Clone)] -pub struct FrameFragment { - pub frame: Frame, - pub style: MathStyle, - pub font_size: Abs, - pub class: MathClass, - pub limits: Limits, - pub spaced: bool, - pub base_ascent: Abs, -} - -impl FrameFragment { - pub fn new(ctx: &MathContext, mut frame: Frame) -> Self { - let base_ascent = frame.ascent(); - frame.meta(ctx.styles(), false); - Self { - frame, - font_size: ctx.size, - style: ctx.style, - class: MathClass::Normal, - limits: Limits::Never, - spaced: false, - base_ascent, - } - } - - pub fn with_class(self, class: MathClass) -> Self { - Self { class, ..self } - } - - pub fn with_limits(self, limits: Limits) -> Self { - Self { limits, ..self } - } - - pub fn with_spaced(self, spaced: bool) -> Self { - Self { spaced, ..self } - } - - pub fn with_base_ascent(self, base_ascent: Abs) -> Self { - Self { base_ascent, ..self } - } -} - -/// Look up the italics correction for a glyph. -fn italics_correction(ctx: &MathContext, id: GlyphId) -> Option { - Some(ctx.table.glyph_info?.italic_corrections?.get(id)?.scaled(ctx)) -} - -/// Look up the script/scriptscript alternates for a glyph -fn script_alternatives<'a>( - ctx: &MathContext<'a, '_, '_>, - id: GlyphId, -) -> Option> { - ctx.ssty_table.and_then(|ssty| { - ssty.coverage.get(id).and_then(|index| ssty.alternate_sets.get(index)) - }) -} - -/// Look up the italics correction for a glyph. -fn is_extended_shape(ctx: &MathContext, id: GlyphId) -> bool { - ctx.table - .glyph_info - .and_then(|info| info.extended_shapes) - .and_then(|info| info.get(id)) - .is_some() -} - -/// Look up a kerning value at a specific corner and height. -/// -/// This can be integrated once we've found a font that actually provides this -/// data. -#[allow(unused)] -fn kern_at_height( - ctx: &MathContext, - id: GlyphId, - corner: Corner, - height: Abs, -) -> Option { - let kerns = ctx.table.glyph_info?.kern_infos?.get(id)?; - let kern = match corner { - Corner::TopLeft => kerns.top_left, - Corner::TopRight => kerns.top_right, - Corner::BottomRight => kerns.bottom_right, - Corner::BottomLeft => kerns.bottom_left, - }?; - - let mut i = 0; - while i < kern.count() && height > kern.height(i)?.scaled(ctx) { - i += 1; - } - - Some(kern.kern(i)?.scaled(ctx)) -} - -/// An OpenType substitution table that is applicable to glyph-wise substitutions. -pub enum GlyphwiseSubsts<'a> { - Single(SingleSubstitution<'a>), - Alternate(AlternateSubstitution<'a>, u32), -} - -impl<'a> GlyphwiseSubsts<'a> { - pub fn new(gsub: LayoutTable<'a>, feature: Feature) -> Option { - let table = gsub - .features - .find(ttf_parser::Tag(feature.tag.0)) - .and_then(|feature| feature.lookup_indices.get(0)) - .and_then(|index| gsub.lookups.get(index))?; - let table = table.subtables.get::(0)?; - match table { - SubstitutionSubtable::Single(single_glyphs) => { - Some(Self::Single(single_glyphs)) - } - SubstitutionSubtable::Alternate(alt_glyphs) => { - Some(Self::Alternate(alt_glyphs, feature.value)) - } - _ => None, - } - } - - pub fn try_apply(&self, glyph_id: GlyphId) -> Option { - match self { - Self::Single(single) => match single { - SingleSubstitution::Format1 { coverage, delta } => coverage - .get(glyph_id) - .map(|_| GlyphId(glyph_id.0.wrapping_add(*delta as u16))), - SingleSubstitution::Format2 { coverage, substitutes } => { - coverage.get(glyph_id).and_then(|idx| substitutes.get(idx)) - } - }, - Self::Alternate(alternate, value) => alternate - .coverage - .get(glyph_id) - .and_then(|idx| alternate.alternate_sets.get(idx)) - .and_then(|set| set.alternates.get(*value as u16)), - } - } - - pub fn apply(&self, glyph_id: GlyphId) -> GlyphId { - self.try_apply(glyph_id).unwrap_or(glyph_id) - } -} diff --git a/crates/typst-library/src/math/lr.rs b/crates/typst-library/src/math/lr.rs deleted file mode 100644 index 39143620..00000000 --- a/crates/typst-library/src/math/lr.rs +++ /dev/null @@ -1,195 +0,0 @@ -use super::*; - -/// How much less high scaled delimiters can be than what they wrap. -pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1); - -/// Scales delimiters. -/// -/// While matched delimiters scale by default, this can be used to scale -/// unmatched delimiters and to control the delimiter scaling more precisely. -#[elem(title = "Left/Right", LayoutMath)] -pub struct LrElem { - /// The size of the brackets, relative to the height of the wrapped content. - pub size: Smart>, - - /// The delimited content, including the delimiters. - #[required] - #[parse( - let mut body = Content::empty(); - for (i, arg) in args.all::()?.into_iter().enumerate() { - if i > 0 { - body += TextElem::packed(','); - } - body += arg; - } - body - )] - pub body: Content, -} - -impl LayoutMath for LrElem { - #[tracing::instrument(skip(ctx))] - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let mut body = self.body(); - if let Some(elem) = body.to::() { - if elem.size(ctx.styles()).is_auto() { - body = elem.body(); - } - } - - let mut fragments = ctx.layout_fragments(body)?; - let axis = scaled!(ctx, axis_height); - let max_extent = fragments - .iter() - .map(|fragment| (fragment.ascent() - axis).max(fragment.descent() + axis)) - .max() - .unwrap_or_default(); - - let height = self - .size(ctx.styles()) - .unwrap_or(Rel::one()) - .resolve(ctx.styles()) - .relative_to(2.0 * max_extent); - - match fragments.as_mut_slice() { - [one] => scale(ctx, one, height, None), - [first, .., last] => { - scale(ctx, first, height, Some(MathClass::Opening)); - scale(ctx, last, height, Some(MathClass::Closing)); - } - _ => {} - } - - ctx.extend(fragments); - - Ok(()) - } -} - -/// Scale a math fragment to a height. -fn scale( - ctx: &mut MathContext, - fragment: &mut MathFragment, - height: Abs, - apply: Option, -) { - if matches!( - fragment.class(), - Some(MathClass::Opening | MathClass::Closing | MathClass::Fence) - ) { - let glyph = match fragment { - MathFragment::Glyph(glyph) => glyph.clone(), - MathFragment::Variant(variant) => { - GlyphFragment::new(ctx, variant.c, variant.span) - } - _ => return, - }; - - let short_fall = DELIM_SHORT_FALL.scaled(ctx); - let mut stretched = glyph.stretch_vertical(ctx, height, short_fall); - stretched.center_on_axis(ctx); - - *fragment = MathFragment::Variant(stretched); - if let Some(class) = apply { - fragment.set_class(class); - } - } -} - -/// Floors an expression. -/// -/// ```example -/// $ floor(x/2) $ -/// ``` -#[func] -pub fn floor( - /// The size of the brackets, relative to the height of the wrapped content. - #[named] - size: Option>>, - /// The expression to floor. - body: Content, -) -> Content { - delimited(body, '⌊', '⌋', size) -} - -/// Ceils an expression. -/// -/// ```example -/// $ ceil(x/2) $ -/// ``` -#[func] -pub fn ceil( - /// The size of the brackets, relative to the height of the wrapped content. - #[named] - size: Option>>, - /// The expression to ceil. - body: Content, -) -> Content { - delimited(body, '⌈', '⌉', size) -} - -/// Rounds an expression. -/// -/// ```example -/// $ round(x/2) $ -/// ``` -#[func] -pub fn round( - /// The size of the brackets, relative to the height of the wrapped content. - #[named] - size: Option>>, - /// The expression to round. - body: Content, -) -> Content { - delimited(body, '⌊', '⌉', size) -} - -/// Takes the absolute value of an expression. -/// -/// ```example -/// $ abs(x/2) $ -/// ``` -#[func] -pub fn abs( - /// The size of the brackets, relative to the height of the wrapped content. - #[named] - size: Option>>, - /// The expression to take the absolute value of. - body: Content, -) -> Content { - delimited(body, '|', '|', size) -} - -/// Takes the norm of an expression. -/// -/// ```example -/// $ norm(x/2) $ -/// ``` -#[func] -pub fn norm( - /// The size of the brackets, relative to the height of the wrapped content. - #[named] - size: Option>>, - /// The expression to take the norm of. - body: Content, -) -> Content { - delimited(body, '‖', '‖', size) -} - -fn delimited( - body: Content, - left: char, - right: char, - size: Option>>, -) -> Content { - let mut elem = LrElem::new(Content::sequence([ - TextElem::packed(left), - body, - TextElem::packed(right), - ])); - // Push size only if size is provided - if let Some(size) = size { - elem.push_size(size); - } - elem.pack() -} diff --git a/crates/typst-library/src/math/matrix.rs b/crates/typst-library/src/math/matrix.rs deleted file mode 100644 index b5d21ed6..00000000 --- a/crates/typst-library/src/math/matrix.rs +++ /dev/null @@ -1,655 +0,0 @@ -use super::*; - -const DEFAULT_ROW_GAP: Em = Em::new(0.5); -const DEFAULT_COL_GAP: Em = Em::new(0.5); -const VERTICAL_PADDING: Ratio = Ratio::new(0.1); - -const DEFAULT_STROKE_THICKNESS: Em = Em::new(0.05); - -/// A column vector. -/// -/// Content in the vector's elements can be aligned with the `&` symbol. -/// -/// # Example -/// ```example -/// $ vec(a, b, c) dot vec(1, 2, 3) -/// = a + 2b + 3c $ -/// ``` -#[elem(title = "Vector", LayoutMath)] -pub struct VecElem { - /// The delimiter to use. - /// - /// ```example - /// #set math.vec(delim: "[") - /// $ vec(1, 2) $ - /// ``` - #[default(Some(Delimiter::Paren))] - pub delim: Option, - - /// The gap between elements. - /// - /// ```example - /// #set math.vec(gap: 1em) - /// $ vec(1, 2) $ - /// ``` - #[resolve] - #[default(DEFAULT_ROW_GAP.into())] - pub gap: Rel, - - /// The elements of the vector. - #[variadic] - pub children: Vec, -} - -impl LayoutMath for VecElem { - #[tracing::instrument(skip(ctx))] - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let delim = self.delim(ctx.styles()); - let frame = layout_vec_body( - ctx, - self.children(), - FixedAlign::Center, - self.gap(ctx.styles()), - )?; - layout_delimiters( - ctx, - frame, - delim.map(Delimiter::open), - delim.map(Delimiter::close), - self.span(), - ) - } -} - -/// A matrix. -/// -/// The elements of a row should be separated by commas, while the rows -/// themselves should be separated by semicolons. The semicolon syntax merges -/// preceding arguments separated by commas into an array. You can also use this -/// special syntax of math function calls to define custom functions that take -/// 2D data. -/// -/// Content in cells that are in the same row can be aligned with the `&` symbol. -/// -/// # Example -/// ```example -/// $ mat( -/// 1, 2, ..., 10; -/// 2, 2, ..., 10; -/// dots.v, dots.v, dots.down, dots.v; -/// 10, 10, ..., 10; -/// ) $ -/// ``` -#[elem(title = "Matrix", LayoutMath)] -pub struct MatElem { - /// The delimiter to use. - /// - /// ```example - /// #set math.mat(delim: "[") - /// $ mat(1, 2; 3, 4) $ - /// ``` - #[default(Some(Delimiter::Paren))] - pub delim: Option, - - /// Draws augmentation lines in a matrix. - /// - /// - `{none}`: No lines are drawn. - /// - A single number: A vertical augmentation line is drawn - /// after the specified column number. Negative numbers start from the end. - /// - A dictionary: With a dictionary, multiple augmentation lines can be - /// drawn both horizontally and vertically. Additionally, the style of the - /// lines can be set. The dictionary can contain the following keys: - /// - `hline`: The offsets at which horizontal lines should be drawn. - /// For example, an offset of `2` would result in a horizontal line - /// being drawn after the second row of the matrix. Accepts either an - /// integer for a single line, or an array of integers - /// for multiple lines. Like for a single number, negative numbers start from the end. - /// - `vline`: The offsets at which vertical lines should be drawn. - /// For example, an offset of `2` would result in a vertical line being - /// drawn after the second column of the matrix. Accepts either an - /// integer for a single line, or an array of integers - /// for multiple lines. Like for a single number, negative numbers start from the end. - /// - `stroke`: How to [stroke]($stroke) the line. If set to `{auto}`, - /// takes on a thickness of 0.05em and square line caps. - /// - /// ```example - /// $ mat(1, 0, 1; 0, 1, 2; augment: #2) $ - /// // Equivalent to: - /// $ mat(1, 0, 1; 0, 1, 2; augment: #(-1)) $ - /// ``` - /// - /// ```example - /// $ mat(0, 0, 0; 1, 1, 1; augment: #(hline: 1, stroke: 2pt + green)) $ - /// ``` - #[resolve] - #[fold] - pub augment: Option, - - /// The gap between rows and columns. - /// - /// ```example - /// #set math.mat(gap: 1em) - /// $ mat(1, 2; 3, 4) $ - /// ``` - #[external] - pub gap: Rel, - - /// The gap between rows. Takes precedence over `gap`. - /// - /// ```example - /// #set math.mat(row-gap: 1em) - /// $ mat(1, 2; 3, 4) $ - /// ``` - #[resolve] - #[parse( - let gap = args.named("gap")?; - args.named("row-gap")?.or(gap) - )] - #[default(DEFAULT_ROW_GAP.into())] - pub row_gap: Rel, - - /// The gap between columns. Takes precedence over `gap`. - /// - /// ```example - /// #set math.mat(column-gap: 1em) - /// $ mat(1, 2; 3, 4) $ - /// ``` - #[resolve] - #[parse(args.named("column-gap")?.or(gap))] - #[default(DEFAULT_COL_GAP.into())] - pub column_gap: Rel, - - /// An array of arrays with the rows of the matrix. - /// - /// ```example - /// #let data = ((1, 2, 3), (4, 5, 6)) - /// #let matrix = math.mat(..data) - /// $ v := matrix $ - /// ``` - #[variadic] - #[parse( - let mut rows = vec![]; - let mut width = 0; - - let values = args.all::>()?; - if values.iter().any(|spanned| matches!(spanned.v, Value::Array(_))) { - for Spanned { v, span } in values { - let array = v.cast::().at(span)?; - let row: Vec<_> = array.into_iter().map(Value::display).collect(); - width = width.max(row.len()); - rows.push(row); - } - } else { - rows = vec![values.into_iter().map(|spanned| spanned.v.display()).collect()]; - } - - for row in &mut rows { - if row.len() < width { - row.resize(width, Content::empty()); - } - } - - rows - )] - pub rows: Vec>, -} - -impl LayoutMath for MatElem { - #[tracing::instrument(skip(ctx))] - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - // validate inputs - - let augment = self.augment(ctx.styles()); - let rows = self.rows(); - - if let Some(aug) = &augment { - for &offset in &aug.hline.0 { - if offset == 0 || offset.unsigned_abs() >= rows.len() { - bail!( - self.span(), - "cannot draw a horizontal line after row {} of a matrix with {} rows", - if offset < 0 { rows.len() as isize + offset } else { offset }, - rows.len() - ); - } - } - - let ncols = self.rows().first().map_or(0, |row| row.len()); - - for &offset in &aug.vline.0 { - if offset == 0 || offset.unsigned_abs() >= ncols { - bail!( - self.span(), - "cannot draw a vertical line after column {} of a matrix with {} columns", - if offset < 0 { ncols as isize + offset } else { offset }, - ncols - ); - } - } - } - - let delim = self.delim(ctx.styles()); - let frame = layout_mat_body( - ctx, - rows, - augment, - Axes::new(self.column_gap(ctx.styles()), self.row_gap(ctx.styles())), - self.span(), - )?; - - layout_delimiters( - ctx, - frame, - delim.map(Delimiter::open), - delim.map(Delimiter::close), - self.span(), - ) - } -} - -/// A case distinction. -/// -/// Content across different branches can be aligned with the `&` symbol. -/// -/// # Example -/// ```example -/// $ f(x, y) := cases( -/// 1 "if" (x dot y)/2 <= 0, -/// 2 "if" x "is even", -/// 3 "if" x in NN, -/// 4 "else", -/// ) $ -/// ``` -#[elem(LayoutMath)] -pub struct CasesElem { - /// The delimiter to use. - /// - /// ```example - /// #set math.cases(delim: "[") - /// $ x = cases(1, 2) $ - /// ``` - #[default(Delimiter::Brace)] - pub delim: Delimiter, - - /// Whether the direction of cases should be reversed. - /// - /// ```example - /// #set math.cases(reverse: true) - /// $ cases(1, 2) = x $ - /// ``` - #[default(false)] - pub reverse: bool, - - /// The gap between branches. - /// - /// ```example - /// #set math.cases(gap: 1em) - /// $ x = cases(1, 2) $ - /// ``` - #[resolve] - #[default(DEFAULT_ROW_GAP.into())] - pub gap: Rel, - - /// The branches of the case distinction. - #[variadic] - pub children: Vec, -} - -impl LayoutMath for CasesElem { - #[tracing::instrument(skip(ctx))] - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let delim = self.delim(ctx.styles()); - let frame = layout_vec_body( - ctx, - self.children(), - FixedAlign::Start, - self.gap(ctx.styles()), - )?; - - let (open, close) = if self.reverse(ctx.styles()) { - (None, Some(delim.close())) - } else { - (Some(delim.open()), None) - }; - - layout_delimiters(ctx, frame, open, close, self.span()) - } -} - -/// A vector / matrix delimiter. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] -pub enum Delimiter { - /// Delimit with parentheses. - #[string("(")] - Paren, - /// Delimit with brackets. - #[string("[")] - Bracket, - /// Delimit with curly braces. - #[string("{")] - Brace, - /// Delimit with vertical bars. - #[string("|")] - Bar, - /// Delimit with double vertical bars. - #[string("||")] - DoubleBar, -} - -impl Delimiter { - /// The delimiter's opening character. - fn open(self) -> char { - match self { - Self::Paren => '(', - Self::Bracket => '[', - Self::Brace => '{', - Self::Bar => '|', - Self::DoubleBar => '‖', - } - } - - /// The delimiter's closing character. - fn close(self) -> char { - match self { - Self::Paren => ')', - Self::Bracket => ']', - Self::Brace => '}', - Self::Bar => '|', - Self::DoubleBar => '‖', - } - } -} - -/// Layout the inner contents of a vector. -fn layout_vec_body( - ctx: &mut MathContext, - column: &[Content], - align: FixedAlign, - row_gap: Rel, -) -> SourceResult { - let gap = row_gap.relative_to(ctx.regions.base().y); - ctx.style(ctx.style.for_denominator()); - let mut flat = vec![]; - for child in column { - flat.push(ctx.layout_row(child)?); - } - ctx.unstyle(); - Ok(stack(ctx, flat, align, gap, 0)) -} - -/// Layout the inner contents of a matrix. -fn layout_mat_body( - ctx: &mut MathContext, - rows: &[Vec], - augment: Option>, - gap: Axes>, - span: Span, -) -> SourceResult { - let gap = gap.zip_map(ctx.regions.base(), Rel::relative_to); - let half_gap = gap * 0.5; - - // We provide a default stroke thickness that scales - // with font size to ensure that augmentation lines - // look correct by default at all matrix sizes. - // The line cap is also set to square because it looks more "correct". - let default_stroke_thickness = DEFAULT_STROKE_THICKNESS.scaled(ctx); - let default_stroke = FixedStroke { - thickness: default_stroke_thickness, - paint: TextElem::fill_in(ctx.styles()).as_decoration(), - line_cap: LineCap::Square, - ..Default::default() - }; - - let (hline, vline, stroke) = match augment { - Some(v) => { - // need to get stroke here for ownership - let stroke = v.stroke_or(default_stroke); - - (v.hline, v.vline, stroke) - } - _ => (Offsets::default(), Offsets::default(), default_stroke), - }; - - let ncols = rows.first().map_or(0, |row| row.len()); - let nrows = rows.len(); - if ncols == 0 || nrows == 0 { - return Ok(Frame::soft(Size::zero())); - } - - // Before the full matrix body can be laid out, the - // individual cells must first be independently laid out - // so we can ensure alignment across rows and columns. - - // This variable stores the maximum ascent and descent for each row. - let mut heights = vec![(Abs::zero(), Abs::zero()); nrows]; - - // We want to transpose our data layout to columns - // before final layout. For efficiency, the columns - // variable is set up here and newly generated - // individual cells are then added to it. - let mut cols = vec![vec![]; ncols]; - - ctx.style(ctx.style.for_denominator()); - for (row, (ascent, descent)) in rows.iter().zip(&mut heights) { - for (cell, col) in row.iter().zip(&mut cols) { - let cell = ctx.layout_row(cell)?; - - ascent.set_max(cell.ascent()); - descent.set_max(cell.descent()); - - col.push(cell); - } - } - ctx.unstyle(); - - // For each row, combine maximum ascent and descent into a row height. - // Sum the row heights, then add the total height of the gaps between rows. - let total_height = - heights.iter().map(|&(a, b)| a + b).sum::() + gap.y * (nrows - 1) as f64; - - // Width starts at zero because it can't be calculated until later - let mut frame = Frame::soft(Size::new(Abs::zero(), total_height)); - - let mut x = Abs::zero(); - - for (index, col) in cols.into_iter().enumerate() { - let AlignmentResult { points, width: rcol } = alignments(&col); - - let mut y = Abs::zero(); - - for (cell, &(ascent, descent)) in col.into_iter().zip(&heights) { - let cell = cell.into_aligned_frame(ctx, &points, FixedAlign::Center); - let pos = Point::new( - if points.is_empty() { x + (rcol - cell.width()) / 2.0 } else { x }, - y + ascent - cell.ascent(), - ); - - frame.push_frame(pos, cell); - - y += ascent + descent + gap.y; - } - - // Advance to the end of the column - x += rcol; - - // If a vertical line should be inserted after this column - if vline.0.contains(&(index as isize + 1)) - || vline.0.contains(&(1 - ((ncols - index) as isize))) - { - frame.push( - Point::with_x(x + half_gap.x), - line_item(total_height, true, stroke.clone(), span), - ); - } - - // Advance to the start of the next column - x += gap.x; - } - - // Once all the columns are laid out, the total width can be calculated - let total_width = x - gap.x; - - // This allows the horizontal lines to be laid out - for line in hline.0 { - let real_line = - if line < 0 { nrows - line.unsigned_abs() } else { line as usize }; - let offset = (heights[0..real_line].iter().map(|&(a, b)| a + b).sum::() - + gap.y * (real_line - 1) as f64) - + half_gap.y; - - frame.push( - Point::with_y(offset), - line_item(total_width, false, stroke.clone(), span), - ); - } - - frame.size_mut().x = total_width; - - Ok(frame) -} - -fn line_item(length: Abs, vertical: bool, stroke: FixedStroke, span: Span) -> FrameItem { - let line_geom = if vertical { - Geometry::Line(Point::with_y(length)) - } else { - Geometry::Line(Point::with_x(length)) - }; - - FrameItem::Shape( - Shape { - geometry: line_geom, - fill: None, - stroke: Some(stroke), - }, - span, - ) -} - -/// Layout the outer wrapper around the body of a vector or matrix. -fn layout_delimiters( - ctx: &mut MathContext, - mut frame: Frame, - left: Option, - right: Option, - span: Span, -) -> SourceResult<()> { - let axis = scaled!(ctx, axis_height); - let short_fall = DELIM_SHORT_FALL.scaled(ctx); - let height = frame.height(); - let target = height + VERTICAL_PADDING.of(height); - frame.set_baseline(height / 2.0 + axis); - - if let Some(left) = left { - let mut left = - GlyphFragment::new(ctx, left, span).stretch_vertical(ctx, target, short_fall); - left.center_on_axis(ctx); - ctx.push(left); - } - - ctx.push(FrameFragment::new(ctx, frame)); - - if let Some(right) = right { - let mut right = GlyphFragment::new(ctx, right, span) - .stretch_vertical(ctx, target, short_fall); - right.center_on_axis(ctx); - ctx.push(right); - } - - Ok(()) -} - -/// Parameters specifying how augmentation lines -/// should be drawn on a matrix. -#[derive(Debug, Default, Clone, PartialEq, Hash)] -pub struct Augment { - pub hline: Offsets, - pub vline: Offsets, - pub stroke: Smart>, -} - -impl Augment { - fn stroke_or(&self, fallback: FixedStroke) -> FixedStroke { - match &self.stroke { - Smart::Custom(v) => v.clone().unwrap_or(fallback), - Smart::Auto => fallback, - } - } -} - -impl Resolve for Augment { - type Output = Augment; - - fn resolve(self, styles: StyleChain) -> Self::Output { - Augment { - hline: self.hline, - vline: self.vline, - stroke: self.stroke.resolve(styles), - } - } -} - -impl Fold for Augment { - type Output = Augment; - - fn fold(mut self, outer: Self::Output) -> Self::Output { - // Special case for handling `auto` strokes in subsequent `Augment`. - if self.stroke.is_auto() && outer.stroke.is_custom() { - self.stroke = outer.stroke; - } else { - self.stroke = self.stroke.fold(outer.stroke); - } - - self - } -} - -cast! { - Augment, - self => { - // if the stroke is auto and there is only one vertical line, - if self.stroke.is_auto() && self.hline.0.is_empty() && self.vline.0.len() == 1 { - return self.vline.0[0].into_value(); - } - - let d = dict! { - "hline" => self.hline.into_value(), - "vline" => self.vline.into_value(), - "stroke" => self.stroke.into_value() - }; - - d.into_value() - }, - v: isize => Augment { - hline: Offsets::default(), - vline: Offsets(smallvec![v]), - stroke: Smart::Auto, - }, - mut dict: Dict => { - // need the transpose for the defaults to work - let hline = dict.take("hline").ok().map(Offsets::from_value) - .transpose().unwrap_or_default().unwrap_or_default(); - let vline = dict.take("vline").ok().map(Offsets::from_value) - .transpose().unwrap_or_default().unwrap_or_default(); - - let stroke = dict.take("stroke").ok().map(Stroke::from_value) - .transpose()?.map(Smart::Custom).unwrap_or(Smart::Auto); - - Augment { hline, vline, stroke } - }, -} - -cast! { - Augment, - self => self.into_value(), -} - -/// The offsets at which augmentation lines should be drawn on a matrix. -#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] -pub struct Offsets(SmallVec<[isize; 1]>); - -cast! { - Offsets, - self => self.0.into_value(), - v: isize => Self(smallvec![v]), - v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), -} diff --git a/crates/typst-library/src/math/mod.rs b/crates/typst-library/src/math/mod.rs deleted file mode 100644 index 7ced638b..00000000 --- a/crates/typst-library/src/math/mod.rs +++ /dev/null @@ -1,500 +0,0 @@ -//! Mathematical formulas. - -#[macro_use] -mod ctx; -mod accent; -mod align; -mod attach; -mod cancel; -mod class; -mod frac; -mod fragment; -mod lr; -mod matrix; -mod op; -mod root; -mod row; -mod spacing; -mod stretch; -mod style; -mod underover; - -pub use self::accent::*; -pub use self::align::*; -pub use self::attach::*; -pub use self::cancel::*; -pub use self::class::*; -pub use self::frac::*; -pub use self::lr::*; -pub use self::matrix::*; -pub use self::op::*; -pub use self::root::*; -pub use self::style::*; -pub use self::underover::*; - -use std::borrow::Cow; - -use ttf_parser::{GlyphId, Rect}; -use typst::eval::{Module, Scope}; -use typst::font::{Font, FontWeight}; -use typst::model::Guard; -use typst::util::option_eq; -use unicode_math_class::MathClass; - -use self::ctx::*; -use self::fragment::*; -use self::row::*; -use self::spacing::*; -use crate::layout::{AlignElem, BoxElem, HElem, ParElem, Spacing}; -use crate::meta::{ - Count, Counter, CounterUpdate, LocalNameIn, Numbering, Outlinable, Refable, - Supplement, -}; -use crate::prelude::*; -use crate::shared::BehavedBuilder; -use crate::text::{ - families, variant, FontFamily, FontList, LinebreakElem, SpaceElem, TextElem, TextSize, -}; - -/// Create a module with all math definitions. -pub fn module() -> Module { - let mut math = Scope::deduplicating(); - math.category("math"); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_elem::(); - math.define_func::(); - math.define_func::(); - math.define_func::(); - math.define_func::(); - math.define_func::(); - math.define_func::(); - math.define_func::(); - math.define_func::(); - math.define_func::(); - math.define_func::(); - math.define_func::(); - math.define_func::(); - math.define_func::(); - math.define_func::(); - math.define_func::(); - math.define_func::(); - math.define_func::(); - math.define_func::