summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore8
-rw-r--r--Cargo.lock5
-rw-r--r--crates/typst-cli/src/world.rs2
-rw-r--r--crates/typst-ide/Cargo.toml5
-rw-r--r--crates/typst-ide/src/complete.rs31
-rw-r--r--crates/typst-ide/src/lib.rs85
-rw-r--r--crates/typst-pdf/src/page.rs4
-rw-r--r--crates/typst-pdf/src/pattern.rs4
-rw-r--r--crates/typst-render/src/lib.rs23
-rw-r--r--crates/typst/src/model/footnote.rs11
-rw-r--r--tests/Cargo.toml14
-rw-r--r--tests/README.md54
-rw-r--r--tests/ref/align-center-in-flow.pngbin0 -> 567 bytes
-rw-r--r--tests/ref/align-in-stack.pngbin0 -> 158 bytes
-rw-r--r--tests/ref/align-right.pngbin0 -> 1434 bytes
-rw-r--r--tests/ref/align-start-and-end.pngbin0 -> 795 bytes
-rw-r--r--tests/ref/array-basic-syntax.pngbin0 -> 2355 bytes
-rw-r--r--tests/ref/array-insert-and-remove.pngbin0 -> 118 bytes
-rw-r--r--tests/ref/array-join-content.pngbin0 -> 546 bytes
-rw-r--r--tests/ref/baseline-box.pngbin0 -> 3908 bytes
-rw-r--r--tests/ref/baseline-text.pngbin0 -> 2217 bytes
-rw-r--r--tests/ref/bibliography-basic.pngbin0 -> 7559 bytes
-rw-r--r--tests/ref/bibliography-before-content.pngbin0 -> 17022 bytes
-rw-r--r--tests/ref/bibliography-full.pngbin0 -> 53991 bytes
-rw-r--r--tests/ref/bibliography-math.pngbin0 -> 4568 bytes
-rw-r--r--tests/ref/bibliography-multiple-files.pngbin0 -> 16122 bytes
-rw-r--r--tests/ref/bibliography-ordering.pngbin0 -> 12210 bytes
-rw-r--r--tests/ref/bidi-consecutive-embedded-ltr-runs.pngbin0 -> 751 bytes
-rw-r--r--tests/ref/bidi-consecutive-embedded-rtl-runs.pngbin0 -> 548 bytes
-rw-r--r--tests/ref/bidi-en-he-top-level.pngbin0 -> 636 bytes
-rw-r--r--tests/ref/bidi-explicit-dir.pngbin0 -> 911 bytes
-rw-r--r--tests/ref/bidi-manual-linebreak.pngbin0 -> 966 bytes
-rw-r--r--tests/ref/bidi-nesting.pngbin0 -> 286 bytes
-rw-r--r--tests/ref/bidi-obj.pngbin0 -> 1498 bytes
-rw-r--r--tests/ref/bidi-raw.pngbin0 -> 2469 bytes
-rw-r--r--tests/ref/bidi-spacing.pngbin0 -> 461 bytes
-rw-r--r--tests/ref/bidi-whitespace-reset.pngbin0 -> 378 bytes
-rw-r--r--tests/ref/block-box-fill.pngbin0 -> 5566 bytes
-rw-r--r--tests/ref/block-clip-svg-glyphs.pngbin0 -> 1980 bytes
-rw-r--r--tests/ref/block-clip-text.pngbin0 -> 1253 bytes
-rw-r--r--tests/ref/block-clipping-multiple-pages.pngbin0 -> 2057 bytes
-rw-r--r--tests/ref/block-fixed-height.pngbin0 -> 7156 bytes
-rw-r--r--tests/ref/block-multiple-pages.pngbin0 -> 2214 bytes
-rw-r--r--tests/ref/block-sizing.pngbin0 -> 139 bytes
-rw-r--r--tests/ref/block-spacing-basic.pngbin0 -> 733 bytes
-rw-r--r--tests/ref/block-spacing-collapse-text-style.pngbin0 -> 299 bytes
-rw-r--r--tests/ref/block-spacing-maximum.pngbin0 -> 1515 bytes
-rw-r--r--tests/ref/block-spacing-table.pngbin0 -> 586 bytes
-rw-r--r--tests/ref/box-clip-radius-without-stroke.pngbin0 -> 1225 bytes
-rw-r--r--tests/ref/box-clip-radius.pngbin0 -> 1245 bytes
-rw-r--r--tests/ref/box-clip-rect.pngbin0 -> 1938 bytes
-rw-r--r--tests/ref/box-layoutable-child.pngbin0 -> 257 bytes
-rw-r--r--tests/ref/box-width-fr.pngbin0 -> 464 bytes
-rw-r--r--tests/ref/box.pngbin0 -> 691 bytes
-rw-r--r--tests/ref/bugs/1050-terms-indent.pngbin22472 -> 0 bytes
-rw-r--r--tests/ref/bugs/1240-stack-fr.pngbin1197 -> 0 bytes
-rw-r--r--tests/ref/bugs/1597-cite-footnote.pngbin4387 -> 0 bytes
-rw-r--r--tests/ref/bugs/2044-invalid-parsed-ident.pngbin603 -> 0 bytes
-rw-r--r--tests/ref/bugs/2105-linebreak-tofu.pngbin473 -> 0 bytes
-rw-r--r--tests/ref/bugs/2595-float-overlap.pngbin15654 -> 0 bytes
-rw-r--r--tests/ref/bugs/2650-cjk-latin-spacing-meta.pngbin1299 -> 0 bytes
-rw-r--r--tests/ref/bugs/2715-float-order.pngbin13006 -> 0 bytes
-rw-r--r--tests/ref/bugs/3082-chinese-punctuation.pngbin7673 -> 0 bytes
-rw-r--r--tests/ref/bugs/3641-float-loop.pngbin3904 -> 0 bytes
-rw-r--r--tests/ref/bugs/3650-italic-equation.pngbin3958 -> 0 bytes
-rw-r--r--tests/ref/bugs/3658-math-size.pngbin531 -> 0 bytes
-rw-r--r--tests/ref/bugs/3662-pdf-smartquotes.pngbin11950 -> 0 bytes
-rw-r--r--tests/ref/bugs/3700-deformed-stroke.pngbin144 -> 0 bytes
-rw-r--r--tests/ref/bugs/3841-tabs-in-raw-typ-code.pngbin13011 -> 0 bytes
-rw-r--r--tests/ref/bugs/870-image-rotation.pngbin296 -> 0 bytes
-rw-r--r--tests/ref/bugs/args-sink.pngbin956 -> 0 bytes
-rw-r--r--tests/ref/bugs/bibliography-math.pngbin15075 -> 0 bytes
-rw-r--r--tests/ref/bugs/bidi-tofus.pngbin513 -> 0 bytes
-rw-r--r--tests/ref/bugs/block-width-box.pngbin1683 -> 0 bytes
-rw-r--r--tests/ref/bugs/cite-locate.pngbin40633 -> 0 bytes
-rw-r--r--tests/ref/bugs/cite-show-set.pngbin2193 -> 0 bytes
-rw-r--r--tests/ref/bugs/clamp-panic.pngbin274 -> 0 bytes
-rw-r--r--tests/ref/bugs/columns-1.pngbin1459 -> 0 bytes
-rw-r--r--tests/ref/bugs/emoji-linebreak.pngbin287 -> 0 bytes
-rw-r--r--tests/ref/bugs/flow-1.pngbin9686 -> 0 bytes
-rw-r--r--tests/ref/bugs/flow-2.pngbin5676 -> 0 bytes
-rw-r--r--tests/ref/bugs/flow-3.pngbin1724 -> 0 bytes
-rw-r--r--tests/ref/bugs/flow-4.pngbin8493 -> 0 bytes
-rw-r--r--tests/ref/bugs/flow-5.pngbin20421 -> 0 bytes
-rw-r--r--tests/ref/bugs/fold-vector.pngbin3771 -> 0 bytes
-rw-r--r--tests/ref/bugs/footnote-keep-multiple.pngbin1914 -> 0 bytes
-rw-r--r--tests/ref/bugs/footnote-list.pngbin1410 -> 0 bytes
-rw-r--r--tests/ref/bugs/gradient-cmyk-encode.pngbin1601 -> 0 bytes
-rw-r--r--tests/ref/bugs/grid-1.pngbin835 -> 0 bytes
-rw-r--r--tests/ref/bugs/grid-2.pngbin15244 -> 0 bytes
-rw-r--r--tests/ref/bugs/grid-3.pngbin1897 -> 0 bytes
-rw-r--r--tests/ref/bugs/grid-4.pngbin1117 -> 0 bytes
-rw-r--r--tests/ref/bugs/hide-meta.pngbin11164 -> 0 bytes
-rw-r--r--tests/ref/bugs/justify-hanging-indent.pngbin1864 -> 0 bytes
-rw-r--r--tests/ref/bugs/line-align.pngbin137 -> 0 bytes
-rw-r--r--tests/ref/bugs/linebreak-no-justifiables.pngbin1829 -> 0 bytes
-rw-r--r--tests/ref/bugs/mat-aug-color.pngbin1692 -> 0 bytes
-rw-r--r--tests/ref/bugs/math-eval.pngbin2589 -> 0 bytes
-rw-r--r--tests/ref/bugs/math-hide.pngbin1719 -> 0 bytes
-rw-r--r--tests/ref/bugs/math-number-spacing.pngbin1188 -> 0 bytes
-rw-r--r--tests/ref/bugs/math-realize.pngbin53710 -> 0 bytes
-rw-r--r--tests/ref/bugs/math-shift.pngbin2262 -> 0 bytes
-rw-r--r--tests/ref/bugs/math-text-break.pngbin885 -> 0 bytes
-rw-r--r--tests/ref/bugs/new-cm-svg.pngbin2845 -> 0 bytes
-rw-r--r--tests/ref/bugs/newline-mode.pngbin7771 -> 0 bytes
-rw-r--r--tests/ref/bugs/pagebreak-bibliography.pngbin1860 -> 0 bytes
-rw-r--r--tests/ref/bugs/pagebreak-numbering.pngbin4136 -> 0 bytes
-rw-r--r--tests/ref/bugs/pagebreak-set-style.pngbin5441 -> 0 bytes
-rw-r--r--tests/ref/bugs/place-base.pngbin1486 -> 0 bytes
-rw-r--r--tests/ref/bugs/place-nested.pngbin1298 -> 0 bytes
-rw-r--r--tests/ref/bugs/place-pagebreak.pngbin978 -> 0 bytes
-rw-r--r--tests/ref/bugs/place-spacing.pngbin6347 -> 0 bytes
-rw-r--r--tests/ref/bugs/raw-color-overwrite.pngbin15184 -> 0 bytes
-rw-r--r--tests/ref/bugs/smartquotes-in-outline.pngbin10122 -> 0 bytes
-rw-r--r--tests/ref/bugs/smartquotes-on-newline.pngbin1625 -> 0 bytes
-rw-r--r--tests/ref/bugs/spacing-behaviour.pngbin638 -> 0 bytes
-rw-r--r--tests/ref/bugs/square-base.pngbin150 -> 0 bytes
-rw-r--r--tests/ref/bugs/table-lines.pngbin1180 -> 0 bytes
-rw-r--r--tests/ref/bugs/table-row-missing.pngbin1029 -> 0 bytes
-rw-r--r--tests/ref/call-basic.pngbin0 -> 973 bytes
-rw-r--r--tests/ref/circle-auto-sizing.pngbin0 -> 12418 bytes
-rw-r--r--tests/ref/circle-directly-in-rect.pngbin0 -> 244 bytes
-rw-r--r--tests/ref/circle-relative-sizing.pngbin0 -> 967 bytes
-rw-r--r--tests/ref/circle-sizing-options.pngbin0 -> 210 bytes
-rw-r--r--tests/ref/circle.pngbin0 -> 889 bytes
-rw-r--r--tests/ref/cite-footnote.pngbin0 -> 13673 bytes
-rw-r--r--tests/ref/cite-form.pngbin0 -> 10731 bytes
-rw-r--r--tests/ref/cite-group.pngbin0 -> 5440 bytes
-rw-r--r--tests/ref/cite-grouping-and-ordering.pngbin0 -> 1308 bytes
-rw-r--r--tests/ref/cjk-punctuation-adjustment-1.pngbin0 -> 1938 bytes
-rw-r--r--tests/ref/cjk-punctuation-adjustment-2.pngbin0 -> 997 bytes
-rw-r--r--tests/ref/cjk-punctuation-adjustment-3.pngbin0 -> 8214 bytes
-rw-r--r--tests/ref/closure-capture-in-lvalue.pngbin0 -> 513 bytes
-rw-r--r--tests/ref/closure-path-resolve-in-layout-phase.pngbin0 -> 2179 bytes
-rw-r--r--tests/ref/closure-without-params-non-atomic.pngbin0 -> 206 bytes
-rw-r--r--tests/ref/code-block-basic-syntax.pngbin0 -> 810 bytes
-rw-r--r--tests/ref/color-cmyk-ops.pngbin0 -> 242 bytes
-rw-r--r--tests/ref/color-luma.pngbin0 -> 106 bytes
-rw-r--r--tests/ref/color-outside-srgb-gamut.pngbin0 -> 102 bytes
-rw-r--r--tests/ref/color-rotate-hue.pngbin0 -> 220 bytes
-rw-r--r--tests/ref/color-saturation.pngbin0 -> 228 bytes
-rw-r--r--tests/ref/color-spaces.pngbin0 -> 143 bytes
-rw-r--r--tests/ref/columns-colbreak-after-place.pngbin0 -> 410 bytes
-rw-r--r--tests/ref/columns-empty-second-column.pngbin0 -> 1175 bytes
-rw-r--r--tests/ref/columns-in-auto-sized-rect.pngbin0 -> 569 bytes
-rw-r--r--tests/ref/columns-in-fixed-size-rect.pngbin0 -> 3833 bytes
-rw-r--r--tests/ref/columns-more-with-gutter.pngbin0 -> 386 bytes
-rw-r--r--tests/ref/columns-one.pngbin0 -> 838 bytes
-rw-r--r--tests/ref/columns-page-height-auto.pngbin0 -> 4156 bytes
-rw-r--r--tests/ref/columns-page-width-auto.pngbin0 -> 747 bytes
-rw-r--r--tests/ref/columns-rtl.pngbin0 -> 4764 bytes
-rw-r--r--tests/ref/columns-set-page-colbreak-pagebreak.pngbin0 -> 376 bytes
-rw-r--r--tests/ref/columns-set-page.pngbin0 -> 5807 bytes
-rw-r--r--tests/ref/coma.pngbin101892 -> 29091 bytes
-rw-r--r--tests/ref/comment-end-of-line.pngbin0 -> 1113 bytes
-rw-r--r--tests/ref/comments.pngbin0 -> 374 bytes
-rw-r--r--tests/ref/compiler/array.pngbin8306 -> 0 bytes
-rw-r--r--tests/ref/compiler/block.pngbin2088 -> 0 bytes
-rw-r--r--tests/ref/compiler/break-continue.pngbin9732 -> 0 bytes
-rw-r--r--tests/ref/compiler/call.pngbin2598 -> 0 bytes
-rw-r--r--tests/ref/compiler/closure.pngbin472 -> 0 bytes
-rw-r--r--tests/ref/compiler/color.pngbin1354 -> 0 bytes
-rw-r--r--tests/ref/compiler/comment.pngbin873 -> 0 bytes
-rw-r--r--tests/ref/compiler/construct.pngbin6113 -> 0 bytes
-rw-r--r--tests/ref/compiler/content-field.pngbin8815 -> 0 bytes
-rw-r--r--tests/ref/compiler/dict.pngbin4055 -> 0 bytes
-rw-r--r--tests/ref/compiler/for.pngbin3342 -> 0 bytes
-rw-r--r--tests/ref/compiler/highlight.pngbin70314 -> 0 bytes
-rw-r--r--tests/ref/compiler/if.pngbin2274 -> 0 bytes
-rw-r--r--tests/ref/compiler/import.pngbin5941 -> 0 bytes
-rw-r--r--tests/ref/compiler/include.pngbin46292 -> 0 bytes
-rw-r--r--tests/ref/compiler/label.pngbin22342 -> 0 bytes
-rw-r--r--tests/ref/compiler/let.pngbin3527 -> 0 bytes
-rw-r--r--tests/ref/compiler/ops.pngbin1029 -> 0 bytes
-rw-r--r--tests/ref/compiler/repr-color-gradient.pngbin50018 -> 0 bytes
-rw-r--r--tests/ref/compiler/repr.pngbin55418 -> 0 bytes
-rw-r--r--tests/ref/compiler/return.pngbin2849 -> 0 bytes
-rw-r--r--tests/ref/compiler/select-where-styles.pngbin17334 -> 0 bytes
-rw-r--r--tests/ref/compiler/selector-logical.pngbin3844 -> 0 bytes
-rw-r--r--tests/ref/compiler/set.pngbin26054 -> 0 bytes
-rw-r--r--tests/ref/compiler/shorthand.pngbin10903 -> 0 bytes
-rw-r--r--tests/ref/compiler/show-bare.pngbin25759 -> 0 bytes
-rw-r--r--tests/ref/compiler/show-node.pngbin21916 -> 0 bytes
-rw-r--r--tests/ref/compiler/show-recursive.pngbin14951 -> 0 bytes
-rw-r--r--tests/ref/compiler/show-selector-logical.pngbin8272 -> 0 bytes
-rw-r--r--tests/ref/compiler/show-selector.pngbin16871 -> 0 bytes
-rw-r--r--tests/ref/compiler/show-set-func.pngbin5772 -> 0 bytes
-rw-r--r--tests/ref/compiler/show-set-text.pngbin8734 -> 0 bytes
-rw-r--r--tests/ref/compiler/show-set.pngbin22168 -> 0 bytes
-rw-r--r--tests/ref/compiler/show-text.pngbin45317 -> 0 bytes
-rw-r--r--tests/ref/compiler/while.pngbin953 -> 0 bytes
-rw-r--r--tests/ref/compute/construct.pngbin1324 -> 0 bytes
-rw-r--r--tests/ref/compute/data.pngbin8386 -> 0 bytes
-rw-r--r--tests/ref/compute/eval-path.pngbin33952 -> 0 bytes
-rw-r--r--tests/ref/compute/foundations.pngbin2344 -> 0 bytes
-rw-r--r--tests/ref/content-field-materialized-heading.pngbin0 -> 203 bytes
-rw-r--r--tests/ref/content-field-materialized-query.pngbin0 -> 269 bytes
-rw-r--r--tests/ref/content-field-materialized-table.pngbin0 -> 1044 bytes
-rw-r--r--tests/ref/content-fields-complex.pngbin0 -> 951 bytes
-rw-r--r--tests/ref/content-label-field-access.pngbin0 -> 689 bytes
-rw-r--r--tests/ref/content-label-fields-method.pngbin0 -> 689 bytes
-rw-r--r--tests/ref/content-label-has-method.pngbin0 -> 689 bytes
-rw-r--r--tests/ref/context-compatibility-locate.pngbin0 -> 1514 bytes
-rw-r--r--tests/ref/context-compatibility-styling.pngbin0 -> 380 bytes
-rw-r--r--tests/ref/counter-basic-1.pngbin0 -> 836 bytes
-rw-r--r--tests/ref/counter-figure.pngbin0 -> 2253 bytes
-rw-r--r--tests/ref/counter-heading.pngbin0 -> 1506 bytes
-rw-r--r--tests/ref/counter-label.pngbin0 -> 649 bytes
-rw-r--r--tests/ref/counter-page.pngbin0 -> 3464 bytes
-rw-r--r--tests/ref/csv.pngbin0 -> 3344 bytes
-rw-r--r--tests/ref/destructuring-during-loop-continue.pngbin0 -> 526 bytes
-rw-r--r--tests/ref/dict-basic-methods.pngbin0 -> 141 bytes
-rw-r--r--tests/ref/dict-basic-syntax.pngbin0 -> 1552 bytes
-rw-r--r--tests/ref/dict-from-module.pngbin0 -> 237 bytes
-rw-r--r--tests/ref/dict-remove-order.pngbin0 -> 141 bytes
-rw-r--r--tests/ref/document-set-title.pngbin0 -> 393 bytes
-rw-r--r--tests/ref/ellipse-auto-sizing.pngbin0 -> 7791 bytes
-rw-r--r--tests/ref/ellipse.pngbin0 -> 458 bytes
-rw-r--r--tests/ref/emph-and-strong-call-in-word.pngbin0 -> 576 bytes
-rw-r--r--tests/ref/emph-double-underscore-empty-hint.pngbin0 -> 319 bytes
-rw-r--r--tests/ref/emph-syntax.pngbin0 -> 5148 bytes
-rw-r--r--tests/ref/empty.pngbin92 -> 0 bytes
-rw-r--r--tests/ref/enum-built-in-loop.pngbin0 -> 551 bytes
-rw-r--r--tests/ref/enum-function-call.pngbin0 -> 877 bytes
-rw-r--r--tests/ref/enum-number-align-2d.pngbin0 -> 756 bytes
-rw-r--r--tests/ref/enum-number-align-default.pngbin0 -> 419 bytes
-rw-r--r--tests/ref/enum-number-align-specified.pngbin0 -> 358 bytes
-rw-r--r--tests/ref/enum-number-align-unaffected.pngbin0 -> 1796 bytes
-rw-r--r--tests/ref/enum-number-align-unfolded.pngbin0 -> 812 bytes
-rw-r--r--tests/ref/enum-number-override-nested.pngbin0 -> 1109 bytes
-rw-r--r--tests/ref/enum-number-override.pngbin0 -> 1166 bytes
-rw-r--r--tests/ref/enum-numbering-closure-nested-complex.pngbin0 -> 1029 bytes
-rw-r--r--tests/ref/enum-numbering-closure-nested.pngbin0 -> 327 bytes
-rw-r--r--tests/ref/enum-numbering-closure.pngbin0 -> 1542 bytes
-rw-r--r--tests/ref/enum-numbering-full.pngbin0 -> 497 bytes
-rw-r--r--tests/ref/enum-numbering-pattern.pngbin0 -> 1318 bytes
-rw-r--r--tests/ref/enum-syntax-at-start.pngbin0 -> 548 bytes
-rw-r--r--tests/ref/enum-syntax-edge-cases.pngbin0 -> 627 bytes
-rw-r--r--tests/ref/escape.pngbin0 -> 3916 bytes
-rw-r--r--tests/ref/eval-in-show-rule.pngbin0 -> 1191 bytes
-rw-r--r--tests/ref/eval-mode.pngbin0 -> 881 bytes
-rw-r--r--tests/ref/eval-path-resolve-in-show-rule.pngbin0 -> 4379 bytes
-rw-r--r--tests/ref/eval-path-resolve.pngbin0 -> 4379 bytes
-rw-r--r--tests/ref/field-function.pngbin0 -> 304 bytes
-rw-r--r--tests/ref/figure-and-caption-show.pngbin0 -> 1153 bytes
-rw-r--r--tests/ref/figure-basic.pngbin0 -> 7931 bytes
-rw-r--r--tests/ref/figure-breakable.pngbin0 -> 805 bytes
-rw-r--r--tests/ref/figure-caption-separator.pngbin0 -> 1133 bytes
-rw-r--r--tests/ref/figure-caption-show.pngbin0 -> 851 bytes
-rw-r--r--tests/ref/figure-caption-where-selector.pngbin0 -> 1612 bytes
-rw-r--r--tests/ref/figure-localization-fr.pngbin0 -> 791 bytes
-rw-r--r--tests/ref/figure-localization-gr.pngbin0 -> 973 bytes
-rw-r--r--tests/ref/figure-localization-ru.pngbin0 -> 789 bytes
-rw-r--r--tests/ref/figure-localization-zh.pngbin0 -> 508 bytes
-rw-r--r--tests/ref/figure-table.pngbin0 -> 2973 bytes
-rw-r--r--tests/ref/figure-theorem.pngbin0 -> 7020 bytes
-rw-r--r--tests/ref/float-display.pngbin0 -> 1621 bytes
-rw-r--r--tests/ref/float-repr.pngbin0 -> 1923 bytes
-rw-r--r--tests/ref/flow-first-region-counter-update-and-placed.pngbin0 -> 976 bytes
-rw-r--r--tests/ref/flow-first-region-counter-update-placed-and-line.pngbin0 -> 1525 bytes
-rw-r--r--tests/ref/flow-first-region-counter-update.pngbin0 -> 685 bytes
-rw-r--r--tests/ref/flow-first-region-no-item.pngbin0 -> 932 bytes
-rw-r--r--tests/ref/flow-first-region-placed.pngbin0 -> 1126 bytes
-rw-r--r--tests/ref/flow-first-region-zero-sized-item.pngbin0 -> 879 bytes
-rw-r--r--tests/ref/flow-fr.pngbin0 -> 570 bytes
-rw-r--r--tests/ref/flow-heading-no-orphan.pngbin0 -> 3162 bytes
-rw-r--r--tests/ref/flow-par-no-orphan-and-widow-lines.pngbin0 -> 23825 bytes
-rw-r--r--tests/ref/fold-vec-order-meta.pngbin0 -> 994 bytes
-rw-r--r--tests/ref/fold-vec-order-text-decos.pngbin0 -> 461 bytes
-rw-r--r--tests/ref/fold-vec-order-text-features.pngbin0 -> 149 bytes
-rw-r--r--tests/ref/footnote-basic.pngbin0 -> 410 bytes
-rw-r--r--tests/ref/footnote-break-across-pages.pngbin0 -> 29276 bytes
-rw-r--r--tests/ref/footnote-duplicate.pngbin0 -> 7518 bytes
-rw-r--r--tests/ref/footnote-entry.pngbin0 -> 1841 bytes
-rw-r--r--tests/ref/footnote-in-caption.pngbin0 -> 6113 bytes
-rw-r--r--tests/ref/footnote-in-columns.pngbin0 -> 3585 bytes
-rw-r--r--tests/ref/footnote-in-table.pngbin0 -> 12304 bytes
-rw-r--r--tests/ref/footnote-invariant.pngbin0 -> 5202 bytes
-rw-r--r--tests/ref/footnote-nested-same-frame.pngbin0 -> 743 bytes
-rw-r--r--tests/ref/footnote-nested.pngbin0 -> 2469 bytes
-rw-r--r--tests/ref/footnote-ref-call.pngbin0 -> 534 bytes
-rw-r--r--tests/ref/footnote-ref-forward.pngbin0 -> 1269 bytes
-rw-r--r--tests/ref/footnote-ref-in-footnote.pngbin0 -> 2554 bytes
-rw-r--r--tests/ref/footnote-ref-multiple.pngbin0 -> 4411 bytes
-rw-r--r--tests/ref/footnote-ref.pngbin0 -> 1480 bytes
-rw-r--r--tests/ref/footnote-space-collapsing.pngbin0 -> 742 bytes
-rw-r--r--tests/ref/footnote-styling.pngbin0 -> 848 bytes
-rw-r--r--tests/ref/for-loop-basic.pngbin0 -> 1256 bytes
-rw-r--r--tests/ref/gradient-conic-angled.pngbin0 -> 2608 bytes
-rw-r--r--tests/ref/gradient-conic-center-shifted-1.pngbin0 -> 2026 bytes
-rw-r--r--tests/ref/gradient-conic-center-shifted-2.pngbin0 -> 2066 bytes
-rw-r--r--tests/ref/gradient-conic-hsl.pngbin0 -> 3366 bytes
-rw-r--r--tests/ref/gradient-conic-hsv.pngbin0 -> 3465 bytes
-rw-r--r--tests/ref/gradient-conic-oklab.pngbin0 -> 3278 bytes
-rw-r--r--tests/ref/gradient-conic-oklch.pngbin0 -> 3291 bytes
-rw-r--r--tests/ref/gradient-conic-relative-parent.pngbin0 -> 2281 bytes
-rw-r--r--tests/ref/gradient-conic-relative-self.pngbin0 -> 3066 bytes
-rw-r--r--tests/ref/gradient-conic-stroke.pngbin0 -> 1341 bytes
-rw-r--r--tests/ref/gradient-conic-text.pngbin0 -> 10468 bytes
-rw-r--r--tests/ref/gradient-conic.pngbin0 -> 2576 bytes
-rw-r--r--tests/ref/gradient-fill-and-stroke.pngbin0 -> 2920 bytes
-rw-r--r--tests/ref/gradient-linear-angled.pngbin0 -> 4165 bytes
-rw-r--r--tests/ref/gradient-linear-hsl.pngbin0 -> 204 bytes
-rw-r--r--tests/ref/gradient-linear-hsv.pngbin0 -> 198 bytes
-rw-r--r--tests/ref/gradient-linear-line.pngbin0 -> 1233 bytes
-rw-r--r--tests/ref/gradient-linear-oklab.pngbin0 -> 206 bytes
-rw-r--r--tests/ref/gradient-linear-oklch.pngbin0 -> 198 bytes
-rw-r--r--tests/ref/gradient-linear-relative-parent.pngbin0 -> 223 bytes
-rw-r--r--tests/ref/gradient-linear-relative-self.pngbin0 -> 541 bytes
-rw-r--r--tests/ref/gradient-linear-repeat-and-mirror-1.pngbin0 -> 292 bytes
-rw-r--r--tests/ref/gradient-linear-repeat-and-mirror-2.pngbin0 -> 325 bytes
-rw-r--r--tests/ref/gradient-linear-repeat-and-mirror-3.pngbin0 -> 218 bytes
-rw-r--r--tests/ref/gradient-linear-sharp-and-repeat.pngbin0 -> 178 bytes
-rw-r--r--tests/ref/gradient-linear-sharp-and-smooth.pngbin0 -> 7457 bytes
-rw-r--r--tests/ref/gradient-linear-sharp-repeat-and-mirror.pngbin0 -> 186 bytes
-rw-r--r--tests/ref/gradient-linear-sharp.pngbin0 -> 1092 bytes
-rw-r--r--tests/ref/gradient-linear-stroke.pngbin0 -> 494 bytes
-rw-r--r--tests/ref/gradient-math-cancel.pngbin0 -> 969 bytes
-rw-r--r--tests/ref/gradient-math-conic.pngbin0 -> 1664 bytes
-rw-r--r--tests/ref/gradient-math-dir.pngbin0 -> 2568 bytes
-rw-r--r--tests/ref/gradient-math-frac.pngbin0 -> 787 bytes
-rw-r--r--tests/ref/gradient-math-mat.pngbin0 -> 1540 bytes
-rw-r--r--tests/ref/gradient-math-misc.pngbin0 -> 3103 bytes
-rw-r--r--tests/ref/gradient-math-radial.pngbin0 -> 1616 bytes
-rw-r--r--tests/ref/gradient-math-root.pngbin0 -> 1569 bytes
-rw-r--r--tests/ref/gradient-math-underover.pngbin0 -> 1314 bytes
-rw-r--r--tests/ref/gradient-presets.pngbin0 -> 17420 bytes
-rw-r--r--tests/ref/gradient-radial-center.pngbin0 -> 1994 bytes
-rw-r--r--tests/ref/gradient-radial-focal-center-and-radius.pngbin0 -> 3987 bytes
-rw-r--r--tests/ref/gradient-radial-hsl.pngbin0 -> 6271 bytes
-rw-r--r--tests/ref/gradient-radial-radius.pngbin0 -> 2935 bytes
-rw-r--r--tests/ref/gradient-radial-relative-parent.pngbin0 -> 1724 bytes
-rw-r--r--tests/ref/gradient-radial-relative-self.pngbin0 -> 2238 bytes
-rw-r--r--tests/ref/gradient-radial-text.pngbin0 -> 10645 bytes
-rw-r--r--tests/ref/gradient-repr.pngbin0 -> 9175 bytes
-rw-r--r--tests/ref/gradient-text-decoration.pngbin0 -> 2343 bytes
-rw-r--r--tests/ref/gradient-text-dir.pngbin0 -> 9419 bytes
-rw-r--r--tests/ref/gradient-text-global.pngbin0 -> 7171 bytes
-rw-r--r--tests/ref/gradient-text-in-container.pngbin0 -> 896 bytes
-rw-r--r--tests/ref/gradient-text-rotate.pngbin0 -> 489 bytes
-rw-r--r--tests/ref/gradient-transformed.pngbin0 -> 223 bytes
-rw-r--r--tests/ref/grid-align.pngbin0 -> 1498 bytes
-rw-r--r--tests/ref/grid-auto-shrink.pngbin0 -> 3321 bytes
-rw-r--r--tests/ref/grid-breaking-expand-vertically.pngbin0 -> 700 bytes
-rw-r--r--tests/ref/grid-calendar.pngbin0 -> 6653 bytes
-rw-r--r--tests/ref/grid-cell-align-override.pngbin0 -> 828 bytes
-rw-r--r--tests/ref/grid-cell-breaking.pngbin0 -> 3581 bytes
-rw-r--r--tests/ref/grid-cell-folding.pngbin0 -> 1096 bytes
-rw-r--r--tests/ref/grid-cell-override-in-header-and-footer-with-gutter.pngbin0 -> 949 bytes
-rw-r--r--tests/ref/grid-cell-override-in-header-and-footer.pngbin0 -> 905 bytes
-rw-r--r--tests/ref/grid-cell-override.pngbin0 -> 3546 bytes
-rw-r--r--tests/ref/grid-cell-position-automatic-skip-manual.pngbin0 -> 572 bytes
-rw-r--r--tests/ref/grid-cell-position-extra-rows.pngbin0 -> 776 bytes
-rw-r--r--tests/ref/grid-cell-position-out-of-order.pngbin0 -> 352 bytes
-rw-r--r--tests/ref/grid-cell-position-partial.pngbin0 -> 1666 bytes
-rw-r--r--tests/ref/grid-cell-set.pngbin0 -> 4391 bytes
-rw-r--r--tests/ref/grid-cell-show-and-override.pngbin0 -> 2626 bytes
-rw-r--r--tests/ref/grid-cell-show-based-on-position.pngbin0 -> 1450 bytes
-rw-r--r--tests/ref/grid-cell-show-emph.pngbin0 -> 697 bytes
-rw-r--r--tests/ref/grid-cell-show-x-y.pngbin0 -> 2740 bytes
-rw-r--r--tests/ref/grid-cell-show.pngbin0 -> 1903 bytes
-rw-r--r--tests/ref/grid-cell-various-overrides.pngbin0 -> 1268 bytes
-rw-r--r--tests/ref/grid-colspan-gutter.pngbin0 -> 7505 bytes
-rw-r--r--tests/ref/grid-colspan-multiple-regions.pngbin0 -> 2571 bytes
-rw-r--r--tests/ref/grid-colspan-over-all-fr-columns-page-width-auto.pngbin0 -> 1843 bytes
-rw-r--r--tests/ref/grid-colspan-over-all-fr-columns.pngbin0 -> 2128 bytes
-rw-r--r--tests/ref/grid-colspan-over-some-fr-columns.pngbin0 -> 1622 bytes
-rw-r--r--tests/ref/grid-colspan-thick-stroke.pngbin0 -> 4027 bytes
-rw-r--r--tests/ref/grid-colspan.pngbin0 -> 6689 bytes
-rw-r--r--tests/ref/grid-column-sizing-auto-base.pngbin0 -> 123 bytes
-rw-r--r--tests/ref/grid-column-sizing-fr-base.pngbin0 -> 126 bytes
-rw-r--r--tests/ref/grid-column-sizing-mixed-base.pngbin0 -> 127 bytes
-rw-r--r--tests/ref/grid-columns-sizings-rect.pngbin0 -> 180 bytes
-rw-r--r--tests/ref/grid-complete-rows.pngbin0 -> 304 bytes
-rw-r--r--tests/ref/grid-consecutive-rows-breaking.pngbin0 -> 2768 bytes
-rw-r--r--tests/ref/grid-exam.pngbin0 -> 4861 bytes
-rw-r--r--tests/ref/grid-fill-func.pngbin0 -> 709 bytes
-rw-r--r--tests/ref/grid-finance.pngbin0 -> 4846 bytes
-rw-r--r--tests/ref/grid-footer-bare-1.pngbin0 -> 252 bytes
-rw-r--r--tests/ref/grid-footer-bare-2.pngbin0 -> 553 bytes
-rw-r--r--tests/ref/grid-footer-below-rowspans.pngbin0 -> 202 bytes
-rw-r--r--tests/ref/grid-footer-cell-with-y.pngbin0 -> 207 bytes
-rw-r--r--tests/ref/grid-footer-expand.pngbin0 -> 364 bytes
-rw-r--r--tests/ref/grid-footer-gutter-and-no-repeat.pngbin0 -> 7032 bytes
-rw-r--r--tests/ref/grid-footer-hline-and-vline-1.pngbin0 -> 690 bytes
-rw-r--r--tests/ref/grid-footer-hline-and-vline-2.pngbin0 -> 413 bytes
-rw-r--r--tests/ref/grid-footer-relative-row-sizes.pngbin0 -> 457 bytes
-rw-r--r--tests/ref/grid-footer-rowspan.pngbin0 -> 1451 bytes
-rw-r--r--tests/ref/grid-footer-stroke-edge-cases.pngbin0 -> 1233 bytes
-rw-r--r--tests/ref/grid-footer-top-stroke.pngbin0 -> 2462 bytes
-rw-r--r--tests/ref/grid-footer.pngbin0 -> 7023 bytes
-rw-r--r--tests/ref/grid-funcs-gutter.pngbin0 -> 3376 bytes
-rw-r--r--tests/ref/grid-gutter-fr.pngbin0 -> 1293 bytes
-rw-r--r--tests/ref/grid-header-and-footer-containing-rowspan.pngbin0 -> 5304 bytes
-rw-r--r--tests/ref/grid-header-and-footer-empty.pngbin0 -> 1592 bytes
-rw-r--r--tests/ref/grid-header-and-footer-lack-of-space.pngbin0 -> 3768 bytes
-rw-r--r--tests/ref/grid-header-and-footer-orphan-prevention.pngbin0 -> 8488 bytes
-rw-r--r--tests/ref/grid-header-and-rowspan-non-contiguous-1.pngbin0 -> 7080 bytes
-rw-r--r--tests/ref/grid-header-and-rowspan-non-contiguous-2.pngbin0 -> 7094 bytes
-rw-r--r--tests/ref/grid-header-and-rowspan-non-contiguous-3.pngbin0 -> 7062 bytes
-rw-r--r--tests/ref/grid-header-block-with-fixed-height.pngbin0 -> 1299 bytes
-rw-r--r--tests/ref/grid-header-cell-with-y.pngbin0 -> 214 bytes
-rw-r--r--tests/ref/grid-header-containing-rowspan.pngbin0 -> 8610 bytes
-rw-r--r--tests/ref/grid-header-empty.pngbin0 -> 4734 bytes
-rw-r--r--tests/ref/grid-header-expand.pngbin0 -> 2024 bytes
-rw-r--r--tests/ref/grid-header-footer-and-rowspan-non-contiguous-1.pngbin0 -> 4334 bytes
-rw-r--r--tests/ref/grid-header-footer-and-rowspan-non-contiguous-2.pngbin0 -> 4403 bytes
-rw-r--r--tests/ref/grid-header-footer-block-with-fixed-height.pngbin0 -> 2151 bytes
-rw-r--r--tests/ref/grid-header-hline-and-vline.pngbin0 -> 1231 bytes
-rw-r--r--tests/ref/grid-header-hline-bottom-manually.pngbin0 -> 506 bytes
-rw-r--r--tests/ref/grid-header-hline-bottom.pngbin0 -> 339 bytes
-rw-r--r--tests/ref/grid-header-lack-of-space.pngbin0 -> 3193 bytes
-rw-r--r--tests/ref/grid-header-last-child.pngbin0 -> 253 bytes
-rw-r--r--tests/ref/grid-header-nested.pngbin0 -> 12613 bytes
-rw-r--r--tests/ref/grid-header-orphan-prevention.pngbin0 -> 10968 bytes
-rw-r--r--tests/ref/grid-header-relative-row-sizes.pngbin0 -> 449 bytes
-rw-r--r--tests/ref/grid-header-rowspan-base.pngbin0 -> 415 bytes
-rw-r--r--tests/ref/grid-header-stroke-edge-cases.pngbin0 -> 1176 bytes
-rw-r--r--tests/ref/grid-headers-gutter.pngbin0 -> 12339 bytes
-rw-r--r--tests/ref/grid-headers-no-repeat.pngbin0 -> 8915 bytes
-rw-r--r--tests/ref/grid-headers.pngbin0 -> 10990 bytes
-rw-r--r--tests/ref/grid-inset-folding.pngbin0 -> 202 bytes
-rw-r--r--tests/ref/grid-inset.pngbin0 -> 4201 bytes
-rw-r--r--tests/ref/grid-nested-breaking.pngbin0 -> 1665 bytes
-rw-r--r--tests/ref/grid-nested-footers.pngbin0 -> 726 bytes
-rw-r--r--tests/ref/grid-nested-headers.pngbin0 -> 494 bytes
-rw-r--r--tests/ref/grid-nested-with-footers.pngbin0 -> 611 bytes
-rw-r--r--tests/ref/grid-nested-with-headers.pngbin0 -> 583 bytes
-rw-r--r--tests/ref/grid-row-sizing-manual-align.pngbin0 -> 594 bytes
-rw-r--r--tests/ref/grid-rowspan-block-full-height.pngbin0 -> 341 bytes
-rw-r--r--tests/ref/grid-rowspan-block-overflow.pngbin0 -> 232 bytes
-rw-r--r--tests/ref/grid-rowspan-cell-coordinates.pngbin0 -> 4633 bytes
-rw-r--r--tests/ref/grid-rowspan-cell-order.pngbin0 -> 2242 bytes
-rw-r--r--tests/ref/grid-rowspan-excessive-gutter.pngbin0 -> 2147 bytes
-rw-r--r--tests/ref/grid-rowspan-excessive.pngbin0 -> 1571 bytes
-rw-r--r--tests/ref/grid-rowspan-fixed-size.pngbin0 -> 1488 bytes
-rw-r--r--tests/ref/grid-rowspan-gutter.pngbin0 -> 10559 bytes
-rw-r--r--tests/ref/grid-rowspan-in-all-columns-stroke-gutter.pngbin0 -> 1109 bytes
-rw-r--r--tests/ref/grid-rowspan-in-all-columns-stroke.pngbin0 -> 939 bytes
-rw-r--r--tests/ref/grid-rowspan-over-auto-row.pngbin0 -> 1004 bytes
-rw-r--r--tests/ref/grid-rowspan-over-fr-row-at-end.pngbin0 -> 682 bytes
-rw-r--r--tests/ref/grid-rowspan-over-fr-row-at-start.pngbin0 -> 685 bytes
-rw-r--r--tests/ref/grid-rowspan-split-1.pngbin0 -> 915 bytes
-rw-r--r--tests/ref/grid-rowspan-split-10.pngbin0 -> 589 bytes
-rw-r--r--tests/ref/grid-rowspan-split-11.pngbin0 -> 924 bytes
-rw-r--r--tests/ref/grid-rowspan-split-12.pngbin0 -> 1530 bytes
-rw-r--r--tests/ref/grid-rowspan-split-13.pngbin0 -> 1463 bytes
-rw-r--r--tests/ref/grid-rowspan-split-14.pngbin0 -> 6200 bytes
-rw-r--r--tests/ref/grid-rowspan-split-15.pngbin0 -> 1727 bytes
-rw-r--r--tests/ref/grid-rowspan-split-16.pngbin0 -> 1121 bytes
-rw-r--r--tests/ref/grid-rowspan-split-17.pngbin0 -> 4650 bytes
-rw-r--r--tests/ref/grid-rowspan-split-2.pngbin0 -> 1004 bytes
-rw-r--r--tests/ref/grid-rowspan-split-3.pngbin0 -> 2049 bytes
-rw-r--r--tests/ref/grid-rowspan-split-4.pngbin0 -> 2113 bytes
-rw-r--r--tests/ref/grid-rowspan-split-5.pngbin0 -> 1414 bytes
-rw-r--r--tests/ref/grid-rowspan-split-6.pngbin0 -> 1433 bytes
-rw-r--r--tests/ref/grid-rowspan-split-7.pngbin0 -> 778 bytes
-rw-r--r--tests/ref/grid-rowspan-split-8.pngbin0 -> 815 bytes
-rw-r--r--tests/ref/grid-rowspan-split-9.pngbin0 -> 967 bytes
-rw-r--r--tests/ref/grid-rowspan-unbreakable-1.pngbin0 -> 740 bytes
-rw-r--r--tests/ref/grid-rowspan-unbreakable-2.pngbin0 -> 3602 bytes
-rw-r--r--tests/ref/grid-rowspan.pngbin0 -> 9089 bytes
-rw-r--r--tests/ref/grid-rtl-colspan-stroke.pngbin0 -> 7997 bytes
-rw-r--r--tests/ref/grid-rtl-colspan.pngbin0 -> 6124 bytes
-rw-r--r--tests/ref/grid-rtl-complex.pngbin0 -> 10001 bytes
-rw-r--r--tests/ref/grid-rtl-header.pngbin0 -> 10680 bytes
-rw-r--r--tests/ref/grid-rtl-multiple-regions.pngbin0 -> 2561 bytes
-rw-r--r--tests/ref/grid-rtl-rowspan.pngbin0 -> 1012 bytes
-rw-r--r--tests/ref/grid-rtl-vline-position.pngbin0 -> 1391 bytes
-rw-r--r--tests/ref/grid-rtl.pngbin0 -> 412 bytes
-rw-r--r--tests/ref/grid-same-row-multiple-columns-breaking.pngbin0 -> 1560 bytes
-rw-r--r--tests/ref/grid-stroke-array.pngbin0 -> 2359 bytes
-rw-r--r--tests/ref/grid-stroke-automatically-positioned-lines.pngbin0 -> 1079 bytes
-rw-r--r--tests/ref/grid-stroke-border-partial.pngbin0 -> 2454 bytes
-rw-r--r--tests/ref/grid-stroke-complex.pngbin0 -> 1756 bytes
-rw-r--r--tests/ref/grid-stroke-field-in-show.pngbin0 -> 255 bytes
-rw-r--r--tests/ref/grid-stroke-folding.pngbin0 -> 1096 bytes
-rw-r--r--tests/ref/grid-stroke-func.pngbin0 -> 534 bytes
-rw-r--r--tests/ref/grid-stroke-hline-position-bottom-gutter.pngbin0 -> 1716 bytes
-rw-r--r--tests/ref/grid-stroke-hline-position-bottom.pngbin0 -> 1300 bytes
-rw-r--r--tests/ref/grid-stroke-hline-rowspan.pngbin0 -> 400 bytes
-rw-r--r--tests/ref/grid-stroke-manually-positioned-lines.pngbin0 -> 972 bytes
-rw-r--r--tests/ref/grid-stroke-none.pngbin0 -> 424 bytes
-rw-r--r--tests/ref/grid-stroke-pattern.pngbin0 -> 1817 bytes
-rw-r--r--tests/ref/grid-stroke-priority-cell.pngbin0 -> 1164 bytes
-rw-r--r--tests/ref/grid-stroke-priority-line-cell.pngbin0 -> 2563 bytes
-rw-r--r--tests/ref/grid-stroke-priority-line.pngbin0 -> 545 bytes
-rw-r--r--tests/ref/grid-stroke-set-on-cell-and-line.pngbin0 -> 1149 bytes
-rw-r--r--tests/ref/grid-stroke-vline-colspan.pngbin0 -> 680 bytes
-rw-r--r--tests/ref/grid-stroke-vline-position-left-and-right.pngbin0 -> 823 bytes
-rw-r--r--tests/ref/grid-trailing-linebreak-region-overflow.pngbin0 -> 715 bytes
-rw-r--r--tests/ref/heading-basic.pngbin0 -> 1029 bytes
-rw-r--r--tests/ref/heading-block.pngbin0 -> 1157 bytes
-rw-r--r--tests/ref/heading-offset-and-level.pngbin0 -> 619 bytes
-rw-r--r--tests/ref/heading-offset.pngbin0 -> 5624 bytes
-rw-r--r--tests/ref/heading-show-where.pngbin0 -> 2349 bytes
-rw-r--r--tests/ref/heading-syntax-at-start.pngbin0 -> 1469 bytes
-rw-r--r--tests/ref/heading-syntax-edge-cases.pngbin0 -> 737 bytes
-rw-r--r--tests/ref/hide-image.pngbin0 -> 8838 bytes
-rw-r--r--tests/ref/hide-line.pngbin0 -> 352 bytes
-rw-r--r--tests/ref/hide-list.pngbin0 -> 775 bytes
-rw-r--r--tests/ref/hide-polygon.pngbin0 -> 791 bytes
-rw-r--r--tests/ref/hide-rect.pngbin0 -> 1203 bytes
-rw-r--r--tests/ref/hide-table.pngbin0 -> 619 bytes
-rw-r--r--tests/ref/hide-text.pngbin0 -> 380 bytes
-rw-r--r--tests/ref/highlight-bounds.pngbin0 -> 1218 bytes
-rw-r--r--tests/ref/highlight-edges-bounds.pngbin0 -> 638 bytes
-rw-r--r--tests/ref/highlight-edges.pngbin0 -> 1288 bytes
-rw-r--r--tests/ref/highlight-radius.pngbin0 -> 1666 bytes
-rw-r--r--tests/ref/highlight-stroke.pngbin0 -> 2186 bytes
-rw-r--r--tests/ref/highlight.pngbin0 -> 4693 bytes
-rw-r--r--tests/ref/hyphenate-between-shape-runs.pngbin0 -> 462 bytes
-rw-r--r--tests/ref/hyphenate-off-temporarily.pngbin0 -> 4793 bytes
-rw-r--r--tests/ref/hyphenate-punctuation.pngbin0 -> 530 bytes
-rw-r--r--tests/ref/hyphenate-shy.pngbin0 -> 607 bytes
-rw-r--r--tests/ref/hyphenate.pngbin0 -> 1249 bytes
-rw-r--r--tests/ref/if-condition-complex.pngbin0 -> 829 bytes
-rw-r--r--tests/ref/if-markup.pngbin0 -> 215 bytes
-rw-r--r--tests/ref/image-baseline-with-box.pngbin0 -> 6375 bytes
-rw-r--r--tests/ref/image-decode-detect-format.pngbin0 -> 10628 bytes
-rw-r--r--tests/ref/image-decode-specify-format.pngbin0 -> 10628 bytes
-rw-r--r--tests/ref/image-decode-svg.pngbin0 -> 686 bytes
-rw-r--r--tests/ref/image-fit.pngbin0 -> 10287 bytes
-rw-r--r--tests/ref/image-jump-to-next-page.pngbin0 -> 5393 bytes
-rw-r--r--tests/ref/image-natural-dpi-sizing.pngbin0 -> 225 bytes
-rw-r--r--tests/ref/image-rgba-png-and-jpeg.pngbin0 -> 18076 bytes
-rw-r--r--tests/ref/image-sizing.pngbin0 -> 8625 bytes
-rw-r--r--tests/ref/image-svg-complex.pngbin0 -> 1249 bytes
-rw-r--r--tests/ref/image-svg-text-font.pngbin0 -> 1363 bytes
-rw-r--r--tests/ref/image-svg-text.pngbin0 -> 5658 bytes
-rw-r--r--tests/ref/import-basic.pngbin0 -> 1529 bytes
-rw-r--r--tests/ref/import-from-function-scope.pngbin0 -> 436 bytes
-rw-r--r--tests/ref/import-source-field-access.pngbin0 -> 177 bytes
-rw-r--r--tests/ref/include-file.pngbin0 -> 16601 bytes
-rw-r--r--tests/ref/int-display.pngbin0 -> 1406 bytes
-rw-r--r--tests/ref/int-repr.pngbin0 -> 1402 bytes
-rw-r--r--tests/ref/issue-1041-smartquotes-in-outline.pngbin0 -> 3482 bytes
-rw-r--r--tests/ref/issue-1050-terms-indent.pngbin0 -> 8165 bytes
-rw-r--r--tests/ref/issue-1052-math-number-spacing.pngbin0 -> 486 bytes
-rw-r--r--tests/ref/issue-1216-clamp-panic.pngbin0 -> 175 bytes
-rw-r--r--tests/ref/issue-1240-stack-h-fr.pngbin0 -> 332 bytes
-rw-r--r--tests/ref/issue-1240-stack-v-fr.pngbin0 -> 281 bytes
-rw-r--r--tests/ref/issue-1368-place-pagebreak.pngbin0 -> 418 bytes
-rw-r--r--tests/ref/issue-1373-bidi-tofus.pngbin0 -> 227 bytes
-rw-r--r--tests/ref/issue-1388-table-row-missing.pngbin0 -> 454 bytes
-rw-r--r--tests/ref/issue-1398-line-align.pngbin0 -> 147 bytes
-rw-r--r--tests/ref/issue-1433-footnote-in-list.pngbin0 -> 529 bytes
-rw-r--r--tests/ref/issue-1540-smartquotes-across-newlines.pngbin0 -> 597 bytes
-rw-r--r--tests/ref/issue-1597-cite-footnote.pngbin0 -> 1380 bytes
-rw-r--r--tests/ref/issue-1825-rect-overflow.pngbin0 -> 10073 bytes
-rw-r--r--tests/ref/issue-183-table-lines.pngbin0 -> 560 bytes
-rw-r--r--tests/ref/issue-1948-math-text-break.pngbin0 -> 385 bytes
-rw-r--r--tests/ref/issue-2044-invalid-parsed-ident.pngbin0 -> 291 bytes
-rw-r--r--tests/ref/issue-2051-new-cm-svg.pngbin0 -> 1214 bytes
-rw-r--r--tests/ref/issue-2055-math-eval.pngbin0 -> 1043 bytes
-rw-r--r--tests/ref/issue-2095-pagebreak-numbering.pngbin0 -> 1407 bytes
-rw-r--r--tests/ref/issue-2105-linebreak-tofu.pngbin0 -> 246 bytes
-rw-r--r--tests/ref/issue-2128-block-width-box.pngbin0 -> 769 bytes
-rw-r--r--tests/ref/issue-2134-pagebreak-bibliography.pngbin0 -> 782 bytes
-rw-r--r--tests/ref/issue-2162-pagebreak-set-style.pngbin0 -> 2169 bytes
-rw-r--r--tests/ref/issue-2199-place-spacing-bottom.pngbin0 -> 1222 bytes
-rw-r--r--tests/ref/issue-2199-place-spacing-default.pngbin0 -> 1225 bytes
-rw-r--r--tests/ref/issue-2214-baseline-math.pngbin0 -> 891 bytes
-rw-r--r--tests/ref/issue-2259-raw-color-overwrite.pngbin0 -> 2617 bytes
-rw-r--r--tests/ref/issue-2268-mat-augment-color.pngbin0 -> 669 bytes
-rw-r--r--tests/ref/issue-2419-justify-hanging-indent.pngbin0 -> 712 bytes
-rw-r--r--tests/ref/issue-2530-enum-item-panic.pngbin0 -> 470 bytes
-rw-r--r--tests/ref/issue-2530-figure-caption-panic.pngbin0 -> 194 bytes
-rw-r--r--tests/ref/issue-2530-list-item-panic.pngbin0 -> 262 bytes
-rw-r--r--tests/ref/issue-2530-term-item-panic.pngbin0 -> 463 bytes
-rw-r--r--tests/ref/issue-2531-cite-show-set.pngbin0 -> 984 bytes
-rw-r--r--tests/ref/issue-2538-cjk-latin-spacing-before-linebreak.pngbin0 -> 914 bytes
-rw-r--r--tests/ref/issue-2595-float-overlap.pngbin0 -> 4055 bytes
-rw-r--r--tests/ref/issue-2650-cjk-latin-spacing-meta.pngbin0 -> 532 bytes
-rw-r--r--tests/ref/issue-2715-float-order.pngbin0 -> 4790 bytes
-rw-r--r--tests/ref/issue-2902-gradient-oklab-panic.pngbin0 -> 1129 bytes
-rw-r--r--tests/ref/issue-2902-gradient-oklch-panic.pngbin0 -> 1140 bytes
-rw-r--r--tests/ref/issue-3082-chinese-punctuation.pngbin0 -> 3012 bytes
-rw-r--r--tests/ref/issue-3191-raw-indent-shrink.pngbin0 -> 1422 bytes
-rw-r--r--tests/ref/issue-3191-raw-normal-paragraphs-still-shrink.pngbin0 -> 1143 bytes
-rw-r--r--tests/ref/issue-3232-dict-empty.pngbin0 -> 261 bytes
-rw-r--r--tests/ref/issue-3264-rect-negative-dimensions.pngbin0 -> 4176 bytes
-rw-r--r--tests/ref/issue-3363-json-large-number.pngbin0 -> 663 bytes
-rw-r--r--tests/ref/issue-3586-figure-caption-separator.pngbin0 -> 133 bytes
-rw-r--r--tests/ref/issue-3624-spacing-behaviour.pngbin0 -> 277 bytes
-rw-r--r--tests/ref/issue-3641-float-loop.pngbin0 -> 1426 bytes
-rw-r--r--tests/ref/issue-3650-italic-equation.pngbin0 -> 1494 bytes
-rw-r--r--tests/ref/issue-3658-math-size.pngbin0 -> 320 bytes
-rw-r--r--tests/ref/issue-3662-pdf-smartquotes.pngbin0 -> 4710 bytes
-rw-r--r--tests/ref/issue-3700-deformed-stroke.pngbin0 -> 103 bytes
-rw-r--r--tests/ref/issue-3841-tabs-in-raw-type-code.pngbin0 -> 4857 bytes
-rw-r--r--tests/ref/issue-622-hide-meta-cite.pngbin0 -> 2419 bytes
-rw-r--r--tests/ref/issue-622-hide-meta-outline.pngbin0 -> 2177 bytes
-rw-r--r--tests/ref/issue-785-cite-locate.pngbin0 -> 15456 bytes
-rw-r--r--tests/ref/issue-80-emoji-linebreak.pngbin0 -> 211 bytes
-rw-r--r--tests/ref/issue-852-mat-type.pngbin0 -> 1926 bytes
-rw-r--r--tests/ref/issue-870-image-rotation.pngbin0 -> 200 bytes
-rw-r--r--tests/ref/issue-886-args-sink.pngbin0 -> 417 bytes
-rw-r--r--tests/ref/issue-columns-heading.pngbin0 -> 585 bytes
-rw-r--r--tests/ref/issue-flow-frame-placement.pngbin0 -> 2985 bytes
-rw-r--r--tests/ref/issue-flow-layout-index-out-of-bounds.pngbin0 -> 7462 bytes
-rw-r--r--tests/ref/issue-flow-overlarge-frames.pngbin0 -> 3276 bytes
-rw-r--r--tests/ref/issue-flow-trailing-leading.pngbin0 -> 1987 bytes
-rw-r--r--tests/ref/issue-flow-weak-spacing.pngbin0 -> 726 bytes
-rw-r--r--tests/ref/issue-gradient-cmyk-encode.pngbin0 -> 303 bytes
-rw-r--r--tests/ref/issue-grid-base-auto-row-list.pngbin0 -> 225 bytes
-rw-r--r--tests/ref/issue-grid-base-auto-row.pngbin0 -> 278 bytes
-rw-r--r--tests/ref/issue-grid-double-skip.pngbin0 -> 685 bytes
-rw-r--r--tests/ref/issue-grid-gutter-skip.pngbin0 -> 527 bytes
-rw-r--r--tests/ref/issue-grid-skip-list.pngbin0 -> 1333 bytes
-rw-r--r--tests/ref/issue-grid-skip.pngbin0 -> 1691 bytes
-rw-r--r--tests/ref/issue-math-realize-hide.pngbin0 -> 494 bytes
-rw-r--r--tests/ref/issue-math-realize-scripting.pngbin0 -> 2607 bytes
-rw-r--r--tests/ref/issue-math-realize-show.pngbin0 -> 1802 bytes
-rw-r--r--tests/ref/issue-multiple-footnote-in-one-line.pngbin0 -> 713 bytes
-rw-r--r--tests/ref/issue-non-atomic-closure.pngbin0 -> 136 bytes
-rw-r--r--tests/ref/issue-place-base.pngbin0 -> 599 bytes
-rw-r--r--tests/ref/issue-rtl-safe-to-break-panic.pngbin0 -> 168 bytes
-rw-r--r--tests/ref/justify-avoid-runts.pngbin0 -> 1879 bytes
-rw-r--r--tests/ref/justify-chinese.pngbin0 -> 6678 bytes
-rw-r--r--tests/ref/justify-code-blocks.pngbin0 -> 2402 bytes
-rw-r--r--tests/ref/justify-japanese.pngbin0 -> 10514 bytes
-rw-r--r--tests/ref/justify-justified-linebreak.pngbin0 -> 387 bytes
-rw-r--r--tests/ref/justify-knuth-story.pngbin0 -> 24663 bytes
-rw-r--r--tests/ref/justify-manual-linebreak.pngbin0 -> 302 bytes
-rw-r--r--tests/ref/justify-no-leading-spaces.pngbin0 -> 2735 bytes
-rw-r--r--tests/ref/justify-punctuation-adjustment.pngbin0 -> 7702 bytes
-rw-r--r--tests/ref/justify-shrink-last-line.pngbin0 -> 801 bytes
-rw-r--r--tests/ref/justify-variants.pngbin0 -> 8052 bytes
-rw-r--r--tests/ref/justify-whitespace-adjustment.pngbin0 -> 1828 bytes
-rw-r--r--tests/ref/justify-without-justifiables.pngbin0 -> 831 bytes
-rw-r--r--tests/ref/justify.pngbin0 -> 4013 bytes
-rw-r--r--tests/ref/label-after-expression.pngbin0 -> 392 bytes
-rw-r--r--tests/ref/label-after-parbreak.pngbin0 -> 290 bytes
-rw-r--r--tests/ref/label-dynamic-show-set.pngbin0 -> 595 bytes
-rw-r--r--tests/ref/label-in-block.pngbin0 -> 835 bytes
-rw-r--r--tests/ref/label-on-text.pngbin0 -> 1404 bytes
-rw-r--r--tests/ref/label-show-where-selector.pngbin0 -> 1307 bytes
-rw-r--r--tests/ref/label-unclosed-is-text.pngbin0 -> 479 bytes
-rw-r--r--tests/ref/layout-in-fixed-size-block.pngbin0 -> 2633 bytes
-rw-r--r--tests/ref/layout-in-page-call.pngbin0 -> 2572 bytes
-rw-r--r--tests/ref/layout/align.pngbin8400 -> 0 bytes
-rw-r--r--tests/ref/layout/block-sizing.pngbin31042 -> 0 bytes
-rw-r--r--tests/ref/layout/block-spacing.pngbin1868 -> 0 bytes
-rw-r--r--tests/ref/layout/cjk-latin-spacing.pngbin8399 -> 0 bytes
-rw-r--r--tests/ref/layout/cjk-punctuation-adjustment.pngbin28665 -> 0 bytes
-rw-r--r--tests/ref/layout/clip.pngbin33092 -> 0 bytes
-rw-r--r--tests/ref/layout/code-indent-shrink.pngbin8232 -> 0 bytes
-rw-r--r--tests/ref/layout/columns.pngbin95706 -> 0 bytes
-rw-r--r--tests/ref/layout/container-fill.pngbin18457 -> 0 bytes
-rw-r--r--tests/ref/layout/container.pngbin21577 -> 0 bytes
-rw-r--r--tests/ref/layout/enum-align.pngbin18701 -> 0 bytes
-rw-r--r--tests/ref/layout/enum-numbering.pngbin19079 -> 0 bytes
-rw-r--r--tests/ref/layout/enum.pngbin14280 -> 0 bytes
-rw-r--r--tests/ref/layout/flow-orphan.pngbin83917 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-1.pngbin8143 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-2.pngbin61931 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-3.pngbin47584 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-4.pngbin425 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-5.pngbin9301 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-auto-shrink.pngbin8390 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-cell.pngbin47260 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-colspan.pngbin91276 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-footers-1.pngbin50791 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-footers-2.pngbin52405 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-footers-3.pngbin52783 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-footers-4.pngbin20513 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-footers-5.pngbin18713 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-headers-1.pngbin126196 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-headers-2.pngbin113070 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-headers-3.pngbin102354 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-headers-4.pngbin42192 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-positioning.pngbin54618 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-rowspan-basic.pngbin99968 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-rowspan-split-1.pngbin30406 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-rowspan-split-2.pngbin11282 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-rowspan-split-3.pngbin100103 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-rtl.pngbin97027 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-stroke.pngbin57859 -> 0 bytes
-rw-r--r--tests/ref/layout/grid-styling.pngbin30784 -> 0 bytes
-rw-r--r--tests/ref/layout/hide.pngbin49286 -> 0 bytes
-rw-r--r--tests/ref/layout/list-attach.pngbin11170 -> 0 bytes
-rw-r--r--tests/ref/layout/list-marker.pngbin3825 -> 0 bytes
-rw-r--r--tests/ref/layout/list.pngbin20705 -> 0 bytes
-rw-r--r--tests/ref/layout/out-of-flow-in-block.pngbin16297 -> 0 bytes
-rw-r--r--tests/ref/layout/pad.pngbin51646 -> 0 bytes
-rw-r--r--tests/ref/layout/page-binding.pngbin16919 -> 0 bytes
-rw-r--r--tests/ref/layout/page-margin.pngbin15338 -> 0 bytes
-rw-r--r--tests/ref/layout/page-marginals.pngbin57998 -> 0 bytes
-rw-r--r--tests/ref/layout/page-number-align.pngbin1343 -> 0 bytes
-rw-r--r--tests/ref/layout/page-style.pngbin1294 -> 0 bytes
-rw-r--r--tests/ref/layout/page.pngbin14273 -> 0 bytes
-rw-r--r--tests/ref/layout/pagebreak-parity.pngbin4906 -> 0 bytes
-rw-r--r--tests/ref/layout/pagebreak-weak.pngbin18958 -> 0 bytes
-rw-r--r--tests/ref/layout/pagebreak.pngbin10413 -> 0 bytes
-rw-r--r--tests/ref/layout/par-bidi.pngbin31441 -> 0 bytes
-rw-r--r--tests/ref/layout/par-indent.pngbin64257 -> 0 bytes
-rw-r--r--tests/ref/layout/par-justify-cjk.pngbin89706 -> 0 bytes
-rw-r--r--tests/ref/layout/par-justify.pngbin52393 -> 0 bytes
-rw-r--r--tests/ref/layout/par-knuth.pngbin68723 -> 0 bytes
-rw-r--r--tests/ref/layout/par-simple.pngbin58603 -> 0 bytes
-rw-r--r--tests/ref/layout/par.pngbin27043 -> 0 bytes
-rw-r--r--tests/ref/layout/place-background.pngbin77357 -> 0 bytes
-rw-r--r--tests/ref/layout/place-float-auto.pngbin3331 -> 0 bytes
-rw-r--r--tests/ref/layout/place-float-columns.pngbin106011 -> 0 bytes
-rw-r--r--tests/ref/layout/place-float-figure.pngbin111833 -> 0 bytes
-rw-r--r--tests/ref/layout/place-nested.pngbin6966 -> 0 bytes
-rw-r--r--tests/ref/layout/place.pngbin44931 -> 0 bytes
-rw-r--r--tests/ref/layout/repeat.pngbin9048 -> 0 bytes
-rw-r--r--tests/ref/layout/spacing.pngbin4158 -> 0 bytes
-rw-r--r--tests/ref/layout/stack-1.pngbin1507 -> 0 bytes
-rw-r--r--tests/ref/layout/stack-2.pngbin8247 -> 0 bytes
-rw-r--r--tests/ref/layout/table-cell.pngbin50913 -> 0 bytes
-rw-r--r--tests/ref/layout/table.pngbin26146 -> 0 bytes
-rw-r--r--tests/ref/layout/terms.pngbin20057 -> 0 bytes
-rw-r--r--tests/ref/layout/transform-layout.pngbin42107 -> 0 bytes
-rw-r--r--tests/ref/layout/transform.pngbin51410 -> 0 bytes
-rw-r--r--tests/ref/let-basic.pngbin0 -> 343 bytes
-rw-r--r--tests/ref/let-termination.pngbin0 -> 479 bytes
-rw-r--r--tests/ref/line-basic.pngbin0 -> 445 bytes
-rw-r--r--tests/ref/line-positioning.pngbin0 -> 506 bytes
-rw-r--r--tests/ref/line-stroke-dash.pngbin0 -> 171 bytes
-rw-r--r--tests/ref/line-stroke-set.pngbin0 -> 150 bytes
-rw-r--r--tests/ref/line-stroke.pngbin0 -> 193 bytes
-rw-r--r--tests/ref/linebreak-cite-punctuation.pngbin0 -> 10248 bytes
-rw-r--r--tests/ref/linebreak-hyphen-nbsp.pngbin0 -> 838 bytes
-rw-r--r--tests/ref/linebreak-link-end.pngbin0 -> 2080 bytes
-rw-r--r--tests/ref/linebreak-link-justify.pngbin0 -> 12258 bytes
-rw-r--r--tests/ref/linebreak-link.pngbin0 -> 6373 bytes
-rw-r--r--tests/ref/linebreak-manual-consecutive.pngbin0 -> 956 bytes
-rw-r--r--tests/ref/linebreak-manual-directly-after-automatic.pngbin0 -> 1002 bytes
-rw-r--r--tests/ref/linebreak-manual-justified.pngbin0 -> 1755 bytes
-rw-r--r--tests/ref/linebreak-manual-trailing-multiple.pngbin0 -> 464 bytes
-rw-r--r--tests/ref/linebreak-manual.pngbin0 -> 398 bytes
-rw-r--r--tests/ref/linebreak-math-punctuation.pngbin0 -> 3054 bytes
-rw-r--r--tests/ref/linebreak-narrow-nbsp.pngbin0 -> 1317 bytes
-rw-r--r--tests/ref/linebreak-overflow-double.pngbin0 -> 1382 bytes
-rw-r--r--tests/ref/linebreak-overflow.pngbin0 -> 858 bytes
-rw-r--r--tests/ref/linebreak-shape-run.pngbin0 -> 706 bytes
-rw-r--r--tests/ref/linebreak-thai.pngbin0 -> 6968 bytes
-rw-r--r--tests/ref/link-basic.pngbin0 -> 6194 bytes
-rw-r--r--tests/ref/link-bracket-balanced.pngbin0 -> 3895 bytes
-rw-r--r--tests/ref/link-bracket-unbalanced-closing.pngbin0 -> 2129 bytes
-rw-r--r--tests/ref/link-on-block.pngbin0 -> 2423 bytes
-rw-r--r--tests/ref/link-show.pngbin0 -> 2639 bytes
-rw-r--r--tests/ref/link-to-label.pngbin0 -> 993 bytes
-rw-r--r--tests/ref/link-to-page.pngbin0 -> 985 bytes
-rw-r--r--tests/ref/link-trailing-period.pngbin0 -> 2990 bytes
-rw-r--r--tests/ref/link-transformed.pngbin0 -> 1274 bytes
-rw-r--r--tests/ref/list-attached-above-spacing.pngbin0 -> 605 bytes
-rw-r--r--tests/ref/list-attached.pngbin0 -> 1464 bytes
-rw-r--r--tests/ref/list-basic.pngbin0 -> 1001 bytes
-rw-r--r--tests/ref/list-content-block.pngbin0 -> 965 bytes
-rw-r--r--tests/ref/list-indent-specifics.pngbin0 -> 392 bytes
-rw-r--r--tests/ref/list-marker-align-unaffected.pngbin0 -> 822 bytes
-rw-r--r--tests/ref/list-marker-bare-hyphen.pngbin0 -> 773 bytes
-rw-r--r--tests/ref/list-marker-closure.pngbin0 -> 503 bytes
-rw-r--r--tests/ref/list-marker-cycle.pngbin0 -> 312 bytes
-rw-r--r--tests/ref/list-marker-dash.pngbin0 -> 239 bytes
-rw-r--r--tests/ref/list-mix.pngbin0 -> 968 bytes
-rw-r--r--tests/ref/list-mixed-tabs-and-spaces.pngbin0 -> 765 bytes
-rw-r--r--tests/ref/list-nested.pngbin0 -> 2740 bytes
-rw-r--r--tests/ref/list-non-attached-followed-by-attached.pngbin0 -> 593 bytes
-rw-r--r--tests/ref/list-rtl.pngbin0 -> 401 bytes
-rw-r--r--tests/ref/list-syntax-edge-cases.pngbin0 -> 565 bytes
-rw-r--r--tests/ref/list-tabs.pngbin0 -> 694 bytes
-rw-r--r--tests/ref/list-tight-non-attached-tight.pngbin0 -> 798 bytes
-rw-r--r--tests/ref/list-top-level-indent.pngbin0 -> 679 bytes
-rw-r--r--tests/ref/list-wide-cannot-attach.pngbin0 -> 609 bytes
-rw-r--r--tests/ref/list-wide-really-cannot-attach.pngbin0 -> 605 bytes
-rw-r--r--tests/ref/locate-element-selector.pngbin0 -> 685 bytes
-rw-r--r--tests/ref/locate-position.pngbin0 -> 685 bytes
-rw-r--r--tests/ref/loop-break-join-in-first-arg.pngbin0 -> 380 bytes
-rw-r--r--tests/ref/loop-break-join-in-nested-blocks.pngbin0 -> 1043 bytes
-rw-r--r--tests/ref/loop-break-join-in-set-rule-args.pngbin0 -> 245 bytes
-rw-r--r--tests/ref/loop-break-join-set-and-show.pngbin0 -> 1128 bytes
-rw-r--r--tests/ref/lorem-pars.pngbin0 -> 6514 bytes
-rw-r--r--tests/ref/lorem.pngbin0 -> 2806 bytes
-rw-r--r--tests/ref/math-accent-align.pngbin0 -> 614 bytes
-rw-r--r--tests/ref/math-accent-bounds.pngbin0 -> 333 bytes
-rw-r--r--tests/ref/math-accent-func.pngbin0 -> 284 bytes
-rw-r--r--tests/ref/math-accent-high-base.pngbin0 -> 571 bytes
-rw-r--r--tests/ref/math-accent-sized.pngbin0 -> 379 bytes
-rw-r--r--tests/ref/math-accent-superscript.pngbin0 -> 472 bytes
-rw-r--r--tests/ref/math-accent-sym-call.pngbin0 -> 926 bytes
-rw-r--r--tests/ref/math-accent-wide-base.pngbin0 -> 510 bytes
-rw-r--r--tests/ref/math-align-aligned-in-source.pngbin0 -> 493 bytes
-rw-r--r--tests/ref/math-align-basic.pngbin0 -> 650 bytes
-rw-r--r--tests/ref/math-align-cases.pngbin0 -> 578 bytes
-rw-r--r--tests/ref/math-align-implicit.pngbin0 -> 706 bytes
-rw-r--r--tests/ref/math-align-lines-mixed.pngbin0 -> 430 bytes
-rw-r--r--tests/ref/math-align-post-fix.pngbin0 -> 709 bytes
-rw-r--r--tests/ref/math-align-toggle.pngbin0 -> 469 bytes
-rw-r--r--tests/ref/math-align-weird.pngbin0 -> 1243 bytes
-rw-r--r--tests/ref/math-align-wider-first-column.pngbin0 -> 704 bytes
-rw-r--r--tests/ref/math-attach-default-placement.pngbin0 -> 1866 bytes
-rw-r--r--tests/ref/math-attach-descender-collision.pngbin0 -> 739 bytes
-rw-r--r--tests/ref/math-attach-followed-by-func-call.pngbin0 -> 1206 bytes
-rw-r--r--tests/ref/math-attach-force-scripts-and-limits.pngbin0 -> 1137 bytes
-rw-r--r--tests/ref/math-attach-high.pngbin0 -> 1296 bytes
-rw-r--r--tests/ref/math-attach-horizontal-align.pngbin0 -> 2001 bytes
-rw-r--r--tests/ref/math-attach-integral.pngbin0 -> 965 bytes
-rw-r--r--tests/ref/math-attach-large-operator.pngbin0 -> 839 bytes
-rw-r--r--tests/ref/math-attach-limit.pngbin0 -> 727 bytes
-rw-r--r--tests/ref/math-attach-mixed.pngbin0 -> 2357 bytes
-rw-r--r--tests/ref/math-attach-nested.pngbin0 -> 952 bytes
-rw-r--r--tests/ref/math-attach-postscripts.pngbin0 -> 497 bytes
-rw-r--r--tests/ref/math-attach-prescripts.pngbin0 -> 675 bytes
-rw-r--r--tests/ref/math-attach-show-limit.pngbin0 -> 673 bytes
-rw-r--r--tests/ref/math-attach-subscript-multiline.pngbin0 -> 772 bytes
-rw-r--r--tests/ref/math-attach-to-group.pngbin0 -> 651 bytes
-rw-r--r--tests/ref/math-binom-multiple.pngbin0 -> 526 bytes
-rw-r--r--tests/ref/math-binom.pngbin0 -> 323 bytes
-rw-r--r--tests/ref/math-box-with-baseline.pngbin0 -> 225 bytes
-rw-r--r--tests/ref/math-box-without-baseline.pngbin0 -> 461 bytes
-rw-r--r--tests/ref/math-call-non-func.pngbin0 -> 761 bytes
-rw-r--r--tests/ref/math-cancel-angle-absolute.pngbin0 -> 353 bytes
-rw-r--r--tests/ref/math-cancel-angle-func.pngbin0 -> 938 bytes
-rw-r--r--tests/ref/math-cancel-cross.pngbin0 -> 1618 bytes
-rw-r--r--tests/ref/math-cancel-customized.pngbin0 -> 1062 bytes
-rw-r--r--tests/ref/math-cancel-display.pngbin0 -> 1315 bytes
-rw-r--r--tests/ref/math-cancel-inline.pngbin0 -> 621 bytes
-rw-r--r--tests/ref/math-cancel-inverted.pngbin0 -> 789 bytes
-rw-r--r--tests/ref/math-cases-gap.pngbin0 -> 285 bytes
-rw-r--r--tests/ref/math-cases.pngbin0 -> 1257 bytes
-rw-r--r--tests/ref/math-class-chars.pngbin0 -> 1331 bytes
-rw-r--r--tests/ref/math-class-content.pngbin0 -> 786 bytes
-rw-r--r--tests/ref/math-class-exceptions.pngbin0 -> 507 bytes
-rw-r--r--tests/ref/math-class-limits.pngbin0 -> 495 bytes
-rw-r--r--tests/ref/math-class-nested.pngbin0 -> 228 bytes
-rw-r--r--tests/ref/math-common-symbols.pngbin0 -> 243 bytes
-rw-r--r--tests/ref/math-dif.pngbin0 -> 1049 bytes
-rw-r--r--tests/ref/math-equation-align-numbered.pngbin0 -> 1314 bytes
-rw-r--r--tests/ref/math-equation-align-unnumbered.pngbin0 -> 826 bytes
-rw-r--r--tests/ref/math-equation-auto-wrapping.pngbin0 -> 160 bytes
-rw-r--r--tests/ref/math-equation-font.pngbin0 -> 1035 bytes
-rw-r--r--tests/ref/math-equation-number-align-end.pngbin0 -> 1541 bytes
-rw-r--r--tests/ref/math-equation-number-align-left.pngbin0 -> 1526 bytes
-rw-r--r--tests/ref/math-equation-number-align-multiline-bottom.pngbin0 -> 758 bytes
-rw-r--r--tests/ref/math-equation-number-align-multiline-expand.pngbin0 -> 510 bytes
-rw-r--r--tests/ref/math-equation-number-align-multiline-top-start.pngbin0 -> 597 bytes
-rw-r--r--tests/ref/math-equation-number-align-multiline.pngbin0 -> 587 bytes
-rw-r--r--tests/ref/math-equation-number-align-right.pngbin0 -> 1506 bytes
-rw-r--r--tests/ref/math-equation-number-align-start.pngbin0 -> 1528 bytes
-rw-r--r--tests/ref/math-equation-number-align.pngbin0 -> 1541 bytes
-rw-r--r--tests/ref/math-equation-numbering.pngbin0 -> 4604 bytes
-rw-r--r--tests/ref/math-equation-show-rule.pngbin0 -> 1033 bytes
-rw-r--r--tests/ref/math-font-fallback.pngbin0 -> 400 bytes
-rw-r--r--tests/ref/math-font-features.pngbin0 -> 1248 bytes
-rw-r--r--tests/ref/math-font-switch.pngbin0 -> 348 bytes
-rw-r--r--tests/ref/math-frac-associativity.pngbin0 -> 484 bytes
-rw-r--r--tests/ref/math-frac-baseline.pngbin0 -> 594 bytes
-rw-r--r--tests/ref/math-frac-large.pngbin0 -> 563 bytes
-rw-r--r--tests/ref/math-frac-paren-removal.pngbin0 -> 506 bytes
-rw-r--r--tests/ref/math-frac-precedence.pngbin0 -> 3867 bytes
-rw-r--r--tests/ref/math-linebreaking-after-binop-and-rel.pngbin0 -> 647 bytes
-rw-r--r--tests/ref/math-linebreaking-after-relation-without-space.pngbin0 -> 439 bytes
-rw-r--r--tests/ref/math-linebreaking-between-consecutive-relations.pngbin0 -> 387 bytes
-rw-r--r--tests/ref/math-linebreaking-empty.pngbin0 -> 615 bytes
-rw-r--r--tests/ref/math-linebreaking-in-box.pngbin0 -> 222 bytes
-rw-r--r--tests/ref/math-linebreaking-lr.pngbin0 -> 559 bytes
-rw-r--r--tests/ref/math-linebreaking-multiline.pngbin0 -> 317 bytes
-rw-r--r--tests/ref/math-linebreaking-trailing-linebreak.pngbin0 -> 296 bytes
-rw-r--r--tests/ref/math-lr-call.pngbin0 -> 847 bytes
-rw-r--r--tests/ref/math-lr-color.pngbin0 -> 670 bytes
-rw-r--r--tests/ref/math-lr-fences.pngbin0 -> 601 bytes
-rw-r--r--tests/ref/math-lr-half.pngbin0 -> 396 bytes
-rw-r--r--tests/ref/math-lr-matching.pngbin0 -> 1262 bytes
-rw-r--r--tests/ref/math-lr-mid.pngbin0 -> 1433 bytes
-rw-r--r--tests/ref/math-lr-shorthands.pngbin0 -> 452 bytes
-rw-r--r--tests/ref/math-lr-size.pngbin0 -> 663 bytes
-rw-r--r--tests/ref/math-lr-symbol-unmatched.pngbin0 -> 349 bytes
-rw-r--r--tests/ref/math-lr-unbalanced.pngbin0 -> 938 bytes
-rw-r--r--tests/ref/math-lr-unmatched.pngbin0 -> 542 bytes
-rw-r--r--tests/ref/math-lr-weak-spacing.pngbin0 -> 647 bytes
-rw-r--r--tests/ref/math-mat-align-complex.pngbin0 -> 1522 bytes
-rw-r--r--tests/ref/math-mat-align-explicit--alternating.pngbin0 -> 908 bytes
-rw-r--r--tests/ref/math-mat-align-explicit-left.pngbin0 -> 896 bytes
-rw-r--r--tests/ref/math-mat-align-explicit-right.pngbin0 -> 916 bytes
-rw-r--r--tests/ref/math-mat-align-implicit.pngbin0 -> 930 bytes
-rw-r--r--tests/ref/math-mat-align-signed-numbers.pngbin0 -> 1865 bytes
-rw-r--r--tests/ref/math-mat-augment-set.pngbin0 -> 1716 bytes
-rw-r--r--tests/ref/math-mat-augment.pngbin0 -> 3489 bytes
-rw-r--r--tests/ref/math-mat-baseline.pngbin0 -> 837 bytes
-rw-r--r--tests/ref/math-mat-delim-direct.pngbin0 -> 1090 bytes
-rw-r--r--tests/ref/math-mat-delim-set.pngbin0 -> 580 bytes
-rw-r--r--tests/ref/math-mat-gap.pngbin0 -> 489 bytes
-rw-r--r--tests/ref/math-mat-gaps.pngbin0 -> 493 bytes
-rw-r--r--tests/ref/math-mat-semicolon.pngbin0 -> 1091 bytes
-rw-r--r--tests/ref/math-mat-sparse.pngbin0 -> 898 bytes
-rw-r--r--tests/ref/math-multiline-multiple-trailing-linebreaks.pngbin0 -> 927 bytes
-rw-r--r--tests/ref/math-multiline-no-trailing-linebreak.pngbin0 -> 759 bytes
-rw-r--r--tests/ref/math-multiline-trailing-linebreak.pngbin0 -> 786 bytes
-rw-r--r--tests/ref/math-nested-normal-layout.pngbin0 -> 1253 bytes
-rw-r--r--tests/ref/math-non-math-content.pngbin0 -> 195 bytes
-rw-r--r--tests/ref/math-op-call.pngbin0 -> 795 bytes
-rw-r--r--tests/ref/math-op-custom.pngbin0 -> 589 bytes
-rw-r--r--tests/ref/math-op-predefined.pngbin0 -> 378 bytes
-rw-r--r--tests/ref/math-op-scripts-vs-limits.pngbin0 -> 863 bytes
-rw-r--r--tests/ref/math-op-styled.pngbin0 -> 314 bytes
-rw-r--r--tests/ref/math-optical-size-frac-script-script.pngbin0 -> 553 bytes
-rw-r--r--tests/ref/math-optical-size-nested-scripts.pngbin0 -> 769 bytes
-rw-r--r--tests/ref/math-optical-size-prime-large-operator.pngbin0 -> 685 bytes
-rw-r--r--tests/ref/math-optical-size-primes.pngbin0 -> 949 bytes
-rw-r--r--tests/ref/math-primes-after-code-expr.pngbin0 -> 484 bytes
-rw-r--r--tests/ref/math-primes-attach.pngbin0 -> 374 bytes
-rw-r--r--tests/ref/math-primes-complex.pngbin0 -> 1274 bytes
-rw-r--r--tests/ref/math-primes-limits.pngbin0 -> 468 bytes
-rw-r--r--tests/ref/math-primes-scripts.pngbin0 -> 513 bytes
-rw-r--r--tests/ref/math-primes-spaces.pngbin0 -> 342 bytes
-rw-r--r--tests/ref/math-primes.pngbin0 -> 341 bytes
-rw-r--r--tests/ref/math-root-basic.pngbin0 -> 366 bytes
-rw-r--r--tests/ref/math-root-large-body.pngbin0 -> 1614 bytes
-rw-r--r--tests/ref/math-root-large-index.pngbin0 -> 638 bytes
-rw-r--r--tests/ref/math-root-precomposed.pngbin0 -> 602 bytes
-rw-r--r--tests/ref/math-root-radical-attachment.pngbin0 -> 1021 bytes
-rw-r--r--tests/ref/math-root-syntax.pngbin0 -> 1255 bytes
-rw-r--r--tests/ref/math-shorthandes.pngbin0 -> 1211 bytes
-rw-r--r--tests/ref/math-size.pngbin0 -> 729 bytes
-rw-r--r--tests/ref/math-spacing-basic.pngbin0 -> 2595 bytes
-rw-r--r--tests/ref/math-spacing-decorated.pngbin0 -> 2378 bytes
-rw-r--r--tests/ref/math-spacing-kept-spaces.pngbin0 -> 884 bytes
-rw-r--r--tests/ref/math-spacing-predefined.pngbin0 -> 847 bytes
-rw-r--r--tests/ref/math-spacing-set-comprehension.pngbin0 -> 687 bytes
-rw-r--r--tests/ref/math-spacing-weak.pngbin0 -> 861 bytes
-rw-r--r--tests/ref/math-style-exceptions.pngbin0 -> 1304 bytes
-rw-r--r--tests/ref/math-style-greek-exceptions.pngbin0 -> 301 bytes
-rw-r--r--tests/ref/math-style-hebrew-exceptions.pngbin0 -> 296 bytes
-rw-r--r--tests/ref/math-style-italic-default.pngbin0 -> 466 bytes
-rw-r--r--tests/ref/math-style.pngbin0 -> 1784 bytes
-rw-r--r--tests/ref/math-symbol-show-rule.pngbin0 -> 194 bytes
-rw-r--r--tests/ref/math-table.pngbin0 -> 770 bytes
-rw-r--r--tests/ref/math-text-color.pngbin0 -> 860 bytes
-rw-r--r--tests/ref/math-underover-brace.pngbin0 -> 751 bytes
-rw-r--r--tests/ref/math-underover-brackets.pngbin0 -> 1029 bytes
-rw-r--r--tests/ref/math-underover-line-bracket.pngbin0 -> 469 bytes
-rw-r--r--tests/ref/math-unicode.pngbin0 -> 743 bytes
-rw-r--r--tests/ref/math-vec-align-explicit-alternating.pngbin0 -> 908 bytes
-rw-r--r--tests/ref/math-vec-delim-set.pngbin0 -> 196 bytes
-rw-r--r--tests/ref/math-vec-gap.pngbin0 -> 397 bytes
-rw-r--r--tests/ref/math-vec-wide.pngbin0 -> 591 bytes
-rw-r--r--tests/ref/math/accent.pngbin8774 -> 0 bytes
-rw-r--r--tests/ref/math/alignment.pngbin7837 -> 0 bytes
-rw-r--r--tests/ref/math/attach-p1.pngbin14206 -> 0 bytes
-rw-r--r--tests/ref/math/attach-p2.pngbin11007 -> 0 bytes
-rw-r--r--tests/ref/math/attach-p3.pngbin15051 -> 0 bytes
-rw-r--r--tests/ref/math/call.pngbin1972 -> 0 bytes
-rw-r--r--tests/ref/math/cancel.pngbin24771 -> 0 bytes
-rw-r--r--tests/ref/math/cases.pngbin3001 -> 0 bytes
-rw-r--r--tests/ref/math/class.pngbin7545 -> 0 bytes
-rw-r--r--tests/ref/math/content.pngbin10306 -> 0 bytes
-rw-r--r--tests/ref/math/delimited.pngbin101279 -> 0 bytes
-rw-r--r--tests/ref/math/equation-block-align.pngbin5170 -> 0 bytes
-rw-r--r--tests/ref/math/equation-number.pngbin23214 -> 0 bytes
-rw-r--r--tests/ref/math/equation-show.pngbin2428 -> 0 bytes
-rw-r--r--tests/ref/math/font-features.pngbin2985 -> 0 bytes
-rw-r--r--tests/ref/math/frac.pngbin30979 -> 0 bytes
-rw-r--r--tests/ref/math/linebreak.pngbin7385 -> 0 bytes
-rw-r--r--tests/ref/math/matrix-alignment.pngbin14061 -> 0 bytes
-rw-r--r--tests/ref/math/matrix-gaps.pngbin2925 -> 0 bytes
-rw-r--r--tests/ref/math/matrix.pngbin43626 -> 0 bytes
-rw-r--r--tests/ref/math/multiline.pngbin13232 -> 0 bytes
-rw-r--r--tests/ref/math/numbering.pngbin12055 -> 0 bytes
-rw-r--r--tests/ref/math/op.pngbin6762 -> 0 bytes
-rw-r--r--tests/ref/math/opticalsize.pngbin14078 -> 0 bytes
-rw-r--r--tests/ref/math/prime.pngbin1248 -> 0 bytes
-rw-r--r--tests/ref/math/root.pngbin12190 -> 0 bytes
-rw-r--r--tests/ref/math/spacing.pngbin100588 -> 0 bytes
-rw-r--r--tests/ref/math/style.pngbin29726 -> 0 bytes
-rw-r--r--tests/ref/math/syntax.pngbin4868 -> 0 bytes
-rw-r--r--tests/ref/math/unbalanced.pngbin2432 -> 0 bytes
-rw-r--r--tests/ref/math/underover.pngbin4868 -> 0 bytes
-rw-r--r--tests/ref/math/vec.pngbin1606 -> 0 bytes
-rw-r--r--tests/ref/meta/bibliography-full.pngbin149158 -> 0 bytes
-rw-r--r--tests/ref/meta/bibliography-ordering.pngbin140609 -> 0 bytes
-rw-r--r--tests/ref/meta/bibliography.pngbin108543 -> 0 bytes
-rw-r--r--tests/ref/meta/cite-footnote.pngbin36731 -> 0 bytes
-rw-r--r--tests/ref/meta/cite-form.pngbin32970 -> 0 bytes
-rw-r--r--tests/ref/meta/cite-group.pngbin15233 -> 0 bytes
-rw-r--r--tests/ref/meta/counter-page.pngbin14766 -> 0 bytes
-rw-r--r--tests/ref/meta/counter.pngbin44350 -> 0 bytes
-rw-r--r--tests/ref/meta/document.pngbin965 -> 0 bytes
-rw-r--r--tests/ref/meta/figure-caption.pngbin9192 -> 0 bytes
-rw-r--r--tests/ref/meta/figure-localization.pngbin6629 -> 0 bytes
-rw-r--r--tests/ref/meta/figure.pngbin54114 -> 0 bytes
-rw-r--r--tests/ref/meta/footnote-break.pngbin90355 -> 0 bytes
-rw-r--r--tests/ref/meta/footnote-columns.pngbin10873 -> 0 bytes
-rw-r--r--tests/ref/meta/footnote-container.pngbin45934 -> 0 bytes
-rw-r--r--tests/ref/meta/footnote-invariant.pngbin28671 -> 0 bytes
-rw-r--r--tests/ref/meta/footnote-refs.pngbin28467 -> 0 bytes
-rw-r--r--tests/ref/meta/footnote-table.pngbin40162 -> 0 bytes
-rw-r--r--tests/ref/meta/footnote.pngbin18782 -> 0 bytes
-rw-r--r--tests/ref/meta/heading.pngbin41024 -> 0 bytes
-rw-r--r--tests/ref/meta/link.pngbin59069 -> 0 bytes
-rw-r--r--tests/ref/meta/numbering.pngbin62312 -> 0 bytes
-rw-r--r--tests/ref/meta/outline-entry.pngbin62248 -> 0 bytes
-rw-r--r--tests/ref/meta/outline-first-par-indent.pngbin28062 -> 0 bytes
-rw-r--r--tests/ref/meta/outline-indent.pngbin309889 -> 0 bytes
-rw-r--r--tests/ref/meta/outline.pngbin108902 -> 0 bytes
-rw-r--r--tests/ref/meta/page-label.pngbin123792 -> 0 bytes
-rw-r--r--tests/ref/meta/query-before-after.pngbin122197 -> 0 bytes
-rw-r--r--tests/ref/meta/query-figure.pngbin290886 -> 0 bytes
-rw-r--r--tests/ref/meta/query-header.pngbin121241 -> 0 bytes
-rw-r--r--tests/ref/meta/ref.pngbin34099 -> 0 bytes
-rw-r--r--tests/ref/meta/state.pngbin47195 -> 0 bytes
-rw-r--r--tests/ref/newline-continuation-code.pngbin0 -> 1230 bytes
-rw-r--r--tests/ref/newline-continuation-markup.pngbin0 -> 906 bytes
-rw-r--r--tests/ref/numbering-chinese.pngbin0 -> 2610 bytes
-rw-r--r--tests/ref/numbering-hebrew.pngbin0 -> 1340 bytes
-rw-r--r--tests/ref/numbering-japanese-aiueo.pngbin0 -> 6285 bytes
-rw-r--r--tests/ref/numbering-japanese-iroha.pngbin0 -> 3840 bytes
-rw-r--r--tests/ref/numbering-korean.pngbin0 -> 4068 bytes
-rw-r--r--tests/ref/numbering-latin.pngbin0 -> 2670 bytes
-rw-r--r--tests/ref/numbering-symbol-and-roman.pngbin0 -> 2997 bytes
-rw-r--r--tests/ref/numbers.pngbin0 -> 4426 bytes
-rw-r--r--tests/ref/ops-add-content.pngbin0 -> 432 bytes
-rw-r--r--tests/ref/ops-multiply-inf-with-length.pngbin0 -> 1066 bytes
-rw-r--r--tests/ref/outline-entry-complex.pngbin0 -> 14497 bytes
-rw-r--r--tests/ref/outline-entry.pngbin0 -> 10052 bytes
-rw-r--r--tests/ref/outline-first-line-indent.pngbin0 -> 10876 bytes
-rw-r--r--tests/ref/outline-indent-no-numbering.pngbin0 -> 4637 bytes
-rw-r--r--tests/ref/outline-indent-numbering.pngbin0 -> 21639 bytes
-rw-r--r--tests/ref/outline.pngbin0 -> 40746 bytes
-rw-r--r--tests/ref/overhang-lone.pngbin0 -> 93 bytes
-rw-r--r--tests/ref/overhang.pngbin0 -> 3848 bytes
-rw-r--r--tests/ref/overline-background.pngbin0 -> 1498 bytes
-rw-r--r--tests/ref/pad-basic.pngbin0 -> 1158 bytes
-rw-r--r--tests/ref/pad-expanding-contents.pngbin0 -> 254 bytes
-rw-r--r--tests/ref/pad-followed-by-content.pngbin0 -> 11851 bytes
-rw-r--r--tests/ref/page-call-followed-by-pagebreak.pngbin0 -> 116 bytes
-rw-r--r--tests/ref/page-call-styled-empty.pngbin0 -> 86 bytes
-rw-r--r--tests/ref/page-fill.pngbin0 -> 824 bytes
-rw-r--r--tests/ref/page-large.pngbin0 -> 304 bytes
-rw-r--r--tests/ref/page-margin-binding-from-text-lang.pngbin0 -> 534 bytes
-rw-r--r--tests/ref/page-margin-individual.pngbin0 -> 1007 bytes
-rw-r--r--tests/ref/page-margin-inside-outside-override.pngbin0 -> 5076 bytes
-rw-r--r--tests/ref/page-margin-inside-with-binding.pngbin0 -> 535 bytes
-rw-r--r--tests/ref/page-margin-inside.pngbin0 -> 466 bytes
-rw-r--r--tests/ref/page-margin-uniform.pngbin0 -> 243 bytes
-rw-r--r--tests/ref/page-marginals.pngbin0 -> 19769 bytes
-rw-r--r--tests/ref/page-number-align-bottom-left.pngbin0 -> 280 bytes
-rw-r--r--tests/ref/page-number-align-top-right.pngbin0 -> 347 bytes
-rw-r--r--tests/ref/page-numbering-pdf-label.pngbin0 -> 8905 bytes
-rw-r--r--tests/ref/page-set-empty.pngbin0 -> 86 bytes
-rw-r--r--tests/ref/page-set-forces-break.pngbin0 -> 753 bytes
-rw-r--r--tests/ref/page-set-only-pagebreak.pngbin0 -> 94 bytes
-rw-r--r--tests/ref/page-set-override-and-mix.pngbin0 -> 414 bytes
-rw-r--r--tests/ref/page-set-override-thrice.pngbin0 -> 73 bytes
-rw-r--r--tests/ref/pagebreak-around-set-page.pngbin0 -> 116 bytes
-rw-r--r--tests/ref/pagebreak-followed-by-page-call.pngbin0 -> 1066 bytes
-rw-r--r--tests/ref/pagebreak-meta.pngbin0 -> 412 bytes
-rw-r--r--tests/ref/pagebreak-set-page-mixed.pngbin0 -> 949 bytes
-rw-r--r--tests/ref/pagebreak-to-auto-sized.pngbin0 -> 381 bytes
-rw-r--r--tests/ref/pagebreak-to-multiple-pages.pngbin0 -> 576 bytes
-rw-r--r--tests/ref/pagebreak-to.pngbin0 -> 1067 bytes
-rw-r--r--tests/ref/pagebreak-weak-after-set-page.pngbin0 -> 815 bytes
-rw-r--r--tests/ref/pagebreak-weak-meta.pngbin0 -> 430 bytes
-rw-r--r--tests/ref/pagebreak-weak-place.pngbin0 -> 620 bytes
-rw-r--r--tests/ref/pagebreak.pngbin0 -> 80 bytes
-rw-r--r--tests/ref/par-basic.pngbin0 -> 20380 bytes
-rw-r--r--tests/ref/par-first-line-indent.pngbin0 -> 11026 bytes
-rw-r--r--tests/ref/par-hanging-indent-manual-linebreak.pngbin0 -> 886 bytes
-rw-r--r--tests/ref/par-hanging-indent-rtl.pngbin0 -> 2114 bytes
-rw-r--r--tests/ref/par-hanging-indent.pngbin0 -> 1473 bytes
-rw-r--r--tests/ref/par-leading-and-block-spacing.pngbin0 -> 2017 bytes
-rw-r--r--tests/ref/par-spacing-and-first-line-indent.pngbin0 -> 1333 bytes
-rw-r--r--tests/ref/parser-backtracking-destructuring-whitespace.pngbin0 -> 444 bytes
-rw-r--r--tests/ref/path.pngbin0 -> 3150 bytes
-rw-r--r--tests/ref/pattern-line.pngbin0 -> 125 bytes
-rw-r--r--tests/ref/pattern-lines.pngbin0 -> 128 bytes
-rw-r--r--tests/ref/pattern-relative-parent.pngbin0 -> 347 bytes
-rw-r--r--tests/ref/pattern-relative-self.pngbin0 -> 423 bytes
-rw-r--r--tests/ref/pattern-small.pngbin0 -> 83 bytes
-rw-r--r--tests/ref/pattern-spacing-negative.pngbin0 -> 202 bytes
-rw-r--r--tests/ref/pattern-spacing-positive.pngbin0 -> 241 bytes
-rw-r--r--tests/ref/pattern-spacing-zero.pngbin0 -> 215 bytes
-rw-r--r--tests/ref/pattern-stroke.pngbin0 -> 248 bytes
-rw-r--r--tests/ref/pattern-text.pngbin0 -> 5324 bytes
-rw-r--r--tests/ref/place-background.pngbin0 -> 19865 bytes
-rw-r--r--tests/ref/place-basic.pngbin0 -> 11811 bytes
-rw-r--r--tests/ref/place-block-spacing.pngbin0 -> 632 bytes
-rw-r--r--tests/ref/place-bottom-in-box.pngbin0 -> 137 bytes
-rw-r--r--tests/ref/place-bottom-right-in-box.pngbin0 -> 1445 bytes
-rw-r--r--tests/ref/place-float-columns.pngbin0 -> 28138 bytes
-rw-r--r--tests/ref/place-float-figure.pngbin0 -> 35400 bytes
-rw-r--r--tests/ref/place-float.pngbin0 -> 1338 bytes
-rw-r--r--tests/ref/place-horizon-in-boxes.pngbin0 -> 570 bytes
-rw-r--r--tests/ref/place-top-left-in-box.pngbin0 -> 325 bytes
-rw-r--r--tests/ref/polygon-line-join.pngbin0 -> 577 bytes
-rw-r--r--tests/ref/polygon.pngbin0 -> 3375 bytes
-rw-r--r--tests/ref/query-and-or.pngbin0 -> 1664 bytes
-rw-r--r--tests/ref/query-before-after.pngbin0 -> 35716 bytes
-rw-r--r--tests/ref/query-complex.pngbin0 -> 1458 bytes
-rw-r--r--tests/ref/query-list-of-figures.pngbin0 -> 10471 bytes
-rw-r--r--tests/ref/query-running-header.pngbin0 -> 9017 bytes
-rw-r--r--tests/ref/quote-block-spacing.pngbin0 -> 2798 bytes
-rw-r--r--tests/ref/quote-cite-format-author-date.pngbin0 -> 2208 bytes
-rw-r--r--tests/ref/quote-cite-format-label-or-numeric.pngbin0 -> 2224 bytes
-rw-r--r--tests/ref/quote-cite-format-note.pngbin0 -> 2943 bytes
-rw-r--r--tests/ref/quote-dir-align.pngbin0 -> 1115 bytes
-rw-r--r--tests/ref/quote-dir-author-pos.pngbin0 -> 967 bytes
-rw-r--r--tests/ref/quote-inline.pngbin0 -> 1523 bytes
-rw-r--r--tests/ref/quote-nesting-custom.pngbin0 -> 545 bytes
-rw-r--r--tests/ref/quote-nesting.pngbin0 -> 4523 bytes
-rw-r--r--tests/ref/raw-align-default.pngbin0 -> 5747 bytes
-rw-r--r--tests/ref/raw-align-specified.pngbin0 -> 5474 bytes
-rw-r--r--tests/ref/raw-block-no-parbreaks.pngbin0 -> 1293 bytes
-rw-r--r--tests/ref/raw-consecutive-single-backticks.pngbin0 -> 177 bytes
-rw-r--r--tests/ref/raw-dedent-empty-line.pngbin0 -> 202 bytes
-rw-r--r--tests/ref/raw-dedent-first-line.pngbin0 -> 242 bytes
-rw-r--r--tests/ref/raw-dedent-last-line.pngbin0 -> 206 bytes
-rw-r--r--tests/ref/raw-empty.pngbin0 -> 542 bytes
-rw-r--r--tests/ref/raw-highlight-typ.pngbin0 -> 25790 bytes
-rw-r--r--tests/ref/raw-highlight.pngbin0 -> 14370 bytes
-rw-r--r--tests/ref/raw-inline-multiline.pngbin0 -> 5127 bytes
-rw-r--r--tests/ref/raw-line-alternating-fill.pngbin0 -> 5028 bytes
-rw-r--r--tests/ref/raw-line-text-fill.pngbin0 -> 4362 bytes
-rw-r--r--tests/ref/raw-line.pngbin0 -> 3926 bytes
-rw-r--r--tests/ref/raw-more-backticks.pngbin0 -> 412 bytes
-rw-r--r--tests/ref/raw-show-set.pngbin0 -> 253 bytes
-rw-r--r--tests/ref/raw-single-backtick-lang.pngbin0 -> 303 bytes
-rw-r--r--tests/ref/raw-syntaxes.pngbin0 -> 1564 bytes
-rw-r--r--tests/ref/raw-tab-size.pngbin0 -> 1543 bytes
-rw-r--r--tests/ref/raw-theme.pngbin0 -> 1874 bytes
-rw-r--r--tests/ref/raw-trimming.pngbin0 -> 2274 bytes
-rw-r--r--tests/ref/raw-typst-lang.pngbin0 -> 922 bytes
-rw-r--r--tests/ref/rect-customization.pngbin0 -> 3895 bytes
-rw-r--r--tests/ref/rect-fill-stroke.pngbin0 -> 1367 bytes
-rw-r--r--tests/ref/rect-stroke.pngbin0 -> 322 bytes
-rw-r--r--tests/ref/rect.pngbin0 -> 135 bytes
-rw-r--r--tests/ref/ref-basic.pngbin0 -> 3979 bytes
-rw-r--r--tests/ref/ref-supplements.pngbin0 -> 8276 bytes
-rw-r--r--tests/ref/repeat-align-and-dir.pngbin0 -> 621 bytes
-rw-r--r--tests/ref/repeat-basic.pngbin0 -> 2330 bytes
-rw-r--r--tests/ref/repeat-dots-rtl.pngbin0 -> 441 bytes
-rw-r--r--tests/ref/repeat-empty.pngbin0 -> 195 bytes
-rw-r--r--tests/ref/repeat-unboxed.pngbin0 -> 133 bytes
-rw-r--r--tests/ref/repr-color.pngbin0 -> 7598 bytes
-rw-r--r--tests/ref/repr-literals.pngbin0 -> 1444 bytes
-rw-r--r--tests/ref/repr-misc.pngbin0 -> 7440 bytes
-rw-r--r--tests/ref/repr-numerical.pngbin0 -> 6688 bytes
-rw-r--r--tests/ref/return-in-nested-content-block.pngbin0 -> 1051 bytes
-rw-r--r--tests/ref/set-if.pngbin0 -> 1576 bytes
-rw-r--r--tests/ref/set-instantiation-site-markup.pngbin0 -> 1084 bytes
-rw-r--r--tests/ref/set-instantiation-site.pngbin0 -> 430 bytes
-rw-r--r--tests/ref/set-scoped-in-code-block.pngbin0 -> 958 bytes
-rw-r--r--tests/ref/set-text-override.pngbin0 -> 1175 bytes
-rw-r--r--tests/ref/set-vs-construct-1.pngbin0 -> 402 bytes
-rw-r--r--tests/ref/set-vs-construct-2.pngbin0 -> 338 bytes
-rw-r--r--tests/ref/set-vs-construct-3.pngbin0 -> 99 bytes
-rw-r--r--tests/ref/set-vs-construct-4.pngbin0 -> 440 bytes
-rw-r--r--tests/ref/shaping-emoji-bad-zwj.pngbin0 -> 647 bytes
-rw-r--r--tests/ref/shaping-emoji-basic.pngbin0 -> 952 bytes
-rw-r--r--tests/ref/shaping-font-fallback.pngbin0 -> 3823 bytes
-rw-r--r--tests/ref/shaping-forced-script-font-feature-enabled.pngbin0 -> 450 bytes
-rw-r--r--tests/ref/shaping-forced-script-font-feature-inhibited.pngbin0 -> 491 bytes
-rw-r--r--tests/ref/shaping-script-separation.pngbin0 -> 993 bytes
-rw-r--r--tests/ref/shorthand-dashes.pngbin0 -> 598 bytes
-rw-r--r--tests/ref/shorthand-ellipsis.pngbin0 -> 311 bytes
-rw-r--r--tests/ref/shorthand-nbsp-and-shy-hyphen.pngbin0 -> 1352 bytes
-rw-r--r--tests/ref/shorthand-nbsp-width.pngbin0 -> 266 bytes
-rw-r--r--tests/ref/shorthands-math.pngbin0 -> 1832 bytes
-rw-r--r--tests/ref/show-bare-basic.pngbin0 -> 3286 bytes
-rw-r--r--tests/ref/show-bare-content-block.pngbin0 -> 289 bytes
-rw-r--r--tests/ref/show-bare-replace-with-content.pngbin0 -> 275 bytes
-rw-r--r--tests/ref/show-bare-vs-set-text.pngbin0 -> 725 bytes
-rw-r--r--tests/ref/show-function-order-with-set.pngbin0 -> 1504 bytes
-rw-r--r--tests/ref/show-function-set-on-it.pngbin0 -> 555 bytes
-rw-r--r--tests/ref/show-in-show.pngbin0 -> 638 bytes
-rw-r--r--tests/ref/show-multiple-rules.pngbin0 -> 352 bytes
-rw-r--r--tests/ref/show-nested-scopes.pngbin0 -> 1347 bytes
-rw-r--r--tests/ref/show-recursive-identity.pngbin0 -> 555 bytes
-rw-r--r--tests/ref/show-recursive-multiple.pngbin0 -> 2035 bytes
-rw-r--r--tests/ref/show-rule-in-function.pngbin0 -> 2667 bytes
-rw-r--r--tests/ref/show-selector-basic.pngbin0 -> 452 bytes
-rw-r--r--tests/ref/show-selector-discard.pngbin0 -> 613 bytes
-rw-r--r--tests/ref/show-selector-element-or-label.pngbin0 -> 2390 bytes
-rw-r--r--tests/ref/show-selector-or-elements-with-set.pngbin0 -> 915 bytes
-rw-r--r--tests/ref/show-selector-realistic.pngbin0 -> 3867 bytes
-rw-r--r--tests/ref/show-selector-replace-and-show-set.pngbin0 -> 241 bytes
-rw-r--r--tests/ref/show-selector-replace.pngbin0 -> 330 bytes
-rw-r--r--tests/ref/show-selector-where.pngbin0 -> 5290 bytes
-rw-r--r--tests/ref/show-set-on-layoutable-element.pngbin0 -> 716 bytes
-rw-r--r--tests/ref/show-set-on-same-element.pngbin0 -> 919 bytes
-rw-r--r--tests/ref/show-set-override.pngbin0 -> 1615 bytes
-rw-r--r--tests/ref/show-set-same-element-and-order.pngbin0 -> 2453 bytes
-rw-r--r--tests/ref/show-set-same-element-matched-field.pngbin0 -> 603 bytes
-rw-r--r--tests/ref/show-set-same-element-matching-interaction.pngbin0 -> 677 bytes
-rw-r--r--tests/ref/show-set-same-element-synthesized-matched-field.pngbin0 -> 554 bytes
-rw-r--r--tests/ref/show-set-text-order-adjacent-1.pngbin0 -> 458 bytes
-rw-r--r--tests/ref/show-set-text-order-adjacent-2.pngbin0 -> 480 bytes
-rw-r--r--tests/ref/show-set-text-order-contained-1.pngbin0 -> 488 bytes
-rw-r--r--tests/ref/show-set-text-order-contained-2.pngbin0 -> 249 bytes
-rw-r--r--tests/ref/show-set-text-order-contained-3.pngbin0 -> 458 bytes
-rw-r--r--tests/ref/show-set-text-order-contained-4.pngbin0 -> 505 bytes
-rw-r--r--tests/ref/show-set-text-order-overlapping-1.pngbin0 -> 653 bytes
-rw-r--r--tests/ref/show-set-text-order-overlapping-2.pngbin0 -> 321 bytes
-rw-r--r--tests/ref/show-set-vs-construct.pngbin0 -> 772 bytes
-rw-r--r--tests/ref/show-set-where-override.pngbin0 -> 1493 bytes
-rw-r--r--tests/ref/show-text-basic.pngbin0 -> 971 bytes
-rw-r--r--tests/ref/show-text-cyclic-raw.pngbin0 -> 463 bytes
-rw-r--r--tests/ref/show-text-cyclic.pngbin0 -> 840 bytes
-rw-r--r--tests/ref/show-text-exactly-once.pngbin0 -> 477 bytes
-rw-r--r--tests/ref/show-text-get-text-on-it.pngbin0 -> 615 bytes
-rw-r--r--tests/ref/show-text-in-other-show.pngbin0 -> 758 bytes
-rw-r--r--tests/ref/show-text-indirectly-cyclic.pngbin0 -> 646 bytes
-rw-r--r--tests/ref/show-text-path-resolving.pngbin0 -> 1722 bytes
-rw-r--r--tests/ref/show-text-regex-case-insensitive.pngbin0 -> 3771 bytes
-rw-r--r--tests/ref/show-text-regex-character-class.pngbin0 -> 1510 bytes
-rw-r--r--tests/ref/show-text-regex-word-boundary.pngbin0 -> 1655 bytes
-rw-r--r--tests/ref/show-text-regex.pngbin0 -> 1031 bytes
-rw-r--r--tests/ref/show-where-folding-stroke.pngbin0 -> 718 bytes
-rw-r--r--tests/ref/show-where-folding-text-size.pngbin0 -> 1330 bytes
-rw-r--r--tests/ref/show-where-optional-field-raw.pngbin0 -> 780 bytes
-rw-r--r--tests/ref/show-where-optional-field-text.pngbin0 -> 2068 bytes
-rw-r--r--tests/ref/show-where-resolving-hyphenate.pngbin0 -> 476 bytes
-rw-r--r--tests/ref/show-where-resolving-length.pngbin0 -> 369 bytes
-rw-r--r--tests/ref/smallcaps.pngbin0 -> 377 bytes
-rw-r--r--tests/ref/smartquote-apostrophe.pngbin0 -> 1845 bytes
-rw-r--r--tests/ref/smartquote-custom-complex.pngbin0 -> 1561 bytes
-rw-r--r--tests/ref/smartquote-custom.pngbin0 -> 1527 bytes
-rw-r--r--tests/ref/smartquote-disable.pngbin0 -> 2575 bytes
-rw-r--r--tests/ref/smartquote-disabled-temporarily.pngbin0 -> 2804 bytes
-rw-r--r--tests/ref/smartquote-empty.pngbin0 -> 126 bytes
-rw-r--r--tests/ref/smartquote-escape.pngbin0 -> 1307 bytes
-rw-r--r--tests/ref/smartquote-nesting.pngbin0 -> 1163 bytes
-rw-r--r--tests/ref/smartquote.pngbin0 -> 20489 bytes
-rw-r--r--tests/ref/space-collapsing-comments.pngbin0 -> 503 bytes
-rw-r--r--tests/ref/space-collapsing-linebreaks.pngbin0 -> 266 bytes
-rw-r--r--tests/ref/space-collapsing-stringy-linebreak.pngbin0 -> 208 bytes
-rw-r--r--tests/ref/space-collapsing-with-h.pngbin0 -> 399 bytes
-rw-r--r--tests/ref/space-collapsing.pngbin0 -> 1370 bytes
-rw-r--r--tests/ref/space-ideographic-kept.pngbin0 -> 905 bytes
-rw-r--r--tests/ref/space-thin-kept.pngbin0 -> 1021 bytes
-rw-r--r--tests/ref/space-trailing-linebreak.pngbin0 -> 596 bytes
-rw-r--r--tests/ref/spacing-h-and-v.pngbin0 -> 860 bytes
-rw-r--r--tests/ref/spacing-rtl.pngbin0 -> 299 bytes
-rw-r--r--tests/ref/square-auto-sized.pngbin0 -> 544 bytes
-rw-r--r--tests/ref/square-base.pngbin0 -> 169 bytes
-rw-r--r--tests/ref/square-circle-alignment.pngbin0 -> 870 bytes
-rw-r--r--tests/ref/square-circle-overspecified.pngbin0 -> 321 bytes
-rw-r--r--tests/ref/square-contents-overflow.pngbin0 -> 2749 bytes
-rw-r--r--tests/ref/square-height-limited-stack.pngbin0 -> 83 bytes
-rw-r--r--tests/ref/square-height-limited.pngbin0 -> 2346 bytes
-rw-r--r--tests/ref/square-overflow.pngbin0 -> 305 bytes
-rw-r--r--tests/ref/square-rect-rounded.pngbin0 -> 857 bytes
-rw-r--r--tests/ref/square-relative-size.pngbin0 -> 339 bytes
-rw-r--r--tests/ref/square-relatively-sized-child.pngbin0 -> 197 bytes
-rw-r--r--tests/ref/square.pngbin0 -> 314 bytes
-rw-r--r--tests/ref/stack-basic.pngbin0 -> 129 bytes
-rw-r--r--tests/ref/stack-fr.pngbin0 -> 2205 bytes
-rw-r--r--tests/ref/stack-overflow.pngbin0 -> 103 bytes
-rw-r--r--tests/ref/stack-rtl-align-and-fr.pngbin0 -> 288 bytes
-rw-r--r--tests/ref/stack-spacing.pngbin0 -> 114 bytes
-rw-r--r--tests/ref/state-basic.pngbin0 -> 758 bytes
-rw-r--r--tests/ref/state-multiple-calls-same-key.pngbin0 -> 245 bytes
-rw-r--r--tests/ref/state-nested.pngbin0 -> 15413 bytes
-rw-r--r--tests/ref/state-no-convergence.pngbin0 -> 139 bytes
-rw-r--r--tests/ref/strike-background.pngbin0 -> 1546 bytes
-rw-r--r--tests/ref/strike-with.pngbin0 -> 2539 bytes
-rw-r--r--tests/ref/stroke-composition.pngbin0 -> 2776 bytes
-rw-r--r--tests/ref/stroke-folding.pngbin0 -> 231 bytes
-rw-r--r--tests/ref/stroke-text.pngbin0 -> 14345 bytes
-rw-r--r--tests/ref/stroke-zero-thickness.pngbin0 -> 621 bytes
-rw-r--r--tests/ref/strong-delta.pngbin0 -> 907 bytes
-rw-r--r--tests/ref/strong-double-star-empty-hint.pngbin0 -> 314 bytes
-rw-r--r--tests/ref/sub-super-non-typographic.pngbin0 -> 312 bytes
-rw-r--r--tests/ref/sub-super.pngbin0 -> 1097 bytes
-rw-r--r--tests/ref/super-underline.pngbin0 -> 1846 bytes
-rw-r--r--tests/ref/symbol-constructor.pngbin0 -> 511 bytes
-rw-r--r--tests/ref/symbol.pngbin0 -> 1542 bytes
-rw-r--r--tests/ref/table-align-array.pngbin0 -> 586 bytes
-rw-r--r--tests/ref/table-cell-align-override.pngbin0 -> 955 bytes
-rw-r--r--tests/ref/table-cell-folding.pngbin0 -> 1242 bytes
-rw-r--r--tests/ref/table-cell-override.pngbin0 -> 3546 bytes
-rw-r--r--tests/ref/table-cell-set.pngbin0 -> 4654 bytes
-rw-r--r--tests/ref/table-cell-show-and-override.pngbin0 -> 2926 bytes
-rw-r--r--tests/ref/table-cell-show-based-on-position.pngbin0 -> 1890 bytes
-rw-r--r--tests/ref/table-cell-show-emph.pngbin0 -> 798 bytes
-rw-r--r--tests/ref/table-cell-show.pngbin0 -> 1903 bytes
-rw-r--r--tests/ref/table-cell-various-overrides.pngbin0 -> 2570 bytes
-rw-r--r--tests/ref/table-fill-basic.pngbin0 -> 454 bytes
-rw-r--r--tests/ref/table-gutters.pngbin0 -> 5071 bytes
-rw-r--r--tests/ref/table-inset-fold.pngbin0 -> 307 bytes
-rw-r--r--tests/ref/table-inset.pngbin0 -> 3548 bytes
-rw-r--r--tests/ref/table-newlines.pngbin0 -> 761 bytes
-rw-r--r--tests/ref/table-stroke-vline-position-left-and-right.pngbin0 -> 1051 bytes
-rw-r--r--tests/ref/terms-built-in-loop.pngbin0 -> 1592 bytes
-rw-r--r--tests/ref/terms-constructor.pngbin0 -> 630 bytes
-rw-r--r--tests/ref/terms-grid.pngbin0 -> 1255 bytes
-rw-r--r--tests/ref/terms-multiline.pngbin0 -> 1664 bytes
-rw-r--r--tests/ref/terms-rtl.pngbin0 -> 794 bytes
-rw-r--r--tests/ref/terms-style-change-interrupted.pngbin0 -> 1524 bytes
-rw-r--r--tests/ref/terms-syntax-edge-cases.pngbin0 -> 682 bytes
-rw-r--r--tests/ref/text-alternates-and-stylistic-sets.pngbin0 -> 458 bytes
-rw-r--r--tests/ref/text-call-body.pngbin0 -> 1522 bytes
-rw-r--r--tests/ref/text-chinese-basic.pngbin0 -> 8210 bytes
-rw-r--r--tests/ref/text-cjk-latin-spacing.pngbin0 -> 2669 bytes
-rw-r--r--tests/ref/text-copy-paste-ligatures.pngbin0 -> 1127 bytes
-rw-r--r--tests/ref/text-edge.pngbin0 -> 8187 bytes
-rw-r--r--tests/ref/text-features.pngbin0 -> 479 bytes
-rw-r--r--tests/ref/text-font-change-after-space.pngbin0 -> 397 bytes
-rw-r--r--tests/ref/text-font-just-a-space.pngbin0 -> 190 bytes
-rw-r--r--tests/ref/text-font-properties.pngbin0 -> 6926 bytes
-rw-r--r--tests/ref/text-kerning.pngbin0 -> 248 bytes
-rw-r--r--tests/ref/text-lang-hyphenate.pngbin0 -> 1287 bytes
-rw-r--r--tests/ref/text-lang-region.pngbin0 -> 375 bytes
-rw-r--r--tests/ref/text-lang-script-shaping.pngbin0 -> 500 bytes
-rw-r--r--tests/ref/text-lang-shaping.pngbin0 -> 374 bytes
-rw-r--r--tests/ref/text-lang-unknown-region.pngbin0 -> 323 bytes
-rw-r--r--tests/ref/text-lang.pngbin0 -> 323 bytes
-rw-r--r--tests/ref/text-ligatures.pngbin0 -> 345 bytes
-rw-r--r--tests/ref/text-number-type.pngbin0 -> 675 bytes
-rw-r--r--tests/ref/text-number-width.pngbin0 -> 973 bytes
-rw-r--r--tests/ref/text-size-em-nesting.pngbin0 -> 660 bytes
-rw-r--r--tests/ref/text-size-em.pngbin0 -> 104 bytes
-rw-r--r--tests/ref/text-slashed-zero-and-fractions.pngbin0 -> 523 bytes
-rw-r--r--tests/ref/text-spacing-relative.pngbin0 -> 412 bytes
-rw-r--r--tests/ref/text-spacing.pngbin0 -> 565 bytes
-rw-r--r--tests/ref/text-tracking-arabic.pngbin0 -> 252 bytes
-rw-r--r--tests/ref/text-tracking-changed-temporarily.pngbin0 -> 437 bytes
-rw-r--r--tests/ref/text-tracking-mark-placement.pngbin0 -> 315 bytes
-rw-r--r--tests/ref/text-tracking-negative.pngbin0 -> 798 bytes
-rw-r--r--tests/ref/text/baseline.pngbin29911 -> 0 bytes
-rw-r--r--tests/ref/text/chinese.pngbin20722 -> 0 bytes
-rw-r--r--tests/ref/text/copy-paste.pngbin2792 -> 0 bytes
-rw-r--r--tests/ref/text/deco.pngbin64132 -> 0 bytes
-rw-r--r--tests/ref/text/edge.pngbin22578 -> 0 bytes
-rw-r--r--tests/ref/text/em.pngbin2311 -> 0 bytes
-rw-r--r--tests/ref/text/emoji.pngbin3727 -> 0 bytes
-rw-r--r--tests/ref/text/emphasis.pngbin16982 -> 0 bytes
-rw-r--r--tests/ref/text/escape.pngbin10601 -> 0 bytes
-rw-r--r--tests/ref/text/fallback.pngbin9877 -> 0 bytes
-rw-r--r--tests/ref/text/features.pngbin9551 -> 0 bytes
-rw-r--r--tests/ref/text/font.pngbin20573 -> 0 bytes
-rw-r--r--tests/ref/text/hyphenate.pngbin20467 -> 0 bytes
-rw-r--r--tests/ref/text/lang-with-region.pngbin1392 -> 0 bytes
-rw-r--r--tests/ref/text/lang.pngbin5252 -> 0 bytes
-rw-r--r--tests/ref/text/linebreak-link.pngbin65582 -> 0 bytes
-rw-r--r--tests/ref/text/linebreak-obj.pngbin46200 -> 0 bytes
-rw-r--r--tests/ref/text/linebreak.pngbin44823 -> 0 bytes
-rw-r--r--tests/ref/text/lorem.pngbin26281 -> 0 bytes
-rw-r--r--tests/ref/text/microtype.pngbin10874 -> 0 bytes
-rw-r--r--tests/ref/text/numbers.pngbin38321 -> 0 bytes
-rw-r--r--tests/ref/text/quote-nesting.pngbin14034 -> 0 bytes
-rw-r--r--tests/ref/text/quote.pngbin60156 -> 0 bytes
-rw-r--r--tests/ref/text/quotes.pngbin86003 -> 0 bytes
-rw-r--r--tests/ref/text/raw-align.pngbin32817 -> 0 bytes
-rw-r--r--tests/ref/text/raw-code.pngbin55071 -> 0 bytes
-rw-r--r--tests/ref/text/raw-line.pngbin32488 -> 0 bytes
-rw-r--r--tests/ref/text/raw-syntaxes.pngbin5142 -> 0 bytes
-rw-r--r--tests/ref/text/raw-tabs.pngbin3382 -> 0 bytes
-rw-r--r--tests/ref/text/raw-theme.pngbin5380 -> 0 bytes
-rw-r--r--tests/ref/text/raw.pngbin21650 -> 0 bytes
-rw-r--r--tests/ref/text/shaping.pngbin3975 -> 0 bytes
-rw-r--r--tests/ref/text/shift.pngbin7826 -> 0 bytes
-rw-r--r--tests/ref/text/smartquotes.pngbin7843 -> 0 bytes
-rw-r--r--tests/ref/text/space.pngbin12663 -> 0 bytes
-rw-r--r--tests/ref/text/stroke.pngbin97009 -> 0 bytes
-rw-r--r--tests/ref/text/symbol.pngbin3958 -> 0 bytes
-rw-r--r--tests/ref/text/tracking-spacing.pngbin6433 -> 0 bytes
-rw-r--r--tests/ref/transform-rotate-and-scale.pngbin0 -> 7901 bytes
-rw-r--r--tests/ref/transform-rotate-origin.pngbin0 -> 4753 bytes
-rw-r--r--tests/ref/transform-rotate-relative-sizing.pngbin0 -> 2382 bytes
-rw-r--r--tests/ref/transform-rotate.pngbin0 -> 4352 bytes
-rw-r--r--tests/ref/transform-scale-origin.pngbin0 -> 116 bytes
-rw-r--r--tests/ref/transform-scale-relative-sizing.pngbin0 -> 1975 bytes
-rw-r--r--tests/ref/transform-scale.pngbin0 -> 1802 bytes
-rw-r--r--tests/ref/transform-tex-logo.pngbin0 -> 877 bytes
-rw-r--r--tests/ref/underline-background.pngbin0 -> 1626 bytes
-rw-r--r--tests/ref/underline-overline-strike.pngbin0 -> 6404 bytes
-rw-r--r--tests/ref/underline-stroke-folding.pngbin0 -> 663 bytes
-rw-r--r--tests/ref/visualize/gradient-conic.pngbin23950 -> 0 bytes
-rw-r--r--tests/ref/visualize/gradient-dir.pngbin1089556 -> 0 bytes
-rw-r--r--tests/ref/visualize/gradient-hue-rotation.pngbin32558 -> 0 bytes
-rw-r--r--tests/ref/visualize/gradient-math.pngbin71803 -> 0 bytes
-rw-r--r--tests/ref/visualize/gradient-presets.pngbin314444 -> 0 bytes
-rw-r--r--tests/ref/visualize/gradient-radial.pngbin56014 -> 0 bytes
-rw-r--r--tests/ref/visualize/gradient-relative-conic.pngbin88675 -> 0 bytes
-rw-r--r--tests/ref/visualize/gradient-relative-linear.pngbin485209 -> 0 bytes
-rw-r--r--tests/ref/visualize/gradient-relative-radial.pngbin403617 -> 0 bytes
-rw-r--r--tests/ref/visualize/gradient-repeat.pngbin159948 -> 0 bytes
-rw-r--r--tests/ref/visualize/gradient-sharp.pngbin30371 -> 0 bytes
-rw-r--r--tests/ref/visualize/gradient-stroke.pngbin15249 -> 0 bytes
-rw-r--r--tests/ref/visualize/gradient-text-decorations.pngbin6393 -> 0 bytes
-rw-r--r--tests/ref/visualize/gradient-text-other.pngbin60270 -> 0 bytes
-rw-r--r--tests/ref/visualize/gradient-text.pngbin48309 -> 0 bytes
-rw-r--r--tests/ref/visualize/gradient-transform.pngbin86942 -> 0 bytes
-rw-r--r--tests/ref/visualize/image-scale.pngbin314 -> 0 bytes
-rw-r--r--tests/ref/visualize/image.pngbin400573 -> 0 bytes
-rw-r--r--tests/ref/visualize/line.pngbin3218 -> 0 bytes
-rw-r--r--tests/ref/visualize/path.pngbin3575 -> 0 bytes
-rw-r--r--tests/ref/visualize/pattern-relative.pngbin1548 -> 0 bytes
-rw-r--r--tests/ref/visualize/pattern-simple.pngbin343 -> 0 bytes
-rw-r--r--tests/ref/visualize/pattern-small.pngbin106 -> 0 bytes
-rw-r--r--tests/ref/visualize/pattern-spacing.pngbin307 -> 0 bytes
-rw-r--r--tests/ref/visualize/pattern-stroke.pngbin352 -> 0 bytes
-rw-r--r--tests/ref/visualize/pattern-text.pngbin29319 -> 0 bytes
-rw-r--r--tests/ref/visualize/polygon.pngbin3865 -> 0 bytes
-rw-r--r--tests/ref/visualize/shape-aspect.pngbin4717 -> 0 bytes
-rw-r--r--tests/ref/visualize/shape-circle.pngbin39242 -> 0 bytes
-rw-r--r--tests/ref/visualize/shape-ellipse.pngbin23998 -> 0 bytes
-rw-r--r--tests/ref/visualize/shape-fill-stroke.pngbin10272 -> 0 bytes
-rw-r--r--tests/ref/visualize/shape-rect.pngbin261994 -> 0 bytes
-rw-r--r--tests/ref/visualize/shape-rounded.pngbin1975 -> 0 bytes
-rw-r--r--tests/ref/visualize/shape-square.pngbin18888 -> 0 bytes
-rw-r--r--tests/ref/visualize/stroke.pngbin4465 -> 0 bytes
-rw-r--r--tests/ref/visualize/svg-text.pngbin17763 -> 0 bytes
-rw-r--r--tests/ref/while-loop-basic.pngbin0 -> 408 bytes
-rw-r--r--tests/src/args.rs46
-rw-r--r--tests/src/collect.rs420
-rw-r--r--tests/src/logger.rs141
-rw-r--r--tests/src/metadata.rs334
-rw-r--r--tests/src/run.rs442
-rw-r--r--tests/src/tests.rs1179
-rw-r--r--tests/src/world.rs229
-rw-r--r--tests/suite/foundations/array.typ494
-rw-r--r--tests/suite/foundations/assert.typ40
-rw-r--r--tests/suite/foundations/bytes.typ31
-rw-r--r--tests/suite/foundations/calc.typ261
-rw-r--r--tests/suite/foundations/content.typ120
-rw-r--r--tests/suite/foundations/context.typ65
-rw-r--r--tests/suite/foundations/datetime.typ93
-rw-r--r--tests/suite/foundations/dict.typ266
-rw-r--r--tests/suite/foundations/duration.typ103
-rw-r--r--tests/suite/foundations/eval.typ54
-rw-r--r--tests/suite/foundations/float.typ66
-rw-r--r--tests/suite/foundations/int.typ73
-rw-r--r--tests/suite/foundations/label.typ70
-rw-r--r--tests/suite/foundations/panic.typ14
-rw-r--r--tests/suite/foundations/plugin.typ47
-rw-r--r--tests/suite/foundations/repr.typ57
-rw-r--r--tests/suite/foundations/str.typ315
-rw-r--r--tests/suite/foundations/type.typ25
-rw-r--r--tests/suite/foundations/version.typ47
-rw-r--r--tests/suite/introspection/counter.typ78
-rw-r--r--tests/suite/introspection/here.typ3
-rw-r--r--tests/suite/introspection/locate.typ32
-rw-r--r--tests/suite/introspection/query.typ267
-rw-r--r--tests/suite/introspection/state.typ63
-rw-r--r--tests/suite/layout/align.typ142
-rw-r--r--tests/suite/layout/angle.typ8
-rw-r--r--tests/suite/layout/clip.typ (renamed from tests/typ/empty.typ)0
-rw-r--r--tests/suite/layout/columns.typ124
-rw-r--r--tests/suite/layout/container.typ183
-rw-r--r--tests/suite/layout/dir.typ24
-rw-r--r--tests/suite/layout/flow/flow.typ67
-rw-r--r--tests/suite/layout/flow/invisibles.typ61
-rw-r--r--tests/suite/layout/flow/orphan.typ31
-rw-r--r--tests/suite/layout/grid/cell.typ132
-rw-r--r--tests/suite/layout/grid/colspan.typ142
-rw-r--r--tests/suite/layout/grid/footers.typ404
-rw-r--r--tests/suite/layout/grid/grid.typ276
-rw-r--r--tests/suite/layout/grid/headers.typ368
-rw-r--r--tests/suite/layout/grid/positioning.typ203
-rw-r--r--tests/suite/layout/grid/rowspan.typ490
-rw-r--r--tests/suite/layout/grid/rtl.typ195
-rw-r--r--tests/suite/layout/grid/stroke.typ435
-rw-r--r--tests/suite/layout/grid/styling.typ160
-rw-r--r--tests/suite/layout/hide.typ104
-rw-r--r--tests/suite/layout/inline/baseline.typ17
-rw-r--r--tests/suite/layout/inline/bidi.typ77
-rw-r--r--tests/suite/layout/inline/cjk.typ90
-rw-r--r--tests/suite/layout/inline/hyphenate.typ51
-rw-r--r--tests/suite/layout/inline/justify.typ170
-rw-r--r--tests/suite/layout/inline/linebreak.typ109
-rw-r--r--tests/suite/layout/inline/overhang.typ24
-rw-r--r--tests/suite/layout/inline/shaping.typ65
-rw-r--r--tests/suite/layout/inline/text.typ89
-rw-r--r--tests/suite/layout/layout.typ14
-rw-r--r--tests/suite/layout/length.typ69
-rw-r--r--tests/suite/layout/limits.typ32
-rw-r--r--tests/suite/layout/measure.typ9
-rw-r--r--tests/suite/layout/pad.typ30
-rw-r--r--tests/suite/layout/page.typ231
-rw-r--r--tests/suite/layout/pagebreak.typ143
-rw-r--r--tests/suite/layout/place.typ226
-rw-r--r--tests/suite/layout/relative.typ7
-rw-r--r--tests/suite/layout/repeat.typ44
-rw-r--r--tests/suite/layout/spacing.typ38
-rw-r--r--tests/suite/layout/stack.typ82
-rw-r--r--tests/suite/layout/table.typ284
-rw-r--r--tests/suite/layout/transform.typ106
-rw-r--r--tests/suite/loading/csv.typ27
-rw-r--r--tests/suite/loading/json.typ16
-rw-r--r--tests/suite/loading/read.typ12
-rw-r--r--tests/suite/loading/toml.typ41
-rw-r--r--tests/suite/loading/xml.typ28
-rw-r--r--tests/suite/loading/yaml.typ17
-rw-r--r--tests/suite/math/accent.typ33
-rw-r--r--tests/suite/math/alignment.typ34
-rw-r--r--tests/suite/math/attach.typ130
-rw-r--r--tests/suite/math/cancel.typ38
-rw-r--r--tests/suite/math/cases.typ13
-rw-r--r--tests/suite/math/class.typ47
-rw-r--r--tests/suite/math/delimited.typ64
-rw-r--r--tests/suite/math/equation.typ212
-rw-r--r--tests/suite/math/frac.typ43
-rw-r--r--tests/suite/math/interactions.typ95
-rw-r--r--tests/suite/math/mat.typ163
-rw-r--r--tests/suite/math/multiline.typ109
-rw-r--r--tests/suite/math/op.typ30
-rw-r--r--tests/suite/math/primes.typ50
-rw-r--r--tests/suite/math/root.typ45
-rw-r--r--tests/suite/math/size.typ9
-rw-r--r--tests/suite/math/spacing.typ59
-rw-r--r--tests/suite/math/style.typ34
-rw-r--r--tests/suite/math/syntax.typ34
-rw-r--r--tests/suite/math/text.typ45
-rw-r--r--tests/suite/math/underover.typ21
-rw-r--r--tests/suite/math/vec.typ27
-rw-r--r--tests/suite/model/bibliography.typ55
-rw-r--r--tests/suite/model/cite.typ92
-rw-r--r--tests/suite/model/document.typ36
-rw-r--r--tests/suite/model/emph-strong.typ74
-rw-r--r--tests/suite/model/enum.typ156
-rw-r--r--tests/suite/model/figure.typ220
-rw-r--r--tests/suite/model/footnote.typ182
-rw-r--r--tests/suite/model/heading.typ80
-rw-r--r--tests/suite/model/link.typ77
-rw-r--r--tests/suite/model/list.typ147
-rw-r--r--tests/suite/model/numbering.typ103
-rw-r--r--tests/suite/model/outline.typ176
-rw-r--r--tests/suite/model/par.typ78
-rw-r--r--tests/suite/model/quote.typ86
-rw-r--r--tests/suite/model/ref.typ56
-rw-r--r--tests/suite/model/terms.typ77
-rw-r--r--tests/suite/playground.typ1
-rw-r--r--tests/suite/scripting/blocks.typ143
-rw-r--r--tests/suite/scripting/call.typ200
-rw-r--r--tests/suite/scripting/closure.typ223
-rw-r--r--tests/suite/scripting/destructuring.typ357
-rw-r--r--tests/suite/scripting/field.typ76
-rw-r--r--tests/suite/scripting/for.typ135
-rw-r--r--tests/suite/scripting/get-rule.typ67
-rw-r--r--tests/suite/scripting/if.typ134
-rw-r--r--tests/suite/scripting/import.typ334
-rw-r--r--tests/suite/scripting/include.typ32
-rw-r--r--tests/suite/scripting/let.typ143
-rw-r--r--tests/suite/scripting/loop.typ142
-rw-r--r--tests/suite/scripting/methods.typ51
-rw-r--r--tests/suite/scripting/module.typ13
-rw-r--r--tests/suite/scripting/modules/chap1.typ8
-rw-r--r--tests/suite/scripting/modules/chap2.typ10
-rw-r--r--tests/suite/scripting/modules/cycle1.typ5
-rw-r--r--tests/suite/scripting/modules/cycle2.typ5
-rw-r--r--tests/suite/scripting/ops.typ465
-rw-r--r--tests/suite/scripting/params.typ69
-rw-r--r--tests/suite/scripting/recursion.typ55
-rw-r--r--tests/suite/scripting/return.typ87
-rw-r--r--tests/suite/scripting/while.typ59
-rw-r--r--tests/suite/styling/fold.typ19
-rw-r--r--tests/suite/styling/set.typ96
-rw-r--r--tests/suite/styling/show-set.typ70
-rw-r--r--tests/suite/styling/show-text.typ133
-rw-r--r--tests/suite/styling/show-where.typ89
-rw-r--r--tests/suite/styling/show.typ262
-rw-r--r--tests/suite/symbols/symbol.typ38
-rw-r--r--tests/suite/syntax/backtracking.typ32
-rw-r--r--tests/suite/syntax/comment.typ43
-rw-r--r--tests/suite/syntax/embedded.typ9
-rw-r--r--tests/suite/syntax/escape.typ36
-rw-r--r--tests/suite/syntax/newlines.typ77
-rw-r--r--tests/suite/syntax/numbers.typ32
-rw-r--r--tests/suite/syntax/shorthand.typ61
-rw-r--r--tests/suite/text/case.typ11
-rw-r--r--tests/suite/text/coma.typ26
-rw-r--r--tests/suite/text/copy-paste.typ8
-rw-r--r--tests/suite/text/deco.typ85
-rw-r--r--tests/suite/text/edge.typ39
-rw-r--r--tests/suite/text/em.typ33
-rw-r--r--tests/suite/text/font.typ66
-rw-r--r--tests/suite/text/lang.typ74
-rw-r--r--tests/suite/text/lorem.typ32
-rw-r--r--tests/suite/text/raw.typ630
-rw-r--r--tests/suite/text/shift.typ19
-rw-r--r--tests/suite/text/smallcaps.typ3
-rw-r--r--tests/suite/text/smartquote.typ122
-rw-r--r--tests/suite/text/space.typ60
-rw-r--r--tests/suite/visualize/circle.typ69
-rw-r--r--tests/suite/visualize/color.typ331
-rw-r--r--tests/suite/visualize/ellipse.typ31
-rw-r--r--tests/suite/visualize/gradient.typ631
-rw-r--r--tests/suite/visualize/image.typ122
-rw-r--r--tests/suite/visualize/line.typ92
-rw-r--r--tests/suite/visualize/path.typ52
-rw-r--r--tests/suite/visualize/pattern.typ131
-rw-r--r--tests/suite/visualize/polygon.typ51
-rw-r--r--tests/suite/visualize/rect.typ107
-rw-r--r--tests/suite/visualize/square.typ146
-rw-r--r--tests/suite/visualize/stroke.typ171
-rw-r--r--tests/typ/autocomplete/showcase.typ13
-rw-r--r--tests/typ/bugs/1050-terms-indent.typ11
-rw-r--r--tests/typ/bugs/1240-stack-fr.typ18
-rw-r--r--tests/typ/bugs/1597-cite-footnote.typ12
-rw-r--r--tests/typ/bugs/2044-invalid-parsed-ident.typ6
-rw-r--r--tests/typ/bugs/2105-linebreak-tofu.typ1
-rw-r--r--tests/typ/bugs/2595-float-overlap.typ13
-rw-r--r--tests/typ/bugs/2650-cjk-latin-spacing-meta.typ5
-rw-r--r--tests/typ/bugs/2715-float-order.typ19
-rw-r--r--tests/typ/bugs/2821-missing-fields.typ9
-rw-r--r--tests/typ/bugs/2902-gradient-oklch-panic.typ20
-rw-r--r--tests/typ/bugs/3082-chinese-punctuation.typ4
-rw-r--r--tests/typ/bugs/3110-no-type-ctor-or-field.typ15
-rw-r--r--tests/typ/bugs/3154-array-dict-mut-entry.typ109
-rw-r--r--tests/typ/bugs/3232-dict-wrong-keys.typ23
-rw-r--r--tests/typ/bugs/3275-loop-errors.typ67
-rw-r--r--tests/typ/bugs/3363-json-large-number.typ8
-rw-r--r--tests/typ/bugs/3502-colon-space.typ14
-rw-r--r--tests/typ/bugs/3586-figure-caption-separator.typ7
-rw-r--r--tests/typ/bugs/3601-empty-raw.typ7
-rw-r--r--tests/typ/bugs/3641-float-loop.typ11
-rw-r--r--tests/typ/bugs/3650-italic-equation.typ4
-rw-r--r--tests/typ/bugs/3658-math-size.typ5
-rw-r--r--tests/typ/bugs/3662-pdf-smartquotes.typ12
-rw-r--r--tests/typ/bugs/3700-deformed-stroke.typ11
-rw-r--r--tests/typ/bugs/3841-tabs-in-raw-typ-code.typ20
-rw-r--r--tests/typ/bugs/870-image-rotation.typ6
-rw-r--r--tests/typ/bugs/args-sink.typ5
-rw-r--r--tests/typ/bugs/args-underscore.typ5
-rw-r--r--tests/typ/bugs/bibliography-math.typ4
-rw-r--r--tests/typ/bugs/bidi-tofus.typ7
-rw-r--r--tests/typ/bugs/block-width-box.typ6
-rw-r--r--tests/typ/bugs/cite-locate.typ23
-rw-r--r--tests/typ/bugs/cite-show-set.typ9
-rw-r--r--tests/typ/bugs/clamp-panic.typ3
-rw-r--r--tests/typ/bugs/columns-1.typ12
-rw-r--r--tests/typ/bugs/emoji-linebreak.typ6
-rw-r--r--tests/typ/bugs/equation-numbering-reference.typ15
-rw-r--r--tests/typ/bugs/flow-1.typ11
-rw-r--r--tests/typ/bugs/flow-2.typ10
-rw-r--r--tests/typ/bugs/flow-3.typ12
-rw-r--r--tests/typ/bugs/flow-4.typ5
-rw-r--r--tests/typ/bugs/flow-5.typ13
-rw-r--r--tests/typ/bugs/fold-vector.typ20
-rw-r--r--tests/typ/bugs/footnote-keep-multiple.typ10
-rw-r--r--tests/typ/bugs/footnote-list.typ11
-rw-r--r--tests/typ/bugs/gradient-cmyk-encode.typ27
-rw-r--r--tests/typ/bugs/grid-1.typ16
-rw-r--r--tests/typ/bugs/grid-2.typ20
-rw-r--r--tests/typ/bugs/grid-3.typ8
-rw-r--r--tests/typ/bugs/grid-4.typ17
-rw-r--r--tests/typ/bugs/hide-meta.typ24
-rw-r--r--tests/typ/bugs/int-constructor.typ7
-rw-r--r--tests/typ/bugs/justify-hanging-indent.typ6
-rw-r--r--tests/typ/bugs/label-fields-dict.typ31
-rw-r--r--tests/typ/bugs/layout-infinite-lengths.typ25
-rw-r--r--tests/typ/bugs/line-align.typ5
-rw-r--r--tests/typ/bugs/linebreak-no-justifiables.typ5
-rw-r--r--tests/typ/bugs/mat-aug-color.typ9
-rw-r--r--tests/typ/bugs/math-eval.typ5
-rw-r--r--tests/typ/bugs/math-number-spacing.typ9
-rw-r--r--tests/typ/bugs/math-realize.typ47
-rw-r--r--tests/typ/bugs/math-shift.typ5
-rw-r--r--tests/typ/bugs/math-text-break.typ4
-rw-r--r--tests/typ/bugs/measure-image.typ8
-rw-r--r--tests/typ/bugs/new-cm-svg.typ2
-rw-r--r--tests/typ/bugs/newline-mode.typ84
-rw-r--r--tests/typ/bugs/pagebreak-bibliography.typ5
-rw-r--r--tests/typ/bugs/pagebreak-numbering.typ12
-rw-r--r--tests/typ/bugs/pagebreak-set-style.typ12
-rw-r--r--tests/typ/bugs/parameter-pattern.typ5
-rw-r--r--tests/typ/bugs/parenthesized.typ98
-rw-r--r--tests/typ/bugs/place-base.typ7
-rw-r--r--tests/typ/bugs/place-pagebreak.typ7
-rw-r--r--tests/typ/bugs/place-spacing.typ15
-rw-r--r--tests/typ/bugs/raw-color-overwrite.typ13
-rw-r--r--tests/typ/bugs/smartquotes-in-outline.typ4
-rw-r--r--tests/typ/bugs/smartquotes-on-newline.typ7
-rw-r--r--tests/typ/bugs/spacing-behaviour.typ9
-rw-r--r--tests/typ/bugs/square-base.typ5
-rw-r--r--tests/typ/bugs/subelement-panic.typ40
-rw-r--r--tests/typ/bugs/table-lines.typ10
-rw-r--r--tests/typ/bugs/table-row-missing.typ8
-rw-r--r--tests/typ/coma.typ24
-rw-r--r--tests/typ/compiler/array.typ392
-rw-r--r--tests/typ/compiler/backtracking.typ33
-rw-r--r--tests/typ/compiler/block.typ145
-rw-r--r--tests/typ/compiler/break-continue.typ162
-rw-r--r--tests/typ/compiler/bytes.typ32
-rw-r--r--tests/typ/compiler/call.typ111
-rw-r--r--tests/typ/compiler/closure.typ219
-rw-r--r--tests/typ/compiler/color.typ101
-rw-r--r--tests/typ/compiler/comment.typ34
-rw-r--r--tests/typ/compiler/construct.typ31
-rw-r--r--tests/typ/compiler/content-field.typ63
-rw-r--r--tests/typ/compiler/delayed-error.typ11
-rw-r--r--tests/typ/compiler/dict.typ160
-rw-r--r--tests/typ/compiler/duration.typ104
-rw-r--r--tests/typ/compiler/embedded-expr.typ22
-rw-r--r--tests/typ/compiler/field.typ200
-rw-r--r--tests/typ/compiler/for.typ136
-rw-r--r--tests/typ/compiler/highlight.typ42
-rw-r--r--tests/typ/compiler/hint.typ41
-rw-r--r--tests/typ/compiler/if.typ136
-rw-r--r--tests/typ/compiler/import.typ262
-rw-r--r--tests/typ/compiler/include.typ32
-rw-r--r--tests/typ/compiler/label.typ72
-rw-r--r--tests/typ/compiler/let.typ302
-rw-r--r--tests/typ/compiler/methods.typ287
-rw-r--r--tests/typ/compiler/module.typ13
-rw-r--r--tests/typ/compiler/modules/chap1.typ9
-rw-r--r--tests/typ/compiler/modules/chap2.typ11
-rw-r--r--tests/typ/compiler/modules/cycle1.typ6
-rw-r--r--tests/typ/compiler/modules/cycle2.typ6
-rw-r--r--tests/typ/compiler/ops-assoc.typ18
-rw-r--r--tests/typ/compiler/ops-invalid.typ134
-rw-r--r--tests/typ/compiler/ops-prec.typ36
-rw-r--r--tests/typ/compiler/ops.typ359
-rw-r--r--tests/typ/compiler/packages.typ69
-rw-r--r--tests/typ/compiler/plugin-oob.typ14
-rw-r--r--tests/typ/compiler/plugin.typ36
-rw-r--r--tests/typ/compiler/raw.typ170
-rw-r--r--tests/typ/compiler/recursion.typ56
-rw-r--r--tests/typ/compiler/repr-color-gradient.typ23
-rw-r--r--tests/typ/compiler/repr.typ55
-rw-r--r--tests/typ/compiler/return.typ89
-rw-r--r--tests/typ/compiler/select-where-styles.typ91
-rw-r--r--tests/typ/compiler/selector-logical.typ126
-rw-r--r--tests/typ/compiler/set.typ66
-rw-r--r--tests/typ/compiler/shorthand.typ61
-rw-r--r--tests/typ/compiler/show-bare.typ41
-rw-r--r--tests/typ/compiler/show-node.typ104
-rw-r--r--tests/typ/compiler/show-recursive.typ51
-rw-r--r--tests/typ/compiler/show-selector-logical.typ21
-rw-r--r--tests/typ/compiler/show-selector.typ39
-rw-r--r--tests/typ/compiler/show-set-func.typ16
-rw-r--r--tests/typ/compiler/show-set-text.typ41
-rw-r--r--tests/typ/compiler/show-set.typ55
-rw-r--r--tests/typ/compiler/show-text.typ93
-rw-r--r--tests/typ/compiler/spread.typ133
-rw-r--r--tests/typ/compiler/string.typ247
-rw-r--r--tests/typ/compiler/type-compatibility.typ10
-rw-r--r--tests/typ/compiler/while.typ60
-rw-r--r--tests/typ/compute/calc.typ355
-rw-r--r--tests/typ/compute/construct.typ316
-rw-r--r--tests/typ/compute/data.typ144
-rw-r--r--tests/typ/compute/eval-path.typ18
-rw-r--r--tests/typ/compute/foundations.typ107
-rw-r--r--tests/typ/compute/version.typ47
-rw-r--r--tests/typ/layout/align.typ52
-rw-r--r--tests/typ/layout/block-sizing.typ24
-rw-r--r--tests/typ/layout/block-spacing.typ9
-rw-r--r--tests/typ/layout/cjk-latin-spacing.typ29
-rw-r--r--tests/typ/layout/cjk-punctuation-adjustment.typ44
-rw-r--r--tests/typ/layout/clip.typ68
-rw-r--r--tests/typ/layout/code-indent-shrink.typ28
-rw-r--r--tests/typ/layout/columns.typ112
-rw-r--r--tests/typ/layout/container-fill.typ7
-rw-r--r--tests/typ/layout/container.typ50
-rw-r--r--tests/typ/layout/enum-align.typ51
-rw-r--r--tests/typ/layout/enum-numbering.typ55
-rw-r--r--tests/typ/layout/enum.typ48
-rw-r--r--tests/typ/layout/flow-orphan.typ30
-rw-r--r--tests/typ/layout/grid-1.typ41
-rw-r--r--tests/typ/layout/grid-2.typ29
-rw-r--r--tests/typ/layout/grid-3.typ61
-rw-r--r--tests/typ/layout/grid-4.typ33
-rw-r--r--tests/typ/layout/grid-5.typ40
-rw-r--r--tests/typ/layout/grid-auto-shrink.typ12
-rw-r--r--tests/typ/layout/grid-cell.typ133
-rw-r--r--tests/typ/layout/grid-colspan.typ141
-rw-r--r--tests/typ/layout/grid-footers-1.typ192
-rw-r--r--tests/typ/layout/grid-footers-2.typ31
-rw-r--r--tests/typ/layout/grid-footers-3.typ44
-rw-r--r--tests/typ/layout/grid-footers-4.typ42
-rw-r--r--tests/typ/layout/grid-footers-5.typ87
-rw-r--r--tests/typ/layout/grid-headers-1.typ162
-rw-r--r--tests/typ/layout/grid-headers-2.typ52
-rw-r--r--tests/typ/layout/grid-headers-3.typ35
-rw-r--r--tests/typ/layout/grid-headers-4.typ112
-rw-r--r--tests/typ/layout/grid-positioning.typ205
-rw-r--r--tests/typ/layout/grid-rowspan-basic.typ252
-rw-r--r--tests/typ/layout/grid-rowspan-split-1.typ89
-rw-r--r--tests/typ/layout/grid-rowspan-split-2.typ37
-rw-r--r--tests/typ/layout/grid-rowspan-split-3.typ108
-rw-r--r--tests/typ/layout/grid-rtl.typ195
-rw-r--r--tests/typ/layout/grid-stroke.typ434
-rw-r--r--tests/typ/layout/grid-styling.typ160
-rw-r--r--tests/typ/layout/hide.typ76
-rw-r--r--tests/typ/layout/list-attach.typ54
-rw-r--r--tests/typ/layout/list-marker.typ34
-rw-r--r--tests/typ/layout/list.typ57
-rw-r--r--tests/typ/layout/out-of-flow-in-block.typ61
-rw-r--r--tests/typ/layout/pad.typ30
-rw-r--r--tests/typ/layout/page-binding.typ46
-rw-r--r--tests/typ/layout/page-margin.typ20
-rw-r--r--tests/typ/layout/page-marginals.typ24
-rw-r--r--tests/typ/layout/page-number-align.typ25
-rw-r--r--tests/typ/layout/page-style.typ28
-rw-r--r--tests/typ/layout/page.typ42
-rw-r--r--tests/typ/layout/pagebreak-parity.typ35
-rw-r--r--tests/typ/layout/pagebreak-weak.typ30
-rw-r--r--tests/typ/layout/pagebreak.typ45
-rw-r--r--tests/typ/layout/par-bidi.typ72
-rw-r--r--tests/typ/layout/par-indent.typ51
-rw-r--r--tests/typ/layout/par-justify-cjk.typ64
-rw-r--r--tests/typ/layout/par-justify.typ65
-rw-r--r--tests/typ/layout/par-knuth.typ30
-rw-r--r--tests/typ/layout/par-simple.typ20
-rw-r--r--tests/typ/layout/par.typ36
-rw-r--r--tests/typ/layout/place-background.typ18
-rw-r--r--tests/typ/layout/place-float-auto.typ31
-rw-r--r--tests/typ/layout/place-float-columns.typ19
-rw-r--r--tests/typ/layout/place-float-figure.typ21
-rw-r--r--tests/typ/layout/place-nested.typ40
-rw-r--r--tests/typ/layout/place.typ35
-rw-r--r--tests/typ/layout/repeat.typ44
-rw-r--r--tests/typ/layout/spacing.typ46
-rw-r--r--tests/typ/layout/stack-1.typ52
-rw-r--r--tests/typ/layout/stack-2.typ23
-rw-r--r--tests/typ/layout/table-cell.typ128
-rw-r--r--tests/typ/layout/table.typ141
-rw-r--r--tests/typ/layout/terms.typ60
-rw-r--r--tests/typ/layout/transform-layout.typ58
-rw-r--r--tests/typ/layout/transform.typ49
-rw-r--r--tests/typ/lint/markup.typ26
-rw-r--r--tests/typ/math/accent.typ33
-rw-r--r--tests/typ/math/alignment.typ34
-rw-r--r--tests/typ/math/attach-p1.typ63
-rw-r--r--tests/typ/math/attach-p2.typ27
-rw-r--r--tests/typ/math/attach-p3.typ42
-rw-r--r--tests/typ/math/call.typ7
-rw-r--r--tests/typ/math/cancel.typ38
-rw-r--r--tests/typ/math/cases.typ9
-rw-r--r--tests/typ/math/class.typ47
-rw-r--r--tests/typ/math/content.typ33
-rw-r--r--tests/typ/math/delimited.typ59
-rw-r--r--tests/typ/math/equation-block-align.typ33
-rw-r--r--tests/typ/math/equation-number.typ145
-rw-r--r--tests/typ/math/equation-show.typ7
-rw-r--r--tests/typ/math/font-features.typ10
-rw-r--r--tests/typ/math/frac.typ43
-rw-r--r--tests/typ/math/linebreak.typ50
-rw-r--r--tests/typ/math/matrix-alignment.typ58
-rw-r--r--tests/typ/math/matrix-gaps.typ17
-rw-r--r--tests/typ/math/matrix.typ97
-rw-r--r--tests/typ/math/multiline.typ56
-rw-r--r--tests/typ/math/numbering.typ11
-rw-r--r--tests/typ/math/op.typ30
-rw-r--r--tests/typ/math/opticalsize.typ69
-rw-r--r--tests/typ/math/prime.typ9
-rw-r--r--tests/typ/math/root.typ45
-rw-r--r--tests/typ/math/spacing.typ48
-rw-r--r--tests/typ/math/style.typ52
-rw-r--r--tests/typ/math/syntax.typ22
-rw-r--r--tests/typ/math/unbalanced.typ6
-rw-r--r--tests/typ/math/underover.typ21
-rw-r--r--tests/typ/math/vec.typ14
-rw-r--r--tests/typ/meta/bibliography-full.typ5
-rw-r--r--tests/typ/meta/bibliography-ordering.typ16
-rw-r--r--tests/typ/meta/bibliography.typ44
-rw-r--r--tests/typ/meta/cite-footnote.typ5
-rw-r--r--tests/typ/meta/cite-form.typ10
-rw-r--r--tests/typ/meta/cite-group.typ19
-rw-r--r--tests/typ/meta/context-compatibility.typ29
-rw-r--r--tests/typ/meta/context.typ181
-rw-r--r--tests/typ/meta/counter-page.typ10
-rw-r--r--tests/typ/meta/counter.typ52
-rw-r--r--tests/typ/meta/document.typ49
-rw-r--r--tests/typ/meta/figure-caption.typ64
-rw-r--r--tests/typ/meta/figure-localization.typ34
-rw-r--r--tests/typ/meta/figure.typ111
-rw-r--r--tests/typ/meta/footnote-break.typ16
-rw-r--r--tests/typ/meta/footnote-columns.typ9
-rw-r--r--tests/typ/meta/footnote-container.typ32
-rw-r--r--tests/typ/meta/footnote-invariant.typ9
-rw-r--r--tests/typ/meta/footnote-refs.typ40
-rw-r--r--tests/typ/meta/footnote-table.typ23
-rw-r--r--tests/typ/meta/footnote.typ34
-rw-r--r--tests/typ/meta/heading.typ73
-rw-r--r--tests/typ/meta/link.typ77
-rw-r--r--tests/typ/meta/numbering.typ103
-rw-r--r--tests/typ/meta/outline-entry.typ59
-rw-r--r--tests/typ/meta/outline-first-par-indent.typ15
-rw-r--r--tests/typ/meta/outline-indent.typ60
-rw-r--r--tests/typ/meta/outline.typ45
-rw-r--r--tests/typ/meta/page-label.typ47
-rw-r--r--tests/typ/meta/query-before-after.typ69
-rw-r--r--tests/typ/meta/query-figure.typ41
-rw-r--r--tests/typ/meta/query-header.typ30
-rw-r--r--tests/typ/meta/ref.typ48
-rw-r--r--tests/typ/meta/state.typ56
-rw-r--r--tests/typ/text/baseline.typ12
-rw-r--r--tests/typ/text/case.typ12
-rw-r--r--tests/typ/text/chinese.typ9
-rw-r--r--tests/typ/text/copy-paste.typ8
-rw-r--r--tests/typ/text/deco.typ85
-rw-r--r--tests/typ/text/edge.typ39
-rw-r--r--tests/typ/text/em.typ33
-rw-r--r--tests/typ/text/emoji.typ18
-rw-r--r--tests/typ/text/emphasis.typ50
-rw-r--r--tests/typ/text/escape.typ36
-rw-r--r--tests/typ/text/fallback.typ20
-rw-r--r--tests/typ/text/features.typ63
-rw-r--r--tests/typ/text/font.typ66
-rw-r--r--tests/typ/text/hyphenate.typ51
-rw-r--r--tests/typ/text/lang-with-region.typ16
-rw-r--r--tests/typ/text/lang.typ59
-rw-r--r--tests/typ/text/linebreak-link.typ23
-rw-r--r--tests/typ/text/linebreak-obj.typ24
-rw-r--r--tests/typ/text/linebreak.typ60
-rw-r--r--tests/typ/text/lorem.typ32
-rw-r--r--tests/typ/text/microtype.typ23
-rw-r--r--tests/typ/text/numbers.typ110
-rw-r--r--tests/typ/text/quote-nesting.typ27
-rw-r--r--tests/typ/text/quote.typ60
-rw-r--r--tests/typ/text/quotes.typ71
-rw-r--r--tests/typ/text/raw-align.typ37
-rw-r--r--tests/typ/text/raw-code.typ86
-rw-r--r--tests/typ/text/raw-line.typ109
-rw-r--r--tests/typ/text/raw-syntaxes.typ14
-rw-r--r--tests/typ/text/raw-tabs.typ11
-rw-r--r--tests/typ/text/raw-theme.typ24
-rw-r--r--tests/typ/text/raw.typ79
-rw-r--r--tests/typ/text/shaping.typ29
-rw-r--r--tests/typ/text/shift.typ19
-rw-r--r--tests/typ/text/smartquotes.typ29
-rw-r--r--tests/typ/text/space.typ53
-rw-r--r--tests/typ/text/stroke.typ21
-rw-r--r--tests/typ/text/symbol.typ18
-rw-r--r--tests/typ/text/tracking-spacing.typ31
-rw-r--r--tests/typ/visualize/gradient-conic.typ25
-rw-r--r--tests/typ/visualize/gradient-dir.typ13
-rw-r--r--tests/typ/visualize/gradient-hue-rotation.typ66
-rw-r--r--tests/typ/visualize/gradient-math.typ89
-rw-r--r--tests/typ/visualize/gradient-presets.typ33
-rw-r--r--tests/typ/visualize/gradient-radial.typ49
-rw-r--r--tests/typ/visualize/gradient-relative-conic.typ29
-rw-r--r--tests/typ/visualize/gradient-relative-linear.typ29
-rw-r--r--tests/typ/visualize/gradient-relative-radial.typ29
-rw-r--r--tests/typ/visualize/gradient-repeat.typ36
-rw-r--r--tests/typ/visualize/gradient-sharp.typ29
-rw-r--r--tests/typ/visualize/gradient-stroke.typ31
-rw-r--r--tests/typ/visualize/gradient-text-decorations.typ9
-rw-r--r--tests/typ/visualize/gradient-text-other.typ14
-rw-r--r--tests/typ/visualize/gradient-text.typ49
-rw-r--r--tests/typ/visualize/gradient-transform.typ12
-rw-r--r--tests/typ/visualize/image-scale.typ6
-rw-r--r--tests/typ/visualize/image.typ82
-rw-r--r--tests/typ/visualize/line.typ51
-rw-r--r--tests/typ/visualize/path.typ52
-rw-r--r--tests/typ/visualize/pattern-relative.typ23
-rw-r--r--tests/typ/visualize/pattern-simple.typ18
-rw-r--r--tests/typ/visualize/pattern-small.typ19
-rw-r--r--tests/typ/visualize/pattern-spacing.typ31
-rw-r--r--tests/typ/visualize/pattern-stroke.typ13
-rw-r--r--tests/typ/visualize/pattern-text.typ28
-rw-r--r--tests/typ/visualize/polygon.typ36
-rw-r--r--tests/typ/visualize/shape-aspect.typ63
-rw-r--r--tests/typ/visualize/shape-circle.typ58
-rw-r--r--tests/typ/visualize/shape-ellipse.typ31
-rw-r--r--tests/typ/visualize/shape-fill-stroke.typ93
-rw-r--r--tests/typ/visualize/shape-rect.typ78
-rw-r--r--tests/typ/visualize/shape-rounded.typ53
-rw-r--r--tests/typ/visualize/shape-square.typ39
-rw-r--r--tests/typ/visualize/stroke.typ115
-rw-r--r--tests/typ/visualize/svg-text.typ18
-rw-r--r--tools/test-helper/README.md38
-rw-r--r--tools/test-helper/extension.js363
-rw-r--r--tools/test-helper/package.json115
-rw-r--r--tools/test-helper/src/extension.ts492
-rw-r--r--tools/test-helper/tsconfig.json11
1963 files changed, 20492 insertions, 20522 deletions
diff --git a/.gitignore b/.gitignore
index c17c6399..a1915f4a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,11 +6,8 @@ desktop.ini
.DS_Store
# Tests and benchmarks
-tests/png
-tests/pdf
-tests/svg
-tests/target
-tests/typ/**/*.pdf
+tests/store
+tests/suite/**/*.pdf
tests/fuzz/target
tests/fuzz/corpus
tests/fuzz/artifacts
@@ -23,6 +20,7 @@ tarpaulin-report.html
# Node
node_modules
+tools/test-helper/dist
package-lock.json
# Nix
diff --git a/Cargo.lock b/Cargo.lock
index 8215e9e4..066f2381 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2675,8 +2675,11 @@ dependencies = [
"ecow",
"if_chain",
"log",
+ "once_cell",
"serde",
"typst",
+ "typst-assets",
+ "typst-dev-assets",
"unscanny",
]
@@ -2773,13 +2776,13 @@ dependencies = [
"ecow",
"once_cell",
"oxipng",
+ "parking_lot",
"rayon",
"tiny-skia",
"ttf-parser",
"typst",
"typst-assets",
"typst-dev-assets",
- "typst-ide",
"typst-pdf",
"typst-render",
"typst-svg",
diff --git a/crates/typst-cli/src/world.rs b/crates/typst-cli/src/world.rs
index 99da838c..5b4259f4 100644
--- a/crates/typst-cli/src/world.rs
+++ b/crates/typst-cli/src/world.rs
@@ -241,7 +241,7 @@ struct FileSlot {
}
impl FileSlot {
- /// Create a new path slot.
+ /// Create a new file slot.
fn new(id: FileId) -> Self {
Self { id, file: SlotCell::new(), source: SlotCell::new() }
}
diff --git a/crates/typst-ide/Cargo.toml b/crates/typst-ide/Cargo.toml
index 45a83d55..01f7a106 100644
--- a/crates/typst-ide/Cargo.toml
+++ b/crates/typst-ide/Cargo.toml
@@ -21,5 +21,10 @@ log = { workspace = true }
serde = { workspace = true }
unscanny = { workspace = true }
+[dev-dependencies]
+typst-assets = { workspace = true }
+typst-dev-assets = { workspace = true }
+once_cell = { workspace = true }
+
[lints]
workspace = true
diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs
index be28a431..c758dd1c 100644
--- a/crates/typst-ide/src/complete.rs
+++ b/crates/typst-ide/src/complete.rs
@@ -1403,3 +1403,34 @@ impl<'a> CompletionContext<'a> {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use typst::eval::Tracer;
+
+ use super::autocomplete;
+ use crate::tests::TestWorld;
+
+ #[track_caller]
+ fn test(text: &str, cursor: usize, contains: &[&str], excludes: &[&str]) {
+ let world = TestWorld::new(text);
+ let doc = typst::compile(&world, &mut Tracer::new()).ok();
+ let (_, completions) =
+ autocomplete(&world, doc.as_ref(), &world.main, cursor, true)
+ .unwrap_or_default();
+
+ let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
+ for item in contains {
+ assert!(labels.contains(item), "{item:?} was not contained in {labels:?}");
+ }
+ for item in excludes {
+ assert!(!labels.contains(item), "{item:?} was not excluded in {labels:?}");
+ }
+ }
+
+ #[test]
+ fn test_autocomplete() {
+ test("#i", 2, &["int", "if conditional"], &["foo"]);
+ test("#().", 4, &["insert", "remove", "len", "all"], &["foo"]);
+ }
+}
diff --git a/crates/typst-ide/src/lib.rs b/crates/typst-ide/src/lib.rs
index bbfd56d9..3967aaad 100644
--- a/crates/typst-ide/src/lib.rs
+++ b/crates/typst-ide/src/lib.rs
@@ -90,3 +90,88 @@ fn summarize_font_family<'a>(variants: impl Iterator<Item = &'a FontInfo>) -> Ec
detail
}
+
+#[cfg(test)]
+mod tests {
+ use comemo::Prehashed;
+ use once_cell::sync::Lazy;
+ use typst::diag::{FileError, FileResult};
+ use typst::foundations::{Bytes, Datetime};
+ use typst::syntax::{FileId, Source};
+ use typst::text::{Font, FontBook};
+ use typst::{Library, World};
+
+ /// A world for IDE testing.
+ pub struct TestWorld {
+ pub main: Source,
+ base: &'static TestBase,
+ }
+
+ impl TestWorld {
+ /// Create a new world for a single test.
+ ///
+ /// This is cheap because the shared base for all test runs is lazily
+ /// initialized just once.
+ pub fn new(text: &str) -> Self {
+ static BASE: Lazy<TestBase> = Lazy::new(TestBase::default);
+ let main = Source::detached(text);
+ Self { main, base: &*BASE }
+ }
+ }
+
+ impl World for TestWorld {
+ fn library(&self) -> &Prehashed<Library> {
+ &self.base.library
+ }
+
+ fn book(&self) -> &Prehashed<FontBook> {
+ &self.base.book
+ }
+
+ fn main(&self) -> Source {
+ self.main.clone()
+ }
+
+ fn source(&self, id: FileId) -> FileResult<Source> {
+ if id == self.main.id() {
+ Ok(self.main.clone())
+ } else {
+ Err(FileError::NotFound(id.vpath().as_rootless_path().into()))
+ }
+ }
+
+ fn file(&self, id: FileId) -> FileResult<Bytes> {
+ Err(FileError::NotFound(id.vpath().as_rootless_path().into()))
+ }
+
+ fn font(&self, index: usize) -> Option<Font> {
+ Some(self.base.fonts[index].clone())
+ }
+
+ fn today(&self, _: Option<i64>) -> Option<Datetime> {
+ None
+ }
+ }
+
+ /// Shared foundation of all test worlds.
+ struct TestBase {
+ library: Prehashed<Library>,
+ book: Prehashed<FontBook>,
+ fonts: Vec<Font>,
+ }
+
+ impl Default for TestBase {
+ fn default() -> Self {
+ let fonts: Vec<_> = typst_assets::fonts()
+ .chain(typst_dev_assets::fonts())
+ .flat_map(|data| Font::iter(Bytes::from_static(data)))
+ .collect();
+
+ Self {
+ library: Prehashed::new(Library::default()),
+ book: Prehashed::new(FontBook::from_fonts(&fonts)),
+ fonts,
+ }
+ }
+ }
+}
diff --git a/crates/typst-pdf/src/page.rs b/crates/typst-pdf/src/page.rs
index c31d1204..42358db5 100644
--- a/crates/typst-pdf/src/page.rs
+++ b/crates/typst-pdf/src/page.rs
@@ -377,7 +377,7 @@ pub struct EncodedPage {
}
/// Represents a resource being used in a PDF page by its name.
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct PageResource {
kind: ResourceKind,
name: EcoString,
@@ -390,7 +390,7 @@ impl PageResource {
}
/// A kind of resource being used in a PDF page.
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum ResourceKind {
XObject,
Font,
diff --git a/crates/typst-pdf/src/pattern.rs b/crates/typst-pdf/src/pattern.rs
index 6dfb0f66..5d5942bc 100644
--- a/crates/typst-pdf/src/pattern.rs
+++ b/crates/typst-pdf/src/pattern.rs
@@ -118,13 +118,15 @@ fn register_pattern(
// Render the body.
let (_, content) = construct_page(ctx.parent, pattern.frame());
- let pdf_pattern = PdfPattern {
+ let mut pdf_pattern = PdfPattern {
transform,
pattern: pattern.clone(),
content: content.content.wait().clone(),
resources: content.resources.into_iter().collect(),
};
+ pdf_pattern.resources.sort();
+
ctx.parent.pattern_map.insert(pdf_pattern)
}
diff --git a/crates/typst-render/src/lib.rs b/crates/typst-render/src/lib.rs
index fdacb597..28302180 100644
--- a/crates/typst-render/src/lib.rs
+++ b/crates/typst-render/src/lib.rs
@@ -42,13 +42,13 @@ pub fn render(frame: &Frame, pixel_per_pt: f32, fill: Color) -> sk::Pixmap {
/// Export a document with potentially multiple pages into a single raster image.
///
-/// The padding will be added around and between the individual frames.
+/// The gap will be added between the individual frames.
pub fn render_merged(
document: &Document,
pixel_per_pt: f32,
frame_fill: Color,
- padding: Abs,
- padding_fill: Color,
+ gap: Abs,
+ gap_fill: Color,
) -> sk::Pixmap {
let pixmaps: Vec<_> = document
.pages
@@ -56,19 +56,18 @@ pub fn render_merged(
.map(|page| render(&page.frame, pixel_per_pt, frame_fill))
.collect();
- let padding = (pixel_per_pt * padding.to_f32()).round() as u32;
- let pxw =
- 2 * padding + pixmaps.iter().map(sk::Pixmap::width).max().unwrap_or_default();
- let pxh =
- padding + pixmaps.iter().map(|pixmap| pixmap.height() + padding).sum::<u32>();
+ let gap = (pixel_per_pt * gap.to_f32()).round() as u32;
+ let pxw = pixmaps.iter().map(sk::Pixmap::width).max().unwrap_or_default();
+ let pxh = pixmaps.iter().map(|pixmap| pixmap.height()).sum::<u32>()
+ + gap * pixmaps.len().saturating_sub(1) as u32;
let mut canvas = sk::Pixmap::new(pxw, pxh).unwrap();
- canvas.fill(to_sk_color(padding_fill));
+ canvas.fill(to_sk_color(gap_fill));
- let [x, mut y] = [padding; 2];
+ let mut y = 0;
for pixmap in pixmaps {
canvas.draw_pixmap(
- x as i32,
+ 0,
y as i32,
pixmap.as_ref(),
&sk::PixmapPaint::default(),
@@ -76,7 +75,7 @@ pub fn render_merged(
None,
);
- y += pixmap.height() + padding;
+ y += pixmap.height() + gap;
}
canvas
diff --git a/crates/typst/src/model/footnote.rs b/crates/typst/src/model/footnote.rs
index 44942341..4945ebb1 100644
--- a/crates/typst/src/model/footnote.rs
+++ b/crates/typst/src/model/footnote.rs
@@ -167,11 +167,6 @@ cast! {
/// This function is not intended to be called directly. Instead, it is used
/// in set and show rules to customize footnote listings.
///
-/// _Note:_ Set and show rules for `footnote.entry` must be defined at the
-/// beginning of the document in order to work correctly.
-/// See [here](https://github.com/typst/typst/issues/1348#issuecomment-1566316463)
-/// for more information.
-///
/// ```example
/// #show footnote.entry: set text(red)
///
@@ -179,6 +174,12 @@ cast! {
/// #footnote[It's down here]
/// has red text!
/// ```
+///
+/// _Note:_ Set and show rules for `footnote.entry` must be defined at the
+/// beginning of the document in order to work correctly. See [here][issue] for
+/// more information.
+///
+/// [issue]: https://github.com/typst/typst/issues/1467#issuecomment-1588799440
#[elem(name = "entry", title = "Footnote Entry", Show, ShowSet)]
pub struct FootnoteEntry {
/// The footnote for this entry. It's location can be used to determine
diff --git a/tests/Cargo.toml b/tests/Cargo.toml
index 1f650c97..62e7a493 100644
--- a/tests/Cargo.toml
+++ b/tests/Cargo.toml
@@ -6,29 +6,29 @@ authors = { workspace = true }
edition = { workspace = true }
publish = false
-[dev-dependencies]
+[[test]]
+name = "tests"
+path = "src/tests.rs"
+harness = false
+
+[dependencies]
typst = { workspace = true }
typst-assets = { workspace = true, features = ["fonts"] }
typst-dev-assets = { workspace = true }
typst-pdf = { workspace = true }
typst-render = { workspace = true }
typst-svg = { workspace = true }
-typst-ide = { workspace = true }
clap = { workspace = true }
comemo = { workspace = true }
ecow = { workspace = true }
once_cell = { workspace = true }
oxipng = { workspace = true }
+parking_lot = { workspace = true }
rayon = { workspace = true }
tiny-skia = { workspace = true }
ttf-parser = { workspace = true }
unscanny = { workspace = true }
walkdir = { workspace = true }
-[[test]]
-name = "tests"
-path = "src/tests.rs"
-harness = false
-
[lints]
workspace = true
diff --git a/tests/README.md b/tests/README.md
index 68b72f2d..6cc79217 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -3,13 +3,10 @@
## Directory structure
Top level directory structure:
- `src`: Testing code.
-- `typ`: Input files. The tests in `compiler` specifically test the compiler
- while the others test the standard library (but also the compiler
- indirectly).
+- `suite`: Input files. Mostly organize in parallel to the code.
- `ref`: Reference images which the output is compared with to determine whether
a test passed or failed.
-- `png`: PNG files produced by tests.
-- `pdf`: PDF files produced by tests.
+- `store`: Store for PNG, PDF, and SVG output files produced by the tests.
## Running the tests
Running all tests (including unit tests):
@@ -37,11 +34,6 @@ Running a test with the exact filename `page.typ`.
testit --exact page.typ
```
-Debug-printing the layout trees for all executed tests.
-```bash
-testit --debug empty.typ
-```
-
To make the integration tests go faster they don't generate PDFs by default.
Pass the `--pdf` flag to generate those. Mind that PDFs are not tested
automatically at the moment, so you should always check the output manually when
@@ -50,18 +42,48 @@ making changes.
testit --pdf
```
-## Update expected images
+## Writing tests
+The syntax for an individual test is `--- {name} ---` followed by some Typst
+code that should be tested. The name must be globally unique in the test suite,
+so that tests can be easily migrated across files.
+
+There are, broadly speaking, three kinds of tests:
+
+- Tests that just ensure that the code runs successfully: Those typically make
+ use of `test` or `assert.eq` (both are very similar, `test` is just shorter)
+ to ensure certain properties hold when executing the Typst code.
+
+- Tests that ensure the code fails with a particular error: Those have inline
+ annotations like `// Error: 2-7 thing was wrong`. An annotation can be
+ either an "Error", a "Warning", or a "Hint". The range designates where
+ in the next non-comment line the error is and after it follows the message.
+ If you the error is in a line further below, you can also write ranges like
+ `3:2-3:7` to indicate the 2-7 column in the 3rd non-comment line.
+
+- Tests that ensure certain visual output is produced: Those render the result
+ of the test with the `typst-render` crate and compare against a reference
+ image stored in the repository. The test runner automatically detects whether
+ a test has visual output and requires a reference image in this case.
+
+ To prevent bloat, it is important that the test images are kept as small as
+ possible. To that effect, the test runner enforces a maximum size of 20 KiB.
+ If truly necessary, this limit can however be lifted by adding `// LARGE` as
+ the first line of a test.
+
+If you have the choice between writing a test using assertions or using
+reference images, prefer assertions. This makes the test easier to understand
+in isolation and prevents bloat due to images.
+
+## Updating reference images
If you created a new test or fixed a bug in an existing test, you need to update
-the reference image used for comparison. For this, you can use the
-`UPDATE_EXPECT` environment variable or the `--update` flag:
+the reference image used for comparison. For this, you can use the `--update`
+flag:
```bash
testit mytest --update
```
If you use the VS Code test helper extension (see the `tools` folder), you can
-alternatively use the checkmark button to update the reference image. In that
-case you should also install `oxipng` on your system so that the test helper
-can optimize the reference images.
+alternatively use the save button to update the reference image.
## Making an alias
If you want to have a quicker way to run the tests, consider adding a shortcut
diff --git a/tests/ref/align-center-in-flow.png b/tests/ref/align-center-in-flow.png
new file mode 100644
index 00000000..ecfe49dc
--- /dev/null
+++ b/tests/ref/align-center-in-flow.png
Binary files differ
diff --git a/tests/ref/align-in-stack.png b/tests/ref/align-in-stack.png
new file mode 100644
index 00000000..556721ab
--- /dev/null
+++ b/tests/ref/align-in-stack.png
Binary files differ
diff --git a/tests/ref/align-right.png b/tests/ref/align-right.png
new file mode 100644
index 00000000..edab8851
--- /dev/null
+++ b/tests/ref/align-right.png
Binary files differ
diff --git a/tests/ref/align-start-and-end.png b/tests/ref/align-start-and-end.png
new file mode 100644
index 00000000..cf3faeae
--- /dev/null
+++ b/tests/ref/align-start-and-end.png
Binary files differ
diff --git a/tests/ref/array-basic-syntax.png b/tests/ref/array-basic-syntax.png
new file mode 100644
index 00000000..6eb95305
--- /dev/null
+++ b/tests/ref/array-basic-syntax.png
Binary files differ
diff --git a/tests/ref/array-insert-and-remove.png b/tests/ref/array-insert-and-remove.png
new file mode 100644
index 00000000..ea4b8cf2
--- /dev/null
+++ b/tests/ref/array-insert-and-remove.png
Binary files differ
diff --git a/tests/ref/array-join-content.png b/tests/ref/array-join-content.png
new file mode 100644
index 00000000..4d08142e
--- /dev/null
+++ b/tests/ref/array-join-content.png
Binary files differ
diff --git a/tests/ref/baseline-box.png b/tests/ref/baseline-box.png
new file mode 100644
index 00000000..8d7627c6
--- /dev/null
+++ b/tests/ref/baseline-box.png
Binary files differ
diff --git a/tests/ref/baseline-text.png b/tests/ref/baseline-text.png
new file mode 100644
index 00000000..72beac79
--- /dev/null
+++ b/tests/ref/baseline-text.png
Binary files differ
diff --git a/tests/ref/bibliography-basic.png b/tests/ref/bibliography-basic.png
new file mode 100644
index 00000000..eeb773bf
--- /dev/null
+++ b/tests/ref/bibliography-basic.png
Binary files differ
diff --git a/tests/ref/bibliography-before-content.png b/tests/ref/bibliography-before-content.png
new file mode 100644
index 00000000..806daa08
--- /dev/null
+++ b/tests/ref/bibliography-before-content.png
Binary files differ
diff --git a/tests/ref/bibliography-full.png b/tests/ref/bibliography-full.png
new file mode 100644
index 00000000..1da15d16
--- /dev/null
+++ b/tests/ref/bibliography-full.png
Binary files differ
diff --git a/tests/ref/bibliography-math.png b/tests/ref/bibliography-math.png
new file mode 100644
index 00000000..3fc36efc
--- /dev/null
+++ b/tests/ref/bibliography-math.png
Binary files differ
diff --git a/tests/ref/bibliography-multiple-files.png b/tests/ref/bibliography-multiple-files.png
new file mode 100644
index 00000000..1293ba22
--- /dev/null
+++ b/tests/ref/bibliography-multiple-files.png
Binary files differ
diff --git a/tests/ref/bibliography-ordering.png b/tests/ref/bibliography-ordering.png
new file mode 100644
index 00000000..b1e14c9a
--- /dev/null
+++ b/tests/ref/bibliography-ordering.png
Binary files differ
diff --git a/tests/ref/bidi-consecutive-embedded-ltr-runs.png b/tests/ref/bidi-consecutive-embedded-ltr-runs.png
new file mode 100644
index 00000000..dbaaff07
--- /dev/null
+++ b/tests/ref/bidi-consecutive-embedded-ltr-runs.png
Binary files differ
diff --git a/tests/ref/bidi-consecutive-embedded-rtl-runs.png b/tests/ref/bidi-consecutive-embedded-rtl-runs.png
new file mode 100644
index 00000000..4cf62d3e
--- /dev/null
+++ b/tests/ref/bidi-consecutive-embedded-rtl-runs.png
Binary files differ
diff --git a/tests/ref/bidi-en-he-top-level.png b/tests/ref/bidi-en-he-top-level.png
new file mode 100644
index 00000000..abab54f4
--- /dev/null
+++ b/tests/ref/bidi-en-he-top-level.png
Binary files differ
diff --git a/tests/ref/bidi-explicit-dir.png b/tests/ref/bidi-explicit-dir.png
new file mode 100644
index 00000000..8b813be0
--- /dev/null
+++ b/tests/ref/bidi-explicit-dir.png
Binary files differ
diff --git a/tests/ref/bidi-manual-linebreak.png b/tests/ref/bidi-manual-linebreak.png
new file mode 100644
index 00000000..4d0eb6f8
--- /dev/null
+++ b/tests/ref/bidi-manual-linebreak.png
Binary files differ
diff --git a/tests/ref/bidi-nesting.png b/tests/ref/bidi-nesting.png
new file mode 100644
index 00000000..e18d6c0a
--- /dev/null
+++ b/tests/ref/bidi-nesting.png
Binary files differ
diff --git a/tests/ref/bidi-obj.png b/tests/ref/bidi-obj.png
new file mode 100644
index 00000000..8cc41528
--- /dev/null
+++ b/tests/ref/bidi-obj.png
Binary files differ
diff --git a/tests/ref/bidi-raw.png b/tests/ref/bidi-raw.png
new file mode 100644
index 00000000..24503ee9
--- /dev/null
+++ b/tests/ref/bidi-raw.png
Binary files differ
diff --git a/tests/ref/bidi-spacing.png b/tests/ref/bidi-spacing.png
new file mode 100644
index 00000000..44ede76f
--- /dev/null
+++ b/tests/ref/bidi-spacing.png
Binary files differ
diff --git a/tests/ref/bidi-whitespace-reset.png b/tests/ref/bidi-whitespace-reset.png
new file mode 100644
index 00000000..7d64012f
--- /dev/null
+++ b/tests/ref/bidi-whitespace-reset.png
Binary files differ
diff --git a/tests/ref/block-box-fill.png b/tests/ref/block-box-fill.png
new file mode 100644
index 00000000..fe4f7258
--- /dev/null
+++ b/tests/ref/block-box-fill.png
Binary files differ
diff --git a/tests/ref/block-clip-svg-glyphs.png b/tests/ref/block-clip-svg-glyphs.png
new file mode 100644
index 00000000..d8db5b61
--- /dev/null
+++ b/tests/ref/block-clip-svg-glyphs.png
Binary files differ
diff --git a/tests/ref/block-clip-text.png b/tests/ref/block-clip-text.png
new file mode 100644
index 00000000..7cd86ddb
--- /dev/null
+++ b/tests/ref/block-clip-text.png
Binary files differ
diff --git a/tests/ref/block-clipping-multiple-pages.png b/tests/ref/block-clipping-multiple-pages.png
new file mode 100644
index 00000000..9c9aa89b
--- /dev/null
+++ b/tests/ref/block-clipping-multiple-pages.png
Binary files differ
diff --git a/tests/ref/block-fixed-height.png b/tests/ref/block-fixed-height.png
new file mode 100644
index 00000000..95c3be1e
--- /dev/null
+++ b/tests/ref/block-fixed-height.png
Binary files differ
diff --git a/tests/ref/block-multiple-pages.png b/tests/ref/block-multiple-pages.png
new file mode 100644
index 00000000..c2f192bd
--- /dev/null
+++ b/tests/ref/block-multiple-pages.png
Binary files differ
diff --git a/tests/ref/block-sizing.png b/tests/ref/block-sizing.png
new file mode 100644
index 00000000..76cb04df
--- /dev/null
+++ b/tests/ref/block-sizing.png
Binary files differ
diff --git a/tests/ref/block-spacing-basic.png b/tests/ref/block-spacing-basic.png
new file mode 100644
index 00000000..875410ac
--- /dev/null
+++ b/tests/ref/block-spacing-basic.png
Binary files differ
diff --git a/tests/ref/block-spacing-collapse-text-style.png b/tests/ref/block-spacing-collapse-text-style.png
new file mode 100644
index 00000000..6c631457
--- /dev/null
+++ b/tests/ref/block-spacing-collapse-text-style.png
Binary files differ
diff --git a/tests/ref/block-spacing-maximum.png b/tests/ref/block-spacing-maximum.png
new file mode 100644
index 00000000..755b1cc3
--- /dev/null
+++ b/tests/ref/block-spacing-maximum.png
Binary files differ
diff --git a/tests/ref/block-spacing-table.png b/tests/ref/block-spacing-table.png
new file mode 100644
index 00000000..1591acb7
--- /dev/null
+++ b/tests/ref/block-spacing-table.png
Binary files differ
diff --git a/tests/ref/box-clip-radius-without-stroke.png b/tests/ref/box-clip-radius-without-stroke.png
new file mode 100644
index 00000000..12137358
--- /dev/null
+++ b/tests/ref/box-clip-radius-without-stroke.png
Binary files differ
diff --git a/tests/ref/box-clip-radius.png b/tests/ref/box-clip-radius.png
new file mode 100644
index 00000000..da20fa5b
--- /dev/null
+++ b/tests/ref/box-clip-radius.png
Binary files differ
diff --git a/tests/ref/box-clip-rect.png b/tests/ref/box-clip-rect.png
new file mode 100644
index 00000000..49a4e4ab
--- /dev/null
+++ b/tests/ref/box-clip-rect.png
Binary files differ
diff --git a/tests/ref/box-layoutable-child.png b/tests/ref/box-layoutable-child.png
new file mode 100644
index 00000000..a1960a24
--- /dev/null
+++ b/tests/ref/box-layoutable-child.png
Binary files differ
diff --git a/tests/ref/box-width-fr.png b/tests/ref/box-width-fr.png
new file mode 100644
index 00000000..30d48163
--- /dev/null
+++ b/tests/ref/box-width-fr.png
Binary files differ
diff --git a/tests/ref/box.png b/tests/ref/box.png
new file mode 100644
index 00000000..fde288a8
--- /dev/null
+++ b/tests/ref/box.png
Binary files differ
diff --git a/tests/ref/bugs/1050-terms-indent.png b/tests/ref/bugs/1050-terms-indent.png
deleted file mode 100644
index 58a7feae..00000000
--- a/tests/ref/bugs/1050-terms-indent.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/1240-stack-fr.png b/tests/ref/bugs/1240-stack-fr.png
deleted file mode 100644
index 29df5d44..00000000
--- a/tests/ref/bugs/1240-stack-fr.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/1597-cite-footnote.png b/tests/ref/bugs/1597-cite-footnote.png
deleted file mode 100644
index c2e219f2..00000000
--- a/tests/ref/bugs/1597-cite-footnote.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/2044-invalid-parsed-ident.png b/tests/ref/bugs/2044-invalid-parsed-ident.png
deleted file mode 100644
index 327150e7..00000000
--- a/tests/ref/bugs/2044-invalid-parsed-ident.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/2105-linebreak-tofu.png b/tests/ref/bugs/2105-linebreak-tofu.png
deleted file mode 100644
index 78f937eb..00000000
--- a/tests/ref/bugs/2105-linebreak-tofu.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/2595-float-overlap.png b/tests/ref/bugs/2595-float-overlap.png
deleted file mode 100644
index 6d8eaf94..00000000
--- a/tests/ref/bugs/2595-float-overlap.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/2650-cjk-latin-spacing-meta.png b/tests/ref/bugs/2650-cjk-latin-spacing-meta.png
deleted file mode 100644
index 35ff0e62..00000000
--- a/tests/ref/bugs/2650-cjk-latin-spacing-meta.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/2715-float-order.png b/tests/ref/bugs/2715-float-order.png
deleted file mode 100644
index 0a4f8812..00000000
--- a/tests/ref/bugs/2715-float-order.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/3082-chinese-punctuation.png b/tests/ref/bugs/3082-chinese-punctuation.png
deleted file mode 100644
index c187d495..00000000
--- a/tests/ref/bugs/3082-chinese-punctuation.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/3641-float-loop.png b/tests/ref/bugs/3641-float-loop.png
deleted file mode 100644
index 092b2ff5..00000000
--- a/tests/ref/bugs/3641-float-loop.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/3650-italic-equation.png b/tests/ref/bugs/3650-italic-equation.png
deleted file mode 100644
index 41f071ab..00000000
--- a/tests/ref/bugs/3650-italic-equation.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/3658-math-size.png b/tests/ref/bugs/3658-math-size.png
deleted file mode 100644
index 94c8d388..00000000
--- a/tests/ref/bugs/3658-math-size.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/3662-pdf-smartquotes.png b/tests/ref/bugs/3662-pdf-smartquotes.png
deleted file mode 100644
index c272a8ff..00000000
--- a/tests/ref/bugs/3662-pdf-smartquotes.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/3700-deformed-stroke.png b/tests/ref/bugs/3700-deformed-stroke.png
deleted file mode 100644
index f1db2836..00000000
--- a/tests/ref/bugs/3700-deformed-stroke.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/3841-tabs-in-raw-typ-code.png b/tests/ref/bugs/3841-tabs-in-raw-typ-code.png
deleted file mode 100644
index 37dab136..00000000
--- a/tests/ref/bugs/3841-tabs-in-raw-typ-code.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/870-image-rotation.png b/tests/ref/bugs/870-image-rotation.png
deleted file mode 100644
index 83d9267d..00000000
--- a/tests/ref/bugs/870-image-rotation.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/args-sink.png b/tests/ref/bugs/args-sink.png
deleted file mode 100644
index 564c59a2..00000000
--- a/tests/ref/bugs/args-sink.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/bibliography-math.png b/tests/ref/bugs/bibliography-math.png
deleted file mode 100644
index 0ab308dc..00000000
--- a/tests/ref/bugs/bibliography-math.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/bidi-tofus.png b/tests/ref/bugs/bidi-tofus.png
deleted file mode 100644
index 1b7a7d8b..00000000
--- a/tests/ref/bugs/bidi-tofus.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/block-width-box.png b/tests/ref/bugs/block-width-box.png
deleted file mode 100644
index 9cb27a5d..00000000
--- a/tests/ref/bugs/block-width-box.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/cite-locate.png b/tests/ref/bugs/cite-locate.png
deleted file mode 100644
index bd31df7d..00000000
--- a/tests/ref/bugs/cite-locate.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/cite-show-set.png b/tests/ref/bugs/cite-show-set.png
deleted file mode 100644
index 566186a4..00000000
--- a/tests/ref/bugs/cite-show-set.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/clamp-panic.png b/tests/ref/bugs/clamp-panic.png
deleted file mode 100644
index c0c4912e..00000000
--- a/tests/ref/bugs/clamp-panic.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/columns-1.png b/tests/ref/bugs/columns-1.png
deleted file mode 100644
index 4b462b60..00000000
--- a/tests/ref/bugs/columns-1.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/emoji-linebreak.png b/tests/ref/bugs/emoji-linebreak.png
deleted file mode 100644
index 6944233d..00000000
--- a/tests/ref/bugs/emoji-linebreak.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/flow-1.png b/tests/ref/bugs/flow-1.png
deleted file mode 100644
index 662a7b14..00000000
--- a/tests/ref/bugs/flow-1.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/flow-2.png b/tests/ref/bugs/flow-2.png
deleted file mode 100644
index c7ece308..00000000
--- a/tests/ref/bugs/flow-2.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/flow-3.png b/tests/ref/bugs/flow-3.png
deleted file mode 100644
index 25acc06d..00000000
--- a/tests/ref/bugs/flow-3.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/flow-4.png b/tests/ref/bugs/flow-4.png
deleted file mode 100644
index 2adcbe15..00000000
--- a/tests/ref/bugs/flow-4.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/flow-5.png b/tests/ref/bugs/flow-5.png
deleted file mode 100644
index 648c8c44..00000000
--- a/tests/ref/bugs/flow-5.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/fold-vector.png b/tests/ref/bugs/fold-vector.png
deleted file mode 100644
index d8503a8e..00000000
--- a/tests/ref/bugs/fold-vector.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/footnote-keep-multiple.png b/tests/ref/bugs/footnote-keep-multiple.png
deleted file mode 100644
index f3b67a74..00000000
--- a/tests/ref/bugs/footnote-keep-multiple.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/footnote-list.png b/tests/ref/bugs/footnote-list.png
deleted file mode 100644
index 1b56f227..00000000
--- a/tests/ref/bugs/footnote-list.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/gradient-cmyk-encode.png b/tests/ref/bugs/gradient-cmyk-encode.png
deleted file mode 100644
index 5002442f..00000000
--- a/tests/ref/bugs/gradient-cmyk-encode.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/grid-1.png b/tests/ref/bugs/grid-1.png
deleted file mode 100644
index f60ad7f4..00000000
--- a/tests/ref/bugs/grid-1.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/grid-2.png b/tests/ref/bugs/grid-2.png
deleted file mode 100644
index 882e0d6a..00000000
--- a/tests/ref/bugs/grid-2.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/grid-3.png b/tests/ref/bugs/grid-3.png
deleted file mode 100644
index 6b5ae649..00000000
--- a/tests/ref/bugs/grid-3.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/grid-4.png b/tests/ref/bugs/grid-4.png
deleted file mode 100644
index 475f561e..00000000
--- a/tests/ref/bugs/grid-4.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/hide-meta.png b/tests/ref/bugs/hide-meta.png
deleted file mode 100644
index 76b4671a..00000000
--- a/tests/ref/bugs/hide-meta.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/justify-hanging-indent.png b/tests/ref/bugs/justify-hanging-indent.png
deleted file mode 100644
index 015cc44e..00000000
--- a/tests/ref/bugs/justify-hanging-indent.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/line-align.png b/tests/ref/bugs/line-align.png
deleted file mode 100644
index 1117ed6b..00000000
--- a/tests/ref/bugs/line-align.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/linebreak-no-justifiables.png b/tests/ref/bugs/linebreak-no-justifiables.png
deleted file mode 100644
index 3f934592..00000000
--- a/tests/ref/bugs/linebreak-no-justifiables.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/mat-aug-color.png b/tests/ref/bugs/mat-aug-color.png
deleted file mode 100644
index 472c1968..00000000
--- a/tests/ref/bugs/mat-aug-color.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/math-eval.png b/tests/ref/bugs/math-eval.png
deleted file mode 100644
index b673e503..00000000
--- a/tests/ref/bugs/math-eval.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/math-hide.png b/tests/ref/bugs/math-hide.png
deleted file mode 100644
index 7ac5d2f1..00000000
--- a/tests/ref/bugs/math-hide.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/math-number-spacing.png b/tests/ref/bugs/math-number-spacing.png
deleted file mode 100644
index 5ec65df3..00000000
--- a/tests/ref/bugs/math-number-spacing.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/math-realize.png b/tests/ref/bugs/math-realize.png
deleted file mode 100644
index e972e099..00000000
--- a/tests/ref/bugs/math-realize.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/math-shift.png b/tests/ref/bugs/math-shift.png
deleted file mode 100644
index d6a2ef3b..00000000
--- a/tests/ref/bugs/math-shift.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/math-text-break.png b/tests/ref/bugs/math-text-break.png
deleted file mode 100644
index 768ca65f..00000000
--- a/tests/ref/bugs/math-text-break.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/new-cm-svg.png b/tests/ref/bugs/new-cm-svg.png
deleted file mode 100644
index d75a6dbb..00000000
--- a/tests/ref/bugs/new-cm-svg.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/newline-mode.png b/tests/ref/bugs/newline-mode.png
deleted file mode 100644
index d4b6c6d8..00000000
--- a/tests/ref/bugs/newline-mode.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/pagebreak-bibliography.png b/tests/ref/bugs/pagebreak-bibliography.png
deleted file mode 100644
index 43de1574..00000000
--- a/tests/ref/bugs/pagebreak-bibliography.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/pagebreak-numbering.png b/tests/ref/bugs/pagebreak-numbering.png
deleted file mode 100644
index 96f047a8..00000000
--- a/tests/ref/bugs/pagebreak-numbering.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/pagebreak-set-style.png b/tests/ref/bugs/pagebreak-set-style.png
deleted file mode 100644
index f81b8c2f..00000000
--- a/tests/ref/bugs/pagebreak-set-style.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/place-base.png b/tests/ref/bugs/place-base.png
deleted file mode 100644
index 4442b173..00000000
--- a/tests/ref/bugs/place-base.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/place-nested.png b/tests/ref/bugs/place-nested.png
deleted file mode 100644
index b59dc5d3..00000000
--- a/tests/ref/bugs/place-nested.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/place-pagebreak.png b/tests/ref/bugs/place-pagebreak.png
deleted file mode 100644
index 2aa3d6b0..00000000
--- a/tests/ref/bugs/place-pagebreak.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/place-spacing.png b/tests/ref/bugs/place-spacing.png
deleted file mode 100644
index d14ce6ec..00000000
--- a/tests/ref/bugs/place-spacing.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/raw-color-overwrite.png b/tests/ref/bugs/raw-color-overwrite.png
deleted file mode 100644
index b01d86a4..00000000
--- a/tests/ref/bugs/raw-color-overwrite.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/smartquotes-in-outline.png b/tests/ref/bugs/smartquotes-in-outline.png
deleted file mode 100644
index 8a2cbc6a..00000000
--- a/tests/ref/bugs/smartquotes-in-outline.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/smartquotes-on-newline.png b/tests/ref/bugs/smartquotes-on-newline.png
deleted file mode 100644
index fdf4623a..00000000
--- a/tests/ref/bugs/smartquotes-on-newline.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/spacing-behaviour.png b/tests/ref/bugs/spacing-behaviour.png
deleted file mode 100644
index 08fcfa73..00000000
--- a/tests/ref/bugs/spacing-behaviour.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/square-base.png b/tests/ref/bugs/square-base.png
deleted file mode 100644
index 290ee54e..00000000
--- a/tests/ref/bugs/square-base.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/table-lines.png b/tests/ref/bugs/table-lines.png
deleted file mode 100644
index 600391cb..00000000
--- a/tests/ref/bugs/table-lines.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/bugs/table-row-missing.png b/tests/ref/bugs/table-row-missing.png
deleted file mode 100644
index 90c46d32..00000000
--- a/tests/ref/bugs/table-row-missing.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/call-basic.png b/tests/ref/call-basic.png
new file mode 100644
index 00000000..9016e9e8
--- /dev/null
+++ b/tests/ref/call-basic.png
Binary files differ
diff --git a/tests/ref/circle-auto-sizing.png b/tests/ref/circle-auto-sizing.png
new file mode 100644
index 00000000..377dbe1d
--- /dev/null
+++ b/tests/ref/circle-auto-sizing.png
Binary files differ
diff --git a/tests/ref/circle-directly-in-rect.png b/tests/ref/circle-directly-in-rect.png
new file mode 100644
index 00000000..cb74496d
--- /dev/null
+++ b/tests/ref/circle-directly-in-rect.png
Binary files differ
diff --git a/tests/ref/circle-relative-sizing.png b/tests/ref/circle-relative-sizing.png
new file mode 100644
index 00000000..efff34cf
--- /dev/null
+++ b/tests/ref/circle-relative-sizing.png
Binary files differ
diff --git a/tests/ref/circle-sizing-options.png b/tests/ref/circle-sizing-options.png
new file mode 100644
index 00000000..778a8249
--- /dev/null
+++ b/tests/ref/circle-sizing-options.png
Binary files differ
diff --git a/tests/ref/circle.png b/tests/ref/circle.png
new file mode 100644
index 00000000..8a86e194
--- /dev/null
+++ b/tests/ref/circle.png
Binary files differ
diff --git a/tests/ref/cite-footnote.png b/tests/ref/cite-footnote.png
new file mode 100644
index 00000000..5bc6433e
--- /dev/null
+++ b/tests/ref/cite-footnote.png
Binary files differ
diff --git a/tests/ref/cite-form.png b/tests/ref/cite-form.png
new file mode 100644
index 00000000..c35a3573
--- /dev/null
+++ b/tests/ref/cite-form.png
Binary files differ
diff --git a/tests/ref/cite-group.png b/tests/ref/cite-group.png
new file mode 100644
index 00000000..70feb4e1
--- /dev/null
+++ b/tests/ref/cite-group.png
Binary files differ
diff --git a/tests/ref/cite-grouping-and-ordering.png b/tests/ref/cite-grouping-and-ordering.png
new file mode 100644
index 00000000..6a70539d
--- /dev/null
+++ b/tests/ref/cite-grouping-and-ordering.png
Binary files differ
diff --git a/tests/ref/cjk-punctuation-adjustment-1.png b/tests/ref/cjk-punctuation-adjustment-1.png
new file mode 100644
index 00000000..a68274cf
--- /dev/null
+++ b/tests/ref/cjk-punctuation-adjustment-1.png
Binary files differ
diff --git a/tests/ref/cjk-punctuation-adjustment-2.png b/tests/ref/cjk-punctuation-adjustment-2.png
new file mode 100644
index 00000000..925c0f3c
--- /dev/null
+++ b/tests/ref/cjk-punctuation-adjustment-2.png
Binary files differ
diff --git a/tests/ref/cjk-punctuation-adjustment-3.png b/tests/ref/cjk-punctuation-adjustment-3.png
new file mode 100644
index 00000000..e5eb70a9
--- /dev/null
+++ b/tests/ref/cjk-punctuation-adjustment-3.png
Binary files differ
diff --git a/tests/ref/closure-capture-in-lvalue.png b/tests/ref/closure-capture-in-lvalue.png
new file mode 100644
index 00000000..5f3ab035
--- /dev/null
+++ b/tests/ref/closure-capture-in-lvalue.png
Binary files differ
diff --git a/tests/ref/closure-path-resolve-in-layout-phase.png b/tests/ref/closure-path-resolve-in-layout-phase.png
new file mode 100644
index 00000000..e56e23a0
--- /dev/null
+++ b/tests/ref/closure-path-resolve-in-layout-phase.png
Binary files differ
diff --git a/tests/ref/closure-without-params-non-atomic.png b/tests/ref/closure-without-params-non-atomic.png
new file mode 100644
index 00000000..7d01ea3c
--- /dev/null
+++ b/tests/ref/closure-without-params-non-atomic.png
Binary files differ
diff --git a/tests/ref/code-block-basic-syntax.png b/tests/ref/code-block-basic-syntax.png
new file mode 100644
index 00000000..7b2e6045
--- /dev/null
+++ b/tests/ref/code-block-basic-syntax.png
Binary files differ
diff --git a/tests/ref/color-cmyk-ops.png b/tests/ref/color-cmyk-ops.png
new file mode 100644
index 00000000..4f799efa
--- /dev/null
+++ b/tests/ref/color-cmyk-ops.png
Binary files differ
diff --git a/tests/ref/color-luma.png b/tests/ref/color-luma.png
new file mode 100644
index 00000000..7bacc744
--- /dev/null
+++ b/tests/ref/color-luma.png
Binary files differ
diff --git a/tests/ref/color-outside-srgb-gamut.png b/tests/ref/color-outside-srgb-gamut.png
new file mode 100644
index 00000000..3a2806c5
--- /dev/null
+++ b/tests/ref/color-outside-srgb-gamut.png
Binary files differ
diff --git a/tests/ref/color-rotate-hue.png b/tests/ref/color-rotate-hue.png
new file mode 100644
index 00000000..a2139714
--- /dev/null
+++ b/tests/ref/color-rotate-hue.png
Binary files differ
diff --git a/tests/ref/color-saturation.png b/tests/ref/color-saturation.png
new file mode 100644
index 00000000..ccac4828
--- /dev/null
+++ b/tests/ref/color-saturation.png
Binary files differ
diff --git a/tests/ref/color-spaces.png b/tests/ref/color-spaces.png
new file mode 100644
index 00000000..ade861cc
--- /dev/null
+++ b/tests/ref/color-spaces.png
Binary files differ
diff --git a/tests/ref/columns-colbreak-after-place.png b/tests/ref/columns-colbreak-after-place.png
new file mode 100644
index 00000000..f6a8a63d
--- /dev/null
+++ b/tests/ref/columns-colbreak-after-place.png
Binary files differ
diff --git a/tests/ref/columns-empty-second-column.png b/tests/ref/columns-empty-second-column.png
new file mode 100644
index 00000000..a00d5fb2
--- /dev/null
+++ b/tests/ref/columns-empty-second-column.png
Binary files differ
diff --git a/tests/ref/columns-in-auto-sized-rect.png b/tests/ref/columns-in-auto-sized-rect.png
new file mode 100644
index 00000000..00088b7e
--- /dev/null
+++ b/tests/ref/columns-in-auto-sized-rect.png
Binary files differ
diff --git a/tests/ref/columns-in-fixed-size-rect.png b/tests/ref/columns-in-fixed-size-rect.png
new file mode 100644
index 00000000..28cb97cb
--- /dev/null
+++ b/tests/ref/columns-in-fixed-size-rect.png
Binary files differ
diff --git a/tests/ref/columns-more-with-gutter.png b/tests/ref/columns-more-with-gutter.png
new file mode 100644
index 00000000..e89c6a0b
--- /dev/null
+++ b/tests/ref/columns-more-with-gutter.png
Binary files differ
diff --git a/tests/ref/columns-one.png b/tests/ref/columns-one.png
new file mode 100644
index 00000000..02abf659
--- /dev/null
+++ b/tests/ref/columns-one.png
Binary files differ
diff --git a/tests/ref/columns-page-height-auto.png b/tests/ref/columns-page-height-auto.png
new file mode 100644
index 00000000..9b3f1f85
--- /dev/null
+++ b/tests/ref/columns-page-height-auto.png
Binary files differ
diff --git a/tests/ref/columns-page-width-auto.png b/tests/ref/columns-page-width-auto.png
new file mode 100644
index 00000000..04d88bc1
--- /dev/null
+++ b/tests/ref/columns-page-width-auto.png
Binary files differ
diff --git a/tests/ref/columns-rtl.png b/tests/ref/columns-rtl.png
new file mode 100644
index 00000000..7efa57f5
--- /dev/null
+++ b/tests/ref/columns-rtl.png
Binary files differ
diff --git a/tests/ref/columns-set-page-colbreak-pagebreak.png b/tests/ref/columns-set-page-colbreak-pagebreak.png
new file mode 100644
index 00000000..48d2fd7b
--- /dev/null
+++ b/tests/ref/columns-set-page-colbreak-pagebreak.png
Binary files differ
diff --git a/tests/ref/columns-set-page.png b/tests/ref/columns-set-page.png
new file mode 100644
index 00000000..42b5bea7
--- /dev/null
+++ b/tests/ref/columns-set-page.png
Binary files differ
diff --git a/tests/ref/coma.png b/tests/ref/coma.png
index fc0f6ba1..96f9e4d9 100644
--- a/tests/ref/coma.png
+++ b/tests/ref/coma.png
Binary files differ
diff --git a/tests/ref/comment-end-of-line.png b/tests/ref/comment-end-of-line.png
new file mode 100644
index 00000000..94da23cb
--- /dev/null
+++ b/tests/ref/comment-end-of-line.png
Binary files differ
diff --git a/tests/ref/comments.png b/tests/ref/comments.png
new file mode 100644
index 00000000..892ff5e4
--- /dev/null
+++ b/tests/ref/comments.png
Binary files differ
diff --git a/tests/ref/compiler/array.png b/tests/ref/compiler/array.png
deleted file mode 100644
index 9b6bf8b3..00000000
--- a/tests/ref/compiler/array.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/block.png b/tests/ref/compiler/block.png
deleted file mode 100644
index 21a38de2..00000000
--- a/tests/ref/compiler/block.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/break-continue.png b/tests/ref/compiler/break-continue.png
deleted file mode 100644
index 9751d395..00000000
--- a/tests/ref/compiler/break-continue.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/call.png b/tests/ref/compiler/call.png
deleted file mode 100644
index 2c5d1e78..00000000
--- a/tests/ref/compiler/call.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/closure.png b/tests/ref/compiler/closure.png
deleted file mode 100644
index 07c171c5..00000000
--- a/tests/ref/compiler/closure.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/color.png b/tests/ref/compiler/color.png
deleted file mode 100644
index 2b416f64..00000000
--- a/tests/ref/compiler/color.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/comment.png b/tests/ref/compiler/comment.png
deleted file mode 100644
index 608df6ea..00000000
--- a/tests/ref/compiler/comment.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/construct.png b/tests/ref/compiler/construct.png
deleted file mode 100644
index f1acf665..00000000
--- a/tests/ref/compiler/construct.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/content-field.png b/tests/ref/compiler/content-field.png
deleted file mode 100644
index d582cfa1..00000000
--- a/tests/ref/compiler/content-field.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/dict.png b/tests/ref/compiler/dict.png
deleted file mode 100644
index c97b2dbf..00000000
--- a/tests/ref/compiler/dict.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/for.png b/tests/ref/compiler/for.png
deleted file mode 100644
index 5608248f..00000000
--- a/tests/ref/compiler/for.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/highlight.png b/tests/ref/compiler/highlight.png
deleted file mode 100644
index ccbbc056..00000000
--- a/tests/ref/compiler/highlight.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/if.png b/tests/ref/compiler/if.png
deleted file mode 100644
index bd3adc88..00000000
--- a/tests/ref/compiler/if.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/import.png b/tests/ref/compiler/import.png
deleted file mode 100644
index 5c6132d2..00000000
--- a/tests/ref/compiler/import.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/include.png b/tests/ref/compiler/include.png
deleted file mode 100644
index 7fdb0310..00000000
--- a/tests/ref/compiler/include.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/label.png b/tests/ref/compiler/label.png
deleted file mode 100644
index 21764f97..00000000
--- a/tests/ref/compiler/label.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/let.png b/tests/ref/compiler/let.png
deleted file mode 100644
index 4423fe0a..00000000
--- a/tests/ref/compiler/let.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/ops.png b/tests/ref/compiler/ops.png
deleted file mode 100644
index 51fb9d1a..00000000
--- a/tests/ref/compiler/ops.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/repr-color-gradient.png b/tests/ref/compiler/repr-color-gradient.png
deleted file mode 100644
index 11bde774..00000000
--- a/tests/ref/compiler/repr-color-gradient.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/repr.png b/tests/ref/compiler/repr.png
deleted file mode 100644
index 105b6c80..00000000
--- a/tests/ref/compiler/repr.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/return.png b/tests/ref/compiler/return.png
deleted file mode 100644
index e8fa3ab2..00000000
--- a/tests/ref/compiler/return.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/select-where-styles.png b/tests/ref/compiler/select-where-styles.png
deleted file mode 100644
index ffdc4bab..00000000
--- a/tests/ref/compiler/select-where-styles.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/selector-logical.png b/tests/ref/compiler/selector-logical.png
deleted file mode 100644
index eafa93c8..00000000
--- a/tests/ref/compiler/selector-logical.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/set.png b/tests/ref/compiler/set.png
deleted file mode 100644
index 26409396..00000000
--- a/tests/ref/compiler/set.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/shorthand.png b/tests/ref/compiler/shorthand.png
deleted file mode 100644
index 4507177b..00000000
--- a/tests/ref/compiler/shorthand.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/show-bare.png b/tests/ref/compiler/show-bare.png
deleted file mode 100644
index c6a1e101..00000000
--- a/tests/ref/compiler/show-bare.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/show-node.png b/tests/ref/compiler/show-node.png
deleted file mode 100644
index 396e5429..00000000
--- a/tests/ref/compiler/show-node.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/show-recursive.png b/tests/ref/compiler/show-recursive.png
deleted file mode 100644
index a5a153c0..00000000
--- a/tests/ref/compiler/show-recursive.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/show-selector-logical.png b/tests/ref/compiler/show-selector-logical.png
deleted file mode 100644
index a7a80053..00000000
--- a/tests/ref/compiler/show-selector-logical.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/show-selector.png b/tests/ref/compiler/show-selector.png
deleted file mode 100644
index 52e99c9a..00000000
--- a/tests/ref/compiler/show-selector.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/show-set-func.png b/tests/ref/compiler/show-set-func.png
deleted file mode 100644
index c5ff2489..00000000
--- a/tests/ref/compiler/show-set-func.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/show-set-text.png b/tests/ref/compiler/show-set-text.png
deleted file mode 100644
index 27803e8a..00000000
--- a/tests/ref/compiler/show-set-text.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/show-set.png b/tests/ref/compiler/show-set.png
deleted file mode 100644
index e87fc600..00000000
--- a/tests/ref/compiler/show-set.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/show-text.png b/tests/ref/compiler/show-text.png
deleted file mode 100644
index 2026cc35..00000000
--- a/tests/ref/compiler/show-text.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compiler/while.png b/tests/ref/compiler/while.png
deleted file mode 100644
index d0f86473..00000000
--- a/tests/ref/compiler/while.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compute/construct.png b/tests/ref/compute/construct.png
deleted file mode 100644
index e1717473..00000000
--- a/tests/ref/compute/construct.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compute/data.png b/tests/ref/compute/data.png
deleted file mode 100644
index 2dab6875..00000000
--- a/tests/ref/compute/data.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compute/eval-path.png b/tests/ref/compute/eval-path.png
deleted file mode 100644
index c59dd2aa..00000000
--- a/tests/ref/compute/eval-path.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/compute/foundations.png b/tests/ref/compute/foundations.png
deleted file mode 100644
index 5d6ba744..00000000
--- a/tests/ref/compute/foundations.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/content-field-materialized-heading.png b/tests/ref/content-field-materialized-heading.png
new file mode 100644
index 00000000..72201627
--- /dev/null
+++ b/tests/ref/content-field-materialized-heading.png
Binary files differ
diff --git a/tests/ref/content-field-materialized-query.png b/tests/ref/content-field-materialized-query.png
new file mode 100644
index 00000000..2d2a1480
--- /dev/null
+++ b/tests/ref/content-field-materialized-query.png
Binary files differ
diff --git a/tests/ref/content-field-materialized-table.png b/tests/ref/content-field-materialized-table.png
new file mode 100644
index 00000000..9eceec8f
--- /dev/null
+++ b/tests/ref/content-field-materialized-table.png
Binary files differ
diff --git a/tests/ref/content-fields-complex.png b/tests/ref/content-fields-complex.png
new file mode 100644
index 00000000..624a8b33
--- /dev/null
+++ b/tests/ref/content-fields-complex.png
Binary files differ
diff --git a/tests/ref/content-label-field-access.png b/tests/ref/content-label-field-access.png
new file mode 100644
index 00000000..bdb7c0f2
--- /dev/null
+++ b/tests/ref/content-label-field-access.png
Binary files differ
diff --git a/tests/ref/content-label-fields-method.png b/tests/ref/content-label-fields-method.png
new file mode 100644
index 00000000..bdb7c0f2
--- /dev/null
+++ b/tests/ref/content-label-fields-method.png
Binary files differ
diff --git a/tests/ref/content-label-has-method.png b/tests/ref/content-label-has-method.png
new file mode 100644
index 00000000..bdb7c0f2
--- /dev/null
+++ b/tests/ref/content-label-has-method.png
Binary files differ
diff --git a/tests/ref/context-compatibility-locate.png b/tests/ref/context-compatibility-locate.png
new file mode 100644
index 00000000..4c8944ab
--- /dev/null
+++ b/tests/ref/context-compatibility-locate.png
Binary files differ
diff --git a/tests/ref/context-compatibility-styling.png b/tests/ref/context-compatibility-styling.png
new file mode 100644
index 00000000..aee16c3a
--- /dev/null
+++ b/tests/ref/context-compatibility-styling.png
Binary files differ
diff --git a/tests/ref/counter-basic-1.png b/tests/ref/counter-basic-1.png
new file mode 100644
index 00000000..92282594
--- /dev/null
+++ b/tests/ref/counter-basic-1.png
Binary files differ
diff --git a/tests/ref/counter-figure.png b/tests/ref/counter-figure.png
new file mode 100644
index 00000000..5e4a4a5f
--- /dev/null
+++ b/tests/ref/counter-figure.png
Binary files differ
diff --git a/tests/ref/counter-heading.png b/tests/ref/counter-heading.png
new file mode 100644
index 00000000..96dafd6a
--- /dev/null
+++ b/tests/ref/counter-heading.png
Binary files differ
diff --git a/tests/ref/counter-label.png b/tests/ref/counter-label.png
new file mode 100644
index 00000000..6fea90df
--- /dev/null
+++ b/tests/ref/counter-label.png
Binary files differ
diff --git a/tests/ref/counter-page.png b/tests/ref/counter-page.png
new file mode 100644
index 00000000..be1653eb
--- /dev/null
+++ b/tests/ref/counter-page.png
Binary files differ
diff --git a/tests/ref/csv.png b/tests/ref/csv.png
new file mode 100644
index 00000000..fd0c9a1c
--- /dev/null
+++ b/tests/ref/csv.png
Binary files differ
diff --git a/tests/ref/destructuring-during-loop-continue.png b/tests/ref/destructuring-during-loop-continue.png
new file mode 100644
index 00000000..9ea8e3c1
--- /dev/null
+++ b/tests/ref/destructuring-during-loop-continue.png
Binary files differ
diff --git a/tests/ref/dict-basic-methods.png b/tests/ref/dict-basic-methods.png
new file mode 100644
index 00000000..20410cc3
--- /dev/null
+++ b/tests/ref/dict-basic-methods.png
Binary files differ
diff --git a/tests/ref/dict-basic-syntax.png b/tests/ref/dict-basic-syntax.png
new file mode 100644
index 00000000..02effef6
--- /dev/null
+++ b/tests/ref/dict-basic-syntax.png
Binary files differ
diff --git a/tests/ref/dict-from-module.png b/tests/ref/dict-from-module.png
new file mode 100644
index 00000000..7fd6eec3
--- /dev/null
+++ b/tests/ref/dict-from-module.png
Binary files differ
diff --git a/tests/ref/dict-remove-order.png b/tests/ref/dict-remove-order.png
new file mode 100644
index 00000000..20410cc3
--- /dev/null
+++ b/tests/ref/dict-remove-order.png
Binary files differ
diff --git a/tests/ref/document-set-title.png b/tests/ref/document-set-title.png
new file mode 100644
index 00000000..74bcfe19
--- /dev/null
+++ b/tests/ref/document-set-title.png
Binary files differ
diff --git a/tests/ref/ellipse-auto-sizing.png b/tests/ref/ellipse-auto-sizing.png
new file mode 100644
index 00000000..ed201521
--- /dev/null
+++ b/tests/ref/ellipse-auto-sizing.png
Binary files differ
diff --git a/tests/ref/ellipse.png b/tests/ref/ellipse.png
new file mode 100644
index 00000000..0f4e92ca
--- /dev/null
+++ b/tests/ref/ellipse.png
Binary files differ
diff --git a/tests/ref/emph-and-strong-call-in-word.png b/tests/ref/emph-and-strong-call-in-word.png
new file mode 100644
index 00000000..4720f994
--- /dev/null
+++ b/tests/ref/emph-and-strong-call-in-word.png
Binary files differ
diff --git a/tests/ref/emph-double-underscore-empty-hint.png b/tests/ref/emph-double-underscore-empty-hint.png
new file mode 100644
index 00000000..a940dfb6
--- /dev/null
+++ b/tests/ref/emph-double-underscore-empty-hint.png
Binary files differ
diff --git a/tests/ref/emph-syntax.png b/tests/ref/emph-syntax.png
new file mode 100644
index 00000000..66f117a8
--- /dev/null
+++ b/tests/ref/emph-syntax.png
Binary files differ
diff --git a/tests/ref/empty.png b/tests/ref/empty.png
deleted file mode 100644
index db3a6695..00000000
--- a/tests/ref/empty.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/enum-built-in-loop.png b/tests/ref/enum-built-in-loop.png
new file mode 100644
index 00000000..298518da
--- /dev/null
+++ b/tests/ref/enum-built-in-loop.png
Binary files differ
diff --git a/tests/ref/enum-function-call.png b/tests/ref/enum-function-call.png
new file mode 100644
index 00000000..a451f27c
--- /dev/null
+++ b/tests/ref/enum-function-call.png
Binary files differ
diff --git a/tests/ref/enum-number-align-2d.png b/tests/ref/enum-number-align-2d.png
new file mode 100644
index 00000000..e205844f
--- /dev/null
+++ b/tests/ref/enum-number-align-2d.png
Binary files differ
diff --git a/tests/ref/enum-number-align-default.png b/tests/ref/enum-number-align-default.png
new file mode 100644
index 00000000..c47f9001
--- /dev/null
+++ b/tests/ref/enum-number-align-default.png
Binary files differ
diff --git a/tests/ref/enum-number-align-specified.png b/tests/ref/enum-number-align-specified.png
new file mode 100644
index 00000000..b2f2d619
--- /dev/null
+++ b/tests/ref/enum-number-align-specified.png
Binary files differ
diff --git a/tests/ref/enum-number-align-unaffected.png b/tests/ref/enum-number-align-unaffected.png
new file mode 100644
index 00000000..3abcaaab
--- /dev/null
+++ b/tests/ref/enum-number-align-unaffected.png
Binary files differ
diff --git a/tests/ref/enum-number-align-unfolded.png b/tests/ref/enum-number-align-unfolded.png
new file mode 100644
index 00000000..8c4f2943
--- /dev/null
+++ b/tests/ref/enum-number-align-unfolded.png
Binary files differ
diff --git a/tests/ref/enum-number-override-nested.png b/tests/ref/enum-number-override-nested.png
new file mode 100644
index 00000000..22bb7611
--- /dev/null
+++ b/tests/ref/enum-number-override-nested.png
Binary files differ
diff --git a/tests/ref/enum-number-override.png b/tests/ref/enum-number-override.png
new file mode 100644
index 00000000..65c0f9d8
--- /dev/null
+++ b/tests/ref/enum-number-override.png
Binary files differ
diff --git a/tests/ref/enum-numbering-closure-nested-complex.png b/tests/ref/enum-numbering-closure-nested-complex.png
new file mode 100644
index 00000000..a756f37c
--- /dev/null
+++ b/tests/ref/enum-numbering-closure-nested-complex.png
Binary files differ
diff --git a/tests/ref/enum-numbering-closure-nested.png b/tests/ref/enum-numbering-closure-nested.png
new file mode 100644
index 00000000..25a5c42d
--- /dev/null
+++ b/tests/ref/enum-numbering-closure-nested.png
Binary files differ
diff --git a/tests/ref/enum-numbering-closure.png b/tests/ref/enum-numbering-closure.png
new file mode 100644
index 00000000..bf86f554
--- /dev/null
+++ b/tests/ref/enum-numbering-closure.png
Binary files differ
diff --git a/tests/ref/enum-numbering-full.png b/tests/ref/enum-numbering-full.png
new file mode 100644
index 00000000..46449e57
--- /dev/null
+++ b/tests/ref/enum-numbering-full.png
Binary files differ
diff --git a/tests/ref/enum-numbering-pattern.png b/tests/ref/enum-numbering-pattern.png
new file mode 100644
index 00000000..4ecb9e4a
--- /dev/null
+++ b/tests/ref/enum-numbering-pattern.png
Binary files differ
diff --git a/tests/ref/enum-syntax-at-start.png b/tests/ref/enum-syntax-at-start.png
new file mode 100644
index 00000000..ce9f3967
--- /dev/null
+++ b/tests/ref/enum-syntax-at-start.png
Binary files differ
diff --git a/tests/ref/enum-syntax-edge-cases.png b/tests/ref/enum-syntax-edge-cases.png
new file mode 100644
index 00000000..496dc8e3
--- /dev/null
+++ b/tests/ref/enum-syntax-edge-cases.png
Binary files differ
diff --git a/tests/ref/escape.png b/tests/ref/escape.png
new file mode 100644
index 00000000..0b49606c
--- /dev/null
+++ b/tests/ref/escape.png
Binary files differ
diff --git a/tests/ref/eval-in-show-rule.png b/tests/ref/eval-in-show-rule.png
new file mode 100644
index 00000000..91a03868
--- /dev/null
+++ b/tests/ref/eval-in-show-rule.png
Binary files differ
diff --git a/tests/ref/eval-mode.png b/tests/ref/eval-mode.png
new file mode 100644
index 00000000..94357ff4
--- /dev/null
+++ b/tests/ref/eval-mode.png
Binary files differ
diff --git a/tests/ref/eval-path-resolve-in-show-rule.png b/tests/ref/eval-path-resolve-in-show-rule.png
new file mode 100644
index 00000000..cf5c183a
--- /dev/null
+++ b/tests/ref/eval-path-resolve-in-show-rule.png
Binary files differ
diff --git a/tests/ref/eval-path-resolve.png b/tests/ref/eval-path-resolve.png
new file mode 100644
index 00000000..cf5c183a
--- /dev/null
+++ b/tests/ref/eval-path-resolve.png
Binary files differ
diff --git a/tests/ref/field-function.png b/tests/ref/field-function.png
new file mode 100644
index 00000000..261fb395
--- /dev/null
+++ b/tests/ref/field-function.png
Binary files differ
diff --git a/tests/ref/figure-and-caption-show.png b/tests/ref/figure-and-caption-show.png
new file mode 100644
index 00000000..daf8f2b6
--- /dev/null
+++ b/tests/ref/figure-and-caption-show.png
Binary files differ
diff --git a/tests/ref/figure-basic.png b/tests/ref/figure-basic.png
new file mode 100644
index 00000000..22a841db
--- /dev/null
+++ b/tests/ref/figure-basic.png
Binary files differ
diff --git a/tests/ref/figure-breakable.png b/tests/ref/figure-breakable.png
new file mode 100644
index 00000000..40cb3ec5
--- /dev/null
+++ b/tests/ref/figure-breakable.png
Binary files differ
diff --git a/tests/ref/figure-caption-separator.png b/tests/ref/figure-caption-separator.png
new file mode 100644
index 00000000..e645f01f
--- /dev/null
+++ b/tests/ref/figure-caption-separator.png
Binary files differ
diff --git a/tests/ref/figure-caption-show.png b/tests/ref/figure-caption-show.png
new file mode 100644
index 00000000..4ed6443a
--- /dev/null
+++ b/tests/ref/figure-caption-show.png
Binary files differ
diff --git a/tests/ref/figure-caption-where-selector.png b/tests/ref/figure-caption-where-selector.png
new file mode 100644
index 00000000..08eb46f6
--- /dev/null
+++ b/tests/ref/figure-caption-where-selector.png
Binary files differ
diff --git a/tests/ref/figure-localization-fr.png b/tests/ref/figure-localization-fr.png
new file mode 100644
index 00000000..665b3552
--- /dev/null
+++ b/tests/ref/figure-localization-fr.png
Binary files differ
diff --git a/tests/ref/figure-localization-gr.png b/tests/ref/figure-localization-gr.png
new file mode 100644
index 00000000..46b52b05
--- /dev/null
+++ b/tests/ref/figure-localization-gr.png
Binary files differ
diff --git a/tests/ref/figure-localization-ru.png b/tests/ref/figure-localization-ru.png
new file mode 100644
index 00000000..102df597
--- /dev/null
+++ b/tests/ref/figure-localization-ru.png
Binary files differ
diff --git a/tests/ref/figure-localization-zh.png b/tests/ref/figure-localization-zh.png
new file mode 100644
index 00000000..f7625b1b
--- /dev/null
+++ b/tests/ref/figure-localization-zh.png
Binary files differ
diff --git a/tests/ref/figure-table.png b/tests/ref/figure-table.png
new file mode 100644
index 00000000..9a09f659
--- /dev/null
+++ b/tests/ref/figure-table.png
Binary files differ
diff --git a/tests/ref/figure-theorem.png b/tests/ref/figure-theorem.png
new file mode 100644
index 00000000..10d6eeac
--- /dev/null
+++ b/tests/ref/figure-theorem.png
Binary files differ
diff --git a/tests/ref/float-display.png b/tests/ref/float-display.png
new file mode 100644
index 00000000..6c33b372
--- /dev/null
+++ b/tests/ref/float-display.png
Binary files differ
diff --git a/tests/ref/float-repr.png b/tests/ref/float-repr.png
new file mode 100644
index 00000000..8b510969
--- /dev/null
+++ b/tests/ref/float-repr.png
Binary files differ
diff --git a/tests/ref/flow-first-region-counter-update-and-placed.png b/tests/ref/flow-first-region-counter-update-and-placed.png
new file mode 100644
index 00000000..21316719
--- /dev/null
+++ b/tests/ref/flow-first-region-counter-update-and-placed.png
Binary files differ
diff --git a/tests/ref/flow-first-region-counter-update-placed-and-line.png b/tests/ref/flow-first-region-counter-update-placed-and-line.png
new file mode 100644
index 00000000..95ca518e
--- /dev/null
+++ b/tests/ref/flow-first-region-counter-update-placed-and-line.png
Binary files differ
diff --git a/tests/ref/flow-first-region-counter-update.png b/tests/ref/flow-first-region-counter-update.png
new file mode 100644
index 00000000..8e883335
--- /dev/null
+++ b/tests/ref/flow-first-region-counter-update.png
Binary files differ
diff --git a/tests/ref/flow-first-region-no-item.png b/tests/ref/flow-first-region-no-item.png
new file mode 100644
index 00000000..e888898c
--- /dev/null
+++ b/tests/ref/flow-first-region-no-item.png
Binary files differ
diff --git a/tests/ref/flow-first-region-placed.png b/tests/ref/flow-first-region-placed.png
new file mode 100644
index 00000000..cae4aa32
--- /dev/null
+++ b/tests/ref/flow-first-region-placed.png
Binary files differ
diff --git a/tests/ref/flow-first-region-zero-sized-item.png b/tests/ref/flow-first-region-zero-sized-item.png
new file mode 100644
index 00000000..2e75fcfe
--- /dev/null
+++ b/tests/ref/flow-first-region-zero-sized-item.png
Binary files differ
diff --git a/tests/ref/flow-fr.png b/tests/ref/flow-fr.png
new file mode 100644
index 00000000..b09a9604
--- /dev/null
+++ b/tests/ref/flow-fr.png
Binary files differ
diff --git a/tests/ref/flow-heading-no-orphan.png b/tests/ref/flow-heading-no-orphan.png
new file mode 100644
index 00000000..87789ea1
--- /dev/null
+++ b/tests/ref/flow-heading-no-orphan.png
Binary files differ
diff --git a/tests/ref/flow-par-no-orphan-and-widow-lines.png b/tests/ref/flow-par-no-orphan-and-widow-lines.png
new file mode 100644
index 00000000..cace5d44
--- /dev/null
+++ b/tests/ref/flow-par-no-orphan-and-widow-lines.png
Binary files differ
diff --git a/tests/ref/fold-vec-order-meta.png b/tests/ref/fold-vec-order-meta.png
new file mode 100644
index 00000000..36e3cd51
--- /dev/null
+++ b/tests/ref/fold-vec-order-meta.png
Binary files differ
diff --git a/tests/ref/fold-vec-order-text-decos.png b/tests/ref/fold-vec-order-text-decos.png
new file mode 100644
index 00000000..62c9e1af
--- /dev/null
+++ b/tests/ref/fold-vec-order-text-decos.png
Binary files differ
diff --git a/tests/ref/fold-vec-order-text-features.png b/tests/ref/fold-vec-order-text-features.png
new file mode 100644
index 00000000..f2ff6f25
--- /dev/null
+++ b/tests/ref/fold-vec-order-text-features.png
Binary files differ
diff --git a/tests/ref/footnote-basic.png b/tests/ref/footnote-basic.png
new file mode 100644
index 00000000..3562438b
--- /dev/null
+++ b/tests/ref/footnote-basic.png
Binary files differ
diff --git a/tests/ref/footnote-break-across-pages.png b/tests/ref/footnote-break-across-pages.png
new file mode 100644
index 00000000..8ec55418
--- /dev/null
+++ b/tests/ref/footnote-break-across-pages.png
Binary files differ
diff --git a/tests/ref/footnote-duplicate.png b/tests/ref/footnote-duplicate.png
new file mode 100644
index 00000000..7c83b8de
--- /dev/null
+++ b/tests/ref/footnote-duplicate.png
Binary files differ
diff --git a/tests/ref/footnote-entry.png b/tests/ref/footnote-entry.png
new file mode 100644
index 00000000..e62315c4
--- /dev/null
+++ b/tests/ref/footnote-entry.png
Binary files differ
diff --git a/tests/ref/footnote-in-caption.png b/tests/ref/footnote-in-caption.png
new file mode 100644
index 00000000..8d548c59
--- /dev/null
+++ b/tests/ref/footnote-in-caption.png
Binary files differ
diff --git a/tests/ref/footnote-in-columns.png b/tests/ref/footnote-in-columns.png
new file mode 100644
index 00000000..e16b4ebc
--- /dev/null
+++ b/tests/ref/footnote-in-columns.png
Binary files differ
diff --git a/tests/ref/footnote-in-table.png b/tests/ref/footnote-in-table.png
new file mode 100644
index 00000000..0fd0acc7
--- /dev/null
+++ b/tests/ref/footnote-in-table.png
Binary files differ
diff --git a/tests/ref/footnote-invariant.png b/tests/ref/footnote-invariant.png
new file mode 100644
index 00000000..c49c268d
--- /dev/null
+++ b/tests/ref/footnote-invariant.png
Binary files differ
diff --git a/tests/ref/footnote-nested-same-frame.png b/tests/ref/footnote-nested-same-frame.png
new file mode 100644
index 00000000..b22276d5
--- /dev/null
+++ b/tests/ref/footnote-nested-same-frame.png
Binary files differ
diff --git a/tests/ref/footnote-nested.png b/tests/ref/footnote-nested.png
new file mode 100644
index 00000000..fecf2e8d
--- /dev/null
+++ b/tests/ref/footnote-nested.png
Binary files differ
diff --git a/tests/ref/footnote-ref-call.png b/tests/ref/footnote-ref-call.png
new file mode 100644
index 00000000..3c795302
--- /dev/null
+++ b/tests/ref/footnote-ref-call.png
Binary files differ
diff --git a/tests/ref/footnote-ref-forward.png b/tests/ref/footnote-ref-forward.png
new file mode 100644
index 00000000..e67671be
--- /dev/null
+++ b/tests/ref/footnote-ref-forward.png
Binary files differ
diff --git a/tests/ref/footnote-ref-in-footnote.png b/tests/ref/footnote-ref-in-footnote.png
new file mode 100644
index 00000000..4718a088
--- /dev/null
+++ b/tests/ref/footnote-ref-in-footnote.png
Binary files differ
diff --git a/tests/ref/footnote-ref-multiple.png b/tests/ref/footnote-ref-multiple.png
new file mode 100644
index 00000000..fc6f11cf
--- /dev/null
+++ b/tests/ref/footnote-ref-multiple.png
Binary files differ
diff --git a/tests/ref/footnote-ref.png b/tests/ref/footnote-ref.png
new file mode 100644
index 00000000..517d997a
--- /dev/null
+++ b/tests/ref/footnote-ref.png
Binary files differ
diff --git a/tests/ref/footnote-space-collapsing.png b/tests/ref/footnote-space-collapsing.png
new file mode 100644
index 00000000..d7d02704
--- /dev/null
+++ b/tests/ref/footnote-space-collapsing.png
Binary files differ
diff --git a/tests/ref/footnote-styling.png b/tests/ref/footnote-styling.png
new file mode 100644
index 00000000..fd7684af
--- /dev/null
+++ b/tests/ref/footnote-styling.png
Binary files differ
diff --git a/tests/ref/for-loop-basic.png b/tests/ref/for-loop-basic.png
new file mode 100644
index 00000000..42d611ef
--- /dev/null
+++ b/tests/ref/for-loop-basic.png
Binary files differ
diff --git a/tests/ref/gradient-conic-angled.png b/tests/ref/gradient-conic-angled.png
new file mode 100644
index 00000000..163366e6
--- /dev/null
+++ b/tests/ref/gradient-conic-angled.png
Binary files differ
diff --git a/tests/ref/gradient-conic-center-shifted-1.png b/tests/ref/gradient-conic-center-shifted-1.png
new file mode 100644
index 00000000..5964b124
--- /dev/null
+++ b/tests/ref/gradient-conic-center-shifted-1.png
Binary files differ
diff --git a/tests/ref/gradient-conic-center-shifted-2.png b/tests/ref/gradient-conic-center-shifted-2.png
new file mode 100644
index 00000000..53e5da98
--- /dev/null
+++ b/tests/ref/gradient-conic-center-shifted-2.png
Binary files differ
diff --git a/tests/ref/gradient-conic-hsl.png b/tests/ref/gradient-conic-hsl.png
new file mode 100644
index 00000000..321a3b07
--- /dev/null
+++ b/tests/ref/gradient-conic-hsl.png
Binary files differ
diff --git a/tests/ref/gradient-conic-hsv.png b/tests/ref/gradient-conic-hsv.png
new file mode 100644
index 00000000..648e1fb5
--- /dev/null
+++ b/tests/ref/gradient-conic-hsv.png
Binary files differ
diff --git a/tests/ref/gradient-conic-oklab.png b/tests/ref/gradient-conic-oklab.png
new file mode 100644
index 00000000..e567eacc
--- /dev/null
+++ b/tests/ref/gradient-conic-oklab.png
Binary files differ
diff --git a/tests/ref/gradient-conic-oklch.png b/tests/ref/gradient-conic-oklch.png
new file mode 100644
index 00000000..f712defa
--- /dev/null
+++ b/tests/ref/gradient-conic-oklch.png
Binary files differ
diff --git a/tests/ref/gradient-conic-relative-parent.png b/tests/ref/gradient-conic-relative-parent.png
new file mode 100644
index 00000000..1685ca44
--- /dev/null
+++ b/tests/ref/gradient-conic-relative-parent.png
Binary files differ
diff --git a/tests/ref/gradient-conic-relative-self.png b/tests/ref/gradient-conic-relative-self.png
new file mode 100644
index 00000000..108fe43a
--- /dev/null
+++ b/tests/ref/gradient-conic-relative-self.png
Binary files differ
diff --git a/tests/ref/gradient-conic-stroke.png b/tests/ref/gradient-conic-stroke.png
new file mode 100644
index 00000000..ae631fd4
--- /dev/null
+++ b/tests/ref/gradient-conic-stroke.png
Binary files differ
diff --git a/tests/ref/gradient-conic-text.png b/tests/ref/gradient-conic-text.png
new file mode 100644
index 00000000..1abef3cb
--- /dev/null
+++ b/tests/ref/gradient-conic-text.png
Binary files differ
diff --git a/tests/ref/gradient-conic.png b/tests/ref/gradient-conic.png
new file mode 100644
index 00000000..0f5f5bad
--- /dev/null
+++ b/tests/ref/gradient-conic.png
Binary files differ
diff --git a/tests/ref/gradient-fill-and-stroke.png b/tests/ref/gradient-fill-and-stroke.png
new file mode 100644
index 00000000..78563517
--- /dev/null
+++ b/tests/ref/gradient-fill-and-stroke.png
Binary files differ
diff --git a/tests/ref/gradient-linear-angled.png b/tests/ref/gradient-linear-angled.png
new file mode 100644
index 00000000..b195b128
--- /dev/null
+++ b/tests/ref/gradient-linear-angled.png
Binary files differ
diff --git a/tests/ref/gradient-linear-hsl.png b/tests/ref/gradient-linear-hsl.png
new file mode 100644
index 00000000..7bfe958b
--- /dev/null
+++ b/tests/ref/gradient-linear-hsl.png
Binary files differ
diff --git a/tests/ref/gradient-linear-hsv.png b/tests/ref/gradient-linear-hsv.png
new file mode 100644
index 00000000..56b446f2
--- /dev/null
+++ b/tests/ref/gradient-linear-hsv.png
Binary files differ
diff --git a/tests/ref/gradient-linear-line.png b/tests/ref/gradient-linear-line.png
new file mode 100644
index 00000000..d32aba89
--- /dev/null
+++ b/tests/ref/gradient-linear-line.png
Binary files differ
diff --git a/tests/ref/gradient-linear-oklab.png b/tests/ref/gradient-linear-oklab.png
new file mode 100644
index 00000000..6f963c77
--- /dev/null
+++ b/tests/ref/gradient-linear-oklab.png
Binary files differ
diff --git a/tests/ref/gradient-linear-oklch.png b/tests/ref/gradient-linear-oklch.png
new file mode 100644
index 00000000..394d0935
--- /dev/null
+++ b/tests/ref/gradient-linear-oklch.png
Binary files differ
diff --git a/tests/ref/gradient-linear-relative-parent.png b/tests/ref/gradient-linear-relative-parent.png
new file mode 100644
index 00000000..2ad1286e
--- /dev/null
+++ b/tests/ref/gradient-linear-relative-parent.png
Binary files differ
diff --git a/tests/ref/gradient-linear-relative-self.png b/tests/ref/gradient-linear-relative-self.png
new file mode 100644
index 00000000..d573a892
--- /dev/null
+++ b/tests/ref/gradient-linear-relative-self.png
Binary files differ
diff --git a/tests/ref/gradient-linear-repeat-and-mirror-1.png b/tests/ref/gradient-linear-repeat-and-mirror-1.png
new file mode 100644
index 00000000..9640d5e2
--- /dev/null
+++ b/tests/ref/gradient-linear-repeat-and-mirror-1.png
Binary files differ
diff --git a/tests/ref/gradient-linear-repeat-and-mirror-2.png b/tests/ref/gradient-linear-repeat-and-mirror-2.png
new file mode 100644
index 00000000..98cf2543
--- /dev/null
+++ b/tests/ref/gradient-linear-repeat-and-mirror-2.png
Binary files differ
diff --git a/tests/ref/gradient-linear-repeat-and-mirror-3.png b/tests/ref/gradient-linear-repeat-and-mirror-3.png
new file mode 100644
index 00000000..641e54c9
--- /dev/null
+++ b/tests/ref/gradient-linear-repeat-and-mirror-3.png
Binary files differ
diff --git a/tests/ref/gradient-linear-sharp-and-repeat.png b/tests/ref/gradient-linear-sharp-and-repeat.png
new file mode 100644
index 00000000..e46af7a0
--- /dev/null
+++ b/tests/ref/gradient-linear-sharp-and-repeat.png
Binary files differ
diff --git a/tests/ref/gradient-linear-sharp-and-smooth.png b/tests/ref/gradient-linear-sharp-and-smooth.png
new file mode 100644
index 00000000..5bd74d24
--- /dev/null
+++ b/tests/ref/gradient-linear-sharp-and-smooth.png
Binary files differ
diff --git a/tests/ref/gradient-linear-sharp-repeat-and-mirror.png b/tests/ref/gradient-linear-sharp-repeat-and-mirror.png
new file mode 100644
index 00000000..5b4b9817
--- /dev/null
+++ b/tests/ref/gradient-linear-sharp-repeat-and-mirror.png
Binary files differ
diff --git a/tests/ref/gradient-linear-sharp.png b/tests/ref/gradient-linear-sharp.png
new file mode 100644
index 00000000..4d63884f
--- /dev/null
+++ b/tests/ref/gradient-linear-sharp.png
Binary files differ
diff --git a/tests/ref/gradient-linear-stroke.png b/tests/ref/gradient-linear-stroke.png
new file mode 100644
index 00000000..490ffec2
--- /dev/null
+++ b/tests/ref/gradient-linear-stroke.png
Binary files differ
diff --git a/tests/ref/gradient-math-cancel.png b/tests/ref/gradient-math-cancel.png
new file mode 100644
index 00000000..0769d6d3
--- /dev/null
+++ b/tests/ref/gradient-math-cancel.png
Binary files differ
diff --git a/tests/ref/gradient-math-conic.png b/tests/ref/gradient-math-conic.png
new file mode 100644
index 00000000..88ff7a85
--- /dev/null
+++ b/tests/ref/gradient-math-conic.png
Binary files differ
diff --git a/tests/ref/gradient-math-dir.png b/tests/ref/gradient-math-dir.png
new file mode 100644
index 00000000..5ed19182
--- /dev/null
+++ b/tests/ref/gradient-math-dir.png
Binary files differ
diff --git a/tests/ref/gradient-math-frac.png b/tests/ref/gradient-math-frac.png
new file mode 100644
index 00000000..1316dc47
--- /dev/null
+++ b/tests/ref/gradient-math-frac.png
Binary files differ
diff --git a/tests/ref/gradient-math-mat.png b/tests/ref/gradient-math-mat.png
new file mode 100644
index 00000000..aa3332b9
--- /dev/null
+++ b/tests/ref/gradient-math-mat.png
Binary files differ
diff --git a/tests/ref/gradient-math-misc.png b/tests/ref/gradient-math-misc.png
new file mode 100644
index 00000000..b8fbdd74
--- /dev/null
+++ b/tests/ref/gradient-math-misc.png
Binary files differ
diff --git a/tests/ref/gradient-math-radial.png b/tests/ref/gradient-math-radial.png
new file mode 100644
index 00000000..c9b966b2
--- /dev/null
+++ b/tests/ref/gradient-math-radial.png
Binary files differ
diff --git a/tests/ref/gradient-math-root.png b/tests/ref/gradient-math-root.png
new file mode 100644
index 00000000..4c2e4272
--- /dev/null
+++ b/tests/ref/gradient-math-root.png
Binary files differ
diff --git a/tests/ref/gradient-math-underover.png b/tests/ref/gradient-math-underover.png
new file mode 100644
index 00000000..89098051
--- /dev/null
+++ b/tests/ref/gradient-math-underover.png
Binary files differ
diff --git a/tests/ref/gradient-presets.png b/tests/ref/gradient-presets.png
new file mode 100644
index 00000000..0c7fabdd
--- /dev/null
+++ b/tests/ref/gradient-presets.png
Binary files differ
diff --git a/tests/ref/gradient-radial-center.png b/tests/ref/gradient-radial-center.png
new file mode 100644
index 00000000..e89e1f30
--- /dev/null
+++ b/tests/ref/gradient-radial-center.png
Binary files differ
diff --git a/tests/ref/gradient-radial-focal-center-and-radius.png b/tests/ref/gradient-radial-focal-center-and-radius.png
new file mode 100644
index 00000000..4bc8a5d6
--- /dev/null
+++ b/tests/ref/gradient-radial-focal-center-and-radius.png
Binary files differ
diff --git a/tests/ref/gradient-radial-hsl.png b/tests/ref/gradient-radial-hsl.png
new file mode 100644
index 00000000..4a2ded18
--- /dev/null
+++ b/tests/ref/gradient-radial-hsl.png
Binary files differ
diff --git a/tests/ref/gradient-radial-radius.png b/tests/ref/gradient-radial-radius.png
new file mode 100644
index 00000000..1037e63f
--- /dev/null
+++ b/tests/ref/gradient-radial-radius.png
Binary files differ
diff --git a/tests/ref/gradient-radial-relative-parent.png b/tests/ref/gradient-radial-relative-parent.png
new file mode 100644
index 00000000..f8addbe0
--- /dev/null
+++ b/tests/ref/gradient-radial-relative-parent.png
Binary files differ
diff --git a/tests/ref/gradient-radial-relative-self.png b/tests/ref/gradient-radial-relative-self.png
new file mode 100644
index 00000000..f5fc6836
--- /dev/null
+++ b/tests/ref/gradient-radial-relative-self.png
Binary files differ
diff --git a/tests/ref/gradient-radial-text.png b/tests/ref/gradient-radial-text.png
new file mode 100644
index 00000000..6da09878
--- /dev/null
+++ b/tests/ref/gradient-radial-text.png
Binary files differ
diff --git a/tests/ref/gradient-repr.png b/tests/ref/gradient-repr.png
new file mode 100644
index 00000000..04908e59
--- /dev/null
+++ b/tests/ref/gradient-repr.png
Binary files differ
diff --git a/tests/ref/gradient-text-decoration.png b/tests/ref/gradient-text-decoration.png
new file mode 100644
index 00000000..d1713c99
--- /dev/null
+++ b/tests/ref/gradient-text-decoration.png
Binary files differ
diff --git a/tests/ref/gradient-text-dir.png b/tests/ref/gradient-text-dir.png
new file mode 100644
index 00000000..eab56d66
--- /dev/null
+++ b/tests/ref/gradient-text-dir.png
Binary files differ
diff --git a/tests/ref/gradient-text-global.png b/tests/ref/gradient-text-global.png
new file mode 100644
index 00000000..7892fbb2
--- /dev/null
+++ b/tests/ref/gradient-text-global.png
Binary files differ
diff --git a/tests/ref/gradient-text-in-container.png b/tests/ref/gradient-text-in-container.png
new file mode 100644
index 00000000..9122a556
--- /dev/null
+++ b/tests/ref/gradient-text-in-container.png
Binary files differ
diff --git a/tests/ref/gradient-text-rotate.png b/tests/ref/gradient-text-rotate.png
new file mode 100644
index 00000000..a32cacf8
--- /dev/null
+++ b/tests/ref/gradient-text-rotate.png
Binary files differ
diff --git a/tests/ref/gradient-transformed.png b/tests/ref/gradient-transformed.png
new file mode 100644
index 00000000..2ad1286e
--- /dev/null
+++ b/tests/ref/gradient-transformed.png
Binary files differ
diff --git a/tests/ref/grid-align.png b/tests/ref/grid-align.png
new file mode 100644
index 00000000..f85abf69
--- /dev/null
+++ b/tests/ref/grid-align.png
Binary files differ
diff --git a/tests/ref/grid-auto-shrink.png b/tests/ref/grid-auto-shrink.png
new file mode 100644
index 00000000..27813e26
--- /dev/null
+++ b/tests/ref/grid-auto-shrink.png
Binary files differ
diff --git a/tests/ref/grid-breaking-expand-vertically.png b/tests/ref/grid-breaking-expand-vertically.png
new file mode 100644
index 00000000..14434d7c
--- /dev/null
+++ b/tests/ref/grid-breaking-expand-vertically.png
Binary files differ
diff --git a/tests/ref/grid-calendar.png b/tests/ref/grid-calendar.png
new file mode 100644
index 00000000..0609b84f
--- /dev/null
+++ b/tests/ref/grid-calendar.png
Binary files differ
diff --git a/tests/ref/grid-cell-align-override.png b/tests/ref/grid-cell-align-override.png
new file mode 100644
index 00000000..8ffde97f
--- /dev/null
+++ b/tests/ref/grid-cell-align-override.png
Binary files differ
diff --git a/tests/ref/grid-cell-breaking.png b/tests/ref/grid-cell-breaking.png
new file mode 100644
index 00000000..c91a3993
--- /dev/null
+++ b/tests/ref/grid-cell-breaking.png
Binary files differ
diff --git a/tests/ref/grid-cell-folding.png b/tests/ref/grid-cell-folding.png
new file mode 100644
index 00000000..ce1108c6
--- /dev/null
+++ b/tests/ref/grid-cell-folding.png
Binary files differ
diff --git a/tests/ref/grid-cell-override-in-header-and-footer-with-gutter.png b/tests/ref/grid-cell-override-in-header-and-footer-with-gutter.png
new file mode 100644
index 00000000..a475bf90
--- /dev/null
+++ b/tests/ref/grid-cell-override-in-header-and-footer-with-gutter.png
Binary files differ
diff --git a/tests/ref/grid-cell-override-in-header-and-footer.png b/tests/ref/grid-cell-override-in-header-and-footer.png
new file mode 100644
index 00000000..4d31e379
--- /dev/null
+++ b/tests/ref/grid-cell-override-in-header-and-footer.png
Binary files differ
diff --git a/tests/ref/grid-cell-override.png b/tests/ref/grid-cell-override.png
new file mode 100644
index 00000000..d6f37d63
--- /dev/null
+++ b/tests/ref/grid-cell-override.png
Binary files differ
diff --git a/tests/ref/grid-cell-position-automatic-skip-manual.png b/tests/ref/grid-cell-position-automatic-skip-manual.png
new file mode 100644
index 00000000..ec615c97
--- /dev/null
+++ b/tests/ref/grid-cell-position-automatic-skip-manual.png
Binary files differ
diff --git a/tests/ref/grid-cell-position-extra-rows.png b/tests/ref/grid-cell-position-extra-rows.png
new file mode 100644
index 00000000..4d73c3f7
--- /dev/null
+++ b/tests/ref/grid-cell-position-extra-rows.png
Binary files differ
diff --git a/tests/ref/grid-cell-position-out-of-order.png b/tests/ref/grid-cell-position-out-of-order.png
new file mode 100644
index 00000000..d6bdad46
--- /dev/null
+++ b/tests/ref/grid-cell-position-out-of-order.png
Binary files differ
diff --git a/tests/ref/grid-cell-position-partial.png b/tests/ref/grid-cell-position-partial.png
new file mode 100644
index 00000000..3012c5b5
--- /dev/null
+++ b/tests/ref/grid-cell-position-partial.png
Binary files differ
diff --git a/tests/ref/grid-cell-set.png b/tests/ref/grid-cell-set.png
new file mode 100644
index 00000000..5dc3fdf6
--- /dev/null
+++ b/tests/ref/grid-cell-set.png
Binary files differ
diff --git a/tests/ref/grid-cell-show-and-override.png b/tests/ref/grid-cell-show-and-override.png
new file mode 100644
index 00000000..6af55596
--- /dev/null
+++ b/tests/ref/grid-cell-show-and-override.png
Binary files differ
diff --git a/tests/ref/grid-cell-show-based-on-position.png b/tests/ref/grid-cell-show-based-on-position.png
new file mode 100644
index 00000000..26ad6284
--- /dev/null
+++ b/tests/ref/grid-cell-show-based-on-position.png
Binary files differ
diff --git a/tests/ref/grid-cell-show-emph.png b/tests/ref/grid-cell-show-emph.png
new file mode 100644
index 00000000..bfc03d6d
--- /dev/null
+++ b/tests/ref/grid-cell-show-emph.png
Binary files differ
diff --git a/tests/ref/grid-cell-show-x-y.png b/tests/ref/grid-cell-show-x-y.png
new file mode 100644
index 00000000..0fb4c2c5
--- /dev/null
+++ b/tests/ref/grid-cell-show-x-y.png
Binary files differ
diff --git a/tests/ref/grid-cell-show.png b/tests/ref/grid-cell-show.png
new file mode 100644
index 00000000..9ac6d269
--- /dev/null
+++ b/tests/ref/grid-cell-show.png
Binary files differ
diff --git a/tests/ref/grid-cell-various-overrides.png b/tests/ref/grid-cell-various-overrides.png
new file mode 100644
index 00000000..74490e84
--- /dev/null
+++ b/tests/ref/grid-cell-various-overrides.png
Binary files differ
diff --git a/tests/ref/grid-colspan-gutter.png b/tests/ref/grid-colspan-gutter.png
new file mode 100644
index 00000000..2ba9c217
--- /dev/null
+++ b/tests/ref/grid-colspan-gutter.png
Binary files differ
diff --git a/tests/ref/grid-colspan-multiple-regions.png b/tests/ref/grid-colspan-multiple-regions.png
new file mode 100644
index 00000000..22811aca
--- /dev/null
+++ b/tests/ref/grid-colspan-multiple-regions.png
Binary files differ
diff --git a/tests/ref/grid-colspan-over-all-fr-columns-page-width-auto.png b/tests/ref/grid-colspan-over-all-fr-columns-page-width-auto.png
new file mode 100644
index 00000000..b5cf6cac
--- /dev/null
+++ b/tests/ref/grid-colspan-over-all-fr-columns-page-width-auto.png
Binary files differ
diff --git a/tests/ref/grid-colspan-over-all-fr-columns.png b/tests/ref/grid-colspan-over-all-fr-columns.png
new file mode 100644
index 00000000..c152f3cc
--- /dev/null
+++ b/tests/ref/grid-colspan-over-all-fr-columns.png
Binary files differ
diff --git a/tests/ref/grid-colspan-over-some-fr-columns.png b/tests/ref/grid-colspan-over-some-fr-columns.png
new file mode 100644
index 00000000..5d8157c2
--- /dev/null
+++ b/tests/ref/grid-colspan-over-some-fr-columns.png
Binary files differ
diff --git a/tests/ref/grid-colspan-thick-stroke.png b/tests/ref/grid-colspan-thick-stroke.png
new file mode 100644
index 00000000..7348551e
--- /dev/null
+++ b/tests/ref/grid-colspan-thick-stroke.png
Binary files differ
diff --git a/tests/ref/grid-colspan.png b/tests/ref/grid-colspan.png
new file mode 100644
index 00000000..419d23b2
--- /dev/null
+++ b/tests/ref/grid-colspan.png
Binary files differ
diff --git a/tests/ref/grid-column-sizing-auto-base.png b/tests/ref/grid-column-sizing-auto-base.png
new file mode 100644
index 00000000..75664027
--- /dev/null
+++ b/tests/ref/grid-column-sizing-auto-base.png
Binary files differ
diff --git a/tests/ref/grid-column-sizing-fr-base.png b/tests/ref/grid-column-sizing-fr-base.png
new file mode 100644
index 00000000..d4a44be7
--- /dev/null
+++ b/tests/ref/grid-column-sizing-fr-base.png
Binary files differ
diff --git a/tests/ref/grid-column-sizing-mixed-base.png b/tests/ref/grid-column-sizing-mixed-base.png
new file mode 100644
index 00000000..dc92564d
--- /dev/null
+++ b/tests/ref/grid-column-sizing-mixed-base.png
Binary files differ
diff --git a/tests/ref/grid-columns-sizings-rect.png b/tests/ref/grid-columns-sizings-rect.png
new file mode 100644
index 00000000..9381103d
--- /dev/null
+++ b/tests/ref/grid-columns-sizings-rect.png
Binary files differ
diff --git a/tests/ref/grid-complete-rows.png b/tests/ref/grid-complete-rows.png
new file mode 100644
index 00000000..192aa911
--- /dev/null
+++ b/tests/ref/grid-complete-rows.png
Binary files differ
diff --git a/tests/ref/grid-consecutive-rows-breaking.png b/tests/ref/grid-consecutive-rows-breaking.png
new file mode 100644
index 00000000..6000271d
--- /dev/null
+++ b/tests/ref/grid-consecutive-rows-breaking.png
Binary files differ
diff --git a/tests/ref/grid-exam.png b/tests/ref/grid-exam.png
new file mode 100644
index 00000000..97edd52e
--- /dev/null
+++ b/tests/ref/grid-exam.png
Binary files differ
diff --git a/tests/ref/grid-fill-func.png b/tests/ref/grid-fill-func.png
new file mode 100644
index 00000000..388a52df
--- /dev/null
+++ b/tests/ref/grid-fill-func.png
Binary files differ
diff --git a/tests/ref/grid-finance.png b/tests/ref/grid-finance.png
new file mode 100644
index 00000000..2ea48594
--- /dev/null
+++ b/tests/ref/grid-finance.png
Binary files differ
diff --git a/tests/ref/grid-footer-bare-1.png b/tests/ref/grid-footer-bare-1.png
new file mode 100644
index 00000000..e8c8b21a
--- /dev/null
+++ b/tests/ref/grid-footer-bare-1.png
Binary files differ
diff --git a/tests/ref/grid-footer-bare-2.png b/tests/ref/grid-footer-bare-2.png
new file mode 100644
index 00000000..bad6a3dd
--- /dev/null
+++ b/tests/ref/grid-footer-bare-2.png
Binary files differ
diff --git a/tests/ref/grid-footer-below-rowspans.png b/tests/ref/grid-footer-below-rowspans.png
new file mode 100644
index 00000000..5c3a2b26
--- /dev/null
+++ b/tests/ref/grid-footer-below-rowspans.png
Binary files differ
diff --git a/tests/ref/grid-footer-cell-with-y.png b/tests/ref/grid-footer-cell-with-y.png
new file mode 100644
index 00000000..3237ea69
--- /dev/null
+++ b/tests/ref/grid-footer-cell-with-y.png
Binary files differ
diff --git a/tests/ref/grid-footer-expand.png b/tests/ref/grid-footer-expand.png
new file mode 100644
index 00000000..118765d5
--- /dev/null
+++ b/tests/ref/grid-footer-expand.png
Binary files differ
diff --git a/tests/ref/grid-footer-gutter-and-no-repeat.png b/tests/ref/grid-footer-gutter-and-no-repeat.png
new file mode 100644
index 00000000..ea36ae03
--- /dev/null
+++ b/tests/ref/grid-footer-gutter-and-no-repeat.png
Binary files differ
diff --git a/tests/ref/grid-footer-hline-and-vline-1.png b/tests/ref/grid-footer-hline-and-vline-1.png
new file mode 100644
index 00000000..a4d9a681
--- /dev/null
+++ b/tests/ref/grid-footer-hline-and-vline-1.png
Binary files differ
diff --git a/tests/ref/grid-footer-hline-and-vline-2.png b/tests/ref/grid-footer-hline-and-vline-2.png
new file mode 100644
index 00000000..0ad2bacc
--- /dev/null
+++ b/tests/ref/grid-footer-hline-and-vline-2.png
Binary files differ
diff --git a/tests/ref/grid-footer-relative-row-sizes.png b/tests/ref/grid-footer-relative-row-sizes.png
new file mode 100644
index 00000000..b533f13f
--- /dev/null
+++ b/tests/ref/grid-footer-relative-row-sizes.png
Binary files differ
diff --git a/tests/ref/grid-footer-rowspan.png b/tests/ref/grid-footer-rowspan.png
new file mode 100644
index 00000000..369e4d07
--- /dev/null
+++ b/tests/ref/grid-footer-rowspan.png
Binary files differ
diff --git a/tests/ref/grid-footer-stroke-edge-cases.png b/tests/ref/grid-footer-stroke-edge-cases.png
new file mode 100644
index 00000000..c3db98e7
--- /dev/null
+++ b/tests/ref/grid-footer-stroke-edge-cases.png
Binary files differ
diff --git a/tests/ref/grid-footer-top-stroke.png b/tests/ref/grid-footer-top-stroke.png
new file mode 100644
index 00000000..ff9aa9f0
--- /dev/null
+++ b/tests/ref/grid-footer-top-stroke.png
Binary files differ
diff --git a/tests/ref/grid-footer.png b/tests/ref/grid-footer.png
new file mode 100644
index 00000000..196563c7
--- /dev/null
+++ b/tests/ref/grid-footer.png
Binary files differ
diff --git a/tests/ref/grid-funcs-gutter.png b/tests/ref/grid-funcs-gutter.png
new file mode 100644
index 00000000..ee6723ef
--- /dev/null
+++ b/tests/ref/grid-funcs-gutter.png
Binary files differ
diff --git a/tests/ref/grid-gutter-fr.png b/tests/ref/grid-gutter-fr.png
new file mode 100644
index 00000000..2fce6949
--- /dev/null
+++ b/tests/ref/grid-gutter-fr.png
Binary files differ
diff --git a/tests/ref/grid-header-and-footer-containing-rowspan.png b/tests/ref/grid-header-and-footer-containing-rowspan.png
new file mode 100644
index 00000000..705d72a4
--- /dev/null
+++ b/tests/ref/grid-header-and-footer-containing-rowspan.png
Binary files differ
diff --git a/tests/ref/grid-header-and-footer-empty.png b/tests/ref/grid-header-and-footer-empty.png
new file mode 100644
index 00000000..c4e7bb0e
--- /dev/null
+++ b/tests/ref/grid-header-and-footer-empty.png
Binary files differ
diff --git a/tests/ref/grid-header-and-footer-lack-of-space.png b/tests/ref/grid-header-and-footer-lack-of-space.png
new file mode 100644
index 00000000..78705776
--- /dev/null
+++ b/tests/ref/grid-header-and-footer-lack-of-space.png
Binary files differ
diff --git a/tests/ref/grid-header-and-footer-orphan-prevention.png b/tests/ref/grid-header-and-footer-orphan-prevention.png
new file mode 100644
index 00000000..8253b657
--- /dev/null
+++ b/tests/ref/grid-header-and-footer-orphan-prevention.png
Binary files differ
diff --git a/tests/ref/grid-header-and-rowspan-non-contiguous-1.png b/tests/ref/grid-header-and-rowspan-non-contiguous-1.png
new file mode 100644
index 00000000..d5088a12
--- /dev/null
+++ b/tests/ref/grid-header-and-rowspan-non-contiguous-1.png
Binary files differ
diff --git a/tests/ref/grid-header-and-rowspan-non-contiguous-2.png b/tests/ref/grid-header-and-rowspan-non-contiguous-2.png
new file mode 100644
index 00000000..4894d141
--- /dev/null
+++ b/tests/ref/grid-header-and-rowspan-non-contiguous-2.png
Binary files differ
diff --git a/tests/ref/grid-header-and-rowspan-non-contiguous-3.png b/tests/ref/grid-header-and-rowspan-non-contiguous-3.png
new file mode 100644
index 00000000..36e9a3c3
--- /dev/null
+++ b/tests/ref/grid-header-and-rowspan-non-contiguous-3.png
Binary files differ
diff --git a/tests/ref/grid-header-block-with-fixed-height.png b/tests/ref/grid-header-block-with-fixed-height.png
new file mode 100644
index 00000000..b7f2eedb
--- /dev/null
+++ b/tests/ref/grid-header-block-with-fixed-height.png
Binary files differ
diff --git a/tests/ref/grid-header-cell-with-y.png b/tests/ref/grid-header-cell-with-y.png
new file mode 100644
index 00000000..e54e35fa
--- /dev/null
+++ b/tests/ref/grid-header-cell-with-y.png
Binary files differ
diff --git a/tests/ref/grid-header-containing-rowspan.png b/tests/ref/grid-header-containing-rowspan.png
new file mode 100644
index 00000000..3cabff9e
--- /dev/null
+++ b/tests/ref/grid-header-containing-rowspan.png
Binary files differ
diff --git a/tests/ref/grid-header-empty.png b/tests/ref/grid-header-empty.png
new file mode 100644
index 00000000..20e4d92c
--- /dev/null
+++ b/tests/ref/grid-header-empty.png
Binary files differ
diff --git a/tests/ref/grid-header-expand.png b/tests/ref/grid-header-expand.png
new file mode 100644
index 00000000..46572441
--- /dev/null
+++ b/tests/ref/grid-header-expand.png
Binary files differ
diff --git a/tests/ref/grid-header-footer-and-rowspan-non-contiguous-1.png b/tests/ref/grid-header-footer-and-rowspan-non-contiguous-1.png
new file mode 100644
index 00000000..e7b153c8
--- /dev/null
+++ b/tests/ref/grid-header-footer-and-rowspan-non-contiguous-1.png
Binary files differ
diff --git a/tests/ref/grid-header-footer-and-rowspan-non-contiguous-2.png b/tests/ref/grid-header-footer-and-rowspan-non-contiguous-2.png
new file mode 100644
index 00000000..525475ac
--- /dev/null
+++ b/tests/ref/grid-header-footer-and-rowspan-non-contiguous-2.png
Binary files differ
diff --git a/tests/ref/grid-header-footer-block-with-fixed-height.png b/tests/ref/grid-header-footer-block-with-fixed-height.png
new file mode 100644
index 00000000..1f2e7c20
--- /dev/null
+++ b/tests/ref/grid-header-footer-block-with-fixed-height.png
Binary files differ
diff --git a/tests/ref/grid-header-hline-and-vline.png b/tests/ref/grid-header-hline-and-vline.png
new file mode 100644
index 00000000..a01fc00b
--- /dev/null
+++ b/tests/ref/grid-header-hline-and-vline.png
Binary files differ
diff --git a/tests/ref/grid-header-hline-bottom-manually.png b/tests/ref/grid-header-hline-bottom-manually.png
new file mode 100644
index 00000000..d944f7b5
--- /dev/null
+++ b/tests/ref/grid-header-hline-bottom-manually.png
Binary files differ
diff --git a/tests/ref/grid-header-hline-bottom.png b/tests/ref/grid-header-hline-bottom.png
new file mode 100644
index 00000000..f1361242
--- /dev/null
+++ b/tests/ref/grid-header-hline-bottom.png
Binary files differ
diff --git a/tests/ref/grid-header-lack-of-space.png b/tests/ref/grid-header-lack-of-space.png
new file mode 100644
index 00000000..4d2b483f
--- /dev/null
+++ b/tests/ref/grid-header-lack-of-space.png
Binary files differ
diff --git a/tests/ref/grid-header-last-child.png b/tests/ref/grid-header-last-child.png
new file mode 100644
index 00000000..4fa1ff7c
--- /dev/null
+++ b/tests/ref/grid-header-last-child.png
Binary files differ
diff --git a/tests/ref/grid-header-nested.png b/tests/ref/grid-header-nested.png
new file mode 100644
index 00000000..9078090f
--- /dev/null
+++ b/tests/ref/grid-header-nested.png
Binary files differ
diff --git a/tests/ref/grid-header-orphan-prevention.png b/tests/ref/grid-header-orphan-prevention.png
new file mode 100644
index 00000000..fa903e42
--- /dev/null
+++ b/tests/ref/grid-header-orphan-prevention.png
Binary files differ
diff --git a/tests/ref/grid-header-relative-row-sizes.png b/tests/ref/grid-header-relative-row-sizes.png
new file mode 100644
index 00000000..69ed1d1e
--- /dev/null
+++ b/tests/ref/grid-header-relative-row-sizes.png
Binary files differ
diff --git a/tests/ref/grid-header-rowspan-base.png b/tests/ref/grid-header-rowspan-base.png
new file mode 100644
index 00000000..1ab83591
--- /dev/null
+++ b/tests/ref/grid-header-rowspan-base.png
Binary files differ
diff --git a/tests/ref/grid-header-stroke-edge-cases.png b/tests/ref/grid-header-stroke-edge-cases.png
new file mode 100644
index 00000000..b86eb632
--- /dev/null
+++ b/tests/ref/grid-header-stroke-edge-cases.png
Binary files differ
diff --git a/tests/ref/grid-headers-gutter.png b/tests/ref/grid-headers-gutter.png
new file mode 100644
index 00000000..c2a48a66
--- /dev/null
+++ b/tests/ref/grid-headers-gutter.png
Binary files differ
diff --git a/tests/ref/grid-headers-no-repeat.png b/tests/ref/grid-headers-no-repeat.png
new file mode 100644
index 00000000..32d281a1
--- /dev/null
+++ b/tests/ref/grid-headers-no-repeat.png
Binary files differ
diff --git a/tests/ref/grid-headers.png b/tests/ref/grid-headers.png
new file mode 100644
index 00000000..13e88dbe
--- /dev/null
+++ b/tests/ref/grid-headers.png
Binary files differ
diff --git a/tests/ref/grid-inset-folding.png b/tests/ref/grid-inset-folding.png
new file mode 100644
index 00000000..7f994264
--- /dev/null
+++ b/tests/ref/grid-inset-folding.png
Binary files differ
diff --git a/tests/ref/grid-inset.png b/tests/ref/grid-inset.png
new file mode 100644
index 00000000..d31197d0
--- /dev/null
+++ b/tests/ref/grid-inset.png
Binary files differ
diff --git a/tests/ref/grid-nested-breaking.png b/tests/ref/grid-nested-breaking.png
new file mode 100644
index 00000000..b203c230
--- /dev/null
+++ b/tests/ref/grid-nested-breaking.png
Binary files differ
diff --git a/tests/ref/grid-nested-footers.png b/tests/ref/grid-nested-footers.png
new file mode 100644
index 00000000..1af85a00
--- /dev/null
+++ b/tests/ref/grid-nested-footers.png
Binary files differ
diff --git a/tests/ref/grid-nested-headers.png b/tests/ref/grid-nested-headers.png
new file mode 100644
index 00000000..e714dcc4
--- /dev/null
+++ b/tests/ref/grid-nested-headers.png
Binary files differ
diff --git a/tests/ref/grid-nested-with-footers.png b/tests/ref/grid-nested-with-footers.png
new file mode 100644
index 00000000..5ceae877
--- /dev/null
+++ b/tests/ref/grid-nested-with-footers.png
Binary files differ
diff --git a/tests/ref/grid-nested-with-headers.png b/tests/ref/grid-nested-with-headers.png
new file mode 100644
index 00000000..6b7ef14b
--- /dev/null
+++ b/tests/ref/grid-nested-with-headers.png
Binary files differ
diff --git a/tests/ref/grid-row-sizing-manual-align.png b/tests/ref/grid-row-sizing-manual-align.png
new file mode 100644
index 00000000..68b0911e
--- /dev/null
+++ b/tests/ref/grid-row-sizing-manual-align.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-block-full-height.png b/tests/ref/grid-rowspan-block-full-height.png
new file mode 100644
index 00000000..078cbda4
--- /dev/null
+++ b/tests/ref/grid-rowspan-block-full-height.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-block-overflow.png b/tests/ref/grid-rowspan-block-overflow.png
new file mode 100644
index 00000000..78e26d72
--- /dev/null
+++ b/tests/ref/grid-rowspan-block-overflow.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-cell-coordinates.png b/tests/ref/grid-rowspan-cell-coordinates.png
new file mode 100644
index 00000000..ebe19fd4
--- /dev/null
+++ b/tests/ref/grid-rowspan-cell-coordinates.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-cell-order.png b/tests/ref/grid-rowspan-cell-order.png
new file mode 100644
index 00000000..c9b1f554
--- /dev/null
+++ b/tests/ref/grid-rowspan-cell-order.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-excessive-gutter.png b/tests/ref/grid-rowspan-excessive-gutter.png
new file mode 100644
index 00000000..8688364c
--- /dev/null
+++ b/tests/ref/grid-rowspan-excessive-gutter.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-excessive.png b/tests/ref/grid-rowspan-excessive.png
new file mode 100644
index 00000000..1e6b4128
--- /dev/null
+++ b/tests/ref/grid-rowspan-excessive.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-fixed-size.png b/tests/ref/grid-rowspan-fixed-size.png
new file mode 100644
index 00000000..c9ae3fa1
--- /dev/null
+++ b/tests/ref/grid-rowspan-fixed-size.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-gutter.png b/tests/ref/grid-rowspan-gutter.png
new file mode 100644
index 00000000..b37a1cab
--- /dev/null
+++ b/tests/ref/grid-rowspan-gutter.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-in-all-columns-stroke-gutter.png b/tests/ref/grid-rowspan-in-all-columns-stroke-gutter.png
new file mode 100644
index 00000000..edad2f01
--- /dev/null
+++ b/tests/ref/grid-rowspan-in-all-columns-stroke-gutter.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-in-all-columns-stroke.png b/tests/ref/grid-rowspan-in-all-columns-stroke.png
new file mode 100644
index 00000000..135d1911
--- /dev/null
+++ b/tests/ref/grid-rowspan-in-all-columns-stroke.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-over-auto-row.png b/tests/ref/grid-rowspan-over-auto-row.png
new file mode 100644
index 00000000..45037382
--- /dev/null
+++ b/tests/ref/grid-rowspan-over-auto-row.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-over-fr-row-at-end.png b/tests/ref/grid-rowspan-over-fr-row-at-end.png
new file mode 100644
index 00000000..1cf8b9fc
--- /dev/null
+++ b/tests/ref/grid-rowspan-over-fr-row-at-end.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-over-fr-row-at-start.png b/tests/ref/grid-rowspan-over-fr-row-at-start.png
new file mode 100644
index 00000000..577db916
--- /dev/null
+++ b/tests/ref/grid-rowspan-over-fr-row-at-start.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-split-1.png b/tests/ref/grid-rowspan-split-1.png
new file mode 100644
index 00000000..e99b105f
--- /dev/null
+++ b/tests/ref/grid-rowspan-split-1.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-split-10.png b/tests/ref/grid-rowspan-split-10.png
new file mode 100644
index 00000000..0b907e7d
--- /dev/null
+++ b/tests/ref/grid-rowspan-split-10.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-split-11.png b/tests/ref/grid-rowspan-split-11.png
new file mode 100644
index 00000000..202665d6
--- /dev/null
+++ b/tests/ref/grid-rowspan-split-11.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-split-12.png b/tests/ref/grid-rowspan-split-12.png
new file mode 100644
index 00000000..3d8985f2
--- /dev/null
+++ b/tests/ref/grid-rowspan-split-12.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-split-13.png b/tests/ref/grid-rowspan-split-13.png
new file mode 100644
index 00000000..f4e9d694
--- /dev/null
+++ b/tests/ref/grid-rowspan-split-13.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-split-14.png b/tests/ref/grid-rowspan-split-14.png
new file mode 100644
index 00000000..1500a89b
--- /dev/null
+++ b/tests/ref/grid-rowspan-split-14.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-split-15.png b/tests/ref/grid-rowspan-split-15.png
new file mode 100644
index 00000000..445f0a95
--- /dev/null
+++ b/tests/ref/grid-rowspan-split-15.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-split-16.png b/tests/ref/grid-rowspan-split-16.png
new file mode 100644
index 00000000..fff83aeb
--- /dev/null
+++ b/tests/ref/grid-rowspan-split-16.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-split-17.png b/tests/ref/grid-rowspan-split-17.png
new file mode 100644
index 00000000..2224c194
--- /dev/null
+++ b/tests/ref/grid-rowspan-split-17.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-split-2.png b/tests/ref/grid-rowspan-split-2.png
new file mode 100644
index 00000000..43a5eed7
--- /dev/null
+++ b/tests/ref/grid-rowspan-split-2.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-split-3.png b/tests/ref/grid-rowspan-split-3.png
new file mode 100644
index 00000000..0d7c3359
--- /dev/null
+++ b/tests/ref/grid-rowspan-split-3.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-split-4.png b/tests/ref/grid-rowspan-split-4.png
new file mode 100644
index 00000000..2af887bb
--- /dev/null
+++ b/tests/ref/grid-rowspan-split-4.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-split-5.png b/tests/ref/grid-rowspan-split-5.png
new file mode 100644
index 00000000..3aa79cda
--- /dev/null
+++ b/tests/ref/grid-rowspan-split-5.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-split-6.png b/tests/ref/grid-rowspan-split-6.png
new file mode 100644
index 00000000..fbf5bf28
--- /dev/null
+++ b/tests/ref/grid-rowspan-split-6.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-split-7.png b/tests/ref/grid-rowspan-split-7.png
new file mode 100644
index 00000000..00e03f02
--- /dev/null
+++ b/tests/ref/grid-rowspan-split-7.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-split-8.png b/tests/ref/grid-rowspan-split-8.png
new file mode 100644
index 00000000..405b5423
--- /dev/null
+++ b/tests/ref/grid-rowspan-split-8.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-split-9.png b/tests/ref/grid-rowspan-split-9.png
new file mode 100644
index 00000000..5346be71
--- /dev/null
+++ b/tests/ref/grid-rowspan-split-9.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-unbreakable-1.png b/tests/ref/grid-rowspan-unbreakable-1.png
new file mode 100644
index 00000000..6112c069
--- /dev/null
+++ b/tests/ref/grid-rowspan-unbreakable-1.png
Binary files differ
diff --git a/tests/ref/grid-rowspan-unbreakable-2.png b/tests/ref/grid-rowspan-unbreakable-2.png
new file mode 100644
index 00000000..8e4a222a
--- /dev/null
+++ b/tests/ref/grid-rowspan-unbreakable-2.png
Binary files differ
diff --git a/tests/ref/grid-rowspan.png b/tests/ref/grid-rowspan.png
new file mode 100644
index 00000000..87ad4180
--- /dev/null
+++ b/tests/ref/grid-rowspan.png
Binary files differ
diff --git a/tests/ref/grid-rtl-colspan-stroke.png b/tests/ref/grid-rtl-colspan-stroke.png
new file mode 100644
index 00000000..248a575c
--- /dev/null
+++ b/tests/ref/grid-rtl-colspan-stroke.png
Binary files differ
diff --git a/tests/ref/grid-rtl-colspan.png b/tests/ref/grid-rtl-colspan.png
new file mode 100644
index 00000000..886e276d
--- /dev/null
+++ b/tests/ref/grid-rtl-colspan.png
Binary files differ
diff --git a/tests/ref/grid-rtl-complex.png b/tests/ref/grid-rtl-complex.png
new file mode 100644
index 00000000..a4177548
--- /dev/null
+++ b/tests/ref/grid-rtl-complex.png
Binary files differ
diff --git a/tests/ref/grid-rtl-header.png b/tests/ref/grid-rtl-header.png
new file mode 100644
index 00000000..1ed532c3
--- /dev/null
+++ b/tests/ref/grid-rtl-header.png
Binary files differ
diff --git a/tests/ref/grid-rtl-multiple-regions.png b/tests/ref/grid-rtl-multiple-regions.png
new file mode 100644
index 00000000..a9ec7340
--- /dev/null
+++ b/tests/ref/grid-rtl-multiple-regions.png
Binary files differ
diff --git a/tests/ref/grid-rtl-rowspan.png b/tests/ref/grid-rtl-rowspan.png
new file mode 100644
index 00000000..2465164b
--- /dev/null
+++ b/tests/ref/grid-rtl-rowspan.png
Binary files differ
diff --git a/tests/ref/grid-rtl-vline-position.png b/tests/ref/grid-rtl-vline-position.png
new file mode 100644
index 00000000..3612fc9f
--- /dev/null
+++ b/tests/ref/grid-rtl-vline-position.png
Binary files differ
diff --git a/tests/ref/grid-rtl.png b/tests/ref/grid-rtl.png
new file mode 100644
index 00000000..c40fc588
--- /dev/null
+++ b/tests/ref/grid-rtl.png
Binary files differ
diff --git a/tests/ref/grid-same-row-multiple-columns-breaking.png b/tests/ref/grid-same-row-multiple-columns-breaking.png
new file mode 100644
index 00000000..b440f336
--- /dev/null
+++ b/tests/ref/grid-same-row-multiple-columns-breaking.png
Binary files differ
diff --git a/tests/ref/grid-stroke-array.png b/tests/ref/grid-stroke-array.png
new file mode 100644
index 00000000..6f8e28b0
--- /dev/null
+++ b/tests/ref/grid-stroke-array.png
Binary files differ
diff --git a/tests/ref/grid-stroke-automatically-positioned-lines.png b/tests/ref/grid-stroke-automatically-positioned-lines.png
new file mode 100644
index 00000000..2118112c
--- /dev/null
+++ b/tests/ref/grid-stroke-automatically-positioned-lines.png
Binary files differ
diff --git a/tests/ref/grid-stroke-border-partial.png b/tests/ref/grid-stroke-border-partial.png
new file mode 100644
index 00000000..ffd8835f
--- /dev/null
+++ b/tests/ref/grid-stroke-border-partial.png
Binary files differ
diff --git a/tests/ref/grid-stroke-complex.png b/tests/ref/grid-stroke-complex.png
new file mode 100644
index 00000000..e68fd5f3
--- /dev/null
+++ b/tests/ref/grid-stroke-complex.png
Binary files differ
diff --git a/tests/ref/grid-stroke-field-in-show.png b/tests/ref/grid-stroke-field-in-show.png
new file mode 100644
index 00000000..695868c0
--- /dev/null
+++ b/tests/ref/grid-stroke-field-in-show.png
Binary files differ
diff --git a/tests/ref/grid-stroke-folding.png b/tests/ref/grid-stroke-folding.png
new file mode 100644
index 00000000..0f2d5960
--- /dev/null
+++ b/tests/ref/grid-stroke-folding.png
Binary files differ
diff --git a/tests/ref/grid-stroke-func.png b/tests/ref/grid-stroke-func.png
new file mode 100644
index 00000000..954e90df
--- /dev/null
+++ b/tests/ref/grid-stroke-func.png
Binary files differ
diff --git a/tests/ref/grid-stroke-hline-position-bottom-gutter.png b/tests/ref/grid-stroke-hline-position-bottom-gutter.png
new file mode 100644
index 00000000..23c7def4
--- /dev/null
+++ b/tests/ref/grid-stroke-hline-position-bottom-gutter.png
Binary files differ
diff --git a/tests/ref/grid-stroke-hline-position-bottom.png b/tests/ref/grid-stroke-hline-position-bottom.png
new file mode 100644
index 00000000..25c003c8
--- /dev/null
+++ b/tests/ref/grid-stroke-hline-position-bottom.png
Binary files differ
diff --git a/tests/ref/grid-stroke-hline-rowspan.png b/tests/ref/grid-stroke-hline-rowspan.png
new file mode 100644
index 00000000..2faf7079
--- /dev/null
+++ b/tests/ref/grid-stroke-hline-rowspan.png
Binary files differ
diff --git a/tests/ref/grid-stroke-manually-positioned-lines.png b/tests/ref/grid-stroke-manually-positioned-lines.png
new file mode 100644
index 00000000..a8a75ee0
--- /dev/null
+++ b/tests/ref/grid-stroke-manually-positioned-lines.png
Binary files differ
diff --git a/tests/ref/grid-stroke-none.png b/tests/ref/grid-stroke-none.png
new file mode 100644
index 00000000..3f978bd3
--- /dev/null
+++ b/tests/ref/grid-stroke-none.png
Binary files differ
diff --git a/tests/ref/grid-stroke-pattern.png b/tests/ref/grid-stroke-pattern.png
new file mode 100644
index 00000000..15e846ea
--- /dev/null
+++ b/tests/ref/grid-stroke-pattern.png
Binary files differ
diff --git a/tests/ref/grid-stroke-priority-cell.png b/tests/ref/grid-stroke-priority-cell.png
new file mode 100644
index 00000000..2c28e9e8
--- /dev/null
+++ b/tests/ref/grid-stroke-priority-cell.png
Binary files differ
diff --git a/tests/ref/grid-stroke-priority-line-cell.png b/tests/ref/grid-stroke-priority-line-cell.png
new file mode 100644
index 00000000..064dc1c9
--- /dev/null
+++ b/tests/ref/grid-stroke-priority-line-cell.png
Binary files differ
diff --git a/tests/ref/grid-stroke-priority-line.png b/tests/ref/grid-stroke-priority-line.png
new file mode 100644
index 00000000..1bcaa2ee
--- /dev/null
+++ b/tests/ref/grid-stroke-priority-line.png
Binary files differ
diff --git a/tests/ref/grid-stroke-set-on-cell-and-line.png b/tests/ref/grid-stroke-set-on-cell-and-line.png
new file mode 100644
index 00000000..d43752f0
--- /dev/null
+++ b/tests/ref/grid-stroke-set-on-cell-and-line.png
Binary files differ
diff --git a/tests/ref/grid-stroke-vline-colspan.png b/tests/ref/grid-stroke-vline-colspan.png
new file mode 100644
index 00000000..7b381437
--- /dev/null
+++ b/tests/ref/grid-stroke-vline-colspan.png
Binary files differ
diff --git a/tests/ref/grid-stroke-vline-position-left-and-right.png b/tests/ref/grid-stroke-vline-position-left-and-right.png
new file mode 100644
index 00000000..852fcf29
--- /dev/null
+++ b/tests/ref/grid-stroke-vline-position-left-and-right.png
Binary files differ
diff --git a/tests/ref/grid-trailing-linebreak-region-overflow.png b/tests/ref/grid-trailing-linebreak-region-overflow.png
new file mode 100644
index 00000000..4f7bc852
--- /dev/null
+++ b/tests/ref/grid-trailing-linebreak-region-overflow.png
Binary files differ
diff --git a/tests/ref/heading-basic.png b/tests/ref/heading-basic.png
new file mode 100644
index 00000000..74a8f2ce
--- /dev/null
+++ b/tests/ref/heading-basic.png
Binary files differ
diff --git a/tests/ref/heading-block.png b/tests/ref/heading-block.png
new file mode 100644
index 00000000..595f18f5
--- /dev/null
+++ b/tests/ref/heading-block.png
Binary files differ
diff --git a/tests/ref/heading-offset-and-level.png b/tests/ref/heading-offset-and-level.png
new file mode 100644
index 00000000..9277e770
--- /dev/null
+++ b/tests/ref/heading-offset-and-level.png
Binary files differ
diff --git a/tests/ref/heading-offset.png b/tests/ref/heading-offset.png
new file mode 100644
index 00000000..3a3670cc
--- /dev/null
+++ b/tests/ref/heading-offset.png
Binary files differ
diff --git a/tests/ref/heading-show-where.png b/tests/ref/heading-show-where.png
new file mode 100644
index 00000000..609e6ec9
--- /dev/null
+++ b/tests/ref/heading-show-where.png
Binary files differ
diff --git a/tests/ref/heading-syntax-at-start.png b/tests/ref/heading-syntax-at-start.png
new file mode 100644
index 00000000..29b824e0
--- /dev/null
+++ b/tests/ref/heading-syntax-at-start.png
Binary files differ
diff --git a/tests/ref/heading-syntax-edge-cases.png b/tests/ref/heading-syntax-edge-cases.png
new file mode 100644
index 00000000..372e1a65
--- /dev/null
+++ b/tests/ref/heading-syntax-edge-cases.png
Binary files differ
diff --git a/tests/ref/hide-image.png b/tests/ref/hide-image.png
new file mode 100644
index 00000000..78bc690c
--- /dev/null
+++ b/tests/ref/hide-image.png
Binary files differ
diff --git a/tests/ref/hide-line.png b/tests/ref/hide-line.png
new file mode 100644
index 00000000..7d8fa6cd
--- /dev/null
+++ b/tests/ref/hide-line.png
Binary files differ
diff --git a/tests/ref/hide-list.png b/tests/ref/hide-list.png
new file mode 100644
index 00000000..055f7b66
--- /dev/null
+++ b/tests/ref/hide-list.png
Binary files differ
diff --git a/tests/ref/hide-polygon.png b/tests/ref/hide-polygon.png
new file mode 100644
index 00000000..5c74eb41
--- /dev/null
+++ b/tests/ref/hide-polygon.png
Binary files differ
diff --git a/tests/ref/hide-rect.png b/tests/ref/hide-rect.png
new file mode 100644
index 00000000..62372c21
--- /dev/null
+++ b/tests/ref/hide-rect.png
Binary files differ
diff --git a/tests/ref/hide-table.png b/tests/ref/hide-table.png
new file mode 100644
index 00000000..e3d890d7
--- /dev/null
+++ b/tests/ref/hide-table.png
Binary files differ
diff --git a/tests/ref/hide-text.png b/tests/ref/hide-text.png
new file mode 100644
index 00000000..1136038c
--- /dev/null
+++ b/tests/ref/hide-text.png
Binary files differ
diff --git a/tests/ref/highlight-bounds.png b/tests/ref/highlight-bounds.png
new file mode 100644
index 00000000..ed868c29
--- /dev/null
+++ b/tests/ref/highlight-bounds.png
Binary files differ
diff --git a/tests/ref/highlight-edges-bounds.png b/tests/ref/highlight-edges-bounds.png
new file mode 100644
index 00000000..f78f3cc3
--- /dev/null
+++ b/tests/ref/highlight-edges-bounds.png
Binary files differ
diff --git a/tests/ref/highlight-edges.png b/tests/ref/highlight-edges.png
new file mode 100644
index 00000000..ca48707f
--- /dev/null
+++ b/tests/ref/highlight-edges.png
Binary files differ
diff --git a/tests/ref/highlight-radius.png b/tests/ref/highlight-radius.png
new file mode 100644
index 00000000..3baa3e6d
--- /dev/null
+++ b/tests/ref/highlight-radius.png
Binary files differ
diff --git a/tests/ref/highlight-stroke.png b/tests/ref/highlight-stroke.png
new file mode 100644
index 00000000..5a8ad3b5
--- /dev/null
+++ b/tests/ref/highlight-stroke.png
Binary files differ
diff --git a/tests/ref/highlight.png b/tests/ref/highlight.png
new file mode 100644
index 00000000..0047b7f4
--- /dev/null
+++ b/tests/ref/highlight.png
Binary files differ
diff --git a/tests/ref/hyphenate-between-shape-runs.png b/tests/ref/hyphenate-between-shape-runs.png
new file mode 100644
index 00000000..a365af24
--- /dev/null
+++ b/tests/ref/hyphenate-between-shape-runs.png
Binary files differ
diff --git a/tests/ref/hyphenate-off-temporarily.png b/tests/ref/hyphenate-off-temporarily.png
new file mode 100644
index 00000000..48e3caa9
--- /dev/null
+++ b/tests/ref/hyphenate-off-temporarily.png
Binary files differ
diff --git a/tests/ref/hyphenate-punctuation.png b/tests/ref/hyphenate-punctuation.png
new file mode 100644
index 00000000..897a15a0
--- /dev/null
+++ b/tests/ref/hyphenate-punctuation.png
Binary files differ
diff --git a/tests/ref/hyphenate-shy.png b/tests/ref/hyphenate-shy.png
new file mode 100644
index 00000000..a548c711
--- /dev/null
+++ b/tests/ref/hyphenate-shy.png
Binary files differ
diff --git a/tests/ref/hyphenate.png b/tests/ref/hyphenate.png
new file mode 100644
index 00000000..c01c9021
--- /dev/null
+++ b/tests/ref/hyphenate.png
Binary files differ
diff --git a/tests/ref/if-condition-complex.png b/tests/ref/if-condition-complex.png
new file mode 100644
index 00000000..4cbebc5e
--- /dev/null
+++ b/tests/ref/if-condition-complex.png
Binary files differ
diff --git a/tests/ref/if-markup.png b/tests/ref/if-markup.png
new file mode 100644
index 00000000..57eb47da
--- /dev/null
+++ b/tests/ref/if-markup.png
Binary files differ
diff --git a/tests/ref/image-baseline-with-box.png b/tests/ref/image-baseline-with-box.png
new file mode 100644
index 00000000..41128069
--- /dev/null
+++ b/tests/ref/image-baseline-with-box.png
Binary files differ
diff --git a/tests/ref/image-decode-detect-format.png b/tests/ref/image-decode-detect-format.png
new file mode 100644
index 00000000..6f12e8b4
--- /dev/null
+++ b/tests/ref/image-decode-detect-format.png
Binary files differ
diff --git a/tests/ref/image-decode-specify-format.png b/tests/ref/image-decode-specify-format.png
new file mode 100644
index 00000000..6f12e8b4
--- /dev/null
+++ b/tests/ref/image-decode-specify-format.png
Binary files differ
diff --git a/tests/ref/image-decode-svg.png b/tests/ref/image-decode-svg.png
new file mode 100644
index 00000000..b7cfcb17
--- /dev/null
+++ b/tests/ref/image-decode-svg.png
Binary files differ
diff --git a/tests/ref/image-fit.png b/tests/ref/image-fit.png
new file mode 100644
index 00000000..5a3bdec1
--- /dev/null
+++ b/tests/ref/image-fit.png
Binary files differ
diff --git a/tests/ref/image-jump-to-next-page.png b/tests/ref/image-jump-to-next-page.png
new file mode 100644
index 00000000..d8f03b3f
--- /dev/null
+++ b/tests/ref/image-jump-to-next-page.png
Binary files differ
diff --git a/tests/ref/image-natural-dpi-sizing.png b/tests/ref/image-natural-dpi-sizing.png
new file mode 100644
index 00000000..3b9f3fa5
--- /dev/null
+++ b/tests/ref/image-natural-dpi-sizing.png
Binary files differ
diff --git a/tests/ref/image-rgba-png-and-jpeg.png b/tests/ref/image-rgba-png-and-jpeg.png
new file mode 100644
index 00000000..60127170
--- /dev/null
+++ b/tests/ref/image-rgba-png-and-jpeg.png
Binary files differ
diff --git a/tests/ref/image-sizing.png b/tests/ref/image-sizing.png
new file mode 100644
index 00000000..7419de14
--- /dev/null
+++ b/tests/ref/image-sizing.png
Binary files differ
diff --git a/tests/ref/image-svg-complex.png b/tests/ref/image-svg-complex.png
new file mode 100644
index 00000000..1ac45477
--- /dev/null
+++ b/tests/ref/image-svg-complex.png
Binary files differ
diff --git a/tests/ref/image-svg-text-font.png b/tests/ref/image-svg-text-font.png
new file mode 100644
index 00000000..2e3b0a0f
--- /dev/null
+++ b/tests/ref/image-svg-text-font.png
Binary files differ
diff --git a/tests/ref/image-svg-text.png b/tests/ref/image-svg-text.png
new file mode 100644
index 00000000..2e41f905
--- /dev/null
+++ b/tests/ref/image-svg-text.png
Binary files differ
diff --git a/tests/ref/import-basic.png b/tests/ref/import-basic.png
new file mode 100644
index 00000000..674c4ecf
--- /dev/null
+++ b/tests/ref/import-basic.png
Binary files differ
diff --git a/tests/ref/import-from-function-scope.png b/tests/ref/import-from-function-scope.png
new file mode 100644
index 00000000..f6169d8c
--- /dev/null
+++ b/tests/ref/import-from-function-scope.png
Binary files differ
diff --git a/tests/ref/import-source-field-access.png b/tests/ref/import-source-field-access.png
new file mode 100644
index 00000000..e42bf209
--- /dev/null
+++ b/tests/ref/import-source-field-access.png
Binary files differ
diff --git a/tests/ref/include-file.png b/tests/ref/include-file.png
new file mode 100644
index 00000000..57c3aca1
--- /dev/null
+++ b/tests/ref/include-file.png
Binary files differ
diff --git a/tests/ref/int-display.png b/tests/ref/int-display.png
new file mode 100644
index 00000000..bfb04648
--- /dev/null
+++ b/tests/ref/int-display.png
Binary files differ
diff --git a/tests/ref/int-repr.png b/tests/ref/int-repr.png
new file mode 100644
index 00000000..a2ee4ee0
--- /dev/null
+++ b/tests/ref/int-repr.png
Binary files differ
diff --git a/tests/ref/issue-1041-smartquotes-in-outline.png b/tests/ref/issue-1041-smartquotes-in-outline.png
new file mode 100644
index 00000000..29ba4065
--- /dev/null
+++ b/tests/ref/issue-1041-smartquotes-in-outline.png
Binary files differ
diff --git a/tests/ref/issue-1050-terms-indent.png b/tests/ref/issue-1050-terms-indent.png
new file mode 100644
index 00000000..ca0521c7
--- /dev/null
+++ b/tests/ref/issue-1050-terms-indent.png
Binary files differ
diff --git a/tests/ref/issue-1052-math-number-spacing.png b/tests/ref/issue-1052-math-number-spacing.png
new file mode 100644
index 00000000..79df2c9f
--- /dev/null
+++ b/tests/ref/issue-1052-math-number-spacing.png
Binary files differ
diff --git a/tests/ref/issue-1216-clamp-panic.png b/tests/ref/issue-1216-clamp-panic.png
new file mode 100644
index 00000000..d51f134c
--- /dev/null
+++ b/tests/ref/issue-1216-clamp-panic.png
Binary files differ
diff --git a/tests/ref/issue-1240-stack-h-fr.png b/tests/ref/issue-1240-stack-h-fr.png
new file mode 100644
index 00000000..ae1ba41e
--- /dev/null
+++ b/tests/ref/issue-1240-stack-h-fr.png
Binary files differ
diff --git a/tests/ref/issue-1240-stack-v-fr.png b/tests/ref/issue-1240-stack-v-fr.png
new file mode 100644
index 00000000..a9ac36e8
--- /dev/null
+++ b/tests/ref/issue-1240-stack-v-fr.png
Binary files differ
diff --git a/tests/ref/issue-1368-place-pagebreak.png b/tests/ref/issue-1368-place-pagebreak.png
new file mode 100644
index 00000000..920cd203
--- /dev/null
+++ b/tests/ref/issue-1368-place-pagebreak.png
Binary files differ
diff --git a/tests/ref/issue-1373-bidi-tofus.png b/tests/ref/issue-1373-bidi-tofus.png
new file mode 100644
index 00000000..783eb473
--- /dev/null
+++ b/tests/ref/issue-1373-bidi-tofus.png
Binary files differ
diff --git a/tests/ref/issue-1388-table-row-missing.png b/tests/ref/issue-1388-table-row-missing.png
new file mode 100644
index 00000000..dd08eb46
--- /dev/null
+++ b/tests/ref/issue-1388-table-row-missing.png
Binary files differ
diff --git a/tests/ref/issue-1398-line-align.png b/tests/ref/issue-1398-line-align.png
new file mode 100644
index 00000000..778aa72c
--- /dev/null
+++ b/tests/ref/issue-1398-line-align.png
Binary files differ
diff --git a/tests/ref/issue-1433-footnote-in-list.png b/tests/ref/issue-1433-footnote-in-list.png
new file mode 100644
index 00000000..28a6e77f
--- /dev/null
+++ b/tests/ref/issue-1433-footnote-in-list.png
Binary files differ
diff --git a/tests/ref/issue-1540-smartquotes-across-newlines.png b/tests/ref/issue-1540-smartquotes-across-newlines.png
new file mode 100644
index 00000000..10fe7337
--- /dev/null
+++ b/tests/ref/issue-1540-smartquotes-across-newlines.png
Binary files differ
diff --git a/tests/ref/issue-1597-cite-footnote.png b/tests/ref/issue-1597-cite-footnote.png
new file mode 100644
index 00000000..bdd9f225
--- /dev/null
+++ b/tests/ref/issue-1597-cite-footnote.png
Binary files differ
diff --git a/tests/ref/issue-1825-rect-overflow.png b/tests/ref/issue-1825-rect-overflow.png
new file mode 100644
index 00000000..70f09e12
--- /dev/null
+++ b/tests/ref/issue-1825-rect-overflow.png
Binary files differ
diff --git a/tests/ref/issue-183-table-lines.png b/tests/ref/issue-183-table-lines.png
new file mode 100644
index 00000000..e4369262
--- /dev/null
+++ b/tests/ref/issue-183-table-lines.png
Binary files differ
diff --git a/tests/ref/issue-1948-math-text-break.png b/tests/ref/issue-1948-math-text-break.png
new file mode 100644
index 00000000..6e3e9e85
--- /dev/null
+++ b/tests/ref/issue-1948-math-text-break.png
Binary files differ
diff --git a/tests/ref/issue-2044-invalid-parsed-ident.png b/tests/ref/issue-2044-invalid-parsed-ident.png
new file mode 100644
index 00000000..7e37ce2c
--- /dev/null
+++ b/tests/ref/issue-2044-invalid-parsed-ident.png
Binary files differ
diff --git a/tests/ref/issue-2051-new-cm-svg.png b/tests/ref/issue-2051-new-cm-svg.png
new file mode 100644
index 00000000..65352860
--- /dev/null
+++ b/tests/ref/issue-2051-new-cm-svg.png
Binary files differ
diff --git a/tests/ref/issue-2055-math-eval.png b/tests/ref/issue-2055-math-eval.png
new file mode 100644
index 00000000..168b8913
--- /dev/null
+++ b/tests/ref/issue-2055-math-eval.png
Binary files differ
diff --git a/tests/ref/issue-2095-pagebreak-numbering.png b/tests/ref/issue-2095-pagebreak-numbering.png
new file mode 100644
index 00000000..e3a515b7
--- /dev/null
+++ b/tests/ref/issue-2095-pagebreak-numbering.png
Binary files differ
diff --git a/tests/ref/issue-2105-linebreak-tofu.png b/tests/ref/issue-2105-linebreak-tofu.png
new file mode 100644
index 00000000..197412b9
--- /dev/null
+++ b/tests/ref/issue-2105-linebreak-tofu.png
Binary files differ
diff --git a/tests/ref/issue-2128-block-width-box.png b/tests/ref/issue-2128-block-width-box.png
new file mode 100644
index 00000000..40fe6b4f
--- /dev/null
+++ b/tests/ref/issue-2128-block-width-box.png
Binary files differ
diff --git a/tests/ref/issue-2134-pagebreak-bibliography.png b/tests/ref/issue-2134-pagebreak-bibliography.png
new file mode 100644
index 00000000..ad0fb165
--- /dev/null
+++ b/tests/ref/issue-2134-pagebreak-bibliography.png
Binary files differ
diff --git a/tests/ref/issue-2162-pagebreak-set-style.png b/tests/ref/issue-2162-pagebreak-set-style.png
new file mode 100644
index 00000000..4ea6f56f
--- /dev/null
+++ b/tests/ref/issue-2162-pagebreak-set-style.png
Binary files differ
diff --git a/tests/ref/issue-2199-place-spacing-bottom.png b/tests/ref/issue-2199-place-spacing-bottom.png
new file mode 100644
index 00000000..1f27559b
--- /dev/null
+++ b/tests/ref/issue-2199-place-spacing-bottom.png
Binary files differ
diff --git a/tests/ref/issue-2199-place-spacing-default.png b/tests/ref/issue-2199-place-spacing-default.png
new file mode 100644
index 00000000..565a8302
--- /dev/null
+++ b/tests/ref/issue-2199-place-spacing-default.png
Binary files differ
diff --git a/tests/ref/issue-2214-baseline-math.png b/tests/ref/issue-2214-baseline-math.png
new file mode 100644
index 00000000..9a3e6f3c
--- /dev/null
+++ b/tests/ref/issue-2214-baseline-math.png
Binary files differ
diff --git a/tests/ref/issue-2259-raw-color-overwrite.png b/tests/ref/issue-2259-raw-color-overwrite.png
new file mode 100644
index 00000000..9cf42c43
--- /dev/null
+++ b/tests/ref/issue-2259-raw-color-overwrite.png
Binary files differ
diff --git a/tests/ref/issue-2268-mat-augment-color.png b/tests/ref/issue-2268-mat-augment-color.png
new file mode 100644
index 00000000..5aca29ca
--- /dev/null
+++ b/tests/ref/issue-2268-mat-augment-color.png
Binary files differ
diff --git a/tests/ref/issue-2419-justify-hanging-indent.png b/tests/ref/issue-2419-justify-hanging-indent.png
new file mode 100644
index 00000000..bb478ba4
--- /dev/null
+++ b/tests/ref/issue-2419-justify-hanging-indent.png
Binary files differ
diff --git a/tests/ref/issue-2530-enum-item-panic.png b/tests/ref/issue-2530-enum-item-panic.png
new file mode 100644
index 00000000..4f6130ba
--- /dev/null
+++ b/tests/ref/issue-2530-enum-item-panic.png
Binary files differ
diff --git a/tests/ref/issue-2530-figure-caption-panic.png b/tests/ref/issue-2530-figure-caption-panic.png
new file mode 100644
index 00000000..025449ef
--- /dev/null
+++ b/tests/ref/issue-2530-figure-caption-panic.png
Binary files differ
diff --git a/tests/ref/issue-2530-list-item-panic.png b/tests/ref/issue-2530-list-item-panic.png
new file mode 100644
index 00000000..14d2f570
--- /dev/null
+++ b/tests/ref/issue-2530-list-item-panic.png
Binary files differ
diff --git a/tests/ref/issue-2530-term-item-panic.png b/tests/ref/issue-2530-term-item-panic.png
new file mode 100644
index 00000000..85b3e92f
--- /dev/null
+++ b/tests/ref/issue-2530-term-item-panic.png
Binary files differ
diff --git a/tests/ref/issue-2531-cite-show-set.png b/tests/ref/issue-2531-cite-show-set.png
new file mode 100644
index 00000000..25723f4d
--- /dev/null
+++ b/tests/ref/issue-2531-cite-show-set.png
Binary files differ
diff --git a/tests/ref/issue-2538-cjk-latin-spacing-before-linebreak.png b/tests/ref/issue-2538-cjk-latin-spacing-before-linebreak.png
new file mode 100644
index 00000000..59571642
--- /dev/null
+++ b/tests/ref/issue-2538-cjk-latin-spacing-before-linebreak.png
Binary files differ
diff --git a/tests/ref/issue-2595-float-overlap.png b/tests/ref/issue-2595-float-overlap.png
new file mode 100644
index 00000000..4b460579
--- /dev/null
+++ b/tests/ref/issue-2595-float-overlap.png
Binary files differ
diff --git a/tests/ref/issue-2650-cjk-latin-spacing-meta.png b/tests/ref/issue-2650-cjk-latin-spacing-meta.png
new file mode 100644
index 00000000..d346b73f
--- /dev/null
+++ b/tests/ref/issue-2650-cjk-latin-spacing-meta.png
Binary files differ
diff --git a/tests/ref/issue-2715-float-order.png b/tests/ref/issue-2715-float-order.png
new file mode 100644
index 00000000..01599d25
--- /dev/null
+++ b/tests/ref/issue-2715-float-order.png
Binary files differ
diff --git a/tests/ref/issue-2902-gradient-oklab-panic.png b/tests/ref/issue-2902-gradient-oklab-panic.png
new file mode 100644
index 00000000..f8e18f7c
--- /dev/null
+++ b/tests/ref/issue-2902-gradient-oklab-panic.png
Binary files differ
diff --git a/tests/ref/issue-2902-gradient-oklch-panic.png b/tests/ref/issue-2902-gradient-oklch-panic.png
new file mode 100644
index 00000000..1af7200e
--- /dev/null
+++ b/tests/ref/issue-2902-gradient-oklch-panic.png
Binary files differ
diff --git a/tests/ref/issue-3082-chinese-punctuation.png b/tests/ref/issue-3082-chinese-punctuation.png
new file mode 100644
index 00000000..642013d0
--- /dev/null
+++ b/tests/ref/issue-3082-chinese-punctuation.png
Binary files differ
diff --git a/tests/ref/issue-3191-raw-indent-shrink.png b/tests/ref/issue-3191-raw-indent-shrink.png
new file mode 100644
index 00000000..e7ac73b7
--- /dev/null
+++ b/tests/ref/issue-3191-raw-indent-shrink.png
Binary files differ
diff --git a/tests/ref/issue-3191-raw-normal-paragraphs-still-shrink.png b/tests/ref/issue-3191-raw-normal-paragraphs-still-shrink.png
new file mode 100644
index 00000000..1eb49995
--- /dev/null
+++ b/tests/ref/issue-3191-raw-normal-paragraphs-still-shrink.png
Binary files differ
diff --git a/tests/ref/issue-3232-dict-empty.png b/tests/ref/issue-3232-dict-empty.png
new file mode 100644
index 00000000..f8d3f324
--- /dev/null
+++ b/tests/ref/issue-3232-dict-empty.png
Binary files differ
diff --git a/tests/ref/issue-3264-rect-negative-dimensions.png b/tests/ref/issue-3264-rect-negative-dimensions.png
new file mode 100644
index 00000000..44a04681
--- /dev/null
+++ b/tests/ref/issue-3264-rect-negative-dimensions.png
Binary files differ
diff --git a/tests/ref/issue-3363-json-large-number.png b/tests/ref/issue-3363-json-large-number.png
new file mode 100644
index 00000000..3e13dea1
--- /dev/null
+++ b/tests/ref/issue-3363-json-large-number.png
Binary files differ
diff --git a/tests/ref/issue-3586-figure-caption-separator.png b/tests/ref/issue-3586-figure-caption-separator.png
new file mode 100644
index 00000000..1d038fe3
--- /dev/null
+++ b/tests/ref/issue-3586-figure-caption-separator.png
Binary files differ
diff --git a/tests/ref/issue-3624-spacing-behaviour.png b/tests/ref/issue-3624-spacing-behaviour.png
new file mode 100644
index 00000000..c7db6753
--- /dev/null
+++ b/tests/ref/issue-3624-spacing-behaviour.png
Binary files differ
diff --git a/tests/ref/issue-3641-float-loop.png b/tests/ref/issue-3641-float-loop.png
new file mode 100644
index 00000000..4490d30a
--- /dev/null
+++ b/tests/ref/issue-3641-float-loop.png
Binary files differ
diff --git a/tests/ref/issue-3650-italic-equation.png b/tests/ref/issue-3650-italic-equation.png
new file mode 100644
index 00000000..484457e8
--- /dev/null
+++ b/tests/ref/issue-3650-italic-equation.png
Binary files differ
diff --git a/tests/ref/issue-3658-math-size.png b/tests/ref/issue-3658-math-size.png
new file mode 100644
index 00000000..db8fccf9
--- /dev/null
+++ b/tests/ref/issue-3658-math-size.png
Binary files differ
diff --git a/tests/ref/issue-3662-pdf-smartquotes.png b/tests/ref/issue-3662-pdf-smartquotes.png
new file mode 100644
index 00000000..ff73cbc8
--- /dev/null
+++ b/tests/ref/issue-3662-pdf-smartquotes.png
Binary files differ
diff --git a/tests/ref/issue-3700-deformed-stroke.png b/tests/ref/issue-3700-deformed-stroke.png
new file mode 100644
index 00000000..9578a675
--- /dev/null
+++ b/tests/ref/issue-3700-deformed-stroke.png
Binary files differ
diff --git a/tests/ref/issue-3841-tabs-in-raw-type-code.png b/tests/ref/issue-3841-tabs-in-raw-type-code.png
new file mode 100644
index 00000000..b7a7b1ba
--- /dev/null
+++ b/tests/ref/issue-3841-tabs-in-raw-type-code.png
Binary files differ
diff --git a/tests/ref/issue-622-hide-meta-cite.png b/tests/ref/issue-622-hide-meta-cite.png
new file mode 100644
index 00000000..8918f668
--- /dev/null
+++ b/tests/ref/issue-622-hide-meta-cite.png
Binary files differ
diff --git a/tests/ref/issue-622-hide-meta-outline.png b/tests/ref/issue-622-hide-meta-outline.png
new file mode 100644
index 00000000..72a82e4d
--- /dev/null
+++ b/tests/ref/issue-622-hide-meta-outline.png
Binary files differ
diff --git a/tests/ref/issue-785-cite-locate.png b/tests/ref/issue-785-cite-locate.png
new file mode 100644
index 00000000..7c2a650a
--- /dev/null
+++ b/tests/ref/issue-785-cite-locate.png
Binary files differ
diff --git a/tests/ref/issue-80-emoji-linebreak.png b/tests/ref/issue-80-emoji-linebreak.png
new file mode 100644
index 00000000..d35a62b3
--- /dev/null
+++ b/tests/ref/issue-80-emoji-linebreak.png
Binary files differ
diff --git a/tests/ref/issue-852-mat-type.png b/tests/ref/issue-852-mat-type.png
new file mode 100644
index 00000000..81af3bb5
--- /dev/null
+++ b/tests/ref/issue-852-mat-type.png
Binary files differ
diff --git a/tests/ref/issue-870-image-rotation.png b/tests/ref/issue-870-image-rotation.png
new file mode 100644
index 00000000..c321a1a9
--- /dev/null
+++ b/tests/ref/issue-870-image-rotation.png
Binary files differ
diff --git a/tests/ref/issue-886-args-sink.png b/tests/ref/issue-886-args-sink.png
new file mode 100644
index 00000000..2ef08adf
--- /dev/null
+++ b/tests/ref/issue-886-args-sink.png
Binary files differ
diff --git a/tests/ref/issue-columns-heading.png b/tests/ref/issue-columns-heading.png
new file mode 100644
index 00000000..700972bc
--- /dev/null
+++ b/tests/ref/issue-columns-heading.png
Binary files differ
diff --git a/tests/ref/issue-flow-frame-placement.png b/tests/ref/issue-flow-frame-placement.png
new file mode 100644
index 00000000..27469c27
--- /dev/null
+++ b/tests/ref/issue-flow-frame-placement.png
Binary files differ
diff --git a/tests/ref/issue-flow-layout-index-out-of-bounds.png b/tests/ref/issue-flow-layout-index-out-of-bounds.png
new file mode 100644
index 00000000..8746cbfc
--- /dev/null
+++ b/tests/ref/issue-flow-layout-index-out-of-bounds.png
Binary files differ
diff --git a/tests/ref/issue-flow-overlarge-frames.png b/tests/ref/issue-flow-overlarge-frames.png
new file mode 100644
index 00000000..016af525
--- /dev/null
+++ b/tests/ref/issue-flow-overlarge-frames.png
Binary files differ
diff --git a/tests/ref/issue-flow-trailing-leading.png b/tests/ref/issue-flow-trailing-leading.png
new file mode 100644
index 00000000..4245d42f
--- /dev/null
+++ b/tests/ref/issue-flow-trailing-leading.png
Binary files differ
diff --git a/tests/ref/issue-flow-weak-spacing.png b/tests/ref/issue-flow-weak-spacing.png
new file mode 100644
index 00000000..e37a5ae3
--- /dev/null
+++ b/tests/ref/issue-flow-weak-spacing.png
Binary files differ
diff --git a/tests/ref/issue-gradient-cmyk-encode.png b/tests/ref/issue-gradient-cmyk-encode.png
new file mode 100644
index 00000000..065d1a3b
--- /dev/null
+++ b/tests/ref/issue-gradient-cmyk-encode.png
Binary files differ
diff --git a/tests/ref/issue-grid-base-auto-row-list.png b/tests/ref/issue-grid-base-auto-row-list.png
new file mode 100644
index 00000000..8da3adf5
--- /dev/null
+++ b/tests/ref/issue-grid-base-auto-row-list.png
Binary files differ
diff --git a/tests/ref/issue-grid-base-auto-row.png b/tests/ref/issue-grid-base-auto-row.png
new file mode 100644
index 00000000..0e05577d
--- /dev/null
+++ b/tests/ref/issue-grid-base-auto-row.png
Binary files differ
diff --git a/tests/ref/issue-grid-double-skip.png b/tests/ref/issue-grid-double-skip.png
new file mode 100644
index 00000000..2901f29a
--- /dev/null
+++ b/tests/ref/issue-grid-double-skip.png
Binary files differ
diff --git a/tests/ref/issue-grid-gutter-skip.png b/tests/ref/issue-grid-gutter-skip.png
new file mode 100644
index 00000000..3404fd10
--- /dev/null
+++ b/tests/ref/issue-grid-gutter-skip.png
Binary files differ
diff --git a/tests/ref/issue-grid-skip-list.png b/tests/ref/issue-grid-skip-list.png
new file mode 100644
index 00000000..bd674337
--- /dev/null
+++ b/tests/ref/issue-grid-skip-list.png
Binary files differ
diff --git a/tests/ref/issue-grid-skip.png b/tests/ref/issue-grid-skip.png
new file mode 100644
index 00000000..1b46fd1a
--- /dev/null
+++ b/tests/ref/issue-grid-skip.png
Binary files differ
diff --git a/tests/ref/issue-math-realize-hide.png b/tests/ref/issue-math-realize-hide.png
new file mode 100644
index 00000000..729e9f00
--- /dev/null
+++ b/tests/ref/issue-math-realize-hide.png
Binary files differ
diff --git a/tests/ref/issue-math-realize-scripting.png b/tests/ref/issue-math-realize-scripting.png
new file mode 100644
index 00000000..a29b0364
--- /dev/null
+++ b/tests/ref/issue-math-realize-scripting.png
Binary files differ
diff --git a/tests/ref/issue-math-realize-show.png b/tests/ref/issue-math-realize-show.png
new file mode 100644
index 00000000..d6b727c1
--- /dev/null
+++ b/tests/ref/issue-math-realize-show.png
Binary files differ
diff --git a/tests/ref/issue-multiple-footnote-in-one-line.png b/tests/ref/issue-multiple-footnote-in-one-line.png
new file mode 100644
index 00000000..1d8c017d
--- /dev/null
+++ b/tests/ref/issue-multiple-footnote-in-one-line.png
Binary files differ
diff --git a/tests/ref/issue-non-atomic-closure.png b/tests/ref/issue-non-atomic-closure.png
new file mode 100644
index 00000000..f60b4654
--- /dev/null
+++ b/tests/ref/issue-non-atomic-closure.png
Binary files differ
diff --git a/tests/ref/issue-place-base.png b/tests/ref/issue-place-base.png
new file mode 100644
index 00000000..45517fe9
--- /dev/null
+++ b/tests/ref/issue-place-base.png
Binary files differ
diff --git a/tests/ref/issue-rtl-safe-to-break-panic.png b/tests/ref/issue-rtl-safe-to-break-panic.png
new file mode 100644
index 00000000..5cd9920c
--- /dev/null
+++ b/tests/ref/issue-rtl-safe-to-break-panic.png
Binary files differ
diff --git a/tests/ref/justify-avoid-runts.png b/tests/ref/justify-avoid-runts.png
new file mode 100644
index 00000000..70513939
--- /dev/null
+++ b/tests/ref/justify-avoid-runts.png
Binary files differ
diff --git a/tests/ref/justify-chinese.png b/tests/ref/justify-chinese.png
new file mode 100644
index 00000000..0284e8b9
--- /dev/null
+++ b/tests/ref/justify-chinese.png
Binary files differ
diff --git a/tests/ref/justify-code-blocks.png b/tests/ref/justify-code-blocks.png
new file mode 100644
index 00000000..088e8b63
--- /dev/null
+++ b/tests/ref/justify-code-blocks.png
Binary files differ
diff --git a/tests/ref/justify-japanese.png b/tests/ref/justify-japanese.png
new file mode 100644
index 00000000..addeba54
--- /dev/null
+++ b/tests/ref/justify-japanese.png
Binary files differ
diff --git a/tests/ref/justify-justified-linebreak.png b/tests/ref/justify-justified-linebreak.png
new file mode 100644
index 00000000..8792e9e2
--- /dev/null
+++ b/tests/ref/justify-justified-linebreak.png
Binary files differ
diff --git a/tests/ref/justify-knuth-story.png b/tests/ref/justify-knuth-story.png
new file mode 100644
index 00000000..9fbcc3c3
--- /dev/null
+++ b/tests/ref/justify-knuth-story.png
Binary files differ
diff --git a/tests/ref/justify-manual-linebreak.png b/tests/ref/justify-manual-linebreak.png
new file mode 100644
index 00000000..144a62c7
--- /dev/null
+++ b/tests/ref/justify-manual-linebreak.png
Binary files differ
diff --git a/tests/ref/justify-no-leading-spaces.png b/tests/ref/justify-no-leading-spaces.png
new file mode 100644
index 00000000..9d2557b5
--- /dev/null
+++ b/tests/ref/justify-no-leading-spaces.png
Binary files differ
diff --git a/tests/ref/justify-punctuation-adjustment.png b/tests/ref/justify-punctuation-adjustment.png
new file mode 100644
index 00000000..28d4ef04
--- /dev/null
+++ b/tests/ref/justify-punctuation-adjustment.png
Binary files differ
diff --git a/tests/ref/justify-shrink-last-line.png b/tests/ref/justify-shrink-last-line.png
new file mode 100644
index 00000000..f839e92e
--- /dev/null
+++ b/tests/ref/justify-shrink-last-line.png
Binary files differ
diff --git a/tests/ref/justify-variants.png b/tests/ref/justify-variants.png
new file mode 100644
index 00000000..81fcc700
--- /dev/null
+++ b/tests/ref/justify-variants.png
Binary files differ
diff --git a/tests/ref/justify-whitespace-adjustment.png b/tests/ref/justify-whitespace-adjustment.png
new file mode 100644
index 00000000..4ea6829c
--- /dev/null
+++ b/tests/ref/justify-whitespace-adjustment.png
Binary files differ
diff --git a/tests/ref/justify-without-justifiables.png b/tests/ref/justify-without-justifiables.png
new file mode 100644
index 00000000..77e5bf1b
--- /dev/null
+++ b/tests/ref/justify-without-justifiables.png
Binary files differ
diff --git a/tests/ref/justify.png b/tests/ref/justify.png
new file mode 100644
index 00000000..4e4fdbf5
--- /dev/null
+++ b/tests/ref/justify.png
Binary files differ
diff --git a/tests/ref/label-after-expression.png b/tests/ref/label-after-expression.png
new file mode 100644
index 00000000..5ceaf342
--- /dev/null
+++ b/tests/ref/label-after-expression.png
Binary files differ
diff --git a/tests/ref/label-after-parbreak.png b/tests/ref/label-after-parbreak.png
new file mode 100644
index 00000000..9339c65c
--- /dev/null
+++ b/tests/ref/label-after-parbreak.png
Binary files differ
diff --git a/tests/ref/label-dynamic-show-set.png b/tests/ref/label-dynamic-show-set.png
new file mode 100644
index 00000000..25681b92
--- /dev/null
+++ b/tests/ref/label-dynamic-show-set.png
Binary files differ
diff --git a/tests/ref/label-in-block.png b/tests/ref/label-in-block.png
new file mode 100644
index 00000000..e97bd725
--- /dev/null
+++ b/tests/ref/label-in-block.png
Binary files differ
diff --git a/tests/ref/label-on-text.png b/tests/ref/label-on-text.png
new file mode 100644
index 00000000..67fb1aa8
--- /dev/null
+++ b/tests/ref/label-on-text.png
Binary files differ
diff --git a/tests/ref/label-show-where-selector.png b/tests/ref/label-show-where-selector.png
new file mode 100644
index 00000000..61e90a9a
--- /dev/null
+++ b/tests/ref/label-show-where-selector.png
Binary files differ
diff --git a/tests/ref/label-unclosed-is-text.png b/tests/ref/label-unclosed-is-text.png
new file mode 100644
index 00000000..051db0cf
--- /dev/null
+++ b/tests/ref/label-unclosed-is-text.png
Binary files differ
diff --git a/tests/ref/layout-in-fixed-size-block.png b/tests/ref/layout-in-fixed-size-block.png
new file mode 100644
index 00000000..6cc321b3
--- /dev/null
+++ b/tests/ref/layout-in-fixed-size-block.png
Binary files differ
diff --git a/tests/ref/layout-in-page-call.png b/tests/ref/layout-in-page-call.png
new file mode 100644
index 00000000..9bc75ae7
--- /dev/null
+++ b/tests/ref/layout-in-page-call.png
Binary files differ
diff --git a/tests/ref/layout/align.png b/tests/ref/layout/align.png
deleted file mode 100644
index a0113597..00000000
--- a/tests/ref/layout/align.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/block-sizing.png b/tests/ref/layout/block-sizing.png
deleted file mode 100644
index 7d57a0d8..00000000
--- a/tests/ref/layout/block-sizing.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/block-spacing.png b/tests/ref/layout/block-spacing.png
deleted file mode 100644
index d73abac0..00000000
--- a/tests/ref/layout/block-spacing.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/cjk-latin-spacing.png b/tests/ref/layout/cjk-latin-spacing.png
deleted file mode 100644
index 629145e4..00000000
--- a/tests/ref/layout/cjk-latin-spacing.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/cjk-punctuation-adjustment.png b/tests/ref/layout/cjk-punctuation-adjustment.png
deleted file mode 100644
index 1da08f23..00000000
--- a/tests/ref/layout/cjk-punctuation-adjustment.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/clip.png b/tests/ref/layout/clip.png
deleted file mode 100644
index f37bf9ad..00000000
--- a/tests/ref/layout/clip.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/code-indent-shrink.png b/tests/ref/layout/code-indent-shrink.png
deleted file mode 100644
index 26f6ec40..00000000
--- a/tests/ref/layout/code-indent-shrink.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/columns.png b/tests/ref/layout/columns.png
deleted file mode 100644
index 38912f1b..00000000
--- a/tests/ref/layout/columns.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/container-fill.png b/tests/ref/layout/container-fill.png
deleted file mode 100644
index 74fdc73d..00000000
--- a/tests/ref/layout/container-fill.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/container.png b/tests/ref/layout/container.png
deleted file mode 100644
index 0cd56b2d..00000000
--- a/tests/ref/layout/container.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/enum-align.png b/tests/ref/layout/enum-align.png
deleted file mode 100644
index 18e392f2..00000000
--- a/tests/ref/layout/enum-align.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/enum-numbering.png b/tests/ref/layout/enum-numbering.png
deleted file mode 100644
index e1b2103b..00000000
--- a/tests/ref/layout/enum-numbering.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/enum.png b/tests/ref/layout/enum.png
deleted file mode 100644
index 62f1e4ab..00000000
--- a/tests/ref/layout/enum.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/flow-orphan.png b/tests/ref/layout/flow-orphan.png
deleted file mode 100644
index 434636c4..00000000
--- a/tests/ref/layout/flow-orphan.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-1.png b/tests/ref/layout/grid-1.png
deleted file mode 100644
index 9e33772c..00000000
--- a/tests/ref/layout/grid-1.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-2.png b/tests/ref/layout/grid-2.png
deleted file mode 100644
index ac1f7014..00000000
--- a/tests/ref/layout/grid-2.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-3.png b/tests/ref/layout/grid-3.png
deleted file mode 100644
index 0f54f2cc..00000000
--- a/tests/ref/layout/grid-3.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-4.png b/tests/ref/layout/grid-4.png
deleted file mode 100644
index 35a05ab5..00000000
--- a/tests/ref/layout/grid-4.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-5.png b/tests/ref/layout/grid-5.png
deleted file mode 100644
index 233ebb00..00000000
--- a/tests/ref/layout/grid-5.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-auto-shrink.png b/tests/ref/layout/grid-auto-shrink.png
deleted file mode 100644
index 34995215..00000000
--- a/tests/ref/layout/grid-auto-shrink.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-cell.png b/tests/ref/layout/grid-cell.png
deleted file mode 100644
index 563d6721..00000000
--- a/tests/ref/layout/grid-cell.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-colspan.png b/tests/ref/layout/grid-colspan.png
deleted file mode 100644
index e16ca347..00000000
--- a/tests/ref/layout/grid-colspan.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-footers-1.png b/tests/ref/layout/grid-footers-1.png
deleted file mode 100644
index 331cf7ad..00000000
--- a/tests/ref/layout/grid-footers-1.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-footers-2.png b/tests/ref/layout/grid-footers-2.png
deleted file mode 100644
index 60e9689c..00000000
--- a/tests/ref/layout/grid-footers-2.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-footers-3.png b/tests/ref/layout/grid-footers-3.png
deleted file mode 100644
index cc4948b8..00000000
--- a/tests/ref/layout/grid-footers-3.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-footers-4.png b/tests/ref/layout/grid-footers-4.png
deleted file mode 100644
index 29a6430b..00000000
--- a/tests/ref/layout/grid-footers-4.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-footers-5.png b/tests/ref/layout/grid-footers-5.png
deleted file mode 100644
index 6cae5592..00000000
--- a/tests/ref/layout/grid-footers-5.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-headers-1.png b/tests/ref/layout/grid-headers-1.png
deleted file mode 100644
index 7ae2d8d3..00000000
--- a/tests/ref/layout/grid-headers-1.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-headers-2.png b/tests/ref/layout/grid-headers-2.png
deleted file mode 100644
index 3dbc07c8..00000000
--- a/tests/ref/layout/grid-headers-2.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-headers-3.png b/tests/ref/layout/grid-headers-3.png
deleted file mode 100644
index 9ee77d50..00000000
--- a/tests/ref/layout/grid-headers-3.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-headers-4.png b/tests/ref/layout/grid-headers-4.png
deleted file mode 100644
index 1f3e4b10..00000000
--- a/tests/ref/layout/grid-headers-4.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-positioning.png b/tests/ref/layout/grid-positioning.png
deleted file mode 100644
index cac93f40..00000000
--- a/tests/ref/layout/grid-positioning.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-rowspan-basic.png b/tests/ref/layout/grid-rowspan-basic.png
deleted file mode 100644
index b464d8b4..00000000
--- a/tests/ref/layout/grid-rowspan-basic.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-rowspan-split-1.png b/tests/ref/layout/grid-rowspan-split-1.png
deleted file mode 100644
index 12cd5fc6..00000000
--- a/tests/ref/layout/grid-rowspan-split-1.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-rowspan-split-2.png b/tests/ref/layout/grid-rowspan-split-2.png
deleted file mode 100644
index e55c5e23..00000000
--- a/tests/ref/layout/grid-rowspan-split-2.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-rowspan-split-3.png b/tests/ref/layout/grid-rowspan-split-3.png
deleted file mode 100644
index c3ff4bd1..00000000
--- a/tests/ref/layout/grid-rowspan-split-3.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-rtl.png b/tests/ref/layout/grid-rtl.png
deleted file mode 100644
index d628ee8a..00000000
--- a/tests/ref/layout/grid-rtl.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-stroke.png b/tests/ref/layout/grid-stroke.png
deleted file mode 100644
index fbba379e..00000000
--- a/tests/ref/layout/grid-stroke.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/grid-styling.png b/tests/ref/layout/grid-styling.png
deleted file mode 100644
index dc50dd90..00000000
--- a/tests/ref/layout/grid-styling.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/hide.png b/tests/ref/layout/hide.png
deleted file mode 100644
index d8980049..00000000
--- a/tests/ref/layout/hide.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/list-attach.png b/tests/ref/layout/list-attach.png
deleted file mode 100644
index 4a6a4573..00000000
--- a/tests/ref/layout/list-attach.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/list-marker.png b/tests/ref/layout/list-marker.png
deleted file mode 100644
index 19d6ed5f..00000000
--- a/tests/ref/layout/list-marker.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/list.png b/tests/ref/layout/list.png
deleted file mode 100644
index 269243eb..00000000
--- a/tests/ref/layout/list.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/out-of-flow-in-block.png b/tests/ref/layout/out-of-flow-in-block.png
deleted file mode 100644
index 97637145..00000000
--- a/tests/ref/layout/out-of-flow-in-block.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/pad.png b/tests/ref/layout/pad.png
deleted file mode 100644
index d228f07f..00000000
--- a/tests/ref/layout/pad.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/page-binding.png b/tests/ref/layout/page-binding.png
deleted file mode 100644
index 5b6d0657..00000000
--- a/tests/ref/layout/page-binding.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/page-margin.png b/tests/ref/layout/page-margin.png
deleted file mode 100644
index f690724b..00000000
--- a/tests/ref/layout/page-margin.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/page-marginals.png b/tests/ref/layout/page-marginals.png
deleted file mode 100644
index bbe6358e..00000000
--- a/tests/ref/layout/page-marginals.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/page-number-align.png b/tests/ref/layout/page-number-align.png
deleted file mode 100644
index b05ca454..00000000
--- a/tests/ref/layout/page-number-align.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/page-style.png b/tests/ref/layout/page-style.png
deleted file mode 100644
index ac6b602c..00000000
--- a/tests/ref/layout/page-style.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/page.png b/tests/ref/layout/page.png
deleted file mode 100644
index bcf32526..00000000
--- a/tests/ref/layout/page.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/pagebreak-parity.png b/tests/ref/layout/pagebreak-parity.png
deleted file mode 100644
index 0dbabe7a..00000000
--- a/tests/ref/layout/pagebreak-parity.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/pagebreak-weak.png b/tests/ref/layout/pagebreak-weak.png
deleted file mode 100644
index 412c4e8d..00000000
--- a/tests/ref/layout/pagebreak-weak.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/pagebreak.png b/tests/ref/layout/pagebreak.png
deleted file mode 100644
index ab055643..00000000
--- a/tests/ref/layout/pagebreak.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/par-bidi.png b/tests/ref/layout/par-bidi.png
deleted file mode 100644
index af66a719..00000000
--- a/tests/ref/layout/par-bidi.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/par-indent.png b/tests/ref/layout/par-indent.png
deleted file mode 100644
index cceaa3b9..00000000
--- a/tests/ref/layout/par-indent.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/par-justify-cjk.png b/tests/ref/layout/par-justify-cjk.png
deleted file mode 100644
index 25adfcb2..00000000
--- a/tests/ref/layout/par-justify-cjk.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/par-justify.png b/tests/ref/layout/par-justify.png
deleted file mode 100644
index 0cd9cbcd..00000000
--- a/tests/ref/layout/par-justify.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/par-knuth.png b/tests/ref/layout/par-knuth.png
deleted file mode 100644
index f3da1753..00000000
--- a/tests/ref/layout/par-knuth.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/par-simple.png b/tests/ref/layout/par-simple.png
deleted file mode 100644
index a645bfd8..00000000
--- a/tests/ref/layout/par-simple.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/par.png b/tests/ref/layout/par.png
deleted file mode 100644
index f25f56d2..00000000
--- a/tests/ref/layout/par.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/place-background.png b/tests/ref/layout/place-background.png
deleted file mode 100644
index d9c1c42f..00000000
--- a/tests/ref/layout/place-background.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/place-float-auto.png b/tests/ref/layout/place-float-auto.png
deleted file mode 100644
index f2e4ee92..00000000
--- a/tests/ref/layout/place-float-auto.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/place-float-columns.png b/tests/ref/layout/place-float-columns.png
deleted file mode 100644
index 186b79d1..00000000
--- a/tests/ref/layout/place-float-columns.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/place-float-figure.png b/tests/ref/layout/place-float-figure.png
deleted file mode 100644
index bf9d21b4..00000000
--- a/tests/ref/layout/place-float-figure.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/place-nested.png b/tests/ref/layout/place-nested.png
deleted file mode 100644
index 864830d8..00000000
--- a/tests/ref/layout/place-nested.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/place.png b/tests/ref/layout/place.png
deleted file mode 100644
index 2ef85a4d..00000000
--- a/tests/ref/layout/place.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/repeat.png b/tests/ref/layout/repeat.png
deleted file mode 100644
index 8e21f102..00000000
--- a/tests/ref/layout/repeat.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/spacing.png b/tests/ref/layout/spacing.png
deleted file mode 100644
index 9bab536a..00000000
--- a/tests/ref/layout/spacing.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/stack-1.png b/tests/ref/layout/stack-1.png
deleted file mode 100644
index 1a3133b8..00000000
--- a/tests/ref/layout/stack-1.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/stack-2.png b/tests/ref/layout/stack-2.png
deleted file mode 100644
index 6cb0aad2..00000000
--- a/tests/ref/layout/stack-2.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/table-cell.png b/tests/ref/layout/table-cell.png
deleted file mode 100644
index d0c39363..00000000
--- a/tests/ref/layout/table-cell.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/table.png b/tests/ref/layout/table.png
deleted file mode 100644
index ddd0e043..00000000
--- a/tests/ref/layout/table.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/terms.png b/tests/ref/layout/terms.png
deleted file mode 100644
index e0cd013a..00000000
--- a/tests/ref/layout/terms.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/transform-layout.png b/tests/ref/layout/transform-layout.png
deleted file mode 100644
index 576824f0..00000000
--- a/tests/ref/layout/transform-layout.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/layout/transform.png b/tests/ref/layout/transform.png
deleted file mode 100644
index 83b7d13a..00000000
--- a/tests/ref/layout/transform.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/let-basic.png b/tests/ref/let-basic.png
new file mode 100644
index 00000000..ded47a5a
--- /dev/null
+++ b/tests/ref/let-basic.png
Binary files differ
diff --git a/tests/ref/let-termination.png b/tests/ref/let-termination.png
new file mode 100644
index 00000000..552bb4ce
--- /dev/null
+++ b/tests/ref/let-termination.png
Binary files differ
diff --git a/tests/ref/line-basic.png b/tests/ref/line-basic.png
new file mode 100644
index 00000000..007672e1
--- /dev/null
+++ b/tests/ref/line-basic.png
Binary files differ
diff --git a/tests/ref/line-positioning.png b/tests/ref/line-positioning.png
new file mode 100644
index 00000000..65678caa
--- /dev/null
+++ b/tests/ref/line-positioning.png
Binary files differ
diff --git a/tests/ref/line-stroke-dash.png b/tests/ref/line-stroke-dash.png
new file mode 100644
index 00000000..f245e32f
--- /dev/null
+++ b/tests/ref/line-stroke-dash.png
Binary files differ
diff --git a/tests/ref/line-stroke-set.png b/tests/ref/line-stroke-set.png
new file mode 100644
index 00000000..f82f899e
--- /dev/null
+++ b/tests/ref/line-stroke-set.png
Binary files differ
diff --git a/tests/ref/line-stroke.png b/tests/ref/line-stroke.png
new file mode 100644
index 00000000..d0213002
--- /dev/null
+++ b/tests/ref/line-stroke.png
Binary files differ
diff --git a/tests/ref/linebreak-cite-punctuation.png b/tests/ref/linebreak-cite-punctuation.png
new file mode 100644
index 00000000..64d930c6
--- /dev/null
+++ b/tests/ref/linebreak-cite-punctuation.png
Binary files differ
diff --git a/tests/ref/linebreak-hyphen-nbsp.png b/tests/ref/linebreak-hyphen-nbsp.png
new file mode 100644
index 00000000..ee88ae58
--- /dev/null
+++ b/tests/ref/linebreak-hyphen-nbsp.png
Binary files differ
diff --git a/tests/ref/linebreak-link-end.png b/tests/ref/linebreak-link-end.png
new file mode 100644
index 00000000..f11e91d6
--- /dev/null
+++ b/tests/ref/linebreak-link-end.png
Binary files differ
diff --git a/tests/ref/linebreak-link-justify.png b/tests/ref/linebreak-link-justify.png
new file mode 100644
index 00000000..8007cf3e
--- /dev/null
+++ b/tests/ref/linebreak-link-justify.png
Binary files differ
diff --git a/tests/ref/linebreak-link.png b/tests/ref/linebreak-link.png
new file mode 100644
index 00000000..d5ba8c9e
--- /dev/null
+++ b/tests/ref/linebreak-link.png
Binary files differ
diff --git a/tests/ref/linebreak-manual-consecutive.png b/tests/ref/linebreak-manual-consecutive.png
new file mode 100644
index 00000000..0dbef35b
--- /dev/null
+++ b/tests/ref/linebreak-manual-consecutive.png
Binary files differ
diff --git a/tests/ref/linebreak-manual-directly-after-automatic.png b/tests/ref/linebreak-manual-directly-after-automatic.png
new file mode 100644
index 00000000..006e3ef2
--- /dev/null
+++ b/tests/ref/linebreak-manual-directly-after-automatic.png
Binary files differ
diff --git a/tests/ref/linebreak-manual-justified.png b/tests/ref/linebreak-manual-justified.png
new file mode 100644
index 00000000..f74ea3fd
--- /dev/null
+++ b/tests/ref/linebreak-manual-justified.png
Binary files differ
diff --git a/tests/ref/linebreak-manual-trailing-multiple.png b/tests/ref/linebreak-manual-trailing-multiple.png
new file mode 100644
index 00000000..edf3a949
--- /dev/null
+++ b/tests/ref/linebreak-manual-trailing-multiple.png
Binary files differ
diff --git a/tests/ref/linebreak-manual.png b/tests/ref/linebreak-manual.png
new file mode 100644
index 00000000..37aca398
--- /dev/null
+++ b/tests/ref/linebreak-manual.png
Binary files differ
diff --git a/tests/ref/linebreak-math-punctuation.png b/tests/ref/linebreak-math-punctuation.png
new file mode 100644
index 00000000..93b77d2a
--- /dev/null
+++ b/tests/ref/linebreak-math-punctuation.png
Binary files differ
diff --git a/tests/ref/linebreak-narrow-nbsp.png b/tests/ref/linebreak-narrow-nbsp.png
new file mode 100644
index 00000000..81cf82f8
--- /dev/null
+++ b/tests/ref/linebreak-narrow-nbsp.png
Binary files differ
diff --git a/tests/ref/linebreak-overflow-double.png b/tests/ref/linebreak-overflow-double.png
new file mode 100644
index 00000000..04a5bbaa
--- /dev/null
+++ b/tests/ref/linebreak-overflow-double.png
Binary files differ
diff --git a/tests/ref/linebreak-overflow.png b/tests/ref/linebreak-overflow.png
new file mode 100644
index 00000000..1dfcbc27
--- /dev/null
+++ b/tests/ref/linebreak-overflow.png
Binary files differ
diff --git a/tests/ref/linebreak-shape-run.png b/tests/ref/linebreak-shape-run.png
new file mode 100644
index 00000000..ebfb87f0
--- /dev/null
+++ b/tests/ref/linebreak-shape-run.png
Binary files differ
diff --git a/tests/ref/linebreak-thai.png b/tests/ref/linebreak-thai.png
new file mode 100644
index 00000000..8053a212
--- /dev/null
+++ b/tests/ref/linebreak-thai.png
Binary files differ
diff --git a/tests/ref/link-basic.png b/tests/ref/link-basic.png
new file mode 100644
index 00000000..d16c7ef1
--- /dev/null
+++ b/tests/ref/link-basic.png
Binary files differ
diff --git a/tests/ref/link-bracket-balanced.png b/tests/ref/link-bracket-balanced.png
new file mode 100644
index 00000000..048a7c52
--- /dev/null
+++ b/tests/ref/link-bracket-balanced.png
Binary files differ
diff --git a/tests/ref/link-bracket-unbalanced-closing.png b/tests/ref/link-bracket-unbalanced-closing.png
new file mode 100644
index 00000000..e1c1341c
--- /dev/null
+++ b/tests/ref/link-bracket-unbalanced-closing.png
Binary files differ
diff --git a/tests/ref/link-on-block.png b/tests/ref/link-on-block.png
new file mode 100644
index 00000000..9076983d
--- /dev/null
+++ b/tests/ref/link-on-block.png
Binary files differ
diff --git a/tests/ref/link-show.png b/tests/ref/link-show.png
new file mode 100644
index 00000000..59542bad
--- /dev/null
+++ b/tests/ref/link-show.png
Binary files differ
diff --git a/tests/ref/link-to-label.png b/tests/ref/link-to-label.png
new file mode 100644
index 00000000..f6075526
--- /dev/null
+++ b/tests/ref/link-to-label.png
Binary files differ
diff --git a/tests/ref/link-to-page.png b/tests/ref/link-to-page.png
new file mode 100644
index 00000000..bbd2f103
--- /dev/null
+++ b/tests/ref/link-to-page.png
Binary files differ
diff --git a/tests/ref/link-trailing-period.png b/tests/ref/link-trailing-period.png
new file mode 100644
index 00000000..4dd11f34
--- /dev/null
+++ b/tests/ref/link-trailing-period.png
Binary files differ
diff --git a/tests/ref/link-transformed.png b/tests/ref/link-transformed.png
new file mode 100644
index 00000000..6b94b5cb
--- /dev/null
+++ b/tests/ref/link-transformed.png
Binary files differ
diff --git a/tests/ref/list-attached-above-spacing.png b/tests/ref/list-attached-above-spacing.png
new file mode 100644
index 00000000..0f499769
--- /dev/null
+++ b/tests/ref/list-attached-above-spacing.png
Binary files differ
diff --git a/tests/ref/list-attached.png b/tests/ref/list-attached.png
new file mode 100644
index 00000000..c1735fd0
--- /dev/null
+++ b/tests/ref/list-attached.png
Binary files differ
diff --git a/tests/ref/list-basic.png b/tests/ref/list-basic.png
new file mode 100644
index 00000000..edf69cac
--- /dev/null
+++ b/tests/ref/list-basic.png
Binary files differ
diff --git a/tests/ref/list-content-block.png b/tests/ref/list-content-block.png
new file mode 100644
index 00000000..18b003e5
--- /dev/null
+++ b/tests/ref/list-content-block.png
Binary files differ
diff --git a/tests/ref/list-indent-specifics.png b/tests/ref/list-indent-specifics.png
new file mode 100644
index 00000000..212e45ed
--- /dev/null
+++ b/tests/ref/list-indent-specifics.png
Binary files differ
diff --git a/tests/ref/list-marker-align-unaffected.png b/tests/ref/list-marker-align-unaffected.png
new file mode 100644
index 00000000..90f9ad45
--- /dev/null
+++ b/tests/ref/list-marker-align-unaffected.png
Binary files differ
diff --git a/tests/ref/list-marker-bare-hyphen.png b/tests/ref/list-marker-bare-hyphen.png
new file mode 100644
index 00000000..37830fd6
--- /dev/null
+++ b/tests/ref/list-marker-bare-hyphen.png
Binary files differ
diff --git a/tests/ref/list-marker-closure.png b/tests/ref/list-marker-closure.png
new file mode 100644
index 00000000..4dba3b9d
--- /dev/null
+++ b/tests/ref/list-marker-closure.png
Binary files differ
diff --git a/tests/ref/list-marker-cycle.png b/tests/ref/list-marker-cycle.png
new file mode 100644
index 00000000..ef219f07
--- /dev/null
+++ b/tests/ref/list-marker-cycle.png
Binary files differ
diff --git a/tests/ref/list-marker-dash.png b/tests/ref/list-marker-dash.png
new file mode 100644
index 00000000..10abc8a6
--- /dev/null
+++ b/tests/ref/list-marker-dash.png
Binary files differ
diff --git a/tests/ref/list-mix.png b/tests/ref/list-mix.png
new file mode 100644
index 00000000..0f2b03cf
--- /dev/null
+++ b/tests/ref/list-mix.png
Binary files differ
diff --git a/tests/ref/list-mixed-tabs-and-spaces.png b/tests/ref/list-mixed-tabs-and-spaces.png
new file mode 100644
index 00000000..fcddff42
--- /dev/null
+++ b/tests/ref/list-mixed-tabs-and-spaces.png
Binary files differ
diff --git a/tests/ref/list-nested.png b/tests/ref/list-nested.png
new file mode 100644
index 00000000..22f73ecb
--- /dev/null
+++ b/tests/ref/list-nested.png
Binary files differ
diff --git a/tests/ref/list-non-attached-followed-by-attached.png b/tests/ref/list-non-attached-followed-by-attached.png
new file mode 100644
index 00000000..22db4e38
--- /dev/null
+++ b/tests/ref/list-non-attached-followed-by-attached.png
Binary files differ
diff --git a/tests/ref/list-rtl.png b/tests/ref/list-rtl.png
new file mode 100644
index 00000000..db1e7546
--- /dev/null
+++ b/tests/ref/list-rtl.png
Binary files differ
diff --git a/tests/ref/list-syntax-edge-cases.png b/tests/ref/list-syntax-edge-cases.png
new file mode 100644
index 00000000..460462e3
--- /dev/null
+++ b/tests/ref/list-syntax-edge-cases.png
Binary files differ
diff --git a/tests/ref/list-tabs.png b/tests/ref/list-tabs.png
new file mode 100644
index 00000000..1fce74c3
--- /dev/null
+++ b/tests/ref/list-tabs.png
Binary files differ
diff --git a/tests/ref/list-tight-non-attached-tight.png b/tests/ref/list-tight-non-attached-tight.png
new file mode 100644
index 00000000..96d51813
--- /dev/null
+++ b/tests/ref/list-tight-non-attached-tight.png
Binary files differ
diff --git a/tests/ref/list-top-level-indent.png b/tests/ref/list-top-level-indent.png
new file mode 100644
index 00000000..beb17ede
--- /dev/null
+++ b/tests/ref/list-top-level-indent.png
Binary files differ
diff --git a/tests/ref/list-wide-cannot-attach.png b/tests/ref/list-wide-cannot-attach.png
new file mode 100644
index 00000000..600041a7
--- /dev/null
+++ b/tests/ref/list-wide-cannot-attach.png
Binary files differ
diff --git a/tests/ref/list-wide-really-cannot-attach.png b/tests/ref/list-wide-really-cannot-attach.png
new file mode 100644
index 00000000..89680c09
--- /dev/null
+++ b/tests/ref/list-wide-really-cannot-attach.png
Binary files differ
diff --git a/tests/ref/locate-element-selector.png b/tests/ref/locate-element-selector.png
new file mode 100644
index 00000000..fc36ddff
--- /dev/null
+++ b/tests/ref/locate-element-selector.png
Binary files differ
diff --git a/tests/ref/locate-position.png b/tests/ref/locate-position.png
new file mode 100644
index 00000000..fc36ddff
--- /dev/null
+++ b/tests/ref/locate-position.png
Binary files differ
diff --git a/tests/ref/loop-break-join-in-first-arg.png b/tests/ref/loop-break-join-in-first-arg.png
new file mode 100644
index 00000000..fbad2125
--- /dev/null
+++ b/tests/ref/loop-break-join-in-first-arg.png
Binary files differ
diff --git a/tests/ref/loop-break-join-in-nested-blocks.png b/tests/ref/loop-break-join-in-nested-blocks.png
new file mode 100644
index 00000000..6e2af47a
--- /dev/null
+++ b/tests/ref/loop-break-join-in-nested-blocks.png
Binary files differ
diff --git a/tests/ref/loop-break-join-in-set-rule-args.png b/tests/ref/loop-break-join-in-set-rule-args.png
new file mode 100644
index 00000000..37e13773
--- /dev/null
+++ b/tests/ref/loop-break-join-in-set-rule-args.png
Binary files differ
diff --git a/tests/ref/loop-break-join-set-and-show.png b/tests/ref/loop-break-join-set-and-show.png
new file mode 100644
index 00000000..8c81c147
--- /dev/null
+++ b/tests/ref/loop-break-join-set-and-show.png
Binary files differ
diff --git a/tests/ref/lorem-pars.png b/tests/ref/lorem-pars.png
new file mode 100644
index 00000000..5ff0a345
--- /dev/null
+++ b/tests/ref/lorem-pars.png
Binary files differ
diff --git a/tests/ref/lorem.png b/tests/ref/lorem.png
new file mode 100644
index 00000000..197acb1c
--- /dev/null
+++ b/tests/ref/lorem.png
Binary files differ
diff --git a/tests/ref/math-accent-align.png b/tests/ref/math-accent-align.png
new file mode 100644
index 00000000..84e8dc8c
--- /dev/null
+++ b/tests/ref/math-accent-align.png
Binary files differ
diff --git a/tests/ref/math-accent-bounds.png b/tests/ref/math-accent-bounds.png
new file mode 100644
index 00000000..d9876182
--- /dev/null
+++ b/tests/ref/math-accent-bounds.png
Binary files differ
diff --git a/tests/ref/math-accent-func.png b/tests/ref/math-accent-func.png
new file mode 100644
index 00000000..00821f70
--- /dev/null
+++ b/tests/ref/math-accent-func.png
Binary files differ
diff --git a/tests/ref/math-accent-high-base.png b/tests/ref/math-accent-high-base.png
new file mode 100644
index 00000000..f4d7580f
--- /dev/null
+++ b/tests/ref/math-accent-high-base.png
Binary files differ
diff --git a/tests/ref/math-accent-sized.png b/tests/ref/math-accent-sized.png
new file mode 100644
index 00000000..76783b25
--- /dev/null
+++ b/tests/ref/math-accent-sized.png
Binary files differ
diff --git a/tests/ref/math-accent-superscript.png b/tests/ref/math-accent-superscript.png
new file mode 100644
index 00000000..8ddf113d
--- /dev/null
+++ b/tests/ref/math-accent-superscript.png
Binary files differ
diff --git a/tests/ref/math-accent-sym-call.png b/tests/ref/math-accent-sym-call.png
new file mode 100644
index 00000000..0837a86c
--- /dev/null
+++ b/tests/ref/math-accent-sym-call.png
Binary files differ
diff --git a/tests/ref/math-accent-wide-base.png b/tests/ref/math-accent-wide-base.png
new file mode 100644
index 00000000..af716bf4
--- /dev/null
+++ b/tests/ref/math-accent-wide-base.png
Binary files differ
diff --git a/tests/ref/math-align-aligned-in-source.png b/tests/ref/math-align-aligned-in-source.png
new file mode 100644
index 00000000..958a42c5
--- /dev/null
+++ b/tests/ref/math-align-aligned-in-source.png
Binary files differ
diff --git a/tests/ref/math-align-basic.png b/tests/ref/math-align-basic.png
new file mode 100644
index 00000000..cf4a8d6a
--- /dev/null
+++ b/tests/ref/math-align-basic.png
Binary files differ
diff --git a/tests/ref/math-align-cases.png b/tests/ref/math-align-cases.png
new file mode 100644
index 00000000..4ea9a264
--- /dev/null
+++ b/tests/ref/math-align-cases.png
Binary files differ
diff --git a/tests/ref/math-align-implicit.png b/tests/ref/math-align-implicit.png
new file mode 100644
index 00000000..05a0d98d
--- /dev/null
+++ b/tests/ref/math-align-implicit.png
Binary files differ
diff --git a/tests/ref/math-align-lines-mixed.png b/tests/ref/math-align-lines-mixed.png
new file mode 100644
index 00000000..d50af28c
--- /dev/null
+++ b/tests/ref/math-align-lines-mixed.png
Binary files differ
diff --git a/tests/ref/math-align-post-fix.png b/tests/ref/math-align-post-fix.png
new file mode 100644
index 00000000..33bc3da7
--- /dev/null
+++ b/tests/ref/math-align-post-fix.png
Binary files differ
diff --git a/tests/ref/math-align-toggle.png b/tests/ref/math-align-toggle.png
new file mode 100644
index 00000000..24448ab5
--- /dev/null
+++ b/tests/ref/math-align-toggle.png
Binary files differ
diff --git a/tests/ref/math-align-weird.png b/tests/ref/math-align-weird.png
new file mode 100644
index 00000000..672742ec
--- /dev/null
+++ b/tests/ref/math-align-weird.png
Binary files differ
diff --git a/tests/ref/math-align-wider-first-column.png b/tests/ref/math-align-wider-first-column.png
new file mode 100644
index 00000000..46c9c3ff
--- /dev/null
+++ b/tests/ref/math-align-wider-first-column.png
Binary files differ
diff --git a/tests/ref/math-attach-default-placement.png b/tests/ref/math-attach-default-placement.png
new file mode 100644
index 00000000..685fb550
--- /dev/null
+++ b/tests/ref/math-attach-default-placement.png
Binary files differ
diff --git a/tests/ref/math-attach-descender-collision.png b/tests/ref/math-attach-descender-collision.png
new file mode 100644
index 00000000..71654916
--- /dev/null
+++ b/tests/ref/math-attach-descender-collision.png
Binary files differ
diff --git a/tests/ref/math-attach-followed-by-func-call.png b/tests/ref/math-attach-followed-by-func-call.png
new file mode 100644
index 00000000..71d78c16
--- /dev/null
+++ b/tests/ref/math-attach-followed-by-func-call.png
Binary files differ
diff --git a/tests/ref/math-attach-force-scripts-and-limits.png b/tests/ref/math-attach-force-scripts-and-limits.png
new file mode 100644
index 00000000..21a1050f
--- /dev/null
+++ b/tests/ref/math-attach-force-scripts-and-limits.png
Binary files differ
diff --git a/tests/ref/math-attach-high.png b/tests/ref/math-attach-high.png
new file mode 100644
index 00000000..4bb6cb29
--- /dev/null
+++ b/tests/ref/math-attach-high.png
Binary files differ
diff --git a/tests/ref/math-attach-horizontal-align.png b/tests/ref/math-attach-horizontal-align.png
new file mode 100644
index 00000000..507cb0ff
--- /dev/null
+++ b/tests/ref/math-attach-horizontal-align.png
Binary files differ
diff --git a/tests/ref/math-attach-integral.png b/tests/ref/math-attach-integral.png
new file mode 100644
index 00000000..baebf44c
--- /dev/null
+++ b/tests/ref/math-attach-integral.png
Binary files differ
diff --git a/tests/ref/math-attach-large-operator.png b/tests/ref/math-attach-large-operator.png
new file mode 100644
index 00000000..774b603c
--- /dev/null
+++ b/tests/ref/math-attach-large-operator.png
Binary files differ
diff --git a/tests/ref/math-attach-limit.png b/tests/ref/math-attach-limit.png
new file mode 100644
index 00000000..5f9f24d7
--- /dev/null
+++ b/tests/ref/math-attach-limit.png
Binary files differ
diff --git a/tests/ref/math-attach-mixed.png b/tests/ref/math-attach-mixed.png
new file mode 100644
index 00000000..4be327e3
--- /dev/null
+++ b/tests/ref/math-attach-mixed.png
Binary files differ
diff --git a/tests/ref/math-attach-nested.png b/tests/ref/math-attach-nested.png
new file mode 100644
index 00000000..8b4309cf
--- /dev/null
+++ b/tests/ref/math-attach-nested.png
Binary files differ
diff --git a/tests/ref/math-attach-postscripts.png b/tests/ref/math-attach-postscripts.png
new file mode 100644
index 00000000..bd94e4bd
--- /dev/null
+++ b/tests/ref/math-attach-postscripts.png
Binary files differ
diff --git a/tests/ref/math-attach-prescripts.png b/tests/ref/math-attach-prescripts.png
new file mode 100644
index 00000000..cd105e9d
--- /dev/null
+++ b/tests/ref/math-attach-prescripts.png
Binary files differ
diff --git a/tests/ref/math-attach-show-limit.png b/tests/ref/math-attach-show-limit.png
new file mode 100644
index 00000000..4ce2b3fb
--- /dev/null
+++ b/tests/ref/math-attach-show-limit.png
Binary files differ
diff --git a/tests/ref/math-attach-subscript-multiline.png b/tests/ref/math-attach-subscript-multiline.png
new file mode 100644
index 00000000..7f9aec2e
--- /dev/null
+++ b/tests/ref/math-attach-subscript-multiline.png
Binary files differ
diff --git a/tests/ref/math-attach-to-group.png b/tests/ref/math-attach-to-group.png
new file mode 100644
index 00000000..a3d1923e
--- /dev/null
+++ b/tests/ref/math-attach-to-group.png
Binary files differ
diff --git a/tests/ref/math-binom-multiple.png b/tests/ref/math-binom-multiple.png
new file mode 100644
index 00000000..7eb60be0
--- /dev/null
+++ b/tests/ref/math-binom-multiple.png
Binary files differ
diff --git a/tests/ref/math-binom.png b/tests/ref/math-binom.png
new file mode 100644
index 00000000..85ab08f9
--- /dev/null
+++ b/tests/ref/math-binom.png
Binary files differ
diff --git a/tests/ref/math-box-with-baseline.png b/tests/ref/math-box-with-baseline.png
new file mode 100644
index 00000000..e38e6442
--- /dev/null
+++ b/tests/ref/math-box-with-baseline.png
Binary files differ
diff --git a/tests/ref/math-box-without-baseline.png b/tests/ref/math-box-without-baseline.png
new file mode 100644
index 00000000..27549587
--- /dev/null
+++ b/tests/ref/math-box-without-baseline.png
Binary files differ
diff --git a/tests/ref/math-call-non-func.png b/tests/ref/math-call-non-func.png
new file mode 100644
index 00000000..da46efc9
--- /dev/null
+++ b/tests/ref/math-call-non-func.png
Binary files differ
diff --git a/tests/ref/math-cancel-angle-absolute.png b/tests/ref/math-cancel-angle-absolute.png
new file mode 100644
index 00000000..125e59fb
--- /dev/null
+++ b/tests/ref/math-cancel-angle-absolute.png
Binary files differ
diff --git a/tests/ref/math-cancel-angle-func.png b/tests/ref/math-cancel-angle-func.png
new file mode 100644
index 00000000..54f6e759
--- /dev/null
+++ b/tests/ref/math-cancel-angle-func.png
Binary files differ
diff --git a/tests/ref/math-cancel-cross.png b/tests/ref/math-cancel-cross.png
new file mode 100644
index 00000000..49fba664
--- /dev/null
+++ b/tests/ref/math-cancel-cross.png
Binary files differ
diff --git a/tests/ref/math-cancel-customized.png b/tests/ref/math-cancel-customized.png
new file mode 100644
index 00000000..9fa5045d
--- /dev/null
+++ b/tests/ref/math-cancel-customized.png
Binary files differ
diff --git a/tests/ref/math-cancel-display.png b/tests/ref/math-cancel-display.png
new file mode 100644
index 00000000..30d30a59
--- /dev/null
+++ b/tests/ref/math-cancel-display.png
Binary files differ
diff --git a/tests/ref/math-cancel-inline.png b/tests/ref/math-cancel-inline.png
new file mode 100644
index 00000000..4d92bc5e
--- /dev/null
+++ b/tests/ref/math-cancel-inline.png
Binary files differ
diff --git a/tests/ref/math-cancel-inverted.png b/tests/ref/math-cancel-inverted.png
new file mode 100644
index 00000000..129d53a0
--- /dev/null
+++ b/tests/ref/math-cancel-inverted.png
Binary files differ
diff --git a/tests/ref/math-cases-gap.png b/tests/ref/math-cases-gap.png
new file mode 100644
index 00000000..e3579130
--- /dev/null
+++ b/tests/ref/math-cases-gap.png
Binary files differ
diff --git a/tests/ref/math-cases.png b/tests/ref/math-cases.png
new file mode 100644
index 00000000..2e8c260a
--- /dev/null
+++ b/tests/ref/math-cases.png
Binary files differ
diff --git a/tests/ref/math-class-chars.png b/tests/ref/math-class-chars.png
new file mode 100644
index 00000000..a4f7d29b
--- /dev/null
+++ b/tests/ref/math-class-chars.png
Binary files differ
diff --git a/tests/ref/math-class-content.png b/tests/ref/math-class-content.png
new file mode 100644
index 00000000..47603fb4
--- /dev/null
+++ b/tests/ref/math-class-content.png
Binary files differ
diff --git a/tests/ref/math-class-exceptions.png b/tests/ref/math-class-exceptions.png
new file mode 100644
index 00000000..8b3ecc81
--- /dev/null
+++ b/tests/ref/math-class-exceptions.png
Binary files differ
diff --git a/tests/ref/math-class-limits.png b/tests/ref/math-class-limits.png
new file mode 100644
index 00000000..140acf95
--- /dev/null
+++ b/tests/ref/math-class-limits.png
Binary files differ
diff --git a/tests/ref/math-class-nested.png b/tests/ref/math-class-nested.png
new file mode 100644
index 00000000..5847868e
--- /dev/null
+++ b/tests/ref/math-class-nested.png
Binary files differ
diff --git a/tests/ref/math-common-symbols.png b/tests/ref/math-common-symbols.png
new file mode 100644
index 00000000..22da84b5
--- /dev/null
+++ b/tests/ref/math-common-symbols.png
Binary files differ
diff --git a/tests/ref/math-dif.png b/tests/ref/math-dif.png
new file mode 100644
index 00000000..dfe88b3c
--- /dev/null
+++ b/tests/ref/math-dif.png
Binary files differ
diff --git a/tests/ref/math-equation-align-numbered.png b/tests/ref/math-equation-align-numbered.png
new file mode 100644
index 00000000..e43054c8
--- /dev/null
+++ b/tests/ref/math-equation-align-numbered.png
Binary files differ
diff --git a/tests/ref/math-equation-align-unnumbered.png b/tests/ref/math-equation-align-unnumbered.png
new file mode 100644
index 00000000..413da120
--- /dev/null
+++ b/tests/ref/math-equation-align-unnumbered.png
Binary files differ
diff --git a/tests/ref/math-equation-auto-wrapping.png b/tests/ref/math-equation-auto-wrapping.png
new file mode 100644
index 00000000..9c600172
--- /dev/null
+++ b/tests/ref/math-equation-auto-wrapping.png
Binary files differ
diff --git a/tests/ref/math-equation-font.png b/tests/ref/math-equation-font.png
new file mode 100644
index 00000000..b105d9e3
--- /dev/null
+++ b/tests/ref/math-equation-font.png
Binary files differ
diff --git a/tests/ref/math-equation-number-align-end.png b/tests/ref/math-equation-number-align-end.png
new file mode 100644
index 00000000..f60a15ec
--- /dev/null
+++ b/tests/ref/math-equation-number-align-end.png
Binary files differ
diff --git a/tests/ref/math-equation-number-align-left.png b/tests/ref/math-equation-number-align-left.png
new file mode 100644
index 00000000..a8ed40a5
--- /dev/null
+++ b/tests/ref/math-equation-number-align-left.png
Binary files differ
diff --git a/tests/ref/math-equation-number-align-multiline-bottom.png b/tests/ref/math-equation-number-align-multiline-bottom.png
new file mode 100644
index 00000000..cb0e5daa
--- /dev/null
+++ b/tests/ref/math-equation-number-align-multiline-bottom.png
Binary files differ
diff --git a/tests/ref/math-equation-number-align-multiline-expand.png b/tests/ref/math-equation-number-align-multiline-expand.png
new file mode 100644
index 00000000..3c3cdc05
--- /dev/null
+++ b/tests/ref/math-equation-number-align-multiline-expand.png
Binary files differ
diff --git a/tests/ref/math-equation-number-align-multiline-top-start.png b/tests/ref/math-equation-number-align-multiline-top-start.png
new file mode 100644
index 00000000..43346de9
--- /dev/null
+++ b/tests/ref/math-equation-number-align-multiline-top-start.png
Binary files differ
diff --git a/tests/ref/math-equation-number-align-multiline.png b/tests/ref/math-equation-number-align-multiline.png
new file mode 100644
index 00000000..a46bc1e9
--- /dev/null
+++ b/tests/ref/math-equation-number-align-multiline.png
Binary files differ
diff --git a/tests/ref/math-equation-number-align-right.png b/tests/ref/math-equation-number-align-right.png
new file mode 100644
index 00000000..e3d588c4
--- /dev/null
+++ b/tests/ref/math-equation-number-align-right.png
Binary files differ
diff --git a/tests/ref/math-equation-number-align-start.png b/tests/ref/math-equation-number-align-start.png
new file mode 100644
index 00000000..67ed3c4c
--- /dev/null
+++ b/tests/ref/math-equation-number-align-start.png
Binary files differ
diff --git a/tests/ref/math-equation-number-align.png b/tests/ref/math-equation-number-align.png
new file mode 100644
index 00000000..f60a15ec
--- /dev/null
+++ b/tests/ref/math-equation-number-align.png
Binary files differ
diff --git a/tests/ref/math-equation-numbering.png b/tests/ref/math-equation-numbering.png
new file mode 100644
index 00000000..b1e6b10e
--- /dev/null
+++ b/tests/ref/math-equation-numbering.png
Binary files differ
diff --git a/tests/ref/math-equation-show-rule.png b/tests/ref/math-equation-show-rule.png
new file mode 100644
index 00000000..26da7cd1
--- /dev/null
+++ b/tests/ref/math-equation-show-rule.png
Binary files differ
diff --git a/tests/ref/math-font-fallback.png b/tests/ref/math-font-fallback.png
new file mode 100644
index 00000000..50fa85c7
--- /dev/null
+++ b/tests/ref/math-font-fallback.png
Binary files differ
diff --git a/tests/ref/math-font-features.png b/tests/ref/math-font-features.png
new file mode 100644
index 00000000..0fd5e6e1
--- /dev/null
+++ b/tests/ref/math-font-features.png
Binary files differ
diff --git a/tests/ref/math-font-switch.png b/tests/ref/math-font-switch.png
new file mode 100644
index 00000000..4c572ea5
--- /dev/null
+++ b/tests/ref/math-font-switch.png
Binary files differ
diff --git a/tests/ref/math-frac-associativity.png b/tests/ref/math-frac-associativity.png
new file mode 100644
index 00000000..a5daca59
--- /dev/null
+++ b/tests/ref/math-frac-associativity.png
Binary files differ
diff --git a/tests/ref/math-frac-baseline.png b/tests/ref/math-frac-baseline.png
new file mode 100644
index 00000000..d65e2c33
--- /dev/null
+++ b/tests/ref/math-frac-baseline.png
Binary files differ
diff --git a/tests/ref/math-frac-large.png b/tests/ref/math-frac-large.png
new file mode 100644
index 00000000..ff9520f3
--- /dev/null
+++ b/tests/ref/math-frac-large.png
Binary files differ
diff --git a/tests/ref/math-frac-paren-removal.png b/tests/ref/math-frac-paren-removal.png
new file mode 100644
index 00000000..4f58f1d3
--- /dev/null
+++ b/tests/ref/math-frac-paren-removal.png
Binary files differ
diff --git a/tests/ref/math-frac-precedence.png b/tests/ref/math-frac-precedence.png
new file mode 100644
index 00000000..236b9989
--- /dev/null
+++ b/tests/ref/math-frac-precedence.png
Binary files differ
diff --git a/tests/ref/math-linebreaking-after-binop-and-rel.png b/tests/ref/math-linebreaking-after-binop-and-rel.png
new file mode 100644
index 00000000..1cdd73c4
--- /dev/null
+++ b/tests/ref/math-linebreaking-after-binop-and-rel.png
Binary files differ
diff --git a/tests/ref/math-linebreaking-after-relation-without-space.png b/tests/ref/math-linebreaking-after-relation-without-space.png
new file mode 100644
index 00000000..7c569ad1
--- /dev/null
+++ b/tests/ref/math-linebreaking-after-relation-without-space.png
Binary files differ
diff --git a/tests/ref/math-linebreaking-between-consecutive-relations.png b/tests/ref/math-linebreaking-between-consecutive-relations.png
new file mode 100644
index 00000000..ba222c57
--- /dev/null
+++ b/tests/ref/math-linebreaking-between-consecutive-relations.png
Binary files differ
diff --git a/tests/ref/math-linebreaking-empty.png b/tests/ref/math-linebreaking-empty.png
new file mode 100644
index 00000000..8fd4dbb9
--- /dev/null
+++ b/tests/ref/math-linebreaking-empty.png
Binary files differ
diff --git a/tests/ref/math-linebreaking-in-box.png b/tests/ref/math-linebreaking-in-box.png
new file mode 100644
index 00000000..e026f1a2
--- /dev/null
+++ b/tests/ref/math-linebreaking-in-box.png
Binary files differ
diff --git a/tests/ref/math-linebreaking-lr.png b/tests/ref/math-linebreaking-lr.png
new file mode 100644
index 00000000..69f08e7e
--- /dev/null
+++ b/tests/ref/math-linebreaking-lr.png
Binary files differ
diff --git a/tests/ref/math-linebreaking-multiline.png b/tests/ref/math-linebreaking-multiline.png
new file mode 100644
index 00000000..cd5f2fce
--- /dev/null
+++ b/tests/ref/math-linebreaking-multiline.png
Binary files differ
diff --git a/tests/ref/math-linebreaking-trailing-linebreak.png b/tests/ref/math-linebreaking-trailing-linebreak.png
new file mode 100644
index 00000000..4a50832b
--- /dev/null
+++ b/tests/ref/math-linebreaking-trailing-linebreak.png
Binary files differ
diff --git a/tests/ref/math-lr-call.png b/tests/ref/math-lr-call.png
new file mode 100644
index 00000000..baf668d4
--- /dev/null
+++ b/tests/ref/math-lr-call.png
Binary files differ
diff --git a/tests/ref/math-lr-color.png b/tests/ref/math-lr-color.png
new file mode 100644
index 00000000..66876819
--- /dev/null
+++ b/tests/ref/math-lr-color.png
Binary files differ
diff --git a/tests/ref/math-lr-fences.png b/tests/ref/math-lr-fences.png
new file mode 100644
index 00000000..32314cb4
--- /dev/null
+++ b/tests/ref/math-lr-fences.png
Binary files differ
diff --git a/tests/ref/math-lr-half.png b/tests/ref/math-lr-half.png
new file mode 100644
index 00000000..311188b4
--- /dev/null
+++ b/tests/ref/math-lr-half.png
Binary files differ
diff --git a/tests/ref/math-lr-matching.png b/tests/ref/math-lr-matching.png
new file mode 100644
index 00000000..e5fd4c7b
--- /dev/null
+++ b/tests/ref/math-lr-matching.png
Binary files differ
diff --git a/tests/ref/math-lr-mid.png b/tests/ref/math-lr-mid.png
new file mode 100644
index 00000000..e4f1e671
--- /dev/null
+++ b/tests/ref/math-lr-mid.png
Binary files differ
diff --git a/tests/ref/math-lr-shorthands.png b/tests/ref/math-lr-shorthands.png
new file mode 100644
index 00000000..d8961672
--- /dev/null
+++ b/tests/ref/math-lr-shorthands.png
Binary files differ
diff --git a/tests/ref/math-lr-size.png b/tests/ref/math-lr-size.png
new file mode 100644
index 00000000..09d24421
--- /dev/null
+++ b/tests/ref/math-lr-size.png
Binary files differ
diff --git a/tests/ref/math-lr-symbol-unmatched.png b/tests/ref/math-lr-symbol-unmatched.png
new file mode 100644
index 00000000..38d0a988
--- /dev/null
+++ b/tests/ref/math-lr-symbol-unmatched.png
Binary files differ
diff --git a/tests/ref/math-lr-unbalanced.png b/tests/ref/math-lr-unbalanced.png
new file mode 100644
index 00000000..eff579ba
--- /dev/null
+++ b/tests/ref/math-lr-unbalanced.png
Binary files differ
diff --git a/tests/ref/math-lr-unmatched.png b/tests/ref/math-lr-unmatched.png
new file mode 100644
index 00000000..9a0f3275
--- /dev/null
+++ b/tests/ref/math-lr-unmatched.png
Binary files differ
diff --git a/tests/ref/math-lr-weak-spacing.png b/tests/ref/math-lr-weak-spacing.png
new file mode 100644
index 00000000..871aaa2e
--- /dev/null
+++ b/tests/ref/math-lr-weak-spacing.png
Binary files differ
diff --git a/tests/ref/math-mat-align-complex.png b/tests/ref/math-mat-align-complex.png
new file mode 100644
index 00000000..682fed22
--- /dev/null
+++ b/tests/ref/math-mat-align-complex.png
Binary files differ
diff --git a/tests/ref/math-mat-align-explicit--alternating.png b/tests/ref/math-mat-align-explicit--alternating.png
new file mode 100644
index 00000000..cb29eb06
--- /dev/null
+++ b/tests/ref/math-mat-align-explicit--alternating.png
Binary files differ
diff --git a/tests/ref/math-mat-align-explicit-left.png b/tests/ref/math-mat-align-explicit-left.png
new file mode 100644
index 00000000..97fe0a1f
--- /dev/null
+++ b/tests/ref/math-mat-align-explicit-left.png
Binary files differ
diff --git a/tests/ref/math-mat-align-explicit-right.png b/tests/ref/math-mat-align-explicit-right.png
new file mode 100644
index 00000000..80966e52
--- /dev/null
+++ b/tests/ref/math-mat-align-explicit-right.png
Binary files differ
diff --git a/tests/ref/math-mat-align-implicit.png b/tests/ref/math-mat-align-implicit.png
new file mode 100644
index 00000000..0c14f1a7
--- /dev/null
+++ b/tests/ref/math-mat-align-implicit.png
Binary files differ
diff --git a/tests/ref/math-mat-align-signed-numbers.png b/tests/ref/math-mat-align-signed-numbers.png
new file mode 100644
index 00000000..02a3c582
--- /dev/null
+++ b/tests/ref/math-mat-align-signed-numbers.png
Binary files differ
diff --git a/tests/ref/math-mat-augment-set.png b/tests/ref/math-mat-augment-set.png
new file mode 100644
index 00000000..f3827c41
--- /dev/null
+++ b/tests/ref/math-mat-augment-set.png
Binary files differ
diff --git a/tests/ref/math-mat-augment.png b/tests/ref/math-mat-augment.png
new file mode 100644
index 00000000..3a272ce9
--- /dev/null
+++ b/tests/ref/math-mat-augment.png
Binary files differ
diff --git a/tests/ref/math-mat-baseline.png b/tests/ref/math-mat-baseline.png
new file mode 100644
index 00000000..51e90a1f
--- /dev/null
+++ b/tests/ref/math-mat-baseline.png
Binary files differ
diff --git a/tests/ref/math-mat-delim-direct.png b/tests/ref/math-mat-delim-direct.png
new file mode 100644
index 00000000..b40fd36c
--- /dev/null
+++ b/tests/ref/math-mat-delim-direct.png
Binary files differ
diff --git a/tests/ref/math-mat-delim-set.png b/tests/ref/math-mat-delim-set.png
new file mode 100644
index 00000000..fc92fd4b
--- /dev/null
+++ b/tests/ref/math-mat-delim-set.png
Binary files differ
diff --git a/tests/ref/math-mat-gap.png b/tests/ref/math-mat-gap.png
new file mode 100644
index 00000000..5eb8460d
--- /dev/null
+++ b/tests/ref/math-mat-gap.png
Binary files differ
diff --git a/tests/ref/math-mat-gaps.png b/tests/ref/math-mat-gaps.png
new file mode 100644
index 00000000..38cf5247
--- /dev/null
+++ b/tests/ref/math-mat-gaps.png
Binary files differ
diff --git a/tests/ref/math-mat-semicolon.png b/tests/ref/math-mat-semicolon.png
new file mode 100644
index 00000000..abb5d1df
--- /dev/null
+++ b/tests/ref/math-mat-semicolon.png
Binary files differ
diff --git a/tests/ref/math-mat-sparse.png b/tests/ref/math-mat-sparse.png
new file mode 100644
index 00000000..4d077931
--- /dev/null
+++ b/tests/ref/math-mat-sparse.png
Binary files differ
diff --git a/tests/ref/math-multiline-multiple-trailing-linebreaks.png b/tests/ref/math-multiline-multiple-trailing-linebreaks.png
new file mode 100644
index 00000000..2c6484c7
--- /dev/null
+++ b/tests/ref/math-multiline-multiple-trailing-linebreaks.png
Binary files differ
diff --git a/tests/ref/math-multiline-no-trailing-linebreak.png b/tests/ref/math-multiline-no-trailing-linebreak.png
new file mode 100644
index 00000000..8ad6204d
--- /dev/null
+++ b/tests/ref/math-multiline-no-trailing-linebreak.png
Binary files differ
diff --git a/tests/ref/math-multiline-trailing-linebreak.png b/tests/ref/math-multiline-trailing-linebreak.png
new file mode 100644
index 00000000..364d8624
--- /dev/null
+++ b/tests/ref/math-multiline-trailing-linebreak.png
Binary files differ
diff --git a/tests/ref/math-nested-normal-layout.png b/tests/ref/math-nested-normal-layout.png
new file mode 100644
index 00000000..4ec7d46e
--- /dev/null
+++ b/tests/ref/math-nested-normal-layout.png
Binary files differ
diff --git a/tests/ref/math-non-math-content.png b/tests/ref/math-non-math-content.png
new file mode 100644
index 00000000..66896d18
--- /dev/null
+++ b/tests/ref/math-non-math-content.png
Binary files differ
diff --git a/tests/ref/math-op-call.png b/tests/ref/math-op-call.png
new file mode 100644
index 00000000..2fcdf2cb
--- /dev/null
+++ b/tests/ref/math-op-call.png
Binary files differ
diff --git a/tests/ref/math-op-custom.png b/tests/ref/math-op-custom.png
new file mode 100644
index 00000000..fbba241d
--- /dev/null
+++ b/tests/ref/math-op-custom.png
Binary files differ
diff --git a/tests/ref/math-op-predefined.png b/tests/ref/math-op-predefined.png
new file mode 100644
index 00000000..bfede9e7
--- /dev/null
+++ b/tests/ref/math-op-predefined.png
Binary files differ
diff --git a/tests/ref/math-op-scripts-vs-limits.png b/tests/ref/math-op-scripts-vs-limits.png
new file mode 100644
index 00000000..41897416
--- /dev/null
+++ b/tests/ref/math-op-scripts-vs-limits.png
Binary files differ
diff --git a/tests/ref/math-op-styled.png b/tests/ref/math-op-styled.png
new file mode 100644
index 00000000..c6890d74
--- /dev/null
+++ b/tests/ref/math-op-styled.png
Binary files differ
diff --git a/tests/ref/math-optical-size-frac-script-script.png b/tests/ref/math-optical-size-frac-script-script.png
new file mode 100644
index 00000000..893b3434
--- /dev/null
+++ b/tests/ref/math-optical-size-frac-script-script.png
Binary files differ
diff --git a/tests/ref/math-optical-size-nested-scripts.png b/tests/ref/math-optical-size-nested-scripts.png
new file mode 100644
index 00000000..8ca35c6e
--- /dev/null
+++ b/tests/ref/math-optical-size-nested-scripts.png
Binary files differ
diff --git a/tests/ref/math-optical-size-prime-large-operator.png b/tests/ref/math-optical-size-prime-large-operator.png
new file mode 100644
index 00000000..b38a934e
--- /dev/null
+++ b/tests/ref/math-optical-size-prime-large-operator.png
Binary files differ
diff --git a/tests/ref/math-optical-size-primes.png b/tests/ref/math-optical-size-primes.png
new file mode 100644
index 00000000..8fc199aa
--- /dev/null
+++ b/tests/ref/math-optical-size-primes.png
Binary files differ
diff --git a/tests/ref/math-primes-after-code-expr.png b/tests/ref/math-primes-after-code-expr.png
new file mode 100644
index 00000000..5ec3bc8c
--- /dev/null
+++ b/tests/ref/math-primes-after-code-expr.png
Binary files differ
diff --git a/tests/ref/math-primes-attach.png b/tests/ref/math-primes-attach.png
new file mode 100644
index 00000000..95b77882
--- /dev/null
+++ b/tests/ref/math-primes-attach.png
Binary files differ
diff --git a/tests/ref/math-primes-complex.png b/tests/ref/math-primes-complex.png
new file mode 100644
index 00000000..5f5558eb
--- /dev/null
+++ b/tests/ref/math-primes-complex.png
Binary files differ
diff --git a/tests/ref/math-primes-limits.png b/tests/ref/math-primes-limits.png
new file mode 100644
index 00000000..f2c5cec2
--- /dev/null
+++ b/tests/ref/math-primes-limits.png
Binary files differ
diff --git a/tests/ref/math-primes-scripts.png b/tests/ref/math-primes-scripts.png
new file mode 100644
index 00000000..2a912180
--- /dev/null
+++ b/tests/ref/math-primes-scripts.png
Binary files differ
diff --git a/tests/ref/math-primes-spaces.png b/tests/ref/math-primes-spaces.png
new file mode 100644
index 00000000..890cc348
--- /dev/null
+++ b/tests/ref/math-primes-spaces.png
Binary files differ
diff --git a/tests/ref/math-primes.png b/tests/ref/math-primes.png
new file mode 100644
index 00000000..f3323197
--- /dev/null
+++ b/tests/ref/math-primes.png
Binary files differ
diff --git a/tests/ref/math-root-basic.png b/tests/ref/math-root-basic.png
new file mode 100644
index 00000000..b8b891eb
--- /dev/null
+++ b/tests/ref/math-root-basic.png
Binary files differ
diff --git a/tests/ref/math-root-large-body.png b/tests/ref/math-root-large-body.png
new file mode 100644
index 00000000..3dd4d848
--- /dev/null
+++ b/tests/ref/math-root-large-body.png
Binary files differ
diff --git a/tests/ref/math-root-large-index.png b/tests/ref/math-root-large-index.png
new file mode 100644
index 00000000..8037222c
--- /dev/null
+++ b/tests/ref/math-root-large-index.png
Binary files differ
diff --git a/tests/ref/math-root-precomposed.png b/tests/ref/math-root-precomposed.png
new file mode 100644
index 00000000..e09f1878
--- /dev/null
+++ b/tests/ref/math-root-precomposed.png
Binary files differ
diff --git a/tests/ref/math-root-radical-attachment.png b/tests/ref/math-root-radical-attachment.png
new file mode 100644
index 00000000..4cb447ec
--- /dev/null
+++ b/tests/ref/math-root-radical-attachment.png
Binary files differ
diff --git a/tests/ref/math-root-syntax.png b/tests/ref/math-root-syntax.png
new file mode 100644
index 00000000..49255493
--- /dev/null
+++ b/tests/ref/math-root-syntax.png
Binary files differ
diff --git a/tests/ref/math-shorthandes.png b/tests/ref/math-shorthandes.png
new file mode 100644
index 00000000..ff26ce96
--- /dev/null
+++ b/tests/ref/math-shorthandes.png
Binary files differ
diff --git a/tests/ref/math-size.png b/tests/ref/math-size.png
new file mode 100644
index 00000000..b44e4c74
--- /dev/null
+++ b/tests/ref/math-size.png
Binary files differ
diff --git a/tests/ref/math-spacing-basic.png b/tests/ref/math-spacing-basic.png
new file mode 100644
index 00000000..5567b087
--- /dev/null
+++ b/tests/ref/math-spacing-basic.png
Binary files differ
diff --git a/tests/ref/math-spacing-decorated.png b/tests/ref/math-spacing-decorated.png
new file mode 100644
index 00000000..e34066ea
--- /dev/null
+++ b/tests/ref/math-spacing-decorated.png
Binary files differ
diff --git a/tests/ref/math-spacing-kept-spaces.png b/tests/ref/math-spacing-kept-spaces.png
new file mode 100644
index 00000000..bb433d4f
--- /dev/null
+++ b/tests/ref/math-spacing-kept-spaces.png
Binary files differ
diff --git a/tests/ref/math-spacing-predefined.png b/tests/ref/math-spacing-predefined.png
new file mode 100644
index 00000000..244e7642
--- /dev/null
+++ b/tests/ref/math-spacing-predefined.png
Binary files differ
diff --git a/tests/ref/math-spacing-set-comprehension.png b/tests/ref/math-spacing-set-comprehension.png
new file mode 100644
index 00000000..63ef46ca
--- /dev/null
+++ b/tests/ref/math-spacing-set-comprehension.png
Binary files differ
diff --git a/tests/ref/math-spacing-weak.png b/tests/ref/math-spacing-weak.png
new file mode 100644
index 00000000..71af3222
--- /dev/null
+++ b/tests/ref/math-spacing-weak.png
Binary files differ
diff --git a/tests/ref/math-style-exceptions.png b/tests/ref/math-style-exceptions.png
new file mode 100644
index 00000000..bdeabb67
--- /dev/null
+++ b/tests/ref/math-style-exceptions.png
Binary files differ
diff --git a/tests/ref/math-style-greek-exceptions.png b/tests/ref/math-style-greek-exceptions.png
new file mode 100644
index 00000000..93ae6309
--- /dev/null
+++ b/tests/ref/math-style-greek-exceptions.png
Binary files differ
diff --git a/tests/ref/math-style-hebrew-exceptions.png b/tests/ref/math-style-hebrew-exceptions.png
new file mode 100644
index 00000000..723466e8
--- /dev/null
+++ b/tests/ref/math-style-hebrew-exceptions.png
Binary files differ
diff --git a/tests/ref/math-style-italic-default.png b/tests/ref/math-style-italic-default.png
new file mode 100644
index 00000000..0a25f6fa
--- /dev/null
+++ b/tests/ref/math-style-italic-default.png
Binary files differ
diff --git a/tests/ref/math-style.png b/tests/ref/math-style.png
new file mode 100644
index 00000000..f514bd18
--- /dev/null
+++ b/tests/ref/math-style.png
Binary files differ
diff --git a/tests/ref/math-symbol-show-rule.png b/tests/ref/math-symbol-show-rule.png
new file mode 100644
index 00000000..68faf937
--- /dev/null
+++ b/tests/ref/math-symbol-show-rule.png
Binary files differ
diff --git a/tests/ref/math-table.png b/tests/ref/math-table.png
new file mode 100644
index 00000000..5eb93218
--- /dev/null
+++ b/tests/ref/math-table.png
Binary files differ
diff --git a/tests/ref/math-text-color.png b/tests/ref/math-text-color.png
new file mode 100644
index 00000000..33ff00f3
--- /dev/null
+++ b/tests/ref/math-text-color.png
Binary files differ
diff --git a/tests/ref/math-underover-brace.png b/tests/ref/math-underover-brace.png
new file mode 100644
index 00000000..d4a3f4a1
--- /dev/null
+++ b/tests/ref/math-underover-brace.png
Binary files differ
diff --git a/tests/ref/math-underover-brackets.png b/tests/ref/math-underover-brackets.png
new file mode 100644
index 00000000..03419bc3
--- /dev/null
+++ b/tests/ref/math-underover-brackets.png
Binary files differ
diff --git a/tests/ref/math-underover-line-bracket.png b/tests/ref/math-underover-line-bracket.png
new file mode 100644
index 00000000..08d8df20
--- /dev/null
+++ b/tests/ref/math-underover-line-bracket.png
Binary files differ
diff --git a/tests/ref/math-unicode.png b/tests/ref/math-unicode.png
new file mode 100644
index 00000000..e74429eb
--- /dev/null
+++ b/tests/ref/math-unicode.png
Binary files differ
diff --git a/tests/ref/math-vec-align-explicit-alternating.png b/tests/ref/math-vec-align-explicit-alternating.png
new file mode 100644
index 00000000..cb29eb06
--- /dev/null
+++ b/tests/ref/math-vec-align-explicit-alternating.png
Binary files differ
diff --git a/tests/ref/math-vec-delim-set.png b/tests/ref/math-vec-delim-set.png
new file mode 100644
index 00000000..8024d594
--- /dev/null
+++ b/tests/ref/math-vec-delim-set.png
Binary files differ
diff --git a/tests/ref/math-vec-gap.png b/tests/ref/math-vec-gap.png
new file mode 100644
index 00000000..06f8cf7d
--- /dev/null
+++ b/tests/ref/math-vec-gap.png
Binary files differ
diff --git a/tests/ref/math-vec-wide.png b/tests/ref/math-vec-wide.png
new file mode 100644
index 00000000..30853a00
--- /dev/null
+++ b/tests/ref/math-vec-wide.png
Binary files differ
diff --git a/tests/ref/math/accent.png b/tests/ref/math/accent.png
deleted file mode 100644
index 52a7037e..00000000
--- a/tests/ref/math/accent.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/alignment.png b/tests/ref/math/alignment.png
deleted file mode 100644
index 4bf739a4..00000000
--- a/tests/ref/math/alignment.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/attach-p1.png b/tests/ref/math/attach-p1.png
deleted file mode 100644
index 45c465ce..00000000
--- a/tests/ref/math/attach-p1.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/attach-p2.png b/tests/ref/math/attach-p2.png
deleted file mode 100644
index 3820f33e..00000000
--- a/tests/ref/math/attach-p2.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/attach-p3.png b/tests/ref/math/attach-p3.png
deleted file mode 100644
index 69e0a7dd..00000000
--- a/tests/ref/math/attach-p3.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/call.png b/tests/ref/math/call.png
deleted file mode 100644
index 907a1a2b..00000000
--- a/tests/ref/math/call.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/cancel.png b/tests/ref/math/cancel.png
deleted file mode 100644
index 4f0de136..00000000
--- a/tests/ref/math/cancel.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/cases.png b/tests/ref/math/cases.png
deleted file mode 100644
index e222ae17..00000000
--- a/tests/ref/math/cases.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/class.png b/tests/ref/math/class.png
deleted file mode 100644
index a4d6e86c..00000000
--- a/tests/ref/math/class.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/content.png b/tests/ref/math/content.png
deleted file mode 100644
index c27a17ea..00000000
--- a/tests/ref/math/content.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/delimited.png b/tests/ref/math/delimited.png
deleted file mode 100644
index 6126f58a..00000000
--- a/tests/ref/math/delimited.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/equation-block-align.png b/tests/ref/math/equation-block-align.png
deleted file mode 100644
index 8736312a..00000000
--- a/tests/ref/math/equation-block-align.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/equation-number.png b/tests/ref/math/equation-number.png
deleted file mode 100644
index 8ba91590..00000000
--- a/tests/ref/math/equation-number.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/equation-show.png b/tests/ref/math/equation-show.png
deleted file mode 100644
index 79a70dc0..00000000
--- a/tests/ref/math/equation-show.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/font-features.png b/tests/ref/math/font-features.png
deleted file mode 100644
index 0ab2c06d..00000000
--- a/tests/ref/math/font-features.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/frac.png b/tests/ref/math/frac.png
deleted file mode 100644
index 3e08f7e5..00000000
--- a/tests/ref/math/frac.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/linebreak.png b/tests/ref/math/linebreak.png
deleted file mode 100644
index f3212a4a..00000000
--- a/tests/ref/math/linebreak.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/matrix-alignment.png b/tests/ref/math/matrix-alignment.png
deleted file mode 100644
index cdf17463..00000000
--- a/tests/ref/math/matrix-alignment.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/matrix-gaps.png b/tests/ref/math/matrix-gaps.png
deleted file mode 100644
index 16788969..00000000
--- a/tests/ref/math/matrix-gaps.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/matrix.png b/tests/ref/math/matrix.png
deleted file mode 100644
index b8ea19e2..00000000
--- a/tests/ref/math/matrix.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/multiline.png b/tests/ref/math/multiline.png
deleted file mode 100644
index 185724af..00000000
--- a/tests/ref/math/multiline.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/numbering.png b/tests/ref/math/numbering.png
deleted file mode 100644
index 813f5f8c..00000000
--- a/tests/ref/math/numbering.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/op.png b/tests/ref/math/op.png
deleted file mode 100644
index ab3f35f6..00000000
--- a/tests/ref/math/op.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/opticalsize.png b/tests/ref/math/opticalsize.png
deleted file mode 100644
index 303f7bee..00000000
--- a/tests/ref/math/opticalsize.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/prime.png b/tests/ref/math/prime.png
deleted file mode 100644
index 81a47643..00000000
--- a/tests/ref/math/prime.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/root.png b/tests/ref/math/root.png
deleted file mode 100644
index 51fdf2e8..00000000
--- a/tests/ref/math/root.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/spacing.png b/tests/ref/math/spacing.png
deleted file mode 100644
index d8198bbf..00000000
--- a/tests/ref/math/spacing.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/style.png b/tests/ref/math/style.png
deleted file mode 100644
index a52136dc..00000000
--- a/tests/ref/math/style.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/syntax.png b/tests/ref/math/syntax.png
deleted file mode 100644
index 3855fa9b..00000000
--- a/tests/ref/math/syntax.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/unbalanced.png b/tests/ref/math/unbalanced.png
deleted file mode 100644
index 84f51837..00000000
--- a/tests/ref/math/unbalanced.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/underover.png b/tests/ref/math/underover.png
deleted file mode 100644
index e974302f..00000000
--- a/tests/ref/math/underover.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/math/vec.png b/tests/ref/math/vec.png
deleted file mode 100644
index f2371e5b..00000000
--- a/tests/ref/math/vec.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/bibliography-full.png b/tests/ref/meta/bibliography-full.png
deleted file mode 100644
index d8778c09..00000000
--- a/tests/ref/meta/bibliography-full.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/bibliography-ordering.png b/tests/ref/meta/bibliography-ordering.png
deleted file mode 100644
index 116c561d..00000000
--- a/tests/ref/meta/bibliography-ordering.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/bibliography.png b/tests/ref/meta/bibliography.png
deleted file mode 100644
index 8fbd09bc..00000000
--- a/tests/ref/meta/bibliography.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/cite-footnote.png b/tests/ref/meta/cite-footnote.png
deleted file mode 100644
index 3a7a0094..00000000
--- a/tests/ref/meta/cite-footnote.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/cite-form.png b/tests/ref/meta/cite-form.png
deleted file mode 100644
index 8adeac92..00000000
--- a/tests/ref/meta/cite-form.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/cite-group.png b/tests/ref/meta/cite-group.png
deleted file mode 100644
index 8d02a903..00000000
--- a/tests/ref/meta/cite-group.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/counter-page.png b/tests/ref/meta/counter-page.png
deleted file mode 100644
index 869718bc..00000000
--- a/tests/ref/meta/counter-page.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/counter.png b/tests/ref/meta/counter.png
deleted file mode 100644
index 6c29ac17..00000000
--- a/tests/ref/meta/counter.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/document.png b/tests/ref/meta/document.png
deleted file mode 100644
index 6db26511..00000000
--- a/tests/ref/meta/document.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/figure-caption.png b/tests/ref/meta/figure-caption.png
deleted file mode 100644
index 8a1d4a59..00000000
--- a/tests/ref/meta/figure-caption.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/figure-localization.png b/tests/ref/meta/figure-localization.png
deleted file mode 100644
index 5fcbd2b7..00000000
--- a/tests/ref/meta/figure-localization.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/figure.png b/tests/ref/meta/figure.png
deleted file mode 100644
index bcdd0d2f..00000000
--- a/tests/ref/meta/figure.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/footnote-break.png b/tests/ref/meta/footnote-break.png
deleted file mode 100644
index 625305c8..00000000
--- a/tests/ref/meta/footnote-break.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/footnote-columns.png b/tests/ref/meta/footnote-columns.png
deleted file mode 100644
index 528ec664..00000000
--- a/tests/ref/meta/footnote-columns.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/footnote-container.png b/tests/ref/meta/footnote-container.png
deleted file mode 100644
index 9327e7ee..00000000
--- a/tests/ref/meta/footnote-container.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/footnote-invariant.png b/tests/ref/meta/footnote-invariant.png
deleted file mode 100644
index 66b41182..00000000
--- a/tests/ref/meta/footnote-invariant.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/footnote-refs.png b/tests/ref/meta/footnote-refs.png
deleted file mode 100644
index 3fab7bd5..00000000
--- a/tests/ref/meta/footnote-refs.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/footnote-table.png b/tests/ref/meta/footnote-table.png
deleted file mode 100644
index 023f8008..00000000
--- a/tests/ref/meta/footnote-table.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/footnote.png b/tests/ref/meta/footnote.png
deleted file mode 100644
index 4c67bbd7..00000000
--- a/tests/ref/meta/footnote.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/heading.png b/tests/ref/meta/heading.png
deleted file mode 100644
index 8467ea53..00000000
--- a/tests/ref/meta/heading.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/link.png b/tests/ref/meta/link.png
deleted file mode 100644
index 3c3ecd2c..00000000
--- a/tests/ref/meta/link.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/numbering.png b/tests/ref/meta/numbering.png
deleted file mode 100644
index fa5b520f..00000000
--- a/tests/ref/meta/numbering.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/outline-entry.png b/tests/ref/meta/outline-entry.png
deleted file mode 100644
index f8f5412f..00000000
--- a/tests/ref/meta/outline-entry.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/outline-first-par-indent.png b/tests/ref/meta/outline-first-par-indent.png
deleted file mode 100644
index f6e4ffe8..00000000
--- a/tests/ref/meta/outline-first-par-indent.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/outline-indent.png b/tests/ref/meta/outline-indent.png
deleted file mode 100644
index 816d86a5..00000000
--- a/tests/ref/meta/outline-indent.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/outline.png b/tests/ref/meta/outline.png
deleted file mode 100644
index 047bcc80..00000000
--- a/tests/ref/meta/outline.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/page-label.png b/tests/ref/meta/page-label.png
deleted file mode 100644
index 301d626a..00000000
--- a/tests/ref/meta/page-label.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/query-before-after.png b/tests/ref/meta/query-before-after.png
deleted file mode 100644
index 80f8fe1f..00000000
--- a/tests/ref/meta/query-before-after.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/query-figure.png b/tests/ref/meta/query-figure.png
deleted file mode 100644
index 2537ebf0..00000000
--- a/tests/ref/meta/query-figure.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/query-header.png b/tests/ref/meta/query-header.png
deleted file mode 100644
index c2dc4689..00000000
--- a/tests/ref/meta/query-header.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/ref.png b/tests/ref/meta/ref.png
deleted file mode 100644
index 51563f54..00000000
--- a/tests/ref/meta/ref.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/meta/state.png b/tests/ref/meta/state.png
deleted file mode 100644
index 25faa0d9..00000000
--- a/tests/ref/meta/state.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/newline-continuation-code.png b/tests/ref/newline-continuation-code.png
new file mode 100644
index 00000000..46a6afd5
--- /dev/null
+++ b/tests/ref/newline-continuation-code.png
Binary files differ
diff --git a/tests/ref/newline-continuation-markup.png b/tests/ref/newline-continuation-markup.png
new file mode 100644
index 00000000..268e5f84
--- /dev/null
+++ b/tests/ref/newline-continuation-markup.png
Binary files differ
diff --git a/tests/ref/numbering-chinese.png b/tests/ref/numbering-chinese.png
new file mode 100644
index 00000000..06b31334
--- /dev/null
+++ b/tests/ref/numbering-chinese.png
Binary files differ
diff --git a/tests/ref/numbering-hebrew.png b/tests/ref/numbering-hebrew.png
new file mode 100644
index 00000000..d7614225
--- /dev/null
+++ b/tests/ref/numbering-hebrew.png
Binary files differ
diff --git a/tests/ref/numbering-japanese-aiueo.png b/tests/ref/numbering-japanese-aiueo.png
new file mode 100644
index 00000000..b06d5c69
--- /dev/null
+++ b/tests/ref/numbering-japanese-aiueo.png
Binary files differ
diff --git a/tests/ref/numbering-japanese-iroha.png b/tests/ref/numbering-japanese-iroha.png
new file mode 100644
index 00000000..2018802f
--- /dev/null
+++ b/tests/ref/numbering-japanese-iroha.png
Binary files differ
diff --git a/tests/ref/numbering-korean.png b/tests/ref/numbering-korean.png
new file mode 100644
index 00000000..281f2ec2
--- /dev/null
+++ b/tests/ref/numbering-korean.png
Binary files differ
diff --git a/tests/ref/numbering-latin.png b/tests/ref/numbering-latin.png
new file mode 100644
index 00000000..e154735a
--- /dev/null
+++ b/tests/ref/numbering-latin.png
Binary files differ
diff --git a/tests/ref/numbering-symbol-and-roman.png b/tests/ref/numbering-symbol-and-roman.png
new file mode 100644
index 00000000..979f3b90
--- /dev/null
+++ b/tests/ref/numbering-symbol-and-roman.png
Binary files differ
diff --git a/tests/ref/numbers.png b/tests/ref/numbers.png
new file mode 100644
index 00000000..e6e7215b
--- /dev/null
+++ b/tests/ref/numbers.png
Binary files differ
diff --git a/tests/ref/ops-add-content.png b/tests/ref/ops-add-content.png
new file mode 100644
index 00000000..bdb8cb5e
--- /dev/null
+++ b/tests/ref/ops-add-content.png
Binary files differ
diff --git a/tests/ref/ops-multiply-inf-with-length.png b/tests/ref/ops-multiply-inf-with-length.png
new file mode 100644
index 00000000..749be056
--- /dev/null
+++ b/tests/ref/ops-multiply-inf-with-length.png
Binary files differ
diff --git a/tests/ref/outline-entry-complex.png b/tests/ref/outline-entry-complex.png
new file mode 100644
index 00000000..c885cacc
--- /dev/null
+++ b/tests/ref/outline-entry-complex.png
Binary files differ
diff --git a/tests/ref/outline-entry.png b/tests/ref/outline-entry.png
new file mode 100644
index 00000000..94e7a5a7
--- /dev/null
+++ b/tests/ref/outline-entry.png
Binary files differ
diff --git a/tests/ref/outline-first-line-indent.png b/tests/ref/outline-first-line-indent.png
new file mode 100644
index 00000000..dd995f31
--- /dev/null
+++ b/tests/ref/outline-first-line-indent.png
Binary files differ
diff --git a/tests/ref/outline-indent-no-numbering.png b/tests/ref/outline-indent-no-numbering.png
new file mode 100644
index 00000000..62bd80a3
--- /dev/null
+++ b/tests/ref/outline-indent-no-numbering.png
Binary files differ
diff --git a/tests/ref/outline-indent-numbering.png b/tests/ref/outline-indent-numbering.png
new file mode 100644
index 00000000..6c936827
--- /dev/null
+++ b/tests/ref/outline-indent-numbering.png
Binary files differ
diff --git a/tests/ref/outline.png b/tests/ref/outline.png
new file mode 100644
index 00000000..e5c24a98
--- /dev/null
+++ b/tests/ref/outline.png
Binary files differ
diff --git a/tests/ref/overhang-lone.png b/tests/ref/overhang-lone.png
new file mode 100644
index 00000000..b48618fb
--- /dev/null
+++ b/tests/ref/overhang-lone.png
Binary files differ
diff --git a/tests/ref/overhang.png b/tests/ref/overhang.png
new file mode 100644
index 00000000..b97ef30c
--- /dev/null
+++ b/tests/ref/overhang.png
Binary files differ
diff --git a/tests/ref/overline-background.png b/tests/ref/overline-background.png
new file mode 100644
index 00000000..8efd147e
--- /dev/null
+++ b/tests/ref/overline-background.png
Binary files differ
diff --git a/tests/ref/pad-basic.png b/tests/ref/pad-basic.png
new file mode 100644
index 00000000..f8c40088
--- /dev/null
+++ b/tests/ref/pad-basic.png
Binary files differ
diff --git a/tests/ref/pad-expanding-contents.png b/tests/ref/pad-expanding-contents.png
new file mode 100644
index 00000000..1bef4a81
--- /dev/null
+++ b/tests/ref/pad-expanding-contents.png
Binary files differ
diff --git a/tests/ref/pad-followed-by-content.png b/tests/ref/pad-followed-by-content.png
new file mode 100644
index 00000000..f0f06a6c
--- /dev/null
+++ b/tests/ref/pad-followed-by-content.png
Binary files differ
diff --git a/tests/ref/page-call-followed-by-pagebreak.png b/tests/ref/page-call-followed-by-pagebreak.png
new file mode 100644
index 00000000..87cd9735
--- /dev/null
+++ b/tests/ref/page-call-followed-by-pagebreak.png
Binary files differ
diff --git a/tests/ref/page-call-styled-empty.png b/tests/ref/page-call-styled-empty.png
new file mode 100644
index 00000000..6a24b1bc
--- /dev/null
+++ b/tests/ref/page-call-styled-empty.png
Binary files differ
diff --git a/tests/ref/page-fill.png b/tests/ref/page-fill.png
new file mode 100644
index 00000000..0c7ab277
--- /dev/null
+++ b/tests/ref/page-fill.png
Binary files differ
diff --git a/tests/ref/page-large.png b/tests/ref/page-large.png
new file mode 100644
index 00000000..a57dceec
--- /dev/null
+++ b/tests/ref/page-large.png
Binary files differ
diff --git a/tests/ref/page-margin-binding-from-text-lang.png b/tests/ref/page-margin-binding-from-text-lang.png
new file mode 100644
index 00000000..8d12ff2f
--- /dev/null
+++ b/tests/ref/page-margin-binding-from-text-lang.png
Binary files differ
diff --git a/tests/ref/page-margin-individual.png b/tests/ref/page-margin-individual.png
new file mode 100644
index 00000000..0bc0f51b
--- /dev/null
+++ b/tests/ref/page-margin-individual.png
Binary files differ
diff --git a/tests/ref/page-margin-inside-outside-override.png b/tests/ref/page-margin-inside-outside-override.png
new file mode 100644
index 00000000..5aa8bf12
--- /dev/null
+++ b/tests/ref/page-margin-inside-outside-override.png
Binary files differ
diff --git a/tests/ref/page-margin-inside-with-binding.png b/tests/ref/page-margin-inside-with-binding.png
new file mode 100644
index 00000000..5b9ec04f
--- /dev/null
+++ b/tests/ref/page-margin-inside-with-binding.png
Binary files differ
diff --git a/tests/ref/page-margin-inside.png b/tests/ref/page-margin-inside.png
new file mode 100644
index 00000000..d70b8604
--- /dev/null
+++ b/tests/ref/page-margin-inside.png
Binary files differ
diff --git a/tests/ref/page-margin-uniform.png b/tests/ref/page-margin-uniform.png
new file mode 100644
index 00000000..8a06fb74
--- /dev/null
+++ b/tests/ref/page-margin-uniform.png
Binary files differ
diff --git a/tests/ref/page-marginals.png b/tests/ref/page-marginals.png
new file mode 100644
index 00000000..cab886b3
--- /dev/null
+++ b/tests/ref/page-marginals.png
Binary files differ
diff --git a/tests/ref/page-number-align-bottom-left.png b/tests/ref/page-number-align-bottom-left.png
new file mode 100644
index 00000000..396f6e98
--- /dev/null
+++ b/tests/ref/page-number-align-bottom-left.png
Binary files differ
diff --git a/tests/ref/page-number-align-top-right.png b/tests/ref/page-number-align-top-right.png
new file mode 100644
index 00000000..3c7e5579
--- /dev/null
+++ b/tests/ref/page-number-align-top-right.png
Binary files differ
diff --git a/tests/ref/page-numbering-pdf-label.png b/tests/ref/page-numbering-pdf-label.png
new file mode 100644
index 00000000..a1cae720
--- /dev/null
+++ b/tests/ref/page-numbering-pdf-label.png
Binary files differ
diff --git a/tests/ref/page-set-empty.png b/tests/ref/page-set-empty.png
new file mode 100644
index 00000000..6a24b1bc
--- /dev/null
+++ b/tests/ref/page-set-empty.png
Binary files differ
diff --git a/tests/ref/page-set-forces-break.png b/tests/ref/page-set-forces-break.png
new file mode 100644
index 00000000..4654ef6c
--- /dev/null
+++ b/tests/ref/page-set-forces-break.png
Binary files differ
diff --git a/tests/ref/page-set-only-pagebreak.png b/tests/ref/page-set-only-pagebreak.png
new file mode 100644
index 00000000..9bf379d6
--- /dev/null
+++ b/tests/ref/page-set-only-pagebreak.png
Binary files differ
diff --git a/tests/ref/page-set-override-and-mix.png b/tests/ref/page-set-override-and-mix.png
new file mode 100644
index 00000000..d9df6acd
--- /dev/null
+++ b/tests/ref/page-set-override-and-mix.png
Binary files differ
diff --git a/tests/ref/page-set-override-thrice.png b/tests/ref/page-set-override-thrice.png
new file mode 100644
index 00000000..99173ced
--- /dev/null
+++ b/tests/ref/page-set-override-thrice.png
Binary files differ
diff --git a/tests/ref/pagebreak-around-set-page.png b/tests/ref/pagebreak-around-set-page.png
new file mode 100644
index 00000000..2c1ce508
--- /dev/null
+++ b/tests/ref/pagebreak-around-set-page.png
Binary files differ
diff --git a/tests/ref/pagebreak-followed-by-page-call.png b/tests/ref/pagebreak-followed-by-page-call.png
new file mode 100644
index 00000000..ee435cdc
--- /dev/null
+++ b/tests/ref/pagebreak-followed-by-page-call.png
Binary files differ
diff --git a/tests/ref/pagebreak-meta.png b/tests/ref/pagebreak-meta.png
new file mode 100644
index 00000000..7953dc51
--- /dev/null
+++ b/tests/ref/pagebreak-meta.png
Binary files differ
diff --git a/tests/ref/pagebreak-set-page-mixed.png b/tests/ref/pagebreak-set-page-mixed.png
new file mode 100644
index 00000000..3502ee42
--- /dev/null
+++ b/tests/ref/pagebreak-set-page-mixed.png
Binary files differ
diff --git a/tests/ref/pagebreak-to-auto-sized.png b/tests/ref/pagebreak-to-auto-sized.png
new file mode 100644
index 00000000..f3e2df45
--- /dev/null
+++ b/tests/ref/pagebreak-to-auto-sized.png
Binary files differ
diff --git a/tests/ref/pagebreak-to-multiple-pages.png b/tests/ref/pagebreak-to-multiple-pages.png
new file mode 100644
index 00000000..a7af0a9a
--- /dev/null
+++ b/tests/ref/pagebreak-to-multiple-pages.png
Binary files differ
diff --git a/tests/ref/pagebreak-to.png b/tests/ref/pagebreak-to.png
new file mode 100644
index 00000000..62a4ee20
--- /dev/null
+++ b/tests/ref/pagebreak-to.png
Binary files differ
diff --git a/tests/ref/pagebreak-weak-after-set-page.png b/tests/ref/pagebreak-weak-after-set-page.png
new file mode 100644
index 00000000..c8014df1
--- /dev/null
+++ b/tests/ref/pagebreak-weak-after-set-page.png
Binary files differ
diff --git a/tests/ref/pagebreak-weak-meta.png b/tests/ref/pagebreak-weak-meta.png
new file mode 100644
index 00000000..aa69e606
--- /dev/null
+++ b/tests/ref/pagebreak-weak-meta.png
Binary files differ
diff --git a/tests/ref/pagebreak-weak-place.png b/tests/ref/pagebreak-weak-place.png
new file mode 100644
index 00000000..f85bdf02
--- /dev/null
+++ b/tests/ref/pagebreak-weak-place.png
Binary files differ
diff --git a/tests/ref/pagebreak.png b/tests/ref/pagebreak.png
new file mode 100644
index 00000000..d07473d9
--- /dev/null
+++ b/tests/ref/pagebreak.png
Binary files differ
diff --git a/tests/ref/par-basic.png b/tests/ref/par-basic.png
new file mode 100644
index 00000000..ffd9de9a
--- /dev/null
+++ b/tests/ref/par-basic.png
Binary files differ
diff --git a/tests/ref/par-first-line-indent.png b/tests/ref/par-first-line-indent.png
new file mode 100644
index 00000000..e6d7ed76
--- /dev/null
+++ b/tests/ref/par-first-line-indent.png
Binary files differ
diff --git a/tests/ref/par-hanging-indent-manual-linebreak.png b/tests/ref/par-hanging-indent-manual-linebreak.png
new file mode 100644
index 00000000..e9c666cd
--- /dev/null
+++ b/tests/ref/par-hanging-indent-manual-linebreak.png
Binary files differ
diff --git a/tests/ref/par-hanging-indent-rtl.png b/tests/ref/par-hanging-indent-rtl.png
new file mode 100644
index 00000000..849e0a01
--- /dev/null
+++ b/tests/ref/par-hanging-indent-rtl.png
Binary files differ
diff --git a/tests/ref/par-hanging-indent.png b/tests/ref/par-hanging-indent.png
new file mode 100644
index 00000000..49455a78
--- /dev/null
+++ b/tests/ref/par-hanging-indent.png
Binary files differ
diff --git a/tests/ref/par-leading-and-block-spacing.png b/tests/ref/par-leading-and-block-spacing.png
new file mode 100644
index 00000000..faaa3116
--- /dev/null
+++ b/tests/ref/par-leading-and-block-spacing.png
Binary files differ
diff --git a/tests/ref/par-spacing-and-first-line-indent.png b/tests/ref/par-spacing-and-first-line-indent.png
new file mode 100644
index 00000000..c322f630
--- /dev/null
+++ b/tests/ref/par-spacing-and-first-line-indent.png
Binary files differ
diff --git a/tests/ref/parser-backtracking-destructuring-whitespace.png b/tests/ref/parser-backtracking-destructuring-whitespace.png
new file mode 100644
index 00000000..d5d72888
--- /dev/null
+++ b/tests/ref/parser-backtracking-destructuring-whitespace.png
Binary files differ
diff --git a/tests/ref/path.png b/tests/ref/path.png
new file mode 100644
index 00000000..9643a476
--- /dev/null
+++ b/tests/ref/path.png
Binary files differ
diff --git a/tests/ref/pattern-line.png b/tests/ref/pattern-line.png
new file mode 100644
index 00000000..b891b6d7
--- /dev/null
+++ b/tests/ref/pattern-line.png
Binary files differ
diff --git a/tests/ref/pattern-lines.png b/tests/ref/pattern-lines.png
new file mode 100644
index 00000000..008d357e
--- /dev/null
+++ b/tests/ref/pattern-lines.png
Binary files differ
diff --git a/tests/ref/pattern-relative-parent.png b/tests/ref/pattern-relative-parent.png
new file mode 100644
index 00000000..786057ef
--- /dev/null
+++ b/tests/ref/pattern-relative-parent.png
Binary files differ
diff --git a/tests/ref/pattern-relative-self.png b/tests/ref/pattern-relative-self.png
new file mode 100644
index 00000000..28408081
--- /dev/null
+++ b/tests/ref/pattern-relative-self.png
Binary files differ
diff --git a/tests/ref/pattern-small.png b/tests/ref/pattern-small.png
new file mode 100644
index 00000000..0406252d
--- /dev/null
+++ b/tests/ref/pattern-small.png
Binary files differ
diff --git a/tests/ref/pattern-spacing-negative.png b/tests/ref/pattern-spacing-negative.png
new file mode 100644
index 00000000..659c2283
--- /dev/null
+++ b/tests/ref/pattern-spacing-negative.png
Binary files differ
diff --git a/tests/ref/pattern-spacing-positive.png b/tests/ref/pattern-spacing-positive.png
new file mode 100644
index 00000000..3e475eee
--- /dev/null
+++ b/tests/ref/pattern-spacing-positive.png
Binary files differ
diff --git a/tests/ref/pattern-spacing-zero.png b/tests/ref/pattern-spacing-zero.png
new file mode 100644
index 00000000..5118471a
--- /dev/null
+++ b/tests/ref/pattern-spacing-zero.png
Binary files differ
diff --git a/tests/ref/pattern-stroke.png b/tests/ref/pattern-stroke.png
new file mode 100644
index 00000000..8b03783b
--- /dev/null
+++ b/tests/ref/pattern-stroke.png
Binary files differ
diff --git a/tests/ref/pattern-text.png b/tests/ref/pattern-text.png
new file mode 100644
index 00000000..de9bfc2e
--- /dev/null
+++ b/tests/ref/pattern-text.png
Binary files differ
diff --git a/tests/ref/place-background.png b/tests/ref/place-background.png
new file mode 100644
index 00000000..7d732717
--- /dev/null
+++ b/tests/ref/place-background.png
Binary files differ
diff --git a/tests/ref/place-basic.png b/tests/ref/place-basic.png
new file mode 100644
index 00000000..07642c34
--- /dev/null
+++ b/tests/ref/place-basic.png
Binary files differ
diff --git a/tests/ref/place-block-spacing.png b/tests/ref/place-block-spacing.png
new file mode 100644
index 00000000..fb01d1b6
--- /dev/null
+++ b/tests/ref/place-block-spacing.png
Binary files differ
diff --git a/tests/ref/place-bottom-in-box.png b/tests/ref/place-bottom-in-box.png
new file mode 100644
index 00000000..fdd8c010
--- /dev/null
+++ b/tests/ref/place-bottom-in-box.png
Binary files differ
diff --git a/tests/ref/place-bottom-right-in-box.png b/tests/ref/place-bottom-right-in-box.png
new file mode 100644
index 00000000..49c40886
--- /dev/null
+++ b/tests/ref/place-bottom-right-in-box.png
Binary files differ
diff --git a/tests/ref/place-float-columns.png b/tests/ref/place-float-columns.png
new file mode 100644
index 00000000..97065b68
--- /dev/null
+++ b/tests/ref/place-float-columns.png
Binary files differ
diff --git a/tests/ref/place-float-figure.png b/tests/ref/place-float-figure.png
new file mode 100644
index 00000000..5411178a
--- /dev/null
+++ b/tests/ref/place-float-figure.png
Binary files differ
diff --git a/tests/ref/place-float.png b/tests/ref/place-float.png
new file mode 100644
index 00000000..ddd49c47
--- /dev/null
+++ b/tests/ref/place-float.png
Binary files differ
diff --git a/tests/ref/place-horizon-in-boxes.png b/tests/ref/place-horizon-in-boxes.png
new file mode 100644
index 00000000..b6d333bf
--- /dev/null
+++ b/tests/ref/place-horizon-in-boxes.png
Binary files differ
diff --git a/tests/ref/place-top-left-in-box.png b/tests/ref/place-top-left-in-box.png
new file mode 100644
index 00000000..914ffa58
--- /dev/null
+++ b/tests/ref/place-top-left-in-box.png
Binary files differ
diff --git a/tests/ref/polygon-line-join.png b/tests/ref/polygon-line-join.png
new file mode 100644
index 00000000..0f65fa70
--- /dev/null
+++ b/tests/ref/polygon-line-join.png
Binary files differ
diff --git a/tests/ref/polygon.png b/tests/ref/polygon.png
new file mode 100644
index 00000000..1dc11083
--- /dev/null
+++ b/tests/ref/polygon.png
Binary files differ
diff --git a/tests/ref/query-and-or.png b/tests/ref/query-and-or.png
new file mode 100644
index 00000000..39cfd076
--- /dev/null
+++ b/tests/ref/query-and-or.png
Binary files differ
diff --git a/tests/ref/query-before-after.png b/tests/ref/query-before-after.png
new file mode 100644
index 00000000..33fde985
--- /dev/null
+++ b/tests/ref/query-before-after.png
Binary files differ
diff --git a/tests/ref/query-complex.png b/tests/ref/query-complex.png
new file mode 100644
index 00000000..f71dcce5
--- /dev/null
+++ b/tests/ref/query-complex.png
Binary files differ
diff --git a/tests/ref/query-list-of-figures.png b/tests/ref/query-list-of-figures.png
new file mode 100644
index 00000000..c94ccd00
--- /dev/null
+++ b/tests/ref/query-list-of-figures.png
Binary files differ
diff --git a/tests/ref/query-running-header.png b/tests/ref/query-running-header.png
new file mode 100644
index 00000000..210c7810
--- /dev/null
+++ b/tests/ref/query-running-header.png
Binary files differ
diff --git a/tests/ref/quote-block-spacing.png b/tests/ref/quote-block-spacing.png
new file mode 100644
index 00000000..3efae5ab
--- /dev/null
+++ b/tests/ref/quote-block-spacing.png
Binary files differ
diff --git a/tests/ref/quote-cite-format-author-date.png b/tests/ref/quote-cite-format-author-date.png
new file mode 100644
index 00000000..43816f8c
--- /dev/null
+++ b/tests/ref/quote-cite-format-author-date.png
Binary files differ
diff --git a/tests/ref/quote-cite-format-label-or-numeric.png b/tests/ref/quote-cite-format-label-or-numeric.png
new file mode 100644
index 00000000..f0f5f90f
--- /dev/null
+++ b/tests/ref/quote-cite-format-label-or-numeric.png
Binary files differ
diff --git a/tests/ref/quote-cite-format-note.png b/tests/ref/quote-cite-format-note.png
new file mode 100644
index 00000000..1092ffdb
--- /dev/null
+++ b/tests/ref/quote-cite-format-note.png
Binary files differ
diff --git a/tests/ref/quote-dir-align.png b/tests/ref/quote-dir-align.png
new file mode 100644
index 00000000..0341f87c
--- /dev/null
+++ b/tests/ref/quote-dir-align.png
Binary files differ
diff --git a/tests/ref/quote-dir-author-pos.png b/tests/ref/quote-dir-author-pos.png
new file mode 100644
index 00000000..842796a2
--- /dev/null
+++ b/tests/ref/quote-dir-author-pos.png
Binary files differ
diff --git a/tests/ref/quote-inline.png b/tests/ref/quote-inline.png
new file mode 100644
index 00000000..4dbc9720
--- /dev/null
+++ b/tests/ref/quote-inline.png
Binary files differ
diff --git a/tests/ref/quote-nesting-custom.png b/tests/ref/quote-nesting-custom.png
new file mode 100644
index 00000000..e26b6258
--- /dev/null
+++ b/tests/ref/quote-nesting-custom.png
Binary files differ
diff --git a/tests/ref/quote-nesting.png b/tests/ref/quote-nesting.png
new file mode 100644
index 00000000..dcd1e378
--- /dev/null
+++ b/tests/ref/quote-nesting.png
Binary files differ
diff --git a/tests/ref/raw-align-default.png b/tests/ref/raw-align-default.png
new file mode 100644
index 00000000..84c51229
--- /dev/null
+++ b/tests/ref/raw-align-default.png
Binary files differ
diff --git a/tests/ref/raw-align-specified.png b/tests/ref/raw-align-specified.png
new file mode 100644
index 00000000..18b48dec
--- /dev/null
+++ b/tests/ref/raw-align-specified.png
Binary files differ
diff --git a/tests/ref/raw-block-no-parbreaks.png b/tests/ref/raw-block-no-parbreaks.png
new file mode 100644
index 00000000..401cc5a9
--- /dev/null
+++ b/tests/ref/raw-block-no-parbreaks.png
Binary files differ
diff --git a/tests/ref/raw-consecutive-single-backticks.png b/tests/ref/raw-consecutive-single-backticks.png
new file mode 100644
index 00000000..159d0eda
--- /dev/null
+++ b/tests/ref/raw-consecutive-single-backticks.png
Binary files differ
diff --git a/tests/ref/raw-dedent-empty-line.png b/tests/ref/raw-dedent-empty-line.png
new file mode 100644
index 00000000..c3c88e7a
--- /dev/null
+++ b/tests/ref/raw-dedent-empty-line.png
Binary files differ
diff --git a/tests/ref/raw-dedent-first-line.png b/tests/ref/raw-dedent-first-line.png
new file mode 100644
index 00000000..c6eee5ce
--- /dev/null
+++ b/tests/ref/raw-dedent-first-line.png
Binary files differ
diff --git a/tests/ref/raw-dedent-last-line.png b/tests/ref/raw-dedent-last-line.png
new file mode 100644
index 00000000..2b1fe747
--- /dev/null
+++ b/tests/ref/raw-dedent-last-line.png
Binary files differ
diff --git a/tests/ref/raw-empty.png b/tests/ref/raw-empty.png
new file mode 100644
index 00000000..a47eb855
--- /dev/null
+++ b/tests/ref/raw-empty.png
Binary files differ
diff --git a/tests/ref/raw-highlight-typ.png b/tests/ref/raw-highlight-typ.png
new file mode 100644
index 00000000..f80bbf89
--- /dev/null
+++ b/tests/ref/raw-highlight-typ.png
Binary files differ
diff --git a/tests/ref/raw-highlight.png b/tests/ref/raw-highlight.png
new file mode 100644
index 00000000..2f99b450
--- /dev/null
+++ b/tests/ref/raw-highlight.png
Binary files differ
diff --git a/tests/ref/raw-inline-multiline.png b/tests/ref/raw-inline-multiline.png
new file mode 100644
index 00000000..7db3126d
--- /dev/null
+++ b/tests/ref/raw-inline-multiline.png
Binary files differ
diff --git a/tests/ref/raw-line-alternating-fill.png b/tests/ref/raw-line-alternating-fill.png
new file mode 100644
index 00000000..b8053129
--- /dev/null
+++ b/tests/ref/raw-line-alternating-fill.png
Binary files differ
diff --git a/tests/ref/raw-line-text-fill.png b/tests/ref/raw-line-text-fill.png
new file mode 100644
index 00000000..5b3c4d19
--- /dev/null
+++ b/tests/ref/raw-line-text-fill.png
Binary files differ
diff --git a/tests/ref/raw-line.png b/tests/ref/raw-line.png
new file mode 100644
index 00000000..c8ada95d
--- /dev/null
+++ b/tests/ref/raw-line.png
Binary files differ
diff --git a/tests/ref/raw-more-backticks.png b/tests/ref/raw-more-backticks.png
new file mode 100644
index 00000000..ab836011
--- /dev/null
+++ b/tests/ref/raw-more-backticks.png
Binary files differ
diff --git a/tests/ref/raw-show-set.png b/tests/ref/raw-show-set.png
new file mode 100644
index 00000000..8a82c2e9
--- /dev/null
+++ b/tests/ref/raw-show-set.png
Binary files differ
diff --git a/tests/ref/raw-single-backtick-lang.png b/tests/ref/raw-single-backtick-lang.png
new file mode 100644
index 00000000..b420627e
--- /dev/null
+++ b/tests/ref/raw-single-backtick-lang.png
Binary files differ
diff --git a/tests/ref/raw-syntaxes.png b/tests/ref/raw-syntaxes.png
new file mode 100644
index 00000000..4e14cd06
--- /dev/null
+++ b/tests/ref/raw-syntaxes.png
Binary files differ
diff --git a/tests/ref/raw-tab-size.png b/tests/ref/raw-tab-size.png
new file mode 100644
index 00000000..132a10b3
--- /dev/null
+++ b/tests/ref/raw-tab-size.png
Binary files differ
diff --git a/tests/ref/raw-theme.png b/tests/ref/raw-theme.png
new file mode 100644
index 00000000..78561ac6
--- /dev/null
+++ b/tests/ref/raw-theme.png
Binary files differ
diff --git a/tests/ref/raw-trimming.png b/tests/ref/raw-trimming.png
new file mode 100644
index 00000000..58d90b7f
--- /dev/null
+++ b/tests/ref/raw-trimming.png
Binary files differ
diff --git a/tests/ref/raw-typst-lang.png b/tests/ref/raw-typst-lang.png
new file mode 100644
index 00000000..3dcafafb
--- /dev/null
+++ b/tests/ref/raw-typst-lang.png
Binary files differ
diff --git a/tests/ref/rect-customization.png b/tests/ref/rect-customization.png
new file mode 100644
index 00000000..93808920
--- /dev/null
+++ b/tests/ref/rect-customization.png
Binary files differ
diff --git a/tests/ref/rect-fill-stroke.png b/tests/ref/rect-fill-stroke.png
new file mode 100644
index 00000000..28a47c12
--- /dev/null
+++ b/tests/ref/rect-fill-stroke.png
Binary files differ
diff --git a/tests/ref/rect-stroke.png b/tests/ref/rect-stroke.png
new file mode 100644
index 00000000..7d59c049
--- /dev/null
+++ b/tests/ref/rect-stroke.png
Binary files differ
diff --git a/tests/ref/rect.png b/tests/ref/rect.png
new file mode 100644
index 00000000..04e435ed
--- /dev/null
+++ b/tests/ref/rect.png
Binary files differ
diff --git a/tests/ref/ref-basic.png b/tests/ref/ref-basic.png
new file mode 100644
index 00000000..94d94789
--- /dev/null
+++ b/tests/ref/ref-basic.png
Binary files differ
diff --git a/tests/ref/ref-supplements.png b/tests/ref/ref-supplements.png
new file mode 100644
index 00000000..46d1524a
--- /dev/null
+++ b/tests/ref/ref-supplements.png
Binary files differ
diff --git a/tests/ref/repeat-align-and-dir.png b/tests/ref/repeat-align-and-dir.png
new file mode 100644
index 00000000..16797d04
--- /dev/null
+++ b/tests/ref/repeat-align-and-dir.png
Binary files differ
diff --git a/tests/ref/repeat-basic.png b/tests/ref/repeat-basic.png
new file mode 100644
index 00000000..61e7f50f
--- /dev/null
+++ b/tests/ref/repeat-basic.png
Binary files differ
diff --git a/tests/ref/repeat-dots-rtl.png b/tests/ref/repeat-dots-rtl.png
new file mode 100644
index 00000000..a0f1a919
--- /dev/null
+++ b/tests/ref/repeat-dots-rtl.png
Binary files differ
diff --git a/tests/ref/repeat-empty.png b/tests/ref/repeat-empty.png
new file mode 100644
index 00000000..c23d7fa4
--- /dev/null
+++ b/tests/ref/repeat-empty.png
Binary files differ
diff --git a/tests/ref/repeat-unboxed.png b/tests/ref/repeat-unboxed.png
new file mode 100644
index 00000000..91678cea
--- /dev/null
+++ b/tests/ref/repeat-unboxed.png
Binary files differ
diff --git a/tests/ref/repr-color.png b/tests/ref/repr-color.png
new file mode 100644
index 00000000..3425f7d4
--- /dev/null
+++ b/tests/ref/repr-color.png
Binary files differ
diff --git a/tests/ref/repr-literals.png b/tests/ref/repr-literals.png
new file mode 100644
index 00000000..1e8e85a4
--- /dev/null
+++ b/tests/ref/repr-literals.png
Binary files differ
diff --git a/tests/ref/repr-misc.png b/tests/ref/repr-misc.png
new file mode 100644
index 00000000..9a876091
--- /dev/null
+++ b/tests/ref/repr-misc.png
Binary files differ
diff --git a/tests/ref/repr-numerical.png b/tests/ref/repr-numerical.png
new file mode 100644
index 00000000..1c109a22
--- /dev/null
+++ b/tests/ref/repr-numerical.png
Binary files differ
diff --git a/tests/ref/return-in-nested-content-block.png b/tests/ref/return-in-nested-content-block.png
new file mode 100644
index 00000000..d688741c
--- /dev/null
+++ b/tests/ref/return-in-nested-content-block.png
Binary files differ
diff --git a/tests/ref/set-if.png b/tests/ref/set-if.png
new file mode 100644
index 00000000..08dc5e82
--- /dev/null
+++ b/tests/ref/set-if.png
Binary files differ
diff --git a/tests/ref/set-instantiation-site-markup.png b/tests/ref/set-instantiation-site-markup.png
new file mode 100644
index 00000000..180444b9
--- /dev/null
+++ b/tests/ref/set-instantiation-site-markup.png
Binary files differ
diff --git a/tests/ref/set-instantiation-site.png b/tests/ref/set-instantiation-site.png
new file mode 100644
index 00000000..593d3e2d
--- /dev/null
+++ b/tests/ref/set-instantiation-site.png
Binary files differ
diff --git a/tests/ref/set-scoped-in-code-block.png b/tests/ref/set-scoped-in-code-block.png
new file mode 100644
index 00000000..8941f6c4
--- /dev/null
+++ b/tests/ref/set-scoped-in-code-block.png
Binary files differ
diff --git a/tests/ref/set-text-override.png b/tests/ref/set-text-override.png
new file mode 100644
index 00000000..83623876
--- /dev/null
+++ b/tests/ref/set-text-override.png
Binary files differ
diff --git a/tests/ref/set-vs-construct-1.png b/tests/ref/set-vs-construct-1.png
new file mode 100644
index 00000000..597e9674
--- /dev/null
+++ b/tests/ref/set-vs-construct-1.png
Binary files differ
diff --git a/tests/ref/set-vs-construct-2.png b/tests/ref/set-vs-construct-2.png
new file mode 100644
index 00000000..2fedd0b4
--- /dev/null
+++ b/tests/ref/set-vs-construct-2.png
Binary files differ
diff --git a/tests/ref/set-vs-construct-3.png b/tests/ref/set-vs-construct-3.png
new file mode 100644
index 00000000..dff0c8af
--- /dev/null
+++ b/tests/ref/set-vs-construct-3.png
Binary files differ
diff --git a/tests/ref/set-vs-construct-4.png b/tests/ref/set-vs-construct-4.png
new file mode 100644
index 00000000..1f6834ef
--- /dev/null
+++ b/tests/ref/set-vs-construct-4.png
Binary files differ
diff --git a/tests/ref/shaping-emoji-bad-zwj.png b/tests/ref/shaping-emoji-bad-zwj.png
new file mode 100644
index 00000000..544d64ee
--- /dev/null
+++ b/tests/ref/shaping-emoji-bad-zwj.png
Binary files differ
diff --git a/tests/ref/shaping-emoji-basic.png b/tests/ref/shaping-emoji-basic.png
new file mode 100644
index 00000000..090ea611
--- /dev/null
+++ b/tests/ref/shaping-emoji-basic.png
Binary files differ
diff --git a/tests/ref/shaping-font-fallback.png b/tests/ref/shaping-font-fallback.png
new file mode 100644
index 00000000..813e3915
--- /dev/null
+++ b/tests/ref/shaping-font-fallback.png
Binary files differ
diff --git a/tests/ref/shaping-forced-script-font-feature-enabled.png b/tests/ref/shaping-forced-script-font-feature-enabled.png
new file mode 100644
index 00000000..0a10087a
--- /dev/null
+++ b/tests/ref/shaping-forced-script-font-feature-enabled.png
Binary files differ
diff --git a/tests/ref/shaping-forced-script-font-feature-inhibited.png b/tests/ref/shaping-forced-script-font-feature-inhibited.png
new file mode 100644
index 00000000..77d8010e
--- /dev/null
+++ b/tests/ref/shaping-forced-script-font-feature-inhibited.png
Binary files differ
diff --git a/tests/ref/shaping-script-separation.png b/tests/ref/shaping-script-separation.png
new file mode 100644
index 00000000..68170dd9
--- /dev/null
+++ b/tests/ref/shaping-script-separation.png
Binary files differ
diff --git a/tests/ref/shorthand-dashes.png b/tests/ref/shorthand-dashes.png
new file mode 100644
index 00000000..f8b4191f
--- /dev/null
+++ b/tests/ref/shorthand-dashes.png
Binary files differ
diff --git a/tests/ref/shorthand-ellipsis.png b/tests/ref/shorthand-ellipsis.png
new file mode 100644
index 00000000..df9a9241
--- /dev/null
+++ b/tests/ref/shorthand-ellipsis.png
Binary files differ
diff --git a/tests/ref/shorthand-nbsp-and-shy-hyphen.png b/tests/ref/shorthand-nbsp-and-shy-hyphen.png
new file mode 100644
index 00000000..e8c81aaa
--- /dev/null
+++ b/tests/ref/shorthand-nbsp-and-shy-hyphen.png
Binary files differ
diff --git a/tests/ref/shorthand-nbsp-width.png b/tests/ref/shorthand-nbsp-width.png
new file mode 100644
index 00000000..a92988cf
--- /dev/null
+++ b/tests/ref/shorthand-nbsp-width.png
Binary files differ
diff --git a/tests/ref/shorthands-math.png b/tests/ref/shorthands-math.png
new file mode 100644
index 00000000..0514fa62
--- /dev/null
+++ b/tests/ref/shorthands-math.png
Binary files differ
diff --git a/tests/ref/show-bare-basic.png b/tests/ref/show-bare-basic.png
new file mode 100644
index 00000000..e389b506
--- /dev/null
+++ b/tests/ref/show-bare-basic.png
Binary files differ
diff --git a/tests/ref/show-bare-content-block.png b/tests/ref/show-bare-content-block.png
new file mode 100644
index 00000000..2631092b
--- /dev/null
+++ b/tests/ref/show-bare-content-block.png
Binary files differ
diff --git a/tests/ref/show-bare-replace-with-content.png b/tests/ref/show-bare-replace-with-content.png
new file mode 100644
index 00000000..51e36a49
--- /dev/null
+++ b/tests/ref/show-bare-replace-with-content.png
Binary files differ
diff --git a/tests/ref/show-bare-vs-set-text.png b/tests/ref/show-bare-vs-set-text.png
new file mode 100644
index 00000000..b1e15d98
--- /dev/null
+++ b/tests/ref/show-bare-vs-set-text.png
Binary files differ
diff --git a/tests/ref/show-function-order-with-set.png b/tests/ref/show-function-order-with-set.png
new file mode 100644
index 00000000..a59f7274
--- /dev/null
+++ b/tests/ref/show-function-order-with-set.png
Binary files differ
diff --git a/tests/ref/show-function-set-on-it.png b/tests/ref/show-function-set-on-it.png
new file mode 100644
index 00000000..6c545e95
--- /dev/null
+++ b/tests/ref/show-function-set-on-it.png
Binary files differ
diff --git a/tests/ref/show-in-show.png b/tests/ref/show-in-show.png
new file mode 100644
index 00000000..65280ad7
--- /dev/null
+++ b/tests/ref/show-in-show.png
Binary files differ
diff --git a/tests/ref/show-multiple-rules.png b/tests/ref/show-multiple-rules.png
new file mode 100644
index 00000000..c92b6269
--- /dev/null
+++ b/tests/ref/show-multiple-rules.png
Binary files differ
diff --git a/tests/ref/show-nested-scopes.png b/tests/ref/show-nested-scopes.png
new file mode 100644
index 00000000..ac0a8125
--- /dev/null
+++ b/tests/ref/show-nested-scopes.png
Binary files differ
diff --git a/tests/ref/show-recursive-identity.png b/tests/ref/show-recursive-identity.png
new file mode 100644
index 00000000..6c545e95
--- /dev/null
+++ b/tests/ref/show-recursive-identity.png
Binary files differ
diff --git a/tests/ref/show-recursive-multiple.png b/tests/ref/show-recursive-multiple.png
new file mode 100644
index 00000000..b56b089c
--- /dev/null
+++ b/tests/ref/show-recursive-multiple.png
Binary files differ
diff --git a/tests/ref/show-rule-in-function.png b/tests/ref/show-rule-in-function.png
new file mode 100644
index 00000000..97aa2845
--- /dev/null
+++ b/tests/ref/show-rule-in-function.png
Binary files differ
diff --git a/tests/ref/show-selector-basic.png b/tests/ref/show-selector-basic.png
new file mode 100644
index 00000000..870166d9
--- /dev/null
+++ b/tests/ref/show-selector-basic.png
Binary files differ
diff --git a/tests/ref/show-selector-discard.png b/tests/ref/show-selector-discard.png
new file mode 100644
index 00000000..13c9f0d6
--- /dev/null
+++ b/tests/ref/show-selector-discard.png
Binary files differ
diff --git a/tests/ref/show-selector-element-or-label.png b/tests/ref/show-selector-element-or-label.png
new file mode 100644
index 00000000..32cd992d
--- /dev/null
+++ b/tests/ref/show-selector-element-or-label.png
Binary files differ
diff --git a/tests/ref/show-selector-or-elements-with-set.png b/tests/ref/show-selector-or-elements-with-set.png
new file mode 100644
index 00000000..f561cad8
--- /dev/null
+++ b/tests/ref/show-selector-or-elements-with-set.png
Binary files differ
diff --git a/tests/ref/show-selector-realistic.png b/tests/ref/show-selector-realistic.png
new file mode 100644
index 00000000..ae4f4a9a
--- /dev/null
+++ b/tests/ref/show-selector-realistic.png
Binary files differ
diff --git a/tests/ref/show-selector-replace-and-show-set.png b/tests/ref/show-selector-replace-and-show-set.png
new file mode 100644
index 00000000..47a7ae33
--- /dev/null
+++ b/tests/ref/show-selector-replace-and-show-set.png
Binary files differ
diff --git a/tests/ref/show-selector-replace.png b/tests/ref/show-selector-replace.png
new file mode 100644
index 00000000..c00a88e8
--- /dev/null
+++ b/tests/ref/show-selector-replace.png
Binary files differ
diff --git a/tests/ref/show-selector-where.png b/tests/ref/show-selector-where.png
new file mode 100644
index 00000000..4cb02efd
--- /dev/null
+++ b/tests/ref/show-selector-where.png
Binary files differ
diff --git a/tests/ref/show-set-on-layoutable-element.png b/tests/ref/show-set-on-layoutable-element.png
new file mode 100644
index 00000000..701bea50
--- /dev/null
+++ b/tests/ref/show-set-on-layoutable-element.png
Binary files differ
diff --git a/tests/ref/show-set-on-same-element.png b/tests/ref/show-set-on-same-element.png
new file mode 100644
index 00000000..9459fca0
--- /dev/null
+++ b/tests/ref/show-set-on-same-element.png
Binary files differ
diff --git a/tests/ref/show-set-override.png b/tests/ref/show-set-override.png
new file mode 100644
index 00000000..e7831b90
--- /dev/null
+++ b/tests/ref/show-set-override.png
Binary files differ
diff --git a/tests/ref/show-set-same-element-and-order.png b/tests/ref/show-set-same-element-and-order.png
new file mode 100644
index 00000000..d55d5e14
--- /dev/null
+++ b/tests/ref/show-set-same-element-and-order.png
Binary files differ
diff --git a/tests/ref/show-set-same-element-matched-field.png b/tests/ref/show-set-same-element-matched-field.png
new file mode 100644
index 00000000..aa44baee
--- /dev/null
+++ b/tests/ref/show-set-same-element-matched-field.png
Binary files differ
diff --git a/tests/ref/show-set-same-element-matching-interaction.png b/tests/ref/show-set-same-element-matching-interaction.png
new file mode 100644
index 00000000..bc061038
--- /dev/null
+++ b/tests/ref/show-set-same-element-matching-interaction.png
Binary files differ
diff --git a/tests/ref/show-set-same-element-synthesized-matched-field.png b/tests/ref/show-set-same-element-synthesized-matched-field.png
new file mode 100644
index 00000000..c3918e8f
--- /dev/null
+++ b/tests/ref/show-set-same-element-synthesized-matched-field.png
Binary files differ
diff --git a/tests/ref/show-set-text-order-adjacent-1.png b/tests/ref/show-set-text-order-adjacent-1.png
new file mode 100644
index 00000000..1bc95e3b
--- /dev/null
+++ b/tests/ref/show-set-text-order-adjacent-1.png
Binary files differ
diff --git a/tests/ref/show-set-text-order-adjacent-2.png b/tests/ref/show-set-text-order-adjacent-2.png
new file mode 100644
index 00000000..caada91a
--- /dev/null
+++ b/tests/ref/show-set-text-order-adjacent-2.png
Binary files differ
diff --git a/tests/ref/show-set-text-order-contained-1.png b/tests/ref/show-set-text-order-contained-1.png
new file mode 100644
index 00000000..8deaaacd
--- /dev/null
+++ b/tests/ref/show-set-text-order-contained-1.png
Binary files differ
diff --git a/tests/ref/show-set-text-order-contained-2.png b/tests/ref/show-set-text-order-contained-2.png
new file mode 100644
index 00000000..00ea3fb8
--- /dev/null
+++ b/tests/ref/show-set-text-order-contained-2.png
Binary files differ
diff --git a/tests/ref/show-set-text-order-contained-3.png b/tests/ref/show-set-text-order-contained-3.png
new file mode 100644
index 00000000..1bc95e3b
--- /dev/null
+++ b/tests/ref/show-set-text-order-contained-3.png
Binary files differ
diff --git a/tests/ref/show-set-text-order-contained-4.png b/tests/ref/show-set-text-order-contained-4.png
new file mode 100644
index 00000000..0946f922
--- /dev/null
+++ b/tests/ref/show-set-text-order-contained-4.png
Binary files differ
diff --git a/tests/ref/show-set-text-order-overlapping-1.png b/tests/ref/show-set-text-order-overlapping-1.png
new file mode 100644
index 00000000..71222567
--- /dev/null
+++ b/tests/ref/show-set-text-order-overlapping-1.png
Binary files differ
diff --git a/tests/ref/show-set-text-order-overlapping-2.png b/tests/ref/show-set-text-order-overlapping-2.png
new file mode 100644
index 00000000..f1b658f2
--- /dev/null
+++ b/tests/ref/show-set-text-order-overlapping-2.png
Binary files differ
diff --git a/tests/ref/show-set-vs-construct.png b/tests/ref/show-set-vs-construct.png
new file mode 100644
index 00000000..a0ec96bf
--- /dev/null
+++ b/tests/ref/show-set-vs-construct.png
Binary files differ
diff --git a/tests/ref/show-set-where-override.png b/tests/ref/show-set-where-override.png
new file mode 100644
index 00000000..7f1ec60d
--- /dev/null
+++ b/tests/ref/show-set-where-override.png
Binary files differ
diff --git a/tests/ref/show-text-basic.png b/tests/ref/show-text-basic.png
new file mode 100644
index 00000000..29bb5840
--- /dev/null
+++ b/tests/ref/show-text-basic.png
Binary files differ
diff --git a/tests/ref/show-text-cyclic-raw.png b/tests/ref/show-text-cyclic-raw.png
new file mode 100644
index 00000000..b7521c44
--- /dev/null
+++ b/tests/ref/show-text-cyclic-raw.png
Binary files differ
diff --git a/tests/ref/show-text-cyclic.png b/tests/ref/show-text-cyclic.png
new file mode 100644
index 00000000..4c4c4886
--- /dev/null
+++ b/tests/ref/show-text-cyclic.png
Binary files differ
diff --git a/tests/ref/show-text-exactly-once.png b/tests/ref/show-text-exactly-once.png
new file mode 100644
index 00000000..f681f721
--- /dev/null
+++ b/tests/ref/show-text-exactly-once.png
Binary files differ
diff --git a/tests/ref/show-text-get-text-on-it.png b/tests/ref/show-text-get-text-on-it.png
new file mode 100644
index 00000000..5c75b9de
--- /dev/null
+++ b/tests/ref/show-text-get-text-on-it.png
Binary files differ
diff --git a/tests/ref/show-text-in-other-show.png b/tests/ref/show-text-in-other-show.png
new file mode 100644
index 00000000..f29de999
--- /dev/null
+++ b/tests/ref/show-text-in-other-show.png
Binary files differ
diff --git a/tests/ref/show-text-indirectly-cyclic.png b/tests/ref/show-text-indirectly-cyclic.png
new file mode 100644
index 00000000..de166dca
--- /dev/null
+++ b/tests/ref/show-text-indirectly-cyclic.png
Binary files differ
diff --git a/tests/ref/show-text-path-resolving.png b/tests/ref/show-text-path-resolving.png
new file mode 100644
index 00000000..1a04f9e6
--- /dev/null
+++ b/tests/ref/show-text-path-resolving.png
Binary files differ
diff --git a/tests/ref/show-text-regex-case-insensitive.png b/tests/ref/show-text-regex-case-insensitive.png
new file mode 100644
index 00000000..70d70d34
--- /dev/null
+++ b/tests/ref/show-text-regex-case-insensitive.png
Binary files differ
diff --git a/tests/ref/show-text-regex-character-class.png b/tests/ref/show-text-regex-character-class.png
new file mode 100644
index 00000000..946c5d22
--- /dev/null
+++ b/tests/ref/show-text-regex-character-class.png
Binary files differ
diff --git a/tests/ref/show-text-regex-word-boundary.png b/tests/ref/show-text-regex-word-boundary.png
new file mode 100644
index 00000000..c171ac02
--- /dev/null
+++ b/tests/ref/show-text-regex-word-boundary.png
Binary files differ
diff --git a/tests/ref/show-text-regex.png b/tests/ref/show-text-regex.png
new file mode 100644
index 00000000..85db10a3
--- /dev/null
+++ b/tests/ref/show-text-regex.png
Binary files differ
diff --git a/tests/ref/show-where-folding-stroke.png b/tests/ref/show-where-folding-stroke.png
new file mode 100644
index 00000000..186ce681
--- /dev/null
+++ b/tests/ref/show-where-folding-stroke.png
Binary files differ
diff --git a/tests/ref/show-where-folding-text-size.png b/tests/ref/show-where-folding-text-size.png
new file mode 100644
index 00000000..9fbe3ff9
--- /dev/null
+++ b/tests/ref/show-where-folding-text-size.png
Binary files differ
diff --git a/tests/ref/show-where-optional-field-raw.png b/tests/ref/show-where-optional-field-raw.png
new file mode 100644
index 00000000..dd381610
--- /dev/null
+++ b/tests/ref/show-where-optional-field-raw.png
Binary files differ
diff --git a/tests/ref/show-where-optional-field-text.png b/tests/ref/show-where-optional-field-text.png
new file mode 100644
index 00000000..b1367d09
--- /dev/null
+++ b/tests/ref/show-where-optional-field-text.png
Binary files differ
diff --git a/tests/ref/show-where-resolving-hyphenate.png b/tests/ref/show-where-resolving-hyphenate.png
new file mode 100644
index 00000000..052a2eda
--- /dev/null
+++ b/tests/ref/show-where-resolving-hyphenate.png
Binary files differ
diff --git a/tests/ref/show-where-resolving-length.png b/tests/ref/show-where-resolving-length.png
new file mode 100644
index 00000000..4c77f2ac
--- /dev/null
+++ b/tests/ref/show-where-resolving-length.png
Binary files differ
diff --git a/tests/ref/smallcaps.png b/tests/ref/smallcaps.png
new file mode 100644
index 00000000..b5ee12b7
--- /dev/null
+++ b/tests/ref/smallcaps.png
Binary files differ
diff --git a/tests/ref/smartquote-apostrophe.png b/tests/ref/smartquote-apostrophe.png
new file mode 100644
index 00000000..d2cc1ebf
--- /dev/null
+++ b/tests/ref/smartquote-apostrophe.png
Binary files differ
diff --git a/tests/ref/smartquote-custom-complex.png b/tests/ref/smartquote-custom-complex.png
new file mode 100644
index 00000000..7204a997
--- /dev/null
+++ b/tests/ref/smartquote-custom-complex.png
Binary files differ
diff --git a/tests/ref/smartquote-custom.png b/tests/ref/smartquote-custom.png
new file mode 100644
index 00000000..6a6bd9d1
--- /dev/null
+++ b/tests/ref/smartquote-custom.png
Binary files differ
diff --git a/tests/ref/smartquote-disable.png b/tests/ref/smartquote-disable.png
new file mode 100644
index 00000000..0218b7ac
--- /dev/null
+++ b/tests/ref/smartquote-disable.png
Binary files differ
diff --git a/tests/ref/smartquote-disabled-temporarily.png b/tests/ref/smartquote-disabled-temporarily.png
new file mode 100644
index 00000000..84bc5e32
--- /dev/null
+++ b/tests/ref/smartquote-disabled-temporarily.png
Binary files differ
diff --git a/tests/ref/smartquote-empty.png b/tests/ref/smartquote-empty.png
new file mode 100644
index 00000000..f9f19989
--- /dev/null
+++ b/tests/ref/smartquote-empty.png
Binary files differ
diff --git a/tests/ref/smartquote-escape.png b/tests/ref/smartquote-escape.png
new file mode 100644
index 00000000..45d8f602
--- /dev/null
+++ b/tests/ref/smartquote-escape.png
Binary files differ
diff --git a/tests/ref/smartquote-nesting.png b/tests/ref/smartquote-nesting.png
new file mode 100644
index 00000000..1f38c097
--- /dev/null
+++ b/tests/ref/smartquote-nesting.png
Binary files differ
diff --git a/tests/ref/smartquote.png b/tests/ref/smartquote.png
new file mode 100644
index 00000000..070e0487
--- /dev/null
+++ b/tests/ref/smartquote.png
Binary files differ
diff --git a/tests/ref/space-collapsing-comments.png b/tests/ref/space-collapsing-comments.png
new file mode 100644
index 00000000..b35d9fec
--- /dev/null
+++ b/tests/ref/space-collapsing-comments.png
Binary files differ
diff --git a/tests/ref/space-collapsing-linebreaks.png b/tests/ref/space-collapsing-linebreaks.png
new file mode 100644
index 00000000..b1f4a3af
--- /dev/null
+++ b/tests/ref/space-collapsing-linebreaks.png
Binary files differ
diff --git a/tests/ref/space-collapsing-stringy-linebreak.png b/tests/ref/space-collapsing-stringy-linebreak.png
new file mode 100644
index 00000000..ceec6da7
--- /dev/null
+++ b/tests/ref/space-collapsing-stringy-linebreak.png
Binary files differ
diff --git a/tests/ref/space-collapsing-with-h.png b/tests/ref/space-collapsing-with-h.png
new file mode 100644
index 00000000..c2e253e7
--- /dev/null
+++ b/tests/ref/space-collapsing-with-h.png
Binary files differ
diff --git a/tests/ref/space-collapsing.png b/tests/ref/space-collapsing.png
new file mode 100644
index 00000000..32bd6039
--- /dev/null
+++ b/tests/ref/space-collapsing.png
Binary files differ
diff --git a/tests/ref/space-ideographic-kept.png b/tests/ref/space-ideographic-kept.png
new file mode 100644
index 00000000..cd292e2d
--- /dev/null
+++ b/tests/ref/space-ideographic-kept.png
Binary files differ
diff --git a/tests/ref/space-thin-kept.png b/tests/ref/space-thin-kept.png
new file mode 100644
index 00000000..6ed3504b
--- /dev/null
+++ b/tests/ref/space-thin-kept.png
Binary files differ
diff --git a/tests/ref/space-trailing-linebreak.png b/tests/ref/space-trailing-linebreak.png
new file mode 100644
index 00000000..42b28264
--- /dev/null
+++ b/tests/ref/space-trailing-linebreak.png
Binary files differ
diff --git a/tests/ref/spacing-h-and-v.png b/tests/ref/spacing-h-and-v.png
new file mode 100644
index 00000000..2c9a2960
--- /dev/null
+++ b/tests/ref/spacing-h-and-v.png
Binary files differ
diff --git a/tests/ref/spacing-rtl.png b/tests/ref/spacing-rtl.png
new file mode 100644
index 00000000..a9cbbca6
--- /dev/null
+++ b/tests/ref/spacing-rtl.png
Binary files differ
diff --git a/tests/ref/square-auto-sized.png b/tests/ref/square-auto-sized.png
new file mode 100644
index 00000000..a2c4a36e
--- /dev/null
+++ b/tests/ref/square-auto-sized.png
Binary files differ
diff --git a/tests/ref/square-base.png b/tests/ref/square-base.png
new file mode 100644
index 00000000..3ef753f2
--- /dev/null
+++ b/tests/ref/square-base.png
Binary files differ
diff --git a/tests/ref/square-circle-alignment.png b/tests/ref/square-circle-alignment.png
new file mode 100644
index 00000000..3fff9e66
--- /dev/null
+++ b/tests/ref/square-circle-alignment.png
Binary files differ
diff --git a/tests/ref/square-circle-overspecified.png b/tests/ref/square-circle-overspecified.png
new file mode 100644
index 00000000..6dde5e51
--- /dev/null
+++ b/tests/ref/square-circle-overspecified.png
Binary files differ
diff --git a/tests/ref/square-contents-overflow.png b/tests/ref/square-contents-overflow.png
new file mode 100644
index 00000000..ae65b0a8
--- /dev/null
+++ b/tests/ref/square-contents-overflow.png
Binary files differ
diff --git a/tests/ref/square-height-limited-stack.png b/tests/ref/square-height-limited-stack.png
new file mode 100644
index 00000000..f52c608d
--- /dev/null
+++ b/tests/ref/square-height-limited-stack.png
Binary files differ
diff --git a/tests/ref/square-height-limited.png b/tests/ref/square-height-limited.png
new file mode 100644
index 00000000..c01dc426
--- /dev/null
+++ b/tests/ref/square-height-limited.png
Binary files differ
diff --git a/tests/ref/square-overflow.png b/tests/ref/square-overflow.png
new file mode 100644
index 00000000..6169f305
--- /dev/null
+++ b/tests/ref/square-overflow.png
Binary files differ
diff --git a/tests/ref/square-rect-rounded.png b/tests/ref/square-rect-rounded.png
new file mode 100644
index 00000000..678ba819
--- /dev/null
+++ b/tests/ref/square-rect-rounded.png
Binary files differ
diff --git a/tests/ref/square-relative-size.png b/tests/ref/square-relative-size.png
new file mode 100644
index 00000000..96e744e6
--- /dev/null
+++ b/tests/ref/square-relative-size.png
Binary files differ
diff --git a/tests/ref/square-relatively-sized-child.png b/tests/ref/square-relatively-sized-child.png
new file mode 100644
index 00000000..3ffe3105
--- /dev/null
+++ b/tests/ref/square-relatively-sized-child.png
Binary files differ
diff --git a/tests/ref/square.png b/tests/ref/square.png
new file mode 100644
index 00000000..e6f8f5c8
--- /dev/null
+++ b/tests/ref/square.png
Binary files differ
diff --git a/tests/ref/stack-basic.png b/tests/ref/stack-basic.png
new file mode 100644
index 00000000..b5f38a83
--- /dev/null
+++ b/tests/ref/stack-basic.png
Binary files differ
diff --git a/tests/ref/stack-fr.png b/tests/ref/stack-fr.png
new file mode 100644
index 00000000..e34dd9b1
--- /dev/null
+++ b/tests/ref/stack-fr.png
Binary files differ
diff --git a/tests/ref/stack-overflow.png b/tests/ref/stack-overflow.png
new file mode 100644
index 00000000..43b3625a
--- /dev/null
+++ b/tests/ref/stack-overflow.png
Binary files differ
diff --git a/tests/ref/stack-rtl-align-and-fr.png b/tests/ref/stack-rtl-align-and-fr.png
new file mode 100644
index 00000000..653ade6f
--- /dev/null
+++ b/tests/ref/stack-rtl-align-and-fr.png
Binary files differ
diff --git a/tests/ref/stack-spacing.png b/tests/ref/stack-spacing.png
new file mode 100644
index 00000000..9667f657
--- /dev/null
+++ b/tests/ref/stack-spacing.png
Binary files differ
diff --git a/tests/ref/state-basic.png b/tests/ref/state-basic.png
new file mode 100644
index 00000000..0c67a751
--- /dev/null
+++ b/tests/ref/state-basic.png
Binary files differ
diff --git a/tests/ref/state-multiple-calls-same-key.png b/tests/ref/state-multiple-calls-same-key.png
new file mode 100644
index 00000000..077b6792
--- /dev/null
+++ b/tests/ref/state-multiple-calls-same-key.png
Binary files differ
diff --git a/tests/ref/state-nested.png b/tests/ref/state-nested.png
new file mode 100644
index 00000000..cc701600
--- /dev/null
+++ b/tests/ref/state-nested.png
Binary files differ
diff --git a/tests/ref/state-no-convergence.png b/tests/ref/state-no-convergence.png
new file mode 100644
index 00000000..dd44b9e1
--- /dev/null
+++ b/tests/ref/state-no-convergence.png
Binary files differ
diff --git a/tests/ref/strike-background.png b/tests/ref/strike-background.png
new file mode 100644
index 00000000..01861d25
--- /dev/null
+++ b/tests/ref/strike-background.png
Binary files differ
diff --git a/tests/ref/strike-with.png b/tests/ref/strike-with.png
new file mode 100644
index 00000000..59a84150
--- /dev/null
+++ b/tests/ref/strike-with.png
Binary files differ
diff --git a/tests/ref/stroke-composition.png b/tests/ref/stroke-composition.png
new file mode 100644
index 00000000..a6c7ce70
--- /dev/null
+++ b/tests/ref/stroke-composition.png
Binary files differ
diff --git a/tests/ref/stroke-folding.png b/tests/ref/stroke-folding.png
new file mode 100644
index 00000000..b4f1b1a9
--- /dev/null
+++ b/tests/ref/stroke-folding.png
Binary files differ
diff --git a/tests/ref/stroke-text.png b/tests/ref/stroke-text.png
new file mode 100644
index 00000000..ac09053a
--- /dev/null
+++ b/tests/ref/stroke-text.png
Binary files differ
diff --git a/tests/ref/stroke-zero-thickness.png b/tests/ref/stroke-zero-thickness.png
new file mode 100644
index 00000000..6d305eaf
--- /dev/null
+++ b/tests/ref/stroke-zero-thickness.png
Binary files differ
diff --git a/tests/ref/strong-delta.png b/tests/ref/strong-delta.png
new file mode 100644
index 00000000..d32459f6
--- /dev/null
+++ b/tests/ref/strong-delta.png
Binary files differ
diff --git a/tests/ref/strong-double-star-empty-hint.png b/tests/ref/strong-double-star-empty-hint.png
new file mode 100644
index 00000000..29cbb90f
--- /dev/null
+++ b/tests/ref/strong-double-star-empty-hint.png
Binary files differ
diff --git a/tests/ref/sub-super-non-typographic.png b/tests/ref/sub-super-non-typographic.png
new file mode 100644
index 00000000..e5a8b673
--- /dev/null
+++ b/tests/ref/sub-super-non-typographic.png
Binary files differ
diff --git a/tests/ref/sub-super.png b/tests/ref/sub-super.png
new file mode 100644
index 00000000..9359cf01
--- /dev/null
+++ b/tests/ref/sub-super.png
Binary files differ
diff --git a/tests/ref/super-underline.png b/tests/ref/super-underline.png
new file mode 100644
index 00000000..99c1c309
--- /dev/null
+++ b/tests/ref/super-underline.png
Binary files differ
diff --git a/tests/ref/symbol-constructor.png b/tests/ref/symbol-constructor.png
new file mode 100644
index 00000000..e6db9491
--- /dev/null
+++ b/tests/ref/symbol-constructor.png
Binary files differ
diff --git a/tests/ref/symbol.png b/tests/ref/symbol.png
new file mode 100644
index 00000000..37339d59
--- /dev/null
+++ b/tests/ref/symbol.png
Binary files differ
diff --git a/tests/ref/table-align-array.png b/tests/ref/table-align-array.png
new file mode 100644
index 00000000..9242ae12
--- /dev/null
+++ b/tests/ref/table-align-array.png
Binary files differ
diff --git a/tests/ref/table-cell-align-override.png b/tests/ref/table-cell-align-override.png
new file mode 100644
index 00000000..dfab2bb0
--- /dev/null
+++ b/tests/ref/table-cell-align-override.png
Binary files differ
diff --git a/tests/ref/table-cell-folding.png b/tests/ref/table-cell-folding.png
new file mode 100644
index 00000000..94897a92
--- /dev/null
+++ b/tests/ref/table-cell-folding.png
Binary files differ
diff --git a/tests/ref/table-cell-override.png b/tests/ref/table-cell-override.png
new file mode 100644
index 00000000..d6f37d63
--- /dev/null
+++ b/tests/ref/table-cell-override.png
Binary files differ
diff --git a/tests/ref/table-cell-set.png b/tests/ref/table-cell-set.png
new file mode 100644
index 00000000..ce873b05
--- /dev/null
+++ b/tests/ref/table-cell-set.png
Binary files differ
diff --git a/tests/ref/table-cell-show-and-override.png b/tests/ref/table-cell-show-and-override.png
new file mode 100644
index 00000000..df745802
--- /dev/null
+++ b/tests/ref/table-cell-show-and-override.png
Binary files differ
diff --git a/tests/ref/table-cell-show-based-on-position.png b/tests/ref/table-cell-show-based-on-position.png
new file mode 100644
index 00000000..db46e260
--- /dev/null
+++ b/tests/ref/table-cell-show-based-on-position.png
Binary files differ
diff --git a/tests/ref/table-cell-show-emph.png b/tests/ref/table-cell-show-emph.png
new file mode 100644
index 00000000..1afc833b
--- /dev/null
+++ b/tests/ref/table-cell-show-emph.png
Binary files differ
diff --git a/tests/ref/table-cell-show.png b/tests/ref/table-cell-show.png
new file mode 100644
index 00000000..9ac6d269
--- /dev/null
+++ b/tests/ref/table-cell-show.png
Binary files differ
diff --git a/tests/ref/table-cell-various-overrides.png b/tests/ref/table-cell-various-overrides.png
new file mode 100644
index 00000000..c8540dfe
--- /dev/null
+++ b/tests/ref/table-cell-various-overrides.png
Binary files differ
diff --git a/tests/ref/table-fill-basic.png b/tests/ref/table-fill-basic.png
new file mode 100644
index 00000000..bc12f8ae
--- /dev/null
+++ b/tests/ref/table-fill-basic.png
Binary files differ
diff --git a/tests/ref/table-gutters.png b/tests/ref/table-gutters.png
new file mode 100644
index 00000000..697ddd48
--- /dev/null
+++ b/tests/ref/table-gutters.png
Binary files differ
diff --git a/tests/ref/table-inset-fold.png b/tests/ref/table-inset-fold.png
new file mode 100644
index 00000000..f2985c9e
--- /dev/null
+++ b/tests/ref/table-inset-fold.png
Binary files differ
diff --git a/tests/ref/table-inset.png b/tests/ref/table-inset.png
new file mode 100644
index 00000000..a8a9adda
--- /dev/null
+++ b/tests/ref/table-inset.png
Binary files differ
diff --git a/tests/ref/table-newlines.png b/tests/ref/table-newlines.png
new file mode 100644
index 00000000..a4da25f3
--- /dev/null
+++ b/tests/ref/table-newlines.png
Binary files differ
diff --git a/tests/ref/table-stroke-vline-position-left-and-right.png b/tests/ref/table-stroke-vline-position-left-and-right.png
new file mode 100644
index 00000000..53b48a10
--- /dev/null
+++ b/tests/ref/table-stroke-vline-position-left-and-right.png
Binary files differ
diff --git a/tests/ref/terms-built-in-loop.png b/tests/ref/terms-built-in-loop.png
new file mode 100644
index 00000000..dc103af9
--- /dev/null
+++ b/tests/ref/terms-built-in-loop.png
Binary files differ
diff --git a/tests/ref/terms-constructor.png b/tests/ref/terms-constructor.png
new file mode 100644
index 00000000..fe161505
--- /dev/null
+++ b/tests/ref/terms-constructor.png
Binary files differ
diff --git a/tests/ref/terms-grid.png b/tests/ref/terms-grid.png
new file mode 100644
index 00000000..6142becf
--- /dev/null
+++ b/tests/ref/terms-grid.png
Binary files differ
diff --git a/tests/ref/terms-multiline.png b/tests/ref/terms-multiline.png
new file mode 100644
index 00000000..b5baea4a
--- /dev/null
+++ b/tests/ref/terms-multiline.png
Binary files differ
diff --git a/tests/ref/terms-rtl.png b/tests/ref/terms-rtl.png
new file mode 100644
index 00000000..538571dd
--- /dev/null
+++ b/tests/ref/terms-rtl.png
Binary files differ
diff --git a/tests/ref/terms-style-change-interrupted.png b/tests/ref/terms-style-change-interrupted.png
new file mode 100644
index 00000000..846e45e1
--- /dev/null
+++ b/tests/ref/terms-style-change-interrupted.png
Binary files differ
diff --git a/tests/ref/terms-syntax-edge-cases.png b/tests/ref/terms-syntax-edge-cases.png
new file mode 100644
index 00000000..e2a557c1
--- /dev/null
+++ b/tests/ref/terms-syntax-edge-cases.png
Binary files differ
diff --git a/tests/ref/text-alternates-and-stylistic-sets.png b/tests/ref/text-alternates-and-stylistic-sets.png
new file mode 100644
index 00000000..877542fc
--- /dev/null
+++ b/tests/ref/text-alternates-and-stylistic-sets.png
Binary files differ
diff --git a/tests/ref/text-call-body.png b/tests/ref/text-call-body.png
new file mode 100644
index 00000000..24cdeb9f
--- /dev/null
+++ b/tests/ref/text-call-body.png
Binary files differ
diff --git a/tests/ref/text-chinese-basic.png b/tests/ref/text-chinese-basic.png
new file mode 100644
index 00000000..ea4a0b82
--- /dev/null
+++ b/tests/ref/text-chinese-basic.png
Binary files differ
diff --git a/tests/ref/text-cjk-latin-spacing.png b/tests/ref/text-cjk-latin-spacing.png
new file mode 100644
index 00000000..1906bf76
--- /dev/null
+++ b/tests/ref/text-cjk-latin-spacing.png
Binary files differ
diff --git a/tests/ref/text-copy-paste-ligatures.png b/tests/ref/text-copy-paste-ligatures.png
new file mode 100644
index 00000000..f0f36a86
--- /dev/null
+++ b/tests/ref/text-copy-paste-ligatures.png
Binary files differ
diff --git a/tests/ref/text-edge.png b/tests/ref/text-edge.png
new file mode 100644
index 00000000..0953eded
--- /dev/null
+++ b/tests/ref/text-edge.png
Binary files differ
diff --git a/tests/ref/text-features.png b/tests/ref/text-features.png
new file mode 100644
index 00000000..7b0b391f
--- /dev/null
+++ b/tests/ref/text-features.png
Binary files differ
diff --git a/tests/ref/text-font-change-after-space.png b/tests/ref/text-font-change-after-space.png
new file mode 100644
index 00000000..83d2ceb6
--- /dev/null
+++ b/tests/ref/text-font-change-after-space.png
Binary files differ
diff --git a/tests/ref/text-font-just-a-space.png b/tests/ref/text-font-just-a-space.png
new file mode 100644
index 00000000..3c91db3c
--- /dev/null
+++ b/tests/ref/text-font-just-a-space.png
Binary files differ
diff --git a/tests/ref/text-font-properties.png b/tests/ref/text-font-properties.png
new file mode 100644
index 00000000..3c65fa33
--- /dev/null
+++ b/tests/ref/text-font-properties.png
Binary files differ
diff --git a/tests/ref/text-kerning.png b/tests/ref/text-kerning.png
new file mode 100644
index 00000000..1bd3a001
--- /dev/null
+++ b/tests/ref/text-kerning.png
Binary files differ
diff --git a/tests/ref/text-lang-hyphenate.png b/tests/ref/text-lang-hyphenate.png
new file mode 100644
index 00000000..6315d6e2
--- /dev/null
+++ b/tests/ref/text-lang-hyphenate.png
Binary files differ
diff --git a/tests/ref/text-lang-region.png b/tests/ref/text-lang-region.png
new file mode 100644
index 00000000..a2736578
--- /dev/null
+++ b/tests/ref/text-lang-region.png
Binary files differ
diff --git a/tests/ref/text-lang-script-shaping.png b/tests/ref/text-lang-script-shaping.png
new file mode 100644
index 00000000..6beaece4
--- /dev/null
+++ b/tests/ref/text-lang-script-shaping.png
Binary files differ
diff --git a/tests/ref/text-lang-shaping.png b/tests/ref/text-lang-shaping.png
new file mode 100644
index 00000000..b892fcd5
--- /dev/null
+++ b/tests/ref/text-lang-shaping.png
Binary files differ
diff --git a/tests/ref/text-lang-unknown-region.png b/tests/ref/text-lang-unknown-region.png
new file mode 100644
index 00000000..de63013e
--- /dev/null
+++ b/tests/ref/text-lang-unknown-region.png
Binary files differ
diff --git a/tests/ref/text-lang.png b/tests/ref/text-lang.png
new file mode 100644
index 00000000..de63013e
--- /dev/null
+++ b/tests/ref/text-lang.png
Binary files differ
diff --git a/tests/ref/text-ligatures.png b/tests/ref/text-ligatures.png
new file mode 100644
index 00000000..6f0e286c
--- /dev/null
+++ b/tests/ref/text-ligatures.png
Binary files differ
diff --git a/tests/ref/text-number-type.png b/tests/ref/text-number-type.png
new file mode 100644
index 00000000..beb6ba6c
--- /dev/null
+++ b/tests/ref/text-number-type.png
Binary files differ
diff --git a/tests/ref/text-number-width.png b/tests/ref/text-number-width.png
new file mode 100644
index 00000000..62d8c61b
--- /dev/null
+++ b/tests/ref/text-number-width.png
Binary files differ
diff --git a/tests/ref/text-size-em-nesting.png b/tests/ref/text-size-em-nesting.png
new file mode 100644
index 00000000..34ae35fe
--- /dev/null
+++ b/tests/ref/text-size-em-nesting.png
Binary files differ
diff --git a/tests/ref/text-size-em.png b/tests/ref/text-size-em.png
new file mode 100644
index 00000000..944bdd29
--- /dev/null
+++ b/tests/ref/text-size-em.png
Binary files differ
diff --git a/tests/ref/text-slashed-zero-and-fractions.png b/tests/ref/text-slashed-zero-and-fractions.png
new file mode 100644
index 00000000..a25ca023
--- /dev/null
+++ b/tests/ref/text-slashed-zero-and-fractions.png
Binary files differ
diff --git a/tests/ref/text-spacing-relative.png b/tests/ref/text-spacing-relative.png
new file mode 100644
index 00000000..ccd2f140
--- /dev/null
+++ b/tests/ref/text-spacing-relative.png
Binary files differ
diff --git a/tests/ref/text-spacing.png b/tests/ref/text-spacing.png
new file mode 100644
index 00000000..240c69c0
--- /dev/null
+++ b/tests/ref/text-spacing.png
Binary files differ
diff --git a/tests/ref/text-tracking-arabic.png b/tests/ref/text-tracking-arabic.png
new file mode 100644
index 00000000..a4e450ff
--- /dev/null
+++ b/tests/ref/text-tracking-arabic.png
Binary files differ
diff --git a/tests/ref/text-tracking-changed-temporarily.png b/tests/ref/text-tracking-changed-temporarily.png
new file mode 100644
index 00000000..f27849b4
--- /dev/null
+++ b/tests/ref/text-tracking-changed-temporarily.png
Binary files differ
diff --git a/tests/ref/text-tracking-mark-placement.png b/tests/ref/text-tracking-mark-placement.png
new file mode 100644
index 00000000..7fc8bb19
--- /dev/null
+++ b/tests/ref/text-tracking-mark-placement.png
Binary files differ
diff --git a/tests/ref/text-tracking-negative.png b/tests/ref/text-tracking-negative.png
new file mode 100644
index 00000000..96589887
--- /dev/null
+++ b/tests/ref/text-tracking-negative.png
Binary files differ
diff --git a/tests/ref/text/baseline.png b/tests/ref/text/baseline.png
deleted file mode 100644
index dcd6eb12..00000000
--- a/tests/ref/text/baseline.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/chinese.png b/tests/ref/text/chinese.png
deleted file mode 100644
index 0c3ddd00..00000000
--- a/tests/ref/text/chinese.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/copy-paste.png b/tests/ref/text/copy-paste.png
deleted file mode 100644
index ae4a5ad9..00000000
--- a/tests/ref/text/copy-paste.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/deco.png b/tests/ref/text/deco.png
deleted file mode 100644
index 3a11e72f..00000000
--- a/tests/ref/text/deco.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/edge.png b/tests/ref/text/edge.png
deleted file mode 100644
index 1daf4c2f..00000000
--- a/tests/ref/text/edge.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/em.png b/tests/ref/text/em.png
deleted file mode 100644
index 04cccd53..00000000
--- a/tests/ref/text/em.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/emoji.png b/tests/ref/text/emoji.png
deleted file mode 100644
index 1dbbba79..00000000
--- a/tests/ref/text/emoji.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/emphasis.png b/tests/ref/text/emphasis.png
deleted file mode 100644
index c19f6ebb..00000000
--- a/tests/ref/text/emphasis.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/escape.png b/tests/ref/text/escape.png
deleted file mode 100644
index c94bc52f..00000000
--- a/tests/ref/text/escape.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/fallback.png b/tests/ref/text/fallback.png
deleted file mode 100644
index 7f1e3e38..00000000
--- a/tests/ref/text/fallback.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/features.png b/tests/ref/text/features.png
deleted file mode 100644
index 566694c6..00000000
--- a/tests/ref/text/features.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/font.png b/tests/ref/text/font.png
deleted file mode 100644
index 39c8a951..00000000
--- a/tests/ref/text/font.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/hyphenate.png b/tests/ref/text/hyphenate.png
deleted file mode 100644
index 7b386a51..00000000
--- a/tests/ref/text/hyphenate.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/lang-with-region.png b/tests/ref/text/lang-with-region.png
deleted file mode 100644
index c7753104..00000000
--- a/tests/ref/text/lang-with-region.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/lang.png b/tests/ref/text/lang.png
deleted file mode 100644
index a5ae8979..00000000
--- a/tests/ref/text/lang.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/linebreak-link.png b/tests/ref/text/linebreak-link.png
deleted file mode 100644
index ffe39caa..00000000
--- a/tests/ref/text/linebreak-link.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/linebreak-obj.png b/tests/ref/text/linebreak-obj.png
deleted file mode 100644
index 127ee687..00000000
--- a/tests/ref/text/linebreak-obj.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/linebreak.png b/tests/ref/text/linebreak.png
deleted file mode 100644
index 3dd2fc15..00000000
--- a/tests/ref/text/linebreak.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/lorem.png b/tests/ref/text/lorem.png
deleted file mode 100644
index 9d55df22..00000000
--- a/tests/ref/text/lorem.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/microtype.png b/tests/ref/text/microtype.png
deleted file mode 100644
index 87622b0f..00000000
--- a/tests/ref/text/microtype.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/numbers.png b/tests/ref/text/numbers.png
deleted file mode 100644
index 9fc76aae..00000000
--- a/tests/ref/text/numbers.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/quote-nesting.png b/tests/ref/text/quote-nesting.png
deleted file mode 100644
index fb16002d..00000000
--- a/tests/ref/text/quote-nesting.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/quote.png b/tests/ref/text/quote.png
deleted file mode 100644
index 653f2d17..00000000
--- a/tests/ref/text/quote.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/quotes.png b/tests/ref/text/quotes.png
deleted file mode 100644
index 535c2829..00000000
--- a/tests/ref/text/quotes.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/raw-align.png b/tests/ref/text/raw-align.png
deleted file mode 100644
index 6d1044f7..00000000
--- a/tests/ref/text/raw-align.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/raw-code.png b/tests/ref/text/raw-code.png
deleted file mode 100644
index 682c7c48..00000000
--- a/tests/ref/text/raw-code.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/raw-line.png b/tests/ref/text/raw-line.png
deleted file mode 100644
index b76eb808..00000000
--- a/tests/ref/text/raw-line.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/raw-syntaxes.png b/tests/ref/text/raw-syntaxes.png
deleted file mode 100644
index ada751e0..00000000
--- a/tests/ref/text/raw-syntaxes.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/raw-tabs.png b/tests/ref/text/raw-tabs.png
deleted file mode 100644
index cac265e9..00000000
--- a/tests/ref/text/raw-tabs.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/raw-theme.png b/tests/ref/text/raw-theme.png
deleted file mode 100644
index 0ce17760..00000000
--- a/tests/ref/text/raw-theme.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/raw.png b/tests/ref/text/raw.png
deleted file mode 100644
index 27120d74..00000000
--- a/tests/ref/text/raw.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/shaping.png b/tests/ref/text/shaping.png
deleted file mode 100644
index 69cba132..00000000
--- a/tests/ref/text/shaping.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/shift.png b/tests/ref/text/shift.png
deleted file mode 100644
index 09d68bac..00000000
--- a/tests/ref/text/shift.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/smartquotes.png b/tests/ref/text/smartquotes.png
deleted file mode 100644
index a6a8cbb5..00000000
--- a/tests/ref/text/smartquotes.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/space.png b/tests/ref/text/space.png
deleted file mode 100644
index bae0e0a8..00000000
--- a/tests/ref/text/space.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/stroke.png b/tests/ref/text/stroke.png
deleted file mode 100644
index d6d85c28..00000000
--- a/tests/ref/text/stroke.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/symbol.png b/tests/ref/text/symbol.png
deleted file mode 100644
index 04d9d77f..00000000
--- a/tests/ref/text/symbol.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/tracking-spacing.png b/tests/ref/text/tracking-spacing.png
deleted file mode 100644
index 68d80213..00000000
--- a/tests/ref/text/tracking-spacing.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/transform-rotate-and-scale.png b/tests/ref/transform-rotate-and-scale.png
new file mode 100644
index 00000000..0dcf67ed
--- /dev/null
+++ b/tests/ref/transform-rotate-and-scale.png
Binary files differ
diff --git a/tests/ref/transform-rotate-origin.png b/tests/ref/transform-rotate-origin.png
new file mode 100644
index 00000000..152b1e1f
--- /dev/null
+++ b/tests/ref/transform-rotate-origin.png
Binary files differ
diff --git a/tests/ref/transform-rotate-relative-sizing.png b/tests/ref/transform-rotate-relative-sizing.png
new file mode 100644
index 00000000..9b81c386
--- /dev/null
+++ b/tests/ref/transform-rotate-relative-sizing.png
Binary files differ
diff --git a/tests/ref/transform-rotate.png b/tests/ref/transform-rotate.png
new file mode 100644
index 00000000..3990ed5b
--- /dev/null
+++ b/tests/ref/transform-rotate.png
Binary files differ
diff --git a/tests/ref/transform-scale-origin.png b/tests/ref/transform-scale-origin.png
new file mode 100644
index 00000000..10e1cfe2
--- /dev/null
+++ b/tests/ref/transform-scale-origin.png
Binary files differ
diff --git a/tests/ref/transform-scale-relative-sizing.png b/tests/ref/transform-scale-relative-sizing.png
new file mode 100644
index 00000000..d10bd3ff
--- /dev/null
+++ b/tests/ref/transform-scale-relative-sizing.png
Binary files differ
diff --git a/tests/ref/transform-scale.png b/tests/ref/transform-scale.png
new file mode 100644
index 00000000..c95b90f1
--- /dev/null
+++ b/tests/ref/transform-scale.png
Binary files differ
diff --git a/tests/ref/transform-tex-logo.png b/tests/ref/transform-tex-logo.png
new file mode 100644
index 00000000..5d16ffb4
--- /dev/null
+++ b/tests/ref/transform-tex-logo.png
Binary files differ
diff --git a/tests/ref/underline-background.png b/tests/ref/underline-background.png
new file mode 100644
index 00000000..33ba381a
--- /dev/null
+++ b/tests/ref/underline-background.png
Binary files differ
diff --git a/tests/ref/underline-overline-strike.png b/tests/ref/underline-overline-strike.png
new file mode 100644
index 00000000..2567fca4
--- /dev/null
+++ b/tests/ref/underline-overline-strike.png
Binary files differ
diff --git a/tests/ref/underline-stroke-folding.png b/tests/ref/underline-stroke-folding.png
new file mode 100644
index 00000000..32119e5c
--- /dev/null
+++ b/tests/ref/underline-stroke-folding.png
Binary files differ
diff --git a/tests/ref/visualize/gradient-conic.png b/tests/ref/visualize/gradient-conic.png
deleted file mode 100644
index ff4a0ca2..00000000
--- a/tests/ref/visualize/gradient-conic.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/gradient-dir.png b/tests/ref/visualize/gradient-dir.png
deleted file mode 100644
index bda3eb17..00000000
--- a/tests/ref/visualize/gradient-dir.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/gradient-hue-rotation.png b/tests/ref/visualize/gradient-hue-rotation.png
deleted file mode 100644
index 2d786f71..00000000
--- a/tests/ref/visualize/gradient-hue-rotation.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/gradient-math.png b/tests/ref/visualize/gradient-math.png
deleted file mode 100644
index 470e6138..00000000
--- a/tests/ref/visualize/gradient-math.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/gradient-presets.png b/tests/ref/visualize/gradient-presets.png
deleted file mode 100644
index e6f7f73a..00000000
--- a/tests/ref/visualize/gradient-presets.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/gradient-radial.png b/tests/ref/visualize/gradient-radial.png
deleted file mode 100644
index 2e8e9af3..00000000
--- a/tests/ref/visualize/gradient-radial.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/gradient-relative-conic.png b/tests/ref/visualize/gradient-relative-conic.png
deleted file mode 100644
index 232c5f0a..00000000
--- a/tests/ref/visualize/gradient-relative-conic.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/gradient-relative-linear.png b/tests/ref/visualize/gradient-relative-linear.png
deleted file mode 100644
index 56e46119..00000000
--- a/tests/ref/visualize/gradient-relative-linear.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/gradient-relative-radial.png b/tests/ref/visualize/gradient-relative-radial.png
deleted file mode 100644
index 210ea7b0..00000000
--- a/tests/ref/visualize/gradient-relative-radial.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/gradient-repeat.png b/tests/ref/visualize/gradient-repeat.png
deleted file mode 100644
index 6be7dc66..00000000
--- a/tests/ref/visualize/gradient-repeat.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/gradient-sharp.png b/tests/ref/visualize/gradient-sharp.png
deleted file mode 100644
index b7698cfa..00000000
--- a/tests/ref/visualize/gradient-sharp.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/gradient-stroke.png b/tests/ref/visualize/gradient-stroke.png
deleted file mode 100644
index 69317f73..00000000
--- a/tests/ref/visualize/gradient-stroke.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/gradient-text-decorations.png b/tests/ref/visualize/gradient-text-decorations.png
deleted file mode 100644
index 887cd500..00000000
--- a/tests/ref/visualize/gradient-text-decorations.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/gradient-text-other.png b/tests/ref/visualize/gradient-text-other.png
deleted file mode 100644
index 78555b18..00000000
--- a/tests/ref/visualize/gradient-text-other.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/gradient-text.png b/tests/ref/visualize/gradient-text.png
deleted file mode 100644
index 478a0586..00000000
--- a/tests/ref/visualize/gradient-text.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/gradient-transform.png b/tests/ref/visualize/gradient-transform.png
deleted file mode 100644
index a55ad91e..00000000
--- a/tests/ref/visualize/gradient-transform.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/image-scale.png b/tests/ref/visualize/image-scale.png
deleted file mode 100644
index 95e9157e..00000000
--- a/tests/ref/visualize/image-scale.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/image.png b/tests/ref/visualize/image.png
deleted file mode 100644
index ec53fa98..00000000
--- a/tests/ref/visualize/image.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/line.png b/tests/ref/visualize/line.png
deleted file mode 100644
index d19dea0e..00000000
--- a/tests/ref/visualize/line.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/path.png b/tests/ref/visualize/path.png
deleted file mode 100644
index c7f710c9..00000000
--- a/tests/ref/visualize/path.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/pattern-relative.png b/tests/ref/visualize/pattern-relative.png
deleted file mode 100644
index 7958bf7f..00000000
--- a/tests/ref/visualize/pattern-relative.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/pattern-simple.png b/tests/ref/visualize/pattern-simple.png
deleted file mode 100644
index ac473a75..00000000
--- a/tests/ref/visualize/pattern-simple.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/pattern-small.png b/tests/ref/visualize/pattern-small.png
deleted file mode 100644
index 6af592dd..00000000
--- a/tests/ref/visualize/pattern-small.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/pattern-spacing.png b/tests/ref/visualize/pattern-spacing.png
deleted file mode 100644
index 4c95a3b0..00000000
--- a/tests/ref/visualize/pattern-spacing.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/pattern-stroke.png b/tests/ref/visualize/pattern-stroke.png
deleted file mode 100644
index d71f1c92..00000000
--- a/tests/ref/visualize/pattern-stroke.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/pattern-text.png b/tests/ref/visualize/pattern-text.png
deleted file mode 100644
index 2ecf2fda..00000000
--- a/tests/ref/visualize/pattern-text.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/polygon.png b/tests/ref/visualize/polygon.png
deleted file mode 100644
index 234aeb14..00000000
--- a/tests/ref/visualize/polygon.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/shape-aspect.png b/tests/ref/visualize/shape-aspect.png
deleted file mode 100644
index 918a5e73..00000000
--- a/tests/ref/visualize/shape-aspect.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/shape-circle.png b/tests/ref/visualize/shape-circle.png
deleted file mode 100644
index a2ee279d..00000000
--- a/tests/ref/visualize/shape-circle.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/shape-ellipse.png b/tests/ref/visualize/shape-ellipse.png
deleted file mode 100644
index 6de5e9f6..00000000
--- a/tests/ref/visualize/shape-ellipse.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/shape-fill-stroke.png b/tests/ref/visualize/shape-fill-stroke.png
deleted file mode 100644
index d4a4817a..00000000
--- a/tests/ref/visualize/shape-fill-stroke.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/shape-rect.png b/tests/ref/visualize/shape-rect.png
deleted file mode 100644
index a279341e..00000000
--- a/tests/ref/visualize/shape-rect.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/shape-rounded.png b/tests/ref/visualize/shape-rounded.png
deleted file mode 100644
index ec926d0a..00000000
--- a/tests/ref/visualize/shape-rounded.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/shape-square.png b/tests/ref/visualize/shape-square.png
deleted file mode 100644
index 46e243e1..00000000
--- a/tests/ref/visualize/shape-square.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/stroke.png b/tests/ref/visualize/stroke.png
deleted file mode 100644
index bdfcae9f..00000000
--- a/tests/ref/visualize/stroke.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/visualize/svg-text.png b/tests/ref/visualize/svg-text.png
deleted file mode 100644
index b2bbe320..00000000
--- a/tests/ref/visualize/svg-text.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/while-loop-basic.png b/tests/ref/while-loop-basic.png
new file mode 100644
index 00000000..3a0e6d24
--- /dev/null
+++ b/tests/ref/while-loop-basic.png
Binary files differ
diff --git a/tests/src/args.rs b/tests/src/args.rs
new file mode 100644
index 00000000..fcd4ead1
--- /dev/null
+++ b/tests/src/args.rs
@@ -0,0 +1,46 @@
+use clap::{Parser, Subcommand};
+
+/// Typst's test runner.
+#[derive(Debug, Clone, Parser)]
+#[clap(name = "typst-test", author)]
+pub struct CliArguments {
+ /// The command to run.
+ #[command(subcommand)]
+ pub command: Option<Command>,
+ /// All the tests that contain the filter string will be run.
+ pub filter: Vec<String>,
+ /// Runs only the tests with the exact specified `filter` names.
+ #[arg(short, long)]
+ pub exact: bool,
+ /// Whether to update the reference images of non-passing tests.
+ #[arg(short, long)]
+ pub update: bool,
+ /// The scaling factor to render the output image with.
+ ///
+ /// Does not affect the comparison or the reference image.
+ #[arg(short, long, default_value_t = 1.0)]
+ pub scale: f32,
+ /// Exports PDF outputs into the artifact store.
+ #[arg(long)]
+ pub pdf: bool,
+ /// Exports SVG outputs into the artifact store.
+ #[arg(long)]
+ pub svg: bool,
+ /// Whether to display the syntax tree.
+ #[arg(long)]
+ pub syntax: bool,
+ /// Prevents the terminal from being cleared of test names.
+ #[arg(short, long)]
+ pub verbose: bool,
+ /// How many threads to spawn when running the tests.
+ #[arg(short = 'j', long)]
+ pub num_threads: Option<usize>,
+}
+
+/// What to do.
+#[derive(Debug, Clone, Subcommand)]
+#[command()]
+pub enum Command {
+ /// Clears the on-disk test artifact store.
+ Clean,
+}
diff --git a/tests/src/collect.rs b/tests/src/collect.rs
new file mode 100644
index 00000000..44a325f2
--- /dev/null
+++ b/tests/src/collect.rs
@@ -0,0 +1,420 @@
+use std::collections::{HashMap, HashSet};
+use std::fmt::{self, Display, Formatter};
+use std::ops::Range;
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+
+use ecow::{eco_format, EcoString};
+use typst::syntax::package::PackageVersion;
+use typst::syntax::{is_id_continue, is_ident, is_newline, FileId, Source, VirtualPath};
+use unscanny::Scanner;
+
+/// Collects all tests from all files.
+///
+/// Returns:
+/// - the tests and the number of skipped tests in the success case.
+/// - parsing errors in the failure case.
+pub fn collect() -> Result<(Vec<Test>, usize), Vec<TestParseError>> {
+ Collector::new().collect()
+}
+
+/// A single test.
+pub struct Test {
+ pub pos: FilePos,
+ pub name: EcoString,
+ pub source: Source,
+ pub notes: Vec<Note>,
+ pub large: bool,
+}
+
+impl Display for Test {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "{} ({})", self.name, self.pos)
+ }
+}
+
+/// A position in a file.
+#[derive(Clone)]
+pub struct FilePos {
+ pub path: PathBuf,
+ pub line: usize,
+}
+
+impl FilePos {
+ fn new(path: impl Into<PathBuf>, line: usize) -> Self {
+ Self { path: path.into(), line }
+ }
+}
+
+impl Display for FilePos {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "{}:{}", self.path.display(), self.line)
+ }
+}
+
+/// The size of a file.
+pub struct FileSize(pub usize);
+
+impl Display for FileSize {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "{:.2} KiB", (self.0 as f64) / 1024.0)
+ }
+}
+
+/// An annotation like `// Error: 2-6 message` in a test.
+pub struct Note {
+ pub pos: FilePos,
+ pub kind: NoteKind,
+ pub range: Option<Range<usize>>,
+ pub message: String,
+}
+
+/// A kind of annotation in a test.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum NoteKind {
+ Error,
+ Warning,
+ Hint,
+}
+
+impl FromStr for NoteKind {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Ok(match s {
+ "Error" => Self::Error,
+ "Warning" => Self::Warning,
+ "Hint" => Self::Hint,
+ _ => return Err(()),
+ })
+ }
+}
+
+impl Display for NoteKind {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ Self::Error => "Error",
+ Self::Warning => "Warning",
+ Self::Hint => "Hint",
+ })
+ }
+}
+
+/// Collects all tests from all files.
+struct Collector {
+ tests: Vec<Test>,
+ errors: Vec<TestParseError>,
+ seen: HashMap<EcoString, FilePos>,
+ large: HashSet<EcoString>,
+ skipped: usize,
+}
+
+impl Collector {
+ /// Creates a new test collector.
+ fn new() -> Self {
+ Self {
+ tests: vec![],
+ errors: vec![],
+ seen: HashMap::new(),
+ large: HashSet::new(),
+ skipped: 0,
+ }
+ }
+
+ /// Collects tests from all files.
+ fn collect(mut self) -> Result<(Vec<Test>, usize), Vec<TestParseError>> {
+ self.walk_files();
+ self.walk_references();
+
+ if self.errors.is_empty() {
+ Ok((self.tests, self.skipped))
+ } else {
+ Err(self.errors)
+ }
+ }
+
+ /// Walks through all test files and collects the tests.
+ fn walk_files(&mut self) {
+ for entry in walkdir::WalkDir::new(crate::SUITE_PATH).sort_by_file_name() {
+ let entry = entry.unwrap();
+ let path = entry.path();
+ if !path.extension().is_some_and(|ext| ext == "typ") {
+ continue;
+ }
+
+ let text = std::fs::read_to_string(path).unwrap();
+ if text.starts_with("// SKIP") {
+ continue;
+ }
+
+ Parser::new(self, path, &text).parse();
+ }
+ }
+
+ /// Walks through all reference images and ensure that a test exists for
+ /// each one.
+ fn walk_references(&mut self) {
+ for entry in walkdir::WalkDir::new(crate::REF_PATH).sort_by_file_name() {
+ let entry = entry.unwrap();
+ let path = entry.path();
+ if !path.extension().is_some_and(|ext| ext == "png") {
+ continue;
+ }
+
+ let stem = path.file_stem().unwrap().to_string_lossy();
+ let name = &*stem;
+
+ let Some(pos) = self.seen.get(name) else {
+ self.errors.push(TestParseError {
+ pos: FilePos::new(path, 0),
+ message: "dangling reference image".into(),
+ });
+ continue;
+ };
+
+ let len = path.metadata().unwrap().len() as usize;
+ if !self.large.contains(name) && len > crate::REF_LIMIT {
+ self.errors.push(TestParseError {
+ pos: pos.clone(),
+ message: format!(
+ "reference image size exceeds {}, but the test is not marked as `// LARGE`",
+ FileSize(crate::REF_LIMIT),
+ ),
+ });
+ }
+ }
+ }
+}
+
+/// Parses a single test file.
+struct Parser<'a> {
+ collector: &'a mut Collector,
+ path: &'a Path,
+ s: Scanner<'a>,
+ test_start_line: usize,
+ line: usize,
+}
+
+impl<'a> Parser<'a> {
+ /// Creates a new parser for a file.
+ fn new(collector: &'a mut Collector, path: &'a Path, source: &'a str) -> Self {
+ Self {
+ collector,
+ path,
+ s: Scanner::new(source),
+ test_start_line: 1,
+ line: 1,
+ }
+ }
+
+ /// Parses an individual file.
+ fn parse(&mut self) {
+ self.skip_preamble();
+
+ while !self.s.done() {
+ let mut name = EcoString::new();
+ let mut notes = vec![];
+ if self.s.eat_if("---") {
+ self.s.eat_while(' ');
+ name = self.s.eat_until(char::is_whitespace).into();
+ self.s.eat_while(' ');
+
+ if name.is_empty() {
+ self.error("expected test name");
+ } else if !is_ident(&name) {
+ self.error(format!("test name `{name}` is not a valid identifier"));
+ } else if !self.s.eat_if("---") {
+ self.error("expected closing ---");
+ }
+ } else {
+ self.error("expected opening ---");
+ }
+
+ if self.collector.seen.contains_key(&name) {
+ self.error(format!("duplicate test {name}"));
+ }
+
+ if self.s.eat_newline() {
+ self.line += 1;
+ }
+
+ let start = self.s.cursor();
+ self.test_start_line = self.line;
+
+ let pos = FilePos::new(self.path, self.test_start_line);
+ self.collector.seen.insert(name.clone(), pos.clone());
+
+ while !self.s.done() && !self.s.at("---") {
+ self.s.eat_until(is_newline);
+ if self.s.eat_newline() {
+ self.line += 1;
+ }
+ }
+
+ let text = self.s.from(start);
+ let large = text.starts_with("// LARGE");
+ if large {
+ self.collector.large.insert(name.clone());
+ }
+
+ if !filtered(&name) {
+ self.collector.skipped += 1;
+ continue;
+ }
+
+ let vpath = VirtualPath::new(self.path);
+ let source = Source::new(FileId::new(None, vpath), text.into());
+
+ self.s.jump(start);
+ self.line = self.test_start_line;
+
+ while !self.s.done() && !self.s.at("---") {
+ self.s.eat_while(' ');
+ if self.s.eat_if("// ") {
+ notes.extend(self.parse_note(&source));
+ }
+
+ self.s.eat_until(is_newline);
+ if self.s.eat_newline() {
+ self.line += 1;
+ }
+ }
+
+ self.collector.tests.push(Test { pos, name, source, notes, large });
+ }
+ }
+
+ /// Skips the preamble of a test.
+ fn skip_preamble(&mut self) {
+ let mut errored = false;
+ while !self.s.done() && !self.s.at("---") {
+ let line = self.s.eat_until(is_newline).trim();
+ if !errored && !line.is_empty() && !line.starts_with("//") {
+ self.error("test preamble may only contain comments and blank lines");
+ errored = true;
+ }
+ if self.s.eat_newline() {
+ self.line += 1;
+ }
+ }
+ }
+
+ /// Parses an annotation in a test.
+ fn parse_note(&mut self, source: &Source) -> Option<Note> {
+ let head = self.s.eat_while(is_id_continue);
+ if !self.s.eat_if(':') {
+ return None;
+ }
+
+ let kind: NoteKind = head.parse().ok()?;
+ self.s.eat_if(' ');
+
+ let mut range = None;
+ if self.s.at('-') || self.s.at(char::is_numeric) {
+ range = self.parse_range(source);
+ if range.is_none() {
+ self.error("range is malformed");
+ return None;
+ }
+ }
+
+ let message = self
+ .s
+ .eat_until(is_newline)
+ .trim()
+ .replace("VERSION", &eco_format!("{}", PackageVersion::compiler()));
+
+ Some(Note {
+ pos: FilePos::new(self.path, self.line),
+ kind,
+ range,
+ message,
+ })
+ }
+
+ /// Parse a range, optionally abbreviated as just a position if the range
+ /// is empty.
+ fn parse_range(&mut self, source: &Source) -> Option<Range<usize>> {
+ let start = self.parse_position(source)?;
+ let end = if self.s.eat_if('-') { self.parse_position(source)? } else { start };
+ Some(start..end)
+ }
+
+ /// Parses a relative `(line:)?column` position.
+ fn parse_position(&mut self, source: &Source) -> Option<usize> {
+ let first = self.parse_number()?;
+ let (line_delta, column) =
+ if self.s.eat_if(':') { (first, self.parse_number()?) } else { (1, first) };
+
+ let text = source.text();
+ let line_idx_in_test = self.line - self.test_start_line;
+ let comments = text
+ .lines()
+ .skip(line_idx_in_test + 1)
+ .take_while(|line| line.trim().starts_with("//"))
+ .count();
+
+ let line_idx = (line_idx_in_test + comments).checked_add_signed(line_delta)?;
+ let column_idx = if column < 0 {
+ // Negative column index is from the back.
+ let range = source.line_to_range(line_idx)?;
+ text[range].chars().count().saturating_add_signed(column)
+ } else {
+ usize::try_from(column).ok()?.checked_sub(1)?
+ };
+
+ source.line_column_to_byte(line_idx, column_idx)
+ }
+
+ /// Parse a number.
+ fn parse_number(&mut self) -> Option<isize> {
+ let start = self.s.cursor();
+ self.s.eat_if('-');
+ self.s.eat_while(char::is_numeric);
+ self.s.from(start).parse().ok()
+ }
+
+ /// Stores a test parsing error.
+ fn error(&mut self, message: impl Into<String>) {
+ self.collector.errors.push(TestParseError {
+ pos: FilePos::new(self.path, self.line),
+ message: message.into(),
+ });
+ }
+}
+
+/// Whether a test is within the filtered set.
+fn filtered(name: &str) -> bool {
+ let exact = crate::ARGS.exact;
+ let filter = &crate::ARGS.filter;
+ filter.is_empty()
+ || filter
+ .iter()
+ .any(|v| if exact { name == v } else { name.contains(v) })
+}
+
+/// An error in a test file.
+pub struct TestParseError {
+ pos: FilePos,
+ message: String,
+}
+
+impl Display for TestParseError {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "{} ({})", self.message, self.pos)
+ }
+}
+
+trait ScannerExt {
+ fn eat_newline(&mut self) -> bool;
+}
+
+impl ScannerExt for Scanner<'_> {
+ fn eat_newline(&mut self) -> bool {
+ let ate = self.eat_if(is_newline);
+ if ate && self.before().ends_with('\r') {
+ self.eat_if('\n');
+ }
+ ate
+ }
+}
diff --git a/tests/src/logger.rs b/tests/src/logger.rs
new file mode 100644
index 00000000..c48650a7
--- /dev/null
+++ b/tests/src/logger.rs
@@ -0,0 +1,141 @@
+use std::io::{self, IsTerminal, StderrLock, Write};
+use std::time::{Duration, Instant};
+
+use crate::collect::Test;
+use crate::run::TestResult;
+
+/// Receives status updates by individual test runs.
+pub struct Logger<'a> {
+ filtered: usize,
+ passed: usize,
+ failed: usize,
+ skipped: usize,
+ mismatched_image: bool,
+ active: Vec<&'a Test>,
+ last_change: Instant,
+ temp_lines: usize,
+ terminal: bool,
+}
+
+impl<'a> Logger<'a> {
+ /// Create a new logger.
+ pub fn new(filtered: usize, skipped: usize) -> Self {
+ Self {
+ filtered,
+ passed: 0,
+ failed: 0,
+ skipped,
+ mismatched_image: false,
+ active: vec![],
+ temp_lines: 0,
+ last_change: Instant::now(),
+ terminal: std::io::stderr().is_terminal(),
+ }
+ }
+
+ /// Register the start of a test.
+ pub fn start(&mut self, test: &'a Test) {
+ self.active.push(test);
+ self.last_change = Instant::now();
+ self.refresh();
+ }
+
+ /// Register a finished test.
+ pub fn end(&mut self, test: &'a Test, result: std::thread::Result<TestResult>) {
+ self.active.retain(|t| t.name != test.name);
+
+ let result = match result {
+ Ok(result) => result,
+ Err(_) => {
+ self.failed += 1;
+ self.temp_lines = 0;
+ self.print(move |out| {
+ writeln!(out, "❌ {test} panicked")?;
+ Ok(())
+ })
+ .unwrap();
+ return;
+ }
+ };
+
+ if result.is_ok() {
+ self.passed += 1;
+ } else {
+ self.failed += 1;
+ }
+
+ self.mismatched_image |= result.mismatched_image;
+ self.last_change = Instant::now();
+
+ self.print(move |out| {
+ if !result.errors.is_empty() {
+ writeln!(out, "❌ {test}")?;
+ for line in result.errors.lines() {
+ writeln!(out, " {line}")?;
+ }
+ } else if crate::ARGS.verbose || !result.infos.is_empty() {
+ writeln!(out, "✅ {test}")?;
+ }
+ for line in result.infos.lines() {
+ writeln!(out, " {line}")?;
+ }
+ Ok(())
+ })
+ .unwrap();
+ }
+
+ /// Prints a summary and returns whether the test suite passed.
+ pub fn finish(&self) -> bool {
+ let Self { filtered, passed, failed, skipped, .. } = *self;
+
+ eprintln!("{passed} passed, {failed} failed, {skipped} skipped");
+ assert_eq!(filtered, passed + failed, "not all tests were executed succesfully");
+
+ if self.mismatched_image {
+ eprintln!(" pass the --update flag to update the reference images");
+ }
+
+ self.failed == 0
+ }
+
+ /// Refresh the status.
+ pub fn refresh(&mut self) {
+ self.print(|_| Ok(())).unwrap();
+ }
+
+ /// Refresh the status print.
+ fn print(
+ &mut self,
+ inner: impl FnOnce(&mut StderrLock<'_>) -> io::Result<()>,
+ ) -> io::Result<()> {
+ let mut out = std::io::stderr().lock();
+
+ // Clear the status lines.
+ for _ in 0..self.temp_lines {
+ write!(out, "\x1B[1F\x1B[0J")?;
+ self.temp_lines = 0;
+ }
+
+ // Print the result of a finished test.
+ inner(&mut out)?;
+
+ // Print the status line.
+ let done = self.failed + self.passed;
+ if done < self.filtered {
+ if self.last_change.elapsed() > Duration::from_secs(2) {
+ for test in &self.active {
+ writeln!(out, "⏰ {test} is taking a long time ...")?;
+ if self.terminal {
+ self.temp_lines += 1;
+ }
+ }
+ }
+ if self.terminal {
+ writeln!(out, "💨 {done} / {}", self.filtered)?;
+ self.temp_lines += 1;
+ }
+ }
+
+ Ok(())
+ }
+}
diff --git a/tests/src/metadata.rs b/tests/src/metadata.rs
deleted file mode 100644
index 53cbbdff..00000000
--- a/tests/src/metadata.rs
+++ /dev/null
@@ -1,334 +0,0 @@
-use std::collections::HashSet;
-use std::fmt::{self, Display, Formatter};
-use std::ops::Range;
-use std::str::FromStr;
-
-use ecow::EcoString;
-use typst::syntax::package::PackageVersion;
-use typst::syntax::Source;
-use unscanny::Scanner;
-
-/// Each test and subset may contain metadata.
-#[derive(Debug)]
-pub struct TestMetadata {
- /// Configures how the test is run.
- pub config: TestConfig,
- /// Declares properties that must hold for a test.
- ///
- /// For instance, `// Warning: 1-3 no text within underscores`
- /// will fail the test if the warning isn't generated by your test.
- pub annotations: HashSet<Annotation>,
-}
-
-/// Configuration of a test or subtest.
-#[derive(Debug, Default)]
-pub struct TestConfig {
- /// Reference images will be generated and compared.
- ///
- /// Defaults to `true`, can be disabled with `Ref: false`.
- pub compare_ref: Option<bool>,
- /// Hint annotations will be compared to compiler hints.
- ///
- /// Defaults to `true`, can be disabled with `Hints: false`.
- pub validate_hints: Option<bool>,
- /// Autocompletion annotations will be validated against autocompletions.
- /// Mutually exclusive with error and hint annotations.
- ///
- /// Defaults to `false`, can be enabled with `Autocomplete: true`.
- pub validate_autocomplete: Option<bool>,
-}
-
-/// Parsing error when the metadata is invalid.
-pub(crate) enum InvalidMetadata {
- /// An invalid annotation and it's error message.
- InvalidAnnotation(Annotation, String),
- /// Setting metadata can only be done with `true` or `false` as a value.
- InvalidSet(String),
-}
-
-impl InvalidMetadata {
- pub(crate) fn write(
- invalid_data: Vec<InvalidMetadata>,
- output: &mut String,
- print_annotation: &mut impl FnMut(&Annotation, &mut String),
- ) {
- use std::fmt::Write;
- for data in invalid_data.into_iter() {
- let (annotation, error) = match data {
- InvalidMetadata::InvalidAnnotation(a, e) => (Some(a), e),
- InvalidMetadata::InvalidSet(e) => (None, e),
- };
- write!(output, "{error}",).unwrap();
- if let Some(annotation) = annotation {
- print_annotation(&annotation, output)
- } else {
- writeln!(output).unwrap();
- }
- }
- }
-}
-
-/// Annotation of the form `// KIND: RANGE TEXT`.
-#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-pub struct Annotation {
- /// Which kind of annotation this is.
- pub kind: AnnotationKind,
- /// May be written as:
- /// - `{line}:{col}-{line}:{col}`, e.g. `0:4-0:6`.
- /// - `{col}-{col}`, e.g. `4-6`:
- /// The line is assumed to be the line after the annotation.
- /// - `-1`: Produces a range of length zero at the end of the next line.
- /// Mostly useful for autocompletion tests which require an index.
- pub range: Option<Range<usize>>,
- /// The raw text after the annotation.
- pub text: EcoString,
-}
-
-/// The different kinds of in-test annotations.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum AnnotationKind {
- Error,
- Warning,
- Hint,
- AutocompleteContains,
- AutocompleteExcludes,
-}
-
-impl AnnotationKind {
- /// Returns the user-facing string for this annotation.
- pub fn as_str(self) -> &'static str {
- match self {
- AnnotationKind::Error => "Error",
- AnnotationKind::Warning => "Warning",
- AnnotationKind::Hint => "Hint",
- AnnotationKind::AutocompleteContains => "Autocomplete contains",
- AnnotationKind::AutocompleteExcludes => "Autocomplete excludes",
- }
- }
-}
-
-impl FromStr for AnnotationKind {
- type Err = &'static str;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- Ok(match s {
- "Error" => AnnotationKind::Error,
- "Warning" => AnnotationKind::Warning,
- "Hint" => AnnotationKind::Hint,
- "Autocomplete contains" => AnnotationKind::AutocompleteContains,
- "Autocomplete excludes" => AnnotationKind::AutocompleteExcludes,
- _ => return Err("invalid annotatino"),
- })
- }
-}
-
-impl Display for AnnotationKind {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.pad(self.as_str())
- }
-}
-
-/// Parse metadata for a test.
-pub fn parse_part_metadata(
- source: &Source,
- is_header: bool,
-) -> Result<TestMetadata, Vec<InvalidMetadata>> {
- let mut config = TestConfig::default();
- let mut annotations = HashSet::default();
- let mut invalid_data = vec![];
-
- let lines = source_to_lines(source);
-
- for (i, line) in lines.iter().enumerate() {
- if let Some((key, value)) = parse_metadata_line(line) {
- let key = key.trim();
- match key {
- "Ref" => validate_set_annotation(
- value,
- &mut config.compare_ref,
- &mut invalid_data,
- ),
- "Hints" => validate_set_annotation(
- value,
- &mut config.validate_hints,
- &mut invalid_data,
- ),
- "Autocomplete" => validate_set_annotation(
- value,
- &mut config.validate_autocomplete,
- &mut invalid_data,
- ),
- annotation_key => {
- let Ok(kind) = AnnotationKind::from_str(annotation_key) else {
- continue;
- };
- let mut s = Scanner::new(value);
- let range = parse_range(&mut s, i, source);
- let rest = if range.is_some() { s.after() } else { s.string() };
- let message = rest
- .trim()
- .replace("VERSION", &PackageVersion::compiler().to_string())
- .into();
-
- let annotation =
- Annotation { kind, range: range.clone(), text: message };
-
- if is_header {
- invalid_data.push(InvalidMetadata::InvalidAnnotation(
- annotation,
- format!(
- "Error: header may not contain annotations of type {kind}"
- ),
- ));
- continue;
- }
-
- if matches!(
- kind,
- AnnotationKind::AutocompleteContains
- | AnnotationKind::AutocompleteExcludes
- ) {
- if let Some(range) = range {
- if range.start != range.end {
- invalid_data.push(InvalidMetadata::InvalidAnnotation(
- annotation,
- "Error: found range in Autocomplete annotation where range.start != range.end, range.end would be ignored."
- .to_string()
- ));
- continue;
- }
- } else {
- invalid_data.push(InvalidMetadata::InvalidAnnotation(
- annotation,
- "Error: autocomplete annotation but no range specified"
- .to_string(),
- ));
- continue;
- }
- }
- annotations.insert(annotation);
- }
- }
- }
- }
- if invalid_data.is_empty() {
- Ok(TestMetadata { config, annotations })
- } else {
- Err(invalid_data)
- }
-}
-
-/// Extract key and value for a metadata line of the form: `// KEY: VALUE`.
-fn parse_metadata_line(line: &str) -> Option<(&str, &str)> {
- let mut s = Scanner::new(line);
- if !s.eat_if("// ") {
- return None;
- }
-
- let key = s.eat_until(':').trim();
- if !s.eat_if(':') {
- return None;
- }
-
- let value = s.eat_until('\n').trim();
- Some((key, value))
-}
-
-/// Parse a quoted string.
-fn parse_string<'a>(s: &mut Scanner<'a>) -> Option<&'a str> {
- if !s.eat_if('"') {
- return None;
- }
- let sub = s.eat_until('"');
- if !s.eat_if('"') {
- return None;
- }
-
- Some(sub)
-}
-
-/// Parse a number.
-fn parse_num(s: &mut Scanner) -> Option<isize> {
- let mut first = true;
- let n = &s.eat_while(|c: char| {
- let valid = first && c == '-' || c.is_numeric();
- first = false;
- valid
- });
- n.parse().ok()
-}
-
-/// Parse a comma-separated list of strings.
-pub fn parse_string_list(text: &str) -> HashSet<&str> {
- let mut s = Scanner::new(text);
- let mut result = HashSet::new();
- while let Some(sub) = parse_string(&mut s) {
- result.insert(sub);
- s.eat_whitespace();
- if !s.eat_if(',') {
- break;
- }
- s.eat_whitespace();
- }
- result
-}
-
-/// Parse a position.
-fn parse_pos(s: &mut Scanner, i: usize, source: &Source) -> Option<usize> {
- let first = parse_num(s)? - 1;
- let (delta, column) =
- if s.eat_if(':') { (first, parse_num(s)? - 1) } else { (0, first) };
- let line = (i + comments_until_code(source, i)).checked_add_signed(delta)?;
- source.line_column_to_byte(line, usize::try_from(column).ok()?)
-}
-
-/// Parse a range.
-fn parse_range(s: &mut Scanner, i: usize, source: &Source) -> Option<Range<usize>> {
- let lines = source_to_lines(source);
- s.eat_whitespace();
- if s.eat_if("-1") {
- let mut add = 1;
- while let Some(line) = lines.get(i + add) {
- if !line.starts_with("//") {
- break;
- }
- add += 1;
- }
- let next_line = lines.get(i + add)?;
- let col = next_line.chars().count();
-
- let index = source.line_column_to_byte(i + add, col)?;
- s.eat_whitespace();
- return Some(index..index);
- }
- let start = parse_pos(s, i, source)?;
- let end = if s.eat_if('-') { parse_pos(s, i, source)? } else { start };
- s.eat_whitespace();
- Some(start..end)
-}
-
-/// Returns the number of lines of comment from line i to next line of code.
-fn comments_until_code(source: &Source, i: usize) -> usize {
- source_to_lines(source)[i..]
- .iter()
- .take_while(|line| line.starts_with("//"))
- .count()
-}
-
-fn source_to_lines(source: &Source) -> Vec<&str> {
- source.text().lines().map(str::trim).collect()
-}
-
-fn validate_set_annotation(
- value: &str,
- flag: &mut Option<bool>,
- invalid_data: &mut Vec<InvalidMetadata>,
-) {
- let value = value.trim();
- if value != "false" && value != "true" {
- invalid_data.push(
- InvalidMetadata::InvalidSet(format!("Error: trying to set Ref, Hints, or Autocomplete with value {value:?} != true, != false.")))
- } else {
- *flag = Some(value == "true")
- }
-}
diff --git a/tests/src/run.rs b/tests/src/run.rs
new file mode 100644
index 00000000..f797147f
--- /dev/null
+++ b/tests/src/run.rs
@@ -0,0 +1,442 @@
+use std::fmt::Write;
+use std::ops::Range;
+use std::path::Path;
+
+use ecow::eco_vec;
+use tiny_skia as sk;
+use typst::diag::SourceDiagnostic;
+use typst::eval::Tracer;
+use typst::foundations::Smart;
+use typst::introspection::Meta;
+use typst::layout::{Abs, Frame, FrameItem, Page, Transform};
+use typst::model::Document;
+use typst::visualize::Color;
+use typst::WorldExt;
+
+use crate::collect::{FileSize, NoteKind, Test};
+use crate::world::TestWorld;
+
+/// Runs a single test.
+///
+/// Returns whether the test passed.
+pub fn run(test: &Test) -> TestResult {
+ Runner::new(test).run()
+}
+
+/// The result of running a single test.
+pub struct TestResult {
+ /// The error log for this test. If empty, the test passed.
+ pub errors: String,
+ /// The info log for this test.
+ pub infos: String,
+ /// Whether the image was mismatched.
+ pub mismatched_image: bool,
+}
+
+impl TestResult {
+ /// Whether the test passed.
+ pub fn is_ok(&self) -> bool {
+ self.errors.is_empty()
+ }
+}
+
+/// Write a line to a log sink, defaulting to the test's error log.
+macro_rules! log {
+ (into: $sink:expr, $($tts:tt)*) => {
+ writeln!($sink, $($tts)*).unwrap();
+ };
+ ($runner:expr, $($tts:tt)*) => {
+ writeln!(&mut $runner.result.errors, $($tts)*).unwrap();
+ };
+}
+
+/// Runs a single test.
+pub struct Runner<'a> {
+ test: &'a Test,
+ world: TestWorld,
+ seen: Vec<bool>,
+ result: TestResult,
+ not_annotated: String,
+}
+
+impl<'a> Runner<'a> {
+ /// Create a new test runner.
+ fn new(test: &'a Test) -> Self {
+ Self {
+ test,
+ world: TestWorld::new(test.source.clone()),
+ seen: vec![false; test.notes.len()],
+ result: TestResult {
+ errors: String::new(),
+ infos: String::new(),
+ mismatched_image: false,
+ },
+ not_annotated: String::new(),
+ }
+ }
+
+ /// Run the test.
+ fn run(mut self) -> TestResult {
+ if crate::ARGS.syntax {
+ log!(into: self.result.infos, "tree: {:#?}", self.test.source.root());
+ }
+
+ let mut tracer = Tracer::new();
+ let (doc, errors) = match typst::compile(&self.world, &mut tracer) {
+ Ok(doc) => (Some(doc), eco_vec![]),
+ Err(errors) => (None, errors),
+ };
+
+ let warnings = tracer.warnings();
+ if doc.is_none() && errors.is_empty() {
+ log!(self, "no document, but also no errors");
+ }
+
+ self.check_document(doc.as_ref());
+
+ for error in &errors {
+ self.check_diagnostic(NoteKind::Error, error);
+ }
+
+ for warning in &warnings {
+ self.check_diagnostic(NoteKind::Warning, warning);
+ }
+
+ self.handle_not_emitted();
+ self.handle_not_annotated();
+
+ self.result
+ }
+
+ /// Handle errors that weren't annotated.
+ fn handle_not_annotated(&mut self) {
+ if !self.not_annotated.is_empty() {
+ log!(self, "not annotated");
+ self.result.errors.push_str(&self.not_annotated);
+ }
+ }
+
+ /// Handle notes that weren't handled before.
+ fn handle_not_emitted(&mut self) {
+ let mut first = true;
+ for (note, &seen) in self.test.notes.iter().zip(&self.seen) {
+ if seen {
+ continue;
+ }
+ let note_range = self.format_range(&note.range);
+ if first {
+ log!(self, "not emitted");
+ first = false;
+ }
+ log!(self, " {}: {note_range} {} ({})", note.kind, note.message, note.pos,);
+ }
+ }
+
+ /// Check that the document output is correct.
+ fn check_document(&mut self, document: Option<&Document>) {
+ let live_path = format!("{}/render/{}.png", crate::STORE_PATH, self.test.name);
+ let ref_path = format!("{}/{}.png", crate::REF_PATH, self.test.name);
+ let has_ref = Path::new(&ref_path).exists();
+
+ let Some(document) = document else {
+ if has_ref {
+ log!(self, "missing document");
+ log!(self, " ref | {ref_path}");
+ }
+ return;
+ };
+
+ let skippable = match document.pages.as_slice() {
+ [page] => skippable(page),
+ _ => false,
+ };
+
+ // Tests without visible output and no reference image don't need to be
+ // compared.
+ if skippable && !has_ref {
+ std::fs::remove_file(&live_path).ok();
+ return;
+ }
+
+ // Render the live version.
+ let pixmap = render(document, 1.0);
+
+ // Save live version, possibly rerendering if different scale is
+ // requested.
+ let mut pixmap_live = &pixmap;
+ let slot;
+ let scale = crate::ARGS.scale;
+ if scale != 1.0 {
+ slot = render(document, scale);
+ pixmap_live = &slot;
+ }
+ let data = pixmap_live.encode_png().unwrap();
+ std::fs::write(&live_path, data).unwrap();
+
+ // Write PDF if requested.
+ if crate::ARGS.pdf {
+ let pdf_path = format!("{}/pdf/{}.pdf", crate::STORE_PATH, self.test.name);
+ let pdf = typst_pdf::pdf(document, Smart::Auto, None);
+ std::fs::write(pdf_path, pdf).unwrap();
+ }
+
+ // Write SVG if requested.
+ if crate::ARGS.svg {
+ let svg_path = format!("{}/svg/{}.svg", crate::STORE_PATH, self.test.name);
+ let svg = typst_svg::svg_merged(document, Abs::pt(5.0));
+ std::fs::write(svg_path, svg).unwrap();
+ }
+
+ // Compare against reference image if available.
+ let equal = has_ref && {
+ let ref_data = std::fs::read(&ref_path).unwrap();
+ let ref_pixmap = sk::Pixmap::decode_png(&ref_data).unwrap();
+ approx_equal(&pixmap, &ref_pixmap)
+ };
+
+ // Test that is ok doesn't need to be updated.
+ if equal {
+ return;
+ }
+
+ if crate::ARGS.update {
+ if skippable {
+ std::fs::remove_file(&ref_path).unwrap();
+ log!(
+ into: self.result.infos,
+ "removed reference image ({ref_path})"
+ );
+ } else {
+ let opts = oxipng::Options::max_compression();
+ let data = pixmap.encode_png().unwrap();
+ let ref_data = oxipng::optimize_from_memory(&data, &opts).unwrap();
+ if !self.test.large && ref_data.len() > crate::REF_LIMIT {
+ log!(self, "reference image would exceed maximum size");
+ log!(self, " maximum | {}", FileSize(crate::REF_LIMIT));
+ log!(self, " size | {}", FileSize(ref_data.len()));
+ log!(self, "please try to minimize the size of the test (smaller pages, less text, etc.)");
+ log!(self, "if you think the test cannot be reasonably minimized, mark it as `// LARGE`");
+ return;
+ }
+ std::fs::write(&ref_path, &ref_data).unwrap();
+ log!(
+ into: self.result.infos,
+ "Updated reference image ({ref_path}, {})",
+ FileSize(ref_data.len()),
+ );
+ }
+ } else {
+ self.result.mismatched_image = true;
+ if has_ref {
+ log!(self, "mismatched rendering");
+ log!(self, " live | {live_path}");
+ log!(self, " ref | {ref_path}");
+ } else {
+ log!(self, "missing reference image");
+ log!(self, " live | {live_path}");
+ }
+ }
+ }
+
+ /// Compare a subset of notes with a given kind against diagnostics of
+ /// that same kind.
+ fn check_diagnostic(&mut self, kind: NoteKind, diag: &SourceDiagnostic) {
+ // Ignore diagnostics from other sources than the test file itself.
+ if diag.span.id().is_some_and(|id| id != self.test.source.id()) {
+ return;
+ }
+
+ let message = diag.message.replace("\\", "/");
+ let range = self.world.range(diag.span);
+ self.validate_note(kind, range.clone(), &message);
+
+ // Check hints.
+ for hint in &diag.hints {
+ self.validate_note(NoteKind::Hint, range.clone(), hint);
+ }
+ }
+
+ /// Try to find a matching note for the given `kind`, `range`, and
+ /// `message`.
+ ///
+ /// - If found, marks it as seen and returns it.
+ /// - If none was found, emits a "Not annotated" error and returns nothing.
+ fn validate_note(
+ &mut self,
+ kind: NoteKind,
+ range: Option<Range<usize>>,
+ message: &str,
+ ) {
+ // Try to find perfect match.
+ if let Some((i, _)) = self.test.notes.iter().enumerate().find(|&(i, note)| {
+ !self.seen[i]
+ && note.kind == kind
+ && note.range == range
+ && note.message == message
+ }) {
+ self.seen[i] = true;
+ return;
+ }
+
+ // Try to find closely matching annotation. If the note has the same
+ // range or message, it's most likely the one we're interested in.
+ let Some((i, note)) = self.test.notes.iter().enumerate().find(|&(i, note)| {
+ !self.seen[i]
+ && note.kind == kind
+ && (note.range == range || note.message == message)
+ }) else {
+ // Not even a close match, diagnostic is not annotated.
+ let diag_range = self.format_range(&range);
+ log!(into: self.not_annotated, " {kind}: {diag_range} {}", message);
+ return;
+ };
+
+ // Mark this annotation as visited and return it.
+ self.seen[i] = true;
+
+ // Range is wrong.
+ if range != note.range {
+ let note_range = self.format_range(&note.range);
+ let note_text = self.text_for_range(&note.range);
+ let diag_range = self.format_range(&range);
+ let diag_text = self.text_for_range(&range);
+ log!(self, "mismatched range ({}):", note.pos);
+ log!(self, " message | {}", note.message);
+ log!(self, " annotated | {note_range:<9} | {note_text}");
+ log!(self, " emitted | {diag_range:<9} | {diag_text}");
+ }
+
+ // Message is wrong.
+ if message != note.message {
+ log!(self, "mismatched message ({}):", note.pos);
+ log!(self, " annotated | {}", note.message);
+ log!(self, " emitted | {message}");
+ }
+ }
+
+ /// Display the text for a range.
+ fn text_for_range(&self, range: &Option<Range<usize>>) -> String {
+ let Some(range) = range else { return "No text".into() };
+ if range.is_empty() {
+ "(empty)".into()
+ } else {
+ format!("`{}`", self.test.source.text()[range.clone()].replace('\n', "\\n"))
+ }
+ }
+
+ /// Display a byte range as a line:column range.
+ fn format_range(&self, range: &Option<Range<usize>>) -> String {
+ let Some(range) = range else { return "No range".into() };
+ if range.start == range.end {
+ self.format_pos(range.start)
+ } else {
+ format!("{}-{}", self.format_pos(range.start,), self.format_pos(range.end,))
+ }
+ }
+
+ /// Display a position as a line:column pair.
+ fn format_pos(&self, pos: usize) -> String {
+ if let (Some(line_idx), Some(column_idx)) =
+ (self.test.source.byte_to_line(pos), self.test.source.byte_to_column(pos))
+ {
+ let line = self.test.pos.line + line_idx;
+ let column = column_idx + 1;
+ if line == 1 {
+ format!("{column}")
+ } else {
+ format!("{line}:{column}")
+ }
+ } else {
+ "oob".into()
+ }
+ }
+}
+
+/// Draw all frames into one image with padding in between.
+fn render(document: &Document, pixel_per_pt: f32) -> sk::Pixmap {
+ for page in &document.pages {
+ let limit = Abs::cm(100.0);
+ if page.frame.width() > limit || page.frame.height() > limit {
+ panic!("overlarge frame: {:?}", page.frame.size());
+ }
+ }
+
+ let gap = Abs::pt(1.0);
+ let mut pixmap = typst_render::render_merged(
+ document,
+ pixel_per_pt,
+ Color::WHITE,
+ gap,
+ Color::BLACK,
+ );
+
+ let gap = (pixel_per_pt * gap.to_pt() as f32).round();
+
+ let mut y = 0.0;
+ for page in &document.pages {
+ let ts =
+ sk::Transform::from_scale(pixel_per_pt, pixel_per_pt).post_translate(0.0, y);
+ render_links(&mut pixmap, ts, &page.frame);
+ y += (pixel_per_pt * page.frame.height().to_pt() as f32).round().max(1.0) + gap;
+ }
+
+ pixmap
+}
+
+/// Draw extra boxes for links so we can see whether they are there.
+fn render_links(canvas: &mut sk::Pixmap, ts: sk::Transform, frame: &Frame) {
+ for (pos, item) in frame.items() {
+ let ts = ts.pre_translate(pos.x.to_pt() as f32, pos.y.to_pt() as f32);
+ match *item {
+ FrameItem::Group(ref group) => {
+ let ts = ts.pre_concat(to_sk_transform(&group.transform));
+ render_links(canvas, ts, &group.frame);
+ }
+ FrameItem::Meta(Meta::Link(_), size) => {
+ let w = size.x.to_pt() as f32;
+ let h = size.y.to_pt() as f32;
+ let rect = sk::Rect::from_xywh(0.0, 0.0, w, h).unwrap();
+ let mut paint = sk::Paint::default();
+ paint.set_color_rgba8(40, 54, 99, 40);
+ canvas.fill_rect(rect, &paint, ts, None);
+ }
+ _ => {}
+ }
+ }
+}
+
+/// Whether rendering of a frame can be skipped.
+fn skippable(page: &Page) -> bool {
+ page.frame.width().approx_eq(Abs::pt(120.0))
+ && page.frame.height().approx_eq(Abs::pt(20.0))
+ && skippable_frame(&page.frame)
+}
+
+/// Whether rendering of a frame can be skipped.
+fn skippable_frame(frame: &Frame) -> bool {
+ frame.items().all(|(_, item)| match item {
+ FrameItem::Group(group) => skippable_frame(&group.frame),
+ FrameItem::Meta(..) => true,
+ _ => false,
+ })
+}
+
+/// Whether to pixel images are approximately equal.
+fn approx_equal(a: &sk::Pixmap, b: &sk::Pixmap) -> bool {
+ a.width() == b.width()
+ && a.height() == b.height()
+ && a.data().iter().zip(b.data()).all(|(&a, &b)| a.abs_diff(b) <= 1)
+}
+
+/// Convert a Typst transform to a tiny-skia transform.
+fn to_sk_transform(transform: &Transform) -> sk::Transform {
+ let Transform { sx, ky, kx, sy, tx, ty } = *transform;
+ sk::Transform::from_row(
+ sx.get() as _,
+ ky.get() as _,
+ kx.get() as _,
+ sy.get() as _,
+ tx.to_pt() as f32,
+ ty.to_pt() as f32,
+ )
+}
diff --git a/tests/src/tests.rs b/tests/src/tests.rs
index e4f60bb6..6d58e969 100644
--- a/tests/src/tests.rs
+++ b/tests/src/tests.rs
@@ -1,1127 +1,112 @@
-/*! This is Typst's test runner.
+//! Typst's test runner.
-Tests are Typst files composed of a header part followed by subtests.
+mod args;
+mod collect;
+mod logger;
+mod run;
+mod world;
-The header may contain:
-- a small description `// tests that features X works well`
-- metadata (see [metadata::TestConfiguration])
-
-The subtests may use extra testing functions defined in [library], most
-importantly, `test(x, y)` which will fail the test `if x != y`.
-*/
-
-#![allow(clippy::comparison_chain)]
-mod metadata;
-
-use self::metadata::*;
-
-use std::borrow::Cow;
-use std::collections::{HashMap, HashSet};
-use std::ffi::OsStr;
-use std::fmt::Write as _;
-use std::io::{self, IsTerminal, Write as _};
-use std::ops::Range;
-use std::path::{Path, PathBuf, MAIN_SEPARATOR_STR};
-use std::sync::{OnceLock, RwLock};
-use std::{env, fs};
+use std::path::Path;
+use std::time::Duration;
use clap::Parser;
-use comemo::{Prehashed, Track};
-use oxipng::{InFile, Options, OutFile};
+use once_cell::sync::Lazy;
+use parking_lot::Mutex;
use rayon::iter::{ParallelBridge, ParallelIterator};
-use tiny_skia as sk;
-use typst::diag::{bail, FileError, FileResult, Severity, SourceDiagnostic, StrResult};
-use typst::eval::Tracer;
-use typst::foundations::{func, Bytes, Datetime, NoneValue, Repr, Smart, Value};
-use typst::introspection::Meta;
-use typst::layout::{Abs, Frame, FrameItem, Margin, Page, PageElem, Transform};
-use typst::model::Document;
-use typst::syntax::{FileId, Source, SyntaxNode, VirtualPath};
-use typst::text::{Font, FontBook, TextElem, TextSize};
-use typst::visualize::Color;
-use typst::{Library, World, WorldExt};
-use walkdir::WalkDir;
-// These directories are all relative to the tests/ directory.
-const TYP_DIR: &str = "typ";
-const REF_DIR: &str = "ref";
-const PNG_DIR: &str = "png";
-const PDF_DIR: &str = "pdf";
-const SVG_DIR: &str = "svg";
+use crate::args::{CliArguments, Command};
+use crate::logger::Logger;
-/// Arguments that modify test behaviour.
-///
-/// Specify them like this when developing:
-/// `cargo test --workspace --test tests -- --help`
-#[derive(Debug, Clone, Parser)]
-#[clap(name = "typst-test", author)]
-struct Args {
- /// All the tests that contains a filter string will be run (unless
- /// `--exact` is specified, which is even stricter).
- filter: Vec<String>,
- /// Runs only the specified subtest.
- #[arg(short, long)]
- #[arg(allow_hyphen_values = true)]
- subtest: Option<isize>,
- /// Runs only the test with the exact name specified in your command.
- ///
- /// Example:
- /// `cargo test --workspace --test tests -- compiler/bytes.typ --exact`
- #[arg(long)]
- exact: bool,
- /// Updates the reference images in `tests/ref`.
- #[arg(long, default_value_t = env::var_os("UPDATE_EXPECT").is_some())]
- update: bool,
- /// Exports the tests as PDF into `tests/pdf`.
- #[arg(long)]
- pdf: bool,
- /// Configuration of what to print.
- #[command(flatten)]
- print: PrintConfig,
- /// Running `cargo test --workspace -- --nocapture` for the unit tests would
- /// fail the test runner without argument.
- // TODO: would it really still happen?
- #[arg(long)]
- nocapture: bool,
- /// Prevents the terminal from being cleared of test names and includes
- /// non-essential test messages.
- #[arg(short, long)]
- verbose: bool,
-}
+/// The parsed command line arguments.
+static ARGS: Lazy<CliArguments> = Lazy::new(CliArguments::parse);
-/// Which things to print out for debugging.
-#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Parser)]
-struct PrintConfig {
- /// Print the syntax tree.
- #[arg(long)]
- syntax: bool,
- /// Print the content model.
- #[arg(long)]
- model: bool,
- /// Print the layouted frames.
- #[arg(long)]
- frames: bool,
-}
+/// The directory where the test suite is located.
+const SUITE_PATH: &str = "tests/suite";
-impl Args {
- fn matches(&self, canonicalized_path: &Path) -> bool {
- let path = canonicalized_path.to_string_lossy();
- if !self.exact {
- return self.filter.is_empty()
- || self.filter.iter().any(|v| path.contains(v));
- }
+/// The directory where the full test results are stored.
+const STORE_PATH: &str = "tests/store";
- self.filter.iter().any(|v| match path.strip_suffix(v) {
- None => false,
- Some(residual) => {
- residual.is_empty() || residual.ends_with(MAIN_SEPARATOR_STR)
- }
- })
- }
-}
-
-/// Tests all test files and prints a summary.
-fn main() {
- let args = Args::parse();
-
- // Create loader and context.
- let world = TestWorld::new(args.print);
-
- println!("Running tests...");
- let results = WalkDir::new(TYP_DIR)
- .sort_by_file_name()
- .into_iter()
- .par_bridge()
- .filter_map(|entry| {
- let entry = entry.unwrap();
- if entry.depth() == 0 {
- return None;
- }
+/// The directory where the reference images are stored.
+const REF_PATH: &str = "tests/ref";
- if entry.path().starts_with("typ/benches") {
- return None;
- }
-
- let src_path = entry.into_path(); // Relative to TYP_DIR.
- if src_path.extension() != Some(OsStr::new("typ")) {
- return None;
- }
-
- if args.matches(&src_path.canonicalize().unwrap()) {
- Some(src_path)
- } else {
- None
- }
- })
- .map_with(world, |world, src_path| {
- let path = src_path.strip_prefix(TYP_DIR).unwrap();
- let png_path = Path::new(PNG_DIR).join(path).with_extension("png");
- let ref_path = Path::new(REF_DIR).join(path).with_extension("png");
- let svg_path = Path::new(SVG_DIR).join(path).with_extension("svg");
- let pdf_path =
- args.pdf.then(|| Path::new(PDF_DIR).join(path).with_extension("pdf"));
-
- test(
- world,
- &src_path,
- &png_path,
- &ref_path,
- pdf_path.as_deref(),
- &svg_path,
- &args,
- ) as usize
- })
- .collect::<Vec<_>>();
-
- let len = results.len();
- let ok = results.iter().sum::<usize>();
- if len > 0 {
- println!("{ok} / {len} test{} passed.", if len > 1 { "s" } else { "" });
- } else {
- println!("No test ran.");
- }
-
- if ok != len {
- println!(
- "Set the UPDATE_EXPECT environment variable or pass the \
- --update flag to update the reference image(s)."
- );
- }
-
- if ok < len {
- std::process::exit(1);
- }
-}
+/// The maximum size of reference images that aren't marked as `// LARGE`.
+const REF_LIMIT: usize = 20 * 1024;
-fn library() -> Library {
- #[func]
- fn test(lhs: Value, rhs: Value) -> StrResult<NoneValue> {
- if lhs != rhs {
- bail!("Assertion failed: {} != {}", lhs.repr(), rhs.repr());
- }
- Ok(NoneValue)
- }
-
- #[func]
- fn test_repr(lhs: Value, rhs: Value) -> StrResult<NoneValue> {
- if lhs.repr() != rhs.repr() {
- bail!("Assertion failed: {} != {}", lhs.repr(), rhs.repr());
- }
- Ok(NoneValue)
- }
-
- #[func]
- fn print(#[variadic] values: Vec<Value>) -> NoneValue {
- let mut stdout = io::stdout().lock();
- write!(stdout, "> ").unwrap();
- for (i, value) in values.into_iter().enumerate() {
- if i > 0 {
- write!(stdout, ", ").unwrap();
- }
- write!(stdout, "{value:?}").unwrap();
- }
- writeln!(stdout).unwrap();
- NoneValue
- }
-
- // Set page width to 120pt with 10pt margins, so that the inner page is
- // exactly 100pt wide. Page height is unbounded and font size is 10pt so
- // that it multiplies to nice round numbers.
- let mut lib = Library::default();
- lib.styles
- .set(PageElem::set_width(Smart::Custom(Abs::pt(120.0).into())));
- lib.styles.set(PageElem::set_height(Smart::Auto));
- lib.styles.set(PageElem::set_margin(Margin::splat(Some(Smart::Custom(
- Abs::pt(10.0).into(),
- )))));
- lib.styles.set(TextElem::set_size(TextSize(Abs::pt(10.0).into())));
-
- // Hook up helpers into the global scope.
- lib.global.scope_mut().define_func::<test>();
- lib.global.scope_mut().define_func::<test_repr>();
- lib.global.scope_mut().define_func::<print>();
- lib.global
- .scope_mut()
- .define("conifer", Color::from_u8(0x9f, 0xEB, 0x52, 0xFF));
- lib.global
- .scope_mut()
- .define("forest", Color::from_u8(0x43, 0xA1, 0x27, 0xFF));
-
- lib
-}
-
-/// A world that provides access to the tests environment.
-struct TestWorld {
- print: PrintConfig,
- main: FileId,
- library: Prehashed<Library>,
- book: Prehashed<FontBook>,
- fonts: Vec<Font>,
- slots: RwLock<HashMap<FileId, FileSlot>>,
-}
-
-#[derive(Clone)]
-struct FileSlot {
- source: OnceLock<FileResult<Source>>,
- buffer: OnceLock<FileResult<Bytes>>,
-}
-
-impl TestWorld {
- fn new(print: PrintConfig) -> Self {
- let fonts: Vec<_> = typst_assets::fonts()
- .chain(typst_dev_assets::fonts())
- .flat_map(|data| Font::iter(Bytes::from_static(data)))
- .collect();
-
- Self {
- print,
- main: FileId::new(None, VirtualPath::new("main.typ")),
- library: Prehashed::new(library()),
- book: Prehashed::new(FontBook::from_fonts(&fonts)),
- fonts,
- slots: RwLock::new(HashMap::new()),
- }
- }
-}
-
-impl World for TestWorld {
- fn library(&self) -> &Prehashed<Library> {
- &self.library
- }
-
- fn book(&self) -> &Prehashed<FontBook> {
- &self.book
- }
-
- fn main(&self) -> Source {
- self.source(self.main).unwrap()
- }
-
- fn source(&self, id: FileId) -> FileResult<Source> {
- self.slot(id, |slot| {
- slot.source
- .get_or_init(|| {
- let buf = read(&system_path(id)?)?;
- let text = String::from_utf8(buf.into_owned())?;
- Ok(Source::new(id, text))
- })
- .clone()
- })
- }
-
- fn file(&self, id: FileId) -> FileResult<Bytes> {
- self.slot(id, |slot| {
- slot.buffer
- .get_or_init(|| {
- read(&system_path(id)?).map(|cow| match cow {
- Cow::Owned(buf) => buf.into(),
- Cow::Borrowed(buf) => Bytes::from_static(buf),
- })
- })
- .clone()
- })
- }
-
- fn font(&self, id: usize) -> Option<Font> {
- Some(self.fonts[id].clone())
- }
-
- fn today(&self, _: Option<i64>) -> Option<Datetime> {
- Some(Datetime::from_ymd(1970, 1, 1).unwrap())
- }
-}
-
-impl TestWorld {
- fn set(&mut self, path: &Path, text: String) -> Source {
- self.main = FileId::new(None, VirtualPath::new(path));
- let source = Source::new(self.main, text);
- self.slot(self.main, |slot| {
- slot.source = OnceLock::from(Ok(source.clone()));
- source
- })
- }
-
- fn slot<F, T>(&self, id: FileId, f: F) -> T
- where
- F: FnOnce(&mut FileSlot) -> T,
- {
- f(self.slots.write().unwrap().entry(id).or_insert_with(|| FileSlot {
- source: OnceLock::new(),
- buffer: OnceLock::new(),
- }))
- }
-}
+fn main() {
+ setup();
-impl Clone for TestWorld {
- fn clone(&self) -> Self {
- Self {
- print: self.print,
- main: self.main,
- library: self.library.clone(),
- book: self.book.clone(),
- fonts: self.fonts.clone(),
- slots: RwLock::new(self.slots.read().unwrap().clone()),
- }
+ match &ARGS.command {
+ None => test(),
+ Some(Command::Clean) => std::fs::remove_dir_all(STORE_PATH).unwrap(),
}
}
-/// The file system path for a file ID.
-fn system_path(id: FileId) -> FileResult<PathBuf> {
- let root: PathBuf = match id.package() {
- Some(spec) => format!("packages/{}-{}", spec.name, spec.version).into(),
- None => PathBuf::new(),
- };
-
- id.vpath().resolve(&root).ok_or(FileError::AccessDenied)
-}
-
-/// Read a file.
-fn read(path: &Path) -> FileResult<Cow<'static, [u8]>> {
- // Basically symlinks `assets/files` to `tests/files` so that the assets
- // are within the test project root.
- let resolved = path.to_path_buf();
- if let Ok(suffix) = path.strip_prefix("assets/") {
- return typst_dev_assets::get(&suffix.to_string_lossy())
- .map(Cow::Borrowed)
- .ok_or_else(|| FileError::NotFound(path.into()));
- }
-
- let f = |e| FileError::from_io(e, path);
- if fs::metadata(&resolved).map_err(f)?.is_dir() {
- Err(FileError::IsDirectory)
- } else {
- fs::read(&resolved).map(Cow::Owned).map_err(f)
- }
-}
+fn setup() {
+ // Make all paths relative to the workspace. That's nicer for IDEs when
+ // clicking on paths printed to the terminal.
+ std::env::set_current_dir("..").unwrap();
-/// Tests a test file and prints the result.
-///
-/// Also tests that the header of each test is written correctly.
-/// See [parse_part_metadata] for more details.
-fn test(
- world: &mut TestWorld,
- src_path: &Path,
- png_path: &Path,
- ref_path: &Path,
- pdf_path: Option<&Path>,
- svg_path: &Path,
- args: &Args,
-) -> bool {
- struct PanicGuard<'a>(&'a Path);
- impl Drop for PanicGuard<'_> {
- fn drop(&mut self) {
- if std::thread::panicking() {
- println!("Panicked in {}", self.0.display());
- }
- }
+ // Create the storage.
+ for ext in ["render", "pdf", "svg"] {
+ std::fs::create_dir_all(Path::new(STORE_PATH).join(ext)).unwrap();
}
- let name = src_path.strip_prefix(TYP_DIR).unwrap_or(src_path);
- let text = fs::read_to_string(src_path).unwrap();
- let _guard = PanicGuard(name);
-
- let mut output = String::new();
- let mut ok = true;
- let mut updated = false;
- let mut pages = vec![];
- let mut line = 0;
- let mut header_configuration = None;
- let mut compare_ever = false;
- let mut rng = LinearShift::new();
-
- let parts: Vec<_> = text
- .split("\n---")
- .map(|s| s.strip_suffix('\r').unwrap_or(s))
- .collect();
-
- for (i, &part) in parts.iter().enumerate() {
- if let Some(x) = args.subtest {
- let x = usize::try_from(
- x.rem_euclid(isize::try_from(parts.len()).unwrap_or_default()),
- )
+ // Set up the thread pool.
+ if let Some(num_threads) = ARGS.num_threads {
+ rayon::ThreadPoolBuilder::new()
+ .num_threads(num_threads)
+ .build_global()
.unwrap();
- if x != i {
- writeln!(output, " Skipped subtest {i}.").unwrap();
- continue;
- }
- }
- let is_header = i == 0
- && parts.len() > 1
- && part
- .lines()
- .all(|s| s.starts_with("//") || s.chars().all(|c| c.is_whitespace()));
-
- if is_header {
- let source = Source::detached(part.to_string());
- let metadata = parse_part_metadata(&source, true);
- match metadata {
- Ok(metadata) => {
- header_configuration = Some(metadata.config);
- }
- Err(invalid_data) => {
- ok = false;
- writeln!(
- output,
- " Test {}: invalid metadata in header, failing the test:",
- name.display()
- )
- .unwrap();
- InvalidMetadata::write(
- invalid_data,
- &mut output,
- &mut |annotation, output| {
- print_annotation(output, &source, line, annotation)
- },
- );
- }
- }
- } else {
- let (part_ok, compare_here, part_frames) = test_part(
- &mut output,
- world,
- src_path,
- part.into(),
- line,
- i,
- header_configuration.as_ref().unwrap_or(&Default::default()),
- &mut rng,
- args.verbose,
- );
-
- ok &= part_ok;
- compare_ever |= compare_here;
- pages.extend(part_frames);
- }
-
- line += part.lines().count() + 1;
- }
-
- let document = Document { pages, ..Default::default() };
- if compare_ever {
- if let Some(pdf_path) = pdf_path {
- let pdf_data = typst_pdf::pdf(
- &document,
- Smart::Custom(&format!("typst-test: {}", name.display())),
- world.today(Some(0)),
- );
- fs::create_dir_all(pdf_path.parent().unwrap()).unwrap();
- fs::write(pdf_path, pdf_data).unwrap();
- }
-
- if world.print.frames {
- for frame in &document.pages {
- writeln!(output, "{frame:#?}\n").unwrap();
- }
- }
-
- let canvas = render(&document);
- fs::create_dir_all(png_path.parent().unwrap()).unwrap();
- canvas.save_png(png_path).unwrap();
-
- let svg = typst_svg::svg_merged(&document, Abs::pt(5.0));
-
- fs::create_dir_all(svg_path.parent().unwrap()).unwrap();
- std::fs::write(svg_path, svg.as_bytes()).unwrap();
-
- if let Ok(ref_pixmap) = sk::Pixmap::load_png(ref_path) {
- if canvas.width() != ref_pixmap.width()
- || canvas.height() != ref_pixmap.height()
- || canvas
- .data()
- .iter()
- .zip(ref_pixmap.data())
- .any(|(&a, &b)| a.abs_diff(b) > 2)
- {
- if args.update {
- update_image(png_path, ref_path);
- updated = true;
- } else {
- writeln!(output, " Does not match reference image.").unwrap();
- ok = false;
- }
- }
- } else if !document.pages.is_empty() {
- if args.update {
- update_image(png_path, ref_path);
- updated = true;
- } else {
- writeln!(output, " Failed to open reference image.").unwrap();
- ok = false;
- }
- }
}
-
- {
- let mut stdout = io::stdout().lock();
- stdout.write_all(name.to_string_lossy().as_bytes()).unwrap();
- if ok {
- writeln!(stdout, " ✔").unwrap();
- // Don't clear the line when in verbose mode or when the reference image
- // was updated, to show in the output which test had its image updated.
- if !updated && !args.verbose && stdout.is_terminal() {
- // ANSI escape codes: cursor moves up and clears the line.
- write!(stdout, "\x1b[1A\x1b[2K").unwrap();
- }
- } else {
- writeln!(stdout, " ❌").unwrap();
- }
- if updated {
- writeln!(stdout, " Updated reference image.").unwrap();
- }
- if !output.is_empty() {
- stdout.write_all(output.as_bytes()).unwrap();
- }
- }
-
- ok
}
-fn update_image(png_path: &Path, ref_path: &Path) {
- oxipng::optimize(
- &InFile::Path(png_path.to_owned()),
- &OutFile::from_path(ref_path.to_owned()),
- &Options::max_compression(),
- )
- .unwrap();
-}
-
-#[allow(clippy::too_many_arguments)]
-fn test_part(
- output: &mut String,
- world: &mut TestWorld,
- src_path: &Path,
- text: String,
- line: usize,
- i: usize,
- header_configuration: &TestConfig,
- rng: &mut LinearShift,
- verbose: bool,
-) -> (bool, bool, Vec<Page>) {
- let source = world.set(src_path, text);
- if world.print.syntax {
- writeln!(output, "Syntax Tree:\n{:#?}\n", source.root()).unwrap();
- }
-
- if world.print.model {
- print_model(world, &source, output);
- }
-
- let mut tracer = Tracer::new();
- let (mut frames, diagnostics) = match typst::compile(world, &mut tracer) {
- Ok(document) => (document.pages, tracer.warnings()),
+fn test() {
+ let (tests, skipped) = match crate::collect::collect() {
+ Ok(output) => output,
Err(errors) => {
- let mut warnings = tracer.warnings();
- warnings.extend(errors);
- (vec![], warnings)
- }
- };
-
- let metadata = parse_part_metadata(&source, false);
- match metadata {
- Ok(metadata) => {
- let mut ok = true;
- let compare_ref = metadata
- .config
- .compare_ref
- .unwrap_or(header_configuration.compare_ref.unwrap_or(true));
- let validate_hints = metadata
- .config
- .validate_hints
- .unwrap_or(header_configuration.validate_hints.unwrap_or(true));
- let validate_autocomplete = metadata
- .config
- .validate_autocomplete
- .unwrap_or(header_configuration.validate_autocomplete.unwrap_or(false));
-
- if verbose {
- writeln!(output, "Subtest {i} runs with compare_ref={compare_ref}; validate_hints={validate_hints}; validate_autocomplete={validate_autocomplete};").unwrap();
- }
- ok &= test_spans(output, source.root());
- ok &= test_reparse(output, source.text(), i, rng);
-
- // Don't retain frames if we don't want to compare with reference images.
- if !compare_ref {
- frames.clear();
- }
-
- // we never check autocomplete and error at the same time
-
- let diagnostic_annotations = metadata
- .annotations
- .iter()
- .filter(|a| {
- !matches!(
- a.kind,
- AnnotationKind::AutocompleteContains
- | AnnotationKind::AutocompleteExcludes
- )
- })
- .cloned()
- .collect::<HashSet<_>>();
-
- if validate_autocomplete {
- // warns and ignores diagnostics
- if !diagnostic_annotations.is_empty() {
- writeln!(
- output,
- " Subtest {i} contains diagnostics but is in autocomplete mode."
- )
- .unwrap();
- for annotation in diagnostic_annotations {
- write!(output, " Ignored | ").unwrap();
- print_annotation(output, &source, line, &annotation);
- }
- }
-
- test_autocomplete(
- output,
- world,
- &source,
- line,
- i,
- &mut ok,
- metadata.annotations.iter(),
- );
- } else {
- test_diagnostics(
- output,
- world,
- &source,
- line,
- i,
- &mut ok,
- validate_hints,
- diagnostics.iter(),
- &diagnostic_annotations,
- );
- }
-
- (ok, compare_ref, frames)
- }
- Err(invalid_data) => {
- writeln!(output, " Subtest {i} has invalid metadata, failing the test:")
- .unwrap();
- InvalidMetadata::write(
- invalid_data,
- output,
- &mut |annotation: &Annotation, output: &mut String| {
- print_annotation(output, &source, line, annotation)
- },
- );
-
- (false, false, frames)
- }
- }
-}
-
-#[allow(clippy::too_many_arguments)]
-fn test_autocomplete<'a>(
- output: &mut String,
- world: &mut TestWorld,
- source: &Source,
- line: usize,
- i: usize,
- ok: &mut bool,
- annotations: impl Iterator<Item = &'a Annotation>,
-) {
- for annotation in annotations.filter(|a| {
- matches!(
- a.kind,
- AnnotationKind::AutocompleteContains | AnnotationKind::AutocompleteExcludes
- )
- }) {
- // Ok cause we checked in parsing that range was Some for this annotation
- let cursor = annotation.range.as_ref().unwrap().start;
-
- // todo, use document if is_some to test labels autocomplete
- let completions = typst_ide::autocomplete(world, None, source, cursor, true)
- .map(|(_, c)| c)
- .unwrap_or_default()
- .into_iter()
- .map(|c| c.label.to_string())
- .collect::<HashSet<_>>();
- let completions =
- completions.iter().map(|s| s.as_str()).collect::<HashSet<&str>>();
-
- let must_contain_or_exclude = parse_string_list(&annotation.text);
- let missing =
- must_contain_or_exclude.difference(&completions).collect::<Vec<_>>();
-
- if !missing.is_empty()
- && matches!(annotation.kind, AnnotationKind::AutocompleteContains)
- {
- writeln!(output, " Subtest {i} does not match expected completions.")
- .unwrap();
- write!(output, " for annotation | ").unwrap();
- print_annotation(output, source, line, annotation);
-
- write!(output, " Not contained // ").unwrap();
- for item in missing {
- write!(output, "{item:?}, ").unwrap()
+ eprintln!("failed to collect tests");
+ for error in errors {
+ eprintln!("❌ {error}");
}
- writeln!(output).unwrap();
- *ok = false;
+ std::process::exit(1);
}
-
- let undesired =
- must_contain_or_exclude.intersection(&completions).collect::<Vec<_>>();
-
- if !undesired.is_empty()
- && matches!(annotation.kind, AnnotationKind::AutocompleteExcludes)
- {
- writeln!(output, " Subtest {i} does not match expected completions.")
- .unwrap();
- write!(output, " for annotation | ").unwrap();
- print_annotation(output, source, line, annotation);
-
- write!(output, " Not excluded // ").unwrap();
- for item in undesired {
- write!(output, "{item:?}, ").unwrap()
- }
- writeln!(output).unwrap();
- *ok = false;
- }
- }
-}
-
-#[allow(clippy::too_many_arguments)]
-fn test_diagnostics<'a>(
- output: &mut String,
- world: &mut TestWorld,
- source: &Source,
- line: usize,
- i: usize,
- ok: &mut bool,
- validate_hints: bool,
- diagnostics: impl Iterator<Item = &'a SourceDiagnostic>,
- diagnostic_annotations: &HashSet<Annotation>,
-) {
- // Map diagnostics to range and message format, discard traces and errors from
- // other files, collect hints.
- //
- // This has one caveat: due to the format of the expected hints, we can not
- // verify if a hint belongs to a diagnostic or not. That should be irrelevant
- // however, as the line of the hint is still verified.
- let mut actual_diagnostics = HashSet::new();
- for diagnostic in diagnostics {
- // Ignore diagnostics from other files.
- if diagnostic.span.id().is_some_and(|id| id != source.id()) {
- continue;
- }
-
- let annotation = Annotation {
- kind: match diagnostic.severity {
- Severity::Error => AnnotationKind::Error,
- Severity::Warning => AnnotationKind::Warning,
- },
- range: world.range(diagnostic.span),
- text: diagnostic.message.replace("\\", "/"),
- };
-
- if validate_hints {
- for hint in &diagnostic.hints {
- actual_diagnostics.insert(Annotation {
- kind: AnnotationKind::Hint,
- text: hint.clone(),
- range: annotation.range.clone(),
- });
- }
- }
-
- actual_diagnostics.insert(annotation);
- }
-
- // Basically symmetric_difference, but we need to know where an item is coming from.
- let mut unexpected_outputs = actual_diagnostics
- .difference(diagnostic_annotations)
- .collect::<Vec<_>>();
- let mut missing_outputs = diagnostic_annotations
- .difference(&actual_diagnostics)
- .collect::<Vec<_>>();
-
- unexpected_outputs.sort_by_key(|&v| v.range.as_ref().map(|r| r.start));
- missing_outputs.sort_by_key(|&v| v.range.as_ref().map(|r| r.start));
-
- // This prints all unexpected emits first, then all missing emits.
- // Is this reasonable or subject to change?
- if !(unexpected_outputs.is_empty() && missing_outputs.is_empty()) {
- writeln!(output, " Subtest {i} does not match expected errors.").unwrap();
- *ok = false;
-
- for unexpected in unexpected_outputs {
- write!(output, " Not annotated // ").unwrap();
- print_annotation(output, source, line, unexpected)
- }
-
- for missing in missing_outputs {
- write!(output, " Not emitted // ").unwrap();
- print_annotation(output, source, line, missing)
- }
- }
-}
-
-fn print_model(world: &mut TestWorld, source: &Source, output: &mut String) {
- let world = (world as &dyn World).track();
- let route = typst::engine::Route::default();
- let mut tracer = typst::eval::Tracer::new();
-
- let module =
- typst::eval::eval(world, route.track(), tracer.track_mut(), source).unwrap();
- writeln!(output, "Model:\n{:#?}\n", module.content()).unwrap();
-}
-
-fn print_annotation(
- output: &mut String,
- source: &Source,
- line: usize,
- annotation: &Annotation,
-) {
- let Annotation { range, text, kind } = annotation;
- write!(output, "{kind}: ").unwrap();
- if let Some(range) = range {
- let start_line = 1 + line + source.byte_to_line(range.start).unwrap();
- let start_col = 1 + source.byte_to_column(range.start).unwrap();
- let end_line = 1 + line + source.byte_to_line(range.end).unwrap();
- let end_col = 1 + source.byte_to_column(range.end).unwrap();
- write!(output, "{start_line}:{start_col}-{end_line}:{end_col} ").unwrap();
- }
- writeln!(output, "{text}").unwrap();
-}
-
-/// Pseudorandomly edit the source file and test whether a reparse produces the
-/// same result as a clean parse.
-///
-/// The method will first inject 10 strings once every 400 source characters
-/// and then select 5 leaf node boundaries to inject an additional, randomly
-/// chosen string from the injection list.
-fn test_reparse(
- output: &mut String,
- text: &str,
- i: usize,
- rng: &mut LinearShift,
-) -> bool {
- let supplements = [
- "[",
- "]",
- "{",
- "}",
- "(",
- ")",
- "#rect()",
- "a word",
- ", a: 1",
- "10.0",
- ":",
- "if i == 0 {true}",
- "for",
- "* hello *",
- "//",
- "/*",
- "\\u{12e4}",
- "```typst",
- " ",
- "trees",
- "\\",
- "$ a $",
- "2.",
- "-",
- "5",
- ];
-
- let mut ok = true;
- let mut apply = |replace: Range<usize>, with| {
- let mut incr_source = Source::detached(text);
- if incr_source.root().len() != text.len() {
- println!(
- " Subtest {i} tree length {} does not match string length {} ❌",
- incr_source.root().len(),
- text.len(),
- );
- return false;
- }
-
- incr_source.edit(replace.clone(), with);
-
- let edited_src = incr_source.text();
- let ref_source = Source::detached(edited_src);
- let ref_root = ref_source.root();
- let incr_root = incr_source.root();
-
- // Ensures that the span numbering invariants hold.
- let spans_ok = test_spans(output, ref_root) && test_spans(output, incr_root);
-
- // Ensure that the reference and incremental trees are the same.
- let tree_ok = ref_root.spanless_eq(incr_root);
-
- if !tree_ok {
- writeln!(
- output,
- " Subtest {i} reparse differs from clean parse when inserting '{with}' at {}-{} ❌\n",
- replace.start, replace.end,
- ).unwrap();
- writeln!(output, " Expected reference tree:\n{ref_root:#?}\n").unwrap();
- writeln!(output, " Found incremental tree:\n{incr_root:#?}").unwrap();
- writeln!(
- output,
- " Full source ({}):\n\"{edited_src:?}\"",
- edited_src.len()
- )
- .unwrap();
- }
-
- spans_ok && tree_ok
- };
-
- let mut pick = |range: Range<usize>| {
- let ratio = rng.next();
- (range.start as f64 + ratio * (range.end - range.start) as f64).floor() as usize
};
- let insertions = (text.len() as f64 / 400.0).ceil() as usize;
- for _ in 0..insertions {
- let supplement = supplements[pick(0..supplements.len())];
- let start = pick(0..text.len());
- let end = pick(start..text.len());
-
- if !text.is_char_boundary(start) || !text.is_char_boundary(end) {
- continue;
- }
-
- ok &= apply(start..end, supplement);
- }
-
- let source = Source::detached(text);
- let leafs = leafs(source.root());
- let start = source.find(leafs[pick(0..leafs.len())].span()).unwrap().offset();
- let supplement = supplements[pick(0..supplements.len())];
- ok &= apply(start..start, supplement);
-
- ok
-}
-
-/// Returns all leaf descendants of a node (may include itself).
-fn leafs(node: &SyntaxNode) -> Vec<SyntaxNode> {
- if node.children().len() == 0 {
- vec![node.clone()]
- } else {
- node.children().flat_map(leafs).collect()
- }
-}
-
-/// Ensure that all spans are properly ordered (and therefore unique).
-#[track_caller]
-fn test_spans(output: &mut String, root: &SyntaxNode) -> bool {
- test_spans_impl(output, root, 0..u64::MAX)
-}
-
-#[track_caller]
-fn test_spans_impl(output: &mut String, node: &SyntaxNode, within: Range<u64>) -> bool {
- if !within.contains(&node.span().number()) {
- writeln!(output, " Node: {node:#?}").unwrap();
- writeln!(
- output,
- " Wrong span order: {} not in {within:?} ❌",
- node.span().number()
- )
- .unwrap();
- }
-
- let start = node.span().number() + 1;
- let mut children = node.children().peekable();
- while let Some(child) = children.next() {
- let end = children.peek().map_or(within.end, |next| next.span().number());
- if !test_spans_impl(output, child, start..end) {
- return false;
- }
- }
-
- true
-}
-
-/// Draw all frames into one image with padding in between.
-fn render(document: &Document) -> sk::Pixmap {
- let pixel_per_pt = 2.0;
- let padding = Abs::pt(5.0);
-
- for page in &document.pages {
- let limit = Abs::cm(100.0);
- if page.frame.width() > limit || page.frame.height() > limit {
- panic!("overlarge frame: {:?}", page.frame.size());
- }
- }
-
- let mut pixmap = typst_render::render_merged(
- document,
- pixel_per_pt,
- Color::WHITE,
- padding,
- Color::BLACK,
- );
-
- let padding = (pixel_per_pt * padding.to_pt() as f32).round();
- let [x, mut y] = [padding; 2];
- for page in &document.pages {
- let ts =
- sk::Transform::from_scale(pixel_per_pt, pixel_per_pt).post_translate(x, y);
- render_links(&mut pixmap, ts, &page.frame);
- y += (pixel_per_pt * page.frame.height().to_pt() as f32).round().max(1.0)
- + padding;
- }
-
- pixmap
-}
-
-/// Draw extra boxes for links so we can see whether they are there.
-fn render_links(canvas: &mut sk::Pixmap, ts: sk::Transform, frame: &Frame) {
- for (pos, item) in frame.items() {
- let ts = ts.pre_translate(pos.x.to_pt() as f32, pos.y.to_pt() as f32);
- match *item {
- FrameItem::Group(ref group) => {
- let ts = ts.pre_concat(to_sk_transform(&group.transform));
- render_links(canvas, ts, &group.frame);
- }
- FrameItem::Meta(Meta::Link(_), size) => {
- let w = size.x.to_pt() as f32;
- let h = size.y.to_pt() as f32;
- let rect = sk::Rect::from_xywh(0.0, 0.0, w, h).unwrap();
- let mut paint = sk::Paint::default();
- paint.set_color_rgba8(40, 54, 99, 40);
- canvas.fill_rect(rect, &paint, ts, None);
- }
- _ => {}
- }
- }
-}
-
-fn to_sk_transform(transform: &Transform) -> sk::Transform {
- let Transform { sx, ky, kx, sy, tx, ty } = *transform;
- sk::Transform::from_row(
- sx.get() as _,
- ky.get() as _,
- kx.get() as _,
- sy.get() as _,
- tx.to_pt() as f32,
- ty.to_pt() as f32,
- )
-}
-
-/// A Linear-feedback shift register using XOR as its shifting function.
-/// Can be used as PRNG.
-struct LinearShift(u64);
-
-impl LinearShift {
- /// Initialize the shift register with a pre-set seed.
- pub fn new() -> Self {
- Self(0xACE5)
- }
-
- /// Return a pseudo-random number between `0.0` and `1.0`.
- pub fn next(&mut self) -> f64 {
- self.0 ^= self.0 >> 3;
- self.0 ^= self.0 << 14;
- self.0 ^= self.0 >> 28;
- self.0 ^= self.0 << 36;
- self.0 ^= self.0 >> 52;
- self.0 as f64 / u64::MAX as f64
+ let filtered = tests.len();
+ if filtered == 0 {
+ eprintln!("no test selected");
+ return;
+ }
+
+ // Run the tests.
+ let logger = Mutex::new(Logger::new(filtered, skipped));
+ std::thread::scope(|scope| {
+ let logger = &logger;
+ let (sender, receiver) = std::sync::mpsc::channel();
+
+ // Regularly refresh the logger in case we make no progress.
+ scope.spawn(move || {
+ while receiver.recv_timeout(Duration::from_millis(500)).is_err() {
+ logger.lock().refresh();
+ }
+ });
+
+ // Run the tests.
+ //
+ // We use `par_bridge` instead of `par_iter` because the former
+ // results in a stack overflow during PDF export. Probably related
+ // to `typst::util::Deferred` yielding.
+ tests.iter().par_bridge().for_each(|test| {
+ logger.lock().start(test);
+ let result = std::panic::catch_unwind(|| run::run(test));
+ logger.lock().end(test, result);
+ });
+
+ sender.send(()).unwrap();
+ });
+
+ let passed = logger.into_inner().finish();
+ if !passed {
+ std::process::exit(1);
}
}
diff --git a/tests/src/world.rs b/tests/src/world.rs
new file mode 100644
index 00000000..86ee8da6
--- /dev/null
+++ b/tests/src/world.rs
@@ -0,0 +1,229 @@
+use std::borrow::Cow;
+use std::collections::HashMap;
+use std::fs;
+use std::io::Write;
+use std::path::{Path, PathBuf};
+use std::sync::OnceLock;
+
+use comemo::Prehashed;
+use once_cell::sync::Lazy;
+use parking_lot::Mutex;
+use typst::diag::{bail, FileError, FileResult, StrResult};
+use typst::foundations::{func, Bytes, Datetime, NoneValue, Repr, Smart, Value};
+use typst::layout::{Abs, Margin, PageElem};
+use typst::syntax::{FileId, Source};
+use typst::text::{Font, FontBook, TextElem, TextSize};
+use typst::visualize::Color;
+use typst::{Library, World};
+
+/// A world that provides access to the tests environment.
+#[derive(Clone)]
+pub struct TestWorld {
+ main: Source,
+ base: &'static TestBase,
+}
+
+impl TestWorld {
+ /// Create a new world for a single test.
+ ///
+ /// This is cheap because the shared base for all test runs is lazily
+ /// initialized just once.
+ pub fn new(source: Source) -> Self {
+ static BASE: Lazy<TestBase> = Lazy::new(TestBase::default);
+ Self { main: source, base: &*BASE }
+ }
+}
+
+impl World for TestWorld {
+ fn library(&self) -> &Prehashed<Library> {
+ &self.base.library
+ }
+
+ fn book(&self) -> &Prehashed<FontBook> {
+ &self.base.book
+ }
+
+ fn main(&self) -> Source {
+ self.main.clone()
+ }
+
+ fn source(&self, id: FileId) -> FileResult<Source> {
+ if id == self.main.id() {
+ Ok(self.main.clone())
+ } else {
+ self.slot(id, FileSlot::source)
+ }
+ }
+
+ fn file(&self, id: FileId) -> FileResult<Bytes> {
+ self.slot(id, FileSlot::file)
+ }
+
+ fn font(&self, index: usize) -> Option<Font> {
+ Some(self.base.fonts[index].clone())
+ }
+
+ fn today(&self, _: Option<i64>) -> Option<Datetime> {
+ Some(Datetime::from_ymd(1970, 1, 1).unwrap())
+ }
+}
+
+impl TestWorld {
+ /// Access the canonical slot for the given file id.
+ fn slot<F, T>(&self, id: FileId, f: F) -> T
+ where
+ F: FnOnce(&mut FileSlot) -> T,
+ {
+ let mut map = self.base.slots.lock();
+ f(map.entry(id).or_insert_with(|| FileSlot::new(id)))
+ }
+}
+
+/// Shared foundation of all test worlds.
+struct TestBase {
+ library: Prehashed<Library>,
+ book: Prehashed<FontBook>,
+ fonts: Vec<Font>,
+ slots: Mutex<HashMap<FileId, FileSlot>>,
+}
+
+impl Default for TestBase {
+ fn default() -> Self {
+ let fonts: Vec<_> = typst_assets::fonts()
+ .chain(typst_dev_assets::fonts())
+ .flat_map(|data| Font::iter(Bytes::from_static(data)))
+ .collect();
+
+ Self {
+ library: Prehashed::new(library()),
+ book: Prehashed::new(FontBook::from_fonts(&fonts)),
+ fonts,
+ slots: Mutex::new(HashMap::new()),
+ }
+ }
+}
+
+/// Holds the processed data for a file ID.
+#[derive(Clone)]
+struct FileSlot {
+ id: FileId,
+ source: OnceLock<FileResult<Source>>,
+ file: OnceLock<FileResult<Bytes>>,
+}
+
+impl FileSlot {
+ /// Create a new file slot.
+ fn new(id: FileId) -> Self {
+ Self { id, file: OnceLock::new(), source: OnceLock::new() }
+ }
+
+ /// Retrieve the source for this file.
+ fn source(&mut self) -> FileResult<Source> {
+ self.source
+ .get_or_init(|| {
+ let buf = read(&system_path(self.id)?)?;
+ let text = String::from_utf8(buf.into_owned())?;
+ Ok(Source::new(self.id, text))
+ })
+ .clone()
+ }
+
+ /// Retrieve the file's bytes.
+ fn file(&mut self) -> FileResult<Bytes> {
+ self.file
+ .get_or_init(|| {
+ read(&system_path(self.id)?).map(|cow| match cow {
+ Cow::Owned(buf) => buf.into(),
+ Cow::Borrowed(buf) => Bytes::from_static(buf),
+ })
+ })
+ .clone()
+ }
+}
+
+/// The file system path for a file ID.
+fn system_path(id: FileId) -> FileResult<PathBuf> {
+ let root: PathBuf = match id.package() {
+ Some(spec) => format!("tests/packages/{}-{}", spec.name, spec.version).into(),
+ None => PathBuf::new(),
+ };
+
+ id.vpath().resolve(&root).ok_or(FileError::AccessDenied)
+}
+
+/// Read a file.
+fn read(path: &Path) -> FileResult<Cow<'static, [u8]>> {
+ // Resolve asset.
+ if let Ok(suffix) = path.strip_prefix("assets/") {
+ return typst_dev_assets::get(&suffix.to_string_lossy())
+ .map(Cow::Borrowed)
+ .ok_or_else(|| FileError::NotFound(path.into()));
+ }
+
+ let f = |e| FileError::from_io(e, path);
+ if fs::metadata(path).map_err(f)?.is_dir() {
+ Err(FileError::IsDirectory)
+ } else {
+ fs::read(path).map(Cow::Owned).map_err(f)
+ }
+}
+
+/// The extended standard library for testing.
+fn library() -> Library {
+ // Set page width to 120pt with 10pt margins, so that the inner page is
+ // exactly 100pt wide. Page height is unbounded and font size is 10pt so
+ // that it multiplies to nice round numbers.
+ let mut lib = Library::default();
+
+ #[func]
+ fn test(lhs: Value, rhs: Value) -> StrResult<NoneValue> {
+ if lhs != rhs {
+ bail!("Assertion failed: {} != {}", lhs.repr(), rhs.repr());
+ }
+ Ok(NoneValue)
+ }
+
+ #[func]
+ fn test_repr(lhs: Value, rhs: Value) -> StrResult<NoneValue> {
+ if lhs.repr() != rhs.repr() {
+ bail!("Assertion failed: {} != {}", lhs.repr(), rhs.repr());
+ }
+ Ok(NoneValue)
+ }
+
+ #[func]
+ fn print(#[variadic] values: Vec<Value>) -> NoneValue {
+ let mut out = std::io::stdout().lock();
+ write!(out, "> ").unwrap();
+ for (i, value) in values.into_iter().enumerate() {
+ if i > 0 {
+ write!(out, ", ").unwrap();
+ }
+ write!(out, "{value:?}").unwrap();
+ }
+ writeln!(out).unwrap();
+ NoneValue
+ }
+
+ // Hook up helpers into the global scope.
+ lib.global.scope_mut().define_func::<test>();
+ lib.global.scope_mut().define_func::<test_repr>();
+ lib.global.scope_mut().define_func::<print>();
+ lib.global
+ .scope_mut()
+ .define("conifer", Color::from_u8(0x9f, 0xEB, 0x52, 0xFF));
+ lib.global
+ .scope_mut()
+ .define("forest", Color::from_u8(0x43, 0xA1, 0x27, 0xFF));
+
+ // Hook up default styles.
+ lib.styles
+ .set(PageElem::set_width(Smart::Custom(Abs::pt(120.0).into())));
+ lib.styles.set(PageElem::set_height(Smart::Auto));
+ lib.styles.set(PageElem::set_margin(Margin::splat(Some(Smart::Custom(
+ Abs::pt(10.0).into(),
+ )))));
+ lib.styles.set(TextElem::set_size(TextSize(Abs::pt(10.0).into())));
+
+ lib
+}
diff --git a/tests/suite/foundations/array.typ b/tests/suite/foundations/array.typ
new file mode 100644
index 00000000..3992d75e
--- /dev/null
+++ b/tests/suite/foundations/array.typ
@@ -0,0 +1,494 @@
+// Test arrays.
+
+--- array-basic-syntax ---
+#set page(width: 150pt)
+
+// Empty.
+#()
+
+// Not an array, just a parenthesized expression.
+#(1)
+
+// One item and trailing comma.
+#(-1,)
+
+// No trailing comma.
+#(true, false)
+
+// Multiple lines and items and trailing comma.
+#("1"
+ , rgb("002")
+ ,)
+
+--- array-bad-token ---
+// Error: 4-6 unexpected end of block comment
+#(1*/2)
+
+--- array-bad-number-suffix ---
+// Error: 6-8 invalid number suffix: u
+#(1, 1u 2)
+
+--- array-leading-comma ---
+// Error: 3-4 unexpected comma
+#(,1)
+
+--- array-incomplete-pair ---
+// Missing expression makes named pair incomplete, making this an empty array.
+// Error: 5 expected expression
+#(a:)
+
+--- array-named-pair ---
+// Named pair after this is already identified as an array.
+// Error: 6-10 expected expression, found named pair
+#(1, b: 2)
+
+--- array-keyed-pair ---
+// Keyed pair after this is already identified as an array.
+// Error: 6-14 expected expression, found keyed pair
+#(1, "key": 2)
+
+--- array-bad-conversion-from-string ---
+// Error: 8-15 expected array, bytes, or version, found string
+#array("hello")
+
+--- spread-into-array ---
+// Test spreading into array and dictionary.
+#{
+ let l = (1, 2, 3)
+ let r = (5, 6, 7)
+ test((..l, 4, ..r), range(1, 8))
+ test((..none), ())
+}
+
+--- spread-dict-into-array ---
+// Error: 9-17 cannot spread dictionary into array
+#(1, 2, ..(a: 1))
+
+--- array-len ---
+// Test the `len` method.
+#test(().len(), 0)
+#test(("A", "B", "C").len(), 3)
+
+--- array-at-lvalue ---
+// Test lvalue and rvalue access.
+#{
+ let array = (1, 2)
+ array.at(1) += 5 + array.at(0)
+ test(array, (1, 8))
+}
+
+--- array-first-and-at-lvalue ---
+// Test different lvalue method.
+#{
+ let array = (1, 2, 3)
+ array.first() = 7
+ array.at(1) *= 8
+ test(array, (7, 16, 3))
+}
+
+--- array-at-out-of-bounds ---
+// Test rvalue out of bounds.
+// Error: 2-17 array index out of bounds (index: 5, len: 3) and no default value was specified
+#(1, 2, 3).at(5)
+
+--- array-at-out-of-bounds-negative ---
+// Error: 2-18 array index out of bounds (index: -4, len: 3) and no default value was specified
+#(1, 2, 3).at(-4)
+
+--- array-at-out-of-bounds-lvalue ---
+// Test lvalue out of bounds.
+#{
+ let array = (1, 2, 3)
+ // Error: 3-14 array index out of bounds (index: 3, len: 3)
+ array.at(3) = 5
+}
+
+--- array-at-with-default ---
+// Test default value.
+#test((1, 2, 3).at(2, default: 5), 3)
+#test((1, 2, 3).at(3, default: 5), 5)
+
+--- array-remove-with-default ---
+// Test remove with default value.
+
+#{
+ let array = (1, 2, 3)
+ test(array.remove(2, default: 5), 3)
+}
+
+#{
+ let array = (1, 2, 3)
+ test(array.remove(3, default: 5), 5)
+}
+
+--- array-range ---
+// Test the `range` function.
+#test(range(4), (0, 1, 2, 3))
+#test(range(1, 4), (1, 2, 3))
+#test(range(-4, 2), (-4, -3, -2, -1, 0, 1))
+#test(range(10, 5), ())
+#test(range(10, step: 3), (0, 3, 6, 9))
+#test(range(1, 4, step: 1), (1, 2, 3))
+#test(range(1, 8, step: 2), (1, 3, 5, 7))
+#test(range(5, 2, step: -1), (5, 4, 3))
+#test(range(10, 0, step: -3), (10, 7, 4, 1))
+
+--- array-range-end-missing ---
+// Error: 2-9 missing argument: end
+#range()
+
+--- array-range-float-invalid ---
+// Error: 11-14 expected integer, found float
+#range(1, 2.0)
+
+--- array-range-bad-step-type ---
+// Error: 17-22 expected integer, found string
+#range(4, step: "one")
+
+--- array-range-step-zero ---
+// Error: 18-19 number must not be zero
+#range(10, step: 0)
+
+--- array-bad-method-lvalue ---
+// Test bad lvalue.
+// Error: 2:3-2:14 cannot mutate a temporary value
+#let array = (1, 2, 3)
+#(array.len() = 4)
+
+--- array-unknown-method-lvalue ---
+// Test bad lvalue.
+// Error: 2:9-2:13 type array has no method `yolo`
+#let array = (1, 2, 3)
+#(array.yolo() = 4)
+
+--- array-negative-indices ---
+// Test negative indices.
+#{
+ let array = (1, 2, 3, 4)
+ test(array.at(0), 1)
+ test(array.at(-1), 4)
+ test(array.at(-2), 3)
+ test(array.at(-3), 2)
+ test(array.at(-4), 1)
+}
+
+--- array-first-and-last ---
+// The the `first` and `last` methods.
+#test((1,).first(), 1)
+#test((2,).last(), 2)
+#test((1, 2, 3).first(), 1)
+#test((1, 2, 3).last(), 3)
+
+--- array-first-empty ---
+// Error: 2-12 array is empty
+#().first()
+
+--- array-last-empty ---
+// Error: 2-11 array is empty
+#().last()
+
+--- array-push-and-pop ---
+// Test the `push` and `pop` methods.
+#{
+ let tasks = (a: (1, 2, 3), b: (4, 5, 6))
+ test(tasks.at("a").pop(), 3)
+ tasks.b.push(7)
+ test(tasks.a, (1, 2))
+ test(tasks.at("b"), (4, 5, 6, 7))
+}
+
+--- array-insert-and-remove ---
+// Test the `insert` and `remove` methods.
+#{
+ let array = (0, 1, 2, 4, 5)
+ array.insert(3, 3)
+ test(array, range(6))
+ array.remove(1)
+ test(array, (0, 2, 3, 4, 5))
+}
+
+--- array-insert-missing-index ---
+// Error: 2:2-2:18 missing argument: index
+#let numbers = ()
+#numbers.insert()
+
+--- array-slice ---
+// Test the `slice` method.
+#test((1, 2, 3, 4).slice(2), (3, 4))
+#test(range(10).slice(2, 6), (2, 3, 4, 5))
+#test(range(10).slice(4, count: 3), (4, 5, 6))
+#test(range(10).slice(-5, count: 2), (5, 6))
+#test((1, 2, 3).slice(2, -2), ())
+#test((1, 2, 3).slice(-2, 2), (2,))
+#test((1, 2, 3).slice(-3, 2), (1, 2))
+#test("ABCD".split("").slice(1, -1).join("-"), "A-B-C-D")
+
+--- array-slice-out-of-bounds ---
+// Error: 2-30 array index out of bounds (index: 12, len: 10)
+#range(10).slice(9, count: 3)
+
+--- array-slice-out-of-bounds-negative ---
+// Error: 2-24 array index out of bounds (index: -4, len: 3)
+#(1, 2, 3).slice(0, -4)
+
+--- array-position ---
+// Test the `position` method.
+#test(("Hi", "❤️", "Love").position(s => s == "❤️"), 1)
+#test(("Bye", "💘", "Apart").position(s => s == "❤️"), none)
+#test(("A", "B", "CDEF", "G").position(v => v.len() > 2), 2)
+
+--- array-filter ---
+// Test the `filter` method.
+#test(().filter(calc.even), ())
+#test((1, 2, 3, 4).filter(calc.even), (2, 4))
+#test((7, 3, 2, 5, 1).filter(x => x < 5), (3, 2, 1))
+
+--- array-map ---
+// Test the `map` method.
+#test(().map(x => x * 2), ())
+#test((2, 3).map(x => x * 2), (4, 6))
+
+--- array-fold ---
+// Test the `fold` method.
+#test(().fold("hi", grid), "hi")
+#test((1, 2, 3, 4).fold(0, (s, x) => s + x), 10)
+
+--- array-fold-closure-without-params ---
+// Error: 20-22 unexpected argument
+#(1, 2, 3).fold(0, () => none)
+
+--- array-sum ---
+// Test the `sum` method.
+#test(().sum(default: 0), 0)
+#test(().sum(default: []), [])
+#test((1, 2, 3).sum(), 6)
+
+--- array-sum-empty ---
+// Error: 2-10 cannot calculate sum of empty array with no default
+#().sum()
+
+--- array-product ---
+// Test the `product` method.
+#test(().product(default: 0), 0)
+#test(().product(default: []), [])
+#test(([ab], 3).product(), [ab]*3)
+#test((1, 2, 3).product(), 6)
+
+--- array-product-empty ---
+// Error: 2-14 cannot calculate product of empty array with no default
+#().product()
+
+--- array-rev ---
+// Test the `rev` method.
+#test(range(3).rev(), (2, 1, 0))
+
+--- array-join ---
+// Test the `join` method.
+#test(().join(), none)
+#test((1,).join(), 1)
+#test(("a", "b", "c").join(), "abc")
+#test("(" + ("a", "b", "c").join(", ") + ")", "(a, b, c)")
+
+--- array-join-bad-values ---
+// Error: 2-22 cannot join boolean with boolean
+#(true, false).join()
+
+--- array-join-bad-separator ---
+// Error: 2-20 cannot join string with integer
+#("a", "b").join(1)
+
+--- array-join-content ---
+// Test joining content.
+#([One], [Two], [Three]).join([, ], last: [ and ]).
+
+--- array-intersperse ---
+// Test the `intersperse` method
+#test(().intersperse("a"), ())
+#test((1,).intersperse("a"), (1,))
+#test((1, 2).intersperse("a"), (1, "a", 2))
+#test((1, 2, "b").intersperse("a"), (1, "a", 2, "a", "b"))
+
+--- array-chunks ---
+// Test the `chunks` method.
+#test(().chunks(10), ())
+#test((1, 2, 3).chunks(10), ((1, 2, 3),))
+#test((1, 2, 3, 4, 5, 6).chunks(3), ((1, 2, 3), (4, 5, 6)))
+#test((1, 2, 3, 4, 5, 6, 7, 8).chunks(3), ((1, 2, 3), (4, 5, 6), (7, 8)))
+
+#test(().chunks(10, exact: true), ())
+#test((1, 2, 3).chunks(10, exact: true), ())
+#test((1, 2, 3, 4, 5, 6).chunks(3, exact: true), ((1, 2, 3), (4, 5, 6)))
+#test((1, 2, 3, 4, 5, 6, 7, 8).chunks(3, exact: true), ((1, 2, 3), (4, 5, 6)))
+
+--- array-chunks-size-zero ---
+// Error: 19-20 number must be positive
+#(1, 2, 3).chunks(0)
+
+--- array-chunks-size-negative ---
+// Error: 19-21 number must be positive
+#(1, 2, 3).chunks(-5)
+
+--- array-sorted ---
+// Test the `sorted` method.
+#test(().sorted(), ())
+#test(().sorted(key: x => x), ())
+#test(((true, false) * 10).sorted(), (false,) * 10 + (true,) * 10)
+#test(("it", "the", "hi", "text").sorted(), ("hi", "it", "text", "the"))
+#test(("I", "the", "hi", "text").sorted(key: x => x), ("I", "hi", "text", "the"))
+#test(("I", "the", "hi", "text").sorted(key: x => x.len()), ("I", "hi", "the", "text"))
+#test((2, 1, 3, 10, 5, 8, 6, -7, 2).sorted(), (-7, 1, 2, 2, 3, 5, 6, 8, 10))
+#test((2, 1, 3, -10, -5, 8, 6, -7, 2).sorted(key: x => x), (-10, -7, -5, 1, 2, 2, 3, 6, 8))
+#test((2, 1, 3, -10, -5, 8, 6, -7, 2).sorted(key: x => x * x), (1, 2, 2, 3, -5, 6, -7, 8, -10))
+
+--- array-sorted-key-function-positional-1 ---
+// Error: 12-18 unexpected argument
+#().sorted(x => x)
+
+--- array-zip ---
+// Test the `zip` method.
+#test(().zip(()), ())
+#test((1,).zip(()), ())
+#test((1,).zip((2,)), ((1, 2),))
+#test((1, 2).zip((3, 4)), ((1, 3), (2, 4)))
+#test((1, 2, 3, 4).zip((5, 6)), ((1, 5), (2, 6)))
+#test(((1, 2), 3).zip((4, 5)), (((1, 2), 4), (3, 5)))
+#test((1, "hi").zip((true, false)), ((1, true), ("hi", false)))
+#test((1, 2, 3).zip((3, 4, 5), (6, 7, 8)), ((1, 3, 6), (2, 4, 7), (3, 5, 8)))
+#test(().zip((), ()), ())
+#test((1,).zip((2,), (3,)), ((1, 2, 3),))
+#test((1, 2, 3).zip(), ((1,), (2,), (3,)))
+#test(array.zip(()), ())
+
+--- array-enumerate ---
+// Test the `enumerate` method.
+#test(().enumerate(), ())
+#test(().enumerate(start: 5), ())
+#test(("a", "b", "c").enumerate(), ((0, "a"), (1, "b"), (2, "c")))
+#test(("a", "b", "c").enumerate(start: 1), ((1, "a"), (2, "b"), (3, "c")))
+#test(("a", "b", "c").enumerate(start: 42), ((42, "a"), (43, "b"), (44, "c")))
+#test(("a", "b", "c").enumerate(start: -7), ((-7, "a"), (-6, "b"), (-5, "c")))
+
+--- array-dedup ---
+// Test the `dedup` method.
+#test(().dedup(), ())
+#test((1,).dedup(), (1,))
+#test((1, 1).dedup(), (1,))
+#test((1, 2, 1).dedup(), (1, 2))
+#test(("Jane", "John", "Eric").dedup(), ("Jane", "John", "Eric"))
+#test(("Jane", "John", "Eric", "John").dedup(), ("Jane", "John", "Eric"))
+
+--- array-dedup-key ---
+// Test the `dedup` method with the `key` argument.
+#test((1, 2, 3, 4, 5, 6).dedup(key: x => calc.rem(x, 2)), (1, 2))
+#test((1, 2, 3, 4, 5, 6).dedup(key: x => calc.rem(x, 3)), (1, 2, 3))
+#test(("Hello", "World", "Hi", "There").dedup(key: x => x.len()), ("Hello", "Hi"))
+#test(("Hello", "World", "Hi", "There").dedup(key: x => x.at(0)), ("Hello", "World", "There"))
+
+--- array-to-dict ---
+// Test the `to-dict` method.
+#test(().to-dict(), (:))
+#test((("a", 1), ("b", 2), ("c", 3)).to-dict(), (a: 1, b: 2, c: 3))
+#test((("a", 1), ("b", 2), ("c", 3), ("b", 4)).to-dict(), (a: 1, b: 4, c: 3))
+
+--- array-to-dict-bad-item-type ---
+// Error: 2-16 expected (str, any) pairs, found integer
+#(1,).to-dict()
+
+--- array-to-dict-bad-pair-length-1 ---
+// Error: 2-19 expected pairs of length 2, found length 1
+#((1,),).to-dict()
+
+--- array-to-dict-bad-pair-length-3 ---
+// Error: 2-26 expected pairs of length 2, found length 3
+#(("key",1,2),).to-dict()
+
+--- array-to-dict-bad-key-type ---
+// Error: 2-21 expected key of type str, found integer
+#((1, 2),).to-dict()
+
+--- array-zip-positional-and-named-argument ---
+// Error: 13-30 unexpected argument: val
+#().zip((), val: "applicable")
+
+--- array-sorted-bad-key ---
+// Error: 32-37 cannot divide by zero
+#(1, 2, 0, 3).sorted(key: x => 5 / x)
+
+--- array-sorted-uncomparable ---
+// Error: 2-26 cannot compare content and content
+#([Hi], [There]).sorted()
+
+--- array-sorted-uncomparable-lengths ---
+// Error: 2-26 cannot compare 3em with 2pt
+#(1pt, 2pt, 3em).sorted()
+
+--- array-sorted-key-function-positional-2 ---
+// Error: 42-52 unexpected argument
+#((k: "a", v: 2), (k: "b", v: 1)).sorted(it => it.v)
+
+--- issue-3014-mix-array-dictionary ---
+// Error: 8-17 expected expression, found named pair
+#(box, fill: red)
+
+--- issue-3154-array-first-empty ---
+#{
+ let array = ()
+ // Error: 3-16 array is empty
+ array.first()
+}
+
+--- issue-3154-array-first-mutable-empty ---
+#{
+ let array = ()
+ // Error: 3-16 array is empty
+ array.first() = 9
+}
+
+--- issue-3154-array-last-empty ---
+#{
+ let array = ()
+ // Error: 3-15 array is empty
+ array.last()
+}
+
+--- issue-3154-array-last-mutable-empty ---
+#{
+ let array = ()
+ // Error: 3-15 array is empty
+ array.last() = 9
+}
+
+--- issue-3154-array-at-out-of-bounds ---
+#{
+ let array = (1,)
+ // Error: 3-14 array index out of bounds (index: 1, len: 1) and no default value was specified
+ array.at(1)
+}
+
+--- issue-3154-array-at-out-of-bounds-default ---
+#{
+ let array = (1,)
+ test(array.at(1, default: 0), 0)
+}
+
+--- issue-3154-array-at-out-of-bounds-mutable ---
+#{
+ let array = (1,)
+ // Error: 3-14 array index out of bounds (index: 1, len: 1)
+ array.at(1) = 9
+}
+
+--- issue-3154-array-at-out-of-bounds-mutable-default ---
+#{
+ let array = (1,)
+ // Error: 3-26 array index out of bounds (index: 1, len: 1)
+ array.at(1, default: 0) = 9
+}
+
+--- array-unopened ---
+// Error: 2-3 unclosed delimiter
+#{)}
+
+--- array-unclosed ---
+// Error: 3-4 unclosed delimiter
+#{(}
diff --git a/tests/suite/foundations/assert.typ b/tests/suite/foundations/assert.typ
new file mode 100644
index 00000000..5de0f387
--- /dev/null
+++ b/tests/suite/foundations/assert.typ
@@ -0,0 +1,40 @@
+--- assert-fail ---
+// Test failing assertions.
+// Error: 2-16 assertion failed
+#assert(1 == 2)
+
+--- assert-fail-message ---
+// Test failing assertions.
+// Error: 2-51 assertion failed: two is smaller than one
+#assert(2 < 1, message: "two is smaller than one")
+
+--- assert-bad-type ---
+// Test failing assertions.
+// Error: 9-15 expected boolean, found string
+#assert("true")
+
+--- assert-eq-fail ---
+// Test failing assertions.
+// Error: 2-19 equality assertion failed: value 10 was not equal to 11
+#assert.eq(10, 11)
+
+--- assert-eq-fail-message ---
+// Test failing assertions.
+// Error: 2-55 equality assertion failed: 10 and 12 are not equal
+#assert.eq(10, 12, message: "10 and 12 are not equal")
+
+--- assert-ne-fail ---
+// Test failing assertions.
+// Error: 2-19 inequality assertion failed: value 11 was equal to 11
+#assert.ne(11, 11)
+
+--- assert-ne-fail-message ---
+// Test failing assertions.
+// Error: 2-57 inequality assertion failed: must be different from 11
+#assert.ne(11, 11, message: "must be different from 11")
+
+--- assert-success ---
+// Test successful assertions.
+#assert(5 > 3)
+#assert.eq(15, 15)
+#assert.ne(10, 12)
diff --git a/tests/suite/foundations/bytes.typ b/tests/suite/foundations/bytes.typ
new file mode 100644
index 00000000..c7089278
--- /dev/null
+++ b/tests/suite/foundations/bytes.typ
@@ -0,0 +1,31 @@
+// Test the bytes type.
+
+--- bytes-basic ---
+#let data = read("/assets/images/rhino.png", encoding: none)
+#test(data.len(), 232243)
+#test(data.slice(0, count: 5), bytes((137, 80, 78, 71, 13)))
+#test(str(data.slice(1, 4)), "PNG")
+#test(repr(data), "bytes(232243)")
+
+--- bytes-string-conversion ---
+#test(str(bytes(range(0x41, 0x50))), "ABCDEFGHIJKLMNO")
+
+--- bytes-array-conversion ---
+#test(array(bytes("Hello")), (0x48, 0x65, 0x6C, 0x6C, 0x6F))
+
+--- bytes-addition ---
+// Test addition and joining.
+#test(bytes((1, 2)) + bytes(()), bytes((1, 2)))
+#test(bytes((1, 2)) + bytes((3, 4)), bytes((1, 2, 3, 4)))
+#test(bytes(()) + bytes((3, 4)), bytes((3, 4)))
+
+--- bytes-joining ---
+#test(str({
+ bytes("Hello")
+ bytes((0x20,))
+ bytes("World")
+}), "Hello World")
+
+--- bytes-bad-conversion-from-dict ---
+// Error: 8-14 expected string, array, or bytes, found dictionary
+#bytes((a: 1))
diff --git a/tests/suite/foundations/calc.typ b/tests/suite/foundations/calc.typ
new file mode 100644
index 00000000..e702be9f
--- /dev/null
+++ b/tests/suite/foundations/calc.typ
@@ -0,0 +1,261 @@
+--- calc-round ---
+#test(calc.round(calc.e, digits: 2), 2.72)
+#test(calc.round(calc.pi, digits: 2), 3.14)
+
+--- calc-abs ---
+// Test the `abs` function.
+#test(calc.abs(-3), 3)
+#test(calc.abs(3), 3)
+#test(calc.abs(-0.0), 0.0)
+#test(calc.abs(0.0), -0.0)
+#test(calc.abs(-3.14), 3.14)
+#test(calc.abs(50%), 50%)
+#test(calc.abs(-25%), 25%)
+
+--- cals-abs-bad-type ---
+// Error: 11-22 expected integer, float, length, angle, ratio, or fraction, found string
+#calc.abs("no number")
+
+--- calc-even-and-odd ---
+// Test the `even` and `odd` functions.
+#test(calc.even(2), true)
+#test(calc.odd(2), false)
+#test(calc.odd(-1), true)
+#test(calc.even(-11), false)
+
+--- calc-rem ---
+// Test the `rem` function.
+#test(calc.rem(1, 1), 0)
+#test(calc.rem(5, 3), 2)
+#test(calc.rem(5, -3), 2)
+#test(calc.rem(22.5, 10), 2.5)
+#test(calc.rem(9, 4.5), 0)
+
+--- calc-rem-divisor-zero-1 ---
+// Error: 14-15 divisor must not be zero
+#calc.rem(5, 0)
+
+--- calc-rem-divisor-zero-2 ---
+// Error: 16-19 divisor must not be zero
+#calc.rem(3.0, 0.0)
+
+--- calc-div-euclid ---
+// Test the `div-euclid` function.
+#test(calc.div-euclid(7, 3), 2)
+#test(calc.div-euclid(7, -3), -2)
+#test(calc.div-euclid(-7, 3), -3)
+#test(calc.div-euclid(-7, -3), 3)
+#test(calc.div-euclid(2.5, 2), 1)
+
+--- calc-div-euclid-divisor-zero-1 ---
+// Error: 21-22 divisor must not be zero
+#calc.div-euclid(5, 0)
+
+--- calc-div-euclid-divisor-zero-2 ---
+// Error: 23-26 divisor must not be zero
+#calc.div-euclid(3.0, 0.0)
+
+--- calc-rem-euclid ---
+// Test the `rem-euclid` function.
+#test(calc.rem-euclid(7, 3), 1)
+#test(calc.rem-euclid(7, -3), 1)
+#test(calc.rem-euclid(-7, 3), 2)
+#test(calc.rem-euclid(-7, -3), 2)
+#test(calc.rem-euclid(2.5, 2), 0.5)
+
+--- calc-rem-euclid-divisor-zero-1 ---
+// Error: 21-22 divisor must not be zero
+#calc.rem-euclid(5, 0)
+
+--- calc-rem-euclid-divisor-zero-2 ---
+// Error: 23-26 divisor must not be zero
+#calc.rem-euclid(3.0, 0.0)
+
+--- calc-quo ---
+// Test the `quo` function.
+#test(calc.quo(1, 1), 1)
+#test(calc.quo(5, 3), 1)
+#test(calc.quo(5, -3), -1)
+#test(calc.quo(22.5, 10), 2)
+#test(calc.quo(9, 4.5), 2)
+
+--- calc-quo-divisor-zero-1 ---
+// Error: 14-15 divisor must not be zero
+#calc.quo(5, 0)
+
+--- calc-quo-divisor-zero-2 ---
+// Error: 16-19 divisor must not be zero
+#calc.quo(3.0, 0.0)
+
+--- calc-min-and-max ---
+// Test the `min` and `max` functions.
+#test(calc.min(2, -4), -4)
+#test(calc.min(3.5, 1e2, -0.1, 3), -0.1)
+#test(calc.max(-3, 11), 11)
+#test(calc.min("hi"), "hi")
+
+--- calc-pow-log-exp-ln ---
+// Test the `pow`, `log`, `exp`, and `ln` functions.
+#test(calc.pow(10, 0), 1)
+#test(calc.pow(2, 4), 16)
+#test(calc.exp(2), calc.pow(calc.e, 2))
+#test(calc.ln(10), calc.log(10, base: calc.e))
+
+--- calc-bit-logical ---
+// Test the `bit-not`, `bit-and`, `bit-or` and `bit-xor` functions.
+#test(64.bit-not(), -65)
+#test(0.bit-not(), -1)
+#test((-56).bit-not(), 55)
+#test(128.bit-and(192), 128)
+#test(192.bit-and(224), 192)
+#test((-1).bit-and(325532), 325532)
+#test(0.bit-and(-53), 0)
+#test(0.bit-or(-1), -1)
+#test(5.bit-or(3), 7)
+#test((-50).bit-or(3), -49)
+#test(64.bit-or(32), 96)
+#test((-1).bit-xor(1), -2)
+#test(64.bit-xor(96), 32)
+#test((-1).bit-xor(-7), 6)
+#test(0.bit-xor(492), 492)
+
+--- calc-bit-shift ---
+// Test the `bit-lshift` and `bit-rshift` functions.
+#test(32.bit-lshift(2), 128)
+#test(694.bit-lshift(0), 694)
+#test(128.bit-rshift(2), 32)
+#test(128.bit-rshift(12345), 0)
+#test((-7).bit-rshift(2), -2)
+#test((-7).bit-rshift(12345), -1)
+#test(128.bit-rshift(2, logical: true), 32)
+#test((-7).bit-rshift(61, logical: true), 7)
+#test(128.bit-rshift(12345, logical: true), 0)
+#test((-7).bit-rshift(12345, logical: true), 0)
+
+--- calc-bit-shift-too-large ---
+// Error: 2-18 the result is too large
+#1.bit-lshift(64)
+
+--- calc-bit-lshift-negative ---
+// Error: 15-17 number must be at least zero
+#1.bit-lshift(-1)
+
+--- calc-bit-rshift-negative ---
+// Error: 15-17 number must be at least zero
+#1.bit-rshift(-1)
+
+--- calc-pow-zero-to-power-of-zero ---
+// Error: 2-16 zero to the power of zero is undefined
+#calc.pow(0, 0)
+
+--- calc-pow-exponent-too-large ---
+// Error: 14-31 exponent is too large
+#calc.pow(2, 10000000000000000)
+
+--- calc-pow-too-large ---
+// Error: 2-25 the result is too large
+#calc.pow(2, 2147483647)
+
+--- calc-pow-bad-exponent ---
+// Error: 14-36 exponent may not be infinite, subnormal, or NaN
+#calc.pow(2, calc.pow(2.0, 10000.0))
+
+--- calc-pow-not-real ---
+// Error: 2-19 the result is not a real number
+#calc.pow(-1, 0.5)
+
+--- calc-sqrt-not-real ---
+// Error: 12-14 cannot take square root of negative number
+#calc.sqrt(-1)
+
+--- calc-root ---
+#test(calc.root(12.0, 1), 12.0)
+#test(calc.root(9.0, 2), 3.0)
+#test(calc.root(27.0, 3), 3.0)
+#test(calc.root(-27.0, 3), -3.0)
+// 100^(-1/2) = (100^(1/2))^-1 = 1/sqrt(100)
+#test(calc.root(100.0, -2), 0.1)
+
+--- calc-root-zeroth ---
+// Error: 17-18 cannot take the 0th root of a number
+#calc.root(1.0, 0)
+
+--- calc-root-negative-even ---
+// Error: 24-25 negative numbers do not have a real nth root when n is even
+#test(calc.root(-27.0, 4), -3.0)
+
+--- calc-log-negative ---
+// Error: 11-13 value must be strictly positive
+#calc.log(-1)
+
+--- calc-log-bad-base ---
+// Error: 20-21 base may not be zero, NaN, infinite, or subnormal
+#calc.log(1, base: 0)
+
+--- calc-log-not-real ---
+// Error: 2-24 the result is not a real number
+#calc.log(10, base: -1)
+
+--- calc-fact ---
+// Test the `fact` function.
+#test(calc.fact(0), 1)
+#test(calc.fact(5), 120)
+
+--- calc-fact-too-large ---
+// Error: 2-15 the result is too large
+#calc.fact(21)
+
+--- calc-perm ---
+// Test the `perm` function.
+#test(calc.perm(0, 0), 1)
+#test(calc.perm(5, 3), 60)
+#test(calc.perm(5, 5), 120)
+#test(calc.perm(5, 6), 0)
+
+--- calc-perm-too-large ---
+// Error: 2-19 the result is too large
+#calc.perm(21, 21)
+
+--- calc-binom ---
+// Test the `binom` function.
+#test(calc.binom(0, 0), 1)
+#test(calc.binom(5, 3), 10)
+#test(calc.binom(5, 5), 1)
+#test(calc.binom(5, 6), 0)
+#test(calc.binom(6, 2), 15)
+
+--- calc-gcd ---
+// Test the `gcd` function.
+#test(calc.gcd(112, 77), 7)
+#test(calc.gcd(12, 96), 12)
+#test(calc.gcd(13, 9), 1)
+#test(calc.gcd(13, -9), 1)
+#test(calc.gcd(272557, 272557), 272557)
+#test(calc.gcd(0, 0), 0)
+#test(calc.gcd(7, 0), 7)
+
+--- calc-lcm ---
+// Test the `lcm` function.
+#test(calc.lcm(112, 77), 1232)
+#test(calc.lcm(12, 96), 96)
+#test(calc.lcm(13, 9), 117)
+#test(calc.lcm(13, -9), 117)
+#test(calc.lcm(272557, 272557), 272557)
+#test(calc.lcm(0, 0), 0)
+#test(calc.lcm(8, 0), 0)
+
+--- calc-lcm-too-large ---
+// Error: 2-41 the result is too large
+#calc.lcm(15486487489457, 4874879896543)
+
+--- calc-min-nothing ---
+// Error: 2-12 expected at least one value
+#calc.min()
+
+--- calc-min-uncomparable ---
+// Error: 14-18 cannot compare string and integer
+#calc.min(1, "hi")
+
+--- calc-max-uncomparable ---
+// Error: 16-19 cannot compare 1pt with 1em
+#calc.max(1em, 1pt)
diff --git a/tests/suite/foundations/content.typ b/tests/suite/foundations/content.typ
new file mode 100644
index 00000000..afecc124
--- /dev/null
+++ b/tests/suite/foundations/content.typ
@@ -0,0 +1,120 @@
+--- content-at-default ---
+// Test .at() default values for content.
+#test(auto, [a].at("doesn't exist", default: auto))
+
+--- content-field-syntax ---
+// Test fields on elements.
+#show list: it => {
+ test(it.children.len(), 3)
+}
+
+- A
+- B
+- C
+
+--- content-field-missing ---
+// Error: 25-28 content does not contain field "fun"
+#show heading: it => it.fun
+= A
+
+--- content-fields ---
+// Test content fields method.
+#test([a].fields(), (text: "a"))
+#test([a *b*].fields(), (children: ([a], [ ], strong[b])))
+
+--- content-fields-mutable-invalid ---
+#{
+ let object = [hi]
+ // Error: 3-9 cannot mutate fields on content
+ object.property = "value"
+}
+
+--- content-field-materialized-table ---
+// Ensure that fields from set rules are materialized into the element before
+// a show rule runs.
+#set table(columns: (10pt, auto))
+#show table: it => it.columns
+#table[A][B][C][D]
+
+--- content-field-materialized-heading ---
+// Test it again with a different element.
+#set heading(numbering: "(I)")
+#show heading: set text(size: 11pt, weight: "regular")
+#show heading: it => it.numbering
+= Heading
+
+--- content-field-materialized-query ---
+// Test it with query.
+#set raw(lang: "rust")
+#context query(<myraw>).first().lang
+`raw` <myraw>
+
+--- content-fields-complex ---
+// Integrated test for content fields.
+#let compute(equation, ..vars) = {
+ let vars = vars.named()
+ let f(elem) = {
+ let func = elem.func()
+ if func == text {
+ let text = elem.text
+ if regex("^\d+$") in text {
+ int(text)
+ } else if text in vars {
+ int(vars.at(text))
+ } else {
+ panic("unknown math variable: " + text)
+ }
+ } else if func == math.attach {
+ let value = f(elem.base)
+ if elem.has("t") {
+ value = calc.pow(value, f(elem.t))
+ }
+ value
+ } else if elem.has("children") {
+ elem
+ .children
+ .filter(v => v != [ ])
+ .split[+]
+ .map(xs => xs.fold(1, (prod, v) => prod * f(v)))
+ .fold(0, (sum, v) => sum + v)
+ }
+ }
+ let result = f(equation.body)
+ [With ]
+ vars
+ .pairs()
+ .map(p => $#p.first() = #p.last()$)
+ .join(", ", last: " and ")
+ [ we have:]
+ $ equation = result $
+}
+
+#compute($x y + y^2$, x: 2, y: 3)
+
+--- content-label-has-method ---
+// Test whether the label is accessible through the `has` method.
+#show heading: it => {
+ assert(it.has("label"))
+ it
+}
+
+= Hello, world! <my-label>
+
+--- content-label-field-access ---
+// Test whether the label is accessible through field syntax.
+#show heading: it => {
+ assert(str(it.label) == "my-label")
+ it
+}
+
+= Hello, world! <my-label>
+
+--- content-label-fields-method ---
+// Test whether the label is accessible through the fields method.
+#show heading: it => {
+ assert("label" in it.fields())
+ assert(str(it.fields().label) == "my-label")
+ it
+}
+
+= Hello, world! <my-label>
diff --git a/tests/suite/foundations/context.typ b/tests/suite/foundations/context.typ
new file mode 100644
index 00000000..fea9f544
--- /dev/null
+++ b/tests/suite/foundations/context.typ
@@ -0,0 +1,65 @@
+// Test context expressions.
+
+--- context-body-atomic-in-markup ---
+// Test that context body is parsed as atomic expression.
+#let c = [#context "hello".]
+#test(c.children.first().func(), (context none).func())
+#test(c.children.last(), [.])
+
+--- context-element-constructor-forbidden ---
+// Test that manual construction is forbidden.
+// Error: 2-25 cannot be constructed manually
+#(context none).func()()
+
+--- context-in-show-rule ---
+// Test that show rule establishes context.
+#set heading(numbering: "1.")
+#show heading: it => test(
+ counter(heading).get(),
+ (intro: (1,), back: (2,)).at(str(it.label)),
+)
+
+= Introduction <intro>
+= Background <back>
+
+--- context-in-show-rule-query ---
+// Test that show rule on non-locatable element allows `query`.
+// Error: 18-47 Assertion failed: 2 != 3
+#show emph: _ => test(query(heading).len(), 3)
+#show strong: _ => test(query(heading).len(), 2)
+= Introduction
+= Background
+*Hi* _there_
+
+--- context-assign-to-captured-variable ---
+// Test error when captured variable is assigned to.
+#let i = 0
+// Error: 11-12 variables from outside the context expression are read-only and cannot be modified
+#context (i = 1)
+
+--- context-compatibility-locate ---
+#let s = state("x", 0)
+#let compute(expr) = [
+ #s.update(x =>
+ eval(expr.replace("x", str(x)))
+ )
+ New value is #s.display().
+]
+
+#locate(loc => {
+ let elem = query(<here>, loc).first()
+ test(s.at(elem.location()), 13)
+})
+
+#compute("10") \
+#compute("x + 3") \
+*Here.* <here> \
+#compute("x * 2") \
+#compute("x - 5")
+
+--- context-compatibility-styling ---
+#style(styles => measure([it], styles).width < 20pt)
+
+--- context-compatibility-counter-display ---
+#counter(heading).update(10)
+#counter(heading).display(n => test(n, 10))
diff --git a/tests/suite/foundations/datetime.typ b/tests/suite/foundations/datetime.typ
new file mode 100644
index 00000000..b54c11f3
--- /dev/null
+++ b/tests/suite/foundations/datetime.typ
@@ -0,0 +1,93 @@
+--- datetime-constructor-empty ---
+// Error: 2-12 at least one of date or time must be fully specified
+#datetime()
+
+--- datetime-constructor-time-invalid ---
+// Error: 2-42 time is invalid
+#datetime(hour: 25, minute: 0, second: 0)
+
+--- datetime-constructor-date-invalid ---
+// Error: 2-41 date is invalid
+#datetime(year: 2000, month: 2, day: 30)
+
+--- datetime-display ---
+// Test displaying of dates.
+#test(datetime(year: 2023, month: 4, day: 29).display(), "2023-04-29")
+#test(datetime(year: 2023, month: 4, day: 29).display("[year]"), "2023")
+#test(
+ datetime(year: 2023, month: 4, day: 29)
+ .display("[year repr:last_two]"),
+ "23",
+)
+#test(
+ datetime(year: 2023, month: 4, day: 29)
+ .display("[year] [month repr:long] [day] [week_number] [weekday]"),
+ "2023 April 29 17 Saturday",
+)
+
+// Test displaying of times
+#test(datetime(hour: 14, minute: 26, second: 50).display(), "14:26:50")
+#test(datetime(hour: 14, minute: 26, second: 50).display("[hour]"), "14")
+#test(
+ datetime(hour: 14, minute: 26, second: 50)
+ .display("[hour repr:12 padding:none]"),
+ "2",
+)
+#test(
+ datetime(hour: 14, minute: 26, second: 50)
+ .display("[hour], [minute], [second]"), "14, 26, 50",
+)
+
+// Test displaying of datetimes
+#test(
+ datetime(year: 2023, month: 4, day: 29, hour: 14, minute: 26, second: 50).display(),
+ "2023-04-29 14:26:50",
+)
+
+// Test getting the year/month/day etc. of a datetime
+#let d = datetime(year: 2023, month: 4, day: 29, hour: 14, minute: 26, second: 50)
+#test(d.year(), 2023)
+#test(d.month(), 4)
+#test(d.weekday(), 6)
+#test(d.day(), 29)
+#test(d.hour(), 14)
+#test(d.minute(), 26)
+#test(d.second(), 50)
+
+#let e = datetime(year: 2023, month: 4, day: 29)
+#test(e.hour(), none)
+#test(e.minute(), none)
+#test(e.second(), none)
+
+// Test today
+#test(datetime.today().display(), "1970-01-01")
+#test(datetime.today(offset: auto).display(), "1970-01-01")
+#test(datetime.today(offset: 2).display(), "1970-01-01")
+
+--- datetime-ordinal ---
+// Test date methods.
+#test(datetime(day: 1, month: 1, year: 2000).ordinal(), 1);
+#test(datetime(day: 1, month: 3, year: 2000).ordinal(), 31 + 29 + 1);
+#test(datetime(day: 31, month: 12, year: 2000).ordinal(), 366);
+#test(datetime(day: 1, month: 3, year: 2001).ordinal(), 31 + 28 + 1);
+#test(datetime(day: 31, month: 12, year: 2001).ordinal(), 365);
+
+--- datetime-display-missing-closing-bracket ---
+// Error: 27-34 missing closing bracket for bracket at index 0
+#datetime.today().display("[year")
+
+--- datetime-display-invalid-component ---
+// Error: 27-38 invalid component name 'nothing' at index 1
+#datetime.today().display("[nothing]")
+
+--- datetime-display-invalid-modifier ---
+// Error: 27-50 invalid modifier 'wrong' at index 6
+#datetime.today().display("[year wrong:last_two]")
+
+--- datetime-display-expected-component ---
+// Error: 27-33 expected component name at index 2
+#datetime.today().display(" []")
+
+--- datetime-display-insufficient-information ---
+// Error: 2-36 failed to format datetime (insufficient information)
+#datetime.today().display("[hour]")
diff --git a/tests/suite/foundations/dict.typ b/tests/suite/foundations/dict.typ
new file mode 100644
index 00000000..2c2d2a41
--- /dev/null
+++ b/tests/suite/foundations/dict.typ
@@ -0,0 +1,266 @@
+// Test dictionaries.
+
+--- dict-basic-syntax ---
+
+// Empty
+#(:)
+
+// Two pairs and string key.
+#let dict = (normal: 1, "spacy key": 2)
+#dict
+
+#test(dict.normal, 1)
+#test(dict.at("spacy key"), 2)
+
+--- dict-fields ---
+// Test field on dictionary.
+#let dict = (nothing: "ness", hello: "world")
+#test(dict.nothing, "ness")
+#{
+ let world = dict
+ .hello
+
+ test(world, "world")
+}
+
+--- dict-missing-field ---
+// Error: 6-13 dictionary does not contain key "invalid"
+#(:).invalid
+
+--- dict-bad-key ---
+// Error: 3-7 expected string, found boolean
+// Error: 16-18 expected string, found integer
+#(true: false, 42: 3)
+
+--- dict-duplicate-key ---
+// Error: 24-29 duplicate key: first
+#(first: 1, second: 2, first: 3)
+
+--- dict-duplicate-key-stringy ---
+// Error: 17-20 duplicate key: a
+#(a: 1, "b": 2, "a": 3)
+
+--- dict-bad-expression ---
+// Simple expression after already being identified as a dictionary.
+// Error: 9-10 expected named or keyed pair, found identifier
+#(a: 1, b)
+
+--- dict-leading-colon ---
+// Identified as dictionary due to initial colon.
+// The boolean key is allowed for now since it will only cause an error at the evaluation stage.
+// Error: 4-5 expected named or keyed pair, found integer
+// Error: 17 expected expression
+#(:1 b:"", true:)
+
+--- spread-into-dict ---
+#{
+ let x = (a: 1)
+ let y = (b: 2)
+ let z = (a: 3)
+ test((:..x, ..y, ..z), (a: 3, b: 2))
+ test((..(a: 1), b: 2), (a: 1, b: 2))
+}
+
+--- spread-array-into-dict ---
+// Error: 3-11 cannot spread array into dictionary
+#(..(1, 2), a: 1)
+
+--- dict-at-lvalue ---
+// Test lvalue and rvalue access.
+#{
+ let dict = (a: 1, "b b": 1)
+ dict.at("b b") += 1
+ dict.state = (ok: true, err: false)
+ test(dict, (a: 1, "b b": 2, state: (ok: true, err: false)))
+ test(dict.state.ok, true)
+ dict.at("state").ok = false
+ test(dict.state.ok, false)
+ test(dict.state.err, false)
+}
+
+--- dict-at-missing-key ---
+// Test rvalue missing key.
+#{
+ let dict = (a: 1, b: 2)
+ // Error: 11-23 dictionary does not contain key "c" and no default value was specified
+ let x = dict.at("c")
+}
+
+--- dict-at-default ---
+// Test default value.
+#test((a: 1, b: 2).at("b", default: 3), 2)
+#test((a: 1, b: 2).at("c", default: 3), 3)
+
+--- dict-insert ---
+// Test insert.
+#{
+ let dict = (a: 1, b: 2)
+ dict.insert("b", 3)
+ test(dict, (a: 1, b: 3))
+ dict.insert("c", 5)
+ test(dict, (a: 1, b: 3, c: 5))
+}
+
+--- dict-remove-with-default ---
+// Test remove with default value.
+#{
+ let dict = (a: 1, b: 2)
+ test(dict.remove("b", default: 3), 2)
+}
+
+#{
+ let dict = (a: 1, b: 2)
+ test(dict.remove("c", default: 3), 3)
+}
+
+--- dict-missing-lvalue ---
+// Missing lvalue is not automatically none-initialized.
+#{
+ let dict = (:)
+ // Error: 3-9 dictionary does not contain key "b"
+ // Hint: 3-9 use `insert` to add or update values
+ dict.b += 1
+}
+
+--- dict-basic-methods ---
+// Test dictionary methods.
+#let dict = (a: 3, c: 2, b: 1)
+#test("c" in dict, true)
+#test(dict.len(), 3)
+#test(dict.values(), (3, 2, 1))
+#test(dict.pairs().map(p => p.first() + str(p.last())).join(), "a3c2b1")
+
+#dict.remove("c")
+#test("c" in dict, false)
+#test(dict, (a: 3, b: 1))
+
+--- dict-from-module ---
+// Test dictionary constructor
+#dictionary(sys).at("version")
+#dictionary(sys).at("no-crash", default: none)
+
+--- dict-remove-order ---
+// Test that removal keeps order.
+#let dict = (a: 1, b: 2, c: 3, d: 4)
+#dict.remove("b")
+#test(dict.keys(), ("a", "c", "d"))
+
+--- dict-temporary-lvalue ---
+// Error: 3-15 cannot mutate a temporary value
+#((key: "val").other = "some")
+
+--- dict-function-item-not-a-method ---
+#{
+ let dict = (
+ call-me: () => 1,
+ )
+ // Error: 8-15 type dictionary has no method `call-me`
+ // Hint: 8-15 to call the function stored in the dictionary, surround the field access with parentheses, e.g. `(dict.call-me)(..)`
+ dict.call-me()
+}
+
+--- dict-item-missing-method ---
+#{
+ let dict = (
+ nonfunc: 1
+ )
+
+ // Error: 8-15 type dictionary has no method `nonfunc`
+ // Hint: 8-15 did you mean to access the field `nonfunc`?
+ dict.nonfunc()
+}
+
+--- dict-dynamic-uplicate-key ---
+#let a = "hello"
+#let b = "world"
+#let c = "value"
+#let d = "conflict"
+
+#assert.eq(((a): b), ("hello": "world"))
+#assert.eq(((a): 1, (a): 2), ("hello": 2))
+#assert.eq((hello: 1, (a): 2), ("hello": 2))
+#assert.eq((a + b: c, (a + b): d, (a): "value2", a: "value3"), ("helloworld": "conflict", "hello": "value2", "a": "value3"))
+
+--- issue-1338-dictionary-underscore ---
+#let foo = "foo"
+#let bar = "bar"
+// Error: 8-9 expected expression, found underscore
+// Error: 16-17 expected expression, found underscore
+#(foo: _, bar: _)
+
+--- issue-1342-dictionary-bare-expressions ---
+// Error: 5-8 expected named or keyed pair, found identifier
+// Error: 10-13 expected named or keyed pair, found identifier
+#(: foo, bar)
+
+--- issue-3154-dict-at-not-contained ---
+#{
+ let dict = (a: 1)
+ // Error: 3-15 dictionary does not contain key "b" and no default value was specified
+ dict.at("b")
+}
+
+--- issue-3154-dict-at-missing-default ---
+#{
+ let dict = (a: 1)
+ test(dict.at("b", default: 0), 0)
+}
+
+--- issue-3154-dict-at-missing-mutable ---
+#{
+ let dict = (a: 1)
+ // Error: 3-15 dictionary does not contain key "b"
+ // Hint: 3-15 use `insert` to add or update values
+ dict.at("b") = 9
+}
+
+--- issue-3154-dict-at-missing-mutable-default ---
+#{
+ let dict = (a: 1)
+ // Error: 3-27 dictionary does not contain key "b"
+ // Hint: 3-27 use `insert` to add or update values
+ dict.at("b", default: 0) = 9
+}
+
+--- issue-3154-dict-syntax-missing ---
+#{
+ let dict = (a: 1)
+ // Error: 8-9 dictionary does not contain key "b"
+ dict.b
+}
+
+--- issue-3154-dict-syntax-missing-mutable ---
+#{
+ let dict = (a: 1)
+ dict.b = 9
+ test(dict, (a: 1, b: 9))
+}
+
+--- issue-3154-dict-syntax-missing-add-assign ---
+#{
+ let dict = (a: 1)
+ // Error: 3-9 dictionary does not contain key "b"
+ // Hint: 3-9 use `insert` to add or update values
+ dict.b += 9
+}
+
+--- issue-3232-dict-unexpected-keys-sides ---
+// Confusing "expected relative length or dictionary, found dictionary"
+// Error: 16-58 unexpected keys "unexpected" and "unexpected-too"
+#block(outset: (unexpected: 0.5em, unexpected-too: 0.2em), [Hi])
+
+--- issue-3232-dict-unexpected-keys-corners ---
+// Error: 14-56 unexpected keys "unexpected" and "unexpected-too"
+#box(radius: (unexpected: 0.5em, unexpected-too: 0.5em), [Hi])
+
+--- issue-3232-dict-unexpected-key-sides ---
+// Error: 16-49 unexpected key "unexpected", valid keys are "left", "top", "right", "bottom", "x", "y", and "rest"
+#block(outset: (unexpected: 0.2em, right: 0.5em), [Hi]) // The 1st key is unexpected
+
+--- issue-3232-dict-unexpected-key-corners ---
+// Error: 14-50 unexpected key "unexpected", valid keys are "top-left", "top-right", "bottom-right", "bottom-left", "left", "top", "right", "bottom", and "rest"
+#box(radius: (top-left: 0.5em, unexpected: 0.5em), [Hi]) // The 2nd key is unexpected
+
+--- issue-3232-dict-empty ---
+#block(outset: (:), [Hi]) // Ok
+#box(radius: (:), [Hi]) // Ok
diff --git a/tests/suite/foundations/duration.typ b/tests/suite/foundations/duration.typ
new file mode 100644
index 00000000..7e53f703
--- /dev/null
+++ b/tests/suite/foundations/duration.typ
@@ -0,0 +1,103 @@
+// Test durations.
+
+--- duration-negate ---
+// Test negating durations.
+#test(-duration(hours: 2), duration(hours: -2))
+
+--- duration-add-and-subtract ---
+// Test adding and subtracting durations.
+#test(duration(weeks: 1, hours: 1), duration(weeks: 1) + duration(hours: 1))
+#test(duration(weeks: 1, hours: -1), duration(weeks: 1) - duration(hours: 1))
+#test(duration(days: 6, hours: 23), duration(weeks: 1) - duration(hours: 1))
+
+--- duration-add-and-subtract-dates ---
+// Test adding and subtracting durations and dates.
+#let d = datetime(day: 1, month: 1, year: 2000)
+#let d2 = datetime(day: 1, month: 2, year: 2000)
+#test(d + duration(weeks: 2), datetime(day: 15, month: 1, year: 2000))
+#test(d + duration(days: 3), datetime(day: 4, month: 1, year: 2000))
+#test(d + duration(weeks: 1, days: 3), datetime(day: 11, month: 1, year: 2000))
+#test(d2 + duration(days: -1), datetime(day: 31, month: 1, year: 2000))
+#test(d2 + duration(days: -3), datetime(day: 29, month: 1, year: 2000))
+#test(d2 + duration(weeks: -1), datetime(day: 25, month: 1, year: 2000))
+#test(d + duration(days: -1), datetime(day: 31, month: 12, year: 1999))
+#test(d + duration(weeks: 1, days: -7), datetime(day: 1, month: 1, year: 2000))
+#test(d2 - duration(days: 1), datetime(day: 31, month: 1, year: 2000))
+#test(d2 - duration(days: 3), datetime(day: 29, month: 1, year: 2000))
+#test(d2 - duration(weeks: 1), datetime(day: 25, month: 1, year: 2000))
+#test(d - duration(days: 1), datetime(day: 31, month: 12, year: 1999))
+#test(datetime(day: 31, month: 1, year: 2000) + duration(days: 1), d2)
+#test(
+ datetime(day: 31, month: 12, year: 2000) + duration(days: 1),
+ datetime(day: 1, month: 1, year: 2001),
+)
+
+--- duration-add-and-subtract-times ---
+// Test adding and subtracting durations and times.
+#let a = datetime(hour: 12, minute: 0, second: 0)
+#test(a + duration(hours: 1, minutes: -60), datetime(hour: 12, minute: 0, second: 0))
+#test(a + duration(hours: 2), datetime(hour: 14, minute: 0, second: 0))
+#test(a + duration(minutes: 10), datetime(hour: 12, minute: 10, second: 0))
+#test(a + duration(seconds: 30), datetime(hour: 12, minute: 0, second: 30))
+#test(a + duration(hours: -2), datetime(hour: 10, minute: 0, second: 0))
+#test(a - duration(hours: 2), datetime(hour: 10, minute: 0, second: 0))
+#test(a + duration(minutes: -10), datetime(hour: 11, minute: 50, second: 0))
+#test(a - duration(minutes: 10), datetime(hour: 11, minute: 50, second: 0))
+#test(a + duration(seconds: -30), datetime(hour: 11, minute: 59, second: 30))
+#test(a - duration(seconds: 30), datetime(hour: 11, minute: 59, second: 30))
+#test(
+ a + duration(hours: 1, minutes: 13, seconds: 13),
+ datetime(hour: 13, minute: 13, second: 13),
+)
+
+--- duration-add-and-subtract-datetimes ---
+// Test adding and subtracting durations and datetimes.
+#test(
+ datetime(day: 1, month: 1, year: 2000, hour: 12, minute: 0, second: 0)
+ + duration(weeks: 1, days: 3, hours: -13, minutes: 10, seconds: -10 ),
+ datetime(day: 10, month: 1, year: 2000, hour: 23, minute: 9, second: 50),
+)
+#test(
+ datetime(day: 1, month: 1, year: 2000, hour: 12, minute: 0, second: 0)
+ + duration(weeks: 1, days: 3, minutes: 10)
+ - duration(hours: 13, seconds: 10),
+ datetime(day: 10, month: 1, year: 2000, hour: 23, minute: 9, second: 50),
+)
+
+--- duration-from-date-subtraction ---
+// Test subtracting dates.
+#let a = datetime(hour: 12, minute: 0, second: 0)
+#let b = datetime(day: 1, month: 1, year: 2000)
+#test(datetime(hour: 14, minute: 0, second: 0) - a, duration(hours: 2))
+#test(datetime(hour: 14, minute: 0, second: 0) - a, duration(minutes: 120))
+#test(datetime(hour: 13, minute: 0, second: 0) - a, duration(seconds: 3600))
+#test(datetime(day: 1, month: 2, year: 2000) - b, duration(days: 31))
+#test(datetime(day: 15, month: 1, year: 2000) - b, duration(weeks: 2))
+
+--- duration-multiply-with-number ---
+// Test multiplying and dividing durations with numbers.
+#test(duration(minutes: 10) * 6, duration(hours: 1))
+#test(duration(minutes: 10) * 2, duration(minutes: 20))
+#test(duration(minutes: 10) * 2.5, duration(minutes: 25))
+#test(duration(minutes: 10) / 2, duration(minutes: 5))
+#test(duration(minutes: 10) / 2.5, duration(minutes: 4))
+
+--- duration-divide ---
+// Test dividing durations with durations
+#test(duration(minutes: 20) / duration(hours: 1), 1 / 3)
+#test(duration(minutes: 20) / duration(minutes: 10), 2)
+#test(duration(minutes: 20) / duration(minutes: 8), 2.5)
+
+--- duration-compare ---
+// Test comparing durations
+#test(duration(minutes: 20) > duration(minutes: 10), true)
+#test(duration(minutes: 20) >= duration(minutes: 10), true)
+#test(duration(minutes: 10) < duration(minutes: 20), true)
+#test(duration(minutes: 10) <= duration(minutes: 20), true)
+#test(duration(minutes: 10) == duration(minutes: 10), true)
+#test(duration(minutes: 10) != duration(minutes: 20), true)
+#test(duration(minutes: 10) <= duration(minutes: 10), true)
+#test(duration(minutes: 10) >= duration(minutes: 10), true)
+#test(duration(minutes: 20) < duration(minutes: 10), false)
+#test(duration(minutes: 20) <= duration(minutes: 10), false)
+#test(duration(minutes: 20) == duration(minutes: 10), false)
diff --git a/tests/suite/foundations/eval.typ b/tests/suite/foundations/eval.typ
new file mode 100644
index 00000000..f85146b2
--- /dev/null
+++ b/tests/suite/foundations/eval.typ
@@ -0,0 +1,54 @@
+--- eval ---
+// Test the eval function.
+#test(eval("1 + 2"), 3)
+#test(eval("1 + x", scope: (x: 3)), 4)
+#test(eval("let x = x + 1; x + 1", scope: (x: 1)), 3)
+
+--- eval-mode ---
+// Test evaluation in other modes.
+#eval("[_Hello" + " World!_]") \
+#eval("_Hello" + " World!_", mode: "markup") \
+#eval("RR_1^NN", mode: "math", scope: (RR: math.NN, NN: math.RR))
+
+--- eval-syntax-error-1 ---
+// Error: 7-12 expected pattern
+#eval("let")
+
+--- eval-in-show-rule ---
+#show raw: it => text(font: "PT Sans", eval("[" + it.text + "]"))
+
+Interacting
+```
+#set text(blue)
+Blue #move(dy: -0.15em)[🌊]
+```
+
+--- eval-runtime-error ---
+// Error: 7-17 cannot continue outside of loop
+#eval("continue")
+
+--- eval-syntax-error-2 ---
+// Error: 7-12 expected semicolon or line break
+#eval("1 2")
+
+--- eval-path-resolve ---
+// Test absolute path.
+#eval("image(\"/assets/images/tiger.jpg\", width: 50%)")
+
+--- eval-path-resolve-in-show-rule ---
+#show raw: it => eval(it.text, mode: "markup")
+
+```
+#show emph: image("/assets/images/tiger.jpg", width: 50%)
+_Tiger!_
+```
+
+--- eval-path-resolve-relative ---
+// Test relative path.
+#test(eval(`"HELLO" in read("./eval.typ")`.text), true)
+
+--- issue-2055-math-eval ---
+// Evaluating a math expr should renders the same as an equation
+#eval(mode: "math", "f(a) = cases(a + b\, space space x >= 3,a + b\, space space x = 5)")
+
+$f(a) = cases(a + b\, space space x >= 3,a + b\, space space x = 5)$
diff --git a/tests/suite/foundations/float.typ b/tests/suite/foundations/float.typ
new file mode 100644
index 00000000..770533b9
--- /dev/null
+++ b/tests/suite/foundations/float.typ
@@ -0,0 +1,66 @@
+--- float-constructor ---
+#test(float(10), 10.0)
+#test(float(50% * 30%), 0.15)
+#test(float("31.4e-1"), 3.14)
+#test(float("31.4e\u{2212}1"), 3.14)
+#test(float("3.1415"), 3.1415)
+#test(float("-7654.321"), -7654.321)
+#test(float("\u{2212}7654.321"), -7654.321)
+#test(type(float(10)), float)
+
+--- float-constructor-bad-type ---
+// Error: 8-13 expected float, boolean, integer, ratio, or string, found type
+#float(float)
+
+--- float-constructor-bad-value ---
+// Error: 8-15 invalid float: 1.2.3
+#float("1.2.3")
+
+--- float-is-nan ---
+// Test float `is-nan()`.
+#test(float(calc.nan).is-nan(), true)
+#test(float(10).is-nan(), false)
+
+--- float-is-infinite ---
+// Test float `is-infinite()`.
+#test(float(calc.inf).is-infinite(), true)
+#test(float(-calc.inf).is-infinite(), true)
+#test(float(10).is-infinite(), false)
+#test(float(-10).is-infinite(), false)
+
+--- float-signum ---
+// Test float `signum()`
+#test(float(0.0).signum(), 1.0)
+#test(float(1.0).signum(), 1.0)
+#test(float(-1.0).signum(), -1.0)
+#test(float(10.0).signum(), 1.0)
+#test(float(-10.0).signum(), -1.0)
+#test(float(calc.nan).signum().is-nan(), true)
+
+--- float-repr ---
+// Test the `repr` function with floats.
+#repr(12.0) \
+#repr(3.14) \
+#repr(1234567890.0) \
+#repr(0123456789.0) \
+#repr(0.0) \
+#repr(-0.0) \
+#repr(-1.0) \
+#repr(-9876543210.0) \
+#repr(-0987654321.0) \
+#repr(-3.14) \
+#repr(4.0 - 8.0)
+
+--- float-display ---
+// Test floats.
+#12.0 \
+#3.14 \
+#1234567890.0 \
+#0123456789.0 \
+#0.0 \
+#(-0.0) \
+#(-1.0) \
+#(-9876543210.0) \
+#(-0987654321.0) \
+#(-3.14) \
+#(4.0 - 8.0)
diff --git a/tests/suite/foundations/int.typ b/tests/suite/foundations/int.typ
new file mode 100644
index 00000000..0c85dcab
--- /dev/null
+++ b/tests/suite/foundations/int.typ
@@ -0,0 +1,73 @@
+--- int-base-alternative ---
+// Test numbers with alternative bases.
+#test(0x10, 16)
+#test(0b1101, 13)
+#test(0xA + 0xa, 0x14)
+
+--- int-base-binary-invalid ---
+// Error: 2-7 invalid binary number: 0b123
+#0b123
+
+--- int-base-hex-invalid ---
+// Error: 2-8 invalid hexadecimal number: 0x123z
+#0x123z
+
+--- int-constructor ---
+// Test conversion to numbers.
+#test(int(false), 0)
+#test(int(true), 1)
+#test(int(10), 10)
+#test(int("150"), 150)
+#test(int("-834"), -834)
+#test(int("\u{2212}79"), -79)
+#test(int(10 / 3), 3)
+
+--- int-constructor-bad-type ---
+// Error: 6-10 expected integer, boolean, float, or string, found length
+#int(10pt)
+
+--- int-constructor-bad-value ---
+// Error: 6-12 invalid integer: nope
+#int("nope")
+
+--- int-signum ---
+// Test int `signum()`
+#test(int(0).signum(), 0)
+#test(int(1.0).signum(), 1)
+#test(int(-1.0).signum(), -1)
+#test(int(10.0).signum(), 1)
+#test(int(-10.0).signum(), -1)
+
+--- int-repr ---
+// Test the `repr` function with integers.
+#repr(12) \
+#repr(1234567890) \
+#repr(0123456789) \
+#repr(0) \
+#repr(-0) \
+#repr(-1) \
+#repr(-9876543210) \
+#repr(-0987654321) \
+#repr(4 - 8)
+
+--- int-display ---
+// Test integers.
+#12 \
+#1234567890 \
+#0123456789 \
+#0 \
+#(-0) \
+#(-1) \
+#(-9876543210) \
+#(-0987654321) \
+#(4 - 8)
+
+--- issue-int-constructor ---
+// Test that integer -> integer conversion doesn't do a roundtrip through float.
+#let x = 9223372036854775800
+#test(type(x), int)
+#test(int(x), x)
+
+--- number-invalid-suffix ---
+// Error: 2-4 invalid number suffix: u
+#1u
diff --git a/tests/suite/foundations/label.typ b/tests/suite/foundations/label.typ
new file mode 100644
index 00000000..2cde102c
--- /dev/null
+++ b/tests/suite/foundations/label.typ
@@ -0,0 +1,70 @@
+// Test labels.
+
+--- label-show-where-selector ---
+// Test labelled headings.
+#show heading: set text(10pt)
+#show heading.where(label: <intro>): underline
+
+= Introduction <intro>
+The beginning.
+
+= Conclusion
+The end.
+
+--- label-after-expression ---
+// Test label after expression.
+#show strong.where(label: <v>): set text(red)
+
+#let a = [*A*]
+#let b = [*B*]
+#a <v> #b
+
+--- label-on-text ---
+// Test labelled text.
+#show "t": it => {
+ set text(blue) if it.has("label") and it.label == <last>
+ it
+}
+
+This is a thing #[that <last>] happened.
+
+--- label-dynamic-show-set ---
+// Test abusing dynamic labels for styling.
+#show <red>: set text(red)
+#show <blue>: set text(blue)
+
+*A* *B* <red> *C* #label("bl" + "ue") *D*
+
+--- label-after-parbreak ---
+// Test that label ignores parbreak.
+#show <hide>: none
+
+_Hidden_
+<hide>
+
+_Hidden_
+
+<hide>
+_Visible_
+
+--- label-in-block ---
+// Test that label only works within one content block.
+#show <strike>: strike
+*This is* #[<strike>] *protected.*
+*This is not.* <strike>
+
+--- label-unclosed-is-text ---
+// Test that incomplete label is text.
+1 < 2 is #if 1 < 2 [not] a label.
+
+--- label-text-styled-and-sequence ---
+// Test label on text, styled, and sequence.
+#test([Hello<hi>].label, <hi>)
+#test([#[A *B* C]<hi>].label, <hi>)
+#test([#text(red)[Hello]<hi>].label, <hi>)
+
+--- label-string-conversion ---
+// Test getting the name of a label.
+#test(str(<hey>), "hey")
+#test(str(label("hey")), "hey")
+#test(str([Hmm<hey>].label), "hey")
diff --git a/tests/suite/foundations/panic.typ b/tests/suite/foundations/panic.typ
new file mode 100644
index 00000000..5d9d4046
--- /dev/null
+++ b/tests/suite/foundations/panic.typ
@@ -0,0 +1,14 @@
+--- panic ---
+// Test panic.
+// Error: 2-9 panicked
+#panic()
+
+--- panic-with-int ---
+// Test panic.
+// Error: 2-12 panicked with: 123
+#panic(123)
+
+--- panic-with-str ---
+// Test panic.
+// Error: 2-24 panicked with: "this is wrong"
+#panic("this is wrong")
diff --git a/tests/suite/foundations/plugin.typ b/tests/suite/foundations/plugin.typ
new file mode 100644
index 00000000..0842980e
--- /dev/null
+++ b/tests/suite/foundations/plugin.typ
@@ -0,0 +1,47 @@
+// Test WebAssembly plugins.
+
+--- plugin-basic ---
+#let p = plugin("/assets/plugins/hello.wasm")
+#test(p.hello(), bytes("Hello from wasm!!!"))
+#test(p.double_it(bytes("hey!")), bytes("hey!.hey!"))
+#test(
+ p.shuffle(bytes("value1"), bytes("value2"), bytes("value3")),
+ bytes("value3-value1-value2"),
+)
+
+--- plugin-wrong-number-of-arguments ---
+#let p = plugin("/assets/plugins/hello.wasm")
+
+// Error: 2-20 plugin function takes 0 arguments, but 1 was given
+#p.hello(bytes(""))
+
+--- plugin-wrong-argument-type ---
+#let p = plugin("/assets/plugins/hello.wasm")
+
+// Error: 10-14 expected bytes, found boolean
+// Error: 27-29 expected bytes, found integer
+#p.hello(true, bytes(()), 10)
+
+--- plugin-error ---
+#let p = plugin("/assets/plugins/hello.wasm")
+
+// Error: 2-17 plugin errored with: This is an `Err`
+#p.returns_err()
+
+--- plugin-panic ---
+#let p = plugin("/assets/plugins/hello.wasm")
+
+// Error: 2-16 plugin panicked: wasm `unreachable` instruction executed
+#p.will_panic()
+
+--- plugin-out-of-bounds-read ---
+#let p = plugin("/assets/plugins/plugin-oob.wasm")
+
+// Error: 2-14 plugin tried to read out of bounds: pointer 0x40000000 is out of bounds for read of length 1
+#p.read_oob()
+
+--- plugin-out-of-bounds-write ---
+#let p = plugin("/assets/plugins/plugin-oob.wasm")
+
+// Error: 2-27 plugin tried to write out of bounds: pointer 0x40000000 is out of bounds for write of length 3
+#p.write_oob(bytes("xyz"))
diff --git a/tests/suite/foundations/repr.typ b/tests/suite/foundations/repr.typ
new file mode 100644
index 00000000..7f03209b
--- /dev/null
+++ b/tests/suite/foundations/repr.typ
@@ -0,0 +1,57 @@
+--- repr ---
+#test(repr(ltr), "ltr")
+#test(repr((1, 2, false, )), "(1, 2, false)")
+
+--- repr-literals ---
+// Literal values.
+#auto \
+#none (empty) \
+#true \
+#false
+
+--- repr-numerical ---
+// Numerical values.
+#1 \
+#1.0e-4 \
+#3.15 \
+#1e-10 \
+#50.368% \
+#0.0000012345pt \
+#4.5cm \
+#12e1pt \
+#2.5rad \
+#45deg \
+#1.7em \
+#(1cm + 0em) \
+#(2em + 10pt) \
+#(100% + (2em + 2pt)) \
+#(100% + 0pt) \
+#(100% - 2em + 2pt) \
+#(100% - 2pt) \
+#2.3fr
+
+--- repr-misc ---
+// Colors and strokes.
+#set text(0.8em)
+#rgb("f7a205") \
+#(2pt + rgb("f7a205"))
+
+// Strings and escaping.
+#raw(repr("hi"), lang: "typc")
+#repr("a\n[]\"\u{1F680}string")
+
+// Content.
+#raw(lang: "typc", repr[*Hey*]) \
+#raw(lang: "typc", repr[A _sequence_]) \
+#raw(lang: "typc", repr[A _longer_ *sequence*!])
+
+// Functions.
+#let f(x) = x
+#f \
+#rect \
+#(() => none)
+
+// Types.
+#int \
+#type("hi") \
+#type((a: 1))
diff --git a/tests/suite/foundations/str.typ b/tests/suite/foundations/str.typ
new file mode 100644
index 00000000..025ec53d
--- /dev/null
+++ b/tests/suite/foundations/str.typ
@@ -0,0 +1,315 @@
+// Test the string methods.
+
+--- str-constructor ---
+// Test conversion to string.
+#test(str(123), "123")
+#test(str(123, base: 3), "11120")
+#test(str(-123, base: 16), "−7b")
+#test(str(9223372036854775807, base: 36), "1y2p0ij32e8e7")
+#test(str(50.14), "50.14")
+#test(str(10 / 3).len() > 10, true)
+
+--- str-from-float ---
+// Test the `str` function with floats.
+#test(str(12.0), "12")
+#test(str(3.14), "3.14")
+#test(str(1234567890.0), "1234567890")
+#test(str(0123456789.0), "123456789")
+#test(str(0.0), "0")
+#test(str(-0.0), "0")
+#test(str(-1.0), "−1")
+#test(str(-9876543210.0), "−9876543210")
+#test(str(-0987654321.0), "−987654321")
+#test(str(-3.14), "−3.14")
+#test(str(4.0 - 8.0), "−4")
+
+--- str-from-int ---
+// Test the `str` function with integers.
+#test(str(12), "12")
+#test(str(1234567890), "1234567890")
+#test(str(0123456789), "123456789")
+#test(str(0), "0")
+#test(str(-0), "0")
+#test(str(-1), "−1")
+#test(str(-9876543210), "−9876543210")
+#test(str(-0987654321), "−987654321")
+#test(str(4 - 8), "−4")
+
+--- str-constructor-bad-type ---
+// Error: 6-8 expected integer, float, version, bytes, label, type, or string, found content
+#str([])
+
+--- str-constructor-bad-base ---
+// Error: 17-19 base must be between 2 and 36
+#str(123, base: 99)
+
+--- str-constructor-unsupported-base ---
+// Error: 18-19 base is only supported for integers
+#str(1.23, base: 2)
+
+--- str-from-and-to-unicode ---
+// Test the unicode function.
+#test(str.from-unicode(97), "a")
+#test(str.to-unicode("a"), 97)
+
+--- str-from-unicode-bad-type ---
+// Error: 19-22 expected integer, found content
+#str.from-unicode([a])
+
+--- str-to-unicode-bad-type ---
+// Error: 17-21 expected exactly one character
+#str.to-unicode("ab")
+
+--- str-from-unicode-negative ---
+// Error: 19-21 number must be at least zero
+#str.from-unicode(-1)
+
+--- str-from-unicode-bad-value ---
+// Error: 2-28 0x110000 is not a valid codepoint
+#str.from-unicode(0x110000) // 0x10ffff is the highest valid code point
+
+--- string-len ---
+// Test the `len` method.
+#test("Hello World!".len(), 12)
+
+--- string-first-and-last ---
+// Test the `first` and `last` methods.
+#test("Hello".first(), "H")
+#test("Hello".last(), "o")
+#test("🏳️‍🌈A🏳️‍⚧️".first(), "🏳️‍🌈")
+#test("🏳️‍🌈A🏳️‍⚧️".last(), "🏳️‍⚧️")
+
+--- string-first-empty ---
+// Error: 2-12 string is empty
+#"".first()
+
+--- string-last-empty ---
+// Error: 2-11 string is empty
+#"".last()
+
+--- string-at ---
+// Test the `at` method.
+#test("Hello".at(1), "e")
+#test("Hello".at(4), "o")
+#test("Hello".at(-1), "o")
+#test("Hello".at(-2), "l")
+#test("Hey: 🏳️‍🌈 there!".at(5), "🏳️‍🌈")
+
+--- string-at-default ---
+// Test `at`'s 'default' parameter.
+#test("z", "Hello".at(5, default: "z"))
+
+--- string-at-not-a-char-boundary ---
+// Error: 2-14 string index 2 is not a character boundary
+#"🏳️‍🌈".at(2)
+
+--- string-at-out-of-bounds ---
+// Error: 2-15 no default value was specified and string index out of bounds (index: 5, len: 5)
+#"Hello".at(5)
+
+--- string-at-at-default-other-type ---
+#test("Hello".at(5, default: (a: 10)), (a: 10))
+
+--- string-slice ---
+// Test the `slice` method.
+#test("abc".slice(1, 2), "b")
+#test("abc🏡def".slice(2, 7), "c🏡")
+#test("abc🏡def".slice(2, -2), "c🏡d")
+#test("abc🏡def".slice(-3, -1), "de")
+
+--- string-slice-not-a-char-boundary ---
+// Error: 2-21 string index -1 is not a character boundary
+#"🏳️‍🌈".slice(0, -1)
+
+--- string-clusters ---
+// Test the `clusters` and `codepoints` methods.
+#test("abc".clusters(), ("a", "b", "c"))
+#test("abc".clusters(), ("a", "b", "c"))
+#test("🏳️‍🌈!".clusters(), ("🏳️‍🌈", "!"))
+
+--- string-codepoints ---
+#test("🏳️‍🌈!".codepoints(), ("🏳", "\u{fe0f}", "\u{200d}", "🌈", "!"))
+
+--- string-contains ---
+// Test the `contains` method.
+#test("abc".contains("b"), true)
+#test("b" in "abc", true)
+#test("1234f".contains(regex("\d")), true)
+#test(regex("\d") in "1234f", true)
+#test("abc".contains("d"), false)
+#test("1234g" in "1234f", false)
+#test("abc".contains(regex("^[abc]$")), false)
+#test("abc".contains(regex("^[abc]+$")), true)
+
+--- string-starts-with ---
+// Test the `starts-with` and `ends-with` methods.
+#test("Typst".starts-with("Ty"), true)
+#test("Typst".starts-with(regex("[Tt]ys")), false)
+#test("Typst".starts-with("st"), false)
+
+--- string-ends-with ---
+#test("Typst".ends-with("st"), true)
+#test("Typst".ends-with(regex("\d*")), true)
+#test("Typst".ends-with(regex("\d+")), false)
+#test("Typ12".ends-with(regex("\d+")), true)
+#test("typst13".ends-with(regex("1[0-9]")), true)
+#test("typst113".ends-with(regex("1[0-9]")), true)
+#test("typst23".ends-with(regex("1[0-9]")), false)
+
+--- string-find-and-position ---
+// Test the `find` and `position` methods.
+#let date = regex("\d{2}:\d{2}")
+#test("Hello World".find("World"), "World")
+#test("Hello World".position("World"), 6)
+#test("It's 12:13 now".find(date), "12:13")
+#test("It's 12:13 now".position(date), 5)
+
+--- string-match ---
+// Test the `match` method.
+#test("Is there a".match("for this?"), none)
+#test(
+ "The time of my life.".match(regex("[mit]+e")),
+ (start: 4, end: 8, text: "time", captures: ()),
+)
+
+--- string-matches ---
+// Test the `matches` method.
+#test("Hello there".matches("\d"), ())
+#test("Day by Day.".matches("Day"), (
+ (start: 0, end: 3, text: "Day", captures: ()),
+ (start: 7, end: 10, text: "Day", captures: ()),
+))
+
+// Compute the sum of all timestamps in the text.
+#let timesum(text) = {
+ let time = 0
+ for match in text.matches(regex("(\d+):(\d+)")) {
+ let caps = match.captures
+ time += 60 * int(caps.at(0)) + int(caps.at(1))
+ }
+ str(int(time / 60)) + ":" + str(calc.rem(time, 60))
+}
+
+#test(timesum(""), "0:0")
+#test(timesum("2:70"), "3:10")
+#test(timesum("1:20, 2:10, 0:40"), "4:10")
+
+--- stgring-replace ---
+// Test the `replace` method with `Str` replacements.
+#test("ABC".replace("", "-"), "-A-B-C-")
+#test("Ok".replace("Ok", "Nope", count: 0), "Ok")
+#test("to add?".replace("", "How ", count: 1), "How to add?")
+#test("AB C DEF GH J".replace(" ", ",", count: 2), "AB,C,DEF GH J")
+#test("Walcemo"
+ .replace("o", "k")
+ .replace("e", "o")
+ .replace("k", "e")
+ .replace("a", "e"),
+ "Welcome"
+)
+#test("123".replace(regex("\d$"), "_"), "12_")
+#test("123".replace(regex("\d{1,2}$"), "__"), "1__")
+
+--- string-replace-function ---
+// Test the `replace` method with `Func` replacements.
+
+#test("abc".replace(regex("[a-z]"), m => {
+ str(m.start) + m.text + str(m.end)
+}), "0a11b22c3")
+#test("abcd, efgh".replace(regex("\w+"), m => {
+ upper(m.text)
+}), "ABCD, EFGH")
+#test("hello : world".replace(regex("^(.+)\s*(:)\s*(.+)$"), m => {
+ upper(m.captures.at(0)) + m.captures.at(1) + " " + upper(m.captures.at(2))
+}), "HELLO : WORLD")
+#test("hello world, lorem ipsum".replace(regex("(\w+) (\w+)"), m => {
+ m.captures.at(1) + " " + m.captures.at(0)
+}), "world hello, ipsum lorem")
+#test("hello world, lorem ipsum".replace(regex("(\w+) (\w+)"), count: 1, m => {
+ m.captures.at(1) + " " + m.captures.at(0)
+}), "world hello, lorem ipsum")
+#test("123 456".replace(regex("[a-z]+"), "a"), "123 456")
+
+#test("abc".replace("", m => "-"), "-a-b-c-")
+#test("abc".replace("", m => "-", count: 1), "-abc")
+#test("123".replace("abc", m => ""), "123")
+#test("123".replace("abc", m => "", count: 2), "123")
+#test("a123b123c".replace("123", m => {
+ str(m.start) + "-" + str(m.end)
+}), "a1-4b5-8c")
+#test("halla warld".replace("a", m => {
+ if m.start == 1 { "e" }
+ else if m.start == 4 or m.start == 7 { "o" }
+}), "hello world")
+#test("aaa".replace("a", m => str(m.captures.len())), "000")
+
+--- string-replace-function-bad-type ---
+// Error: 23-24 expected string, found integer
+#"123".replace("123", m => 1)
+
+--- string-replace-bad-type ---
+// Error: 23-32 expected string or function, found array
+#"123".replace("123", (1, 2, 3))
+
+--- string-trim-basic ---
+// Test the `trim` method; the pattern is not provided.
+#let str = "Typst, LaTeX, Word, InDesign"
+#let array = ("Typst", "LaTeX", "Word", "InDesign")
+#test(str.split(",").map(s => s.trim()), array)
+#test("".trim(), "")
+#test(" ".trim(), "")
+#test("\t".trim(), "")
+#test("\n".trim(), "")
+#test("\t \n".trim(), "")
+#test(" abc ".trim(at: start), "abc ")
+#test("\tabc ".trim(at: start), "abc ")
+#test("abc\n".trim(at: end), "abc")
+#test(" abc ".trim(at: end, repeat: true), " abc")
+#test(" abc".trim(at: start, repeat: false), "abc")
+
+--- string-trim-pattern-str ---
+// Test the `trim` method; the pattern is a string.
+#test("aabcaa".trim("a", repeat: false), "abca")
+#test("aabca".trim("a", at: start), "bca")
+#test("aabcaa".trim("a", at: end, repeat: false), "aabca")
+#test(" abc\n".trim("\n"), " abc")
+#test("whole".trim("whole", at: start), "")
+
+--- string-trim-pattern-regex ---
+// Test the `trim` method; the pattern is a regex.
+#test("".trim(regex(".")), "")
+#test("123abc456".trim(regex("\d")), "abc")
+#test("123abc456".trim(regex("\d"), repeat: false), "23abc45")
+#test("123a4b5c678".trim(regex("\d"), repeat: true), "a4b5c")
+#test("123a4b5c678".trim(regex("\d"), repeat: false), "23a4b5c67")
+#test("123abc456".trim(regex("\d"), at: start), "abc456")
+#test("123abc456".trim(regex("\d"), at: end), "123abc")
+#test("123abc456".trim(regex("\d+"), at: end, repeat: false), "123abc")
+#test("123abc456".trim(regex("\d{1,2}$"), repeat: false), "123abc4")
+#test("hello world".trim(regex(".")), "")
+#test("12306".trim(regex("\d"), at: start), "")
+#test("12306abc".trim(regex("\d"), at: start), "abc")
+#test("whole".trim(regex("whole"), at: start), "")
+#test("12306".trim(regex("\d"), at: end), "")
+#test("abc12306".trim(regex("\d"), at: end), "abc")
+#test("whole".trim(regex("whole"), at: end), "")
+
+--- string-trim-at-bad-alignment ---
+// Error: 17-21 expected either `start` or `end`
+#"abc".trim(at: left)
+
+--- string-split ---
+// Test the `split` method.
+#test("abc".split(""), ("", "a", "b", "c", ""))
+#test("abc".split("b"), ("a", "c"))
+#test("a123c".split(regex("\d")), ("a", "", "", "c"))
+#test("a123c".split(regex("\d+")), ("a", "c"))
+
+--- string-rev ---
+// Test the `rev` method.
+#test("abc".rev(), "cba")
+#test("ax̂e".rev(), "ex̂a")
+
+--- string-unclosed ---
+// Error: 2-2:1 unclosed string
+#"hello\"
diff --git a/tests/suite/foundations/type.typ b/tests/suite/foundations/type.typ
new file mode 100644
index 00000000..f2a98845
--- /dev/null
+++ b/tests/suite/foundations/type.typ
@@ -0,0 +1,25 @@
+--- type ---
+#test(type(1), int)
+#test(type(ltr), direction)
+#test(type(10 / 3), float)
+
+--- type-string-compatibility ---
+#test(type(10), int)
+#test(type(10), "integer")
+#test("is " + type(10), "is integer")
+#test(int in ("integer", "string"), true)
+#test(int in "integers or strings", true)
+#test(str in "integers or strings", true)
+
+--- issue-3110-type-constructor ---
+// Let the error message report the type name.
+// Error: 2-9 type content does not have a constructor
+#content()
+
+--- issue-3110-associated-field ---
+// Error: 6-12 type integer does not contain field `MAXVAL`
+#int.MAXVAL
+
+--- issue-3110-associated-function ---
+// Error: 6-18 type string does not contain field `from-unïcode`
+#str.from-unïcode(97)
diff --git a/tests/suite/foundations/version.typ b/tests/suite/foundations/version.typ
new file mode 100644
index 00000000..bf2cadb1
--- /dev/null
+++ b/tests/suite/foundations/version.typ
@@ -0,0 +1,47 @@
+// Test versions.
+
+--- version-constructor ---
+// Test version constructor.
+
+// Empty.
+#version()
+
+// Plain.
+#test(version(1, 2).major, 1)
+
+// Single Array argument.
+#test(version((1, 2)).minor, 2)
+
+// Mixed arguments.
+#test(version(1, (2, 3), 4, (5, 6), 7).at(5), 6)
+
+--- version-equality ---
+// Test equality of different-length versions
+#test(version(), version(0))
+#test(version(0), version(0, 0))
+#test(version(1, 2), version(1, 2, 0, 0, 0, 0))
+
+--- version-at ---
+// Test `version.at`.
+
+// Non-negative index in bounds
+#test(version(1, 2).at(1), 2)
+
+// Non-negative index out of bounds
+#test(version(1, 2).at(4), 0)
+
+// Negative index in bounds
+#test(version(1, 2).at(-2), 1)
+
+// Error: 2-22 component index out of bounds (index: -3, len: 2)
+#version(1, 2).at(-3)
+
+--- version-fields ---
+// Test version fields.
+#test(version(1, 2, 3).major, 1)
+#test(version(1, 2, 3).minor, 2)
+#test(version(1, 2, 3).patch, 3)
+
+--- version-type ---
+// Test the type of `sys.version`
+#test(type(sys.version), version)
diff --git a/tests/suite/introspection/counter.typ b/tests/suite/introspection/counter.typ
new file mode 100644
index 00000000..8a5315f9
--- /dev/null
+++ b/tests/suite/introspection/counter.typ
@@ -0,0 +1,78 @@
+// Test counters.
+
+--- counter-basic-1 ---
+// Count with string key.
+#let mine = counter("mine!")
+
+Final: #context mine.final().at(0) \
+#mine.step()
+First: #context mine.display() \
+#mine.update(7)
+#context mine.display("1 of 1", both: true) \
+#mine.step()
+#mine.step()
+Second: #context mine.display("I")
+#mine.update(n => n * 2)
+#mine.step()
+
+--- counter-basic-2 ---
+// Test `counter`.
+#let c = counter("heading")
+#c.update(2)
+#c.update(n => n + 2)
+#context test(c.get(), (4,))
+#c.update(n => n - 3)
+#context test(c.at(here()), (1,))
+
+--- counter-label ---
+// Count labels.
+#let label = <heya>
+#let count = context counter(label).display()
+#let elem(it) = [#box(it) #label]
+
+#elem[hey, there!] #count \
+#elem[more here!] #count
+
+--- counter-heading ---
+// Count headings.
+#set heading(numbering: "1.a.")
+#show heading: set text(10pt)
+#counter(heading).step()
+
+= Alpha
+In #context counter(heading).display()
+== Beta
+
+#set heading(numbering: none)
+= Gamma
+#heading(numbering: "I.")[Delta]
+
+At Beta, it was #context {
+ let it = query(heading).find(it => it.body == [Beta])
+ numbering(it.numbering, ..counter(heading).at(it.location()))
+}
+
+--- counter-page ---
+#set page(height: 50pt, margin: (bottom: 20pt, rest: 10pt))
+#lorem(12)
+#set page(numbering: "(i)")
+#lorem(6)
+#pagebreak()
+#set page(numbering: "1 / 1")
+#counter(page).update(1)
+#lorem(20)
+
+--- counter-figure ---
+// Count figures.
+#figure(numbering: "A", caption: [Four 'A's], kind: image, supplement: "Figure")[_AAAA!_]
+#figure(numbering: none, caption: [Four 'B's], kind: image, supplement: "Figure")[_BBBB!_]
+#figure(caption: [Four 'C's], kind: image, supplement: "Figure")[_CCCC!_]
+#counter(figure.where(kind: image)).update(n => n + 3)
+#figure(caption: [Four 'D's], kind: image, supplement: "Figure")[_DDDD!_]
+
+--- counter-at-no-context ---
+// Test `counter.at` outside of context.
+// Error: 2-28 can only be used when context is known
+// Hint: 2-28 try wrapping this in a `context` expression
+// Hint: 2-28 the `context` expression should wrap everything that depends on this function
+#counter("key").at(<label>)
diff --git a/tests/suite/introspection/here.typ b/tests/suite/introspection/here.typ
new file mode 100644
index 00000000..18fff439
--- /dev/null
+++ b/tests/suite/introspection/here.typ
@@ -0,0 +1,3 @@
+--- here-position ---
+// Test `context` + `here`.
+#context test(here().position().y, 10pt)
diff --git a/tests/suite/introspection/locate.typ b/tests/suite/introspection/locate.typ
new file mode 100644
index 00000000..981f8c46
--- /dev/null
+++ b/tests/suite/introspection/locate.typ
@@ -0,0 +1,32 @@
+--- locate-position ---
+// Test `locate`.
+#v(10pt)
+= Introduction <intro>
+#context test(locate(<intro>).position().y, 20pt)
+
+--- locate-missing-label ---
+// Error: 10-25 label `<intro>` does not exist in the document
+#context locate(<intro>)
+
+--- locate-duplicate-label ---
+= Introduction <intro>
+= Introduction <intro>
+
+// Error: 10-25 label `<intro>` occurs multiple times in the document
+#context locate(<intro>)
+
+--- locate-element-selector ---
+#v(10pt)
+= Introduction <intro>
+#context test(locate(heading).position().y, 20pt)
+
+--- locate-element-selector-no-match ---
+// Error: 10-25 selector does not match any element
+#context locate(heading)
+
+--- locate-element-selector-multiple-matches ---
+= Introduction <intro>
+= Introduction <intro>
+
+// Error: 10-25 selector matches multiple elements
+#context locate(heading)
diff --git a/tests/suite/introspection/query.typ b/tests/suite/introspection/query.typ
new file mode 100644
index 00000000..3a4b4fbf
--- /dev/null
+++ b/tests/suite/introspection/query.typ
@@ -0,0 +1,267 @@
+// Test creating a header with the query function.
+
+--- query-here ---
+// Test that `here()` yields the context element's location.
+#context test(query(here()).first().func(), (context none).func())
+
+--- query-running-header ---
+#set page(
+ paper: "a8",
+ margin: (y: 1cm, x: 0.5cm),
+ header: context {
+ smallcaps[Typst Academy]
+ h(1fr)
+ let after = query(selector(heading).after(here()))
+ let before = query(selector(heading).before(here()))
+ let elem = if before.len() != 0 {
+ before.last()
+ } else if after.len() != 0 {
+ after.first()
+ }
+ emph(elem.body)
+ }
+)
+
+#outline()
+
+= Introduction
+#v(1cm)
+
+= Background
+#v(2cm)
+
+= Approach
+
+--- query-list-of-figures ---
+#set page(
+ paper: "a8",
+ numbering: "1 / 1",
+ margin: (bottom: 1cm, rest: 0.5cm),
+)
+
+#set figure(numbering: "I")
+#show figure: set image(width: 80%)
+
+= List of Figures
+#context {
+ let elements = query(selector(figure).after(here()))
+ for it in elements [
+ Figure
+ #numbering(it.numbering,
+ ..counter(figure).at(it.location())):
+ #it.caption.body
+ #box(width: 1fr, repeat[.])
+ #counter(page).at(it.location()).first() \
+ ]
+}
+
+#figure(
+ image("/assets/images/cylinder.svg", width: 50%),
+ caption: [Cylinder],
+)
+
+#figure(
+ rect[Just some stand-in text],
+ kind: image,
+ supplement: "Figure",
+ caption: [Stand-in text],
+)
+
+#figure(
+ image("/assets/images/tetrahedron.svg", width: 50%),
+ caption: [Tetrahedron],
+)
+
+--- query-before-after ---
+// LARGE
+#set page(
+ paper: "a7",
+ numbering: "1 / 1",
+ margin: (bottom: 1cm, rest: 0.5cm),
+)
+
+#show heading.where(level: 1, outlined: true): it => [
+ #it
+
+ #set text(size: 12pt, weight: "regular")
+ #outline(
+ title: "Chapter outline",
+ indent: true,
+ target: heading
+ .where(level: 1)
+ .or(heading.where(level: 2))
+ .after(it.location(), inclusive: true)
+ .before(
+ heading
+ .where(level: 1, outlined: true)
+ .after(it.location(), inclusive: false),
+ inclusive: false,
+ )
+ )
+]
+
+#set heading(outlined: true, numbering: "1.")
+
+= Section 1
+== Subsection 1
+== Subsection 2
+=== Subsubsection 1
+=== Subsubsection 2
+== Subsection 3
+
+= Section 2
+== Subsection 1
+== Subsection 2
+
+= Section 3
+== Subsection 1
+== Subsection 2
+=== Subsubsection 1
+=== Subsubsection 2
+=== Subsubsection 3
+== Subsection 3
+
+--- query-and-or ---
+#set page(
+ paper: "a7",
+ numbering: "1 / 1",
+ margin: (bottom: 1cm, rest: 0.5cm),
+)
+
+#set heading(outlined: true, numbering: "1.")
+
+#context [
+ Non-outlined elements:
+ #(query(selector(heading).and(heading.where(outlined: false)))
+ .map(it => it.body).join(", "))
+]
+
+#heading("A", outlined: false)
+#heading("B", outlined: true)
+#heading("C", outlined: true)
+#heading("D", outlined: false)
+
+--- query-complex ---
+= A
+== B
+#figure([Cat], kind: "cat", supplement: [Other])
+=== D
+= E <first>
+#figure([Frog], kind: "frog", supplement: none)
+#figure([Giraffe], kind: "giraffe", supplement: none) <second>
+#figure([GiraffeCat], kind: "cat", supplement: [Other]) <second>
+= H
+#figure([Iguana], kind: "iguana", supplement: none)
+== I
+
+#let test-selector(selector, ref) = context {
+ test(query(selector).map(e => e.body), ref)
+}
+
+// Test `or`.
+#test-selector(
+ heading.where(level: 1).or(heading.where(level: 3)),
+ ([A], [D], [E], [H]),
+)
+
+#test-selector(
+ heading.where(level: 1).or(
+ heading.where(level: 3),
+ figure.where(kind: "frog"),
+ ),
+ ([A], [D], [E], [Frog], [H]),
+)
+
+#test-selector(
+ heading.where(level: 1).or(
+ heading.where(level: 2),
+ figure.where(kind: "frog"),
+ figure.where(kind: "cat"),
+ ),
+ ([A], [B], [Cat], [E], [Frog], [GiraffeCat], [H], [I]),
+)
+
+#test-selector(
+ figure.where(kind: "dog").or(heading.where(level: 3)),
+ ([D],),
+)
+
+#test-selector(
+ figure.where(kind: "dog").or(figure.where(kind: "fish")),
+ (),
+)
+
+// Test `or` duplicates removal.
+#test-selector(
+ heading.where(level: 1).or(heading.where(level: 1)),
+ ([A], [E], [H]),
+)
+
+// Test `and`.
+#test-selector(
+ figure.where(kind: "cat").and(figure.where(kind: "frog")),
+ (),
+)
+
+// Test `or` with `before`/`after`
+#test-selector(
+ selector(heading)
+ .before(<first>)
+ .or(selector(figure).before(<first>)),
+ ([A], [B], [Cat], [D], [E]),
+)
+
+#test-selector(
+ heading.where(level: 2)
+ .after(<first>)
+ .or(selector(figure).after(<first>)),
+ ([Frog], [Giraffe], [GiraffeCat], [Iguana], [I]),
+)
+
+// Test `and` with `after`
+#test-selector(
+ figure.where(kind: "cat")
+ .and(figure.where(supplement: [Other]))
+ .after(<first>),
+ ([GiraffeCat],),
+)
+
+// Test `and` (with nested `or`)
+#test-selector(
+ heading.where(level: 2)
+ .or(heading.where(level: 3))
+ .and(heading.where(level: 2).or(heading.where(level: 1))),
+ ([B], [I]),
+)
+
+#test-selector(
+ heading.where(level: 2)
+ .or(heading.where(level: 3), heading.where(level:1))
+ .and(
+ heading.where(level: 2).or(heading.where(level: 1)),
+ heading.where(level: 3).or(heading.where(level: 1)),
+ ),
+ ([A], [E], [H]),
+)
+
+// Test `and` with `or` and `before`/`after`
+#test-selector(
+ heading.where(level: 1).before(<first>)
+ .or(heading.where(level: 3).before(<first>))
+ .and(
+ heading.where(level: 1).before(<first>)
+ .or(heading.where(level: 2).before(<first>))
+ ),
+ ([A], [E]),
+)
+
+#test-selector(
+ heading.where(level: 1).before(<first>, inclusive: false)
+ .or(selector(figure).after(<first>))
+ .and(figure.where(kind: "iguana").or(
+ figure.where(kind: "frog"),
+ figure.where(kind: "cat"),
+ heading.where(level: 1).after(<first>),
+ )),
+ ([Frog], [GiraffeCat], [Iguana])
+)
diff --git a/tests/suite/introspection/state.typ b/tests/suite/introspection/state.typ
new file mode 100644
index 00000000..208a4ea2
--- /dev/null
+++ b/tests/suite/introspection/state.typ
@@ -0,0 +1,63 @@
+// Test state.
+
+--- state-basic ---
+#let s = state("hey", "a")
+#let double(it) = 2 * it
+
+#s.update(double)
+#s.update(double)
+$ 2 + 3 $
+#s.update(double)
+
+Is: #context s.get(),
+Was: #context {
+ let it = query(math.equation).first()
+ s.at(it.location())
+}.
+
+--- state-multiple-calls-same-key ---
+// Try same key with different initial value.
+#context state("key", 2).get()
+#state("key").update(x => x + 1)
+#context state("key", 2).get()
+#context state("key", 3).get()
+#state("key").update(x => x + 1)
+#context state("key", 2).get()
+
+--- state-nested ---
+#set page(width: 200pt)
+#set text(8pt)
+
+#let ls = state("lorem", lorem(1000).split("."))
+#let loremum(count) = {
+ context ls.get().slice(0, count).join(".").trim() + "."
+ ls.update(list => list.slice(count))
+}
+
+#let fs = state("fader", red)
+#let trait(title) = block[
+ #context text(fill: fs.get())[
+ *#title:* #loremum(1)
+ ]
+ #fs.update(color => color.lighten(30%))
+]
+
+#trait[Boldness]
+#trait[Adventure]
+#trait[Fear]
+#trait[Anger]
+
+--- state-no-convergence ---
+// Make sure that a warning is produced if the layout fails to converge.
+// Warning: layout did not converge within 5 attempts
+// Hint: check if any states or queries are updating themselves
+#let s = state("s", 1)
+#context s.update(s.final() + 1)
+#context s.get()
+
+--- state-at-no-context ---
+// Test `state.at` outside of context.
+// Error: 2-26 can only be used when context is known
+// Hint: 2-26 try wrapping this in a `context` expression
+// Hint: 2-26 the `context` expression should wrap everything that depends on this function
+#state("key").at(<label>)
diff --git a/tests/suite/layout/align.typ b/tests/suite/layout/align.typ
new file mode 100644
index 00000000..61b79975
--- /dev/null
+++ b/tests/suite/layout/align.typ
@@ -0,0 +1,142 @@
+// Test alignment.
+
+--- align-right ---
+// Test ragged-left.
+#set align(right)
+To the right! Where the sunlight peeks behind the mountain.
+
+--- align-in-stack ---
+#set page(height: 100pt)
+#stack(dir: ltr,
+ align(left, square(size: 15pt, fill: eastern)),
+ align(center, square(size: 20pt, fill: eastern)),
+ align(right, square(size: 15pt, fill: eastern)),
+)
+#align(center + horizon, rect(fill: eastern, height: 10pt))
+#align(bottom, stack(
+ align(center, rect(fill: conifer, height: 10pt)),
+ rect(fill: forest, height: 10pt, width: 100%),
+))
+
+--- align-center-in-flow ---
+// Test that multiple paragraphs in subflow also respect alignment.
+#align(center)[
+ Lorem Ipsum
+
+ Dolor
+]
+
+--- align-start-and-end ---
+// Test start and end alignment.
+#rotate(-30deg, origin: end + horizon)[Hello]
+
+#set text(lang: "de")
+#align(start)[Start]
+#align(end)[Ende]
+
+#set text(lang: "ar")
+#align(start)[يبدأ]
+#align(end)[نهاية]
+
+--- alignment-fields-x ---
+// Test 2d alignment 'horizontal' field.
+#test((start + top).x, start)
+#test((end + top).x, end)
+#test((left + top).x, left)
+#test((right + top).x, right)
+#test((center + top).x, center)
+#test((start + bottom).x, start)
+#test((end + bottom).x, end)
+#test((left + bottom).x, left)
+#test((right + bottom).x, right)
+#test((center + bottom).x, center)
+#test((start + horizon).x, start)
+#test((end + horizon).x, end)
+#test((left + horizon).x, left)
+#test((right + horizon).x, right)
+#test((center + horizon).x, center)
+#test((top + start).x, start)
+#test((bottom + end).x, end)
+#test((horizon + center).x, center)
+
+--- alignment-fields-y ---
+// Test 2d alignment 'vertical' field.
+#test((start + top).y, top)
+#test((end + top).y, top)
+#test((left + top).y, top)
+#test((right + top).y, top)
+#test((center + top).y, top)
+#test((start + bottom).y, bottom)
+#test((end + bottom).y, bottom)
+#test((left + bottom).y, bottom)
+#test((right + bottom).y, bottom)
+#test((center + bottom).y, bottom)
+#test((start + horizon).y, horizon)
+#test((end + horizon).y, horizon)
+#test((left + horizon).y, horizon)
+#test((right + horizon).y, horizon)
+#test((center + horizon).y, horizon)
+#test((top + start).y, top)
+#test((bottom + end).y, bottom)
+#test((horizon + center).y, horizon)
+
+--- alignment-type ---
+#test(type(center), alignment)
+#test(type(horizon), alignment)
+#test(type(center + horizon), alignment)
+
+--- alignment-axis ---
+// Test alignment methods.
+#test(start.axis(), "horizontal")
+#test(end.axis(), "horizontal")
+#test(left.axis(), "horizontal")
+#test(right.axis(), "horizontal")
+#test(center.axis(), "horizontal")
+#test(top.axis(), "vertical")
+#test(bottom.axis(), "vertical")
+#test(horizon.axis(), "vertical")
+
+--- alignment-inv ---
+#test(start.inv(), end)
+#test(end.inv(), start)
+#test(left.inv(), right)
+#test(right.inv(), left)
+#test(center.inv(), center)
+#test(top.inv(), bottom)
+#test(bottom.inv(), top)
+#test(horizon.inv(), horizon)
+#test((start + top).inv(), (end + bottom))
+#test((end + top).inv(), (start + bottom))
+#test((left + top).inv(), (right + bottom))
+#test((right + top).inv(), (left + bottom))
+#test((center + top).inv(), (center + bottom))
+#test((start + bottom).inv(), (end + top))
+#test((end + bottom).inv(), (start + top))
+#test((left + bottom).inv(), (right + top))
+#test((right + bottom).inv(), (left + top))
+#test((center + bottom).inv(), (center + top))
+#test((start + horizon).inv(), (end + horizon))
+#test((end + horizon).inv(), (start + horizon))
+#test((left + horizon).inv(), (right + horizon))
+#test((right + horizon).inv(), (left + horizon))
+#test((center + horizon).inv(), (center + horizon))
+#test((top + start).inv(), (end + bottom))
+#test((bottom + end).inv(), (start + top))
+#test((horizon + center).inv(), (center + horizon))
+
+--- alignment-add-two-horizontal ---
+// Error: 8-22 cannot add two horizontal alignments
+#align(center + right, [A])
+
+--- alignment-add-two-vertical ---
+// Error: 8-20 cannot add two vertical alignments
+#align(top + bottom, [A])
+
+--- alignment-add-vertical-and-2d ---
+// Error: 8-30 cannot add a vertical and a 2D alignment
+#align(top + (bottom + right), [A])
+
+--- issue-1398-line-align ---
+// Test right-aligning a line and a rectangle.
+#align(right, line(length: 30%))
+#align(right, rect())
diff --git a/tests/suite/layout/angle.typ b/tests/suite/layout/angle.typ
new file mode 100644
index 00000000..65e80aeb
--- /dev/null
+++ b/tests/suite/layout/angle.typ
@@ -0,0 +1,8 @@
+--- angle-to-unit ---
+// Test angle methods.
+#test(1rad.rad(), 1.0)
+#test(1.23rad.rad(), 1.23)
+#test(0deg.rad(), 0.0)
+#test(2deg.deg(), 2.0)
+#test(2.94deg.deg(), 2.94)
+#test(0rad.deg(), 0.0)
diff --git a/tests/typ/empty.typ b/tests/suite/layout/clip.typ
index e69de29b..e69de29b 100644
--- a/tests/typ/empty.typ
+++ b/tests/suite/layout/clip.typ
diff --git a/tests/suite/layout/columns.typ b/tests/suite/layout/columns.typ
new file mode 100644
index 00000000..87a9f773
--- /dev/null
+++ b/tests/suite/layout/columns.typ
@@ -0,0 +1,124 @@
+// Test the column layouter.
+
+--- columns-rtl ---
+// Test normal operation and RTL directions.
+#set page(height: 3.25cm, width: 7.05cm, columns: 2)
+#set text(lang: "ar", font: ("Noto Sans Arabic", "Linux Libertine"))
+#set columns(gutter: 30pt)
+
+#box(fill: conifer, height: 8pt, width: 6pt) وتحفيز
+العديد من التفاعلات الكيميائية. (DNA) من أهم الأحماض النووية التي تُشكِّل
+إلى جانب كل من البروتينات والليبيدات والسكريات المتعددة
+#box(fill: eastern, height: 8pt, width: 6pt)
+الجزيئات الضخمة الأربعة الضرورية للحياة.
+
+--- columns-in-fixed-size-rect ---
+// Test the `columns` function.
+#set page(width: auto)
+
+#rect(width: 180pt, height: 100pt, inset: 8pt, columns(2, [
+ A special plight has befallen our document.
+ Columns in text boxes reigned down unto the soil
+ to waste a year's crop of rich layouts.
+ The columns at least were graciously balanced.
+]))
+
+--- columns-set-page ---
+// Test columns for a sized page.
+#set page(height: 5cm, width: 7.05cm, columns: 2)
+
+Lorem ipsum dolor sit amet is a common blind text
+and I again am in need of filling up this page
+#align(bottom, rect(fill: eastern, width: 100%, height: 12pt))
+#colbreak()
+
+so I'm returning to this trusty tool of tangible terror.
+Sure, it is not the most creative way of filling up
+a page for a test but it does get the job done.
+
+--- columns-in-auto-sized-rect ---
+// Test the expansion behaviour.
+#set page(height: 2.5cm, width: 7.05cm)
+
+#rect(inset: 6pt, columns(2, [
+ ABC \
+ BCD
+ #colbreak()
+ DEF
+]))
+
+--- columns-more-with-gutter ---
+// Test setting a column gutter and more than two columns.
+#set page(height: 3.25cm, width: 7.05cm, columns: 3)
+#set columns(gutter: 30pt)
+
+#rect(width: 100%, height: 2.5cm, fill: conifer) #parbreak()
+#rect(width: 100%, height: 2cm, fill: eastern) #parbreak()
+#circle(fill: eastern)
+
+--- columns-set-page-colbreak-pagebreak ---
+// Test the `colbreak` and `pagebreak` functions.
+#set page(height: 1cm, width: 7.05cm, columns: 2)
+
+A
+#colbreak()
+#colbreak()
+B
+#pagebreak()
+C
+#colbreak()
+D
+
+--- columns-empty-second-column ---
+// Test an empty second column.
+#set page(width: 7.05cm, columns: 2)
+
+#rect(width: 100%, inset: 3pt)[So there isn't anything in the second column?]
+
+--- columns-page-width-auto ---
+// Test columns when one of them is empty.
+#set page(width: auto, columns: 3)
+
+Arbitrary horizontal growth.
+
+--- columns-page-height-auto ---
+// Test columns in an infinitely high frame.
+#set page(width: 7.05cm, columns: 2)
+
+There can be as much content as you want in the left column
+and the document will grow with it.
+
+#rect(fill: conifer, width: 100%, height: 30pt)
+
+Only an explicit #colbreak() `#colbreak()` can put content in the
+second column.
+
+--- columns-one ---
+// Test a page with a single column.
+#set page(height: auto, width: 7.05cm, columns: 1)
+
+This is a normal page. Very normal.
+
+--- columns-zero ---
+// Test a page with zero columns.
+// Error: 49-50 number must be positive
+#set page(height: auto, width: 7.05cm, columns: 0)
+
+--- columns-colbreak-after-place ---
+// Test colbreak after only out-of-flow elements.
+#set page(width: 7.05cm, columns: 2)
+#place[OOF]
+#colbreak()
+In flow.
+
+--- issue-columns-heading ---
+// The well-known columns bug.
+#set page(height: 70pt)
+
+Hallo
+#columns(2)[
+ = A
+ Text
+ = B
+ Text
+]
diff --git a/tests/suite/layout/container.typ b/tests/suite/layout/container.typ
new file mode 100644
index 00000000..ede051db
--- /dev/null
+++ b/tests/suite/layout/container.typ
@@ -0,0 +1,183 @@
+// Test the `box` and `block` containers.
+
+--- box ---
+// Test box in paragraph.
+A #box[B \ C] D.
+
+// Test box with height.
+Spaced \
+#box(height: 0.5cm) \
+Apart
+
+--- block-sizing ---
+// Test block sizing.
+#set page(height: 120pt)
+#set block(spacing: 0pt)
+#block(width: 90pt, height: 80pt, fill: red)[
+ #block(width: 60%, height: 60%, fill: green)
+ #block(width: 50%, height: 60%, fill: blue)
+]
+
+--- box-layoutable-child ---
+// Test box sizing with layoutable child.
+#box(
+ width: 50pt,
+ height: 50pt,
+ fill: yellow,
+ path(
+ fill: purple,
+ (0pt, 0pt),
+ (30pt, 30pt),
+ (0pt, 30pt),
+ (30pt, 0pt),
+ ),
+)
+
+--- box-width-fr ---
+// Test fr box.
+Hello #box(width: 1fr, rect(height: 0.7em, width: 100%)) World
+
+--- block-multiple-pages ---
+// Test block over multiple pages.
+#set page(height: 60pt)
+
+First!
+
+#block[
+ But, soft! what light through yonder window breaks? It is the east, and Juliet
+ is the sun.
+]
+
+--- block-box-fill ---
+#set page(height: 100pt)
+#let words = lorem(18).split()
+#block(inset: 8pt, width: 100%, fill: aqua, stroke: aqua.darken(30%))[
+ #words.slice(0, 13).join(" ")
+ #box(fill: teal, outset: 2pt)[tempor]
+ #words.slice(13).join(" ")
+]
+
+--- block-spacing-basic ---
+#set block(spacing: 10pt)
+Hello
+
+There
+
+#block(spacing: 20pt)[Further down]
+
+--- block-spacing-table ---
+// Test that paragraph spacing loses against block spacing.
+// TODO
+#set block(spacing: 100pt)
+#show table: set block(above: 5pt, below: 5pt)
+Hello
+#table(columns: 4, fill: (x, y) => if calc.odd(x + y) { silver })[A][B][C][D]
+
+--- block-spacing-maximum ---
+// While we're at it, test the larger block spacing wins.
+#set block(spacing: 0pt)
+#show raw: set block(spacing: 15pt)
+#show list: set block(spacing: 2.5pt)
+
+```rust
+fn main() {}
+```
+
+- List
+
+Paragraph
+
+--- block-spacing-collapse-text-style ---
+// Test spacing collapsing with different font sizes.
+#grid(columns: 2)[
+ #text(size: 12pt, block(below: 1em)[A])
+ #text(size: 8pt, block(above: 1em)[B])
+][
+ #text(size: 12pt, block(below: 1em)[A])
+ #text(size: 8pt, block(above: 1.25em)[B])
+]
+
+--- block-fixed-height ---
+#set page(height: 100pt)
+#set align(center)
+
+#lorem(10)
+#block(width: 80%, height: 60pt, fill: aqua)
+#lorem(6)
+#block(
+ breakable: false,
+ width: 100%,
+ inset: 4pt,
+ fill: aqua,
+ lorem(8) + colbreak(),
+)
+
+--- box-clip-rect ---
+// Test box clipping with a rectangle
+Hello #box(width: 1em, height: 1em, clip: false)[#rect(width: 3em, height: 3em, fill: red)]
+world 1
+
+Space
+
+Hello #box(width: 1em, height: 1em, clip: true)[#rect(width: 3em, height: 3em, fill: red)]
+world 2
+
+--- block-clip-text ---
+// Test cliping text
+#block(width: 5em, height: 2em, clip: false, stroke: 1pt + black)[
+ But, soft! what light through
+]
+
+#v(2em)
+
+#block(width: 5em, height: 2em, clip: true, stroke: 1pt + black)[
+ But, soft! what light through yonder window breaks? It is the east, and Juliet
+ is the sun.
+]
+
+--- block-clip-svg-glyphs ---
+// Test clipping svg glyphs
+Emoji: #box(height: 0.5em, stroke: 1pt + black)[🐪, 🌋, 🏞]
+
+Emoji: #box(height: 0.5em, clip: true, stroke: 1pt + black)[🐪, 🌋, 🏞]
+
+--- block-clipping-multiple-pages ---
+// Test block clipping over multiple pages.
+#set page(height: 60pt)
+
+First!
+
+#block(height: 4em, clip: true, stroke: 1pt + black)[
+ But, soft! what light through yonder window breaks? It is the east, and Juliet
+ is the sun.
+]
+
+--- box-clip-radius ---
+// Test clipping with `radius`.
+#set page(height: 60pt)
+
+#box(
+ radius: 5pt,
+ stroke: 2pt + black,
+ width: 20pt,
+ height: 20pt,
+ clip: true,
+ image("/assets/images/rhino.png", width: 30pt)
+)
+
+--- box-clip-radius-without-stroke ---
+// Test clipping with `radius`, but without `stroke`.
+#set page(height: 60pt)
+
+#box(
+ radius: 5pt,
+ width: 20pt,
+ height: 20pt,
+ clip: true,
+ image("/assets/images/rhino.png", width: 30pt)
+)
+
+--- issue-2128-block-width-box ---
+// Test box in 100% width block.
+#block(width: 100%, fill: red, box("a box"))
+#block(width: 100%, fill: red, [#box("a box") #box()])
diff --git a/tests/suite/layout/dir.typ b/tests/suite/layout/dir.typ
new file mode 100644
index 00000000..139a2285
--- /dev/null
+++ b/tests/suite/layout/dir.typ
@@ -0,0 +1,24 @@
+--- dir-axis ---
+// Test direction methods.
+#test(ltr.axis(), "horizontal")
+#test(rtl.axis(), "horizontal")
+#test(ttb.axis(), "vertical")
+#test(btt.axis(), "vertical")
+
+--- dir-start ---
+#test(ltr.start(), left)
+#test(rtl.start(), right)
+#test(ttb.start(), top)
+#test(btt.start(), bottom)
+
+--- dir-end ---
+#test(ltr.end(), right)
+#test(rtl.end(), left)
+#test(ttb.end(), bottom)
+#test(btt.end(), top)
+
+--- dir-inv ---
+#test(ltr.inv(), rtl)
+#test(rtl.inv(), ltr)
+#test(ttb.inv(), btt)
+#test(btt.inv(), ttb)
diff --git a/tests/suite/layout/flow/flow.typ b/tests/suite/layout/flow/flow.typ
new file mode 100644
index 00000000..9c48c9ac
--- /dev/null
+++ b/tests/suite/layout/flow/flow.typ
@@ -0,0 +1,67 @@
+--- flow-fr ---
+#set page(height: 2cm)
+#set text(white)
+#rect(fill: forest)[
+ #v(1fr)
+ #h(1fr) Hi you!
+]
+
+--- issue-flow-overlarge-frames ---
+// In this bug, the first line of the second paragraph was on its page alone an
+// the rest moved down. The reason was that the second block resulted in
+// overlarge frames because the region wasn't finished properly.
+#set page(height: 70pt)
+#block[This file tests a bug where an almost empty page occurs.]
+#block[
+ The text in this second block was torn apart and split up for
+ some reason beyond my knowledge.
+]
+
+--- issue-flow-trailing-leading ---
+// In this bug, the first part of the paragraph moved down to the second page
+// because trailing leading wasn't trimmed, resulting in an overlarge frame.
+#set page(height: 60pt)
+#v(19pt)
+#block[
+ But, soft! what light through yonder window breaks?
+ It is the east, and Juliet is the sun.
+]
+
+--- issue-flow-weak-spacing ---
+// In this bug, there was a bit of space below the heading because weak spacing
+// directly before a layout-induced column or page break wasn't trimmed.
+#set page(height: 60pt)
+#rect(inset: 0pt, columns(2)[
+ Text
+ #v(12pt)
+ Hi
+ #v(10pt, weak: true)
+ At column break.
+])
+
+--- issue-flow-frame-placement ---
+// In this bug, a frame intended for the second region ended up in the first.
+#set page(height: 105pt)
+#block(lorem(20))
+
+--- issue-flow-layout-index-out-of-bounds ---
+// This bug caused an index-out-of-bounds panic when layouting paragraphs needed
+// multiple reorderings.
+#set page(height: 200pt)
+#lorem(30)
+
+#figure(placement: auto, block(height: 100%))
+
+#lorem(10)
+
+#lorem(10)
+
+--- issue-3641-float-loop ---
+// Flow layout should terminate!
+//
+// This is not yet ideal: The heading should not move to the second page, but
+// that's a separate bug and not a regression.
+#set page(height: 40pt)
+
+= Heading
+#lorem(6)
diff --git a/tests/suite/layout/flow/invisibles.typ b/tests/suite/layout/flow/invisibles.typ
new file mode 100644
index 00000000..7e460373
--- /dev/null
+++ b/tests/suite/layout/flow/invisibles.typ
@@ -0,0 +1,61 @@
+// Test out-of-flow items (place, counter updates, etc.) at the
+// beginning of a block not creating a frame just for them.
+
+--- flow-first-region-no-item ---
+// No item in the first region.
+#set page(height: 5cm, margin: 1cm)
+No item in the first region.
+#block(breakable: true, stroke: 1pt, inset: 0.5cm)[
+ #rect(height: 2cm, fill: gray)
+]
+
+--- flow-first-region-counter-update ---
+// Counter update in the first region.
+#set page(height: 5cm, margin: 1cm)
+Counter update.
+#block(breakable: true, stroke: 1pt, inset: 0.5cm)[
+ #counter("dummy").step()
+ #rect(height: 2cm, fill: gray)
+]
+
+--- flow-first-region-placed ---
+// Placed item in the first region.
+#set page(height: 5cm, margin: 1cm)
+Placed item in the first region.
+#block(breakable: true, above: 1cm, stroke: 1pt, inset: 0.5cm)[
+ #place(dx: -0.5cm, dy: -0.75cm, box(width: 200%)[OOF])
+ #rect(height: 2cm, fill: gray)
+]
+
+--- flow-first-region-zero-sized-item ---
+// In-flow item with size zero in the first region.
+#set page(height: 5cm, margin: 1cm)
+In-flow, zero-sized item.
+#block(breakable: true, stroke: 1pt, inset: 0.5cm)[
+ #set block(spacing: 0pt)
+ #line(length: 0pt)
+ #rect(height: 2cm, fill: gray)
+ #line(length: 100%)
+]
+
+--- flow-first-region-counter-update-and-placed ---
+// Counter update and placed item in the first region.
+#set page(height: 5cm, margin: 1cm)
+Counter update + place.
+#block(breakable: true, above: 1cm, stroke: 1pt, inset: 0.5cm)[
+ #counter("dummy").step()
+ #place(dx: -0.5cm, dy: -0.75cm, box([OOF]))
+ #rect(height: 2cm, fill: gray)
+]
+
+--- flow-first-region-counter-update-placed-and-line ---
+// Mix-and-match all the previous ones.
+#set page(height: 5cm, margin: 1cm)
+Mix-and-match all the previous tests.
+#block(breakable: true, above: 1cm, stroke: 1pt, inset: 0.5cm)[
+ #counter("dummy").step()
+ #place(dx: -0.5cm, dy: -0.75cm, box(width: 200%)[OOF])
+ #line(length: 100%)
+ #place(dy: -0.8em)[OOF]
+ #rect(height: 2cm, fill: gray)
+]
diff --git a/tests/suite/layout/flow/orphan.typ b/tests/suite/layout/flow/orphan.typ
new file mode 100644
index 00000000..70eac731
--- /dev/null
+++ b/tests/suite/layout/flow/orphan.typ
@@ -0,0 +1,31 @@
+// Test that lines and headings doesn't become orphan.
+
+--- flow-heading-no-orphan ---
+#set page(height: 100pt)
+#lorem(12)
+
+= Introduction
+This is the start and it goes on.
+
+--- flow-par-no-orphan-and-widow-lines ---
+// LARGE
+#set page("a8", height: 140pt)
+#set text(weight: 700)
+
+// Fits fully onto the first page.
+#set text(blue)
+#lorem(27)
+
+// The first line would fit, but is moved to the second page.
+#lorem(20)
+
+// The second-to-last line is moved to the third page so that the last is isn't
+// as lonely.
+#set text(maroon)
+#lorem(11)
+
+#lorem(13)
+
+// All three lines go to the next page.
+#set text(olive)
+#lorem(10)
diff --git a/tests/suite/layout/grid/cell.typ b/tests/suite/layout/grid/cell.typ
new file mode 100644
index 00000000..3b08c752
--- /dev/null
+++ b/tests/suite/layout/grid/cell.typ
@@ -0,0 +1,132 @@
+// Test basic styling using the grid.cell element.
+
+--- grid-cell-override ---
+// Cell override
+#grid(
+ align: left,
+ fill: red,
+ stroke: blue,
+ inset: 5pt,
+ columns: 2,
+ [AAAAA], [BBBBB],
+ [A], [B],
+ grid.cell(align: right)[C], [D],
+ align(right)[E], [F],
+ align(horizon)[G], [A\ A\ A],
+ grid.cell(align: horizon)[G2], [A\ A\ A],
+ grid.cell(inset: 0pt)[I], [F],
+ [H], grid.cell(fill: blue)[J]
+)
+
+--- grid-cell-show ---
+// Cell show rule
+#show grid.cell: it => [Zz]
+
+#grid(
+ align: left,
+ fill: red,
+ stroke: blue,
+ inset: 5pt,
+ columns: 2,
+ [AAAAA], [BBBBB],
+ [A], [B],
+ grid.cell(align: right)[C], [D],
+ align(right)[E], [F],
+ align(horizon)[G], [A\ A\ A]
+)
+
+--- grid-cell-show-and-override ---
+#show grid.cell: it => (it.align, it.fill)
+#grid(
+ align: left,
+ row-gutter: 5pt,
+ [A],
+ grid.cell(align: right)[B],
+ grid.cell(fill: aqua)[B],
+)
+
+--- grid-cell-set ---
+// Cell set rules
+#set grid.cell(align: center)
+#show grid.cell: it => (it.align, it.fill, it.inset)
+#set grid.cell(inset: 20pt)
+#grid(
+ align: left,
+ row-gutter: 5pt,
+ [A],
+ grid.cell(align: right)[B],
+ grid.cell(fill: aqua)[B],
+)
+
+--- grid-cell-folding ---
+// Test folding per-cell properties (align and inset)
+#grid(
+ columns: (1fr, 1fr),
+ rows: (2.5em, auto),
+ align: right,
+ inset: 5pt,
+ fill: (x, y) => (green, aqua).at(calc.rem(x + y, 2)),
+ [Top], grid.cell(align: bottom)[Bot],
+ grid.cell(inset: (bottom: 0pt))[Bot], grid.cell(inset: (bottom: 0pt))[Bot]
+)
+
+--- grid-cell-align-override ---
+// Test overriding outside alignment
+#set align(bottom + right)
+#grid(
+ columns: (1fr, 1fr),
+ rows: 2em,
+ align: auto,
+ fill: green,
+ [BR], [BR],
+ grid.cell(align: left, fill: aqua)[BL], grid.cell(align: top, fill: red.lighten(50%))[TR]
+)
+
+--- grid-cell-various-overrides ---
+#grid(
+ columns: 2,
+ fill: red,
+ align: left,
+ inset: 5pt,
+ [ABC], [ABC],
+ grid.cell(fill: blue)[C], [D],
+ grid.cell(align: center)[E], [F],
+ [G], grid.cell(inset: 0pt)[H]
+)
+
+--- grid-cell-show-emph ---
+#{
+ show grid.cell: emph
+ grid(
+ columns: 2,
+ gutter: 3pt,
+ [Hello], [World],
+ [Sweet], [Italics]
+ )
+}
+
+--- grid-cell-show-based-on-position ---
+// Style based on position
+#{
+ show grid.cell: it => {
+ if it.y == 0 {
+ strong(it)
+ } else if it.x == 1 {
+ emph(it)
+ } else {
+ it
+ }
+ }
+ grid(
+ columns: 3,
+ gutter: 3pt,
+ [Name], [Age], [Info],
+ [John], [52], [Nice],
+ [Mary], [50], [Cool],
+ [Jake], [49], [Epic]
+ )
+}
+
+--- table-cell-in-grid ---
+// Error: 7-19 cannot use `table.cell` as a grid cell; use `grid.cell` instead
+#grid(table.cell[])
diff --git a/tests/suite/layout/grid/colspan.typ b/tests/suite/layout/grid/colspan.typ
new file mode 100644
index 00000000..707a9456
--- /dev/null
+++ b/tests/suite/layout/grid/colspan.typ
@@ -0,0 +1,142 @@
+--- grid-colspan ---
+#grid(
+ columns: 4,
+ fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
+ inset: 5pt,
+ align: center,
+ grid.cell(colspan: 4)[*Full Header*],
+ grid.cell(colspan: 2, fill: orange)[*Half*],
+ grid.cell(colspan: 2, fill: orange.darken(10%))[*Half*],
+ [*A*], [*B*], [*C*], [*D*],
+ [1], [2], [3], [4],
+ [5], grid.cell(colspan: 3, fill: orange.darken(10%))[6],
+ grid.cell(colspan: 2, fill: orange)[7], [8], [9],
+ [10], grid.cell(colspan: 2, fill: orange.darken(10%))[11], [12]
+)
+
+#table(
+ columns: 4,
+ fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
+ inset: 5pt,
+ align: center,
+ table.cell(colspan: 4)[*Full Header*],
+ table.cell(colspan: 2, fill: orange)[*Half*],
+ table.cell(colspan: 2, fill: orange.darken(10%))[*Half*],
+ [*A*], [*B*], [*C*], [*D*],
+ [1], [2], [3], [4],
+ [5], table.cell(colspan: 3, fill: orange.darken(10%))[6],
+ table.cell(colspan: 2, fill: orange)[7], [8], [9],
+ [10], table.cell(colspan: 2, fill: orange.darken(10%))[11], [12]
+)
+
+--- grid-colspan-gutter ---
+#grid(
+ columns: 4,
+ fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
+ inset: 5pt,
+ align: center,
+ gutter: 3pt,
+ grid.cell(colspan: 4)[*Full Header*],
+ grid.cell(colspan: 2, fill: orange)[*Half*],
+ grid.cell(colspan: 2, fill: orange.darken(10%))[*Half*],
+ [*A*], [*B*], [*C*], [*D*],
+ [1], [2], [3], [4],
+ [5], grid.cell(colspan: 3, fill: orange.darken(10%))[6],
+ grid.cell(colspan: 2, fill: orange)[7], [8], [9],
+ [10], grid.cell(colspan: 2, fill: orange.darken(10%))[11], [12]
+)
+
+#table(
+ columns: 4,
+ fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
+ inset: 5pt,
+ align: center,
+ gutter: 3pt,
+ table.cell(colspan: 4)[*Full Header*],
+ table.cell(colspan: 2, fill: orange)[*Half*],
+ table.cell(colspan: 2, fill: orange.darken(10%))[*Half*],
+ [*A*], [*B*], [*C*], [*D*],
+ [1], [2], [3], [4],
+ [5], table.cell(colspan: 3, fill: orange.darken(10%))[6],
+ table.cell(colspan: 2, fill: orange)[7], [8], [9],
+ [10], table.cell(colspan: 2, fill: orange.darken(10%))[11], [12]
+)
+
+--- grid-colspan-thick-stroke ---
+#set page(width: 300pt)
+#table(
+ columns: (2em, 2em, auto, auto),
+ stroke: 5pt,
+ [A], [B], [C], [D],
+ table.cell(colspan: 4, lorem(20)),
+ [A], table.cell(colspan: 2)[BCBCBCBC], [D]
+)
+
+--- grid-colspan-out-of-bounds ---
+// Error: 3:8-3:32 cell's colspan would cause it to exceed the available column(s)
+// Hint: 3:8-3:32 try placing the cell in another position or reducing its colspan
+#grid(
+ columns: 3,
+ [a], grid.cell(colspan: 3)[b]
+)
+
+--- grid-colspan-overlap ---
+// Error: 4:8-4:32 cell would span a previously placed cell at column 2, row 0
+// Hint: 4:8-4:32 try specifying your cells in a different order or reducing the cell's rowspan or colspan
+#grid(
+ columns: 3,
+ grid.cell(x: 2, y: 0)[x],
+ [a], grid.cell(colspan: 2)[b]
+)
+
+--- grid-colspan-over-all-fr-columns ---
+// Colspan over all fractional columns shouldn't expand auto columns on finite pages
+#table(
+ columns: (1fr, 1fr, auto),
+ [A], [B], [C],
+ [D], [E], [F]
+)
+#table(
+ columns: (1fr, 1fr, auto),
+ table.cell(colspan: 3, lorem(8)),
+ [A], [B], [C],
+ [D], [E], [F]
+)
+
+--- grid-colspan-over-some-fr-columns ---
+// Colspan over only some fractional columns will not trigger the heuristic, and
+// the auto column will expand more than it should. The table looks off, as a result.
+#table(
+ columns: (1fr, 1fr, auto),
+ [], table.cell(colspan: 2, lorem(8)),
+ [A], [B], [C],
+ [D], [E], [F]
+)
+
+--- grid-colspan-over-all-fr-columns-page-width-auto ---
+// On infinite pages, colspan over all fractional columns SHOULD expand auto columns
+#set page(width: auto)
+#table(
+ columns: (1fr, 1fr, auto),
+ [A], [B], [C],
+ [D], [E], [F]
+)
+#table(
+ columns: (1fr, 1fr, auto),
+ table.cell(colspan: 3, lorem(8)),
+ [A], [B], [C],
+ [D], [E], [F]
+)
+
+--- grid-colspan-multiple-regions ---
+// Test multiple regions
+#set page(height: 5em)
+#grid(
+ stroke: red,
+ fill: aqua,
+ columns: 4,
+ [a], [b], [c], [d],
+ [a], grid.cell(colspan: 2)[e, f, g, h, i], [f],
+ [e], [g], grid.cell(colspan: 2)[eee\ e\ e\ e],
+ grid.cell(colspan: 4)[eeee e e e]
+)
diff --git a/tests/suite/layout/grid/footers.typ b/tests/suite/layout/grid/footers.typ
new file mode 100644
index 00000000..c73bcb39
--- /dev/null
+++ b/tests/suite/layout/grid/footers.typ
@@ -0,0 +1,404 @@
+--- grid-footer ---
+#set page(width: auto, height: 15em)
+#set text(6pt)
+#set table(inset: 2pt, stroke: 0.5pt)
+#table(
+ columns: 5,
+ align: center + horizon,
+ table.header(
+ table.cell(colspan: 5)[*Cool Zone*],
+ table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*],
+ table.hline(start: 2, end: 3, stroke: yellow)
+ ),
+ ..range(0, 5).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten(),
+ table.footer(
+ table.hline(start: 2, end: 3, stroke: yellow),
+ table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*],
+ table.cell(colspan: 5)[*Cool Zone*]
+ )
+)
+
+--- grid-footer-gutter-and-no-repeat ---
+// Gutter & no repetition
+#set page(width: auto, height: 16em)
+#set text(6pt)
+#set table(inset: 2pt, stroke: 0.5pt)
+#table(
+ columns: 5,
+ gutter: 2pt,
+ align: center + horizon,
+ table.header(
+ table.cell(colspan: 5)[*Cool Zone*],
+ table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*],
+ table.hline(start: 2, end: 3, stroke: yellow)
+ ),
+ ..range(0, 5).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten(),
+ table.footer(
+ repeat: false,
+ table.hline(start: 2, end: 3, stroke: yellow),
+ table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*],
+ table.cell(colspan: 5)[*Cool Zone*]
+ )
+)
+
+--- grid-cell-override-in-header-and-footer ---
+#table(
+ table.header(table.cell(stroke: red)[Hello]),
+ table.footer(table.cell(stroke: aqua)[Bye]),
+)
+
+--- grid-cell-override-in-header-and-footer-with-gutter ---
+#table(
+ gutter: 3pt,
+ table.header(table.cell(stroke: red)[Hello]),
+ table.footer(table.cell(stroke: aqua)[Bye]),
+)
+
+--- grid-footer-top-stroke ---
+// Footer's top stroke should win when repeated, but lose at the last page.
+#set page(height: 10em)
+#table(
+ stroke: green,
+ table.header(table.cell(stroke: red)[Hello]),
+ table.cell(stroke: yellow)[Hi],
+ table.cell(stroke: yellow)[Bye],
+ table.cell(stroke: yellow)[Ok],
+ table.footer[Bye],
+)
+
+--- grid-footer-relative-row-sizes ---
+// Relative lengths
+#set page(height: 10em)
+#table(
+ rows: (30%, 30%, auto),
+ [C],
+ [C],
+ table.footer[*A*][*B*],
+)
+
+--- grid-footer-cell-with-y ---
+#grid(
+ grid.footer(grid.cell(y: 2)[b]),
+ grid.cell(y: 0)[a],
+ grid.cell(y: 1)[c],
+)
+
+--- grid-footer-expand ---
+// Ensure footer properly expands
+#grid(
+ columns: 2,
+ [a], [],
+ [b], [],
+ grid.cell(x: 1, y: 3, rowspan: 4)[b],
+ grid.cell(y: 2, rowspan: 2)[a],
+ grid.footer(),
+ grid.cell(y: 4)[d],
+ grid.cell(y: 5)[e],
+ grid.cell(y: 6)[f],
+)
+
+--- grid-footer-not-at-last-row ---
+// Error: 2:3-2:19 footer must end at the last row
+#grid(
+ grid.footer([a]),
+ [b],
+)
+
+--- grid-footer-not-at-last-row-two-columns ---
+// Error: 3:3-3:19 footer must end at the last row
+#grid(
+ columns: 2,
+ grid.footer([a]),
+ [b],
+)
+
+--- grid-footer-overlap ---
+// Error: 4:3-4:19 footer would conflict with a cell placed before it at column 1 row 0
+// Hint: 4:3-4:19 try reducing that cell's rowspan or moving the footer
+#grid(
+ columns: 2,
+ grid.header(),
+ grid.footer([a]),
+ grid.cell(x: 1, y: 0, rowspan: 2)[a],
+)
+
+--- grid-footer-multiple ---
+// Error: 4:3-4:19 cannot have more than one footer
+#grid(
+ [a],
+ grid.footer([a]),
+ grid.footer([b]),
+)
+
+--- table-footer-in-grid ---
+// Error: 3:3-3:20 cannot use `table.footer` as a grid footer; use `grid.footer` instead
+#grid(
+ [a],
+ table.footer([a]),
+)
+
+--- grid-footer-in-table ---
+// Error: 3:3-3:19 cannot use `grid.footer` as a table footer; use `table.footer` instead
+#table(
+ [a],
+ grid.footer([a]),
+)
+
+--- grid-footer-in-grid-header ---
+// Error: 14-28 cannot place a grid footer within another footer or header
+#grid.header(grid.footer[a])
+
+--- table-footer-in-grid-header ---
+// Error: 14-29 cannot place a table footer within another footer or header
+#grid.header(table.footer[a])
+
+--- grid-footer-in-table-header ---
+// Error: 15-29 cannot place a grid footer within another footer or header
+#table.header(grid.footer[a])
+
+--- table-footer-in-table-header ---
+// Error: 15-30 cannot place a table footer within another footer or header
+#table.header(table.footer[a])
+
+--- grid-footer-in-grid-footer ---
+// Error: 14-28 cannot place a grid footer within another footer or header
+#grid.footer(grid.footer[a])
+
+--- table-footer-in-grid-footer ---
+// Error: 14-29 cannot place a table footer within another footer or header
+#grid.footer(table.footer[a])
+
+--- grid-footer-in-table-footer ---
+// Error: 15-29 cannot place a grid footer within another footer or header
+#table.footer(grid.footer[a])
+
+--- table-footer-in-table-footer ---
+// Error: 15-30 cannot place a table footer within another footer or header
+#table.footer(table.footer[a])
+
+--- grid-header-in-grid-footer ---
+// Error: 14-28 cannot place a grid header within another header or footer
+#grid.footer(grid.header[a])
+
+--- table-header-in-grid-footer ---
+// Error: 14-29 cannot place a table header within another header or footer
+#grid.footer(table.header[a])
+
+--- grid-header-in-table-footer ---
+// Error: 15-29 cannot place a grid header within another header or footer
+#table.footer(grid.header[a])
+
+--- table-header-in-table-footer ---
+// Error: 15-30 cannot place a table header within another header or footer
+#table.footer(table.header[a])
+
+--- grid-header-footer-block-with-fixed-height ---
+#set page(height: 17em)
+#table(
+ rows: (auto, 2.5em, auto),
+ table.header[*Hello*][*World*],
+ block(width: 2em, height: 10em, fill: red),
+ table.footer[*Bye*][*World*],
+)
+
+--- grid-header-footer-and-rowspan-non-contiguous-1 ---
+// Rowspan sizing algorithm doesn't do the best job at non-contiguous content
+// ATM.
+#set page(height: 20em)
+
+#table(
+ rows: (auto, 2.5em, 2em, auto, 5em, 2em, 2.5em),
+ table.header[*Hello*][*World*],
+ table.cell(rowspan: 3, lorem(20)),
+ table.footer[*Ok*][*Bye*],
+)
+
+--- grid-header-footer-and-rowspan-non-contiguous-2 ---
+// This should look right
+#set page(height: 20em)
+
+#table(
+ rows: (auto, 2.5em, 2em, auto),
+ gutter: 3pt,
+ table.header[*Hello*][*World*],
+ table.cell(rowspan: 3, lorem(20)),
+ table.footer[*Ok*][*Bye*],
+)
+--- grid-header-and-footer-lack-of-space ---
+// Test lack of space for header + text.
+#set page(height: 9em + 2.5em + 1.5em)
+
+#table(
+ rows: (auto, 2.5em, auto, auto, 10em, 2.5em, auto),
+ gutter: 3pt,
+ table.header[*Hello*][*World*],
+ table.cell(rowspan: 3, lorem(30)),
+ table.footer[*Ok*][*Bye*],
+)
+
+--- grid-header-and-footer-orphan-prevention ---
+// Orphan header prevention test
+#set page(height: 13em)
+#v(8em)
+#grid(
+ columns: 3,
+ gutter: 5pt,
+ grid.header(
+ [*Mui*], [*A*], grid.cell(rowspan: 2, fill: orange)[*B*],
+ [*Header*], [*Header* #v(0.1em)],
+ ),
+ ..([Test], [Test], [Test]) * 7,
+ grid.footer(
+ [*Mui*], [*A*], grid.cell(rowspan: 2, fill: orange)[*B*],
+ [*Footer*], [*Footer* #v(0.1em)],
+ ),
+)
+
+--- grid-header-and-footer-empty ---
+// Empty footer should just be a repeated blank row
+#set page(height: 8em)
+#table(
+ columns: 4,
+ align: center + horizon,
+ table.header(),
+ ..range(0, 2).map(i => (
+ [John \##i],
+ table.cell(stroke: green)[123],
+ table.cell(stroke: blue)[456],
+ [789]
+ )).flatten(),
+ table.footer(),
+)
+
+--- grid-header-and-footer-containing-rowspan ---
+// When a footer has a rowspan with an empty row, it should be displayed
+// properly
+#set page(height: 14em, width: auto)
+
+#let count = counter("g")
+#table(
+ rows: (auto, 2em, auto, auto),
+ table.header(
+ [eeec],
+ table.cell(rowspan: 2, count.step() + count.display()),
+ ),
+ [d],
+ block(width: 5em, fill: yellow, lorem(7)),
+ [d],
+ table.footer(
+ [eeec],
+ table.cell(rowspan: 2, count.step() + count.display()),
+ )
+)
+#count.display()
+
+--- grid-nested-with-footers ---
+// Nested table with footer should repeat both footers
+#set page(height: 10em, width: auto)
+#table(
+ table(
+ [a\ b\ c\ d],
+ table.footer[b],
+ ),
+ table.footer[a],
+)
+
+--- grid-nested-footers ---
+#set page(height: 12em, width: auto)
+#table(
+ [a\ b\ c\ d],
+ table.footer(table(
+ [c],
+ [d],
+ table.footer[b],
+ ))
+)
+
+--- grid-footer-rowspan ---
+// General footer-only tests
+#set page(height: 9em)
+#table(
+ columns: 2,
+ [a], [],
+ [b], [],
+ [c], [],
+ [d], [],
+ [e], [],
+ table.footer(
+ [*Ok*], table.cell(rowspan: 2)[test],
+ [*Thanks*]
+ )
+)
+
+--- grid-footer-bare-1 ---
+#set page(height: 5em)
+#table(
+ table.footer[a][b][c]
+)
+
+--- grid-footer-bare-2 ---
+#table(table.footer[a][b][c])
+
+#table(
+ gutter: 3pt,
+ table.footer[a][b][c]
+)
+
+--- grid-footer-stroke-edge-cases ---
+// Test footer stroke priority edge case
+#set page(height: 10em)
+#table(
+ columns: 2,
+ stroke: black,
+ ..(table.cell(stroke: aqua)[d],) * 8,
+ table.footer(
+ table.cell(rowspan: 2, colspan: 2)[a],
+ [c], [d]
+ )
+)
+
+--- grid-footer-hline-and-vline-1 ---
+// Footer should appear at the bottom. Red line should be above the footer.
+// Green line should be on the left border.
+#set page(margin: 2pt)
+#set text(6pt)
+#table(
+ columns: 2,
+ inset: 1.5pt,
+ table.cell(y: 0)[a],
+ table.cell(x: 1, y: 1)[a],
+ table.cell(y: 2)[a],
+ table.footer(
+ table.hline(stroke: red),
+ table.vline(stroke: green),
+ [b],
+ ),
+ table.cell(x: 1, y: 3)[c]
+)
+
+--- grid-footer-hline-and-vline-2 ---
+// Table should be just one row. [c] appears at the third column.
+#set page(margin: 2pt)
+#set text(6pt)
+#table(
+ columns: 3,
+ inset: 1.5pt,
+ table.cell(y: 0)[a],
+ table.footer(
+ table.hline(stroke: red),
+ table.hline(y: 1, stroke: aqua),
+ table.cell(y: 0)[b],
+ [c]
+ )
+)
+
+--- grid-footer-below-rowspans ---
+// Footer should go below the rowspans.
+#set page(margin: 2pt)
+#set text(6pt)
+#table(
+ columns: 2,
+ inset: 1.5pt,
+ table.cell(rowspan: 2)[a], table.cell(rowspan: 2)[b],
+ table.footer()
+)
diff --git a/tests/suite/layout/grid/grid.typ b/tests/suite/layout/grid/grid.typ
new file mode 100644
index 00000000..f4f0b90a
--- /dev/null
+++ b/tests/suite/layout/grid/grid.typ
@@ -0,0 +1,276 @@
+// Test grid layouts.
+
+--- grid-columns-sizings-rect ---
+#let cell(width, color) = rect(width: width, height: 2cm, fill: color)
+#set page(width: 100pt, height: 140pt)
+#grid(
+ columns: (auto, 1fr, 3fr, 0.25cm, 3%, 2mm + 10%),
+ cell(0.5cm, rgb("2a631a")),
+ cell(100%, forest),
+ cell(100%, conifer),
+ cell(100%, rgb("ff0000")),
+ cell(100%, rgb("00ff00")),
+ cell(80%, rgb("00faf0")),
+ cell(1cm, rgb("00ff00")),
+ cell(0.5cm, rgb("2a631a")),
+ cell(100%, forest),
+ cell(100%, conifer),
+ cell(100%, rgb("ff0000")),
+ cell(100%, rgb("00ff00")),
+)
+
+--- grid-gutter-fr ---
+#set rect(inset: 0pt)
+#grid(
+ columns: (auto, auto, 40%),
+ column-gutter: 1fr,
+ row-gutter: 1fr,
+ rect(fill: eastern)[dddaa aaa aaa],
+ rect(fill: conifer)[ccc],
+ rect(fill: rgb("dddddd"))[aaa],
+)
+
+--- grid-row-sizing-manual-align ---
+#set page(height: 3cm, margin: 0pt)
+#grid(
+ columns: (1fr,),
+ rows: (1fr, auto, 2fr),
+ [],
+ align(center)[A bit more to the top],
+ [],
+)
+
+--- grid-finance ---
+// Test using the `grid` function to create a finance table.
+#set page(width: 11cm, height: 2.5cm)
+#grid(
+ columns: 5,
+ column-gutter: (2fr, 1fr, 1fr),
+ row-gutter: 6pt,
+ [*Quarter*],
+ [Expenditure],
+ [External Revenue],
+ [Financial ROI],
+ [_total_],
+ [*Q1*],
+ [173,472.57 \$],
+ [472,860.91 \$],
+ [51,286.84 \$],
+ [_350,675.18 \$_],
+ [*Q2*],
+ [93,382.12 \$],
+ [439,382.85 \$],
+ [-1,134.30 \$],
+ [_344,866.43 \$_],
+ [*Q3*],
+ [96,421.49 \$],
+ [238,583.54 \$],
+ [3,497.12 \$],
+ [_145,659.17 \$_],
+)
+// Test grid cells that overflow to the next region.
+
+--- grid-cell-breaking ---
+#set page(width: 5cm, height: 3cm)
+#grid(
+ columns: 2,
+ row-gutter: 8pt,
+ [Lorem ipsum dolor sit amet.
+
+ Aenean commodo ligula eget dolor. Aenean massa. Penatibus et magnis.],
+ [Text that is rather short],
+ [Fireflies],
+ [Critical],
+ [Decorum],
+ [Rampage],
+)
+
+--- grid-consecutive-rows-breaking ---
+// Test a column that starts overflowing right after another row/column did
+// that.
+#set page(width: 5cm, height: 2cm)
+#grid(
+ columns: 4 * (1fr,),
+ row-gutter: 10pt,
+ column-gutter: (0pt, 10%),
+ align(top, image("/assets/images/rhino.png")),
+ align(top, rect(inset: 0pt, fill: eastern, align(right)[LoL])),
+ [rofl],
+ [\ A] * 3,
+ [Ha!\ ] * 3,
+)
+
+--- grid-same-row-multiple-columns-breaking ---
+// Test two columns in the same row overflowing by a different amount.
+#set page(width: 5cm, height: 2cm)
+#grid(
+ columns: 3 * (1fr,),
+ row-gutter: 8pt,
+ column-gutter: (0pt, 10%),
+ [A], [B], [C],
+ [Ha!\ ] * 6,
+ [rofl],
+ [\ A] * 3,
+ [hello],
+ [darkness],
+ [my old]
+)
+
+--- grid-nested-breaking ---
+// Test grid within a grid, overflowing.
+#set page(width: 5cm, height: 2.25cm)
+#grid(
+ columns: 4 * (1fr,),
+ row-gutter: 10pt,
+ column-gutter: (0pt, 10%),
+ [A], [B], [C], [D],
+ grid(columns: 2, [A], [B], [C\ ]*3, [D]),
+ align(top, rect(inset: 0pt, fill: eastern, align(right)[LoL])),
+ [rofl],
+ [E\ ]*4,
+)
+
+--- grid-column-sizing-auto-base ---
+// Test that auto and relative columns use the correct base.
+#grid(
+ columns: (auto, 60%),
+ rows: (auto, auto),
+ rect(width: 50%, height: 0.5cm, fill: conifer),
+ rect(width: 100%, height: 0.5cm, fill: eastern),
+ rect(width: 50%, height: 0.5cm, fill: forest),
+)
+
+--- grid-column-sizing-fr-base ---
+// Test that fr columns use the correct base.
+#grid(
+ columns: (1fr,) * 4,
+ rows: (1cm,),
+ rect(width: 50%, fill: conifer),
+ rect(width: 50%, fill: forest),
+ rect(width: 50%, fill: conifer),
+ rect(width: 50%, fill: forest),
+)
+
+--- grid-column-sizing-mixed-base ---
+// Test that all three kinds of rows use the correct bases.
+#set page(height: 4cm, margin: 0cm)
+#grid(
+ rows: (1cm, 1fr, 1fr, auto),
+ rect(height: 50%, width: 100%, fill: conifer),
+ rect(height: 50%, width: 100%, fill: forest),
+ rect(height: 50%, width: 100%, fill: conifer),
+ rect(height: 25%, width: 100%, fill: forest),
+)
+
+--- grid-trailing-linebreak-region-overflow ---
+// Test that trailing linebreak doesn't overflow the region.
+#set page(height: 2cm)
+#grid[
+ Hello \
+ Hello \
+ Hello \
+
+ World
+]
+
+--- grid-breaking-expand-vertically ---
+// Test that broken cell expands vertically.
+#set page(height: 2.25cm)
+#grid(
+ columns: 2,
+ gutter: 10pt,
+ align(bottom)[A],
+ [
+ Top
+ #align(bottom)[
+ Bottom \
+ Bottom \
+ #v(0pt)
+ Top
+ ]
+ ],
+ align(top)[B],
+)
+
+--- grid-complete-rows ---
+// Ensure grids expand enough for the given rows.
+#grid(
+ columns: (2em, 2em),
+ rows: (2em,) * 4,
+ fill: red,
+ stroke: aqua,
+ [a]
+)
+
+--- grid-auto-shrink ---
+// Test iterative auto column shrinking.
+#set page(width: 210mm - 2 * 2.5cm + 2 * 10pt)
+#set text(11pt)
+#table(
+ columns: 4,
+ [Hello!],
+ [Hello there, my friend!],
+ [Hello there, my friends! Hi!],
+ [Hello there, my friends! Hi! What is going on right now?],
+)
+
+--- issue-grid-base-auto-row ---
+// Test that grid base for auto rows makes sense.
+#set page(height: 150pt)
+#table(
+ columns: (1.5cm, auto),
+ rows: (auto, auto),
+ rect(width: 100%, fill: red),
+ rect(width: 100%, fill: blue),
+ rect(width: 100%, height: 50%, fill: green),
+)
+
+--- issue-grid-base-auto-row-list ---
+#rect(width: 100%, height: 1em)
+- #rect(width: 100%, height: 1em)
+ - #rect(width: 100%, height: 1em)
+
+--- issue-grid-skip ---
+// Grid now skips a remaining region when one of the cells
+// doesn't fit into it at all.
+#set page(height: 100pt)
+#grid(
+ columns: (2cm, auto),
+ rows: (auto, auto),
+ rect(width: 100%, fill: red),
+ rect(width: 100%, fill: blue),
+ rect(width: 100%, height: 80%, fill: green),
+ [hello \ darkness #parbreak() my \ old \ friend \ I],
+ rect(width: 100%, height: 20%, fill: blue),
+ polygon(fill: red, (0%, 0%), (100%, 0%), (100%, 20%))
+)
+
+--- issue-grid-skip-list ---
+#set page(height: 60pt)
+#lorem(5)
+- #lorem(5)
+
+--- issue-grid-double-skip ---
+// Ensure that the list does not jump to the third page.
+#set page(height: 70pt)
+#v(40pt)
+The following:
++ A
++ B
+
+--- issue-grid-gutter-skip ---
+// Ensure gutter rows at the top or bottom of a region are skipped.
+#set page(height: 10em)
+
+#table(
+ row-gutter: 1.5em,
+ inset: 0pt,
+ rows: (1fr, auto),
+ [a],
+ [],
+ [],
+ [f],
+ [e\ e],
+ [],
+ [a]
+)
diff --git a/tests/suite/layout/grid/headers.typ b/tests/suite/layout/grid/headers.typ
new file mode 100644
index 00000000..b9a90461
--- /dev/null
+++ b/tests/suite/layout/grid/headers.typ
@@ -0,0 +1,368 @@
+--- grid-headers ---
+#set page(width: auto, height: 12em)
+#table(
+ columns: 5,
+ align: center + horizon,
+ table.header(
+ table.cell(colspan: 5)[*Cool Zone*],
+ table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*],
+ table.hline(start: 2, end: 3, stroke: yellow)
+ ),
+ ..range(0, 6).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten()
+)
+
+--- grid-headers-no-repeat ---
+// Disable repetition
+#set page(width: auto, height: 12em)
+#table(
+ columns: 5,
+ align: center + horizon,
+ table.header(
+ table.cell(colspan: 5)[*Cool Zone*],
+ table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*],
+ table.hline(start: 2, end: 3, stroke: yellow),
+ repeat: false
+ ),
+ ..range(0, 6).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten()
+)
+
+--- grid-headers-gutter ---
+#set page(width: auto, height: 12em)
+#table(
+ columns: 5,
+ align: center + horizon,
+ gutter: 3pt,
+ table.header(
+ table.cell(colspan: 5)[*Cool Zone*],
+ table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*],
+ table.hline(start: 2, end: 3, stroke: yellow),
+ ),
+ ..range(0, 6).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten()
+)
+
+--- grid-header-relative-row-sizes ---
+// Relative lengths
+#set page(height: 10em)
+#table(
+ rows: (30%, 30%, auto),
+ table.header(
+ [*A*],
+ [*B*]
+ ),
+ [C],
+ [C]
+)
+
+--- grid-header-cell-with-y ---
+#grid(
+ grid.cell(y: 1)[a],
+ grid.header(grid.cell(y: 0)[b]),
+ grid.cell(y: 2)[c]
+)
+
+--- grid-header-last-child ---
+// When the header is the last grid child, it shouldn't include the gutter row
+// after it, because there is none.
+#grid(
+ columns: 2,
+ gutter: 3pt,
+ grid.header(
+ [a], [b],
+ [c], [d]
+ )
+)
+
+--- grid-header-nested ---
+#set page(height: 14em)
+#let t(n) = table(
+ columns: 3,
+ align: center + horizon,
+ gutter: 3pt,
+ table.header(
+ table.cell(colspan: 3)[*Cool Zone #n*],
+ [*Name*], [*Num*], [*Data*]
+ ),
+ ..range(0, 5).map(i => ([\##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456])).flatten()
+)
+#grid(
+ gutter: 3pt,
+ t(0),
+ t(1)
+)
+
+--- grid-header-hline-and-vline ---
+// Test line positioning in header
+#table(
+ columns: 3,
+ stroke: none,
+ table.hline(stroke: red, end: 2),
+ table.vline(stroke: red, end: 3),
+ table.header(
+ table.hline(stroke: aqua, start: 2),
+ table.vline(stroke: aqua, start: 3), [*A*], table.hline(stroke: orange), table.vline(stroke: orange), [*B*],
+ [*C*], [*D*]
+ ),
+ [a], [b],
+ [c], [d],
+ [e], [f]
+)
+
+--- grid-header-not-at-first-row ---
+// Error: 3:3-3:19 header must start at the first row
+// Hint: 3:3-3:19 remove any rows before the header
+#grid(
+ [a],
+ grid.header([b])
+)
+
+--- grid-header-not-at-first-row-two-columns ---
+// Error: 4:3-4:19 header must start at the first row
+// Hint: 4:3-4:19 remove any rows before the header
+#grid(
+ columns: 2,
+ [a],
+ grid.header([b])
+)
+
+--- grow-header-multiple ---
+// Error: 3:3-3:19 cannot have more than one header
+#grid(
+ grid.header([a]),
+ grid.header([b]),
+ [a],
+)
+
+--- table-header-in-grid ---
+// Error: 2:3-2:20 cannot use `table.header` as a grid header; use `grid.header` instead
+#grid(
+ table.header([a]),
+ [a],
+)
+
+--- grid-header-in-table ---
+// Error: 2:3-2:19 cannot use `grid.header` as a table header; use `table.header` instead
+#table(
+ grid.header([a]),
+ [a],
+)
+
+--- grid-header-in-grid-header ---
+// Error: 14-28 cannot place a grid header within another header or footer
+#grid.header(grid.header[a])
+
+--- table-header-in-grid-header ---
+// Error: 14-29 cannot place a table header within another header or footer
+#grid.header(table.header[a])
+
+--- grid-header-in-table-header ---
+// Error: 15-29 cannot place a grid header within another header or footer
+#table.header(grid.header[a])
+
+--- table-header-in-table-header ---
+// Error: 15-30 cannot place a table header within another header or footer
+#table.header(table.header[a])
+
+--- grid-header-block-with-fixed-height ---
+#set page(height: 15em)
+#table(
+ rows: (auto, 2.5em, auto),
+ table.header(
+ [*Hello*],
+ [*World*]
+ ),
+ block(width: 2em, height: 20em, fill: red)
+)
+
+--- grid-header-and-rowspan-non-contiguous-1 ---
+// Rowspan sizing algorithm doesn't do the best job at non-contiguous content
+// ATM.
+#set page(height: 15em)
+
+#table(
+ rows: (auto, 2.5em, 2em, auto, 5em),
+ table.header(
+ [*Hello*],
+ [*World*]
+ ),
+ table.cell(rowspan: 3, lorem(40))
+)
+
+--- grid-header-and-rowspan-non-contiguous-2 ---
+// Rowspan sizing algorithm doesn't do the best job at non-contiguous content
+// ATM.
+#set page(height: 15em)
+
+#table(
+ rows: (auto, 2.5em, 2em, auto, 5em),
+ gutter: 3pt,
+ table.header(
+ [*Hello*],
+ [*World*]
+ ),
+ table.cell(rowspan: 3, lorem(40))
+)
+
+--- grid-header-and-rowspan-non-contiguous-3 ---
+// This should look right
+#set page(height: 15em)
+
+#table(
+ rows: (auto, 2.5em, 2em, auto),
+ gutter: 3pt,
+ table.header(
+ [*Hello*],
+ [*World*]
+ ),
+ table.cell(rowspan: 3, lorem(40))
+)
+
+--- grid-header-lack-of-space ---
+// Test lack of space for header + text.
+#set page(height: 9em)
+
+#table(
+ rows: (auto, 2.5em, auto, auto, 10em),
+ gutter: 3pt,
+ table.header(
+ [*Hello*],
+ [*World*]
+ ),
+ table.cell(rowspan: 3, lorem(80))
+)
+
+--- grid-header-orphan-prevention ---
+// Orphan header prevention test
+#set page(height: 12em)
+#v(8em)
+#grid(
+ columns: 3,
+ grid.header(
+ [*Mui*], [*A*], grid.cell(rowspan: 2, fill: orange)[*B*],
+ [*Header*], [*Header* #v(0.1em)]
+ ),
+ ..([Test], [Test], [Test]) * 20
+)
+
+--- grid-header-empty ---
+// Empty header should just be a repeated blank row
+#set page(height: 12em)
+#table(
+ columns: 4,
+ align: center + horizon,
+ table.header(),
+ ..range(0, 4).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789])).flatten()
+)
+
+--- grid-header-containing-rowspan ---
+// When a header has a rowspan with an empty row, it should be displayed
+// properly
+#set page(height: 10em)
+
+#let count = counter("g")
+#table(
+ rows: (auto, 2em, auto, auto),
+ table.header(
+ [eeec],
+ table.cell(rowspan: 2, count.step() + count.display()),
+ ),
+ [d],
+ block(width: 5em, fill: yellow, lorem(15)),
+ [d]
+)
+#count.display()
+
+--- grid-header-expand ---
+// Ensure header expands to fit cell placed in it after its declaration
+#set page(height: 10em)
+#table(
+ columns: 2,
+ table.header(
+ [a], [b],
+ [c],
+ ),
+ table.cell(x: 1, y: 1, rowspan: 2, lorem(80))
+)
+
+--- grid-nested-with-headers ---
+// Nested table with header should repeat both headers
+#set page(height: 10em)
+#table(
+ table.header(
+ [a]
+ ),
+ table(
+ table.header(
+ [b]
+ ),
+ [a\ b\ c\ d]
+ )
+)
+
+--- grid-nested-headers ---
+#set page(height: 12em)
+#table(
+ table.header(
+ table(
+ table.header(
+ [b]
+ ),
+ [c],
+ [d]
+ )
+ ),
+ [a\ b]
+)
+
+--- grid-header-stroke-edge-cases ---
+// Test header stroke priority edge case (last header row removed)
+#set page(height: 8em)
+#table(
+ columns: 2,
+ stroke: black,
+ gutter: (auto, 3pt),
+ table.header(
+ [c], [d],
+ ),
+ ..(table.cell(stroke: aqua)[d],) * 8,
+)
+
+--- grid-header-hline-bottom ---
+// Yellow line should be kept here
+#set text(6pt)
+#table(
+ column-gutter: 3pt,
+ inset: 1pt,
+ table.header(
+ [a],
+ table.hline(stroke: yellow),
+ ),
+ table.cell(rowspan: 2)[b]
+)
+
+--- grid-header-hline-bottom-manually ---
+// Red line should be kept here
+#set page(height: 6em)
+#set text(6pt)
+#table(
+ column-gutter: 3pt,
+ inset: 1pt,
+ table.header(
+ table.hline(stroke: red, position: bottom),
+ [a],
+ ),
+ [a],
+ table.cell(stroke: aqua)[b]
+)
+
+--- grid-header-rowspan-base ---
+#set page(height: 7em)
+#set text(6pt)
+#let full-block = block(width: 2em, height: 100%, fill: red)
+#table(
+ columns: 3,
+ inset: 1.5pt,
+ table.header(
+ [a], full-block, table.cell(rowspan: 2, full-block),
+ [b]
+ )
+)
diff --git a/tests/suite/layout/grid/positioning.typ b/tests/suite/layout/grid/positioning.typ
new file mode 100644
index 00000000..31896d99
--- /dev/null
+++ b/tests/suite/layout/grid/positioning.typ
@@ -0,0 +1,203 @@
+// Test cell positioning in grids.
+
+--- grid-cell-show-x-y ---
+#{
+ show grid.cell: it => (it.x, it.y)
+ grid(
+ columns: 2,
+ inset: 5pt,
+ fill: aqua,
+ gutter: 3pt,
+ [Hello], [World],
+ [Sweet], [Home]
+ )
+}
+#{
+ show table.cell: it => pad(rest: it.inset)[#(it.x, it.y)]
+ table(
+ columns: 2,
+ gutter: 3pt,
+ [Hello], [World],
+ [Sweet], [Home]
+ )
+}
+
+--- grid-cell-position-out-of-order ---
+// Positioning cells in a different order than they appear
+#grid(
+ columns: 2,
+ [A], [B],
+ grid.cell(x: 1, y: 2)[C], grid.cell(x: 0, y: 2)[D],
+ grid.cell(x: 1, y: 1)[E], grid.cell(x: 0, y: 1)[F],
+)
+
+--- grid-cell-position-extra-rows ---
+// Creating more rows by positioning out of bounds
+#grid(
+ columns: 3,
+ rows: 1.5em,
+ inset: 5pt,
+ fill: (x, y) => if (x, y) == (0, 0) { blue } else if (x, y) == (2, 3) { red } else { green },
+ [A],
+ grid.cell(x: 2, y: 3)[B]
+)
+
+#table(
+ columns: (3em, 1em, 3em),
+ rows: 1.5em,
+ inset: (top: 0pt, bottom: 0pt, rest: 5pt),
+ fill: (x, y) => if (x, y) == (0, 0) { blue } else if (x, y) == (2, 3) { red } else { green },
+ align: (x, y) => (left, center, right).at(x),
+ [A],
+ table.cell(x: 2, y: 3)[B]
+)
+
+--- grid-cell-position-collide ---
+// Error: 3:3-3:42 attempted to place a second cell at column 0, row 0
+// Hint: 3:3-3:42 try specifying your cells in a different order
+#grid(
+ [A],
+ grid.cell(x: 0, y: 0)[This shall error]
+)
+
+--- table-cell-position-collide ---
+// Error: 3:3-3:43 attempted to place a second cell at column 0, row 0
+// Hint: 3:3-3:43 try specifying your cells in a different order
+#table(
+ [A],
+ table.cell(x: 0, y: 0)[This shall error]
+)
+
+--- grid-cell-position-automatic-skip-manual ---
+// Automatic position cell skips custom position cell
+#grid(
+ grid.cell(x: 0, y: 0)[This shall not error],
+ [A]
+)
+
+--- grid-cell-position-x-out-of-bounds ---
+// Error: 4:3-4:36 cell could not be placed at invalid column 2
+#grid(
+ columns: 2,
+ [A],
+ grid.cell(x: 2)[This shall error]
+)
+
+--- grid-cell-position-partial ---
+// Partial positioning
+#grid(
+ columns: 3,
+ rows: 1.5em,
+ inset: 5pt,
+ fill: aqua,
+ [A], grid.cell(y: 1, fill: green)[B], [C], grid.cell(x: auto, y: 1, fill: green)[D], [E],
+ grid.cell(y: 2, fill: green)[F], grid.cell(x: 0, fill: orange)[G], grid.cell(x: 0, y: auto, fill: orange)[H],
+ grid.cell(x: 1, fill: orange)[I]
+)
+
+#table(
+ columns: 3,
+ rows: 1.5em,
+ inset: 5pt,
+ fill: aqua,
+ [A], table.cell(y: 1, fill: green)[B], [C], table.cell(x: auto, y: 1, fill: green)[D], [E],
+ table.cell(y: 2, fill: green)[F], table.cell(x: 0, fill: orange)[G], table.cell(x: 0, y: auto, fill: orange)[H],
+ table.cell(x: 1, fill: orange)[I]
+)
+
+--- grid-cell-position-partial-collide ---
+// Error: 4:3-4:21 cell could not be placed in row 0 because it was full
+// Hint: 4:3-4:21 try specifying your cells in a different order
+#grid(
+ columns: 2,
+ [A], [B],
+ grid.cell(y: 0)[C]
+)
+
+--- table-cell-position-partial-collide ---
+// Error: 4:3-4:22 cell could not be placed in row 0 because it was full
+// Hint: 4:3-4:22 try specifying your cells in a different order
+#table(
+ columns: 2,
+ [A], [B],
+ table.cell(y: 0)[C]
+)
+
+--- grid-calendar ---
+#set page(width: auto)
+#show grid.cell: it => {
+ if it.y == 0 {
+ set text(white)
+ strong(it)
+ } else {
+ // For the second row and beyond, we will write the day number for each
+ // cell.
+
+ // In general, a cell's index is given by cell.x + columns * cell.y.
+ // Days start in the second grid row, so we subtract 1 row.
+ // But the first day is day 1, not day 0, so we add 1.
+ let day = it.x + 7 * (it.y - 1) + 1
+ if day <= 31 {
+ // Place the day's number at the top left of the cell.
+ // Only if the day is valid for this month (not 32 or higher).
+ place(top + left, dx: 2pt, dy: 2pt, text(8pt, red.darken(40%))[#day])
+ }
+ it
+ }
+}
+
+#grid(
+ fill: (x, y) => if y == 0 { gray.darken(50%) },
+ columns: (30pt,) * 7,
+ rows: (auto, 30pt),
+ // Events will be written at the bottom of each day square.
+ align: bottom,
+ inset: 5pt,
+ stroke: (thickness: 0.5pt, dash: "densely-dotted"),
+
+ [Sun], [Mon], [Tue], [Wed], [Thu], [Fri], [Sat],
+
+ // This event will occur on the first Friday (sixth column).
+ grid.cell(x: 5, fill: yellow.darken(10%))[Call],
+
+ // This event will occur every Monday (second column).
+ // We have to repeat it 5 times so it occurs every week.
+ ..(grid.cell(x: 1, fill: red.lighten(50%))[Meet],) * 5,
+
+ // This event will occur at day 19.
+ grid.cell(x: 4, y: 3, fill: orange.lighten(25%))[Talk],
+
+ // These events will occur at the second week, where available.
+ grid.cell(y: 2, fill: aqua)[Chat],
+ grid.cell(y: 2, fill: aqua)[Walk],
+)
+
+--- grid-exam ---
+#set page(width: auto)
+#show table.cell: it => {
+ if it.x == 0 or it.y == 0 {
+ set text(white)
+ strong(it)
+ } else if it.body == [] {
+ // Replace empty cells with 'N/A'
+ pad(rest: it.inset)[_N/A_]
+ } else {
+ it
+ }
+}
+
+#table(
+ fill: (x, y) => if x == 0 or y == 0 { gray.darken(50%) },
+ columns: 4,
+ [], [Exam 1], [Exam 2], [Exam 3],
+ ..([John], [Mary], [Jake], [Robert]).map(table.cell.with(x: 0)),
+
+ // Mary got grade A on Exam 3.
+ table.cell(x: 3, y: 2, fill: green)[A],
+
+ // Everyone got grade A on Exam 2.
+ ..(table.cell(x: 2, fill: green)[A],) * 4,
+
+ // Robert got grade B on other exams.
+ ..(table.cell(y: 4, fill: aqua)[B],) * 2,
+)
diff --git a/tests/suite/layout/grid/rowspan.typ b/tests/suite/layout/grid/rowspan.typ
new file mode 100644
index 00000000..f7a377b6
--- /dev/null
+++ b/tests/suite/layout/grid/rowspan.typ
@@ -0,0 +1,490 @@
+--- grid-rowspan ---
+#grid(
+ columns: 4,
+ fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
+ inset: 5pt,
+ align: center,
+ grid.cell(rowspan: 2, fill: orange)[*Left*],
+ [Right A], [Right A], [Right A],
+ [Right B], grid.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide],
+ [Left A], [Left A],
+ [Left B], [Left B], grid.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long]
+)
+
+#table(
+ columns: 4,
+ fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
+ inset: 5pt,
+ align: center,
+ table.cell(rowspan: 2, fill: orange)[*Left*],
+ [Right A], [Right A], [Right A],
+ [Right B], table.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide],
+ [Left A], [Left A],
+ [Left B], [Left B], table.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long]
+)
+
+--- grid-rowspan-gutter ---
+#grid(
+ columns: 4,
+ fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
+ inset: 5pt,
+ align: center,
+ gutter: 3pt,
+ grid.cell(rowspan: 2, fill: orange)[*Left*],
+ [Right A], [Right A], [Right A],
+ [Right B], grid.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide],
+ [Left A], [Left A],
+ [Left B], [Left B], grid.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long]
+)
+
+#table(
+ columns: 4,
+ fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
+ inset: 5pt,
+ align: center,
+ gutter: 3pt,
+ table.cell(rowspan: 2, fill: orange)[*Left*],
+ [Right A], [Right A], [Right A],
+ [Right B], table.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide],
+ [Left A], [Left A],
+ [Left B], [Left B], table.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long]
+)
+
+--- grid-rowspan-fixed-size ---
+// Fixed-size rows
+#set page(height: 10em)
+#grid(
+ columns: 2,
+ rows: 1.5em,
+ fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
+ grid.cell(rowspan: 3)[R1], [b],
+ [c],
+ [d],
+ [e], [f],
+ grid.cell(rowspan: 5)[R2], [h],
+ [i],
+ [j],
+ [k],
+ [l],
+ [m], [n]
+)
+
+--- grid-rowspan-cell-coordinates ---
+// Cell coordinate tests
+#set page(height: 10em)
+#show table.cell: it => [(#it.x, #it.y)]
+#table(
+ columns: 3,
+ fill: red,
+ [a], [b], table.cell(rowspan: 2)[c],
+ table.cell(colspan: 2)[d],
+ table.cell(colspan: 3, rowspan: 10)[a],
+ table.cell(colspan: 2)[b],
+)
+#table(
+ columns: 3,
+ gutter: 3pt,
+ fill: red,
+ [a], [b], table.cell(rowspan: 2)[c],
+ table.cell(colspan: 2)[d],
+ table.cell(colspan: 3, rowspan: 9)[a],
+ table.cell(colspan: 2)[b],
+)
+
+--- grid-rowspan-over-auto-row ---
+// Auto row expansion
+#set page(height: 10em)
+#grid(
+ columns: (1em, 1em),
+ rows: (0.5em, 0.5em, auto),
+ fill: orange,
+ gutter: 3pt,
+ grid.cell(rowspan: 4, [x x x x] + place(bottom)[*Bot*]),
+ [a],
+ [b],
+ [c],
+ [d]
+)
+
+--- grid-rowspan-excessive ---
+// Excessive rowspan (no gutter)
+#set page(height: 10em)
+#table(
+ columns: 4,
+ fill: red,
+ [a], [b], table.cell(rowspan: 2)[c], [d],
+ table.cell(colspan: 2, stroke: (bottom: aqua + 2pt))[e], table.cell(stroke: (bottom: aqua))[f],
+ table.cell(colspan: 2, rowspan: 10)[R1], table.cell(colspan: 2, rowspan: 10)[R2],
+ [b],
+)
+
+--- grid-rowspan-excessive-gutter ---
+// Excessive rowspan (with gutter)
+#set page(height: 10em)
+#table(
+ columns: 4,
+ gutter: 3pt,
+ fill: red,
+ [a], [b], table.cell(rowspan: 2)[c], [d],
+ table.cell(colspan: 2, stroke: (bottom: aqua + 2pt))[e], table.cell(stroke: (bottom: aqua))[f],
+ table.cell(colspan: 2, rowspan: 10)[R1], table.cell(colspan: 2, rowspan: 10)[R2],
+ [b],
+)
+
+--- grid-rowspan-over-fr-row-at-end ---
+// Fractional rows
+// They cause the auto row to expand more than needed.
+#set page(height: 10em)
+#grid(
+ fill: red,
+ gutter: 3pt,
+ columns: 3,
+ rows: (1em, auto, 1fr),
+ [a], [b], grid.cell(rowspan: 3, block(height: 4em, width: 1em, fill: orange)),
+ [c], [d],
+ [e], [f]
+)
+
+--- grid-rowspan-over-fr-row-at-start ---
+// Fractional rows
+#set page(height: 10em)
+#grid(
+ fill: red,
+ gutter: 3pt,
+ columns: 3,
+ rows: (1fr, auto, 1em),
+ [a], [b], grid.cell(rowspan: 3, block(height: 4em, width: 1em, fill: orange)),
+ [c], [d],
+ [e], [f]
+)
+
+--- grid-rowspan-cell-order ---
+// Cell order
+#let count = counter("count")
+#show grid.cell: it => {
+ count.step()
+ count.display()
+}
+
+#grid(
+ columns: (2em,) * 3,
+ stroke: aqua,
+ rows: 1.2em,
+ fill: (x, y) => if calc.odd(x + y) { red } else { orange },
+ [a], grid.cell(rowspan: 2)[b], grid.cell(rowspan: 2)[c],
+ [d],
+ grid.cell(rowspan: 2)[f], [g], [h],
+ [i], [j],
+ [k], [l], [m],
+ grid.cell(rowspan: 2)[n], [o], [p],
+ [q], [r],
+ [s], [t], [u]
+)
+
+--- grid-rowspan-unbreakable-1 ---
+#table(
+ columns: 3,
+ rows: (auto, auto, auto, 2em),
+ gutter: 3pt,
+ table.cell(rowspan: 4)[a \ b\ c\ d\ e], [c], [d],
+ [e], table.cell(breakable: false, rowspan: 2)[f],
+ [g]
+)
+
+--- grid-rowspan-unbreakable-2 ---
+// Test cell breakability
+#show grid.cell: it => {
+ assert.eq(it.breakable, (it.x, it.y) != (0, 6) and (it.y in (2, 5, 6) or (it.x, it.y) in ((0, 1), (2, 3), (1, 7))))
+ it.breakable
+}
+#grid(
+ columns: 3,
+ rows: (6pt, 1fr, auto, 1%, 1em, auto, auto, 0.2in),
+ row-gutter: (0pt, 0pt, 0pt, auto),
+ [a], [b], [c],
+ grid.cell(rowspan: 3)[d], [e], [f],
+ [g], [h],
+ [i], grid.cell(rowspan: 2)[j],
+ [k],
+ grid.cell(y: 5)[l],
+ grid.cell(y: 6, breakable: false)[m], grid.cell(y: 6, breakable: true)[n],
+ grid.cell(y: 7, breakable: false)[o], grid.cell(y: 7, breakable: true)[p], grid.cell(y: 7, breakable: auto)[q]
+)
+
+--- grid-rowspan-in-all-columns-stroke ---
+#table(
+ columns: 2,
+ table.cell(stroke: (bottom: red))[a], [b],
+ table.hline(stroke: green),
+ table.cell(stroke: (top: yellow, left: green, right: aqua, bottom: blue), colspan: 1, rowspan: 2)[d], table.cell(colspan: 1, rowspan: 2)[e],
+ [f],
+ [g]
+)
+
+--- grid-rowspan-in-all-columns-stroke-gutter ---
+#table(
+ columns: 2,
+ gutter: 3pt,
+ table.cell(stroke: (bottom: red))[a], [b],
+ table.hline(stroke: green),
+ table.cell(stroke: (top: yellow, left: green, right: aqua, bottom: blue), colspan: 1, rowspan: 2)[d], table.cell(colspan: 1, rowspan: 2)[e],
+ [f],
+ [g]
+)
+
+--- grid-rowspan-block-full-height ---
+// Block below shouldn't expand to the end of the page, but stay within its
+// rows' boundaries.
+#set page(height: 9em)
+#table(
+ rows: (1em, 1em, 1fr, 1fr, auto),
+ table.cell(rowspan: 2, block(width: 2em, height: 100%, fill: red)),
+ table.cell(rowspan: 2, block(width: 2em, height: 100%, fill: red)),
+ [a]
+)
+
+--- grid-rowspan-block-overflow ---
+#set page(height: 7em)
+#table(
+ columns: 3,
+ [], [], table.cell(breakable: true, rowspan: 2, block(width: 2em, height: 100%, fill: red)),
+ table.cell(breakable: false, block(width: 2em, height: 100%, fill: red)),
+ table.cell(breakable: false, rowspan: 2, block(width: 2em, height: 100%, fill: red)),
+)
+
+// Rowspan split tests
+
+--- grid-rowspan-split-1 ---
+#set page(height: 10em)
+#table(
+ columns: 2,
+ rows: (auto, auto, 3em),
+ fill: red,
+ [a], table.cell(rowspan: 3, block(width: 50%, height: 10em, fill: orange) + place(bottom)[*ZD*]),
+ [e],
+ [f]
+)
+
+--- grid-rowspan-split-2 ---
+#set page(height: 10em)
+#table(
+ columns: 2,
+ rows: (auto, auto, 3em),
+ row-gutter: 1em,
+ fill: red,
+ [a], table.cell(rowspan: 3, block(width: 50%, height: 10em, fill: orange) + place(bottom)[*ZD*]),
+ [e],
+ [f]
+)
+
+--- grid-rowspan-split-3 ---
+#set page(height: 5em)
+#table(
+ columns: 2,
+ fill: red,
+ inset: 0pt,
+ table.cell(fill: orange, rowspan: 10, place(bottom)[*Z*] + [x\ ] * 10 + place(bottom)[*ZZ*]),
+ ..([y],) * 10,
+ [a], [b],
+)
+
+--- grid-rowspan-split-4 ---
+#set page(height: 5em)
+#table(
+ columns: 2,
+ fill: red,
+ inset: 0pt,
+ gutter: 2pt,
+ table.cell(fill: orange, rowspan: 10, place(bottom)[*Z*] + [x\ ] * 10 + place(bottom)[*ZZ*]),
+ ..([y],) * 10,
+ [a], [b],
+)
+
+--- grid-rowspan-split-5 ---
+#set page(height: 5em)
+#table(
+ columns: 2,
+ fill: red,
+ inset: 0pt,
+ table.cell(fill: orange, rowspan: 10, breakable: false, place(bottom)[*Z*] + [x\ ] * 10 + place(bottom)[*ZZ*]),
+ ..([y],) * 10,
+ [a], [b],
+)
+
+--- grid-rowspan-split-6 ---
+#set page(height: 5em)
+#table(
+ columns: 2,
+ fill: red,
+ inset: 0pt,
+ gutter: 2pt,
+ table.cell(fill: orange, rowspan: 10, breakable: false, place(bottom)[*Z*] + [x\ ] * 10 + place(bottom)[*ZZ*]),
+ ..([y],) * 10,
+ [a], [b],
+)
+
+--- grid-rowspan-split-7 ---
+#set page(height: 5em)
+#grid(
+ columns: 2,
+ stroke: red,
+ inset: 5pt,
+ grid.cell(rowspan: 5)[a\ b\ c\ d\ e]
+)
+
+--- grid-rowspan-split-8 ---
+#set page(height: 5em)
+#table(
+ columns: 2,
+ gutter: 3pt,
+ stroke: red,
+ inset: 5pt,
+ table.cell(rowspan: 5)[a\ b\ c\ d\ e]
+)
+
+// Rowspan split without ending at the auto row
+
+--- grid-rowspan-split-9 ---
+#set page(height: 6em)
+#table(
+ rows: (4em,) * 7 + (auto,) + (4em,) * 7,
+ columns: 2,
+ column-gutter: 1em,
+ row-gutter: (1em, 2em) * 4,
+ fill: (x, y) => if calc.odd(x + y) { orange.lighten(20%) } else { red },
+ table.cell(rowspan: 15, [a \ ] * 15),
+ [] * 15
+)
+
+--- grid-rowspan-split-10 ---
+#set page(height: 6em)
+#table(
+ rows: (4em,) * 7 + (auto,) + (4em,) * 7,
+ columns: 2,
+ column-gutter: 1em,
+ row-gutter: (1em, 2em) * 4,
+ fill: (x, y) => if calc.odd(x + y) { green } else { green.darken(40%) },
+ table.cell(rowspan: 15, block(fill: blue, width: 2em, height: 4em * 14 + 3em)),
+ [] * 15
+)
+
+--- grid-rowspan-split-11 ---
+#set page(height: 6em)
+#table(
+ rows: (3em,) * 15,
+ columns: 2,
+ column-gutter: 1em,
+ row-gutter: (1em, 2em) * 4,
+ fill: (x, y) => if calc.odd(x + y) { aqua } else { blue },
+ table.cell(breakable: true, rowspan: 15, [a \ ] * 15),
+ [] * 15
+)
+
+// Some splitting corner cases
+
+--- grid-rowspan-split-12 ---
+// Inside the larger rowspan's range, there's an unbreakable rowspan and a
+// breakable rowspan. This should work normally.
+// The auto row will also expand ignoring the last fractional row.
+#set page(height: 10em)
+#table(
+ gutter: 0.5em,
+ columns: 2,
+ rows: (2em,) * 10 + (auto, auto, 2em, 1fr),
+ fill: (_, y) => if calc.even(y) { aqua } else { blue },
+ table.cell(rowspan: 14, block(width: 2em, height: 2em * 10 + 2em + 5em, fill: red)[]),
+ ..([a],) * 5,
+ table.cell(rowspan: 3)[a\ b],
+ table.cell(rowspan: 5, [a\ b\ c\ d\ e\ f\ g\ h]),
+ [z]
+)
+
+--- grid-rowspan-split-13 ---
+// Inset moving to next region bug
+#set page(width: 10cm, height: 2.5cm, margin: 0.5cm)
+#set text(size: 11pt)
+#table(
+ columns: (1fr, 1fr, 1fr),
+ [A],
+ [B],
+ [C],
+ [D],
+ table.cell(rowspan: 2, lorem(4)),
+ [E],
+ [F],
+ [G],
+)
+
+--- grid-rowspan-split-14 ---
+// Second lorem must be sent to the next page, too big
+#set page(width: 10cm, height: 9cm, margin: 1cm)
+#set text(size: 11pt)
+#table(
+ columns: (1fr, 1fr, 1fr),
+ align: center,
+ rows: (4cm, auto),
+ [A], [B], [C],
+ table.cell(rowspan: 4, breakable: false, lorem(10)),
+ [D],
+ table.cell(rowspan: 2, breakable: false, lorem(20)),
+ [E],
+)
+
+--- grid-rowspan-split-15 ---
+// Auto row must expand properly in both cases
+#set text(10pt)
+#show table.cell: it => if it.x == 0 { it } else { layout(size => size.height) }
+#table(
+ columns: 2,
+ rows: (1em, auto, 2em, 3em, 4em),
+ gutter: 3pt,
+ table.cell(rowspan: 5, block(fill: orange, height: 15em)[a]),
+ [b],
+ [c],
+ [d],
+ [e],
+ [f]
+)
+
+#table(
+ columns: 2,
+ rows: (1em, auto, 2em, 3em, 4em),
+ gutter: 3pt,
+ table.cell(rowspan: 5, breakable: false, block(fill: orange, height: 15em)[a]),
+ [b],
+ [c],
+ [d],
+ [e],
+ [f]
+)
+
+--- grid-rowspan-split-16 ---
+// Expanding on unbreakable auto row
+#set page(height: 7em, margin: (bottom: 2em))
+#grid(
+ columns: 2,
+ rows: (1em, 1em, auto, 1em, 1em, 1em),
+ fill: (x, y) => if x == 0 { aqua } else { blue },
+ stroke: black,
+ gutter: 2pt,
+ grid.cell(rowspan: 5, block(height: 10em)[a]),
+ [a],
+ [b],
+ grid.cell(breakable: false, v(3em) + [c]),
+ [d],
+ [e],
+ [f], [g]
+)
+
+--- grid-rowspan-split-17 ---
+#show table.cell.where(x: 0): strong
+#show table.cell.where(y: 0): strong
+#set page(height: 13em)
+#let lets-repeat(thing, n) = ((thing + colbreak(),) * (calc.max(0, n - 1)) + (thing,)).join()
+#table(
+ columns: 4,
+ fill: (x, y) => if x == 0 or y == 0 { gray },
+ [], [Test 1], [Test 2], [Test 3],
+ table.cell(rowspan: 15, align: horizon, lets-repeat((rotate(-90deg, reflow: true)[*All Tests*]), 3)),
+ ..([123], [456], [789]) * 15
+)
diff --git a/tests/suite/layout/grid/rtl.typ b/tests/suite/layout/grid/rtl.typ
new file mode 100644
index 00000000..7c0e999a
--- /dev/null
+++ b/tests/suite/layout/grid/rtl.typ
@@ -0,0 +1,195 @@
+// Test RTL grid.
+
+--- list-rtl ---
+#set text(dir: rtl)
+- מימין לשמאל
+
+--- grid-rtl ---
+#set text(dir: rtl)
+#table(columns: 2)[A][B][C][D]
+
+--- grid-rtl-colspan ---
+// Test interaction between RTL and colspans
+#set text(dir: rtl)
+#grid(
+ columns: 4,
+ fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
+ inset: 5pt,
+ align: center,
+ grid.cell(colspan: 4)[*Full Header*],
+ grid.cell(colspan: 2, fill: orange)[*Half*],
+ grid.cell(colspan: 2, fill: orange.darken(10%))[*Half*],
+ [*A*], [*B*], [*C*], [*D*],
+ [1], [2], [3], [4],
+ [5], grid.cell(colspan: 3, fill: orange.darken(10%))[6],
+ grid.cell(colspan: 2, fill: orange)[7], [8], [9],
+ [10], grid.cell(colspan: 2, fill: orange.darken(10%))[11], [12]
+)
+
+#grid(
+ columns: 4,
+ fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
+ inset: 5pt,
+ align: center,
+ gutter: 3pt,
+ grid.cell(colspan: 4)[*Full Header*],
+ grid.cell(colspan: 2, fill: orange)[*Half*],
+ grid.cell(colspan: 2, fill: orange.darken(10%))[*Half*],
+ [*A*], [*B*], [*C*], [*D*],
+ [1], [2], [3], [4],
+ [5], grid.cell(colspan: 3, fill: orange.darken(10%))[6],
+ grid.cell(colspan: 2, fill: orange)[7], [8], [9],
+ [10], grid.cell(colspan: 2, fill: orange.darken(10%))[11], [12]
+)
+
+--- grid-rtl-colspan-stroke ---
+#set text(dir: rtl)
+#table(
+ columns: 4,
+ fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
+ inset: 5pt,
+ align: center,
+ table.cell(colspan: 4)[*Full Header*],
+ table.cell(colspan: 2, fill: orange)[*Half*],
+ table.cell(colspan: 2, fill: orange.darken(10%))[*Half*],
+ [*A*], [*B*], [*C*], [*D*],
+ [1], [2], [3], [4],
+ [5], table.cell(colspan: 3, fill: orange.darken(10%))[6],
+ table.cell(colspan: 2, fill: orange)[7], [8], [9],
+ [10], table.cell(colspan: 2, fill: orange.darken(10%))[11], [12]
+)
+
+#table(
+ columns: 4,
+ fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
+ inset: 5pt,
+ align: center,
+ gutter: 3pt,
+ table.cell(colspan: 4)[*Full Header*],
+ table.cell(colspan: 2, fill: orange)[*Half*],
+ table.cell(colspan: 2, fill: orange.darken(10%))[*Half*],
+ [*A*], [*B*], [*C*], [*D*],
+ [1], [2], [3], [4],
+ [5], table.cell(colspan: 3, fill: orange.darken(10%))[6],
+ table.cell(colspan: 2, fill: orange)[7], [8], [9],
+ [10], table.cell(colspan: 2, fill: orange.darken(10%))[11], [12]
+)
+
+--- grid-rtl-multiple-regions ---
+// Test multiple regions
+#set page(height: 5em)
+#set text(dir: rtl)
+#grid(
+ stroke: red,
+ fill: aqua,
+ columns: 4,
+ [a], [b], [c], [d],
+ [a], grid.cell(colspan: 2)[e, f, g, h, i], [f],
+ [e], [g], grid.cell(colspan: 2)[eee\ e\ e\ e],
+ grid.cell(colspan: 4)[eeee e e e]
+)
+
+--- grid-rtl-vline-position ---
+// Test left and right for vlines in RTL
+#set text(dir: rtl)
+#grid(
+ columns: 3,
+ inset: 5pt,
+ grid.vline(stroke: red, position: left), grid.vline(stroke: green, position: right), [a],
+ grid.vline(stroke: red, position: left), grid.vline(stroke: 2pt, position: right), [b],
+ grid.vline(stroke: red, position: left), grid.vline(stroke: 2pt, position: right), [c],
+ grid.vline(stroke: aqua, position: right)
+)
+
+#grid(
+ columns: 3,
+ inset: 5pt,
+ gutter: 3pt,
+ grid.vline(stroke: green, position: left), grid.vline(stroke: red, position: right), [a],
+ grid.vline(stroke: blue, position: left), grid.vline(stroke: red, position: right), [b],
+ grid.vline(stroke: blue, position: left), grid.vline(stroke: red, position: right), [c],
+ grid.vline(stroke: 2pt, position: right)
+)
+
+#grid(
+ columns: 3,
+ inset: 5pt,
+ grid.vline(stroke: green, position: start), grid.vline(stroke: red, position: end), [a],
+ grid.vline(stroke: 2pt, position: start), grid.vline(stroke: red, position: end), [b],
+ grid.vline(stroke: 2pt, position: start), grid.vline(stroke: red, position: end), [c],
+ grid.vline(stroke: 2pt, position: start)
+)
+
+#grid(
+ columns: 3,
+ inset: 5pt,
+ gutter: 3pt,
+ grid.vline(stroke: green, position: start), grid.vline(stroke: red, position: end), [a],
+ grid.vline(stroke: blue, position: start), grid.vline(stroke: red, position: end), [b],
+ grid.vline(stroke: blue, position: start), grid.vline(stroke: red, position: end), [c],
+ grid.vline(stroke: 2pt, position: start)
+)
+
+--- grid-rtl-vline-out-of-bounds ---
+// Error: 3:8-3:34 cannot place vertical line at the 'end' position of the end border (x = 1)
+// Hint: 3:8-3:34 set the line's position to 'start' or place it at a smaller 'x' index
+#set text(dir: rtl)
+#grid(
+ [a], grid.vline(position: left)
+)
+
+--- grid-rtl-complex ---
+#set text(dir: rtl)
+
+#grid(
+ columns: 4,
+ fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
+ inset: 5pt,
+ align: center,
+ grid.cell(rowspan: 2, fill: orange)[*Left*],
+ [Right A], [Right A], [Right A],
+ [Right B], grid.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide],
+ [Left A], [Left A],
+ [Left B], [Left B], grid.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long]
+)
+
+#table(
+ columns: 4,
+ fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
+ inset: 5pt,
+ align: center,
+ gutter: 3pt,
+ table.cell(rowspan: 2, fill: orange)[*Left*],
+ [Right A], [Right A], [Right A],
+ [Right B], table.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide],
+ [Left A], [Left A],
+ [Left B], [Left B], table.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long]
+)
+
+--- grid-rtl-rowspan ---
+#set page(height: 10em)
+#set text(dir: rtl)
+#table(
+ columns: 2,
+ rows: (auto, auto, 3em),
+ row-gutter: 1em,
+ fill: red,
+ [a], table.cell(rowspan: 3, block(width: 50%, height: 10em, fill: orange) + place(bottom)[*ZD*]),
+ [e],
+ [f]
+)
+
+--- grid-rtl-header ---
+// Headers
+#set page(height: 15em)
+#set text(dir: rtl)
+#table(
+ columns: 5,
+ align: center + horizon,
+ table.header(
+ table.cell(colspan: 5)[*Cool Zone*],
+ table.cell(stroke: red)[*N1*], table.cell(stroke: aqua)[*N2*], [*D1*], [*D2*], [*Etc*],
+ table.hline(start: 2, end: 3, stroke: yellow)
+ ),
+ ..range(0, 10).map(i => ([\##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten()
+)
diff --git a/tests/suite/layout/grid/stroke.typ b/tests/suite/layout/grid/stroke.typ
new file mode 100644
index 00000000..9c1c3482
--- /dev/null
+++ b/tests/suite/layout/grid/stroke.typ
@@ -0,0 +1,435 @@
+--- grid-stroke-pattern ---
+#let double-line = pattern(size: (1.5pt, 1.5pt), {
+ place(line(stroke: .6pt, start: (0%, 50%), end: (100%, 50%)))
+})
+
+#table(
+ stroke: (_, y) => if y != 1 { (bottom: black) },
+ columns: 3,
+ table.cell(colspan: 3, align: center)[*Epic Table*],
+ align(center)[*Name*], align(center)[*Age*], align(center)[*Data*],
+ table.hline(stroke: (paint: double-line, thickness: 2pt)),
+ [John], [30], [None],
+ [Martha], [20], [A],
+ [Joseph], [35], [D]
+)
+
+--- grid-stroke-folding ---
+// Test folding
+#set grid(stroke: red)
+#set grid(stroke: 5pt)
+
+#grid(
+ inset: 10pt,
+ columns: 2,
+ stroke: stroke(dash: "loosely-dotted"),
+ grid.vline(start: 2, end: 3, stroke: (paint: green, dash: none)),
+ [a], [b],
+ grid.hline(end: 1, stroke: blue),
+ [c], [d],
+ [e], grid.cell(stroke: aqua)[f]
+)
+
+--- grid-stroke-set-on-cell-and-line ---
+// Test set rules on cells and folding
+#set table.cell(stroke: 4pt)
+#set table.cell(stroke: blue)
+#set table.hline(stroke: red)
+#set table.hline(stroke: 0.75pt)
+#set table.vline(stroke: 0.75pt)
+#set table.vline(stroke: aqua)
+
+#table(
+ columns: 3,
+ gutter: 3pt,
+ inset: 5pt,
+ [a], [b], table.vline(position: end), [c],
+ [d], [e], [f],
+ table.hline(position: bottom),
+ [g], [h], [i],
+)
+
+--- grid-stroke-field-in-show ---
+// Test stroke field on cell show rules
+#set grid.cell(stroke: (x: 4pt))
+#set grid.cell(stroke: (x: blue))
+#show grid.cell: it => {
+ test(it.stroke, (left: stroke(paint: blue, thickness: 4pt, dash: "loosely-dotted"), right: blue + 4pt, top: stroke(thickness: 1pt), bottom: none))
+ it
+}
+#grid(
+ stroke: (left: (dash: "loosely-dotted")),
+ inset: 5pt,
+ grid.hline(stroke: red),
+ grid.cell(stroke: (top: 1pt))[a], grid.vline(stroke: yellow),
+)
+
+--- grid-stroke-complex ---
+#table(
+ columns: 3,
+ [a], table.cell(colspan: 2)[b c],
+ table.cell(stroke: blue)[d], [e], [f],
+ [g], [h], table.cell(stroke: (left: yellow, top: green, right: aqua, bottom: red))[i],
+ [j], [k], [l],
+ table.cell(stroke: 3pt)[m], [n], table.cell(stroke: (dash: "loosely-dotted"))[o],
+)
+
+--- grid-stroke-array ---
+// Test per-column stroke array
+#let t = table(
+ columns: 3,
+ stroke: (red, blue, green),
+ [a], [b], [c],
+ [d], [e], [f],
+ [h], [i], [j],
+)
+#t
+#set text(dir: rtl)
+#t
+
+--- grid-stroke-func ---
+#grid(
+ columns: 3,
+ inset: 3pt,
+ stroke: (x, _) => (right: (5pt, (dash: "dotted")).at(calc.rem(x, 2)), bottom: (dash: "densely-dotted")),
+ grid.vline(x: 0, stroke: red),
+ grid.vline(x: 1, stroke: red),
+ grid.vline(x: 2, stroke: red),
+ grid.vline(x: 3, stroke: red),
+ grid.hline(y: 0, end: 1, stroke: blue),
+ grid.hline(y: 1, end: 1, stroke: blue),
+ grid.cell[a],
+ [b], [c]
+)
+
+--- grid-stroke-manually-positioned-lines ---
+#set page(height: 5em)
+#table(
+ columns: 3,
+ inset: 3pt,
+ table.hline(y: 0, end: none, stroke: 3pt + blue),
+ table.vline(x: 0, end: none, stroke: 3pt + green),
+ table.hline(y: 5, end: none, stroke: 3pt + red),
+ table.vline(x: 3, end: none, stroke: 3pt + yellow),
+ [a], [b], [c],
+ [a], [b], [c],
+ [a], [b], [c],
+ [a], [b], [c],
+ [a], [b], [c],
+)
+
+--- grid-stroke-automatically-positioned-lines ---
+// Automatically positioned lines
+// Plus stroke thickness ordering
+#table(
+ columns: 3,
+ table.hline(stroke: red + 5pt),
+ table.vline(stroke: blue + 5pt),
+ table.vline(stroke: 2pt),
+ [a],
+ table.vline(x: 1, stroke: aqua + 5pt),
+ [b],
+ table.vline(stroke: aqua + 5pt),
+ [c],
+ table.vline(stroke: yellow + 5.2pt),
+ table.hline(stroke: green + 5pt),
+ [a], [b], [c],
+ [a], table.hline(stroke: green + 2pt), table.vline(stroke: 2pt), [b], [c],
+)
+
+--- grid-stroke-priority-line ---
+// Line specification order priority
+// The last line should be blue, not red.
+// The middle aqua line should be gone due to the 'none' override.
+#grid(
+ columns: 2,
+ inset: 2pt,
+ grid.hline(y: 2, stroke: red + 5pt),
+ grid.vline(),
+ [a], [b],
+ grid.hline(stroke: red),
+ grid.hline(stroke: none),
+ [c], grid.cell(stroke: (top: aqua))[d],
+ grid.hline(stroke: blue),
+)
+
+--- grid-stroke-hline-position-bottom-gutter ---
+// Position: bottom and position: end with gutter should have a visible effect
+// of moving the lines after the next track.
+#table(
+ columns: 3,
+ gutter: 3pt,
+ stroke: blue,
+ table.hline(end: 2, stroke: red),
+ table.hline(end: 2, stroke: aqua, position: bottom),
+ table.vline(end: 2, stroke: green), [a], table.vline(end: 2, stroke: green), table.vline(end: 2, position: end, stroke: orange), [b], table.vline(end: 2, stroke: aqua, position: end), table.vline(end: 2, stroke: green), [c], table.vline(end: 2, stroke: green),
+ [d], [e], [f],
+ table.hline(end: 2, stroke: red),
+ [g], [h], [ie],
+ table.hline(end: 2, stroke: green),
+)
+
+--- grid-stroke-hline-position-bottom ---
+// Using position: bottom and position: end without gutter should be the same
+// as placing a line after the next track.
+#table(
+ columns: 3,
+ stroke: blue,
+ table.hline(end: 2, stroke: red),
+ table.hline(end: 2, stroke: aqua, position: bottom),
+ table.vline(end: 2, stroke: green), [a], table.vline(end: 2, stroke: green), [b], table.vline(end: 2, stroke: aqua, position: end), table.vline(end: 2, stroke: green), [c], table.vline(end: 2, stroke: green),
+ table.hline(end: 2, stroke: 5pt),
+ [d], [e], [f],
+ table.hline(end: 2, stroke: red),
+ [g], [h], [i],
+ table.hline(end: 2, stroke: red),
+)
+
+--- grid-stroke-vline-position-left-and-right ---
+// Test left and right for grid vlines.
+#grid(
+ columns: 3,
+ inset: 5pt,
+ grid.vline(stroke: green, position: left), grid.vline(stroke: red, position: right), [a],
+ grid.vline(stroke: 2pt, position: left), grid.vline(stroke: red, position: right), [b],
+ grid.vline(stroke: 2pt, position: left), grid.vline(stroke: red, position: right), [c],
+ grid.vline(stroke: 2pt, position: left)
+)
+
+#grid(
+ columns: 3,
+ inset: 5pt,
+ gutter: 3pt,
+ grid.vline(stroke: green, position: left), grid.vline(stroke: red, position: right), [a],
+ grid.vline(stroke: blue, position: left), grid.vline(stroke: red, position: right), [b],
+ grid.vline(stroke: blue, position: left), grid.vline(stroke: red, position: right), [c],
+ grid.vline(stroke: 2pt, position: left)
+)
+
+--- table-stroke-vline-position-left-and-right ---
+// Test left and right for table vlines.
+#table(
+ columns: 3,
+ inset: 5pt,
+ table.vline(stroke: green, position: left), table.vline(stroke: red, position: right), [a],
+ table.vline(stroke: 2pt, position: left), table.vline(stroke: red, position: right), [b],
+ table.vline(stroke: 2pt, position: left), table.vline(stroke: red, position: right), [c],
+ table.vline(stroke: 2pt, position: left)
+)
+
+#table(
+ columns: 3,
+ inset: 5pt,
+ gutter: 3pt,
+ table.vline(stroke: green, position: left), table.vline(stroke: red, position: right), [a],
+ table.vline(stroke: blue, position: left), table.vline(stroke: red, position: right), [b],
+ table.vline(stroke: blue, position: left), table.vline(stroke: red, position: right), [c],
+ table.vline(stroke: 2pt, position: left)
+)
+
+--- grid-stroke-priority-line-cell ---
+// Hlines and vlines should always appear on top of cell strokes.
+#table(
+ columns: 3,
+ stroke: aqua,
+ table.vline(stroke: red, position: end), [a], table.vline(stroke: red), [b], [c],
+ table.cell(stroke: blue)[d], [e], [f],
+ table.hline(stroke: red),
+ [g], table.cell(stroke: blue)[h], [i],
+)
+
+#table(
+ columns: 3,
+ gutter: 3pt,
+ stroke: aqua,
+ table.vline(stroke: red, position: end), [a], table.vline(stroke: red), [b], [c],
+ table.cell(stroke: blue)[d], [e], [f],
+ table.hline(stroke: red),
+ [g], table.cell(stroke: blue)[h], [i],
+)
+
+--- grid-stroke-priority-cell ---
+// Ensure cell stroke overrides always appear on top.
+#table(
+ columns: 2,
+ stroke: black,
+ table.cell(stroke: red)[a], [b],
+ [c], [d],
+)
+
+#table(
+ columns: 2,
+ table.cell(stroke: red)[a], [b],
+ [c], [d],
+)
+
+--- grid-stroke-hline-position-bad ---
+// Error: 7:3-7:32 cannot place horizontal line at the 'bottom' position of the bottom border (y = 2)
+// Hint: 7:3-7:32 set the line's position to 'top' or place it at a smaller 'y' index
+#table(
+ columns: 2,
+ [a], [b],
+ [c], [d],
+ table.hline(stroke: aqua),
+ table.hline(position: top),
+ table.hline(position: bottom)
+)
+
+--- grid-stroke-border-partial ---
+// Test partial border line overrides
+#set page(width: auto, height: 7em, margin: (bottom: 1em))
+#table(
+ columns: 4,
+ stroke: (x, y) => if y == 0 or y == 4 { orange } else { aqua },
+ table.hline(stroke: blue, start: 1, end: 2), table.cell(stroke: red, v(3em)), table.cell(stroke: blue)[b], table.cell(stroke: green)[c], [M],
+ [a], [b], [c], [M],
+ [d], [e], [f], [M],
+ [g], [h], [i], [M],
+ table.cell(stroke: red)[a], table.cell(stroke: blue)[b], table.cell(stroke: green)[c], [M],
+ table.hline(stroke: blue, start: 1, end: 2),
+)
+
+--- grid-stroke-vline-colspan ---
+// - Vline should be placed after the colspan.
+// - Hline should be placed under the full-width rowspan.
+#table(
+ columns: 3,
+ rows: 1.25em,
+ inset: 1pt,
+ stroke: none,
+ table.cell(colspan: 2)[a], table.vline(stroke: red), table.hline(stroke: blue), [b],
+ [c], [d], [e],
+ table.cell(colspan: 3, rowspan: 2)[a], table.vline(stroke: blue), table.hline(stroke: red)
+)
+
+--- grid-stroke-hline-rowspan ---
+// Red line should be above [c] (hline skips the shortest rowspan).
+#set text(6pt)
+#table(
+ rows: 1em,
+ columns: 2,
+ inset: 1.5pt,
+ table.cell(rowspan: 3)[a], table.cell(rowspan: 2)[b],
+ table.hline(stroke: red),
+ [c]
+)
+
+--- grid-stroke-hline-position-bottom-out-of-bounds ---
+// Error: 8:3-8:32 cannot place horizontal line at the 'bottom' position of the bottom border (y = 2)
+// Hint: 8:3-8:32 set the line's position to 'top' or place it at a smaller 'y' index
+#table(
+ columns: 2,
+ gutter: 3pt,
+ [a], [b],
+ [c], [d], table.vline(stroke: red),
+ table.hline(stroke: aqua),
+ table.hline(position: top),
+ table.hline(position: bottom)
+)
+
+--- grid-stroke-vline-position-bottom-out-of-bounds ---
+// Error: 6:3-6:28 cannot place vertical line at the 'end' position of the end border (x = 2)
+// Hint: 6:3-6:28 set the line's position to 'start' or place it at a smaller 'x' index
+#grid(
+ columns: 2,
+ [a], [b],
+ grid.vline(stroke: aqua),
+ grid.vline(position: start),
+ grid.vline(position: end)
+)
+
+--- grid-stroke-vline-position-bottom-out-of-bounds-gutter ---
+// Error: 7:3-7:28 cannot place vertical line at the 'end' position of the end border (x = 2)
+// Hint: 7:3-7:28 set the line's position to 'start' or place it at a smaller 'x' index
+#grid(
+ columns: 2,
+ gutter: 3pt,
+ [a], [b],
+ grid.vline(stroke: aqua),
+ grid.vline(position: start),
+ grid.vline(position: end)
+)
+
+--- grid-stroke-hline-out-of-bounds ---
+// Error: 4:3-4:19 cannot place horizontal line at invalid row 3
+#grid(
+ [a],
+ [b],
+ grid.hline(y: 3)
+)
+
+--- grid-stroke-hline-out-of-bounds-gutter ---
+// Error: 5:3-5:19 cannot place horizontal line at invalid row 3
+#grid(
+ gutter: 3pt,
+ [a],
+ [b],
+ grid.hline(y: 3)
+)
+
+--- grid-stroke-vline-out-of-bounds ---
+// Error: 4:3-4:20 cannot place vertical line at invalid column 3
+#table(
+ columns: 2,
+ [a], [b],
+ table.vline(x: 3)
+)
+
+--- grid-stroke-vline-out-of-bounds-gutter ---
+// Error: 5:3-5:20 cannot place vertical line at invalid column 3
+#table(
+ columns: 2,
+ gutter: 3pt,
+ [a], [b],
+ table.vline(x: 3)
+)
+
+--- table-hline-in-grid ---
+// Error: 7-20 cannot use `table.hline` as a grid line; use `grid.hline` instead
+#grid(table.hline())
+
+--- table-vline-in-grid ---
+// Error: 7-20 cannot use `table.vline` as a grid line; use `grid.vline` instead
+#grid(table.vline())
+
+--- grid-hline-in-table ---
+// Error: 8-20 cannot use `grid.hline` as a table line; use `table.hline` instead
+#table(grid.hline())
+
+--- grid-vline-in-table ---
+// Error: 8-20 cannot use `grid.vline` as a table line; use `table.vline` instead
+#table(grid.vline())
+
+--- grid-hline-end-before-start-1 ---
+// Error: 3:3-3:31 line cannot end before it starts
+#grid(
+ columns: 3,
+ grid.hline(start: 2, end: 1),
+ [a], [b], [c],
+)
+
+--- grid-hline-end-before-start-2 ---
+// Error: 3:3-3:32 line cannot end before it starts
+#table(
+ columns: 3,
+ table.vline(start: 2, end: 1),
+ [a], [b], [c],
+ [d], [e], [f],
+ [g], [h], [i],
+)
+
+--- grid-hline-position-horizon ---
+// Error: 24-31 expected `top` or `bottom`, found horizon
+#table.hline(position: horizon)
+
+--- grid-vline-position-center ---
+// Error: 24-30 expected `start`, `left`, `right`, or `end`, found center
+#table.vline(position: center)
+
+--- grid-hline-position-right ---
+// Error: 24-29 expected `top` or `bottom`, found right
+#table.hline(position: right)
+
+--- grid-vline-position-top ---
+// Error: 24-27 expected `start`, `left`, `right`, or `end`, found top
+#table.vline(position: top)
diff --git a/tests/suite/layout/grid/styling.typ b/tests/suite/layout/grid/styling.typ
new file mode 100644
index 00000000..f7cfb97d
--- /dev/null
+++ b/tests/suite/layout/grid/styling.typ
@@ -0,0 +1,160 @@
+// Test grid styling options.
+
+--- grid-fill-func ---
+#set page(height: 70pt)
+#set grid(fill: (x, y) => if calc.even(x + y) { rgb("aaa") })
+
+#grid(
+ columns: (1fr,) * 3,
+ stroke: 2pt + rgb("333"),
+ [A], [B], [C], [], [], [D \ E \ F \ \ \ G], [H],
+)
+
+--- grid-stroke-none ---
+#grid(columns: 3, stroke: none, fill: green, [A], [B], [C])
+
+--- grid-align ---
+// Test general alignment.
+#grid(
+ columns: 3,
+ align: left,
+ [Hello], [Hello], [Hello],
+ [A], [B], [C],
+)
+
+// Test alignment with a function.
+#grid(
+ columns: 3,
+ align: (x, y) => (left, center, right).at(x),
+ [Hello], [Hello], [Hello],
+ [A], [B], [C],
+)
+
+// Test alignment with array.
+#grid(
+ columns: (1fr, 1fr, 1fr),
+ align: (left, center, right),
+ [A], [B], [C]
+)
+
+// Test empty array.
+#set align(center)
+#grid(
+ columns: (1fr, 1fr, 1fr),
+ align: (),
+ [A], [B], [C]
+)
+
+a
+
+--- grid-inset ---
+// Test inset.
+#grid(
+ columns: (1fr,) * 3,
+ stroke: 2pt + rgb("333"),
+ inset: 5pt,
+ [A], [B], [C], [], [], [D \ E \ F \ \ \ G], [H],
+)
+
+#grid(
+ columns: 3,
+ inset: 10pt,
+ fill: blue,
+ [A], [B], [C]
+)
+
+#grid(
+ columns: 3,
+ inset: (y: 10pt),
+ [A], [B], [C]
+)
+
+#grid(
+ columns: 3,
+ inset: (left: 20pt, rest: 10pt),
+ stroke: 3pt + red,
+ [A], [B], [C]
+)
+
+#grid(
+ columns: 2,
+ inset: (
+ left: 20pt,
+ right: 5pt,
+ top: 10pt,
+ bottom: 3pt,
+ ),
+ [A],
+ [B],
+)
+
+#grid(
+ columns: 3,
+ fill: (x, y) => (if y == 0 { aqua } else { orange }).darken(x * 15%),
+ inset: (x, y) => (left: if x == 0 { 0pt } else { 5pt }, right: if x == 0 { 5pt } else { 0pt }, y: if y == 0 { 0pt } else { 5pt }),
+ [A], [B], [C],
+ [A], [B], [C],
+)
+
+#grid(
+ columns: 3,
+ inset: (0pt, 5pt, 10pt),
+ fill: (x, _) => aqua.darken(x * 15%),
+ [A], [B], [C],
+)
+
+--- grid-inset-folding ---
+// Test inset folding
+#set grid(inset: 10pt)
+#set grid(inset: (left: 0pt))
+
+#grid(
+ fill: red,
+ inset: (right: 0pt),
+ grid.cell(inset: (top: 0pt))[a]
+)
+
+--- grid-funcs-gutter ---
+// Test interaction with gutters.
+#grid(
+ columns: (3em, 3em),
+ fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
+ align: (x, y) => (left, right).at(calc.rem(y, 2)),
+ [A], [B],
+ [C], [D],
+ [E], [F],
+ [G], [H]
+)
+
+#grid(
+ columns: (3em, 3em),
+ fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
+ align: (x, y) => (left, right).at(calc.rem(y, 2)),
+ row-gutter: 5pt,
+ [A], [B],
+ [C], [D],
+ [E], [F],
+ [G], [H]
+)
+
+#grid(
+ columns: (3em, 3em),
+ fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
+ align: (x, y) => (left, right).at(calc.rem(y, 2)),
+ column-gutter: 5pt,
+ [A], [B],
+ [C], [D],
+ [E], [F],
+ [G], [H]
+)
+
+#grid(
+ columns: (3em, 3em),
+ fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
+ align: (x, y) => (left, right).at(calc.rem(y, 2)),
+ gutter: 5pt,
+ [A], [B],
+ [C], [D],
+ [E], [F],
+ [G], [H]
+)
diff --git a/tests/suite/layout/hide.typ b/tests/suite/layout/hide.typ
new file mode 100644
index 00000000..a10090d7
--- /dev/null
+++ b/tests/suite/layout/hide.typ
@@ -0,0 +1,104 @@
+// Test the `hide` function.
+
+--- hide-text ---
+AB #h(1fr) CD \
+#hide[A]B #h(1fr) C#hide[D]
+
+--- hide-line ---
+Hidden:
+#hide[#line(length: 100%)]
+#line(length: 100%)
+
+--- hide-table ---
+Hidden:
+#hide(table(rows: 2, columns: 2)[a][b][c][d])
+#table(rows: 2, columns: 2)[a][b][c][d]
+
+--- hide-polygon ---
+Hidden:
+#hide[
+ #polygon((20%, 0pt),
+ (60%, 0pt),
+ (80%, 2cm),
+ (0%, 2cm),)
+]
+#polygon((20%, 0pt),
+ (60%, 0pt),
+ (80%, 2cm),
+ (0%, 2cm),)
+
+--- hide-rect ---
+#set rect(
+ inset: 8pt,
+ fill: rgb("e4e5ea"),
+ width: 100%,
+)
+
+Hidden:
+#hide[
+#grid(
+ columns: (1fr, 1fr, 2fr),
+ rows: (auto, 40pt),
+ gutter: 3pt,
+ rect[A],
+ rect[B],
+ rect[C],
+ rect(height: 100%)[D],
+)
+]
+#grid(
+ columns: (1fr, 1fr, 2fr),
+ rows: (auto, 40pt),
+ gutter: 3pt,
+ rect[A],
+ rect[B],
+ rect[C],
+ rect(height: 100%)[D],
+)
+
+--- hide-list ---
+Hidden:
+#hide[
+- 1
+- 2
+ 1. A
+ 2. B
+- 3
+]
+
+
+- 1
+- 2
+ 1. A
+ 2. B
+- 3
+
+--- hide-image ---
+Hidden:
+#hide(image("/assets/images/tiger.jpg", width: 5cm, height: 1cm,))
+
+#image("/assets/images/tiger.jpg", width: 5cm, height: 1cm,)
+
+--- issue-622-hide-meta-cite ---
+// Test that metadata of hidden stuff stays available.
+#set cite(style: "chicago-notes")
+
+A pirate. @arrgh \
+#set text(2pt)
+#hide[
+ A @arrgh pirate.
+ #bibliography("/assets/bib/works.bib")
+]
+
+--- issue-622-hide-meta-outline ---
+#set text(8pt)
+#outline()
+#set text(2pt)
+#hide(block(grid(
+ [= A],
+ [= B],
+ block(grid(
+ [= C],
+ [= D],
+ ))
+)))
diff --git a/tests/suite/layout/inline/baseline.typ b/tests/suite/layout/inline/baseline.typ
new file mode 100644
index 00000000..e9f9a645
--- /dev/null
+++ b/tests/suite/layout/inline/baseline.typ
@@ -0,0 +1,17 @@
+// Test baseline handling.
+
+--- baseline-text ---
+Hi #text(1.5em)[You], #text(0.75em)[how are you?]
+
+Our cockatoo was one of the
+#text(baseline: -0.2em)[#box(circle(radius: 2pt)) first]
+#text(baseline: 0.2em)[birds #box(circle(radius: 2pt))]
+that ever learned to mimic a human voice.
+
+--- baseline-box ---
+Hey #box(baseline: 40%, image("/assets/images/tiger.jpg", width: 1.5cm)) there!
+
+--- issue-2214-baseline-math ---
+// The math content should also be affected by the TextElem baseline.
+hello #text(baseline: -5pt)[123 #sym.WW\orld]\
+hello #text(baseline: -5pt)[$123 WW#text[or]$ld]\
diff --git a/tests/suite/layout/inline/bidi.typ b/tests/suite/layout/inline/bidi.typ
new file mode 100644
index 00000000..7da23b41
--- /dev/null
+++ b/tests/suite/layout/inline/bidi.typ
@@ -0,0 +1,77 @@
+// Test bidirectional text and language configuration.
+
+--- bidi-en-he-top-level ---
+// Test reordering with different top-level paragraph directions.
+#let content = par[Text טֶקסט]
+#text(lang: "he", content)
+#text(lang: "de", content)
+
+--- bidi-consecutive-embedded-ltr-runs ---
+// Test that consecutive, embedded LTR runs stay LTR.
+// Here, we have two runs: "A" and italic "B".
+#let content = par[أنت A#emph[B]مطرC]
+#set text(font: ("PT Sans", "Noto Sans Arabic"))
+#text(lang: "ar", content)
+#text(lang: "de", content)
+
+--- bidi-consecutive-embedded-rtl-runs ---
+// Test that consecutive, embedded RTL runs stay RTL.
+// Here, we have three runs: "גֶ", bold "שֶׁ", and "ם".
+#let content = par[Aגֶ#strong[שֶׁ]םB]
+#set text(font: ("Linux Libertine", "Noto Serif Hebrew"))
+#text(lang: "he", content)
+#text(lang: "de", content)
+
+--- bidi-nesting ---
+// Test embedding up to level 4 with isolates.
+#set text(dir: rtl)
+א\u{2066}A\u{2067}Bב\u{2069}?
+
+--- bidi-manual-linebreak ---
+// Test hard line break (leads to two paragraphs in unicode-bidi).
+#set text(lang: "ar", font: ("Noto Sans Arabic", "PT Sans"))
+Life المطر هو الحياة \
+الحياة تمطر is rain.
+
+--- bidi-spacing ---
+// Test spacing.
+L #h(1cm) ריווחR \
+Lריווח #h(1cm) R
+
+--- bidi-obj ---
+// Test inline object.
+#set text(lang: "he")
+קרנפיםRh#box(image("/assets/images/rhino.png", height: 11pt))inoחיים
+
+--- bidi-whitespace-reset ---
+// Test whether L1 whitespace resetting destroys stuff.
+الغالب #h(70pt) ن#" "ة
+
+--- bidi-explicit-dir ---
+// Test explicit dir
+#set text(dir: rtl)
+#text("8:00 - 9:00", dir: ltr) בבוקר
+#linebreak()
+ב #text("12:00 - 13:00", dir: ltr) בצהריים
+
+--- bidi-raw ---
+// Mixing raw
+#set text(lang: "he")
+לדוג. `if a == b:` זה תנאי
+#set raw(lang: "python")
+לדוג. `if a == b:` זה תנאי
+
+#show raw: set text(dir:rtl)
+לתכנת בעברית `אם א == ב:`
+
+--- bidi-vertical ---
+// Test setting a vertical direction.
+// Error: 16-19 text direction must be horizontal
+#set text(dir: ttb)
+
+--- issue-1373-bidi-tofus ---
+// Test that shaping missing characters in both left-to-right and
+// right-to-left directions does not cause a crash.
+#"\u{590}\u{591}\u{592}\u{593}"
+
+#"\u{30000}\u{30001}\u{30002}\u{30003}"
diff --git a/tests/suite/layout/inline/cjk.typ b/tests/suite/layout/inline/cjk.typ
new file mode 100644
index 00000000..0540cd19
--- /dev/null
+++ b/tests/suite/layout/inline/cjk.typ
@@ -0,0 +1,90 @@
+// Test CJK-specific features.
+
+--- text-chinese-basic ---
+// Test basic Chinese text from Wikipedia.
+#set text(font: "Noto Serif CJK SC")
+
+是美国广播公司电视剧《迷失》第3季的第22和23集,也是全剧的第71集和72集
+由执行制作人戴蒙·林道夫和卡尔顿·库斯编剧,导演则是另一名执行制作人杰克·本德
+节目于2007年5月23日在美国和加拿大首播,共计吸引了1400万美国观众收看
+本集加上插播广告一共也持续有两个小时
+
+--- text-cjk-latin-spacing ---
+#set page(width: 50pt + 10pt, margin: (x: 5pt))
+#set text(lang: "zh", font: "Noto Serif CJK SC", cjk-latin-spacing: auto)
+#set par(justify: true)
+
+中文,中12文1中,文12中文
+
+中文,中ab文a中,文ab中文
+
+#set text(cjk-latin-spacing: none)
+
+中文,中12文1中,文12中文
+
+中文,中ab文a中,文ab中文
+
+--- cjk-punctuation-adjustment-1 ---
+#set page(width: 15em)
+
+// In the following example, the space between 》! and ? should be squeezed.
+// because zh-CN follows GB style
+#set text(lang: "zh", region: "CN", font: "Noto Serif CJK SC")
+原来,你也玩《原神》!?
+
+// However, in the following example, the space between 》! and ? should not be squeezed.
+// because zh-TW does not follow GB style
+#set text(lang: "zh", region: "TW", font: "Noto Serif CJK TC")
+原來,你也玩《原神》! ?
+
+#set text(lang: "zh", region: "CN", font: "Noto Serif CJK SC")
+「真的吗?」
+
+#set text(lang: "ja", font: "Noto Serif CJK JP")
+「本当に?」
+
+--- cjk-punctuation-adjustment-2 ---
+#set text(lang: "zh", region: "CN", font: "Noto Serif CJK SC")
+《书名〈章节〉》 // the space between 〉 and 》 should be squeezed
+
+〔茸毛〕:很细的毛 // the space between 〕 and : should be squeezed
+
+--- cjk-punctuation-adjustment-3 ---
+#set page(width: 21em)
+#set text(lang: "zh", region: "CN", font: "Noto Serif CJK SC")
+
+// These examples contain extensive use of Chinese punctuation marks,
+// from 《Which parentheses should be used when applying parentheses?》.
+// link: https://archive.md/2bb1N
+
+
+(〔中〕医、〔中〕药、技)系列评审
+
+(长三角[长江三角洲])(GB/T 16159—2012《汉语拼音正词法基本规则》)
+
+【爱因斯坦(Albert Einstein)】物理学家
+
+〔(2009)民申字第1622号〕
+
+“江南海北长相忆,浅水深山独掩扉。”([唐]刘长卿《会赦后酬主簿所问》)
+
+参看1378页〖象形文字〗。(《现代汉语词典》修订本)
+
+--- issue-2538-cjk-latin-spacing-before-linebreak ---
+// Issue #2538
+#set text(cjk-latin-spacing: auto)
+
+abc字
+
+abc字#linebreak()
+
+abc字#linebreak()
+母
+
+abc字\
+母
+
+--- issue-2650-cjk-latin-spacing-meta ---
+测a试
+
+测#context [a]试
diff --git a/tests/suite/layout/inline/hyphenate.typ b/tests/suite/layout/inline/hyphenate.typ
new file mode 100644
index 00000000..aaabe816
--- /dev/null
+++ b/tests/suite/layout/inline/hyphenate.typ
@@ -0,0 +1,51 @@
+// Test hyphenation.
+
+--- hyphenate ---
+// Test hyphenating english and greek.
+#set text(hyphenate: true)
+#set page(width: auto)
+#grid(
+ columns: (50pt, 50pt),
+ [Warm welcomes to Typst.],
+ text(lang: "el")[διαμερίσματα. \ λατρευτός],
+)
+
+--- hyphenate-off-temporarily ---
+// Test disabling hyphenation for short passages.
+#set page(width: 110pt)
+#set text(hyphenate: true)
+
+Welcome to wonderful experiences. \
+Welcome to `wonderful` experiences. \
+Welcome to #text(hyphenate: false)[wonderful] experiences. \
+Welcome to wonde#text(hyphenate: false)[rf]ul experiences. \
+
+// Test enabling hyphenation for short passages.
+#set text(hyphenate: false)
+Welcome to wonderful experiences. \
+Welcome to wo#text(hyphenate: true)[nd]erful experiences. \
+
+--- hyphenate-between-shape-runs ---
+// Hyphenate between shape runs.
+#set page(width: 80pt)
+#set text(hyphenate: true)
+It's a #emph[Tree]beard.
+
+--- hyphenate-shy ---
+// Test shy hyphens.
+#set text(lang: "de", hyphenate: true)
+#grid(
+ columns: 2 * (20pt,),
+ gutter: 20pt,
+ [Barankauf],
+ [Bar-?ankauf],
+)
+
+--- hyphenate-punctuation ---
+// This sequence would confuse hypher if we passed trailing / leading
+// punctuation instead of just the words. So this tests that we don't
+// do that. The test passes if there's just one hyphenation between
+// "net" and "works".
+#set page(width: 60pt)
+#set text(hyphenate: true)
+#h(6pt) networks, the rest.
diff --git a/tests/suite/layout/inline/justify.typ b/tests/suite/layout/inline/justify.typ
new file mode 100644
index 00000000..e1e15578
--- /dev/null
+++ b/tests/suite/layout/inline/justify.typ
@@ -0,0 +1,170 @@
+--- justify ---
+#set page(width: 180pt)
+#set block(spacing: 5pt)
+#set par(justify: true, first-line-indent: 14pt, leading: 5pt)
+
+This text is justified, meaning that spaces are stretched so that the text
+forms a "block" with flush edges at both sides.
+
+First line indents and hyphenation play nicely with justified text.
+
+--- justify-knuth-story ---
+// LARGE
+#set page(width: auto, height: auto)
+#set par(leading: 4pt, justify: true)
+#set text(font: "New Computer Modern")
+
+#let story = [
+ In olden times when wishing still helped one, there lived a king whose
+ daughters were all beautiful; and the youngest was so beautiful that the sun
+ itself, which has seen so much, was astonished whenever it shone in her face.
+ Close by the king’s castle lay a great dark forest, and under an old lime-tree
+ in the forest was a well, and when the day was very warm, the king’s child
+ went out into the forest and sat down by the side of the cool fountain; and
+ when she was bored she took a golden ball, and threw it up on high and caught
+ it; and this ball was her favorite plaything.
+]
+
+#let column(title, linebreaks, hyphenate) = {
+ rect(inset: 0pt, width: 132pt, fill: rgb("eee"))[
+ #set par(linebreaks: linebreaks)
+ #set text(hyphenate: hyphenate)
+ #strong(title) \ #story
+ ]
+}
+
+#grid(
+ columns: 3,
+ gutter: 10pt,
+ column([Simple without hyphens], "simple", false),
+ column([Simple with hyphens], "simple", true),
+ column([Optimized with hyphens], "optimized", true),
+)
+
+--- justify-manual-linebreak ---
+// Test that lines with hard breaks aren't justified.
+#set par(justify: true)
+A B C \
+D
+
+--- justify-justified-linebreak ---
+// Test forced justification with justified break.
+A B C #linebreak(justify: true)
+D E F #linebreak(justify: true)
+
+--- justify-basically-empty ---
+// Test that there are no hick-ups with justification enabled and
+// basically empty paragraph.
+#set par(justify: true)
+#""
+
+--- justify-shrink-last-line ---
+// Test that the last line can be shrunk
+#set page(width: 155pt)
+#set par(justify: true)
+This text can be fitted in one line.
+
+--- justify-avoid-runts ---
+// Test that runts are avoided when it's not too costly to do so.
+#set page(width: 124pt)
+#set par(justify: true)
+#for i in range(0, 20) {
+ "a b c "
+}
+#"d"
+
+--- justify-no-leading-spaces ---
+// Test that justification cannot lead to a leading space
+#set par(justify: true)
+#set text(size: 12pt)
+#set page(width: 45mm, height: auto)
+
+lorem ipsum 1234, lorem ipsum dolor sit amet
+
+#" leading whitespace should still be displayed"
+
+--- justify-code-blocks ---
+// Test that justification doesn't break code blocks
+#set par(justify: true)
+
+```cpp
+int main() {
+ printf("Hello world\n");
+ return 0;
+}
+```
+
+--- justify-chinese ---
+// In Chinese typography, line length should be multiples of the character size
+// and the line ends should be aligned with each other. Most Chinese
+// publications do not use hanging punctuation at line end.
+#set page(width: auto)
+#set par(justify: true)
+#set text(lang: "zh", font: "Noto Serif CJK SC")
+
+#rect(inset: 0pt, width: 80pt, fill: rgb("eee"))[
+ 中文维基百科使用汉字书写,汉字是汉族或华人的共同文字,是中国大陆、新加坡、马来西亚、台湾、香港、澳门的唯一官方文字或官方文字之一。25.9%,而美国和荷兰则分別占13.7%及8.2%。近年來,中国大陆地区的维基百科编辑者正在迅速增加;
+]
+
+--- justify-japanese ---
+// Japanese typography is more complex, make sure it is at least a bit sensible.
+#set page(width: auto)
+#set par(justify: true)
+#set text(lang: "ja", font: ("Linux Libertine", "Noto Serif CJK JP"))
+#rect(inset: 0pt, width: 80pt, fill: rgb("eee"))[
+ ウィキペディア(英: Wikipedia)は、世界中のボランティアの共同作業によって執筆及び作成されるフリーの多言語インターネット百科事典である。主に寄付に依って活動している非営利団体「ウィキメディア財団」が所有・運営している。
+
+ 専門家によるオンライン百科事典プロジェクトNupedia(ヌーペディア)を前身として、2001年1月、ラリー・サンガーとジミー・ウェールズ(英: Jimmy Donal "Jimbo" Wales)により英語でプロジェクトが開始された。
+]
+
+--- justify-whitespace-adjustment ---
+// Test punctuation whitespace adjustment
+#set page(width: auto)
+#set text(lang: "zh", font: "Noto Serif CJK SC")
+#set par(justify: true)
+#rect(inset: 0pt, width: 80pt, fill: rgb("eee"))[
+ “引号测试”,还,
+
+ 《书名》《测试》下一行
+
+ 《书名》《测试》。
+]
+
+「『引号』」。“‘引号’”。
+
+--- justify-variants ---
+// Test Variants of Mainland China, Hong Kong, and Japan.
+
+// 17 characters a line.
+#set page(width: 170pt + 10pt, margin: (x: 5pt))
+#set text(lang: "zh", font: "Noto Serif CJK SC")
+#set par(justify: true)
+
+孔雀最早见于《山海经》中的《海内经》:“有孔雀。”东汉杨孚著《异物志》记载,岭南:“孔雀,其大如大雁而足高,毛皆有斑纹彩,捕而蓄之,拍手即舞。”
+
+#set text(lang: "zh", region: "hk", font: "Noto Serif CJK TC")
+孔雀最早见于《山海经》中的《海内经》:「有孔雀。」东汉杨孚著《异物志》记载,岭南:「孔雀,其大如大雁而足高,毛皆有斑纹彩,捕而蓄之,拍手即舞。」
+
+--- justify-punctuation-adjustment ---
+// Test punctuation marks adjustment in justified paragraph.
+
+// The test case includes the following scenarios:
+// - Compression of punctuation marks at line start or line end
+// - Adjustment of adjacent punctuation marks
+
+#set page(width: 110pt + 10pt, margin: (x: 5pt))
+#set text(lang: "zh", font: "Noto Serif CJK SC")
+#set par(justify: true)
+
+标注在字间的标点符号(乙式括号省略号以外)通常占一个汉字宽度,使其易于识别、适合配置及排版,有些排版风格完全不对标点宽度进行任何调整。但是为了让文字体裁更加紧凑易读,,,以及执行3.1.4 行首行尾禁则时,就需要对标点符号的宽度进行调整。是否调整取决于……
+
+--- justify-without-justifiables ---
+// Test breaking a line without justifiables.
+#set par(justify: true)
+#block(width: 1cm, fill: aqua, lorem(2))
+
+--- issue-2419-justify-hanging-indent ---
+// Test that combination of justification and hanging indent doesn't result in
+// an underfull first line.
+#set par(hanging-indent: 2.5cm, justify: true)
+#lorem(5)
diff --git a/tests/suite/layout/inline/linebreak.typ b/tests/suite/layout/inline/linebreak.typ
new file mode 100644
index 00000000..2fa29b6c
--- /dev/null
+++ b/tests/suite/layout/inline/linebreak.typ
@@ -0,0 +1,109 @@
+// Test line breaks.
+
+--- linebreak-overflow ---
+// Test overlong word that is not directly after a hard break.
+This is a spaceexceedinglylongy.
+
+--- linebreak-overflow-double ---
+// Test two overlong words in a row.
+Supercalifragilisticexpialidocious Expialigoricmetrioxidation.
+
+--- linebreak-hyphen-nbsp ---
+// Test for non-breaking space and hyphen.
+There are non\u{2011}breaking~characters.
+
+--- linebreak-narrow-nbsp ---
+// Test for narrow non-breaking space.
+#show "_": sym.space.nobreak.narrow
+0.1_g, 1_g, 10_g, 100_g, 1_000_g, 10_000_g, 100_000_g, 1_000_000_g
+
+--- linebreak-shape-run ---
+// Test that there are no unwanted line break opportunities on run change.
+This is partly emp#emph[has]ized.
+
+--- linebreak-manual ---
+Hard #linebreak() break.
+
+--- linebreak-manual-directly-after-automatic ---
+// Test hard break directly after normal break.
+Hard break directly after \ normal break.
+
+--- linebreak-manual-consecutive ---
+// Test consecutive breaks.
+Two consecutive \ \ breaks and three \ \ more.
+
+--- linebreak-manual-trailing-multiple ---
+// Test forcing an empty trailing line.
+Trailing break \ \
+
+--- linebreak-manual-justified ---
+// Test justified breaks.
+#set par(justify: true)
+With a soft #linebreak(justify: true)
+break you can force a break without #linebreak(justify: true)
+breaking justification. #linebreak(justify: false)
+Nice!
+
+--- linebreak-thai ---
+// Test linebreak for East Asian languages
+ทีวีตรวจทานนอร์ทแฟรีเลคเชอร์โกลด์อัลบัมเชอร์รี่เย้วสโตร์กฤษณ์เคลมเยอบีร่าพ่อค้าบลูเบอร์รี่สหัสวรรษโฮปแคนูโยโย่จูนสตรอว์เบอร์รีซื่อบื้อเยนแบ็กโฮเป็นไงโดนัททอมสเตริโอแคนูวิทย์แดรี่โดนัทวิทย์แอปพริคอทเซอร์ไพรส์ไฮบริดกิฟท์อินเตอร์โซนเซอร์วิสเทียมทานโคโยตี้ม็อบเที่ยงคืนบุญคุณ
+
+--- linebreak-cite-punctuation ---
+// Test punctuation after citations.
+#set page(width: 162pt)
+
+They can look for the details in @netwok,
+which is the authoritative source.
+
+#bibliography("/assets/bib/works.bib")
+
+--- linebreak-math-punctuation ---
+// Test punctuation after math equations.
+#set page(width: 85pt)
+
+We prove $1 < 2$. \
+We prove $1 < 2$! \
+We prove $1 < 2$? \
+We prove $1 < 2$, \
+We prove $1 < 2$; \
+We prove $1 < 2$: \
+We prove $1 < 2$- \
+We prove $1 < 2$– \
+We prove $1 < 2$— \
+
+--- linebreak-link ---
+#link("https://example.com/(ab") \
+#link("https://example.com/(ab)") \
+#link("https://example.com/(paren)") \
+#link("https://example.com/paren)") \
+#link("https://hi.com/%%%%%%%%abcdef") \
+
+--- linebreak-link-justify ---
+#set page(width: 240pt)
+#set par(justify: true)
+
+Here's a link https://url.com/data/extern12840%data_urlenc and then there are more
+links #link("www.url.com/data/extern12840%data_urlenc") in my text of links
+http://mydataurl/hash/12098541029831025981024980124124214/incremental/progress%linkdata_information_setup_my_link_just_never_stops_going/on?query=false
+
+--- linebreak-link-end ---
+// Ensure that there's no unconditional break at the end of a link.
+#set page(width: 180pt, height: auto, margin: auto)
+#set text(11pt)
+
+For info see #link("https://myhost.tld").
+
+--- issue-2105-linebreak-tofu ---
+#linebreak()中文
+
+--- issue-3082-chinese-punctuation ---
+#set text(font: "Noto Serif CJK TC", lang: "zh")
+#set page(width: 230pt)
+
+課有手冬,朱得過已誰卜服見以大您即乙太邊良,因且行肉因和拉幸,念姐遠米巴急(abc0),松黃貫誰。
+
+--- issue-80-emoji-linebreak ---
+// Test that there are no linebreaks in composite emoji (issue #80).
+#set page(width: 50pt, height: auto)
+#h(99%) 🏳️‍🌈
+🏳️‍🌈
diff --git a/tests/suite/layout/inline/overhang.typ b/tests/suite/layout/inline/overhang.typ
new file mode 100644
index 00000000..40b0e7f7
--- /dev/null
+++ b/tests/suite/layout/inline/overhang.typ
@@ -0,0 +1,24 @@
+// Test micro-typographical shenanigans.
+
+--- overhang ---
+// Test hanging punctuation.
+// TODO: This test was broken at some point.
+#set page(width: 130pt, margin: 15pt)
+#set par(justify: true, linebreaks: "simple")
+#set text(size: 9pt)
+#rect(inset: 0pt, fill: rgb(0, 0, 0, 0), width: 100%)[
+ This is a little bit of text that builds up to
+ hang-ing hyphens and dash---es and then, you know,
+ some punctuation in the margin.
+]
+
+// Test hanging punctuation with RTL.
+#set text(lang: "he", font: ("PT Sans", "Noto Serif Hebrew"))
+בנייה נכונה של משפטים ארוכים דורשת ידע בשפה. אז בואו נדבר על מזג האוויר.
+
+--- overhang-lone ---
+// Test that lone punctuation doesn't overhang into the margin.
+#set page(margin: 0pt)
+#set align(end)
+#set text(dir: rtl)
+:
diff --git a/tests/suite/layout/inline/shaping.typ b/tests/suite/layout/inline/shaping.typ
new file mode 100644
index 00000000..ec93eb47
--- /dev/null
+++ b/tests/suite/layout/inline/shaping.typ
@@ -0,0 +1,65 @@
+// Test shaping quirks.
+
+--- shaping-script-separation ---
+// Test separation by script.
+#set text(font: ("Linux Libertine", "IBM Plex Sans Devanagari"))
+ABCअपार्टमेंट
+
+// This is how it should look like.
+अपार्टमेंट
+
+// This (without the spaces) is how it would look
+// if we didn't separate by script.
+अ पा र् ट में ट
+
+--- shaping-forced-script-font-feature-inhibited ---
+// A forced `latn` script inhibits Devanagari font features.
+#set text(font: ("Linux Libertine", "IBM Plex Sans Devanagari"), script: "latn")
+ABCअपार्टमेंट
+
+--- shaping-forced-script-font-feature-enabled ---
+// A forced `deva` script enables Devanagari font features.
+#set text(font: ("Linux Libertine", "IBM Plex Sans Devanagari"), script: "deva")
+ABCअपार्टमेंट
+
+--- issue-rtl-safe-to-break-panic ---
+// Test that RTL safe-to-break doesn't panic even though newline
+// doesn't exist in shaping output.
+#set text(dir: rtl, font: "Noto Serif Hebrew")
+\ ט
+
+--- shaping-font-fallback ---
+// Font fallback for emoji.
+A😀B
+
+// Font fallback for entire text.
+دع النص يمطر عليك
+
+// Font fallback in right-to-left text.
+ب🐈😀سم
+
+// Multi-layer font fallback.
+Aب😀🏞سمB
+
+// Font fallback with composed emojis and multiple fonts.
+01️⃣2
+
+// Tofus are rendered with the first font.
+A🐈ዲሞB
+
+--- shaping-emoji-basic ---
+// This should form a three-member family.
+👩‍👩‍👦
+
+// This should form a pride flag.
+🏳️‍🌈
+
+// Skin tone modifier should be applied.
+👍🏿
+
+// This should be a 1 in a box.
+1️⃣
+
+--- shaping-emoji-bad-zwj ---
+// These two shouldn't be affected by a zero-width joiner.
+🏞‍🌋
diff --git a/tests/suite/layout/inline/text.typ b/tests/suite/layout/inline/text.typ
new file mode 100644
index 00000000..e2bc84ef
--- /dev/null
+++ b/tests/suite/layout/inline/text.typ
@@ -0,0 +1,89 @@
+// Test OpenType features.
+
+--- text-kerning ---
+// Test turning kerning off.
+#text(kerning: true)[Tq] \
+#text(kerning: false)[Tq]
+
+--- text-alternates-and-stylistic-sets ---
+// Test alternates and stylistic sets.
+#set text(font: "IBM Plex Serif")
+a vs #text(alternates: true)[a] \
+ß vs #text(stylistic-set: 5)[ß]
+
+--- text-ligatures ---
+// Test ligatures.
+fi vs. #text(ligatures: false)[No fi]
+
+--- text-number-type ---
+// Test number type.
+#set text(number-type: "old-style")
+0123456789 \
+#text(number-type: auto)[0123456789]
+
+--- text-number-width ---
+// Test number width.
+#text(number-width: "proportional")[0123456789] \
+#text(number-width: "tabular")[3456789123] \
+#text(number-width: "tabular")[0123456789]
+
+--- text-slashed-zero-and-fractions ---
+// Test extra number stuff.
+#set text(font: "IBM Plex Serif")
+0 vs. #text(slashed-zero: true)[0] \
+1/2 vs. #text(fractions: true)[1/2]
+
+--- text-features ---
+// Test raw features.
+#text(features: ("smcp",))[Smcp] \
+fi vs. #text(features: (liga: 0))[No fi]
+
+--- text-stylistic-set-bad-type ---
+// Error: 26-31 expected integer or none, found boolean
+#set text(stylistic-set: false)
+
+--- text-stylistic-set-out-of-bounds ---
+// Error: 26-28 stylistic set must be between 1 and 20
+#set text(stylistic-set: 25)
+
+--- text-number-type-bad ---
+// Error: 24-25 expected "lining", "old-style", or auto, found integer
+#set text(number-type: 2)
+
+--- text-features-bad ---
+// Error: 21-26 expected array or dictionary, found boolean
+#set text(features: false)
+
+--- text-features-bad-nested-type ---
+// Error: 21-35 expected string, found boolean
+#set text(features: ("tag", false))
+
+--- text-tracking-negative ---
+// Test tracking.
+#set text(tracking: -0.01em)
+I saw Zoe yӛsterday, on the tram.
+
+--- text-tracking-changed-temporarily ---
+// Test tracking for only part of paragraph.
+I'm in#text(tracking: 0.15em + 1.5pt)[ spaace]!
+
+--- text-tracking-mark-placement ---
+// Test that tracking doesn't disrupt mark placement.
+#set text(font: ("PT Sans", "Noto Serif Hebrew"))
+#set text(tracking: 0.3em)
+טֶקסט
+
+--- text-tracking-arabic ---
+// Test tracking in arabic text (makes no sense whatsoever)
+#set text(tracking: 0.3em)
+النص
+
+--- text-spacing ---
+// Test word spacing.
+#set text(spacing: 1em)
+My text has spaces.
+
+--- text-spacing-relative ---
+// Test word spacing relative to the font's space width.
+#set text(spacing: 50% + 1pt)
+This is tight.
diff --git a/tests/suite/layout/layout.typ b/tests/suite/layout/layout.typ
new file mode 100644
index 00000000..257e478b
--- /dev/null
+++ b/tests/suite/layout/layout.typ
@@ -0,0 +1,14 @@
+--- layout-in-fixed-size-block ---
+// Layout inside a block with certain dimensions should provide those dimensions.
+#set page(height: 120pt)
+#block(width: 60pt, height: 80pt, layout(size => [
+ This block has a width of #size.width and height of #size.height
+]))
+
+--- layout-in-page-call ---
+// Layout without any container should provide the page's dimensions, minus its margins.
+#page(width: 100pt, height: 100pt, {
+ layout(size => [This page has a width of #size.width and height of #size.height ])
+ h(1em)
+ place(left, rect(width: 80pt, stroke: blue))
+})
diff --git a/tests/suite/layout/length.typ b/tests/suite/layout/length.typ
new file mode 100644
index 00000000..68755619
--- /dev/null
+++ b/tests/suite/layout/length.typ
@@ -0,0 +1,69 @@
+--- length-fields ---
+// Test length fields.
+#test((1pt).em, 0.0)
+#test((1pt).abs, 1pt)
+#test((3em).em, 3.0)
+#test((3em).abs, 0pt)
+#test((2em + 2pt).em, 2.0)
+#test((2em + 2pt).abs, 2pt)
+
+--- length-to-unit ---
+// Test length unit conversions.
+#test((500.934pt).pt(), 500.934)
+#test((3.3453cm).cm(), 3.3453)
+#test((4.3452mm).mm(), 4.3452)
+#test((5.345in).inches(), 5.345)
+#test((500.333666999pt).pt(), 500.333666999)
+#test((3.5234354cm).cm(), 3.5234354)
+#test((4.12345678mm).mm(), 4.12345678)
+#test((5.333666999in).inches(), 5.333666999)
+#test((4.123456789123456mm).mm(), 4.123456789123456)
+#test((254cm).mm(), 2540.0)
+#test(calc.round((254cm).inches(), digits: 2), 100.0)
+#test((2540mm).cm(), 254.0)
+#test(calc.round((2540mm).inches(), digits: 2), 100.0)
+#test((100in).pt(), 7200.0)
+#test(calc.round((100in).cm(), digits: 2), 254.0)
+#test(calc.round((100in).mm(), digits: 2), 2540.0)
+#test(5em.abs.cm(), 0.0)
+#test((5em + 6in).abs.inches(), 6.0)
+
+--- length-to-absolute ---
+// Test length `to-absolute` method.
+#set text(size: 12pt)
+#context {
+ test((6pt).to-absolute(), 6pt)
+ test((6pt + 10em).to-absolute(), 126pt)
+ test((10em).to-absolute(), 120pt)
+}
+
+#set text(size: 64pt)
+#context {
+ test((6pt).to-absolute(), 6pt)
+ test((6pt + 10em).to-absolute(), 646pt)
+ test((10em).to-absolute(), 640pt)
+}
+
+--- length-unit-hint ---
+// Error: 1:17-1:19 expected length, found integer: a length needs a unit - did you mean 12pt?
+#set text(size: 12)
+
+--- length-ignore-em-pt-hint ---
+// Error: 2-21 cannot convert a length with non-zero em units (`-6pt + 10.5em`) to pt
+// Hint: 2-21 use `length.abs.pt()` instead to ignore its em component
+#(10.5em - 6pt).pt()
+
+--- length-ignore-em-cm-hint ---
+// Error: 2-12 cannot convert a length with non-zero em units (`3em`) to cm
+// Hint: 2-12 use `length.abs.cm()` instead to ignore its em component
+#(3em).cm()
+
+--- length-ignore-em-mm-hint ---
+// Error: 2-20 cannot convert a length with non-zero em units (`-226.77pt + 93em`) to mm
+// Hint: 2-20 use `length.abs.mm()` instead to ignore its em component
+#(93em - 80mm).mm()
+
+--- length-ignore-em-inches-hint ---
+// Error: 2-24 cannot convert a length with non-zero em units (`432pt + 4.5em`) to inches
+// Hint: 2-24 use `length.abs.inches()` instead to ignore its em component
+#(4.5em + 6in).inches()
diff --git a/tests/suite/layout/limits.typ b/tests/suite/layout/limits.typ
new file mode 100644
index 00000000..e1f0ec5f
--- /dev/null
+++ b/tests/suite/layout/limits.typ
@@ -0,0 +1,32 @@
+// Test how the layout engine reacts when reaching limits like
+// zero, infinity or when dealing with NaN.
+
+--- issue-1216-clamp-panic ---
+#set page(height: 20pt, margin: 0pt)
+#v(22pt)
+#block(fill: red, width: 100%, height: 10pt, radius: 4pt)
+
+--- issue-1918-layout-infinite-length-grid-columns ---
+// Test that passing infinite lengths to drawing primitives does not crash Typst.
+#set page(width: auto, height: auto)
+
+// Error: 58-59 cannot expand into infinite width
+#layout(size => grid(columns: (size.width, size.height))[a][b][c][d])
+
+--- issue-1918-layout-infinite-length-grid-rows ---
+#set page(width: auto, height: auto)
+
+// Error: 17-66 cannot create grid with infinite height
+#layout(size => grid(rows: (size.width, size.height))[a][b][c][d])
+
+--- issue-1918-layout-infinite-length-line ---
+#set page(width: auto, height: auto)
+
+// Error: 17-41 cannot create line with infinite length
+#layout(size => line(length: size.width))
+
+--- issue-1918-layout-infinite-length-polygon ---
+#set page(width: auto, height: auto)
+
+// Error: 17-54 cannot create polygon with infinite size
+#layout(size => polygon((0pt,0pt), (0pt, size.width)))
diff --git a/tests/suite/layout/measure.typ b/tests/suite/layout/measure.typ
new file mode 100644
index 00000000..5f82e915
--- /dev/null
+++ b/tests/suite/layout/measure.typ
@@ -0,0 +1,9 @@
+--- measure ---
+// Test `measure`.
+#let f(lo, hi) = context {
+ let h = measure[Hello].height
+ assert(h > lo)
+ assert(h < hi)
+}
+#text(10pt, f(6pt, 8pt))
+#text(20pt, f(13pt, 14pt))
diff --git a/tests/suite/layout/pad.typ b/tests/suite/layout/pad.typ
new file mode 100644
index 00000000..3a7439d0
--- /dev/null
+++ b/tests/suite/layout/pad.typ
@@ -0,0 +1,30 @@
+// Test the `pad` function.
+
+--- pad-basic ---
+// Use for indentation.
+#pad(left: 10pt, [Indented!])
+
+// All sides together.
+#set rect(inset: 0pt)
+#rect(fill: conifer,
+ pad(10pt, right: 20pt,
+ rect(width: 20pt, height: 20pt, fill: rgb("eb5278"))
+ )
+)
+
+Hi #box(pad(left: 10pt)[A]) there
+
+--- pad-expanding-contents ---
+// Pad can grow.
+#pad(left: 10pt, right: 10pt)[PL #h(1fr) PR]
+
+--- pad-followed-by-content ---
+// Test that the pad element doesn't consume the whole region.
+#set page(height: 6cm)
+#align(left)[Before]
+#pad(10pt, image("/assets/images/tiger.jpg"))
+#align(right)[After]
+
+--- pad-adding-to-100-percent ---
+// Test that padding adding up to 100% does not panic.
+#pad(50%)[]
diff --git a/tests/suite/layout/page.typ b/tests/suite/layout/page.typ
new file mode 100644
index 00000000..a529b429
--- /dev/null
+++ b/tests/suite/layout/page.typ
@@ -0,0 +1,231 @@
+// Test the page class.
+
+--- page-call-empty ---
+// Just empty page.
+// Should result in auto-sized page, just like nothing.
+#page[]
+
+--- page-call-styled-empty ---
+// Just empty page with styles.
+// Should result in one conifer-colored A11 page.
+#page("a11", flipped: true, fill: conifer)[]
+
+--- page-call-followed-by-pagebreak ---
+// Just page followed by pagebreak.
+// Should result in one forest-colored A11 page and one auto-sized page.
+#page("a11", flipped: true, fill: forest)[]
+#pagebreak()
+
+--- page-set-forces-break ---
+// Set width and height.
+// Should result in one high and one wide page.
+#set page(width: 80pt, height: 80pt)
+#[#set page(width: 40pt);High]
+#[#set page(height: 40pt);Wide]
+
+// Flipped predefined paper.
+#[#set page(paper: "a11", flipped: true);Flipped A11]
+
+--- page-set-in-container ---
+#box[
+ // Error: 4-18 page configuration is not allowed inside of containers
+ #set page("a4")
+]
+
+--- page-set-empty ---
+// Empty with styles
+// Should result in one conifer-colored A11 page.
+#set page("a11", flipped: true, fill: conifer)
+
+--- page-set-only-pagebreak ---
+// Empty with styles and then pagebreak
+// Should result in two forest-colored pages.
+#set page(fill: forest)
+#pagebreak()
+
+--- page-set-override-thrice ---
+// Empty with multiple page styles.
+// Should result in a small white page.
+#set page("a4")
+#set page("a5")
+#set page(width: 1cm, height: 1cm)
+
+--- page-set-override-and-mix ---
+// Empty with multiple page styles.
+// Should result in one eastern-colored A11 page.
+#set page("a4")
+#set page("a5")
+#set page("a11", flipped: true, fill: eastern)
+#set text(font: "Roboto", white)
+#smallcaps[Typst]
+
+--- page-large ---
+#set page("a4")
+
+--- page-fill ---
+// Test page fill.
+#set page(width: 80pt, height: 40pt, fill: eastern)
+#text(15pt, font: "Roboto", fill: white, smallcaps[Typst])
+#page(width: 40pt, fill: none, margin: (top: 10pt, rest: auto))[Hi]
+
+--- page-margin-uniform ---
+// Set all margins at once.
+#[
+ #set page(height: 20pt, margin: 5pt)
+ #place(top + left)[TL]
+ #place(bottom + right)[BR]
+]
+
+--- page-margin-individual ---
+// Set individual margins.
+#set page(height: 40pt)
+#[#set page(margin: (left: 0pt)); #align(left)[Left]]
+#[#set page(margin: (right: 0pt)); #align(right)[Right]]
+#[#set page(margin: (top: 0pt)); #align(top)[Top]]
+#[#set page(margin: (bottom: 0pt)); #align(bottom)[Bottom]]
+
+// Ensure that specific margins override general margins.
+#[#set page(margin: (rest: 0pt, left: 20pt)); Overridden]
+
+--- page-margin-inside-outside-override ---
+#set page(height: 100pt, margin: (inside: 30pt, outside: 20pt))
+#set par(justify: true)
+#set text(size: 8pt)
+
+#page(margin: (x: 20pt), {
+ set align(center + horizon)
+ text(20pt, strong[Title])
+ v(2em, weak: true)
+ text(15pt)[Author]
+})
+
+= Introduction
+#lorem(35)
+
+--- page-margin-inside ---
+#set page(margin: (inside: 30pt))
+#rect(width: 100%)[Bound]
+#pagebreak()
+#rect(width: 100%)[Left]
+
+--- page-margin-inside-with-binding ---
+// Test setting the binding explicitly.
+#set page(binding: right, margin: (inside: 30pt))
+#rect(width: 100%)[Bound]
+#pagebreak()
+#rect(width: 100%)[Right]
+
+--- page-margin-binding-from-text-lang ---
+// Test setting the binding implicitly.
+#set page(margin: (inside: 30pt))
+#set text(lang: "he")
+#rect(width: 100%)[Bound]
+#pagebreak()
+#rect(width: 100%)[Right]
+
+--- page-margin-left-and-outside ---
+// Error: 19-44 `inside` and `outside` are mutually exclusive with `left` and `right`
+#set page(margin: (left: 1cm, outside: 2cm))
+
+--- page-margin-binding-bad ---
+// Error: 20-23 must be `left` or `right`
+#set page(binding: top)
+
+--- page-marginals ---
+#set page(
+ paper: "a8",
+ margin: (x: 15pt, y: 30pt),
+ header: {
+ text(eastern)[*Typst*]
+ h(1fr)
+ text(0.8em)[_Chapter 1_]
+ },
+ footer: context align(center)[\~ #counter(page).display() \~],
+ background: context if counter(page).get().first() <= 2 {
+ place(center + horizon, circle(radius: 1cm, fill: luma(90%)))
+ }
+)
+
+But, soft! what light through yonder window breaks? It is the east, and Juliet
+is the sun. Arise, fair sun, and kill the envious moon, Who is already sick and
+pale with grief, That thou her maid art far more fair than she: Be not her maid,
+since she is envious; Her vestal livery is but sick and green And none but fools
+do wear it; cast it off. It is my lady, O, it is my love! O, that she knew she
+were! She speaks yet she says nothing: what of that? Her eye discourses; I will
+answer it.
+
+#set page(header: none, height: auto, margin: (top: 15pt, bottom: 25pt))
+The END.
+
+--- page-number-align-top-right ---
+#set page(
+ height: 100pt,
+ margin: 30pt,
+ numbering: "(1)",
+ number-align: top + right,
+)
+
+#block(width: 100%, height: 100%, fill: aqua.lighten(50%))
+
+--- page-number-align-bottom-left ---
+#set page(
+ height: 100pt,
+ margin: 30pt,
+ numbering: "[1]",
+ number-align: bottom + left,
+)
+
+#block(width: 100%, height: 100%, fill: aqua.lighten(50%))
+
+--- page-number-align-left-horizon ---
+// Error: 25-39 expected `top` or `bottom`, found horizon
+#set page(number-align: left + horizon)
+
+--- page-numbering-pdf-label ---
+#set page(margin: (bottom: 20pt, rest: 10pt))
+#let filler = lorem(20)
+
+// (i) - (ii). No style opt. because of suffix.
+#set page(numbering: "(i)")
+#filler
+#pagebreak()
+#filler
+
+// 3 - 4. Style opt. Page Label should use /D style.
+#set page(numbering: "1")
+#filler
+#pagebreak()
+#filler
+
+// I - IV. Style opt. Page Label should use /R style and start at 1 again.
+#set page(numbering: "I / I")
+#counter(page).update(1)
+#filler
+#pagebreak()
+#filler
+#pagebreak()
+#filler
+#pagebreak()
+#filler
+
+// Pre: ほ, Pre: ろ, Pre: は, Pre: に. No style opt. Uses prefix field entirely.
+// Counter update without numbering change.
+#set page(numbering: "Pre: い")
+#filler
+#pagebreak()
+#filler
+#counter(page).update(2)
+#filler
+#pagebreak()
+#filler
+#pagebreak()
+#filler
+
+// aa & ba. Style opt only for values <= 26. Page Label uses lower alphabet style.
+// Repeats letter each 26 pages or uses numbering directly as prefix.
+#set page(numbering: "a")
+#counter(page).update(27)
+#filler
+#pagebreak()
+#counter(page).update(53)
+#filler
diff --git a/tests/suite/layout/pagebreak.typ b/tests/suite/layout/pagebreak.typ
new file mode 100644
index 00000000..a1734596
--- /dev/null
+++ b/tests/suite/layout/pagebreak.typ
@@ -0,0 +1,143 @@
+// Test forced page breaks.
+
+--- pagebreak ---
+// Just a pagebreak.
+// Should result in two pages.
+#pagebreak()
+
+--- pagebreak-around-set-page ---
+// Pagebreak, empty with styles and then pagebreak
+// Should result in one auto-sized page and two conifer-colored 2cm wide pages.
+#pagebreak()
+#set page(width: 2cm, fill: conifer)
+#pagebreak()
+
+--- pagebreak-weak-after-set-page ---
+// Two text bodies separated with and surrounded by weak pagebreaks.
+// Should result in two aqua-colored pages.
+#set page(fill: aqua)
+#pagebreak(weak: true)
+First
+#pagebreak(weak: true)
+Second
+#pagebreak(weak: true)
+
+--- pagebreak-set-page-mixed ---
+// Test a combination of pagebreaks, styled pages and pages with bodies.
+// Should result in three five pages, with the fourth one being forest-colored.
+#set page(width: 80pt, height: 30pt)
+#[#set page(width: 60pt); First]
+#pagebreak()
+#pagebreak()
+Third
+#page(height: 20pt, fill: forest)[]
+Fif#[#set page();th]
+
+--- pagebreak-followed-by-page-call ---
+// Test hard and weak pagebreak followed by page with body.
+// Should result in three navy-colored pages.
+#set page(fill: navy)
+#set text(fill: white)
+First
+#pagebreak()
+#page[Second]
+#pagebreak(weak: true)
+#page[Third]
+
+--- pagebreak-in-container ---
+#box[
+ // Error: 4-15 pagebreaks are not allowed inside of containers
+ #pagebreak()
+]
+
+--- pagebreak-weak-place ---
+// After place
+// Should result in three pages.
+First
+#pagebreak(weak: true)
+#place(right)[placed A]
+#pagebreak(weak: true)
+Third
+
+--- pagebreak-weak-meta ---
+// After only ignorables & invisibles
+// Should result in two pages.
+First
+#pagebreak(weak: true)
+#counter(page).update(1)
+#metadata("Some")
+#pagebreak(weak: true)
+Second
+
+--- pagebreak-meta ---
+// After only ignorables, but regular break
+// Should result in three pages.
+First
+#pagebreak()
+#counter(page).update(1)
+#metadata("Some")
+#pagebreak()
+Third
+
+--- pagebreak-to ---
+#set page(width: 80pt, height: 30pt)
+First
+#pagebreak(to: "odd")
+Third
+#pagebreak(to: "even")
+Fourth
+#pagebreak(to: "even")
+Sixth
+#pagebreak()
+Seventh
+#pagebreak(to: "odd")
+#page[Ninth]
+
+--- pagebreak-to-auto-sized ---
+#set page(width: auto, height: auto)
+
+// Test with auto-sized page.
+First
+#pagebreak(to: "odd")
+Third
+
+--- pagebreak-to-multiple-pages ---
+#set page(height: 30pt, width: 80pt)
+
+// Test when content extends to more than one page
+First
+
+Second
+
+#pagebreak(to: "odd")
+
+Third
+
+--- issue-2134-pagebreak-bibliography ---
+// Test weak pagebreak before bibliography.
+#pagebreak(weak: true)
+#bibliography("/assets/bib/works.bib")
+
+--- issue-2095-pagebreak-numbering ---
+// The empty page 2 should not have a page number
+#set page(numbering: none)
+This and next page should not be numbered
+
+#pagebreak(weak: true, to: "odd")
+
+#set page(numbering: "1")
+#counter(page).update(1)
+
+This page should
+
+--- issue-2162-pagebreak-set-style ---
+// The styles should not be applied to the pagebreak empty page,
+// it should only be applied after that.
+#pagebreak(to: "even") // We should now skip to page 2
+
+Some text on page 2
+
+#pagebreak(to: "even") // We should now skip to page 4
+
+#set page(fill: orange) // This sets the color of the page starting from page 4
+Some text on page 4
diff --git a/tests/suite/layout/place.typ b/tests/suite/layout/place.typ
new file mode 100644
index 00000000..b8765e93
--- /dev/null
+++ b/tests/suite/layout/place.typ
@@ -0,0 +1,226 @@
+// Test the `place` function.
+
+--- place-basic ---
+#set page("a8")
+#place(bottom + center)[© Typst]
+
+= Placement
+#place(right, image("/assets/images/tiger.jpg", width: 1.8cm))
+Hi there. This is \
+a placed element. \
+Unfortunately, \
+the line breaks still had to be inserted manually.
+
+#stack(
+ rect(fill: eastern, height: 10pt, width: 100%),
+ place(right, dy: 1.5pt)[ABC],
+ rect(fill: conifer, height: 10pt, width: 80%),
+ rect(fill: forest, height: 10pt, width: 100%),
+ 10pt,
+ block[
+ #place(center, dx: -7pt, dy: -5pt)[Hello]
+ #place(center, dx: 7pt, dy: 5pt)[Hello]
+ Hello #h(1fr) Hello
+ ]
+)
+
+--- place-block-spacing ---
+// Test how the placed element interacts with paragraph spacing around it.
+#set page("a8", height: 60pt)
+
+First
+
+#place(bottom + right)[Placed]
+
+Second
+
+--- place-background ---
+#set page(paper: "a10", flipped: true)
+#set text(fill: white)
+#place(
+ dx: -10pt,
+ dy: -10pt,
+ image(
+ "/assets/images/tiger.jpg",
+ fit: "cover",
+ width: 100% + 20pt,
+ height: 100% + 20pt,
+ )
+)
+#align(bottom + right)[
+ _Welcome to_ #underline[*Tigerland*]
+]
+
+--- place-float ---
+#set page(height: 140pt)
+#set place(clearance: 5pt)
+#lorem(6)
+#place(auto, float: true, rect[A])
+#place(auto, float: true, rect[B])
+#place(auto, float: true, rect[C])
+#place(auto, float: true, rect[D])
+
+--- place-float-missing ---
+// Error: 2-20 automatic positioning is only available for floating placement
+// Hint: 2-20 you can enable floating placement with `place(float: true, ..)`
+#place(auto)[Hello]
+
+--- place-float-center-horizon ---
+// Error: 2-45 floating placement must be `auto`, `top`, or `bottom`
+#place(center + horizon, float: true)[Hello]
+
+--- place-float-horizon ---
+// Error: 2-36 floating placement must be `auto`, `top`, or `bottom`
+#place(horizon, float: true)[Hello]
+
+--- place-float-default ---
+// Error: 2-27 floating placement must be `auto`, `top`, or `bottom`
+#place(float: true)[Hello]
+
+--- place-float-right ---
+// Error: 2-34 floating placement must be `auto`, `top`, or `bottom`
+#place(right, float: true)[Hello]
+
+--- place-float-columns ---
+// LARGE
+#set page(height: 200pt, width: 300pt)
+#show: columns.with(2)
+
+= Introduction
+#figure(
+ placement: bottom,
+ caption: [A glacier],
+ image("/assets/images/glacier.jpg", width: 50%),
+)
+#lorem(45)
+#figure(
+ placement: top,
+ caption: [A rectangle],
+ rect[Hello!],
+)
+#lorem(20)
+
+--- place-float-figure ---
+// LARGE
+#set page(height: 250pt, width: 150pt)
+
+= Introduction
+#lorem(10) #footnote[Lots of Latin]
+
+#figure(
+ placement: bottom,
+ caption: [A glacier #footnote[Lots of Ice]],
+ image("/assets/images/glacier.jpg", width: 80%),
+)
+
+#lorem(40)
+
+#figure(
+ placement: top,
+ caption: [An important],
+ image("/assets/images/diagram.svg", width: 80%),
+)
+
+--- place-bottom-in-box ---
+#box(
+ fill: aqua,
+ width: 30pt,
+ height: 30pt,
+ place(bottom,
+ place(line(start: (0pt, 0pt), end: (20pt, 0pt), stroke: red + 3pt))
+ )
+)
+
+--- place-horizon-in-boxes ---
+#box(
+ fill: aqua,
+ width: 30pt,
+ height: 30pt,
+ {
+ box(fill: yellow, {
+ [Hello]
+ place(horizon, line(start: (0pt, 0pt), end: (20pt, 0pt), stroke: red + 2pt))
+ })
+ place(horizon, line(start: (0pt, 0pt), end: (20pt, 0pt), stroke: green + 3pt))
+ }
+)
+
+--- place-bottom-right-in-box ---
+#box(fill: aqua)[
+ #place(bottom + right)[Hi]
+ Hello World \
+ How are \
+ you?
+]
+
+--- place-top-left-in-box ---
+#box(fill: aqua)[
+ #place(top + left, dx: 50%, dy: 50%)[Hi]
+ #v(30pt)
+ #line(length: 50pt)
+]
+
+--- issue-place-base ---
+// Test that placement is relative to container and not itself.
+#set page(height: 80pt, margin: 0pt)
+#place(right, dx: -70%, dy: 20%, [First])
+#place(left, dx: 20%, dy: 60%, [Second])
+#place(center + horizon, dx: 25%, dy: 25%, [Third])
+
+--- issue-1368-place-pagebreak ---
+// Test placing on an already full page.
+// It shouldn't result in a page break.
+#set page(height: 40pt)
+#block(height: 100%)
+#place(bottom + right)[Hello world]
+
+--- issue-2199-place-spacing-bottom ---
+// Test that placed elements don't add extra block spacing.
+#show figure: set block(spacing: 4em)
+
+Paragraph before float.
+#figure(rect(), placement: bottom)
+Paragraph after float.
+
+--- issue-2199-place-spacing-default ---
+#show place: set block(spacing: 4em)
+
+Paragraph before place.
+#place(rect())
+Paragraph after place.
+
+--- issue-2595-float-overlap ---
+#set page(height: 80pt)
+
+Start.
+
+#place(auto, float: true, [
+ #block(height: 100%, width: 100%, fill: aqua)
+])
+
+#place(auto, float: true, [
+ #block(height: 100%, width: 100%, fill: red)
+])
+
+#lorem(20)
+
+--- issue-2715-float-order ---
+#set page(height: 180pt)
+#set figure(placement: auto)
+
+#figure(
+ rect(height: 60pt),
+ caption: [Rectangle I],
+)
+
+#figure(
+ rect(height: 50pt),
+ caption: [Rectangle II],
+)
+
+#figure(
+ circle(),
+ caption: [Circle],
+)
+
+#lorem(20)
diff --git a/tests/suite/layout/relative.typ b/tests/suite/layout/relative.typ
new file mode 100644
index 00000000..958aee3d
--- /dev/null
+++ b/tests/suite/layout/relative.typ
@@ -0,0 +1,7 @@
+--- relative-fields ---
+// Test relative length fields.
+#test((100% + 2em + 2pt).ratio, 100%)
+#test((100% + 2em + 2pt).length, 2em + 2pt)
+#test((100% + 2pt).length, 2pt)
+#test((100% + 2pt - 2pt).length, 0pt)
+#test((56% + 2pt - 56%).ratio, 0%)
diff --git a/tests/suite/layout/repeat.typ b/tests/suite/layout/repeat.typ
new file mode 100644
index 00000000..5c82fc19
--- /dev/null
+++ b/tests/suite/layout/repeat.typ
@@ -0,0 +1,44 @@
+// Test the `repeat` function.
+
+--- repeat-basic ---
+// Test multiple repeats.
+#let sections = (
+ ("Introduction", 1),
+ ("Approach", 1),
+ ("Evaluation", 3),
+ ("Discussion", 15),
+ ("Related Work", 16),
+ ("Conclusion", 253),
+)
+
+#for section in sections [
+ #section.at(0) #box(width: 1fr, repeat[.]) #section.at(1) \
+]
+
+--- repeat-dots-rtl ---
+// Test dots with RTL.
+#set text(lang: "ar")
+مقدمة #box(width: 1fr, repeat[.]) 15
+
+--- repeat-empty ---
+// Test empty repeat.
+A #box(width: 1fr, repeat[]) B
+
+--- repeat-unboxed ---
+// Test unboxed repeat.
+#repeat(rect(width: 2em, height: 1em))
+
+--- repeat-align-and-dir ---
+// Test single repeat in both directions.
+A#box(width: 1fr, repeat(rect(width: 6em, height: 0.7em)))B
+
+#set align(center)
+A#box(width: 1fr, repeat(rect(width: 6em, height: 0.7em)))B
+
+#set text(dir: rtl)
+ريجين#box(width: 1fr, repeat(rect(width: 4em, height: 0.7em)))سون
+
+--- repeat-unrestricted ---
+// Error: 2:2-2:13 repeat with no size restrictions
+#set page(width: auto)
+#repeat(".")
diff --git a/tests/suite/layout/spacing.typ b/tests/suite/layout/spacing.typ
new file mode 100644
index 00000000..430e9779
--- /dev/null
+++ b/tests/suite/layout/spacing.typ
@@ -0,0 +1,38 @@
+// Test the `h` and `v` functions.
+
+--- spacing-h-and-v ---
+// Linebreak and leading-sized weak spacing are equivalent.
+#box[A \ B] #box[A #v(0.65em, weak: true) B]
+
+// Eating up soft spacing.
+Inv#h(0pt)isible
+
+// Multiple spacings in a row.
+Add #h(10pt) #h(10pt) up
+
+// Relative to area.
+#let x = 25% - 4pt
+|#h(x)|#h(x)|#h(x)|#h(x)|
+
+// Fractional.
+| #h(1fr) | #h(2fr) | #h(1fr) |
+
+--- spacing-rtl ---
+// Test RTL spacing.
+#set text(dir: rtl)
+A #h(10pt) B \
+A #h(1fr) B
+
+--- spacing-missing-amount ---
+// Missing spacing.
+// Error: 10-13 missing argument: amount
+Totally #h() ignored
+
+--- issue-3624-spacing-behaviour ---
+// Test that metadata after spacing does not force a new paragraph.
+#{
+ h(1em)
+ counter(heading).update(4)
+ [Hello ]
+ counter(heading).display()
+}
diff --git a/tests/suite/layout/stack.typ b/tests/suite/layout/stack.typ
new file mode 100644
index 00000000..1eca52c9
--- /dev/null
+++ b/tests/suite/layout/stack.typ
@@ -0,0 +1,82 @@
+// Test stack layouts.
+
+--- stack-basic ---
+// Test stacks with different directions.
+#let widths = (
+ 30pt, 20pt, 40pt, 15pt,
+ 30pt, 50%, 20pt, 100%,
+)
+
+#let shaded(i, w) = {
+ let v = (i + 1) * 10%
+ rect(width: w, height: 10pt, fill: rgb(v, v, v))
+}
+
+#let items = for (i, w) in widths.enumerate() {
+ (align(right, shaded(i, w)),)
+}
+
+#set page(width: 50pt, margin: 0pt)
+#stack(dir: btt, ..items)
+
+--- stack-spacing ---
+// Test spacing.
+#set page(width: 50pt, margin: 0pt)
+
+#let x = square(size: 10pt, fill: eastern)
+#stack(
+ spacing: 5pt,
+ stack(dir: rtl, spacing: 5pt, x, x, x),
+ stack(dir: ltr, x, 20%, x, 20%, x),
+ stack(dir: ltr, spacing: 5pt, x, x, 7pt, 3pt, x),
+)
+
+--- stack-overflow ---
+// Test overflow.
+#set page(width: 50pt, height: 30pt, margin: 0pt)
+#box(stack(
+ rect(width: 40pt, height: 20pt, fill: conifer),
+ rect(width: 30pt, height: 13pt, fill: forest),
+))
+
+--- stack-fr ---
+#set page(height: 3.5cm)
+#stack(
+ dir: ltr,
+ spacing: 1fr,
+ ..for c in "ABCDEFGHI" {([#c],)}
+)
+
+Hello
+#v(2fr)
+from #h(1fr) the #h(1fr) wonderful
+#v(1fr)
+World! 🌍
+
+--- stack-rtl-align-and-fr ---
+// Test aligning things in RTL stack with align function & fr units.
+#set page(width: 50pt, margin: 5pt)
+#set block(spacing: 5pt)
+#set text(8pt)
+#stack(dir: rtl, 1fr, [A], 1fr, [B], [C])
+#stack(dir: rtl,
+ align(center, [A]),
+ align(left, [B]),
+ [C],
+)
+
+--- issue-1240-stack-h-fr ---
+// This issue is sort of horrible: When you write `h(1fr)` in a `stack` instead
+// of directly `1fr`, things go awry. To fix this, we now transparently detect
+// h/v children.
+#stack(dir: ltr, [a], 1fr, [b], 1fr, [c])
+#stack(dir: ltr, [a], h(1fr), [b], h(1fr), [c])
+
+--- issue-1240-stack-v-fr ---
+#set page(height: 60pt)
+#stack(
+ dir: ltr,
+ spacing: 1fr,
+ stack([a], 1fr, [b]),
+ stack([a], v(1fr), [b]),
+)
diff --git a/tests/suite/layout/table.typ b/tests/suite/layout/table.typ
new file mode 100644
index 00000000..7eec46a1
--- /dev/null
+++ b/tests/suite/layout/table.typ
@@ -0,0 +1,284 @@
+// Test tables.
+
+--- table-empty ---
+#table()
+
+--- table-newlines ---
+#set page(height: 70pt)
+#set table(fill: (x, y) => if calc.even(x + y) { rgb("aaa") })
+
+#table(
+ columns: (1fr,) * 3,
+ stroke: 2pt + rgb("333"),
+ [A], [B], [C], [], [], [D \ E \ F \ \ \ G], [H],
+)
+
+--- table-fill-basic ---
+#table(columns: 3, stroke: none, fill: green, [A], [B], [C])
+
+--- table-fill-bad ---
+// Error: 14-19 expected color, gradient, pattern, none, array, or function, found string
+#table(fill: "hey")
+
+--- table-align-array ---
+// Test alignment with array.
+#table(
+ columns: (1fr, 1fr, 1fr),
+ align: (left, center, right),
+ [A], [B], [C]
+)
+
+// Test empty array.
+#set align(center)
+#table(
+ columns: (1fr, 1fr, 1fr),
+ align: (),
+ [A], [B], [C]
+)
+
+--- table-inset ---
+// Test inset.
+#table(
+ columns: 3,
+ inset: 10pt,
+ [A], [B], [C]
+)
+
+#table(
+ columns: 3,
+ inset: (y: 10pt),
+ [A], [B], [C]
+)
+
+#table(
+ columns: 3,
+ inset: (left: 20pt, rest: 10pt),
+ [A], [B], [C]
+)
+
+#table(
+ columns: 2,
+ inset: (
+ left: 20pt,
+ right: 5pt,
+ top: 10pt,
+ bottom: 3pt,
+ ),
+ [A],
+ [B],
+)
+
+#table(
+ columns: 3,
+ fill: (x, y) => (if y == 0 { aqua } else { orange }).darken(x * 15%),
+ inset: (x, y) => (left: if x == 0 { 0pt } else { 5pt }, right: if x == 0 { 5pt } else { 0pt }, y: if y == 0 { 0pt } else { 5pt }),
+ [A], [B], [C],
+ [A], [B], [C],
+)
+
+#table(
+ columns: 3,
+ inset: (0pt, 5pt, 10pt),
+ fill: (x, _) => aqua.darken(x * 15%),
+ [A], [B], [C],
+)
+
+--- table-inset-fold ---
+// Test inset folding
+#set table(inset: 10pt)
+#set table(inset: (left: 0pt))
+
+#table(
+ fill: red,
+ inset: (right: 0pt),
+ table.cell(inset: (top: 0pt))[a]
+)
+
+--- table-gutters ---
+// Test interaction with gutters.
+#table(
+ columns: (3em, 3em),
+ fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
+ align: (x, y) => (left, right).at(calc.rem(y, 2)),
+ [A], [B],
+ [C], [D],
+ [E], [F],
+ [G], [H]
+)
+
+#table(
+ columns: (3em, 3em),
+ fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
+ align: (x, y) => (left, right).at(calc.rem(y, 2)),
+ row-gutter: 5pt,
+ [A], [B],
+ [C], [D],
+ [E], [F],
+ [G], [H]
+)
+
+#table(
+ columns: (3em, 3em),
+ fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
+ align: (x, y) => (left, right).at(calc.rem(y, 2)),
+ column-gutter: 5pt,
+ [A], [B],
+ [C], [D],
+ [E], [F],
+ [G], [H]
+)
+
+#table(
+ columns: (3em, 3em),
+ fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
+ align: (x, y) => (left, right).at(calc.rem(y, 2)),
+ gutter: 5pt,
+ [A], [B],
+ [C], [D],
+ [E], [F],
+ [G], [H]
+)
+
+--- table-cell-override ---
+// Cell override
+#table(
+ align: left,
+ fill: red,
+ stroke: blue,
+ columns: 2,
+ [AAAAA], [BBBBB],
+ [A], [B],
+ table.cell(align: right)[C], [D],
+ align(right)[E], [F],
+ align(horizon)[G], [A\ A\ A],
+ table.cell(align: horizon)[G2], [A\ A\ A],
+ table.cell(inset: 0pt)[I], [F],
+ [H], table.cell(fill: blue)[J]
+)
+
+--- table-cell-show ---
+// Cell show rule
+#show table.cell: it => [Zz]
+
+#table(
+ align: left,
+ fill: red,
+ stroke: blue,
+ columns: 2,
+ [AAAAA], [BBBBB],
+ [A], [B],
+ table.cell(align: right)[C], [D],
+ align(right)[E], [F],
+ align(horizon)[G], [A\ A\ A]
+)
+
+--- table-cell-show-and-override ---
+#show table.cell: it => (it.align, it.fill)
+#table(
+ align: left,
+ row-gutter: 5pt,
+ [A],
+ table.cell(align: right)[B],
+ table.cell(fill: aqua)[B],
+)
+
+--- table-cell-set ---
+// Cell set rules
+#set table.cell(align: center)
+#show table.cell: it => (it.align, it.fill, it.inset)
+#set table.cell(inset: 20pt)
+#table(
+ align: left,
+ row-gutter: 5pt,
+ [A],
+ table.cell(align: right)[B],
+ table.cell(fill: aqua)[B],
+)
+
+--- table-cell-folding ---
+// Test folding per-cell properties (align and inset)
+#table(
+ columns: (1fr, 1fr),
+ rows: (2.5em, auto),
+ align: right,
+ fill: (x, y) => (green, aqua).at(calc.rem(x + y, 2)),
+ [Top], table.cell(align: bottom)[Bot],
+ table.cell(inset: (bottom: 0pt))[Bot], table.cell(inset: (bottom: 0pt))[Bot]
+)
+
+--- table-cell-align-override ---
+// Test overriding outside alignment
+#set align(bottom + right)
+#table(
+ columns: (1fr, 1fr),
+ rows: 2em,
+ align: auto,
+ fill: green,
+ [BR], [BR],
+ table.cell(align: left, fill: aqua)[BL], table.cell(align: top, fill: red.lighten(50%))[TR]
+)
+
+--- table-cell-various-overrides ---
+#table(
+ columns: 2,
+ fill: green,
+ align: right,
+ [*Name*], [*Data*],
+ table.cell(fill: blue)[J.], [Organizer],
+ table.cell(align: center)[K.], [Leader],
+ [M.], table.cell(inset: 0pt)[Player]
+)
+
+--- table-cell-show-emph ---
+#{
+ show table.cell: emph
+ table(
+ columns: 2,
+ [Person], [Animal],
+ [John], [Dog]
+ )
+}
+
+--- table-cell-show-based-on-position ---
+// Style based on position
+#{
+ show table.cell: it => {
+ if it.y == 0 {
+ strong(it)
+ } else if it.x == 1 {
+ emph(it)
+ } else {
+ it
+ }
+ }
+ table(
+ columns: 3,
+ gutter: 3pt,
+ [Name], [Age], [Info],
+ [John], [52], [Nice],
+ [Mary], [50], [Cool],
+ [Jake], [49], [Epic]
+ )
+}
+
+--- grid-cell-in-table ---
+// Error: 8-19 cannot use `grid.cell` as a table cell; use `table.cell` instead
+#table(grid.cell[])
+
+--- issue-183-table-lines ---
+// Ensure no empty lines before a table that doesn't fit into the first page.
+#set page(height: 50pt)
+
+Hello
+#table(
+ columns: 4,
+ [1], [2], [3], [4]
+)
+
+--- issue-1388-table-row-missing ---
+// Test that a table row isn't wrongly treated like a gutter row.
+#set page(height: 70pt)
+#table(
+ rows: 16pt,
+ ..range(6).map(str).flatten(),
+)
diff --git a/tests/suite/layout/transform.typ b/tests/suite/layout/transform.typ
new file mode 100644
index 00000000..50a6d417
--- /dev/null
+++ b/tests/suite/layout/transform.typ
@@ -0,0 +1,106 @@
+// Test transformations.
+
+--- transform-tex-logo ---
+// Test creating the TeX and XeTeX logos.
+#let size = 11pt
+#let tex = {
+ [T]
+ h(-0.14 * size)
+ box(move(dy: 0.22 * size)[E])
+ h(-0.12 * size)
+ [X]
+}
+
+#let xetex = {
+ [X]
+ h(-0.14 * size)
+ box(scale(x: -100%, move(dy: 0.26 * size)[E]))
+ h(-0.14 * size)
+ [T]
+ h(-0.14 * size)
+ box(move(dy: 0.26 * size)[E])
+ h(-0.12 * size)
+ [X]
+}
+
+#set text(font: "New Computer Modern", size)
+Neither #tex, \
+nor #xetex!
+
+--- transform-rotate-and-scale ---
+// Test combination of scaling and rotation.
+#set page(height: 80pt)
+#align(center + horizon,
+ rotate(20deg, scale(70%, image("/assets/images/tiger.jpg")))
+)
+
+--- transform-rotate-origin ---
+// Test setting rotation origin.
+#rotate(10deg, origin: top + left,
+ image("/assets/images/tiger.jpg", width: 50%)
+)
+
+--- transform-scale-origin ---
+// Test setting scaling origin.
+#let r = rect(width: 100pt, height: 10pt, fill: forest)
+#set page(height: 65pt)
+#box(scale(r, x: 50%, y: 200%, origin: left + top))
+#box(scale(r, x: 50%, origin: center))
+#box(scale(r, x: 50%, y: 200%, origin: right + bottom))
+
+--- transform-rotate ---
+// Test that rotation impact layout.
+#set page(width: 200pt)
+#set rotate(reflow: true)
+
+#let one(angle) = box(fill: aqua, rotate(angle)[Test Text])
+#for angle in range(0, 360, step: 15) {
+ one(angle * 1deg)
+}
+
+--- transform-rotate-relative-sizing ---
+// Test relative sizing in rotated boxes.
+#set page(width: 200pt, height: 200pt)
+#set text(size: 32pt)
+#let rotated(body) = box(rotate(
+ 90deg,
+ box(stroke: 0.5pt, height: 20%, clip: true, body)
+))
+
+#set rotate(reflow: false)
+Hello #rotated[World]!\
+
+#set rotate(reflow: true)
+Hello #rotated[World]!
+
+--- transform-scale ---
+// Test that scaling impact layout.
+#set page(width: 200pt)
+#set text(size: 32pt)
+#let scaled(body) = box(scale(
+ x: 20%,
+ y: 40%,
+ body
+))
+
+#set scale(reflow: false)
+Hello #scaled[World]!
+
+#set scale(reflow: true)
+Hello #scaled[World]!
+
+--- transform-scale-relative-sizing ---
+// Test relative sizing in scaled boxes.
+#set page(width: 200pt, height: 200pt)
+#set text(size: 32pt)
+#let scaled(body) = box(scale(
+ x: 60%,
+ y: 40%,
+ box(stroke: 0.5pt, width: 30%, clip: true, body)
+))
+
+#set scale(reflow: false)
+Hello #scaled[World]!\
+
+#set scale(reflow: true)
+Hello #scaled[World]!
diff --git a/tests/suite/loading/csv.typ b/tests/suite/loading/csv.typ
new file mode 100644
index 00000000..415488fc
--- /dev/null
+++ b/tests/suite/loading/csv.typ
@@ -0,0 +1,27 @@
+--- csv ---
+// Test reading CSV data.
+#set page(width: auto)
+#let data = csv("/assets/data/zoo.csv")
+#let cells = data.at(0).map(strong) + data.slice(1).flatten()
+#table(columns: data.at(0).len(), ..cells)
+
+--- csv-row-type-dict ---
+// Test reading CSV data with dictionary rows enabled.
+#let data = csv("/assets/data/zoo.csv", row-type: dictionary)
+#test(data.len(), 3)
+#test(data.at(0).Name, "Debby")
+#test(data.at(2).Weight, "150kg")
+#test(data.at(1).Species, "Tiger")
+
+--- csv-file-not-found ---
+// Error: 6-16 file not found (searched at tests/suite/loading/nope.csv)
+#csv("nope.csv")
+
+--- csv-invalid ---
+// Error: 6-28 failed to parse CSV (found 3 instead of 2 fields in line 3)
+#csv("/assets/data/bad.csv")
+
+--- csv-invalid-row-type-dict ---
+// Test error numbering with dictionary rows.
+// Error: 6-28 failed to parse CSV (found 3 instead of 2 fields in line 3)
+#csv("/assets/data/bad.csv", row-type: dictionary)
diff --git a/tests/suite/loading/json.typ b/tests/suite/loading/json.typ
new file mode 100644
index 00000000..3ebeaf2f
--- /dev/null
+++ b/tests/suite/loading/json.typ
@@ -0,0 +1,16 @@
+--- json ---
+// Test reading JSON data.
+#let data = json("/assets/data/zoo.json")
+#test(data.len(), 3)
+#test(data.at(0).name, "Debby")
+#test(data.at(2).weight, 150)
+
+--- json-invalid ---
+// Error: 7-30 failed to parse JSON (expected value at line 3 column 14)
+#json("/assets/data/bad.json")
+
+--- issue-3363-json-large-number ---
+// Big numbers (larger than what i64 can store) should just lose some precision
+// but not overflow
+#let bignum = json("/assets/data/big-number.json")
+#bignum
diff --git a/tests/suite/loading/read.typ b/tests/suite/loading/read.typ
new file mode 100644
index 00000000..b5c9c089
--- /dev/null
+++ b/tests/suite/loading/read.typ
@@ -0,0 +1,12 @@
+--- read-text ---
+// Test reading plain text files
+#let data = read("/assets/text/hello.txt")
+#test(data, "Hello, world!\n")
+
+--- read-file-not-found ---
+// Error: 18-44 file not found (searched at assets/text/missing.txt)
+#let data = read("/assets/text/missing.txt")
+
+--- read-invalid-utf-8 ---
+// Error: 18-40 file is not valid utf-8
+#let data = read("/assets/text/bad.txt")
diff --git a/tests/suite/loading/toml.typ b/tests/suite/loading/toml.typ
new file mode 100644
index 00000000..855ca995
--- /dev/null
+++ b/tests/suite/loading/toml.typ
@@ -0,0 +1,41 @@
+--- toml ---
+// Test reading TOML data.
+#let data = toml("/assets/data/toml-types.toml")
+#test(data.string, "wonderful")
+#test(data.integer, 42)
+#test(data.float, 3.14)
+#test(data.boolean, true)
+#test(data.array, (1, "string", 3.0, false))
+#test(data.inline_table, ("first": "amazing", "second": "greater") )
+#test(data.table.element, 5)
+#test(data.table.others, (false, "indeed", 7))
+#test(data.date_time, datetime(
+ year: 2023,
+ month: 2,
+ day: 1,
+ hour: 15,
+ minute: 38,
+ second: 57,
+))
+#test(data.date_time2, datetime(
+ year: 2023,
+ month: 2,
+ day: 1,
+ hour: 15,
+ minute: 38,
+ second: 57,
+))
+#test(data.date, datetime(
+ year: 2023,
+ month: 2,
+ day: 1,
+))
+#test(data.time, datetime(
+ hour: 15,
+ minute: 38,
+ second: 57,
+))
+
+--- toml-invalid ---
+// Error: 7-30 failed to parse TOML (expected `.`, `=` at line 1 column 16)
+#toml("/assets/data/bad.toml")
diff --git a/tests/suite/loading/xml.typ b/tests/suite/loading/xml.typ
new file mode 100644
index 00000000..41cd20e7
--- /dev/null
+++ b/tests/suite/loading/xml.typ
@@ -0,0 +1,28 @@
+--- xml ---
+// Test reading XML data.
+#let data = xml("/assets/data/hello.xml")
+#test(data, ((
+ tag: "data",
+ attrs: (:),
+ children: (
+ "\n ",
+ (tag: "hello", attrs: (name: "hi"), children: ("1",)),
+ "\n ",
+ (
+ tag: "data",
+ attrs: (:),
+ children: (
+ "\n ",
+ (tag: "hello", attrs: (:), children: ("World",)),
+ "\n ",
+ (tag: "hello", attrs: (:), children: ("World",)),
+ "\n ",
+ ),
+ ),
+ "\n",
+ ),
+),))
+
+--- xml-invalid ---
+// Error: 6-28 failed to parse XML (found closing tag 'data' instead of 'hello' in line 3)
+#xml("/assets/data/bad.xml")
diff --git a/tests/suite/loading/yaml.typ b/tests/suite/loading/yaml.typ
new file mode 100644
index 00000000..bbfea41c
--- /dev/null
+++ b/tests/suite/loading/yaml.typ
@@ -0,0 +1,17 @@
+--- yaml ---
+// Test reading YAML data
+#let data = yaml("/assets/data/yaml-types.yaml")
+#test(data.len(), 9)
+#test(data.null_key, (none, none))
+#test(data.string, "text")
+#test(data.integer, 5)
+#test(data.float, 1.12)
+#test(data.mapping, ("1": "one", "2": "two"))
+#test(data.seq, (1,2,3,4))
+#test(data.bool, false)
+#test(data.keys().contains("true"), true)
+#test(data.at("1"), "ok")
+
+--- yaml-invalid ---
+// Error: 7-30 failed to parse YAML (did not find expected ',' or ']' at line 2 column 1, while parsing a flow sequence at line 1 column 18)
+#yaml("/assets/data/bad.yaml")
diff --git a/tests/suite/math/accent.typ b/tests/suite/math/accent.typ
new file mode 100644
index 00000000..9f57d69b
--- /dev/null
+++ b/tests/suite/math/accent.typ
@@ -0,0 +1,33 @@
+// Test math accents.
+
+--- math-accent-sym-call ---
+// Test function call.
+$grave(a), acute(b), hat(f), tilde(§), macron(ä), diaer(a), ä \
+ breve(\&), dot(!), circle(a), caron(@), arrow(Z), arrow.l(Z)$
+
+--- math-accent-align ---
+$ x &= p \ dot(x) &= v \ dot.double(x) &= a \ dot.triple(x) &= j \ dot.quad(x) &= s $
+
+--- math-accent-func ---
+// Test `accent` function.
+$accent(ö, .), accent(v, <-), accent(ZZ, \u{0303})$
+
+--- math-accent-bounds ---
+// Test accent bounds.
+$sqrt(tilde(T)) + hat(f)/hat(g)$
+
+--- math-accent-wide-base ---
+// Test wide base.
+$arrow("ABC" + d), tilde(sum)$
+
+--- math-accent-superscript ---
+// Test effect of accent on superscript.
+$A^x != hat(A)^x != hat(hat(A))^x$
+
+--- math-accent-high-base ---
+// Test high base.
+$ tilde(integral), tilde(integral)_a^b, tilde(integral_a^b) $
+
+--- math-accent-sized ---
+// Test accent size.
+$tilde(sum), tilde(sum, size: #50%), accent(H, hat, size: #200%)$
diff --git a/tests/suite/math/alignment.typ b/tests/suite/math/alignment.typ
new file mode 100644
index 00000000..f110b139
--- /dev/null
+++ b/tests/suite/math/alignment.typ
@@ -0,0 +1,34 @@
+// Test implicit alignment math.
+
+--- math-align-weird ---
+// Test alignment step functions.
+#set page(width: 225pt)
+$
+"a" &= c \
+&= c + 1 & "By definition" \
+&= d + 100 + 1000 \
+&= x && "Even longer" \
+$
+
+--- math-align-post-fix ---
+// Test post-fix alignment.
+$
+& "right" \
+"a very long line" \
+"left" \
+$
+
+--- math-align-implicit ---
+// Test no alignment.
+$
+"right" \
+"a very long line" \
+"left" \
+$
+
+--- math-align-toggle ---
+// Test #460 equations.
+$
+a &=b & quad c&=d \
+e &=f & g&=h
+$
diff --git a/tests/suite/math/attach.typ b/tests/suite/math/attach.typ
new file mode 100644
index 00000000..c9510c6a
--- /dev/null
+++ b/tests/suite/math/attach.typ
@@ -0,0 +1,130 @@
+// Test t and b attachments, part 1.
+
+--- math-attach-postscripts ---
+// Test basics, postscripts.
+$f_x + t^b + V_1^2 + attach(A, t: alpha, b: beta)$
+
+--- math-attach-prescripts ---
+// Test basics, prescripts. Notably, the upper and lower prescripts' content need to be
+// aligned on the right edge of their bounding boxes, not on the left as in postscripts.
+$
+attach(upright(O), bl: 8, tl: 16, br: 2, tr: 2-),
+attach("Pb", bl: 82, tl: 207) + attach(upright(e), bl: -1, tl: 0) + macron(v)_e \
+$
+
+--- math-attach-mixed ---
+// A mixture of attachment positioning schemes.
+$
+attach(a, tl: u), attach(a, tr: v), attach(a, bl: x),
+attach(a, br: y), limits(a)^t, limits(a)_b \
+
+attach(a, tr: v, t: t),
+attach(a, tr: v, br: y),
+attach(a, br: y, b: b),
+attach(limits(a), b: b, bl: x),
+attach(a, tl: u, bl: x),
+attach(limits(a), t: t, tl: u) \
+
+attach(a, tl: u, tr: v),
+attach(limits(a), t: t, br: y),
+attach(limits(a), b: b, tr: v),
+attach(a, bl: x, br: y),
+attach(limits(a), b: b, tl: u),
+attach(limits(a), t: t, bl: u),
+limits(a)^t_b \
+
+attach(a, tl: u, tr: v, bl: x, br: y),
+attach(limits(a), t: t, bl: x, br: y, b: b),
+attach(limits(a), t: t, tl: u, tr: v, b: b),
+attach(limits(a), tl: u, bl: x, t: t, b: b),
+attach(limits(a), t: t, b: b, tr: v, br: y),
+attach(a, tl: u, t: t, tr: v, bl: x, b: b, br: y)
+$
+
+--- math-attach-followed-by-func-call ---
+// Test function call after subscript.
+$pi_1(Y), a_f(x), a^zeta (x), a^abs(b)_sqrt(c) \
+ a^subset.eq (x), a_(zeta(x)), pi_(1(Y)), a^(abs(b))_(sqrt(c))$
+
+--- math-attach-nested ---
+// Test associativity and scaling.
+$ 1/(V^2^3^4^5),
+ frac(
+ attach(
+ limits(V), br: attach(2, br: 3), b: attach(limits(2), b: 3)),
+ attach(
+ limits(V), tl: attach(2, tl: 3), t: attach(limits(2), t: 3))),
+ attach(Omega,
+ tl: attach(2, tl: attach(3, tl: attach(4, tl: 5))),
+ tr: attach(2, tr: attach(3, tr: attach(4, tr: 5))),
+ bl: attach(2, bl: attach(3, bl: attach(4, bl: 5))),
+ br: attach(2, br: attach(3, br: attach(4, br: 5))),
+ )
+$
+
+--- math-attach-high ---
+// Test high subscript and superscript.
+$ sqrt(a_(1/2)^zeta), sqrt(a_alpha^(1/2)), sqrt(a_(1/2)^(3/4)) \
+ sqrt(attach(a, tl: 1/2, bl: 3/4)),
+ sqrt(attach(a, tl: 1/2, bl: 3/4, tr: 1/2, br: 3/4)) $
+
+--- math-attach-descender-collision ---
+// Test for no collisions between descenders/ascenders and attachments
+
+$ sup_(x in P_i) quad inf_(x in P_i) $
+$ op("fff",limits: #true)^(y) quad op("yyy", limits:#true)_(f) $
+
+--- math-attach-to-group ---
+// Test frame base.
+$ (-1)^n + (1/2 + 3)^(-1/2) $
+
+--- math-attach-horizontal-align ---
+#set text(size: 8pt)
+
+// Test that the attachments are aligned horizontally.
+$ x_1 p_1 frak(p)_1 2_1 dot_1 lg_1 !_1 \\_1 ]_1 "ip"_1 op("iq")_1 \
+ x^1 b^1 frak(b)^1 2^1 dot^1 lg^1 !^1 \\^1 ]^1 "ib"^1 op("id")^1 \
+ x_1 y_1 "_"_1 x^1 l^1 "`"^1 attach(I,tl:1,bl:1,tr:1,br:1)
+ scripts(sum)_1^1 integral_1^1 abs(1/2)_1^1 \
+ x^1_1, "("b y")"^1_1 != (b y)^1_1, "[∫]"_1 [integral]_1 $
+
+--- math-attach-limit ---
+// Test limit.
+$ lim_(n->oo \ n "grows") sum_(k=0 \ k in NN)^n k $
+
+--- math-attach-force-scripts-and-limits ---
+// Test forcing scripts and limits.
+$ limits(A)_1^2 != A_1^2 $
+$ scripts(sum)_1^2 != sum_1^2 $
+$ limits(integral)_a^b != integral_a^b $
+
+--- issue-math-attach-realize-panic ---
+// Error: 25-29 unknown variable: oops
+$ attach(A, t: #context oops) $
+
+--- math-attach-show-limit ---
+// Show and let rules for limits and scripts
+#let eq = $ ∫_a^b iota_a^b $
+#eq
+#show "∫": math.limits
+#show math.iota: math.limits.with(inline: false)
+#eq
+$iota_a^b$
+
+--- math-attach-default-placement ---
+// Test default of limit attachments on relations at all sizes
+#set page(width: auto)
+$ a =^"def" b quad a lt.eq_"really" b quad a arrow.r.long.squiggly^"slowly" b $
+$a =^"def" b quad a lt.eq_"really" b quad a arrow.r.long.squiggly^"slowly" b$
+
+$a scripts(=)^"def" b quad a scripts(lt.eq)_"really" b quad a scripts(arrow.r.long.squiggly)^"slowly" b$
+
+--- math-attach-integral ---
+// Test default of scripts attachments on integrals at display size
+$ integral.sect_a^b quad \u{2a1b}_a^b quad limits(\u{2a1b})_a^b $
+$integral.sect_a^b quad \u{2a1b}_a^b quad limits(\u{2a1b})_a^b$
+
+--- math-attach-large-operator ---
+// Test default of limit attachments on large operators at display size only
+$ tack.t.big_0^1 quad \u{02A0A}_0^1 quad join_0^1 $
+$tack.t.big_0^1 quad \u{02A0A}_0^1 quad join_0^1$
diff --git a/tests/suite/math/cancel.typ b/tests/suite/math/cancel.typ
new file mode 100644
index 00000000..e2fd5efd
--- /dev/null
+++ b/tests/suite/math/cancel.typ
@@ -0,0 +1,38 @@
+// Tests the cancel() function.
+
+--- math-cancel-inline ---
+// Inline
+$a + 5 + cancel(x) + b - cancel(x)$
+
+$c + (a dot.c cancel(b dot.c c))/(cancel(b dot.c c))$
+
+--- math-cancel-display ---
+// Display
+#set page(width: auto)
+$ a + b + cancel(b + c) - cancel(b) - cancel(c) - 5 + cancel(6) - cancel(6) $
+$ e + (a dot.c cancel((b + c + d)))/(cancel(b + c + d)) $
+
+--- math-cancel-inverted ---
+// Inverted
+$a + cancel(x, inverted: #true) - cancel(x, inverted: #true) + 10 + cancel(y) - cancel(y)$
+$ x + cancel("abcdefg", inverted: #true) $
+
+--- math-cancel-cross ---
+// Cross
+$a + cancel(b + c + d, cross: #true, stroke: #red) + e$
+$ a + cancel(b + c + d, cross: #true) + e $
+
+--- math-cancel-customized ---
+// Resized and styled
+#set page(width: 200pt, height: auto)
+$a + cancel(x, length: #200%) - cancel(x, length: #50%, stroke: #(red + 1.1pt))$
+$ b + cancel(x, length: #150%) - cancel(a + b + c, length: #50%, stroke: #(blue + 1.2pt)) $
+
+--- math-cancel-angle-absolute ---
+// Specifying cancel line angle with an absolute angle
+$cancel(x, angle: #0deg) + cancel(x, angle: #45deg) + cancel(x, angle: #90deg) + cancel(x, angle: #135deg)$
+
+--- math-cancel-angle-func ---
+// Specifying cancel line angle with a function
+$x + cancel(y, angle: #{angle => angle + 90deg}) - cancel(z, angle: #(angle => angle + 135deg))$
+$ e + cancel((j + e)/(f + e)) - cancel((j + e)/(f + e), angle: #(angle => angle + 30deg)) $
diff --git a/tests/suite/math/cases.typ b/tests/suite/math/cases.typ
new file mode 100644
index 00000000..e6c4956d
--- /dev/null
+++ b/tests/suite/math/cases.typ
@@ -0,0 +1,13 @@
+// Test case distinction.
+
+--- math-cases ---
+$ f(x, y) := cases(
+ 1 quad &"if" (x dot y)/2 <= 0,
+ 2 &"if" x divides 2,
+ 3 &"if" x in NN,
+ 4 &"else",
+) $
+
+--- math-cases-gap ---
+#set math.cases(gap: 1em)
+$ x = cases(1, 2) $
diff --git a/tests/suite/math/class.typ b/tests/suite/math/class.typ
new file mode 100644
index 00000000..7aad0446
--- /dev/null
+++ b/tests/suite/math/class.typ
@@ -0,0 +1,47 @@
+// Test math classes.
+
+--- math-class-chars ---
+// Test characters.
+$ a class("normal", +) b \
+ a class("binary", .) b \
+ lr(class("opening", \/) a/b class("closing", \\)) \
+ { x class("fence", \;) x > 0} \
+ a class("large", \/) b \
+ a class("punctuation", :) b \
+ a class("relation", ~) b \
+ a + class("unary", times) b \
+ class("vary", :) a class("vary", :) b $
+
+--- math-class-content ---
+// Test custom content.
+#let dotsq = square(
+ size: 0.7em,
+ stroke: 0.5pt,
+ align(center+horizon, circle(radius: 0.15em, fill: black))
+)
+
+$ a dotsq b \
+ a class("normal", dotsq) b \
+ a class("vary", dotsq) b \
+ a + class("vary", dotsq) b \
+ a class("punctuation", dotsq) b $
+
+--- math-class-nested ---
+// Test nested.
+#let normal = math.class.with("normal")
+#let pluseq = $class("binary", normal(+) normal(=))$
+$ a pluseq 5 $
+
+--- math-class-exceptions ---
+// Test exceptions.
+$ sqrt(3)\/2 quad d_0.d_1d_2 dots $
+
+--- math-class-limits ---
+// Test if the math class changes the limit configuration.
+$ class("normal", ->)_a $
+$class("relation", x)_a$
+$ class("large", x)_a $
+$class("large", ->)_a$
+
+$limits(class("normal", ->))_a$
+$ scripts(class("relation", x))_a $
diff --git a/tests/suite/math/delimited.typ b/tests/suite/math/delimited.typ
new file mode 100644
index 00000000..98579947
--- /dev/null
+++ b/tests/suite/math/delimited.typ
@@ -0,0 +1,64 @@
+// Test delimiter matching and scaling.
+
+--- math-lr-matching ---
+// Test automatic matching.
+#set page(width:122pt)
+$ (a) + {b/2} + abs(a)/2 + (b) $
+$f(x/2) < zeta(c^2 + abs(a + b/2))$
+
+--- math-lr-unmatched ---
+// Test unmatched.
+$[1,2[ = [1,2) != zeta\(x/2\) $
+
+--- math-lr-call ---
+// Test manual matching.
+$ [|a/b|] != lr(|]a/b|]) != [a/b) $
+$ lr(| ]1,2\[ + 1/2|) $
+
+--- math-lr-fences ---
+// Test fence confusion.
+$ |x + |y| + z/a| \
+ lr(|x + |y| + z/a|) $
+
+--- math-lr-symbol-unmatched ---
+// Test that symbols aren't matched automatically.
+$ bracket.l a/b bracket.r
+ = lr(bracket.l a/b bracket.r) $
+
+--- math-lr-half ---
+// Test half LRs.
+$ lr(a/b\]) = a = lr(\{a/b) $
+
+--- math-lr-size ---
+// Test manual scaling.
+$ lr(]sum_(x=1)^n x], size: #70%)
+ < lr((1, 2), size: #200%) $
+
+--- math-lr-shorthands ---
+// Test predefined delimiter pairings.
+$floor(x/2), ceil(x/2), abs(x), norm(x)$
+
+--- math-lr-color ---
+// Test colored delimiters
+$ lr(
+ text("(", fill: #green) a/b
+ text(")", fill: #blue)
+ ) $
+
+--- math-lr-mid ---
+// Test middle functions
+$ { x mid(|) sum_(i=1)^oo phi_i (x) < 1 } \
+ { integral |x| dif x
+ mid(bar.v.double)
+ floor(hat(A) mid(|) { x mid(|) y } mid(|) A) } $
+
+--- math-lr-unbalanced ---
+// Test unbalanced delimiters.
+$ 1/(2 (x) $
+$ 1_(2 y (x) () $
+$ 1/(2 y (x) (2(3)) $
+
+--- math-lr-weak-spacing ---
+// Test ignoring weak spacing immediately after the opening
+// and immediately before the closing.
+$ [#h(1em, weak: true)A(dif x, f(x) dif x)sum#h(1em, weak: true)] $
diff --git a/tests/suite/math/equation.typ b/tests/suite/math/equation.typ
new file mode 100644
index 00000000..dd2745d1
--- /dev/null
+++ b/tests/suite/math/equation.typ
@@ -0,0 +1,212 @@
+// Test alignment of block equations.
+// Test show rules on equations.
+
+--- math-equation-numbering ---
+#set page(width: 150pt)
+#set math.equation(numbering: "(I)")
+
+We define $x$ in preparation of @fib:
+$ phi.alt := (1 + sqrt(5)) / 2 $ <ratio>
+
+With @ratio, we get
+$ F_n = round(1 / sqrt(5) phi.alt^n) $ <fib>
+
+--- math-equation-font ---
+// Test different font.
+#show math.equation: set text(font: "Fira Math")
+$ v := vec(1 + 2, 2 - 4, sqrt(3), arrow(x)) + 1 $
+
+--- math-equation-show-rule ---
+This is small: $sum_(i=0)^n$
+
+#show math.equation: math.display
+This is big: $sum_(i=0)^n$
+
+--- math-equation-align-unnumbered ---
+// Test unnumbered
+#let eq(alignment) = {
+ show math.equation: set align(alignment)
+ $ a + b = c $
+}
+
+#eq(center)
+#eq(left)
+#eq(right)
+
+#set text(dir: rtl)
+#eq(start)
+#eq(end)
+
+--- math-equation-align-numbered ---
+// Test numbered
+#let eq(alignment) = {
+ show math.equation: set align(alignment)
+ $ a + b = c $
+}
+
+#set math.equation(numbering: "(1)")
+
+#eq(center)
+#eq(left)
+#eq(right)
+
+#set text(dir: rtl)
+#eq(start)
+#eq(end)
+
+--- math-equation-number-align ---
+#set math.equation(numbering: "(1)")
+
+$ a + b = c $
+
+#show math.equation: set align(center)
+$ a + b = c $
+#show math.equation: set align(left)
+$ a + b = c $
+#show math.equation: set align(right)
+$ a + b = c $
+
+#set text(dir: rtl)
+#show math.equation: set align(start)
+$ a + b = c $
+#show math.equation: set align(end)
+$ a + b = c $
+
+--- math-equation-number-align-start ---
+#set math.equation(numbering: "(1)", number-align: start)
+
+$ a + b = c $
+
+#show math.equation: set align(center)
+$ a + b = c $
+#show math.equation: set align(left)
+$ a + b = c $
+#show math.equation: set align(right)
+$ a + b = c $
+
+#set text(dir: rtl)
+#show math.equation: set align(start)
+$ a + b = c $
+#show math.equation: set align(end)
+$ a + b = c $
+
+--- math-equation-number-align-end ---
+#set math.equation(numbering: "(1)", number-align: end)
+
+$ a + b = c $
+
+#show math.equation: set align(center)
+$ a + b = c $
+#show math.equation: set align(left)
+$ a + b = c $
+#show math.equation: set align(right)
+$ a + b = c $
+
+#set text(dir: rtl)
+#show math.equation: set align(start)
+$ a + b = c $
+#show math.equation: set align(end)
+$ a + b = c $
+
+--- math-equation-number-align-left ---
+#set math.equation(numbering: "(1)", number-align: left)
+
+$ a + b = c $
+
+#show math.equation: set align(center)
+$ a + b = c $
+#show math.equation: set align(left)
+$ a + b = c $
+#show math.equation: set align(right)
+$ a + b = c $
+
+#set text(dir: rtl)
+#show math.equation: set align(start)
+$ a + b = c $
+#show math.equation: set align(end)
+$ a + b = c $
+
+--- math-equation-number-align-right ---
+#set math.equation(numbering: "(1)", number-align: right)
+
+$ a + b = c $
+
+#show math.equation: set align(center)
+$ a + b = c $
+#show math.equation: set align(left)
+$ a + b = c $
+#show math.equation: set align(right)
+$ a + b = c $
+
+#set text(dir: rtl)
+#show math.equation: set align(start)
+$ a + b = c $
+#show math.equation: set align(end)
+$ a + b = c $
+
+--- math-equation-number-align-center ---
+// Error: 52-58 expected `start`, `left`, `right`, or `end`, found center
+#set math.equation(numbering: "(1)", number-align: center)
+
+--- math-equation-number-align-center-bottom ---
+// Error: 52-67 expected `start`, `left`, `right`, or `end`, found center
+#set math.equation(numbering: "(1)", number-align: center + bottom)
+
+--- math-equation-number-align-multiline ---
+#set math.equation(numbering: "(1)")
+
+$ p &= ln a b \
+ &= ln a + ln b $
+
+--- math-equation-number-align-multiline-top-start ---
+#set math.equation(numbering: "(1)", number-align: top+start)
+
+$ p &= ln a b \
+ &= ln a + ln b $
+
+--- math-equation-number-align-multiline-bottom ---
+#show math.equation: set align(left)
+#set math.equation(numbering: "(1)", number-align: bottom)
+
+$ q &= ln sqrt(a b) \
+ &= 1/2 (ln a + ln b) $
+
+--- math-equation-number-align-multiline-expand ---
+// Tests that if the numbering's layout box vertically exceeds the box of
+// the equation frame's boundary, the latter's frame is resized correctly
+// to encompass the numbering. #box() below delineates the resized frame.
+//
+// A row with "-" only has a height that's smaller than the height of the
+// numbering's layout box. Note we use pattern "1" here, not "(1)", since
+// the parenthesis exceeds the numbering's layout box, due to the default
+// settings of top-edge and bottom-edge of the TextElem that laid it out.
+#set math.equation(numbering: "1", number-align: top)
+#box(
+$ - &- - \
+ a &= b $,
+fill: silver)
+
+#set math.equation(numbering: "1", number-align: horizon)
+#box(
+$ - - - $,
+fill: silver)
+
+#set math.equation(numbering: "1", number-align: bottom)
+#box(
+$ a &= b \
+ - &- - $,
+fill: silver)
+
+--- issue-numbering-hint ---
+// In this bug, the hint and error messages for an equation
+// being reference mentioned that it was a "heading" and was
+// lacking the proper path.
+#set page(height: 70pt)
+
+$
+ Delta = b^2 - 4 a c
+$ <quadratic>
+
+// Error: 14-24 cannot reference equation without numbering
+// Hint: 14-24 you can enable equation numbering with `#set math.equation(numbering: "1.")`
+Looks at the @quadratic formula.
diff --git a/tests/suite/math/frac.typ b/tests/suite/math/frac.typ
new file mode 100644
index 00000000..b3ca8aa0
--- /dev/null
+++ b/tests/suite/math/frac.typ
@@ -0,0 +1,43 @@
+// Test fractions.
+
+--- math-frac-baseline ---
+// Test that denominator baseline matches in the common case.
+$ x = 1/2 = a/(a h) = a/a = a/(1/2) $
+
+--- math-frac-paren-removal ---
+// Test parenthesis removal.
+$ (|x| + |y|)/2 < [1+2]/3 $
+
+--- math-frac-large ---
+// Test large fraction.
+$ x = (-b plus.minus sqrt(b^2 - 4a c))/(2a) $
+
+--- math-binom ---
+// Test binomial.
+$ binom(circle, square) $
+
+--- math-binom-multiple ---
+// Test multinomial coefficients.
+$ binom(n, k_1, k_2, k_3) $
+
+--- math-binom-missing-lower ---
+// Error: 3-13 missing argument: lower
+$ binom(x^2) $
+
+--- math-dif ---
+// Test dif.
+$ (dif y)/(dif x), dif/x, x/dif, dif/dif \
+ frac(dif y, dif x), frac(dif, x), frac(x, dif), frac(dif, dif) $
+
+--- math-frac-associativity ---
+// Test associativity.
+$ 1/2/3 = (1/2)/3 = 1/(2/3) $
+
+--- math-frac-precedence ---
+// Test precedence.
+$ a_1/b_2, 1/f(x), zeta(x)/2, "foo"[|x|]/2 \
+ 1.2/3.7, 2.3^3.4 \
+ 🏳️‍🌈[x]/2, f [x]/2, phi [x]/2, 🏳️‍🌈 [x]/2 \
+ +[x]/2, 1(x)/2, 2[x]/2 \
+ (a)b/2, b(a)[b]/2 \
+ n!/2, 5!/2, n !/2, 1/n!, 1/5! $
diff --git a/tests/suite/math/interactions.typ b/tests/suite/math/interactions.typ
new file mode 100644
index 00000000..37185962
--- /dev/null
+++ b/tests/suite/math/interactions.typ
@@ -0,0 +1,95 @@
+// Test interactions with styling and normal layout.
+// Hint: They are bad ...
+
+--- math-nested-normal-layout ---
+// Test images and font fallback.
+#let monkey = move(dy: 0.2em, image("/assets/images/monkey.svg", height: 1em))
+$ sum_(i=#emoji.apple)^#emoji.apple.red i + monkey/2 $
+
+--- math-table ---
+// Test tables.
+$ x := #table(columns: 2)[x][y]/mat(1, 2, 3)
+ = #table[A][B][C] $
+
+--- math-equation-auto-wrapping ---
+// Test non-equation math directly in content.
+#math.attach($a$, t: [b])
+
+--- math-font-switch ---
+// Test font switch.
+#let here = text.with(font: "Noto Sans")
+$#here[f] := #here[Hi there]$.
+
+--- math-box-without-baseline ---
+// Test boxes without a baseline act as if the baseline is at the base
+#{
+ box(stroke: 0.2pt, $a #box(stroke: 0.2pt, $a$)$)
+ h(12pt)
+ box(stroke: 0.2pt, $a #box(stroke: 0.2pt, $g$)$)
+ h(12pt)
+ box(stroke: 0.2pt, $g #box(stroke: 0.2pt, $g$)$)
+}
+
+--- math-box-with-baseline ---
+// Test boxes with a baseline are respected
+#box(stroke: 0.2pt, $a #box(baseline:0.5em, stroke: 0.2pt, $a$)$)
+
+--- issue-2821-missing-fields ---
+// Issue #2821: Setting a figure's supplement to none removes the field
+#show figure.caption: it => {
+ assert(it.has("supplement"))
+ assert(it.supplement == none)
+}
+#figure([], caption: [], supplement: none)
+
+--- math-symbol-show-rule ---
+// Test using rules for symbols
+#show sym.tack: it => $#h(1em) it #h(1em)$
+$ a tack b $
+
+--- issue-math-realize-show ---
+// Test that content in math can be realized without breaking
+// nested equations.
+#let my = $pi$
+#let f1 = box(baseline: 10pt, [f])
+#let f2 = context f1
+#show math.vec: [nope]
+
+$ pi a $
+$ my a $
+$ 1 + sqrt(x/2) + sqrt(#hide($x/2$)) $
+$ a x #link("url", $+ b$) $
+$ f f1 f2 $
+$ vec(1,2) * 2 $
+
+--- issue-math-realize-hide ---
+$ x^2 #hide[$(>= phi.alt) union y^2 0$] z^2 $
+Hello #hide[there $x$]
+and #hide[$ f(x) := x^2 $]
+
+--- issue-math-realize-scripting ---
+// Test equations can embed equation pieces built by functions
+#let foo(v1, v2) = {
+ // Return an equation piece that would've been rendered in
+ // inline style if the piece is not embedded
+ $v1 v2^2$
+}
+#let bar(v1, v2) = {
+ // Return an equation piece that would've been rendered in
+ // block style if the piece is not embedded
+ $ v1 v2^2 $
+}
+#let baz(..sink) = {
+ // Return an equation piece built by joining arrays
+ sink.pos().map(x => $hat(#x)$).join(sym.and)
+}
+
+Inline $2 foo(alpha, (M+foo(a, b)))$.
+
+Inline $2 bar(alpha, (M+foo(a, b)))$.
+
+Inline $2 baz(x,y,baz(u, v))$.
+
+$ 2 foo(alpha, (M+foo(a, b))) $
+$ 2 bar(alpha, (M+foo(a, b))) $
+$ 2 baz(x,y,baz(u, v)) $
diff --git a/tests/suite/math/mat.typ b/tests/suite/math/mat.typ
new file mode 100644
index 00000000..e6148a34
--- /dev/null
+++ b/tests/suite/math/mat.typ
@@ -0,0 +1,163 @@
+// Test matrices.
+
+--- math-mat-semicolon ---
+// Test semicolon syntax.
+#set align(center)
+$mat() dot
+ mat(;) dot
+ mat(1, 2) dot
+ mat(1, 2;) \
+ mat(1; 2) dot
+ mat(1, 2; 3, 4) dot
+ mat(1 + &2, 1/2; &3, 4)$
+
+--- math-mat-sparse ---
+// Test sparse matrix.
+$ mat(
+ 1, 2, ..., 10;
+ 2, 2, ..., 10;
+ dots.v, dots.v, dots.down, dots.v;
+ 10, 10, ..., 10;
+) $
+
+--- math-mat-baseline ---
+// Test baseline alignment.
+$ mat(
+ a, b^2;
+ sum_(x \ y) x, a^(1/2);
+ zeta, alpha;
+) $
+
+--- math-mat-delim-set ---
+// Test alternative delimiter with set rule.
+#set math.mat(delim: "[")
+$ mat(1, 2; 3, 4) $
+$ a + mat(delim: #none, 1, 2; 3, 4) + b $
+
+--- math-mat-delim-direct ---
+// Test alternative math delimiter directly in call.
+#set align(center)
+#grid(
+ columns: 3,
+ gutter: 10pt,
+
+ $ mat(1, 2, delim: "[") $,
+ $ mat(1, 2; delim: "[") $,
+ $ mat(delim: "[", 1, 2) $,
+
+ $ mat(1; 2; delim: "[") $,
+ $ mat(1; delim: "[", 2) $,
+ $ mat(delim: "[", 1; 2) $,
+
+ $ mat(1, 2; delim: "[", 3, 4) $,
+ $ mat(delim: "[", 1, 2; 3, 4) $,
+ $ mat(1, 2; 3, 4; delim: "[") $,
+)
+
+--- math-mat-gap ---
+#set math.mat(gap: 1em)
+$ mat(1, 2; 3, 4) $
+
+--- math-mat-gaps ---
+#set math.mat(row-gap: 1em, column-gap: 2em)
+$ mat(1, 2; 3, 4) $
+
+--- math-mat-augment ---
+// Test matrix line drawing (augmentation).
+#grid(
+ columns: 2,
+ gutter: 10pt,
+
+ $ mat(10, 2, 3, 4; 5, 6, 7, 8; augment: #3) $,
+ $ mat(10, 2, 3, 4; 5, 6, 7, 8; augment: #(-1)) $,
+ $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(hline: 2)) $,
+ $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(hline: -1)) $,
+ $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(hline: 1, vline: 1)) $,
+ $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(hline: -2, vline: -2)) $,
+ $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(vline: 2, stroke: 1pt + blue)) $,
+ $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(vline: -1, stroke: 1pt + blue)) $,
+)
+
+--- math-mat-augment-set ---
+// Test using matrix line drawing with a set rule.
+#set math.mat(augment: (hline: 2, vline: 1, stroke: 2pt + green))
+$ mat(1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 1) $
+
+#set math.mat(augment: 2)
+$ mat(1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 1) $
+
+#set math.mat(augment: none)
+
+--- math-mat-augment-line-out-of-bounds ---
+// Error: 3-37 cannot draw a vertical line after column 3 of a matrix with 3 columns
+$ mat(1, 0, 0; 0, 1, 1; augment: #3) $,
+
+--- math-mat-align-explicit--alternating ---
+// Test alternating explicit alignment in a matrix.
+$ mat(
+ "a" & "a a a" & "a a";
+ "a a" & "a a" & "a";
+ "a a a" & "a" & "a a a";
+) $
+
+--- math-mat-align-implicit ---
+// Test alignment in a matrix.
+$ mat(
+ "a", "a a a", "a a";
+ "a a", "a a", "a";
+ "a a a", "a", "a a a";
+) $
+
+--- math-mat-align-explicit-left ---
+// Test explicit left alignment in a matrix.
+$ mat(
+ &"a", &"a a a", &"a a";
+ &"a a", &"a a", &"a";
+ &"a a a", &"a", &"a a a";
+) $
+
+--- math-mat-align-explicit-right ---
+// Test explicit right alignment in a matrix.
+$ mat(
+ "a"&, "a a a"&, "a a"&;
+ "a a"&, "a a"&, "a"&;
+ "a a a"&, "a"&, "a a a"&;
+) $
+
+--- math-mat-align-complex ---
+// Test #460 equations.
+#let stop = {
+ math.class("punctuation",$.$)
+}
+$ mat(&a+b,c;&d, e) $
+$ mat(&a+b&,c;&d&, e) $
+$ mat(&&&a+b,c;&&&d, e) $
+$ mat(stop &a+b&stop,c;...stop stop&d&...stop stop, e) $
+
+--- math-mat-align-signed-numbers ---
+// Test #454 equations.
+$ mat(-1, 1, 1; 1, -1, 1; 1, 1, -1) $
+$ mat(-1&, 1&, 1&; 1&, -1&, 1&; 1&, 1&, -1&) $
+$ mat(-1&, 1&, 1&; 1, -1, 1; 1, 1, -1) $
+$ mat(&-1, &1, &1; 1, -1, 1; 1, 1, -1) $
+
+--- math-mat-bad-comma ---
+// This error message is bad.
+// Error: 13-14 expected array, found content
+$ mat(1, 2; 3, 4, delim: "[") $,
+
+--- issue-852-mat-type ---
+$ mat(B, A B) $
+$ mat(B, A B, dots) $
+$ mat(B, A B, dots;) $
+$ mat(#1, #(foo: "bar")) $
+
+--- issue-2268-mat-augment-color ---
+// The augment line should be of the same color as the text
+#set text(
+ font: "New Computer Modern",
+ lang: "en",
+ fill: yellow,
+)
+
+$mat(augment: #1, M, v) arrow.r.squiggly mat(augment: #1, R, b)$
diff --git a/tests/suite/math/multiline.typ b/tests/suite/math/multiline.typ
new file mode 100644
index 00000000..85433627
--- /dev/null
+++ b/tests/suite/math/multiline.typ
@@ -0,0 +1,109 @@
+// Test multiline math.
+
+--- math-align-basic ---
+// Test basic alignment.
+$ x &= x + y \
+ &= x + 2z \
+ &= sum x dot 2z $
+
+--- math-align-wider-first-column ---
+// Test text before first alignment point.
+$ x + 1 &= a^2 + b^2 \
+ y &= a + b^2 \
+ z &= alpha dot beta $
+
+--- math-align-aligned-in-source ---
+// Test space between inner alignment points.
+$ a + b &= 2 + 3 &= 5 \
+ b &= c &= 3 $
+
+--- math-align-cases ---
+// Test in case distinction.
+$ f := cases(
+ 1 + 2 &"iff" &x,
+ 3 &"if" &y,
+) $
+
+--- math-align-lines-mixed ---
+// Test mixing lines with and some without alignment points.
+$ "abc" &= c \
+ &= d + 1 \
+ = x $
+
+--- math-attach-subscript-multiline ---
+// Test multiline subscript.
+$ sum_(n in NN \ n <= 5) n = (5(5+1))/2 = 15 $
+
+--- math-multiline-no-trailing-linebreak ---
+// Test no trailing line break.
+$
+"abc" &= c
+$
+No trailing line break.
+
+--- math-multiline-trailing-linebreak ---
+// Test single trailing line break.
+$
+"abc" &= c \
+$
+One trailing line break.
+
+--- math-multiline-multiple-trailing-linebreaks ---
+// Test multiple trailing line breaks.
+$
+"abc" &= c \ \ \
+$
+Multiple trailing line breaks.
+
+--- math-linebreaking-after-binop-and-rel ---
+// Basic breaking after binop, rel
+#let hrule(x) = box(line(length: x))
+#hrule(45pt)$e^(pi i)+1 = 0$\
+#hrule(55pt)$e^(pi i)+1 = 0$\
+#hrule(70pt)$e^(pi i)+1 = 0$
+
+--- math-linebreaking-lr ---
+// LR groups prevent linbreaking.
+#let hrule(x) = box(line(length: x))
+#hrule(76pt)$a+b$\
+#hrule(74pt)$(a+b)$\
+#hrule(74pt)$paren.l a+b paren.r$
+
+--- math-linebreaking-multiline ---
+// Multiline yet inline does not linebreak
+#let hrule(x) = box(line(length: x))
+#hrule(80pt)$a + b \ c + d$\
+
+--- math-linebreaking-trailing-linebreak ---
+// A single linebreak at the end still counts as one line.
+#let hrule(x) = box(line(length: x))
+#hrule(60pt)$e^(pi i)+1 = 0\ $
+
+--- math-linebreaking-in-box ---
+// Inline, in a box, doesn't linebreak.
+#let hrule(x) = box(line(length: x))
+#hrule(80pt)#box($a+b$)
+
+--- math-linebreaking-between-consecutive-relations ---
+// A relation followed by a relation doesn't linebreak
+#let hrule(x) = box(line(length: x))
+#hrule(70pt)$a < = b$\
+#hrule(74pt)$a < = b$
+
+--- math-linebreaking-after-relation-without-space ---
+// Line breaks can happen after a relation even if there is no
+// explicit space.
+#let hrule(x) = box(line(length: x))
+#hrule(90pt)$<;$\
+#hrule(95pt)$<;$\
+#hrule(90pt)$<)$\
+#hrule(95pt)$<)$
+
+--- math-linebreaking-empty ---
+// Verify empty rows are handled ok.
+$ $\
+Nothing: $ $, just empty.
+
+--- issue-1948-math-text-break ---
+// Test text with linebreaks in math.
+$ x := "a\nb\nc\nd\ne" $
diff --git a/tests/suite/math/op.typ b/tests/suite/math/op.typ
new file mode 100644
index 00000000..4139a08b
--- /dev/null
+++ b/tests/suite/math/op.typ
@@ -0,0 +1,30 @@
+// Test text operators.
+
+--- math-op-predefined ---
+// Test predefined.
+$ max_(1<=n<=m) n $
+
+--- math-op-call ---
+// With or without parens.
+$ &sin x + log_2 x \
+ = &sin(x) + log_2(x) $
+
+--- math-op-scripts-vs-limits ---
+// Test scripts vs limits.
+#set page(width: auto)
+#set text(font: "New Computer Modern")
+Discuss $lim_(n->oo) 1/n$ now.
+$ lim_(n->infinity) 1/n = 0 $
+
+--- math-op-custom ---
+// Test custom operator.
+$ op("myop", limits: #false)_(x:=1) x \
+ op("myop", limits: #true)_(x:=1) x $
+
+--- math-op-styled ---
+// Test styled operator.
+$ bold(op("bold", limits: #true))_x y $
+
+--- math-non-math-content ---
+// With non-text content
+$ op(#underline[ul]) a $
diff --git a/tests/suite/math/primes.typ b/tests/suite/math/primes.typ
new file mode 100644
index 00000000..e10f8876
--- /dev/null
+++ b/tests/suite/math/primes.typ
@@ -0,0 +1,50 @@
+--- math-primes ---
+// Test dedicated syntax for primes
+$a'$, $a'''_b$, $'$, $'''''''$
+
+--- math-primes-spaces ---
+// Test spaces between
+$a' ' '$, $' ' '$, $a' '/b$
+
+--- math-primes-complex ---
+// Test complex prime combinations
+$a'_b^c$, $a_b'^c$, $a_b^c'$, $a_b'^c'^d'$
+
+$(a'_b')^(c'_d')$, $a'/b'$, $a_b'/c_d'$
+
+$∫'$, $∑'$, $ ∑'_S' $
+
+--- math-primes-attach ---
+// Test attaching primes only
+$a' = a^', a_', a_'''^''^'$
+
+--- math-primes-scripts ---
+// Test primes always attaching as scripts
+$ x' $
+$ x^' $
+$ attach(x, t: ') $
+$ <' $
+$ attach(<, br: ') $
+$ op(<, limits: #true)' $
+$ limits(<)' $
+
+--- math-primes-limits ---
+// Test forcefully attaching primes as limits
+$ attach(<, t: ') $
+$ <^' $
+$ attach(<, b: ') $
+$ <_' $
+
+$ limits(x)^' $
+$ attach(limits(x), t: ') $
+
+--- math-primes-after-code-expr ---
+// Test prime symbols after code mode.
+#let g = $f$
+#let gg = $f$
+
+$
+ #(g)' #g' #g ' \
+ #g''''''''''''''''' \
+ gg'
+$
diff --git a/tests/suite/math/root.typ b/tests/suite/math/root.typ
new file mode 100644
index 00000000..a690802e
--- /dev/null
+++ b/tests/suite/math/root.typ
@@ -0,0 +1,45 @@
+// Test roots.
+
+--- math-root-basic ---
+// Test root with more than one character.
+$A = sqrt(x + y) = c$
+
+--- math-root-radical-attachment ---
+// Test root size with radicals containing attachments.
+$ sqrt(a) quad
+ sqrt(f) quad
+ sqrt(q) quad
+ sqrt(a^2) \
+ sqrt(n_0) quad
+ sqrt(b^()) quad
+ sqrt(b^2) quad
+ sqrt(q_1^2) $
+
+--- math-root-precomposed ---
+// Test precomposed vs constructed roots.
+// 3 and 4 are precomposed.
+$sqrt(x)$
+$root(2, x)$
+$root(3, x)$
+$root(4, x)$
+$root(5, x)$
+
+--- math-root-large-body ---
+// Test large bodies
+$ sqrt([|x|]^2 + [|y|]^2) < [|z|] $
+$ v = sqrt((1/2) / (4/5))
+ = root(3, (1/2/3) / (4/5/6))
+ = root(4, ((1/2) / (3/4)) / ((1/2) / (3/4))) $
+
+--- math-root-large-index ---
+// Test large index.
+$ root(2, x) quad
+ root(3/(2/1), x) quad
+ root(1/11, x) quad
+ root(1/2/3, 1) $
+
+--- math-root-syntax ---
+// Test shorthand.
+$ √2^3 = sqrt(2^3) $
+$ √(x+y) quad ∛x quad ∜x $
+$ (√2+3) = (sqrt(2)+3) $
diff --git a/tests/suite/math/size.typ b/tests/suite/math/size.typ
new file mode 100644
index 00000000..d0d41dc9
--- /dev/null
+++ b/tests/suite/math/size.typ
@@ -0,0 +1,9 @@
+--- math-size ---
+// Test forcing math size
+$a/b, display(a/b), display(a)/display(b), inline(a/b), script(a/b), sscript(a/b) \
+ mono(script(a/b)), script(mono(a/b))\
+ script(a^b, cramped: #true), script(a^b, cramped: #false)$
+
+--- issue-3658-math-size ---
+$ #rect[$1/2$] $
+$#rect[$1/2$]$
diff --git a/tests/suite/math/spacing.typ b/tests/suite/math/spacing.typ
new file mode 100644
index 00000000..2a387f92
--- /dev/null
+++ b/tests/suite/math/spacing.typ
@@ -0,0 +1,59 @@
+// Test spacing in math formulas.
+
+--- math-spacing-basic ---
+// Test spacing cases.
+$ä, +, c, (, )$ \
+$=), (+), {times}$ \
+$⟧<⟦, abs(-), [=$ \
+$a=b, a==b$ \
+$-a, +a$ \
+$a not b$ \
+$a+b, a*b$ \
+$sum x, sum(x)$ \
+$sum product x$ \
+$f(x), zeta(x), "frac"(x)$ \
+$a+dots.c+b$
+$f(x) sin(y)$
+
+--- math-spacing-kept-spaces ---
+// Test ignored vs non-ignored spaces.
+$f (x), f(x)$ \
+$[a|b], [a | b]$ \
+$a"is"b, a "is" b$
+
+--- math-spacing-predefined ---
+// Test predefined spacings.
+$a thin b, a med b, a thick b, a quad b$ \
+$a = thin b$ \
+$a - b equiv c quad (mod 2)$
+
+--- math-spacing-set-comprehension ---
+// Test spacing for set comprehension.
+#set page(width: auto)
+$ { x in RR | x "is natural" and x < 10 } $
+
+--- math-spacing-decorated ---
+// Test spacing for operators with decorations and modifiers on them
+#set page(width: auto)
+$a equiv b + c - d => e log 5 op("ln") 6$ \
+$a cancel(equiv) b overline(+) c arrow(-) d hat(=>) e cancel(log) 5 dot(op("ln")) 6$ \
+$a overbrace(equiv) b underline(+) c grave(-) d underbracket(=>) e circle(log) 5 caron(op("ln")) 6$ \
+\
+$a attach(equiv, tl: a, tr: b) b attach(limits(+), t: a, b: b) c tilde(-) d breve(=>) e attach(limits(log), t: a, b: b) 5 attach(op("ln"), tr: a, bl: b) 6$
+
+--- math-spacing-weak ---
+// Test weak spacing
+$integral f(x) dif x$,
+// Not weak
+$integral f(x) thin dif x$,
+// Both are weak, collide
+$integral f(x) #h(0.166em, weak: true)dif x$
+
+--- issue-1052-math-number-spacing ---
+// Test spacing after numbers in math.
+$
+10degree \
+10 degree \
+10.1degree \
+10.1 degree
+$
diff --git a/tests/suite/math/style.typ b/tests/suite/math/style.typ
new file mode 100644
index 00000000..09ddd3c1
--- /dev/null
+++ b/tests/suite/math/style.typ
@@ -0,0 +1,34 @@
+// Test text styling in math.
+
+--- math-style-italic-default ---
+// Test italic defaults.
+$a, A, delta, ϵ, diff, Delta, ϴ$
+
+--- math-style ---
+// Test forcing a specific style.
+$A, italic(A), upright(A), bold(A), bold(upright(A)), \
+ serif(A), sans(A), cal(A), frak(A), mono(A), bb(A), \
+ italic(diff), upright(diff), \
+ bb("hello") + bold(cal("world")), \
+ mono("SQRT")(x) wreath mono(123 + 456)$
+
+--- math-style-exceptions ---
+// Test a few style exceptions.
+$h, bb(N), cal(R), Theta, italic(Theta), sans(Theta), sans(italic(Theta)) \
+ bb(d), bb(italic(d)), italic(bb(d)), bb(e), bb(italic(e)), italic(bb(e)) \
+ bb(i), bb(italic(i)), italic(bb(i)), bb(j), bb(italic(j)), italic(bb(j)) \
+ bb(D), bb(italic(D)), italic(bb(D))$
+
+--- math-style-greek-exceptions ---
+// Test a few greek exceptions.
+$bb(Gamma) , bb(gamma), bb(Pi), bb(pi), bb(sum)$
+
+--- math-style-hebrew-exceptions ---
+// Test hebrew exceptions.
+$aleph, beth, gimel, daleth$
+
+--- issue-3650-italic-equation ---
+_abc $sin(x) "abc"$_ \
+$italic(sin(x) "abc" #box[abc])$ \
+*abc $sin(x) "abc"$* \
+$bold(sin(x) "abc" #box[abc])$ \
diff --git a/tests/suite/math/syntax.typ b/tests/suite/math/syntax.typ
new file mode 100644
index 00000000..fcb8b89e
--- /dev/null
+++ b/tests/suite/math/syntax.typ
@@ -0,0 +1,34 @@
+// Test math syntax.
+
+--- math-call-non-func ---
+$ pi(a) $
+$ pi(a,) $
+$ pi(a,b) $
+$ pi(a,b,) $
+
+--- math-unicode ---
+// Test Unicode math.
+$ ∑_(i=0)^ℕ a ∘ b = \u{2211}_(i=0)^NN a compose b $
+
+--- math-shorthandes ---
+// Test a few shorthands.
+$ underline(f' : NN -> RR) \
+ n |-> cases(
+ [|1|] &"if" n >>> 10,
+ 2 * 3 &"if" n != 5,
+ 1 - 0 thick &...,
+ ) $
+
+--- math-common-symbols ---
+// Test common symbols.
+$ dot \ dots \ ast \ tilde \ star $
+
+--- issue-2044-invalid-parsed-ident ---
+// In this bug, the dot at the end was causing the right parenthesis to be
+// parsed as an identifier instead of the closing right parenthesis.
+$floor(phi.alt.)$
+$floor(phi.alt. )$
+
+--- math-unclosed ---
+// Error: 1-2 unclosed delimiter
+$a
diff --git a/tests/suite/math/text.typ b/tests/suite/math/text.typ
new file mode 100644
index 00000000..760910f4
--- /dev/null
+++ b/tests/suite/math/text.typ
@@ -0,0 +1,45 @@
+// Test that setting font features in math.equation has an effect.
+
+--- math-font-fallback ---
+// Test font fallback.
+$ よ and 🏳️‍🌈 $
+
+--- math-text-color ---
+// Test text properties.
+$text(#red, "time"^2) + sqrt("place")$
+
+--- math-font-features ---
+$ nothing $
+$ "hi ∅ hey" $
+$ sum_(i in NN) 1 + i $
+#show math.equation: set text(features: ("cv01",), fallback: false)
+$ nothing $
+$ "hi ∅ hey" $
+$ sum_(i in NN) 1 + i $
+
+--- math-optical-size-nested-scripts ---
+// Test transition from script to scriptscript.
+#[
+#set text(size:20pt)
+$ e^(e^(e^(e))) $
+]
+A large number: $e^(e^(e^(e)))$.
+
+--- math-optical-size-primes ---
+// Test prime/double prime via scriptsize
+#let prime = [ \u{2032} ]
+#let dprime = [ \u{2033} ]
+#let tprime = [ \u{2034} ]
+$ y^dprime-2y^prime + y = 0 $
+$y^dprime-2y^prime + y = 0$
+$ y^tprime_3 + g^(prime 2) $
+
+--- math-optical-size-prime-large-operator ---
+// Test prime superscript on large symbol
+$ scripts(sum_(k in NN))^prime 1/k^2 $
+$sum_(k in NN)^prime 1/k^2$
+
+--- math-optical-size-frac-script-script ---
+// Test script-script in a fraction.
+$ 1/(x^A) $
+#[#set text(size:18pt); $1/(x^A)$] vs. #[#set text(size:14pt); $x^A$]
diff --git a/tests/suite/math/underover.typ b/tests/suite/math/underover.typ
new file mode 100644
index 00000000..0768bf73
--- /dev/null
+++ b/tests/suite/math/underover.typ
@@ -0,0 +1,21 @@
+// Test under/over things.
+
+--- math-underover-brace ---
+// Test braces.
+$ x = underbrace(
+ 1 + 2 + ... + 5,
+ underbrace("numbers", x + y)
+) $
+
+--- math-underover-line-bracket ---
+// Test lines and brackets.
+$ x = overbracket(
+ overline(underline(x + y)),
+ 1 + 2 + ... + 5,
+) $
+
+--- math-underover-brackets ---
+// Test brackets.
+$ underbracket([1, 2/3], "relevant stuff")
+ arrow.l.r.double.long
+ overbracket([4/5,6], "irrelevant stuff") $
diff --git a/tests/suite/math/vec.typ b/tests/suite/math/vec.typ
new file mode 100644
index 00000000..312c0ee4
--- /dev/null
+++ b/tests/suite/math/vec.typ
@@ -0,0 +1,27 @@
+// Test vectors.
+
+--- math-vec-gap ---
+#set math.vec(gap: 1em)
+$ vec(1, 2) $
+
+
+--- math-vec-align-explicit-alternating ---
+// Test alternating alignment in a vector.
+$ vec(
+ "a" & "a a a" & "a a",
+ "a a" & "a a" & "a",
+ "a a a" & "a" & "a a a",
+) $
+
+--- math-vec-wide ---
+// Test wide cell.
+$ v = vec(1, 2+3, 4) $
+
+--- math-vec-delim-set ---
+// Test alternative delimiter.
+#set math.vec(delim: "[")
+$ vec(1, 2) $
+
+--- math-vec-delim-invalid ---
+// Error: 22-25 expected "(", "[", "{", "|", "||", or none
+#set math.vec(delim: "%")
diff --git a/tests/suite/model/bibliography.typ b/tests/suite/model/bibliography.typ
new file mode 100644
index 00000000..7197082f
--- /dev/null
+++ b/tests/suite/model/bibliography.typ
@@ -0,0 +1,55 @@
+// Test citations and bibliographies.
+
+--- bibliography-basic ---
+#set page(width: 200pt)
+
+= Details
+See also @arrgh #cite(<distress>, supplement: [p.~22]), @arrgh[p.~4], and @distress[p.~5].
+#bibliography("/assets/bib/works.bib")
+
+--- bibliography-before-content ---
+// Test unconventional order.
+#set page(width: 200pt)
+#bibliography(
+ "/assets/bib/works.bib",
+ title: [Works to be cited],
+ style: "chicago-author-date",
+)
+#line(length: 100%)
+
+As described by #cite(<netwok>, form: "prose"),
+the net-work is a creature of its own.
+This is close to piratery! @arrgh
+And quark! @quark
+
+--- bibliography-multiple-files ---
+#set page(width: 200pt)
+#set heading(numbering: "1.")
+#show bibliography: set heading(numbering: "1.")
+
+= Multiple Bibs
+Now we have multiple bibliographies containing @glacier-melt @keshav2007read
+#bibliography(("/assets/bib/works.bib", "/assets/bib/works_too.bib"))
+
+--- bibliography-duplicate-key ---
+// Error: 15-65 duplicate bibliography keys: netwok, issue201, arrgh, quark, distress, glacier-melt, tolkien54, DBLP:books/lib/Knuth86a, sharing, restful, mcintosh_anxiety, psychology25
+#bibliography(("/assets/bib/works.bib", "/assets/bib/works.bib"))
+
+--- bibliography-ordering ---
+#set page(width: 300pt)
+
+@mcintosh_anxiety
+@psychology25
+
+#bibliography("/assets/bib/works.bib")
+
+--- bibliography-full ---
+// LARGE
+#set page(paper: "a6", height: 170mm)
+#bibliography("/assets/bib/works.bib", full: true)
+
+--- bibliography-math ---
+#set page(width: 200pt)
+
+@Zee04
+#bibliography("/assets/bib/works_too.bib", style: "mla")
diff --git a/tests/suite/model/cite.typ b/tests/suite/model/cite.typ
new file mode 100644
index 00000000..c6bdccb1
--- /dev/null
+++ b/tests/suite/model/cite.typ
@@ -0,0 +1,92 @@
+--- cite-footnote ---
+Hello @netwok
+And again: @netwok
+
+#pagebreak()
+#bibliography("/assets/bib/works.bib", style: "chicago-notes")
+
+--- cite-form ---
+#set page(width: 200pt)
+
+Nothing: #cite(<arrgh>, form: none)
+
+#cite(<netwok>, form: "prose") say stuff.
+
+#bibliography("/assets/bib/works.bib", style: "apa")
+
+--- cite-group ---
+A#[@netwok@arrgh]B \
+A@netwok@arrgh B \
+A@netwok @arrgh B \
+A@netwok @arrgh. B \
+
+A @netwok#[@arrgh]B \
+A @netwok@arrgh, B \
+A @netwok @arrgh, B \
+A @netwok @arrgh. B \
+
+A#[@netwok @arrgh @quark]B. \
+A @netwok @arrgh @quark B. \
+A @netwok @arrgh @quark, B.
+
+#set text(0pt)
+#bibliography("/assets/bib/works.bib")
+
+--- cite-grouping-and-ordering ---
+@mcintosh_anxiety
+@psychology25
+@netwok
+@issue201
+@arrgh
+@quark
+@distress,
+@glacier-melt
+@issue201
+@tolkien54
+@sharing
+@restful
+
+#show bibliography: none
+#bibliography("/assets/bib/works.bib")
+
+--- issue-785-cite-locate ---
+// Test citation in other introspection.
+#set page(width: 180pt)
+#set heading(numbering: "1")
+
+#outline(
+ title: [List of Figures],
+ target: figure.where(kind: image),
+)
+
+#pagebreak()
+
+= Introduction <intro>
+#figure(
+ rect[-- PIRATE --],
+ caption: [A pirate @arrgh in @intro],
+)
+
+#context [Citation @distress on page #here().page()]
+
+#pagebreak()
+#bibliography("/assets/bib/works.bib", style: "chicago-notes")
+
+--- issue-1597-cite-footnote ---
+// Tests that when a citation footnote is pushed to next page, things still
+// work as expected.
+#set page(height: 60pt)
+#lorem(4)
+
+#footnote[@netwok]
+#show bibliography: none
+#bibliography("/assets/bib/works.bib")
+
+--- issue-2531-cite-show-set ---
+// Test show set rules on citations.
+#show cite: set text(red)
+A @netwok @arrgh.
+B #cite(<netwok>) #cite(<arrgh>).
+
+#show bibliography: none
+#bibliography("/assets/bib/works.bib")
diff --git a/tests/suite/model/document.typ b/tests/suite/model/document.typ
new file mode 100644
index 00000000..6f8e7131
--- /dev/null
+++ b/tests/suite/model/document.typ
@@ -0,0 +1,36 @@
+// Test document and page-level styles.
+
+--- document-set-title ---
+// This is okay.
+#set document(title: [Hello])
+What's up?
+
+--- document-set-author-date ---
+// This, too.
+#set document(author: ("A", "B"), date: datetime.today())
+
+--- document-date-bad ---
+// Error: 21-28 expected datetime, none, or auto, found string
+#set document(date: "today")
+
+--- document-author-bad ---
+// This, too.
+// Error: 23-29 expected string, found integer
+#set document(author: (123,))
+What's up?
+
+--- document-set-after-content ---
+Hello
+
+// Error: 2-30 document set rules must appear before any content
+#set document(title: [Hello])
+
+--- document-constructor ---
+// Error: 2-12 can only be used in set rules
+#document()
+
+--- document-set-in-container ---
+#box[
+ // Error: 4-32 document set rules are not allowed inside of containers
+ #set document(title: [Hello])
+]
diff --git a/tests/suite/model/emph-strong.typ b/tests/suite/model/emph-strong.typ
new file mode 100644
index 00000000..2af8bb16
--- /dev/null
+++ b/tests/suite/model/emph-strong.typ
@@ -0,0 +1,74 @@
+// Test emph and strong.
+
+--- emph-syntax ---
+// Basic.
+_Emphasized and *strong* words!_
+
+// Inside of a word it's a normal underscore or star.
+hello_world Nutzer*innen
+
+// CJK characters will not need spaces.
+中文一般使用*粗体*或者_楷体_来表示强调。
+
+日本語では、*太字*や_斜体_を使って強調します。
+
+中文中混有*Strong*和_Empasis_。
+
+// Can contain paragraph in nested content block.
+_Still #[
+
+] emphasized._
+
+--- emph-and-strong-call-in-word ---
+// Inside of words can still use the functions.
+P#strong[art]ly em#emph[phas]ized.
+
+--- emph-empty-hint ---
+// Warning: 1-3 no text within underscores
+// Hint: 1-3 using multiple consecutive underscores (e.g. __) has no additional effect
+__
+
+--- emph-double-underscore-empty-hint ---
+// Warning: 1-3 no text within underscores
+// Hint: 1-3 using multiple consecutive underscores (e.g. __) has no additional effect
+// Warning: 13-15 no text within underscores
+// Hint: 13-15 using multiple consecutive underscores (e.g. __) has no additional effect
+__not italic__
+
+--- emph-unclosed ---
+// Error: 6-7 unclosed delimiter
+#box[_Scoped] to body.
+
+--- emph-ends-at-parbreak ---
+// Ends at paragraph break.
+// Error: 1-2 unclosed delimiter
+_Hello
+
+World
+
+--- emph-strong-unclosed-nested ---
+// Error: 11-12 unclosed delimiter
+// Error: 3-4 unclosed delimiter
+#[_Cannot *be interleaved]
+
+--- strong-delta ---
+// Adjusting the delta that strong applies on the weight.
+Normal
+
+#set strong(delta: 300)
+*Bold*
+
+#set strong(delta: 150)
+*Medium* and *#[*Bold*]*
+
+--- strong-empty-hint ---
+// Warning: 1-3 no text within stars
+// Hint: 1-3 using multiple consecutive stars (e.g. **) has no additional effect
+**
+
+--- strong-double-star-empty-hint ---
+// Warning: 1-3 no text within stars
+// Hint: 1-3 using multiple consecutive stars (e.g. **) has no additional effect
+// Warning: 11-13 no text within stars
+// Hint: 11-13 using multiple consecutive stars (e.g. **) has no additional effect
+**not bold**
diff --git a/tests/suite/model/enum.typ b/tests/suite/model/enum.typ
new file mode 100644
index 00000000..57a4d7a6
--- /dev/null
+++ b/tests/suite/model/enum.typ
@@ -0,0 +1,156 @@
+// Test enumerations.
+
+--- enum-function-call ---
+#enum[Embrace][Extend][Extinguish]
+
+--- enum-number-override-nested ---
+0. Before first!
+1. First.
+ 2. Indented
+
++ Second
+
+--- enum-built-in-loop ---
+// Test automatic numbering in summed content.
+#for i in range(5) {
+ [+ #numbering("I", 1 + i)]
+}
+
+--- list-mix ---
+// Mix of different lists
+- Bullet List
++ Numbered List
+/ Term: List
+
+--- enum-syntax-at-start ---
+// In the line.
+1.2 \
+This is 0. \
+See 0.3. \
+
+--- enum-syntax-edge-cases ---
+// Edge cases.
++
+Empty \
++Nope \
+a + 0.
+
+--- enum-number-override ---
+// Test item number overriding.
+1. first
++ second
+5. fifth
+
+#enum(
+ enum.item(1)[First],
+ [Second],
+ enum.item(5)[Fifth]
+)
+
+--- enum-numbering-pattern ---
+// Test numbering pattern.
+#set enum(numbering: "(1.a.*)")
++ First
++ Second
+ 2. Nested
+ + Deep
++ Normal
+
+--- enum-numbering-full ---
+// Test full numbering.
+#set enum(numbering: "1.a.", full: true)
++ First
+ + Nested
+
+--- enum-numbering-closure ---
+// Test numbering with closure.
+#enum(
+ start: 3,
+ spacing: 0.65em - 3pt,
+ tight: false,
+ numbering: n => text(
+ fill: (red, green, blue).at(calc.rem(n, 3)),
+ numbering("A", n),
+ ),
+ [Red], [Green], [Blue], [Red],
+)
+
+--- enum-numbering-closure-nested ---
+// Test numbering with closure and nested lists.
+#set enum(numbering: n => super[#n])
++ A
+ + B
++ C
+
+--- enum-numbering-closure-nested-complex ---
+// Test numbering with closure and nested lists.
+#set text(font: "New Computer Modern")
+#set enum(numbering: (..args) => math.mat(args.pos()), full: true)
++ A
+ + B
+ + C
+ + D
++ E
++ F
+
+--- enum-numbering-pattern-empty ---
+// Error: 22-24 invalid numbering pattern
+#set enum(numbering: "")
+
+--- enum-numbering-pattern-invalid ---
+// Error: 22-28 invalid numbering pattern
+#set enum(numbering: "(())")
+
+--- enum-number-align-unaffected ---
+// Alignment shouldn't affect number
+#set align(horizon)
+
++ ABCDEF\ GHIJKL\ MNOPQR
+ + INNER\ INNER\ INNER
++ BACK\ HERE
+
+--- enum-number-align-default ---
+// Enum number alignment should be 'end' by default
+1. a
+10. b
+100. c
+
+--- enum-number-align-specified ---
+#set enum(number-align: start)
+1. a
+8. b
+16. c
+
+--- enum-number-align-2d ---
+#set enum(number-align: center + horizon)
+1. #box(fill: teal, inset: 10pt )[a]
+8. #box(fill: teal, inset: 10pt )[b]
+16. #box(fill: teal,inset: 10pt )[c]
+
+--- enum-number-align-unfolded ---
+// Number align option should not be affected by the context.
+#set align(center)
+#set enum(number-align: start)
+
+4. c
+8. d
+16. e\ f
+ 2. f\ g
+ 32. g
+ 64. h
+
+--- enum-number-align-values ---
+// Test valid number align values (horizontal and vertical)
+#set enum(number-align: start)
+#set enum(number-align: end)
+#set enum(number-align: left)
+#set enum(number-align: center)
+#set enum(number-align: right)
+#set enum(number-align: top)
+#set enum(number-align: horizon)
+#set enum(number-align: bottom)
+
+--- issue-2530-enum-item-panic ---
+// Enum item (pre-emptive)
+#enum.item(none)[Hello]
+#enum.item(17)[Hello]
diff --git a/tests/suite/model/figure.typ b/tests/suite/model/figure.typ
new file mode 100644
index 00000000..6846760f
--- /dev/null
+++ b/tests/suite/model/figure.typ
@@ -0,0 +1,220 @@
+// Test figures.
+
+--- figure-basic ---
+#set page(width: 150pt)
+#set figure(numbering: "I")
+
+We can clearly see that @fig-cylinder and
+@tab-complex are relevant in this context.
+
+#figure(
+ table(columns: 2)[a][b],
+ caption: [The basic table.],
+) <tab-basic>
+
+#figure(
+ pad(y: -6pt, image("/assets/images/cylinder.svg", height: 2cm)),
+ caption: [The basic shapes.],
+ numbering: "I",
+) <fig-cylinder>
+
+#figure(
+ table(columns: 3)[a][b][c][d][e][f],
+ caption: [The complex table.],
+) <tab-complex>
+
+--- figure-table ---
+// Testing figures with tables.
+#figure(
+ table(
+ columns: 2,
+ [Second cylinder],
+ image("/assets/images/cylinder.svg"),
+ ),
+ caption: "A table containing images."
+) <fig-image-in-table>
+
+--- figure-theorem ---
+// Testing show rules with figures with a simple theorem display
+#show figure.where(kind: "theorem"): it => {
+ let name = none
+ if not it.caption == none {
+ name = [ #emph(it.caption.body)]
+ } else {
+ name = []
+ }
+
+ let title = none
+ if not it.numbering == none {
+ title = it.supplement
+ if not it.numbering == none {
+ title += " " + it.counter.display(it.numbering)
+ }
+ }
+ title = strong(title)
+ pad(
+ top: 0em, bottom: 0em,
+ block(
+ fill: green.lighten(90%),
+ stroke: 1pt + green,
+ inset: 10pt,
+ width: 100%,
+ radius: 5pt,
+ breakable: false,
+ [#title#name#h(0.1em):#h(0.2em)#it.body#v(0.5em)]
+ )
+ )
+}
+
+#set page(width: 150pt)
+#figure(
+ $a^2 + b^2 = c^2$,
+ supplement: "Theorem",
+ kind: "theorem",
+ caption: "Pythagoras' theorem.",
+ numbering: "1",
+) <fig-formula>
+
+#figure(
+ $a^2 + b^2 = c^2$,
+ supplement: "Theorem",
+ kind: "theorem",
+ caption: "Another Pythagoras' theorem.",
+ numbering: none,
+) <fig-formula>
+
+#figure(
+ ```rust
+ fn main() {
+ println!("Hello!");
+ }
+ ```,
+ caption: [Hello world in _rust_],
+)
+
+--- figure-breakable ---
+// Test breakable figures
+#set page(height: 6em)
+#show figure: set block(breakable: true)
+
+#figure(table[a][b][c][d][e], caption: [A table])
+
+--- figure-caption-separator ---
+// Test custom separator for figure caption
+#set figure.caption(separator: [ --- ])
+
+#figure(
+ table(columns: 2)[a][b],
+ caption: [The table with custom separator.],
+)
+
+--- figure-caption-show ---
+// Test figure.caption element
+#show figure.caption: emph
+
+#figure(
+ [Not italicized],
+ caption: [Italicized],
+)
+
+--- figure-caption-where-selector ---
+// Test figure.caption element for specific figure kinds
+#show figure.caption.where(kind: table): underline
+
+#figure(
+ [Not a table],
+ caption: [Not underlined],
+)
+
+#figure(
+ table[A table],
+ caption: [Underlined],
+)
+
+--- figure-and-caption-show ---
+// Test creating custom figure and custom caption
+
+#let gap = 0.7em
+#show figure.where(kind: "custom"): it => rect(inset: gap, {
+ align(center, it.body)
+ v(gap, weak: true)
+ line(length: 100%)
+ v(gap, weak: true)
+ align(center, it.caption)
+})
+
+#figure(
+ [A figure],
+ kind: "custom",
+ caption: [Hi],
+ supplement: [A],
+)
+
+#show figure.caption: it => emph[
+ #it.body
+ (#it.supplement
+ #context it.counter.display(it.numbering))
+]
+
+#figure(
+ [Another figure],
+ kind: "custom",
+ caption: [Hi],
+ supplement: [B],
+)
+
+--- figure-caption-position ---
+#set figure.caption(position: top)
+
+--- figure-caption-position-bad ---
+// Error: 31-38 expected `top` or `bottom`, found horizon
+#set figure.caption(position: horizon)
+
+--- figure-localization-fr ---
+// Test French
+#set text(lang: "fr")
+#figure(
+ circle(),
+ caption: [Un cercle.],
+)
+
+--- figure-localization-zh ---
+// Test Chinese
+#set text(lang: "zh")
+#figure(
+ rect(),
+ caption: [一个矩形],
+)
+
+--- figure-localization-ru ---
+// Test Russian
+#set text(lang: "ru")
+
+#figure(
+ polygon.regular(size: 1cm, vertices: 8),
+ caption: [Пятиугольник],
+)
+
+--- figure-localization-gr ---
+// Test Greek
+#set text(lang: "gr")
+#figure(
+ circle(),
+ caption: [Ένας κύκλος.],
+)
+
+--- issue-2165-figure-caption-panic ---
+#figure.caption[]
+
+--- issue-2328-figure-entry-panic ---
+// Error: 4-43 footnote entry must have a location
+// Hint: 4-43 try using a query or a show rule to customize the footnote instead
+HI#footnote.entry(clearance: 2.5em)[There]
+
+--- issue-2530-figure-caption-panic ---
+#figure(caption: [test])[].caption
+
+--- issue-3586-figure-caption-separator ---
+// Test that figure caption separator is synthesized correctly.
+#show figure.caption: c => test(c.separator, [#": "])
+#figure(table[], caption: [This is a test caption])
diff --git a/tests/suite/model/footnote.typ b/tests/suite/model/footnote.typ
new file mode 100644
index 00000000..c41db577
--- /dev/null
+++ b/tests/suite/model/footnote.typ
@@ -0,0 +1,182 @@
+// Test footnotes.
+
+--- footnote-basic ---
+#footnote[Hi]
+
+--- footnote-space-collapsing ---
+// Test space collapsing before footnote.
+A#footnote[A] \
+A #footnote[A]
+
+--- footnote-nested ---
+// Test nested footnotes.
+First \
+Second #footnote[A, #footnote[B, #footnote[C]]] \
+Third #footnote[D, #footnote[E]] \
+Fourth
+
+--- footnote-nested-same-frame ---
+// Currently, numbers a bit out of order if a nested footnote ends up in the
+// same frame as another one. :(
+#footnote[A, #footnote[B]], #footnote[C]
+
+--- footnote-entry ---
+// Test customization.
+#show footnote: set text(red)
+#show footnote.entry: set text(8pt, style: "italic")
+#set footnote.entry(
+ indent: 0pt,
+ gap: 0.6em,
+ clearance: 0.3em,
+ separator: repeat[.],
+)
+
+Beautiful footnotes. #footnote[Wonderful, aren't they?]
+
+--- footnote-break-across-pages ---
+// LARGE
+#set page(height: 200pt)
+
+#lorem(5)
+#footnote[ // 1
+ A simple footnote.
+ #footnote[Well, not that simple ...] // 2
+]
+#lorem(15)
+#footnote[Another footnote: #lorem(30)] // 3
+#lorem(15)
+#footnote[My fourth footnote: #lorem(50)] // 4
+#lorem(15)
+#footnote[And a final footnote.] // 5
+
+--- footnote-in-columns ---
+// Test footnotes in columns, even those that are not enabled via `set page`.
+#set page(height: 120pt)
+#align(center, strong[Title])
+#show: columns.with(2)
+#lorem(3) #footnote(lorem(6))
+Hello there #footnote(lorem(2))
+
+--- footnote-in-caption ---
+// Test footnote in caption.
+Read the docs #footnote[https://typst.app/docs]!
+#figure(
+ image("/assets/images/graph.png", width: 70%),
+ caption: [
+ A graph #footnote[A _graph_ is a structure with nodes and edges.]
+ ]
+)
+More #footnote[just for ...] footnotes #footnote[... testing. :)]
+
+--- footnote-duplicate ---
+// Test duplicate footnotes.
+#let lang = footnote[Languages.]
+#let nums = footnote[Numbers.]
+
+/ "Hello": A word #lang
+/ "123": A number #nums
+
+- "Hello" #lang
+- "123" #nums
+
++ "Hello" #lang
++ "123" #nums
+
+#table(
+ columns: 2,
+ [Hello], [A word #lang],
+ [123], [A number #nums],
+)
+
+--- footnote-invariant ---
+// Ensure that a footnote and the first line of its entry
+// always end up on the same page.
+#set page(height: 120pt)
+
+#lorem(13)
+
+There #footnote(lorem(20))
+
+--- footnote-ref ---
+// Test references to footnotes.
+A footnote #footnote[Hi]<fn> \
+A reference to it @fn
+
+--- footnote-ref-multiple ---
+// Multiple footnotes are refs
+First #footnote[A]<fn1> \
+Second #footnote[B]<fn2> \
+First ref @fn1 \
+Third #footnote[C] \
+Fourth #footnote[D]<fn4> \
+Fourth ref @fn4 \
+Second ref @fn2 \
+Second ref again @fn2
+
+--- footnote-ref-forward ---
+// Forward reference
+Usage @fn \
+Definition #footnote[Hi]<fn>
+
+--- footnote-ref-in-footnote ---
+// Footnote ref in footnote
+#footnote[Reference to next @fn]
+#footnote[Reference to myself @fn]<fn>
+#footnote[Reference to previous @fn]
+
+--- footnote-styling ---
+// Styling
+#show footnote: text.with(fill: red)
+Real #footnote[...]<fn> \
+Ref @fn
+
+--- footnote-ref-call ---
+// Footnote call with label
+#footnote(<fn>)
+#footnote[Hi]<fn>
+#ref(<fn>)
+#footnote(<fn>)
+
+--- footnote-in-table ---
+// Test footnotes in tables. When the table spans multiple pages, the footnotes
+// will all be after the table, but it shouldn't create any empty pages.
+#set page(height: 100pt)
+
+= Tables
+#table(
+ columns: 2,
+ [Hello footnote #footnote[This is a footnote.]],
+ [This is more text],
+ [This cell
+ #footnote[This footnote is not on the same page]
+ breaks over multiple pages.],
+ image("/assets/images/tiger.jpg"),
+)
+
+#table(
+ columns: 3,
+ ..range(1, 10)
+ .map(numbering.with("a"))
+ .map(v => upper(v) + footnote(v))
+)
+
+--- issue-multiple-footnote-in-one-line ---
+// Test that the logic that keeps footnote entry together with
+// their markers also works for multiple footnotes in a single
+// line or frame (here, there are two lines, but they are one
+// unit due to orphan prevention).
+#set page(height: 100pt)
+#v(40pt)
+A #footnote[a] \
+B #footnote[b]
+
+--- issue-1433-footnote-in-list ---
+// Test that footnotes in lists do not produce extraneous page breaks. The list
+// layout itself does not currently react to the footnotes layout, weakening the
+// "footnote and its entry are on the same page" invariant somewhat, but at
+// least there shouldn't be extra page breaks.
+#set page(height: 100pt)
+#block(height: 50pt, width: 100%, fill: aqua)
+
+- #footnote[1]
+- #footnote[2]
diff --git a/tests/suite/model/heading.typ b/tests/suite/model/heading.typ
new file mode 100644
index 00000000..5d50eeee
--- /dev/null
+++ b/tests/suite/model/heading.typ
@@ -0,0 +1,80 @@
+// Test headings.
+
+--- heading-basic ---
+// Different number of equals signs.
+
+= Level 1
+== Level 2
+=== Level 3
+
+// After three, it stops shrinking.
+=========== Level 11
+
+--- heading-syntax-at-start ---
+// Heading vs. no heading.
+
+// Parsed as headings if at start of the context.
+/**/ = Level 1
+#[== Level 2]
+#box[=== Level 3]
+
+// Not at the start of the context.
+No = heading
+
+// Escaped.
+\= No heading
+
+--- heading-block ---
+// Blocks can continue the heading.
+
+= #[This
+is
+multiline.
+]
+
+= This
+ is not.
+
+--- heading-show-where ---
+// Test styling.
+#show heading.where(level: 5): it => block(
+ text(font: "Roboto", fill: eastern, it.body + [!])
+)
+
+= Heading
+===== Heading 🌍
+#heading(level: 5)[Heading]
+
+--- heading-offset ---
+// Test setting the starting offset.
+#set heading(numbering: "1.1")
+#show heading.where(level: 2): set text(blue)
+= Level 1
+
+#heading(depth: 1)[We're twins]
+#heading(level: 1)[We're twins]
+
+== Real level 2
+
+#set heading(offset: 1)
+= Fake level 2
+== Fake level 3
+
+--- heading-offset-and-level ---
+// Passing level directly still overrides all other set values
+#set heading(numbering: "1.1", offset: 1)
+#heading(level: 1)[Still level 1]
+
+--- heading-syntax-edge-cases ---
+// Edge cases.
+#set heading(numbering: "1.")
+=
+Not in heading
+=Nope
+
+--- heading-numbering-hint ---
+= Heading <intro>
+
+// Error: 1:20-1:26 cannot reference heading without numbering
+// Hint: 1:20-1:26 you can enable heading numbering with `#set heading(numbering: "1.")`
+Can not be used as @intro
diff --git a/tests/suite/model/link.typ b/tests/suite/model/link.typ
new file mode 100644
index 00000000..27afd53c
--- /dev/null
+++ b/tests/suite/model/link.typ
@@ -0,0 +1,77 @@
+// Test hyperlinking.
+
+--- link-basic ---
+// Link syntax.
+https://example.com/
+
+// Link with body.
+#link("https://typst.org/")[Some text text text]
+
+// With line break.
+This link appears #link("https://google.com/")[in the middle of] a paragraph.
+
+// Certain prefixes are trimmed when using the `link` function.
+Contact #link("mailto:hi@typst.app") or
+call #link("tel:123") for more information.
+
+--- link-trailing-period ---
+// Test that the period is trimmed.
+#show link: underline
+https://a.b.?q=%10#. \
+Wahttp://link \
+Nohttps:\//link \
+Nohttp\://comment
+
+--- link-bracket-balanced ---
+// Verify that brackets are included in links.
+https://[::1]:8080/ \
+https://example.com/(paren) \
+https://example.com/#(((nested))) \
+
+--- link-bracket-unbalanced-closing ---
+// Check that unbalanced brackets are not included in links.
+#[https://example.com/] \
+https://example.com/)
+
+--- link-bracket-unbalanced-opening ---
+// Verify that opening brackets without closing brackets throw an error.
+// Error: 1-22 automatic links cannot contain unbalanced brackets, use the `link` function instead
+https://exam(ple.com/
+
+--- link-show ---
+// Styled with underline and color.
+#show link: it => underline(text(fill: rgb("283663"), it))
+You could also make the
+#link("https://html5zombo.com/")[link look way more typical.]
+
+--- link-transformed ---
+// Transformed link.
+#set page(height: 60pt)
+#let mylink = link("https://typst.org/")[LINK]
+My cool #box(move(dx: 0.7cm, dy: 0.7cm, rotate(10deg, scale(200%, mylink))))
+
+--- link-on-block ---
+// Link containing a block.
+#link("https://example.com/", block[
+ My cool rhino
+ #box(move(dx: 10pt, image("/assets/images/rhino.png", width: 1cm)))
+])
+
+--- link-to-page ---
+// Link to page one.
+#link((page: 1, x: 10pt, y: 20pt))[Back to the start]
+
+--- link-to-label ---
+// Test link to label.
+Text <hey>
+#link(<hey>)[Go to text.]
+
+--- link-to-label-missing ---
+// Error: 2-20 label `<hey>` does not exist in the document
+#link(<hey>)[Nope.]
+
+--- link-to-label-duplicate ---
+Text <hey>
+Text <hey>
+// Error: 2-20 label `<hey>` occurs multiple times in the document
+#link(<hey>)[Nope.]
diff --git a/tests/suite/model/list.typ b/tests/suite/model/list.typ
new file mode 100644
index 00000000..e37fa65d
--- /dev/null
+++ b/tests/suite/model/list.typ
@@ -0,0 +1,147 @@
+// Test bullet lists.
+
+--- list-basic ---
+_Shopping list_
+#list[Apples][Potatoes][Juice]
+
+--- list-nested ---
+- First level.
+
+ - Second level.
+ There are multiple paragraphs.
+
+ - Third level.
+
+ Still the same bullet point.
+
+ - Still level 2.
+
+- At the top.
+
+--- list-content-block ---
+- Level 1
+ - Level #[
+2 through content block
+]
+
+--- list-top-level-indent ---
+ - Top-level indent
+- is fine.
+
+--- list-indent-specifics ---
+ - A
+ - B
+ - C
+- D
+
+--- list-tabs ---
+// This works because tabs are used consistently.
+ - A with 1 tab
+ - B with 2 tabs
+
+--- list-mixed-tabs-and-spaces ---
+// This doesn't work because of mixed tabs and spaces.
+ - A with 2 spaces
+ - B with 2 tabs
+
+--- list-syntax-edge-cases ---
+// Edge cases.
+-
+Not in list
+-Nope
+
+--- list-marker-align-unaffected ---
+// Alignment shouldn't affect marker
+#set align(horizon)
+
+- ABCDEF\ GHIJKL\ MNOPQR
+
+--- list-marker-dash ---
+// Test en-dash.
+#set list(marker: [--])
+- A
+- B
+
+--- list-marker-cycle ---
+// Test that items are cycled.
+#set list(marker: ([--], [•]))
+- A
+ - B
+ - C
+
+--- list-marker-closure ---
+// Test function.
+#set list(marker: n => if n == 1 [--] else [•])
+- A
+- B
+ - C
+ - D
+ - E
+- F
+
+--- list-marker-bare-hyphen ---
+// Test that bare hyphen doesn't lead to cycles and crashes.
+#set list(marker: [-])
+- Bare hyphen is
+- a bad marker
+
+--- list-marker-array-empty ---
+// Error: 19-21 array must contain at least one marker
+#set list(marker: ())
+
+--- list-attached ---
+// Test basic attached list.
+Attached to:
+- the bottom
+- of the paragraph
+
+Next paragraph.
+
+--- list-attached-above-spacing ---
+// Test that attached list isn't affected by block spacing.
+#show list: set block(above: 100pt)
+Hello
+- A
+World
+- B
+
+--- list-non-attached-followed-by-attached ---
+// Test non-attached list followed by attached list,
+// separated by only word.
+Hello
+
+- A
+
+World
+- B
+
+--- list-tight-non-attached-tight ---
+// Test non-attached tight list.
+#set block(spacing: 15pt)
+Hello
+- A
+World
+
+- B
+- C
+
+More.
+
+--- list-wide-cannot-attach ---
+// Test that wide lists cannot be ...
+#set block(spacing: 15pt)
+Hello
+- A
+
+- B
+World
+
+--- list-wide-really-cannot-attach ---
+// ... even if forced to.
+Hello
+#list(tight: false)[A][B]
+World
+
+--- issue-2530-list-item-panic ---
+// List item (pre-emptive)
+#list.item[Hello]
diff --git a/tests/suite/model/numbering.typ b/tests/suite/model/numbering.typ
new file mode 100644
index 00000000..c2de1e05
--- /dev/null
+++ b/tests/suite/model/numbering.typ
@@ -0,0 +1,103 @@
+// Test integrated numbering patterns.
+
+--- numbering-symbol-and-roman ---
+#for i in range(0, 9) {
+ numbering("*", i)
+ [ and ]
+ numbering("I.a", i, i)
+ [ for #i \ ]
+}
+
+--- numbering-latin ---
+#for i in range(0, 4) {
+ numbering("A", i)
+ [ for #i \ ]
+}
+... \
+#for i in range(26, 30) {
+ numbering("A", i)
+ [ for #i \ ]
+}
+... \
+#for i in range(702, 706) {
+ numbering("A", i)
+ [ for #i \ ]
+}
+
+--- numbering-hebrew ---
+#set text(lang: "he")
+#for i in range(9, 21, step: 2) {
+ numbering("א.", i)
+ [ עבור #i \ ]
+}
+
+--- numbering-chinese ---
+#set text(lang: "zh", font: ("Linux Libertine", "Noto Serif CJK SC"))
+#for i in range(9, 21, step: 2){
+ numbering("一", i)
+ [ and ]
+ numbering("壹", i)
+ [ for #i \ ]
+}
+
+--- numbering-japanese-iroha ---
+#set text(lang: "ja", font: ("Linux Libertine", "Noto Serif CJK JP"))
+#for i in range(0, 4) {
+ numbering("イ", i)
+ [ (or ]
+ numbering("い", i)
+ [) for #i \ ]
+}
+... \
+#for i in range(47, 51) {
+ numbering("イ", i)
+ [ (or ]
+ numbering("い", i)
+ [) for #i \ ]
+}
+... \
+#for i in range(2256, 2260) {
+ numbering("イ", i)
+ [ for #i \ ]
+}
+
+--- numbering-korean ---
+#set text(lang: "ko", font: ("Linux Libertine", "Noto Serif CJK KR"))
+#for i in range(0, 4) {
+ numbering("가", i)
+ [ (or ]
+ numbering("ㄱ", i)
+ [) for #i \ ]
+}
+... \
+#for i in range(47, 51) {
+ numbering("가", i)
+ [ (or ]
+ numbering("ㄱ", i)
+ [) for #i \ ]
+}
+... \
+#for i in range(2256, 2260) {
+ numbering("ㄱ", i)
+ [ for #i \ ]
+}
+
+--- numbering-japanese-aiueo ---
+#set text(lang: "jp", font: ("Linux Libertine", "Noto Serif CJK JP"))
+#for i in range(0, 9) {
+ numbering("あ", i)
+ [ and ]
+ numbering("I.あ", i, i)
+ [ for #i \ ]
+}
+
+#for i in range(0, 9) {
+ numbering("ア", i)
+ [ and ]
+ numbering("I.ア", i, i)
+ [ for #i \ ]
+}
+
+--- numbering-negative ---
+// Error: 17-19 number must be at least zero
+#numbering("1", -1)
diff --git a/tests/suite/model/outline.typ b/tests/suite/model/outline.typ
new file mode 100644
index 00000000..d8fc1a43
--- /dev/null
+++ b/tests/suite/model/outline.typ
@@ -0,0 +1,176 @@
+--- outline ---
+// LARGE
+#set page("a7", margin: 20pt, numbering: "1")
+#set heading(numbering: "(1/a)")
+#show heading.where(level: 1): set text(12pt)
+#show heading.where(level: 2): set text(10pt)
+#set math.equation(numbering: "1")
+
+#outline()
+#outline(title: [Figures], target: figure)
+#outline(title: [Equations], target: math.equation)
+
+= Introduction
+#lorem(12)
+
+= Analysis
+#lorem(10)
+
+#[
+ #set heading(outlined: false)
+ == Methodology
+ #lorem(6)
+]
+
+== Math
+$x$ is a very useful constant. See it in action:
+$ x = x $
+
+== Interesting figures
+#figure(rect[CENSORED], kind: image, caption: [A picture showing a programmer at work.])
+#figure(table[1x1], caption: [A very small table.])
+
+== Programming
+```rust
+fn main() {
+ panic!("in the disco");
+}
+```
+
+==== Deep Stuff
+Ok ...
+
+// Ensure 'bookmarked' option doesn't affect the outline
+#set heading(numbering: "(I)", bookmarked: false)
+
+= #text(blue)[Sum]mary
+#lorem(10)
+
+--- outline-indent-numbering ---
+// LARGE
+// With heading numbering
+#set page(width: 200pt)
+#set heading(numbering: "1.a.")
+#show heading: none
+#set outline(fill: none)
+
+#context test(outline.indent, none)
+#outline(indent: false)
+#outline(indent: true)
+#outline(indent: none)
+#outline(indent: auto)
+#outline(indent: 2em)
+#outline(indent: n => ([-], [], [==], [====]).at(n))
+
+= About ACME Corp.
+== History
+== Products
+=== Categories
+==== General
+
+--- outline-indent-no-numbering ---
+// Without heading numbering
+#set page(width: 200pt)
+#show heading: none
+#set outline(fill: none)
+
+#outline(indent: false)
+#outline(indent: true)
+#outline(indent: none)
+#outline(indent: auto)
+#outline(indent: n => 2em * n)
+
+= About
+== History
+
+--- outline-indent-bad-type ---
+// Error: 2-35 expected relative length or content, found dictionary
+#outline(indent: n => (a: "dict"))
+
+= Heading
+
+--- outline-first-line-indent ---
+#set par(first-line-indent: 1.5em)
+#set heading(numbering: "1.1.a.")
+#show outline.entry.where(level: 1): it => {
+ v(0.5em, weak: true)
+ strong(it)
+}
+
+#outline()
+
+= Introduction
+= Background
+== History
+== State of the Art
+= Analysis
+== Setup
+
+--- outline-entry ---
+#set page(width: 150pt)
+#set heading(numbering: "1.")
+
+#show outline.entry.where(
+ level: 1
+): it => {
+ v(12pt, weak: true)
+ strong(it)
+}
+
+#outline(indent: auto)
+
+#set text(8pt)
+#show heading: set block(spacing: 0.65em)
+
+= Introduction
+= Background
+== History
+== State of the Art
+= Analysis
+== Setup
+
+--- outline-entry-complex ---
+#set page(width: 150pt, numbering: "I", margin: (bottom: 20pt))
+#set heading(numbering: "1.")
+#show outline.entry.where(level: 1): it => [
+ #let loc = it.element.location()
+ #let num = numbering(loc.page-numbering(), ..counter(page).at(loc))
+ #emph(link(loc, it.body))
+ #text(luma(100), box(width: 1fr, repeat[#it.fill.body;·]))
+ #link(loc, num)
+]
+
+#counter(page).update(3)
+#outline(indent: auto, fill: repeat[--])
+
+#set text(8pt)
+#show heading: set block(spacing: 0.65em)
+
+= Top heading
+== Not top heading
+=== Lower heading
+=== Lower too
+== Also not top
+
+#pagebreak()
+#set page(numbering: "1")
+
+= Another top heading
+== Middle heading
+=== Lower heading
+
+--- outline-bad-element ---
+// Error: 2-27 cannot outline metadata
+#outline(target: metadata)
+#metadata("hello")
+
+--- issue-2530-outline-entry-panic-text ---
+// Outline entry (pre-emptive)
+// Error: 2-48 cannot outline text
+#outline.entry(1, [Hello], [World!], none, [1])
+
+--- issue-2530-outline-entry-panic-heading ---
+// Outline entry (pre-emptive, improved error)
+// Error: 2-55 heading must have a location
+// Hint: 2-55 try using a query or a show rule to customize the outline.entry instead
+#outline.entry(1, heading[Hello], [World!], none, [1])
diff --git a/tests/suite/model/par.typ b/tests/suite/model/par.typ
new file mode 100644
index 00000000..65779f6a
--- /dev/null
+++ b/tests/suite/model/par.typ
@@ -0,0 +1,78 @@
+// Test configuring paragraph properties.
+
+--- par-basic ---
+#set page(width: 250pt, height: 120pt)
+
+But, soft! what light through yonder window breaks? It is the east, and Juliet
+is the sun. Arise, fair sun, and kill the envious moon, Who is already sick and
+pale with grief, That thou her maid art far more fair than she: Be not her maid,
+since she is envious; Her vestal livery is but sick and green And none but fools
+do wear it; cast it off. It is my lady, O, it is my love! O, that she knew she
+were! She speaks yet she says nothing: what of that? Her eye discourses; I will
+answer it.
+
+I am too bold, 'tis not to me she speaks: Two of the fairest stars in all the
+heaven, Having some business, do entreat her eyes To twinkle in their spheres
+till they return. What if her eyes were there, they in her head? The brightness
+of her cheek would shame those stars, As daylight doth a lamp; her eyes in
+heaven Would through the airy region stream so bright That birds would sing and
+think it were not night. See, how she leans her cheek upon her hand! O, that I
+were a glove upon that hand, That I might touch that cheek!
+
+--- par-leading-and-block-spacing ---
+// Test changing leading and spacing.
+#set block(spacing: 1em)
+#set par(leading: 2pt)
+But, soft! what light through yonder window breaks?
+
+It is the east, and Juliet is the sun.
+
+--- par-first-line-indent ---
+#set par(first-line-indent: 12pt, leading: 5pt)
+#set block(spacing: 5pt)
+#show heading: set text(size: 10pt)
+
+The first paragraph has no indent.
+
+But the second one does.
+
+#box(image("/assets/images/tiger.jpg", height: 6pt))
+starts a paragraph, also with indent.
+
+#align(center, image("/assets/images/rhino.png", width: 1cm))
+
+= Headings
+- And lists.
+- Have no indent.
+
+ Except if you have another paragraph in them.
+
+#set text(8pt, lang: "ar", font: ("Noto Sans Arabic", "Linux Libertine"))
+#set par(leading: 8pt)
+
+= Arabic
+دع النص يمطر عليك
+
+ثم يصبح النص رطبًا وقابل للطرق ويبدو المستند رائعًا.
+
+--- par-spacing-and-first-line-indent ---
+// This is madness.
+#set par(first-line-indent: 12pt)
+Why would anybody ever ...
+
+... want spacing and indent?
+
+--- par-hanging-indent ---
+// Test hanging indent.
+#set par(hanging-indent: 15pt, justify: true)
+#lorem(10)
+
+--- par-hanging-indent-manual-linebreak ---
+#set par(hanging-indent: 1em)
+Welcome \ here. Does this work well?
+
+--- par-hanging-indent-rtl ---
+#set par(hanging-indent: 2em)
+#set text(dir: rtl)
+لآن وقد أظلم الليل وبدأت النجوم
+تنضخ وجه الطبيعة التي أعْيَتْ من طول ما انبعثت في النهار
diff --git a/tests/suite/model/quote.typ b/tests/suite/model/quote.typ
new file mode 100644
index 00000000..446784ee
--- /dev/null
+++ b/tests/suite/model/quote.typ
@@ -0,0 +1,86 @@
+// Test the quote element.
+
+--- quote-dir-author-pos ---
+// Text direction affects author positioning
+And I quote: #quote(attribution: [René Descartes])[cogito, ergo sum].
+
+#set text(lang: "ar")
+#quote(attribution: [عالم])[مرحبًا]
+
+--- quote-dir-align ---
+// Text direction affects block alignment
+#set quote(block: true)
+#quote(attribution: [René Descartes])[cogito, ergo sum]
+
+#set text(lang: "ar")
+#quote(attribution: [عالم])[مرحبًا]
+
+--- quote-block-spacing ---
+// Spacing with other blocks
+#set quote(block: true)
+#set text(8pt)
+
+#lorem(10)
+#quote(lorem(10))
+#lorem(10)
+
+--- quote-inline ---
+// Inline citation
+#set text(8pt)
+#quote(attribution: <tolkien54>)[In a hole in the ground there lived a hobbit.]
+
+#set text(0pt)
+#bibliography("/assets/bib/works.bib")
+
+--- quote-cite-format-label-or-numeric ---
+// Citation-format: label or numeric
+#set text(8pt)
+#set quote(block: true)
+#quote(attribution: <tolkien54>)[In a hole in the ground there lived a hobbit.]
+
+#set text(0pt)
+#bibliography("/assets/bib/works.bib", style: "ieee")
+
+--- quote-cite-format-note ---
+// Citation-format: note
+#set text(8pt)
+#set quote(block: true)
+#quote(attribution: <tolkien54>)[In a hole in the ground there lived a hobbit.]
+
+#set text(0pt)
+#bibliography("/assets/bib/works.bib", style: "chicago-notes")
+
+--- quote-cite-format-author-date ---
+// Citation-format: author-date or author
+#set text(8pt)
+#set quote(block: true)
+#quote(attribution: <tolkien54>)[In a hole in the ground there lived a hobbit.]
+
+#set text(0pt)
+#bibliography("/assets/bib/works.bib", style: "apa")
+
+--- quote-nesting ---
+// Test quote selection.
+#set page(width: auto)
+#set text(lang: "en")
+=== EN
+#quote[An apostroph'] \
+#quote[A #quote[nested] quote] \
+#quote[A #quote[very #quote[nested]] quote]
+
+#set text(lang: "de")
+=== DE
+#quote[Satz mit Apostroph'] \
+#quote[Satz mit #quote[Zitat]] \
+#quote[A #quote[very #quote[nested]] quote]
+
+#set smartquote(alternative: true)
+=== DE Alternative
+#quote[Satz mit Apostroph'] \
+#quote[Satz mit #quote[Zitat]] \
+#quote[A #quote[very #quote[nested]] quote]
+
+--- quote-nesting-custom ---
+// With custom quotes.
+#set smartquote(quotes: (single: ("<", ">"), double: ("(", ")")))
+#quote[A #quote[nested] quote]
diff --git a/tests/suite/model/ref.typ b/tests/suite/model/ref.typ
new file mode 100644
index 00000000..200f40aa
--- /dev/null
+++ b/tests/suite/model/ref.typ
@@ -0,0 +1,56 @@
+// Test references.
+
+--- ref-basic ---
+#set heading(numbering: "1.")
+
+= Introduction <intro>
+See @setup.
+
+== Setup <setup>
+As seen in @intro, we proceed.
+
+--- ref-label-missing ---
+// Error: 1-5 label `<foo>` does not exist in the document
+@foo
+
+--- ref-label-duplicate ---
+= First <foo>
+= Second <foo>
+
+// Error: 1-5 label `<foo>` occurs multiple times in the document
+@foo
+
+--- ref-supplements ---
+#set heading(numbering: "1.", supplement: [Chapter])
+#set math.equation(numbering: "(1)", supplement: [Eq.])
+
+= Intro
+#figure(
+ image("/assets/images/cylinder.svg", height: 1cm),
+ caption: [A cylinder.],
+ supplement: "Fig",
+) <fig1>
+
+#figure(
+ image("/assets/images/tiger.jpg", height: 1cm),
+ caption: [A tiger.],
+ supplement: "Tig",
+) <fig2>
+
+$ A = 1 $ <eq1>
+
+#set math.equation(supplement: none)
+$ A = 1 $ <eq2>
+
+@fig1, @fig2, @eq1, (@eq2)
+
+#set ref(supplement: none)
+@fig1, @fig2, @eq1, @eq2
+
+--- ref-ambigious ---
+// Test ambiguous reference.
+= Introduction <arrgh>
+
+// Error: 1-7 label occurs in the document and its bibliography
+@arrgh
+#bibliography("/assets/bib/works.bib")
diff --git a/tests/suite/model/terms.typ b/tests/suite/model/terms.typ
new file mode 100644
index 00000000..6a08b923
--- /dev/null
+++ b/tests/suite/model/terms.typ
@@ -0,0 +1,77 @@
+// Test term list.
+
+--- terms-constructor ---
+// Test with constructor.
+#terms(
+ ([One], [First]),
+ ([Two], [Second]),
+)
+
+--- terms-built-in-loop ---
+// Test joining.
+#for word in lorem(4).split().map(s => s.trim(".")) [
+ / #word: Latin stuff.
+]
+
+--- terms-multiline ---
+// Test multiline.
+#set text(8pt)
+
+/ Fruit: A tasty, edible thing.
+/ Veggie:
+ An important energy source
+ for vegetarians.
+
+ And healthy!
+
+--- terms-style-change-interrupted ---
+// Test style change.
+#set text(8pt)
+/ First list: #lorem(6)
+
+#set terms(hanging-indent: 30pt)
+/ Second list: #lorem(5)
+
+--- terms-rtl ---
+// Test RTL.
+#set text(8pt, dir: rtl)
+
+/ פרי: דבר טעים, אכיל. ומקור אנרגיה חשוב לצמחונים.
+
+--- terms-grid ---
+// Test grid like show rule.
+#show terms: it => table(
+ columns: 2,
+ inset: 3pt,
+ ..it.children.map(v => (emph(v.term), v.description)).flatten(),
+)
+
+/ A: One letter
+/ BB: Two letters
+/ CCC: Three letters
+
+--- terms-syntax-edge-cases ---
+/ Term:
+Not in list
+/Nope
+
+--- terms-missing-colon ---
+// Error: 8 expected colon
+/ Hello
+
+--- issue-1050-terms-indent ---
+#set page(width: 200pt)
+#set par(first-line-indent: 0.5cm)
+
+- #lorem(10)
+- #lorem(10)
+
++ #lorem(10)
++ #lorem(10)
+
+/ Term 1: #lorem(10)
+/ Term 2: #lorem(10)
+
+--- issue-2530-term-item-panic ---
+// Term item (pre-emptive)
+#terms.item[Hello][World!]
diff --git a/tests/suite/playground.typ b/tests/suite/playground.typ
new file mode 100644
index 00000000..1af70bea
--- /dev/null
+++ b/tests/suite/playground.typ
@@ -0,0 +1 @@
+--- playground ---
diff --git a/tests/suite/scripting/blocks.typ b/tests/suite/scripting/blocks.typ
new file mode 100644
index 00000000..f139b8c6
--- /dev/null
+++ b/tests/suite/scripting/blocks.typ
@@ -0,0 +1,143 @@
+// Test code blocks.
+
+--- code-block-basic-syntax ---
+
+// Evaluates to join of none, [My ] and the two loop bodies.
+#{
+ let parts = ("my fri", "end.")
+ [Hello, ]
+ for s in parts [#s]
+}
+
+// Evaluates to join of the content and strings.
+#{
+ [How]
+ if true {
+ " are"
+ }
+ [ ]
+ if false [Nope]
+ [you] + "?"
+}
+
+--- code-block-empty ---
+// Nothing evaluates to none.
+#test({}, none)
+
+--- code-block-let ---
+// Let evaluates to none.
+#test({ let v = 0 }, none)
+
+--- code-block-single-expression ---
+// Evaluates to single expression.
+#test({ "hello" }, "hello")
+
+--- code-block-multiple-expressions-single-line ---
+// Evaluates to string.
+#test({ let x = "m"; x + "y" }, "my")
+
+--- code-block-join-let-with-expression ---
+// Evaluated to int.
+#test({
+ let x = 1
+ let y = 2
+ x + y
+}, 3)
+
+--- code-block-join-expression-with-none ---
+// String is joined with trailing none, evaluates to string.
+#test({
+ type("")
+ none
+}, str)
+
+--- code-block-join-int-with-content ---
+// Some things can't be joined.
+#{
+ [A]
+ // Error: 3-4 cannot join content with integer
+ 1
+ [B]
+}
+
+--- code-block-scope-in-markup ---
+// Block directly in markup also creates a scope.
+#{ let x = 1 }
+
+// Error: 7-8 unknown variable: x
+#test(x, 1)
+
+--- code-block-scope-in-let ---
+// Block in expression does create a scope.
+#let a = {
+ let b = 1
+ b
+}
+
+#test(a, 1)
+
+// Error: 3-4 unknown variable: b
+#{b}
+
+--- code-block-double-scope ---
+// Double block creates a scope.
+#{{
+ import "module.typ": b
+ test(b, 1)
+}}
+
+// Error: 2-3 unknown variable: b
+#b
+
+--- code-block-nested-scopes ---
+// Multiple nested scopes.
+#{
+ let a = "a1"
+ {
+ let a = "a2"
+ {
+ test(a, "a2")
+ let a = "a3"
+ test(a, "a3")
+ }
+ test(a, "a2")
+ }
+ test(a, "a1")
+}
+
+--- code-block-multiple-literals-without-semicolon ---
+// Multiple unseparated expressions in one line.
+// Error: 4 expected semicolon or line break
+#{1 2}
+
+--- code-block-multiple-expressions-without-semicolon ---
+// Error: 13 expected semicolon or line break
+// Error: 23 expected semicolon or line break
+#{let x = -1 let y = 3 x + y}
+
+--- code-block-incomplete-expressions ---
+#{
+ // Error: 7-10 expected pattern, found string
+ for "v"
+
+ // Error: 8 expected keyword `in`
+ // Error: 22 expected block
+ for v let z = 1 + 2
+
+ z
+}
+
+--- code-block-unclosed ---
+// Error: 2-3 unclosed delimiter
+#{
+
+--- code-block-unopened ---
+// Error: 2-3 unexpected closing brace
+#}
+
+--- content-block-in-markup-scope ---
+// Content blocks also create a scope.
+#[#let x = 1]
+
+// Error: 2-3 unknown variable: x
+#x
diff --git a/tests/suite/scripting/call.typ b/tests/suite/scripting/call.typ
new file mode 100644
index 00000000..e79fc949
--- /dev/null
+++ b/tests/suite/scripting/call.typ
@@ -0,0 +1,200 @@
+// Test function calls.
+
+--- call-basic ---
+
+// Omitted space.
+#let f() = {}
+#[#f()*Bold*]
+
+// Call return value of function with body.
+#let f(x, body) = (y) => [#x] + body + [#y]
+#f(1)[2](3)
+
+// Don't parse this as a function.
+#test (it)
+
+#let f(body) = body
+#f[A]
+#f()[A]
+#f([A])
+
+#let g(a, b) = a + b
+#g[A][B]
+#g([A], [B])
+#g()[A][B]
+
+--- call-aliased-function ---
+// Call function assigned to variable.
+#let alias = type
+#test(alias(alias), type)
+
+--- call-complex-callee-expression ---
+// Callee expressions.
+#{
+ // Wrapped in parens.
+ test((type)("hi"), str)
+
+ // Call the return value of a function.
+ let adder(dx) = x => x + dx
+ test(adder(2)(5), 7)
+}
+
+--- call-bad-type-bool-literal ---
+// Error: 2-6 expected function, found boolean
+#true()
+
+--- call-bad-type-string-var ---
+#let x = "x"
+
+// Error: 2-3 expected function, found string
+#x()
+
+--- call-bad-type-int-expr ---
+#let f(x) = x
+
+// Error: 2-6 expected function, found integer
+#f(1)(2)
+
+--- call-bad-type-content-expr ---
+#let f(x) = x
+
+// Error: 2-6 expected function, found content
+#f[1](2)
+
+--- call-args-trailing-comma ---
+// Trailing comma.
+#test(1 + 1, 2,)
+
+--- call-args-duplicate ---
+// Error: 26-30 duplicate argument: font
+#set text(font: "Arial", font: "Helvetica")
+
+--- call-args-bad-positional-as-named ---
+// Error: 4-15 the argument `amount` is positional
+// Hint: 4-15 try removing `amount:`
+#h(amount: 0.5)
+
+--- call-args-bad-colon ---
+// Error: 7-8 unexpected colon
+#func(:)
+
+--- call-args-bad-token ---
+// Error: 10-12 unexpected end of block comment
+#func(a:1*/)
+
+--- call-args-missing-comma ---
+// Error: 8 expected comma
+#func(1 2)
+
+--- call-args-bad-name-and-incomplete-pair ---
+// Error: 7-8 expected identifier, found integer
+// Error: 9 expected expression
+#func(1:)
+
+--- call-args-bad-name-int ---
+// Error: 7-8 expected identifier, found integer
+#func(1:2)
+
+--- call-args-bad-name-string ---
+// Error: 7-12 expected identifier, found string
+#func("abc": 2)
+
+--- call-args-bad-name-group ---
+// Error: 7-10 expected identifier, found group
+#func((x):1)
+
+--- call-args-lone-underscore ---
+// Test that lone underscore works.
+#test((1, 2, 3).map(_ => {}).len(), 3)
+
+--- call-args-spread-override ---
+// Test standard argument overriding.
+#{
+ let f(style: "normal", weight: "regular") = {
+ "(style: " + style + ", weight: " + weight + ")"
+ }
+
+ let myf(..args) = f(weight: "bold", ..args)
+ test(myf(), "(style: normal, weight: bold)")
+ test(myf(weight: "black"), "(style: normal, weight: black)")
+ test(myf(style: "italic"), "(style: italic, weight: bold)")
+}
+
+--- call-args-spread-forward ---
+// Test multiple calls.
+#{
+ let f(b, c: "!") = b + c
+ let g(a, ..sink) = a + f(..sink)
+ test(g("a", "b", c: "c"), "abc")
+}
+
+--- call-args-spread-type-repr ---
+// Test doing things with arguments.
+#{
+ let save(..args) = {
+ test(type(args), arguments)
+ test(repr(args), "(three: true, 1, 2)")
+ }
+
+ save(1, 2, three: true)
+}
+
+--- call-args-spread-array-and-dict ---
+// Test spreading array and dictionary.
+#{
+ let more = (3, -3, 6, 10)
+ test(calc.min(1, 2, ..more), -3)
+ test(calc.max(..more, 9), 10)
+ test(calc.max(..more, 11), 11)
+}
+
+#{
+ let more = (c: 3, d: 4)
+ let tostr(..args) = repr(args)
+ test(tostr(a: 1, ..more, b: 2), "(a: 1, c: 3, d: 4, b: 2)")
+}
+
+--- call-args-spread-none ---
+// None is spreadable.
+#let f() = none
+#f(..none)
+#f(..if false {})
+#f(..for x in () [])
+
+--- call-args-spread-string-invalid ---
+// Error: 11-19 cannot spread string
+#calc.min(.."nope")
+
+--- call-args-content-block-unclosed ---
+// Error: 6-7 unclosed delimiter
+#func[`a]`
+
+--- issue-886-args-sink ---
+// Test bugs with argument sinks.
+#let foo(..body) = repr(body.pos())
+#foo(a: "1", b: "2", 1, 2, 3, 4, 5, 6)
+
+--- issue-3144-unexpected-arrow ---
+#let f(a: 10) = a(1) + 1
+#test(f(a: _ => 5), 6)
+
+--- issue-3502-space-and-comments-around-destructuring-colon ---
+#let ( key : /* hi */ binding ) = ( key: "ok" )
+#test(binding, "ok")
+
+--- issue-3502-space-around-dict-colon ---
+#test(( key : "value" ).key, "value")
+
+--- issue-3502-space-around-param-colon ---
+// Test that a space after a named parameter is permissible.
+#let f( param : v ) = param
+#test(f( param /* ok */ : 2 ), 2)
+
+--- call-args-unclosed ---
+// Error: 7-8 unclosed delimiter
+#{func(}
+
+--- call-args-unclosed-string ---
+// Error: 6-7 unclosed delimiter
+// Error: 1:7-2:1 unclosed string
+#func("]
diff --git a/tests/suite/scripting/closure.typ b/tests/suite/scripting/closure.typ
new file mode 100644
index 00000000..e3677d33
--- /dev/null
+++ b/tests/suite/scripting/closure.typ
@@ -0,0 +1,223 @@
+// Test closures.
+
+--- closure-without-params-non-atomic ---
+// Don't parse closure directly in content.
+
+#let x = "x"
+
+// Should output `x => y`.
+#x => y
+
+--- closure-without-captures ---
+// Basic closure without captures.
+#{
+ let adder = (x, y) => x + y
+ test(adder(2, 3), 5)
+}
+
+--- closure-as-arg ---
+// Pass closure as argument and return closure.
+// Also uses shorthand syntax for a single argument.
+#{
+ let chain = (f, g) => (x) => f(g(x))
+ let f = x => x + 1
+ let g = x => 2 * x
+ let h = chain(f, g)
+ test(h(2), 5)
+}
+
+--- closure-capture-from-popped-stack-frame ---
+// Capture environment.
+#{
+ let mark = "!"
+ let greet = {
+ let hi = "Hi"
+ name => {
+ hi + ", " + name + mark
+ }
+ }
+
+ test(greet("Typst"), "Hi, Typst!")
+
+ // Changing the captured variable after the closure definition has no effect.
+ mark = "?"
+ test(greet("Typst"), "Hi, Typst!")
+}
+
+--- closure-shadows-outer-var ---
+// Redefined variable.
+#{
+ let x = 1
+ let f() = {
+ let x = x + 2
+ x
+ }
+ test(f(), 3)
+}
+
+--- closure-shadows-outer-var-import ---
+// Import bindings.
+#{
+ let b = "module.typ"
+ let f() = {
+ import b: b
+ b
+ }
+ test(f(), 1)
+}
+
+--- closure-shadows-outer-var-for-loop ---
+// For loop bindings.
+#{
+ let v = (1, 2, 3)
+ let f() = {
+ let s = 0
+ for v in v { s += v }
+ s
+ }
+ test(f(), 6)
+}
+
+--- closure-let-basic ---
+// Let + closure bindings.
+#{
+ let g = "hi"
+ let f() = {
+ let g() = "bye"
+ g()
+ }
+ test(f(), "bye")
+}
+
+--- closure-let-args ---
+// Parameter bindings.
+#{
+ let x = 5
+ let g() = {
+ let f(x, y: x) = x + y
+ f
+ }
+
+ test(g()(8), 13)
+}
+
+--- closure-bad-capture ---
+// Don't leak environment.
+#{
+ // Error: 16-17 unknown variable: x
+ let func() = x
+ let x = "hi"
+ func()
+}
+
+--- closure-missing-arg-positional ---
+// Too few arguments.
+#{
+ let types(x, y) = "[" + str(type(x)) + ", " + str(type(y)) + "]"
+ test(types(14%, 12pt), "[ratio, length]")
+
+ // Error: 8-21 missing argument: y
+ test(types("nope"), "[string, none]")
+}
+
+--- closure-too-many-args-positional ---
+// Too many arguments.
+#{
+ let f(x) = x + 1
+
+ // Error: 8-13 unexpected argument
+ f(1, "two", () => x)
+}
+
+--- closure-capture-in-lvalue ---
+// Mutable method with capture in argument.
+#let x = "b"
+#let f() = {
+ let a = (b: 5)
+ a.at(x) = 10
+ a
+}
+#f()
+
+--- closure-capture-mutate ---
+#let x = ()
+#let f() = {
+ // Error: 3-4 variables from outside the function are read-only and cannot be modified
+ x.at(1) = 2
+}
+#f()
+
+--- closure-named-args-basic ---
+// Named arguments.
+#{
+ let greet(name, birthday: false) = {
+ if birthday { "Happy Birthday, " } else { "Hey, " } + name + "!"
+ }
+
+ test(greet("Typst"), "Hey, Typst!")
+ test(greet("Typst", birthday: true), "Happy Birthday, Typst!")
+
+ // Error: 23-35 unexpected argument: whatever
+ test(greet("Typst", whatever: 10))
+}
+
+--- closure-args-sink ---
+// Parameter unpacking.
+#let f((a, b), ..c) = (a, b, c)
+#test(f((1, 2), 3, 4), (1, 2, (3, 4)))
+
+#let f((k: a, b), c: 3, (d,)) = (a, b, c, d)
+#test(f((k: 1, b: 2), (4,)), (1, 2, 3, 4))
+
+// Error: 8-14 expected identifier, found destructuring pattern
+#let f((a, b): 0) = none
+
+// Error: 10-19 expected pattern, found array
+#let f(..(a, b: c)) = none
+
+// Error: 10-16 expected pattern, found array
+#let f(..(a, b)) = none
+
+--- closure-param-duplicate-positional ---
+// Error: 11-12 duplicate parameter: x
+#let f(x, x) = none
+
+--- closure-body-multiple-expressions ---
+// Error: 21 expected comma
+// Error: 22-23 expected pattern, found integer
+// Error: 24-25 unexpected plus
+// Error: 26-27 expected pattern, found integer
+#let f = (x: () => 1 2 + 3) => 4
+
+--- closure-param-duplicate-mixed ---
+// Error: 14-15 duplicate parameter: a
+// Error: 23-24 duplicate parameter: b
+// Error: 35-36 duplicate parameter: b
+#let f(a, b, a: none, b: none, c, b) = none
+
+--- closure-param-duplicate-spread ---
+// Error: 13-14 duplicate parameter: a
+#let f(a, ..a) = none
+
+--- closure-pattern-bad-string ---
+// Error: 7-14 expected pattern, found string
+#((a, "named": b) => none)
+
+--- closure-let-pattern-bad-string ---
+// Error: 10-15 expected pattern, found string
+#let foo("key": b) = key
+
+--- closure-param-keyword ---
+// Error: 10-14 expected pattern, found `none`
+// Hint: 10-14 keyword `none` is not allowed as an identifier; try `none_` instead
+#let foo(none: b) = key
+
+--- closure-param-named-underscore ---
+// Error: 10-11 expected identifier, found underscore
+#let foo(_: 3) = none
+
+--- issue-non-atomic-closure ---
+// Ensure that we can't have non-atomic closures.
+#let x = 1
+#let c = [#(x) => (1, 2)]
+#test(c.children.last(), [(1, 2)]))
diff --git a/tests/suite/scripting/destructuring.typ b/tests/suite/scripting/destructuring.typ
new file mode 100644
index 00000000..0a3c1c54
--- /dev/null
+++ b/tests/suite/scripting/destructuring.typ
@@ -0,0 +1,357 @@
+--- destructuring-group-1 ---
+// This wasn't allowed.
+#let ((x)) = 1
+#test(x, 1)
+
+--- destructuring-group-2 ---
+// This also wasn't allowed.
+#let ((a, b)) = (1, 2)
+#test(a, 1)
+#test(b, 2)
+
+--- destructuring-dict-underscore ---
+// Here, `best` was accessed as a variable, where it shouldn't have.
+#{
+ (best: _) = (best: "brr")
+}
+
+--- destructuring-dict-array-at ---
+// Same here.
+#{
+ let array = (1, 2, 3, 4)
+ (test: array.at(1), best: _) = (test: "baz", best: "brr")
+ test(array, (1, "baz", 3, 4))
+}
+
+--- destructuring-dict-bad ---
+// Error: 7-10 expected identifier, found group
+// Error: 12-14 expected pattern, found integer
+#let ((a): 10) = "world"
+
+--- destructuring-bad-duplicate ---
+// Here, `a` is not duplicate, where it was previously identified as one.
+#let f((a: b), (c,), a) = (a, b, c)
+#test(f((a: 1), (2,), 3), (3, 1, 2))
+
+--- destructuring-non-atomic ---
+// Ensure that we can't have non-atomic destructuring.
+#let x = 1
+#let c = [#() = ()]
+#test(c.children.last(), [()])
+
+--- destructuring-let-array ---
+// Simple destructuring.
+#let (a, b) = (1, 2)
+#test(a, 1)
+#test(b, 2)
+
+--- destructuring-let-array-single-item ---
+#let (a,) = (1,)
+#test(a, 1)
+
+--- destructuring-let-array-placeholders ---
+// Destructuring with multiple placeholders.
+#let (a, _, c, _) = (1, 2, 3, 4)
+#test(a, 1)
+#test(c, 3)
+
+--- destructuring-let-array-with-sink-at-end ---
+// Destructuring with a sink.
+#let (a, b, ..c) = (1, 2, 3, 4, 5, 6)
+#test(a, 1)
+#test(b, 2)
+#test(c, (3, 4, 5, 6))
+
+--- destructuring-let-array-with-sink-in-middle ---
+// Destructuring with a sink in the middle.
+#let (a, ..b, c) = (1, 2, 3, 4, 5, 6)
+#test(a, 1)
+#test(b, (2, 3, 4, 5))
+#test(c, 6)
+
+--- destructuring-let-array-with-sink-at-start-empty ---
+// Destructuring with an empty sink.
+#let (..a, b, c) = (1, 2)
+#test(a, ())
+#test(b, 1)
+#test(c, 2)
+
+--- destructuring-let-array-with-sink-in-middle-empty ---
+// Destructuring with an empty sink.
+#let (a, ..b, c) = (1, 2)
+#test(a, 1)
+#test(b, ())
+#test(c, 2)
+
+--- destructuring-let-array-with-sink-at-end-empty ---
+// Destructuring with an empty sink.
+#let (a, b, ..c) = (1, 2)
+#test(a, 1)
+#test(b, 2)
+#test(c, ())
+
+--- destructuring-let-array-with-sink-empty ---
+// Destructuring with an empty sink and empty array.
+#let (..a) = ()
+#test(a, ())
+
+--- destructuring-let-array-with-unnamed-sink ---
+// Destructuring with unnamed sink.
+#let (a, .., b) = (1, 2, 3, 4)
+#test(a, 1)
+#test(b, 4)
+
+// Error: 10-11 duplicate binding: a
+#let (a, a) = (1, 2)
+
+// Error: 12-15 only one destructuring sink is allowed
+#let (..a, ..a) = (1, 2)
+
+// Error: 12-13 duplicate binding: a
+#let (a, ..a) = (1, 2)
+
+// Error: 13-14 duplicate binding: a
+#let (a: a, a) = (a: 1, b: 2)
+
+// Error: 13-20 expected pattern, found function call
+#let (a, b: b.at(0)) = (a: 1, b: 2)
+
+// Error: 7-14 expected pattern, found function call
+#let (a.at(0),) = (1,)
+
+--- destructuring-let-array-too-few-elements ---
+// Error: 13-14 not enough elements to destructure
+#let (a, b, c) = (1, 2)
+
+--- destructuring-let-array-too-few-elements-with-sink ---
+// Error: 7-10 not enough elements to destructure
+#let (..a, b, c, d) = (1, 2)
+
+--- destructuring-let-array-bool-invalid ---
+// Error: 6-12 cannot destructure boolean
+#let (a, b) = true
+
+--- destructuring-let-dict ---
+// Simple destructuring.
+#let (a: a, b, x: c) = (a: 1, b: 2, x: 3)
+#test(a, 1)
+#test(b, 2)
+#test(c, 3)
+
+--- destructuring-let-dict-with-sink-at-end ---
+// Destructuring with a sink.
+#let (a: _, ..b) = (a: 1, b: 2, c: 3)
+#test(b, (b: 2, c: 3))
+
+--- destructuring-let-dict-with-sink-in-middle ---
+// Destructuring with a sink in the middle.
+#let (a: _, ..b, c: _) = (a: 1, b: 2, c: 3)
+#test(b, (b: 2))
+
+--- destructuring-let-dict-with-sink-at-end-empty ---
+// Destructuring with an empty sink.
+#let (a: _, ..b) = (a: 1)
+#test(b, (:))
+
+--- destructuring-let-dict-with-sink-empty ---
+// Destructuring with an empty sink and empty dict.
+#let (..a) = (:)
+#test(a, (:))
+
+--- destructuring-let-dict-with-unnamed-sink ---
+// Destructuring with unnamed sink.
+#let (a, ..) = (a: 1, b: 2)
+#test(a, 1)
+
+--- destructuring-let-nested ---
+// Nested destructuring.
+#let ((a, b), (key: c)) = ((1, 2), (key: 3))
+#test((a, b, c), (1, 2, 3))
+
+--- destructuring-let-dict-key-string-invalid ---
+// Keyed destructuring is not currently supported.
+// Error: 7-18 expected pattern, found string
+#let ("spacy key": val) = ("spacy key": 123)
+#val
+
+--- destructuring-let-dict-key-expr-invalid ---
+// Keyed destructuring is not currently supported.
+#let x = "spacy key"
+// Error: 7-10 expected identifier, found group
+#let ((x): v) = ("spacy key": 123)
+
+--- destructuring-let-array-trailing-placeholders ---
+// Trailing placeholders.
+// Error: 10-11 not enough elements to destructure
+#let (a, _, _, _, _) = (1,)
+#test(a, 1)
+
+--- destructuring-let-dict-patterns-invalid ---
+// Error: 10-13 expected pattern, found string
+// Error: 18-19 expected pattern, found integer
+#let (a: "a", b: 2) = (a: 1, b: 2)
+
+--- destructuring-let-dict-shorthand-missing-key ---
+// Error: 10-11 dictionary does not contain key "b"
+#let (a, b) = (a: 1)
+
+--- destructuring-let-dict-missing-key ---
+// Error: 10-11 dictionary does not contain key "b"
+#let (a, b: b) = (a: 1)
+
+--- destructuring-let-dict-from-array ---
+// Error: 7-11 cannot destructure named pattern from an array
+#let (a: a, b) = (1, 2, 3)
+
+--- destructuring-during-loop-continue ---
+// Test continue while destructuring.
+// Should output "one = I \ two = II \ one = I".
+#for num in (1, 2, 3, 1) {
+ let (word, roman) = if num == 1 {
+ ("one", "I")
+ } else if num == 2 {
+ ("two", "II")
+ } else {
+ continue
+ }
+ [#word = #roman \ ]
+}
+
+--- destructuring-assign ---
+// Test destructuring assignments.
+
+#let a = none
+#let b = none
+#let c = none
+#((a,) = (1,))
+#test(a, 1)
+
+#((_, a, b, _) = (1, 2, 3, 4))
+#test(a, 2)
+#test(b, 3)
+
+#((a, b, ..c) = (1, 2, 3, 4, 5, 6))
+#test(a, 1)
+#test(b, 2)
+#test(c, (3, 4, 5, 6))
+
+#((a: a, b, x: c) = (a: 1, b: 2, x: 3))
+#test(a, 1)
+#test(b, 2)
+#test(c, 3)
+
+#let a = (1, 2)
+#((a: a.at(0), b) = (a: 3, b: 4))
+#test(a, (3, 2))
+#test(b, 4)
+
+#let a = (1, 2)
+#((a.at(0), b) = (3, 4))
+#test(a, (3, 2))
+#test(b, 4)
+
+#((a, ..b) = (1, 2, 3, 4))
+#test(a, 1)
+#test(b, (2, 3, 4))
+
+#let a = (1, 2)
+#((b, ..a.at(0)) = (1, 2, 3, 4))
+#test(a, ((2, 3, 4), 2))
+#test(b, 1)
+
+--- destructuring-assign-commas ---
+// Test comma placement in destructuring assignment.
+#let array = (1, 2, 3)
+#((key: array.at(1)) = (key: "hi"))
+#test(array, (1, "hi", 3))
+
+#let array = (1, 2, 3)
+#((array.at(1)) = ("hi"))
+#test(array, (1, "hi", 3))
+
+#let array = (1, 2, 3)
+#((array.at(1),) = ("hi",))
+#test(array, (1, "hi", 3))
+
+#let array = (1, 2, 3)
+#((array.at(1)) = ("hi",))
+#test(array, (1, ("hi",), 3))
+
+--- destructuring-assign-nested ---
+// Test nested destructuring assignment.
+#let a
+#let b
+#let c
+#(((a, b), (key: c)) = ((1, 2), (key: 3)))
+#test((a, b, c), (1, 2, 3))
+
+--- destructuring-assign-nested-invalid ---
+#let array = (1, 2, 3)
+// Error: 3-17 cannot destructure string
+#((array.at(1),) = ("hi"))
+#test(array, (1, ("hi",), 3))
+
+--- issue-3275-normal-variable ---
+// Normal variable.
+#for x in (1, 2) {}
+#for x in (a: 1, b: 2) {}
+#for x in "foo" {}
+#for x in bytes("😊") {}
+
+--- issue-3275-placeholder ---
+// Placeholder.
+#for _ in (1, 2) {}
+#for _ in (a: 1, b: 2) {}
+#for _ in "foo" {}
+#for _ in bytes("😊") {}
+
+--- issue-3275-destructuring ---
+// Destructuring.
+#for (a,b,c) in (("a", 1, bytes(())), ("b", 2, bytes(""))) {}
+#for (a, ..) in (("a", 1, bytes(())), ("b", 2, bytes(""))) {}
+#for (k, v) in (a: 1, b: 2, c: 3) {}
+#for (.., v) in (a: 1, b: 2, c: 3) {}
+
+--- issue-3275-loop-over-content ---
+// Error: 11-17 cannot loop over content
+#for x in [1, 2] {}
+
+--- issue-3275-loop-over-arguments ---
+// Error: 11-25 cannot loop over arguments
+#for _ in arguments("a") {}
+
+--- issue-3275-loop-over-integer ---
+// Error: 16-21 cannot loop over integer
+#for (x, y) in 12306 {}
+
+--- issue-3275-destructuring-loop-over-content ---
+// Error: 16-22 cannot loop over content
+#for (x, y) in [1, 2] {}
+
+--- issue-3275-destructuring-loop-over-string ---
+// Error: 6-12 cannot destructure values of string
+#for (x, y) in "foo" {}
+
+--- issue-3275-destructuring-loop-over-string-array ---
+// Error: 6-12 cannot destructure string
+#for (x, y) in ("foo", "bar") {}
+
+--- issue-3275-destructuring-loop-over-bytes ---
+// Error: 6-12 cannot destructure values of bytes
+#for (x, y) in bytes("😊") {}
+
+--- issue-3275-destructuring-loop-over-bytes-array ---
+// Error: 6-12 cannot destructure bytes
+#for (x, y) in (bytes((1,2)), bytes((1,2))) {}
+
+--- issue-3275-destructuring-loop-over-int-array ---
+// Error: 6-12 cannot destructure integer
+#for (x, y) in (1, 2) {}
+
+--- issue-3275-destructuring-loop-over-2d-array-1 ---
+// Error: 10-11 not enough elements to destructure
+#for (x, y) in ((1,), (2,)) {}
+
+--- issue-3275-destructuring-loop-over-2d-array-2 ---
+// Error: 6-12 too many elements to destructure
+#for (x, y) in ((1,2,3), (4,5,6)) {}
diff --git a/tests/suite/scripting/field.typ b/tests/suite/scripting/field.typ
new file mode 100644
index 00000000..7b2427e3
--- /dev/null
+++ b/tests/suite/scripting/field.typ
@@ -0,0 +1,76 @@
+// Test field access.
+
+--- field-function ---
+// Test fields on function scopes.
+#enum.item
+#assert.eq
+#assert.ne
+
+--- field-normal-function-invalid ---
+// Error: 9-16 function `assert` does not contain field `invalid`
+#assert.invalid
+
+--- field-elem-function-invalid ---
+// Error: 7-14 function `enum` does not contain field `invalid`
+#enum.invalid
+
+--- field-elem-function-invalid-call ---
+// Error: 7-14 function `enum` does not contain field `invalid`
+#enum.invalid()
+
+--- field-closure-invalid ---
+// Closures cannot have fields.
+#let f(x) = x
+// Error: 4-11 cannot access fields on user-defined functions
+#f.invalid
+
+--- field-bool-invalid ---
+// Error: 8-10 cannot access fields on type boolean
+#false.ok
+
+--- field-bool-keyword-invalid ---
+// Error: 9-13 cannot access fields on type boolean
+#{false.true}
+
+--- field-invalid-none ---
+#{
+ let object = none
+ // Error: 3-9 none does not have accessible fields
+ object.property = "value"
+}
+
+--- field-invalid-int ---
+#{
+ let object = 10
+ // Error: 3-9 integer does not have accessible fields
+ object.property = "value"
+}
+
+--- field-mutable-invalid-symbol ---
+#{
+ let object = sym.eq.not
+ // Error: 3-9 cannot mutate fields on symbol
+ object.property = "value"
+}
+
+--- field-mutable-invalid-module ---
+#{
+ let object = calc
+ // Error: 3-9 cannot mutate fields on module
+ object.property = "value"
+}
+
+--- field-mutable-invalid-function ---
+#{
+ let object = calc.sin
+ // Error: 3-9 cannot mutate fields on function
+ object.property = "value"
+}
+
+--- field-mutable-invalid-stroke ---
+#{
+ let s = 1pt + red
+ // Error: 3-4 fields on stroke are not yet mutable
+ // Hint: 3-4 try creating a new stroke with the updated field value instead
+ s.thickness = 5pt
+}
diff --git a/tests/suite/scripting/for.typ b/tests/suite/scripting/for.typ
new file mode 100644
index 00000000..e98b3c72
--- /dev/null
+++ b/tests/suite/scripting/for.typ
@@ -0,0 +1,135 @@
+// Test for loops.
+
+--- for-loop-basic ---
+
+// Empty array.
+#for x in () [Nope]
+
+// Dictionary is traversed in insertion order.
+// Should output `Name: Typst. Age: 2.`.
+#for (k, v) in (Name: "Typst", Age: 2) [
+ #k: #v.
+]
+
+// Block body.
+// Should output `[1st, 2nd, 3rd, 4th]`.
+#{
+ "["
+ for v in (1, 2, 3, 4) {
+ if v > 1 [, ]
+ [#v]
+ if v == 1 [st]
+ if v == 2 [nd]
+ if v == 3 [rd]
+ if v >= 4 [th]
+ }
+ "]"
+}
+
+// Content block body.
+// Should output `2345`.
+#for v in (1, 2, 3, 4, 5, 6, 7) [#if v >= 2 and v <= 5 { repr(v) }]
+
+// Map captured arguments.
+#let f1(..args) = args.pos().map(repr)
+#let f2(..args) = args.named().pairs().map(p => repr(p.first()) + ": " + repr(p.last()))
+#let f(..args) = (f1(..args) + f2(..args)).join(", ")
+#f(1, a: 2)
+
+--- for-loop-integrated ---
+#let out = ()
+
+// Values of array.
+#for v in (1, 2, 3) {
+ out += (v,)
+}
+
+// Indices and values of array.
+#for (i, v) in ("1", "2", "3").enumerate() {
+ test(repr(i + 1), v)
+}
+
+// Pairs of dictionary.
+#for v in (a: 4, b: 5) {
+ out += (v,)
+}
+
+// Keys and values of dictionary.
+#for (k, v) in (a: 6, b: 7) {
+ out += (k,)
+ out += (v,)
+}
+
+#test(out, (1, 2, 3, ("a", 4), ("b", 5), "a", 6, "b", 7))
+
+// Grapheme clusters of string.
+#let first = true
+#let joined = for c in "abc👩‍👩‍👦‍👦" {
+ if not first { ", " }
+ first = false
+ c
+}
+
+#test(joined, "a, b, c, 👩‍👩‍👦‍👦")
+
+// Return value.
+#test(for v in "" [], none)
+#test(type(for v in "1" []), content)
+
+--- for-loop-over-bool ---
+// Uniterable expression.
+// Error: 11-15 cannot loop over boolean
+#for v in true {}
+
+--- for-loop-over-string ---
+// Keys and values of strings.
+// Error: 6-12 cannot destructure values of string
+#for (k, v) in "hi" {
+ dont-care
+}
+
+--- for-loop-destructuring-without-parentheses ---
+// Destructuring without parentheses.
+// Error: 7-8 unexpected comma
+// Hint: 7-8 destructuring patterns must be wrapped in parentheses
+#for k, v in (a: 4, b: 5) {
+ dont-care
+}
+
+--- for-loop-destructuring-half ---
+// Error: 7-8 unexpected comma
+// Hint: 7-8 destructuring patterns must be wrapped in parentheses
+#for k, in () {}
+
+--- for-loop-incomplete ---
+// Error: 5 expected pattern
+#for
+
+// Error: 5 expected pattern
+#for//
+
+// Error: 6 expected pattern
+#{for}
+
+// Error: 7 expected keyword `in`
+#for v
+
+// Error: 10 expected expression
+#for v in
+
+// Error: 15 expected block
+#for v in iter
+
+// Error: 5 expected pattern
+#for
+v in iter {}
+
+// Error: 7-10 expected pattern, found string
+// Error: 16 expected block
+A#for "v" thing
+
+// Error: 6-9 expected pattern, found string
+#for "v" in iter {}
+
+// Error: 7 expected keyword `in`
+#for a + b in iter {}
diff --git a/tests/suite/scripting/get-rule.typ b/tests/suite/scripting/get-rule.typ
new file mode 100644
index 00000000..24d4e5db
--- /dev/null
+++ b/tests/suite/scripting/get-rule.typ
@@ -0,0 +1,67 @@
+--- get-rule-basic ---
+// Test basic get rule.
+#context test(text.lang, "en")
+#set text(lang: "de")
+#context test(text.lang, "de")
+#text(lang: "es", context test(text.lang, "es"))
+
+--- get-rule-in-function ---
+// Test whether context is retained in nested function.
+#let translate(..args) = args.named().at(text.lang)
+#set text(lang: "de")
+#context test(translate(de: "Inhalt", en: "Contents"), "Inhalt")
+
+--- get-rule-in-array-callback ---
+// Test whether context is retained in built-in callback.
+#set text(lang: "de")
+#context test(
+ ("en", "de", "fr").sorted(key: v => v != text.lang),
+ ("de", "en", "fr"),
+)
+
+--- get-rule-folding ---
+// Test folding.
+#set rect(stroke: red)
+#context {
+ test(type(rect.stroke), stroke)
+ test(rect.stroke.paint, red)
+}
+#[
+ #set rect(stroke: 4pt)
+ #context test(rect.stroke, 4pt + red)
+]
+#context test(rect.stroke, stroke(red))
+
+--- get-rule-figure-caption-collision ---
+// We have one collision: `figure.caption` could be both the element and a get
+// rule for the `caption` field, which is settable. We always prefer the
+// element. It's unfortunate, but probably nobody writes
+// `set figure(caption: ..)` anyway.
+#test(type(figure.caption), function)
+#context test(type(figure.caption), function)
+
+--- get-rule-assertion-failure ---
+// Error: 10-31 Assertion failed: "en" != "de"
+#context test(text.lang, "de")
+
+--- get-rule-unknown-field ---
+// Error: 15-20 function `text` does not contain field `langs`
+#context text.langs
+
+--- get-rule-inherent-field ---
+// Error: 18-22 function `heading` does not contain field `body`
+#context heading.body
+
+--- get-rule-missing-context-no-context ---
+// Error: 7-11 can only be used when context is known
+// Hint: 7-11 try wrapping this in a `context` expression
+// Hint: 7-11 the `context` expression should wrap everything that depends on this function
+#text.lang
+
+--- get-rule-unknown-field-no-context ---
+// Error: 7-12 function `text` does not contain field `langs`
+#text.langs
+
+--- get-rule-inherent-field-no-context ---
+// Error: 10-14 function `heading` does not contain field `body`
+#heading.body
diff --git a/tests/suite/scripting/if.typ b/tests/suite/scripting/if.typ
new file mode 100644
index 00000000..cc88925f
--- /dev/null
+++ b/tests/suite/scripting/if.typ
@@ -0,0 +1,134 @@
+// Test if-else expressions.
+
+--- if-markup ---
+// Test condition evaluation.
+#if 1 < 2 [
+ One.
+]
+
+#if true == false [
+ {Bad}, but we {dont-care}!
+]
+
+--- if-condition-complex ---
+// Braced condition.
+#if {true} [
+ One.
+]
+
+// Content block in condition.
+#if [] != none [
+ Two.
+]
+
+// Multi-line condition with parens.
+#if (
+ 1 + 1
+ == 1
+) [
+ Nope.
+] else {
+ "Three."
+}
+
+// Multiline.
+#if false [
+ Bad.
+] else {
+ let point = "."
+ "Four" + point
+}
+
+// Content block can be argument or body depending on whitespace.
+#{
+ if content == type[b] [Fi] else [Nope]
+ if content == type [Nope] else [ve.]
+}
+
+#let i = 3
+#if i < 2 [
+ Five.
+] else if i < 4 [
+ Six.
+] else [
+ Seven.
+]
+
+--- if-else-if-else ---
+// Test else if.
+
+#let nth(n) = {
+ str(n)
+ if n == 1 { "st" }
+ else if n == 2 { "nd" }
+ else if n == 3 { "rd" }
+ else { "th" }
+}
+
+#test(nth(1), "1st")
+#test(nth(2), "2nd")
+#test(nth(3), "3rd")
+#test(nth(4), "4th")
+#test(nth(5), "5th")
+
+--- if-expression ---
+// Value of if expressions.
+
+#{
+ let x = 1
+ let y = 2
+ let z
+
+ // Returns if branch.
+ z = if x < y { "ok" }
+ test(z, "ok")
+
+ // Returns else branch.
+ z = if x > y { "bad" } else { "ok" }
+ test(z, "ok")
+
+ // Missing else evaluates to none.
+ z = if x > y { "bad" }
+ test(z, none)
+}
+
+--- if-condition-string-invalid ---
+// Condition must be boolean.
+// If it isn't, neither branch is evaluated.
+// Error: 5-14 expected boolean, found string
+#if "a" + "b" { nope } else { nope }
+
+--- if-condition-invalid-and-wrong-type ---
+// Make sure that we don't complain twice.
+// Error: 5-12 cannot add integer and string
+#if 1 + "2" {}
+
+--- if-incomplete ---
+// Error: 4 expected expression
+#if
+
+// Error: 5 expected expression
+#{if}
+
+// Error: 6 expected block
+#if x
+
+// Error: 2-6 unexpected keyword `else`
+#else {}
+
+// Should output `x`.
+// Error: 4 expected expression
+#if
+x {}
+
+// Should output `something`.
+// Error: 6 expected block
+#if x something
+
+// Should output `A thing.`
+// Error: 19 expected block
+A#if false {} else thing
+
+#if a []else [b]
+#if a [] else [b]
+#if a {} else [b]
diff --git a/tests/suite/scripting/import.typ b/tests/suite/scripting/import.typ
new file mode 100644
index 00000000..820f81d6
--- /dev/null
+++ b/tests/suite/scripting/import.typ
@@ -0,0 +1,334 @@
+// Test function and module imports.
+
+--- import-basic ---
+// Test basic syntax and semantics.
+
+// Test that this will be overwritten.
+#let value = [foo]
+
+// Import multiple things.
+#import "module.typ": fn, value
+#fn[Like and Subscribe!]
+#value
+
+// Should output `bye`.
+// Stop at semicolon.
+#import "module.typ": a, c;bye
+
+--- import-item-markup ---
+// An item import.
+#import "module.typ": item
+#test(item(1, 2), 3)
+
+--- import-item-in-code ---
+// Code mode
+#{
+ import "module.typ": b
+ test(b, 1)
+}
+
+--- import-wildcard-in-markup ---
+// A wildcard import.
+#import "module.typ": *
+
+// It exists now!
+#test(d, 3)
+
+--- import-item-renamed ---
+// A renamed item import.
+#import "module.typ": item as something
+#test(something(1, 2), 3)
+
+--- import-items-renamed-mixed ---
+// Mixing renamed and not renamed items.
+#import "module.typ": fn, b as val, item as other
+#test(val, 1)
+#test(other(1, 2), 3)
+
+--- import-from-function-scope ---
+// Test importing from function scopes.
+
+#import enum: item
+#import assert.with(true): *
+
+#enum(
+ item(1)[First],
+ item(5)[Fifth]
+)
+#eq(10, 10)
+#ne(5, 6)
+
+--- import-from-function-scope-item-renamed ---
+// Test renaming items imported from function scopes.
+#import assert: eq as aseq
+#aseq(10, 10)
+
+--- import-from-file-bare ---
+// A module import without items.
+#import "module.typ"
+#test(module.b, 1)
+#test(module.item(1, 2), 3)
+#test(module.push(2), 3)
+
+--- import-from-file-renamed ---
+// A renamed module import without items.
+#import "module.typ" as other
+#test(other.b, 1)
+#test(other.item(1, 2), 3)
+#test(other.push(2), 3)
+
+--- import-from-file-items-renamed-mixed ---
+// Mixing renamed module and items.
+#import "module.typ" as newname: b as newval, item
+#test(newname.b, 1)
+#test(newval, 1)
+#test(item(1, 2), 3)
+#test(newname.item(1, 2), 3)
+
+--- import-from-function-scope-renamed ---
+// Renamed module import with function scopes.
+#import enum as othernum
+#test(enum, othernum)
+
+--- import-from-function-scope-renamed-twice ---
+// Mixing renamed module import from function with renamed item import.
+#import assert as asrt
+#import asrt: ne as asne
+#asne(1, 2)
+
+--- import-module-item-name-mutating ---
+// Edge case for module access that isn't fixed.
+#import "module.typ"
+
+// Works because the method name isn't categorized as mutating.
+#test((module,).at(0).item(1, 2), 3)
+
+// Doesn't work because of mutating name.
+// Error: 2-11 cannot mutate a temporary value
+#(module,).at(0).push()
+
+--- import-no-whitespace ---
+// Who needs whitespace anyways?
+#import"module.typ":*
+
+--- import-trailing-comma ---
+// Allow the trailing comma.
+#import "module.typ": a, c,
+
+--- import-source-field-access ---
+// Usual importing syntax also works for function scopes
+#let d = (e: enum)
+#import d.e
+#import d.e as renamed
+#import d.e: item
+#item(2)[a]
+
+--- import-item-rename-unnecessary ---
+// Warning: 23-27 unnecessary import rename to same name
+#import enum: item as item
+
+--- import-rename-unnecessary ---
+// Warning: 17-21 unnecessary import rename to same name
+#import enum as enum
+
+--- import-rename-unnecessary-mixed ---
+// Warning: 17-21 unnecessary import rename to same name
+#import enum as enum: item
+
+// Warning: 17-21 unnecessary import rename to same name
+// Warning: 31-35 unnecessary import rename to same name
+#import enum as enum: item as item
+
+--- import-item-rename-unnecessary-but-ok ---
+// No warning on a case that isn't obviously pathological
+#import "module.typ" as module
+
+--- import-from-closure-invalid ---
+// Can't import from closures.
+#let f(x) = x
+// Error: 9-10 cannot import from user-defined functions
+#import f: x
+
+--- import-from-closure-renamed-invalid ---
+// Can't import from closures, despite renaming.
+#let f(x) = x
+// Error: 9-10 cannot import from user-defined functions
+#import f as g
+
+--- import-from-with-closure-invalid ---
+// Can't import from closures, despite modifiers.
+#let f(x) = x
+// Error: 9-18 cannot import from user-defined functions
+#import f.with(5): x
+
+--- import-from-with-closure-literal-invalid ---
+// Error: 9-18 cannot import from user-defined functions
+#import () => {5}: x
+
+--- import-from-int-invalid ---
+// Error: 9-10 expected path, module, function, or type, found integer
+#import 5: something
+
+--- import-from-int-renamed-invalid ---
+// Error: 9-10 expected path, module, function, or type, found integer
+#import 5 as x
+
+--- import-from-string-invalid ---
+// Error: 9-11 failed to load file (is a directory)
+#import "": name
+
+--- import-from-string-renamed-invalid ---
+// Error: 9-11 failed to load file (is a directory)
+#import "" as x
+
+--- import-file-not-found-invalid ---
+// Error: 9-20 file not found (searched at tests/suite/scripting/lib/0.2.1)
+#import "lib/0.2.1"
+
+--- import-file-not-found-renamed-invalid ---
+// Error: 9-20 file not found (searched at tests/suite/scripting/lib/0.2.1)
+#import "lib/0.2.1" as x
+
+--- import-file-not-valid-utf-8 ---
+// Some non-text stuff.
+// Error: 9-35 file is not valid utf-8
+#import "/assets/images/rhino.png"
+
+--- import-item-not-found ---
+// Unresolved import.
+// Error: 23-35 unresolved import
+#import "module.typ": non_existing
+
+--- import-cyclic ---
+// Cyclic import of this very file.
+// Error: 9-23 cyclic import
+#import "./import.typ"
+
+--- import-cyclic-in-other-file ---
+// Cyclic import in other file.
+#import "./modules/cycle1.typ": *
+
+This is never reached.
+
+--- import-renamed-old-name ---
+// Renaming does not import the old name (without items).
+#import "./modules/chap1.typ" as something
+#test(something.name, "Klaus")
+// Error: 7-12 unknown variable: chap1
+#test(chap1.name, "Klaus")
+
+--- import-items-renamed-old-name ---
+// Renaming does not import the old name (with items).
+#import "./modules/chap1.typ" as something: name as other
+#test(other, "Klaus")
+#test(something.name, "Klaus")
+// Error: 7-12 unknown variable: chap1
+#test(chap1.b, "Klaus")
+
+--- import-incomplete ---
+// Error: 8 expected expression
+#import
+
+--- import-item-string-invalid ---
+// Error: 26-29 unexpected string
+#import "module.typ": a, "b", c
+
+--- import-bad-token ---
+// Error: 23-24 unexpected equals sign
+#import "module.typ": =
+
+--- import-duplicate-comma ---
+// An additional trailing comma.
+// Error: 31-32 unexpected comma
+#import "module.typ": a, b, c,,
+
+--- import-no-colon ---
+// Error: 2:2 expected semicolon or line break
+#import "module.typ
+"stuff
+
+--- import-bad-token-star ---
+// A star in the list.
+// Error: 26-27 unexpected star
+#import "module.typ": a, *, b
+
+--- import-item-after-star ---
+// An item after a star.
+// Error: 24 expected semicolon or line break
+#import "module.typ": *, a
+
+--- import-bad-colon-in-items ---
+// Error: 14-15 unexpected colon
+// Error: 16-17 unexpected integer
+#import "": a: 1
+
+--- import-missing-comma ---
+// Error: 14 expected comma
+#import "": a b
+
+--- import-from-package-bare ---
+// Test import without items.
+#import "@test/adder:0.1.0"
+#test(adder.add(2, 8), 10)
+
+--- import-from-package-items ---
+// Test import with items.
+#import "@test/adder:0.1.0": add
+#test(add(2, 8), 10)
+
+--- import-from-package-required-compiler-version ---
+// Test too high required compiler version.
+// Error: 9-29 package requires typst 1.0.0 or newer (current version is VERSION)
+#import "@test/future:0.1.0": future
+
+--- import-from-package-namespace-invalid-1 ---
+// Error: 9-13 `@` is not a valid package namespace
+#import "@@": *
+
+--- import-from-package-name-missing-1 ---
+// Error: 9-16 package specification is missing name
+#import "@heya": *
+
+--- import-from-package-namespace-invalid-2 ---
+// Error: 9-15 `123` is not a valid package namespace
+#import "@123": *
+
+--- import-from-package-name-missing-2 ---
+// Error: 9-17 package specification is missing name
+#import "@test/": *
+
+--- import-from-package-version-missing-1 ---
+// Error: 9-22 package specification is missing version
+#import "@test/mypkg": *
+
+--- import-from-package-name-invalid ---
+// Error: 9-20 `$$$` is not a valid package name
+#import "@test/$$$": *
+
+--- import-from-package-version-missing-2 ---
+// Error: 9-23 package specification is missing version
+#import "@test/mypkg:": *
+
+--- import-from-package-version-missing-minor ---
+// Error: 9-24 version number is missing minor version
+#import "@test/mypkg:0": *
+
+--- import-from-package-version-major-invalid-1 ---
+// Error: 9-29 `latest` is not a valid major version
+#import "@test/mypkg:latest": *
+
+--- import-from-package-version-major-invalid-2 ---
+// Error: 9-29 `-3` is not a valid major version
+#import "@test/mypkg:-3.0.0": *
+
+--- import-from-package-version-missing-patch-1 ---
+// Error: 9-26 version number is missing patch version
+#import "@test/mypkg:0.3": *
+
+--- import-from-package-version-missing-patch-2 ---
+// Error: 9-27 version number is missing patch version
+#import "@test/mypkg:0.3.": *
+
+--- import-from-file-package-lookalike ---
+// Error: 9-28 file not found (searched at tests/suite/scripting/#test/mypkg:1.0.0)
+#import "#test/mypkg:1.0.0": *
diff --git a/tests/suite/scripting/include.typ b/tests/suite/scripting/include.typ
new file mode 100644
index 00000000..e4da6d19
--- /dev/null
+++ b/tests/suite/scripting/include.typ
@@ -0,0 +1,32 @@
+// Test module includes.
+
+--- include-file ---
+#set page(width: 200pt)
+
+= Document
+
+// Include a file
+#include "modules/chap1.typ"
+
+// Expression as a file name.
+#let chap2 = include "modu" + "les/chap" + "2.typ"
+
+-- _Intermission_ --
+#chap2
+
+--- include-file-not-found ---
+#{
+ // Error: 19-38 file not found (searched at tests/suite/scripting/modules/chap3.typ)
+ let x = include "modules/chap3.typ"
+}
+
+--- include-no-bindings ---
+#include "modules/chap1.typ"
+
+// The variables of the file should not appear in this scope.
+// Error: 2-6 unknown variable: name
+#name
+
+--- include-semicolon-or-linebreak ---
+// Error: 18 expected semicolon or line break
+#include "hi.typ" Hi
diff --git a/tests/suite/scripting/let.typ b/tests/suite/scripting/let.typ
new file mode 100644
index 00000000..2604c4ea
--- /dev/null
+++ b/tests/suite/scripting/let.typ
@@ -0,0 +1,143 @@
+// Test let bindings.
+
+--- let-basic ---
+// Automatically initialized with none.
+#let x
+#test(x, none)
+
+// Manually initialized with one.
+#let z = 1
+#test(z, 1)
+
+// Syntax sugar for function definitions.
+#let fill = conifer
+#let f(body) = rect(width: 2cm, fill: fill, inset: 5pt, body)
+#f[Hi!]
+
+--- let-termination ---
+// Termination.
+
+// Terminated by line break.
+#let v1 = 1
+One
+
+// Terminated by semicolon.
+#let v2 = 2; Two
+
+// Terminated by semicolon and line break.
+#let v3 = 3;
+Three
+
+#test(v1, 1)
+#test(v2, 2)
+#test(v3, 3)
+
+--- let-valid-idents ---
+// Test what constitutes a valid Typst identifier.
+#let name = 1
+#test(name, 1)
+#let name_ = 1
+#test(name_, 1)
+#let name-2 = 1
+#test(name-2, 1)
+#let name_2 = 1
+#test(name_2, 1)
+#let __name = 1
+#test(__name, 1)
+#let ůñıćóðė = 1
+#test(ůñıćóðė, 1)
+
+--- let-binding-keyword-in-markup ---
+// Error: 6-8 expected pattern, found keyword `as`
+// Hint: 6-8 keyword `as` is not allowed as an identifier; try `as_` instead
+#let as = 1 + 2
+
+--- let-binding-keyword-in-code ---
+#{
+ // Error: 7-9 expected pattern, found keyword `as`
+ // Hint: 7-9 keyword `as` is not allowed as an identifier; try `as_` instead
+ let as = 10
+}
+
+--- let-ident-parenthesized ---
+// Test parenthesised assignments.
+#let (a) = (1, 2)
+
+--- let-incomplete ---
+// Error: 5 expected pattern
+#let
+
+// Error: 6 expected pattern
+#{let}
+
+// Error: 6-9 expected pattern, found string
+#let "v"
+
+// Error: 7 expected semicolon or line break
+#let v 1
+
+// Error: 9 expected expression
+#let v =
+
+// Error: 6-9 expected pattern, found string
+#let "v" = 1
+
+// Terminated because expression ends.
+// Error: 12 expected semicolon or line break
+#let v4 = 4 Four
+
+// Terminated by semicolon even though we are in a paren group.
+// Error: 18 expected expression
+// Error: 11-12 unclosed delimiter
+#let v5 = (1, 2 + ; Five
+
+// Error: 9-13 expected pattern, found boolean
+#let (..true) = false
+
+--- underscore-invalid ---
+#let _ = 4
+
+#for _ in range(2) []
+
+// Error: 2-3 unexpected underscore
+#_
+
+// Error: 8-9 expected expression, found underscore
+#lorem(_)
+
+// Error: 3-4 expected expression, found underscore
+#(_,)
+
+// Error: 3-4 expected expression, found underscore
+#{_}
+
+// Error: 8-9 expected expression, found underscore
+#{ 1 + _ }
+
+--- let-function-incomplete ---
+// Error: 13 expected equals sign
+#let func(x)
+
+// Error: 15 expected expression
+#let func(x) =
+
+--- let-function-parenthesized ---
+// This is not yet parsed in the ideal way.
+// Error: 12 expected equals sign
+#let (func)(x)
+
+--- let-function-parenthesized-with-init ---
+// These errors aren't great.
+// Error: 12 expected equals sign
+// Error: 15-15 expected semicolon or line break
+#let (func)(x) = 3
+
+--- let-with-no-init-group ---
+// This was unintentionally allowed ...
+// Error: 9 expected equals sign
+#let (a)
+
+--- let-with-no-init-destructuring ---
+// ... where this wasn't.
+// Error: 12 expected equals sign
+#let (a, b)
diff --git a/tests/suite/scripting/loop.typ b/tests/suite/scripting/loop.typ
new file mode 100644
index 00000000..689c1c93
--- /dev/null
+++ b/tests/suite/scripting/loop.typ
@@ -0,0 +1,142 @@
+// Test break and continue in loops.
+
+--- loop-break-basic ---
+// Test break.
+
+#let var = 0
+#let error = false
+
+#for i in range(10) {
+ var += i
+ if i > 5 {
+ break
+ error = true
+ }
+}
+
+#test(var, 21)
+#test(error, false)
+
+--- loop-break-join-basic ---
+// Test joining with break.
+
+#let i = 0
+#let x = while true {
+ i += 1
+ str(i)
+ if i >= 5 {
+ "."
+ break
+ }
+}
+
+#test(x, "12345.")
+
+--- loop-continue-basic ---
+// Test continue.
+
+#let i = 0
+#let x = 0
+
+#while x < 8 {
+ i += 1
+ if calc.rem(i, 3) == 0 {
+ continue
+ }
+ x += i
+}
+
+// If continue did not work, this would equal 10.
+#test(x, 12)
+
+--- loop-continue-join ---
+// Test joining with continue.
+
+#let x = for i in range(5) {
+ "a"
+ if calc.rem(i, 3) == 0 {
+ "_"
+ continue
+ }
+ str(i)
+}
+
+#test(x, "a_a1a2a_a4")
+
+--- loop-break-outside-of-loop ---
+// Test break outside of loop.
+#let f() = {
+ // Error: 3-8 cannot break outside of loop
+ break
+}
+
+#for i in range(1) {
+ f()
+}
+
+--- loop-break-join-in-last-arg ---
+// Test break in function call.
+#let identity(x) = x
+#let out = for i in range(5) {
+ "A"
+ identity({
+ "B"
+ break
+ })
+ "C"
+}
+
+#test(out, "AB")
+
+--- loop-continue-outside-of-loop-in-block ---
+// Test continue outside of loop.
+
+// Error: 12-20 cannot continue outside of loop
+#let x = { continue }
+
+--- loop-continue-outside-of-loop-in-markup ---
+// Error: 2-10 cannot continue outside of loop
+#continue
+
+--- loop-break-join-in-nested-blocks ---
+// Should output `Hello World 🌎`.
+#for _ in range(10) {
+ [Hello ]
+ [World #{
+ [🌎]
+ break
+ }]
+}
+
+--- loop-break-join-set-and-show ---
+// Should output `Some` in red, `Some` in blue and `Last` in green.
+// Everything should be in smallcaps.
+#for color in (red, blue, green, yellow) [
+ #set text(font: "Roboto")
+ #show: it => text(fill: color, it)
+ #smallcaps(if color != green [
+ Some
+ ] else [
+ Last
+ #break
+ ])
+]
+
+--- loop-break-join-in-set-rule-args ---
+// Test break in set rule.
+// Should output `Hi` in blue.
+#for i in range(10) {
+ [Hello]
+ set text(blue, ..break)
+ [Not happening]
+}
+
+--- loop-break-join-in-first-arg ---
+// Test second block during break flow.
+
+#for i in range(10) {
+ table(
+ { [A]; break },
+ for _ in range(3) [B]
+ )
+}
diff --git a/tests/suite/scripting/methods.typ b/tests/suite/scripting/methods.typ
new file mode 100644
index 00000000..5deea2cf
--- /dev/null
+++ b/tests/suite/scripting/methods.typ
@@ -0,0 +1,51 @@
+// Test method calls.
+
+--- method-whitespace ---
+// Test whitespace around dot.
+#test( "Hi there" . split() , ("Hi", "there"))
+
+--- method-mutating ---
+// Test mutating indexed value.
+#{
+ let matrix = (((1,), (2,)), ((3,), (4,)))
+ matrix.at(1).at(0).push(5)
+ test(matrix, (((1,), (2,)), ((3, 5), (4,))))
+}
+
+--- method-multiline ---
+// Test multiline chain in code block.
+#{
+ let rewritten = "Hello. This is a sentence. And one more."
+ .split(".")
+ .map(s => s.trim())
+ .filter(s => s != "")
+ .map(s => s + "!")
+ .join("\n ")
+
+ test(rewritten, "Hello!\n This is a sentence!\n And one more!")
+}
+
+--- method-unknown ---
+// Error: 2:10-2:13 type array has no method `fun`
+#let numbers = ()
+#numbers.fun()
+
+--- method-unknown-but-field-exists ---
+// Error: 2:4-2:10 type content has no method `stroke`
+// Hint: 2:4-2:10 did you mean to access the field `stroke`?
+#let l = line(stroke: red)
+#l.stroke()
+
+--- method-mutate-on-temporary ---
+// Error: 2:2-2:43 cannot mutate a temporary value
+#let numbers = (1, 2, 3)
+#numbers.map(v => v / 2).sorted().map(str).remove(4)
+
+--- assign-to-method-invalid ---
+// Error: 2:3-2:19 cannot mutate a temporary value
+#let numbers = (1, 2, 3)
+#(numbers.sorted() = 1)
+
+--- method-mutate-on-std-constant ---
+// Error: 2-5 cannot mutate a constant: box
+#box.push(1)
diff --git a/tests/suite/scripting/module.typ b/tests/suite/scripting/module.typ
new file mode 100644
index 00000000..8a67d225
--- /dev/null
+++ b/tests/suite/scripting/module.typ
@@ -0,0 +1,13 @@
+// SKIP
+// A file to import in import / include tests.
+
+#let a
+#let b = 1
+#let c = 2
+#let d = 3
+#let value = [hi]
+#let item(a, b) = a + b
+#let push(a) = a + 1
+#let fn = rect.with(fill: conifer, inset: 5pt)
+
+Some _includable_ text.
diff --git a/tests/suite/scripting/modules/chap1.typ b/tests/suite/scripting/modules/chap1.typ
new file mode 100644
index 00000000..13d0acf8
--- /dev/null
+++ b/tests/suite/scripting/modules/chap1.typ
@@ -0,0 +1,8 @@
+// SKIP
+#let name = "Klaus"
+
+== Chapter 1
+#name stood in a field of wheat. There was nothing of particular interest about
+the field #name just casually surveyed for any paths on which the corn would not
+totally ruin his semi-new outdorsy jacket but then again, most of us spend
+considerable time in non-descript environments.
diff --git a/tests/suite/scripting/modules/chap2.typ b/tests/suite/scripting/modules/chap2.typ
new file mode 100644
index 00000000..9c9d12d7
--- /dev/null
+++ b/tests/suite/scripting/modules/chap2.typ
@@ -0,0 +1,10 @@
+// SKIP
+#let name = "Klaus"
+
+== Chapter 2
+Their motivations, however, were pretty descript, so to speak. #name had not yet
+conceptualized their consequences, but that should change pretty quickly. #name
+approached the center of the field and picked up a 4-foot long disk made from
+what could only be cow manure. The hair on the back of #name' neck bristled as
+he stared at the unusual sight. After studying the object for a while, he
+promptly popped the question, "How much?"
diff --git a/tests/suite/scripting/modules/cycle1.typ b/tests/suite/scripting/modules/cycle1.typ
new file mode 100644
index 00000000..0f924ac7
--- /dev/null
+++ b/tests/suite/scripting/modules/cycle1.typ
@@ -0,0 +1,5 @@
+// SKIP
+#import "cycle2.typ": *
+#let inaccessible = "wow"
+
+This is the first element of an import cycle.
diff --git a/tests/suite/scripting/modules/cycle2.typ b/tests/suite/scripting/modules/cycle2.typ
new file mode 100644
index 00000000..69eb2033
--- /dev/null
+++ b/tests/suite/scripting/modules/cycle2.typ
@@ -0,0 +1,5 @@
+// SKIP
+#import "cycle1.typ": *
+#let val = "much cycle"
+
+This is the second element of an import cycle.
diff --git a/tests/suite/scripting/ops.typ b/tests/suite/scripting/ops.typ
new file mode 100644
index 00000000..0f13d212
--- /dev/null
+++ b/tests/suite/scripting/ops.typ
@@ -0,0 +1,465 @@
+// Test binary expressions.
+
+--- ops-add-content ---
+// Test adding content.
+#([*Hello* ] + [world!])
+
+--- ops-unary-basic ---
+// Test math operators.
+
+// Test plus and minus.
+#for v in (1, 3.14, 12pt, 45deg, 90%, 13% + 10pt, 6.3fr) {
+ // Test plus.
+ test(+v, v)
+
+ // Test minus.
+ test(-v, -1 * v)
+ test(--v, v)
+
+ // Test combination.
+ test(-++ --v, -v)
+}
+
+#test(-(4 + 2), 6-12)
+
+// Addition.
+#test(2 + 4, 6)
+#test("a" + "b", "ab")
+#test("a" + if false { "b" }, "a")
+#test("a" + if true { "b" }, "ab")
+#test(13 * "a" + "bbbbbb", "aaaaaaaaaaaaabbbbbb")
+#test((1, 2) + (3, 4), (1, 2, 3, 4))
+#test((a: 1) + (b: 2, c: 3), (a: 1, b: 2, c: 3))
+
+--- ops-add-too-large ---
+// Error: 3-26 value is too large
+#(9223372036854775807 + 1)
+
+--- ops-binary-basic ---
+// Subtraction.
+#test(1-4, 3*-1)
+#test(4cm - 2cm, 2cm)
+#test(1e+2-1e-2, 99.99)
+
+// Multiplication.
+#test(2 * 4, 8)
+
+// Division.
+#test(12pt/.4, 30pt)
+#test(7 / 2, 3.5)
+
+// Combination.
+#test(3-4 * 5 < -10, true)
+#test({ let x; x = 1 + 4*5 >= 21 and { x = "a"; x + "b" == "ab" }; x }, true)
+
+// With block.
+#test(if true {
+ 1
+} + 2, 3)
+
+// Mathematical identities.
+#let nums = (
+ 1, 3.14,
+ 12pt, 3em, 12pt + 3em,
+ 45deg,
+ 90%,
+ 13% + 10pt, 5% + 1em + 3pt,
+ 2.3fr,
+)
+
+#for v in nums {
+ // Test plus and minus.
+ test(v + v - v, v)
+ test(v - v - v, -v)
+
+ // Test plus/minus and multiplication.
+ test(v - v, 0 * v)
+ test(v + v, 2 * v)
+
+ // Integer addition does not give a float.
+ if type(v) != int {
+ test(v + v, 2.0 * v)
+ }
+
+ if type(v) != relative and ("pt" not in repr(v) or "em" not in repr(v)) {
+ test(v / v, 1.0)
+ }
+}
+
+// Make sure length, ratio and relative length
+// - can all be added to / subtracted from each other,
+// - multiplied with integers and floats,
+// - divided by integers and floats.
+#let dims = (10pt, 1em, 10pt + 1em, 30%, 50% + 3cm, 40% + 2em + 1cm)
+#for a in dims {
+ for b in dims {
+ test(type(a + b), type(a - b))
+ }
+
+ for b in (7, 3.14) {
+ test(type(a * b), type(a))
+ test(type(b * a), type(a))
+ test(type(a / b), type(a))
+ }
+}
+
+// Test division of different numeric types with zero components.
+#for a in (0pt, 0em, 0%) {
+ for b in (10pt, 10em, 10%) {
+ test((2 * b) / b, 2)
+ test((a + b * 2) / b, 2)
+ test(b / (b * 2 + a), 0.5)
+ }
+}
+
+--- ops-multiply-inf-with-length ---
+// Test that multiplying infinite numbers by certain units does not crash.
+#(float("inf") * 1pt)
+#(float("inf") * 1em)
+#(float("inf") * (1pt + 1em))
+
+--- ops-attempt-nan-length ---
+// Test that trying to produce a NaN scalar (such as in lengths) does not crash.
+#let infpt = float("inf") * 1pt
+#test(infpt - infpt, 0pt)
+#test(infpt + (-infpt), 0pt)
+// TODO: this result is surprising
+#test(infpt / float("inf"), 0pt)
+
+--- ops-unary-bool ---
+// Test boolean operators.
+
+// Test not.
+#test(not true, false)
+#test(not false, true)
+
+// And.
+#test(false and false, false)
+#test(false and true, false)
+#test(true and false, false)
+#test(true and true, true)
+
+// Or.
+#test(false or false, false)
+#test(false or true, true)
+#test(true or false, true)
+#test(true or true, true)
+
+// Short-circuiting.
+#test(false and dont-care, false)
+#test(true or dont-care, true)
+
+--- ops-equality ---
+// Test equality operators.
+
+// Most things compare by value.
+#test(1 == "hi", false)
+#test(1 == 1.0, true)
+#test(30% == 30% + 0cm, true)
+#test(1in == 0% + 72pt, true)
+#test(30% == 30% + 1cm, false)
+#test("ab" == "a" + "b", true)
+#test(() == (1,), false)
+#test((1, 2, 3) == (1, 2.0) + (3,), true)
+#test((:) == (a: 1), false)
+#test((a: 2 - 1.0, b: 2) == (b: 2, a: 1), true)
+#test("a" != "a", false)
+
+// Functions compare by identity.
+#test(test == test, true)
+#test((() => {}) == (() => {}), false)
+
+// Content compares field by field.
+#let t = [a]
+#test(t == t, true)
+#test([] == [], true)
+#test([a] == [a], true)
+#test(grid[a] == grid[a], true)
+#test(grid[a] == grid[b], false)
+
+--- ops-compare ---
+// Test comparison operators.
+
+#test(13 * 3 < 14 * 4, true)
+#test(5 < 10, true)
+#test(5 > 5, false)
+#test(5 <= 5, true)
+#test(5 <= 4, false)
+#test(45deg < 1rad, true)
+#test(10% < 20%, true)
+#test(50% < 40% + 0pt, false)
+#test(40% + 0pt < 50% + 0pt, true)
+#test(1em < 2em, true)
+#test((0, 1, 2, 4) < (0, 1, 2, 5), true)
+#test((0, 1, 2, 4) < (0, 1, 2, 3), false)
+#test((0, 1, 2, 3.3) > (0, 1, 2, 4), false)
+#test((0, 1, 2) < (0, 1, 2, 3), true)
+#test((0, 1, "b") > (0, 1, "a", 3), true)
+#test((0, 1.1, 3) >= (0, 1.1, 3), true)
+#test((0, 1, datetime(day: 1, month: 12, year: 2023)) <= (0, 1, datetime(day: 1, month: 12, year: 2023), 3), true)
+#test(("a", 23, 40, "b") > ("a", 23, 40), true)
+#test(() <= (), true)
+#test(() >= (), true)
+#test(() <= (1,), true)
+#test((1,) <= (), false)
+
+--- ops-in ---
+// Test `in` operator.
+#test("hi" in "worship", true)
+#test("hi" in ("we", "hi", "bye"), true)
+#test("Hey" in "abHeyCd", true)
+#test("Hey" in "abheyCd", false)
+#test(5 in range(10), true)
+#test(12 in range(10), false)
+#test("" in (), false)
+#test("key" in (key: "value"), true)
+#test("value" in (key: "value"), false)
+#test("Hey" not in "abheyCd", true)
+#test("a" not
+/* fun comment? */ in "abc", false)
+
+--- ops-not-trailing ---
+// Error: 10 expected keyword `in`
+#("a" not)
+
+--- func-with ---
+// Test `with` method.
+
+// Apply positional arguments.
+#let add(x, y) = x + y
+#test(add.with(2)(3), 5)
+#test(add.with(2, 3)(), 5)
+#test(add.with(2).with(3)(), 5)
+#test((add.with(2))(4), 6)
+#test((add.with(2).with(3))(), 5)
+
+// Make sure that named arguments are overridable.
+#let inc(x, y: 1) = x + y
+#test(inc(1), 2)
+
+#let inc2 = inc.with(y: 2)
+#test(inc2(2), 4)
+#test(inc2(2, y: 4), 6)
+
+// Apply arguments to an argument sink.
+#let times(..sink) = {
+ let res = sink.pos().product()
+ if sink.named().at("negate", default: false) { res *= -1 }
+ res
+}
+#test((times.with(2, negate: true).with(5))(), -10)
+#test((times.with(2).with(5).with(negate: true))(), -10)
+#test((times.with(2).with(5, negate: true))(), -10)
+#test((times.with(2).with(negate: true))(5), -10)
+
+--- ops-precedence-basic ---
+// Multiplication binds stronger than addition.
+#test(1+2*-3, -5)
+
+// Subtraction binds stronger than comparison.
+#test(3 == 5 - 2, true)
+
+// Boolean operations bind stronger than '=='.
+#test("a" == "a" and 2 < 3, true)
+#test(not "b" == "b", false)
+
+--- ops-precedence-boolean-ops ---
+// Assignment binds stronger than boolean operations.
+// Error: 2:3-2:8 cannot mutate a temporary value
+#let x = false
+#(not x = "a")
+
+--- ops-precedence-unary ---
+// Precedence doesn't matter for chained unary operators.
+// Error: 3-12 cannot apply '-' to boolean
+#(-not true)
+
+--- ops-precedence-not-in ---
+// Not in handles precedence.
+#test(-1 not in (1, 2, 3), true)
+
+--- ops-precedence-parentheses ---
+// Parentheses override precedence.
+#test((1), 1)
+#test((1+2)*-3, -9)
+
+// Error: 8-9 unclosed delimiter
+#test({(1 + 1}, 2)
+
+--- ops-associativity-left ---
+// Math operators are left-associative.
+#test(10 / 2 / 2 == (10 / 2) / 2, true)
+#test(10 / 2 / 2 == 10 / (2 / 2), false)
+#test(1 / 2 * 3, 1.5)
+
+--- ops-associativity-right ---
+// Assignment is right-associative.
+#{
+ let x = 1
+ let y = 2
+ x = y = "ok"
+ test(x, none)
+ test(y, "ok")
+}
+
+--- ops-unary-minus-missing-expr ---
+// Error: 4 expected expression
+#(-)
+
+--- ops-add-missing-rhs ---
+// Error: 10 expected expression
+#test({1+}, 1)
+
+--- ops-mul-missing-rhs ---
+// Error: 10 expected expression
+#test({2*}, 2)
+
+--- ops-unary-plus-on-content ---
+// Error: 3-13 cannot apply unary '+' to content
+#(+([] + []))
+
+--- ops-unary-plus-on-string ---
+// Error: 3-6 cannot apply '-' to string
+#(-"")
+
+--- ops-not-on-array ---
+// Error: 3-9 cannot apply 'not' to array
+#(not ())
+
+--- ops-compare-relative-length-and-ratio ---
+// Error: 3-19 cannot compare relative length and ratio
+#(30% + 1pt <= 40%)
+
+--- ops-compare-em-with-abs ---
+// Error: 3-14 cannot compare 1em with 10pt
+#(1em <= 10pt)
+
+--- ops-compare-normal-float-with-nan ---
+// Error: 3-22 cannot compare 2.2 with NaN
+#(2.2 <= float("nan"))
+
+--- ops-compare-int-and-str ---
+// Error: 3-26 cannot compare integer and string
+#((0, 1, 3) > (0, 1, "a"))
+
+--- ops-compare-array-nested-failure ---
+// Error: 3-42 cannot compare 3.5 with NaN
+#((0, "a", 3.5) <= (0, "a", float("nan")))
+
+--- ops-divide-by-zero-float ---
+// Error: 3-12 cannot divide by zero
+#(1.2 / 0.0)
+
+--- ops-divide-by-zero-int ---
+// Error: 3-8 cannot divide by zero
+#(1 / 0)
+
+--- ops-divide-by-zero-angle ---
+// Error: 3-15 cannot divide by zero
+#(15deg / 0deg)
+
+--- ops-binary-arithmetic-error-message ---
+// Special messages for +, -, * and /.
+// Error: 3-10 cannot add integer and string
+#(1 + "2", 40% - 1)
+
+--- add-assign-int-and-str ---
+// Error: 15-23 cannot add integer and string
+#{ let x = 1; x += "2" }
+
+--- ops-divide-ratio-by-length ---
+// Error: 4-13 cannot divide ratio by length
+#( 10% / 5pt )
+
+--- ops-divide-em-by-abs ---
+// Error: 3-12 cannot divide these two lengths
+#(1em / 5pt)
+
+--- ops-divide-relative-length-by-ratio ---
+// Error: 3-19 cannot divide relative length by ratio
+#((10% + 1pt) / 5%)
+
+--- ops-divide-relative-lengths ---
+// Error: 3-28 cannot divide these two relative lengths
+#((10% + 1pt) / (20% + 1pt))
+
+--- ops-subtract-int-from-ratio ---
+// Error: 13-20 cannot subtract integer from ratio
+#((1234567, 40% - 1))
+
+--- ops-multiply-int-with-bool ---
+// Error: 3-11 cannot multiply integer with boolean
+#(2 * true)
+
+--- ops-divide-int-by-length ---
+// Error: 3-11 cannot divide integer by length
+#(3 / 12pt)
+
+--- multiply-negative-int-with-str ---
+// Error: 3-10 number must be at least zero
+#(-1 * "")
+
+--- ops-assign ---
+// Test assignment operators.
+
+#let x = 0
+#(x = 10) #test(x, 10)
+#(x -= 5) #test(x, 5)
+#(x += 1) #test(x, 6)
+#(x *= x) #test(x, 36)
+#(x /= 2.0) #test(x, 18.0)
+#(x = "some") #test(x, "some")
+#(x += "thing") #test(x, "something")
+
+--- ops-assign-unknown-var-lhs ---
+#{
+ // Error: 3-6 unknown variable: a-1
+ // Hint: 3-6 if you meant to use subtraction, try adding spaces around the minus sign
+ a-1 = 2
+}
+
+--- ops-assign-unknown-var-rhs ---
+#{
+ let a = 2
+ a = 1-a
+ a = a -1
+
+ // Error: 7-10 unknown variable: a-1
+ // Hint: 7-10 if you meant to use subtraction, try adding spaces around the minus sign
+ a = a-1
+}
+
+--- ops-assign-unknown-parenthesized-variable ---
+// Error: 4-5 unknown variable: x
+#((x) = "")
+
+--- ops-assign-destructuring-unknown-variable ---
+// Error: 4-5 unknown variable: x
+#((x,) = (1,))
+
+--- ops-assign-to-temporary ---
+// Error: 3-8 cannot mutate a temporary value
+#(1 + 2 += 3)
+
+--- ops-assign-to-invalid-unary-op ---
+// Error: 2:3-2:8 cannot apply 'not' to string
+#let x = "Hey"
+#(not x = "a")
+
+--- ops-assign-to-invalid-binary-op ---
+// Error: 7-8 unknown variable: x
+#(1 + x += 3)
+
+--- ops-assign-unknown-variable ---
+// Error: 3-4 unknown variable: z
+#(z = 1)
+
+--- ops-assign-to-std-constant ---
+// Error: 3-7 cannot mutate a constant: rect
+#(rect = "hi")
+
+--- ops-assign-to-shadowed-std-constant ---
+// Works if we define rect beforehand
+// (since then it doesn't resolve to the standard library version anymore).
+#let rect = ""
+#(rect = "hi")
diff --git a/tests/suite/scripting/params.typ b/tests/suite/scripting/params.typ
new file mode 100644
index 00000000..688124f2
--- /dev/null
+++ b/tests/suite/scripting/params.typ
@@ -0,0 +1,69 @@
+--- param-underscore-missing-argument ---
+// Error: 17-20 missing argument: pattern parameter
+#let f(a: 10) = a() + 1
+#f(a: _ => 5)
+
+--- params-sink-named ---
+// ... but this was.
+#let f(..x) = {}
+#f(arg: 1)
+
+--- params-sink-unnamed ---
+// unnamed spread
+#let f(.., a) = a
+#test(f(1, 2, 3), 3)
+
+// This wasn't allowed before the bug fix ...
+#let f(..) = 2
+#test(f(arg: 1), 2)
+
+--- params-sink-bool-invalid ---
+// Error: 10-14 expected pattern, found boolean
+#let f(..true) = none
+
+--- params-sink-multiple-invalid ---
+// Error: 13-16 only one argument sink is allowed
+#let f(..a, ..b) = none
+
+--- params-sink-at-start ---
+// Spread at beginning.
+#{
+ let f(..a, b) = (a, b)
+ test(repr(f(1)), "((), 1)")
+ test(repr(f(1, 2, 3)), "((1, 2), 3)")
+ test(repr(f(1, 2, 3, 4, 5)), "((1, 2, 3, 4), 5)")
+}
+
+--- params-sink-in-middle ---
+// Spread in the middle.
+#{
+ let f(a, ..b, c) = (a, b, c)
+ test(repr(f(1, 2)), "(1, (), 2)")
+ test(repr(f(1, 2, 3, 4, 5)), "(1, (2, 3, 4), 5)")
+}
+
+--- params-sink-unnamed-empty ---
+// Unnamed sink should just ignore any extra arguments.
+#{
+ let f(a, b: 5, ..) = (a, b)
+ test(f(4), (4, 5))
+ test(f(10, b: 11), (10, 11))
+ test(f(13, 20, b: 12), (13, 12))
+ test(f(15, b: 16, c: 13), (15, 16))
+}
+
+--- params-sink-missing-arguments ---
+#{
+ let f(..a, b, c, d) = none
+
+ // Error: 3-10 missing argument: d
+ f(1, 2)
+}
+
+--- issue-1029-parameter-destructuring ---
+// Test that underscore works in parameter patterns.
+#test((1, 2, 3).zip((1, 2, 3)).map(((_, x)) => x), (1, 2, 3))
+
+--- issue-1351-parameter-dictionary ---
+// Error: 17-22 expected pattern, found string
+#let foo((test: "bar")) = {}
diff --git a/tests/suite/scripting/recursion.typ b/tests/suite/scripting/recursion.typ
new file mode 100644
index 00000000..43fe848e
--- /dev/null
+++ b/tests/suite/scripting/recursion.typ
@@ -0,0 +1,55 @@
+// Test recursive function calls.
+
+--- recursion-named ---
+// Test with named function.
+#let fib(n) = {
+ if n <= 2 {
+ 1
+ } else {
+ fib(n - 1) + fib(n - 2)
+ }
+}
+
+#test(fib(10), 55)
+
+--- recursion-unnamed-invalid ---
+// Test with unnamed function.
+// Error: 17-18 unknown variable: f
+#let f = (n) => f(n - 1)
+#f(10)
+
+--- recursion-named-returns-itself ---
+// Test capturing with named function.
+#let f = 10
+#let f() = f
+#test(type(f()), function)
+
+--- recursion-unnamed-does-not-return-itself ---
+// Test capturing with unnamed function.
+#let f = 10
+#let f = () => f
+#test(type(f()), int)
+
+--- recursion-shadowing ---
+// Test redefinition.
+#let f(x) = "hello"
+#let f(x) = if x != none { f(none) } else { "world" }
+#test(f(1), "world")
+
+--- recursion-maximum-depth ---
+// Error: 15-21 maximum function call depth exceeded
+#let rec(n) = rec(n) + 1
+#rec(1)
+
+--- recursion-via-include-in-layout ---
+// Test cyclic imports during layout.
+// Error: 2-38 maximum show rule depth exceeded
+// Hint: 2-38 check whether the show rule matches its own output
+#layout(_ => include "recursion.typ")
+
+--- recursion-show-math ---
+// Test recursive show rules.
+// Error: 22-25 maximum show rule depth exceeded
+// Hint: 22-25 check whether the show rule matches its own output
+#show math.equation: $x$
+$ x $
diff --git a/tests/suite/scripting/return.typ b/tests/suite/scripting/return.typ
new file mode 100644
index 00000000..63e1c0b9
--- /dev/null
+++ b/tests/suite/scripting/return.typ
@@ -0,0 +1,87 @@
+// Test return out of functions.
+
+--- return-with-value ---
+// Test return with value.
+#let f(x) = {
+ return x + 1
+}
+
+#test(f(1), 2)
+
+--- return-join ---
+// Test return with joining.
+
+#let f(x) = {
+ "a"
+ if x == 0 {
+ return "b"
+ } else if x == 1 {
+ "c"
+ } else {
+ "d"
+ return
+ "e"
+ }
+}
+
+#test(f(0), "b")
+#test(f(1), "ac")
+#test(f(2), "ad")
+
+--- return-in-nested-content-block ---
+// Test return with joining and content.
+
+#let f(text, caption: none) = {
+ text
+ if caption == none [\.#return]
+ [, ]
+ emph(caption)
+ [\.]
+}
+
+#f(caption: [with caption])[My figure]
+
+#f[My other figure]
+
+--- return-outside-of-function ---
+// Test return outside of function.
+
+#for x in range(5) {
+ // Error: 3-9 cannot return outside of function
+ return
+}
+
+--- return-in-first-arg ---
+// Test that the expression is evaluated to the end.
+#let sum(..args) = {
+ let s = 0
+ for v in args.pos() {
+ s += v
+ }
+ s
+}
+
+#let f() = {
+ sum(..return, 1, 2, 3)
+ "nope"
+}
+
+#test(f(), 6)
+
+--- return-in-content-block ---
+// Test value return from content.
+#let x = 3
+#let f() = [
+ Hello 😀
+ #return "nope"
+ World
+]
+
+#test(f(), "nope")
+
+--- return-semicolon-or-linebreak ---
+// Test rejection of extra value
+#let f() = [
+ // Error: 16-16 expected semicolon or line break
+ #return a + b Hello World
+]
diff --git a/tests/suite/scripting/while.typ b/tests/suite/scripting/while.typ
new file mode 100644
index 00000000..5e452a89
--- /dev/null
+++ b/tests/suite/scripting/while.typ
@@ -0,0 +1,59 @@
+// Test while expressions.
+
+--- while-loop-basic ---
+// Should output `2 4 6 8 10`.
+#let i = 0
+#while i < 10 [
+ #(i += 2)
+ #i
+]
+
+// Should output `Hi`.
+#let iter = true
+#while iter {
+ iter = false
+ "Hi."
+}
+
+#while false {
+ dont-care
+}
+
+--- while-loop-expr ---
+// Value of while loops.
+
+#test(while false {}, none)
+
+#let i = 0
+#test(type(while i < 1 [#(i += 1)]), content)
+
+--- while-loop-condition-content-invalid ---
+// Condition must be boolean.
+// Error: 8-14 expected boolean, found content
+#while [nope] [nope]
+
+--- while-loop-condition-always-true ---
+// Error: 8-25 condition is always true
+#while 2 < "hello".len() {}
+
+--- while-loop-limit ---
+// Error: 2:2-2:24 loop seems to be infinite
+#let i = 1
+#while i > 0 { i += 1 }
+
+--- while-loop-incomplete ---
+// Error: 7 expected expression
+#while
+
+// Error: 8 expected expression
+#{while}
+
+// Error: 9 expected block
+#while x
+
+// Error: 7 expected expression
+#while
+x {}
+
+// Error: 9 expected block
+#while x something
diff --git a/tests/suite/styling/fold.typ b/tests/suite/styling/fold.typ
new file mode 100644
index 00000000..26fe991b
--- /dev/null
+++ b/tests/suite/styling/fold.typ
@@ -0,0 +1,19 @@
+--- fold-vec-order-text-features ---
+// Test fold order of vectors.
+#set text(features: (liga: 1))
+#set text(features: (liga: 0))
+fi
+
+--- fold-vec-order-text-decos ---
+#underline(stroke: aqua + 4pt)[
+ #underline[Hello]
+]
+
+--- fold-vec-order-meta ---
+#let c = counter("mycounter")
+#c.update(1)
+#locate(loc => [
+ #c.update(2)
+ #c.at(loc) \
+ Second: #locate(loc => c.at(loc))
+])
diff --git a/tests/suite/styling/set.typ b/tests/suite/styling/set.typ
new file mode 100644
index 00000000..a31cd165
--- /dev/null
+++ b/tests/suite/styling/set.typ
@@ -0,0 +1,96 @@
+// General tests for set.
+
+--- set-instantiation-site ---
+// Test that text is affected by instantiation-site bold.
+#let x = [World]
+Hello *#x*
+
+--- set-instantiation-site-markup ---
+// Test that lists are affected by correct indents.
+#let fruit = [
+ - Apple
+ - Orange
+ #list(body-indent: 20pt)[Pear]
+]
+
+- Fruit
+#[#set list(indent: 10pt)
+ #fruit]
+- No more fruit
+
+--- set-text-override ---
+// Test that that block spacing and text style are respected from
+// the outside, but the more specific fill is respected.
+#set block(spacing: 4pt)
+#set text(style: "italic", fill: eastern)
+#let x = [And the forest #parbreak() lay silent!]
+#text(fill: forest, x)
+
+--- set-scoped-in-code-block ---
+// Test that scoping works as expected.
+#{
+ if true {
+ set text(blue)
+ [Blue ]
+ }
+ [Not blue]
+}
+
+--- closure-path-resolve-in-layout-phase ---
+// Test relative path resolving in layout phase.
+#let choice = ("monkey.svg", "rhino.png", "tiger.jpg")
+#set enum(numbering: n => {
+ let path = "/assets/images/" + choice.at(n - 1)
+ move(dy: -0.15em, image(path, width: 1em, height: 1em))
+})
+
++ Monkey
++ Rhino
++ Tiger
+
+--- set-if ---
+// Test conditional set.
+#show ref: it => {
+ set text(red) if it.target == <unknown>
+ "@" + str(it.target)
+}
+
+@hello from the @unknown
+
+--- set-if-bad-type ---
+// Error: 19-24 expected boolean, found integer
+#set text(red) if 1 + 2
+
+--- set-in-expr ---
+// Error: 12-26 set is only allowed directly in code and content blocks
+#{ let x = set text(blue) }
+
+--- set-vs-construct-1 ---
+// Ensure that constructor styles aren't passed down the tree.
+// The inner list should have no extra indent.
+#set par(leading: 2pt)
+#list(body-indent: 20pt, [First], list[A][B])
+
+--- set-vs-construct-2 ---
+// Ensure that constructor styles win, but not over outer styles.
+// The outer paragraph should be right-aligned,
+// but the B should be center-aligned.
+#set list(marker: [>])
+#list(marker: [--])[
+ #rect(width: 2cm, fill: conifer, inset: 4pt, list[A])
+]
+
+--- set-vs-construct-3 ---
+// The inner rectangle should also be yellow here.
+// (and therefore invisible)
+#[#set rect(fill: yellow);#text(1em, rect(inset: 5pt, rect()))]
+
+--- set-vs-construct-4 ---
+// The inner rectangle should not be yellow here.
+A #box(rect(fill: yellow, inset: 5pt, rect())) B
+
+--- show-set-vs-construct ---
+// The constructor property should still work
+// when there are recursive show rules.
+#show enum: set text(blue)
+#enum(numbering: "(a)", [A], enum[B])
diff --git a/tests/suite/styling/show-set.typ b/tests/suite/styling/show-set.typ
new file mode 100644
index 00000000..ea788c93
--- /dev/null
+++ b/tests/suite/styling/show-set.typ
@@ -0,0 +1,70 @@
+// Test show-set rules.
+
+--- show-set-override ---
+// Test overriding show-set rules.
+#show strong: set text(red)
+Hello *World*
+
+#show strong: set text(blue)
+Hello *World*
+
+--- show-set-on-same-element ---
+// Test show-set rule on the same element.
+#set figure(supplement: [Default])
+#show figure.where(kind: table): set figure(supplement: [Tableau])
+#figure(
+ table(columns: 2)[A][B][C][D],
+ caption: [Four letters],
+)
+
+--- show-set-same-element-and-order ---
+// Test both things at once.
+#show heading: set text(red)
+= Level 1
+== Level 2
+
+#show heading.where(level: 1): set text(blue)
+#show heading.where(level: 1): set text(green)
+#show heading.where(level: 1): set heading(numbering: "(I)")
+= Level 1
+== Level 2
+
+--- show-set-same-element-matched-field ---
+// Test setting the thing we just matched on.
+// This is quite cursed, but it works.
+#set heading(numbering: "(I)")
+#show heading.where(numbering: "(I)"): set heading(numbering: "1.")
+= Heading
+
+--- show-set-same-element-synthesized-matched-field ---
+// Same thing, but even more cursed, because `kind` is synthesized.
+#show figure.where(kind: table): set figure(kind: raw)
+#figure(table[A], caption: [Code])
+
+--- show-set-same-element-matching-interaction ---
+// Test that show-set rules on the same element don't affect each other. This
+// could be implemented, but isn't as of yet.
+#show heading.where(level: 1): set heading(numbering: "(I)")
+#show heading.where(numbering: "(I)"): set text(red)
+= Heading
+
+--- show-set-on-layoutable-element ---
+// Test show-set rules on layoutable element to ensure it is realized
+// even though it implements `LayoutMultiple`.
+#show table: set text(red)
+#pad(table(columns: 4)[A][B][C][D])
+
+--- show-function-order-with-set ---
+// These are both red because in the expanded form, `set text(red)` ends up
+// closer to the content than `set text(blue)`.
+#show strong: it => { set text(red); it }
+Hello *World*
+
+#show strong: it => { set text(blue); it }
+Hello *World*
+
+--- show-function-set-on-it ---
+// This doesn't have an effect. An element is materialized before any show
+// rules run.
+#show heading: it => { set heading(numbering: "(I)"); it }
+= Heading
diff --git a/tests/suite/styling/show-text.typ b/tests/suite/styling/show-text.typ
new file mode 100644
index 00000000..56b659b2
--- /dev/null
+++ b/tests/suite/styling/show-text.typ
@@ -0,0 +1,133 @@
+// Test text replacement show rules.
+
+--- show-text-basic ---
+// Test classic example.
+#set text(font: "Roboto")
+#show "Der Spiegel": smallcaps
+Die Zeitung Der Spiegel existiert.
+
+--- show-text-regex ---
+// Another classic example.
+#show "TeX": [T#h(-0.145em)#box(move(dy: 0.233em)[E])#h(-0.135em)X]
+#show regex("(Lua)?(La)?TeX"): name => box(text(font: "New Computer Modern")[#name])
+
+TeX, LaTeX, LuaTeX and LuaLaTeX!
+
+--- show-text-cyclic ---
+// Test direct cycle.
+#show "Hello": text(red)[Hello]
+Hello World!
+
+--- show-text-cyclic-raw ---
+// Test replacing text with raw text.
+#show "rax": `rax`
+The register rax.
+
+--- show-text-indirectly-cyclic ---
+// Test indirect cycle.
+#show "Good": [Typst!]
+#show "Typst": [Fun!]
+#show "Fun": [Good!]
+
+#set text(ligatures: false)
+Good \
+Fun \
+Typst \
+
+--- show-text-exactly-once ---
+// Test that replacements happen exactly once.
+#show "A": [BB]
+#show "B": [CC]
+AA (8)
+
+--- show-text-regex-word-boundary ---
+// Test caseless match and word boundaries.
+#show regex("(?i)\bworld\b"): [🌍]
+
+Treeworld, the World of worlds, is a world.
+
+--- show-text-empty ---
+// Test there is no crashing on empty strings
+// Error: 1:7-1:9 text selector is empty
+#show "": []
+
+--- show-text-regex-empty ---
+// Error: 1:7-1:16 regex selector is empty
+#show regex(""): [AA]
+
+--- show-text-regex-matches-empty ---
+// Error: 1:7-1:42 regex matches empty text
+#show regex("(VAR_GLOBAL|END_VAR||BOOL)") : []
+
+--- show-text-regex-character-class ---
+// This is a fun one.
+#set par(justify: true)
+#show regex("\S"): letter => box(stroke: 1pt, inset: 2pt, upper(letter))
+#lorem(5)
+
+--- show-text-regex-case-insensitive ---
+// See also: https://github.com/mTvare6/hello-world.rs
+#show regex("(?i)rust"): it => [#it (🚀)]
+Rust is memory-safe and blazingly fast. Let's rewrite everything in rust.
+
+--- show-text-get-text-on-it ---
+// Test accessing the string itself.
+#show "hello": it => it.text.split("").map(upper).join("|")
+Oh, hello there!
+
+--- show-text-in-other-show ---
+// Replace worlds but only in lists.
+#show list: it => [
+ #show "World": [🌎]
+ #it
+]
+
+World
+- World
+
+--- show-text-path-resolving ---
+// Test absolute path in layout phase.
+
+#show "GRAPH": image("/assets/images/graph.png")
+
+The GRAPH has nodes.
+
+--- show-set-text-order-adjacent-1 ---
+#show "He": set text(red)
+#show "ya": set text(blue)
+Heya
+
+--- show-set-text-order-contained-1 ---
+#show "Heya": set text(red)
+#show "ya": set text(blue)
+Heya
+
+--- show-set-text-order-contained-3 ---
+#show "He": set text(red)
+#show "Heya": set text(blue)
+Heya
+
+--- show-set-text-order-overlapping-1 ---
+#show "Heya": set text(red)
+#show "yaho": set text(blue)
+Heyaho
+
+--- show-set-text-order-adjacent-2 ---
+#show "He": set text(red)
+#show "ya": set text(weight: "bold")
+Heya
+
+--- show-set-text-order-contained-2 ---
+#show "Heya": set text(red)
+#show "ya": set text(weight: "bold")
+Heya
+
+--- show-set-text-order-contained-4 ---
+#show "He": set text(red)
+#show "Heya": set text(weight: "bold")
+Heya
+
+--- show-set-text-order-overlapping-2 ---
+#show "Heya": set text(red)
+#show "yaho": set text(weight: "bold")
+Heyaho
diff --git a/tests/suite/styling/show-where.typ b/tests/suite/styling/show-where.typ
new file mode 100644
index 00000000..72dbae69
--- /dev/null
+++ b/tests/suite/styling/show-where.typ
@@ -0,0 +1,89 @@
+--- show-where-optional-field-raw ---
+// Test that where selectors also trigger on set rule fields.
+#show raw.where(block: false): box.with(
+ fill: luma(220),
+ inset: (x: 3pt, y: 0pt),
+ outset: (y: 3pt),
+ radius: 2pt,
+)
+
+This is #raw("fn main() {}") some text.
+
+--- show-where-optional-field-text ---
+// Note: This show rule is horribly inefficient because it triggers for
+// every individual text element. But it should still work.
+#show text.where(lang: "de"): set text(red)
+
+#set text(lang: "es")
+Hola, mundo!
+
+#set text(lang: "de")
+Hallo Welt!
+
+#set text(lang: "en")
+Hello World!
+
+--- show-where-folding-text-size ---
+// Test that folding is taken into account.
+#set text(5pt)
+#set text(2em)
+
+#[
+ #show text.where(size: 2em): set text(blue)
+ 2em not blue
+]
+
+#[
+ #show text.where(size: 10pt): set text(blue)
+ 10pt blue
+]
+
+--- show-where-folding-stroke ---
+// Test again that folding is taken into account.
+#set rect(width: 40pt, height: 10pt)
+#set rect(stroke: blue)
+#set rect(stroke: 2pt)
+
+#{
+ show rect.where(stroke: blue): "Not Triggered"
+ rect()
+}
+#{
+ show rect.where(stroke: 2pt): "Not Triggered"
+ rect()
+}
+#{
+ show rect.where(stroke: 2pt + blue): "Triggered"
+ rect()
+}
+
+--- show-where-resolving-length ---
+// Test that resolving is *not* taken into account.
+#set line(start: (1em, 1em + 2pt))
+
+#{
+ show line.where(start: (1em, 1em + 2pt)): "Triggered"
+ line()
+}
+#{
+ show line.where(start: (10pt, 12pt)): "Not Triggered"
+ line()
+}
+
+
+--- show-where-resolving-hyphenate ---
+// Test again that resolving is *not* taken into account.
+#set text(hyphenate: auto)
+
+#[
+ #show text.where(hyphenate: auto): underline
+ Auto
+]
+#[
+ #show text.where(hyphenate: true): underline
+ True
+]
+#[
+ #show text.where(hyphenate: false): underline
+ False
+]
diff --git a/tests/suite/styling/show.typ b/tests/suite/styling/show.typ
new file mode 100644
index 00000000..aa121bff
--- /dev/null
+++ b/tests/suite/styling/show.typ
@@ -0,0 +1,262 @@
+// Test show rules.
+
+--- show-selector-basic ---
+// Override lists.
+#show list: it => "(" + it.children.map(v => v.body).join(", ") + ")"
+
+- A
+ - B
+ - C
+- D
+- E
+
+--- show-selector-replace-and-show-set ---
+// Test full reset.
+#show heading: [B]
+#show heading: set text(size: 10pt, weight: 400)
+A #[= Heading] C
+
+--- show-selector-discard ---
+// Test full removal.
+#show heading: none
+
+Where is
+= There are no headings around here!
+my heading?
+
+--- show-selector-realistic ---
+// Test integrated example.
+#show heading: it => block({
+ set text(10pt)
+ box(move(dy: -1pt)[📖])
+ h(5pt)
+ if it.level == 1 {
+ underline(text(1.25em, blue, it.body))
+ } else {
+ text(red, it.body)
+ }
+})
+
+= Task 1
+Some text.
+
+== Subtask
+Some more text.
+
+= Task 2
+Another text.
+
+--- show-in-show ---
+// Test set and show in code blocks.
+#show heading: it => {
+ set text(red)
+ show "ding": [🛎]
+ it.body
+}
+
+= Heading
+
+--- show-nested-scopes ---
+// Test that scoping works as expected.
+#{
+ let world = [ World ]
+ show "W": strong
+ world
+ {
+ set text(blue)
+ show: it => {
+ show "o": "Ø"
+ it
+ }
+ world
+ }
+ world
+}
+
+--- show-selector-replace ---
+#show heading: [1234]
+= Heading
+
+--- show-unknown-field ---
+// Error: 25-29 content does not contain field "page"
+#show heading: it => it.page
+= Heading
+
+--- show-text-element-discard ---
+#show text: none
+Hey
+
+--- show-selector-not-an-element-function ---
+// Error: 7-12 only element functions can be used as selectors
+#show upper: it => {}
+
+--- show-bad-replacement-type ---
+// Error: 16-20 expected content or function, found integer
+#show heading: 1234
+= Heading
+
+--- show-bad-selector-type ---
+// Error: 7-10 expected symbol, string, label, function, regex, or selector, found color
+#show red: []
+
+--- show-selector-in-expression ---
+// Error: 7-25 show is only allowed directly in code and content blocks
+#(1 + show heading: none)
+
+--- show-bare-basic ---
+#set page(height: 130pt)
+#set text(0.7em)
+
+#align(center)[
+ #text(1.3em)[*Essay on typography*] \
+ T. Ypst
+]
+
+#show: columns.with(2)
+Great typography is at the essence of great storytelling. It is the medium that
+transports meaning from parchment to reader, the wave that sparks a flame
+in booklovers and the great fulfiller of human need.
+
+--- show-bare-content-block ---
+// Test bare show in content block.
+A #[_B #show: c => [*#c*]; C_] D
+
+--- show-bare-vs-set-text ---
+// Test style precedence.
+#set text(fill: eastern, size: 1.5em)
+#show: text.with(fill: forest)
+Forest
+
+--- show-bare-replace-with-content ---
+#show: [Shown]
+Ignored
+
+--- show-bare-in-expression ---
+// Error: 4-19 show is only allowed directly in code and content blocks
+#((show: body => 2) * body)
+
+--- show-bare-missing-colon-closure ---
+// Error: 6 expected colon
+#show it => {}
+
+--- show-bare-missing-colon ---
+// Error: 6 expected colon
+#show it
+
+--- show-recursive-identity ---
+// Test basic identity.
+#show heading: it => it
+= Heading
+
+--- show-multiple-rules ---
+// Test more recipes down the chain.
+#show list: scale.with(origin: left, x: 80%)
+#show heading: []
+#show enum: []
+- Actual
+- Tight
+- List
+= Nope
+
+--- show-rule-in-function ---
+// Test show rule in function.
+#let starwars(body) = {
+ show list: it => block({
+ stack(dir: ltr,
+ text(red, it),
+ 1fr,
+ scale(x: -100%, text(blue, it)),
+ )
+ })
+ body
+}
+
+- Normal list
+
+#starwars[
+ - Star
+ - Wars
+ - List
+]
+
+- Normal list
+
+--- show-recursive-multiple ---
+// Test multi-recursion with nested lists.
+#set rect(inset: 3pt)
+#show list: rect.with(stroke: blue)
+#show list: rect.with(stroke: red)
+#show list: block
+
+- List
+ - Nested
+ - List
+- Recursive!
+
+--- show-selector-where ---
+// Inline code.
+#show raw.where(block: false): box.with(
+ radius: 2pt,
+ outset: (y: 2.5pt),
+ inset: (x: 3pt, y: 0pt),
+ fill: luma(230),
+)
+
+// Code blocks.
+#show raw.where(block: true): block.with(
+ outset: -3pt,
+ inset: 11pt,
+ fill: luma(230),
+ stroke: (left: 1.5pt + luma(180)),
+)
+
+#set page(margin: (top: 12pt))
+#set par(justify: true)
+
+This code tests `code`
+with selectors and justification.
+
+```rs
+code!("it");
+```
+
+You can use the ```rs *const T``` pointer or
+the ```rs &mut T``` reference.
+
+--- show-set-where-override ---
+#show heading: set text(green)
+#show heading.where(level: 1): set text(red)
+#show heading.where(level: 2): set text(blue)
+= Red
+== Blue
+=== Green
+
+--- show-selector-or-elements-with-set ---
+// Looking forward to `heading.where(level: 1 | 2)` :)
+#show heading.where(level: 1).or(heading.where(level: 2)): set text(red)
+= L1
+== L2
+=== L3
+==== L4
+
+--- show-selector-element-or-label ---
+// Test element selector combined with label selector.
+#show selector(strong).or(<special>): highlight
+I am *strong*, I am _emphasized_, and I am #[special<special>].
+
+--- show-selector-element-or-text ---
+// Ensure that text selector cannot be nested in and/or. That's too complicated,
+// at least for now.
+
+// Error: 7-41 this selector cannot be used with show
+#show heading.where(level: 1).or("more"): set text(red)
+
+--- show-delayed-error ---
+// Error: 21-34 panicked with: "hey1"
+#show heading: _ => panic("hey1")
+
+// Error: 20-33 panicked with: "hey2"
+#show strong: _ => panic("hey2")
+
+= Hello
+*strong*
diff --git a/tests/suite/symbols/symbol.typ b/tests/suite/symbols/symbol.typ
new file mode 100644
index 00000000..8f9a49ca
--- /dev/null
+++ b/tests/suite/symbols/symbol.typ
@@ -0,0 +1,38 @@
+// Test symbols.
+
+--- symbol ---
+#emoji.face
+#emoji.woman.old
+#emoji.turtle
+
+#set text(font: "New Computer Modern Math")
+#sym.arrow
+#sym.arrow.l
+#sym.arrow.r.squiggly
+#sym.arrow.tr.hook
+
+#sym.arrow.r;this and this#sym.arrow.l;
+
+--- symbol-constructor ---
+#let envelope = symbol(
+ "🖂",
+ ("stamped", "🖃"),
+ ("stamped.pen", "🖆"),
+ ("lightning", "🖄"),
+ ("fly", "🖅"),
+)
+
+#envelope
+#envelope.stamped
+#envelope.pen
+#envelope.stamped.pen
+#envelope.lightning
+#envelope.fly
+
+--- symbol-constructor-empty ---
+// Error: 2-10 expected at least one variant
+#symbol()
+
+--- symbol-unknown-modifier ---
+// Error: 13-20 unknown symbol modifier
+#emoji.face.garbage
diff --git a/tests/suite/syntax/backtracking.typ b/tests/suite/syntax/backtracking.typ
new file mode 100644
index 00000000..33f05770
--- /dev/null
+++ b/tests/suite/syntax/backtracking.typ
@@ -0,0 +1,32 @@
+// Ensure that parser backtracking doesn't lead to exponential time consumption.
+// If this regresses, the test suite will not terminate, which is a bit
+// unfortunate compared to a good error, but at least we know something is up.
+//
+
+--- parser-backtracking-param-default-value ---
+#{
+ let s = "(x: 1) => x"
+ let pat = "(x: {}) => 1 + x()"
+ for _ in range(50) {
+ s = pat.replace("{}", s)
+ }
+ test(eval(s)(), 51)
+}
+
+--- parser-backtracking-destructuring-assignment ---
+#{
+ let s = "(x) = 1"
+ let pat = "(x: {_}) = 1"
+ for _ in range(100) {
+ s = pat.replace("_", s)
+ }
+ // Error: 8-9 cannot destructure integer
+ eval(s)
+}
+
+--- parser-backtracking-destructuring-whitespace ---
+// Test whitespace after memoized part.
+#( (x: () => 1 ) => 1 )
+// -------
+// This is memoized and we want to ensure that whitespace after this
+// is handled correctly.
diff --git a/tests/suite/syntax/comment.typ b/tests/suite/syntax/comment.typ
new file mode 100644
index 00000000..ac3e1943
--- /dev/null
+++ b/tests/suite/syntax/comment.typ
@@ -0,0 +1,43 @@
+// Test line and block comments.
+
+--- comments ---
+// Line comment acts as spacing.
+A// you
+B
+
+// Block comment does not act as spacing, nested block comments.
+C/*
+ /* */
+*/D
+
+// Works in code.
+#test(type(/*1*/ 1) //
+, int)
+
+// End of block comment in line comment.
+// Hello */
+
+// Nested "//" doesn't count as line comment.
+/* // */
+E
+
+/*//*/
+This is a comment.
+*/*/
+
+--- comment-end-of-line ---
+// Test comments at the end of a line
+First part//
+Second part
+
+// Test comments at the end of a line with pre-spacing
+First part //
+Second part
+
+--- comment-block-unclosed ---
+// End should not appear without start.
+// Error: 7-9 unexpected end of block comment
+/* */ */
+
+// Unterminated is okay.
+/*
diff --git a/tests/suite/syntax/embedded.typ b/tests/suite/syntax/embedded.typ
new file mode 100644
index 00000000..74ce4a03
--- /dev/null
+++ b/tests/suite/syntax/embedded.typ
@@ -0,0 +1,9 @@
+// Test embedded expressions.
+
+--- markup-expr-incomplete ---
+// Error: 2-2 expected expression
+#
+
+--- markup-expr-incomplete-followed-by-text ---
+// Error: 2-2 expected expression
+# hello
diff --git a/tests/suite/syntax/escape.typ b/tests/suite/syntax/escape.typ
new file mode 100644
index 00000000..ff05aa99
--- /dev/null
+++ b/tests/suite/syntax/escape.typ
@@ -0,0 +1,36 @@
+// Test escape sequences.
+
+--- escape ---
+// Escapable symbols.
+\\ \/ \[ \] \{ \} \# \* \_ \+ \= \~ \
+\` \$ \" \' \< \> \@ \( \) \A
+
+// No need to escape.
+( ) ;
+
+// Escaped comments.
+\//
+\/\* \*\/
+\/* \*/ *
+
+// Unicode escape sequence.
+\u{1F3D5} == 🏕
+
+// Escaped escape sequence.
+\u{41} vs. \\u\{41\}
+
+// Some code stuff in text.
+let f() , ; : | + - /= == 12 "string"
+
+// Escaped dot.
+10\. May
+
+--- escape-invalid-codepoint ---
+// Unicode codepoint does not exist.
+// Error: 1-11 invalid Unicode codepoint: FFFFFF
+\u{FFFFFF}
+
+--- escape-unclosed ---
+// Unterminated.
+// Error: 1-6 unclosed Unicode escape sequence
+\u{41[*Bold*]
diff --git a/tests/suite/syntax/newlines.typ b/tests/suite/syntax/newlines.typ
new file mode 100644
index 00000000..eef45619
--- /dev/null
+++ b/tests/suite/syntax/newlines.typ
@@ -0,0 +1,77 @@
+// Test newline continuations.
+
+--- newline-continuation-code ---
+#{
+ "hello"
+ .clusters()
+ if false {
+
+ }
+ else {
+ ("1", "2")
+ }
+}
+
+--- newline-continuation-markup ---
+#"hello"
+ .codepoints()
+
+#if false {
+
+}
+else {
+ ("1", "2")
+}
+
+--- newline-continuation-method-blank ---
+#test({
+ "hi 1"
+
+ .clusters()
+}, ("h", "i", " ", "1"))
+
+--- newline-continuation-method-line-comment-after ---
+#test({
+ "hi 2"// comment
+ .clusters()
+}, ("h", "i", " ", "2"))
+
+--- newline-continuation-method-block-comment-after ---
+#test({
+ "hi 3"/* comment */
+ .clusters()
+}, ("h", "i", " ", "3"))
+
+--- newline-continuation-method-line-comment-between ---
+#test({
+ "hi 4"
+ // comment
+ .clusters()
+}, ("h", "i", " ", "4"))
+
+--- newline-continuation-method-block-comment-between ---
+#test({
+ "hi 5"
+ /*comment*/.clusters()
+}, ("h", "i", " ", "5"))
+
+--- newline-continuation-method-comments-and-blanks ---
+#test({
+ "hi 6"
+ // comment
+
+
+ /* comment */
+ .clusters()
+}, ("h", "i", " ", "6"))
+
+--- newline-continuation-if-else-comment ---
+#test({
+ let foo(x) = {
+ if x < 0 { "negative" }
+ // comment
+ else { "non-negative" }
+ }
+
+ foo(1)
+}, "non-negative")
diff --git a/tests/suite/syntax/numbers.typ b/tests/suite/syntax/numbers.typ
new file mode 100644
index 00000000..1f15ac72
--- /dev/null
+++ b/tests/suite/syntax/numbers.typ
@@ -0,0 +1,32 @@
+// Test how numbers are displayed.
+
+--- numbers ---
+// Test numbers in text mode.
+12 \
+12.0 \
+3.14 \
+1234567890 \
+0123456789 \
+0 \
+0.0 \
++0 \
++0.0 \
+-0 \
+-0.0 \
+-1 \
+-3.14 \
+-9876543210 \
+-0987654321 \
+٣٫١٤ \
+-٣٫١٤ \
+-¾ \
+#text(fractions: true)[-3/2] \
+2022 - 2023 \
+2022 -- 2023 \
+2022--2023 \
+2022-2023 \
+٢٠٢٢ - ٢٠٢٣ \
+٢٠٢٢ -- ٢٠٢٣ \
+٢٠٢٢--٢٠٢٣ \
+٢٠٢٢-٢٠٢٣ \
+-500 -- -400
diff --git a/tests/suite/syntax/shorthand.typ b/tests/suite/syntax/shorthand.typ
new file mode 100644
index 00000000..81aa6b7b
--- /dev/null
+++ b/tests/suite/syntax/shorthand.typ
@@ -0,0 +1,61 @@
+// Test shorthands for unicode codepoints.
+
+--- shorthand-nbsp-and-shy-hyphen ---
+The non-breaking space~does work, soft-?hyphen also does.
+
+--- shorthand-nbsp-width ---
+// Make sure non-breaking and normal space always
+// have the same width. Even if the font decided
+// differently.
+#set text(font: "New Computer Modern")
+a b \
+a~b
+
+--- shorthand-dashes ---
+- En dash: --
+- Em dash: ---
+
+--- shorthand-ellipsis ---
+#set text(font: "Roboto")
+A... vs #"A..."
+
+--- shorthands-math ---
+// Check all math shorthands
+$...$\
+$-$\
+$'$\
+$*$\
+$!=$\
+$:=$\
+$::=$\
+$=:$\
+$<<$\
+$<<<$\
+$>>$\
+$>>>$\
+$<=$\
+$>=$\
+$->$\
+$-->$\
+$|->$\
+$>->$\
+$->>$\
+$<-$\
+$<--$\
+$<-<$\
+$<<-$\
+$<->$\
+$<-->$\
+$~>$\
+$~~>$\
+$<~$\
+$<~~$\
+$=>$\
+$|=>$\
+$==>$\
+$<==$\
+$<=>$\
+$<==>$\
+$[|$\
+$|]$\
+$||$
diff --git a/tests/suite/text/case.typ b/tests/suite/text/case.typ
new file mode 100644
index 00000000..2bf68bc3
--- /dev/null
+++ b/tests/suite/text/case.typ
@@ -0,0 +1,11 @@
+// Test the `upper` and `lower` functions.
+
+--- lower-and-upper ---
+#let memes = "ArE mEmEs gReAt?";
+#test(lower(memes), "are memes great?")
+#test(upper(memes), "ARE MEMES GREAT?")
+#test(upper("Ελλάδα"), "ΕΛΛΆΔΑ")
+
+--- upper-bad-type ---
+// Error: 8-9 expected string or content, found integer
+#upper(1)
diff --git a/tests/suite/text/coma.typ b/tests/suite/text/coma.typ
new file mode 100644
index 00000000..df757633
--- /dev/null
+++ b/tests/suite/text/coma.typ
@@ -0,0 +1,26 @@
+--- coma ---
+// LARGE
+#set page(width: 450pt, margin: 1cm)
+
+*Technische Universität Berlin* #h(1fr) *WiSe 2019/2020* \
+*Fakultät II, Institut for Mathematik* #h(1fr) Woche 3 \
+Sekretariat MA \
+Dr. Max Mustermann \
+Ola Nordmann, John Doe
+
+#v(3mm)
+#align(center)[
+ #set par(leading: 3mm)
+ #text(1.2em)[*3. Übungsblatt Computerorientierte Mathematik II*] \
+ *Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) \
+ *Alle Antworten sind zu beweisen.*
+]
+
+*1. Aufgabe* #h(1fr) (1 + 1 + 2 Punkte)
+
+Ein _Binärbaum_ ist ein Wurzelbaum, in dem jeder Knoten ≤ 2 Kinder hat.
+Die Tiefe eines Knotens _v_ ist die Länge des eindeutigen Weges von der Wurzel
+zu _v_, und die Höhe von _v_ ist die Länge eines längsten (absteigenden) Weges
+von _v_ zu einem Blatt. Die Höhe des Baumes ist die Höhe der Wurzel.
+
+#align(center, image("/assets/images/graph.png", width: 75%))
diff --git a/tests/suite/text/copy-paste.typ b/tests/suite/text/copy-paste.typ
new file mode 100644
index 00000000..ff6da893
--- /dev/null
+++ b/tests/suite/text/copy-paste.typ
@@ -0,0 +1,8 @@
+// Test copy-paste and search in PDF with ligatures
+// and Arabic test. Must be tested manually!
+
+--- text-copy-paste-ligatures ---
+The after fira 🏳️‍🌈!
+
+#set text(lang: "ar", font: "Noto Sans Arabic")
+مرحبًا
diff --git a/tests/suite/text/deco.typ b/tests/suite/text/deco.typ
new file mode 100644
index 00000000..07fdb6c1
--- /dev/null
+++ b/tests/suite/text/deco.typ
@@ -0,0 +1,85 @@
+// Test text decorations.
+
+--- underline-overline-strike ---
+#let red = rgb("fc0030")
+
+// Basic strikethrough.
+#strike[Statements dreamt up by the utterly deranged.]
+
+// Move underline down.
+#underline(offset: 5pt)[Further below.]
+
+// Different color.
+#underline(stroke: red, evade: false)[Critical information is conveyed here.]
+
+// Inherits font color.
+#text(fill: red, underline[Change with the wind.])
+
+// Both over- and underline.
+#overline(underline[Running amongst the wolves.])
+
+--- strike-with ---
+#let redact = strike.with(stroke: 10pt, extent: 0.05em)
+#let highlight-custom = strike.with(stroke: 10pt + rgb("abcdef88"), extent: 0.05em)
+
+// Abuse thickness and transparency for redacting and highlighting stuff.
+Sometimes, we work #redact[in secret].
+There might be #highlight-custom[redacted] things.
+
+--- underline-stroke-folding ---
+// Test stroke folding.
+#set underline(stroke: 2pt, offset: 2pt)
+#underline(text(red, [DANGER!]))
+
+--- underline-background ---
+// Test underline background
+#set underline(background: true, stroke: (thickness: 0.5em, paint: red, cap: "round"))
+#underline[This is in the background]
+
+--- overline-background ---
+// Test overline background
+#set overline(background: true, stroke: (thickness: 0.5em, paint: red, cap: "round"))
+#overline[This is in the background]
+
+--- strike-background ---
+// Test strike background
+#set strike(background: true, stroke: 5pt + red)
+#strike[This is in the background]
+
+--- highlight ---
+// Test highlight.
+This is the built-in #highlight[highlight with default color].
+We can also specify a customized value
+#highlight(fill: green.lighten(80%))[to highlight].
+
+--- highlight-bounds ---
+// Test default highlight bounds.
+#highlight[ace],
+#highlight[base],
+#highlight[super],
+#highlight[phone #sym.integral]
+
+--- highlight-edges ---
+// Test a tighter highlight.
+#set highlight(top-edge: "x-height", bottom-edge: "baseline")
+#highlight[ace],
+#highlight[base],
+#highlight[super],
+#highlight[phone #sym.integral]
+
+--- highlight-edges-bounds ---
+// Test a bounds highlight.
+#set highlight(top-edge: "bounds", bottom-edge: "bounds")
+#highlight[abc]
+#highlight[abc #sym.integral]
+
+--- highlight-radius ---
+// Test highlight radius
+#highlight(radius: 3pt)[abc],
+#highlight(radius: 1em)[#lorem(5)]
+
+--- highlight-stroke ---
+// Test highlight stroke
+#highlight(stroke: 2pt + blue)[abc]
+#highlight(stroke: (top: blue, left: red, bottom: green, right: orange))[abc]
+#highlight(stroke: 1pt, radius: 3pt)[#lorem(5)]
diff --git a/tests/suite/text/edge.typ b/tests/suite/text/edge.typ
new file mode 100644
index 00000000..57732156
--- /dev/null
+++ b/tests/suite/text/edge.typ
@@ -0,0 +1,39 @@
+// Test top and bottom text edge.
+
+--- text-edge ---
+#set page(width: 160pt)
+#set text(size: 8pt)
+
+#let try(top, bottom) = rect(inset: 0pt, fill: conifer)[
+ #set text(font: "IBM Plex Mono", top-edge: top, bottom-edge: bottom)
+ From #top to #bottom
+]
+
+#let try-bounds(top, bottom) = rect(inset: 0pt, fill: conifer)[
+ #set text(font: "IBM Plex Mono", top-edge: top, bottom-edge: bottom)
+ #top to #bottom: "yay, Typst"
+]
+
+#try("ascender", "descender")
+#try("ascender", "baseline")
+#try("cap-height", "baseline")
+#try("x-height", "baseline")
+#try-bounds("cap-height", "baseline")
+#try-bounds("bounds", "baseline")
+#try-bounds("bounds", "bounds")
+#try-bounds("x-height", "bounds")
+
+#try(4pt, -2pt)
+#try(1pt + 0.3em, -0.15em)
+
+--- text-edge-bad-type ---
+// Error: 21-23 expected "ascender", "cap-height", "x-height", "baseline", "bounds", or length, found array
+#set text(top-edge: ())
+
+--- text-edge-bad-value ---
+// Error: 24-26 expected "baseline", "descender", "bounds", or length
+#set text(bottom-edge: "")
+
+--- text-edge-wrong-edge ---
+// Error: 24-36 expected "baseline", "descender", "bounds", or length
+#set text(bottom-edge: "cap-height")
diff --git a/tests/suite/text/em.typ b/tests/suite/text/em.typ
new file mode 100644
index 00000000..be7e3428
--- /dev/null
+++ b/tests/suite/text/em.typ
@@ -0,0 +1,33 @@
+// Test font-relative sizing.
+
+--- text-size-em-nesting ---
+#set text(size: 5pt)
+A // 5pt
+#[
+ #set text(size: 2em)
+ B // 10pt
+ #[
+ #set text(size: 1.5em + 1pt)
+ C // 16pt
+ #text(size: 2em)[D] // 32pt
+ E // 16pt
+ ]
+ F // 10pt
+]
+G // 5pt
+
+--- text-size-em ---
+// Test using ems in arbitrary places.
+#set text(size: 5pt)
+#set text(size: 2em)
+#set square(fill: red)
+
+#let size = {
+ let size = 0.25em + 1pt
+ for _ in range(3) {
+ size *= 2
+ }
+ size - 3pt
+}
+
+#stack(dir: ltr, spacing: 1fr, square(size: size), square(size: 25pt))
diff --git a/tests/suite/text/font.typ b/tests/suite/text/font.typ
new file mode 100644
index 00000000..47ec6419
--- /dev/null
+++ b/tests/suite/text/font.typ
@@ -0,0 +1,66 @@
+// Test configuring font properties.
+
+--- text-font-properties ---
+// Set same font size in three different ways.
+#text(20pt)[A]
+#text(2em)[A]
+#text(size: 15pt + 0.5em)[A]
+
+// Do nothing.
+#text()[Normal]
+
+// Set style (is available).
+#text(style: "italic")[Italic]
+
+// Set weight (is available).
+#text(weight: "bold")[Bold]
+
+// Set stretch (not available, matching closest).
+#text(stretch: 50%)[Condensed]
+
+// Set font family.
+#text(font: "IBM Plex Serif")[Serif]
+
+// Emoji.
+Emoji: 🐪, 🌋, 🏞
+
+// Colors.
+#[
+ #set text(fill: eastern)
+ This is #text(rgb("FA644B"))[way more] colorful.
+]
+
+// Transparency.
+#block(fill: green)[
+ #set text(fill: rgb("FF000080"))
+ This text is transparent.
+]
+
+// Disable font fallback beyond the user-specified list.
+// Without disabling, New Computer Modern Math would come to the rescue.
+#set text(font: ("PT Sans", "Twitter Color Emoji"), fallback: false)
+2π = 𝛼 + 𝛽. ✅
+
+--- text-call-body ---
+// Test string body.
+#text("Text") \
+#text(red, "Text") \
+#text(font: "Ubuntu", blue, "Text") \
+#text([Text], teal, font: "IBM Plex Serif") \
+#text(forest, font: "New Computer Modern", [Text]) \
+
+--- text-bad-argument ---
+// Error: 11-16 unexpected argument
+#set text(false)
+
+--- text-style-bad ---
+// Error: 18-24 expected "normal", "italic", or "oblique"
+#set text(style: "bold", weight: "thin")
+
+--- text-bad-extra-argument ---
+// Error: 23-27 unexpected argument
+#set text(size: 10pt, 12pt)
+
+--- text-bad-named-argument ---
+// Error: 11-31 unexpected argument: something
+#set text(something: "invalid")
diff --git a/tests/suite/text/lang.typ b/tests/suite/text/lang.typ
new file mode 100644
index 00000000..74f70140
--- /dev/null
+++ b/tests/suite/text/lang.typ
@@ -0,0 +1,74 @@
+// Test setting the document language.
+
+--- text-lang ---
+// without any region
+#set text(font: "Noto Serif CJK TC", lang: "zh")
+#outline()
+
+--- text-lang-unknown-region ---
+// with unknown region configured
+#set text(font: "Noto Serif CJK TC", lang: "zh", region: "XX")
+#outline()
+
+--- text-lang-region ---
+// with region configured
+#set text(font: "Noto Serif CJK TC", lang: "zh", region: "TW")
+#outline()
+
+--- text-lang-hyphenate ---
+// Ensure that setting the language does have effects.
+#set text(hyphenate: true)
+#grid(
+ columns: 2 * (20pt,),
+ gutter: 1fr,
+ text(lang: "en")["Eingabeaufforderung"],
+ text(lang: "de")["Eingabeaufforderung"],
+)
+
+--- text-lang-shaping ---
+// Test that the language passed to the shaper has an effect.
+#set text(font: "Ubuntu")
+
+// Some lowercase letters are different in Serbian Cyrillic compared to other
+// Cyrillic languages. Since there is only one set of Unicode codepoints for
+// Cyrillic, these can only be seen when setting the language to Serbian and
+// selecting one of the few fonts that support these letterforms.
+Бб
+#text(lang: "uk")[Бб]
+#text(lang: "sr")[Бб]
+
+--- text-lang-script-shaping ---
+// Verify that writing script/language combination has an effect
+#{
+ set text(size:20pt)
+ set text(script: "latn", lang: "en")
+ [Ş ]
+ set text(script: "latn", lang: "ro")
+ [Ş ]
+ set text(script: "grek", lang: "ro")
+ [Ş ]
+}
+
+--- text-script-bad-type ---
+// Error: 19-23 expected string or auto, found none
+#set text(script: none)
+
+--- text-script-bad-value ---
+// Error: 19-23 expected three or four letter script code (ISO 15924 or 'math')
+#set text(script: "ab")
+
+--- text-lang-bad-type ---
+// Error: 17-21 expected string, found none
+#set text(lang: none)
+
+--- text-lang-bad-value ---
+// Error: 17-20 expected two or three letter language code (ISO 639-1/2/3)
+#set text(lang: "ӛ")
+
+--- text-lang-bad-value-emoji ---
+// Error: 17-20 expected two or three letter language code (ISO 639-1/2/3)
+#set text(lang: "😃")
+
+--- text-region-bad-value ---
+// Error: 19-24 expected two letter region code (ISO 3166-1 alpha-2)
+#set text(region: "hey")
diff --git a/tests/suite/text/lorem.typ b/tests/suite/text/lorem.typ
new file mode 100644
index 00000000..1524e2a3
--- /dev/null
+++ b/tests/suite/text/lorem.typ
@@ -0,0 +1,32 @@
+// Test blind text.
+
+--- lorem ---
+// Test basic call.
+#lorem(19)
+
+--- lorem-pars ---
+// Test custom paragraphs with user code.
+#set text(8pt)
+
+#{
+ let sentences = lorem(59)
+ .split(".")
+ .filter(s => s != "")
+ .map(s => s + ".")
+
+ let used = 0
+ for s in sentences {
+ if used < 2 {
+ used += 1
+ } else {
+ parbreak()
+ used = 0
+ }
+ s.trim()
+ [ ]
+ }
+}
+
+--- lorem-missing-words ---
+// Error: 2-9 missing argument: words
+#lorem()
diff --git a/tests/suite/text/raw.typ b/tests/suite/text/raw.typ
new file mode 100644
index 00000000..dce77fdb
--- /dev/null
+++ b/tests/suite/text/raw.typ
@@ -0,0 +1,630 @@
+// Test raw blocks.
+
+--- raw-empty ---
+// Empty raw block.
+Empty raw block:``.
+
+--- raw-consecutive-single-backticks ---
+// No extra space.
+`A``B`
+
+--- raw-typst-lang ---
+// Typst syntax inside.
+```typ #let x = 1``` \
+```typ #f(1)```
+
+--- raw-block-no-parbreaks ---
+// Multiline block splits paragraphs.
+
+Text
+```rust
+fn code() {}
+```
+Text
+
+--- raw-more-backticks ---
+// Lots of backticks inside.
+````
+```backticks```
+````
+
+--- raw-trimming ---
+// Trimming.
+
+// Space between "rust" and "let" is trimmed.
+The keyword ```rust let```.
+
+// Trimming depends on number backticks.
+(``) \
+(` untrimmed `) \
+(``` trimmed` ```) \
+(``` trimmed ```) \
+(``` trimmed```) \
+
+--- raw-single-backtick-lang ---
+// Single ticks should not have a language.
+`rust let`
+
+--- raw-dedent-first-line ---
+// First line is not dedented and leading space is still possible.
+ ``` A
+ B
+ C
+ ```
+
+--- raw-dedent-empty-line ---
+// Do not take empty lines into account when computing dedent.
+```
+ A
+
+ B
+```
+
+--- raw-dedent-last-line ---
+// Take last line into account when computing dedent.
+```
+ A
+
+ B
+ ```
+
+--- raw-tab-size ---
+#set raw(tab-size: 8)
+
+```tsv
+Year Month Day
+2000 2 3
+2001 2 1
+2002 3 10
+```
+
+--- raw-syntaxes ---
+#set page(width: 180pt)
+#set text(6pt)
+#set raw(syntaxes: "/assets/syntaxes/SExpressions.sublime-syntax")
+
+```sexp
+(defun factorial (x)
+ (if (zerop x)
+ ; with a comment
+ 1
+ (* x (factorial (- x 1)))))
+```
+
+
+--- raw-theme ---
+// Test code highlighting with custom theme.
+#set page(width: 180pt)
+#set text(6pt)
+#set raw(theme: "/assets/themes/halcyon.tmTheme")
+#show raw: it => {
+ set text(fill: rgb("a2aabc"))
+ rect(
+ width: 100%,
+ inset: (x: 4pt, y: 5pt),
+ radius: 4pt,
+ fill: rgb("1d2433"),
+ place(right, text(luma(240), it.lang)) + it,
+ )
+}
+
+```typ
+= Chapter 1
+#lorem(100)
+
+#let hi = "Hello World"
+#show heading: emph
+```
+
+--- raw-show-set ---
+// Text show rule
+#show raw: set text(font: "Roboto")
+`Roboto`
+
+--- raw-align-default ---
+// Text inside raw block should be unaffected by outer alignment by default.
+#set align(center)
+#set page(width: 180pt)
+#set text(6pt)
+
+#lorem(20)
+
+```py
+def something(x):
+ return x
+
+a = 342395823859823958329
+b = 324923
+```
+
+#lorem(20)
+
+--- raw-align-specified ---
+// Text inside raw block should follow the specified alignment.
+#set page(width: 180pt)
+#set text(6pt)
+
+#lorem(20)
+#align(center, raw(
+ lang: "typ",
+ block: true,
+ align: right,
+ "#let f(x) = x\n#align(center, line(length: 1em))",
+))
+#lorem(20)
+
+--- raw-align-invalid ---
+// Error: 17-20 expected `start`, `left`, `center`, `right`, or `end`, found top
+#set raw(align: top)
+
+--- raw-highlight-typ ---
+// LARGE
+#set page(width: auto)
+
+```typ
+#set hello()
+#set hello()
+#set hello.world()
+#set hello.my.world()
+#let foo(x) = x * 2
+#show heading: func
+#show module.func: func
+#show module.func: it => {}
+#foo(ident: ident)
+#hello
+#hello()
+#box[]
+#hello.world
+#hello.world()
+#hello().world()
+#hello.my.world
+#hello.my.world()
+#hello.my().world
+#hello.my().world()
+#{ hello }
+#{ hello() }
+#{ hello.world() }
+$ hello $
+$ hello() $
+$ box[] $
+$ hello.world $
+$ hello.world() $
+$ hello.my.world() $
+$ f_zeta(x), f_zeta(x)/1 $
+$ emph(hello.my.world()) $
+$ emph(hello.my().world) $
+$ emph(hello.my().world()) $
+$ #hello $
+$ #hello() $
+$ #hello.world $
+$ #hello.world() $
+$ #box[] $
+#if foo []
+```
+
+--- raw-highlight ---
+#set page(width: 180pt)
+#set text(6pt)
+#show raw: it => rect(
+ width: 100%,
+ inset: (x: 4pt, y: 5pt),
+ radius: 4pt,
+ fill: rgb(239, 241, 243),
+ place(right, text(luma(110), it.lang)) + it,
+)
+
+```typ
+= Chapter 1
+#lorem(100)
+
+#let hi = "Hello World"
+#show heading: emph
+```
+
+```rust
+/// A carefully designed state machine.
+#[derive(Debug)]
+enum State<'a> { A(u8), B(&'a str) }
+
+fn advance(state: State<'_>) -> State<'_> {
+ unimplemented!("state machine")
+}
+```
+
+```py
+import this
+
+def hi():
+ print("Hi!")
+```
+
+```cpp
+#include <iostream>
+
+int main() {
+ std::cout << "Hello, world!";
+}
+```
+
+```julia
+# Add two numbers
+function add(x, y)
+ return x * y
+end
+```
+
+ // Try with some indent.
+ ```html
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <h1>Topic</h1>
+ <p>The Hypertext Markup Language.</p>
+ <script>
+ function foo(a, b) {
+ return a + b + "string";
+ }
+ </script>
+ </body>
+ </html>
+ ```
+
+--- raw-inline-multiline ---
+#set page(width: 180pt)
+#set text(6pt)
+#set raw(lang:"python")
+
+Inline raws, multiline e.g. `for i in range(10):
+ # Only this line is a comment.
+ print(i)` or otherwise e.g. `print(j)`, are colored properly.
+
+Inline raws, multiline e.g. `
+# Appears blocky due to linebreaks at the boundary.
+for i in range(10):
+ print(i)
+` or otherwise e.g. `print(j)`, are colored properly.
+
+--- raw-blocky ---
+// Test various raw parsing edge cases.
+#let empty = (
+ name: "empty",
+ input: ``,
+ text: "",
+)
+
+#let backtick = (
+ name: "backtick",
+ input: ``` ` ```,
+ text: "`",
+ block: false,
+)
+
+#let lang-backtick = (
+ name: "lang-backtick",
+ input: ```js ` ```,
+ lang: "js",
+ text: "`",
+ block: false,
+)
+
+// The language tag stops on space
+#let lang-space = (
+ name: "lang-space",
+ input: ```js test ```,
+ lang: "js",
+ text: "test ",
+ block: false,
+)
+
+// The language tag stops on newline
+#let lang-newline = (
+ name: "lang-newline",
+ input: ```js
+test
+```,
+ lang: "js",
+ text: "test",
+ block: true,
+)
+
+// The first line and the last line are ignored
+#let blocky = (
+ name: "blocky",
+ input: {
+```
+test
+```
+},
+ text: "test",
+ block: true,
+)
+
+// A blocky raw should handle dedents
+#let blocky-dedent = (
+ name: "blocky-dedent",
+ input: {
+```
+ test
+ ```
+ },
+ text: "test",
+ block: true,
+)
+
+// When there is content in the first line, it should exactly eat a whitespace char.
+#let blocky-dedent-firstline = (
+ name: "blocky-dedent-firstline",
+ input: ``` test
+ ```,
+ text: "test",
+ block: true,
+)
+
+// When there is content in the first line, it should exactly eat a whitespace char.
+#let blocky-dedent-firstline2 = (
+ name: "blocky-dedent-firstline2",
+ input: ``` test
+```,
+ text: "test",
+ block: true,
+)
+
+// The first line is not affected by dedent, and the middle lines don't consider the whitespace prefix of the first line.
+#let blocky-dedent-firstline3 = (
+ name: "blocky-dedent-firstline3",
+ input: ``` test
+ test2
+ ```,
+ text: "test\n test2",
+ block: true,
+)
+
+// The first line is not affected by dedent, and the middle lines don't consider the whitespace prefix of the first line.
+#let blocky-dedent-firstline4 = (
+ name: "blocky-dedent-firstline4",
+ input: ``` test
+ test2
+ ```,
+ text: " test\ntest2",
+ block: true,
+)
+
+#let blocky-dedent-lastline = (
+ name: "blocky-dedent-lastline",
+ input: ```
+ test
+ ```,
+ text: " test",
+ block: true,
+)
+
+#let blocky-dedent-lastline2 = (
+ name: "blocky-dedent-lastline2",
+ input: ```
+ test
+ ```,
+ text: "test",
+ block: true,
+)
+
+#let blocky-tab = (
+ name: "blocky-tab",
+ input: {
+```
+ test
+```
+},
+ text: "\ttest",
+ block: true,
+)
+
+// This one is a bit problematic because there is a trailing tab below "test"
+// which the editor constantly wants to remove.
+#let blocky-tab-dedent = (
+ name: "blocky-tab-dedent",
+ input: eval("```\n\ttest\n \n ```"),
+ text: "test\n ",
+ block: true,
+)
+
+#let cases = (
+ empty,
+ backtick,
+ lang-backtick,
+ lang-space,
+ lang-newline,
+ blocky,
+ blocky-dedent,
+ blocky-dedent-firstline,
+ blocky-dedent-firstline2,
+ blocky-dedent-firstline3,
+ blocky-dedent-lastline,
+ blocky-dedent-lastline2,
+ blocky-tab,
+ blocky-tab-dedent,
+)
+
+#for c in cases {
+ assert.eq(c.text, c.input.text, message: "in point " + c.name + ", expect " + repr(c.text) + ", got " + repr(c.input.text) + "")
+ let block = c.at("block", default: false)
+ assert.eq(block, c.input.block, message: "in point " + c.name + ", expect " + repr(block) + ", got " + repr(c.input.block) + "")
+}
+
+--- raw-line ---
+#set page(width: 200pt)
+
+```rs
+fn main() {
+ println!("Hello, world!");
+}
+```
+
+#show raw.line: it => {
+ box(stack(
+ dir: ltr,
+ box(width: 15pt)[#it.number],
+ it.body,
+ ))
+ linebreak()
+}
+
+```rs
+fn main() {
+ println!("Hello, world!");
+}
+```
+
+--- raw-line-alternating-fill ---
+#set page(width: 200pt)
+#show raw: it => stack(dir: ttb, ..it.lines)
+#show raw.line: it => {
+ box(
+ width: 100%,
+ height: 1.75em,
+ inset: 0.25em,
+ fill: if calc.rem(it.number, 2) == 0 {
+ luma(90%)
+ } else {
+ white
+ },
+ align(horizon, stack(
+ dir: ltr,
+ box(width: 15pt)[#it.number],
+ it.body,
+ ))
+ )
+}
+
+```typ
+#show raw.line: block.with(
+ fill: luma(60%)
+);
+
+Hello, world!
+
+= A heading for good measure
+```
+
+--- raw-line-text-fill ---
+#set page(width: 200pt)
+#show raw.line: set text(fill: red)
+
+```py
+import numpy as np
+
+def f(x):
+ return x**2
+
+x = np.linspace(0, 10, 100)
+y = f(x)
+
+print(x)
+print(y)
+```
+
+--- raw-line-scripting ---
+
+// Test line extraction works.
+
+#show raw: code => {
+ for i in code.lines {
+ test(i.count, 10)
+ }
+
+ test(code.lines.at(0).text, "import numpy as np")
+ test(code.lines.at(1).text, "")
+ test(code.lines.at(2).text, "def f(x):")
+ test(code.lines.at(3).text, " return x**2")
+ test(code.lines.at(4).text, "")
+ test(code.lines.at(5).text, "x = np.linspace(0, 10, 100)")
+ test(code.lines.at(6).text, "y = f(x)")
+ test(code.lines.at(7).text, "")
+ test(code.lines.at(8).text, "print(x)")
+ test(code.lines.at(9).text, "print(y)")
+ test(code.lines.at(10, default: none), none)
+}
+
+```py
+import numpy as np
+
+def f(x):
+ return x**2
+
+x = np.linspace(0, 10, 100)
+y = f(x)
+
+print(x)
+print(y)
+```
+
+--- issue-3601-empty-raw ---
+// Test that empty raw block with `typ` language doesn't cause a crash.
+```typ
+```
+
+--- issue-3841-tabs-in-raw-type-code ---
+// Tab chars were not rendered in raw blocks with lang: "typ(c)"
+#raw("#if true {\n\tf()\t// typ\n}", lang: "typ")
+
+#raw("if true {\n\tf()\t// typc\n}", lang: "typc")
+
+```typ
+#if true {
+ // tabs around f()
+ f() // typ
+}
+```
+
+```typc
+if true {
+ // tabs around f()
+ f() // typc
+}
+```
+
+--- issue-2259-raw-color-overwrite ---
+// Test that the color of a raw block is not overwritten
+#show raw: set text(fill: blue)
+
+`Hello, World!`
+
+```rs
+fn main() {
+ println!("Hello, World!");
+}
+```
+
+--- issue-3191-raw-indent-shrink ---
+// Spaces in raw blocks should not be shrunk as it would mess up the indentation
+// of code.
+#set par(justify: true)
+
+#show raw.where(block: true): block.with(
+ fill: luma(240),
+ inset: 10pt,
+)
+
+#block(
+ width: 60%,
+ ```py
+ for x in xs:
+ print("x=",x)
+ ```
+)
+
+--- issue-3191-raw-normal-paragraphs-still-shrink ---
+// In normal paragraphs, spaces should still be shrunk.
+// The first line here serves as a reference, while the second
+// uses non-breaking spaces to create an overflowing line
+// (which should shrink).
+~~~~No shrinking here
+
+~~~~The~spaces~on~this~line~shrink
+
+--- raw-unclosed ---
+// Unterminated.
+// Error: 1-2:1 unclosed raw text
+`endless
diff --git a/tests/suite/text/shift.typ b/tests/suite/text/shift.typ
new file mode 100644
index 00000000..090f6ee8
--- /dev/null
+++ b/tests/suite/text/shift.typ
@@ -0,0 +1,19 @@
+// Test sub- and superscipt shifts.
+
+--- sub-super ---
+#table(
+ columns: 3,
+ [Typo.], [Fallb.], [Synth],
+ [x#super[1]], [x#super[5n]], [x#super[2 #box(square(size: 6pt))]],
+ [x#sub[1]], [x#sub[5n]], [x#sub[2 #box(square(size: 6pt))]],
+)
+
+--- sub-super-non-typographic ---
+#set super(typographic: false, baseline: -0.25em, size: 0.7em)
+n#super[1], n#sub[2], ... n#super[N]
+
+--- super-underline ---
+#set underline(stroke: 0.5pt, offset: 0.15em)
+#underline[The claim#super[\[4\]]] has been disputed. \
+The claim#super[#underline[\[4\]]] has been disputed. \
+It really has been#super(box(text(baseline: 0pt, underline[\[4\]]))) \
diff --git a/tests/suite/text/smallcaps.typ b/tests/suite/text/smallcaps.typ
new file mode 100644
index 00000000..6f977244
--- /dev/null
+++ b/tests/suite/text/smallcaps.typ
@@ -0,0 +1,3 @@
+--- smallcaps ---
+// Test smallcaps.
+#smallcaps[Smallcaps]
diff --git a/tests/suite/text/smartquote.typ b/tests/suite/text/smartquote.typ
new file mode 100644
index 00000000..28fcba5b
--- /dev/null
+++ b/tests/suite/text/smartquote.typ
@@ -0,0 +1,122 @@
+--- smartquote ---
+// LARGE
+#set page(width: 250pt)
+
+// Test simple quotations in various languages.
+#set text(lang: "en")
+"The horse eats no cucumber salad" was the first sentence ever uttered on the 'telephone.'
+
+#set text(lang: "de")
+"Das Pferd frisst keinen Gurkensalat" war der erste jemals am 'Fernsprecher' gesagte Satz.
+
+#set text(lang: "de", region: "CH")
+"Das Pferd frisst keinen Gurkensalat" war der erste jemals am 'Fernsprecher' gesagte Satz.
+
+#set text(lang: "es", region: none)
+"El caballo no come ensalada de pepino" fue la primera frase pronunciada por 'teléfono'.
+
+#set text(lang: "es", region: "MX")
+"El caballo no come ensalada de pepino" fue la primera frase pronunciada por 'teléfono'.
+
+#set text(lang: "fr", region: none)
+"Le cheval ne mange pas de salade de concombres" est la première phrase jamais prononcée au 'téléphone'.
+
+#set text(lang: "fi")
+"Hevonen ei syö kurkkusalaattia" oli ensimmäinen koskaan 'puhelimessa' lausuttu lause.
+
+#set text(lang: "gr")
+"Το άλογο δεν τρώει αγγουροσαλάτα" ήταν η πρώτη πρόταση που ειπώθηκε στο 'τηλέφωνο'.
+
+#set text(lang: "he")
+"הסוס לא אוכל סלט מלפפונים" היה המשפט ההראשון שנאמר ב 'טלפון'.
+
+#set text(lang: "ro")
+"Calul nu mănâncă salată de castraveți" a fost prima propoziție rostită vreodată la 'telefon'.
+
+#set text(lang: "ru")
+"Лошадь не ест салат из огурцов" - это была первая фраза, сказанная по 'телефону'.
+
+--- smartquote-empty ---
+// Test single pair of quotes.
+""
+
+--- smartquote-apostrophe ---
+// Test sentences with numbers and apostrophes.
+The 5'11" 'quick' brown fox jumps over the "lazy" dog's ear.
+
+He said "I'm a big fella."
+
+--- smartquote-escape ---
+// Test escape sequences.
+The 5\'11\" 'quick\' brown fox jumps over the \"lazy" dog\'s ear.
+
+--- smartquote-disable ---
+// Test turning smart quotes off.
+He's told some books contain questionable "example text".
+
+#set smartquote(enabled: false)
+He's told some books contain questionable "example text".
+
+--- smartquote-disabled-temporarily ---
+// Test changing properties within text.
+"She suddenly started speaking french: #text(lang: "fr")['Je suis une banane.']" Roman told me.
+
+Some people's thought on this would be #[#set smartquote(enabled: false); "strange."]
+
+--- smartquote-nesting ---
+// Test nested double and single quotes.
+"'test statement'" \
+"'test' statement" \
+"statement 'test'"
+
+--- smartquote-custom ---
+// Use language quotes for missing keys, allow partial reset
+#set smartquote(quotes: "«»")
+"Double and 'Single' Quotes"
+
+#set smartquote(quotes: (double: auto, single: "«»"))
+"Double and 'Single' Quotes"
+
+--- smartquote-custom-complex ---
+// Allow 2 graphemes
+#set smartquote(quotes: "a\u{0301}a\u{0301}")
+"Double and 'Single' Quotes"
+
+#set smartquote(quotes: (single: "a\u{0301}a\u{0301}"))
+"Double and 'Single' Quotes"
+
+--- smartquote-custom-bad-string ---
+// Error: 25-28 expected 2 characters, found 1 character
+#set smartquote(quotes: "'")
+
+--- smartquote-custom-bad-array ---
+// Error: 25-35 expected 2 quotes, found 4 quotes
+#set smartquote(quotes: ("'",) * 4)
+
+--- smartquote-custom-bad-dict ---
+// Error: 25-45 expected 2 quotes, found 4 quotes
+#set smartquote(quotes: (single: ("'",) * 4))
+
+--- issue-3662-pdf-smartquotes ---
+// Smart quotes were not appearing in the PDF outline, because they didn't
+// implement `PlainText`.
+= It's "Unnormal Heading"
+= It’s “Normal Heading”
+
+#set smartquote(enabled: false)
+= It's "Unnormal Heading"
+= It's 'single quotes'
+= It’s “Normal Heading”
+
+--- issue-1041-smartquotes-in-outline ---
+#set page(width: 15em)
+#outline()
+
+= "This" "is" "a" "test"
+
+--- issue-1540-smartquotes-across-newlines ---
+// Test that smart quotes are inferred correctly across newlines.
+"test"#linebreak()"test"
+
+"test"\
+"test"
diff --git a/tests/suite/text/space.typ b/tests/suite/text/space.typ
new file mode 100644
index 00000000..97541e38
--- /dev/null
+++ b/tests/suite/text/space.typ
@@ -0,0 +1,60 @@
+// Test whitespace handling.
+
+--- space-collapsing ---
+// Spacing around code constructs.
+A#let x = 1;B #test(x, 1) \
+C #let x = 2;D #test(x, 2) \
+E#if true [F]G \
+H #if true{"I"} J \
+K #if true [L] else []M \
+#let c = true; N#while c [#(c = false)O] P \
+#let c = true; Q #while c { c = false; "R" } S \
+T#for _ in (none,) {"U"}V
+#let foo = "A" ; \
+#foo;B \
+#foo; B \
+#foo ;B
+
+--- space-collapsing-comments ---
+// Test spacing with comments.
+A/**/B/**/C \
+A /**/ B/**/C \
+A /**/B/**/ C
+
+--- space-collapsing-with-h ---
+// Test spacing collapsing before spacing.
+#set align(right)
+A #h(0pt) B #h(0pt) \
+A B \
+A #h(-1fr) B
+
+--- text-font-just-a-space ---
+// Test that a run consisting only of whitespace isn't trimmed.
+A#text(font: "IBM Plex Serif")[ ]B
+
+--- text-font-change-after-space ---
+// Test font change after space.
+Left #text(font: "IBM Plex Serif")[Right].
+
+--- space-collapsing-linebreaks ---
+// Test that linebreak consumed surrounding spaces.
+#align(center)[A \ B \ C]
+
+--- space-collapsing-stringy-linebreak ---
+// Test that space at start of non-backslash-linebreak line isn't trimmed.
+A#"\n" B
+
+--- space-trailing-linebreak ---
+// Test that trailing space does not force a line break.
+LLLLLLLLLLLLLLLLLL R _L_
+
+--- space-ideographic-kept ---
+// Test that ideographic spaces are preserved.
+#set text(lang: "ja", font: "Noto Serif CJK JP")
+
+だろうか? 何のために! 私は、
+
+--- space-thin-kept ---
+// Test that thin spaces are preserved.
+| | U+0020 regular space \
+| | U+2009 thin space
diff --git a/tests/suite/visualize/circle.typ b/tests/suite/visualize/circle.typ
new file mode 100644
index 00000000..43459eb5
--- /dev/null
+++ b/tests/suite/visualize/circle.typ
@@ -0,0 +1,69 @@
+// Test the `circle` function.
+
+--- circle ---
+// Default circle.
+#box(circle())
+#box(circle[Hey])
+
+--- circle-auto-sizing ---
+// Test auto sizing.
+#set circle(inset: 0pt)
+
+Auto-sized circle.
+#circle(fill: rgb("eb5278"), stroke: 2pt + black,
+ align(center + horizon)[But, soft!]
+)
+
+Center-aligned rect in auto-sized circle.
+#circle(fill: forest, stroke: conifer,
+ align(center + horizon,
+ rect(fill: conifer, inset: 5pt)[But, soft!]
+ )
+)
+
+Rect in auto-sized circle.
+#circle(fill: forest,
+ rect(fill: conifer, stroke: white, inset: 4pt)[
+ #set text(8pt)
+ But, soft! what light through yonder window breaks?
+ ]
+)
+
+Expanded by height.
+#circle(stroke: black, align(center)[A \ B \ C])
+
+--- circle-directly-in-rect ---
+// Ensure circle directly in rect works.
+#rect(width: 40pt, height: 30pt, fill: forest,
+ circle(fill: conifer))
+
+--- circle-relative-sizing ---
+// Test relative sizing.
+#set text(fill: white)
+#show: rect.with(width: 100pt, height: 50pt, inset: 0pt, fill: rgb("aaa"))
+#set align(center + horizon)
+#stack(
+ dir: ltr,
+ spacing: 1fr,
+ 1fr,
+ circle(radius: 10pt, fill: eastern, [A]), // D=20pt
+ circle(height: 60%, fill: eastern, [B]), // D=30pt
+ circle(width: 20% + 20pt, fill: eastern, [C]), // D=40pt
+ 1fr,
+)
+
+--- circle-radius-width-and-height ---
+// Radius wins over width and height.
+// Error: 23-34 unexpected argument: width
+#circle(radius: 10pt, width: 50pt, height: 100pt, fill: eastern)
+
+--- circle-sizing-options ---
+// Test different ways of sizing.
+#set page(width: 120pt, height: 40pt)
+#stack(
+ dir: ltr,
+ spacing: 2pt,
+ circle(radius: 5pt),
+ circle(width: 10%),
+ circle(height: 50%),
+)
diff --git a/tests/suite/visualize/color.typ b/tests/suite/visualize/color.typ
new file mode 100644
index 00000000..6cf887a4
--- /dev/null
+++ b/tests/suite/visualize/color.typ
@@ -0,0 +1,331 @@
+// Test color modification methods.
+
+--- color-mix ---
+// Compare both ways.
+#test-repr(rgb(0%, 30.2%, 70.2%), rgb("004db3"))
+
+// Alpha channel.
+#test(rgb(255, 0, 0, 50%), rgb("ff000080"))
+
+// Test color modification methods.
+#test(rgb(25, 35, 45).lighten(10%), rgb(48, 57, 66))
+#test(rgb(40, 30, 20).darken(10%), rgb(36, 27, 18))
+#test(rgb("#133337").negate(space: rgb), rgb(236, 204, 200))
+#test(white.lighten(100%), white)
+
+// Color mixing, in Oklab space by default.
+#test(rgb(color.mix(rgb("#ff0000"), rgb("#00ff00"))), rgb("#d0a800"))
+#test(rgb(color.mix(rgb("#ff0000"), rgb("#00ff00"), space: oklab)), rgb("#d0a800"))
+#test(rgb(color.mix(rgb("#ff0000"), rgb("#00ff00"), space: rgb)), rgb("#808000"))
+
+#test(rgb(color.mix(red, green, blue)), rgb("#909282"))
+#test(rgb(color.mix(red, blue, green)), rgb("#909282"))
+#test(rgb(color.mix(blue, red, green)), rgb("#909282"))
+
+// Mix with weights.
+#test(rgb(color.mix((red, 50%), (green, 50%))), rgb("#c0983b"))
+#test(rgb(color.mix((red, 0.5), (green, 0.5))), rgb("#c0983b"))
+#test(rgb(color.mix((red, 5), (green, 5))), rgb("#c0983b"))
+#test(rgb(color.mix((green, 5), (white, 0), (red, 5))), rgb("#c0983b"))
+#test(color.mix((rgb("#aaff00"), 25%), (rgb("#aa00ff"), 75%), space: rgb), rgb("#aa40bf"))
+#test(color.mix((rgb("#aaff00"), 50%), (rgb("#aa00ff"), 50%), space: rgb), rgb("#aa8080"))
+#test(color.mix((rgb("#aaff00"), 75%), (rgb("#aa00ff"), 25%), space: rgb), rgb("#aabf40"))
+
+// Mix in hue-based space.
+#test(rgb(color.mix(red, blue, space: color.hsl)), rgb("#c408ff"))
+#test(rgb(color.mix((red, 50%), (blue, 100%), space: color.hsl)), rgb("#5100f8"))
+// Error: 6-51 cannot mix more than two colors in a hue-based space
+#rgb(color.mix(red, blue, white, space: color.hsl))
+
+--- color-conversion ---
+// Test color conversion method kinds
+#test(rgb(rgb(10, 20, 30)).space(), rgb)
+#test(color.linear-rgb(rgb(10, 20, 30)).space(), color.linear-rgb)
+#test(oklab(rgb(10, 20, 30)).space(), oklab)
+#test(oklch(rgb(10, 20, 30)).space(), oklch)
+#test(color.hsl(rgb(10, 20, 30)).space(), color.hsl)
+#test(color.hsv(rgb(10, 20, 30)).space(), color.hsv)
+#test(cmyk(rgb(10, 20, 30)).space(), cmyk)
+#test(luma(rgb(10, 20, 30)).space(), luma)
+
+#test(rgb(color.linear-rgb(10, 20, 30)).space(), rgb)
+#test(color.linear-rgb(color.linear-rgb(10, 20, 30)).space(), color.linear-rgb)
+#test(oklab(color.linear-rgb(10, 20, 30)).space(), oklab)
+#test(oklch(color.linear-rgb(10, 20, 30)).space(), oklch)
+#test(color.hsl(color.linear-rgb(10, 20, 30)).space(), color.hsl)
+#test(color.hsv(color.linear-rgb(10, 20, 30)).space(), color.hsv)
+#test(cmyk(color.linear-rgb(10, 20, 30)).space(), cmyk)
+#test(luma(color.linear-rgb(10, 20, 30)).space(), luma)
+
+#test(rgb(oklab(10%, 20%, 30%)).space(), rgb)
+#test(color.linear-rgb(oklab(10%, 20%, 30%)).space(), color.linear-rgb)
+#test(oklab(oklab(10%, 20%, 30%)).space(), oklab)
+#test(oklch(oklab(10%, 20%, 30%)).space(), oklch)
+#test(color.hsl(oklab(10%, 20%, 30%)).space(), color.hsl)
+#test(color.hsv(oklab(10%, 20%, 30%)).space(), color.hsv)
+#test(cmyk(oklab(10%, 20%, 30%)).space(), cmyk)
+#test(luma(oklab(10%, 20%, 30%)).space(), luma)
+
+#test(rgb(oklch(60%, 40%, 0deg)).space(), rgb)
+#test(color.linear-rgb(oklch(60%, 40%, 0deg)).space(), color.linear-rgb)
+#test(oklab(oklch(60%, 40%, 0deg)).space(), oklab)
+#test(oklch(oklch(60%, 40%, 0deg)).space(), oklch)
+#test(color.hsl(oklch(60%, 40%, 0deg)).space(), color.hsl)
+#test(color.hsv(oklch(60%, 40%, 0deg)).space(), color.hsv)
+#test(cmyk(oklch(60%, 40%, 0deg)).space(), cmyk)
+#test(luma(oklch(60%, 40%, 0deg)).space(), luma)
+
+#test(rgb(color.hsl(10deg, 20%, 30%)).space(), rgb)
+#test(color.linear-rgb(color.hsl(10deg, 20%, 30%)).space(), color.linear-rgb)
+#test(oklab(color.hsl(10deg, 20%, 30%)).space(), oklab)
+#test(oklch(color.hsl(10deg, 20%, 30%)).space(), oklch)
+#test(color.hsl(color.hsl(10deg, 20%, 30%)).space(), color.hsl)
+#test(color.hsv(color.hsl(10deg, 20%, 30%)).space(), color.hsv)
+#test(cmyk(color.hsl(10deg, 20%, 30%)).space(), cmyk)
+#test(luma(color.hsl(10deg, 20%, 30%)).space(), luma)
+
+#test(rgb(color.hsv(10deg, 20%, 30%)).space(), rgb)
+#test(color.linear-rgb(color.hsv(10deg, 20%, 30%)).space(), color.linear-rgb)
+#test(oklab(color.hsv(10deg, 20%, 30%)).space(), oklab)
+#test(oklch(color.hsv(10deg, 20%, 30%)).space(), oklch)
+#test(color.hsl(color.hsv(10deg, 20%, 30%)).space(), color.hsl)
+#test(color.hsv(color.hsv(10deg, 20%, 30%)).space(), color.hsv)
+#test(cmyk(color.hsv(10deg, 20%, 30%)).space(), cmyk)
+#test(luma(color.hsv(10deg, 20%, 30%)).space(), luma)
+
+#test(rgb(cmyk(10%, 20%, 30%, 40%)).space(), rgb)
+#test(color.linear-rgb(cmyk(10%, 20%, 30%, 40%)).space(), color.linear-rgb)
+#test(oklab(cmyk(10%, 20%, 30%, 40%)).space(), oklab)
+#test(oklch(cmyk(10%, 20%, 30%, 40%)).space(), oklch)
+#test(color.hsl(cmyk(10%, 20%, 30%, 40%)).space(), color.hsl)
+#test(color.hsv(cmyk(10%, 20%, 30%, 40%)).space(), color.hsv)
+#test(cmyk(cmyk(10%, 20%, 30%, 40%)).space(), cmyk)
+#test(luma(cmyk(10%, 20%, 30%, 40%)).space(), luma)
+
+#test(rgb(luma(10%)).space(), rgb)
+#test(color.linear-rgb(luma(10%)).space(), color.linear-rgb)
+#test(oklab(luma(10%)).space(), oklab)
+#test(oklch(luma(10%)).space(), oklch)
+#test(color.hsl(luma(10%)).space(), color.hsl)
+#test(color.hsv(luma(10%)).space(), color.hsv)
+#test(cmyk(luma(10%)).space(), cmyk)
+#test(luma(luma(10%)).space(), luma)
+
+#test(rgb(1, 2, 3).to-hex(), "#010203")
+#test(rgb(1, 2, 3, 4).to-hex(), "#01020304")
+#test(luma(40).to-hex(), "#282828")
+#test-repr(cmyk(4%, 5%, 6%, 7%).to-hex(), "#e0dcda")
+#test-repr(rgb(cmyk(4%, 5%, 6%, 7%)), rgb(87.84%, 86.27%, 85.49%, 100%))
+#test-repr(rgb(luma(40%)), rgb(40%, 40%, 40%))
+#test-repr(cmyk(luma(40)), cmyk(11.76%, 10.67%, 10.51%, 14.12%))
+#test-repr(cmyk(rgb(1, 2, 3)), cmyk(66.67%, 33.33%, 0%, 98.82%))
+#test-repr(luma(rgb(1, 2, 3)), luma(0.73%))
+#test-repr(color.hsl(luma(40)), color.hsl(0deg, 0%, 15.69%))
+#test-repr(color.hsv(luma(40)), color.hsv(0deg, 0%, 15.69%))
+#test-repr(color.linear-rgb(luma(40)), color.linear-rgb(2.12%, 2.12%, 2.12%))
+#test-repr(color.linear-rgb(rgb(1, 2, 3)), color.linear-rgb(0.03%, 0.06%, 0.09%))
+#test-repr(color.hsl(rgb(1, 2, 3)), color.hsl(-150deg, 50%, 0.78%))
+#test-repr(color.hsv(rgb(1, 2, 3)), color.hsv(-150deg, 66.67%, 1.18%))
+#test-repr(oklab(luma(40)), oklab(27.68%, 0.0, 0.0, 100%))
+#test-repr(oklab(rgb(1, 2, 3)), oklab(8.23%, -0.004, -0.007, 100%))
+#test-repr(oklch(oklab(40%, 0.2, 0.2)), oklch(40%, 0.283, 45deg, 100%))
+#test-repr(oklch(luma(40)), oklch(27.68%, 0.0, 72.49deg, 100%))
+#test-repr(oklch(rgb(1, 2, 3)), oklch(8.23%, 0.008, 240.75deg, 100%))
+
+--- color-spaces ---
+// The the different color spaces
+#let col = rgb(50%, 64%, 16%)
+#box(square(size: 9pt, fill: col))
+#box(square(size: 9pt, fill: rgb(col)))
+#box(square(size: 9pt, fill: oklab(col)))
+#box(square(size: 9pt, fill: oklch(col)))
+#box(square(size: 9pt, fill: luma(col)))
+#box(square(size: 9pt, fill: cmyk(col)))
+#box(square(size: 9pt, fill: color.linear-rgb(col)))
+#box(square(size: 9pt, fill: color.hsl(col)))
+#box(square(size: 9pt, fill: color.hsv(col)))
+
+--- color-space ---
+// Test color kind method.
+#test(rgb(1, 2, 3, 4).space(), rgb)
+#test(cmyk(4%, 5%, 6%, 7%).space(), cmyk)
+#test(luma(40).space(), luma)
+#test(rgb(1, 2, 3, 4).space() != luma, true)
+
+--- color-components ---
+// Test color '.components()' without conversions
+
+#let test-components(col, ref, has-alpha: true) = {
+ // Perform an approximate scalar comparison.
+ let are-equal((a, b)) = {
+ let to-float(x) = if type(x) == angle { x.rad() } else { float(x) }
+ let epsilon = 1e-4 // The maximum error between both numbers
+ assert.eq(type(a), type(b))
+ calc.abs(to-float(a) - to-float(b)) < epsilon
+ }
+
+ let ref-without-alpha = if has-alpha { ref.slice(0, -1) } else { ref }
+ assert.eq(col.components().len(), ref.len())
+ assert(col.components().zip(ref).all(are-equal))
+ assert(col.components(alpha: false).zip(ref-without-alpha).all(are-equal))
+}
+#test-components(rgb(1, 2, 3, 4), (0.39%, 0.78%, 1.18%, 1.57%))
+#test-components(luma(40), (15.69%, 100%))
+#test-components(luma(40, 50%), (15.69%, 50%))
+#test-components(cmyk(4%, 5%, 6%, 7%), (4%, 5%, 6%, 7%), has-alpha: false)
+#test-components(oklab(10%, 0.2, 0.4), (10%, 0.2, 0.4, 100%))
+#test-components(oklch(10%, 0.2, 90deg), (10%, 0.2, 90deg, 100%))
+#test-components(oklab(10%, 50%, 200%), (10%, 0.2, 0.8, 100%))
+#test-components(oklch(10%, 50%, 90deg), (10%, 0.2, 90deg, 100%))
+#test-components(color.linear-rgb(10%, 20%, 30%), (10%, 20%, 30%, 100%))
+#test-components(color.hsv(10deg, 20%, 30%), (10deg, 20%, 30%, 100%))
+#test-components(color.hsl(10deg, 20%, 30%), (10deg, 20%, 30%, 100%))
+
+--- color-luma ---
+// Test gray color conversion.
+#stack(dir: ltr, rect(fill: luma(0)), rect(fill: luma(80%)))
+
+--- color-rgb-out-of-range ---
+// Error for values that are out of range.
+// Error: 11-14 number must be between 0 and 255
+#test(rgb(-30, 15, 50))
+
+--- color-rgb-bad-string ---
+// Error: 6-11 color string contains non-hexadecimal letters
+#rgb("lol")
+
+--- color-rgb-missing-argument-red ---
+// Error: 2-7 missing argument: red component
+#rgb()
+
+--- color-rgb-missing-argument-blue ---
+// Error: 2-11 missing argument: blue component
+#rgb(0, 1)
+
+--- color-rgb-bad-type ---
+// Error: 21-26 expected integer or ratio, found boolean
+#rgb(10%, 20%, 30%, false)
+
+--- color-luma-unexpected-argument ---
+// Error: 10-20 unexpected argument: key
+#luma(1, key: "val")
+
+--- color-mix-bad-amount-type ---
+// Error: 12-24 expected float or ratio, found string
+// Error: 26-39 expected float or ratio, found string
+#color.mix((red, "yes"), (green, "no"), (green, 10%))
+
+--- color-mix-bad-value ---
+// Error: 12-23 expected a color or color-weight pair
+#color.mix((red, 1, 2))
+
+--- color-mix-bad-space-type ---
+// Error: 31-38 expected `rgb`, `luma`, `cmyk`, `oklab`, `oklch`, `color.linear-rgb`, `color.hsl`, or `color.hsv`, found string
+#color.mix(red, green, space: "cyber")
+
+--- color-mix-bad-space-value-1 ---
+// Error: 31-36 expected `rgb`, `luma`, `cmyk`, `oklab`, `oklch`, `color.linear-rgb`, `color.hsl`, or `color.hsv`
+#color.mix(red, green, space: image)
+
+--- color-mix-bad-space-value-2 ---
+// Error: 31-41 expected `rgb`, `luma`, `cmyk`, `oklab`, `oklch`, `color.linear-rgb`, `color.hsl`, or `color.hsv`
+#color.mix(red, green, space: calc.round)
+
+--- color-cmyk-ops ---
+// Test CMYK color conversion.
+#let c = cmyk(50%, 64%, 16%, 17%)
+#stack(
+ dir: ltr,
+ spacing: 1fr,
+ rect(width: 1cm, fill: cmyk(69%, 11%, 69%, 41%)),
+ rect(width: 1cm, fill: c),
+ rect(width: 1cm, fill: c.negate(space: cmyk)),
+)
+
+#for x in range(0, 11) {
+ box(square(size: 9pt, fill: c.lighten(x * 10%)))
+}
+#for x in range(0, 11) {
+ box(square(size: 9pt, fill: c.darken(x * 10%)))
+}
+
+--- color-outside-srgb-gamut ---
+// Colors outside the sRGB gamut.
+#box(square(size: 9pt, fill: oklab(90%, -0.2, -0.1)))
+#box(square(size: 9pt, fill: oklch(50%, 0.5, 0deg)))
+
+--- color-rotate-hue ---
+// Test hue rotation
+#let col = rgb(50%, 64%, 16%)
+
+// Oklch
+#for x in range(0, 11) {
+ box(square(size: 9pt, fill: rgb(col).rotate(x * 36deg)))
+}
+
+// HSL
+#for x in range(0, 11) {
+ box(square(size: 9pt, fill: rgb(col).rotate(x * 36deg, space: color.hsl)))
+}
+
+// HSV
+#for x in range(0, 11) {
+ box(square(size: 9pt, fill: rgb(col).rotate(x * 36deg, space: color.hsv)))
+}
+
+--- color-saturation ---
+// Test saturation
+#let col = color.hsl(180deg, 0%, 50%)
+#for x in range(0, 11) {
+ box(square(size: 9pt, fill: col.saturate(x * 10%)))
+}
+
+#let col = color.hsl(180deg, 100%, 50%)
+#for x in range(0, 11) {
+ box(square(size: 9pt, fill: col.desaturate(x * 10%)))
+}
+
+#let col = color.hsv(180deg, 0%, 50%)
+#for x in range(0, 11) {
+ box(square(size: 9pt, fill: col.saturate(x * 10%)))
+}
+
+#let col = color.hsv(180deg, 100%, 50%)
+#for x in range(0, 11) {
+ box(square(size: 9pt, fill: col.desaturate(x * 10%)))
+}
+
+--- color-luma-ops ---
+// Test gray color modification.
+#test-repr(luma(20%).lighten(50%), luma(60%))
+#test-repr(luma(80%).darken(20%), luma(64%))
+#test-repr(luma(80%).negate(space: luma), luma(20%))
+
+--- color-transparentize ---
+// Test alpha modification.
+#test-repr(luma(100%, 100%).transparentize(50%), luma(100%, 50%))
+#test-repr(luma(100%, 100%).transparentize(75%), luma(100%, 25%))
+#test-repr(luma(100%, 50%).transparentize(50%), luma(100%, 25%))
+#test-repr(luma(100%, 10%).transparentize(250%), luma(100%, 0%))
+#test-repr(luma(100%, 40%).transparentize(-50%), luma(100%, 70%))
+#test-repr(luma(100%, 0%).transparentize(-100%), luma(100%, 100%))
+
+--- color-opacify ---
+#test-repr(luma(100%, 50%).opacify(50%), luma(100%, 75%))
+#test-repr(luma(100%, 20%).opacify(100%), luma(100%, 100%))
+#test-repr(luma(100%, 100%).opacify(250%), luma(100%, 100%))
+#test-repr(luma(100%, 50%).opacify(-50%), luma(100%, 25%))
+#test-repr(luma(100%, 0%).opacify(0%), luma(100%, 0%))
+
+--- repr-color ---
+// Colors
+#set page(width: 400pt)
+#set text(0.8em)
+#blue \
+#color.linear-rgb(blue) \
+#oklab(blue) \
+#oklch(blue) \
+#cmyk(blue) \
+#color.hsl(blue) \
+#color.hsv(blue) \
+#luma(blue)
diff --git a/tests/suite/visualize/ellipse.typ b/tests/suite/visualize/ellipse.typ
new file mode 100644
index 00000000..970a795e
--- /dev/null
+++ b/tests/suite/visualize/ellipse.typ
@@ -0,0 +1,31 @@
+// Test the `ellipse` function.
+
+--- ellipse ---
+// Default ellipse.
+#ellipse()
+
+--- ellipse-auto-sizing ---
+#set rect(inset: 0pt)
+#set ellipse(inset: 0pt)
+
+Rect in ellipse in fixed rect.
+#rect(width: 3cm, height: 2cm, fill: rgb("2a631a"),
+ ellipse(fill: forest, width: 100%, height: 100%,
+ rect(fill: conifer, width: 100%, height: 100%,
+ align(center + horizon)[
+ Stuff inside an ellipse!
+ ]
+ )
+ )
+)
+
+Auto-sized ellipse.
+#ellipse(fill: conifer, stroke: 3pt + forest, inset: 3pt)[
+ #set text(8pt)
+ But, soft! what light through yonder window breaks?
+]
+
+
+An inline
+#box(ellipse(width: 8pt, height: 6pt, outset: (top: 3pt, rest: 5.5pt)))
+ellipse.
diff --git a/tests/suite/visualize/gradient.typ b/tests/suite/visualize/gradient.typ
new file mode 100644
index 00000000..1ee5489a
--- /dev/null
+++ b/tests/suite/visualize/gradient.typ
@@ -0,0 +1,631 @@
+--- gradient-linear-angled ---
+// Test gradients with direction.
+#set page(width: 90pt)
+#grid(
+ gutter: 3pt,
+ columns: 4,
+ ..range(0, 360, step: 15).map(i => box(
+ height: 15pt,
+ width: 15pt,
+ fill: gradient.linear(angle: i * 1deg, (red, 0%), (blue, 100%)),
+ ))
+)
+
+
+--- gradient-linear-oklab ---
+// The tests below test whether hue rotation works correctly.
+// Here we test in Oklab space for reference.
+#set page(
+ width: 100pt,
+ height: 30pt,
+ fill: gradient.linear(red, purple, space: oklab)
+)
+
+--- gradient-linear-oklch ---
+// Test in OkLCH space.
+#set page(
+ width: 100pt,
+ height: 30pt,
+ fill: gradient.linear(red, purple, space: oklch)
+)
+
+--- gradient-linear-hsv ---
+// Test in HSV space.
+#set page(
+ width: 100pt,
+ height: 30pt,
+ fill: gradient.linear(red, purple, space: color.hsv)
+)
+
+--- gradient-linear-hsl ---
+// Test in HSL space.
+#set page(
+ width: 100pt,
+ height: 30pt,
+ fill: gradient.linear(red, purple, space: color.hsl)
+)
+
+
+--- gradient-linear-relative-parent ---
+// The image should look as if there is a single gradient that is being used for
+// both the page and the rectangles.
+#let grad = gradient.linear(red, blue, green, purple, relative: "parent");
+#let my-rect = rect(width: 50%, height: 50%, fill: grad)
+#set page(
+ height: 50pt,
+ width: 50pt,
+ margin: 2.5pt,
+ fill: grad,
+ background: place(top + left, my-rect),
+)
+#place(top + right, my-rect)
+#place(bottom + center, rotate(45deg, my-rect))
+
+--- gradient-linear-relative-self ---
+// The image should look as if there are multiple gradients, one for each
+// rectangle.
+#let grad = gradient.linear(red, blue, green, purple, relative: "self");
+#let my-rect = rect(width: 50%, height: 50%, fill: grad)
+#set page(
+ height: 50pt,
+ width: 50pt,
+ margin: 2.5pt,
+ fill: grad,
+ background: place(top + left, my-rect),
+)
+#place(top + right, my-rect)
+#place(bottom + center, rotate(45deg, my-rect))
+
+--- gradient-linear-repeat-and-mirror-1 ---
+// Test repeated gradients.
+#rect(
+ height: 40pt,
+ width: 100%,
+ fill: gradient.linear(..color.map.inferno).repeat(2, mirror: true)
+)
+
+--- gradient-linear-repeat-and-mirror-2 ---
+#rect(
+ height: 40pt,
+ width: 100%,
+ fill: gradient.linear(..color.map.rainbow).repeat(2, mirror: true),
+)
+
+--- gradient-linear-repeat-and-mirror-3 ---
+#rect(
+ height: 40pt,
+ width: 100%,
+ fill: gradient.linear(..color.map.rainbow).repeat(5, mirror: true)
+)
+
+--- gradient-linear-sharp-and-repeat ---
+#rect(
+ height: 40pt,
+ width: 100%,
+ fill: gradient.linear(..color.map.rainbow).sharp(10).repeat(5, mirror: false)
+)
+
+--- gradient-linear-sharp-repeat-and-mirror ---
+#rect(
+ height: 40pt,
+ width: 100%,
+ fill: gradient.linear(..color.map.rainbow).sharp(10).repeat(5, mirror: true)
+)
+
+--- gradient-linear-sharp ---
+#square(
+ size: 100pt,
+ fill: gradient.linear(..color.map.rainbow, space: color.hsl).sharp(10),
+)
+#square(
+ size: 100pt,
+ fill: gradient.radial(..color.map.rainbow, space: color.hsl).sharp(10),
+)
+#square(
+ size: 100pt,
+ fill: gradient.conic(..color.map.rainbow, space: color.hsl).sharp(10),
+)
+
+--- gradient-linear-sharp-and-smooth ---
+#square(
+ size: 100pt,
+ fill: gradient.linear(..color.map.rainbow, space: color.hsl).sharp(10, smoothness: 40%),
+)
+#square(
+ size: 100pt,
+ fill: gradient.radial(..color.map.rainbow, space: color.hsl).sharp(10, smoothness: 40%),
+)
+#square(
+ size: 100pt,
+ fill: gradient.conic(..color.map.rainbow, space: color.hsl).sharp(10, smoothness: 40%),
+)
+
+--- gradient-linear-stroke ---
+#align(center + top, square(size: 50pt, fill: black, stroke: 5pt + gradient.linear(red, blue)))
+
+--- gradient-fill-and-stroke ---
+#align(
+ center + bottom,
+ square(
+ size: 50pt,
+ fill: gradient.radial(red, blue, radius: 70.7%, focal-center: (10%, 10%)),
+ stroke: 10pt + gradient.radial(red, blue, radius: 70.7%, focal-center: (10%, 10%))
+ )
+)
+
+--- gradient-linear-line ---
+// Test gradient on lines
+#set page(width: 100pt, height: 100pt)
+#line(length: 100%, stroke: 1pt + gradient.linear(red, blue))
+#line(length: 100%, angle: 10deg, stroke: 1pt + gradient.linear(red, blue))
+#line(length: 100%, angle: 10deg, stroke: 1pt + gradient.linear(red, blue, relative: "parent"))
+
+--- gradient-radial-hsl ---
+#square(
+ size: 100pt,
+ fill: gradient.radial(..color.map.rainbow, space: color.hsl),
+)
+
+--- gradient-radial-center ---
+#grid(
+ columns: 2,
+ square(
+ size: 50pt,
+ fill: gradient.radial(..color.map.rainbow, space: color.hsl, center: (0%, 0%)),
+ ),
+ square(
+ size: 50pt,
+ fill: gradient.radial(..color.map.rainbow, space: color.hsl, center: (0%, 100%)),
+ ),
+ square(
+ size: 50pt,
+ fill: gradient.radial(..color.map.rainbow, space: color.hsl, center: (100%, 0%)),
+ ),
+ square(
+ size: 50pt,
+ fill: gradient.radial(..color.map.rainbow, space: color.hsl, center: (100%, 100%)),
+ ),
+)
+
+--- gradient-radial-radius ---
+#square(
+ size: 50pt,
+ fill: gradient.radial(..color.map.rainbow, space: color.hsl, radius: 10%),
+)
+#square(
+ size: 50pt,
+ fill: gradient.radial(..color.map.rainbow, space: color.hsl, radius: 72%),
+)
+
+--- gradient-radial-focal-center-and-radius ---
+#circle(
+ radius: 25pt,
+ fill: gradient.radial(white, rgb("#8fbc8f"), focal-center: (35%, 35%), focal-radius: 5%),
+)
+#circle(
+ radius: 25pt,
+ fill: gradient.radial(white, rgb("#8fbc8f"), focal-center: (75%, 35%), focal-radius: 5%),
+)
+
+--- gradient-radial-relative-parent ---
+// The image should look as if there is a single gradient that is being used for
+// both the page and the rectangles.
+#let grad = gradient.radial(red, blue, green, purple, relative: "parent");
+#let my-rect = rect(width: 50%, height: 50%, fill: grad)
+#set page(
+ height: 50pt,
+ width: 50pt,
+ margin: 2.5pt,
+ fill: grad,
+ background: place(top + left, my-rect),
+)
+#place(top + right, my-rect)
+#place(bottom + center, rotate(45deg, my-rect))
+
+--- gradient-radial-relative-self ---
+// The image should look as if there are multiple gradients, one for each
+// rectangle.
+#let grad = gradient.radial(red, blue, green, purple, relative: "self");
+#let my-rect = rect(width: 50%, height: 50%, fill: grad)
+#set page(
+ height: 50pt,
+ width: 50pt,
+ margin: 2.5pt,
+ fill: grad,
+ background: place(top + left, my-rect),
+)
+#place(top + right, my-rect)
+#place(bottom + center, rotate(45deg, my-rect))
+
+--- gradient-radial-text ---
+// Test that gradient fills on text.
+// The solid bar gradients are used to make sure that all transforms are
+// correct: if you can see the text through the bar, then the gradient is
+// misaligned to its reference container.
+#set page(width: 200pt, height: auto, margin: 10pt)
+#set par(justify: true)
+#set text(fill: gradient.radial(red, blue))
+#lorem(30)
+
+--- gradient-conic ---
+#square(
+ size: 50pt,
+ fill: gradient.conic(..color.map.rainbow, space: color.hsv),
+)
+
+--- gradient-conic-center-shifted-1 ---
+#square(
+ size: 50pt,
+ fill: gradient.conic(..color.map.rainbow, space: color.hsv, center: (10%, 10%)),
+)
+
+--- gradient-conic-center-shifted-2 ---
+#square(
+ size: 50pt,
+ fill: gradient.conic(..color.map.rainbow, space: color.hsv, center: (90%, 90%)),
+)
+
+--- gradient-conic-angled ---
+#square(
+ size: 50pt,
+ fill: gradient.conic(..color.map.rainbow, space: color.hsv, angle: 90deg),
+)
+
+--- gradient-conic-oklab ---
+// Test in Oklab space for reference.
+#set page(
+ width: 100pt,
+ height: 100pt,
+ fill: gradient.conic(red, purple, space: oklab)
+)
+
+--- gradient-conic-oklch ---
+// Test in OkLCH space.
+#set page(
+ width: 100pt,
+ height: 100pt,
+ fill: gradient.conic(red, purple, space: oklch)
+)
+
+--- gradient-conic-hsv ---
+// Test in HSV space.
+#set page(
+ width: 100pt,
+ height: 100pt,
+ fill: gradient.conic(red, purple, space: color.hsv)
+)
+
+--- gradient-conic-hsl ---
+// Test in HSL space.
+#set page(
+ width: 100pt,
+ height: 100pt,
+ fill: gradient.conic(red, purple, space: color.hsl)
+)
+
+--- gradient-conic-relative-parent ---
+// The image should look as if there is a single gradient that is being used for
+// both the page and the rectangles.
+#let grad = gradient.conic(red, blue, green, purple, relative: "parent");
+#let my-rect = rect(width: 50%, height: 50%, fill: grad)
+#set page(
+ height: 50pt,
+ width: 50pt,
+ margin: 2.5pt,
+ fill: grad,
+ background: place(top + left, my-rect),
+)
+#place(top + right, my-rect)
+#place(bottom + center, rotate(45deg, my-rect))
+
+--- gradient-conic-relative-self ---
+// The image should look as if there are multiple gradients, one for each
+// rectangle.
+#let grad = gradient.conic(red, blue, green, purple, relative: "self");
+#let my-rect = rect(width: 50%, height: 50%, fill: grad)
+#set page(
+ height: 50pt,
+ width: 50pt,
+ margin: 2.5pt,
+ fill: grad,
+ background: place(top + left, my-rect),
+)
+#place(top + right, my-rect)
+#place(bottom + center, rotate(45deg, my-rect))
+
+--- gradient-conic-stroke ---
+#align(
+ center + bottom,
+ square(
+ size: 50pt,
+ fill: black,
+ stroke: 10pt + gradient.conic(red, blue)
+ )
+)
+
+--- gradient-conic-text ---
+#set page(width: 200pt, height: auto, margin: 10pt)
+#set par(justify: true)
+#set text(fill: gradient.conic(red, blue, angle: 45deg))
+#lorem(30)
+
+--- gradient-text-bad-relative ---
+// Make sure they don't work when `relative: "self"`.
+// Hint: 17-61 make sure to set `relative: auto` on your text fill
+// Error: 17-61 gradients and patterns on text must be relative to the parent
+#set text(fill: gradient.linear(red, blue, relative: "self"))
+
+--- gradient-text-global ---
+// Test that gradient fills on text work for globally defined gradients.
+#set page(width: 200pt, height: auto, margin: 10pt, background: {
+ rect(width: 100%, height: 30pt, fill: gradient.linear(red, blue))
+})
+#set par(justify: true)
+#set text(fill: gradient.linear(red, blue))
+#lorem(30)
+
+--- gradient-text-dir ---
+// Sanity check that the direction works on text.
+#set page(width: 200pt, height: auto, margin: 10pt, background: {
+ rect(height: 100%, width: 30pt, fill: gradient.linear(dir: btt, red, blue))
+})
+#set par(justify: true)
+#set text(fill: gradient.linear(dir: btt, red, blue))
+#lorem(30)
+
+--- gradient-text-in-container ---
+// Test that gradient fills on text work for locally defined gradients.
+#set page(width: auto, height: auto, margin: 10pt)
+#show box: set text(fill: gradient.linear(..color.map.rainbow))
+Hello, #box[World]!
+
+--- gradient-text-rotate ---
+// Test that gradients fills on text work with transforms.
+#set page(width: auto, height: auto, margin: 10pt)
+#show box: set text(fill: gradient.linear(..color.map.rainbow))
+#rotate(45deg, box[World])
+
+--- gradient-text-decoration ---
+#set text(fill: gradient.linear(red, blue))
+
+Hello #underline[World]! \
+Hello #overline[World]! \
+Hello #strike[World]! \
+
+--- gradient-transformed ---
+// Test whether gradients work well when they are contained within a transform.
+#let grad = gradient.linear(red, blue, green, purple, relative: "parent");
+#let my-rect = rect(width: 50pt, height: 50pt, fill: grad)
+#set page(
+ height: 50pt,
+ width: 50pt,
+ margin: 2.5pt,
+)
+#place(top + right, scale(x: 200%, y: 130%, my-rect))
+#place(bottom + center, rotate(45deg, my-rect))
+#place(horizon + center, scale(x: 200%, y: 130%, rotate(45deg, my-rect)))
+
+--- gradient-presets ---
+// Test all gradient presets.
+#set page(width: 100pt, height: auto, margin: 0pt)
+#set text(fill: white, size: 18pt)
+#set text(top-edge: "bounds", bottom-edge: "bounds")
+
+#let presets = (
+ ("turbo", color.map.turbo),
+ ("cividis", color.map.cividis),
+ ("rainbow", color.map.rainbow),
+ ("spectral", color.map.spectral),
+ ("viridis", color.map.viridis),
+ ("inferno", color.map.inferno),
+ ("magma", color.map.magma),
+ ("plasma", color.map.plasma),
+ ("rocket", color.map.rocket),
+ ("mako", color.map.mako),
+ ("vlag", color.map.vlag),
+ ("icefire", color.map.icefire),
+ ("flare", color.map.flare),
+ ("crest", color.map.crest),
+)
+
+#stack(
+ spacing: 3pt,
+ ..presets.map(((name, preset)) => block(
+ width: 100%,
+ height: 20pt,
+ fill: gradient.linear(..preset),
+ align(center + horizon, smallcaps(name)),
+ ))
+)
+
+// Test that gradients are applied correctly on equations.
+
+--- gradient-math-cancel ---
+// Test on cancel
+#show math.equation: set text(fill: gradient.linear(..color.map.rainbow))
+#show math.equation: box
+
+$ a dot cancel(5) = cancel(25) 5 x + cancel(5) 1 $
+
+--- gradient-math-frac ---
+// Test on frac
+#show math.equation: set text(fill: gradient.linear(..color.map.rainbow))
+#show math.equation: box
+
+$ nabla dot bold(E) = frac(rho, epsilon_0) $
+
+--- gradient-math-root ---
+// Test on root
+#show math.equation: set text(fill: gradient.linear(..color.map.rainbow))
+#show math.equation: box
+
+$ x_"1,2" = frac(-b +- sqrt(b^2 - 4 a c), 2 a) $
+
+--- gradient-math-mat ---
+// Test on matrix
+#show math.equation: set text(fill: gradient.linear(..color.map.rainbow))
+#show math.equation: box
+
+$ A = mat(
+ 1, 2, 3;
+ 4, 5, 6;
+ 7, 8, 9
+) $
+
+--- gradient-math-underover ---
+// Test on underover
+#show math.equation: set text(fill: gradient.linear(..color.map.rainbow))
+#show math.equation: box
+
+$ underline(X^2) $
+$ overline("hello, world!") $
+
+--- gradient-math-dir ---
+// Test a different direction
+#show math.equation: set text(fill: gradient.linear(..color.map.rainbow, dir: ttb))
+#show math.equation: box
+
+$ A = mat(
+ 1, 2, 3;
+ 4, 5, 6;
+ 7, 8, 9
+) $
+
+$ x_"1,2" = frac(-b +- sqrt(b^2 - 4 a c), 2 a) $
+
+--- gradient-math-misc ---
+// Test miscellaneous
+#show math.equation: set text(fill: gradient.linear(..color.map.rainbow))
+#show math.equation: box
+
+$ hat(x) = bar x bar = vec(x, y, z) = tilde(x) = dot(x) $
+$ x prime = vec(1, 2, delim: "[") $
+$ sum_(i in NN) 1 + i $
+$ attach(
+ Pi, t: alpha, b: beta,
+ tl: 1, tr: 2+3, bl: 4+5, br: 6,
+) $
+
+--- gradient-math-radial ---
+// Test radial gradient
+#show math.equation: set text(fill: gradient.radial(..color.map.rainbow, center: (30%, 30%)))
+#show math.equation: box
+
+$ A = mat(
+ 1, 2, 3;
+ 4, 5, 6;
+ 7, 8, 9
+) $
+
+--- gradient-math-conic ---
+// Test conic gradient
+#show math.equation: set text(fill: gradient.conic(red, blue, angle: 45deg))
+#show math.equation: box
+
+$ A = mat(
+ 1, 2, 3;
+ 4, 5, 6;
+ 7, 8, 9
+) $
+
+
+--- gradient-kind ---
+// Test gradient functions.
+#test(gradient.linear(red, green, blue).kind(), gradient.linear)
+
+--- gradient-stops ---
+#test(gradient.linear(red, green, blue).stops(), ((red, 0%), (green, 50%), (blue, 100%)))
+
+--- gradient-sample ---
+#test(gradient.linear(red, green, blue, space: rgb).sample(0%), red)
+#test(gradient.linear(red, green, blue, space: rgb).sample(25%), rgb("#97873b"))
+#test(gradient.linear(red, green, blue, space: rgb).sample(50%), green)
+#test(gradient.linear(red, green, blue, space: rgb).sample(75%), rgb("#17a08c"))
+#test(gradient.linear(red, green, blue, space: rgb).sample(100%), blue)
+
+--- gradient-space ---
+#test(gradient.linear(red, green, space: rgb).space(), rgb)
+#test(gradient.linear(red, green, space: oklab).space(), oklab)
+#test(gradient.linear(red, green, space: oklch).space(), oklch)
+#test(gradient.linear(red, green, space: cmyk).space(), cmyk)
+#test(gradient.linear(red, green, space: luma).space(), luma)
+#test(gradient.linear(red, green, space: color.linear-rgb).space(), color.linear-rgb)
+#test(gradient.linear(red, green, space: color.hsl).space(), color.hsl)
+#test(gradient.linear(red, green, space: color.hsv).space(), color.hsv)
+
+--- gradient-relative ---
+#test(gradient.linear(red, green, relative: "self").relative(), "self")
+#test(gradient.linear(red, green, relative: "parent").relative(), "parent")
+#test(gradient.linear(red, green).relative(), auto)
+
+--- gradient-angle ---
+#test(gradient.linear(red, green).angle(), 0deg)
+#test(gradient.linear(red, green, dir: ltr).angle(), 0deg)
+#test(gradient.linear(red, green, dir: rtl).angle(), 180deg)
+#test(gradient.linear(red, green, dir: ttb).angle(), 90deg)
+#test(gradient.linear(red, green, dir: btt).angle(), 270deg)
+
+--- gradient-repeat ---
+#test(
+ gradient.linear(red, green, blue).repeat(2).stops(),
+ ((red, 0%), (green, 25%), (blue, 50%), (red, 50%), (green, 75%), (blue, 100%))
+)
+#test(
+ gradient.linear(red, green, blue).repeat(2, mirror: true).stops(),
+ ((red, 0%), (green, 25%), (blue, 50%), (green, 75%), (red, 100%))
+)
+
+--- gradient-repr ---
+// Gradients
+#set page(width: 400pt)
+#set text(0.8em)
+#gradient.linear(blue, red) \
+#gradient.linear(blue, red, dir: ttb) \
+#gradient.linear(blue, red, angle: 45deg, relative: "self") \
+#gradient.linear(blue, red, angle: 45deg, space: rgb)
+
+--- issue-2902-gradient-oklch-panic ---
+// Minimal reproduction of #2902
+#set page(width: 15cm, height: auto, margin: 1em)
+#set block(width: 100%, height: 1cm, above: 2pt)
+
+// Oklch
+#block(fill: gradient.linear(red, purple, space: oklch))
+#block(fill: gradient.linear(..color.map.rainbow, space: oklch))
+#block(fill: gradient.linear(..color.map.plasma, space: oklch))
+
+--- issue-2902-gradient-oklab-panic ---
+#set page(width: 15cm, height: auto, margin: 1em)
+#set block(width: 100%, height: 1cm, above: 2pt)
+
+// Oklab
+#block(fill: gradient.linear(red, purple, space: oklab))
+#block(fill: gradient.linear(..color.map.rainbow, space: oklab))
+#block(fill: gradient.linear(..color.map.plasma, space: oklab))
+
+--- issue-gradient-cmyk-encode ---
+// Test that CMYK works on gradients
+#set page(margin: 0pt, width: 100pt, height: auto)
+
+#let violet = cmyk(75%, 80%, 0%, 0%)
+#let blue = cmyk(75%, 30%, 0%, 0%)
+
+#rect(
+ width: 100%,
+ height: 10pt,
+ fill: gradient.linear(violet, blue)
+)
+
+#rect(
+ width: 100%,
+ height: 10pt,
+ fill: gradient.linear(rgb(violet), rgb(blue))
+)
+
+// In PDF format, this gradient can look different from the others.
+// This is because PDF readers do weird things with CMYK.
+#rect(
+ width: 100%,
+ height: 10pt,
+ fill: gradient.linear(violet, blue, space: cmyk)
+)
diff --git a/tests/suite/visualize/image.typ b/tests/suite/visualize/image.typ
new file mode 100644
index 00000000..ac2d5af9
--- /dev/null
+++ b/tests/suite/visualize/image.typ
@@ -0,0 +1,122 @@
+// Test the `image` function.
+
+--- image-rgba-png-and-jpeg ---
+// Test loading different image formats.
+
+// Load an RGBA PNG image.
+#image("/assets/images/rhino.png")
+
+// Load an RGB JPEG image.
+#set page(height: 60pt)
+#image("/assets/images/tiger.jpg")
+
+--- image-sizing ---
+// Test configuring the size and fitting behaviour of images.
+
+// Set width and height explicitly.
+#box(image("/assets/images/rhino.png", width: 30pt))
+#box(image("/assets/images/rhino.png", height: 30pt))
+
+// Set width and height explicitly and force stretching.
+#image("/assets/images/monkey.svg", width: 100%, height: 20pt, fit: "stretch")
+
+// Make sure the bounding-box of the image is correct.
+#align(bottom + right, image("/assets/images/tiger.jpg", width: 40pt, alt: "A tiger"))
+
+--- image-fit ---
+// Test all three fit modes.
+#set page(height: 50pt, margin: 0pt)
+#grid(
+ columns: (1fr, 1fr, 1fr),
+ rows: 100%,
+ gutter: 3pt,
+ image("/assets/images/tiger.jpg", width: 100%, height: 100%, fit: "contain"),
+ image("/assets/images/tiger.jpg", width: 100%, height: 100%, fit: "cover"),
+ image("/assets/images/monkey.svg", width: 100%, height: 100%, fit: "stretch"),
+)
+
+--- image-jump-to-next-page ---
+// Does not fit to remaining height of page.
+#set page(height: 60pt)
+Stuff
+#image("/assets/images/rhino.png")
+
+--- image-baseline-with-box ---
+// Test baseline.
+A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
+
+--- image-svg-complex ---
+// Test advanced SVG features.
+#image("/assets/images/pattern.svg")
+
+--- image-svg-text ---
+#set page(width: 250pt)
+
+#figure(
+ image("/assets/images/diagram.svg"),
+ caption: [A textful diagram],
+)
+
+--- image-svg-text-font ---
+#set page(width: 250pt)
+#show image: set text(font: ("Roboto", "Noto Serif CJK SC"))
+
+#figure(
+ image("/assets/images/chinese.svg"),
+ caption: [Bilingual text]
+)
+
+--- image-natural-dpi-sizing ---
+// Test that images aren't upscaled.
+// Image is just 48x80 at 220dpi. It should not be scaled to fit the page
+// width, but rather max out at its natural size.
+#image("/assets/images/f2t.jpg")
+
+--- image-file-not-found ---
+// Error: 8-29 file not found (searched at tests/suite/visualize/path/does/not/exist)
+#image("path/does/not/exist")
+
+--- image-bad-format ---
+// Error: 2-22 unknown image format
+#image("./image.typ")
+
+--- image-bad-svg ---
+// Error: 2-33 failed to parse SVG (found closing tag 'g' instead of 'style' in line 4)
+#image("/assets/images/bad.svg")
+
+--- image-decode-svg ---
+// Test parsing from svg data
+#image.decode(`<svg xmlns="http://www.w3.org/2000/svg" height="140" width="500"><ellipse cx="200" cy="80" rx="100" ry="50" style="fill:yellow;stroke:purple;stroke-width:2" /></svg>`.text, format: "svg")
+
+--- image-decode-bad-svg ---
+// Error: 2-168 failed to parse SVG (missing root node)
+#image.decode(`<svg height="140" width="500"><ellipse cx="200" cy="80" rx="100" ry="50" style="fill:yellow;stroke:purple;stroke-width:2" /></svg>`.text, format: "svg")
+
+--- image-decode-detect-format ---
+// Test format auto detect
+#image.decode(read("/assets/images/tiger.jpg", encoding: none), width: 80%)
+
+--- image-decode-specify-format ---
+// Test format manual
+#image.decode(read("/assets/images/tiger.jpg", encoding: none), format: "jpg", width: 80%)
+
+--- image-decode-specify-wrong-format ---
+// Error: 2-91 failed to decode image (Format error decoding Png: Invalid PNG signature.)
+#image.decode(read("/assets/images/tiger.jpg", encoding: none), format: "png", width: 80%)
+
+--- issue-870-image-rotation ---
+// Ensure that EXIF rotation is applied.
+// https://github.com/image-rs/image/issues/1045
+// File is from https://magnushoff.com/articles/jpeg-orientation/
+#image("/assets/images/f2t.jpg", width: 10pt)
+
+--- issue-measure-image ---
+// Test that image measurement doesn't turn `inf / some-value` into 0pt.
+#context {
+ let size = measure(image("/assets/images/tiger.jpg"))
+ test(size, (width: 1024pt, height: 670pt))
+}
+
+--- issue-2051-new-cm-svg ---
+#set text(font: "New Computer Modern")
+#image("/assets/images/diagram.svg")
diff --git a/tests/suite/visualize/line.typ b/tests/suite/visualize/line.typ
new file mode 100644
index 00000000..7259f72b
--- /dev/null
+++ b/tests/suite/visualize/line.typ
@@ -0,0 +1,92 @@
+// Test lines.
+
+--- line-basic ---
+#set page(height: 60pt)
+#box({
+ set line(stroke: 0.75pt)
+ place(line(end: (0.4em, 0pt)))
+ place(line(start: (0pt, 0.4em), end: (0pt, 0pt)))
+ line(end: (0.6em, 0.6em))
+}) Hello #box(line(length: 1cm))!
+
+#line(end: (70%, 50%))
+
+--- line-positioning ---
+// Test the angle argument and positioning.
+
+#set page(fill: rgb("0B1026"))
+#set line(stroke: white)
+
+#let star(size, ..args) = box(width: size, height: size)[
+ #set text(spacing: 0%)
+ #set line(..args)
+ #set align(left)
+ #v(30%)
+ #place(line(length: +30%, start: (09.0%, 02%)))
+ #place(line(length: +30%, start: (38.7%, 02%), angle: -72deg))
+ #place(line(length: +30%, start: (57.5%, 02%), angle: 252deg))
+ #place(line(length: +30%, start: (57.3%, 02%)))
+ #place(line(length: -30%, start: (88.0%, 02%), angle: -36deg))
+ #place(line(length: +30%, start: (73.3%, 48%), angle: 252deg))
+ #place(line(length: -30%, start: (73.5%, 48%), angle: 36deg))
+ #place(line(length: +30%, start: (25.4%, 48%), angle: -36deg))
+ #place(line(length: +30%, start: (25.6%, 48%), angle: -72deg))
+ #place(line(length: +32%, start: (8.50%, 02%), angle: 34deg))
+]
+
+#align(center, grid(
+ columns: 3,
+ column-gutter: 10pt,
+ ..((star(20pt, stroke: 0.5pt),) * 9)
+))
+
+--- line-stroke ---
+// Some simple test lines
+#line(length: 60pt, stroke: red)
+#v(3pt)
+#line(length: 60pt, stroke: 2pt)
+#v(3pt)
+#line(length: 60pt, stroke: blue + 1.5pt)
+#v(3pt)
+#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: "dashed"))
+#v(3pt)
+#line(length: 60pt, stroke: (paint: red, thickness: 4pt, cap: "round"))
+
+--- line-stroke-set ---
+// Set rules with stroke
+#set line(stroke: (paint: red, thickness: 1pt, cap: "butt", dash: "dash-dotted"))
+#line(length: 60pt)
+#v(3pt)
+#line(length: 60pt, stroke: blue)
+#v(3pt)
+#line(length: 60pt, stroke: (dash: none))
+
+--- line-stroke-dash ---
+// Dashing
+#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: ("dot", 1pt)))
+#v(3pt)
+#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: ("dot", 1pt, 4pt, 2pt)))
+#v(3pt)
+#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: (array: ("dot", 1pt, 4pt, 2pt), phase: 5pt)))
+#v(3pt)
+#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: ()))
+#v(3pt)
+#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: (1pt, 3pt, 9pt)))
+
+--- line-stroke-field-typo ---
+// Error: 29-56 unexpected key "thicknes", valid keys are "paint", "thickness", "cap", "join", "dash", and "miter-limit"
+#line(length: 60pt, stroke: (paint: red, thicknes: 1pt))
+
+--- line-stroke-bad-dash-kind ---
+// Error: 29-55 expected "solid", "dotted", "densely-dotted", "loosely-dotted", "dashed", "densely-dashed", "loosely-dashed", "dash-dotted", "densely-dash-dotted", "loosely-dash-dotted", array, dictionary, none, or auto
+#line(length: 60pt, stroke: (paint: red, dash: "dash"))
+
+--- line-bad-point-array ---
+// Test errors.
+
+// Error: 12-19 point array must contain exactly two entries
+#line(end: (50pt,))
+
+--- line-bad-point-component-type ---
+// Error: 14-26 expected relative length, found angle
+#line(start: (3deg, 10pt), length: 5cm)
diff --git a/tests/suite/visualize/path.typ b/tests/suite/visualize/path.typ
new file mode 100644
index 00000000..10955f14
--- /dev/null
+++ b/tests/suite/visualize/path.typ
@@ -0,0 +1,52 @@
+// Test paths.
+
+--- path ---
+#set page(height: 200pt, width: 200pt)
+#table(
+ columns: (1fr, 1fr),
+ rows: (1fr, 1fr),
+ align: center + horizon,
+ path(
+ fill: red,
+ closed: true,
+ ((0%, 0%), (4%, -4%)),
+ ((50%, 50%), (4%, -4%)),
+ ((0%, 50%), (4%, 4%)),
+ ((50%, 0%), (4%, 4%)),
+ ),
+ path(
+ fill: purple,
+ stroke: 1pt,
+ (0pt, 0pt),
+ (30pt, 30pt),
+ (0pt, 30pt),
+ (30pt, 0pt),
+ ),
+ path(
+ fill: blue,
+ stroke: 1pt,
+ closed: true,
+ ((30%, 0%), (35%, 30%), (-20%, 0%)),
+ ((30%, 60%), (-20%, 0%), (0%, 0%)),
+ ((50%, 30%), (60%, -30%), (60%, 0%)),
+ ),
+ path(
+ stroke: 5pt,
+ closed: true,
+ (0pt, 30pt),
+ (30pt, 30pt),
+ (15pt, 0pt),
+ ),
+)
+
+--- path-bad-vertex ---
+// Error: 7-9 path vertex must have 1, 2, or 3 points
+#path(())
+
+--- path-bad-point-count ---
+// Error: 7-47 path vertex must have 1, 2, or 3 points
+#path(((0%, 0%), (0%, 0%), (0%, 0%), (0%, 0%)))
+
+--- path-bad-point-array ---
+// Error: 7-31 point array must contain exactly two entries
+#path(((0%, 0%), (0%, 0%, 0%)))
diff --git a/tests/suite/visualize/pattern.typ b/tests/suite/visualize/pattern.typ
new file mode 100644
index 00000000..08051ed2
--- /dev/null
+++ b/tests/suite/visualize/pattern.typ
@@ -0,0 +1,131 @@
+// Test patterns.
+
+--- pattern-line ---
+// Tests that simple patterns work.
+#set page(width: auto, height: auto, margin: 0pt)
+#let pat = pattern(size: (10pt, 10pt), line(stroke: 4pt, start: (0%, 0%), end: (100%, 100%)))
+#rect(width: 50pt, height: 50pt, fill: pat)
+
+--- pattern-lines ---
+#set page(width: auto, height: auto, margin: 0pt)
+
+#let pat = pattern(size: (10pt, 10pt), {
+ place(line(stroke: 4pt, start: (0%, 0%), end: (100%, 100%)))
+ place(line(stroke: 4pt, start: (100%,0%), end: (200%, 100%)))
+ place(line(stroke: 4pt, start: (0%,100%), end: (100%, 200%)))
+ place(line(stroke: 4pt, start: (-100%,0%), end: (0%, 100%)))
+ place(line(stroke: 4pt, start: (0%,-100%), end: (100%, 0%)))
+})
+#rect(width: 50pt, height: 50pt, fill: pat)
+
+--- pattern-relative-self ---
+// Test with relative set to `"self"`
+#let pat(..args) = pattern(size: (30pt, 30pt), ..args)[
+ #place(top + left, line(start: (0%, 0%), end: (100%, 100%), stroke: 1pt))
+ #place(top + left, line(start: (0%, 100%), end: (100%, 0%), stroke: 1pt))
+]
+
+#set page(fill: pat(), width: 100pt, height: 100pt)
+
+#rect(fill: pat(relative: "self"), width: 100%, height: 100%, stroke: 1pt)
+
+--- pattern-relative-parent ---
+// Test with relative set to `"parent"`
+#let pat(..args) = pattern(size: (30pt, 30pt), ..args)[
+ #place(top + left, line(start: (0%, 0%), end: (100%, 100%), stroke: 1pt))
+ #place(top + left, line(start: (0%, 100%), end: (100%, 0%), stroke: 1pt))
+]
+
+#set page(fill: pat(), width: 100pt, height: 100pt)
+
+#rect(fill: pat(relative: "parent"), width: 100%, height: 100%, stroke: 1pt)
+
+--- pattern-small ---
+// Tests small patterns for pixel accuracy.
+#box(
+ width: 8pt,
+ height: 1pt,
+ fill: pattern(size: (1pt, 1pt), square(size: 1pt, fill: black))
+)
+#v(-1em)
+#box(
+ width: 8pt,
+ height: 1pt,
+ fill: pattern(size: (2pt, 1pt), square(size: 1pt, fill: black))
+)
+
+--- pattern-zero-sized ---
+// Error: 15-52 pattern tile size must be non-zero
+// Hint: 15-52 try setting the size manually
+#line(stroke: pattern(path((0pt, 0pt), (1em, 0pt))))
+
+--- pattern-spacing-negative ---
+// Test with spacing set to `(-10pt, -10pt)`
+#let pat(..args) = pattern(size: (30pt, 30pt), ..args)[
+ #square(width: 100%, height: 100%, stroke: 1pt, fill: blue)
+]
+
+#set page(width: 100pt, height: 100pt)
+
+#rect(fill: pat(spacing: (-10pt, -10pt)), width: 100%, height: 100%, stroke: 1pt)
+
+--- pattern-spacing-zero ---
+// Test with spacing set to `(0pt, 0pt)`
+#let pat(..args) = pattern(size: (30pt, 30pt), ..args)[
+ #square(width: 100%, height: 100%, stroke: 1pt, fill: blue)
+]
+
+#set page(width: 100pt, height: 100pt)
+
+#rect(fill: pat(spacing: (0pt, 0pt)), width: 100%, height: 100%, stroke: 1pt)
+
+--- pattern-spacing-positive ---
+// Test with spacing set to `(10pt, 10pt)`
+#let pat(..args) = pattern(size: (30pt, 30pt), ..args)[
+ #square(width: 100%, height: 100%, stroke: 1pt, fill: blue)
+]
+
+#set page(width: 100pt, height: 100pt)
+
+#rect(fill: pat(spacing: (10pt, 10pt,)), width: 100%, height: 100%, stroke: 1pt)
+
+--- pattern-stroke ---
+// Test pattern on strokes
+#align(
+ center + top,
+ square(
+ size: 50pt,
+ stroke: 5pt + pattern(
+ size: (5pt, 5pt),
+ align(horizon + center, circle(fill: blue, radius: 2.5pt))
+ )
+ )
+)
+
+--- pattern-text ---
+// Test a pattern on some text
+// You shouldn't be able to see the text, if you can then
+// that means that the transform matrices are not being
+// applied to the text correctly.
+#let pat = pattern(
+ size: (30pt, 30pt),
+ relative: "parent",
+ square(size: 30pt, fill: gradient.conic(..color.map.rainbow))
+);
+
+#set page(
+ width: 140pt,
+ height: 140pt,
+ fill: pat
+)
+
+#rotate(45deg, scale(x: 50%, y: 70%, rect(
+ width: 100%,
+ height: 100%,
+ stroke: 1pt,
+)[
+ #lorem(10)
+
+ #set text(fill: pat)
+ #lorem(10)
+]))
diff --git a/tests/suite/visualize/polygon.typ b/tests/suite/visualize/polygon.typ
new file mode 100644
index 00000000..a3f4c8ef
--- /dev/null
+++ b/tests/suite/visualize/polygon.typ
@@ -0,0 +1,51 @@
+// Test polygons.
+
+--- polygon ---
+#set page(width: 50pt)
+#set polygon(stroke: 0.75pt, fill: blue)
+
+// These are not visible, but should also not give an error
+#polygon()
+#polygon((0em, 0pt))
+#polygon((0pt, 0pt), (10pt, 0pt))
+#polygon.regular(size: 0pt, vertices: 9)
+
+#polygon((5pt, 0pt), (0pt, 10pt), (10pt, 10pt))
+#polygon(
+ (0pt, 0pt), (5pt, 5pt), (10pt, 0pt),
+ (15pt, 5pt),
+ (5pt, 10pt)
+)
+#polygon(stroke: none, (5pt, 0pt), (0pt, 10pt), (10pt, 10pt))
+#polygon(stroke: 3pt, fill: none, (5pt, 0pt), (0pt, 10pt), (10pt, 10pt))
+
+// Relative size
+#polygon((0pt, 0pt), (100%, 5pt), (50%, 10pt))
+
+// Antiparallelogram
+#polygon((0pt, 5pt), (5pt, 0pt), (0pt, 10pt), (5pt, 15pt))
+
+// Self-intersections
+#polygon((0pt, 10pt), (30pt, 20pt), (0pt, 30pt), (20pt, 0pt), (20pt, 35pt))
+
+// Regular polygon; should have equal side lengths
+#for k in range(3, 9) {polygon.regular(size: 30pt, vertices: k,)}
+
+--- polygon-line-join ---
+// Line joins
+#stack(
+ dir: ltr,
+ spacing: 1em,
+ polygon(stroke: (thickness: 4pt, paint: blue, join: "round"),
+ (0pt, 20pt), (15pt, 0pt), (0pt, 40pt), (15pt, 45pt)),
+ polygon(stroke: (thickness: 4pt, paint: blue, join: "bevel"),
+ (0pt, 20pt), (15pt, 0pt), (0pt, 40pt), (15pt, 45pt)),
+ polygon(stroke: (thickness: 4pt, paint: blue, join: "miter"),
+ (0pt, 20pt), (15pt, 0pt), (0pt, 40pt), (15pt, 45pt)),
+ polygon(stroke: (thickness: 4pt, paint: blue, join: "miter", miter-limit: 20.0),
+ (0pt, 20pt), (15pt, 0pt), (0pt, 40pt), (15pt, 45pt)),
+)
+
+--- polygon-bad-point-array ---
+// Error: 10-17 point array must contain exactly two entries
+#polygon((50pt,))
diff --git a/tests/suite/visualize/rect.typ b/tests/suite/visualize/rect.typ
new file mode 100644
index 00000000..f84fafcb
--- /dev/null
+++ b/tests/suite/visualize/rect.typ
@@ -0,0 +1,107 @@
+// Test the `rect` function.
+
+--- rect ---
+// Default rectangle.
+#rect()
+
+--- rect-customization ---
+#set page(width: 150pt)
+
+// Fit to text.
+#rect(fill: conifer)[Textbox]
+
+// Empty with fixed width and height.
+#block(rect(
+ height: 15pt,
+ fill: rgb("46b3c2"),
+ stroke: 2pt + rgb("234994"),
+))
+
+// Fixed width, text height.
+#rect(width: 2cm, fill: rgb("9650d6"))[Fixed and padded]
+
+// Page width, fixed height.
+#rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft]
+
+// These are inline with text.
+{#box(rect(width: 0.5in, height: 7pt, fill: rgb("d6cd67")))
+ #box(rect(width: 0.5in, height: 7pt, fill: rgb("edd466")))
+ #box(rect(width: 0.5in, height: 7pt, fill: rgb("e3be62")))}
+
+// Rounded corners.
+#stack(
+ dir: ltr,
+ spacing: 1fr,
+ rect(width: 2cm, radius: 30%),
+ rect(width: 1cm, radius: (left: 10pt, right: 5pt)),
+ rect(width: 1.25cm, radius: (
+ top-left: 2pt,
+ top-right: 5pt,
+ bottom-right: 8pt,
+ bottom-left: 11pt
+ )),
+)
+
+// Different strokes.
+#set rect(stroke: (right: red))
+#rect(width: 100%, fill: lime, stroke: (x: 5pt, y: 1pt))
+
+--- rect-stroke ---
+// Rectangle strokes
+#rect(width: 20pt, height: 20pt, stroke: red)
+#v(3pt)
+#rect(width: 20pt, height: 20pt, stroke: (rest: red, top: (paint: blue, dash: "dashed")))
+#v(3pt)
+#rect(width: 20pt, height: 20pt, stroke: (thickness: 5pt, join: "round"))
+
+--- red-stroke-bad-type ---
+// Error: 15-21 expected length, color, gradient, pattern, dictionary, stroke, none, or auto, found array
+#rect(stroke: (1, 2))
+
+--- rect-fill-stroke ---
+#let variant = rect.with(width: 20pt, height: 10pt)
+#let items = for (i, item) in (
+ variant(stroke: none),
+ variant(),
+ variant(fill: none),
+ variant(stroke: 2pt),
+ variant(stroke: eastern),
+ variant(stroke: eastern + 2pt),
+ variant(fill: eastern),
+ variant(fill: eastern, stroke: none),
+ variant(fill: forest, stroke: none),
+ variant(fill: forest, stroke: conifer),
+ variant(fill: forest, stroke: black + 2pt),
+ variant(fill: forest, stroke: conifer + 2pt),
+).enumerate() {
+ (align(horizon)[#(i + 1).], item, [])
+}
+
+#grid(
+ columns: (auto, auto, 1fr, auto, auto, 0fr),
+ gutter: 5pt,
+ ..items,
+)
+
+--- rect-radius-bad-key ---
+// Error: 15-38 unexpected key "cake", valid keys are "top-left", "top-right", "bottom-right", "bottom-left", "left", "top", "right", "bottom", and "rest"
+#rect(radius: (left: 10pt, cake: 5pt))
+
+--- issue-1825-rect-overflow ---
+#set page(width: 17.8cm)
+#set par(justify: true)
+#rect(lorem(70))
+
+--- issue-3264-rect-negative-dimensions ---
+// Negative dimensions
+#rect(width: -1cm, fill: gradient.linear(red, blue))[Reverse left]
+
+#rect(width: 1cm, fill: gradient.linear(red, blue))[Left]
+
+#align(center, rect(width: -1cm, fill: gradient.linear(red, blue))[Reverse center])
+
+#align(center, rect(width: 1cm, fill: gradient.linear(red, blue))[Center])
+
+#align(right, rect(width: -1cm, fill: gradient.linear(red, blue))[Reverse right])
+
+#align(right, rect(width: 1cm, fill: gradient.linear(red, blue))[Right])
diff --git a/tests/suite/visualize/square.typ b/tests/suite/visualize/square.typ
new file mode 100644
index 00000000..caa1fc21
--- /dev/null
+++ b/tests/suite/visualize/square.typ
@@ -0,0 +1,146 @@
+// Test the `square` function.
+
+--- square ---
+// Default square.
+#box(square())
+#box(square[hey!])
+
+--- square-auto-sized ---
+// Test auto-sized square.
+#square(fill: eastern)[
+ #set text(fill: white, weight: "bold")
+ Typst
+]
+
+--- square-relatively-sized-child ---
+// Test relative-sized child.
+#square(fill: eastern)[
+ #rect(width: 10pt, height: 5pt, fill: conifer)
+ #rect(width: 40%, height: 5pt, stroke: conifer)
+]
+
+--- square-contents-overflow ---
+// Test text overflowing height.
+#set page(width: 75pt, height: 100pt)
+#square(fill: conifer)[
+ But, soft! what light through yonder window breaks?
+]
+
+--- square-height-limited ---
+// Test that square does not overflow page.
+#set page(width: 100pt, height: 75pt)
+#square(fill: conifer)[
+ But, soft! what light through yonder window breaks?
+]
+
+--- square-size-width-and-height ---
+// Size wins over width and height.
+// Error: 09-20 unexpected argument: width
+#square(width: 10cm, height: 20cm, size: 1cm, fill: rgb("eb5278"))
+
+--- square-relative-size ---
+// Test relative width and height and size that is smaller
+// than default size.
+#set page(width: 120pt, height: 70pt)
+#set align(bottom)
+#let centered = align.with(center + horizon)
+#stack(
+ dir: ltr,
+ spacing: 1fr,
+ square(width: 50%, centered[A]),
+ square(height: 50%),
+ stack(
+ square(size: 10pt),
+ square(size: 20pt, centered[B])
+ ),
+)
+
+--- square-circle-alignment ---
+// Test alignment in automatically sized square and circle.
+#set text(8pt)
+#box(square(inset: 4pt)[
+ Hey there, #align(center + bottom, rotate(180deg, [you!]))
+])
+#box(circle(align(center + horizon, [Hey.])))
+
+--- square-circle-overspecified ---
+// Test that minimum wins if both width and height are given.
+#stack(
+ dir: ltr,
+ spacing: 2pt,
+ square(width: 20pt, height: 40pt),
+ circle(width: 20%, height: 100pt),
+)
+
+--- square-height-limited-stack ---
+// Test square that is limited by region size.
+#set page(width: 20pt, height: 10pt, margin: 0pt)
+#stack(dir: ltr, square(fill: forest), square(fill: conifer))
+
+--- square-overflow ---
+// Test that square doesn't overflow due to its aspect ratio.
+#set page(width: 40pt, height: 25pt, margin: 5pt)
+#square(width: 100%)
+#square(width: 100%)[Hello there]
+
+--- square-size-relative-invalid ---
+// Size cannot be relative because we wouldn't know
+// relative to which axis.
+// Error: 15-18 expected length or auto, found ratio
+#square(size: 50%)
+
+--- square-rect-rounded ---
+#set square(size: 20pt, stroke: 4pt)
+
+// no radius for non-rounded corners
+#stack(
+ dir: ltr,
+ square(),
+ h(10pt),
+ square(radius: 0pt),
+ h(10pt),
+ square(radius: -10pt),
+)
+
+#stack(
+ dir: ltr,
+ square(),
+ h(10pt),
+ square(radius: 0%),
+ h(10pt),
+ square(radius: -10%),
+)
+
+// small values for small radius
+#stack(
+ dir: ltr,
+ square(radius: 1pt),
+ h(10pt),
+ square(radius: 5%),
+ h(10pt),
+ square(radius: 2pt),
+)
+
+// large values for large radius or circle
+#stack(
+ dir: ltr,
+ square(radius: 8pt),
+ h(10pt),
+ square(radius: 10pt),
+ h(10pt),
+ square(radius: 12pt),
+)
+
+#stack(
+ dir: ltr,
+ square(radius: 45%),
+ h(10pt),
+ square(radius: 50%),
+ h(10pt),
+ square(radius: 55%),
+)
+
+--- square-base ---
+// Test that square sets correct base for its content.
+#set page(height: 80pt)
+#square(width: 40%, rect(width: 60%, height: 80%))
diff --git a/tests/suite/visualize/stroke.typ b/tests/suite/visualize/stroke.typ
new file mode 100644
index 00000000..b03c96c5
--- /dev/null
+++ b/tests/suite/visualize/stroke.typ
@@ -0,0 +1,171 @@
+// Test lines.
+
+--- stroke-constructor ---
+// Converting to stroke
+#assert.eq(stroke(red).paint, red)
+#assert.eq(stroke(red).thickness, auto)
+#assert.eq(stroke(2pt).paint, auto)
+#assert.eq(stroke((cap: "round", paint: blue)).cap, "round")
+#assert.eq(stroke((cap: auto, paint: blue)).cap, auto)
+#assert.eq(stroke((cap: auto, paint: blue)).thickness, auto)
+
+// Constructing with named arguments
+#assert.eq(stroke(paint: blue, thickness: 8pt), 8pt + blue)
+#assert.eq(stroke(thickness: 2pt), stroke(2pt))
+#assert.eq(stroke(cap: "round").thickness, auto)
+#assert.eq(stroke(cap: "round", thickness: auto).thickness, auto)
+
+--- stroke-constructor-unknown-key ---
+// Error: 9-21 unexpected key "foo", valid keys are "paint", "thickness", "cap", "join", "dash", and "miter-limit"
+#stroke((foo: "bar"))
+
+--- stroke-fields-simple ---
+// Test stroke fields for simple strokes.
+#test((1em + blue).paint, blue)
+#test((1em + blue).thickness, 1em)
+#test((1em + blue).cap, auto)
+#test((1em + blue).join, auto)
+#test((1em + blue).dash, auto)
+#test((1em + blue).miter-limit, auto)
+
+--- stroke-fields-complex ---
+// Test complex stroke fields.
+#let r1 = rect(stroke: (paint: cmyk(1%, 2%, 3%, 4%), thickness: 4em + 2pt, cap: "round", join: "bevel", miter-limit: 5.0, dash: none))
+#let r2 = rect(stroke: (paint: cmyk(1%, 2%, 3%, 4%), thickness: 4em + 2pt, cap: "round", join: "bevel", miter-limit: 5.0, dash: (3pt, "dot", 4em)))
+#let r3 = rect(stroke: (paint: cmyk(1%, 2%, 3%, 4%), thickness: 4em + 2pt, cap: "round", join: "bevel", dash: (array: (3pt, "dot", 4em), phase: 5em)))
+#let s1 = r1.stroke
+#let s2 = r2.stroke
+#let s3 = r3.stroke
+#test(s1.paint, cmyk(1%, 2%, 3%, 4%))
+#test(s1.thickness, 4em + 2pt)
+#test(s1.cap, "round")
+#test(s1.join, "bevel")
+#test(s1.miter-limit, 5.0)
+#test(s3.miter-limit, auto)
+#test(s1.dash, none)
+#test(s2.dash, (array: (3pt, "dot", 4em), phase: 0pt))
+#test(s3.dash, (array: (3pt, "dot", 4em), phase: 5em))
+
+--- stroke-zero-thickness ---
+// 0pt strokes must function exactly like 'none' strokes and not draw anything
+#rect(width: 10pt, height: 10pt, stroke: none)
+#rect(width: 10pt, height: 10pt, stroke: 0pt)
+#rect(width: 10pt, height: 10pt, stroke: none, fill: blue)
+#rect(width: 10pt, height: 10pt, stroke: 0pt + red, fill: blue)
+
+#line(length: 30pt, stroke: 0pt)
+#line(length: 30pt, stroke: (paint: red, thickness: 0pt, dash: ("dot", 1pt)))
+
+#table(columns: 2, stroke: none)[A][B]
+#table(columns: 2, stroke: 0pt)[A][B]
+
+#path(
+ fill: red,
+ stroke: none,
+ closed: true,
+ ((0%, 0%), (4%, -4%)),
+ ((50%, 50%), (4%, -4%)),
+ ((0%, 50%), (4%, 4%)),
+ ((50%, 0%), (4%, 4%)),
+)
+
+#path(
+ fill: red,
+ stroke: 0pt,
+ closed: true,
+ ((0%, 0%), (4%, -4%)),
+ ((50%, 50%), (4%, -4%)),
+ ((0%, 50%), (4%, 4%)),
+ ((50%, 0%), (4%, 4%)),
+)
+
+--- stroke-text ---
+#set text(size: 20pt)
+#set page(width: auto)
+#let v = [测试字体Test]
+
+#text(stroke: 0.3pt + red, v)
+
+#text(stroke: 0.7pt + red, v)
+
+#text(stroke: 7pt + red, v)
+
+#text(stroke: (paint: blue, thickness: 1pt, dash: "dashed"), v)
+
+#text(stroke: 1pt + gradient.linear(..color.map.rainbow), v)
+
+--- stroke-folding ---
+// Test stroke folding.
+#let sq(..args) = box(square(size: 10pt, ..args))
+
+#set square(stroke: none)
+#sq()
+#set square(stroke: auto)
+#sq()
+#sq(fill: teal)
+#sq(stroke: 2pt)
+#sq(stroke: blue)
+#sq(fill: teal, stroke: blue)
+#sq(fill: teal, stroke: 2pt + blue)
+
+--- stroke-composition ---
+// Test stroke composition.
+#set square(stroke: 4pt)
+#set text(font: "Roboto")
+#stack(
+ dir: ltr,
+ square(
+ stroke: (left: red, top: yellow, right: green, bottom: blue),
+ radius: 50%, align(center+horizon)[*G*],
+ inset: 8pt
+ ),
+ h(0.5cm),
+ square(
+ stroke: (left: red, top: yellow + 8pt, right: green, bottom: blue + 2pt),
+ radius: 50%, align(center+horizon)[*G*],
+ inset: 8pt
+ ),
+ h(0.5cm),
+ square(
+ stroke: (left: red, top: yellow, right: green, bottom: blue),
+ radius: 100%, align(center+horizon)[*G*],
+ inset: 8pt
+ ),
+)
+
+// Join between different solid strokes
+#set square(size: 20pt, stroke: 2pt)
+#set square(stroke: (left: green + 4pt, top: black + 2pt, right: blue, bottom: black + 2pt))
+#stack(
+ dir: ltr,
+ square(),
+ h(0.2cm),
+ square(radius: (top-left: 0pt, rest: 1pt)),
+ h(0.2cm),
+ square(radius: (top-left: 0pt, rest: 8pt)),
+ h(0.2cm),
+ square(radius: (top-left: 0pt, rest: 100pt)),
+)
+
+// Join between solid and dotted strokes
+#set square(stroke: (left: green + 4pt, top: black + 2pt, right: (paint: blue, dash: "dotted"), bottom: (paint: black, dash: "dotted")))
+#stack(
+ dir: ltr,
+ square(),
+ h(0.2cm),
+ square(radius: (top-left: 0pt, rest: 1pt)),
+ h(0.2cm),
+ square(radius: (top-left: 0pt, rest: 8pt)),
+ h(0.2cm),
+ square(radius: (top-left: 0pt, rest: 100pt)),
+)
+
+--- issue-3700-deformed-stroke ---
+// Test shape fill & stroke for specific values that used to make the stroke
+// deformed.
+#rect(
+ radius: 1mm,
+ width: 100%,
+ height: 10pt,
+ stroke: (left: rgb("46b3c2") + 16.0mm),
+)
diff --git a/tests/typ/autocomplete/showcase.typ b/tests/typ/autocomplete/showcase.typ
deleted file mode 100644
index 8ea94f2e..00000000
--- a/tests/typ/autocomplete/showcase.typ
+++ /dev/null
@@ -1,13 +0,0 @@
-// Autocomplete: true
-// Ref: false
-
----
-// Autocomplete contains: -1 "int", "if conditional"
-// Autocomplete excludes: -1 "foo"
-#i
-
----
-
-// Autocomplete contains: -1 "insert", "remove", "len", "all"
-// Autocomplete excludes: -1 "foobar", "foo",
-#().
diff --git a/tests/typ/bugs/1050-terms-indent.typ b/tests/typ/bugs/1050-terms-indent.typ
deleted file mode 100644
index 82376820..00000000
--- a/tests/typ/bugs/1050-terms-indent.typ
+++ /dev/null
@@ -1,11 +0,0 @@
-#set page(width: 200pt)
-#set par(first-line-indent: 0.5cm)
-
-- #lorem(10)
-- #lorem(10)
-
-+ #lorem(10)
-+ #lorem(10)
-
-/ Term 1: #lorem(10)
-/ Term 2: #lorem(10)
diff --git a/tests/typ/bugs/1240-stack-fr.typ b/tests/typ/bugs/1240-stack-fr.typ
deleted file mode 100644
index fa49dce7..00000000
--- a/tests/typ/bugs/1240-stack-fr.typ
+++ /dev/null
@@ -1,18 +0,0 @@
-// This issue is sort of horrible: When you write `h(1fr)` in a `stack` instead
-// of directly `1fr`, things go awry. To fix this, we now transparently detect
-// h/v children.
-//
-// https://github.com/typst/typst/issues/1240
-
----
-#stack(dir: ltr, [a], 1fr, [b], 1fr, [c])
-#stack(dir: ltr, [a], h(1fr), [b], h(1fr), [c])
-
----
-#set page(height: 60pt)
-#stack(
- dir: ltr,
- spacing: 1fr,
- stack([a], 1fr, [b]),
- stack([a], v(1fr), [b]),
-)
diff --git a/tests/typ/bugs/1597-cite-footnote.typ b/tests/typ/bugs/1597-cite-footnote.typ
deleted file mode 100644
index cc231e2a..00000000
--- a/tests/typ/bugs/1597-cite-footnote.typ
+++ /dev/null
@@ -1,12 +0,0 @@
-// Tests that when a citation footnote is pushed to next page, things still
-// work as expected.
-//
-// Issue: https://github.com/typst/typst/issues/1597
-
----
-#set page(height: 60pt)
-#lorem(4)
-
-#footnote[@netwok]
-#show bibliography: none
-#bibliography("/assets/bib/works.bib")
diff --git a/tests/typ/bugs/2044-invalid-parsed-ident.typ b/tests/typ/bugs/2044-invalid-parsed-ident.typ
deleted file mode 100644
index 5e4b560c..00000000
--- a/tests/typ/bugs/2044-invalid-parsed-ident.typ
+++ /dev/null
@@ -1,6 +0,0 @@
-// In this bug, the dot at the end was causing the right parenthesis to be
-// parsed as an identifier instead of the closing right parenthesis.
-// Issue: https://github.com/typst/typst/issues/2044
-
-$floor(phi.alt.)$
-$floor(phi.alt. )$
diff --git a/tests/typ/bugs/2105-linebreak-tofu.typ b/tests/typ/bugs/2105-linebreak-tofu.typ
deleted file mode 100644
index 4dd5a244..00000000
--- a/tests/typ/bugs/2105-linebreak-tofu.typ
+++ /dev/null
@@ -1 +0,0 @@
-#linebreak()中文
diff --git a/tests/typ/bugs/2595-float-overlap.typ b/tests/typ/bugs/2595-float-overlap.typ
deleted file mode 100644
index 7c7f68c9..00000000
--- a/tests/typ/bugs/2595-float-overlap.typ
+++ /dev/null
@@ -1,13 +0,0 @@
-#set page(height: 80pt)
-
-Start.
-
-#place(auto, float: true, [
- #block(height: 100%, width: 100%, fill: aqua)
-])
-
-#place(auto, float: true, [
- #block(height: 100%, width: 100%, fill: red)
-])
-
-#lorem(20)
diff --git a/tests/typ/bugs/2650-cjk-latin-spacing-meta.typ b/tests/typ/bugs/2650-cjk-latin-spacing-meta.typ
deleted file mode 100644
index 12c7ea41..00000000
--- a/tests/typ/bugs/2650-cjk-latin-spacing-meta.typ
+++ /dev/null
@@ -1,5 +0,0 @@
-// https://github.com/typst/typst/issues/2650
-
-测a试
-
-测#context [a]试
diff --git a/tests/typ/bugs/2715-float-order.typ b/tests/typ/bugs/2715-float-order.typ
deleted file mode 100644
index af0684a1..00000000
--- a/tests/typ/bugs/2715-float-order.typ
+++ /dev/null
@@ -1,19 +0,0 @@
-#set page(height: 180pt)
-#set figure(placement: auto)
-
-#figure(
- rect(height: 60pt),
- caption: [Rectangle I],
-)
-
-#figure(
- rect(height: 50pt),
- caption: [Rectangle II],
-)
-
-#figure(
- circle(),
- caption: [Circle],
-)
-
-#lorem(20)
diff --git a/tests/typ/bugs/2821-missing-fields.typ b/tests/typ/bugs/2821-missing-fields.typ
deleted file mode 100644
index 0fec2043..00000000
--- a/tests/typ/bugs/2821-missing-fields.typ
+++ /dev/null
@@ -1,9 +0,0 @@
-// Issue #2821: Setting a figure's supplement to none removes the field
-// Ref: false
-
----
-#show figure.caption: it => {
- assert(it.has("supplement"))
- assert(it.supplement == none)
-}
-#figure([], caption: [], supplement: none)
diff --git a/tests/typ/bugs/2902-gradient-oklch-panic.typ b/tests/typ/bugs/2902-gradient-oklch-panic.typ
deleted file mode 100644
index 6e09df52..00000000
--- a/tests/typ/bugs/2902-gradient-oklch-panic.typ
+++ /dev/null
@@ -1,20 +0,0 @@
-// Minimal reproduction of #2902
-// Ref: false
-
----
-#set page(width: 15cm, height: auto, margin: 1em)
-#set block(width: 100%, height: 1cm, above: 2pt)
-
-// Oklch
-#block(fill: gradient.linear(red, purple, space: oklch))
-#block(fill: gradient.linear(..color.map.rainbow, space: oklch))
-#block(fill: gradient.linear(..color.map.plasma, space: oklch))
-
----
-#set page(width: 15cm, height: auto, margin: 1em)
-#set block(width: 100%, height: 1cm, above: 2pt)
-
-// Oklab
-#block(fill: gradient.linear(red, purple, space: oklab))
-#block(fill: gradient.linear(..color.map.rainbow, space: oklab))
-#block(fill: gradient.linear(..color.map.plasma, space: oklab))
diff --git a/tests/typ/bugs/3082-chinese-punctuation.typ b/tests/typ/bugs/3082-chinese-punctuation.typ
deleted file mode 100644
index 82cab6f6..00000000
--- a/tests/typ/bugs/3082-chinese-punctuation.typ
+++ /dev/null
@@ -1,4 +0,0 @@
-#set text(font: "Noto Serif CJK TC", lang: "zh")
-#set page(width: 230pt)
-
-課有手冬,朱得過已誰卜服見以大您即乙太邊良,因且行肉因和拉幸,念姐遠米巴急(abc0),松黃貫誰。
diff --git a/tests/typ/bugs/3110-no-type-ctor-or-field.typ b/tests/typ/bugs/3110-no-type-ctor-or-field.typ
deleted file mode 100644
index 61d65253..00000000
--- a/tests/typ/bugs/3110-no-type-ctor-or-field.typ
+++ /dev/null
@@ -1,15 +0,0 @@
-// Issue #3110: let the error message report the type name.
-// https://github.com/typst/typst/issues/3110
-// Ref: false
-
----
-// Error: 2-9 type content does not have a constructor
-#content()
-
----
-// Error: 6-12 type integer does not contain field `MAXVAL`
-#int.MAXVAL
-
----
-// Error: 6-18 type string does not contain field `from-unïcode`
-#str.from-unïcode(97)
diff --git a/tests/typ/bugs/3154-array-dict-mut-entry.typ b/tests/typ/bugs/3154-array-dict-mut-entry.typ
deleted file mode 100644
index b5a52814..00000000
--- a/tests/typ/bugs/3154-array-dict-mut-entry.typ
+++ /dev/null
@@ -1,109 +0,0 @@
-// Issue #3154: Confusing errors from methods supposed to return a mutable entry
-// https://github.com/typst/typst/issues/3154
-// Ref: false
-
----
-#{
- let array = ()
- // Error: 3-16 array is empty
- array.first()
-}
-
----
-#{
- let array = ()
- // Error: 3-16 array is empty
- array.first() = 9
-}
-
----
-#{
- let array = ()
- // Error: 3-15 array is empty
- array.last()
-}
-
----
-#{
- let array = ()
- // Error: 3-15 array is empty
- array.last() = 9
-}
-
----
-#{
- let array = (1,)
- // Error: 3-14 array index out of bounds (index: 1, len: 1) and no default value was specified
- array.at(1)
-}
-
----
-#{
- let array = (1,)
- test(array.at(1, default: 0), 0)
-}
-
----
-#{
- let array = (1,)
- // Error: 3-14 array index out of bounds (index: 1, len: 1)
- array.at(1) = 9
-}
-
----
-#{
- let array = (1,)
- // Error: 3-26 array index out of bounds (index: 1, len: 1)
- array.at(1, default: 0) = 9
-}
-
----
-#{
- let dict = (a: 1)
- // Error: 3-15 dictionary does not contain key "b" and no default value was specified
- dict.at("b")
-}
-
----
-#{
- let dict = (a: 1)
- test(dict.at("b", default: 0), 0)
-}
-
----
-#{
- let dict = (a: 1)
- // Error: 3-15 dictionary does not contain key "b"
- // Hint: 3-15 use `insert` to add or update values
- dict.at("b") = 9
-}
-
----
-#{
- let dict = (a: 1)
- // Error: 3-27 dictionary does not contain key "b"
- // Hint: 3-27 use `insert` to add or update values
- dict.at("b", default: 0) = 9
-}
-
----
-#{
- let dict = (a: 1)
- // Error: 8-9 dictionary does not contain key "b"
- dict.b
-}
-
----
-#{
- let dict = (a: 1)
- dict.b = 9
- test(dict, (a: 1, b: 9))
-}
-
----
-#{
- let dict = (a: 1)
- // Error: 3-9 dictionary does not contain key "b"
- // Hint: 3-9 use `insert` to add or update values
- dict.b += 9
-}
diff --git a/tests/typ/bugs/3232-dict-wrong-keys.typ b/tests/typ/bugs/3232-dict-wrong-keys.typ
deleted file mode 100644
index 61d9e8b8..00000000
--- a/tests/typ/bugs/3232-dict-wrong-keys.typ
+++ /dev/null
@@ -1,23 +0,0 @@
-// Issue #3232: Confusing "expected relative length or dictionary, found dictionary"
-// https://github.com/typst/typst/issues/3232
-// Ref: false
-
----
-// Error: 16-58 unexpected keys "unexpected" and "unexpected-too"
-#block(outset: (unexpected: 0.5em, unexpected-too: 0.2em), [Hi])
-
----
-// Error: 14-56 unexpected keys "unexpected" and "unexpected-too"
-#box(radius: (unexpected: 0.5em, unexpected-too: 0.5em), [Hi])
-
----
-// Error: 16-49 unexpected key "unexpected", valid keys are "left", "top", "right", "bottom", "x", "y", and "rest"
-#block(outset: (unexpected: 0.2em, right: 0.5em), [Hi]) // The 1st key is unexpected
-
----
-// Error: 14-50 unexpected key "unexpected", valid keys are "top-left", "top-right", "bottom-right", "bottom-left", "left", "top", "right", "bottom", and "rest"
-#box(radius: (top-left: 0.5em, unexpected: 0.5em), [Hi]) // The 2nd key is unexpected
-
----
-#block(outset: (:), [Hi]) // Ok
-#box(radius: (:), [Hi]) // Ok
diff --git a/tests/typ/bugs/3275-loop-errors.typ b/tests/typ/bugs/3275-loop-errors.typ
deleted file mode 100644
index 9fdd2961..00000000
--- a/tests/typ/bugs/3275-loop-errors.typ
+++ /dev/null
@@ -1,67 +0,0 @@
-// Issue #3275: clearer errors for loops, https://github.com/typst/typst/issues/3275
-// Ref: false
-
----
-// Normal variable.
-#for x in (1, 2) {}
-#for x in (a: 1, b: 2) {}
-#for x in "foo" {}
-#for x in bytes("😊") {}
-
----
-// Placeholder.
-#for _ in (1, 2) {}
-#for _ in (a: 1, b: 2) {}
-#for _ in "foo" {}
-#for _ in bytes("😊") {}
-
----
-// Destructuring.
-#for (a,b,c) in (("a", 1, bytes(())), ("b", 2, bytes(""))) {}
-#for (a, ..) in (("a", 1, bytes(())), ("b", 2, bytes(""))) {}
-#for (k, v) in (a: 1, b: 2, c: 3) {}
-#for (.., v) in (a: 1, b: 2, c: 3) {}
-
----
-// Error: 11-17 cannot loop over content
-#for x in [1, 2] {}
-
----
-// Error: 11-25 cannot loop over arguments
-#for _ in arguments("a") {}
-
----
-// Error: 16-21 cannot loop over integer
-#for (x, y) in 12306 {}
-
----
-// Error: 16-22 cannot loop over content
-#for (x, y) in [1, 2] {}
-
----
-// Error: 6-12 cannot destructure values of string
-#for (x, y) in "foo" {}
-
----
-// Error: 6-12 cannot destructure string
-#for (x, y) in ("foo", "bar") {}
-
----
-// Error: 6-12 cannot destructure values of bytes
-#for (x, y) in bytes("😊") {}
-
----
-// Error: 6-12 cannot destructure bytes
-#for (x, y) in (bytes((1,2)), bytes((1,2))) {}
-
----
-// Error: 6-12 cannot destructure integer
-#for (x, y) in (1, 2) {}
-
----
-// Error: 10-11 not enough elements to destructure
-#for (x, y) in ((1,), (2,)) {}
-
----
-// Error: 6-12 too many elements to destructure
-#for (x, y) in ((1,2,3), (4,5,6)) {}
diff --git a/tests/typ/bugs/3363-json-large-number.typ b/tests/typ/bugs/3363-json-large-number.typ
deleted file mode 100644
index 57d37f1b..00000000
--- a/tests/typ/bugs/3363-json-large-number.typ
+++ /dev/null
@@ -1,8 +0,0 @@
-// Big numbers (larger than what i64 can store) should just lose some precision
-// but not overflow
-// https://github.com/typst/typst/issues/3363
-// Ref: false
-
-#let bignum = json("/assets/data/big-number.json")
-
-#bignum \ No newline at end of file
diff --git a/tests/typ/bugs/3502-colon-space.typ b/tests/typ/bugs/3502-colon-space.typ
deleted file mode 100644
index 35f38a9b..00000000
--- a/tests/typ/bugs/3502-colon-space.typ
+++ /dev/null
@@ -1,14 +0,0 @@
-// Test that a space after a named parameter is permissible.
-// https://github.com/typst/typst/issues/3502
-// Ref: false
-
----
-#let f( param : v ) = param
-#test(f( param /* ok */ : 2 ), 2)
-
----
-#let ( key : /* hi */ binding ) = ( key: "ok" )
-#test(binding, "ok")
-
----
-#test(( key : "value" ).key, "value")
diff --git a/tests/typ/bugs/3586-figure-caption-separator.typ b/tests/typ/bugs/3586-figure-caption-separator.typ
deleted file mode 100644
index ee992c50..00000000
--- a/tests/typ/bugs/3586-figure-caption-separator.typ
+++ /dev/null
@@ -1,7 +0,0 @@
-// Test that figure caption separator is synthesized correctly.
-// https://github.com/typst/typst/issues/3586
-// Ref: false
-
----
-#show figure.caption: c => test(c.separator, [#": "])
-#figure(table[], caption: [This is a test caption])
diff --git a/tests/typ/bugs/3601-empty-raw.typ b/tests/typ/bugs/3601-empty-raw.typ
deleted file mode 100644
index 3fb39aca..00000000
--- a/tests/typ/bugs/3601-empty-raw.typ
+++ /dev/null
@@ -1,7 +0,0 @@
-// Test that empty raw block with `typ` language doesn't cause a crash.
-// https://github.com/typst/typst/issues/3601
-// Ref: false
-
----
-```typ
-```
diff --git a/tests/typ/bugs/3641-float-loop.typ b/tests/typ/bugs/3641-float-loop.typ
deleted file mode 100644
index 4021fb4f..00000000
--- a/tests/typ/bugs/3641-float-loop.typ
+++ /dev/null
@@ -1,11 +0,0 @@
-// Flow layout should terminate!
-// https://github.com/typst/typst/issues/3641
-//
-// This is not yet ideal: The heading should not move to the second page, but
-// that's a separate bug and not a regression.
-
----
-#set page(height: 40pt)
-
-= Heading
-#lorem(6)
diff --git a/tests/typ/bugs/3650-italic-equation.typ b/tests/typ/bugs/3650-italic-equation.typ
deleted file mode 100644
index c9b47543..00000000
--- a/tests/typ/bugs/3650-italic-equation.typ
+++ /dev/null
@@ -1,4 +0,0 @@
-_abc $sin(x) "abc"$_ \
-$italic(sin(x) "abc" #box[abc])$ \
-*abc $sin(x) "abc"$* \
-$bold(sin(x) "abc" #box[abc])$ \
diff --git a/tests/typ/bugs/3658-math-size.typ b/tests/typ/bugs/3658-math-size.typ
deleted file mode 100644
index 63c020b2..00000000
--- a/tests/typ/bugs/3658-math-size.typ
+++ /dev/null
@@ -1,5 +0,0 @@
-// https://github.com/typst/typst/issues/3658
-
----
-$ #rect[$1/2$] $
-$#rect[$1/2$]$
diff --git a/tests/typ/bugs/3662-pdf-smartquotes.typ b/tests/typ/bugs/3662-pdf-smartquotes.typ
deleted file mode 100644
index 36dc8a15..00000000
--- a/tests/typ/bugs/3662-pdf-smartquotes.typ
+++ /dev/null
@@ -1,12 +0,0 @@
-// Smart quotes were not appearing in the PDF outline, because they didn't
-// implement `PlainText`
-// https://github.com/typst/typst/issues/3662
-
----
-= It's "Unnormal Heading"
-= It’s “Normal Heading”
-
-#set smartquote(enabled: false)
-= It's "Unnormal Heading"
-= It's 'single quotes'
-= It’s “Normal Heading” \ No newline at end of file
diff --git a/tests/typ/bugs/3700-deformed-stroke.typ b/tests/typ/bugs/3700-deformed-stroke.typ
deleted file mode 100644
index 7ca6ba6b..00000000
--- a/tests/typ/bugs/3700-deformed-stroke.typ
+++ /dev/null
@@ -1,11 +0,0 @@
-// Test shape fill & stroke for specific values that used to make the stroke
-// deformed.
-// https://github.com/typst/typst/issues/3700
-
----
-#rect(
- radius: 1mm,
- width: 100%,
- height: 10pt,
- stroke: (left: rgb("46b3c2") + 16.0mm),
-) \ No newline at end of file
diff --git a/tests/typ/bugs/3841-tabs-in-raw-typ-code.typ b/tests/typ/bugs/3841-tabs-in-raw-typ-code.typ
deleted file mode 100644
index db04fe3c..00000000
--- a/tests/typ/bugs/3841-tabs-in-raw-typ-code.typ
+++ /dev/null
@@ -1,20 +0,0 @@
-// Issue 3841 Tab chars are not rendered in raw blocks with lang: "typ(c)"
-// https://github.com/typst/typst/issues/3841
-
-#raw("#if true {\n\tf()\t// typ\n}", lang: "typ")
-
-#raw("if true {\n\tf()\t// typc\n}", lang: "typc")
-
-```typ
-#if true {
- // tabs around f()
- f() // typ
-}
-```
-
-```typc
-if true {
- // tabs around f()
- f() // typc
-}
-```
diff --git a/tests/typ/bugs/870-image-rotation.typ b/tests/typ/bugs/870-image-rotation.typ
deleted file mode 100644
index 5d7b5597..00000000
--- a/tests/typ/bugs/870-image-rotation.typ
+++ /dev/null
@@ -1,6 +0,0 @@
-// Ensure that EXIF rotation is applied.
-// https://github.com/image-rs/image/issues/1045
-
----
-// File is from https://magnushoff.com/articles/jpeg-orientation/
-#image("/assets/images/f2t.jpg", width: 10pt)
diff --git a/tests/typ/bugs/args-sink.typ b/tests/typ/bugs/args-sink.typ
deleted file mode 100644
index 4f7492ac..00000000
--- a/tests/typ/bugs/args-sink.typ
+++ /dev/null
@@ -1,5 +0,0 @@
-// Test bugs with argument sinks.
-
----
-#let foo(..body) = repr(body.pos())
-#foo(a: "1", b: "2", 1, 2, 3, 4, 5, 6)
diff --git a/tests/typ/bugs/args-underscore.typ b/tests/typ/bugs/args-underscore.typ
deleted file mode 100644
index ca3c0ff8..00000000
--- a/tests/typ/bugs/args-underscore.typ
+++ /dev/null
@@ -1,5 +0,0 @@
-// Test that lone underscore works.
-// Ref: false
-
----
-#test((1, 2, 3).map(_ => {}).len(), 3)
diff --git a/tests/typ/bugs/bibliography-math.typ b/tests/typ/bugs/bibliography-math.typ
deleted file mode 100644
index 3aab4b88..00000000
--- a/tests/typ/bugs/bibliography-math.typ
+++ /dev/null
@@ -1,4 +0,0 @@
-#set page(width: 200pt)
-
-@Zee04
-#bibliography("/assets/bib/works_too.bib", style: "mla")
diff --git a/tests/typ/bugs/bidi-tofus.typ b/tests/typ/bugs/bidi-tofus.typ
deleted file mode 100644
index 3b43b280..00000000
--- a/tests/typ/bugs/bidi-tofus.typ
+++ /dev/null
@@ -1,7 +0,0 @@
-// Test that shaping missing characters in both left-to-right and
-// right-to-left directions does not cause a crash.
-
----
-#"\u{590}\u{591}\u{592}\u{593}"
-
-#"\u{30000}\u{30001}\u{30002}\u{30003}"
diff --git a/tests/typ/bugs/block-width-box.typ b/tests/typ/bugs/block-width-box.typ
deleted file mode 100644
index a039bc66..00000000
--- a/tests/typ/bugs/block-width-box.typ
+++ /dev/null
@@ -1,6 +0,0 @@
-// Test box in 100% width block.
-
----
-#block(width: 100%, fill: red, box("a box"))
-
-#block(width: 100%, fill: red, [#box("a box") #box()])
diff --git a/tests/typ/bugs/cite-locate.typ b/tests/typ/bugs/cite-locate.typ
deleted file mode 100644
index 699bb085..00000000
--- a/tests/typ/bugs/cite-locate.typ
+++ /dev/null
@@ -1,23 +0,0 @@
-// Test citation in other introspection.
-
----
-#set page(width: 180pt)
-#set heading(numbering: "1")
-
-#outline(
- title: [List of Figures],
- target: figure.where(kind: image),
-)
-
-#pagebreak()
-
-= Introduction <intro>
-#figure(
- rect[-- PIRATE --],
- caption: [A pirate @arrgh in @intro],
-)
-
-#context [Citation @distress on page #here().page()]
-
-#pagebreak()
-#bibliography("/assets/bib/works.bib", style: "chicago-notes")
diff --git a/tests/typ/bugs/cite-show-set.typ b/tests/typ/bugs/cite-show-set.typ
deleted file mode 100644
index f476dd49..00000000
--- a/tests/typ/bugs/cite-show-set.typ
+++ /dev/null
@@ -1,9 +0,0 @@
-// Test show set rules on citations.
-
----
-#show cite: set text(red)
-A @netwok @arrgh.
-B #cite(<netwok>) #cite(<arrgh>).
-
-#show bibliography: none
-#bibliography("/assets/bib/works.bib")
diff --git a/tests/typ/bugs/clamp-panic.typ b/tests/typ/bugs/clamp-panic.typ
deleted file mode 100644
index 5f167c76..00000000
--- a/tests/typ/bugs/clamp-panic.typ
+++ /dev/null
@@ -1,3 +0,0 @@
-#set page(height: 20pt, margin: 0pt)
-#v(22pt)
-#block(fill: red, width: 100%, height: 10pt, radius: 4pt)
diff --git a/tests/typ/bugs/columns-1.typ b/tests/typ/bugs/columns-1.typ
deleted file mode 100644
index 96a4d0e5..00000000
--- a/tests/typ/bugs/columns-1.typ
+++ /dev/null
@@ -1,12 +0,0 @@
-// The well-known columns bug.
-
----
-#set page(height: 70pt)
-
-Hallo
-#columns(2)[
- = A
- Text
- = B
- Text
-]
diff --git a/tests/typ/bugs/emoji-linebreak.typ b/tests/typ/bugs/emoji-linebreak.typ
deleted file mode 100644
index 2f7e74e7..00000000
--- a/tests/typ/bugs/emoji-linebreak.typ
+++ /dev/null
@@ -1,6 +0,0 @@
-// Test that there are no linebreaks in composite emoji (issue #80).
-
----
-#set page(width: 50pt, height: auto)
-#h(99%) 🏳️‍🌈
-🏳️‍🌈
diff --git a/tests/typ/bugs/equation-numbering-reference.typ b/tests/typ/bugs/equation-numbering-reference.typ
deleted file mode 100644
index 3423f022..00000000
--- a/tests/typ/bugs/equation-numbering-reference.typ
+++ /dev/null
@@ -1,15 +0,0 @@
-// In this bug, the hint and error messages for an equation
-// being reference mentioned that it was a "heading" and was
-// lacking the proper path.
-// Ref: false
-
----
-#set page(height: 70pt)
-
-$
- Delta = b^2 - 4 a c
-$ <quadratic>
-
-// Error: 14-24 cannot reference equation without numbering
-// Hint: 14-24 you can enable equation numbering with `#set math.equation(numbering: "1.")`
-Looks at the @quadratic formula. \ No newline at end of file
diff --git a/tests/typ/bugs/flow-1.typ b/tests/typ/bugs/flow-1.typ
deleted file mode 100644
index 425a0ce8..00000000
--- a/tests/typ/bugs/flow-1.typ
+++ /dev/null
@@ -1,11 +0,0 @@
-// In this bug, the first line of the second paragraph was on its page alone an
-// the rest moved down. The reason was that the second block resulted in
-// overlarge frames because the region wasn't finished properly.
-
----
-#set page(height: 70pt)
-#block[This file tests a bug where an almost empty page occurs.]
-#block[
- The text in this second block was torn apart and split up for
- some reason beyond my knowledge.
-]
diff --git a/tests/typ/bugs/flow-2.typ b/tests/typ/bugs/flow-2.typ
deleted file mode 100644
index 5ffffd58..00000000
--- a/tests/typ/bugs/flow-2.typ
+++ /dev/null
@@ -1,10 +0,0 @@
-// In this bug, the first part of the paragraph moved down to the second page
-// because trailing leading wasn't trimmed, resulting in an overlarge frame.
-
----
-#set page(height: 60pt)
-#v(19pt)
-#block[
- But, soft! what light through yonder window breaks?
- It is the east, and Juliet is the sun.
-]
diff --git a/tests/typ/bugs/flow-3.typ b/tests/typ/bugs/flow-3.typ
deleted file mode 100644
index 71af1914..00000000
--- a/tests/typ/bugs/flow-3.typ
+++ /dev/null
@@ -1,12 +0,0 @@
-// In this bug, there was a bit of space below the heading because weak spacing
-// directly before a layout-induced column or page break wasn't trimmed.
-
----
-#set page(height: 60pt)
-#rect(inset: 0pt, columns(2)[
- Text
- #v(12pt)
- Hi
- #v(10pt, weak: true)
- At column break.
-])
diff --git a/tests/typ/bugs/flow-4.typ b/tests/typ/bugs/flow-4.typ
deleted file mode 100644
index f49873f5..00000000
--- a/tests/typ/bugs/flow-4.typ
+++ /dev/null
@@ -1,5 +0,0 @@
-// In this bug, a frame intended for the second region ended up in the first.
-
----
-#set page(height: 105pt)
-#block(lorem(20))
diff --git a/tests/typ/bugs/flow-5.typ b/tests/typ/bugs/flow-5.typ
deleted file mode 100644
index 5e580b9e..00000000
--- a/tests/typ/bugs/flow-5.typ
+++ /dev/null
@@ -1,13 +0,0 @@
-// This bug caused an index-out-of-bounds panic when layouting paragraphs needed
-// multiple reorderings.
-
----
-#set page(height: 200pt)
-#lorem(30)
-
-#figure(placement: auto, block(height: 100%))
-
-#lorem(10)
-
-#lorem(10)
-
diff --git a/tests/typ/bugs/fold-vector.typ b/tests/typ/bugs/fold-vector.typ
deleted file mode 100644
index 5d57ad33..00000000
--- a/tests/typ/bugs/fold-vector.typ
+++ /dev/null
@@ -1,20 +0,0 @@
-// Test fold order of vectors.
-
----
-#set text(features: (liga: 1))
-#set text(features: (liga: 0))
-fi
-
----
-#underline(stroke: aqua + 4pt)[
- #underline[Hello]
-]
-
----
-#let c = counter("mycounter")
-#c.update(1)
-#locate(loc => [
- #c.update(2)
- #c.at(loc) \
- Second: #locate(loc => c.at(loc))
-])
diff --git a/tests/typ/bugs/footnote-keep-multiple.typ b/tests/typ/bugs/footnote-keep-multiple.typ
deleted file mode 100644
index e4efe3ce..00000000
--- a/tests/typ/bugs/footnote-keep-multiple.typ
+++ /dev/null
@@ -1,10 +0,0 @@
-// Test that the logic that keeps footnote entry together with
-// their markers also works for multiple footnotes in a single
-// line or frame (here, there are two lines, but they are one
-// unit due to orphan prevention).
-
----
-#set page(height: 100pt)
-#v(40pt)
-A #footnote[a] \
-B #footnote[b]
diff --git a/tests/typ/bugs/footnote-list.typ b/tests/typ/bugs/footnote-list.typ
deleted file mode 100644
index ceece0ca..00000000
--- a/tests/typ/bugs/footnote-list.typ
+++ /dev/null
@@ -1,11 +0,0 @@
-// Test that footnotes in lists do not produce extraneous page breaks. The list
-// layout itself does not currently react to the footnotes layout, weakening the
-// "footnote and its entry are on the same page" invariant somewhat, but at
-// least there shouldn't be extra page breaks.
-
----
-#set page(height: 100pt)
-#block(height: 50pt, width: 100%, fill: aqua)
-
-- #footnote[1]
-- #footnote[2]
diff --git a/tests/typ/bugs/gradient-cmyk-encode.typ b/tests/typ/bugs/gradient-cmyk-encode.typ
deleted file mode 100644
index 5e0b58dc..00000000
--- a/tests/typ/bugs/gradient-cmyk-encode.typ
+++ /dev/null
@@ -1,27 +0,0 @@
-// Test that CMYK works on gradients
-
----
-#set page(margin: 0pt, width: 200pt, height: auto)
-
-#let violet = cmyk(75%, 80%, 0%, 0%)
-#let blue = cmyk(75%, 30%, 0%, 0%)
-
-#rect(
- width: 100%,
- height: 30pt,
- fill: gradient.linear(violet, blue)
-)
-
-#rect(
- width: 100%,
- height: 30pt,
- fill: gradient.linear(rgb(violet), rgb(blue))
-)
-
-// In PDF format, this gradient can look different from the others.
-// This is because PDF readers do weird things with CMYK.
-#rect(
- width: 100%,
- height: 30pt,
- fill: gradient.linear(violet, blue, space: cmyk)
-)
diff --git a/tests/typ/bugs/grid-1.typ b/tests/typ/bugs/grid-1.typ
deleted file mode 100644
index c583cfe5..00000000
--- a/tests/typ/bugs/grid-1.typ
+++ /dev/null
@@ -1,16 +0,0 @@
-// Test that grid base for auto rows makes sense.
-
----
-#set page(height: 150pt)
-#table(
- columns: (1.5cm, auto),
- rows: (auto, auto),
- rect(width: 100%, fill: red),
- rect(width: 100%, fill: blue),
- rect(width: 100%, height: 50%, fill: green),
-)
-
----
-#rect(width: 100%, height: 1em)
-- #rect(width: 100%, height: 1em)
- - #rect(width: 100%, height: 1em)
diff --git a/tests/typ/bugs/grid-2.typ b/tests/typ/bugs/grid-2.typ
deleted file mode 100644
index b7528b7b..00000000
--- a/tests/typ/bugs/grid-2.typ
+++ /dev/null
@@ -1,20 +0,0 @@
-// Grid now skips a remaining region when one of the cells
-// doesn't fit into it at all.
-
----
-#set page(height: 100pt)
-#grid(
- columns: (2cm, auto),
- rows: (auto, auto),
- rect(width: 100%, fill: red),
- rect(width: 100%, fill: blue),
- rect(width: 100%, height: 80%, fill: green),
- [hello \ darkness #parbreak() my \ old \ friend \ I],
- rect(width: 100%, height: 20%, fill: blue),
- polygon(fill: red, (0%, 0%), (100%, 0%), (100%, 20%))
-)
-
----
-#set page(height: 60pt)
-#lorem(5)
-- #lorem(5)
diff --git a/tests/typ/bugs/grid-3.typ b/tests/typ/bugs/grid-3.typ
deleted file mode 100644
index 19317c50..00000000
--- a/tests/typ/bugs/grid-3.typ
+++ /dev/null
@@ -1,8 +0,0 @@
-// Ensure that the list does not jump to the third page.
-
----
-#set page(height: 70pt)
-#v(40pt)
-The following:
-+ A
-+ B
diff --git a/tests/typ/bugs/grid-4.typ b/tests/typ/bugs/grid-4.typ
deleted file mode 100644
index 691bf877..00000000
--- a/tests/typ/bugs/grid-4.typ
+++ /dev/null
@@ -1,17 +0,0 @@
-// Ensure gutter rows at the top or bottom of a region are skipped.
-
----
-#set page(height: 10em)
-
-#table(
- row-gutter: 1.5em,
- inset: 0pt,
- rows: (1fr, auto),
- [a],
- [],
- [],
- [f],
- [e\ e],
- [],
- [a]
-)
diff --git a/tests/typ/bugs/hide-meta.typ b/tests/typ/bugs/hide-meta.typ
deleted file mode 100644
index 8d2c7cb6..00000000
--- a/tests/typ/bugs/hide-meta.typ
+++ /dev/null
@@ -1,24 +0,0 @@
-// Test that metadata of hidden stuff stays available.
-
----
-#set cite(style: "chicago-notes")
-
-A pirate. @arrgh \
-#set text(2pt)
-#hide[
- A @arrgh pirate.
- #bibliography("/assets/bib/works.bib")
-]
-
----
-#set text(8pt)
-#outline()
-#set text(2pt)
-#hide(block(grid(
- [= A],
- [= B],
- block(grid(
- [= C],
- [= D],
- ))
-)))
diff --git a/tests/typ/bugs/int-constructor.typ b/tests/typ/bugs/int-constructor.typ
deleted file mode 100644
index 0bdce612..00000000
--- a/tests/typ/bugs/int-constructor.typ
+++ /dev/null
@@ -1,7 +0,0 @@
-// Test that integer -> integer conversion doesn't do a roundtrip through float.
-// Ref: false
-
----
-#let x = 9223372036854775800
-#test(type(x), int)
-#test(int(x), x)
diff --git a/tests/typ/bugs/justify-hanging-indent.typ b/tests/typ/bugs/justify-hanging-indent.typ
deleted file mode 100644
index 511aa172..00000000
--- a/tests/typ/bugs/justify-hanging-indent.typ
+++ /dev/null
@@ -1,6 +0,0 @@
-// Test that combination of justification and hanging indent doesn't result in
-// an underfull first line.
-
----
-#set par(hanging-indent: 2.5cm, justify: true)
-#lorem(5)
diff --git a/tests/typ/bugs/label-fields-dict.typ b/tests/typ/bugs/label-fields-dict.typ
deleted file mode 100644
index 05c7006a..00000000
--- a/tests/typ/bugs/label-fields-dict.typ
+++ /dev/null
@@ -1,31 +0,0 @@
-// Tests whether the label is accessible through the has, field,
-// and fields accessors
-// Ref: false
-
----
-// Test whether the label is accessible through the has method
-#show heading: it => {
- assert(it.has("label"))
- it
-}
-
-= Hello, world! <my_label>
-
----
-// Test whether the label is accessible through the field method
-#show heading: it => {
- assert(str(it.label) == "my_label")
- it
-}
-
-= Hello, world! <my_label>
-
----
-// Test whether the label is accessible through the fields method
-#show heading: it => {
- assert("label" in it.fields())
- assert(str(it.fields().label) == "my_label")
- it
-}
-
-= Hello, world! <my_label>
diff --git a/tests/typ/bugs/layout-infinite-lengths.typ b/tests/typ/bugs/layout-infinite-lengths.typ
deleted file mode 100644
index 7fbc6216..00000000
--- a/tests/typ/bugs/layout-infinite-lengths.typ
+++ /dev/null
@@ -1,25 +0,0 @@
-// Test that passing infinite lengths to drawing primitives does not crash Typst.
-
----
-#set page(width: auto, height: auto)
-
-// Error: 58-59 cannot expand into infinite width
-#layout(size => grid(columns: (size.width, size.height))[a][b][c][d])
-
----
-#set page(width: auto, height: auto)
-
-// Error: 17-66 cannot create grid with infinite height
-#layout(size => grid(rows: (size.width, size.height))[a][b][c][d])
-
----
-#set page(width: auto, height: auto)
-
-// Error: 17-41 cannot create line with infinite length
-#layout(size => line(length: size.width))
-
----
-#set page(width: auto, height: auto)
-
-// Error: 17-54 cannot create polygon with infinite size
-#layout(size => polygon((0pt,0pt), (0pt, size.width)))
diff --git a/tests/typ/bugs/line-align.typ b/tests/typ/bugs/line-align.typ
deleted file mode 100644
index 0518eaaa..00000000
--- a/tests/typ/bugs/line-align.typ
+++ /dev/null
@@ -1,5 +0,0 @@
-// Test right-aligning a line and a rectangle.
-
----
-#align(right, line(length: 30%))
-#align(right, rect())
diff --git a/tests/typ/bugs/linebreak-no-justifiables.typ b/tests/typ/bugs/linebreak-no-justifiables.typ
deleted file mode 100644
index ab1b2732..00000000
--- a/tests/typ/bugs/linebreak-no-justifiables.typ
+++ /dev/null
@@ -1,5 +0,0 @@
-// Test breaking a line without justifiables.
-
----
-#set par(justify: true)
-#block(width: 1cm, fill: aqua, lorem(2))
diff --git a/tests/typ/bugs/mat-aug-color.typ b/tests/typ/bugs/mat-aug-color.typ
deleted file mode 100644
index c2e617d6..00000000
--- a/tests/typ/bugs/mat-aug-color.typ
+++ /dev/null
@@ -1,9 +0,0 @@
-// https://github.com/typst/typst/issues/2268
-// The augment line should be of the same color as the text
-#set text(
- font: "New Computer Modern",
- lang: "en",
- fill: yellow,
-)
-
-$mat(augment: #1, M, v) arrow.r.squiggly mat(augment: #1, R, b)$
diff --git a/tests/typ/bugs/math-eval.typ b/tests/typ/bugs/math-eval.typ
deleted file mode 100644
index 31450b8d..00000000
--- a/tests/typ/bugs/math-eval.typ
+++ /dev/null
@@ -1,5 +0,0 @@
-// Evaluating a math expr should renders the same as an equation
-
-#eval(mode: "math", "f(a) = cases(a + b\, space space x >= 3,a + b\, space space x = 5)")
-
-$f(a) = cases(a + b\, space space x >= 3,a + b\, space space x = 5)$
diff --git a/tests/typ/bugs/math-number-spacing.typ b/tests/typ/bugs/math-number-spacing.typ
deleted file mode 100644
index 9450caca..00000000
--- a/tests/typ/bugs/math-number-spacing.typ
+++ /dev/null
@@ -1,9 +0,0 @@
-// Test spacing after numbers in math.
-
----
-$
-10degree \
-10 degree \
-10.1degree \
-10.1 degree
-$
diff --git a/tests/typ/bugs/math-realize.typ b/tests/typ/bugs/math-realize.typ
deleted file mode 100644
index 10d8b78e..00000000
--- a/tests/typ/bugs/math-realize.typ
+++ /dev/null
@@ -1,47 +0,0 @@
-// Test that content in math can be realized without breaking
-// nested equations.
-
----
-#let my = $pi$
-#let f1 = box(baseline: 10pt, [f])
-#let f2 = context f1
-#show math.vec: [nope]
-
-$ pi a $
-$ my a $
-$ 1 + sqrt(x/2) + sqrt(#hide($x/2$)) $
-$ a x #link("url", $+ b$) $
-$ f f1 f2 $
-$ vec(1,2) * 2 $
-
----
-$ x^2 #hide[$(>= phi.alt) union y^2 0$] z^2 $
-Hello #hide[there $x$]
-and #hide[$ f(x) := x^2 $]
-
----
-// Test equations can embed equation pieces built by functions
-#let foo(v1, v2) = {
- // Return an equation piece that would've been rendered in
- // inline style if the piece is not embedded
- $v1 v2^2$
-}
-#let bar(v1, v2) = {
- // Return an equation piece that would've been rendered in
- // block style if the piece is not embedded
- $ v1 v2^2 $
-}
-#let baz(..sink) = {
- // Return an equation piece built by joining arrays
- sink.pos().map(x => $hat(#x)$).join(sym.and)
-}
-
-Inline $2 foo(alpha, (M+foo(a, b)))$.
-
-Inline $2 bar(alpha, (M+foo(a, b)))$.
-
-Inline $2 baz(x,y,baz(u, v))$.
-
-$ 2 foo(alpha, (M+foo(a, b))) $
-$ 2 bar(alpha, (M+foo(a, b))) $
-$ 2 baz(x,y,baz(u, v)) $
diff --git a/tests/typ/bugs/math-shift.typ b/tests/typ/bugs/math-shift.typ
deleted file mode 100644
index 4a833e31..00000000
--- a/tests/typ/bugs/math-shift.typ
+++ /dev/null
@@ -1,5 +0,0 @@
-// https://github.com/typst/typst/issues/2214
-// The math content should also be affected by the TextElem baseline.
-
-hello #text(baseline: -5pt)[123 #sym.WW\orld]\
-hello #text(baseline: -5pt)[$123 WW#text[or]$ld]\
diff --git a/tests/typ/bugs/math-text-break.typ b/tests/typ/bugs/math-text-break.typ
deleted file mode 100644
index a8aa1d0a..00000000
--- a/tests/typ/bugs/math-text-break.typ
+++ /dev/null
@@ -1,4 +0,0 @@
-// Test text with linebreaks in math.
-
----
-$ x := "a\nb\nc\nd\ne" $
diff --git a/tests/typ/bugs/measure-image.typ b/tests/typ/bugs/measure-image.typ
deleted file mode 100644
index bd8703b3..00000000
--- a/tests/typ/bugs/measure-image.typ
+++ /dev/null
@@ -1,8 +0,0 @@
-// Test that image measurement doesn't turn `inf / some-value` into 0pt.
-// Ref: false
-
----
-#context {
- let size = measure(image("/assets/images/tiger.jpg"))
- test(size, (width: 1024pt, height: 670pt))
-}
diff --git a/tests/typ/bugs/new-cm-svg.typ b/tests/typ/bugs/new-cm-svg.typ
deleted file mode 100644
index eeafcbbd..00000000
--- a/tests/typ/bugs/new-cm-svg.typ
+++ /dev/null
@@ -1,2 +0,0 @@
-#set text(font: "New Computer Modern")
-#image("/assets/images/diagram.svg")
diff --git a/tests/typ/bugs/newline-mode.typ b/tests/typ/bugs/newline-mode.typ
deleted file mode 100644
index 04333cc5..00000000
--- a/tests/typ/bugs/newline-mode.typ
+++ /dev/null
@@ -1,84 +0,0 @@
-// Test newline continuations.
-
----
-#{
- "hello"
- .clusters()
- if false {
-
- }
- else {
- ("1", "2")
- }
-}
-
----
-#"hello"
- .codepoints()
-
-#if false {
-
-}
-else {
- ("1", "2")
-}
-
----
-// Ref: false
-#test({
- "hi 1"
-
- .clusters()
-}, ("h", "i", " ", "1"))
-
----
-// Ref: false
-#test({
- "hi 2"// comment
- .clusters()
-}, ("h", "i", " ", "2"))
-
----
-// Ref: false
-#test({
- "hi 3"/* comment */
- .clusters()
-}, ("h", "i", " ", "3"))
-
----
-// Ref: false
-#test({
- "hi 4"
- // comment
- .clusters()
-}, ("h", "i", " ", "4"))
-
----
-// Ref: false
-#test({
- "hi 5"
- /*comment*/.clusters()
-}, ("h", "i", " ", "5"))
-
----
-// Ref: false
-#test({
- "hi 6"
- // comment
-
-
- /* comment */
- .clusters()
-}, ("h", "i", " ", "6"))
-
----
-// Ref: false
-#test({
- let foo(x) = {
- if x < 0 { "negative" }
- // comment
- else { "non-negative" }
- }
-
- foo(1)
-}, "non-negative")
diff --git a/tests/typ/bugs/pagebreak-bibliography.typ b/tests/typ/bugs/pagebreak-bibliography.typ
deleted file mode 100644
index 257043a3..00000000
--- a/tests/typ/bugs/pagebreak-bibliography.typ
+++ /dev/null
@@ -1,5 +0,0 @@
-// Test weak pagebreak before bibliography.
-
----
-#pagebreak(weak: true)
-#bibliography("/assets/bib/works.bib")
diff --git a/tests/typ/bugs/pagebreak-numbering.typ b/tests/typ/bugs/pagebreak-numbering.typ
deleted file mode 100644
index a9fae3e4..00000000
--- a/tests/typ/bugs/pagebreak-numbering.typ
+++ /dev/null
@@ -1,12 +0,0 @@
-// https://github.com/typst/typst/issues/2095
-// The empty page 2 should not have a page number
-
-#set page(numbering: none)
-This and next page should not be numbered
-
-#pagebreak(weak: true, to: "odd")
-
-#set page(numbering: "1")
-#counter(page).update(1)
-
-This page should
diff --git a/tests/typ/bugs/pagebreak-set-style.typ b/tests/typ/bugs/pagebreak-set-style.typ
deleted file mode 100644
index 1ac24652..00000000
--- a/tests/typ/bugs/pagebreak-set-style.typ
+++ /dev/null
@@ -1,12 +0,0 @@
-// https://github.com/typst/typst/issues/2162
-// The styles should not be applied to the pagebreak empty page,
-// it should only be applied after that.
-
-#pagebreak(to: "even") // We should now skip to page 2
-
-Some text on page 2
-
-#pagebreak(to: "even") // We should now skip to page 4
-
-#set page(fill: orange) // This sets the color of the page starting from page 4
-Some text on page 4
diff --git a/tests/typ/bugs/parameter-pattern.typ b/tests/typ/bugs/parameter-pattern.typ
deleted file mode 100644
index 31b07f2c..00000000
--- a/tests/typ/bugs/parameter-pattern.typ
+++ /dev/null
@@ -1,5 +0,0 @@
-// Test that underscore works in parameter patterns.
-// Ref: false
-
----
-#test((1, 2, 3).zip((1, 2, 3)).map(((_, x)) => x), (1, 2, 3))
diff --git a/tests/typ/bugs/parenthesized.typ b/tests/typ/bugs/parenthesized.typ
deleted file mode 100644
index f8f3190f..00000000
--- a/tests/typ/bugs/parenthesized.typ
+++ /dev/null
@@ -1,98 +0,0 @@
-// Ref: false
-// Test bugs related to destructuring and parenthesized parsing.
-
----
-// https://github.com/typst/typst/issues/1338
-#let foo = "foo"
-#let bar = "bar"
-// Error: 8-9 expected expression, found underscore
-// Error: 16-17 expected expression, found underscore
-#(foo: _, bar: _)
-
----
-// https://github.com/typst/typst/issues/1342
-// Error: 5-8 expected named or keyed pair, found identifier
-// Error: 10-13 expected named or keyed pair, found identifier
-#(: foo, bar)
-
----
-// https://github.com/typst/typst/issues/1351
-// Error: 17-22 expected pattern, found string
-#let foo((test: "bar")) = {}
-
----
-// https://github.com/typst/typst/issues/3014
-// Error: 8-17 expected expression, found named pair
-#(box, fill: red)
-
----
-// https://github.com/typst/typst/issues/3144
-#let f(a: 10) = a(1) + 1
-#test(f(a: _ => 5), 6)
-
----
-// Error: 17-20 missing argument: pattern parameter
-#let f(a: 10) = a() + 1
-#f(a: _ => 5)
-
----
-// This wasn't allowed.
-#let ((x)) = 1
-#test(x, 1)
-
----
-// This also wasn't allowed.
-#let ((a, b)) = (1, 2)
-#test(a, 1)
-#test(b, 2)
-
----
-// This was unintentionally allowed ...
-// Error: 9 expected equals sign
-#let (a)
-
----
-// ... where this wasn't.
-// Error: 12 expected equals sign
-#let (a, b)
-
----
-// This wasn't allowed before the bug fix ...
-#let f(..) = {}
-#f(arg: 1)
-
----
-// ... but this was.
-#let f(..x) = {}
-#f(arg: 1)
-
----
-// Here, `best` was accessed as a variable, where it shouldn't have.
-#{
- (best: _) = (best: "brr")
-}
-
----
-// Same here.
-#{
- let array = (1, 2, 3, 4)
- (test: array.at(1), best: _) = (test: "baz", best: "brr")
- test(array, (1, "baz", 3, 4))
-}
-
----
-// Here, `a` is not duplicate, where it was previously identified as one.
-#let f((a: b), (c,), a) = (a, b, c)
-#test(f((a: 1), (2,), 3), (3, 1, 2))
-
----
-// Ensure that we can't have non-atomic closures.
-#let x = 1
-#let c = [#(x) => (1, 2)]
-#test(c.children.last(), [(1, 2)]))
-
----
-// Ensure that we can't have non-atomic destructuring.
-#let x = 1
-#let c = [#() = ()]
-#test(c.children.last(), [()])
diff --git a/tests/typ/bugs/place-base.typ b/tests/typ/bugs/place-base.typ
deleted file mode 100644
index 4a0bd029..00000000
--- a/tests/typ/bugs/place-base.typ
+++ /dev/null
@@ -1,7 +0,0 @@
-// Test that placement is relative to container and not itself.
-
----
-#set page(height: 80pt, margin: 0pt)
-#place(right, dx: -70%, dy: 20%, [First])
-#place(left, dx: 20%, dy: 60%, [Second])
-#place(center + horizon, dx: 25%, dy: 25%, [Third])
diff --git a/tests/typ/bugs/place-pagebreak.typ b/tests/typ/bugs/place-pagebreak.typ
deleted file mode 100644
index bc04af1a..00000000
--- a/tests/typ/bugs/place-pagebreak.typ
+++ /dev/null
@@ -1,7 +0,0 @@
-// Test placing on an already full page.
-// It shouldn't result in a page break.
-
----
-#set page(height: 40pt)
-#block(height: 100%)
-#place(bottom + right)[Hello world]
diff --git a/tests/typ/bugs/place-spacing.typ b/tests/typ/bugs/place-spacing.typ
deleted file mode 100644
index 4d7b5fe3..00000000
--- a/tests/typ/bugs/place-spacing.typ
+++ /dev/null
@@ -1,15 +0,0 @@
-// Test that placed elements don't add extra block spacing.
-
----
-#show figure: set block(spacing: 4em)
-
-Paragraph before float.
-#figure(rect(), placement: bottom)
-Paragraph after float.
-
----
-#show place: set block(spacing: 4em)
-
-Paragraph before place.
-#place(rect())
-Paragraph after place.
diff --git a/tests/typ/bugs/raw-color-overwrite.typ b/tests/typ/bugs/raw-color-overwrite.typ
deleted file mode 100644
index ec306ef1..00000000
--- a/tests/typ/bugs/raw-color-overwrite.typ
+++ /dev/null
@@ -1,13 +0,0 @@
-// Test that the color of a raw block is not overwritten
-
----
-
-#show raw: set text(fill: blue)
-
-`Hello, World!`
-
-```rs
-fn main() {
- println!("Hello, World!");
-}
-``` \ No newline at end of file
diff --git a/tests/typ/bugs/smartquotes-in-outline.typ b/tests/typ/bugs/smartquotes-in-outline.typ
deleted file mode 100644
index 1ecfcdc4..00000000
--- a/tests/typ/bugs/smartquotes-in-outline.typ
+++ /dev/null
@@ -1,4 +0,0 @@
-#set page(width: 15em)
-#outline()
-
-= "This" "is" "a" "test"
diff --git a/tests/typ/bugs/smartquotes-on-newline.typ b/tests/typ/bugs/smartquotes-on-newline.typ
deleted file mode 100644
index 3180350e..00000000
--- a/tests/typ/bugs/smartquotes-on-newline.typ
+++ /dev/null
@@ -1,7 +0,0 @@
-// Test that smart quotes are inferred correctly across newlines.
-
----
-"test"#linebreak()"test"
-
-"test"\
-"test"
diff --git a/tests/typ/bugs/spacing-behaviour.typ b/tests/typ/bugs/spacing-behaviour.typ
deleted file mode 100644
index a2a30b8a..00000000
--- a/tests/typ/bugs/spacing-behaviour.typ
+++ /dev/null
@@ -1,9 +0,0 @@
-// Test that metadata after spacing does not force a new paragraph.
-
----
-#{
- h(1em)
- counter(heading).update(4)
- [Hello ]
- counter(heading).display()
-}
diff --git a/tests/typ/bugs/square-base.typ b/tests/typ/bugs/square-base.typ
deleted file mode 100644
index d8339c1a..00000000
--- a/tests/typ/bugs/square-base.typ
+++ /dev/null
@@ -1,5 +0,0 @@
-// Test that square sets correct base for its content.
-
----
-#set page(height: 80pt)
-#square(width: 40%, rect(width: 60%, height: 80%))
diff --git a/tests/typ/bugs/subelement-panic.typ b/tests/typ/bugs/subelement-panic.typ
deleted file mode 100644
index fcad83bc..00000000
--- a/tests/typ/bugs/subelement-panic.typ
+++ /dev/null
@@ -1,40 +0,0 @@
-// Test that figure captions don't cause panics.
-// Ref: false
-
----
-// #2530
-#figure(caption: [test])[].caption
-
----
-// #2165
-#figure.caption[]
-
----
-// #2328
-// Error: 4-43 footnote entry must have a location
-// Hint: 4-43 try using a query or a show rule to customize the footnote instead
-HI#footnote.entry(clearance: 2.5em)[There]
-
----
-// Enum item (pre-emptive)
-#enum.item(none)[Hello]
-#enum.item(17)[Hello]
-
----
-// List item (pre-emptive)
-#list.item[Hello]
-
----
-// Term item (pre-emptive)
-#terms.item[Hello][World!]
-
----
-// Outline entry (pre-emptive)
-// Error: 2-48 cannot outline text
-#outline.entry(1, [Hello], [World!], none, [1])
-
----
-// Outline entry (pre-emptive, improved error)
-// Error: 2-55 heading must have a location
-// Hint: 2-55 try using a query or a show rule to customize the outline.entry instead
-#outline.entry(1, heading[Hello], [World!], none, [1])
diff --git a/tests/typ/bugs/table-lines.typ b/tests/typ/bugs/table-lines.typ
deleted file mode 100644
index 7e954012..00000000
--- a/tests/typ/bugs/table-lines.typ
+++ /dev/null
@@ -1,10 +0,0 @@
-// Ensure no empty lines before a table that doesn't fit into the first page.
-
----
-#set page(height: 50pt)
-
-Hello
-#table(
- columns: 4,
- [1], [2], [3], [4]
-)
diff --git a/tests/typ/bugs/table-row-missing.typ b/tests/typ/bugs/table-row-missing.typ
deleted file mode 100644
index d72305ba..00000000
--- a/tests/typ/bugs/table-row-missing.typ
+++ /dev/null
@@ -1,8 +0,0 @@
-// Test that a table row isn't wrongly treated like a gutter row.
-
----
-#set page(height: 70pt)
-#table(
- rows: 16pt,
- ..range(6).map(str).flatten(),
-)
diff --git a/tests/typ/coma.typ b/tests/typ/coma.typ
deleted file mode 100644
index 8ca08ddb..00000000
--- a/tests/typ/coma.typ
+++ /dev/null
@@ -1,24 +0,0 @@
-#set page(width: 450pt, margin: 1cm)
-
-*Technische Universität Berlin* #h(1fr) *WiSe 2019/2020* \
-*Fakultät II, Institut for Mathematik* #h(1fr) Woche 3 \
-Sekretariat MA \
-Dr. Max Mustermann \
-Ola Nordmann, John Doe
-
-#v(3mm)
-#align(center)[
- #set par(leading: 3mm)
- #text(1.2em)[*3. Übungsblatt Computerorientierte Mathematik II*] \
- *Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) \
- *Alle Antworten sind zu beweisen.*
-]
-
-*1. Aufgabe* #h(1fr) (1 + 1 + 2 Punkte)
-
-Ein _Binärbaum_ ist ein Wurzelbaum, in dem jeder Knoten ≤ 2 Kinder hat.
-Die Tiefe eines Knotens _v_ ist die Länge des eindeutigen Weges von der Wurzel
-zu _v_, und die Höhe von _v_ ist die Länge eines längsten (absteigenden) Weges
-von _v_ zu einem Blatt. Die Höhe des Baumes ist die Höhe der Wurzel.
-
-#align(center, image("/assets/images/graph.png", width: 75%))
diff --git a/tests/typ/compiler/array.typ b/tests/typ/compiler/array.typ
deleted file mode 100644
index 597f242c..00000000
--- a/tests/typ/compiler/array.typ
+++ /dev/null
@@ -1,392 +0,0 @@
-// Test arrays.
-// Ref: false
-
----
-// Ref: true
-
-#set page(width: 150pt)
-
-// Empty.
-#()
-
-// Not an array, just a parenthesized expression.
-#(1)
-
-// One item and trailing comma.
-#(-1,)
-
-// No trailing comma.
-#(true, false)
-
-// Multiple lines and items and trailing comma.
-#("1"
- , rgb("002")
- ,)
-
----
-// Test the `len` method.
-#test(().len(), 0)
-#test(("A", "B", "C").len(), 3)
-
----
-// Test lvalue and rvalue access.
-#{
- let array = (1, 2)
- array.at(1) += 5 + array.at(0)
- test(array, (1, 8))
-}
-
----
-// Test different lvalue method.
-#{
- let array = (1, 2, 3)
- array.first() = 7
- array.at(1) *= 8
- test(array, (7, 16, 3))
-}
-
----
-// Test rvalue out of bounds.
-// Error: 2-17 array index out of bounds (index: 5, len: 3) and no default value was specified
-#(1, 2, 3).at(5)
-
----
-// Test lvalue out of bounds.
-#{
- let array = (1, 2, 3)
- // Error: 3-14 array index out of bounds (index: 3, len: 3)
- array.at(3) = 5
-}
-
----
-// Test default value.
-#test((1, 2, 3).at(2, default: 5), 3)
-#test((1, 2, 3).at(3, default: 5), 5)
-
----
-// Test remove with default value.
-
-#{
- let array = (1, 2, 3)
- test(array.remove(2, default: 5), 3)
-}
-
-#{
- let array = (1, 2, 3)
- test(array.remove(3, default: 5), 5)
-}
-
----
-// Test bad lvalue.
-// Error: 2:3-2:14 cannot mutate a temporary value
-#let array = (1, 2, 3)
-#(array.len() = 4)
-
----
-// Test bad lvalue.
-// Error: 2:9-2:13 type array has no method `yolo`
-#let array = (1, 2, 3)
-#(array.yolo() = 4)
-
----
-// Test negative indices.
-#{
- let array = (1, 2, 3, 4)
- test(array.at(0), 1)
- test(array.at(-1), 4)
- test(array.at(-2), 3)
- test(array.at(-3), 2)
- test(array.at(-4), 1)
-}
-
----
-// The the `first` and `last` methods.
-#test((1,).first(), 1)
-#test((2,).last(), 2)
-#test((1, 2, 3).first(), 1)
-#test((1, 2, 3).last(), 3)
-
----
-// Error: 2-12 array is empty
-#().first()
-
----
-// Error: 2-11 array is empty
-#().last()
-
----
-// Test the `push` and `pop` methods.
-#{
- let tasks = (a: (1, 2, 3), b: (4, 5, 6))
- test(tasks.at("a").pop(), 3)
- tasks.b.push(7)
- test(tasks.a, (1, 2))
- test(tasks.at("b"), (4, 5, 6, 7))
-}
-
----
-// Test the `insert` and `remove` methods.
-#{
- let array = (0, 1, 2, 4, 5)
- array.insert(3, 3)
- test(array, range(6))
- array.remove(1)
- test(array, (0, 2, 3, 4, 5))
-}
-
----
-// Error: 2:2-2:18 missing argument: index
-#let numbers = ()
-#numbers.insert()
-
----
-// Test the `slice` method.
-#test((1, 2, 3, 4).slice(2), (3, 4))
-#test(range(10).slice(2, 6), (2, 3, 4, 5))
-#test(range(10).slice(4, count: 3), (4, 5, 6))
-#test(range(10).slice(-5, count: 2), (5, 6))
-#test((1, 2, 3).slice(2, -2), ())
-#test((1, 2, 3).slice(-2, 2), (2,))
-#test((1, 2, 3).slice(-3, 2), (1, 2))
-#test("ABCD".split("").slice(1, -1).join("-"), "A-B-C-D")
-
----
-// Error: 2-30 array index out of bounds (index: 12, len: 10)
-#range(10).slice(9, count: 3)
-
----
-// Error: 2-24 array index out of bounds (index: -4, len: 3)
-#(1, 2, 3).slice(0, -4)
-
----
-// Test the `position` method.
-#test(("Hi", "❤️", "Love").position(s => s == "❤️"), 1)
-#test(("Bye", "💘", "Apart").position(s => s == "❤️"), none)
-#test(("A", "B", "CDEF", "G").position(v => v.len() > 2), 2)
-
----
-// Test the `filter` method.
-#test(().filter(calc.even), ())
-#test((1, 2, 3, 4).filter(calc.even), (2, 4))
-#test((7, 3, 2, 5, 1).filter(x => x < 5), (3, 2, 1))
-
----
-// Test the `map` method.
-#test(().map(x => x * 2), ())
-#test((2, 3).map(x => x * 2), (4, 6))
-
----
-// Test the `fold` method.
-#test(().fold("hi", grid), "hi")
-#test((1, 2, 3, 4).fold(0, (s, x) => s + x), 10)
-
----
-// Error: 20-22 unexpected argument
-#(1, 2, 3).fold(0, () => none)
-
----
-// Test the `sum` method.
-#test(().sum(default: 0), 0)
-#test(().sum(default: []), [])
-#test((1, 2, 3).sum(), 6)
-
----
-// Error: 2-10 cannot calculate sum of empty array with no default
-#().sum()
-
----
-// Test the `product` method.
-#test(().product(default: 0), 0)
-#test(().product(default: []), [])
-#test(([ab], 3).product(), [ab]*3)
-#test((1, 2, 3).product(), 6)
-
----
-// Error: 2-14 cannot calculate product of empty array with no default
-#().product()
-
----
-// Test the `rev` method.
-#test(range(3).rev(), (2, 1, 0))
-
----
-// Test the `join` method.
-#test(().join(), none)
-#test((1,).join(), 1)
-#test(("a", "b", "c").join(), "abc")
-#test("(" + ("a", "b", "c").join(", ") + ")", "(a, b, c)")
-
----
-// Error: 2-22 cannot join boolean with boolean
-#(true, false).join()
-
----
-// Error: 2-20 cannot join string with integer
-#("a", "b").join(1)
-
----
-// Test joining content.
-// Ref: true
-#([One], [Two], [Three]).join([, ], last: [ and ]).
-
----
-// Test the `intersperse` method
-#test(().intersperse("a"), ())
-#test((1,).intersperse("a"), (1,))
-#test((1, 2).intersperse("a"), (1, "a", 2))
-#test((1, 2, "b").intersperse("a"), (1, "a", 2, "a", "b"))
-
----
-// Test the `chunks` method.
-#test(().chunks(10), ())
-#test((1, 2, 3).chunks(10), ((1, 2, 3),))
-#test((1, 2, 3, 4, 5, 6).chunks(3), ((1, 2, 3), (4, 5, 6)))
-#test((1, 2, 3, 4, 5, 6, 7, 8).chunks(3), ((1, 2, 3), (4, 5, 6), (7, 8)))
-
-#test(().chunks(10, exact: true), ())
-#test((1, 2, 3).chunks(10, exact: true), ())
-#test((1, 2, 3, 4, 5, 6).chunks(3, exact: true), ((1, 2, 3), (4, 5, 6)))
-#test((1, 2, 3, 4, 5, 6, 7, 8).chunks(3, exact: true), ((1, 2, 3), (4, 5, 6)))
-
----
-// Error: 19-20 number must be positive
-#(1, 2, 3).chunks(0)
-
----
-// Error: 19-21 number must be positive
-#(1, 2, 3).chunks(-5)
-
----
-// Test the `sorted` method.
-#test(().sorted(), ())
-#test(().sorted(key: x => x), ())
-#test(((true, false) * 10).sorted(), (false,) * 10 + (true,) * 10)
-#test(("it", "the", "hi", "text").sorted(), ("hi", "it", "text", "the"))
-#test(("I", "the", "hi", "text").sorted(key: x => x), ("I", "hi", "text", "the"))
-#test(("I", "the", "hi", "text").sorted(key: x => x.len()), ("I", "hi", "the", "text"))
-#test((2, 1, 3, 10, 5, 8, 6, -7, 2).sorted(), (-7, 1, 2, 2, 3, 5, 6, 8, 10))
-#test((2, 1, 3, -10, -5, 8, 6, -7, 2).sorted(key: x => x), (-10, -7, -5, 1, 2, 2, 3, 6, 8))
-#test((2, 1, 3, -10, -5, 8, 6, -7, 2).sorted(key: x => x * x), (1, 2, 2, 3, -5, 6, -7, 8, -10))
-
----
-// Error: 12-18 unexpected argument
-#().sorted(x => x)
-
----
-// Test the `zip` method.
-#test(().zip(()), ())
-#test((1,).zip(()), ())
-#test((1,).zip((2,)), ((1, 2),))
-#test((1, 2).zip((3, 4)), ((1, 3), (2, 4)))
-#test((1, 2, 3, 4).zip((5, 6)), ((1, 5), (2, 6)))
-#test(((1, 2), 3).zip((4, 5)), (((1, 2), 4), (3, 5)))
-#test((1, "hi").zip((true, false)), ((1, true), ("hi", false)))
-#test((1, 2, 3).zip((3, 4, 5), (6, 7, 8)), ((1, 3, 6), (2, 4, 7), (3, 5, 8)))
-#test(().zip((), ()), ())
-#test((1,).zip((2,), (3,)), ((1, 2, 3),))
-#test((1, 2, 3).zip(), ((1,), (2,), (3,)))
-#test(array.zip(()), ())
-
-
----
-// Test the `enumerate` method.
-#test(().enumerate(), ())
-#test(().enumerate(start: 5), ())
-#test(("a", "b", "c").enumerate(), ((0, "a"), (1, "b"), (2, "c")))
-#test(("a", "b", "c").enumerate(start: 1), ((1, "a"), (2, "b"), (3, "c")))
-#test(("a", "b", "c").enumerate(start: 42), ((42, "a"), (43, "b"), (44, "c")))
-#test(("a", "b", "c").enumerate(start: -7), ((-7, "a"), (-6, "b"), (-5, "c")))
-
----
-// Test the `dedup` method.
-#test(().dedup(), ())
-#test((1,).dedup(), (1,))
-#test((1, 1).dedup(), (1,))
-#test((1, 2, 1).dedup(), (1, 2))
-#test(("Jane", "John", "Eric").dedup(), ("Jane", "John", "Eric"))
-#test(("Jane", "John", "Eric", "John").dedup(), ("Jane", "John", "Eric"))
-
----
-// Test the `dedup` method with the `key` argument.
-#test((1, 2, 3, 4, 5, 6).dedup(key: x => calc.rem(x, 2)), (1, 2))
-#test((1, 2, 3, 4, 5, 6).dedup(key: x => calc.rem(x, 3)), (1, 2, 3))
-#test(("Hello", "World", "Hi", "There").dedup(key: x => x.len()), ("Hello", "Hi"))
-#test(("Hello", "World", "Hi", "There").dedup(key: x => x.at(0)), ("Hello", "World", "There"))
-
----
-// Test the `to-dict` method.
-#test(().to-dict(), (:))
-#test((("a", 1), ("b", 2), ("c", 3)).to-dict(), (a: 1, b: 2, c: 3))
-#test((("a", 1), ("b", 2), ("c", 3), ("b", 4)).to-dict(), (a: 1, b: 4, c: 3))
-
----
-// Error: 2-16 expected (str, any) pairs, found integer
-#(1,).to-dict()
-
----
-// Error: 2-19 expected pairs of length 2, found length 1
-#((1,),).to-dict()
-
----
-// Error: 2-26 expected pairs of length 2, found length 3
-#(("key",1,2),).to-dict()
-
----
-// Error: 2-21 expected key of type str, found integer
-#((1, 2),).to-dict()
-
----
-// Error: 9-26 unexpected argument: val
-#().zip(val: "applicable")
-
----
-// Error: 13-30 unexpected argument: val
-#().zip((), val: "applicable")
-
----
-// Error: 32-37 cannot divide by zero
-#(1, 2, 0, 3).sorted(key: x => 5 / x)
-
----
-// Error: 2-26 cannot compare content and content
-#([Hi], [There]).sorted()
-
----
-// Error: 2-26 cannot compare 3em with 2pt
-#(1pt, 2pt, 3em).sorted()
-
----
-// Error: 42-52 unexpected argument
-#((k: "a", v: 2), (k: "b", v: 1)).sorted(it => it.v)
-
----
-// Error: 2-18 array index out of bounds (index: -4, len: 3) and no default value was specified
-#(1, 2, 3).at(-4)
-
----
-// Error: 3-4 unclosed delimiter
-#{(}
-
-// Error: 2-3 unclosed delimiter
-#{)}
-
-// Error: 4-6 unexpected end of block comment
-#(1*/2)
-
-// Error: 6-8 invalid number suffix: u
-#(1, 1u 2)
-
-// Error: 3-4 unexpected comma
-#(,1)
-
-// Missing expression makes named pair incomplete, making this an empty array.
-// Error: 5 expected expression
-#(a:)
-
-// Named pair after this is already identified as an array.
-// Error: 6-10 expected expression, found named pair
-#(1, b: 2)
-
-// Keyed pair after this is already identified as an array.
-// Error: 6-14 expected expression, found keyed pair
-#(1, "key": 2)
diff --git a/tests/typ/compiler/backtracking.typ b/tests/typ/compiler/backtracking.typ
deleted file mode 100644
index 9c3ab8ec..00000000
--- a/tests/typ/compiler/backtracking.typ
+++ /dev/null
@@ -1,33 +0,0 @@
-// Ensure that parser backtracking doesn't lead to exponential time consumption.
-// If this regresses, the test suite will not terminate, which is a bit
-// unfortunate compared to a good error, but at least we know something is up.
-//
-// Ref: false
-
----
-#{
- let s = "(x: 1) => x"
- let pat = "(x: {}) => 1 + x()"
- for _ in range(50) {
- s = pat.replace("{}", s)
- }
- test(eval(s)(), 51)
-}
-
----
-#{
- let s = "(x) = 1"
- let pat = "(x: {_}) = 1"
- for _ in range(100) {
- s = pat.replace("_", s)
- }
- // Error: 8-9 cannot destructure integer
- eval(s)
-}
-
----
-// Test whitespace after memoized part.
-#( (x: () => 1 ) => 1 )
-// -------
-// This is memoized and we want to ensure that whitespace after this
-// is handled correctly.
diff --git a/tests/typ/compiler/block.typ b/tests/typ/compiler/block.typ
deleted file mode 100644
index 48c9fefc..00000000
--- a/tests/typ/compiler/block.typ
+++ /dev/null
@@ -1,145 +0,0 @@
-// Test code blocks.
-// Ref: false
-
----
-// Ref: true
-
-// Evaluates to join of none, [My ] and the two loop bodies.
-#{
- let parts = ("my fri", "end.")
- [Hello, ]
- for s in parts [#s]
-}
-
-// Evaluates to join of the content and strings.
-#{
- [How]
- if true {
- " are"
- }
- [ ]
- if false [Nope]
- [you] + "?"
-}
-
----
-// Nothing evaluates to none.
-#test({}, none)
-
-// Let evaluates to none.
-#test({ let v = 0 }, none)
-
-// Evaluates to single expression.
-#test({ "hello" }, "hello")
-
-// Evaluates to string.
-#test({ let x = "m"; x + "y" }, "my")
-
-// Evaluated to int.
-#test({
- let x = 1
- let y = 2
- x + y
-}, 3)
-
-// String is joined with trailing none, evaluates to string.
-#test({
- type("")
- none
-}, str)
-
----
-// Some things can't be joined.
-#{
- [A]
- // Error: 3-4 cannot join content with integer
- 1
- [B]
-}
-
----
-// Block directly in markup also creates a scope.
-#{ let x = 1 }
-
-// Error: 7-8 unknown variable: x
-#test(x, 1)
-
----
-// Block in expression does create a scope.
-#let a = {
- let b = 1
- b
-}
-
-#test(a, 1)
-
-// Error: 3-4 unknown variable: b
-#{b}
-
----
-// Double block creates a scope.
-#{{
- import "module.typ": b
- test(b, 1)
-}}
-
-// Error: 2-3 unknown variable: b
-#b
-
----
-// Multiple nested scopes.
-#{
- let a = "a1"
- {
- let a = "a2"
- {
- test(a, "a2")
- let a = "a3"
- test(a, "a3")
- }
- test(a, "a2")
- }
- test(a, "a1")
-}
-
----
-// Content blocks also create a scope.
-#[#let x = 1]
-
-// Error: 2-3 unknown variable: x
-#x
-
----
-// Multiple unseparated expressions in one line.
-
-// Error: 2-4 invalid number suffix: u
-#1u
-
-// Should output `1`.
-// Error: 4 expected semicolon or line break
-#{1 2}
-
-// Should output `2`.
-// Error: 13 expected semicolon or line break
-// Error: 23 expected semicolon or line break
-#{let x = -1 let y = 3 x + y}
-
-// Should output `3`.
-#{
- // Error: 7-10 expected pattern, found string
- for "v"
-
- // Error: 8 expected keyword `in`
- // Error: 22 expected block
- for v let z = 1 + 2
-
- z
-}
-
----
-// Error: 2-3 unclosed delimiter
-#{
-
----
-// Error: 2-3 unexpected closing brace
-#}
diff --git a/tests/typ/compiler/break-continue.typ b/tests/typ/compiler/break-continue.typ
deleted file mode 100644
index 4c4738bb..00000000
--- a/tests/typ/compiler/break-continue.typ
+++ /dev/null
@@ -1,162 +0,0 @@
-// Test break and continue in loops.
-// Ref: false
-
----
-// Test break.
-
-#let var = 0
-#let error = false
-
-#for i in range(10) {
- var += i
- if i > 5 {
- break
- error = true
- }
-}
-
-#test(var, 21)
-#test(error, false)
-
----
-// Test joining with break.
-
-#let i = 0
-#let x = while true {
- i += 1
- str(i)
- if i >= 5 {
- "."
- break
- }
-}
-
-#test(x, "12345.")
-
----
-// Test continue.
-
-#let i = 0
-#let x = 0
-
-#while x < 8 {
- i += 1
- if calc.rem(i, 3) == 0 {
- continue
- }
- x += i
-}
-
-// If continue did not work, this would equal 10.
-#test(x, 12)
-
----
-// Test joining with continue.
-
-#let x = for i in range(5) {
- "a"
- if calc.rem(i, 3) == 0 {
- "_"
- continue
- }
- str(i)
-}
-
-#test(x, "a_a1a2a_a4")
-
----
-// Test break outside of loop.
-#let f() = {
- // Error: 3-8 cannot break outside of loop
- break
-}
-
-#for i in range(1) {
- f()
-}
-
----
-// Test break in function call.
-#let identity(x) = x
-#let out = for i in range(5) {
- "A"
- identity({
- "B"
- break
- })
- "C"
-}
-
-#test(out, "AB")
-
----
-// Test continue outside of loop.
-
-// Error: 12-20 cannot continue outside of loop
-#let x = { continue }
-
----
-// Error: 2-10 cannot continue outside of loop
-#continue
-
----
-// Ref: true
-// Should output `Hello World 🌎`.
-#for _ in range(10) {
- [Hello ]
- [World #{
- [🌎]
- break
- }]
-}
-
----
-// Ref: true
-// Should output `Some` in red, `Some` in blue and `Last` in green.
-// Everything should be in smallcaps.
-#for color in (red, blue, green, yellow) [
- #set text(font: "Roboto")
- #show: it => text(fill: color, it)
- #smallcaps(if color != green [
- Some
- ] else [
- Last
- #break
- ])
-]
-
----
-// Ref: true
-// Test break in set rule.
-// Should output `Hi` in blue.
-#for i in range(10) {
- [Hello]
- set text(blue, ..break)
- [Not happening]
-}
-
----
-// Test second block during break flow.
-// Ref: true
-
-#for i in range(10) {
- table(
- { [A]; break },
- for _ in range(3) [B]
- )
-}
-
----
-// Ref: true
-// Test continue while destructuring.
-// Should output "one = I \ two = II \ one = I".
-#for num in (1, 2, 3, 1) {
- let (word, roman) = if num == 1 {
- ("one", "I")
- } else if num == 2 {
- ("two", "II")
- } else {
- continue
- }
- [#word = #roman \ ]
-}
diff --git a/tests/typ/compiler/bytes.typ b/tests/typ/compiler/bytes.typ
deleted file mode 100644
index a9249bdd..00000000
--- a/tests/typ/compiler/bytes.typ
+++ /dev/null
@@ -1,32 +0,0 @@
-// Test the bytes type.
-// Ref: false
-
----
-#let data = read("/assets/images/rhino.png", encoding: none)
-#test(data.len(), 232243)
-#test(data.slice(0, count: 5), bytes((137, 80, 78, 71, 13)))
-#test(str(data.slice(1, 4)), "PNG")
-#test(repr(data), "bytes(232243)")
-
----
-#test(str(bytes(range(0x41, 0x50))), "ABCDEFGHIJKLMNO")
-#test(array(bytes("Hello")), (0x48, 0x65, 0x6C, 0x6C, 0x6F))
-
----
-// Test addition and joining.
-#test(bytes((1, 2)) + bytes(()), bytes((1, 2)))
-#test(bytes((1, 2)) + bytes((3, 4)), bytes((1, 2, 3, 4)))
-#test(bytes(()) + bytes((3, 4)), bytes((3, 4)))
-#test(str({
- bytes("Hello")
- bytes((0x20,))
- bytes("World")
-}), "Hello World")
-
----
-// Error: 8-14 expected string, array, or bytes, found dictionary
-#bytes((a: 1))
-
----
-// Error: 8-15 expected array, bytes, or version, found string
-#array("hello")
diff --git a/tests/typ/compiler/call.typ b/tests/typ/compiler/call.typ
deleted file mode 100644
index 0c225a1c..00000000
--- a/tests/typ/compiler/call.typ
+++ /dev/null
@@ -1,111 +0,0 @@
-// Test function calls.
-// Ref: false
-
----
-// Ref: true
-
-// Omitted space.
-#let f() = {}
-#[#f()*Bold*]
-
-// Call return value of function with body.
-#let f(x, body) = (y) => [#x] + body + [#y]
-#f(1)[2](3)
-
-// Don't parse this as a function.
-#test (it)
-
-#let f(body) = body
-#f[A]
-#f()[A]
-#f([A])
-
-#let g(a, b) = a + b
-#g[A][B]
-#g([A], [B])
-#g()[A][B]
-
----
-// Trailing comma.
-#test(1 + 1, 2,)
-
-// Call function assigned to variable.
-#let alias = type
-#test(alias(alias), type)
-
-// Callee expressions.
-#{
- // Wrapped in parens.
- test((type)("hi"), str)
-
- // Call the return value of a function.
- let adder(dx) = x => x + dx
- test(adder(2)(5), 7)
-}
-
----
-// Error: 26-30 duplicate argument: font
-#set text(font: "Arial", font: "Helvetica")
-
----
-// Error: 4-15 the argument `amount` is positional
-// Hint: 4-15 try removing `amount:`
-#h(amount: 0.5)
-
----
-// Error: 2-6 expected function, found boolean
-#true()
-
----
-#let x = "x"
-
-// Error: 2-3 expected function, found string
-#x()
-
----
-#let f(x) = x
-
-// Error: 2-6 expected function, found integer
-#f(1)(2)
-
----
-#let f(x) = x
-
-// Error: 2-6 expected function, found content
-#f[1](2)
-
----
-// Error: 7-8 unexpected colon
-#func(:)
-
-// Error: 10-12 unexpected end of block comment
-#func(a:1*/)
-
-// Error: 8 expected comma
-#func(1 2)
-
-// Error: 7-8 expected identifier, found integer
-// Error: 9 expected expression
-#func(1:)
-
-// Error: 7-8 expected identifier, found integer
-#func(1:2)
-
-// Error: 7-12 expected identifier, found string
-#func("abc": 2)
-
-// Error: 7-10 expected identifier, found group
-#func((x):1)
-
----
-// Error: 6-7 unclosed delimiter
-#func[`a]`
-
----
-// Error: 7-8 unclosed delimiter
-#{func(}
-
----
-// Error: 6-7 unclosed delimiter
-// Error: 1:7-2:1 unclosed string
-#func("]
diff --git a/tests/typ/compiler/closure.typ b/tests/typ/compiler/closure.typ
deleted file mode 100644
index 29c092b7..00000000
--- a/tests/typ/compiler/closure.typ
+++ /dev/null
@@ -1,219 +0,0 @@
-// Test closures.
-// Ref: false
-
----
-// Don't parse closure directly in content.
-// Ref: true
-
-#let x = "x"
-
-// Should output `x => y`.
-#x => y
-
----
-// Basic closure without captures.
-#{
- let adder = (x, y) => x + y
- test(adder(2, 3), 5)
-}
-
----
-// Pass closure as argument and return closure.
-// Also uses shorthand syntax for a single argument.
-#{
- let chain = (f, g) => (x) => f(g(x))
- let f = x => x + 1
- let g = x => 2 * x
- let h = chain(f, g)
- test(h(2), 5)
-}
-
----
-// Capture environment.
-#{
- let mark = "!"
- let greet = {
- let hi = "Hi"
- name => {
- hi + ", " + name + mark
- }
- }
-
- test(greet("Typst"), "Hi, Typst!")
-
- // Changing the captured variable after the closure definition has no effect.
- mark = "?"
- test(greet("Typst"), "Hi, Typst!")
-}
-
----
-// Redefined variable.
-#{
- let x = 1
- let f() = {
- let x = x + 2
- x
- }
- test(f(), 3)
-}
-
----
-// Import bindings.
-#{
- let b = "module.typ"
- let f() = {
- import b: b
- b
- }
- test(f(), 1)
-}
-
----
-// For loop bindings.
-#{
- let v = (1, 2, 3)
- let f() = {
- let s = 0
- for v in v { s += v }
- s
- }
- test(f(), 6)
-}
-
----
-// Let + closure bindings.
-#{
- let g = "hi"
- let f() = {
- let g() = "bye"
- g()
- }
- test(f(), "bye")
-}
-
----
-// Parameter bindings.
-#{
- let x = 5
- let g() = {
- let f(x, y: x) = x + y
- f
- }
-
- test(g()(8), 13)
-}
-
----
-// Don't leak environment.
-#{
- // Error: 16-17 unknown variable: x
- let func() = x
- let x = "hi"
- func()
-}
-
----
-// Too few arguments.
-#{
- let types(x, y) = "[" + str(type(x)) + ", " + str(type(y)) + "]"
- test(types(14%, 12pt), "[ratio, length]")
-
- // Error: 8-21 missing argument: y
- test(types("nope"), "[string, none]")
-}
-
----
-// Too many arguments.
-#{
- let f(x) = x + 1
-
- // Error: 8-13 unexpected argument
- f(1, "two", () => x)
-}
-
----
-// Mutable method with capture in argument.
-#let x = "b"
-#let f() = {
- let a = (b: 5)
- a.at(x) = 10
- a
-}
-#f()
-
----
-#let x = ()
-#let f() = {
- // Error: 3-4 variables from outside the function are read-only and cannot be modified
- x.at(1) = 2
-}
-#f()
-
----
-// Named arguments.
-#{
- let greet(name, birthday: false) = {
- if birthday { "Happy Birthday, " } else { "Hey, " } + name + "!"
- }
-
- test(greet("Typst"), "Hey, Typst!")
- test(greet("Typst", birthday: true), "Happy Birthday, Typst!")
-
- // Error: 23-35 unexpected argument: whatever
- test(greet("Typst", whatever: 10))
-}
-
----
-// Parameter unpacking.
-#let f((a, b), ..c) = (a, b, c)
-#test(f((1, 2), 3, 4), (1, 2, (3, 4)))
-
-#let f((k: a, b), c: 3, (d,)) = (a, b, c, d)
-#test(f((k: 1, b: 2), (4,)), (1, 2, 3, 4))
-
-// Error: 8-14 expected identifier, found destructuring pattern
-#let f((a, b): 0) = none
-
-// Error: 10-19 expected pattern, found array
-#let f(..(a, b: c)) = none
-
-// Error: 10-16 expected pattern, found array
-#let f(..(a, b)) = none
-
----
-// Error: 11-12 duplicate parameter: x
-#let f(x, x) = none
-
----
-// Error: 21 expected comma
-// Error: 22-23 expected pattern, found integer
-// Error: 24-25 unexpected plus
-// Error: 26-27 expected pattern, found integer
-#let f = (x: () => 1 2 + 3) => 4
-
----
-// Error: 14-15 duplicate parameter: a
-// Error: 23-24 duplicate parameter: b
-// Error: 35-36 duplicate parameter: b
-#let f(a, b, a: none, b: none, c, b) = none
-
----
-// Error: 13-14 duplicate parameter: a
-#let f(a, ..a) = none
-
----
-// Error: 7-14 expected pattern, found string
-#((a, "named": b) => none)
-
----
-// Error: 10-15 expected pattern, found string
-#let foo("key": b) = key
-
----
-// Error: 10-14 expected pattern, found `none`
-// Hint: 10-14 keyword `none` is not allowed as an identifier; try `none_` instead
-#let foo(none: b) = key
-
----
-// Error: 10-11 expected identifier, found underscore
-#let foo(_: 3) = none
diff --git a/tests/typ/compiler/color.typ b/tests/typ/compiler/color.typ
deleted file mode 100644
index ac83355d..00000000
--- a/tests/typ/compiler/color.typ
+++ /dev/null
@@ -1,101 +0,0 @@
-// Test color modification methods.
-
----
-// Test CMYK color conversion.
-#let c = cmyk(50%, 64%, 16%, 17%)
-#stack(
- dir: ltr,
- spacing: 1fr,
- rect(width: 1cm, fill: cmyk(69%, 11%, 69%, 41%)),
- rect(width: 1cm, fill: c),
- rect(width: 1cm, fill: c.negate(space: cmyk)),
-)
-
-#for x in range(0, 11) {
- box(square(size: 9pt, fill: c.lighten(x * 10%)))
-}
-#for x in range(0, 11) {
- box(square(size: 9pt, fill: c.darken(x * 10%)))
-}
-
----
-// The the different color spaces
-#let col = rgb(50%, 64%, 16%)
-#box(square(size: 9pt, fill: col))
-#box(square(size: 9pt, fill: rgb(col)))
-#box(square(size: 9pt, fill: oklab(col)))
-#box(square(size: 9pt, fill: oklch(col)))
-#box(square(size: 9pt, fill: luma(col)))
-#box(square(size: 9pt, fill: cmyk(col)))
-#box(square(size: 9pt, fill: color.linear-rgb(col)))
-#box(square(size: 9pt, fill: color.hsl(col)))
-#box(square(size: 9pt, fill: color.hsv(col)))
-
----
-// Colors outside the sRGB gamut.
-#box(square(size: 9pt, fill: oklab(90%, -0.2, -0.1)))
-#box(square(size: 9pt, fill: oklch(50%, 0.5, 0deg)))
-
----
-// Test hue rotation
-#let col = rgb(50%, 64%, 16%)
-
-// Oklch
-#for x in range(0, 11) {
- box(square(size: 9pt, fill: rgb(col).rotate(x * 36deg)))
-}
-
-// HSL
-#for x in range(0, 11) {
- box(square(size: 9pt, fill: rgb(col).rotate(x * 36deg, space: color.hsl)))
-}
-
-// HSV
-#for x in range(0, 11) {
- box(square(size: 9pt, fill: rgb(col).rotate(x * 36deg, space: color.hsv)))
-}
-
----
-// Test saturation
-#let col = color.hsl(180deg, 0%, 50%)
-#for x in range(0, 11) {
- box(square(size: 9pt, fill: col.saturate(x * 10%)))
-}
-
-#let col = color.hsl(180deg, 100%, 50%)
-#for x in range(0, 11) {
- box(square(size: 9pt, fill: col.desaturate(x * 10%)))
-}
-
-#let col = color.hsv(180deg, 0%, 50%)
-#for x in range(0, 11) {
- box(square(size: 9pt, fill: col.saturate(x * 10%)))
-}
-
-#let col = color.hsv(180deg, 100%, 50%)
-#for x in range(0, 11) {
- box(square(size: 9pt, fill: col.desaturate(x * 10%)))
-}
-
----
-// Test gray color modification.
-// Ref: false
-#test-repr(luma(20%).lighten(50%), luma(60%))
-#test-repr(luma(80%).darken(20%), luma(64%))
-#test-repr(luma(80%).negate(space: luma), luma(20%))
-
----
-// Test alpha modification.
-// Ref: false
-#test-repr(luma(100%, 100%).transparentize(50%), luma(100%, 50%))
-#test-repr(luma(100%, 100%).transparentize(75%), luma(100%, 25%))
-#test-repr(luma(100%, 50%).transparentize(50%), luma(100%, 25%))
-#test-repr(luma(100%, 10%).transparentize(250%), luma(100%, 0%))
-#test-repr(luma(100%, 40%).transparentize(-50%), luma(100%, 70%))
-#test-repr(luma(100%, 0%).transparentize(-100%), luma(100%, 100%))
-
-#test-repr(luma(100%, 50%).opacify(50%), luma(100%, 75%))
-#test-repr(luma(100%, 20%).opacify(100%), luma(100%, 100%))
-#test-repr(luma(100%, 100%).opacify(250%), luma(100%, 100%))
-#test-repr(luma(100%, 50%).opacify(-50%), luma(100%, 25%))
-#test-repr(luma(100%, 0%).opacify(0%), luma(100%, 0%))
diff --git a/tests/typ/compiler/comment.typ b/tests/typ/compiler/comment.typ
deleted file mode 100644
index 31025de6..00000000
--- a/tests/typ/compiler/comment.typ
+++ /dev/null
@@ -1,34 +0,0 @@
-// Test line and block comments.
-
----
-// Line comment acts as spacing.
-A// you
-B
-
-// Block comment does not act as spacing, nested block comments.
-C/*
- /* */
-*/D
-
-// Works in code.
-#test(type(/*1*/ 1) //
-, int)
-
-// End of block comment in line comment.
-// Hello */
-
-// Nested "//" doesn't count as line comment.
-/* // */
-E
-
-/*//*/
-This is a comment.
-*/*/
-
----
-// End should not appear without start.
-// Error: 7-9 unexpected end of block comment
-/* */ */
-
-// Unterminated is okay.
-/*
diff --git a/tests/typ/compiler/construct.typ b/tests/typ/compiler/construct.typ
deleted file mode 100644
index da077e7e..00000000
--- a/tests/typ/compiler/construct.typ
+++ /dev/null
@@ -1,31 +0,0 @@
-// Test constructors.
-
----
-// Ensure that constructor styles aren't passed down the tree.
-// The inner list should have no extra indent.
-#set par(leading: 2pt)
-#list(body-indent: 20pt, [First], list[A][B])
-
----
-// Ensure that constructor styles win, but not over outer styles.
-// The outer paragraph should be right-aligned,
-// but the B should be center-aligned.
-#set list(marker: [>])
-#list(marker: [--])[
- #rect(width: 2cm, fill: conifer, inset: 4pt, list[A])
-]
-
----
-// The inner rectangle should also be yellow here.
-// (and therefore invisible)
-#[#set rect(fill: yellow);#text(1em, rect(inset: 5pt, rect()))]
-
----
-// The inner rectangle should not be yellow here.
-A #box(rect(fill: yellow, inset: 5pt, rect())) B
-
----
-// The constructor property should still work
-// when there are recursive show rules.
-#show enum: set text(blue)
-#enum(numbering: "(a)", [A], enum[B])
diff --git a/tests/typ/compiler/content-field.typ b/tests/typ/compiler/content-field.typ
deleted file mode 100644
index 96ce1dca..00000000
--- a/tests/typ/compiler/content-field.typ
+++ /dev/null
@@ -1,63 +0,0 @@
-// Tests content field access.
-
----
-// Ensure that fields from set rules are materialized into the element before
-// a show rule runs.
-#set table(columns: (10pt, auto))
-#show table: it => it.columns
-#table[A][B][C][D]
-
----
-// Test it again with a different element.
-#set heading(numbering: "(I)")
-#show heading: set text(size: 11pt, weight: "regular")
-#show heading: it => it.numbering
-= Heading
-
----
-// Test it with query.
-#set raw(lang: "rust")
-#context query(<myraw>).first().lang
-`raw` <myraw>
-
----
-// Integrated test for content fields.
-#let compute(equation, ..vars) = {
- let vars = vars.named()
- let f(elem) = {
- let func = elem.func()
- if func == text {
- let text = elem.text
- if regex("^\d+$") in text {
- int(text)
- } else if text in vars {
- int(vars.at(text))
- } else {
- panic("unknown math variable: " + text)
- }
- } else if func == math.attach {
- let value = f(elem.base)
- if elem.has("t") {
- value = calc.pow(value, f(elem.t))
- }
- value
- } else if elem.has("children") {
- elem
- .children
- .filter(v => v != [ ])
- .split[+]
- .map(xs => xs.fold(1, (prod, v) => prod * f(v)))
- .fold(0, (sum, v) => sum + v)
- }
- }
- let result = f(equation.body)
- [With ]
- vars
- .pairs()
- .map(p => $#p.first() = #p.last()$)
- .join(", ", last: " and ")
- [ we have:]
- $ equation = result $
-}
-
-#compute($x y + y^2$, x: 2, y: 3)
diff --git a/tests/typ/compiler/delayed-error.typ b/tests/typ/compiler/delayed-error.typ
deleted file mode 100644
index eff6b85b..00000000
--- a/tests/typ/compiler/delayed-error.typ
+++ /dev/null
@@ -1,11 +0,0 @@
-// Test that errors in show rules are delayed: There can be multiple at once.
-
----
-// Error: 21-34 panicked with: "hey1"
-#show heading: _ => panic("hey1")
-
-// Error: 20-33 panicked with: "hey2"
-#show strong: _ => panic("hey2")
-
-= Hello
-*strong*
diff --git a/tests/typ/compiler/dict.typ b/tests/typ/compiler/dict.typ
deleted file mode 100644
index 552b243c..00000000
--- a/tests/typ/compiler/dict.typ
+++ /dev/null
@@ -1,160 +0,0 @@
-// Test dictionaries.
-// Ref: false
-
----
-// Ref: true
-
-// Empty
-#(:)
-
-// Two pairs and string key.
-#let dict = (normal: 1, "spacy key": 2)
-#dict
-
-#test(dict.normal, 1)
-#test(dict.at("spacy key"), 2)
-
----
-// Test lvalue and rvalue access.
-#{
- let dict = (a: 1, "b b": 1)
- dict.at("b b") += 1
- dict.state = (ok: true, err: false)
- test(dict, (a: 1, "b b": 2, state: (ok: true, err: false)))
- test(dict.state.ok, true)
- dict.at("state").ok = false
- test(dict.state.ok, false)
- test(dict.state.err, false)
-}
-
----
-// Test rvalue missing key.
-#{
- let dict = (a: 1, b: 2)
- // Error: 11-23 dictionary does not contain key "c" and no default value was specified
- let x = dict.at("c")
-}
-
----
-// Test default value.
-#test((a: 1, b: 2).at("b", default: 3), 2)
-#test((a: 1, b: 2).at("c", default: 3), 3)
-
----
-// Test insert.
-#{
- let dict = (a: 1, b: 2)
- dict.insert("b", 3)
- test(dict, (a: 1, b: 3))
- dict.insert("c", 5)
- test(dict, (a: 1, b: 3, c: 5))
-}
-
----
-// Test remove with default value.
-#{
- let dict = (a: 1, b: 2)
- test(dict.remove("b", default: 3), 2)
-}
-
-#{
- let dict = (a: 1, b: 2)
- test(dict.remove("c", default: 3), 3)
-}
-
----
-// Missing lvalue is not automatically none-initialized.
-#{
- let dict = (:)
- // Error: 3-9 dictionary does not contain key "b"
- // Hint: 3-9 use `insert` to add or update values
- dict.b += 1
-}
-
----
-// Test dictionary methods.
-#let dict = (a: 3, c: 2, b: 1)
-#test("c" in dict, true)
-#test(dict.len(), 3)
-#test(dict.values(), (3, 2, 1))
-#test(dict.pairs().map(p => p.first() + str(p.last())).join(), "a3c2b1")
-
-#dict.remove("c")
-#test("c" in dict, false)
-#test(dict, (a: 3, b: 1))
-
----
-// Test dictionary constructor
-#dictionary(sys).at("version")
-#dictionary(sys).at("no_crash", default: none)
-
----
-// Test that removal keeps order.
-#let dict = (a: 1, b: 2, c: 3, d: 4)
-#dict.remove("b")
-#test(dict.keys(), ("a", "c", "d"))
-
----
-// Error: 24-29 duplicate key: first
-#(first: 1, second: 2, first: 3)
-
----
-// Error: 17-20 duplicate key: a
-#(a: 1, "b": 2, "a": 3)
-
----
-// Simple expression after already being identified as a dictionary.
-// Error: 9-10 expected named or keyed pair, found identifier
-#(a: 1, b)
-
-// Identified as dictionary due to initial colon.
-// The boolean key is allowed for now since it will only cause an error at the evaluation stage.
-// Error: 4-5 expected named or keyed pair, found integer
-// Error: 17 expected expression
-#(:1 b:"", true:)
-
----
-// Error: 3-15 cannot mutate a temporary value
-#((key: "val").other = "some")
-
----
-#{
- let dict = (
- call-me: () => 1,
- )
- // Error: 8-15 type dictionary has no method `call-me`
- // Hint: 8-15 to call the function stored in the dictionary, surround the field access with parentheses, e.g. `(dict.call-me)(..)`
- dict.call-me()
-}
-
----
-#{
- let dict = (
- nonfunc: 1
- )
-
- // Error: 8-15 type dictionary has no method `nonfunc`
- // Hint: 8-15 did you mean to access the field `nonfunc`?
- dict.nonfunc()
-}
-
----
-#let a = "hello"
-#let b = "world"
-#let c = "value"
-#let d = "conflict"
-
-#assert.eq(((a): b), ("hello": "world"))
-#assert.eq(((a): 1, (a): 2), ("hello": 2))
-#assert.eq((hello: 1, (a): 2), ("hello": 2))
-#assert.eq((a + b: c, (a + b): d, (a): "value2", a: "value3"), ("helloworld": "conflict", "hello": "value2", "a": "value3"))
-
----
-// Error: 7-10 expected identifier, found group
-// Error: 12-14 expected pattern, found integer
-#let ((a): 10) = "world"
-
----
-// Error: 3-7 expected string, found boolean
-// Error: 16-18 expected string, found integer
-#(true: false, 42: 3)
diff --git a/tests/typ/compiler/duration.typ b/tests/typ/compiler/duration.typ
deleted file mode 100644
index 1d831a6f..00000000
--- a/tests/typ/compiler/duration.typ
+++ /dev/null
@@ -1,104 +0,0 @@
-// Test durations.
-// Ref: false
-
----
-// Test negating durations.
-#test(-duration(hours: 2), duration(hours: -2))
-
----
-// Test adding and subtracting durations.
-#test(duration(weeks: 1, hours: 1), duration(weeks: 1) + duration(hours: 1))
-#test(duration(weeks: 1, hours: -1), duration(weeks: 1) - duration(hours: 1))
-#test(duration(days: 6, hours: 23), duration(weeks: 1) - duration(hours: 1))
-
----
-// Test adding and subtracting durations and dates.
-#let d = datetime(day: 1, month: 1, year: 2000)
-#let d2 = datetime(day: 1, month: 2, year: 2000)
-#test(d + duration(weeks: 2), datetime(day: 15, month: 1, year: 2000))
-#test(d + duration(days: 3), datetime(day: 4, month: 1, year: 2000))
-#test(d + duration(weeks: 1, days: 3), datetime(day: 11, month: 1, year: 2000))
-#test(d2 + duration(days: -1), datetime(day: 31, month: 1, year: 2000))
-#test(d2 + duration(days: -3), datetime(day: 29, month: 1, year: 2000))
-#test(d2 + duration(weeks: -1), datetime(day: 25, month: 1, year: 2000))
-#test(d + duration(days: -1), datetime(day: 31, month: 12, year: 1999))
-#test(d + duration(weeks: 1, days: -7), datetime(day: 1, month: 1, year: 2000))
-#test(d2 - duration(days: 1), datetime(day: 31, month: 1, year: 2000))
-#test(d2 - duration(days: 3), datetime(day: 29, month: 1, year: 2000))
-#test(d2 - duration(weeks: 1), datetime(day: 25, month: 1, year: 2000))
-#test(d - duration(days: 1), datetime(day: 31, month: 12, year: 1999))
-#test(datetime(day: 31, month: 1, year: 2000) + duration(days: 1), d2)
-#test(
- datetime(day: 31, month: 12, year: 2000) + duration(days: 1),
- datetime(day: 1, month: 1, year: 2001),
-)
-
----
-// Test adding and subtracting durations and times.
-#let a = datetime(hour: 12, minute: 0, second: 0)
-#test(a + duration(hours: 1, minutes: -60), datetime(hour: 12, minute: 0, second: 0))
-#test(a + duration(hours: 2), datetime(hour: 14, minute: 0, second: 0))
-#test(a + duration(minutes: 10), datetime(hour: 12, minute: 10, second: 0))
-#test(a + duration(seconds: 30), datetime(hour: 12, minute: 0, second: 30))
-#test(a + duration(hours: -2), datetime(hour: 10, minute: 0, second: 0))
-#test(a - duration(hours: 2), datetime(hour: 10, minute: 0, second: 0))
-#test(a + duration(minutes: -10), datetime(hour: 11, minute: 50, second: 0))
-#test(a - duration(minutes: 10), datetime(hour: 11, minute: 50, second: 0))
-#test(a + duration(seconds: -30), datetime(hour: 11, minute: 59, second: 30))
-#test(a - duration(seconds: 30), datetime(hour: 11, minute: 59, second: 30))
-#test(
- a + duration(hours: 1, minutes: 13, seconds: 13),
- datetime(hour: 13, minute: 13, second: 13),
-)
-
----
-// Test adding and subtracting durations and datetimes.
-#test(
- datetime(day: 1, month: 1, year: 2000, hour: 12, minute: 0, second: 0)
- + duration(weeks: 1, days: 3, hours: -13, minutes: 10, seconds: -10 ),
- datetime(day: 10, month: 1, year: 2000, hour: 23, minute: 9, second: 50),
-)
-#test(
- datetime(day: 1, month: 1, year: 2000, hour: 12, minute: 0, second: 0)
- + duration(weeks: 1, days: 3, minutes: 10)
- - duration(hours: 13, seconds: 10),
- datetime(day: 10, month: 1, year: 2000, hour: 23, minute: 9, second: 50),
-)
-
----
-// Test subtracting dates.
-#let a = datetime(hour: 12, minute: 0, second: 0)
-#let b = datetime(day: 1, month: 1, year: 2000)
-#test(datetime(hour: 14, minute: 0, second: 0) - a, duration(hours: 2))
-#test(datetime(hour: 14, minute: 0, second: 0) - a, duration(minutes: 120))
-#test(datetime(hour: 13, minute: 0, second: 0) - a, duration(seconds: 3600))
-#test(datetime(day: 1, month: 2, year: 2000) - b, duration(days: 31))
-#test(datetime(day: 15, month: 1, year: 2000) - b, duration(weeks: 2))
-
----
-// Test multiplying and dividing durations with numbers.
-#test(duration(minutes: 10) * 6, duration(hours: 1))
-#test(duration(minutes: 10) * 2, duration(minutes: 20))
-#test(duration(minutes: 10) * 2.5, duration(minutes: 25))
-#test(duration(minutes: 10) / 2, duration(minutes: 5))
-#test(duration(minutes: 10) / 2.5, duration(minutes: 4))
-
----
-// Test dividing durations with durations
-#test(duration(minutes: 20) / duration(hours: 1), 1 / 3)
-#test(duration(minutes: 20) / duration(minutes: 10), 2)
-#test(duration(minutes: 20) / duration(minutes: 8), 2.5)
-
----
-// Test comparing durations
-#test(duration(minutes: 20) > duration(minutes: 10), true)
-#test(duration(minutes: 20) >= duration(minutes: 10), true)
-#test(duration(minutes: 10) < duration(minutes: 20), true)
-#test(duration(minutes: 10) <= duration(minutes: 20), true)
-#test(duration(minutes: 10) == duration(minutes: 10), true)
-#test(duration(minutes: 10) != duration(minutes: 20), true)
-#test(duration(minutes: 10) <= duration(minutes: 10), true)
-#test(duration(minutes: 10) >= duration(minutes: 10), true)
-#test(duration(minutes: 20) < duration(minutes: 10), false)
-#test(duration(minutes: 20) <= duration(minutes: 10), false)
-#test(duration(minutes: 20) == duration(minutes: 10), false)
diff --git a/tests/typ/compiler/embedded-expr.typ b/tests/typ/compiler/embedded-expr.typ
deleted file mode 100644
index ee6e07f9..00000000
--- a/tests/typ/compiler/embedded-expr.typ
+++ /dev/null
@@ -1,22 +0,0 @@
-// Test embedded expressions.
-// Ref: false
-
----
-// Error: 6-8 expected pattern, found keyword `as`
-// Hint: 6-8 keyword `as` is not allowed as an identifier; try `as_` instead
-#let as = 1 + 2
-
----
-#{
- // Error: 7-9 expected pattern, found keyword `as`
- // Hint: 7-9 keyword `as` is not allowed as an identifier; try `as_` instead
- let as = 10
-}
-
----
-// Error: 2-2 expected expression
-#
-
----
-// Error: 2-2 expected expression
-# hello
diff --git a/tests/typ/compiler/field.typ b/tests/typ/compiler/field.typ
deleted file mode 100644
index 35768ec5..00000000
--- a/tests/typ/compiler/field.typ
+++ /dev/null
@@ -1,200 +0,0 @@
-// Test field access.
-// Ref: false
-
----
-// Test field on dictionary.
-#let dict = (nothing: "ness", hello: "world")
-#test(dict.nothing, "ness")
-#{
- let world = dict
- .hello
-
- test(world, "world")
-}
-
----
-// Test fields on elements.
-#show list: it => {
- test(it.children.len(), 3)
-}
-
-- A
-- B
-- C
-
----
-// Test fields on function scopes.
-#enum.item
-#assert.eq
-#assert.ne
-
----
-// Error: 9-16 function `assert` does not contain field `invalid`
-#assert.invalid
-
----
-// Error: 7-14 function `enum` does not contain field `invalid`
-#enum.invalid
-
----
-// Error: 7-14 function `enum` does not contain field `invalid`
-#enum.invalid()
-
----
-// Closures cannot have fields.
-#let f(x) = x
-// Error: 4-11 cannot access fields on user-defined functions
-#f.invalid
-
----
-// Error: 6-13 dictionary does not contain key "invalid"
-#(:).invalid
-
----
-// Error: 8-10 cannot access fields on type boolean
-#false.ok
-
----
-// Error: 25-28 content does not contain field "fun"
-#show heading: it => it.fun
-= A
-
----
-// Error: 9-13 cannot access fields on type boolean
-#{false.true}
-
----
-// Test relative length fields.
-#test((100% + 2em + 2pt).ratio, 100%)
-#test((100% + 2em + 2pt).length, 2em + 2pt)
-#test((100% + 2pt).length, 2pt)
-#test((100% + 2pt - 2pt).length, 0pt)
-#test((56% + 2pt - 56%).ratio, 0%)
-
----
-// Test length fields.
-#test((1pt).em, 0.0)
-#test((1pt).abs, 1pt)
-#test((3em).em, 3.0)
-#test((3em).abs, 0pt)
-#test((2em + 2pt).em, 2.0)
-#test((2em + 2pt).abs, 2pt)
-
----
-// Test stroke fields for simple strokes.
-#test((1em + blue).paint, blue)
-#test((1em + blue).thickness, 1em)
-#test((1em + blue).cap, auto)
-#test((1em + blue).join, auto)
-#test((1em + blue).dash, auto)
-#test((1em + blue).miter-limit, auto)
-
----
-// Test complex stroke fields.
-#let r1 = rect(stroke: (paint: cmyk(1%, 2%, 3%, 4%), thickness: 4em + 2pt, cap: "round", join: "bevel", miter-limit: 5.0, dash: none))
-#let r2 = rect(stroke: (paint: cmyk(1%, 2%, 3%, 4%), thickness: 4em + 2pt, cap: "round", join: "bevel", miter-limit: 5.0, dash: (3pt, "dot", 4em)))
-#let r3 = rect(stroke: (paint: cmyk(1%, 2%, 3%, 4%), thickness: 4em + 2pt, cap: "round", join: "bevel", dash: (array: (3pt, "dot", 4em), phase: 5em)))
-#let s1 = r1.stroke
-#let s2 = r2.stroke
-#let s3 = r3.stroke
-#test(s1.paint, cmyk(1%, 2%, 3%, 4%))
-#test(s1.thickness, 4em + 2pt)
-#test(s1.cap, "round")
-#test(s1.join, "bevel")
-#test(s1.miter-limit, 5.0)
-#test(s3.miter-limit, auto)
-#test(s1.dash, none)
-#test(s2.dash, (array: (3pt, "dot", 4em), phase: 0pt))
-#test(s3.dash, (array: (3pt, "dot", 4em), phase: 5em))
-
----
-// Test 2d alignment 'horizontal' field.
-#test((start + top).x, start)
-#test((end + top).x, end)
-#test((left + top).x, left)
-#test((right + top).x, right)
-#test((center + top).x, center)
-#test((start + bottom).x, start)
-#test((end + bottom).x, end)
-#test((left + bottom).x, left)
-#test((right + bottom).x, right)
-#test((center + bottom).x, center)
-#test((start + horizon).x, start)
-#test((end + horizon).x, end)
-#test((left + horizon).x, left)
-#test((right + horizon).x, right)
-#test((center + horizon).x, center)
-#test((top + start).x, start)
-#test((bottom + end).x, end)
-#test((horizon + center).x, center)
-
----
-// Test 2d alignment 'vertical' field.
-#test((start + top).y, top)
-#test((end + top).y, top)
-#test((left + top).y, top)
-#test((right + top).y, top)
-#test((center + top).y, top)
-#test((start + bottom).y, bottom)
-#test((end + bottom).y, bottom)
-#test((left + bottom).y, bottom)
-#test((right + bottom).y, bottom)
-#test((center + bottom).y, bottom)
-#test((start + horizon).y, horizon)
-#test((end + horizon).y, horizon)
-#test((left + horizon).y, horizon)
-#test((right + horizon).y, horizon)
-#test((center + horizon).y, horizon)
-#test((top + start).y, top)
-#test((bottom + end).y, bottom)
-#test((horizon + center).y, horizon)
-
----
-#{
- let object = sym.eq.not
- // Error: 3-9 cannot mutate fields on symbol
- object.property = "value"
-}
-
----
-#{
- let object = [hi]
- // Error: 3-9 cannot mutate fields on content
- object.property = "value"
-}
-
----
-#{
- let object = calc
- // Error: 3-9 cannot mutate fields on module
- object.property = "value"
-}
-
----
-#{
- let object = calc.sin
- // Error: 3-9 cannot mutate fields on function
- object.property = "value"
-}
-
----
-#{
- let object = none
- // Error: 3-9 none does not have accessible fields
- object.property = "value"
-}
-
----
-#{
- let object = 10
- // Error: 3-9 integer does not have accessible fields
- object.property = "value"
-}
-
----
-#{
- let s = 1pt + red
- // Error: 3-4 fields on stroke are not yet mutable
- // Hint: 3-4 try creating a new stroke with the updated field value instead
- s.thickness = 5pt
-}
diff --git a/tests/typ/compiler/for.typ b/tests/typ/compiler/for.typ
deleted file mode 100644
index 392dd676..00000000
--- a/tests/typ/compiler/for.typ
+++ /dev/null
@@ -1,136 +0,0 @@
-// Test for loops.
-// Ref: false
-
----
-// Ref: true
-
-// Empty array.
-#for x in () [Nope]
-
-// Dictionary is traversed in insertion order.
-// Should output `Name: Typst. Age: 2.`.
-#for (k, v) in (Name: "Typst", Age: 2) [
- #k: #v.
-]
-
-// Block body.
-// Should output `[1st, 2nd, 3rd, 4th]`.
-#{
- "["
- for v in (1, 2, 3, 4) {
- if v > 1 [, ]
- [#v]
- if v == 1 [st]
- if v == 2 [nd]
- if v == 3 [rd]
- if v >= 4 [th]
- }
- "]"
-}
-
-// Content block body.
-// Should output `2345`.
-#for v in (1, 2, 3, 4, 5, 6, 7) [#if v >= 2 and v <= 5 { repr(v) }]
-
-// Map captured arguments.
-#let f1(..args) = args.pos().map(repr)
-#let f2(..args) = args.named().pairs().map(p => repr(p.first()) + ": " + repr(p.last()))
-#let f(..args) = (f1(..args) + f2(..args)).join(", ")
-#f(1, a: 2)
-
----
-#let out = ()
-
-// Values of array.
-#for v in (1, 2, 3) {
- out += (v,)
-}
-
-// Indices and values of array.
-#for (i, v) in ("1", "2", "3").enumerate() {
- test(repr(i + 1), v)
-}
-
-// Pairs of dictionary.
-#for v in (a: 4, b: 5) {
- out += (v,)
-}
-
-// Keys and values of dictionary.
-#for (k, v) in (a: 6, b: 7) {
- out += (k,)
- out += (v,)
-}
-
-#test(out, (1, 2, 3, ("a", 4), ("b", 5), "a", 6, "b", 7))
-
-// Grapheme clusters of string.
-#let first = true
-#let joined = for c in "abc👩‍👩‍👦‍👦" {
- if not first { ", " }
- first = false
- c
-}
-
-#test(joined, "a, b, c, 👩‍👩‍👦‍👦")
-
-// Return value.
-#test(for v in "" [], none)
-#test(type(for v in "1" []), content)
-
----
-// Uniterable expression.
-// Error: 11-15 cannot loop over boolean
-#for v in true {}
-
----
-// Keys and values of strings.
-// Error: 6-12 cannot destructure values of string
-#for (k, v) in "hi" {
- dont-care
-}
-
----
-// Destructuring without parentheses.
-// Error: 7-8 unexpected comma
-// Hint: 7-8 destructuring patterns must be wrapped in parentheses
-#for k, v in (a: 4, b: 5) {
- dont-care
-}
-
-// Error: 7-8 unexpected comma
-// Hint: 7-8 destructuring patterns must be wrapped in parentheses
-#for k, in () {}
-
----
-// Error: 5 expected pattern
-#for
-
-// Error: 5 expected pattern
-#for//
-
-// Error: 6 expected pattern
-#{for}
-
-// Error: 7 expected keyword `in`
-#for v
-
-// Error: 10 expected expression
-#for v in
-
-// Error: 15 expected block
-#for v in iter
-
-// Error: 5 expected pattern
-#for
-v in iter {}
-
-// Error: 7-10 expected pattern, found string
-// Error: 16 expected block
-A#for "v" thing
-
-// Error: 6-9 expected pattern, found string
-#for "v" in iter {}
-
-// Error: 7 expected keyword `in`
-#for a + b in iter {}
diff --git a/tests/typ/compiler/highlight.typ b/tests/typ/compiler/highlight.typ
deleted file mode 100644
index 1cbeaf9d..00000000
--- a/tests/typ/compiler/highlight.typ
+++ /dev/null
@@ -1,42 +0,0 @@
-#set page(width: auto)
-
-```typ
-#set hello()
-#set hello()
-#set hello.world()
-#set hello.my.world()
-#let foo(x) = x * 2
-#show heading: func
-#show module.func: func
-#show module.func: it => {}
-#foo(ident: ident)
-#hello
-#hello()
-#box[]
-#hello.world
-#hello.world()
-#hello().world()
-#hello.my.world
-#hello.my.world()
-#hello.my().world
-#hello.my().world()
-#{ hello }
-#{ hello() }
-#{ hello.world() }
-$ hello $
-$ hello() $
-$ box[] $
-$ hello.world $
-$ hello.world() $
-$ hello.my.world() $
-$ f_zeta(x), f_zeta(x)/1 $
-$ emph(hello.my.world()) $
-$ emph(hello.my().world) $
-$ emph(hello.my().world()) $
-$ #hello $
-$ #hello() $
-$ #hello.world $
-$ #hello.world() $
-$ #box[] $
-#if foo []
-```
diff --git a/tests/typ/compiler/hint.typ b/tests/typ/compiler/hint.typ
deleted file mode 100644
index 1a5efcaa..00000000
--- a/tests/typ/compiler/hint.typ
+++ /dev/null
@@ -1,41 +0,0 @@
-// Test hints on diagnostics.
-// Ref: false
-
----
-// Error: 1:17-1:19 expected length, found integer: a length needs a unit - did you mean 12pt?
-#set text(size: 12)
-
----
-#{
- let a = 2
- a = 1-a
- a = a -1
-
- // Error: 7-10 unknown variable: a-1
- // Hint: 7-10 if you meant to use subtraction, try adding spaces around the minus sign
- a = a-1
-}
-
----
-#{
- // Error: 3-6 unknown variable: a-1
- // Hint: 3-6 if you meant to use subtraction, try adding spaces around the minus sign
- a-1 = 2
-}
-
----
-= Heading <intro>
-
-// Error: 1:20-1:26 cannot reference heading without numbering
-// Hint: 1:20-1:26 you can enable heading numbering with `#set heading(numbering: "1.")`
-Can not be used as @intro
-
----
-// This test is more of a tooling test. It checks if hint annotation validation
-// can be turned off.
-// Hints: false
-
-= Heading <intro>
-
-// Error: 1:20-1:26 cannot reference heading without numbering
-Can not be used as @intro
diff --git a/tests/typ/compiler/if.typ b/tests/typ/compiler/if.typ
deleted file mode 100644
index 1d2ed88b..00000000
--- a/tests/typ/compiler/if.typ
+++ /dev/null
@@ -1,136 +0,0 @@
-// Test if-else expressions.
-
----
-// Test condition evaluation.
-#if 1 < 2 [
- One.
-]
-
-#if true == false [
- {Bad}, but we {dont-care}!
-]
-
----
-// Braced condition.
-#if {true} [
- One.
-]
-
-// Content block in condition.
-#if [] != none [
- Two.
-]
-
-// Multi-line condition with parens.
-#if (
- 1 + 1
- == 1
-) [
- Nope.
-] else {
- "Three."
-}
-
-// Multiline.
-#if false [
- Bad.
-] else {
- let point = "."
- "Four" + point
-}
-
-// Content block can be argument or body depending on whitespace.
-#{
- if content == type[b] [Fi] else [Nope]
- if content == type [Nope] else [ve.]
-}
-
-#let i = 3
-#if i < 2 [
- Five.
-] else if i < 4 [
- Six.
-] else [
- Seven.
-]
-
----
-// Test else if.
-// Ref: false
-
-#let nth(n) = {
- str(n)
- if n == 1 { "st" }
- else if n == 2 { "nd" }
- else if n == 3 { "rd" }
- else { "th" }
-}
-
-#test(nth(1), "1st")
-#test(nth(2), "2nd")
-#test(nth(3), "3rd")
-#test(nth(4), "4th")
-#test(nth(5), "5th")
-
----
-// Value of if expressions.
-// Ref: false
-
-#{
- let x = 1
- let y = 2
- let z
-
- // Returns if branch.
- z = if x < y { "ok" }
- test(z, "ok")
-
- // Returns else branch.
- z = if x > y { "bad" } else { "ok" }
- test(z, "ok")
-
- // Missing else evaluates to none.
- z = if x > y { "bad" }
- test(z, none)
-}
-
----
-// Condition must be boolean.
-// If it isn't, neither branch is evaluated.
-// Error: 5-14 expected boolean, found string
-#if "a" + "b" { nope } else { nope }
-
----
-// Make sure that we don't complain twice.
-// Error: 5-12 cannot add integer and string
-#if 1 + "2" {}
-
----
-// Error: 4 expected expression
-#if
-
-// Error: 5 expected expression
-#{if}
-
-// Error: 6 expected block
-#if x
-
-// Error: 2-6 unexpected keyword `else`
-#else {}
-
-// Should output `x`.
-// Error: 4 expected expression
-#if
-x {}
-
-// Should output `something`.
-// Error: 6 expected block
-#if x something
-
-// Should output `A thing.`
-// Error: 19 expected block
-A#if false {} else thing
-
-#if a []else [b]
-#if a [] else [b]
-#if a {} else [b]
diff --git a/tests/typ/compiler/import.typ b/tests/typ/compiler/import.typ
deleted file mode 100644
index 5c3a05c9..00000000
--- a/tests/typ/compiler/import.typ
+++ /dev/null
@@ -1,262 +0,0 @@
-// Test function and module imports.
-// Ref: false
-
----
-// Test basic syntax and semantics.
-// Ref: true
-
-// Test that this will be overwritten.
-#let value = [foo]
-
-// Import multiple things.
-#import "module.typ": fn, value
-#fn[Like and Subscribe!]
-#value
-
-// Should output `bye`.
-// Stop at semicolon.
-#import "module.typ": a, c;bye
-
----
-// An item import.
-#import "module.typ": item
-#test(item(1, 2), 3)
-
-// Code mode
-{
- import "module.typ": b
- test(b, 1)
-}
-
-// A wildcard import.
-#import "module.typ": *
-
-// It exists now!
-#test(d, 3)
-
----
-// A renamed item import.
-#import "module.typ": item as something
-#test(something(1, 2), 3)
-
-// Mixing renamed and not renamed items.
-#import "module.typ": fn, b as val, item as other
-#test(val, 1)
-#test(other(1, 2), 3)
-
----
-// Test importing from function scopes.
-// Ref: true
-
-#import enum: item
-#import assert.with(true): *
-
-#enum(
- item(1)[First],
- item(5)[Fifth]
-)
-#eq(10, 10)
-#ne(5, 6)
-
----
-// Test renaming items imported from function scopes.
-#import assert: eq as aseq
-#aseq(10, 10)
-
----
-// A module import without items.
-#import "module.typ"
-#test(module.b, 1)
-#test(module.item(1, 2), 3)
-#test(module.push(2), 3)
-
----
-// A renamed module import without items.
-#import "module.typ" as other
-#test(other.b, 1)
-#test(other.item(1, 2), 3)
-#test(other.push(2), 3)
-
----
-// Mixing renamed module and items.
-#import "module.typ" as newname: b as newval, item
-#test(newname.b, 1)
-#test(newval, 1)
-#test(item(1, 2), 3)
-#test(newname.item(1, 2), 3)
-
----
-// Renamed module import with function scopes.
-#import enum as othernum
-#test(enum, othernum)
-
----
-// Mixing renamed module import from function with renamed item import.
-#import assert as asrt
-#import asrt: ne as asne
-#asne(1, 2)
-
----
-// Edge case for module access that isn't fixed.
-#import "module.typ"
-
-// Works because the method name isn't categorized as mutating.
-#test((module,).at(0).item(1, 2), 3)
-
-// Doesn't work because of mutating name.
-// Error: 2-11 cannot mutate a temporary value
-#(module,).at(0).push()
-
----
-// Who needs whitespace anyways?
-#import"module.typ":*
-
-// Allow the trailing comma.
-#import "module.typ": a, c,
-
----
-// Usual importing syntax also works for function scopes
-#let d = (e: enum)
-#import d.e
-#import d.e as renamed
-#import d.e: item
-#item(2)[a]
-
----
-// Warning: 23-27 unnecessary import rename to same name
-#import enum: item as item
-
----
-// Warning: 17-21 unnecessary import rename to same name
-#import enum as enum
-
----
-// Warning: 17-21 unnecessary import rename to same name
-#import enum as enum: item
-// Warning: 17-21 unnecessary import rename to same name
-// Warning: 31-35 unnecessary import rename to same name
-#import enum as enum: item as item
-
----
-// No warning on a case that isn't obviously pathological
-#import "module.typ" as module
-
----
-// Can't import from closures.
-#let f(x) = x
-// Error: 9-10 cannot import from user-defined functions
-#import f: x
-
----
-// Can't import from closures, despite renaming.
-#let f(x) = x
-// Error: 9-10 cannot import from user-defined functions
-#import f as g
-
----
-// Can't import from closures, despite modifiers.
-#let f(x) = x
-// Error: 9-18 cannot import from user-defined functions
-#import f.with(5): x
-
----
-// Error: 9-18 cannot import from user-defined functions
-#import () => {5}: x
-
----
-// Error: 9-10 expected path, module, function, or type, found integer
-#import 5: something
-
----
-// Error: 9-10 expected path, module, function, or type, found integer
-#import 5 as x
-
----
-// Error: 9-11 failed to load file (is a directory)
-#import "": name
-
----
-// Error: 9-11 failed to load file (is a directory)
-#import "" as x
-
----
-// Error: 9-20 file not found (searched at typ/compiler/lib/0.2.1)
-#import "lib/0.2.1"
-
----
-// Error: 9-20 file not found (searched at typ/compiler/lib/0.2.1)
-#import "lib/0.2.1" as x
-
----
-// Some non-text stuff.
-// Error: 9-35 file is not valid utf-8
-#import "/assets/images/rhino.png"
-
----
-// Unresolved import.
-// Error: 23-35 unresolved import
-#import "module.typ": non_existing
-
----
-// Cyclic import of this very file.
-// Error: 9-23 cyclic import
-#import "./import.typ"
-
----
-// Cyclic import in other file.
-#import "./modules/cycle1.typ": *
-
-This is never reached.
-
----
-// Renaming does not import the old name (without items).
-#import "module.typ" as something
-// Error: 7-12 unknown variable: mymod
-#test(mymod.b, 1)
-
----
-// Renaming does not import the old name (with items).
-#import "module.typ" as something: b as other
-// Error: 7-12 unknown variable: mymod
-#test(mymod.b, 1)
-
----
-// Error: 8 expected expression
-#import
-
----
-// Error: 26-29 unexpected string
-#import "module.typ": a, "b", c
-
----
-// Error: 23-24 unexpected equals sign
-#import "module.typ": =
-
----
-// An additional trailing comma.
-// Error: 31-32 unexpected comma
-#import "module.typ": a, b, c,,
-
----
-// Error: 2:2 expected semicolon or line break
-#import "module.typ
-"stuff
-
----
-// A star in the list.
-// Error: 26-27 unexpected star
-#import "module.typ": a, *, b
-
----
-// An item after a star.
-// Error: 24 expected semicolon or line break
-#import "module.typ": *, a
-
----
-// Error: 14-15 unexpected colon
-// Error: 16-17 unexpected integer
-#import "": a: 1
-
----
-// Error: 14 expected comma
-#import "": a b
diff --git a/tests/typ/compiler/include.typ b/tests/typ/compiler/include.typ
deleted file mode 100644
index 586e869b..00000000
--- a/tests/typ/compiler/include.typ
+++ /dev/null
@@ -1,32 +0,0 @@
-// Test module includes.
-
----
-#set page(width: 200pt)
-
-= Document
-
-// Include a file
-#include "modules/chap1.typ"
-
-// Expression as a file name.
-#let chap2 = include "modu" + "les/chap" + "2.typ"
-
--- _Intermission_ --
-#chap2
-
----
-#{
- // Error: 19-38 file not found (searched at typ/compiler/modules/chap3.typ)
- let x = include "modules/chap3.typ"
-}
-
----
-#include "modules/chap1.typ"
-
-// The variables of the file should not appear in this scope.
-// Error: 2-6 unknown variable: name
-#name
-
----
-// Error: 18 expected semicolon or line break
-#include "hi.typ" Hi
diff --git a/tests/typ/compiler/label.typ b/tests/typ/compiler/label.typ
deleted file mode 100644
index fabbac80..00000000
--- a/tests/typ/compiler/label.typ
+++ /dev/null
@@ -1,72 +0,0 @@
-// Test labels.
-
----
-// Test labelled headings.
-#show heading: set text(10pt)
-#show heading.where(label: <intro>): underline
-
-= Introduction <intro>
-The beginning.
-
-= Conclusion
-The end.
-
----
-// Test label after expression.
-#show strong.where(label: <v>): set text(red)
-
-#let a = [*A*]
-#let b = [*B*]
-#a <v> #b
-
----
-// Test labelled text.
-#show "t": it => {
- set text(blue) if it.has("label") and it.label == <last>
- it
-}
-
-This is a thing #[that <last>] happened.
-
----
-// Test abusing dynamic labels for styling.
-#show <red>: set text(red)
-#show <blue>: set text(blue)
-
-*A* *B* <red> *C* #label("bl" + "ue") *D*
-
----
-// Test that label ignores parbreak.
-#show <hide>: none
-
-_Hidden_
-<hide>
-
-_Hidden_
-
-<hide>
-_Visible_
-
----
-// Test that label only works within one content block.
-#show <strike>: strike
-*This is* #[<strike>] *protected.*
-*This is not.* <strike>
-
----
-// Test that incomplete label is text.
-1 < 2 is #if 1 < 2 [not] a label.
-
----
-// Test label on text, styled, and sequence.
-// Ref: false
-#test([Hello<hi>].label, <hi>)
-#test([#[A *B* C]<hi>].label, <hi>)
-#test([#text(red)[Hello]<hi>].label, <hi>)
-
----
-// Test getting the name of a label.
-// Ref: false
-#test(str(<hey>), "hey")
-#test(str(label("hey")), "hey")
-#test(str([Hmm<hey>].label), "hey")
diff --git a/tests/typ/compiler/let.typ b/tests/typ/compiler/let.typ
deleted file mode 100644
index 411509ff..00000000
--- a/tests/typ/compiler/let.typ
+++ /dev/null
@@ -1,302 +0,0 @@
-// Test let bindings.
-
----
-// Automatically initialized with none.
-#let x
-#test(x, none)
-
-// Manually initialized with one.
-#let z = 1
-#test(z, 1)
-
-// Syntax sugar for function definitions.
-#let fill = conifer
-#let f(body) = rect(width: 2cm, fill: fill, inset: 5pt, body)
-#f[Hi!]
-
----
-// Termination.
-
-// Terminated by line break.
-#let v1 = 1
-One
-
-// Terminated by semicolon.
-#let v2 = 2; Two
-
-// Terminated by semicolon and line break.
-#let v3 = 3;
-Three
-
-#test(v1, 1)
-#test(v2, 2)
-#test(v3, 3)
-
----
-// Test what constitutes a valid Typst identifier.
-// Ref: false
-#let name = 1
-#test(name, 1)
-#let name_ = 1
-#test(name_, 1)
-#let name-2 = 1
-#test(name-2, 1)
-#let name_2 = 1
-#test(name_2, 1)
-#let __name = 1
-#test(__name, 1)
-#let ůñıćóðė = 1
-#test(ůñıćóðė, 1)
-
----
-// Test parenthesised assignments.
-// Ref: false
-#let (a) = (1, 2)
-
----
-// Ref: false
-// Simple destructuring.
-#let (a, b) = (1, 2)
-#test(a, 1)
-#test(b, 2)
-
----
-// Ref: false
-#let (a,) = (1,)
-#test(a, 1)
-
----
-// Ref: false
-// Destructuring with multiple placeholders.
-#let (a, _, c, _) = (1, 2, 3, 4)
-#test(a, 1)
-#test(c, 3)
-
----
-// Ref: false
-// Destructuring with a sink.
-#let (a, b, ..c) = (1, 2, 3, 4, 5, 6)
-#test(a, 1)
-#test(b, 2)
-#test(c, (3, 4, 5, 6))
-
----
-// Ref: false
-// Destructuring with a sink in the middle.
-#let (a, ..b, c) = (1, 2, 3, 4, 5, 6)
-#test(a, 1)
-#test(b, (2, 3, 4, 5))
-#test(c, 6)
-
----
-// Ref: false
-// Destructuring with an empty sink.
-#let (..a, b, c) = (1, 2)
-#test(a, ())
-#test(b, 1)
-#test(c, 2)
-
----
-// Ref: false
-// Destructuring with an empty sink.
-#let (a, ..b, c) = (1, 2)
-#test(a, 1)
-#test(b, ())
-#test(c, 2)
-
----
-// Ref: false
-// Destructuring with an empty sink.
-#let (a, b, ..c) = (1, 2)
-#test(a, 1)
-#test(b, 2)
-#test(c, ())
-
----
-// Ref: false
-// Destructuring with an empty sink and empty array.
-#let (..a) = ()
-#test(a, ())
-
----
-// Ref: false
-// Destructuring with unnamed sink.
-#let (a, .., b) = (1, 2, 3, 4)
-#test(a, 1)
-#test(b, 4)
-
-// Error: 10-11 duplicate binding: a
-#let (a, a) = (1, 2)
-
-// Error: 12-15 only one destructuring sink is allowed
-#let (..a, ..a) = (1, 2)
-
-// Error: 12-13 duplicate binding: a
-#let (a, ..a) = (1, 2)
-
-// Error: 13-14 duplicate binding: a
-#let (a: a, a) = (a: 1, b: 2)
-
-// Error: 13-20 expected pattern, found function call
-#let (a, b: b.at(0)) = (a: 1, b: 2)
-
-// Error: 7-14 expected pattern, found function call
-#let (a.at(0),) = (1,)
-
----
-// Error: 13-14 not enough elements to destructure
-#let (a, b, c) = (1, 2)
-
----
-// Error: 7-10 not enough elements to destructure
-#let (..a, b, c, d) = (1, 2)
-
----
-// Error: 6-12 cannot destructure boolean
-#let (a, b) = true
-
----
-// Ref: false
-// Simple destructuring.
-#let (a: a, b, x: c) = (a: 1, b: 2, x: 3)
-#test(a, 1)
-#test(b, 2)
-#test(c, 3)
-
----
-// Ref: false
-// Destructuring with a sink.
-#let (a: _, ..b) = (a: 1, b: 2, c: 3)
-#test(b, (b: 2, c: 3))
-
----
-// Ref: false
-// Destructuring with a sink in the middle.
-#let (a: _, ..b, c: _) = (a: 1, b: 2, c: 3)
-#test(b, (b: 2))
-
----
-// Ref: false
-// Destructuring with an empty sink.
-#let (a: _, ..b) = (a: 1)
-#test(b, (:))
-
----
-// Ref: false
-// Destructuring with an empty sink and empty dict.
-#let (..a) = (:)
-#test(a, (:))
-
----
-// Ref: false
-// Destructuring with unnamed sink.
-#let (a, ..) = (a: 1, b: 2)
-#test(a, 1)
-
----
-// Ref: false
-// Nested destructuring.
-#let ((a, b), (key: c)) = ((1, 2), (key: 3))
-#test((a, b, c), (1, 2, 3))
-
----
-// Keyed destructuring is not currently supported.
-// Error: 7-18 expected pattern, found string
-#let ("spacy key": val) = ("spacy key": 123)
-#val
-
----
-// Keyed destructuring is not currently supported.
-#let x = "spacy key"
-// Error: 7-10 expected identifier, found group
-#let ((x): v) = ("spacy key": 123)
-
----
-// Trailing placeholders.
-// Error: 10-11 not enough elements to destructure
-#let (a, _, _, _, _) = (1,)
-#test(a, 1)
-
----
-// Error: 10-13 expected pattern, found string
-// Error: 18-19 expected pattern, found integer
-#let (a: "a", b: 2) = (a: 1, b: 2)
-
----
-// Error: 10-11 dictionary does not contain key "b"
-#let (a, b) = (a: 1)
-
----
-// Error: 10-11 dictionary does not contain key "b"
-#let (a, b: b) = (a: 1)
-
----
-// Error: 7-11 cannot destructure named pattern from an array
-#let (a: a, b) = (1, 2, 3)
-
----
-// Error: 5 expected pattern
-#let
-
-// Error: 6 expected pattern
-#{let}
-
-// Error: 6-9 expected pattern, found string
-#let "v"
-
-// Error: 7 expected semicolon or line break
-#let v 1
-
-// Error: 9 expected expression
-#let v =
-
-// Error: 6-9 expected pattern, found string
-#let "v" = 1
-
-// Terminated because expression ends.
-// Error: 12 expected semicolon or line break
-#let v4 = 4 Four
-
-// Terminated by semicolon even though we are in a paren group.
-// Error: 18 expected expression
-// Error: 11-12 unclosed delimiter
-#let v5 = (1, 2 + ; Five
-
-// Error: 9-13 expected pattern, found boolean
-#let (..true) = false
-
----
-#let _ = 4
-
-#for _ in range(2) []
-
-// Error: 2-3 unexpected underscore
-#_
-
-// Error: 8-9 expected expression, found underscore
-#lorem(_)
-
-// Error: 3-4 expected expression, found underscore
-#(_,)
-
-// Error: 3-4 expected expression, found underscore
-#{_}
-
-// Error: 8-9 expected expression, found underscore
-#{ 1 + _ }
-
----
-// Error: 13 expected equals sign
-#let func(x)
-
-// Error: 15 expected expression
-#let func(x) =
-
----
-// Error: 12 expected equals sign
-#let (func)(x)
-
----
-// Error: 12 expected equals sign
-// Error: 15-15 expected semicolon or line break
-#let (func)(x) = 3
diff --git a/tests/typ/compiler/methods.typ b/tests/typ/compiler/methods.typ
deleted file mode 100644
index 8d5484ed..00000000
--- a/tests/typ/compiler/methods.typ
+++ /dev/null
@@ -1,287 +0,0 @@
-// Test method calls.
-// Ref: false
-
----
-// Test whitespace around dot.
-#test( "Hi there" . split() , ("Hi", "there"))
-
----
-// Test mutating indexed value.
-#{
- let matrix = (((1,), (2,)), ((3,), (4,)))
- matrix.at(1).at(0).push(5)
- test(matrix, (((1,), (2,)), ((3, 5), (4,))))
-}
-
----
-// Test multiline chain in code block.
-#{
- let rewritten = "Hello. This is a sentence. And one more."
- .split(".")
- .map(s => s.trim())
- .filter(s => s != "")
- .map(s => s + "!")
- .join("\n ")
-
- test(rewritten, "Hello!\n This is a sentence!\n And one more!")
-}
-
----
-// Test .at() default values for content.
-#test(auto, [a].at("doesn't exist", default: auto))
-
----
-// Error: 2:10-2:13 type array has no method `fun`
-#let numbers = ()
-#numbers.fun()
-
----
-// Error: 2:4-2:10 type content has no method `stroke`
-// Hint: 2:4-2:10 did you mean to access the field `stroke`?
-#let l = line(stroke: red)
-#l.stroke()
-
----
-// Error: 2:2-2:43 cannot mutate a temporary value
-#let numbers = (1, 2, 3)
-#numbers.map(v => v / 2).sorted().map(str).remove(4)
-
----
-// Error: 2:3-2:19 cannot mutate a temporary value
-#let numbers = (1, 2, 3)
-#(numbers.sorted() = 1)
-
----
-// Error: 2-5 cannot mutate a constant: box
-#box.push(1)
-
----
-// Test content fields method.
-#test([a].fields(), (text: "a"))
-#test([a *b*].fields(), (children: ([a], [ ], strong[b])))
-
----
-// Test length unit conversions.
-#test((500.934pt).pt(), 500.934)
-#test((3.3453cm).cm(), 3.3453)
-#test((4.3452mm).mm(), 4.3452)
-#test((5.345in).inches(), 5.345)
-#test((500.333666999pt).pt(), 500.333666999)
-#test((3.5234354cm).cm(), 3.5234354)
-#test((4.12345678mm).mm(), 4.12345678)
-#test((5.333666999in).inches(), 5.333666999)
-#test((4.123456789123456mm).mm(), 4.123456789123456)
-#test((254cm).mm(), 2540.0)
-#test(calc.round((254cm).inches(), digits: 2), 100.0)
-#test((2540mm).cm(), 254.0)
-#test(calc.round((2540mm).inches(), digits: 2), 100.0)
-#test((100in).pt(), 7200.0)
-#test(calc.round((100in).cm(), digits: 2), 254.0)
-#test(calc.round((100in).mm(), digits: 2), 2540.0)
-#test(5em.abs.cm(), 0.0)
-#test((5em + 6in).abs.inches(), 6.0)
-
----
-// Test length `to-absolute` method.
-#set text(size: 12pt)
-#context {
- test((6pt).to-absolute(), 6pt)
- test((6pt + 10em).to-absolute(), 126pt)
- test((10em).to-absolute(), 120pt)
-}
-
-#set text(size: 64pt)
-#context {
- test((6pt).to-absolute(), 6pt)
- test((6pt + 10em).to-absolute(), 646pt)
- test((10em).to-absolute(), 640pt)
-}
-
----
-// Error: 2-21 cannot convert a length with non-zero em units (`-6pt + 10.5em`) to pt
-// Hint: 2-21 use `length.abs.pt()` instead to ignore its em component
-#(10.5em - 6pt).pt()
-
----
-// Error: 2-12 cannot convert a length with non-zero em units (`3em`) to cm
-// Hint: 2-12 use `length.abs.cm()` instead to ignore its em component
-#(3em).cm()
-
----
-// Error: 2-20 cannot convert a length with non-zero em units (`-226.77pt + 93em`) to mm
-// Hint: 2-20 use `length.abs.mm()` instead to ignore its em component
-#(93em - 80mm).mm()
-
----
-// Error: 2-24 cannot convert a length with non-zero em units (`432pt + 4.5em`) to inches
-// Hint: 2-24 use `length.abs.inches()` instead to ignore its em component
-#(4.5em + 6in).inches()
-
----
-// Test color kind method.
-#test(rgb(1, 2, 3, 4).space(), rgb)
-#test(cmyk(4%, 5%, 6%, 7%).space(), cmyk)
-#test(luma(40).space(), luma)
-#test(rgb(1, 2, 3, 4).space() != luma, true)
-
----
-// Test color '.components()' without conversions
-
-#let test-components(col, ref, has-alpha: true) = {
- // Perform an approximate scalar comparison.
- let are-equal((a, b)) = {
- let to-float(x) = if type(x) == angle { x.rad() } else { float(x) }
- let epsilon = 1e-4 // The maximum error between both numbers
- assert.eq(type(a), type(b))
- calc.abs(to-float(a) - to-float(b)) < epsilon
- }
-
- let ref-without-alpha = if has-alpha { ref.slice(0, -1) } else { ref }
- assert.eq(col.components().len(), ref.len())
- assert(col.components().zip(ref).all(are-equal))
- assert(col.components(alpha: false).zip(ref-without-alpha).all(are-equal))
-}
-#test-components(rgb(1, 2, 3, 4), (0.39%, 0.78%, 1.18%, 1.57%))
-#test-components(luma(40), (15.69%, 100%))
-#test-components(luma(40, 50%), (15.69%, 50%))
-#test-components(cmyk(4%, 5%, 6%, 7%), (4%, 5%, 6%, 7%), has-alpha: false)
-#test-components(oklab(10%, 0.2, 0.4), (10%, 0.2, 0.4, 100%))
-#test-components(oklch(10%, 0.2, 90deg), (10%, 0.2, 90deg, 100%))
-#test-components(oklab(10%, 50%, 200%), (10%, 0.2, 0.8, 100%))
-#test-components(oklch(10%, 50%, 90deg), (10%, 0.2, 90deg, 100%))
-#test-components(color.linear-rgb(10%, 20%, 30%), (10%, 20%, 30%, 100%))
-#test-components(color.hsv(10deg, 20%, 30%), (10deg, 20%, 30%, 100%))
-#test-components(color.hsl(10deg, 20%, 30%), (10deg, 20%, 30%, 100%))
-
----
-// Test color conversions.
-#test(rgb(1, 2, 3).to-hex(), "#010203")
-#test(rgb(1, 2, 3, 4).to-hex(), "#01020304")
-#test(luma(40).to-hex(), "#282828")
-#test-repr(cmyk(4%, 5%, 6%, 7%).to-hex(), "#e0dcda")
-#test-repr(rgb(cmyk(4%, 5%, 6%, 7%)), rgb(87.84%, 86.27%, 85.49%, 100%))
-#test-repr(rgb(luma(40%)), rgb(40%, 40%, 40%))
-#test-repr(cmyk(luma(40)), cmyk(11.76%, 10.67%, 10.51%, 14.12%))
-#test-repr(cmyk(rgb(1, 2, 3)), cmyk(66.67%, 33.33%, 0%, 98.82%))
-#test-repr(luma(rgb(1, 2, 3)), luma(0.73%))
-#test-repr(color.hsl(luma(40)), color.hsl(0deg, 0%, 15.69%))
-#test-repr(color.hsv(luma(40)), color.hsv(0deg, 0%, 15.69%))
-#test-repr(color.linear-rgb(luma(40)), color.linear-rgb(2.12%, 2.12%, 2.12%))
-#test-repr(color.linear-rgb(rgb(1, 2, 3)), color.linear-rgb(0.03%, 0.06%, 0.09%))
-#test-repr(color.hsl(rgb(1, 2, 3)), color.hsl(-150deg, 50%, 0.78%))
-#test-repr(color.hsv(rgb(1, 2, 3)), color.hsv(-150deg, 66.67%, 1.18%))
-#test-repr(oklab(luma(40)), oklab(27.68%, 0.0, 0.0, 100%))
-#test-repr(oklab(rgb(1, 2, 3)), oklab(8.23%, -0.004, -0.007, 100%))
-#test-repr(oklch(oklab(40%, 0.2, 0.2)), oklch(40%, 0.283, 45deg, 100%))
-#test-repr(oklch(luma(40)), oklch(27.68%, 0.0, 72.49deg, 100%))
-#test-repr(oklch(rgb(1, 2, 3)), oklch(8.23%, 0.008, 240.75deg, 100%))
-
----
-// Test gradient functions.
-#test(gradient.linear(red, green, blue).kind(), gradient.linear)
-#test(gradient.linear(red, green, blue).stops(), ((red, 0%), (green, 50%), (blue, 100%)))
-#test(gradient.linear(red, green, blue, space: rgb).sample(0%), red)
-#test(gradient.linear(red, green, blue, space: rgb).sample(25%), rgb("#97873b"))
-#test(gradient.linear(red, green, blue, space: rgb).sample(50%), green)
-#test(gradient.linear(red, green, blue, space: rgb).sample(75%), rgb("#17a08c"))
-#test(gradient.linear(red, green, blue, space: rgb).sample(100%), blue)
-#test(gradient.linear(red, green, space: rgb).space(), rgb)
-#test(gradient.linear(red, green, space: oklab).space(), oklab)
-#test(gradient.linear(red, green, space: oklch).space(), oklch)
-#test(gradient.linear(red, green, space: cmyk).space(), cmyk)
-#test(gradient.linear(red, green, space: luma).space(), luma)
-#test(gradient.linear(red, green, space: color.linear-rgb).space(), color.linear-rgb)
-#test(gradient.linear(red, green, space: color.hsl).space(), color.hsl)
-#test(gradient.linear(red, green, space: color.hsv).space(), color.hsv)
-#test(gradient.linear(red, green, relative: "self").relative(), "self")
-#test(gradient.linear(red, green, relative: "parent").relative(), "parent")
-#test(gradient.linear(red, green).relative(), auto)
-#test(gradient.linear(red, green).angle(), 0deg)
-#test(gradient.linear(red, green, dir: ltr).angle(), 0deg)
-#test(gradient.linear(red, green, dir: rtl).angle(), 180deg)
-#test(gradient.linear(red, green, dir: ttb).angle(), 90deg)
-#test(gradient.linear(red, green, dir: btt).angle(), 270deg)
-#test(
- gradient.linear(red, green, blue).repeat(2).stops(),
- ((red, 0%), (green, 25%), (blue, 50%), (red, 50%), (green, 75%), (blue, 100%))
-)
-#test(
- gradient.linear(red, green, blue).repeat(2, mirror: true).stops(),
- ((red, 0%), (green, 25%), (blue, 50%), (green, 75%), (red, 100%))
-)
-
----
-// Test alignment methods.
-#test(start.axis(), "horizontal")
-#test(end.axis(), "horizontal")
-#test(left.axis(), "horizontal")
-#test(right.axis(), "horizontal")
-#test(center.axis(), "horizontal")
-#test(top.axis(), "vertical")
-#test(bottom.axis(), "vertical")
-#test(horizon.axis(), "vertical")
-#test(start.inv(), end)
-#test(end.inv(), start)
-#test(left.inv(), right)
-#test(right.inv(), left)
-#test(center.inv(), center)
-#test(top.inv(), bottom)
-#test(bottom.inv(), top)
-#test(horizon.inv(), horizon)
-
----
-// Test 2d alignment methods.
-#test((start + top).inv(), (end + bottom))
-#test((end + top).inv(), (start + bottom))
-#test((left + top).inv(), (right + bottom))
-#test((right + top).inv(), (left + bottom))
-#test((center + top).inv(), (center + bottom))
-#test((start + bottom).inv(), (end + top))
-#test((end + bottom).inv(), (start + top))
-#test((left + bottom).inv(), (right + top))
-#test((right + bottom).inv(), (left + top))
-#test((center + bottom).inv(), (center + top))
-#test((start + horizon).inv(), (end + horizon))
-#test((end + horizon).inv(), (start + horizon))
-#test((left + horizon).inv(), (right + horizon))
-#test((right + horizon).inv(), (left + horizon))
-#test((center + horizon).inv(), (center + horizon))
-#test((top + start).inv(), (end + bottom))
-#test((bottom + end).inv(), (start + top))
-#test((horizon + center).inv(), (center + horizon))
-
----
-// Test direction methods.
-#test(ltr.axis(), "horizontal")
-#test(rtl.axis(), "horizontal")
-#test(ttb.axis(), "vertical")
-#test(btt.axis(), "vertical")
-#test(ltr.start(), left)
-#test(rtl.start(), right)
-#test(ttb.start(), top)
-#test(btt.start(), bottom)
-#test(ltr.end(), right)
-#test(rtl.end(), left)
-#test(ttb.end(), bottom)
-#test(btt.end(), top)
-#test(ltr.inv(), rtl)
-#test(rtl.inv(), ltr)
-#test(ttb.inv(), btt)
-#test(btt.inv(), ttb)
-
----
-// Test angle methods.
-#test(1rad.rad(), 1.0)
-#test(1.23rad.rad(), 1.23)
-#test(0deg.rad(), 0.0)
-#test(2deg.deg(), 2.0)
-#test(2.94deg.deg(), 2.94)
-#test(0rad.deg(), 0.0)
-
----
-// Test date methods.
-#test(datetime(day: 1, month: 1, year: 2000).ordinal(), 1);
-#test(datetime(day: 1, month: 3, year: 2000).ordinal(), 31 + 29 + 1);
-#test(datetime(day: 31, month: 12, year: 2000).ordinal(), 366);
-#test(datetime(day: 1, month: 3, year: 2001).ordinal(), 31 + 28 + 1);
-#test(datetime(day: 31, month: 12, year: 2001).ordinal(), 365);
diff --git a/tests/typ/compiler/module.typ b/tests/typ/compiler/module.typ
deleted file mode 100644
index f0652677..00000000
--- a/tests/typ/compiler/module.typ
+++ /dev/null
@@ -1,13 +0,0 @@
-// A file to import in import / include tests.
-// Ref: false
-
-#let a
-#let b = 1
-#let c = 2
-#let d = 3
-#let value = [hi]
-#let item(a, b) = a + b
-#let push(a) = a + 1
-#let fn = rect.with(fill: conifer, inset: 5pt)
-
-Some _includable_ text.
diff --git a/tests/typ/compiler/modules/chap1.typ b/tests/typ/compiler/modules/chap1.typ
deleted file mode 100644
index 06a4c1a1..00000000
--- a/tests/typ/compiler/modules/chap1.typ
+++ /dev/null
@@ -1,9 +0,0 @@
-// Ref: false
-
-#let name = "Klaus"
-
-== Chapter 1
-#name stood in a field of wheat. There was nothing of particular interest about
-the field #name just casually surveyed for any paths on which the corn would not
-totally ruin his semi-new outdorsy jacket but then again, most of us spend
-considerable time in non-descript environments.
diff --git a/tests/typ/compiler/modules/chap2.typ b/tests/typ/compiler/modules/chap2.typ
deleted file mode 100644
index d4aedc60..00000000
--- a/tests/typ/compiler/modules/chap2.typ
+++ /dev/null
@@ -1,11 +0,0 @@
-// Ref: false
-
-#let name = "Klaus"
-
-== Chapter 2
-Their motivations, however, were pretty descript, so to speak. #name had not yet
-conceptualized their consequences, but that should change pretty quickly. #name
-approached the center of the field and picked up a 4-foot long disk made from
-what could only be cow manure. The hair on the back of #name' neck bristled as
-he stared at the unusual sight. After studying the object for a while, he
-promptly popped the question, "How much?"
diff --git a/tests/typ/compiler/modules/cycle1.typ b/tests/typ/compiler/modules/cycle1.typ
deleted file mode 100644
index 02067b71..00000000
--- a/tests/typ/compiler/modules/cycle1.typ
+++ /dev/null
@@ -1,6 +0,0 @@
-// Ref: false
-
-#import "cycle2.typ": *
-#let inaccessible = "wow"
-
-This is the first element of an import cycle.
diff --git a/tests/typ/compiler/modules/cycle2.typ b/tests/typ/compiler/modules/cycle2.typ
deleted file mode 100644
index 191647db..00000000
--- a/tests/typ/compiler/modules/cycle2.typ
+++ /dev/null
@@ -1,6 +0,0 @@
-// Ref: false
-
-#import "cycle1.typ": *
-#let val = "much cycle"
-
-This is the second element of an import cycle.
diff --git a/tests/typ/compiler/ops-assoc.typ b/tests/typ/compiler/ops-assoc.typ
deleted file mode 100644
index ec128c61..00000000
--- a/tests/typ/compiler/ops-assoc.typ
+++ /dev/null
@@ -1,18 +0,0 @@
-// Test operator associativity.
-// Ref: false
-
----
-// Math operators are left-associative.
-#test(10 / 2 / 2 == (10 / 2) / 2, true)
-#test(10 / 2 / 2 == 10 / (2 / 2), false)
-#test(1 / 2 * 3, 1.5)
-
----
-// Assignment is right-associative.
-{
- let x = 1
- let y = 2
- x = y = "ok"
- test(x, none)
- test(y, "ok")
-}
diff --git a/tests/typ/compiler/ops-invalid.typ b/tests/typ/compiler/ops-invalid.typ
deleted file mode 100644
index 64e3a878..00000000
--- a/tests/typ/compiler/ops-invalid.typ
+++ /dev/null
@@ -1,134 +0,0 @@
-// Test invalid operations.
-// Ref: false
-
----
-// Error: 4 expected expression
-#(-)
-
----
-// Error: 10 expected expression
-#test({1+}, 1)
-
----
-// Error: 10 expected expression
-#test({2*}, 2)
-
----
-// Error: 3-13 cannot apply unary '+' to content
-#(+([] + []))
-
----
-// Error: 3-6 cannot apply '-' to string
-#(-"")
-
----
-// Error: 3-9 cannot apply 'not' to array
-#(not ())
-
----
-// Error: 3-19 cannot compare relative length and ratio
-#(30% + 1pt <= 40%)
-
----
-// Error: 3-14 cannot compare 1em with 10pt
-#(1em <= 10pt)
-
----
-// Error: 3-22 cannot compare 2.2 with NaN
-#(2.2 <= float("nan"))
-
----
-// Error: 3-26 cannot compare integer and string
-#((0, 1, 3) > (0, 1, "a"))
-
----
-// Error: 3-42 cannot compare 3.5 with NaN
-#((0, "a", 3.5) <= (0, "a", float("nan")))
-
----
-// Error: 3-12 cannot divide by zero
-#(1.2 / 0.0)
-
----
-// Error: 3-8 cannot divide by zero
-#(1 / 0)
-
----
-// Error: 3-15 cannot divide by zero
-#(15deg / 0deg)
-
----
-// Special messages for +, -, * and /.
-// Error: 3-10 cannot add integer and string
-#(1 + "2", 40% - 1)
-
----
-// Error: 15-23 cannot add integer and string
-#{ let x = 1; x += "2" }
-
----
-// Error: 4-13 cannot divide ratio by length
-#( 10% / 5pt )
-
----
-// Error: 3-12 cannot divide these two lengths
-#(1em / 5pt)
-
----
-// Error: 3-19 cannot divide relative length by ratio
-#((10% + 1pt) / 5%)
-
----
-// Error: 3-28 cannot divide these two relative lengths
-#((10% + 1pt) / (20% + 1pt))
-
----
-// Error: 13-20 cannot subtract integer from ratio
-#((1234567, 40% - 1))
-
----
-// Error: 3-11 cannot multiply integer with boolean
-#(2 * true)
-
----
-// Error: 3-11 cannot divide integer by length
-#(3 / 12pt)
-
----
-// Error: 3-10 number must be at least zero
-#(-1 * "")
-
----
-// Error: 4-5 unknown variable: x
-#((x) = "")
-
----
-// Error: 4-5 unknown variable: x
-#((x,) = (1,))
-
----
-// Error: 3-8 cannot mutate a temporary value
-#(1 + 2 += 3)
-
----
-// Error: 2:3-2:8 cannot apply 'not' to string
-#let x = "Hey"
-#(not x = "a")
-
----
-// Error: 7-8 unknown variable: x
-#(1 + x += 3)
-
----
-// Error: 3-4 unknown variable: z
-#(z = 1)
-
----
-// Error: 3-7 cannot mutate a constant: rect
-#(rect = "hi")
-
----
-// Works if we define rect beforehand
-// (since then it doesn't resolve to the standard library version anymore).
-#let rect = ""
-#(rect = "hi")
diff --git a/tests/typ/compiler/ops-prec.typ b/tests/typ/compiler/ops-prec.typ
deleted file mode 100644
index d3fe01b5..00000000
--- a/tests/typ/compiler/ops-prec.typ
+++ /dev/null
@@ -1,36 +0,0 @@
-// Test operator precedence.
-// Ref: false
-
----
-// Multiplication binds stronger than addition.
-#test(1+2*-3, -5)
-
-// Subtraction binds stronger than comparison.
-#test(3 == 5 - 2, true)
-
-// Boolean operations bind stronger than '=='.
-#test("a" == "a" and 2 < 3, true)
-#test(not "b" == "b", false)
-
----
-// Assignment binds stronger than boolean operations.
-// Error: 2:3-2:8 cannot mutate a temporary value
-#let x = false
-#(not x = "a")
-
----
-// Precedence doesn't matter for chained unary operators.
-// Error: 3-12 cannot apply '-' to boolean
-#(-not true)
-
----
-// Not in handles precedence.
-#test(-1 not in (1, 2, 3), true)
-
----
-// Parentheses override precedence.
-#test((1), 1)
-#test((1+2)*-3, -9)
-
-// Error: 8-9 unclosed delimiter
-#test({(1 + 1}, 2)
diff --git a/tests/typ/compiler/ops.typ b/tests/typ/compiler/ops.typ
deleted file mode 100644
index e148dd19..00000000
--- a/tests/typ/compiler/ops.typ
+++ /dev/null
@@ -1,359 +0,0 @@
-// Test binary expressions.
-// Ref: false
-
----
-// Test adding content.
-// Ref: true
-#([*Hello* ] + [world!])
-
----
-// Test math operators.
-
-// Test plus and minus.
-#for v in (1, 3.14, 12pt, 45deg, 90%, 13% + 10pt, 6.3fr) {
- // Test plus.
- test(+v, v)
-
- // Test minus.
- test(-v, -1 * v)
- test(--v, v)
-
- // Test combination.
- test(-++ --v, -v)
-}
-
-#test(-(4 + 2), 6-12)
-
-// Addition.
-#test(2 + 4, 6)
-#test("a" + "b", "ab")
-#test("a" + if false { "b" }, "a")
-#test("a" + if true { "b" }, "ab")
-#test(13 * "a" + "bbbbbb", "aaaaaaaaaaaaabbbbbb")
-#test((1, 2) + (3, 4), (1, 2, 3, 4))
-#test((a: 1) + (b: 2, c: 3), (a: 1, b: 2, c: 3))
-
----
-// Error: 3-26 value is too large
-#(9223372036854775807 + 1)
-
----
-// Subtraction.
-#test(1-4, 3*-1)
-#test(4cm - 2cm, 2cm)
-#test(1e+2-1e-2, 99.99)
-
-// Multiplication.
-#test(2 * 4, 8)
-
-// Division.
-#test(12pt/.4, 30pt)
-#test(7 / 2, 3.5)
-
-// Combination.
-#test(3-4 * 5 < -10, true)
-#test({ let x; x = 1 + 4*5 >= 21 and { x = "a"; x + "b" == "ab" }; x }, true)
-
-// With block.
-#test(if true {
- 1
-} + 2, 3)
-
-// Mathematical identities.
-#let nums = (
- 1, 3.14,
- 12pt, 3em, 12pt + 3em,
- 45deg,
- 90%,
- 13% + 10pt, 5% + 1em + 3pt,
- 2.3fr,
-)
-
-#for v in nums {
- // Test plus and minus.
- test(v + v - v, v)
- test(v - v - v, -v)
-
- // Test plus/minus and multiplication.
- test(v - v, 0 * v)
- test(v + v, 2 * v)
-
- // Integer addition does not give a float.
- if type(v) != int {
- test(v + v, 2.0 * v)
- }
-
- if type(v) != relative and ("pt" not in repr(v) or "em" not in repr(v)) {
- test(v / v, 1.0)
- }
-}
-
-// Make sure length, ratio and relative length
-// - can all be added to / subtracted from each other,
-// - multiplied with integers and floats,
-// - divided by integers and floats.
-#let dims = (10pt, 1em, 10pt + 1em, 30%, 50% + 3cm, 40% + 2em + 1cm)
-#for a in dims {
- for b in dims {
- test(type(a + b), type(a - b))
- }
-
- for b in (7, 3.14) {
- test(type(a * b), type(a))
- test(type(b * a), type(a))
- test(type(a / b), type(a))
- }
-}
-
-// Test division of different numeric types with zero components.
-#for a in (0pt, 0em, 0%) {
- for b in (10pt, 10em, 10%) {
- test((2 * b) / b, 2)
- test((a + b * 2) / b, 2)
- test(b / (b * 2 + a), 0.5)
- }
-}
-
----
-// Test numbers with alternative bases.
-#test(0x10, 16)
-#test(0b1101, 13)
-#test(0xA + 0xa, 0x14)
-
----
-// Error: 2-7 invalid binary number: 0b123
-#0b123
-
----
-// Error: 2-8 invalid hexadecimal number: 0x123z
-#0x123z
-
----
-// Test that multiplying infinite numbers by certain units does not crash.
-#(float("inf") * 1pt)
-#(float("inf") * 1em)
-#(float("inf") * (1pt + 1em))
-
----
-// Test that trying to produce a NaN scalar (such as in lengths) does not crash.
-#let infpt = float("inf") * 1pt
-#test(infpt - infpt, 0pt)
-#test(infpt + (-infpt), 0pt)
-// TODO: this result is surprising
-#test(infpt / float("inf"), 0pt)
-
----
-// Test boolean operators.
-
-// Test not.
-#test(not true, false)
-#test(not false, true)
-
-// And.
-#test(false and false, false)
-#test(false and true, false)
-#test(true and false, false)
-#test(true and true, true)
-
-// Or.
-#test(false or false, false)
-#test(false or true, true)
-#test(true or false, true)
-#test(true or true, true)
-
-// Short-circuiting.
-#test(false and dont-care, false)
-#test(true or dont-care, true)
-
----
-// Test equality operators.
-
-// Most things compare by value.
-#test(1 == "hi", false)
-#test(1 == 1.0, true)
-#test(30% == 30% + 0cm, true)
-#test(1in == 0% + 72pt, true)
-#test(30% == 30% + 1cm, false)
-#test("ab" == "a" + "b", true)
-#test(() == (1,), false)
-#test((1, 2, 3) == (1, 2.0) + (3,), true)
-#test((:) == (a: 1), false)
-#test((a: 2 - 1.0, b: 2) == (b: 2, a: 1), true)
-#test("a" != "a", false)
-
-// Functions compare by identity.
-#test(test == test, true)
-#test((() => {}) == (() => {}), false)
-
-// Content compares field by field.
-#let t = [a]
-#test(t == t, true)
-#test([] == [], true)
-#test([a] == [a], true)
-#test(grid[a] == grid[a], true)
-#test(grid[a] == grid[b], false)
-
----
-// Test comparison operators.
-
-#test(13 * 3 < 14 * 4, true)
-#test(5 < 10, true)
-#test(5 > 5, false)
-#test(5 <= 5, true)
-#test(5 <= 4, false)
-#test(45deg < 1rad, true)
-#test(10% < 20%, true)
-#test(50% < 40% + 0pt, false)
-#test(40% + 0pt < 50% + 0pt, true)
-#test(1em < 2em, true)
-#test((0, 1, 2, 4) < (0, 1, 2, 5), true)
-#test((0, 1, 2, 4) < (0, 1, 2, 3), false)
-#test((0, 1, 2, 3.3) > (0, 1, 2, 4), false)
-#test((0, 1, 2) < (0, 1, 2, 3), true)
-#test((0, 1, "b") > (0, 1, "a", 3), true)
-#test((0, 1.1, 3) >= (0, 1.1, 3), true)
-#test((0, 1, datetime(day: 1, month: 12, year: 2023)) <= (0, 1, datetime(day: 1, month: 12, year: 2023), 3), true)
-#test(("a", 23, 40, "b") > ("a", 23, 40), true)
-#test(() <= (), true)
-#test(() >= (), true)
-#test(() <= (1,), true)
-#test((1,) <= (), false)
-
----
-// Test assignment operators.
-
-#let x = 0
-#(x = 10) #test(x, 10)
-#(x -= 5) #test(x, 5)
-#(x += 1) #test(x, 6)
-#(x *= x) #test(x, 36)
-#(x /= 2.0) #test(x, 18.0)
-#(x = "some") #test(x, "some")
-#(x += "thing") #test(x, "something")
-
----
-// Test destructuring assignments.
-
-#let a = none
-#let b = none
-#let c = none
-#((a,) = (1,))
-#test(a, 1)
-
-#((_, a, b, _) = (1, 2, 3, 4))
-#test(a, 2)
-#test(b, 3)
-
-#((a, b, ..c) = (1, 2, 3, 4, 5, 6))
-#test(a, 1)
-#test(b, 2)
-#test(c, (3, 4, 5, 6))
-
-#((a: a, b, x: c) = (a: 1, b: 2, x: 3))
-#test(a, 1)
-#test(b, 2)
-#test(c, 3)
-
-#let a = (1, 2)
-#((a: a.at(0), b) = (a: 3, b: 4))
-#test(a, (3, 2))
-#test(b, 4)
-
-#let a = (1, 2)
-#((a.at(0), b) = (3, 4))
-#test(a, (3, 2))
-#test(b, 4)
-
-#((a, ..b) = (1, 2, 3, 4))
-#test(a, 1)
-#test(b, (2, 3, 4))
-
-#let a = (1, 2)
-#((b, ..a.at(0)) = (1, 2, 3, 4))
-#test(a, ((2, 3, 4), 2))
-#test(b, 1)
-
----
-// Test comma placement in destructuring assignment.
-#let array = (1, 2, 3)
-#((key: array.at(1)) = (key: "hi"))
-#test(array, (1, "hi", 3))
-
-#let array = (1, 2, 3)
-#((array.at(1)) = ("hi"))
-#test(array, (1, "hi", 3))
-
-#let array = (1, 2, 3)
-#((array.at(1),) = ("hi",))
-#test(array, (1, "hi", 3))
-
-#let array = (1, 2, 3)
-#((array.at(1)) = ("hi",))
-#test(array, (1, ("hi",), 3))
-
----
-// Test nested destructuring assignment.
-#let a
-#let b
-#let c
-#(((a, b), (key: c)) = ((1, 2), (key: 3)))
-#test((a, b, c), (1, 2, 3))
-
----
-#let array = (1, 2, 3)
-// Error: 3-17 cannot destructure string
-#((array.at(1),) = ("hi"))
-#test(array, (1, ("hi",), 3))
-
----
-// Error: 3-6 cannot mutate a constant: box
-#(box = 1)
-
----
-// Test `in` operator.
-#test("hi" in "worship", true)
-#test("hi" in ("we", "hi", "bye"), true)
-#test("Hey" in "abHeyCd", true)
-#test("Hey" in "abheyCd", false)
-#test(5 in range(10), true)
-#test(12 in range(10), false)
-#test("" in (), false)
-#test("key" in (key: "value"), true)
-#test("value" in (key: "value"), false)
-#test("Hey" not in "abheyCd", true)
-#test("a" not
-/* fun comment? */ in "abc", false)
-
----
-// Error: 10 expected keyword `in`
-#("a" not)
-
----
-// Test `with` method.
-
-// Apply positional arguments.
-#let add(x, y) = x + y
-#test(add.with(2)(3), 5)
-#test(add.with(2, 3)(), 5)
-#test(add.with(2).with(3)(), 5)
-#test((add.with(2))(4), 6)
-#test((add.with(2).with(3))(), 5)
-
-// Make sure that named arguments are overridable.
-#let inc(x, y: 1) = x + y
-#test(inc(1), 2)
-
-#let inc2 = inc.with(y: 2)
-#test(inc2(2), 4)
-#test(inc2(2, y: 4), 6)
-
-// Apply arguments to an argument sink.
-#let times(..sink) = {
- let res = sink.pos().product()
- if sink.named().at("negate", default: false) { res *= -1 }
- res
-}
-#test((times.with(2, negate: true).with(5))(), -10)
-#test((times.with(2).with(5).with(negate: true))(), -10)
-#test((times.with(2).with(5, negate: true))(), -10)
-#test((times.with(2).with(negate: true))(5), -10)
diff --git a/tests/typ/compiler/packages.typ b/tests/typ/compiler/packages.typ
deleted file mode 100644
index 0d3fda58..00000000
--- a/tests/typ/compiler/packages.typ
+++ /dev/null
@@ -1,69 +0,0 @@
-// Test package imports
-// Ref: false
-
----
-// Test import without items.
-#import "@test/adder:0.1.0"
-#test(adder.add(2, 8), 10)
-
----
-// Test import with items.
-#import "@test/adder:0.1.0": add
-#test(add(2, 8), 10)
-
----
-// Test too high required compiler version.
-// Error: 9-29 package requires typst 1.0.0 or newer (current version is VERSION)
-#import "@test/future:0.1.0": future
-
----
-// Error: 9-13 `@` is not a valid package namespace
-#import "@@": *
-
----
-// Error: 9-16 package specification is missing name
-#import "@heya": *
-
----
-// Error: 9-15 `123` is not a valid package namespace
-#import "@123": *
-
----
-// Error: 9-17 package specification is missing name
-#import "@test/": *
-
----
-// Error: 9-22 package specification is missing version
-#import "@test/mypkg": *
-
----
-// Error: 9-20 `$$$` is not a valid package name
-#import "@test/$$$": *
-
----
-// Error: 9-23 package specification is missing version
-#import "@test/mypkg:": *
-
----
-// Error: 9-24 version number is missing minor version
-#import "@test/mypkg:0": *
-
----
-// Error: 9-29 `latest` is not a valid major version
-#import "@test/mypkg:latest": *
-
----
-// Error: 9-29 `-3` is not a valid major version
-#import "@test/mypkg:-3.0.0": *
-
----
-// Error: 9-26 version number is missing patch version
-#import "@test/mypkg:0.3": *
-
----
-// Error: 9-27 version number is missing patch version
-#import "@test/mypkg:0.3.": *
-
----
-// Error: 9-28 file not found (searched at typ/compiler/#test/mypkg:1.0.0)
-#import "#test/mypkg:1.0.0": *
diff --git a/tests/typ/compiler/plugin-oob.typ b/tests/typ/compiler/plugin-oob.typ
deleted file mode 100644
index 4d1ba205..00000000
--- a/tests/typ/compiler/plugin-oob.typ
+++ /dev/null
@@ -1,14 +0,0 @@
-// Test Out Of Bound read/write in WebAssembly plugins communication.
-// Ref: false
-
----
-#let p = plugin("/assets/plugins/plugin-oob.wasm")
-
-// Error: 2-14 plugin tried to read out of bounds: pointer 0x40000000 is out of bounds for read of length 1
-#p.read_oob()
-
----
-#let p = plugin("/assets/plugins/plugin-oob.wasm")
-
-// Error: 2-27 plugin tried to write out of bounds: pointer 0x40000000 is out of bounds for write of length 3
-#p.write_oob(bytes("xyz"))
diff --git a/tests/typ/compiler/plugin.typ b/tests/typ/compiler/plugin.typ
deleted file mode 100644
index e727355f..00000000
--- a/tests/typ/compiler/plugin.typ
+++ /dev/null
@@ -1,36 +0,0 @@
-// Test WebAssembly plugins.
-// Ref: false
-
----
-#let p = plugin("/assets/plugins/hello.wasm")
-#test(p.hello(), bytes("Hello from wasm!!!"))
-#test(p.double_it(bytes("hey!")), bytes("hey!.hey!"))
-#test(
- p.shuffle(bytes("value1"), bytes("value2"), bytes("value3")),
- bytes("value3-value1-value2"),
-)
-
----
-#let p = plugin("/assets/plugins/hello.wasm")
-
-// Error: 2-20 plugin function takes 0 arguments, but 1 was given
-#p.hello(bytes(""))
-
----
-#let p = plugin("/assets/plugins/hello.wasm")
-
-// Error: 10-14 expected bytes, found boolean
-// Error: 27-29 expected bytes, found integer
-#p.hello(true, bytes(()), 10)
-
----
-#let p = plugin("/assets/plugins/hello.wasm")
-
-// Error: 2-17 plugin errored with: This is an `Err`
-#p.returns_err()
-
----
-#let p = plugin("/assets/plugins/hello.wasm")
-
-// Error: 2-16 plugin panicked: wasm `unreachable` instruction executed
-#p.will_panic()
diff --git a/tests/typ/compiler/raw.typ b/tests/typ/compiler/raw.typ
deleted file mode 100644
index 3084146d..00000000
--- a/tests/typ/compiler/raw.typ
+++ /dev/null
@@ -1,170 +0,0 @@
-// Test new raw parser
-// Ref: false
-
----
-#let empty = (
- name: "empty",
- input: ``,
- text: "",
-)
-
-#let backtick = (
- name: "backtick",
- input: ``` ` ```,
- text: "`",
- block: false,
-)
-
-#let lang-backtick = (
- name: "lang-backtick",
- input: ```js ` ```,
- lang: "js",
- text: "`",
- block: false,
-)
-
-// The language tag stops on space
-#let lang-space = (
- name: "lang-space",
- input: ```js test ```,
- lang: "js",
- text: "test ",
- block: false,
-)
-
-// The language tag stops on newline
-#let lang-newline = (
- name: "lang-newline",
- input: ```js
-test
-```,
- lang: "js",
- text: "test",
- block: true,
-)
-
-// The first line and the last line are ignored
-#let blocky = (
- name: "blocky",
- input: {
-```
-test
-```
-},
- text: "test",
- block: true,
-)
-
-// A blocky raw should handle dedents
-#let blocky-dedent = (
- name: "blocky-dedent",
- input: {
-```
- test
- ```
- },
- text: "test",
- block: true,
-)
-
-// When there is content in the first line, it should exactly eat a whitespace char.
-#let blocky-dedent-firstline = (
- name: "blocky-dedent-firstline",
- input: ``` test
- ```,
- text: "test",
- block: true,
-)
-
-// When there is content in the first line, it should exactly eat a whitespace char.
-#let blocky-dedent-firstline2 = (
- name: "blocky-dedent-firstline2",
- input: ``` test
-```,
- text: "test",
- block: true,
-)
-
-// The first line is not affected by dedent, and the middle lines don't consider the whitespace prefix of the first line.
-#let blocky-dedent-firstline3 = (
- name: "blocky-dedent-firstline3",
- input: ``` test
- test2
- ```,
- text: "test\n test2",
- block: true,
-)
-
-// The first line is not affected by dedent, and the middle lines don't consider the whitespace prefix of the first line.
-#let blocky-dedent-firstline4 = (
- name: "blocky-dedent-firstline4",
- input: ``` test
- test2
- ```,
- text: " test\ntest2",
- block: true,
-)
-
-#let blocky-dedent-lastline = (
- name: "blocky-dedent-lastline",
- input: ```
- test
- ```,
- text: " test",
- block: true,
-)
-
-#let blocky-dedent-lastline2 = (
- name: "blocky-dedent-lastline2",
- input: ```
- test
- ```,
- text: "test",
- block: true,
-)
-
-#let blocky-tab = (
- name: "blocky-tab",
- input: {
-```
- test
-```
-},
- text: "\ttest",
- block: true,
-)
-
-#let blocky-tab-dedent = (
- name: "blocky-tab-dedent",
- input: {
-```
- test
-
- ```
-},
- text: "test\n ",
- block: true,
-)
-
-#let cases = (
- empty,
- backtick,
- lang-backtick,
- lang-space,
- lang-newline,
- blocky,
- blocky-dedent,
- blocky-dedent-firstline,
- blocky-dedent-firstline2,
- blocky-dedent-firstline3,
- blocky-dedent-lastline,
- blocky-dedent-lastline2,
- blocky-tab,
- blocky-tab-dedent,
-)
-
-#for c in cases {
- assert.eq(c.text, c.input.text, message: "in point " + c.name + ", expect " + repr(c.text) + ", got " + repr(c.input.text) + "")
- let block = c.at("block", default: false)
- assert.eq(block, c.input.block, message: "in point " + c.name + ", expect " + repr(block) + ", got " + repr(c.input.block) + "")
-}
diff --git a/tests/typ/compiler/recursion.typ b/tests/typ/compiler/recursion.typ
deleted file mode 100644
index 421b638b..00000000
--- a/tests/typ/compiler/recursion.typ
+++ /dev/null
@@ -1,56 +0,0 @@
-// Test recursive function calls.
-// Ref: false
-
----
-// Test with named function.
-#let fib(n) = {
- if n <= 2 {
- 1
- } else {
- fib(n - 1) + fib(n - 2)
- }
-}
-
-#test(fib(10), 55)
-
----
-// Test with unnamed function.
-// Error: 17-18 unknown variable: f
-#let f = (n) => f(n - 1)
-#f(10)
-
----
-// Test capturing with named function.
-#let f = 10
-#let f() = f
-#test(type(f()), function)
-
----
-// Test capturing with unnamed function.
-#let f = 10
-#let f = () => f
-#test(type(f()), int)
-
----
-// Test redefinition.
-#let f(x) = "hello"
-#let f(x) = if x != none { f(none) } else { "world" }
-#test(f(1), "world")
-
----
-// Error: 15-21 maximum function call depth exceeded
-#let rec(n) = rec(n) + 1
-#rec(1)
-
----
-// Test cyclic imports during layout.
-// Error: 2-38 maximum show rule depth exceeded
-// Hint: 2-38 check whether the show rule matches its own output
-#layout(_ => include "recursion.typ")
-
----
-// Test recursive show rules.
-// Error: 22-25 maximum show rule depth exceeded
-// Hint: 22-25 check whether the show rule matches its own output
-#show math.equation: $x$
-$ x $
diff --git a/tests/typ/compiler/repr-color-gradient.typ b/tests/typ/compiler/repr-color-gradient.typ
deleted file mode 100644
index ef158974..00000000
--- a/tests/typ/compiler/repr-color-gradient.typ
+++ /dev/null
@@ -1,23 +0,0 @@
-// Test representation of values in the document.
-
----
-// Colors
-#set page(width: 400pt)
-#set text(0.8em)
-#blue \
-#color.linear-rgb(blue) \
-#oklab(blue) \
-#oklch(blue) \
-#cmyk(blue) \
-#color.hsl(blue) \
-#color.hsv(blue) \
-#luma(blue)
-
----
-// Gradients
-#set page(width: 400pt)
-#set text(0.8em)
-#gradient.linear(blue, red) \
-#gradient.linear(blue, red, dir: ttb) \
-#gradient.linear(blue, red, angle: 45deg, relative: "self") \
-#gradient.linear(blue, red, angle: 45deg, space: rgb)
diff --git a/tests/typ/compiler/repr.typ b/tests/typ/compiler/repr.typ
deleted file mode 100644
index 5aaf5710..00000000
--- a/tests/typ/compiler/repr.typ
+++ /dev/null
@@ -1,55 +0,0 @@
-// Test representation of values in the document.
-
----
-// Literal values.
-#auto \
-#none (empty) \
-#true \
-#false
-
----
-// Numerical values.
-#1 \
-#1.0e-4 \
-#3.15 \
-#1e-10 \
-#50.368% \
-#0.0000012345pt \
-#4.5cm \
-#12e1pt \
-#2.5rad \
-#45deg \
-#1.7em \
-#(1cm + 0em) \
-#(2em + 10pt) \
-#(100% + (2em + 2pt)) \
-#(100% + 0pt) \
-#(100% - 2em + 2pt) \
-#(100% - 2pt) \
-#2.3fr
-
----
-// Colors and strokes.
-#set text(0.8em)
-#rgb("f7a205") \
-#(2pt + rgb("f7a205"))
-
-// Strings and escaping.
-#raw(repr("hi"), lang: "typc")
-#repr("a\n[]\"\u{1F680}string")
-
-// Content.
-#raw(lang: "typc", repr[*Hey*]) \
-#raw(lang: "typc", repr[A _sequence_]) \
-#raw(lang: "typc", repr[A _longer_ *sequence*!])
-
-// Functions.
-#let f(x) = x
-#f \
-#rect \
-#(() => none)
-
-// Types.
-#int \
-#type("hi") \
-#type((a: 1))
diff --git a/tests/typ/compiler/return.typ b/tests/typ/compiler/return.typ
deleted file mode 100644
index e709d6a7..00000000
--- a/tests/typ/compiler/return.typ
+++ /dev/null
@@ -1,89 +0,0 @@
-// Test return out of functions.
-// Ref: false
-
----
-// Test return with value.
-#let f(x) = {
- return x + 1
-}
-
-#test(f(1), 2)
-
----
-// Test return with joining.
-
-#let f(x) = {
- "a"
- if x == 0 {
- return "b"
- } else if x == 1 {
- "c"
- } else {
- "d"
- return
- "e"
- }
-}
-
-#test(f(0), "b")
-#test(f(1), "ac")
-#test(f(2), "ad")
-
----
-// Test return with joining and content.
-// Ref: true
-
-#let f(text, caption: none) = {
- text
- if caption == none [\.#return]
- [, ]
- emph(caption)
- [\.]
-}
-
-#f(caption: [with caption])[My figure]
-
-#f[My other figure]
-
----
-// Test return outside of function.
-
-#for x in range(5) {
- // Error: 3-9 cannot return outside of function
- return
-}
-
----
-// Test that the expression is evaluated to the end.
-#let sum(..args) = {
- let s = 0
- for v in args.pos() {
- s += v
- }
- s
-}
-
-#let f() = {
- sum(..return, 1, 2, 3)
- "nope"
-}
-
-#test(f(), 6)
-
----
-// Test value return from content.
-#let x = 3
-#let f() = [
- Hello 😀
- #return "nope"
- World
-]
-
-#test(f(), "nope")
-
----
-// Test rejection of extra value
-#let f() = [
- // Error: 16-16 expected semicolon or line break
- #return a + b Hello World
-]
diff --git a/tests/typ/compiler/select-where-styles.typ b/tests/typ/compiler/select-where-styles.typ
deleted file mode 100644
index 028be2e9..00000000
--- a/tests/typ/compiler/select-where-styles.typ
+++ /dev/null
@@ -1,91 +0,0 @@
-// Test that where selectors also work with settable fields.
-
----
-// Test that where selectors also trigger on set rule fields.
-#show raw.where(block: false): box.with(
- fill: luma(220),
- inset: (x: 3pt, y: 0pt),
- outset: (y: 3pt),
- radius: 2pt,
-)
-
-This is #raw("fn main() {}") some text.
-
----
-// Note: This show rule is horribly inefficient because it triggers for
-// every individual text element. But it should still work.
-#show text.where(lang: "de"): set text(red)
-
-#set text(lang: "es")
-Hola, mundo!
-
-#set text(lang: "de")
-Hallo Welt!
-
-#set text(lang: "en")
-Hello World!
-
----
-// Test that folding is taken into account.
-#set text(5pt)
-#set text(2em)
-
-#[
- #show text.where(size: 2em): set text(blue)
- 2em not blue
-]
-
-#[
- #show text.where(size: 10pt): set text(blue)
- 10pt blue
-]
-
----
-// Test again that folding is taken into account.
-#set rect(width: 40pt, height: 10pt)
-#set rect(stroke: blue)
-#set rect(stroke: 2pt)
-
-#{
- show rect.where(stroke: blue): "Not Triggered"
- rect()
-}
-#{
- show rect.where(stroke: 2pt): "Not Triggered"
- rect()
-}
-#{
- show rect.where(stroke: 2pt + blue): "Triggered"
- rect()
-}
-
----
-// Test that resolving is *not* taken into account.
-#set line(start: (1em, 1em + 2pt))
-
-#{
- show line.where(start: (1em, 1em + 2pt)): "Triggered"
- line()
-}
-#{
- show line.where(start: (10pt, 12pt)): "Not Triggered"
- line()
-}
-
-
----
-// Test again that resolving is *not* taken into account.
-#set text(hyphenate: auto)
-
-#[
- #show text.where(hyphenate: auto): underline
- Auto
-]
-#[
- #show text.where(hyphenate: true): underline
- True
-]
-#[
- #show text.where(hyphenate: false): underline
- False
-]
diff --git a/tests/typ/compiler/selector-logical.typ b/tests/typ/compiler/selector-logical.typ
deleted file mode 100644
index 5369e4c7..00000000
--- a/tests/typ/compiler/selector-logical.typ
+++ /dev/null
@@ -1,126 +0,0 @@
-//Tests for logical (and/or) selectors
-
----
-= A
-== B
-#figure([Cat], kind: "cat", supplement: [Other])
-=== D
-= E <first>
-#figure([Frog], kind: "frog", supplement: none)
-#figure([Giraffe], kind: "giraffe", supplement: none) <second>
-#figure([GiraffeCat], kind: "cat", supplement: [Other]) <second>
-= H
-#figure([Iguana], kind: "iguana", supplement: none)
-== I
-
-#let test-selector(selector, ref) = context {
- test(query(selector).map(e => e.body), ref)
-}
-
-// Test `or`.
-#test-selector(
- heading.where(level: 1).or(heading.where(level: 3)),
- ([A], [D], [E], [H]),
-)
-
-#test-selector(
- heading.where(level: 1).or(
- heading.where(level: 3),
- figure.where(kind: "frog"),
- ),
- ([A], [D], [E], [Frog], [H]),
-)
-
-#test-selector(
- heading.where(level: 1).or(
- heading.where(level: 2),
- figure.where(kind: "frog"),
- figure.where(kind: "cat"),
- ),
- ([A], [B], [Cat], [E], [Frog], [GiraffeCat], [H], [I]),
-)
-
-#test-selector(
- figure.where(kind: "dog").or(heading.where(level: 3)),
- ([D],),
-)
-
-#test-selector(
- figure.where(kind: "dog").or(figure.where(kind: "fish")),
- (),
-)
-
-// Test `or` duplicates removal.
-#test-selector(
- heading.where(level: 1).or(heading.where(level: 1)),
- ([A], [E], [H]),
-)
-
-// Test `and`.
-#test-selector(
- figure.where(kind: "cat").and(figure.where(kind: "frog")),
- (),
-)
-
-// Test `or` with `before`/`after`
-#test-selector(
- selector(heading)
- .before(<first>)
- .or(selector(figure).before(<first>)),
- ([A], [B], [Cat], [D], [E]),
-)
-
-#test-selector(
- heading.where(level: 2)
- .after(<first>)
- .or(selector(figure).after(<first>)),
- ([Frog], [Giraffe], [GiraffeCat], [Iguana], [I]),
-)
-
-// Test `and` with `after`
-#test-selector(
- figure.where(kind: "cat")
- .and(figure.where(supplement: [Other]))
- .after(<first>),
- ([GiraffeCat],),
-)
-
-// Test `and` (with nested `or`)
-#test-selector(
- heading.where(level: 2)
- .or(heading.where(level: 3))
- .and(heading.where(level: 2).or(heading.where(level: 1))),
- ([B], [I]),
-)
-
-#test-selector(
- heading.where(level: 2)
- .or(heading.where(level: 3), heading.where(level:1))
- .and(
- heading.where(level: 2).or(heading.where(level: 1)),
- heading.where(level: 3).or(heading.where(level: 1)),
- ),
- ([A], [E], [H]),
-)
-
-// Test `and` with `or` and `before`/`after`
-#test-selector(
- heading.where(level: 1).before(<first>)
- .or(heading.where(level: 3).before(<first>))
- .and(
- heading.where(level: 1).before(<first>)
- .or(heading.where(level: 2).before(<first>))
- ),
- ([A], [E]),
-)
-
-#test-selector(
- heading.where(level: 1).before(<first>, inclusive: false)
- .or(selector(figure).after(<first>))
- .and(figure.where(kind: "iguana").or(
- figure.where(kind: "frog"),
- figure.where(kind: "cat"),
- heading.where(level: 1).after(<first>),
- )),
- ([Frog], [GiraffeCat], [Iguana])
-)
diff --git a/tests/typ/compiler/set.typ b/tests/typ/compiler/set.typ
deleted file mode 100644
index 23b3a7c6..00000000
--- a/tests/typ/compiler/set.typ
+++ /dev/null
@@ -1,66 +0,0 @@
-// General tests for set.
-
----
-// Test that text is affected by instantiation-site bold.
-#let x = [World]
-Hello *#x*
-
----
-// Test that lists are affected by correct indents.
-#let fruit = [
- - Apple
- - Orange
- #list(body-indent: 20pt)[Pear]
-]
-
-- Fruit
-#[#set list(indent: 10pt)
- #fruit]
-- No more fruit
-
----
-// Test that that block spacing and text style are respected from
-// the outside, but the more specific fill is respected.
-#set block(spacing: 4pt)
-#set text(style: "italic", fill: eastern)
-#let x = [And the forest #parbreak() lay silent!]
-#text(fill: forest, x)
-
----
-// Test that scoping works as expected.
-#{
- if true {
- set text(blue)
- [Blue ]
- }
- [Not blue]
-}
-
----
-// Test relative path resolving in layout phase.
-#let choice = ("monkey.svg", "rhino.png", "tiger.jpg")
-#set enum(numbering: n => {
- let path = "/assets/images/" + choice.at(n - 1)
- move(dy: -0.15em, image(path, width: 1em, height: 1em))
-})
-
-+ Monkey
-+ Rhino
-+ Tiger
-
----
-// Test conditional set.
-#show ref: it => {
- set text(red) if it.target == <unknown>
- "@" + str(it.target)
-}
-
-@hello from the @unknown
-
----
-// Error: 19-24 expected boolean, found integer
-#set text(red) if 1 + 2
-
----
-// Error: 12-26 set is only allowed directly in code and content blocks
-#{ let x = set text(blue) }
diff --git a/tests/typ/compiler/shorthand.typ b/tests/typ/compiler/shorthand.typ
deleted file mode 100644
index 54ae7473..00000000
--- a/tests/typ/compiler/shorthand.typ
+++ /dev/null
@@ -1,61 +0,0 @@
-// Test shorthands for unicode codepoints.
-
----
-The non-breaking space~does work, soft-?hyphen also does.
-
----
-// Make sure non-breaking and normal space always
-// have the same width. Even if the font decided
-// differently.
-#set text(font: "New Computer Modern")
-a b \
-a~b
-
----
-- En dash: --
-- Em dash: ---
-
----
-#set text(font: "Roboto")
-A... vs #"A..."
-
----
-// Check all math shorthands
-$...$\
-$-$\
-$'$\
-$*$\
-$!=$\
-$:=$\
-$::=$\
-$=:$\
-$<<$\
-$<<<$\
-$>>$\
-$>>>$\
-$<=$\
-$>=$\
-$->$\
-$-->$\
-$|->$\
-$>->$\
-$->>$\
-$<-$\
-$<--$\
-$<-<$\
-$<<-$\
-$<->$\
-$<-->$\
-$~>$\
-$~~>$\
-$<~$\
-$<~~$\
-$=>$\
-$|=>$\
-$==>$\
-$<==$\
-$<=>$\
-$<==>$\
-$[|$\
-$|]$\
-$||$
diff --git a/tests/typ/compiler/show-bare.typ b/tests/typ/compiler/show-bare.typ
deleted file mode 100644
index 210f072d..00000000
--- a/tests/typ/compiler/show-bare.typ
+++ /dev/null
@@ -1,41 +0,0 @@
-// Test bare show without selector.
-
----
-#set page(height: 130pt)
-#set text(0.7em)
-
-#align(center)[
- #text(1.3em)[*Essay on typography*] \
- T. Ypst
-]
-
-#show: columns.with(2)
-Great typography is at the essence of great storytelling. It is the medium that
-transports meaning from parchment to reader, the wave that sparks a flame
-in booklovers and the great fulfiller of human need.
-
----
-// Test bare show in content block.
-A #[_B #show: c => [*#c*]; C_] D
-
----
-// Test style precedence.
-#set text(fill: eastern, size: 1.5em)
-#show: text.with(fill: forest)
-Forest
-
----
-#show: [Shown]
-Ignored
-
----
-// Error: 4-19 show is only allowed directly in code and content blocks
-#((show: body => 2) * body)
-
----
-// Error: 6 expected colon
-#show it => {}
-
----
-// Error: 6 expected colon
-#show it
diff --git a/tests/typ/compiler/show-node.typ b/tests/typ/compiler/show-node.typ
deleted file mode 100644
index ff2bdb5a..00000000
--- a/tests/typ/compiler/show-node.typ
+++ /dev/null
@@ -1,104 +0,0 @@
-// Test show rules.
-
----
-// Override lists.
-#show list: it => "(" + it.children.map(v => v.body).join(", ") + ")"
-
-- A
- - B
- - C
-- D
-- E
-
----
-// Test full reset.
-#show heading: [B]
-#show heading: set text(size: 10pt, weight: 400)
-A #[= Heading] C
-
----
-// Test full removal.
-#show heading: none
-
-Where is
-= There are no headings around here!
-my heading?
-
----
-// Test integrated example.
-#show heading: it => block({
- set text(10pt)
- box(move(dy: -1pt)[📖])
- h(5pt)
- if it.level == 1 {
- underline(text(1.25em, blue, it.body))
- } else {
- text(red, it.body)
- }
-})
-
-= Task 1
-Some text.
-
-== Subtask
-Some more text.
-
-= Task 2
-Another text.
-
----
-// Test set and show in code blocks.
-#show heading: it => {
- set text(red)
- show "ding": [🛎]
- it.body
-}
-
-= Heading
-
----
-// Test that scoping works as expected.
-#{
- let world = [ World ]
- show "W": strong
- world
- {
- set text(blue)
- show: it => {
- show "o": "Ø"
- it
- }
- world
- }
- world
-}
-
----
-#show heading: [1234]
-= Heading
-
----
-// Error: 25-29 content does not contain field "page"
-#show heading: it => it.page
-= Heading
-
----
-#show text: none
-Hey
-
----
-// Error: 7-12 only element functions can be used as selectors
-#show upper: it => {}
-
----
-// Error: 16-20 expected content or function, found integer
-#show heading: 1234
-= Heading
-
----
-// Error: 7-10 expected symbol, string, label, function, regex, or selector, found color
-#show red: []
-
----
-// Error: 7-25 show is only allowed directly in code and content blocks
-#(1 + show heading: none)
diff --git a/tests/typ/compiler/show-recursive.typ b/tests/typ/compiler/show-recursive.typ
deleted file mode 100644
index 91a295f2..00000000
--- a/tests/typ/compiler/show-recursive.typ
+++ /dev/null
@@ -1,51 +0,0 @@
-// Test recursive show rules.
-
----
-// Test basic identity.
-#show heading: it => it
-= Heading
-
----
-// Test more recipes down the chain.
-#show list: scale.with(origin: left, x: 80%)
-#show heading: []
-#show enum: []
-- Actual
-- Tight
-- List
-= Nope
-
----
-// Test show rule in function.
-#let starwars(body) = {
- show list: it => block({
- stack(dir: ltr,
- text(red, it),
- 1fr,
- scale(x: -100%, text(blue, it)),
- )
- })
- body
-}
-
-- Normal list
-
-#starwars[
- - Star
- - Wars
- - List
-]
-
-- Normal list
-
----
-// Test multi-recursion with nested lists.
-#set rect(inset: 3pt)
-#show list: rect.with(stroke: blue)
-#show list: rect.with(stroke: red)
-#show list: block
-
-- List
- - Nested
- - List
-- Recursive!
diff --git a/tests/typ/compiler/show-selector-logical.typ b/tests/typ/compiler/show-selector-logical.typ
deleted file mode 100644
index a11e20b6..00000000
--- a/tests/typ/compiler/show-selector-logical.typ
+++ /dev/null
@@ -1,21 +0,0 @@
-// Test and/or selectors in show rules.
-
----
-// Looking forward to `heading.where(level: 1 | 2)` :)
-#show heading.where(level: 1).or(heading.where(level: 2)): set text(red)
-= L1
-== L2
-=== L3
-==== L4
-
----
-// Test element selector combined with label selector.
-#show selector(strong).or(<special>): highlight
-I am *strong*, I am _emphasized_, and I am #[special<special>].
-
----
-// Ensure that text selector cannot be nested in and/or. That's too complicated,
-// at least for now.
-
-// Error: 7-41 this selector cannot be used with show
-#show heading.where(level: 1).or("more"): set text(red)
diff --git a/tests/typ/compiler/show-selector.typ b/tests/typ/compiler/show-selector.typ
deleted file mode 100644
index db6db40f..00000000
--- a/tests/typ/compiler/show-selector.typ
+++ /dev/null
@@ -1,39 +0,0 @@
-// Test show rule patterns.
-
----
-// Inline code.
-#show raw.where(block: false): box.with(
- radius: 2pt,
- outset: (y: 2.5pt),
- inset: (x: 3pt, y: 0pt),
- fill: luma(230),
-)
-
-// Code blocks.
-#show raw.where(block: true): block.with(
- outset: -3pt,
- inset: 11pt,
- fill: luma(230),
- stroke: (left: 1.5pt + luma(180)),
-)
-
-#set page(margin: (top: 12pt))
-#set par(justify: true)
-
-This code tests `code`
-with selectors and justification.
-
-```rs
-code!("it");
-```
-
-You can use the ```rs *const T``` pointer or
-the ```rs &mut T``` reference.
-
----
-#show heading: set text(green)
-#show heading.where(level: 1): set text(red)
-#show heading.where(level: 2): set text(blue)
-= Red
-== Blue
-=== Green
diff --git a/tests/typ/compiler/show-set-func.typ b/tests/typ/compiler/show-set-func.typ
deleted file mode 100644
index 0447d946..00000000
--- a/tests/typ/compiler/show-set-func.typ
+++ /dev/null
@@ -1,16 +0,0 @@
-// Test set rules on an element in show rules for said element.
-
----
-// These are both red because in the expanded form, `set text(red)` ends up
-// closer to the content than `set text(blue)`.
-#show strong: it => { set text(red); it }
-Hello *World*
-
-#show strong: it => { set text(blue); it }
-Hello *World*
-
----
-// This doesn't have an effect. An element is materialized before any show
-// rules run.
-#show heading: it => { set heading(numbering: "(I)"); it }
-= Heading
diff --git a/tests/typ/compiler/show-set-text.typ b/tests/typ/compiler/show-set-text.typ
deleted file mode 100644
index 464cad90..00000000
--- a/tests/typ/compiler/show-set-text.typ
+++ /dev/null
@@ -1,41 +0,0 @@
-// Text show-set rules are weird.
-
----
-#show "He": set text(red)
-#show "ya": set text(blue)
-Heya
-
----
-#show "Heya": set text(red)
-#show "ya": set text(blue)
-Heya
-
----
-#show "He": set text(red)
-#show "Heya": set text(blue)
-Heya
-
----
-#show "Heya": set text(red)
-#show "yaho": set text(blue)
-Heyaho
-
----
-#show "He": set text(red)
-#show "ya": set text(weight: "bold")
-Heya
-
----
-#show "Heya": set text(red)
-#show "ya": set text(weight: "bold")
-Heya
-
----
-#show "He": set text(red)
-#show "Heya": set text(weight: "bold")
-Heya
-
----
-#show "Heya": set text(red)
-#show "yaho": set text(weight: "bold")
-Heyaho
diff --git a/tests/typ/compiler/show-set.typ b/tests/typ/compiler/show-set.typ
deleted file mode 100644
index e336f517..00000000
--- a/tests/typ/compiler/show-set.typ
+++ /dev/null
@@ -1,55 +0,0 @@
-// Test show-set rules.
-
----
-// Test overriding show-set rules.
-#show strong: set text(red)
-Hello *World*
-
-#show strong: set text(blue)
-Hello *World*
-
----
-// Test show-set rule on the same element.
-#set figure(supplement: [Default])
-#show figure.where(kind: table): set figure(supplement: [Tableau])
-#figure(
- table(columns: 2)[A][B][C][D],
- caption: [Four letters],
-)
-
----
-// Test both things at once.
-#show heading: set text(red)
-= Level 1
-== Level 2
-
-#show heading.where(level: 1): set text(blue)
-#show heading.where(level: 1): set text(green)
-#show heading.where(level: 1): set heading(numbering: "(I)")
-= Level 1
-== Level 2
-
----
-// Test setting the thing we just matched on.
-// This is quite cursed, but it works.
-#set heading(numbering: "(I)")
-#show heading.where(numbering: "(I)"): set heading(numbering: "1.")
-= Heading
-
----
-// Same thing, but even more cursed, because `kind` is synthesized.
-#show figure.where(kind: table): set figure(kind: raw)
-#figure(table[A], caption: [Code])
-
----
-// Test that show-set rules on the same element don't affect each other. This
-// could be implemented, but isn't as of yet.
-#show heading.where(level: 1): set heading(numbering: "(I)")
-#show heading.where(numbering: "(I)"): set text(red)
-= Heading
-
----
-// Test show-set rules on layoutable element to ensure it is realized
-// even though it implements `LayoutMultiple`.
-#show table: set text(red)
-#pad(table(columns: 4)[A][B][C][D])
diff --git a/tests/typ/compiler/show-text.typ b/tests/typ/compiler/show-text.typ
deleted file mode 100644
index a42abfb2..00000000
--- a/tests/typ/compiler/show-text.typ
+++ /dev/null
@@ -1,93 +0,0 @@
-// Test text replacement show rules.
-
----
-// Test classic example.
-#set text(font: "Roboto")
-#show "Der Spiegel": smallcaps
-Die Zeitung Der Spiegel existiert.
-
----
-// Another classic example.
-#show "TeX": [T#h(-0.145em)#box(move(dy: 0.233em)[E])#h(-0.135em)X]
-#show regex("(Lua)?(La)?TeX"): name => box(text(font: "New Computer Modern")[#name])
-
-TeX, LaTeX, LuaTeX and LuaLaTeX!
-
----
-// Test direct cycle.
-#show "Hello": text(red)[Hello]
-Hello World!
-
----
-// Test replacing text with raw text.
-#show "rax": `rax`
-The register rax.
-
----
-// Test indirect cycle.
-#show "Good": [Typst!]
-#show "Typst": [Fun!]
-#show "Fun": [Good!]
-
-#set text(ligatures: false)
-Good \
-Fun \
-Typst \
-
----
-// Test that replacements happen exactly once.
-#show "A": [BB]
-#show "B": [CC]
-AA (8)
-
----
-// Test caseless match and word boundaries.
-#show regex("(?i)\bworld\b"): [🌍]
-
-Treeworld, the World of worlds, is a world.
-
----
-// Test there is no crashing on empty strings
-// Error: 1:7-1:9 text selector is empty
-#show "": []
-
----
-// Error: 1:7-1:16 regex selector is empty
-#show regex(""): [AA]
-
----
-// Error: 1:7-1:42 regex matches empty text
-#show regex("(VAR_GLOBAL|END_VAR||BOOL)") : []
-
----
-// This is a fun one.
-#set par(justify: true)
-#show regex("\S"): letter => box(stroke: 1pt, inset: 2pt, upper(letter))
-#lorem(5)
-
----
-// See also: https://github.com/mTvare6/hello-world.rs
-#show regex("(?i)rust"): it => [#it (🚀)]
-Rust is memory-safe and blazingly fast. Let's rewrite everything in rust.
-
----
-// Test accessing the string itself.
-#show "hello": it => it.text.split("").map(upper).join("|")
-Oh, hello there!
-
----
-// Replace worlds but only in lists.
-#show list: it => [
- #show "World": [🌎]
- #it
-]
-
-World
-- World
-
----
-// Test absolute path in layout phase.
-
-#show "GRAPH": image("/assets/images/graph.png")
-
-The GRAPH has nodes.
diff --git a/tests/typ/compiler/spread.typ b/tests/typ/compiler/spread.typ
deleted file mode 100644
index db658453..00000000
--- a/tests/typ/compiler/spread.typ
+++ /dev/null
@@ -1,133 +0,0 @@
-// Test argument sinks and spreading.
-// Ref: false
-
----
-// Test standard argument overriding.
-#{
- let f(style: "normal", weight: "regular") = {
- "(style: " + style + ", weight: " + weight + ")"
- }
-
- let myf(..args) = f(weight: "bold", ..args)
- test(myf(), "(style: normal, weight: bold)")
- test(myf(weight: "black"), "(style: normal, weight: black)")
- test(myf(style: "italic"), "(style: italic, weight: bold)")
-}
-
----
-// Test multiple calls.
-#{
- let f(b, c: "!") = b + c
- let g(a, ..sink) = a + f(..sink)
- test(g("a", "b", c: "c"), "abc")
-}
-
----
-// Test doing things with arguments.
-#{
- let save(..args) = {
- test(type(args), arguments)
- test(repr(args), "(three: true, 1, 2)")
- }
-
- save(1, 2, three: true)
-}
-
----
-// Test spreading array and dictionary.
-#{
- let more = (3, -3, 6, 10)
- test(calc.min(1, 2, ..more), -3)
- test(calc.max(..more, 9), 10)
- test(calc.max(..more, 11), 11)
-}
-
-#{
- let more = (c: 3, d: 4)
- let tostr(..args) = repr(args)
- test(tostr(a: 1, ..more, b: 2), "(a: 1, c: 3, d: 4, b: 2)")
-}
-
----
-// None is spreadable.
-#let f() = none
-#f(..none)
-#f(..if false {})
-#f(..for x in () [])
-
----
-// unnamed spread
-#let f(.., a) = a
-#test(f(1, 2, 3), 3)
-
----
-// Error: 11-19 cannot spread string
-#calc.min(.."nope")
-
----
-// Error: 10-14 expected pattern, found boolean
-#let f(..true) = none
-
----
-// Error: 13-16 only one argument sink is allowed
-#let f(..a, ..b) = none
-
----
-// Test spreading into array and dictionary.
-#{
- let l = (1, 2, 3)
- let r = (5, 6, 7)
- test((..l, 4, ..r), range(1, 8))
- test((..none), ())
-}
-
-#{
- let x = (a: 1)
- let y = (b: 2)
- let z = (a: 3)
- test((:..x, ..y, ..z), (a: 3, b: 2))
- test((..(a: 1), b: 2), (a: 1, b: 2))
-}
-
----
-// Error: 9-17 cannot spread dictionary into array
-#(1, 2, ..(a: 1))
-
----
-// Error: 3-11 cannot spread array into dictionary
-#(..(1, 2), a: 1)
-
----
-// Spread at beginning.
-#{
- let f(..a, b) = (a, b)
- test(repr(f(1)), "((), 1)")
- test(repr(f(1, 2, 3)), "((1, 2), 3)")
- test(repr(f(1, 2, 3, 4, 5)), "((1, 2, 3, 4), 5)")
-}
-
----
-// Spread in the middle.
-#{
- let f(a, ..b, c) = (a, b, c)
- test(repr(f(1, 2)), "(1, (), 2)")
- test(repr(f(1, 2, 3, 4, 5)), "(1, (2, 3, 4), 5)")
-}
-
----
-// Unnamed sink should just ignore any extra arguments.
-#{
- let f(a, b: 5, ..) = (a, b)
- test(f(4), (4, 5))
- test(f(10, b: 11), (10, 11))
- test(f(13, 20, b: 12), (13, 12))
- test(f(15, b: 16, c: 13), (15, 16))
-}
-
----
-#{
- let f(..a, b, c, d) = none
-
- // Error: 3-10 missing argument: d
- f(1, 2)
-}
diff --git a/tests/typ/compiler/string.typ b/tests/typ/compiler/string.typ
deleted file mode 100644
index 949a2154..00000000
--- a/tests/typ/compiler/string.typ
+++ /dev/null
@@ -1,247 +0,0 @@
-// Test the string methods.
-// Ref: false
-
----
-// Test the `len` method.
-#test("Hello World!".len(), 12)
-
----
-// Test the `first` and `last` methods.
-#test("Hello".first(), "H")
-#test("Hello".last(), "o")
-#test("🏳️‍🌈A🏳️‍⚧️".first(), "🏳️‍🌈")
-#test("🏳️‍🌈A🏳️‍⚧️".last(), "🏳️‍⚧️")
-
----
-// Error: 2-12 string is empty
-#"".first()
-
----
-// Error: 2-11 string is empty
-#"".last()
-
----
-// Test the `at` method.
-#test("Hello".at(1), "e")
-#test("Hello".at(4), "o")
-#test("Hello".at(-1), "o")
-#test("Hello".at(-2), "l")
-#test("Hey: 🏳️‍🌈 there!".at(5), "🏳️‍🌈")
-
----
-// Test `at`'s 'default' parameter.
-#test("z", "Hello".at(5, default: "z"))
-
----
-// Error: 2-14 string index 2 is not a character boundary
-#"🏳️‍🌈".at(2)
-
----
-// Error: 2-15 no default value was specified and string index out of bounds (index: 5, len: 5)
-#"Hello".at(5)
-
----
-#test("Hello".at(5, default: (a: 10)), (a: 10))
-
----
-// Test the `slice` method.
-#test("abc".slice(1, 2), "b")
-#test("abc🏡def".slice(2, 7), "c🏡")
-#test("abc🏡def".slice(2, -2), "c🏡d")
-#test("abc🏡def".slice(-3, -1), "de")
-
----
-// Error: 2-21 string index -1 is not a character boundary
-#"🏳️‍🌈".slice(0, -1)
-
----
-// Test the `clusters` and `codepoints` methods.
-#test("abc".clusters(), ("a", "b", "c"))
-#test("abc".clusters(), ("a", "b", "c"))
-#test("🏳️‍🌈!".clusters(), ("🏳️‍🌈", "!"))
-#test("🏳️‍🌈!".codepoints(), ("🏳", "\u{fe0f}", "\u{200d}", "🌈", "!"))
-
----
-// Test the `contains` method.
-#test("abc".contains("b"), true)
-#test("b" in "abc", true)
-#test("1234f".contains(regex("\d")), true)
-#test(regex("\d") in "1234f", true)
-#test("abc".contains("d"), false)
-#test("1234g" in "1234f", false)
-#test("abc".contains(regex("^[abc]$")), false)
-#test("abc".contains(regex("^[abc]+$")), true)
-
----
-// Test the `starts-with` and `ends-with` methods.
-#test("Typst".starts-with("Ty"), true)
-#test("Typst".starts-with(regex("[Tt]ys")), false)
-#test("Typst".starts-with("st"), false)
-#test("Typst".ends-with("st"), true)
-#test("Typst".ends-with(regex("\d*")), true)
-#test("Typst".ends-with(regex("\d+")), false)
-#test("Typ12".ends-with(regex("\d+")), true)
-#test("typst13".ends-with(regex("1[0-9]")), true)
-#test("typst113".ends-with(regex("1[0-9]")), true)
-#test("typst23".ends-with(regex("1[0-9]")), false)
-
----
-// Test the `find` and `position` methods.
-#let date = regex("\d{2}:\d{2}")
-#test("Hello World".find("World"), "World")
-#test("Hello World".position("World"), 6)
-#test("It's 12:13 now".find(date), "12:13")
-#test("It's 12:13 now".position(date), 5)
-
----
-// Test the `match` method.
-#test("Is there a".match("for this?"), none)
-#test(
- "The time of my life.".match(regex("[mit]+e")),
- (start: 4, end: 8, text: "time", captures: ()),
-)
-
-// Test the `matches` method.
-#test("Hello there".matches("\d"), ())
-#test("Day by Day.".matches("Day"), (
- (start: 0, end: 3, text: "Day", captures: ()),
- (start: 7, end: 10, text: "Day", captures: ()),
-))
-
-// Compute the sum of all timestamps in the text.
-#let timesum(text) = {
- let time = 0
- for match in text.matches(regex("(\d+):(\d+)")) {
- let caps = match.captures
- time += 60 * int(caps.at(0)) + int(caps.at(1))
- }
- str(int(time / 60)) + ":" + str(calc.rem(time, 60))
-}
-
-#test(timesum(""), "0:0")
-#test(timesum("2:70"), "3:10")
-#test(timesum("1:20, 2:10, 0:40"), "4:10")
-
----
-// Test the `replace` method with `Str` replacements.
-#test("ABC".replace("", "-"), "-A-B-C-")
-#test("Ok".replace("Ok", "Nope", count: 0), "Ok")
-#test("to add?".replace("", "How ", count: 1), "How to add?")
-#test("AB C DEF GH J".replace(" ", ",", count: 2), "AB,C,DEF GH J")
-#test("Walcemo"
- .replace("o", "k")
- .replace("e", "o")
- .replace("k", "e")
- .replace("a", "e"),
- "Welcome"
-)
-#test("123".replace(regex("\d$"), "_"), "12_")
-#test("123".replace(regex("\d{1,2}$"), "__"), "1__")
-
----
-// Test the `replace` method with `Func` replacements.
-
-#test("abc".replace(regex("[a-z]"), m => {
- str(m.start) + m.text + str(m.end)
-}), "0a11b22c3")
-#test("abcd, efgh".replace(regex("\w+"), m => {
- upper(m.text)
-}), "ABCD, EFGH")
-#test("hello : world".replace(regex("^(.+)\s*(:)\s*(.+)$"), m => {
- upper(m.captures.at(0)) + m.captures.at(1) + " " + upper(m.captures.at(2))
-}), "HELLO : WORLD")
-#test("hello world, lorem ipsum".replace(regex("(\w+) (\w+)"), m => {
- m.captures.at(1) + " " + m.captures.at(0)
-}), "world hello, ipsum lorem")
-#test("hello world, lorem ipsum".replace(regex("(\w+) (\w+)"), count: 1, m => {
- m.captures.at(1) + " " + m.captures.at(0)
-}), "world hello, lorem ipsum")
-#test("123 456".replace(regex("[a-z]+"), "a"), "123 456")
-
-#test("abc".replace("", m => "-"), "-a-b-c-")
-#test("abc".replace("", m => "-", count: 1), "-abc")
-#test("123".replace("abc", m => ""), "123")
-#test("123".replace("abc", m => "", count: 2), "123")
-#test("a123b123c".replace("123", m => {
- str(m.start) + "-" + str(m.end)
-}), "a1-4b5-8c")
-#test("halla warld".replace("a", m => {
- if m.start == 1 { "e" }
- else if m.start == 4 or m.start == 7 { "o" }
-}), "hello world")
-#test("aaa".replace("a", m => str(m.captures.len())), "000")
-
----
-// Error: 23-24 expected string, found integer
-#"123".replace("123", m => 1)
-
----
-// Error: 23-32 expected string or function, found array
-#"123".replace("123", (1, 2, 3))
-
----
-// Test the `trim` method; the pattern is not provided.
-#let str = "Typst, LaTeX, Word, InDesign"
-#let array = ("Typst", "LaTeX", "Word", "InDesign")
-#test(str.split(",").map(s => s.trim()), array)
-#test("".trim(), "")
-#test(" ".trim(), "")
-#test("\t".trim(), "")
-#test("\n".trim(), "")
-#test("\t \n".trim(), "")
-#test(" abc ".trim(at: start), "abc ")
-#test("\tabc ".trim(at: start), "abc ")
-#test("abc\n".trim(at: end), "abc")
-#test(" abc ".trim(at: end, repeat: true), " abc")
-#test(" abc".trim(at: start, repeat: false), "abc")
-
----
-// Test the `trim` method; the pattern is a string.
-#test("aabcaa".trim("a", repeat: false), "abca")
-#test("aabca".trim("a", at: start), "bca")
-#test("aabcaa".trim("a", at: end, repeat: false), "aabca")
-#test(" abc\n".trim("\n"), " abc")
-#test("whole".trim("whole", at: start), "")
-
----
-// Test the `trim` method; the pattern is a regex.
-#test("".trim(regex(".")), "")
-#test("123abc456".trim(regex("\d")), "abc")
-#test("123abc456".trim(regex("\d"), repeat: false), "23abc45")
-#test("123a4b5c678".trim(regex("\d"), repeat: true), "a4b5c")
-#test("123a4b5c678".trim(regex("\d"), repeat: false), "23a4b5c67")
-#test("123abc456".trim(regex("\d"), at: start), "abc456")
-#test("123abc456".trim(regex("\d"), at: end), "123abc")
-#test("123abc456".trim(regex("\d+"), at: end, repeat: false), "123abc")
-#test("123abc456".trim(regex("\d{1,2}$"), repeat: false), "123abc4")
-#test("hello world".trim(regex(".")), "")
-#test("12306".trim(regex("\d"), at: start), "")
-#test("12306abc".trim(regex("\d"), at: start), "abc")
-#test("whole".trim(regex("whole"), at: start), "")
-#test("12306".trim(regex("\d"), at: end), "")
-#test("abc12306".trim(regex("\d"), at: end), "abc")
-#test("whole".trim(regex("whole"), at: end), "")
-
----
-// Error: 17-21 expected either `start` or `end`
-#"abc".trim(at: left)
-
----
-// Test the `split` method.
-#test("abc".split(""), ("", "a", "b", "c", ""))
-#test("abc".split("b"), ("a", "c"))
-#test("a123c".split(regex("\d")), ("a", "", "", "c"))
-#test("a123c".split(regex("\d+")), ("a", "c"))
-
----
-// Test the `rev` method.
-#test("abc".rev(), "cba")
-#test("ax̂e".rev(), "ex̂a")
-
----
-// Error: 12-15 unknown variable: arg
-#"abc".rev(arg)
-
----
-// Error: 2-2:1 unclosed string
-#"hello\"
diff --git a/tests/typ/compiler/type-compatibility.typ b/tests/typ/compiler/type-compatibility.typ
deleted file mode 100644
index 664981f4..00000000
--- a/tests/typ/compiler/type-compatibility.typ
+++ /dev/null
@@ -1,10 +0,0 @@
-// Test compatibility between types and strings.
-// Ref: false
-
----
-#test(type(10), int)
-#test(type(10), "integer")
-#test("is " + type(10), "is integer")
-#test(int in ("integer", "string"), true)
-#test(int in "integers or strings", true)
-#test(str in "integers or strings", true)
diff --git a/tests/typ/compiler/while.typ b/tests/typ/compiler/while.typ
deleted file mode 100644
index 56409c6e..00000000
--- a/tests/typ/compiler/while.typ
+++ /dev/null
@@ -1,60 +0,0 @@
-// Test while expressions.
-
----
-// Should output `2 4 6 8 10`.
-#let i = 0
-#while i < 10 [
- #(i += 2)
- #i
-]
-
-// Should output `Hi`.
-#let iter = true
-#while iter {
- iter = false
- "Hi."
-}
-
-#while false {
- dont-care
-}
-
----
-// Value of while loops.
-// Ref: false
-
-#test(while false {}, none)
-
-#let i = 0
-#test(type(while i < 1 [#(i += 1)]), content)
-
----
-// Condition must be boolean.
-// Error: 8-14 expected boolean, found content
-#while [nope] [nope]
-
----
-// Error: 8-25 condition is always true
-#while 2 < "hello".len() {}
-
----
-// Error: 2:2-2:24 loop seems to be infinite
-#let i = 1
-#while i > 0 { i += 1 }
-
----
-// Error: 7 expected expression
-#while
-
-// Error: 8 expected expression
-#{while}
-
-// Error: 9 expected block
-#while x
-
-// Error: 7 expected expression
-#while
-x {}
-
-// Error: 9 expected block
-#while x something
diff --git a/tests/typ/compute/calc.typ b/tests/typ/compute/calc.typ
deleted file mode 100644
index 94a13105..00000000
--- a/tests/typ/compute/calc.typ
+++ /dev/null
@@ -1,355 +0,0 @@
-// Test math functions.
-// Ref: false
-
----
-// Test conversion to numbers.
-#test(int(false), 0)
-#test(int(true), 1)
-#test(int(10), 10)
-#test(int("150"), 150)
-#test(int("-834"), -834)
-#test(int("\u{2212}79"), -79)
-#test(int(10 / 3), 3)
-#test(float(10), 10.0)
-#test(float(50% * 30%), 0.15)
-#test(float("31.4e-1"), 3.14)
-#test(float("31.4e\u{2212}1"), 3.14)
-#test(float("3.1415"), 3.1415)
-#test(float("-7654.321"), -7654.321)
-#test(float("\u{2212}7654.321"), -7654.321)
-#test(type(float(10)), float)
-
----
-// Test float `is-nan()`.
-#test(float(calc.nan).is-nan(), true)
-#test(float(10).is-nan(), false)
-
----
-// Test float `is-infinite()`.
-#test(float(calc.inf).is-infinite(), true)
-#test(float(-calc.inf).is-infinite(), true)
-#test(float(10).is-infinite(), false)
-#test(float(-10).is-infinite(), false)
-
----
-// Test float `signum()`
-#test(float(0.0).signum(), 1.0)
-#test(float(1.0).signum(), 1.0)
-#test(float(-1.0).signum(), -1.0)
-#test(float(10.0).signum(), 1.0)
-#test(float(-10.0).signum(), -1.0)
-#test(float(calc.nan).signum().is-nan(), true)
-
----
-// Test int `signum()`
-#test(int(0).signum(), 0)
-#test(int(1.0).signum(), 1)
-#test(int(-1.0).signum(), -1)
-#test(int(10.0).signum(), 1)
-#test(int(-10.0).signum(), -1)
-
----
-#test(calc.round(calc.e, digits: 2), 2.72)
-#test(calc.round(calc.pi, digits: 2), 3.14)
-
----
-// Error: 6-10 expected integer, boolean, float, or string, found length
-#int(10pt)
-
----
-// Error: 8-13 expected float, boolean, integer, ratio, or string, found type
-#float(float)
-
----
-// Error: 6-12 invalid integer: nope
-#int("nope")
-
----
-// Error: 8-15 invalid float: 1.2.3
-#float("1.2.3")
-
----
-// Test the `abs` function.
-#test(calc.abs(-3), 3)
-#test(calc.abs(3), 3)
-#test(calc.abs(-0.0), 0.0)
-#test(calc.abs(0.0), -0.0)
-#test(calc.abs(-3.14), 3.14)
-#test(calc.abs(50%), 50%)
-#test(calc.abs(-25%), 25%)
-
----
-// Error: 11-22 expected integer, float, length, angle, ratio, or fraction, found string
-#calc.abs("no number")
-
----
-// Test the `even` and `odd` functions.
-#test(calc.even(2), true)
-#test(calc.odd(2), false)
-#test(calc.odd(-1), true)
-#test(calc.even(-11), false)
-
----
-// Test the `rem` function.
-#test(calc.rem(1, 1), 0)
-#test(calc.rem(5, 3), 2)
-#test(calc.rem(5, -3), 2)
-#test(calc.rem(22.5, 10), 2.5)
-#test(calc.rem(9, 4.5), 0)
-
----
-// Error: 14-15 divisor must not be zero
-#calc.rem(5, 0)
-
----
-// Error: 16-19 divisor must not be zero
-#calc.rem(3.0, 0.0)
-
----
-// Test the `div-euclid` function.
-#test(calc.div-euclid(7, 3), 2)
-#test(calc.div-euclid(7, -3), -2)
-#test(calc.div-euclid(-7, 3), -3)
-#test(calc.div-euclid(-7, -3), 3)
-#test(calc.div-euclid(2.5, 2), 1)
-
----
-// Error: 21-22 divisor must not be zero
-#calc.div-euclid(5, 0)
-
----
-// Error: 23-26 divisor must not be zero
-#calc.div-euclid(3.0, 0.0)
-
----
-// Test the `rem-euclid` function.
-#test(calc.rem-euclid(7, 3), 1)
-#test(calc.rem-euclid(7, -3), 1)
-#test(calc.rem-euclid(-7, 3), 2)
-#test(calc.rem-euclid(-7, -3), 2)
-#test(calc.rem-euclid(2.5, 2), 0.5)
-
----
-// Error: 21-22 divisor must not be zero
-#calc.rem-euclid(5, 0)
-
----
-// Error: 23-26 divisor must not be zero
-#calc.rem-euclid(3.0, 0.0)
-
----
-// Test the `quo` function.
-#test(calc.quo(1, 1), 1)
-#test(calc.quo(5, 3), 1)
-#test(calc.quo(5, -3), -1)
-#test(calc.quo(22.5, 10), 2)
-#test(calc.quo(9, 4.5), 2)
-
----
-// Error: 14-15 divisor must not be zero
-#calc.quo(5, 0)
-
----
-// Error: 16-19 divisor must not be zero
-#calc.quo(3.0, 0.0)
-
----
-// Test the `min` and `max` functions.
-#test(calc.min(2, -4), -4)
-#test(calc.min(3.5, 1e2, -0.1, 3), -0.1)
-#test(calc.max(-3, 11), 11)
-#test(calc.min("hi"), "hi")
-
----
-// Test the `pow`, `log`, `exp`, and `ln` functions.
-#test(calc.pow(10, 0), 1)
-#test(calc.pow(2, 4), 16)
-#test(calc.exp(2), calc.pow(calc.e, 2))
-#test(calc.ln(10), calc.log(10, base: calc.e))
-
----
-// Test the `bit-not`, `bit-and`, `bit-or` and `bit-xor` functions.
-#test(64.bit-not(), -65)
-#test(0.bit-not(), -1)
-#test((-56).bit-not(), 55)
-#test(128.bit-and(192), 128)
-#test(192.bit-and(224), 192)
-#test((-1).bit-and(325532), 325532)
-#test(0.bit-and(-53), 0)
-#test(0.bit-or(-1), -1)
-#test(5.bit-or(3), 7)
-#test((-50).bit-or(3), -49)
-#test(64.bit-or(32), 96)
-#test((-1).bit-xor(1), -2)
-#test(64.bit-xor(96), 32)
-#test((-1).bit-xor(-7), 6)
-#test(0.bit-xor(492), 492)
-
----
-// Test the `bit-lshift` and `bit-rshift` functions.
-#test(32.bit-lshift(2), 128)
-#test(694.bit-lshift(0), 694)
-#test(128.bit-rshift(2), 32)
-#test(128.bit-rshift(12345), 0)
-#test((-7).bit-rshift(2), -2)
-#test((-7).bit-rshift(12345), -1)
-#test(128.bit-rshift(2, logical: true), 32)
-#test((-7).bit-rshift(61, logical: true), 7)
-#test(128.bit-rshift(12345, logical: true), 0)
-#test((-7).bit-rshift(12345, logical: true), 0)
-
----
-// Error: 2-18 the result is too large
-#1.bit-lshift(64)
-
----
-// Error: 15-17 number must be at least zero
-#1.bit-lshift(-1)
-
----
-// Error: 15-17 number must be at least zero
-#1.bit-rshift(-1)
-
----
-// Error: 2-16 zero to the power of zero is undefined
-#calc.pow(0, 0)
-
----
-// Error: 14-31 exponent is too large
-#calc.pow(2, 10000000000000000)
-
----
-// Error: 2-25 the result is too large
-#calc.pow(2, 2147483647)
-
----
-// Error: 14-36 exponent may not be infinite, subnormal, or NaN
-#calc.pow(2, calc.pow(2.0, 10000.0))
-
----
-// Error: 2-19 the result is not a real number
-#calc.pow(-1, 0.5)
-
----
-// Error: 12-14 cannot take square root of negative number
-#calc.sqrt(-1)
-
----
-#test(calc.root(12.0, 1), 12.0)
-#test(calc.root(9.0, 2), 3.0)
-#test(calc.root(27.0, 3), 3.0)
-#test(calc.root(-27.0, 3), -3.0)
-// 100^(-1/2) = (100^(1/2))^-1 = 1/sqrt(100)
-#test(calc.root(100.0, -2), 0.1)
-
----
-// Error: 17-18 cannot take the 0th root of a number
-#calc.root(1.0, 0)
-
----
-// Error: 24-25 negative numbers do not have a real nth root when n is even
-#test(calc.root(-27.0, 4), -3.0)
-
----
-// Error: 11-13 value must be strictly positive
-#calc.log(-1)
-
----
-// Error: 20-21 base may not be zero, NaN, infinite, or subnormal
-#calc.log(1, base: 0)
-
----
-// Error: 2-24 the result is not a real number
-#calc.log(10, base: -1)
-
----
-// Test the `fact` function.
-#test(calc.fact(0), 1)
-#test(calc.fact(5), 120)
-
----
-// Error: 2-15 the result is too large
-#calc.fact(21)
-
----
-// Test the `perm` function.
-#test(calc.perm(0, 0), 1)
-#test(calc.perm(5, 3), 60)
-#test(calc.perm(5, 5), 120)
-#test(calc.perm(5, 6), 0)
-
----
-// Error: 2-19 the result is too large
-#calc.perm(21, 21)
-
----
-// Test the `binom` function.
-#test(calc.binom(0, 0), 1)
-#test(calc.binom(5, 3), 10)
-#test(calc.binom(5, 5), 1)
-#test(calc.binom(5, 6), 0)
-#test(calc.binom(6, 2), 15)
-
----
-// Test the `gcd` function.
-#test(calc.gcd(112, 77), 7)
-#test(calc.gcd(12, 96), 12)
-#test(calc.gcd(13, 9), 1)
-#test(calc.gcd(13, -9), 1)
-#test(calc.gcd(272557, 272557), 272557)
-#test(calc.gcd(0, 0), 0)
-#test(calc.gcd(7, 0), 7)
-
----
-// Test the `lcm` function.
-#test(calc.lcm(112, 77), 1232)
-#test(calc.lcm(12, 96), 96)
-#test(calc.lcm(13, 9), 117)
-#test(calc.lcm(13, -9), 117)
-#test(calc.lcm(272557, 272557), 272557)
-#test(calc.lcm(0, 0), 0)
-#test(calc.lcm(8, 0), 0)
-
----
-// Error: 2-41 the result is too large
-#calc.lcm(15486487489457, 4874879896543)
-
----
-// Error: 2-12 expected at least one value
-#calc.min()
-
----
-// Error: 14-18 cannot compare string and integer
-#calc.min(1, "hi")
-
----
-// Error: 16-19 cannot compare 1pt with 1em
-#calc.max(1em, 1pt)
-
----
-// Test the `range` function.
-#test(range(4), (0, 1, 2, 3))
-#test(range(1, 4), (1, 2, 3))
-#test(range(-4, 2), (-4, -3, -2, -1, 0, 1))
-#test(range(10, 5), ())
-#test(range(10, step: 3), (0, 3, 6, 9))
-#test(range(1, 4, step: 1), (1, 2, 3))
-#test(range(1, 8, step: 2), (1, 3, 5, 7))
-#test(range(5, 2, step: -1), (5, 4, 3))
-#test(range(10, 0, step: -3), (10, 7, 4, 1))
-
----
-// Error: 2-9 missing argument: end
-#range()
-
----
-// Error: 11-14 expected integer, found float
-#range(1, 2.0)
-
----
-// Error: 17-22 expected integer, found string
-#range(4, step: "one")
-
----
-// Error: 18-19 number must not be zero
-#range(10, step: 0)
diff --git a/tests/typ/compute/construct.typ b/tests/typ/compute/construct.typ
deleted file mode 100644
index e429efc5..00000000
--- a/tests/typ/compute/construct.typ
+++ /dev/null
@@ -1,316 +0,0 @@
-// Test creation and conversion functions.
-// Ref: false
-
----
-// Compare both ways.
-#test-repr(rgb(0%, 30.2%, 70.2%), rgb("004db3"))
-
-// Alpha channel.
-#test(rgb(255, 0, 0, 50%), rgb("ff000080"))
-
-// Test color modification methods.
-#test(rgb(25, 35, 45).lighten(10%), rgb(48, 57, 66))
-#test(rgb(40, 30, 20).darken(10%), rgb(36, 27, 18))
-#test(rgb("#133337").negate(space: rgb), rgb(236, 204, 200))
-#test(white.lighten(100%), white)
-
-// Color mixing, in Oklab space by default.
-#test(rgb(color.mix(rgb("#ff0000"), rgb("#00ff00"))), rgb("#d0a800"))
-#test(rgb(color.mix(rgb("#ff0000"), rgb("#00ff00"), space: oklab)), rgb("#d0a800"))
-#test(rgb(color.mix(rgb("#ff0000"), rgb("#00ff00"), space: rgb)), rgb("#808000"))
-
-#test(rgb(color.mix(red, green, blue)), rgb("#909282"))
-#test(rgb(color.mix(red, blue, green)), rgb("#909282"))
-#test(rgb(color.mix(blue, red, green)), rgb("#909282"))
-
-// Mix with weights.
-#test(rgb(color.mix((red, 50%), (green, 50%))), rgb("#c0983b"))
-#test(rgb(color.mix((red, 0.5), (green, 0.5))), rgb("#c0983b"))
-#test(rgb(color.mix((red, 5), (green, 5))), rgb("#c0983b"))
-#test(rgb(color.mix((green, 5), (white, 0), (red, 5))), rgb("#c0983b"))
-#test(color.mix((rgb("#aaff00"), 25%), (rgb("#aa00ff"), 75%), space: rgb), rgb("#aa40bf"))
-#test(color.mix((rgb("#aaff00"), 50%), (rgb("#aa00ff"), 50%), space: rgb), rgb("#aa8080"))
-#test(color.mix((rgb("#aaff00"), 75%), (rgb("#aa00ff"), 25%), space: rgb), rgb("#aabf40"))
-
-// Mix in hue-based space.
-#test(rgb(color.mix(red, blue, space: color.hsl)), rgb("#c408ff"))
-#test(rgb(color.mix((red, 50%), (blue, 100%), space: color.hsl)), rgb("#5100f8"))
-// Error: 6-51 cannot mix more than two colors in a hue-based space
-#rgb(color.mix(red, blue, white, space: color.hsl))
-
----
-// Test color conversion method kinds
-#test(rgb(rgb(10, 20, 30)).space(), rgb)
-#test(color.linear-rgb(rgb(10, 20, 30)).space(), color.linear-rgb)
-#test(oklab(rgb(10, 20, 30)).space(), oklab)
-#test(oklch(rgb(10, 20, 30)).space(), oklch)
-#test(color.hsl(rgb(10, 20, 30)).space(), color.hsl)
-#test(color.hsv(rgb(10, 20, 30)).space(), color.hsv)
-#test(cmyk(rgb(10, 20, 30)).space(), cmyk)
-#test(luma(rgb(10, 20, 30)).space(), luma)
-
-#test(rgb(color.linear-rgb(10, 20, 30)).space(), rgb)
-#test(color.linear-rgb(color.linear-rgb(10, 20, 30)).space(), color.linear-rgb)
-#test(oklab(color.linear-rgb(10, 20, 30)).space(), oklab)
-#test(oklch(color.linear-rgb(10, 20, 30)).space(), oklch)
-#test(color.hsl(color.linear-rgb(10, 20, 30)).space(), color.hsl)
-#test(color.hsv(color.linear-rgb(10, 20, 30)).space(), color.hsv)
-#test(cmyk(color.linear-rgb(10, 20, 30)).space(), cmyk)
-#test(luma(color.linear-rgb(10, 20, 30)).space(), luma)
-
-#test(rgb(oklab(10%, 20%, 30%)).space(), rgb)
-#test(color.linear-rgb(oklab(10%, 20%, 30%)).space(), color.linear-rgb)
-#test(oklab(oklab(10%, 20%, 30%)).space(), oklab)
-#test(oklch(oklab(10%, 20%, 30%)).space(), oklch)
-#test(color.hsl(oklab(10%, 20%, 30%)).space(), color.hsl)
-#test(color.hsv(oklab(10%, 20%, 30%)).space(), color.hsv)
-#test(cmyk(oklab(10%, 20%, 30%)).space(), cmyk)
-#test(luma(oklab(10%, 20%, 30%)).space(), luma)
-
-#test(rgb(oklch(60%, 40%, 0deg)).space(), rgb)
-#test(color.linear-rgb(oklch(60%, 40%, 0deg)).space(), color.linear-rgb)
-#test(oklab(oklch(60%, 40%, 0deg)).space(), oklab)
-#test(oklch(oklch(60%, 40%, 0deg)).space(), oklch)
-#test(color.hsl(oklch(60%, 40%, 0deg)).space(), color.hsl)
-#test(color.hsv(oklch(60%, 40%, 0deg)).space(), color.hsv)
-#test(cmyk(oklch(60%, 40%, 0deg)).space(), cmyk)
-#test(luma(oklch(60%, 40%, 0deg)).space(), luma)
-
-#test(rgb(color.hsl(10deg, 20%, 30%)).space(), rgb)
-#test(color.linear-rgb(color.hsl(10deg, 20%, 30%)).space(), color.linear-rgb)
-#test(oklab(color.hsl(10deg, 20%, 30%)).space(), oklab)
-#test(oklch(color.hsl(10deg, 20%, 30%)).space(), oklch)
-#test(color.hsl(color.hsl(10deg, 20%, 30%)).space(), color.hsl)
-#test(color.hsv(color.hsl(10deg, 20%, 30%)).space(), color.hsv)
-#test(cmyk(color.hsl(10deg, 20%, 30%)).space(), cmyk)
-#test(luma(color.hsl(10deg, 20%, 30%)).space(), luma)
-
-#test(rgb(color.hsv(10deg, 20%, 30%)).space(), rgb)
-#test(color.linear-rgb(color.hsv(10deg, 20%, 30%)).space(), color.linear-rgb)
-#test(oklab(color.hsv(10deg, 20%, 30%)).space(), oklab)
-#test(oklch(color.hsv(10deg, 20%, 30%)).space(), oklch)
-#test(color.hsl(color.hsv(10deg, 20%, 30%)).space(), color.hsl)
-#test(color.hsv(color.hsv(10deg, 20%, 30%)).space(), color.hsv)
-#test(cmyk(color.hsv(10deg, 20%, 30%)).space(), cmyk)
-#test(luma(color.hsv(10deg, 20%, 30%)).space(), luma)
-
-#test(rgb(cmyk(10%, 20%, 30%, 40%)).space(), rgb)
-#test(color.linear-rgb(cmyk(10%, 20%, 30%, 40%)).space(), color.linear-rgb)
-#test(oklab(cmyk(10%, 20%, 30%, 40%)).space(), oklab)
-#test(oklch(cmyk(10%, 20%, 30%, 40%)).space(), oklch)
-#test(color.hsl(cmyk(10%, 20%, 30%, 40%)).space(), color.hsl)
-#test(color.hsv(cmyk(10%, 20%, 30%, 40%)).space(), color.hsv)
-#test(cmyk(cmyk(10%, 20%, 30%, 40%)).space(), cmyk)
-#test(luma(cmyk(10%, 20%, 30%, 40%)).space(), luma)
-
-#test(rgb(luma(10%)).space(), rgb)
-#test(color.linear-rgb(luma(10%)).space(), color.linear-rgb)
-#test(oklab(luma(10%)).space(), oklab)
-#test(oklch(luma(10%)).space(), oklch)
-#test(color.hsl(luma(10%)).space(), color.hsl)
-#test(color.hsv(luma(10%)).space(), color.hsv)
-#test(cmyk(luma(10%)).space(), cmyk)
-#test(luma(luma(10%)).space(), luma)
-
----
-// Test gray color conversion.
-// Ref: true
-#stack(dir: ltr, rect(fill: luma(0)), rect(fill: luma(80%)))
-
----
-// Error for values that are out of range.
-// Error: 11-14 number must be between 0 and 255
-#test(rgb(-30, 15, 50))
-
----
-// Error: 6-11 color string contains non-hexadecimal letters
-#rgb("lol")
-
----
-// Error: 2-7 missing argument: red component
-#rgb()
-
----
-// Error: 2-11 missing argument: blue component
-#rgb(0, 1)
-
----
-// Error: 21-26 expected integer or ratio, found boolean
-#rgb(10%, 20%, 30%, false)
-
----
-// Error: 10-20 unexpected argument: key
-#luma(1, key: "val")
-
----
-// Error: 12-24 expected float or ratio, found string
-// Error: 26-39 expected float or ratio, found string
-#color.mix((red, "yes"), (green, "no"), (green, 10%))
-
----
-// Error: 12-23 expected a color or color-weight pair
-#color.mix((red, 1, 2))
-
----
-// Error: 31-38 expected `rgb`, `luma`, `cmyk`, `oklab`, `oklch`, `color.linear-rgb`, `color.hsl`, or `color.hsv`, found string
-#color.mix(red, green, space: "cyber")
-
----
-// Error: 31-36 expected `rgb`, `luma`, `cmyk`, `oklab`, `oklch`, `color.linear-rgb`, `color.hsl`, or `color.hsv`
-#color.mix(red, green, space: image)
-
----
-// Error: 31-41 expected `rgb`, `luma`, `cmyk`, `oklab`, `oklch`, `color.linear-rgb`, `color.hsl`, or `color.hsv`
-#color.mix(red, green, space: calc.round)
-
----
-// Ref: true
-#let envelope = symbol(
- "🖂",
- ("stamped", "🖃"),
- ("stamped.pen", "🖆"),
- ("lightning", "🖄"),
- ("fly", "🖅"),
-)
-
-#envelope
-#envelope.stamped
-#envelope.pen
-#envelope.stamped.pen
-#envelope.lightning
-#envelope.fly
-
----
-// Error: 2-10 expected at least one variant
-#symbol()
-
----
-// Test conversion to string.
-#test(str(123), "123")
-#test(str(123, base: 3), "11120")
-#test(str(-123, base: 16), "−7b")
-#test(str(9223372036854775807, base: 36), "1y2p0ij32e8e7")
-#test(str(50.14), "50.14")
-#test(str(10 / 3).len() > 10, true)
-
----
-// Error: 6-8 expected integer, float, version, bytes, label, type, or string, found content
-#str([])
-
----
-// Error: 17-19 base must be between 2 and 36
-#str(123, base: 99)
-
----
-// Error: 18-19 base is only supported for integers
-#str(1.23, base: 2)
-
----
-// Test the unicode function.
-#test(str.from-unicode(97), "a")
-#test(str.to-unicode("a"), 97)
-
----
-// Error: 19-22 expected integer, found content
-#str.from-unicode([a])
-
----
-// Error: 17-21 expected exactly one character
-#str.to-unicode("ab")
-
----
-// Error: 19-21 number must be at least zero
-#str.from-unicode(-1)
-
----
-// Error: 2-28 0x110000 is not a valid codepoint
-#str.from-unicode(0x110000) // 0x10ffff is the highest valid code point
-
----
-#assert(range(2, 5) == (2, 3, 4))
-
----
-// Test displaying of dates.
-#test(datetime(year: 2023, month: 4, day: 29).display(), "2023-04-29")
-#test(datetime(year: 2023, month: 4, day: 29).display("[year]"), "2023")
-#test(
- datetime(year: 2023, month: 4, day: 29)
- .display("[year repr:last_two]"),
- "23",
-)
-#test(
- datetime(year: 2023, month: 4, day: 29)
- .display("[year] [month repr:long] [day] [week_number] [weekday]"),
- "2023 April 29 17 Saturday",
-)
-
-// Test displaying of times
-#test(datetime(hour: 14, minute: 26, second: 50).display(), "14:26:50")
-#test(datetime(hour: 14, minute: 26, second: 50).display("[hour]"), "14")
-#test(
- datetime(hour: 14, minute: 26, second: 50)
- .display("[hour repr:12 padding:none]"),
- "2",
-)
-#test(
- datetime(hour: 14, minute: 26, second: 50)
- .display("[hour], [minute], [second]"), "14, 26, 50",
-)
-
-// Test displaying of datetimes
-#test(
- datetime(year: 2023, month: 4, day: 29, hour: 14, minute: 26, second: 50).display(),
- "2023-04-29 14:26:50",
-)
-
-// Test getting the year/month/day etc. of a datetime
-#let d = datetime(year: 2023, month: 4, day: 29, hour: 14, minute: 26, second: 50)
-#test(d.year(), 2023)
-#test(d.month(), 4)
-#test(d.weekday(), 6)
-#test(d.day(), 29)
-#test(d.hour(), 14)
-#test(d.minute(), 26)
-#test(d.second(), 50)
-
-#let e = datetime(year: 2023, month: 4, day: 29)
-#test(e.hour(), none)
-#test(e.minute(), none)
-#test(e.second(), none)
-
-// Test today
-#test(datetime.today().display(), "1970-01-01")
-#test(datetime.today(offset: auto).display(), "1970-01-01")
-#test(datetime.today(offset: 2).display(), "1970-01-01")
-
----
-// Error: 2-12 at least one of date or time must be fully specified
-#datetime()
-
----
-// Error: 2-42 time is invalid
-#datetime(hour: 25, minute: 0, second: 0)
-
----
-// Error: 2-41 date is invalid
-#datetime(year: 2000, month: 2, day: 30)
-
----
-// Error: 27-34 missing closing bracket for bracket at index 0
-#datetime.today().display("[year")
-
----
-// Error: 27-38 invalid component name 'nothing' at index 1
-#datetime.today().display("[nothing]")
-
----
-// Error: 27-50 invalid modifier 'wrong' at index 6
-#datetime.today().display("[year wrong:last_two]")
-
----
-// Error: 27-33 expected component name at index 2
-#datetime.today().display(" []")
-
----
-// Error: 2-36 failed to format datetime (insufficient information)
-#datetime.today().display("[hour]")
diff --git a/tests/typ/compute/data.typ b/tests/typ/compute/data.typ
deleted file mode 100644
index 03b17aed..00000000
--- a/tests/typ/compute/data.typ
+++ /dev/null
@@ -1,144 +0,0 @@
-// Test reading structured data and files.
-// Ref: false
-
----
-// Test reading plain text files
-#let data = read("/assets/text/hello.txt")
-#test(data, "Hello, world!\n")
-
----
-// Error: 18-44 file not found (searched at assets/text/missing.txt)
-#let data = read("/assets/text/missing.txt")
-
----
-// Error: 18-40 file is not valid utf-8
-#let data = read("/assets/text/bad.txt")
-
----
-// Test reading CSV data.
-// Ref: true
-#set page(width: auto)
-#let data = csv("/assets/data/zoo.csv")
-#let cells = data.at(0).map(strong) + data.slice(1).flatten()
-#table(columns: data.at(0).len(), ..cells)
-
----
-// Test reading CSV data with dictionary rows enabled.
-#let data = csv("/assets/data/zoo.csv", row-type: dictionary)
-#test(data.len(), 3)
-#test(data.at(0).Name, "Debby")
-#test(data.at(2).Weight, "150kg")
-#test(data.at(1).Species, "Tiger")
-
----
-// Error: 6-16 file not found (searched at typ/compute/nope.csv)
-#csv("nope.csv")
-
----
-// Error: 6-28 failed to parse CSV (found 3 instead of 2 fields in line 3)
-#csv("/assets/data/bad.csv")
-
----
-// Test error numbering with dictionary rows.
-// Error: 6-28 failed to parse CSV (found 3 instead of 2 fields in line 3)
-#csv("/assets/data/bad.csv", row-type: dictionary)
-
----
-// Test reading JSON data.
-#let data = json("/assets/data/zoo.json")
-#test(data.len(), 3)
-#test(data.at(0).name, "Debby")
-#test(data.at(2).weight, 150)
-
----
-// Error: 7-30 failed to parse JSON (expected value at line 3 column 14)
-#json("/assets/data/bad.json")
-
----
-// Test reading TOML data.
-#let data = toml("/assets/data/toml-types.toml")
-#test(data.string, "wonderful")
-#test(data.integer, 42)
-#test(data.float, 3.14)
-#test(data.boolean, true)
-#test(data.array, (1, "string", 3.0, false))
-#test(data.inline_table, ("first": "amazing", "second": "greater") )
-#test(data.table.element, 5)
-#test(data.table.others, (false, "indeed", 7))
-#test(data.date_time, datetime(
- year: 2023,
- month: 2,
- day: 1,
- hour: 15,
- minute: 38,
- second: 57,
-))
-#test(data.date_time2, datetime(
- year: 2023,
- month: 2,
- day: 1,
- hour: 15,
- minute: 38,
- second: 57,
-))
-#test(data.date, datetime(
- year: 2023,
- month: 2,
- day: 1,
-))
-#test(data.time, datetime(
- hour: 15,
- minute: 38,
- second: 57,
-))
-
----
-// Error: 7-30 failed to parse TOML (expected `.`, `=` at line 1 column 16)
-#toml("/assets/data/bad.toml")
-
----
-// Test reading YAML data
-#let data = yaml("/assets/data/yaml-types.yaml")
-#test(data.len(), 9)
-#test(data.null_key, (none, none))
-#test(data.string, "text")
-#test(data.integer, 5)
-#test(data.float, 1.12)
-#test(data.mapping, ("1": "one", "2": "two"))
-#test(data.seq, (1,2,3,4))
-#test(data.bool, false)
-#test(data.keys().contains("true"), true)
-#test(data.at("1"), "ok")
-
----
-// Error: 7-30 failed to parse YAML (did not find expected ',' or ']' at line 2 column 1, while parsing a flow sequence at line 1 column 18)
-#yaml("/assets/data/bad.yaml")
-
----
-// Test reading XML data.
-#let data = xml("/assets/data/hello.xml")
-#test(data, ((
- tag: "data",
- attrs: (:),
- children: (
- "\n ",
- (tag: "hello", attrs: (name: "hi"), children: ("1",)),
- "\n ",
- (
- tag: "data",
- attrs: (:),
- children: (
- "\n ",
- (tag: "hello", attrs: (:), children: ("World",)),
- "\n ",
- (tag: "hello", attrs: (:), children: ("World",)),
- "\n ",
- ),
- ),
- "\n",
- ),
-),))
-
----
-// Error: 6-28 failed to parse XML (found closing tag 'data' instead of 'hello' in line 3)
-#xml("/assets/data/bad.xml")
diff --git a/tests/typ/compute/eval-path.typ b/tests/typ/compute/eval-path.typ
deleted file mode 100644
index 863b6203..00000000
--- a/tests/typ/compute/eval-path.typ
+++ /dev/null
@@ -1,18 +0,0 @@
-// Test file loading in eval.
-
----
-// Test absolute path.
-#eval("image(\"/assets/images/tiger.jpg\", width: 50%)")
-
----
-#show raw: it => eval(it.text, mode: "markup")
-
-```
-#show emph: image("/assets/images/tiger.jpg", width: 50%)
-_Tiger!_
-```
-
----
-// Test relative path.
-// Ref: false
-#test(eval(`"HELLO" in read("./eval-path.typ")`.text), true)
diff --git a/tests/typ/compute/foundations.typ b/tests/typ/compute/foundations.typ
deleted file mode 100644
index eb50c072..00000000
--- a/tests/typ/compute/foundations.typ
+++ /dev/null
@@ -1,107 +0,0 @@
-// Test foundational functions.
-// Ref: false
-
----
-#test(type(1), int)
-#test(type(ltr), direction)
-#test(type(10 / 3), float)
-
----
-#test(repr(ltr), "ltr")
-#test(repr((1, 2, false, )), "(1, 2, false)")
-
----
-// Test panic.
-// Error: 2-9 panicked
-#panic()
-
----
-// Test panic.
-// Error: 2-12 panicked with: 123
-#panic(123)
-
----
-// Test panic.
-// Error: 2-24 panicked with: "this is wrong"
-#panic("this is wrong")
-
----
-// Test failing assertions.
-// Error: 2-16 assertion failed
-#assert(1 == 2)
-
----
-// Test failing assertions.
-// Error: 2-51 assertion failed: two is smaller than one
-#assert(2 < 1, message: "two is smaller than one")
-
----
-// Test failing assertions.
-// Error: 9-15 expected boolean, found string
-#assert("true")
-
----
-// Test failing assertions.
-// Error: 2-19 equality assertion failed: value 10 was not equal to 11
-#assert.eq(10, 11)
-
----
-// Test failing assertions.
-// Error: 2-55 equality assertion failed: 10 and 12 are not equal
-#assert.eq(10, 12, message: "10 and 12 are not equal")
-
----
-// Test failing assertions.
-// Error: 2-19 inequality assertion failed: value 11 was equal to 11
-#assert.ne(11, 11)
-
----
-// Test failing assertions.
-// Error: 2-57 inequality assertion failed: must be different from 11
-#assert.ne(11, 11, message: "must be different from 11")
-
----
-// Test successful assertions.
-#assert(5 > 3)
-#assert.eq(15, 15)
-#assert.ne(10, 12)
-
----
-// Test the `type` function.
-#test(type(1), int)
-#test(type(ltr), direction)
-#test(type(10 / 3), float)
-
----
-// Test the eval function.
-#test(eval("1 + 2"), 3)
-#test(eval("1 + x", scope: (x: 3)), 4)
-#test(eval("let x = x + 1; x + 1", scope: (x: 1)), 3)
-
----
-// Test evaluation in other modes.
-// Ref: true
-#eval("[_Hello" + " World!_]") \
-#eval("_Hello" + " World!_", mode: "markup") \
-#eval("RR_1^NN", mode: "math", scope: (RR: math.NN, NN: math.RR))
-
----
-// Error: 7-12 expected pattern
-#eval("let")
-
----
-#show raw: it => text(font: "PT Sans", eval("[" + it.text + "]"))
-
-Interacting
-```
-#set text(blue)
-Blue #move(dy: -0.15em)[🌊]
-```
-
----
-// Error: 7-17 cannot continue outside of loop
-#eval("continue")
-
----
-// Error: 7-12 expected semicolon or line break
-#eval("1 2")
diff --git a/tests/typ/compute/version.typ b/tests/typ/compute/version.typ
deleted file mode 100644
index e33eeb6f..00000000
--- a/tests/typ/compute/version.typ
+++ /dev/null
@@ -1,47 +0,0 @@
-// Test versions.
-// Ref: false
-
----
-// Test version constructor.
-
-// Empty.
-#version()
-
-// Plain.
-#version(1, 2)
-
-// Single Array argument.
-#version((1, 2))
-
-// Mixed arguments.
-#version(1, (2, 3), 4, (5, 6), 7)
-
----
-// Test equality of different-length versions
-#test(version(), version(0))
-#test(version(0), version(0, 0))
-#test(version(1, 2), version(1, 2, 0, 0, 0, 0))
----
-// Test `version.at`.
-
-// Non-negative index in bounds
-#test(version(1, 2).at(1), 2)
-
-// Non-negative index out of bounds
-#test(version(1, 2).at(4), 0)
-
-// Negative index in bounds
-#test(version(1, 2).at(-2), 1)
-
-// Error: 2-22 component index out of bounds (index: -3, len: 2)
-#version(1, 2).at(-3)
-
----
-// Test version fields.
-#test(version(1, 2, 3).major, 1)
-#test(version(1, 2, 3).minor, 2)
-#test(version(1, 2, 3).patch, 3)
-
----
-// Test the type of `sys.version`
-#test(type(sys.version), version)
diff --git a/tests/typ/layout/align.typ b/tests/typ/layout/align.typ
deleted file mode 100644
index 98ee8da5..00000000
--- a/tests/typ/layout/align.typ
+++ /dev/null
@@ -1,52 +0,0 @@
-// Test alignment.
-
----
-#set page(height: 100pt)
-#stack(dir: ltr,
- align(left, square(size: 15pt, fill: eastern)),
- align(center, square(size: 20pt, fill: eastern)),
- align(right, square(size: 15pt, fill: eastern)),
-)
-#align(center + horizon, rect(fill: eastern, height: 10pt))
-#align(bottom, stack(
- align(center, rect(fill: conifer, height: 10pt)),
- rect(fill: forest, height: 10pt, width: 100%),
-))
-
----
-// Test that multiple paragraphs in subflow also respect alignment.
-#align(center)[
- Lorem Ipsum
-
- Dolor
-]
-
----
-// Test start and end alignment.
-#rotate(-30deg, origin: end + horizon)[Hello]
-
-#set text(lang: "de")
-#align(start)[Start]
-#align(end)[Ende]
-
-#set text(lang: "ar")
-#align(start)[يبدأ]
-#align(end)[نهاية]
-
----
-// Ref: false
-#test(type(center), alignment)
-#test(type(horizon), alignment)
-#test(type(center + horizon), alignment)
-
----
-// Error: 8-22 cannot add two horizontal alignments
-#align(center + right, [A])
-
----
-// Error: 8-20 cannot add two vertical alignments
-#align(top + bottom, [A])
-
----
-// Error: 8-30 cannot add a vertical and a 2D alignment
-#align(top + (bottom + right), [A])
diff --git a/tests/typ/layout/block-sizing.typ b/tests/typ/layout/block-sizing.typ
deleted file mode 100644
index 181bbe31..00000000
--- a/tests/typ/layout/block-sizing.typ
+++ /dev/null
@@ -1,24 +0,0 @@
-// Test blocks with fixed height.
-
----
-#set page(height: 100pt)
-#set align(center)
-
-#lorem(10)
-#block(width: 80%, height: 60pt, fill: aqua)
-#lorem(6)
-#block(
- breakable: false,
- width: 100%,
- inset: 4pt,
- fill: aqua,
- lorem(8) + colbreak(),
-)
-
----
-// Layout inside a block with certain dimensions should provide those dimensions.
-
-#set page(height: 120pt)
-#block(width: 60pt, height: 80pt, layout(size => [
- This block has a width of #size.width and height of #size.height
-]))
diff --git a/tests/typ/layout/block-spacing.typ b/tests/typ/layout/block-spacing.typ
deleted file mode 100644
index 2c636676..00000000
--- a/tests/typ/layout/block-spacing.typ
+++ /dev/null
@@ -1,9 +0,0 @@
-// Test block spacing.
-
----
-#set block(spacing: 10pt)
-Hello
-
-There
-
-#block(spacing: 20pt)[Further down]
diff --git a/tests/typ/layout/cjk-latin-spacing.typ b/tests/typ/layout/cjk-latin-spacing.typ
deleted file mode 100644
index c6fff5d7..00000000
--- a/tests/typ/layout/cjk-latin-spacing.typ
+++ /dev/null
@@ -1,29 +0,0 @@
-// Test CJK-Latin spacing.
-
-#set page(width: 50pt + 10pt, margin: (x: 5pt))
-#set text(lang: "zh", font: "Noto Serif CJK SC", cjk-latin-spacing: auto)
-#set par(justify: true)
-
-中文,中12文1中,文12中文
-
-中文,中ab文a中,文ab中文
-
-#set text(cjk-latin-spacing: none)
-
-中文,中12文1中,文12中文
-
-中文,中ab文a中,文ab中文
-
----
-// Issue #2538
-#set text(cjk-latin-spacing: auto)
-
-abc字
-
-abc字#linebreak()
-
-abc字#linebreak()
-母
-
-abc字\
-母
diff --git a/tests/typ/layout/cjk-punctuation-adjustment.typ b/tests/typ/layout/cjk-punctuation-adjustment.typ
deleted file mode 100644
index 88ee9560..00000000
--- a/tests/typ/layout/cjk-punctuation-adjustment.typ
+++ /dev/null
@@ -1,44 +0,0 @@
-#set page(width: 15em)
-
-// In the following example, the space between 》! and ? should be squeezed.
-// because zh-CN follows GB style
-#set text(lang: "zh", region: "CN", font: "Noto Serif CJK SC")
-原来,你也玩《原神》!?
-
-// However, in the following example, the space between 》! and ? should not be squeezed.
-// because zh-TW does not follow GB style
-#set text(lang: "zh", region: "TW", font: "Noto Serif CJK TC")
-原來,你也玩《原神》! ?
-
-#set text(lang: "zh", region: "CN", font: "Noto Serif CJK SC")
-「真的吗?」
-
-#set text(lang: "ja", font: "Noto Serif CJK JP")
-「本当に?」
----
-
-#set text(lang: "zh", region: "CN", font: "Noto Serif CJK SC")
-《书名〈章节〉》 // the space between 〉 and 》 should be squeezed
-
-〔茸毛〕:很细的毛 // the space between 〕 and : should be squeezed
-
----
-#set page(width: 21em)
-#set text(lang: "zh", region: "CN", font: "Noto Serif CJK SC")
-
-// These examples contain extensive use of Chinese punctuation marks,
-// from 《Which parentheses should be used when applying parentheses?》.
-// link: https://archive.md/2bb1N
-
-
-(〔中〕医、〔中〕药、技)系列评审
-
-(长三角[长江三角洲])(GB/T 16159—2012《汉语拼音正词法基本规则》)
-
-【爱因斯坦(Albert Einstein)】物理学家
-
-〔(2009)民申字第1622号〕
-
-“江南海北长相忆,浅水深山独掩扉。”([唐]刘长卿《会赦后酬主簿所问》)
-
-参看1378页〖象形文字〗。(《现代汉语词典》修订本)
diff --git a/tests/typ/layout/clip.typ b/tests/typ/layout/clip.typ
deleted file mode 100644
index 3ca74556..00000000
--- a/tests/typ/layout/clip.typ
+++ /dev/null
@@ -1,68 +0,0 @@
-// Test clipping with the `box` and `block` containers.
-
----
-// Test box clipping with a rectangle
-Hello #box(width: 1em, height: 1em, clip: false)[#rect(width: 3em, height: 3em, fill: red)]
-world 1
-
-Space
-
-Hello #box(width: 1em, height: 1em, clip: true)[#rect(width: 3em, height: 3em, fill: red)]
-world 2
-
----
-// Test cliping text
-#block(width: 5em, height: 2em, clip: false, stroke: 1pt + black)[
- But, soft! what light through
-]
-
-#v(2em)
-
-#block(width: 5em, height: 2em, clip: true, stroke: 1pt + black)[
- But, soft! what light through yonder window breaks? It is the east, and Juliet
- is the sun.
-]
-
----
-// Test clipping svg glyphs
-Emoji: #box(height: 0.5em, stroke: 1pt + black)[🐪, 🌋, 🏞]
-
-Emoji: #box(height: 0.5em, clip: true, stroke: 1pt + black)[🐪, 🌋, 🏞]
-
----
-// Test block clipping over multiple pages.
-
-#set page(height: 60pt)
-
-First!
-
-#block(height: 4em, clip: true, stroke: 1pt + black)[
- But, soft! what light through yonder window breaks? It is the east, and Juliet
- is the sun.
-]
-
----
-// Test clipping with `radius`.
-
-#set page(height: 60pt)
-
-#box(
- radius: 5pt,
- stroke: 2pt + black,
- width: 20pt,
- height: 20pt,
- clip: true,
- image("/assets/images/rhino.png", width: 30pt)
-)
----
-// Test clipping with `radius`, but without `stroke`.
-
-#set page(height: 60pt)
-
-#box(
- radius: 5pt,
- width: 20pt,
- height: 20pt,
- clip: true,
- image("/assets/images/rhino.png", width: 30pt)
-)
diff --git a/tests/typ/layout/code-indent-shrink.typ b/tests/typ/layout/code-indent-shrink.typ
deleted file mode 100644
index 1527e061..00000000
--- a/tests/typ/layout/code-indent-shrink.typ
+++ /dev/null
@@ -1,28 +0,0 @@
-// Spaces in raw blocks should not be shrunk
-// as it would mess up the indentation of code
-// https://github.com/typst/typst/issues/3191
-
----
-#set par(justify: true)
-
-#show raw.where(block: true): block.with(
- fill: luma(240),
- inset: 10pt,
-)
-
-#block(
- width: 60%,
- ```py
- for x in xs:
- print("x=",x)
- ```
-)
-
----
-// In normal paragraphs, spaces should still be shrunk.
-// The first line here serves as a reference, while the second
-// uses non-breaking spaces to create an overflowing line
-// (which should shrink).
-~~~~No shrinking here
-
-~~~~The~spaces~on~this~line~shrink \ No newline at end of file
diff --git a/tests/typ/layout/columns.typ b/tests/typ/layout/columns.typ
deleted file mode 100644
index ecf636e7..00000000
--- a/tests/typ/layout/columns.typ
+++ /dev/null
@@ -1,112 +0,0 @@
-// Test the column layouter.
-
----
-// Test normal operation and RTL directions.
-#set page(height: 3.25cm, width: 7.05cm, columns: 2)
-#set text(lang: "ar", font: ("Noto Sans Arabic", "Linux Libertine"))
-#set columns(gutter: 30pt)
-
-#box(fill: conifer, height: 8pt, width: 6pt) وتحفيز
-العديد من التفاعلات الكيميائية. (DNA) من أهم الأحماض النووية التي تُشكِّل
-إلى جانب كل من البروتينات والليبيدات والسكريات المتعددة
-#box(fill: eastern, height: 8pt, width: 6pt)
-الجزيئات الضخمة الأربعة الضرورية للحياة.
-
----
-// Test the `columns` function.
-#set page(width: auto)
-
-#rect(width: 180pt, height: 100pt, inset: 8pt, columns(2, [
- A special plight has befallen our document.
- Columns in text boxes reigned down unto the soil
- to waste a year's crop of rich layouts.
- The columns at least were graciously balanced.
-]))
-
----
-// Test columns for a sized page.
-#set page(height: 5cm, width: 7.05cm, columns: 2)
-
-Lorem ipsum dolor sit amet is a common blind text
-and I again am in need of filling up this page
-#align(bottom, rect(fill: eastern, width: 100%, height: 12pt))
-#colbreak()
-
-so I'm returning to this trusty tool of tangible terror.
-Sure, it is not the most creative way of filling up
-a page for a test but it does get the job done.
-
----
-// Test the expansion behaviour.
-#set page(height: 2.5cm, width: 7.05cm)
-
-#rect(inset: 6pt, columns(2, [
- ABC \
- BCD
- #colbreak()
- DEF
-]))
-
----
-// Test setting a column gutter and more than two columns.
-#set page(height: 3.25cm, width: 7.05cm, columns: 3)
-#set columns(gutter: 30pt)
-
-#rect(width: 100%, height: 2.5cm, fill: conifer) #parbreak()
-#rect(width: 100%, height: 2cm, fill: eastern) #parbreak()
-#circle(fill: eastern)
-
----
-// Test the `colbreak` and `pagebreak` functions.
-#set page(height: 1cm, width: 7.05cm, columns: 2)
-
-A
-#colbreak()
-#colbreak()
-B
-#pagebreak()
-C
-#colbreak()
-D
-
----
-// Test an empty second column.
-#set page(width: 7.05cm, columns: 2)
-
-#rect(width: 100%, inset: 3pt)[So there isn't anything in the second column?]
-
----
-// Test columns when one of them is empty.
-#set page(width: auto, columns: 3)
-
-Arbitrary horizontal growth.
-
----
-// Test columns in an infinitely high frame.
-#set page(width: 7.05cm, columns: 2)
-
-There can be as much content as you want in the left column
-and the document will grow with it.
-
-#rect(fill: conifer, width: 100%, height: 30pt)
-
-Only an explicit #colbreak() `#colbreak()` can put content in the
-second column.
-
----
-// Test a page with a single column.
-#set page(height: auto, width: 7.05cm, columns: 1)
-
-This is a normal page. Very normal.
-
----
-// Test a page with zero columns.
-// Error: 49-50 number must be positive
-#set page(height: auto, width: 7.05cm, columns: 0)
-
----
-// Test colbreak after only out-of-flow elements.
-#set page(width: 7.05cm, columns: 2)
-#place[OOF]
-#colbreak()
-In flow.
diff --git a/tests/typ/layout/container-fill.typ b/tests/typ/layout/container-fill.typ
deleted file mode 100644
index f5947e06..00000000
--- a/tests/typ/layout/container-fill.typ
+++ /dev/null
@@ -1,7 +0,0 @@
-#set page(height: 100pt)
-#let words = lorem(18).split()
-#block(inset: 8pt, width: 100%, fill: aqua, stroke: aqua.darken(30%))[
- #words.slice(0, 13).join(" ")
- #box(fill: teal, outset: 2pt)[tempor]
- #words.slice(13).join(" ")
-]
diff --git a/tests/typ/layout/container.typ b/tests/typ/layout/container.typ
deleted file mode 100644
index 8d4ec34b..00000000
--- a/tests/typ/layout/container.typ
+++ /dev/null
@@ -1,50 +0,0 @@
-// Test the `box` and `block` containers.
-
----
-// Test box in paragraph.
-A #box[B \ C] D.
-
-// Test box with height.
-Spaced \
-#box(height: 0.5cm) \
-Apart
-
----
-// Test block sizing.
-#set page(height: 120pt)
-#set block(spacing: 0pt)
-#block(width: 90pt, height: 80pt, fill: red)[
- #block(width: 60%, height: 60%, fill: green)
- #block(width: 50%, height: 60%, fill: blue)
-]
-
----
-// Test box sizing with layoutable child.
-#box(
- width: 50pt,
- height: 50pt,
- fill: yellow,
- path(
- fill: purple,
- (0pt, 0pt),
- (30pt, 30pt),
- (0pt, 30pt),
- (30pt, 0pt),
- ),
-)
-
----
-// Test fr box.
-Hello #box(width: 1fr, rect(height: 0.7em, width: 100%)) World
-
----
-// Test block over multiple pages.
-
-#set page(height: 60pt)
-
-First!
-
-#block[
- But, soft! what light through yonder window breaks? It is the east, and Juliet
- is the sun.
-]
diff --git a/tests/typ/layout/enum-align.typ b/tests/typ/layout/enum-align.typ
deleted file mode 100644
index d64ee374..00000000
--- a/tests/typ/layout/enum-align.typ
+++ /dev/null
@@ -1,51 +0,0 @@
-// Test the alignment of enum numbers.
-
----
-// Alignment shouldn't affect number
-#set align(horizon)
-
-+ ABCDEF\ GHIJKL\ MNOPQR
- + INNER\ INNER\ INNER
-+ BACK\ HERE
-
----
-// Enum number alignment should be 'end' by default
-1. a
-10. b
-100. c
-
-#set enum(number-align: start)
-1. a
-8. b
-16. c
-
----
-#set enum(number-align: center + horizon)
-1. #box(fill: teal, inset: 10pt )[a]
-8. #box(fill: teal, inset: 10pt )[b]
-16. #box(fill: teal,inset: 10pt )[c]
-
----
-// Number align option should not be affected by the context.
-#set align(center)
-#set enum(number-align: start)
-
-4. c
-8. d
-16. e\ f
- 2. f\ g
- 32. g
- 64. h
-
----
-// Test valid number align values (horizontal and vertical)
-// Ref: false
-#set enum(number-align: start)
-#set enum(number-align: end)
-#set enum(number-align: left)
-#set enum(number-align: center)
-#set enum(number-align: right)
-#set enum(number-align: top)
-#set enum(number-align: horizon)
-#set enum(number-align: bottom)
-
diff --git a/tests/typ/layout/enum-numbering.typ b/tests/typ/layout/enum-numbering.typ
deleted file mode 100644
index 7efe195f..00000000
--- a/tests/typ/layout/enum-numbering.typ
+++ /dev/null
@@ -1,55 +0,0 @@
-// Test enum numbering styles.
-
----
-// Test numbering pattern.
-#set enum(numbering: "(1.a.*)")
-+ First
-+ Second
- 2. Nested
- + Deep
-+ Normal
-
----
-// Test full numbering.
-#set enum(numbering: "1.a.", full: true)
-+ First
- + Nested
-
----
-// Test numbering with closure.
-#enum(
- start: 3,
- spacing: 0.65em - 3pt,
- tight: false,
- numbering: n => text(
- fill: (red, green, blue).at(calc.rem(n, 3)),
- numbering("A", n),
- ),
- [Red], [Green], [Blue], [Red],
-)
-
----
-// Test numbering with closure and nested lists.
-#set enum(numbering: n => super[#n])
-+ A
- + B
-+ C
-
----
-// Test numbering with closure and nested lists.
-#set text(font: "New Computer Modern")
-#set enum(numbering: (..args) => math.mat(args.pos()), full: true)
-+ A
- + B
- + C
- + D
-+ E
-+ F
-
----
-// Error: 22-24 invalid numbering pattern
-#set enum(numbering: "")
-
----
-// Error: 22-28 invalid numbering pattern
-#set enum(numbering: "(())")
diff --git a/tests/typ/layout/enum.typ b/tests/typ/layout/enum.typ
deleted file mode 100644
index f9fe2648..00000000
--- a/tests/typ/layout/enum.typ
+++ /dev/null
@@ -1,48 +0,0 @@
-// Test enumerations.
-
----
-#enum[Embrace][Extend][Extinguish]
-
----
-0. Before first!
-1. First.
- 2. Indented
-
-+ Second
-
----
-// Test automatic numbering in summed content.
-#for i in range(5) {
- [+ #numbering("I", 1 + i)]
-}
-
----
-// Mix of different lists
-- Bullet List
-+ Numbered List
-/ Term: List
-
----
-// In the line.
-1.2 \
-This is 0. \
-See 0.3. \
-
----
-// Edge cases.
-+
-Empty \
-+Nope \
-a + 0.
-
----
-// Test item number overriding.
-1. first
-+ second
-5. fifth
-
-#enum(
- enum.item(1)[First],
- [Second],
- enum.item(5)[Fifth]
-)
diff --git a/tests/typ/layout/flow-orphan.typ b/tests/typ/layout/flow-orphan.typ
deleted file mode 100644
index 9c5c2399..00000000
--- a/tests/typ/layout/flow-orphan.typ
+++ /dev/null
@@ -1,30 +0,0 @@
-// Test that lines and headings doesn't become orphan.
-
----
-#set page(height: 100pt)
-#lorem(12)
-
-= Introduction
-This is the start and it goes on.
-
----
-#set page("a8", height: 140pt)
-#set text(weight: 700)
-
-// Fits fully onto the first page.
-#set text(blue)
-#lorem(27)
-
-// The first line would fit, but is moved to the second page.
-#lorem(20)
-
-// The second-to-last line is moved to the third page so that the last is isn't
-// as lonely.
-#set text(maroon)
-#lorem(11)
-
-#lorem(13)
-
-// All three lines go to the next page.
-#set text(olive)
-#lorem(10)
diff --git a/tests/typ/layout/grid-1.typ b/tests/typ/layout/grid-1.typ
deleted file mode 100644
index 411fd8d0..00000000
--- a/tests/typ/layout/grid-1.typ
+++ /dev/null
@@ -1,41 +0,0 @@
-// Test grid layouts.
-
----
-#let cell(width, color) = rect(width: width, height: 2cm, fill: color)
-#set page(width: 100pt, height: 140pt)
-#grid(
- columns: (auto, 1fr, 3fr, 0.25cm, 3%, 2mm + 10%),
- cell(0.5cm, rgb("2a631a")),
- cell(100%, forest),
- cell(100%, conifer),
- cell(100%, rgb("ff0000")),
- cell(100%, rgb("00ff00")),
- cell(80%, rgb("00faf0")),
- cell(1cm, rgb("00ff00")),
- cell(0.5cm, rgb("2a631a")),
- cell(100%, forest),
- cell(100%, conifer),
- cell(100%, rgb("ff0000")),
- cell(100%, rgb("00ff00")),
-)
-
----
-#set rect(inset: 0pt)
-#grid(
- columns: (auto, auto, 40%),
- column-gutter: 1fr,
- row-gutter: 1fr,
- rect(fill: eastern)[dddaa aaa aaa],
- rect(fill: conifer)[ccc],
- rect(fill: rgb("dddddd"))[aaa],
-)
-
----
-#set page(height: 3cm, margin: 0pt)
-#grid(
- columns: (1fr,),
- rows: (1fr, auto, 2fr),
- [],
- align(center)[A bit more to the top],
- [],
-)
diff --git a/tests/typ/layout/grid-2.typ b/tests/typ/layout/grid-2.typ
deleted file mode 100644
index 66623878..00000000
--- a/tests/typ/layout/grid-2.typ
+++ /dev/null
@@ -1,29 +0,0 @@
-// Test using the `grid` function to create a finance table.
-
----
-#set page(width: 11cm, height: 2.5cm)
-#grid(
- columns: 5,
- column-gutter: (2fr, 1fr, 1fr),
- row-gutter: 6pt,
- [*Quarter*],
- [Expenditure],
- [External Revenue],
- [Financial ROI],
- [_total_],
- [*Q1*],
- [173,472.57 \$],
- [472,860.91 \$],
- [51,286.84 \$],
- [_350,675.18 \$_],
- [*Q2*],
- [93,382.12 \$],
- [439,382.85 \$],
- [-1,134.30 \$],
- [_344,866.43 \$_],
- [*Q3*],
- [96,421.49 \$],
- [238,583.54 \$],
- [3,497.12 \$],
- [_145,659.17 \$_],
-)
diff --git a/tests/typ/layout/grid-3.typ b/tests/typ/layout/grid-3.typ
deleted file mode 100644
index a6c72d6c..00000000
--- a/tests/typ/layout/grid-3.typ
+++ /dev/null
@@ -1,61 +0,0 @@
-// Test grid cells that overflow to the next region.
-
----
-#set page(width: 5cm, height: 3cm)
-#grid(
- columns: 2,
- row-gutter: 8pt,
- [Lorem ipsum dolor sit amet.
-
- Aenean commodo ligula eget dolor. Aenean massa. Penatibus et magnis.],
- [Text that is rather short],
- [Fireflies],
- [Critical],
- [Decorum],
- [Rampage],
-)
-
----
-// Test a column that starts overflowing right after another row/column did
-// that.
-#set page(width: 5cm, height: 2cm)
-#grid(
- columns: 4 * (1fr,),
- row-gutter: 10pt,
- column-gutter: (0pt, 10%),
- align(top, image("/assets/images/rhino.png")),
- align(top, rect(inset: 0pt, fill: eastern, align(right)[LoL])),
- [rofl],
- [\ A] * 3,
- [Ha!\ ] * 3,
-)
-
----
-// Test two columns in the same row overflowing by a different amount.
-#set page(width: 5cm, height: 2cm)
-#grid(
- columns: 3 * (1fr,),
- row-gutter: 8pt,
- column-gutter: (0pt, 10%),
- [A], [B], [C],
- [Ha!\ ] * 6,
- [rofl],
- [\ A] * 3,
- [hello],
- [darkness],
- [my old]
-)
-
----
-// Test grid within a grid, overflowing.
-#set page(width: 5cm, height: 2.25cm)
-#grid(
- columns: 4 * (1fr,),
- row-gutter: 10pt,
- column-gutter: (0pt, 10%),
- [A], [B], [C], [D],
- grid(columns: 2, [A], [B], [C\ ]*3, [D]),
- align(top, rect(inset: 0pt, fill: eastern, align(right)[LoL])),
- [rofl],
- [E\ ]*4,
-)
diff --git a/tests/typ/layout/grid-4.typ b/tests/typ/layout/grid-4.typ
deleted file mode 100644
index c7ae7649..00000000
--- a/tests/typ/layout/grid-4.typ
+++ /dev/null
@@ -1,33 +0,0 @@
-// Test relative sizing inside grids.
-
----
-// Test that auto and relative columns use the correct base.
-#grid(
- columns: (auto, 60%),
- rows: (auto, auto),
- rect(width: 50%, height: 0.5cm, fill: conifer),
- rect(width: 100%, height: 0.5cm, fill: eastern),
- rect(width: 50%, height: 0.5cm, fill: forest),
-)
-
----
-// Test that fr columns use the correct base.
-#grid(
- columns: (1fr,) * 4,
- rows: (1cm,),
- rect(width: 50%, fill: conifer),
- rect(width: 50%, fill: forest),
- rect(width: 50%, fill: conifer),
- rect(width: 50%, fill: forest),
-)
-
----
-// Test that all three kinds of rows use the correct bases.
-#set page(height: 4cm, margin: 0cm)
-#grid(
- rows: (1cm, 1fr, 1fr, auto),
- rect(height: 50%, width: 100%, fill: conifer),
- rect(height: 50%, width: 100%, fill: forest),
- rect(height: 50%, width: 100%, fill: conifer),
- rect(height: 25%, width: 100%, fill: forest),
-)
diff --git a/tests/typ/layout/grid-5.typ b/tests/typ/layout/grid-5.typ
deleted file mode 100644
index 66272421..00000000
--- a/tests/typ/layout/grid-5.typ
+++ /dev/null
@@ -1,40 +0,0 @@
-
----
-// Test that trailing linebreak doesn't overflow the region.
-#set page(height: 2cm)
-#grid[
- Hello \
- Hello \
- Hello \
-
- World
-]
-
----
-// Test that broken cell expands vertically.
-#set page(height: 2.25cm)
-#grid(
- columns: 2,
- gutter: 10pt,
- align(bottom)[A],
- [
- Top
- #align(bottom)[
- Bottom \
- Bottom \
- #v(0pt)
- Top
- ]
- ],
- align(top)[B],
-)
-
----
-// Ensure grids expand enough for the given rows.
-#grid(
- columns: (2em, 2em),
- rows: (2em,) * 4,
- fill: red,
- stroke: aqua,
- [a]
-)
diff --git a/tests/typ/layout/grid-auto-shrink.typ b/tests/typ/layout/grid-auto-shrink.typ
deleted file mode 100644
index 4d9ef0d8..00000000
--- a/tests/typ/layout/grid-auto-shrink.typ
+++ /dev/null
@@ -1,12 +0,0 @@
-// Test iterative auto column shrinking.
-
----
-#set page(width: 210mm - 2 * 2.5cm + 2 * 10pt)
-#set text(11pt)
-#table(
- columns: 4,
- [Hello!],
- [Hello there, my friend!],
- [Hello there, my friends! Hi!],
- [Hello there, my friends! Hi! What is going on right now?],
-)
diff --git a/tests/typ/layout/grid-cell.typ b/tests/typ/layout/grid-cell.typ
deleted file mode 100644
index a812fefc..00000000
--- a/tests/typ/layout/grid-cell.typ
+++ /dev/null
@@ -1,133 +0,0 @@
-// Test basic styling using the grid.cell element.
-
----
-// Cell override
-#grid(
- align: left,
- fill: red,
- stroke: blue,
- inset: 5pt,
- columns: 2,
- [AAAAA], [BBBBB],
- [A], [B],
- grid.cell(align: right)[C], [D],
- align(right)[E], [F],
- align(horizon)[G], [A\ A\ A],
- grid.cell(align: horizon)[G2], [A\ A\ A],
- grid.cell(inset: 0pt)[I], [F],
- [H], grid.cell(fill: blue)[J]
-)
-
----
-// Cell show rule
-#show grid.cell: it => [Zz]
-
-#grid(
- align: left,
- fill: red,
- stroke: blue,
- inset: 5pt,
- columns: 2,
- [AAAAA], [BBBBB],
- [A], [B],
- grid.cell(align: right)[C], [D],
- align(right)[E], [F],
- align(horizon)[G], [A\ A\ A]
-)
-
----
-#show grid.cell: it => (it.align, it.fill)
-#grid(
- align: left,
- row-gutter: 5pt,
- [A],
- grid.cell(align: right)[B],
- grid.cell(fill: aqua)[B],
-)
-
----
-// Cell set rules
-#set grid.cell(align: center)
-#show grid.cell: it => (it.align, it.fill, it.inset)
-#set grid.cell(inset: 20pt)
-#grid(
- align: left,
- row-gutter: 5pt,
- [A],
- grid.cell(align: right)[B],
- grid.cell(fill: aqua)[B],
-)
-
----
-// Test folding per-cell properties (align and inset)
-#grid(
- columns: (1fr, 1fr),
- rows: (2.5em, auto),
- align: right,
- inset: 5pt,
- fill: (x, y) => (green, aqua).at(calc.rem(x + y, 2)),
- [Top], grid.cell(align: bottom)[Bot],
- grid.cell(inset: (bottom: 0pt))[Bot], grid.cell(inset: (bottom: 0pt))[Bot]
-)
-
----
-// Test overriding outside alignment
-#set align(bottom + right)
-#grid(
- columns: (1fr, 1fr),
- rows: 2em,
- align: auto,
- fill: green,
- [BR], [BR],
- grid.cell(align: left, fill: aqua)[BL], grid.cell(align: top, fill: red.lighten(50%))[TR]
-)
-
----
-// First doc example
-#grid(
- columns: 2,
- fill: red,
- align: left,
- inset: 5pt,
- [ABC], [ABC],
- grid.cell(fill: blue)[C], [D],
- grid.cell(align: center)[E], [F],
- [G], grid.cell(inset: 0pt)[H]
-)
-
----
-#{
- show grid.cell: emph
- grid(
- columns: 2,
- gutter: 3pt,
- [Hello], [World],
- [Sweet], [Italics]
- )
-}
-
----
-// Style based on position
-#{
- show grid.cell: it => {
- if it.y == 0 {
- strong(it)
- } else if it.x == 1 {
- emph(it)
- } else {
- it
- }
- }
- grid(
- columns: 3,
- gutter: 3pt,
- [Name], [Age], [Info],
- [John], [52], [Nice],
- [Mary], [50], [Cool],
- [Jake], [49], [Epic]
- )
-}
-
----
-// Error: 7-19 cannot use `table.cell` as a grid cell; use `grid.cell` instead
-#grid(table.cell[])
diff --git a/tests/typ/layout/grid-colspan.typ b/tests/typ/layout/grid-colspan.typ
deleted file mode 100644
index 1bdadcf1..00000000
--- a/tests/typ/layout/grid-colspan.typ
+++ /dev/null
@@ -1,141 +0,0 @@
-#grid(
- columns: 4,
- fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
- inset: 5pt,
- align: center,
- grid.cell(colspan: 4)[*Full Header*],
- grid.cell(colspan: 2, fill: orange)[*Half*],
- grid.cell(colspan: 2, fill: orange.darken(10%))[*Half*],
- [*A*], [*B*], [*C*], [*D*],
- [1], [2], [3], [4],
- [5], grid.cell(colspan: 3, fill: orange.darken(10%))[6],
- grid.cell(colspan: 2, fill: orange)[7], [8], [9],
- [10], grid.cell(colspan: 2, fill: orange.darken(10%))[11], [12]
-)
-
-#table(
- columns: 4,
- fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
- inset: 5pt,
- align: center,
- table.cell(colspan: 4)[*Full Header*],
- table.cell(colspan: 2, fill: orange)[*Half*],
- table.cell(colspan: 2, fill: orange.darken(10%))[*Half*],
- [*A*], [*B*], [*C*], [*D*],
- [1], [2], [3], [4],
- [5], table.cell(colspan: 3, fill: orange.darken(10%))[6],
- table.cell(colspan: 2, fill: orange)[7], [8], [9],
- [10], table.cell(colspan: 2, fill: orange.darken(10%))[11], [12]
-)
-
----
-#grid(
- columns: 4,
- fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
- inset: 5pt,
- align: center,
- gutter: 3pt,
- grid.cell(colspan: 4)[*Full Header*],
- grid.cell(colspan: 2, fill: orange)[*Half*],
- grid.cell(colspan: 2, fill: orange.darken(10%))[*Half*],
- [*A*], [*B*], [*C*], [*D*],
- [1], [2], [3], [4],
- [5], grid.cell(colspan: 3, fill: orange.darken(10%))[6],
- grid.cell(colspan: 2, fill: orange)[7], [8], [9],
- [10], grid.cell(colspan: 2, fill: orange.darken(10%))[11], [12]
-)
-
-#table(
- columns: 4,
- fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
- inset: 5pt,
- align: center,
- gutter: 3pt,
- table.cell(colspan: 4)[*Full Header*],
- table.cell(colspan: 2, fill: orange)[*Half*],
- table.cell(colspan: 2, fill: orange.darken(10%))[*Half*],
- [*A*], [*B*], [*C*], [*D*],
- [1], [2], [3], [4],
- [5], table.cell(colspan: 3, fill: orange.darken(10%))[6],
- table.cell(colspan: 2, fill: orange)[7], [8], [9],
- [10], table.cell(colspan: 2, fill: orange.darken(10%))[11], [12]
-)
-
----
-#set page(width: 300pt)
-#table(
- columns: (2em, 2em, auto, auto),
- stroke: 5pt,
- [A], [B], [C], [D],
- table.cell(colspan: 4, lorem(20)),
- [A], table.cell(colspan: 2)[BCBCBCBC], [D]
-)
-
----
-// Error: 3:8-3:32 cell's colspan would cause it to exceed the available column(s)
-// Hint: 3:8-3:32 try placing the cell in another position or reducing its colspan
-#grid(
- columns: 3,
- [a], grid.cell(colspan: 3)[b]
-)
-
----
-// Error: 4:8-4:32 cell would span a previously placed cell at column 2, row 0
-// Hint: 4:8-4:32 try specifying your cells in a different order or reducing the cell's rowspan or colspan
-#grid(
- columns: 3,
- grid.cell(x: 2, y: 0)[x],
- [a], grid.cell(colspan: 2)[b]
-)
-
----
-// Colspan over all fractional columns shouldn't expand auto columns on finite pages
-#table(
- columns: (1fr, 1fr, auto),
- [A], [B], [C],
- [D], [E], [F]
-)
-#table(
- columns: (1fr, 1fr, auto),
- table.cell(colspan: 3, lorem(8)),
- [A], [B], [C],
- [D], [E], [F]
-)
-
----
-// Colspan over only some fractional columns will not trigger the heuristic, and
-// the auto column will expand more than it should. The table looks off, as a result.
-#table(
- columns: (1fr, 1fr, auto),
- [], table.cell(colspan: 2, lorem(8)),
- [A], [B], [C],
- [D], [E], [F]
-)
-
----
-// On infinite pages, colspan over all fractional columns SHOULD expand auto columns
-#set page(width: auto)
-#table(
- columns: (1fr, 1fr, auto),
- [A], [B], [C],
- [D], [E], [F]
-)
-#table(
- columns: (1fr, 1fr, auto),
- table.cell(colspan: 3, lorem(8)),
- [A], [B], [C],
- [D], [E], [F]
-)
-
----
-// Test multiple regions
-#set page(height: 5em)
-#grid(
- stroke: red,
- fill: aqua,
- columns: 4,
- [a], [b], [c], [d],
- [a], grid.cell(colspan: 2)[e, f, g, h, i], [f],
- [e], [g], grid.cell(colspan: 2)[eee\ e\ e\ e],
- grid.cell(colspan: 4)[eeee e e e]
-)
diff --git a/tests/typ/layout/grid-footers-1.typ b/tests/typ/layout/grid-footers-1.typ
deleted file mode 100644
index c7a59e60..00000000
--- a/tests/typ/layout/grid-footers-1.typ
+++ /dev/null
@@ -1,192 +0,0 @@
-#set page(width: auto, height: 15em)
-#set text(6pt)
-#set table(inset: 2pt, stroke: 0.5pt)
-#table(
- columns: 5,
- align: center + horizon,
- table.header(
- table.cell(colspan: 5)[*Cool Zone*],
- table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*],
- table.hline(start: 2, end: 3, stroke: yellow)
- ),
- ..range(0, 5).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten(),
- table.footer(
- table.hline(start: 2, end: 3, stroke: yellow),
- table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*],
- table.cell(colspan: 5)[*Cool Zone*]
- )
-)
-
----
-// Gutter & no repetition
-#set page(width: auto, height: 16em)
-#set text(6pt)
-#set table(inset: 2pt, stroke: 0.5pt)
-#table(
- columns: 5,
- gutter: 2pt,
- align: center + horizon,
- table.header(
- table.cell(colspan: 5)[*Cool Zone*],
- table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*],
- table.hline(start: 2, end: 3, stroke: yellow)
- ),
- ..range(0, 5).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten(),
- table.footer(
- repeat: false,
- table.hline(start: 2, end: 3, stroke: yellow),
- table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*],
- table.cell(colspan: 5)[*Cool Zone*]
- )
-)
-
----
-#table(
- table.header(table.cell(stroke: red)[Hello]),
- table.footer(table.cell(stroke: aqua)[Bye]),
-)
-
----
-#table(
- gutter: 3pt,
- table.header(table.cell(stroke: red)[Hello]),
- table.footer(table.cell(stroke: aqua)[Bye]),
-)
-
----
-// Footer's top stroke should win when repeated, but lose at the last page.
-#set page(height: 10em)
-#table(
- stroke: green,
- table.header(table.cell(stroke: red)[Hello]),
- table.cell(stroke: yellow)[Hi],
- table.cell(stroke: yellow)[Bye],
- table.cell(stroke: yellow)[Ok],
- table.footer[Bye],
-)
-
----
-// Relative lengths
-#set page(height: 10em)
-#table(
- rows: (30%, 30%, auto),
- [C],
- [C],
- table.footer[*A*][*B*],
-)
-
----
-#grid(
- grid.footer(grid.cell(y: 2)[b]),
- grid.cell(y: 0)[a],
- grid.cell(y: 1)[c],
-)
-
----
-// Ensure footer properly expands
-#grid(
- columns: 2,
- [a], [],
- [b], [],
- grid.cell(x: 1, y: 3, rowspan: 4)[b],
- grid.cell(y: 2, rowspan: 2)[a],
- grid.footer(),
- grid.cell(y: 4)[d],
- grid.cell(y: 5)[e],
- grid.cell(y: 6)[f],
-)
-
----
-// Error: 2:3-2:19 footer must end at the last row
-#grid(
- grid.footer([a]),
- [b],
-)
-
----
-// Error: 3:3-3:19 footer must end at the last row
-#grid(
- columns: 2,
- grid.footer([a]),
- [b],
-)
-
----
-// Error: 4:3-4:19 footer would conflict with a cell placed before it at column 1 row 0
-// Hint: 4:3-4:19 try reducing that cell's rowspan or moving the footer
-#grid(
- columns: 2,
- grid.header(),
- grid.footer([a]),
- grid.cell(x: 1, y: 0, rowspan: 2)[a],
-)
-
----
-// Error: 4:3-4:19 cannot have more than one footer
-#grid(
- [a],
- grid.footer([a]),
- grid.footer([b]),
-)
-
----
-// Error: 3:3-3:20 cannot use `table.footer` as a grid footer; use `grid.footer` instead
-#grid(
- [a],
- table.footer([a]),
-)
-
----
-// Error: 3:3-3:19 cannot use `grid.footer` as a table footer; use `table.footer` instead
-#table(
- [a],
- grid.footer([a]),
-)
-
----
-// Error: 14-28 cannot place a grid footer within another footer or header
-#grid.header(grid.footer[a])
-
----
-// Error: 14-29 cannot place a table footer within another footer or header
-#grid.header(table.footer[a])
-
----
-// Error: 15-29 cannot place a grid footer within another footer or header
-#table.header(grid.footer[a])
-
----
-// Error: 15-30 cannot place a table footer within another footer or header
-#table.header(table.footer[a])
-
----
-// Error: 14-28 cannot place a grid footer within another footer or header
-#grid.footer(grid.footer[a])
-
----
-// Error: 14-29 cannot place a table footer within another footer or header
-#grid.footer(table.footer[a])
-
----
-// Error: 15-29 cannot place a grid footer within another footer or header
-#table.footer(grid.footer[a])
-
----
-// Error: 15-30 cannot place a table footer within another footer or header
-#table.footer(table.footer[a])
-
----
-// Error: 14-28 cannot place a grid header within another header or footer
-#grid.footer(grid.header[a])
-
----
-// Error: 14-29 cannot place a table header within another header or footer
-#grid.footer(table.header[a])
-
----
-// Error: 15-29 cannot place a grid header within another header or footer
-#table.footer(grid.header[a])
-
----
-// Error: 15-30 cannot place a table header within another header or footer
-#table.footer(table.header[a])
diff --git a/tests/typ/layout/grid-footers-2.typ b/tests/typ/layout/grid-footers-2.typ
deleted file mode 100644
index df333434..00000000
--- a/tests/typ/layout/grid-footers-2.typ
+++ /dev/null
@@ -1,31 +0,0 @@
-#set page(height: 17em)
-#table(
- rows: (auto, 2.5em, auto),
- table.header[*Hello*][*World*],
- block(width: 2em, height: 10em, fill: red),
- table.footer[*Bye*][*World*],
-)
-
----
-// Rowspan sizing algorithm doesn't do the best job at non-contiguous content
-// ATM.
-#set page(height: 20em)
-
-#table(
- rows: (auto, 2.5em, 2em, auto, 5em, 2em, 2.5em),
- table.header[*Hello*][*World*],
- table.cell(rowspan: 3, lorem(20)),
- table.footer[*Ok*][*Bye*],
-)
-
----
-// This should look right
-#set page(height: 20em)
-
-#table(
- rows: (auto, 2.5em, 2em, auto),
- gutter: 3pt,
- table.header[*Hello*][*World*],
- table.cell(rowspan: 3, lorem(20)),
- table.footer[*Ok*][*Bye*],
-)
diff --git a/tests/typ/layout/grid-footers-3.typ b/tests/typ/layout/grid-footers-3.typ
deleted file mode 100644
index 070500f1..00000000
--- a/tests/typ/layout/grid-footers-3.typ
+++ /dev/null
@@ -1,44 +0,0 @@
-// Test lack of space for header + text.
-#set page(height: 9em + 2.5em + 1.5em)
-
-#table(
- rows: (auto, 2.5em, auto, auto, 10em, 2.5em, auto),
- gutter: 3pt,
- table.header[*Hello*][*World*],
- table.cell(rowspan: 3, lorem(30)),
- table.footer[*Ok*][*Bye*],
-)
-
----
-// Orphan header prevention test
-#set page(height: 13em)
-#v(8em)
-#grid(
- columns: 3,
- gutter: 5pt,
- grid.header(
- [*Mui*], [*A*], grid.cell(rowspan: 2, fill: orange)[*B*],
- [*Header*], [*Header* #v(0.1em)],
- ),
- ..([Test], [Test], [Test]) * 7,
- grid.footer(
- [*Mui*], [*A*], grid.cell(rowspan: 2, fill: orange)[*B*],
- [*Footer*], [*Footer* #v(0.1em)],
- ),
-)
-
----
-// Empty footer should just be a repeated blank row
-#set page(height: 8em)
-#table(
- columns: 4,
- align: center + horizon,
- table.header(),
- ..range(0, 2).map(i => (
- [John \##i],
- table.cell(stroke: green)[123],
- table.cell(stroke: blue)[456],
- [789]
- )).flatten(),
- table.footer(),
-)
diff --git a/tests/typ/layout/grid-footers-4.typ b/tests/typ/layout/grid-footers-4.typ
deleted file mode 100644
index b6d978e9..00000000
--- a/tests/typ/layout/grid-footers-4.typ
+++ /dev/null
@@ -1,42 +0,0 @@
-// When a footer has a rowspan with an empty row, it should be displayed
-// properly
-#set page(height: 14em, width: auto)
-
-#let count = counter("g")
-#table(
- rows: (auto, 2em, auto, auto),
- table.header(
- [eeec],
- table.cell(rowspan: 2, count.step() + count.display()),
- ),
- [d],
- block(width: 5em, fill: yellow, lorem(7)),
- [d],
- table.footer(
- [eeec],
- table.cell(rowspan: 2, count.step() + count.display()),
- )
-)
-#count.display()
-
----
-// Nested table with footer should repeat both footers
-#set page(height: 10em, width: auto)
-#table(
- table(
- [a\ b\ c\ d],
- table.footer[b],
- ),
- table.footer[a],
-)
-
----
-#set page(height: 12em, width: auto)
-#table(
- [a\ b\ c\ d],
- table.footer(table(
- [c],
- [d],
- table.footer[b],
- ))
-)
diff --git a/tests/typ/layout/grid-footers-5.typ b/tests/typ/layout/grid-footers-5.typ
deleted file mode 100644
index 16da940a..00000000
--- a/tests/typ/layout/grid-footers-5.typ
+++ /dev/null
@@ -1,87 +0,0 @@
-// General footer-only tests
-#set page(height: 9em)
-#table(
- columns: 2,
- [a], [],
- [b], [],
- [c], [],
- [d], [],
- [e], [],
- table.footer(
- [*Ok*], table.cell(rowspan: 2)[test],
- [*Thanks*]
- )
-)
-
----
-#set page(height: 5em)
-#table(
- table.footer[a][b][c]
-)
-
----
-#table(table.footer[a][b][c])
-
-#table(
- gutter: 3pt,
- table.footer[a][b][c]
-)
-
----
-// Test footer stroke priority edge case
-#set page(height: 10em)
-#table(
- columns: 2,
- stroke: black,
- ..(table.cell(stroke: aqua)[d],) * 8,
- table.footer(
- table.cell(rowspan: 2, colspan: 2)[a],
- [c], [d]
- )
-)
-
----
-// Footer should appear at the bottom. Red line should be above the footer.
-// Green line should be on the left border.
-#set page(margin: 2pt)
-#set text(6pt)
-#table(
- columns: 2,
- inset: 1.5pt,
- table.cell(y: 0)[a],
- table.cell(x: 1, y: 1)[a],
- table.cell(y: 2)[a],
- table.footer(
- table.hline(stroke: red),
- table.vline(stroke: green),
- [b],
- ),
- table.cell(x: 1, y: 3)[c]
-)
-
----
-// Table should be just one row. [c] appears at the third column.
-#set page(margin: 2pt)
-#set text(6pt)
-#table(
- columns: 3,
- inset: 1.5pt,
- table.cell(y: 0)[a],
- table.footer(
- table.hline(stroke: red),
- table.hline(y: 1, stroke: aqua),
- table.cell(y: 0)[b],
- [c]
- )
-)
-
----
-// Footer should go below the rowspans.
-#set page(margin: 2pt)
-#set text(6pt)
-#table(
- columns: 2,
- inset: 1.5pt,
- table.cell(rowspan: 2)[a], table.cell(rowspan: 2)[b],
- table.footer()
-)
diff --git a/tests/typ/layout/grid-headers-1.typ b/tests/typ/layout/grid-headers-1.typ
deleted file mode 100644
index ac998029..00000000
--- a/tests/typ/layout/grid-headers-1.typ
+++ /dev/null
@@ -1,162 +0,0 @@
-#set page(width: auto, height: 12em)
-#table(
- columns: 5,
- align: center + horizon,
- table.header(
- table.cell(colspan: 5)[*Cool Zone*],
- table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*],
- table.hline(start: 2, end: 3, stroke: yellow)
- ),
- ..range(0, 6).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten()
-)
-
----
-// Disable repetition
-#set page(width: auto, height: 12em)
-#table(
- columns: 5,
- align: center + horizon,
- table.header(
- table.cell(colspan: 5)[*Cool Zone*],
- table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*],
- table.hline(start: 2, end: 3, stroke: yellow),
- repeat: false
- ),
- ..range(0, 6).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten()
-)
-
----
-#set page(width: auto, height: 12em)
-#table(
- columns: 5,
- align: center + horizon,
- gutter: 3pt,
- table.header(
- table.cell(colspan: 5)[*Cool Zone*],
- table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*],
- table.hline(start: 2, end: 3, stroke: yellow),
- ),
- ..range(0, 6).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten()
-)
-
----
-// Relative lengths
-#set page(height: 10em)
-#table(
- rows: (30%, 30%, auto),
- table.header(
- [*A*],
- [*B*]
- ),
- [C],
- [C]
-)
-
----
-#grid(
- grid.cell(y: 1)[a],
- grid.header(grid.cell(y: 0)[b]),
- grid.cell(y: 2)[c]
-)
-
----
-// When the header is the last grid child, it shouldn't include the gutter row
-// after it, because there is none.
-#grid(
- columns: 2,
- gutter: 3pt,
- grid.header(
- [a], [b],
- [c], [d]
- )
-)
-
----
-#set page(height: 14em)
-#let t(n) = table(
- columns: 3,
- align: center + horizon,
- gutter: 3pt,
- table.header(
- table.cell(colspan: 3)[*Cool Zone #n*],
- [*Name*], [*Num*], [*Data*]
- ),
- ..range(0, 5).map(i => ([\##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456])).flatten()
-)
-#grid(
- gutter: 3pt,
- t(0),
- t(1)
-)
-
----
-// Test line positioning in header
-#table(
- columns: 3,
- stroke: none,
- table.hline(stroke: red, end: 2),
- table.vline(stroke: red, end: 3),
- table.header(
- table.hline(stroke: aqua, start: 2),
- table.vline(stroke: aqua, start: 3), [*A*], table.hline(stroke: orange), table.vline(stroke: orange), [*B*],
- [*C*], [*D*]
- ),
- [a], [b],
- [c], [d],
- [e], [f]
-)
-
----
-// Error: 3:3-3:19 header must start at the first row
-// Hint: 3:3-3:19 remove any rows before the header
-#grid(
- [a],
- grid.header([b])
-)
-
----
-// Error: 4:3-4:19 header must start at the first row
-// Hint: 4:3-4:19 remove any rows before the header
-#grid(
- columns: 2,
- [a],
- grid.header([b])
-)
-
----
-// Error: 3:3-3:19 cannot have more than one header
-#grid(
- grid.header([a]),
- grid.header([b]),
- [a],
-)
-
----
-// Error: 2:3-2:20 cannot use `table.header` as a grid header; use `grid.header` instead
-#grid(
- table.header([a]),
- [a],
-)
-
----
-// Error: 2:3-2:19 cannot use `grid.header` as a table header; use `table.header` instead
-#table(
- grid.header([a]),
- [a],
-)
-
----
-// Error: 14-28 cannot place a grid header within another header or footer
-#grid.header(grid.header[a])
-
----
-// Error: 14-29 cannot place a table header within another header or footer
-#grid.header(table.header[a])
-
----
-// Error: 15-29 cannot place a grid header within another header or footer
-#table.header(grid.header[a])
-
----
-// Error: 15-30 cannot place a table header within another header or footer
-#table.header(table.header[a])
diff --git a/tests/typ/layout/grid-headers-2.typ b/tests/typ/layout/grid-headers-2.typ
deleted file mode 100644
index 75c9b330..00000000
--- a/tests/typ/layout/grid-headers-2.typ
+++ /dev/null
@@ -1,52 +0,0 @@
-#set page(height: 15em)
-#table(
- rows: (auto, 2.5em, auto),
- table.header(
- [*Hello*],
- [*World*]
- ),
- block(width: 2em, height: 20em, fill: red)
-)
-
----
-// Rowspan sizing algorithm doesn't do the best job at non-contiguous content
-// ATM.
-#set page(height: 15em)
-
-#table(
- rows: (auto, 2.5em, 2em, auto, 5em),
- table.header(
- [*Hello*],
- [*World*]
- ),
- table.cell(rowspan: 3, lorem(40))
-)
-
----
-// Rowspan sizing algorithm doesn't do the best job at non-contiguous content
-// ATM.
-#set page(height: 15em)
-
-#table(
- rows: (auto, 2.5em, 2em, auto, 5em),
- gutter: 3pt,
- table.header(
- [*Hello*],
- [*World*]
- ),
- table.cell(rowspan: 3, lorem(40))
-)
-
----
-// This should look right
-#set page(height: 15em)
-
-#table(
- rows: (auto, 2.5em, 2em, auto),
- gutter: 3pt,
- table.header(
- [*Hello*],
- [*World*]
- ),
- table.cell(rowspan: 3, lorem(40))
-)
diff --git a/tests/typ/layout/grid-headers-3.typ b/tests/typ/layout/grid-headers-3.typ
deleted file mode 100644
index e7437cf7..00000000
--- a/tests/typ/layout/grid-headers-3.typ
+++ /dev/null
@@ -1,35 +0,0 @@
-// Test lack of space for header + text.
-#set page(height: 9em)
-
-#table(
- rows: (auto, 2.5em, auto, auto, 10em),
- gutter: 3pt,
- table.header(
- [*Hello*],
- [*World*]
- ),
- table.cell(rowspan: 3, lorem(80))
-)
-
----
-// Orphan header prevention test
-#set page(height: 12em)
-#v(8em)
-#grid(
- columns: 3,
- grid.header(
- [*Mui*], [*A*], grid.cell(rowspan: 2, fill: orange)[*B*],
- [*Header*], [*Header* #v(0.1em)]
- ),
- ..([Test], [Test], [Test]) * 20
-)
-
----
-// Empty header should just be a repeated blank row
-#set page(height: 12em)
-#table(
- columns: 4,
- align: center + horizon,
- table.header(),
- ..range(0, 4).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789])).flatten()
-)
diff --git a/tests/typ/layout/grid-headers-4.typ b/tests/typ/layout/grid-headers-4.typ
deleted file mode 100644
index 6ab0d612..00000000
--- a/tests/typ/layout/grid-headers-4.typ
+++ /dev/null
@@ -1,112 +0,0 @@
-// When a header has a rowspan with an empty row, it should be displayed
-// properly
-#set page(height: 10em)
-
-#let count = counter("g")
-#table(
- rows: (auto, 2em, auto, auto),
- table.header(
- [eeec],
- table.cell(rowspan: 2, count.step() + count.display()),
- ),
- [d],
- block(width: 5em, fill: yellow, lorem(15)),
- [d]
-)
-#count.display()
-
----
-// Ensure header expands to fit cell placed in it after its declaration
-#set page(height: 10em)
-#table(
- columns: 2,
- table.header(
- [a], [b],
- [c],
- ),
- table.cell(x: 1, y: 1, rowspan: 2, lorem(80))
-)
-
----
-// Nested table with header should repeat both headers
-#set page(height: 10em)
-#table(
- table.header(
- [a]
- ),
- table(
- table.header(
- [b]
- ),
- [a\ b\ c\ d]
- )
-)
-
----
-#set page(height: 12em)
-#table(
- table.header(
- table(
- table.header(
- [b]
- ),
- [c],
- [d]
- )
- ),
- [a\ b]
-)
-
----
-// Test header stroke priority edge case (last header row removed)
-#set page(height: 8em)
-#table(
- columns: 2,
- stroke: black,
- gutter: (auto, 3pt),
- table.header(
- [c], [d],
- ),
- ..(table.cell(stroke: aqua)[d],) * 8,
-)
-
----
-// Yellow line should be kept here
-#set text(6pt)
-#table(
- column-gutter: 3pt,
- inset: 1pt,
- table.header(
- [a],
- table.hline(stroke: yellow),
- ),
- table.cell(rowspan: 2)[b]
-)
-
----
-// Red line should be kept here
-#set page(height: 6em)
-#set text(6pt)
-#table(
- column-gutter: 3pt,
- inset: 1pt,
- table.header(
- table.hline(stroke: red, position: bottom),
- [a],
- ),
- [a],
- table.cell(stroke: aqua)[b]
-)
-
----
-#set page(height: 7em)
-#set text(6pt)
-#let full-block = block(width: 2em, height: 100%, fill: red)
-#table(
- columns: 3,
- inset: 1.5pt,
- table.header(
- [a], full-block, table.cell(rowspan: 2, full-block),
- [b]
- )
-)
diff --git a/tests/typ/layout/grid-positioning.typ b/tests/typ/layout/grid-positioning.typ
deleted file mode 100644
index 228fadf7..00000000
--- a/tests/typ/layout/grid-positioning.typ
+++ /dev/null
@@ -1,205 +0,0 @@
-// Test cell positioning in grids.
-
----
-#{
- show grid.cell: it => (it.x, it.y)
- grid(
- columns: 2,
- inset: 5pt,
- fill: aqua,
- gutter: 3pt,
- [Hello], [World],
- [Sweet], [Home]
- )
-}
-#{
- show table.cell: it => pad(rest: it.inset)[#(it.x, it.y)]
- table(
- columns: 2,
- gutter: 3pt,
- [Hello], [World],
- [Sweet], [Home]
- )
-}
-
----
-// Positioning cells in a different order than they appear
-#grid(
- columns: 2,
- [A], [B],
- grid.cell(x: 1, y: 2)[C], grid.cell(x: 0, y: 2)[D],
- grid.cell(x: 1, y: 1)[E], grid.cell(x: 0, y: 1)[F],
-)
-
----
-// Creating more rows by positioning out of bounds
-#grid(
- columns: 3,
- rows: 1.5em,
- inset: 5pt,
- fill: (x, y) => if (x, y) == (0, 0) { blue } else if (x, y) == (2, 3) { red } else { green },
- [A],
- grid.cell(x: 2, y: 3)[B]
-)
-
-#table(
- columns: (3em, 1em, 3em),
- rows: 1.5em,
- inset: (top: 0pt, bottom: 0pt, rest: 5pt),
- fill: (x, y) => if (x, y) == (0, 0) { blue } else if (x, y) == (2, 3) { red } else { green },
- align: (x, y) => (left, center, right).at(x),
- [A],
- table.cell(x: 2, y: 3)[B]
-)
-
----
-// Error: 3:3-3:42 attempted to place a second cell at column 0, row 0
-// Hint: 3:3-3:42 try specifying your cells in a different order
-#grid(
- [A],
- grid.cell(x: 0, y: 0)[This shall error]
-)
-
----
-// Error: 3:3-3:43 attempted to place a second cell at column 0, row 0
-// Hint: 3:3-3:43 try specifying your cells in a different order
-#table(
- [A],
- table.cell(x: 0, y: 0)[This shall error]
-)
-
----
-// Automatic position cell skips custom position cell
-#grid(
- grid.cell(x: 0, y: 0)[This shall not error],
- [A]
-)
-
----
-// Error: 4:3-4:36 cell could not be placed at invalid column 2
-#grid(
- columns: 2,
- [A],
- grid.cell(x: 2)[This shall error]
-)
-
----
-// Partial positioning
-#grid(
- columns: 3,
- rows: 1.5em,
- inset: 5pt,
- fill: aqua,
- [A], grid.cell(y: 1, fill: green)[B], [C], grid.cell(x: auto, y: 1, fill: green)[D], [E],
- grid.cell(y: 2, fill: green)[F], grid.cell(x: 0, fill: orange)[G], grid.cell(x: 0, y: auto, fill: orange)[H],
- grid.cell(x: 1, fill: orange)[I]
-)
-
-#table(
- columns: 3,
- rows: 1.5em,
- inset: 5pt,
- fill: aqua,
- [A], table.cell(y: 1, fill: green)[B], [C], table.cell(x: auto, y: 1, fill: green)[D], [E],
- table.cell(y: 2, fill: green)[F], table.cell(x: 0, fill: orange)[G], table.cell(x: 0, y: auto, fill: orange)[H],
- table.cell(x: 1, fill: orange)[I]
-)
-
----
-// Error: 4:3-4:21 cell could not be placed in row 0 because it was full
-// Hint: 4:3-4:21 try specifying your cells in a different order
-#grid(
- columns: 2,
- [A], [B],
- grid.cell(y: 0)[C]
-)
-
----
-// Error: 4:3-4:22 cell could not be placed in row 0 because it was full
-// Hint: 4:3-4:22 try specifying your cells in a different order
-#table(
- columns: 2,
- [A], [B],
- table.cell(y: 0)[C]
-)
-
----
-// Doc example 1
-#set page(width: auto)
-#show grid.cell: it => {
- if it.y == 0 {
- set text(white)
- strong(it)
- } else {
- // For the second row and beyond, we will write the day number for each
- // cell.
-
- // In general, a cell's index is given by cell.x + columns * cell.y.
- // Days start in the second grid row, so we subtract 1 row.
- // But the first day is day 1, not day 0, so we add 1.
- let day = it.x + 7 * (it.y - 1) + 1
- if day <= 31 {
- // Place the day's number at the top left of the cell.
- // Only if the day is valid for this month (not 32 or higher).
- place(top + left, dx: 2pt, dy: 2pt, text(8pt, red.darken(40%))[#day])
- }
- it
- }
-}
-
-#grid(
- fill: (x, y) => if y == 0 { gray.darken(50%) },
- columns: (30pt,) * 7,
- rows: (auto, 30pt),
- // Events will be written at the bottom of each day square.
- align: bottom,
- inset: 5pt,
- stroke: (thickness: 0.5pt, dash: "densely-dotted"),
-
- [Sun], [Mon], [Tue], [Wed], [Thu], [Fri], [Sat],
-
- // This event will occur on the first Friday (sixth column).
- grid.cell(x: 5, fill: yellow.darken(10%))[Call],
-
- // This event will occur every Monday (second column).
- // We have to repeat it 5 times so it occurs every week.
- ..(grid.cell(x: 1, fill: red.lighten(50%))[Meet],) * 5,
-
- // This event will occur at day 19.
- grid.cell(x: 4, y: 3, fill: orange.lighten(25%))[Talk],
-
- // These events will occur at the second week, where available.
- grid.cell(y: 2, fill: aqua)[Chat],
- grid.cell(y: 2, fill: aqua)[Walk],
-)
-
----
-// Doc example 2
-#set page(width: auto)
-#show table.cell: it => {
- if it.x == 0 or it.y == 0 {
- set text(white)
- strong(it)
- } else if it.body == [] {
- // Replace empty cells with 'N/A'
- pad(rest: it.inset)[_N/A_]
- } else {
- it
- }
-}
-
-#table(
- fill: (x, y) => if x == 0 or y == 0 { gray.darken(50%) },
- columns: 4,
- [], [Exam 1], [Exam 2], [Exam 3],
- ..([John], [Mary], [Jake], [Robert]).map(table.cell.with(x: 0)),
-
- // Mary got grade A on Exam 3.
- table.cell(x: 3, y: 2, fill: green)[A],
-
- // Everyone got grade A on Exam 2.
- ..(table.cell(x: 2, fill: green)[A],) * 4,
-
- // Robert got grade B on other exams.
- ..(table.cell(y: 4, fill: aqua)[B],) * 2,
-)
diff --git a/tests/typ/layout/grid-rowspan-basic.typ b/tests/typ/layout/grid-rowspan-basic.typ
deleted file mode 100644
index bbd1c047..00000000
--- a/tests/typ/layout/grid-rowspan-basic.typ
+++ /dev/null
@@ -1,252 +0,0 @@
-#grid(
- columns: 4,
- fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
- inset: 5pt,
- align: center,
- grid.cell(rowspan: 2, fill: orange)[*Left*],
- [Right A], [Right A], [Right A],
- [Right B], grid.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide],
- [Left A], [Left A],
- [Left B], [Left B], grid.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long]
-)
-
-#table(
- columns: 4,
- fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
- inset: 5pt,
- align: center,
- table.cell(rowspan: 2, fill: orange)[*Left*],
- [Right A], [Right A], [Right A],
- [Right B], table.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide],
- [Left A], [Left A],
- [Left B], [Left B], table.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long]
-)
-
----
-#grid(
- columns: 4,
- fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
- inset: 5pt,
- align: center,
- gutter: 3pt,
- grid.cell(rowspan: 2, fill: orange)[*Left*],
- [Right A], [Right A], [Right A],
- [Right B], grid.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide],
- [Left A], [Left A],
- [Left B], [Left B], grid.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long]
-)
-
-#table(
- columns: 4,
- fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
- inset: 5pt,
- align: center,
- gutter: 3pt,
- table.cell(rowspan: 2, fill: orange)[*Left*],
- [Right A], [Right A], [Right A],
- [Right B], table.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide],
- [Left A], [Left A],
- [Left B], [Left B], table.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long]
-)
-
----
-// Fixed-size rows
-#set page(height: 10em)
-#grid(
- columns: 2,
- rows: 1.5em,
- fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
- grid.cell(rowspan: 3)[R1], [b],
- [c],
- [d],
- [e], [f],
- grid.cell(rowspan: 5)[R2], [h],
- [i],
- [j],
- [k],
- [l],
- [m], [n]
-)
-
----
-// Cell coordinate tests
-#set page(height: 10em)
-#show table.cell: it => [(#it.x, #it.y)]
-#table(
- columns: 3,
- fill: red,
- [a], [b], table.cell(rowspan: 2)[c],
- table.cell(colspan: 2)[d],
- table.cell(colspan: 3, rowspan: 10)[a],
- table.cell(colspan: 2)[b],
-)
-#table(
- columns: 3,
- gutter: 3pt,
- fill: red,
- [a], [b], table.cell(rowspan: 2)[c],
- table.cell(colspan: 2)[d],
- table.cell(colspan: 3, rowspan: 9)[a],
- table.cell(colspan: 2)[b],
-)
-
----
-// Auto row expansion
-#set page(height: 10em)
-#grid(
- columns: (1em, 1em),
- rows: (0.5em, 0.5em, auto),
- fill: orange,
- gutter: 3pt,
- grid.cell(rowspan: 4, [x x x x] + place(bottom)[*Bot*]),
- [a],
- [b],
- [c],
- [d]
-)
-
----
-// Excessive rowspan (no gutter)
-#set page(height: 10em)
-#table(
- columns: 4,
- fill: red,
- [a], [b], table.cell(rowspan: 2)[c], [d],
- table.cell(colspan: 2, stroke: (bottom: aqua + 2pt))[e], table.cell(stroke: (bottom: aqua))[f],
- table.cell(colspan: 2, rowspan: 10)[R1], table.cell(colspan: 2, rowspan: 10)[R2],
- [b],
-)
-
----
-// Excessive rowspan (with gutter)
-#set page(height: 10em)
-#table(
- columns: 4,
- gutter: 3pt,
- fill: red,
- [a], [b], table.cell(rowspan: 2)[c], [d],
- table.cell(colspan: 2, stroke: (bottom: aqua + 2pt))[e], table.cell(stroke: (bottom: aqua))[f],
- table.cell(colspan: 2, rowspan: 10)[R1], table.cell(colspan: 2, rowspan: 10)[R2],
- [b],
-)
-
----
-// Fractional rows
-// They cause the auto row to expand more than needed.
-#set page(height: 10em)
-#grid(
- fill: red,
- gutter: 3pt,
- columns: 3,
- rows: (1em, auto, 1fr),
- [a], [b], grid.cell(rowspan: 3, block(height: 4em, width: 1em, fill: orange)),
- [c], [d],
- [e], [f]
-)
-
----
-// Fractional rows
-#set page(height: 10em)
-#grid(
- fill: red,
- gutter: 3pt,
- columns: 3,
- rows: (1fr, auto, 1em),
- [a], [b], grid.cell(rowspan: 3, block(height: 4em, width: 1em, fill: orange)),
- [c], [d],
- [e], [f]
-)
-
----
-// Cell order
-#let count = counter("count")
-#show grid.cell: it => {
- count.step()
- count.display()
-}
-
-#grid(
- columns: (2em,) * 3,
- stroke: aqua,
- rows: 1.2em,
- fill: (x, y) => if calc.odd(x + y) { red } else { orange },
- [a], grid.cell(rowspan: 2)[b], grid.cell(rowspan: 2)[c],
- [d],
- grid.cell(rowspan: 2)[f], [g], [h],
- [i], [j],
- [k], [l], [m],
- grid.cell(rowspan: 2)[n], [o], [p],
- [q], [r],
- [s], [t], [u]
-)
-
----
-#table(
- columns: 3,
- rows: (auto, auto, auto, 2em),
- gutter: 3pt,
- table.cell(rowspan: 4)[a \ b\ c\ d\ e], [c], [d],
- [e], table.cell(breakable: false, rowspan: 2)[f],
- [g]
-)
-
----
-// Test cell breakability
-#show grid.cell: it => {
- assert.eq(it.breakable, (it.x, it.y) != (0, 6) and (it.y in (2, 5, 6) or (it.x, it.y) in ((0, 1), (2, 3), (1, 7))))
- it.breakable
-}
-#grid(
- columns: 3,
- rows: (6pt, 1fr, auto, 1%, 1em, auto, auto, 0.2in),
- row-gutter: (0pt, 0pt, 0pt, auto),
- [a], [b], [c],
- grid.cell(rowspan: 3)[d], [e], [f],
- [g], [h],
- [i], grid.cell(rowspan: 2)[j],
- [k],
- grid.cell(y: 5)[l],
- grid.cell(y: 6, breakable: false)[m], grid.cell(y: 6, breakable: true)[n],
- grid.cell(y: 7, breakable: false)[o], grid.cell(y: 7, breakable: true)[p], grid.cell(y: 7, breakable: auto)[q]
-)
-
----
-#table(
- columns: 2,
- table.cell(stroke: (bottom: red))[a], [b],
- table.hline(stroke: green),
- table.cell(stroke: (top: yellow, left: green, right: aqua, bottom: blue), colspan: 1, rowspan: 2)[d], table.cell(colspan: 1, rowspan: 2)[e],
- [f],
- [g]
-)
-
----
-#table(
- columns: 2,
- gutter: 3pt,
- table.cell(stroke: (bottom: red))[a], [b],
- table.hline(stroke: green),
- table.cell(stroke: (top: yellow, left: green, right: aqua, bottom: blue), colspan: 1, rowspan: 2)[d], table.cell(colspan: 1, rowspan: 2)[e],
- [f],
- [g]
-)
-
----
-// Block below shouldn't expand to the end of the page, but stay within its
-// rows' boundaries.
-#set page(height: 9em)
-#table(
- rows: (1em, 1em, 1fr, 1fr, auto),
- table.cell(rowspan: 2, block(width: 2em, height: 100%, fill: red)),
- table.cell(rowspan: 2, block(width: 2em, height: 100%, fill: red)),
- [a]
-)
-
----
-#set page(height: 7em)
-#table(
- columns: 3,
- [], [], table.cell(breakable: true, rowspan: 2, block(width: 2em, height: 100%, fill: red)),
- table.cell(breakable: false, block(width: 2em, height: 100%, fill: red)),
- table.cell(breakable: false, rowspan: 2, block(width: 2em, height: 100%, fill: red)),
-)
diff --git a/tests/typ/layout/grid-rowspan-split-1.typ b/tests/typ/layout/grid-rowspan-split-1.typ
deleted file mode 100644
index e247fa80..00000000
--- a/tests/typ/layout/grid-rowspan-split-1.typ
+++ /dev/null
@@ -1,89 +0,0 @@
-// Rowspan split tests
-
----
-#set page(height: 10em)
-#table(
- columns: 2,
- rows: (auto, auto, 3em),
- fill: red,
- [a], table.cell(rowspan: 3, block(width: 50%, height: 10em, fill: orange) + place(bottom)[*ZD*]),
- [e],
- [f]
-)
-
----
-#set page(height: 10em)
-#table(
- columns: 2,
- rows: (auto, auto, 3em),
- row-gutter: 1em,
- fill: red,
- [a], table.cell(rowspan: 3, block(width: 50%, height: 10em, fill: orange) + place(bottom)[*ZD*]),
- [e],
- [f]
-)
-
----
-#set page(height: 5em)
-#table(
- columns: 2,
- fill: red,
- inset: 0pt,
- table.cell(fill: orange, rowspan: 10, place(bottom)[*Z*] + [x\ ] * 10 + place(bottom)[*ZZ*]),
- ..([y],) * 10,
- [a], [b],
-)
-
----
-#set page(height: 5em)
-#table(
- columns: 2,
- fill: red,
- inset: 0pt,
- gutter: 2pt,
- table.cell(fill: orange, rowspan: 10, place(bottom)[*Z*] + [x\ ] * 10 + place(bottom)[*ZZ*]),
- ..([y],) * 10,
- [a], [b],
-)
-
----
-#set page(height: 5em)
-#table(
- columns: 2,
- fill: red,
- inset: 0pt,
- table.cell(fill: orange, rowspan: 10, breakable: false, place(bottom)[*Z*] + [x\ ] * 10 + place(bottom)[*ZZ*]),
- ..([y],) * 10,
- [a], [b],
-)
-
----
-#set page(height: 5em)
-#table(
- columns: 2,
- fill: red,
- inset: 0pt,
- gutter: 2pt,
- table.cell(fill: orange, rowspan: 10, breakable: false, place(bottom)[*Z*] + [x\ ] * 10 + place(bottom)[*ZZ*]),
- ..([y],) * 10,
- [a], [b],
-)
-
----
-#set page(height: 5em)
-#grid(
- columns: 2,
- stroke: red,
- inset: 5pt,
- grid.cell(rowspan: 5)[a\ b\ c\ d\ e]
-)
-
----
-#set page(height: 5em)
-#table(
- columns: 2,
- gutter: 3pt,
- stroke: red,
- inset: 5pt,
- table.cell(rowspan: 5)[a\ b\ c\ d\ e]
-)
diff --git a/tests/typ/layout/grid-rowspan-split-2.typ b/tests/typ/layout/grid-rowspan-split-2.typ
deleted file mode 100644
index 189feed3..00000000
--- a/tests/typ/layout/grid-rowspan-split-2.typ
+++ /dev/null
@@ -1,37 +0,0 @@
-// Rowspan split without ending at the auto row
-
----
-#set page(height: 6em)
-#table(
- rows: (4em,) * 7 + (auto,) + (4em,) * 7,
- columns: 2,
- column-gutter: 1em,
- row-gutter: (1em, 2em) * 4,
- fill: (x, y) => if calc.odd(x + y) { orange.lighten(20%) } else { red },
- table.cell(rowspan: 15, [a \ ] * 15),
- [] * 15
-)
-
----
-#set page(height: 6em)
-#table(
- rows: (4em,) * 7 + (auto,) + (4em,) * 7,
- columns: 2,
- column-gutter: 1em,
- row-gutter: (1em, 2em) * 4,
- fill: (x, y) => if calc.odd(x + y) { green } else { green.darken(40%) },
- table.cell(rowspan: 15, block(fill: blue, width: 2em, height: 4em * 14 + 3em)),
- [] * 15
-)
-
----
-#set page(height: 6em)
-#table(
- rows: (3em,) * 15,
- columns: 2,
- column-gutter: 1em,
- row-gutter: (1em, 2em) * 4,
- fill: (x, y) => if calc.odd(x + y) { aqua } else { blue },
- table.cell(breakable: true, rowspan: 15, [a \ ] * 15),
- [] * 15
-)
diff --git a/tests/typ/layout/grid-rowspan-split-3.typ b/tests/typ/layout/grid-rowspan-split-3.typ
deleted file mode 100644
index 4c3ce7d8..00000000
--- a/tests/typ/layout/grid-rowspan-split-3.typ
+++ /dev/null
@@ -1,108 +0,0 @@
-// Some splitting corner cases
-
----
-// Inside the larger rowspan's range, there's an unbreakable rowspan and a
-// breakable rowspan. This should work normally.
-// The auto row will also expand ignoring the last fractional row.
-#set page(height: 10em)
-#table(
- gutter: 0.5em,
- columns: 2,
- rows: (2em,) * 10 + (auto, auto, 2em, 1fr),
- fill: (_, y) => if calc.even(y) { aqua } else { blue },
- table.cell(rowspan: 14, block(width: 2em, height: 2em * 10 + 2em + 5em, fill: red)[]),
- ..([a],) * 5,
- table.cell(rowspan: 3)[a\ b],
- table.cell(rowspan: 5, [a\ b\ c\ d\ e\ f\ g\ h]),
- [z]
-)
-
----
-// Inset moving to next region bug
-#set page(width: 10cm, height: 2.5cm, margin: 0.5cm)
-#set text(size: 11pt)
-#table(
- columns: (1fr, 1fr, 1fr),
- [A],
- [B],
- [C],
- [D],
- table.cell(rowspan: 2, lorem(4)),
- [E],
- [F],
- [G],
-)
-
----
-// Second lorem must be sent to the next page, too big
-#set page(width: 10cm, height: 9cm, margin: 1cm)
-#set text(size: 11pt)
-#table(
- columns: (1fr, 1fr, 1fr),
- align: center,
- rows: (4cm, auto),
- [A], [B], [C],
- table.cell(rowspan: 4, breakable: false, lorem(10)),
- [D],
- table.cell(rowspan: 2, breakable: false, lorem(20)),
- [E],
-)
-
----
-// Auto row must expand properly in both cases
-#set text(10pt)
-#show table.cell: it => if it.x == 0 { it } else { layout(size => size.height) }
-#table(
- columns: 2,
- rows: (1em, auto, 2em, 3em, 4em),
- gutter: 3pt,
- table.cell(rowspan: 5, block(fill: orange, height: 15em)[a]),
- [b],
- [c],
- [d],
- [e],
- [f]
-)
-
-#table(
- columns: 2,
- rows: (1em, auto, 2em, 3em, 4em),
- gutter: 3pt,
- table.cell(rowspan: 5, breakable: false, block(fill: orange, height: 15em)[a]),
- [b],
- [c],
- [d],
- [e],
- [f]
-)
-
----
-// Expanding on unbreakable auto row
-#set page(height: 7em, margin: (bottom: 2em))
-#grid(
- columns: 2,
- rows: (1em, 1em, auto, 1em, 1em, 1em),
- fill: (x, y) => if x == 0 { aqua } else { blue },
- stroke: black,
- gutter: 2pt,
- grid.cell(rowspan: 5, block(height: 10em)[a]),
- [a],
- [b],
- grid.cell(breakable: false, v(3em) + [c]),
- [d],
- [e],
- [f], [g]
-)
-
----
-#show table.cell.where(x: 0): strong
-#show table.cell.where(y: 0): strong
-#set page(height: 13em)
-#let lets-repeat(thing, n) = ((thing + colbreak(),) * (calc.max(0, n - 1)) + (thing,)).join()
-#table(
- columns: 4,
- fill: (x, y) => if x == 0 or y == 0 { gray },
- [], [Test 1], [Test 2], [Test 3],
- table.cell(rowspan: 15, align: horizon, lets-repeat((rotate(-90deg, reflow: true)[*All Tests*]), 3)),
- ..([123], [456], [789]) * 15
-)
diff --git a/tests/typ/layout/grid-rtl.typ b/tests/typ/layout/grid-rtl.typ
deleted file mode 100644
index cea67d96..00000000
--- a/tests/typ/layout/grid-rtl.typ
+++ /dev/null
@@ -1,195 +0,0 @@
-// Test RTL grid.
-
----
-#set text(dir: rtl)
-- מימין לשמאל
-
----
-#set text(dir: rtl)
-#table(columns: 2)[A][B][C][D]
-
----
-// Test interaction between RTL and colspans
-#set text(dir: rtl)
-#grid(
- columns: 4,
- fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
- inset: 5pt,
- align: center,
- grid.cell(colspan: 4)[*Full Header*],
- grid.cell(colspan: 2, fill: orange)[*Half*],
- grid.cell(colspan: 2, fill: orange.darken(10%))[*Half*],
- [*A*], [*B*], [*C*], [*D*],
- [1], [2], [3], [4],
- [5], grid.cell(colspan: 3, fill: orange.darken(10%))[6],
- grid.cell(colspan: 2, fill: orange)[7], [8], [9],
- [10], grid.cell(colspan: 2, fill: orange.darken(10%))[11], [12]
-)
-
-#grid(
- columns: 4,
- fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
- inset: 5pt,
- align: center,
- gutter: 3pt,
- grid.cell(colspan: 4)[*Full Header*],
- grid.cell(colspan: 2, fill: orange)[*Half*],
- grid.cell(colspan: 2, fill: orange.darken(10%))[*Half*],
- [*A*], [*B*], [*C*], [*D*],
- [1], [2], [3], [4],
- [5], grid.cell(colspan: 3, fill: orange.darken(10%))[6],
- grid.cell(colspan: 2, fill: orange)[7], [8], [9],
- [10], grid.cell(colspan: 2, fill: orange.darken(10%))[11], [12]
-)
-
----
-#set text(dir: rtl)
-#table(
- columns: 4,
- fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
- inset: 5pt,
- align: center,
- table.cell(colspan: 4)[*Full Header*],
- table.cell(colspan: 2, fill: orange)[*Half*],
- table.cell(colspan: 2, fill: orange.darken(10%))[*Half*],
- [*A*], [*B*], [*C*], [*D*],
- [1], [2], [3], [4],
- [5], table.cell(colspan: 3, fill: orange.darken(10%))[6],
- table.cell(colspan: 2, fill: orange)[7], [8], [9],
- [10], table.cell(colspan: 2, fill: orange.darken(10%))[11], [12]
-)
-
-#table(
- columns: 4,
- fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
- inset: 5pt,
- align: center,
- gutter: 3pt,
- table.cell(colspan: 4)[*Full Header*],
- table.cell(colspan: 2, fill: orange)[*Half*],
- table.cell(colspan: 2, fill: orange.darken(10%))[*Half*],
- [*A*], [*B*], [*C*], [*D*],
- [1], [2], [3], [4],
- [5], table.cell(colspan: 3, fill: orange.darken(10%))[6],
- table.cell(colspan: 2, fill: orange)[7], [8], [9],
- [10], table.cell(colspan: 2, fill: orange.darken(10%))[11], [12]
-)
-
----
-// Test multiple regions
-#set page(height: 5em)
-#set text(dir: rtl)
-#grid(
- stroke: red,
- fill: aqua,
- columns: 4,
- [a], [b], [c], [d],
- [a], grid.cell(colspan: 2)[e, f, g, h, i], [f],
- [e], [g], grid.cell(colspan: 2)[eee\ e\ e\ e],
- grid.cell(colspan: 4)[eeee e e e]
-)
-
----
-// Test left and right for vlines in RTL
-#set text(dir: rtl)
-#grid(
- columns: 3,
- inset: 5pt,
- grid.vline(stroke: red, position: left), grid.vline(stroke: green, position: right), [a],
- grid.vline(stroke: red, position: left), grid.vline(stroke: 2pt, position: right), [b],
- grid.vline(stroke: red, position: left), grid.vline(stroke: 2pt, position: right), [c],
- grid.vline(stroke: aqua, position: right)
-)
-
-#grid(
- columns: 3,
- inset: 5pt,
- gutter: 3pt,
- grid.vline(stroke: green, position: left), grid.vline(stroke: red, position: right), [a],
- grid.vline(stroke: blue, position: left), grid.vline(stroke: red, position: right), [b],
- grid.vline(stroke: blue, position: left), grid.vline(stroke: red, position: right), [c],
- grid.vline(stroke: 2pt, position: right)
-)
-
-#grid(
- columns: 3,
- inset: 5pt,
- grid.vline(stroke: green, position: start), grid.vline(stroke: red, position: end), [a],
- grid.vline(stroke: 2pt, position: start), grid.vline(stroke: red, position: end), [b],
- grid.vline(stroke: 2pt, position: start), grid.vline(stroke: red, position: end), [c],
- grid.vline(stroke: 2pt, position: start)
-)
-
-#grid(
- columns: 3,
- inset: 5pt,
- gutter: 3pt,
- grid.vline(stroke: green, position: start), grid.vline(stroke: red, position: end), [a],
- grid.vline(stroke: blue, position: start), grid.vline(stroke: red, position: end), [b],
- grid.vline(stroke: blue, position: start), grid.vline(stroke: red, position: end), [c],
- grid.vline(stroke: 2pt, position: start)
-)
-
----
-// Error: 3:8-3:34 cannot place vertical line at the 'end' position of the end border (x = 1)
-// Hint: 3:8-3:34 set the line's position to 'start' or place it at a smaller 'x' index
-#set text(dir: rtl)
-#grid(
- [a], grid.vline(position: left)
-)
-
----
-#set text(dir: rtl)
-
-#grid(
- columns: 4,
- fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
- inset: 5pt,
- align: center,
- grid.cell(rowspan: 2, fill: orange)[*Left*],
- [Right A], [Right A], [Right A],
- [Right B], grid.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide],
- [Left A], [Left A],
- [Left B], [Left B], grid.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long]
-)
-
-#table(
- columns: 4,
- fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) },
- inset: 5pt,
- align: center,
- gutter: 3pt,
- table.cell(rowspan: 2, fill: orange)[*Left*],
- [Right A], [Right A], [Right A],
- [Right B], table.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide],
- [Left A], [Left A],
- [Left B], [Left B], table.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long]
-)
-
----
-#set page(height: 10em)
-#set text(dir: rtl)
-#table(
- columns: 2,
- rows: (auto, auto, 3em),
- row-gutter: 1em,
- fill: red,
- [a], table.cell(rowspan: 3, block(width: 50%, height: 10em, fill: orange) + place(bottom)[*ZD*]),
- [e],
- [f]
-)
-
----
-// Headers
-#set page(height: 15em)
-#set text(dir: rtl)
-#table(
- columns: 5,
- align: center + horizon,
- table.header(
- table.cell(colspan: 5)[*Cool Zone*],
- table.cell(stroke: red)[*N1*], table.cell(stroke: aqua)[*N2*], [*D1*], [*D2*], [*Etc*],
- table.hline(start: 2, end: 3, stroke: yellow)
- ),
- ..range(0, 10).map(i => ([\##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten()
-)
diff --git a/tests/typ/layout/grid-stroke.typ b/tests/typ/layout/grid-stroke.typ
deleted file mode 100644
index 35a65ae6..00000000
--- a/tests/typ/layout/grid-stroke.typ
+++ /dev/null
@@ -1,434 +0,0 @@
-#let double-line = pattern(size: (1.5pt, 1.5pt), {
- place(line(stroke: .6pt, start: (0%, 50%), end: (100%, 50%)))
-})
-
-#table(
- stroke: (_, y) => if y != 1 { (bottom: black) },
- columns: 3,
- table.cell(colspan: 3, align: center)[*Epic Table*],
- align(center)[*Name*], align(center)[*Age*], align(center)[*Data*],
- table.hline(stroke: (paint: double-line, thickness: 2pt)),
- [John], [30], [None],
- [Martha], [20], [A],
- [Joseph], [35], [D]
-)
-
----
-// Test folding
-#set grid(stroke: red)
-#set grid(stroke: 5pt)
-
-#grid(
- inset: 10pt,
- columns: 2,
- stroke: stroke(dash: "loosely-dotted"),
- grid.vline(start: 2, end: 3, stroke: (paint: green, dash: none)),
- [a], [b],
- grid.hline(end: 1, stroke: blue),
- [c], [d],
- [e], grid.cell(stroke: aqua)[f]
-)
-
----
-// Test set rules on cells and folding
-#set table.cell(stroke: 4pt)
-#set table.cell(stroke: blue)
-#set table.hline(stroke: red)
-#set table.hline(stroke: 0.75pt)
-#set table.vline(stroke: 0.75pt)
-#set table.vline(stroke: aqua)
-
-#table(
- columns: 3,
- gutter: 3pt,
- inset: 5pt,
- [a], [b], table.vline(position: end), [c],
- [d], [e], [f],
- table.hline(position: bottom),
- [g], [h], [i],
-)
-
----
-// Test stroke field on cell show rules
-#set grid.cell(stroke: (x: 4pt))
-#set grid.cell(stroke: (x: blue))
-#show grid.cell: it => {
- test(it.stroke, (left: stroke(paint: blue, thickness: 4pt, dash: "loosely-dotted"), right: blue + 4pt, top: stroke(thickness: 1pt), bottom: none))
- it
-}
-#grid(
- stroke: (left: (dash: "loosely-dotted")),
- inset: 5pt,
- grid.hline(stroke: red),
- grid.cell(stroke: (top: 1pt))[a], grid.vline(stroke: yellow),
-)
-
----
-#table(
- columns: 3,
- [a], table.cell(colspan: 2)[b c],
- table.cell(stroke: blue)[d], [e], [f],
- [g], [h], table.cell(stroke: (left: yellow, top: green, right: aqua, bottom: red))[i],
- [j], [k], [l],
- table.cell(stroke: 3pt)[m], [n], table.cell(stroke: (dash: "loosely-dotted"))[o],
-)
-
----
-// Test per-column stroke array
-#let t = table(
- columns: 3,
- stroke: (red, blue, green),
- [a], [b], [c],
- [d], [e], [f],
- [h], [i], [j],
-)
-#t
-#set text(dir: rtl)
-#t
-
----
-#grid(
- columns: 3,
- inset: 3pt,
- stroke: (x, _) => (right: (5pt, (dash: "dotted")).at(calc.rem(x, 2)), bottom: (dash: "densely-dotted")),
- grid.vline(x: 0, stroke: red),
- grid.vline(x: 1, stroke: red),
- grid.vline(x: 2, stroke: red),
- grid.vline(x: 3, stroke: red),
- grid.hline(y: 0, end: 1, stroke: blue),
- grid.hline(y: 1, end: 1, stroke: blue),
- grid.cell[a],
- [b], [c]
-)
-
----
-#set page(height: 5em)
-#table(
- columns: 3,
- inset: 3pt,
- table.hline(y: 0, end: none, stroke: 3pt + blue),
- table.vline(x: 0, end: none, stroke: 3pt + green),
- table.hline(y: 5, end: none, stroke: 3pt + red),
- table.vline(x: 3, end: none, stroke: 3pt + yellow),
- [a], [b], [c],
- [a], [b], [c],
- [a], [b], [c],
- [a], [b], [c],
- [a], [b], [c],
-)
-
----
-// Automatically positioned lines
-// Plus stroke thickness ordering
-#table(
- columns: 3,
- table.hline(stroke: red + 5pt),
- table.vline(stroke: blue + 5pt),
- table.vline(stroke: 2pt),
- [a],
- table.vline(x: 1, stroke: aqua + 5pt),
- [b],
- table.vline(stroke: aqua + 5pt),
- [c],
- table.vline(stroke: yellow + 5.2pt),
- table.hline(stroke: green + 5pt),
- [a], [b], [c],
- [a], table.hline(stroke: green + 2pt), table.vline(stroke: 2pt), [b], [c],
-)
-
----
-// Line specification order priority
-// The last line should be blue, not red.
-// The middle aqua line should be gone due to the 'none' override.
-#grid(
- columns: 2,
- inset: 2pt,
- grid.hline(y: 2, stroke: red + 5pt),
- grid.vline(),
- [a], [b],
- grid.hline(stroke: red),
- grid.hline(stroke: none),
- [c], grid.cell(stroke: (top: aqua))[d],
- grid.hline(stroke: blue),
-)
-
----
-// Position: bottom and position: end with gutter should have a visible effect
-// of moving the lines after the next track.
-#table(
- columns: 3,
- gutter: 3pt,
- stroke: blue,
- table.hline(end: 2, stroke: red),
- table.hline(end: 2, stroke: aqua, position: bottom),
- table.vline(end: 2, stroke: green), [a], table.vline(end: 2, stroke: green), table.vline(end: 2, position: end, stroke: orange), [b], table.vline(end: 2, stroke: aqua, position: end), table.vline(end: 2, stroke: green), [c], table.vline(end: 2, stroke: green),
- [d], [e], [f],
- table.hline(end: 2, stroke: red),
- [g], [h], [ie],
- table.hline(end: 2, stroke: green),
-)
-
----
-// Using position: bottom and position: end without gutter should be the same
-// as placing a line after the next track.
-#table(
- columns: 3,
- stroke: blue,
- table.hline(end: 2, stroke: red),
- table.hline(end: 2, stroke: aqua, position: bottom),
- table.vline(end: 2, stroke: green), [a], table.vline(end: 2, stroke: green), [b], table.vline(end: 2, stroke: aqua, position: end), table.vline(end: 2, stroke: green), [c], table.vline(end: 2, stroke: green),
- table.hline(end: 2, stroke: 5pt),
- [d], [e], [f],
- table.hline(end: 2, stroke: red),
- [g], [h], [i],
- table.hline(end: 2, stroke: red),
-)
-
----
-// Test left and right for grid vlines.
-#grid(
- columns: 3,
- inset: 5pt,
- grid.vline(stroke: green, position: left), grid.vline(stroke: red, position: right), [a],
- grid.vline(stroke: 2pt, position: left), grid.vline(stroke: red, position: right), [b],
- grid.vline(stroke: 2pt, position: left), grid.vline(stroke: red, position: right), [c],
- grid.vline(stroke: 2pt, position: left)
-)
-
-#grid(
- columns: 3,
- inset: 5pt,
- gutter: 3pt,
- grid.vline(stroke: green, position: left), grid.vline(stroke: red, position: right), [a],
- grid.vline(stroke: blue, position: left), grid.vline(stroke: red, position: right), [b],
- grid.vline(stroke: blue, position: left), grid.vline(stroke: red, position: right), [c],
- grid.vline(stroke: 2pt, position: left)
-)
-
----
-// Test left and right for table vlines.
-#table(
- columns: 3,
- inset: 5pt,
- table.vline(stroke: green, position: left), table.vline(stroke: red, position: right), [a],
- table.vline(stroke: 2pt, position: left), table.vline(stroke: red, position: right), [b],
- table.vline(stroke: 2pt, position: left), table.vline(stroke: red, position: right), [c],
- table.vline(stroke: 2pt, position: left)
-)
-
-#table(
- columns: 3,
- inset: 5pt,
- gutter: 3pt,
- table.vline(stroke: green, position: left), table.vline(stroke: red, position: right), [a],
- table.vline(stroke: blue, position: left), table.vline(stroke: red, position: right), [b],
- table.vline(stroke: blue, position: left), table.vline(stroke: red, position: right), [c],
- table.vline(stroke: 2pt, position: left)
-)
-
----
-// Hlines and vlines should always appear on top of cell strokes.
-#table(
- columns: 3,
- stroke: aqua,
- table.vline(stroke: red, position: end), [a], table.vline(stroke: red), [b], [c],
- table.cell(stroke: blue)[d], [e], [f],
- table.hline(stroke: red),
- [g], table.cell(stroke: blue)[h], [i],
-)
-
-#table(
- columns: 3,
- gutter: 3pt,
- stroke: aqua,
- table.vline(stroke: red, position: end), [a], table.vline(stroke: red), [b], [c],
- table.cell(stroke: blue)[d], [e], [f],
- table.hline(stroke: red),
- [g], table.cell(stroke: blue)[h], [i],
-)
-
----
-// Ensure cell stroke overrides always appear on top.
-#table(
- columns: 2,
- stroke: black,
- table.cell(stroke: red)[a], [b],
- [c], [d],
-)
-
-#table(
- columns: 2,
- table.cell(stroke: red)[a], [b],
- [c], [d],
-)
-
----
-// Error: 7:3-7:32 cannot place horizontal line at the 'bottom' position of the bottom border (y = 2)
-// Hint: 7:3-7:32 set the line's position to 'top' or place it at a smaller 'y' index
-#table(
- columns: 2,
- [a], [b],
- [c], [d],
- table.hline(stroke: aqua),
- table.hline(position: top),
- table.hline(position: bottom)
-)
-
----
-// Test partial border line overrides
-#set page(width: auto, height: 7em, margin: (bottom: 1em))
-#table(
- columns: 4,
- stroke: (x, y) => if y == 0 or y == 4 { orange } else { aqua },
- table.hline(stroke: blue, start: 1, end: 2), table.cell(stroke: red, v(3em)), table.cell(stroke: blue)[b], table.cell(stroke: green)[c], [M],
- [a], [b], [c], [M],
- [d], [e], [f], [M],
- [g], [h], [i], [M],
- table.cell(stroke: red)[a], table.cell(stroke: blue)[b], table.cell(stroke: green)[c], [M],
- table.hline(stroke: blue, start: 1, end: 2),
-)
-
----
-// - Vline should be placed after the colspan.
-// - Hline should be placed under the full-width rowspan.
-#table(
- columns: 3,
- rows: 1.25em,
- inset: 1pt,
- stroke: none,
- table.cell(colspan: 2)[a], table.vline(stroke: red), table.hline(stroke: blue), [b],
- [c], [d], [e],
- table.cell(colspan: 3, rowspan: 2)[a], table.vline(stroke: blue), table.hline(stroke: red)
-)
-
----
-// Red line should be above [c] (hline skips the shortest rowspan).
-#set text(6pt)
-#table(
- rows: 1em,
- columns: 2,
- inset: 1.5pt,
- table.cell(rowspan: 3)[a], table.cell(rowspan: 2)[b],
- table.hline(stroke: red),
- [c]
-)
-
----
-// Error: 8:3-8:32 cannot place horizontal line at the 'bottom' position of the bottom border (y = 2)
-// Hint: 8:3-8:32 set the line's position to 'top' or place it at a smaller 'y' index
-#table(
- columns: 2,
- gutter: 3pt,
- [a], [b],
- [c], [d], table.vline(stroke: red),
- table.hline(stroke: aqua),
- table.hline(position: top),
- table.hline(position: bottom)
-)
-
----
-// Error: 6:3-6:28 cannot place vertical line at the 'end' position of the end border (x = 2)
-// Hint: 6:3-6:28 set the line's position to 'start' or place it at a smaller 'x' index
-#grid(
- columns: 2,
- [a], [b],
- grid.vline(stroke: aqua),
- grid.vline(position: start),
- grid.vline(position: end)
-)
-
----
-// Error: 7:3-7:28 cannot place vertical line at the 'end' position of the end border (x = 2)
-// Hint: 7:3-7:28 set the line's position to 'start' or place it at a smaller 'x' index
-#grid(
- columns: 2,
- gutter: 3pt,
- [a], [b],
- grid.vline(stroke: aqua),
- grid.vline(position: start),
- grid.vline(position: end)
-)
-
----
-// Error: 4:3-4:19 cannot place horizontal line at invalid row 3
-#grid(
- [a],
- [b],
- grid.hline(y: 3)
-)
-
----
-// Error: 5:3-5:19 cannot place horizontal line at invalid row 3
-#grid(
- gutter: 3pt,
- [a],
- [b],
- grid.hline(y: 3)
-)
-
----
-// Error: 4:3-4:20 cannot place vertical line at invalid column 3
-#table(
- columns: 2,
- [a], [b],
- table.vline(x: 3)
-)
-
----
-// Error: 5:3-5:20 cannot place vertical line at invalid column 3
-#table(
- columns: 2,
- gutter: 3pt,
- [a], [b],
- table.vline(x: 3)
-)
-
----
-// Error: 7-20 cannot use `table.hline` as a grid line; use `grid.hline` instead
-#grid(table.hline())
-
----
-// Error: 7-20 cannot use `table.vline` as a grid line; use `grid.vline` instead
-#grid(table.vline())
-
----
-// Error: 8-20 cannot use `grid.hline` as a table line; use `table.hline` instead
-#table(grid.hline())
-
----
-// Error: 8-20 cannot use `grid.vline` as a table line; use `table.vline` instead
-#table(grid.vline())
-
----
-// Error: 3:3-3:31 line cannot end before it starts
-#grid(
- columns: 3,
- grid.hline(start: 2, end: 1),
- [a], [b], [c],
-)
-
----
-// Error: 3:3-3:32 line cannot end before it starts
-#table(
- columns: 3,
- table.vline(start: 2, end: 1),
- [a], [b], [c],
- [d], [e], [f],
- [g], [h], [i],
-)
-
----
-// Error: 24-31 expected `top` or `bottom`, found horizon
-#table.hline(position: horizon)
-
----
-// Error: 24-30 expected `start`, `left`, `right`, or `end`, found center
-#table.vline(position: center)
-
----
-// Error: 24-29 expected `top` or `bottom`, found right
-#table.hline(position: right)
-
----
-// Error: 24-27 expected `start`, `left`, `right`, or `end`, found top
-#table.vline(position: top)
diff --git a/tests/typ/layout/grid-styling.typ b/tests/typ/layout/grid-styling.typ
deleted file mode 100644
index f83c3cc4..00000000
--- a/tests/typ/layout/grid-styling.typ
+++ /dev/null
@@ -1,160 +0,0 @@
-// Test grid styling options.
-
----
-#set page(height: 70pt)
-#set grid(fill: (x, y) => if calc.even(x + y) { rgb("aaa") })
-
-#grid(
- columns: (1fr,) * 3,
- stroke: 2pt + rgb("333"),
- [A], [B], [C], [], [], [D \ E \ F \ \ \ G], [H],
-)
-
----
-#grid(columns: 3, stroke: none, fill: green, [A], [B], [C])
-
----
-// Test general alignment.
-#grid(
- columns: 3,
- align: left,
- [Hello], [Hello], [Hello],
- [A], [B], [C],
-)
-
-// Test alignment with a function.
-#grid(
- columns: 3,
- align: (x, y) => (left, center, right).at(x),
- [Hello], [Hello], [Hello],
- [A], [B], [C],
-)
-
-// Test alignment with array.
-#grid(
- columns: (1fr, 1fr, 1fr),
- align: (left, center, right),
- [A], [B], [C]
-)
-
-// Test empty array.
-#set align(center)
-#grid(
- columns: (1fr, 1fr, 1fr),
- align: (),
- [A], [B], [C]
-)
-
-a
-
----
-// Test inset.
-#grid(
- columns: (1fr,) * 3,
- stroke: 2pt + rgb("333"),
- inset: 5pt,
- [A], [B], [C], [], [], [D \ E \ F \ \ \ G], [H],
-)
-
-#grid(
- columns: 3,
- inset: 10pt,
- fill: blue,
- [A], [B], [C]
-)
-
-#grid(
- columns: 3,
- inset: (y: 10pt),
- [A], [B], [C]
-)
-
-#grid(
- columns: 3,
- inset: (left: 20pt, rest: 10pt),
- stroke: 3pt + red,
- [A], [B], [C]
-)
-
-#grid(
- columns: 2,
- inset: (
- left: 20pt,
- right: 5pt,
- top: 10pt,
- bottom: 3pt,
- ),
- [A],
- [B],
-)
-
-#grid(
- columns: 3,
- fill: (x, y) => (if y == 0 { aqua } else { orange }).darken(x * 15%),
- inset: (x, y) => (left: if x == 0 { 0pt } else { 5pt }, right: if x == 0 { 5pt } else { 0pt }, y: if y == 0 { 0pt } else { 5pt }),
- [A], [B], [C],
- [A], [B], [C],
-)
-
-#grid(
- columns: 3,
- inset: (0pt, 5pt, 10pt),
- fill: (x, _) => aqua.darken(x * 15%),
- [A], [B], [C],
-)
-
----
-// Test inset folding
-#set grid(inset: 10pt)
-#set grid(inset: (left: 0pt))
-
-#grid(
- fill: red,
- inset: (right: 0pt),
- grid.cell(inset: (top: 0pt))[a]
-)
-
----
-// Test interaction with gutters.
-#grid(
- columns: (3em, 3em),
- fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
- align: (x, y) => (left, right).at(calc.rem(y, 2)),
- [A], [B],
- [C], [D],
- [E], [F],
- [G], [H]
-)
-
-#grid(
- columns: (3em, 3em),
- fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
- align: (x, y) => (left, right).at(calc.rem(y, 2)),
- row-gutter: 5pt,
- [A], [B],
- [C], [D],
- [E], [F],
- [G], [H]
-)
-
-#grid(
- columns: (3em, 3em),
- fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
- align: (x, y) => (left, right).at(calc.rem(y, 2)),
- column-gutter: 5pt,
- [A], [B],
- [C], [D],
- [E], [F],
- [G], [H]
-)
-
-#grid(
- columns: (3em, 3em),
- fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
- align: (x, y) => (left, right).at(calc.rem(y, 2)),
- gutter: 5pt,
- [A], [B],
- [C], [D],
- [E], [F],
- [G], [H]
-)
diff --git a/tests/typ/layout/hide.typ b/tests/typ/layout/hide.typ
deleted file mode 100644
index ede65c70..00000000
--- a/tests/typ/layout/hide.typ
+++ /dev/null
@@ -1,76 +0,0 @@
-// Test the `hide` function.
-
----
-AB #h(1fr) CD \
-#hide[A]B #h(1fr) C#hide[D]
----
-Hidden:
-#hide[#line(length: 100%)]
-#line(length: 100%)
----
-Hidden:
-#hide(table(rows: 2, columns: 2)[a][b][c][d])
-#table(rows: 2, columns: 2)[a][b][c][d]
----
-Hidden:
-#hide[
- #polygon((20%, 0pt),
- (60%, 0pt),
- (80%, 2cm),
- (0%, 2cm),)
-]
-#polygon((20%, 0pt),
- (60%, 0pt),
- (80%, 2cm),
- (0%, 2cm),)
----
-#set rect(
- inset: 8pt,
- fill: rgb("e4e5ea"),
- width: 100%,
-)
-
-Hidden:
-#hide[
-#grid(
- columns: (1fr, 1fr, 2fr),
- rows: (auto, 40pt),
- gutter: 3pt,
- rect[A],
- rect[B],
- rect[C],
- rect(height: 100%)[D],
-)
-]
-#grid(
- columns: (1fr, 1fr, 2fr),
- rows: (auto, 40pt),
- gutter: 3pt,
- rect[A],
- rect[B],
- rect[C],
- rect(height: 100%)[D],
-)
----
-
-Hidden:
-#hide[
-- 1
-- 2
- 1. A
- 2. B
-- 3
-]
-
-
-- 1
-- 2
- 1. A
- 2. B
-- 3
-
----
-Hidden:
-#hide(image("/assets/images/tiger.jpg", width: 5cm, height: 1cm,))
-
-#image("/assets/images/tiger.jpg", width: 5cm, height: 1cm,)
diff --git a/tests/typ/layout/list-attach.typ b/tests/typ/layout/list-attach.typ
deleted file mode 100644
index 9d043eb0..00000000
--- a/tests/typ/layout/list-attach.typ
+++ /dev/null
@@ -1,54 +0,0 @@
-// Test list attaching.
-
----
-// Test basic attached list.
-Attached to:
-- the bottom
-- of the paragraph
-
-Next paragraph.
-
----
-// Test that attached list isn't affected by block spacing.
-#show list: set block(above: 100pt)
-Hello
-- A
-World
-- B
-
----
-// Test non-attached list followed by attached list,
-// separated by only word.
-Hello
-
-- A
-
-World
-- B
-
----
-// Test non-attached tight list.
-#set block(spacing: 15pt)
-Hello
-- A
-World
-
-- B
-- C
-
-More.
-
----
-// Test that wide lists cannot be ...
-#set block(spacing: 15pt)
-Hello
-- A
-
-- B
-World
-
----
-// ... even if forced to.
-Hello
-#list(tight: false)[A][B]
-World
diff --git a/tests/typ/layout/list-marker.typ b/tests/typ/layout/list-marker.typ
deleted file mode 100644
index 9e28faad..00000000
--- a/tests/typ/layout/list-marker.typ
+++ /dev/null
@@ -1,34 +0,0 @@
-// Test list marker configuration.
-
----
-// Test en-dash.
-#set list(marker: [--])
-- A
-- B
-
----
-// Test that items are cycled.
-#set list(marker: ([--], [•]))
-- A
- - B
- - C
-
----
-// Test function.
-#set list(marker: n => if n == 1 [--] else [•])
-- A
-- B
- - C
- - D
- - E
-- F
-
----
-// Test that bare hyphen doesn't lead to cycles and crashes.
-#set list(marker: [-])
-- Bare hyphen is
-- a bad marker
-
----
-// Error: 19-21 array must contain at least one marker
-#set list(marker: ())
diff --git a/tests/typ/layout/list.typ b/tests/typ/layout/list.typ
deleted file mode 100644
index 7b938944..00000000
--- a/tests/typ/layout/list.typ
+++ /dev/null
@@ -1,57 +0,0 @@
-// Test bullet lists.
-
----
-_Shopping list_
-#list[Apples][Potatoes][Juice]
-
----
-- First level.
-
- - Second level.
- There are multiple paragraphs.
-
- - Third level.
-
- Still the same bullet point.
-
- - Still level 2.
-
-- At the top.
-
----
-- Level 1
- - Level #[
-2 through content block
-]
-
----
- - Top-level indent
-- is fine.
-
----
- - A
- - B
- - C
-- D
-
----
-// This works because tabs are used consistently.
- - A with 1 tab
- - B with 2 tabs
-
----
-// This doesn't work because of mixed tabs and spaces.
- - A with 2 spaces
- - B with 2 tabs
-
----
-// Edge cases.
--
-Not in list
--Nope
-
----
-// Alignment shouldn't affect marker
-#set align(horizon)
-
-- ABCDEF\ GHIJKL\ MNOPQR
diff --git a/tests/typ/layout/out-of-flow-in-block.typ b/tests/typ/layout/out-of-flow-in-block.typ
deleted file mode 100644
index 2461aa5d..00000000
--- a/tests/typ/layout/out-of-flow-in-block.typ
+++ /dev/null
@@ -1,61 +0,0 @@
-// Test out-of-flow items (place, counter updates, etc.) at the
-// beginning of a block not creating a frame just for them.
-
----
-// No item in the first region.
-#set page(height: 5cm, margin: 1cm)
-No item in the first region.
-#block(breakable: true, stroke: 1pt, inset: 0.5cm)[
- #rect(height: 2cm, fill: gray)
-]
-
----
-// Counter update in the first region.
-#set page(height: 5cm, margin: 1cm)
-Counter update.
-#block(breakable: true, stroke: 1pt, inset: 0.5cm)[
- #counter("dummy").step()
- #rect(height: 2cm, fill: gray)
-]
-
----
-// Placed item in the first region.
-#set page(height: 5cm, margin: 1cm)
-Placed item in the first region.
-#block(breakable: true, above: 1cm, stroke: 1pt, inset: 0.5cm)[
- #place(dx: -0.5cm, dy: -0.75cm, box(width: 200%)[OOF])
- #rect(height: 2cm, fill: gray)
-]
-
----
-// In-flow item with size zero in the first region.
-#set page(height: 5cm, margin: 1cm)
-In-flow, zero-sized item.
-#block(breakable: true, stroke: 1pt, inset: 0.5cm)[
- #set block(spacing: 0pt)
- #line(length: 0pt)
- #rect(height: 2cm, fill: gray)
- #line(length: 100%)
-]
-
----
-// Counter update and placed item in the first region.
-#set page(height: 5cm, margin: 1cm)
-Counter update + place.
-#block(breakable: true, above: 1cm, stroke: 1pt, inset: 0.5cm)[
- #counter("dummy").step()
- #place(dx: -0.5cm, dy: -0.75cm, box([OOF]))
- #rect(height: 2cm, fill: gray)
-]
-
----
-// Mix-and-match all the previous ones.
-#set page(height: 5cm, margin: 1cm)
-Mix-and-match all the previous tests.
-#block(breakable: true, above: 1cm, stroke: 1pt, inset: 0.5cm)[
- #counter("dummy").step()
- #place(dx: -0.5cm, dy: -0.75cm, box(width: 200%)[OOF])
- #line(length: 100%)
- #place(dy: -0.8em)[OOF]
- #rect(height: 2cm, fill: gray)
-]
diff --git a/tests/typ/layout/pad.typ b/tests/typ/layout/pad.typ
deleted file mode 100644
index 0eff5876..00000000
--- a/tests/typ/layout/pad.typ
+++ /dev/null
@@ -1,30 +0,0 @@
-// Test the `pad` function.
-
----
-// Use for indentation.
-#pad(left: 10pt, [Indented!])
-
-// All sides together.
-#set rect(inset: 0pt)
-#rect(fill: conifer,
- pad(10pt, right: 20pt,
- rect(width: 20pt, height: 20pt, fill: rgb("eb5278"))
- )
-)
-
-Hi #box(pad(left: 10pt)[A]) there
-
----
-// Pad can grow.
-#pad(left: 10pt, right: 10pt)[PL #h(1fr) PR]
-
----
-// Test that the pad element doesn't consume the whole region.
-#set page(height: 6cm)
-#align(left)[Before]
-#pad(10pt, image("/assets/images/tiger.jpg"))
-#align(right)[After]
-
----
-// Test that padding adding up to 100% does not panic.
-#pad(50%)[]
diff --git a/tests/typ/layout/page-binding.typ b/tests/typ/layout/page-binding.typ
deleted file mode 100644
index 66c8a3c6..00000000
--- a/tests/typ/layout/page-binding.typ
+++ /dev/null
@@ -1,46 +0,0 @@
-// Tests multi-page document with binding.
-
----
-#set page(height: 100pt, margin: (inside: 30pt, outside: 20pt))
-#set par(justify: true)
-#set text(size: 8pt)
-
-#page(margin: (x: 20pt), {
- set align(center + horizon)
- text(20pt, strong[Title])
- v(2em, weak: true)
- text(15pt)[Author]
-})
-
-= Introduction
-#lorem(35)
-
----
-// Test setting the binding explicitly.
-#set page(margin: (inside: 30pt))
-#rect(width: 100%)[Bound]
-#pagebreak()
-#rect(width: 100%)[Left]
-
----
-// Test setting the binding explicitly.
-#set page(binding: right, margin: (inside: 30pt))
-#rect(width: 100%)[Bound]
-#pagebreak()
-#rect(width: 100%)[Right]
-
----
-// Test setting the binding implicitly.
-#set page(margin: (inside: 30pt))
-#set text(lang: "he")
-#rect(width: 100%)[Bound]
-#pagebreak()
-#rect(width: 100%)[Right]
-
----
-// Error: 19-44 `inside` and `outside` are mutually exclusive with `left` and `right`
-#set page(margin: (left: 1cm, outside: 2cm))
-
----
-// Error: 20-23 must be `left` or `right`
-#set page(binding: top)
diff --git a/tests/typ/layout/page-margin.typ b/tests/typ/layout/page-margin.typ
deleted file mode 100644
index 00f2ed8a..00000000
--- a/tests/typ/layout/page-margin.typ
+++ /dev/null
@@ -1,20 +0,0 @@
-// Test page margins.
-
----
-// Set all margins at once.
-#[
- #set page(height: 20pt, margin: 5pt)
- #place(top + left)[TL]
- #place(bottom + right)[BR]
-]
-
----
-// Set individual margins.
-#set page(height: 40pt)
-#[#set page(margin: (left: 0pt)); #align(left)[Left]]
-#[#set page(margin: (right: 0pt)); #align(right)[Right]]
-#[#set page(margin: (top: 0pt)); #align(top)[Top]]
-#[#set page(margin: (bottom: 0pt)); #align(bottom)[Bottom]]
-
-// Ensure that specific margins override general margins.
-#[#set page(margin: (rest: 0pt, left: 20pt)); Overridden]
diff --git a/tests/typ/layout/page-marginals.typ b/tests/typ/layout/page-marginals.typ
deleted file mode 100644
index 589fb299..00000000
--- a/tests/typ/layout/page-marginals.typ
+++ /dev/null
@@ -1,24 +0,0 @@
-#set page(
- paper: "a8",
- margin: (x: 15pt, y: 30pt),
- header: {
- text(eastern)[*Typst*]
- h(1fr)
- text(0.8em)[_Chapter 1_]
- },
- footer: context align(center)[\~ #counter(page).display() \~],
- background: context if counter(page).get().first() <= 2 {
- place(center + horizon, circle(radius: 1cm, fill: luma(90%)))
- }
-)
-
-But, soft! what light through yonder window breaks? It is the east, and Juliet
-is the sun. Arise, fair sun, and kill the envious moon, Who is already sick and
-pale with grief, That thou her maid art far more fair than she: Be not her maid,
-since she is envious; Her vestal livery is but sick and green And none but fools
-do wear it; cast it off. It is my lady, O, it is my love! O, that she knew she
-were! She speaks yet she says nothing: what of that? Her eye discourses; I will
-answer it.
-
-#set page(header: none, height: auto, margin: (top: 15pt, bottom: 25pt))
-The END.
diff --git a/tests/typ/layout/page-number-align.typ b/tests/typ/layout/page-number-align.typ
deleted file mode 100644
index 7559dd65..00000000
--- a/tests/typ/layout/page-number-align.typ
+++ /dev/null
@@ -1,25 +0,0 @@
-// Test page number alignment.
-
----
-#set page(
- height: 100pt,
- margin: 30pt,
- numbering: "(1)",
- number-align: top + right,
-)
-
-#block(width: 100%, height: 100%, fill: aqua.lighten(50%))
-
----
-#set page(
- height: 100pt,
- margin: 30pt,
- numbering: "[1]",
- number-align: bottom + left,
-)
-
-#block(width: 100%, height: 100%, fill: aqua.lighten(50%))
-
----
-// Error: 25-39 expected `top` or `bottom`, found horizon
-#set page(number-align: left + horizon)
diff --git a/tests/typ/layout/page-style.typ b/tests/typ/layout/page-style.typ
deleted file mode 100644
index 0d097554..00000000
--- a/tests/typ/layout/page-style.typ
+++ /dev/null
@@ -1,28 +0,0 @@
-// Test setting page styles.
-
----
-// Empty with styles
-// Should result in one conifer-colored A11 page.
-#set page("a11", flipped: true, fill: conifer)
-
----
-// Empty with styles and then pagebreak
-// Should result in two forest-colored pages.
-#set page(fill: forest)
-#pagebreak()
-
----
-// Empty with multiple page styles.
-// Should result in a small white page.
-#set page("a4")
-#set page("a5")
-#set page(width: 1cm, height: 1cm)
-
----
-// Empty with multiple page styles.
-// Should result in one eastern-colored A11 page.
-#set page("a4")
-#set page("a5")
-#set page("a11", flipped: true, fill: eastern)
-#set text(font: "Roboto", white)
-#smallcaps[Typst]
diff --git a/tests/typ/layout/page.typ b/tests/typ/layout/page.typ
deleted file mode 100644
index f5c7822d..00000000
--- a/tests/typ/layout/page.typ
+++ /dev/null
@@ -1,42 +0,0 @@
-// Test the page class.
-
----
-// Just empty page.
-// Should result in auto-sized page, just like nothing.
-#page[]
-
----
-// Just empty page with styles.
-// Should result in one conifer-colored A11 page.
-#page("a11", flipped: true, fill: conifer)[]
-
----
-// Set width and height.
-// Should result in one high and one wide page.
-#set page(width: 80pt, height: 80pt)
-#[#set page(width: 40pt);High]
-#[#set page(height: 40pt);Wide]
-
-// Flipped predefined paper.
-#[#set page(paper: "a11", flipped: true);Flipped A11]
-
----
-// Test page fill.
-#set page(width: 80pt, height: 40pt, fill: eastern)
-#text(15pt, font: "Roboto", fill: white, smallcaps[Typst])
-#page(width: 40pt, fill: none, margin: (top: 10pt, rest: auto))[Hi]
-
----
-// Just page followed by pagebreak.
-// Should result in one forest-colored A11 page and one auto-sized page.
-#page("a11", flipped: true, fill: forest)[]
-#pagebreak()
-
----
-// Layout without any container should provide the page's dimensions, minus its margins.
-
-#page(width: 100pt, height: 100pt, {
- layout(size => [This page has a width of #size.width and height of #size.height ])
- h(1em)
- place(left, rect(width: 80pt, stroke: blue))
-})
diff --git a/tests/typ/layout/pagebreak-parity.typ b/tests/typ/layout/pagebreak-parity.typ
deleted file mode 100644
index 4dbf723e..00000000
--- a/tests/typ/layout/pagebreak-parity.typ
+++ /dev/null
@@ -1,35 +0,0 @@
-// Test clearing to even or odd pages.
-
----
-#set page(width: 80pt, height: 30pt)
-First
-#pagebreak(to: "odd")
-Third
-#pagebreak(to: "even")
-Fourth
-#pagebreak(to: "even")
-Sixth
-#pagebreak()
-Seventh
-#pagebreak(to: "odd")
-#page[Ninth]
-
----
-#set page(width: auto, height: auto)
-
-// Test with auto-sized page.
-First
-#pagebreak(to: "odd")
-Third
-
----
-#set page(height: 30pt, width: 80pt)
-
-// Test when content extends to more than one page
-First
-
-Second
-
-#pagebreak(to: "odd")
-
-Third
diff --git a/tests/typ/layout/pagebreak-weak.typ b/tests/typ/layout/pagebreak-weak.typ
deleted file mode 100644
index c228959e..00000000
--- a/tests/typ/layout/pagebreak-weak.typ
+++ /dev/null
@@ -1,30 +0,0 @@
-// Test page breaks on basically empty pages.
-
----
-// After place
-// Should result in three pages.
-First
-#pagebreak(weak: true)
-#place(right)[placed A]
-#pagebreak(weak: true)
-Third
-
----
-// After only ignorables & invisibles
-// Should result in two pages.
-First
-#pagebreak(weak: true)
-#counter(page).update(1)
-#metadata("Some")
-#pagebreak(weak: true)
-Second
-
----
-// After only ignorables, but regular break
-// Should result in three pages.
-First
-#pagebreak()
-#counter(page).update(1)
-#metadata("Some")
-#pagebreak()
-Third
diff --git a/tests/typ/layout/pagebreak.typ b/tests/typ/layout/pagebreak.typ
deleted file mode 100644
index f5921191..00000000
--- a/tests/typ/layout/pagebreak.typ
+++ /dev/null
@@ -1,45 +0,0 @@
-// Test forced page breaks.
-
----
-// Just a pagebreak.
-// Should result in two pages.
-#pagebreak()
-
----
-// Pagebreak, empty with styles and then pagebreak
-// Should result in one auto-sized page and two conifer-colored 2cm wide pages.
-#pagebreak()
-#set page(width: 2cm, fill: conifer)
-#pagebreak()
-
----
-// Two text bodies separated with and surrounded by weak pagebreaks.
-// Should result in two aqua-colored pages.
-#set page(fill: aqua)
-#pagebreak(weak: true)
-First
-#pagebreak(weak: true)
-Second
-#pagebreak(weak: true)
-
----
-// Test a combination of pagebreaks, styled pages and pages with bodies.
-// Should result in three five pages, with the fourth one being forest-colored.
-#set page(width: 80pt, height: 30pt)
-#[#set page(width: 60pt); First]
-#pagebreak()
-#pagebreak()
-Third
-#page(height: 20pt, fill: forest)[]
-Fif#[#set page();th]
-
----
-// Test hard and weak pagebreak followed by page with body.
-// Should result in three navy-colored pages.
-#set page(fill: navy)
-#set text(fill: white)
-First
-#pagebreak()
-#page[Second]
-#pagebreak(weak: true)
-#page[Third]
diff --git a/tests/typ/layout/par-bidi.typ b/tests/typ/layout/par-bidi.typ
deleted file mode 100644
index 4ff83802..00000000
--- a/tests/typ/layout/par-bidi.typ
+++ /dev/null
@@ -1,72 +0,0 @@
-// Test bidirectional text and language configuration.
-
----
-// Test reordering with different top-level paragraph directions.
-#let content = par[Text טֶקסט]
-#text(lang: "he", content)
-#text(lang: "de", content)
-
----
-// Test that consecutive, embedded LTR runs stay LTR.
-// Here, we have two runs: "A" and italic "B".
-#let content = par[أنت A#emph[B]مطرC]
-#set text(font: ("PT Sans", "Noto Sans Arabic"))
-#text(lang: "ar", content)
-#text(lang: "de", content)
-
----
-// Test that consecutive, embedded RTL runs stay RTL.
-// Here, we have three runs: "גֶ", bold "שֶׁ", and "ם".
-#let content = par[Aגֶ#strong[שֶׁ]םB]
-#set text(font: ("Linux Libertine", "Noto Serif Hebrew"))
-#text(lang: "he", content)
-#text(lang: "de", content)
-
----
-// Test embedding up to level 4 with isolates.
-#set text(dir: rtl)
-א\u{2066}A\u{2067}Bב\u{2069}?
-
----
-// Test hard line break (leads to two paragraphs in unicode-bidi).
-#set text(lang: "ar", font: ("Noto Sans Arabic", "PT Sans"))
-Life المطر هو الحياة \
-الحياة تمطر is rain.
-
----
-// Test spacing.
-L #h(1cm) ריווחR \
-Lריווח #h(1cm) R
-
----
-// Test inline object.
-#set text(lang: "he")
-קרנפיםRh#box(image("/assets/images/rhino.png", height: 11pt))inoחיים
-
----
-// Test whether L1 whitespace resetting destroys stuff.
-الغالب #h(70pt) ن#" "ة
-
----
-// Test explicit dir
-#set text(dir: rtl)
-#text("8:00 - 9:00",dir:ltr) בבוקר
-#linebreak()
-ב #text("12:00 - 13:00",dir:ltr) בצהריים
-
----
-// Mixing raw
-#set text(lang: "he")
-לדוג. `if a == b:` זה תנאי
-#set raw(lang:"python")
-לדוג. `if a == b:` זה תנאי
-
-#show raw: set text(dir:rtl)
-לתכנת בעברית `אם א == ב:`
-
----
-// Test setting a vertical direction.
-// Ref: false
-
-// Error: 16-19 text direction must be horizontal
-#set text(dir: ttb)
diff --git a/tests/typ/layout/par-indent.typ b/tests/typ/layout/par-indent.typ
deleted file mode 100644
index e807d74d..00000000
--- a/tests/typ/layout/par-indent.typ
+++ /dev/null
@@ -1,51 +0,0 @@
-// Test paragraph indent.
-
----
-#set par(first-line-indent: 12pt, leading: 5pt)
-#set block(spacing: 5pt)
-#show heading: set text(size: 10pt)
-
-The first paragraph has no indent.
-
-But the second one does.
-
-#box(image("/assets/images/tiger.jpg", height: 6pt))
-starts a paragraph, also with indent.
-
-#align(center, image("/assets/images/rhino.png", width: 1cm))
-
-= Headings
-- And lists.
-- Have no indent.
-
- Except if you have another paragraph in them.
-
-#set text(8pt, lang: "ar", font: ("Noto Sans Arabic", "Linux Libertine"))
-#set par(leading: 8pt)
-
-= Arabic
-دع النص يمطر عليك
-
-ثم يصبح النص رطبًا وقابل للطرق ويبدو المستند رائعًا.
-
----
-// This is madness.
-#set par(first-line-indent: 12pt)
-Why would anybody ever ...
-
-... want spacing and indent?
-
----
-// Test hanging indent.
-#set par(hanging-indent: 15pt, justify: true)
-#lorem(10)
-
----
-#set par(hanging-indent: 1em)
-Welcome \ here. Does this work well?
-
----
-#set par(hanging-indent: 2em)
-#set text(dir: rtl)
-لآن وقد أظلم الليل وبدأت النجوم
-تنضخ وجه الطبيعة التي أعْيَتْ من طول ما انبعثت في النهار
diff --git a/tests/typ/layout/par-justify-cjk.typ b/tests/typ/layout/par-justify-cjk.typ
deleted file mode 100644
index d0fef644..00000000
--- a/tests/typ/layout/par-justify-cjk.typ
+++ /dev/null
@@ -1,64 +0,0 @@
-// Test Chinese text in narrow lines.
-
-// In Chinese typography, line length should be multiples of the character size
-// and the line ends should be aligned with each other.
-// Most Chinese publications do not use hanging punctuation at line end.
-#set page(width: auto)
-#set par(justify: true)
-#set text(lang: "zh", font: "Noto Serif CJK SC")
-
-#rect(inset: 0pt, width: 80pt, fill: rgb("eee"))[
- 中文维基百科使用汉字书写,汉字是汉族或华人的共同文字,是中国大陆、新加坡、马来西亚、台湾、香港、澳门的唯一官方文字或官方文字之一。25.9%,而美国和荷兰则分別占13.7%及8.2%。近年來,中国大陆地区的维基百科编辑者正在迅速增加;
-]
-
----
-// Japanese typography is more complex, make sure it is at least a bit sensible.
-#set page(width: auto)
-#set par(justify: true)
-#set text(lang: "ja", font: ("Linux Libertine", "Noto Serif CJK JP"))
-#rect(inset: 0pt, width: 80pt, fill: rgb("eee"))[
- ウィキペディア(英: Wikipedia)は、世界中のボランティアの共同作業によって執筆及び作成されるフリーの多言語インターネット百科事典である。主に寄付に依って活動している非営利団体「ウィキメディア財団」が所有・運営している。
-
- 専門家によるオンライン百科事典プロジェクトNupedia(ヌーペディア)を前身として、2001年1月、ラリー・サンガーとジミー・ウェールズ(英: Jimmy Donal "Jimbo" Wales)により英語でプロジェクトが開始された。
-]
-
----
-// Test punctuation whitespace adjustment
-#set page(width: auto)
-#set text(lang: "zh", font: "Noto Serif CJK SC")
-#set par(justify: true)
-#rect(inset: 0pt, width: 80pt, fill: rgb("eee"))[
- “引号测试”,还,
-
- 《书名》《测试》下一行
-
- 《书名》《测试》。
-]
-
-「『引号』」。“‘引号’”。
-
----
-// Test Variants of Mainland China, Hong Kong, and Japan.
-
-// 17 characters a line.
-#set page(width: 170pt + 10pt, margin: (x: 5pt))
-#set text(lang: "zh", font: "Noto Serif CJK SC")
-#set par(justify: true)
-
-孔雀最早见于《山海经》中的《海内经》:“有孔雀。”东汉杨孚著《异物志》记载,岭南:“孔雀,其大如大雁而足高,毛皆有斑纹彩,捕而蓄之,拍手即舞。”
-
-#set text(lang: "zh", region: "hk", font: "Noto Serif CJK TC")
-孔雀最早见于《山海经》中的《海内经》:「有孔雀。」东汉杨孚著《异物志》记载,岭南:「孔雀,其大如大雁而足高,毛皆有斑纹彩,捕而蓄之,拍手即舞。」
-
----
-// Test punctuation marks adjustment in justified paragraph.
-
-// The test case includes the following scenarios:
-// - Compression of punctuation marks at line start or line end
-// - Adjustment of adjacent punctuation marks
-
-#set page(width: 110pt + 10pt, margin: (x: 5pt))
-#set text(lang: "zh", font: "Noto Serif CJK SC")
-#set par(justify: true)
-
-标注在字间的标点符号(乙式括号省略号以外)通常占一个汉字宽度,使其易于识别、适合配置及排版,有些排版风格完全不对标点宽度进行任何调整。但是为了让文字体裁更加紧凑易读,,,以及执行3.1.4 行首行尾禁则时,就需要对标点符号的宽度进行调整。是否调整取决于……
diff --git a/tests/typ/layout/par-justify.typ b/tests/typ/layout/par-justify.typ
deleted file mode 100644
index 69576c7d..00000000
--- a/tests/typ/layout/par-justify.typ
+++ /dev/null
@@ -1,65 +0,0 @@
-
----
-#set page(width: 180pt)
-#set block(spacing: 5pt)
-#set par(justify: true, first-line-indent: 14pt, leading: 5pt)
-
-This text is justified, meaning that spaces are stretched so that the text
-forms a "block" with flush edges at both sides.
-
-First line indents and hyphenation play nicely with justified text.
-
----
-// Test that lines with hard breaks aren't justified.
-#set par(justify: true)
-A B C \
-D
-
----
-// Test forced justification with justified break.
-A B C #linebreak(justify: true)
-D E F #linebreak(justify: true)
-
----
-// Test that there are no hick-ups with justification enabled and
-// basically empty paragraph.
-#set par(justify: true)
-#""
-
----
-// Test that the last line can be shrunk
-#set page(width: 155pt)
-#set par(justify: true)
-This text can be fitted in one line.
-
----
-// Test that runts are avoided when it's not too costly to do so.
-#set page(width: 124pt)
-#set par(justify: true)
-#for i in range(0, 20) {
- "a b c "
-}
-#"d"
-
----
-// Test that justification cannot lead to a leading space
-#set par(justify: true)
-#set text(size: 12pt)
-#set page(width: 45mm, height: auto)
-
-lorem ipsum 1234, lorem ipsum dolor sit amet
-
-#" leading whitespace should still be displayed"
-
----
-// Test that justification doesn't break code blocks
-
-#set par(justify: true)
-
-```cpp
-int main() {
- printf("Hello world\n");
- return 0;
-}
-```
-
diff --git a/tests/typ/layout/par-knuth.typ b/tests/typ/layout/par-knuth.typ
deleted file mode 100644
index 50f9280a..00000000
--- a/tests/typ/layout/par-knuth.typ
+++ /dev/null
@@ -1,30 +0,0 @@
-#set page(width: auto, height: auto)
-#set par(leading: 4pt, justify: true)
-#set text(font: "New Computer Modern")
-
-#let story = [
- In olden times when wishing still helped one, there lived a king whose
- daughters were all beautiful; and the youngest was so beautiful that the sun
- itself, which has seen so much, was astonished whenever it shone in her face.
- Close by the king’s castle lay a great dark forest, and under an old lime-tree
- in the forest was a well, and when the day was very warm, the king’s child
- went out into the forest and sat down by the side of the cool fountain; and
- when she was bored she took a golden ball, and threw it up on high and caught
- it; and this ball was her favorite plaything.
-]
-
-#let column(title, linebreaks, hyphenate) = {
- rect(inset: 0pt, width: 132pt, fill: rgb("eee"))[
- #set par(linebreaks: linebreaks)
- #set text(hyphenate: hyphenate)
- #strong(title) \ #story
- ]
-}
-
-#grid(
- columns: 3,
- gutter: 10pt,
- column([Simple without hyphens], "simple", false),
- column([Simple with hyphens], "simple", true),
- column([Optimized with hyphens], "optimized", true),
-)
diff --git a/tests/typ/layout/par-simple.typ b/tests/typ/layout/par-simple.typ
deleted file mode 100644
index 34a2d626..00000000
--- a/tests/typ/layout/par-simple.typ
+++ /dev/null
@@ -1,20 +0,0 @@
-// Test plain text.
-
----
-#set page(width: 250pt, height: 120pt)
-
-But, soft! what light through yonder window breaks? It is the east, and Juliet
-is the sun. Arise, fair sun, and kill the envious moon, Who is already sick and
-pale with grief, That thou her maid art far more fair than she: Be not her maid,
-since she is envious; Her vestal livery is but sick and green And none but fools
-do wear it; cast it off. It is my lady, O, it is my love! O, that she knew she
-were! She speaks yet she says nothing: what of that? Her eye discourses; I will
-answer it.
-
-I am too bold, 'tis not to me she speaks: Two of the fairest stars in all the
-heaven, Having some business, do entreat her eyes To twinkle in their spheres
-till they return. What if her eyes were there, they in her head? The brightness
-of her cheek would shame those stars, As daylight doth a lamp; her eyes in
-heaven Would through the airy region stream so bright That birds would sing and
-think it were not night. See, how she leans her cheek upon her hand! O, that I
-were a glove upon that hand, That I might touch that cheek!
diff --git a/tests/typ/layout/par.typ b/tests/typ/layout/par.typ
deleted file mode 100644
index 45b60cf5..00000000
--- a/tests/typ/layout/par.typ
+++ /dev/null
@@ -1,36 +0,0 @@
-// Test configuring paragraph properties.
-
----
-// Test ragged-left.
-#set align(right)
-To the right! Where the sunlight peeks behind the mountain.
-
----
-// Test changing leading and spacing.
-#set block(spacing: 1em)
-#set par(leading: 2pt)
-But, soft! what light through yonder window breaks?
-
-It is the east, and Juliet is the sun.
-
----
-// Test that paragraph spacing loses against block spacing.
-// TODO
-#set block(spacing: 100pt)
-#show table: set block(above: 5pt, below: 5pt)
-Hello
-#table(columns: 4, fill: (x, y) => if calc.odd(x + y) { silver })[A][B][C][D]
-
----
-// While we're at it, test the larger block spacing wins.
-#set block(spacing: 0pt)
-#show raw: set block(spacing: 15pt)
-#show list: set block(spacing: 2.5pt)
-
-```rust
-fn main() {}
-```
-
-- List
-
-Paragraph
diff --git a/tests/typ/layout/place-background.typ b/tests/typ/layout/place-background.typ
deleted file mode 100644
index afee0622..00000000
--- a/tests/typ/layout/place-background.typ
+++ /dev/null
@@ -1,18 +0,0 @@
-// Test placing a background image on a page.
-
----
-#set page(paper: "a10", flipped: true)
-#set text(fill: white)
-#place(
- dx: -10pt,
- dy: -10pt,
- image(
- "/assets/images/tiger.jpg",
- fit: "cover",
- width: 100% + 20pt,
- height: 100% + 20pt,
- )
-)
-#align(bottom + right)[
- _Welcome to_ #underline[*Tigerland*]
-]
diff --git a/tests/typ/layout/place-float-auto.typ b/tests/typ/layout/place-float-auto.typ
deleted file mode 100644
index 2ca3dc3a..00000000
--- a/tests/typ/layout/place-float-auto.typ
+++ /dev/null
@@ -1,31 +0,0 @@
-// Test floating placement.
-
----
-#set page(height: 140pt)
-#set place(clearance: 5pt)
-#lorem(6)
-#place(auto, float: true, rect[A])
-#place(auto, float: true, rect[B])
-#place(auto, float: true, rect[C])
-#place(auto, float: true, rect[D])
-
----
-// Error: 2-20 automatic positioning is only available for floating placement
-// Hint: 2-20 you can enable floating placement with `place(float: true, ..)`
-#place(auto)[Hello]
-
----
-// Error: 2-45 floating placement must be `auto`, `top`, or `bottom`
-#place(center + horizon, float: true)[Hello]
-
----
-// Error: 2-36 floating placement must be `auto`, `top`, or `bottom`
-#place(horizon, float: true)[Hello]
-
----
-// Error: 2-27 floating placement must be `auto`, `top`, or `bottom`
-#place(float: true)[Hello]
-
----
-// Error: 2-34 floating placement must be `auto`, `top`, or `bottom`
-#place(right, float: true)[Hello]
diff --git a/tests/typ/layout/place-float-columns.typ b/tests/typ/layout/place-float-columns.typ
deleted file mode 100644
index e2b88d46..00000000
--- a/tests/typ/layout/place-float-columns.typ
+++ /dev/null
@@ -1,19 +0,0 @@
-// Test floats in columns.
-
----
-#set page(height: 200pt, width: 300pt)
-#show: columns.with(2)
-
-= Introduction
-#figure(
- placement: bottom,
- caption: [A glacier],
- image("/assets/images/glacier.jpg", width: 50%),
-)
-#lorem(45)
-#figure(
- placement: top,
- caption: [A rectangle],
- rect[Hello!],
-)
-#lorem(20)
diff --git a/tests/typ/layout/place-float-figure.typ b/tests/typ/layout/place-float-figure.typ
deleted file mode 100644
index 4d76fd8f..00000000
--- a/tests/typ/layout/place-float-figure.typ
+++ /dev/null
@@ -1,21 +0,0 @@
-// Test floating figures.
-
----
-#set page(height: 250pt, width: 150pt)
-
-= Introduction
-#lorem(10) #footnote[Lots of Latin]
-
-#figure(
- placement: bottom,
- caption: [A glacier #footnote[Lots of Ice]],
- image("/assets/images/glacier.jpg", width: 80%),
-)
-
-#lorem(40)
-
-#figure(
- placement: top,
- caption: [An important],
- image("/assets/images/diagram.svg", width: 80%),
-)
diff --git a/tests/typ/layout/place-nested.typ b/tests/typ/layout/place-nested.typ
deleted file mode 100644
index 93006ff5..00000000
--- a/tests/typ/layout/place-nested.typ
+++ /dev/null
@@ -1,40 +0,0 @@
-// Test vertical alignment with nested placement.
-
----
-#box(
- fill: aqua,
- width: 30pt,
- height: 30pt,
- place(bottom,
- place(line(start: (0pt, 0pt), end: (20pt, 0pt), stroke: red + 3pt))
- )
-)
-
----
-#box(
- fill: aqua,
- width: 30pt,
- height: 30pt,
- {
- box(fill: yellow, {
- [Hello]
- place(horizon, line(start: (0pt, 0pt), end: (20pt, 0pt), stroke: red + 2pt))
- })
- place(horizon, line(start: (0pt, 0pt), end: (20pt, 0pt), stroke: green + 3pt))
- }
-)
-
----
-#box(fill: aqua)[
- #place(bottom + right)[Hi]
- Hello World \
- How are \
- you?
-]
-
----
-#box(fill: aqua)[
- #place(top + left, dx: 50%, dy: 50%)[Hi]
- #v(30pt)
- #line(length: 50pt)
-]
diff --git a/tests/typ/layout/place.typ b/tests/typ/layout/place.typ
deleted file mode 100644
index d426e07f..00000000
--- a/tests/typ/layout/place.typ
+++ /dev/null
@@ -1,35 +0,0 @@
-// Test the `place` function.
-
----
-#set page("a8")
-#place(bottom + center)[© Typst]
-
-= Placement
-#place(right, image("/assets/images/tiger.jpg", width: 1.8cm))
-Hi there. This is \
-a placed element. \
-Unfortunately, \
-the line breaks still had to be inserted manually.
-
-#stack(
- rect(fill: eastern, height: 10pt, width: 100%),
- place(right, dy: 1.5pt)[ABC],
- rect(fill: conifer, height: 10pt, width: 80%),
- rect(fill: forest, height: 10pt, width: 100%),
- 10pt,
- block[
- #place(center, dx: -7pt, dy: -5pt)[Hello]
- #place(center, dx: 7pt, dy: 5pt)[Hello]
- Hello #h(1fr) Hello
- ]
-)
-
----
-// Test how the placed element interacts with paragraph spacing around it.
-#set page("a8", height: 60pt)
-
-First
-
-#place(bottom + right)[Placed]
-
-Second
diff --git a/tests/typ/layout/repeat.typ b/tests/typ/layout/repeat.typ
deleted file mode 100644
index 173f9d57..00000000
--- a/tests/typ/layout/repeat.typ
+++ /dev/null
@@ -1,44 +0,0 @@
-// Test the `repeat` function.
-
----
-// Test multiple repeats.
-#let sections = (
- ("Introduction", 1),
- ("Approach", 1),
- ("Evaluation", 3),
- ("Discussion", 15),
- ("Related Work", 16),
- ("Conclusion", 253),
-)
-
-#for section in sections [
- #section.at(0) #box(width: 1fr, repeat[.]) #section.at(1) \
-]
-
----
-// Test dots with RTL.
-#set text(lang: "ar")
-مقدمة #box(width: 1fr, repeat[.]) 15
-
----
-// Test empty repeat.
-A #box(width: 1fr, repeat[]) B
-
----
-// Test unboxed repeat.
-#repeat(rect(width: 2em, height: 1em))
-
----
-// Test single repeat in both directions.
-A#box(width: 1fr, repeat(rect(width: 6em, height: 0.7em)))B
-
-#set align(center)
-A#box(width: 1fr, repeat(rect(width: 6em, height: 0.7em)))B
-
-#set text(dir: rtl)
-ريجين#box(width: 1fr, repeat(rect(width: 4em, height: 0.7em)))سون
-
----
-// Error: 2:2-2:13 repeat with no size restrictions
-#set page(width: auto)
-#repeat(".")
diff --git a/tests/typ/layout/spacing.typ b/tests/typ/layout/spacing.typ
deleted file mode 100644
index faba60b4..00000000
--- a/tests/typ/layout/spacing.typ
+++ /dev/null
@@ -1,46 +0,0 @@
-// Test the `h` and `v` functions.
-
----
-// Linebreak and leading-sized weak spacing are equivalent.
-#box[A \ B] #box[A #v(0.65em, weak: true) B]
-
-// Eating up soft spacing.
-Inv#h(0pt)isible
-
-// Multiple spacings in a row.
-Add #h(10pt) #h(10pt) up
-
-// Relative to area.
-#let x = 25% - 4pt
-|#h(x)|#h(x)|#h(x)|#h(x)|
-
-// Fractional.
-| #h(1fr) | #h(2fr) | #h(1fr) |
-
----
-// Test spacing collapsing before spacing.
-#set align(right)
-A #h(0pt) B #h(0pt) \
-A B \
-A #h(-1fr) B
-
----
-// Test spacing collapsing with different font sizes.
-#grid(columns: 2)[
- #text(size: 12pt, block(below: 1em)[A])
- #text(size: 8pt, block(above: 1em)[B])
-][
- #text(size: 12pt, block(below: 1em)[A])
- #text(size: 8pt, block(above: 1.25em)[B])
-]
-
----
-// Test RTL spacing.
-#set text(dir: rtl)
-A #h(10pt) B \
-A #h(1fr) B
-
----
-// Missing spacing.
-// Error: 10-13 missing argument: amount
-Totally #h() ignored
diff --git a/tests/typ/layout/stack-1.typ b/tests/typ/layout/stack-1.typ
deleted file mode 100644
index 8b5472e8..00000000
--- a/tests/typ/layout/stack-1.typ
+++ /dev/null
@@ -1,52 +0,0 @@
-// Test stack layouts.
-
----
-// Test stacks with different directions.
-#let widths = (
- 30pt, 20pt, 40pt, 15pt,
- 30pt, 50%, 20pt, 100%,
-)
-
-#let shaded(i, w) = {
- let v = (i + 1) * 10%
- rect(width: w, height: 10pt, fill: rgb(v, v, v))
-}
-
-#let items = for (i, w) in widths.enumerate() {
- (align(right, shaded(i, w)),)
-}
-
-#set page(width: 50pt, margin: 0pt)
-#stack(dir: btt, ..items)
-
----
-// Test spacing.
-#set page(width: 50pt, margin: 0pt)
-
-#let x = square(size: 10pt, fill: eastern)
-#stack(
- spacing: 5pt,
- stack(dir: rtl, spacing: 5pt, x, x, x),
- stack(dir: ltr, x, 20%, x, 20%, x),
- stack(dir: ltr, spacing: 5pt, x, x, 7pt, 3pt, x),
-)
-
----
-// Test overflow.
-#set page(width: 50pt, height: 30pt, margin: 0pt)
-#box(stack(
- rect(width: 40pt, height: 20pt, fill: conifer),
- rect(width: 30pt, height: 13pt, fill: forest),
-))
-
----
-// Test aligning things in RTL stack with align function & fr units.
-#set page(width: 50pt, margin: 5pt)
-#set block(spacing: 5pt)
-#set text(8pt)
-#stack(dir: rtl, 1fr, [A], 1fr, [B], [C])
-#stack(dir: rtl,
- align(center, [A]),
- align(left, [B]),
- [C],
-)
diff --git a/tests/typ/layout/stack-2.typ b/tests/typ/layout/stack-2.typ
deleted file mode 100644
index a06950f1..00000000
--- a/tests/typ/layout/stack-2.typ
+++ /dev/null
@@ -1,23 +0,0 @@
-// Test fr units in stacks.
-
----
-#set page(height: 3.5cm)
-#stack(
- dir: ltr,
- spacing: 1fr,
- ..for c in "ABCDEFGHI" {([#c],)}
-)
-
-Hello
-#v(2fr)
-from #h(1fr) the #h(1fr) wonderful
-#v(1fr)
-World! 🌍
-
----
-#set page(height: 2cm)
-#set text(white)
-#rect(fill: forest)[
- #v(1fr)
- #h(1fr) Hi you!
-]
diff --git a/tests/typ/layout/table-cell.typ b/tests/typ/layout/table-cell.typ
deleted file mode 100644
index cbe0b9f0..00000000
--- a/tests/typ/layout/table-cell.typ
+++ /dev/null
@@ -1,128 +0,0 @@
-// Test basic styling using the table.cell element.
-
----
-// Cell override
-#table(
- align: left,
- fill: red,
- stroke: blue,
- columns: 2,
- [AAAAA], [BBBBB],
- [A], [B],
- table.cell(align: right)[C], [D],
- align(right)[E], [F],
- align(horizon)[G], [A\ A\ A],
- table.cell(align: horizon)[G2], [A\ A\ A],
- table.cell(inset: 0pt)[I], [F],
- [H], table.cell(fill: blue)[J]
-)
-
----
-// Cell show rule
-#show table.cell: it => [Zz]
-
-#table(
- align: left,
- fill: red,
- stroke: blue,
- columns: 2,
- [AAAAA], [BBBBB],
- [A], [B],
- table.cell(align: right)[C], [D],
- align(right)[E], [F],
- align(horizon)[G], [A\ A\ A]
-)
-
----
-#show table.cell: it => (it.align, it.fill)
-#table(
- align: left,
- row-gutter: 5pt,
- [A],
- table.cell(align: right)[B],
- table.cell(fill: aqua)[B],
-)
-
----
-// Cell set rules
-#set table.cell(align: center)
-#show table.cell: it => (it.align, it.fill, it.inset)
-#set table.cell(inset: 20pt)
-#table(
- align: left,
- row-gutter: 5pt,
- [A],
- table.cell(align: right)[B],
- table.cell(fill: aqua)[B],
-)
-
----
-// Test folding per-cell properties (align and inset)
-#table(
- columns: (1fr, 1fr),
- rows: (2.5em, auto),
- align: right,
- fill: (x, y) => (green, aqua).at(calc.rem(x + y, 2)),
- [Top], table.cell(align: bottom)[Bot],
- table.cell(inset: (bottom: 0pt))[Bot], table.cell(inset: (bottom: 0pt))[Bot]
-)
-
----
-// Test overriding outside alignment
-#set align(bottom + right)
-#table(
- columns: (1fr, 1fr),
- rows: 2em,
- align: auto,
- fill: green,
- [BR], [BR],
- table.cell(align: left, fill: aqua)[BL], table.cell(align: top, fill: red.lighten(50%))[TR]
-)
-
----
-// First doc example
-#table(
- columns: 2,
- fill: green,
- align: right,
- [*Name*], [*Data*],
- table.cell(fill: blue)[J.], [Organizer],
- table.cell(align: center)[K.], [Leader],
- [M.], table.cell(inset: 0pt)[Player]
-)
-
----
-#{
- show table.cell: emph
- table(
- columns: 2,
- [Person], [Animal],
- [John], [Dog]
- )
-}
-
----
-// Style based on position
-#{
- show table.cell: it => {
- if it.y == 0 {
- strong(it)
- } else if it.x == 1 {
- emph(it)
- } else {
- it
- }
- }
- table(
- columns: 3,
- gutter: 3pt,
- [Name], [Age], [Info],
- [John], [52], [Nice],
- [Mary], [50], [Cool],
- [Jake], [49], [Epic]
- )
-}
-
----
-// Error: 8-19 cannot use `grid.cell` as a table cell; use `table.cell` instead
-#table(grid.cell[])
diff --git a/tests/typ/layout/table.typ b/tests/typ/layout/table.typ
deleted file mode 100644
index 89a7dab5..00000000
--- a/tests/typ/layout/table.typ
+++ /dev/null
@@ -1,141 +0,0 @@
-// Test tables.
-
----
-#set page(height: 70pt)
-#set table(fill: (x, y) => if calc.even(x + y) { rgb("aaa") })
-
-#table(
- columns: (1fr,) * 3,
- stroke: 2pt + rgb("333"),
- [A], [B], [C], [], [], [D \ E \ F \ \ \ G], [H],
-)
-
----
-#table(columns: 3, stroke: none, fill: green, [A], [B], [C])
-
----
-// Test alignment with array.
-#table(
- columns: (1fr, 1fr, 1fr),
- align: (left, center, right),
- [A], [B], [C]
-)
-
-// Test empty array.
-#set align(center)
-#table(
- columns: (1fr, 1fr, 1fr),
- align: (),
- [A], [B], [C]
-)
-
----
-// Test inset.
-#table(
- columns: 3,
- inset: 10pt,
- [A], [B], [C]
-)
-
-#table(
- columns: 3,
- inset: (y: 10pt),
- [A], [B], [C]
-)
-
-#table(
- columns: 3,
- inset: (left: 20pt, rest: 10pt),
- [A], [B], [C]
-)
-
-#table(
- columns: 2,
- inset: (
- left: 20pt,
- right: 5pt,
- top: 10pt,
- bottom: 3pt,
- ),
- [A],
- [B],
-)
-
-#table(
- columns: 3,
- fill: (x, y) => (if y == 0 { aqua } else { orange }).darken(x * 15%),
- inset: (x, y) => (left: if x == 0 { 0pt } else { 5pt }, right: if x == 0 { 5pt } else { 0pt }, y: if y == 0 { 0pt } else { 5pt }),
- [A], [B], [C],
- [A], [B], [C],
-)
-
-#table(
- columns: 3,
- inset: (0pt, 5pt, 10pt),
- fill: (x, _) => aqua.darken(x * 15%),
- [A], [B], [C],
-)
-
----
-// Test inset folding
-#set table(inset: 10pt)
-#set table(inset: (left: 0pt))
-
-#table(
- fill: red,
- inset: (right: 0pt),
- table.cell(inset: (top: 0pt))[a]
-)
-
----
-// Test interaction with gutters.
-#table(
- columns: (3em, 3em),
- fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
- align: (x, y) => (left, right).at(calc.rem(y, 2)),
- [A], [B],
- [C], [D],
- [E], [F],
- [G], [H]
-)
-
-#table(
- columns: (3em, 3em),
- fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
- align: (x, y) => (left, right).at(calc.rem(y, 2)),
- row-gutter: 5pt,
- [A], [B],
- [C], [D],
- [E], [F],
- [G], [H]
-)
-
-#table(
- columns: (3em, 3em),
- fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
- align: (x, y) => (left, right).at(calc.rem(y, 2)),
- column-gutter: 5pt,
- [A], [B],
- [C], [D],
- [E], [F],
- [G], [H]
-)
-
-#table(
- columns: (3em, 3em),
- fill: (x, y) => (red, blue).at(calc.rem(x, 2)),
- align: (x, y) => (left, right).at(calc.rem(y, 2)),
- gutter: 5pt,
- [A], [B],
- [C], [D],
- [E], [F],
- [G], [H]
-)
-
----
-// Ref: false
-#table()
-
----
-// Error: 14-19 expected color, gradient, pattern, none, array, or function, found string
-#table(fill: "hey")
diff --git a/tests/typ/layout/terms.typ b/tests/typ/layout/terms.typ
deleted file mode 100644
index 178aa98b..00000000
--- a/tests/typ/layout/terms.typ
+++ /dev/null
@@ -1,60 +0,0 @@
-// Test term list.
-
----
-// Test with constructor.
-#terms(
- ([One], [First]),
- ([Two], [Second]),
-)
-
----
-// Test joining.
-#for word in lorem(4).split().map(s => s.trim(".")) [
- / #word: Latin stuff.
-]
-
----
-// Test multiline.
-#set text(8pt)
-
-/ Fruit: A tasty, edible thing.
-/ Veggie:
- An important energy source
- for vegetarians.
-
- And healthy!
-
----
-// Test style change.
-#set text(8pt)
-/ First list: #lorem(6)
-
-#set terms(hanging-indent: 30pt)
-/ Second list: #lorem(5)
-
----
-// Test RTL.
-#set text(8pt, dir: rtl)
-
-/ פרי: דבר טעים, אכיל. ומקור אנרגיה חשוב לצמחונים.
-
----
-// Test grid like show rule.
-#show terms: it => table(
- columns: 2,
- inset: 3pt,
- ..it.children.map(v => (emph(v.term), v.description)).flatten(),
-)
-
-/ A: One letter
-/ BB: Two letters
-/ CCC: Three letters
-
----
-/ Term:
-Not in list
-/Nope
-
----
-// Error: 8 expected colon
-/ Hello
diff --git a/tests/typ/layout/transform-layout.typ b/tests/typ/layout/transform-layout.typ
deleted file mode 100644
index ce6dc930..00000000
--- a/tests/typ/layout/transform-layout.typ
+++ /dev/null
@@ -1,58 +0,0 @@
-// Test layout transformations
-
----
-// Test that rotation impact layout.
-#set page(width: 200pt)
-#set rotate(reflow: true)
-
-#let one(angle) = box(fill: aqua, rotate(angle)[Test Text])
-#for angle in range(0, 360, step: 15) {
- one(angle * 1deg)
-}
-
----
-// Test relative sizing in rotated boxes.
-#set page(width: 200pt, height: 200pt)
-#set text(size: 32pt)
-#let rotated(body) = box(rotate(
- 90deg,
- box(stroke: 0.5pt, height: 20%, clip: true, body)
-))
-
-#set rotate(reflow: false)
-Hello #rotated[World]!\
-
-#set rotate(reflow: true)
-Hello #rotated[World]!
-
----
-// Test that scaling impact layout.
-#set page(width: 200pt)
-#set text(size: 32pt)
-#let scaled(body) = box(scale(
- x: 20%,
- y: 40%,
- body
-))
-
-#set scale(reflow: false)
-Hello #scaled[World]!
-
-#set scale(reflow: true)
-Hello #scaled[World]!
-
----
-// Test relative sizing in scaled boxes.
-#set page(width: 200pt, height: 200pt)
-#set text(size: 32pt)
-#let scaled(body) = box(scale(
- x: 60%,
- y: 40%,
- box(stroke: 0.5pt, width: 30%, clip: true, body)
-))
-
-#set scale(reflow: false)
-Hello #scaled[World]!\
-
-#set scale(reflow: true)
-Hello #scaled[World]!
diff --git a/tests/typ/layout/transform.typ b/tests/typ/layout/transform.typ
deleted file mode 100644
index f74ec2b0..00000000
--- a/tests/typ/layout/transform.typ
+++ /dev/null
@@ -1,49 +0,0 @@
-// Test transformations.
-
----
-// Test creating the TeX and XeTeX logos.
-#let size = 11pt
-#let tex = {
- [T]
- h(-0.14 * size)
- box(move(dy: 0.22 * size)[E])
- h(-0.12 * size)
- [X]
-}
-
-#let xetex = {
- [X]
- h(-0.14 * size)
- box(scale(x: -100%, move(dy: 0.26 * size)[E]))
- h(-0.14 * size)
- [T]
- h(-0.14 * size)
- box(move(dy: 0.26 * size)[E])
- h(-0.12 * size)
- [X]
-}
-
-#set text(font: "New Computer Modern", size)
-Neither #tex, \
-nor #xetex!
-
----
-// Test combination of scaling and rotation.
-#set page(height: 80pt)
-#align(center + horizon,
- rotate(20deg, scale(70%, image("/assets/images/tiger.jpg")))
-)
-
----
-// Test setting rotation origin.
-#rotate(10deg, origin: top + left,
- image("/assets/images/tiger.jpg", width: 50%)
-)
-
----
-// Test setting scaling origin.
-#let r = rect(width: 100pt, height: 10pt, fill: forest)
-#set page(height: 65pt)
-#box(scale(r, x: 50%, y: 200%, origin: left + top))
-#box(scale(r, x: 50%, origin: center))
-#box(scale(r, x: 50%, y: 200%, origin: right + bottom))
diff --git a/tests/typ/lint/markup.typ b/tests/typ/lint/markup.typ
deleted file mode 100644
index 319fec11..00000000
--- a/tests/typ/lint/markup.typ
+++ /dev/null
@@ -1,26 +0,0 @@
-/// Test markup lints.
-// Ref: false
-
----
-// Warning: 1-3 no text within stars
-// Hint: 1-3 using multiple consecutive stars (e.g. **) has no additional effect
-**
-
----
-// Warning: 1-3 no text within stars
-// Hint: 1-3 using multiple consecutive stars (e.g. **) has no additional effect
-// Warning: 11-13 no text within stars
-// Hint: 11-13 using multiple consecutive stars (e.g. **) has no additional effect
-**not bold**
-
----
-// Warning: 1-3 no text within underscores
-// Hint: 1-3 using multiple consecutive underscores (e.g. __) has no additional effect
-__
-
----
-// Warning: 1-3 no text within underscores
-// Hint: 1-3 using multiple consecutive underscores (e.g. __) has no additional effect
-// Warning: 13-15 no text within underscores
-// Hint: 13-15 using multiple consecutive underscores (e.g. __) has no additional effect
-__not italic__
diff --git a/tests/typ/math/accent.typ b/tests/typ/math/accent.typ
deleted file mode 100644
index 315e14b3..00000000
--- a/tests/typ/math/accent.typ
+++ /dev/null
@@ -1,33 +0,0 @@
-// Test math accents.
-
----
-// Test function call.
-$grave(a), acute(b), hat(f), tilde(§), macron(ä), diaer(a), ä \
- breve(\&), dot(!), circle(a), caron(@), arrow(Z), arrow.l(Z)$
-
----
-$ x &= p \ dot(x) &= v \ dot.double(x) &= a \ dot.triple(x) &= j \ dot.quad(x) &= s $
-
----
-// Test `accent` function.
-$accent(ö, .), accent(v, <-), accent(ZZ, \u{0303})$
-
----
-// Test accent bounds.
-$sqrt(tilde(T)) + hat(f)/hat(g)$
-
----
-// Test wide base.
-$arrow("ABC" + d), tilde(sum)$
-
----
-// Test effect of accent on superscript.
-$A^x != hat(A)^x != hat(hat(A))^x$
-
----
-// Test high base.
-$ tilde(integral), tilde(integral)_a^b, tilde(integral_a^b) $
-
----
-// Test accent size.
-$tilde(sum), tilde(sum, size: #50%), accent(H, hat, size: #200%)$
diff --git a/tests/typ/math/alignment.typ b/tests/typ/math/alignment.typ
deleted file mode 100644
index 177e0188..00000000
--- a/tests/typ/math/alignment.typ
+++ /dev/null
@@ -1,34 +0,0 @@
-// Test implicit alignment math.
-
----
-// Test alignment step functions.
-#set page(width: 225pt)
-$
-"a" &= c \
-&= c + 1 & "By definition" \
-&= d + 100 + 1000 \
-&= x && "Even longer" \
-$
-
----
-// Test post-fix alignment.
-$
-& "right" \
-"a very long line" \
-"left" \
-$
-
----
-// Test no alignment.
-$
-"right" \
-"a very long line" \
-"left" \
-$
-
----
-// Test #460 equations.
-$
-a &=b & quad c&=d \
-e &=f & g&=h
-$
diff --git a/tests/typ/math/attach-p1.typ b/tests/typ/math/attach-p1.typ
deleted file mode 100644
index 5564ec60..00000000
--- a/tests/typ/math/attach-p1.typ
+++ /dev/null
@@ -1,63 +0,0 @@
-// Test t and b attachments, part 1.
-
----
-// Test basics, postscripts.
-$f_x + t^b + V_1^2 + attach(A, t: alpha, b: beta)$
-
----
-// Test basics, prescripts. Notably, the upper and lower prescripts' content need to be
-// aligned on the right edge of their bounding boxes, not on the left as in postscripts.
-$
-attach(upright(O), bl: 8, tl: 16, br: 2, tr: 2-),
-attach("Pb", bl: 82, tl: 207) + attach(upright(e), bl: -1, tl: 0) + macron(v)_e \
-$
-
----
-// A mixture of attachment positioning schemes.
-$
-attach(a, tl: u), attach(a, tr: v), attach(a, bl: x),
-attach(a, br: y), limits(a)^t, limits(a)_b \
-
-attach(a, tr: v, t: t),
-attach(a, tr: v, br: y),
-attach(a, br: y, b: b),
-attach(limits(a), b: b, bl: x),
-attach(a, tl: u, bl: x),
-attach(limits(a), t: t, tl: u) \
-
-attach(a, tl: u, tr: v),
-attach(limits(a), t: t, br: y),
-attach(limits(a), b: b, tr: v),
-attach(a, bl: x, br: y),
-attach(limits(a), b: b, tl: u),
-attach(limits(a), t: t, bl: u),
-limits(a)^t_b \
-
-attach(a, tl: u, tr: v, bl: x, br: y),
-attach(limits(a), t: t, bl: x, br: y, b: b),
-attach(limits(a), t: t, tl: u, tr: v, b: b),
-attach(limits(a), tl: u, bl: x, t: t, b: b),
-attach(limits(a), t: t, b: b, tr: v, br: y),
-attach(a, tl: u, t: t, tr: v, bl: x, b: b, br: y)
-$
-
----
-// Test function call after subscript.
-$pi_1(Y), a_f(x), a^zeta (x), a^abs(b)_sqrt(c) \
- a^subset.eq (x), a_(zeta(x)), pi_(1(Y)), a^(abs(b))_(sqrt(c))$
-
----
-// Test associativity and scaling.
-$ 1/(V^2^3^4^5),
- frac(
- attach(
- limits(V), br: attach(2, br: 3), b: attach(limits(2), b: 3)),
- attach(
- limits(V), tl: attach(2, tl: 3), t: attach(limits(2), t: 3))),
- attach(Omega,
- tl: attach(2, tl: attach(3, tl: attach(4, tl: 5))),
- tr: attach(2, tr: attach(3, tr: attach(4, tr: 5))),
- bl: attach(2, bl: attach(3, bl: attach(4, bl: 5))),
- br: attach(2, br: attach(3, br: attach(4, br: 5))),
- )
-$
diff --git a/tests/typ/math/attach-p2.typ b/tests/typ/math/attach-p2.typ
deleted file mode 100644
index 8f97f74f..00000000
--- a/tests/typ/math/attach-p2.typ
+++ /dev/null
@@ -1,27 +0,0 @@
-// Test t and b attachments, part 2.
-
----
-// Test high subscript and superscript.
-$ sqrt(a_(1/2)^zeta), sqrt(a_alpha^(1/2)), sqrt(a_(1/2)^(3/4)) \
- sqrt(attach(a, tl: 1/2, bl: 3/4)),
- sqrt(attach(a, tl: 1/2, bl: 3/4, tr: 1/2, br: 3/4)) $
-
----
-// Test for no collisions between descenders/ascenders and attachments
-
-$ sup_(x in P_i) quad inf_(x in P_i) $
-$ op("fff",limits: #true)^(y) quad op("yyy", limits:#true)_(f) $
-
----
-// Test frame base.
-$ (-1)^n + (1/2 + 3)^(-1/2) $
-
----
-#set text(size: 8pt)
-
-// Test that the attachments are aligned horizontally.
-$ x_1 p_1 frak(p)_1 2_1 dot_1 lg_1 !_1 \\_1 ]_1 "ip"_1 op("iq")_1 \
- x^1 b^1 frak(b)^1 2^1 dot^1 lg^1 !^1 \\^1 ]^1 "ib"^1 op("id")^1 \
- x_1 y_1 "_"_1 x^1 l^1 "`"^1 attach(I,tl:1,bl:1,tr:1,br:1)
- scripts(sum)_1^1 integral_1^1 abs(1/2)_1^1 \
- x^1_1, "("b y")"^1_1 != (b y)^1_1, "[∫]"_1 [integral]_1 $
diff --git a/tests/typ/math/attach-p3.typ b/tests/typ/math/attach-p3.typ
deleted file mode 100644
index 3591c248..00000000
--- a/tests/typ/math/attach-p3.typ
+++ /dev/null
@@ -1,42 +0,0 @@
-// Test t and b attachments, part 3.
-
----
-// Test limit.
-$ lim_(n->oo \ n "grows") sum_(k=0 \ k in NN)^n k $
-
----
-// Test forcing scripts and limits.
-$ limits(A)_1^2 != A_1^2 $
-$ scripts(sum)_1^2 != sum_1^2 $
-$ limits(integral)_a^b != integral_a^b $
-
----
-// Error: 25-29 unknown variable: oops
-$ attach(A, t: #context oops) $
-
----
-// Show and let rules for limits and scripts
-#let eq = $ ∫_a^b iota_a^b $
-#eq
-#show "∫": math.limits
-#show math.iota: math.limits.with(inline: false)
-#eq
-$iota_a^b$
-
----
-// Test default of limit attachments on relations at all sizes
-#set page(width: auto)
-$ a =^"def" b quad a lt.eq_"really" b quad a arrow.r.long.squiggly^"slowly" b $
-$a =^"def" b quad a lt.eq_"really" b quad a arrow.r.long.squiggly^"slowly" b$
-
-$a scripts(=)^"def" b quad a scripts(lt.eq)_"really" b quad a scripts(arrow.r.long.squiggly)^"slowly" b$
-
----
-// Test default of scripts attachments on integrals at display size
-$ integral.sect_a^b quad \u{2a1b}_a^b quad limits(\u{2a1b})_a^b $
-$integral.sect_a^b quad \u{2a1b}_a^b quad limits(\u{2a1b})_a^b$
-
----
-// Test default of limit attachments on large operators at display size only
-$ tack.t.big_0^1 quad \u{02A0A}_0^1 quad join_0^1 $
-$tack.t.big_0^1 quad \u{02A0A}_0^1 quad join_0^1$
diff --git a/tests/typ/math/call.typ b/tests/typ/math/call.typ
deleted file mode 100644
index 0fce1627..00000000
--- a/tests/typ/math/call.typ
+++ /dev/null
@@ -1,7 +0,0 @@
-// Test function calls that aren't typst functions
-
----
-$ pi(a) $
-$ pi(a,) $
-$ pi(a,b) $
-$ pi(a,b,) $
diff --git a/tests/typ/math/cancel.typ b/tests/typ/math/cancel.typ
deleted file mode 100644
index ac715154..00000000
--- a/tests/typ/math/cancel.typ
+++ /dev/null
@@ -1,38 +0,0 @@
-// Tests the cancel() function.
-
----
-// Inline
-$a + 5 + cancel(x) + b - cancel(x)$
-
-$c + (a dot.c cancel(b dot.c c))/(cancel(b dot.c c))$
-
----
-// Display
-#set page(width: auto)
-$ a + b + cancel(b + c) - cancel(b) - cancel(c) - 5 + cancel(6) - cancel(6) $
-$ e + (a dot.c cancel((b + c + d)))/(cancel(b + c + d)) $
-
----
-// Inverted
-$a + cancel(x, inverted: #true) - cancel(x, inverted: #true) + 10 + cancel(y) - cancel(y)$
-$ x + cancel("abcdefg", inverted: #true) $
-
----
-// Cross
-$a + cancel(b + c + d, cross: #true, stroke: #red) + e$
-$ a + cancel(b + c + d, cross: #true) + e $
-
----
-// Resized and styled
-#set page(width: 200pt, height: auto)
-$a + cancel(x, length: #200%) - cancel(x, length: #50%, stroke: #(red + 1.1pt))$
-$ b + cancel(x, length: #150%) - cancel(a + b + c, length: #50%, stroke: #(blue + 1.2pt)) $
-
----
-// Specifying cancel line angle with an absolute angle
-$cancel(x, angle: #0deg) + cancel(x, angle: #45deg) + cancel(x, angle: #90deg) + cancel(x, angle: #135deg)$
-
----
-// Specifying cancel line angle with a function
-$x + cancel(y, angle: #{angle => angle + 90deg}) - cancel(z, angle: #(angle => angle + 135deg))$
-$ e + cancel((j + e)/(f + e)) - cancel((j + e)/(f + e), angle: #(angle => angle + 30deg)) $
diff --git a/tests/typ/math/cases.typ b/tests/typ/math/cases.typ
deleted file mode 100644
index d591ae50..00000000
--- a/tests/typ/math/cases.typ
+++ /dev/null
@@ -1,9 +0,0 @@
-// Test case distinction.
-
----
-$ f(x, y) := cases(
- 1 quad &"if" (x dot y)/2 <= 0,
- 2 &"if" x divides 2,
- 3 &"if" x in NN,
- 4 &"else",
-) $
diff --git a/tests/typ/math/class.typ b/tests/typ/math/class.typ
deleted file mode 100644
index a5901b31..00000000
--- a/tests/typ/math/class.typ
+++ /dev/null
@@ -1,47 +0,0 @@
-// Test math classes.
-
----
-// Test characters.
-$ a class("normal", +) b \
- a class("binary", .) b \
- lr(class("opening", \/) a/b class("closing", \\)) \
- { x class("fence", \;) x > 0} \
- a class("large", \/) b \
- a class("punctuation", :) b \
- a class("relation", ~) b \
- a + class("unary", times) b \
- class("vary", :) a class("vary", :) b $
-
----
-// Test custom content.
-#let dotsq = square(
- size: 0.7em,
- stroke: 0.5pt,
- align(center+horizon, circle(radius: 0.15em, fill: black))
-)
-
-$ a dotsq b \
- a class("normal", dotsq) b \
- a class("vary", dotsq) b \
- a + class("vary", dotsq) b \
- a class("punctuation", dotsq) b $
-
----
-// Test nested.
-#let normal = math.class.with("normal")
-#let pluseq = $class("binary", normal(+) normal(=))$
-$ a pluseq 5 $
-
----
-// Test exceptions.
-$ sqrt(3)\/2 quad d_0.d_1d_2 dots $
-
----
-// Test if the math class changes the limit configuration.
-$ class("normal", ->)_a $
-$class("relation", x)_a$
-$ class("large", x)_a $
-$class("large", ->)_a$
-
-$limits(class("normal", ->))_a$
-$ scripts(class("relation", x))_a $
diff --git a/tests/typ/math/content.typ b/tests/typ/math/content.typ
deleted file mode 100644
index 739377ae..00000000
--- a/tests/typ/math/content.typ
+++ /dev/null
@@ -1,33 +0,0 @@
-// Test arbitrary content in math.
-
----
-// Test images and font fallback.
-#let monkey = move(dy: 0.2em, image("/assets/images/monkey.svg", height: 1em))
-$ sum_(i=#emoji.apple)^#emoji.apple.red i + monkey/2 $
-
----
-// Test tables.
-$ x := #table(columns: 2)[x][y]/mat(1, 2, 3)
- = #table[A][B][C] $
----
-// Test non-equation math directly in content.
-#math.attach($a$, t: [b])
-
----
-// Test font switch.
-#let here = text.with(font: "Noto Sans")
-$#here[f] := #here[Hi there]$.
-
----
-// Test boxes without a baseline act as if the baseline is at the base
-#{
- box(stroke: 0.2pt, $a #box(stroke: 0.2pt, $a$)$)
- h(12pt)
- box(stroke: 0.2pt, $a #box(stroke: 0.2pt, $g$)$)
- h(12pt)
- box(stroke: 0.2pt, $g #box(stroke: 0.2pt, $g$)$)
-}
-
----
-// Test boxes with a baseline are respected
-#box(stroke: 0.2pt, $a #box(baseline:0.5em, stroke: 0.2pt, $a$)$)
diff --git a/tests/typ/math/delimited.typ b/tests/typ/math/delimited.typ
deleted file mode 100644
index 8500aec2..00000000
--- a/tests/typ/math/delimited.typ
+++ /dev/null
@@ -1,59 +0,0 @@
-// Test delimiter matching and scaling.
-
----
-// Test automatic matching.
-#set page(width:122pt)
-$ (a) + {b/2} + abs(a)/2 + (b) $
-$f(x/2) < zeta(c^2 + abs(a + b/2))$
-
----
-// Test unmatched.
-$[1,2[ = [1,2) != zeta\(x/2\) $
-
----
-// Test manual matching.
-$ [|a/b|] != lr(|]a/b|]) != [a/b) $
-$ lr(| ]1,2\[ + 1/2|) $
-
----
-// Test fence confusion.
-$ |x + |y| + z/a| \
- lr(|x + |y| + z/a|) $
-
----
-// Test that symbols aren't matched automatically.
-$ bracket.l a/b bracket.r
- = lr(bracket.l a/b bracket.r) $
-
----
-// Test half LRs.
-$ lr(a/b\]) = a = lr(\{a/b) $
-
----
-// Test manual scaling.
-$ lr(]sum_(x=1)^n x], size: #70%)
- < lr((1, 2), size: #200%) $
-
----
-// Test predefined delimiter pairings.
-$floor(x/2), ceil(x/2), abs(x), norm(x)$
-
----
-// Test colored delimiters
-$ lr(
- text("(", fill: #green) a/b
- text(")", fill: #blue)
- ) $
-
----
-// Test middle functions
-$ { x mid(|) sum_(i=1)^oo phi_i (x) < 1 } \
- { integral |x| dif x
- mid(bar.v.double)
- floor(hat(A) mid(|) { x mid(|) y } mid(|) A) } $
-
----
-// Test ignoring weak spacing immediately after the opening
-// and immediately before the closing.
-
-$ [#h(1em, weak: true)A(dif x, f(x) dif x)sum#h(1em, weak: true)] $
diff --git a/tests/typ/math/equation-block-align.typ b/tests/typ/math/equation-block-align.typ
deleted file mode 100644
index c6c9cd89..00000000
--- a/tests/typ/math/equation-block-align.typ
+++ /dev/null
@@ -1,33 +0,0 @@
-// Test alignment of block equations.
-
----
-// Test unnumbered
-#let eq(alignment) = {
- show math.equation: set align(alignment)
- $ a + b = c $
-}
-
-#eq(center)
-#eq(left)
-#eq(right)
-
-#set text(dir: rtl)
-#eq(start)
-#eq(end)
-
----
-// Test numbered
-#let eq(alignment) = {
- show math.equation: set align(alignment)
- $ a + b = c $
-}
-
-#set math.equation(numbering: "(1)")
-
-#eq(center)
-#eq(left)
-#eq(right)
-
-#set text(dir: rtl)
-#eq(start)
-#eq(end)
diff --git a/tests/typ/math/equation-number.typ b/tests/typ/math/equation-number.typ
deleted file mode 100644
index 3de611b3..00000000
--- a/tests/typ/math/equation-number.typ
+++ /dev/null
@@ -1,145 +0,0 @@
-// Test equation number, and its interaction with equation
-// block's alignment and text direction.
-
----
-#set math.equation(numbering: "(1)")
-
-$ a + b = c $
-
-#show math.equation: set align(center)
-$ a + b = c $
-#show math.equation: set align(left)
-$ a + b = c $
-#show math.equation: set align(right)
-$ a + b = c $
-
-#set text(dir: rtl)
-#show math.equation: set align(start)
-$ a + b = c $
-#show math.equation: set align(end)
-$ a + b = c $
-
----
-#set math.equation(numbering: "(1)", number-align: start)
-
-$ a + b = c $
-
-#show math.equation: set align(center)
-$ a + b = c $
-#show math.equation: set align(left)
-$ a + b = c $
-#show math.equation: set align(right)
-$ a + b = c $
-
-#set text(dir: rtl)
-#show math.equation: set align(start)
-$ a + b = c $
-#show math.equation: set align(end)
-$ a + b = c $
-
----
-#set math.equation(numbering: "(1)", number-align: end)
-
-$ a + b = c $
-
-#show math.equation: set align(center)
-$ a + b = c $
-#show math.equation: set align(left)
-$ a + b = c $
-#show math.equation: set align(right)
-$ a + b = c $
-
-#set text(dir: rtl)
-#show math.equation: set align(start)
-$ a + b = c $
-#show math.equation: set align(end)
-$ a + b = c $
-
----
-#set math.equation(numbering: "(1)", number-align: left)
-
-$ a + b = c $
-
-#show math.equation: set align(center)
-$ a + b = c $
-#show math.equation: set align(left)
-$ a + b = c $
-#show math.equation: set align(right)
-$ a + b = c $
-
-#set text(dir: rtl)
-#show math.equation: set align(start)
-$ a + b = c $
-#show math.equation: set align(end)
-$ a + b = c $
-
----
-#set math.equation(numbering: "(1)", number-align: right)
-
-$ a + b = c $
-
-#show math.equation: set align(center)
-$ a + b = c $
-#show math.equation: set align(left)
-$ a + b = c $
-#show math.equation: set align(right)
-$ a + b = c $
-
-#set text(dir: rtl)
-#show math.equation: set align(start)
-$ a + b = c $
-#show math.equation: set align(end)
-$ a + b = c $
-
----
-// Error: 52-58 expected `start`, `left`, `right`, or `end`, found center
-#set math.equation(numbering: "(1)", number-align: center)
-
----
-// Error: 52-67 expected `start`, `left`, `right`, or `end`, found center
-#set math.equation(numbering: "(1)", number-align: center + bottom)
-
----
-#set math.equation(numbering: "(1)")
-
-$ p &= ln a b \
- &= ln a + ln b $
-
----
-#set math.equation(numbering: "(1)", number-align: top+start)
-
-$ p &= ln a b \
- &= ln a + ln b $
-
----
-#show math.equation: set align(left)
-#set math.equation(numbering: "(1)", number-align: bottom)
-
-$ q &= ln sqrt(a b) \
- &= 1/2 (ln a + ln b) $
-
----
-// Tests that if the numbering's layout box vertically exceeds the box of
-// the equation frame's boundary, the latter's frame is resized correctly
-// to encompass the numbering. #box() below delineates the resized frame.
-//
-// A row with "-" only has a height that's smaller than the height of the
-// numbering's layout box. Note we use pattern "1" here, not "(1)", since
-// the parenthesis exceeds the numbering's layout box, due to the default
-// settings of top-edge and bottom-edge of the TextElem that laid it out.
-#set math.equation(numbering: "1", number-align: top)
-#box(
-$ - &- - \
- a &= b $,
-fill: silver)
-
-#set math.equation(numbering: "1", number-align: horizon)
-#box(
-$ - - - $,
-fill: silver)
-
-#set math.equation(numbering: "1", number-align: bottom)
-#box(
-$ a &= b \
- - &- - $,
-fill: silver)
diff --git a/tests/typ/math/equation-show.typ b/tests/typ/math/equation-show.typ
deleted file mode 100644
index 9334c54e..00000000
--- a/tests/typ/math/equation-show.typ
+++ /dev/null
@@ -1,7 +0,0 @@
-// Test show rules on equations.
-
----
-This is small: $sum_(i=0)^n$
-
-#show math.equation: math.display
-This is big: $sum_(i=0)^n$
diff --git a/tests/typ/math/font-features.typ b/tests/typ/math/font-features.typ
deleted file mode 100644
index ffdd1924..00000000
--- a/tests/typ/math/font-features.typ
+++ /dev/null
@@ -1,10 +0,0 @@
-// Test that setting font features in math.equation has an effect.
-
----
-$ nothing $
-$ "hi ∅ hey" $
-$ sum_(i in NN) 1 + i $
-#show math.equation: set text(features: ("cv01",), fallback: false)
-$ nothing $
-$ "hi ∅ hey" $
-$ sum_(i in NN) 1 + i $
diff --git a/tests/typ/math/frac.typ b/tests/typ/math/frac.typ
deleted file mode 100644
index fc7dd14b..00000000
--- a/tests/typ/math/frac.typ
+++ /dev/null
@@ -1,43 +0,0 @@
-// Test fractions.
-
----
-// Test that denominator baseline matches in the common case.
-$ x = 1/2 = a/(a h) = a/a = a/(1/2) $
-
----
-// Test parenthesis removal.
-$ (|x| + |y|)/2 < [1+2]/3 $
-
----
-// Test large fraction.
-$ x = (-b plus.minus sqrt(b^2 - 4a c))/(2a) $
-
----
-// Test binomial.
-$ binom(circle, square) $
-
----
-// Test multinomial coefficients.
-$ binom(n, k_1, k_2, k_3) $
-
----
-// Error: 3-13 missing argument: lower
-$ binom(x^2) $
-
----
-// Test dif.
-$ (dif y)/(dif x), dif/x, x/dif, dif/dif \
- frac(dif y, dif x), frac(dif, x), frac(x, dif), frac(dif, dif) $
-
----
-// Test associativity.
-$ 1/2/3 = (1/2)/3 = 1/(2/3) $
-
----
-// Test precedence.
-$ a_1/b_2, 1/f(x), zeta(x)/2, "foo"[|x|]/2 \
- 1.2/3.7, 2.3^3.4 \
- 🏳️‍🌈[x]/2, f [x]/2, phi [x]/2, 🏳️‍🌈 [x]/2 \
- +[x]/2, 1(x)/2, 2[x]/2 \
- (a)b/2, b(a)[b]/2 \
- n!/2, 5!/2, n !/2, 1/n!, 1/5! $
diff --git a/tests/typ/math/linebreak.typ b/tests/typ/math/linebreak.typ
deleted file mode 100644
index 88ce69d2..00000000
--- a/tests/typ/math/linebreak.typ
+++ /dev/null
@@ -1,50 +0,0 @@
-// Test inline equation line breaking.
-
----
-// Basic breaking after binop, rel
-#let hrule(x) = box(line(length: x))
-#hrule(45pt)$e^(pi i)+1 = 0$\
-#hrule(55pt)$e^(pi i)+1 = 0$\
-#hrule(70pt)$e^(pi i)+1 = 0$
-
----
-// LR groups prevent linbreaking.
-#let hrule(x) = box(line(length: x))
-#hrule(76pt)$a+b$\
-#hrule(74pt)$(a+b)$\
-#hrule(74pt)$paren.l a+b paren.r$
-
----
-// Multiline yet inline does not linebreak
-#let hrule(x) = box(line(length: x))
-#hrule(80pt)$a + b \ c + d$\
-
----
-// A single linebreak at the end still counts as one line.
-#let hrule(x) = box(line(length: x))
-#hrule(60pt)$e^(pi i)+1 = 0\ $
-
----
-// Inline, in a box, doesn't linebreak.
-#let hrule(x) = box(line(length: x))
-#hrule(80pt)#box($a+b$)
-
----
-// A relation followed by a relation doesn't linebreak
-#let hrule(x) = box(line(length: x))
-#hrule(70pt)$a < = b$\
-#hrule(74pt)$a < = b$
-
----
-// Page breaks can happen after a relation even if there is no
-// explicit space.
-#let hrule(x) = box(line(length: x))
-#hrule(90pt)$<;$\
-#hrule(95pt)$<;$\
-#hrule(90pt)$<)$\
-#hrule(95pt)$<)$
-
----
-// Verify empty rows are handled ok.
-$ $\
-Nothing: $ $, just empty.
diff --git a/tests/typ/math/matrix-alignment.typ b/tests/typ/math/matrix-alignment.typ
deleted file mode 100644
index 0b3aa456..00000000
--- a/tests/typ/math/matrix-alignment.typ
+++ /dev/null
@@ -1,58 +0,0 @@
-// Test matrix alignment math.
-
----
-// Test alternating alignment in a vector.
-$ vec(
- "a" & "a a a" & "a a",
- "a a" & "a a" & "a",
- "a a a" & "a" & "a a a",
-) $
-
----
-// Test alternating explicit alignment in a matrix.
-$ mat(
- "a" & "a a a" & "a a";
- "a a" & "a a" & "a";
- "a a a" & "a" & "a a a";
-) $
-
----
-// Test alignment in a matrix.
-$ mat(
- "a", "a a a", "a a";
- "a a", "a a", "a";
- "a a a", "a", "a a a";
-) $
-
----
-// Test explicit left alignment in a matrix.
-$ mat(
- &"a", &"a a a", &"a a";
- &"a a", &"a a", &"a";
- &"a a a", &"a", &"a a a";
-) $
-
----
-// Test explicit right alignment in a matrix.
-$ mat(
- "a"&, "a a a"&, "a a"&;
- "a a"&, "a a"&, "a"&;
- "a a a"&, "a"&, "a a a"&;
-) $
-
----
-// Test #460 equations.
-#let stop = {
- math.class("punctuation",$.$)
-}
-$ mat(&a+b,c;&d, e) $
-$ mat(&a+b&,c;&d&, e) $
-$ mat(&&&a+b,c;&&&d, e) $
-$ mat(stop &a+b&stop,c;...stop stop&d&...stop stop, e) $
-
----
-// Test #454 equations.
-$ mat(-1, 1, 1; 1, -1, 1; 1, 1, -1) $
-$ mat(-1&, 1&, 1&; 1&, -1&, 1&; 1&, 1&, -1&) $
-$ mat(-1&, 1&, 1&; 1, -1, 1; 1, 1, -1) $
-$ mat(&-1, &1, &1; 1, -1, 1; 1, 1, -1) $
diff --git a/tests/typ/math/matrix-gaps.typ b/tests/typ/math/matrix-gaps.typ
deleted file mode 100644
index ef33b518..00000000
--- a/tests/typ/math/matrix-gaps.typ
+++ /dev/null
@@ -1,17 +0,0 @@
-// Test matrices, cases and vec gaps
-
----
-#set math.mat(row-gap: 1em, column-gap: 2em)
-$ mat(1, 2; 3, 4) $
-
----
-#set math.mat(gap: 1em)
-$ mat(1, 2; 3, 4) $
-
----
-#set math.cases(gap: 1em)
-$ x = cases(1, 2) $
-
----
-#set math.vec(gap: 1em)
-$ vec(1, 2) $
diff --git a/tests/typ/math/matrix.typ b/tests/typ/math/matrix.typ
deleted file mode 100644
index 57ecd85f..00000000
--- a/tests/typ/math/matrix.typ
+++ /dev/null
@@ -1,97 +0,0 @@
-// Test matrices.
-
----
-// Test semicolon syntax.
-#set align(center)
-$mat() dot
- mat(;) dot
- mat(1, 2) dot
- mat(1, 2;) \
- mat(1; 2) dot
- mat(1, 2; 3, 4) dot
- mat(1 + &2, 1/2; &3, 4)$
-
----
-// Test sparse matrix.
-$ mat(
- 1, 2, ..., 10;
- 2, 2, ..., 10;
- dots.v, dots.v, dots.down, dots.v;
- 10, 10, ..., 10;
-) $
-
----
-// Test baseline alignment.
-$ mat(
- a, b^2;
- sum_(x \ y) x, a^(1/2);
- zeta, alpha;
-) $
-
----
-// Test alternative delimiter with set rule.
-#set math.mat(delim: "[")
-$ mat(1, 2; 3, 4) $
-$ a + mat(delim: #none, 1, 2; 3, 4) + b $
-
----
-// Test alternative math delimiter directly in call.
-#set align(center)
-#grid(
- columns: 3,
- gutter: 10pt,
-
- $ mat(1, 2, delim: "[") $,
- $ mat(1, 2; delim: "[") $,
- $ mat(delim: "[", 1, 2) $,
-
- $ mat(1; 2; delim: "[") $,
- $ mat(1; delim: "[", 2) $,
- $ mat(delim: "[", 1; 2) $,
-
- $ mat(1, 2; delim: "[", 3, 4) $,
- $ mat(delim: "[", 1, 2; 3, 4) $,
- $ mat(1, 2; 3, 4; delim: "[") $,
-)
-
----
-// Error: 13-14 expected array, found content
-$ mat(1, 2; 3, 4, delim: "[") $,
-
----
-$ mat(B, A B) $
-$ mat(B, A B, dots) $
-$ mat(B, A B, dots;) $
-$ mat(#1, #(foo: "bar")) $
-
----
-
-// Test matrix line drawing (augmentation).
-#grid(
- columns: 2,
- gutter: 10pt,
-
- $ mat(10, 2, 3, 4; 5, 6, 7, 8; augment: #3) $,
- $ mat(10, 2, 3, 4; 5, 6, 7, 8; augment: #(-1)) $,
- $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(hline: 2)) $,
- $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(hline: -1)) $,
- $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(hline: 1, vline: 1)) $,
- $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(hline: -2, vline: -2)) $,
- $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(vline: 2, stroke: 1pt + blue)) $,
- $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(vline: -1, stroke: 1pt + blue)) $,
-)
-
----
-
-// Test using matrix line drawing with a set rule.
-#set math.mat(augment: (hline: 2, vline: 1, stroke: 2pt + green))
-$ mat(1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 1) $
-
-#set math.mat(augment: 2)
-$ mat(1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 1) $
-
-#set math.mat(augment: none)
-
----
-// Error: 3-37 cannot draw a vertical line after column 3 of a matrix with 3 columns
-$ mat(1, 0, 0; 0, 1, 1; augment: #3) $,
diff --git a/tests/typ/math/multiline.typ b/tests/typ/math/multiline.typ
deleted file mode 100644
index b1f43800..00000000
--- a/tests/typ/math/multiline.typ
+++ /dev/null
@@ -1,56 +0,0 @@
-// Test multiline math.
-
----
-// Test basic alignment.
-$ x &= x + y \
- &= x + 2z \
- &= sum x dot 2z $
-
----
-// Test text before first alignment point.
-$ x + 1 &= a^2 + b^2 \
- y &= a + b^2 \
- z &= alpha dot beta $
-
----
-// Test space between inner alignment points.
-$ a + b &= 2 + 3 &= 5 \
- b &= c &= 3 $
-
----
-// Test in case distinction.
-$ f := cases(
- 1 + 2 &"iff" &x,
- 3 &"if" &y,
-) $
-
----
-// Test mixing lines with and some without alignment points.
-$ "abc" &= c \
- &= d + 1 \
- = x $
-
----
-// Test multiline subscript.
-$ sum_(n in NN \ n <= 5) n = (5(5+1))/2 = 15 $
-
----
-// Test no trailing line break.
-$
-"abc" &= c
-$
-No trailing line break.
-
----
-// Test single trailing line break.
-$
-"abc" &= c \
-$
-One trailing line break.
-
----
-// Test multiple trailing line breaks.
-$
-"abc" &= c \ \ \
-$
-Multiple trailing line breaks.
diff --git a/tests/typ/math/numbering.typ b/tests/typ/math/numbering.typ
deleted file mode 100644
index fd303ff2..00000000
--- a/tests/typ/math/numbering.typ
+++ /dev/null
@@ -1,11 +0,0 @@
-// Test equation numbering.
-
----
-#set page(width: 150pt)
-#set math.equation(numbering: "(I)")
-
-We define $x$ in preparation of @fib:
-$ phi.alt := (1 + sqrt(5)) / 2 $ <ratio>
-
-With @ratio, we get
-$ F_n = round(1 / sqrt(5) phi.alt^n) $ <fib>
diff --git a/tests/typ/math/op.typ b/tests/typ/math/op.typ
deleted file mode 100644
index 14e1c6e6..00000000
--- a/tests/typ/math/op.typ
+++ /dev/null
@@ -1,30 +0,0 @@
-// Test text operators.
-
----
-// Test predefined.
-$ max_(1<=n<=m) n $
-
----
-// With or without parens.
-$ &sin x + log_2 x \
- = &sin(x) + log_2(x) $
-
----
-// Test scripts vs limits.
-#set page(width: auto)
-#set text(font: "New Computer Modern")
-Discuss $lim_(n->oo) 1/n$ now.
-$ lim_(n->infinity) 1/n = 0 $
-
----
-// Test custom operator.
-$ op("myop", limits: #false)_(x:=1) x \
- op("myop", limits: #true)_(x:=1) x $
-
----
-// Test styled operator.
-$ bold(op("bold", limits: #true))_x y $
-
----
-// With non-text content
-$ op(#underline[ul]) a $
diff --git a/tests/typ/math/opticalsize.typ b/tests/typ/math/opticalsize.typ
deleted file mode 100644
index 83e2ce3d..00000000
--- a/tests/typ/math/opticalsize.typ
+++ /dev/null
@@ -1,69 +0,0 @@
-// test optical sized variants in sub/superscripts
-
----
-
-// Test transition from script to scriptscript.
-#[
-#set text(size:20pt)
-$ e^(e^(e^(e))) $
-]
-A large number: $e^(e^(e^(e)))$.
-
----
-// Test prime/double prime via scriptsize
-#let prime = [ \u{2032} ]
-#let dprime = [ \u{2033} ]
-#let tprime = [ \u{2034} ]
-$ y^dprime-2y^prime + y = 0 $
-$y^dprime-2y^prime + y = 0$
-$ y^tprime_3 + g^(prime 2) $
-
----
-// Test prime superscript on large symbol
-$ scripts(sum_(k in NN))^prime 1/k^2 $
-$sum_(k in NN)^prime 1/k^2$
-
----
-// Test script-script in a fraction.
-$ 1/(x^A) $
-#[#set text(size:18pt); $1/(x^A)$] vs. #[#set text(size:14pt); $x^A$]
-
----
-// Test dedicated syntax for primes
-$a'$, $a'''_b$, $'$, $'''''''$
-
----
-// Test spaces between
-$a' ' '$, $' ' '$, $a' '/b$
-
----
-// Test complex prime combinations
-$a'_b^c$, $a_b'^c$, $a_b^c'$, $a_b'^c'^d'$
-
-$(a'_b')^(c'_d')$, $a'/b'$, $a_b'/c_d'$
-
-$∫'$, $∑'$, $ ∑'_S' $
-
----
-// Test attaching primes only
-$a' = a^', a_', a_'''^''^'$
-
----
-// Test primes always attaching as scripts
-$ x' $
-$ x^' $
-$ attach(x, t: ') $
-$ <' $
-$ attach(<, br: ') $
-$ op(<, limits: #true)' $
-$ limits(<)' $
-
----
-// Test forcefully attaching primes as limits
-$ attach(<, t: ') $
-$ <^' $
-$ attach(<, b: ') $
-$ <_' $
-
-$ limits(x)^' $
-$ attach(limits(x), t: ') $
diff --git a/tests/typ/math/prime.typ b/tests/typ/math/prime.typ
deleted file mode 100644
index 61e0c0b2..00000000
--- a/tests/typ/math/prime.typ
+++ /dev/null
@@ -1,9 +0,0 @@
-// Test prime symbols after code mode.
-#let g = $f$
-#let gg = $f$
-
-$
- #(g)' #g' #g ' \
- #g''''''''''''''''' \
- gg'
-$
diff --git a/tests/typ/math/root.typ b/tests/typ/math/root.typ
deleted file mode 100644
index 6eba1275..00000000
--- a/tests/typ/math/root.typ
+++ /dev/null
@@ -1,45 +0,0 @@
-// Test roots.
-
----
-// Test root with more than one character.
-$A = sqrt(x + y) = c$
-
----
-// Test root size with radicals containing attachments.
-$ sqrt(a) quad
- sqrt(f) quad
- sqrt(q) quad
- sqrt(a^2) \
- sqrt(n_0) quad
- sqrt(b^()) quad
- sqrt(b^2) quad
- sqrt(q_1^2) $
-
----
-// Test precomposed vs constructed roots.
-// 3 and 4 are precomposed.
-$sqrt(x)$
-$root(2, x)$
-$root(3, x)$
-$root(4, x)$
-$root(5, x)$
-
----
-// Test large bodies
-$ sqrt([|x|]^2 + [|y|]^2) < [|z|] $
-$ v = sqrt((1/2) / (4/5))
- = root(3, (1/2/3) / (4/5/6))
- = root(4, ((1/2) / (3/4)) / ((1/2) / (3/4))) $
-
----
-// Test large index.
-$ root(2, x) quad
- root(3/(2/1), x) quad
- root(1/11, x) quad
- root(1/2/3, 1) $
-
----
-// Test shorthand.
-$ √2^3 = sqrt(2^3) $
-$ √(x+y) quad ∛x quad ∜x $
-$ (√2+3) = (sqrt(2)+3) $
diff --git a/tests/typ/math/spacing.typ b/tests/typ/math/spacing.typ
deleted file mode 100644
index 63a60ae1..00000000
--- a/tests/typ/math/spacing.typ
+++ /dev/null
@@ -1,48 +0,0 @@
-// Test spacing in math formulas.
-
----
-// Test spacing cases.
-$ä, +, c, (, )$ \
-$=), (+), {times}$ \
-$⟧<⟦, abs(-), [=$ \
-$a=b, a==b$ \
-$-a, +a$ \
-$a not b$ \
-$a+b, a*b$ \
-$sum x, sum(x)$ \
-$sum product x$ \
-$f(x), zeta(x), "frac"(x)$ \
-$a+dots.c+b$
-$f(x) sin(y)$
----
-// Test ignored vs non-ignored spaces.
-$f (x), f(x)$ \
-$[a|b], [a | b]$ \
-$a"is"b, a "is" b$
-
----
-// Test predefined spacings.
-$a thin b, a med b, a thick b, a quad b$ \
-$a = thin b$ \
-$a - b equiv c quad (mod 2)$
-
----
-// Test spacing for set comprehension.
-#set page(width: auto)
-$ { x in RR | x "is natural" and x < 10 } $
-
----
-// Test spacing for operators with decorations and modifiers on them
-#set page(width: auto)
-$a equiv b + c - d => e log 5 op("ln") 6$ \
-$a cancel(equiv) b overline(+) c arrow(-) d hat(=>) e cancel(log) 5 dot(op("ln")) 6$ \
-$a overbrace(equiv) b underline(+) c grave(-) d underbracket(=>) e circle(log) 5 caron(op("ln")) 6$ \
-\
-$a attach(equiv, tl: a, tr: b) b attach(limits(+), t: a, b: b) c tilde(-) d breve(=>) e attach(limits(log), t: a, b: b) 5 attach(op("ln"), tr: a, bl: b) 6$
----
-// Test weak spacing
-$integral f(x) dif x$,
-// Not weak
-$integral f(x) thin dif x$,
-// Both are weak, collide
-$integral f(x) #h(0.166em, weak: true)dif x$
diff --git a/tests/typ/math/style.typ b/tests/typ/math/style.typ
deleted file mode 100644
index f1a38405..00000000
--- a/tests/typ/math/style.typ
+++ /dev/null
@@ -1,52 +0,0 @@
-// Test text styling in math.
-
----
-// Test italic defaults.
-$a, A, delta, ϵ, diff, Delta, ϴ$
-
----
-// Test forcing a specific style.
-$A, italic(A), upright(A), bold(A), bold(upright(A)), \
- serif(A), sans(A), cal(A), frak(A), mono(A), bb(A), \
- italic(diff), upright(diff), \
- bb("hello") + bold(cal("world")), \
- mono("SQRT")(x) wreath mono(123 + 456)$
-
----
-// Test forcing math size
-$a/b, display(a/b), display(a)/display(b), inline(a/b), script(a/b), sscript(a/b) \
- mono(script(a/b)), script(mono(a/b))\
- script(a^b, cramped: #true), script(a^b, cramped: #false)$
-
----
-// Test a few style exceptions.
-$h, bb(N), cal(R), Theta, italic(Theta), sans(Theta), sans(italic(Theta)) \
- bb(d), bb(italic(d)), italic(bb(d)), bb(e), bb(italic(e)), italic(bb(e)) \
- bb(i), bb(italic(i)), italic(bb(i)), bb(j), bb(italic(j)), italic(bb(j)) \
- bb(D), bb(italic(D)), italic(bb(D))$
-
----
-// Test a few greek exceptions.
-$bb(Gamma) , bb(gamma), bb(Pi), bb(pi), bb(sum)$
-
----
-// Test hebrew exceptions.
-$aleph, beth, gimel, daleth$
-
----
-// Test font fallback.
-$ よ and 🏳️‍🌈 $
-
----
-// Test text properties.
-$text(#red, "time"^2) + sqrt("place")$
-
----
-// Test different font.
-#show math.equation: set text(font: "Fira Math")
-$ v := vec(1 + 2, 2 - 4, sqrt(3), arrow(x)) + 1 $
-
----
-// Test using rules for symbols
-#show sym.tack: it => $#h(1em) it #h(1em)$
-$ a tack b $
diff --git a/tests/typ/math/syntax.typ b/tests/typ/math/syntax.typ
deleted file mode 100644
index 503d3031..00000000
--- a/tests/typ/math/syntax.typ
+++ /dev/null
@@ -1,22 +0,0 @@
-// Test math syntax.
-
----
-// Test Unicode math.
-$ ∑_(i=0)^ℕ a ∘ b = \u{2211}_(i=0)^NN a compose b $
-
----
-// Test a few shorthands.
-$ underline(f' : NN -> RR) \
- n |-> cases(
- [|1|] &"if" n >>> 10,
- 2 * 3 &"if" n != 5,
- 1 - 0 thick &...,
- ) $
-
----
-// Test common symbols.
-$ dot \ dots \ ast \ tilde \ star $
-
----
-// Error: 1-2 unclosed delimiter
-$a
diff --git a/tests/typ/math/unbalanced.typ b/tests/typ/math/unbalanced.typ
deleted file mode 100644
index 9eeb3bfc..00000000
--- a/tests/typ/math/unbalanced.typ
+++ /dev/null
@@ -1,6 +0,0 @@
-// Test unbalanced delimiters.
-
----
-$ 1/(2 (x) $
-$ 1_(2 y (x) () $
-$ 1/(2 y (x) (2(3)) $
diff --git a/tests/typ/math/underover.typ b/tests/typ/math/underover.typ
deleted file mode 100644
index f0f67308..00000000
--- a/tests/typ/math/underover.typ
+++ /dev/null
@@ -1,21 +0,0 @@
-// Test under/over things.
-
----
-// Test braces.
-$ x = underbrace(
- 1 + 2 + ... + 5,
- underbrace("numbers", x + y)
-) $
-
----
-// Test lines and brackets.
-$ x = overbracket(
- overline(underline(x + y)),
- 1 + 2 + ... + 5,
-) $
-
----
-// Test brackets.
-$ underbracket([1, 2/3], "relevant stuff")
- arrow.l.r.double.long
- overbracket([4/5,6], "irrelevant stuff") $
diff --git a/tests/typ/math/vec.typ b/tests/typ/math/vec.typ
deleted file mode 100644
index 445b8104..00000000
--- a/tests/typ/math/vec.typ
+++ /dev/null
@@ -1,14 +0,0 @@
-// Test vectors.
-
----
-// Test wide cell.
-$ v = vec(1, 2+3, 4) $
-
----
-// Test alternative delimiter.
-#set math.vec(delim: "[")
-$ vec(1, 2) $
-
----
-// Error: 22-25 expected "(", "[", "{", "|", "||", or none
-#set math.vec(delim: "%")
diff --git a/tests/typ/meta/bibliography-full.typ b/tests/typ/meta/bibliography-full.typ
deleted file mode 100644
index 9b652cd2..00000000
--- a/tests/typ/meta/bibliography-full.typ
+++ /dev/null
@@ -1,5 +0,0 @@
-// Test the full bibliography.
-
----
-#set page(paper: "a6", height: 170mm)
-#bibliography("/assets/bib/works.bib", full: true)
diff --git a/tests/typ/meta/bibliography-ordering.typ b/tests/typ/meta/bibliography-ordering.typ
deleted file mode 100644
index 4732ad68..00000000
--- a/tests/typ/meta/bibliography-ordering.typ
+++ /dev/null
@@ -1,16 +0,0 @@
-#set page(width: 300pt)
-
-@mcintosh_anxiety
-@psychology25
-@netwok
-@issue201
-@arrgh
-@quark
-@distress,
-@glacier-melt
-@issue201
-@tolkien54
-@sharing
-@restful
-
-#bibliography("/assets/bib/works.bib")
diff --git a/tests/typ/meta/bibliography.typ b/tests/typ/meta/bibliography.typ
deleted file mode 100644
index 107f790e..00000000
--- a/tests/typ/meta/bibliography.typ
+++ /dev/null
@@ -1,44 +0,0 @@
-// Test citations and bibliographies.
-
----
-#set page(width: 200pt)
-
-= Details
-See also @arrgh #cite(<distress>, supplement: [p.~22]), @arrgh[p.~4], and @distress[p.~5].
-#bibliography("/assets/bib/works.bib")
-
----
-// Test unconventional order.
-#set page(width: 200pt)
-#bibliography(
- "/assets/bib/works.bib",
- title: [Works to be cited],
- style: "chicago-author-date",
-)
-#line(length: 100%)
-
-As described by #cite(<netwok>, form: "prose"),
-the net-work is a creature of its own.
-This is close to piratery! @arrgh
-And quark! @quark
-
----
-#set page(width: 200pt)
-#set heading(numbering: "1.")
-#show bibliography: set heading(numbering: "1.")
-
-= Multiple Bibs
-Now we have multiple bibliographies containing @glacier-melt @keshav2007read
-#bibliography(("/assets/bib/works.bib", "/assets/bib/works_too.bib"))
-
----
-// Test ambiguous reference.
-= Introduction <arrgh>
-
-// Error: 1-7 label occurs in the document and its bibliography
-@arrgh
-#bibliography("/assets/bib/works.bib")
-
----
-// Error: 15-65 duplicate bibliography keys: netwok, issue201, arrgh, quark, distress, glacier-melt, tolkien54, DBLP:books/lib/Knuth86a, sharing, restful, mcintosh_anxiety, psychology25
-#bibliography(("/assets/bib/works.bib", "/assets/bib/works.bib"))
diff --git a/tests/typ/meta/cite-footnote.typ b/tests/typ/meta/cite-footnote.typ
deleted file mode 100644
index c6eff59d..00000000
--- a/tests/typ/meta/cite-footnote.typ
+++ /dev/null
@@ -1,5 +0,0 @@
-Hello @netwok
-And again: @netwok
-
-#pagebreak()
-#bibliography("/assets/bib/works.bib", style: "chicago-notes")
diff --git a/tests/typ/meta/cite-form.typ b/tests/typ/meta/cite-form.typ
deleted file mode 100644
index 343981d7..00000000
--- a/tests/typ/meta/cite-form.typ
+++ /dev/null
@@ -1,10 +0,0 @@
-// Test citation forms.
-
----
-#set page(width: 200pt)
-
-Nothing: #cite(<arrgh>, form: none)
-
-#cite(<netwok>, form: "prose") say stuff.
-
-#bibliography("/assets/bib/works.bib", style: "apa")
diff --git a/tests/typ/meta/cite-group.typ b/tests/typ/meta/cite-group.typ
deleted file mode 100644
index 331c87ca..00000000
--- a/tests/typ/meta/cite-group.typ
+++ /dev/null
@@ -1,19 +0,0 @@
-// Test citation grouping.
-
----
-A#[@netwok@arrgh]B \
-A@netwok@arrgh B \
-A@netwok @arrgh B \
-A@netwok @arrgh. B \
-
-A @netwok#[@arrgh]B \
-A @netwok@arrgh, B \
-A @netwok @arrgh, B \
-A @netwok @arrgh. B \
-
-A#[@netwok @arrgh @quark]B. \
-A @netwok @arrgh @quark B. \
-A @netwok @arrgh @quark, B.
-
-#set text(0pt)
-#bibliography("/assets/bib/works.bib")
diff --git a/tests/typ/meta/context-compatibility.typ b/tests/typ/meta/context-compatibility.typ
deleted file mode 100644
index 60124255..00000000
--- a/tests/typ/meta/context-compatibility.typ
+++ /dev/null
@@ -1,29 +0,0 @@
-// Test compatibility with the pre-context way of things.
-// Ref: false
-
----
-#let s = state("x", 0)
-#let compute(expr) = [
- #s.update(x =>
- eval(expr.replace("x", str(x)))
- )
- New value is #s.display().
-]
-
-#locate(loc => {
- let elem = query(<here>, loc).first()
- test(s.at(elem.location()), 13)
-})
-
-#compute("10") \
-#compute("x + 3") \
-*Here.* <here> \
-#compute("x * 2") \
-#compute("x - 5")
-
----
-#style(styles => measure([it], styles).width < 20pt)
-
----
-#counter(heading).update(10)
-#counter(heading).display(n => test(n, 10))
diff --git a/tests/typ/meta/context.typ b/tests/typ/meta/context.typ
deleted file mode 100644
index 729d9fa2..00000000
--- a/tests/typ/meta/context.typ
+++ /dev/null
@@ -1,181 +0,0 @@
-// Test context expressions.
-// Ref: false
-
----
-// Test that context body is parsed as atomic expression.
-#let c = [#context "hello".]
-#test(c.children.first().func(), (context none).func())
-#test(c.children.last(), [.])
-
----
-// Test that manual construction is forbidden.
-// Error: 2-25 cannot be constructed manually
-#(context none).func()()
-
----
-// Test that `here()` yields the context element's location.
-#context test(query(here()).first().func(), (context none).func())
-
----
-// Test whether context is retained in nested function.
-#let translate(..args) = args.named().at(text.lang)
-#set text(lang: "de")
-#context test(translate(de: "Inhalt", en: "Contents"), "Inhalt")
-
----
-// Test whether context is retained in built-in callback.
-#set text(lang: "de")
-#context test(
- ("en", "de", "fr").sorted(key: v => v != text.lang),
- ("de", "en", "fr"),
-)
-
----
-// Test `locate` + `here`.
-#context test(here().position().y, 10pt)
-
----
-// Test `locate`.
-#v(10pt)
-= Introduction <intro>
-#context test(locate(<intro>).position().y, 20pt)
-
----
-// Error: 10-25 label `<intro>` does not exist in the document
-#context locate(<intro>)
-
----
-= Introduction <intro>
-= Introduction <intro>
-
-// Error: 10-25 label `<intro>` occurs multiple times in the document
-#context locate(<intro>)
-
----
-#v(10pt)
-= Introduction <intro>
-#context test(locate(heading).position().y, 20pt)
-
----
-// Error: 10-25 selector does not match any element
-#context locate(heading)
-
----
-= Introduction <intro>
-= Introduction <intro>
-
-// Error: 10-25 selector matches multiple elements
-#context locate(heading)
-
----
-// Test `counter`.
-#let c = counter("heading")
-#c.update(2)
-#c.update(n => n + 2)
-#context test(c.get(), (4,))
-#c.update(n => n - 3)
-#context test(c.at(here()), (1,))
-
----
-// Test `state.at` outside of context.
-// Error: 2-26 can only be used when context is known
-// Hint: 2-26 try wrapping this in a `context` expression
-// Hint: 2-26 the `context` expression should wrap everything that depends on this function
-#state("key").at(<label>)
-
----
-// Test `counter.at` outside of context.
-// Error: 2-28 can only be used when context is known
-// Hint: 2-28 try wrapping this in a `context` expression
-// Hint: 2-28 the `context` expression should wrap everything that depends on this function
-#counter("key").at(<label>)
-
----
-// Test `measure`.
-#let f(lo, hi) = context {
- let h = measure[Hello].height
- assert(h > lo)
- assert(h < hi)
-}
-#text(10pt, f(6pt, 8pt))
-#text(20pt, f(13pt, 14pt))
-
----
-// Test basic get rule.
-#context test(text.lang, "en")
-#set text(lang: "de")
-#context test(text.lang, "de")
-#text(lang: "es", context test(text.lang, "es"))
-
----
-// Test folding.
-#set rect(stroke: red)
-#context {
- test(type(rect.stroke), stroke)
- test(rect.stroke.paint, red)
-}
-#[
- #set rect(stroke: 4pt)
- #context test(rect.stroke, 4pt + red)
-]
-#context test(rect.stroke, stroke(red))
-
----
-// We have one collision: `figure.caption` could be both the element and a get
-// rule for the `caption` field, which is settable. We always prefer the
-// element. It's unfortunate, but probably nobody writes
-// `set figure(caption: ..)` anyway.
-#test(type(figure.caption), function)
-#context test(type(figure.caption), function)
-
----
-// Error: 10-31 Assertion failed: "en" != "de"
-#context test(text.lang, "de")
-
----
-// Error: 15-20 function `text` does not contain field `langs`
-#context text.langs
-
----
-// Error: 18-22 function `heading` does not contain field `body`
-#context heading.body
-
----
-// Error: 7-11 can only be used when context is known
-// Hint: 7-11 try wrapping this in a `context` expression
-// Hint: 7-11 the `context` expression should wrap everything that depends on this function
-#text.lang
-
----
-// Error: 7-12 function `text` does not contain field `langs`
-#text.langs
-
----
-// Error: 10-14 function `heading` does not contain field `body`
-#heading.body
-
----
-// Test that show rule establishes context.
-#set heading(numbering: "1.")
-#show heading: it => test(
- counter(heading).get(),
- (intro: (1,), back: (2,)).at(str(it.label)),
-)
-
-= Introduction <intro>
-= Background <back>
-
----
-// Test that show rule on non-locatable element allows `query`.
-// Error: 18-47 Assertion failed: 2 != 3
-#show emph: _ => test(query(heading).len(), 3)
-#show strong: _ => test(query(heading).len(), 2)
-= Introduction
-= Background
-*Hi* _there_
-
----
-// Test error when captured variable is assigned to.
-#let i = 0
-// Error: 11-12 variables from outside the context expression are read-only and cannot be modified
-#context (i = 1)
diff --git a/tests/typ/meta/counter-page.typ b/tests/typ/meta/counter-page.typ
deleted file mode 100644
index 54134b6e..00000000
--- a/tests/typ/meta/counter-page.typ
+++ /dev/null
@@ -1,10 +0,0 @@
-// Test the page counter.
-
-#set page(height: 50pt, margin: (bottom: 20pt, rest: 10pt))
-#lorem(12)
-#set page(numbering: "(i)")
-#lorem(6)
-#pagebreak()
-#set page(numbering: "1 / 1")
-#counter(page).update(1)
-#lorem(20)
diff --git a/tests/typ/meta/counter.typ b/tests/typ/meta/counter.typ
deleted file mode 100644
index 6d72f246..00000000
--- a/tests/typ/meta/counter.typ
+++ /dev/null
@@ -1,52 +0,0 @@
-// Test counters.
-
----
-// Count with string key.
-#let mine = counter("mine!")
-
-Final: #context mine.final().at(0) \
-#mine.step()
-First: #context mine.display() \
-#mine.update(7)
-#context mine.display("1 of 1", both: true) \
-#mine.step()
-#mine.step()
-Second: #context mine.display("I")
-#mine.update(n => n * 2)
-#mine.step()
-
----
-// Count labels.
-#let label = <heya>
-#let count = context counter(label).display()
-#let elem(it) = [#box(it) #label]
-
-#elem[hey, there!] #count \
-#elem[more here!] #count
-
----
-// Count headings.
-#set heading(numbering: "1.a.")
-#show heading: set text(10pt)
-#counter(heading).step()
-
-= Alpha
-In #context counter(heading).display()
-== Beta
-
-#set heading(numbering: none)
-= Gamma
-#heading(numbering: "I.")[Delta]
-
-At Beta, it was #context {
- let it = query(heading).find(it => it.body == [Beta])
- numbering(it.numbering, ..counter(heading).at(it.location()))
-}
-
----
-// Count figures.
-#figure(numbering: "A", caption: [Four 'A's], kind: image, supplement: "Figure")[_AAAA!_]
-#figure(numbering: none, caption: [Four 'B's], kind: image, supplement: "Figure")[_BBBB!_]
-#figure(caption: [Four 'C's], kind: image, supplement: "Figure")[_CCCC!_]
-#counter(figure.where(kind: image)).update(n => n + 3)
-#figure(caption: [Four 'D's], kind: image, supplement: "Figure")[_DDDD!_]
diff --git a/tests/typ/meta/document.typ b/tests/typ/meta/document.typ
deleted file mode 100644
index b058bd96..00000000
--- a/tests/typ/meta/document.typ
+++ /dev/null
@@ -1,49 +0,0 @@
-// Test document and page-level styles.
-
----
-// This is okay.
-#set document(title: [Hello])
-What's up?
-
----
-// This, too.
-// Ref: false
-#set document(author: ("A", "B"), date: datetime.today())
-
----
-// Error: 21-28 expected datetime, none, or auto, found string
-#set document(date: "today")
-
----
-// This, too.
-// Error: 23-29 expected string, found integer
-#set document(author: (123,))
-What's up?
-
----
-Hello
-
-// Error: 2-30 document set rules must appear before any content
-#set document(title: [Hello])
-
----
-// Error: 2-12 can only be used in set rules
-#document()
-
----
-#box[
- // Error: 4-32 document set rules are not allowed inside of containers
- #set document(title: [Hello])
-]
-
----
-#box[
- // Error: 4-18 page configuration is not allowed inside of containers
- #set page("a4")
-]
-
----
-#box[
- // Error: 4-15 pagebreaks are not allowed inside of containers
- #pagebreak()
-]
diff --git a/tests/typ/meta/figure-caption.typ b/tests/typ/meta/figure-caption.typ
deleted file mode 100644
index 0cdc2bbb..00000000
--- a/tests/typ/meta/figure-caption.typ
+++ /dev/null
@@ -1,64 +0,0 @@
-// Test figure captions.
-
----
-// Test figure.caption element
-#show figure.caption: emph
-
-#figure(
- [Not italicized],
- caption: [Italicized],
-)
-
----
-// Test figure.caption element for specific figure kinds
-#show figure.caption.where(kind: table): underline
-
-#figure(
- [Not a table],
- caption: [Not underlined],
-)
-
-#figure(
- table[A table],
- caption: [Underlined],
-)
-
----
-// Test creating custom figure and custom caption
-
-#let gap = 0.7em
-#show figure.where(kind: "custom"): it => rect(inset: gap, {
- align(center, it.body)
- v(gap, weak: true)
- line(length: 100%)
- v(gap, weak: true)
- align(center, it.caption)
-})
-
-#figure(
- [A figure],
- kind: "custom",
- caption: [Hi],
- supplement: [A],
-)
-
-#show figure.caption: it => emph[
- #it.body
- (#it.supplement
- #context it.counter.display(it.numbering))
-]
-
-#figure(
- [Another figure],
- kind: "custom",
- caption: [Hi],
- supplement: [B],
-)
-
----
-// Ref: false
-#set figure.caption(position: top)
-
----
-// Error: 31-38 expected `top` or `bottom`, found horizon
-#set figure.caption(position: horizon)
diff --git a/tests/typ/meta/figure-localization.typ b/tests/typ/meta/figure-localization.typ
deleted file mode 100644
index 144d9d67..00000000
--- a/tests/typ/meta/figure-localization.typ
+++ /dev/null
@@ -1,34 +0,0 @@
-// Test localization-related figure features.
-
----
-// Test French
-#set text(lang: "fr")
-#figure(
- circle(),
- caption: [Un cercle.],
-)
-
----
-// Test Chinese
-#set text(lang: "zh")
-#figure(
- rect(),
- caption: [一个矩形],
-)
-
----
-// Test Russian
-#set text(lang: "ru")
-
-#figure(
- polygon.regular(size: 1cm, vertices: 8),
- caption: [Пятиугольник],
-)
-
----
-// Test Greek
-#set text(lang: "gr")
-#figure(
- circle(),
- caption: [Ένας κύκλος.],
-)
diff --git a/tests/typ/meta/figure.typ b/tests/typ/meta/figure.typ
deleted file mode 100644
index b6c1bfd6..00000000
--- a/tests/typ/meta/figure.typ
+++ /dev/null
@@ -1,111 +0,0 @@
-// Test figures.
-
----
-#set page(width: 150pt)
-#set figure(numbering: "I")
-
-We can clearly see that @fig-cylinder and
-@tab-complex are relevant in this context.
-
-#figure(
- table(columns: 2)[a][b],
- caption: [The basic table.],
-) <tab-basic>
-
-#figure(
- pad(y: -6pt, image("/assets/images/cylinder.svg", height: 2cm)),
- caption: [The basic shapes.],
- numbering: "I",
-) <fig-cylinder>
-
-#figure(
- table(columns: 3)[a][b][c][d][e][f],
- caption: [The complex table.],
-) <tab-complex>
-
----
-
-// Testing figures with tables.
-#figure(
- table(
- columns: 2,
- [Second cylinder],
- image("/assets/images/cylinder.svg"),
- ),
- caption: "A table containing images."
-) <fig-image-in-table>
-
----
-
-// Testing show rules with figures with a simple theorem display
-#show figure.where(kind: "theorem"): it => {
- let name = none
- if not it.caption == none {
- name = [ #emph(it.caption.body)]
- } else {
- name = []
- }
-
- let title = none
- if not it.numbering == none {
- title = it.supplement
- if not it.numbering == none {
- title += " " + it.counter.display(it.numbering)
- }
- }
- title = strong(title)
- pad(
- top: 0em, bottom: 0em,
- block(
- fill: green.lighten(90%),
- stroke: 1pt + green,
- inset: 10pt,
- width: 100%,
- radius: 5pt,
- breakable: false,
- [#title#name#h(0.1em):#h(0.2em)#it.body#v(0.5em)]
- )
- )
-}
-
-#set page(width: 150pt)
-#figure(
- $a^2 + b^2 = c^2$,
- supplement: "Theorem",
- kind: "theorem",
- caption: "Pythagoras' theorem.",
- numbering: "1",
-) <fig-formula>
-
-#figure(
- $a^2 + b^2 = c^2$,
- supplement: "Theorem",
- kind: "theorem",
- caption: "Another Pythagoras' theorem.",
- numbering: none,
-) <fig-formula>
-
-#figure(
- ```rust
- fn main() {
- println!("Hello!");
- }
- ```,
- caption: [Hello world in _rust_],
-)
-
----
-// Test breakable figures
-#set page(height: 6em)
-#show figure: set block(breakable: true)
-
-#figure(table[a][b][c][d][e], caption: [A table])
-
----
-// Test custom separator for figure caption
-#set figure.caption(separator: [ --- ])
-
-#figure(
- table(columns: 2)[a][b],
- caption: [The table with custom separator.],
-)
diff --git a/tests/typ/meta/footnote-break.typ b/tests/typ/meta/footnote-break.typ
deleted file mode 100644
index 9e213aeb..00000000
--- a/tests/typ/meta/footnote-break.typ
+++ /dev/null
@@ -1,16 +0,0 @@
-// Test footnotes that break across pages.
-
----
-#set page(height: 200pt)
-
-#lorem(5)
-#footnote[ // 1
- A simple footnote.
- #footnote[Well, not that simple ...] // 2
-]
-#lorem(15)
-#footnote[Another footnote: #lorem(30)] // 3
-#lorem(15)
-#footnote[My fourth footnote: #lorem(50)] // 4
-#lorem(15)
-#footnote[And a final footnote.] // 5
diff --git a/tests/typ/meta/footnote-columns.typ b/tests/typ/meta/footnote-columns.typ
deleted file mode 100644
index 0dc88930..00000000
--- a/tests/typ/meta/footnote-columns.typ
+++ /dev/null
@@ -1,9 +0,0 @@
-// Test footnotes in columns, even those
-// that are not enabled via `set page`.
-
----
-#set page(height: 120pt)
-#align(center, strong[Title])
-#show: columns.with(2)
-#lorem(3) #footnote(lorem(6))
-Hello there #footnote(lorem(2))
diff --git a/tests/typ/meta/footnote-container.typ b/tests/typ/meta/footnote-container.typ
deleted file mode 100644
index 2fa14fac..00000000
--- a/tests/typ/meta/footnote-container.typ
+++ /dev/null
@@ -1,32 +0,0 @@
-// Test footnotes in containers.
-
----
-// Test footnote in caption.
-Read the docs #footnote[https://typst.app/docs]!
-#figure(
- image("/assets/images/graph.png", width: 70%),
- caption: [
- A graph #footnote[A _graph_ is a structure with nodes and edges.]
- ]
-)
-More #footnote[just for ...] footnotes #footnote[... testing. :)]
-
----
-// Test duplicate footnotes.
-#let lang = footnote[Languages.]
-#let nums = footnote[Numbers.]
-
-/ "Hello": A word #lang
-/ "123": A number #nums
-
-- "Hello" #lang
-- "123" #nums
-
-+ "Hello" #lang
-+ "123" #nums
-
-#table(
- columns: 2,
- [Hello], [A word #lang],
- [123], [A number #nums],
-)
diff --git a/tests/typ/meta/footnote-invariant.typ b/tests/typ/meta/footnote-invariant.typ
deleted file mode 100644
index e4d6ded1..00000000
--- a/tests/typ/meta/footnote-invariant.typ
+++ /dev/null
@@ -1,9 +0,0 @@
-// Ensure that a footnote and the first line of its entry
-// always end up on the same page.
-
----
-#set page(height: 120pt)
-
-#lorem(13)
-
-There #footnote(lorem(20))
diff --git a/tests/typ/meta/footnote-refs.typ b/tests/typ/meta/footnote-refs.typ
deleted file mode 100644
index 0caee7bc..00000000
--- a/tests/typ/meta/footnote-refs.typ
+++ /dev/null
@@ -1,40 +0,0 @@
-// Test references to footnotes.
-
----
-A footnote #footnote[Hi]<fn> \
-A reference to it @fn
-
----
-// Multiple footnotes are refs
-First #footnote[A]<fn1> \
-Second #footnote[B]<fn2> \
-First ref @fn1 \
-Third #footnote[C] \
-Fourth #footnote[D]<fn4> \
-Fourth ref @fn4 \
-Second ref @fn2 \
-Second ref again @fn2
-
----
-// Forward reference
-Usage @fn \
-Definition #footnote[Hi]<fn>
-
----
-// Footnote ref in footnote
-#footnote[Reference to next @fn]
-#footnote[Reference to myself @fn]<fn>
-#footnote[Reference to previous @fn]
-
----
-// Styling
-#show footnote: text.with(fill: red)
-Real #footnote[...]<fn> \
-Ref @fn
-
----
-// Footnote call with label
-#footnote(<fn>)
-#footnote[Hi]<fn>
-#ref(<fn>)
-#footnote(<fn>)
diff --git a/tests/typ/meta/footnote-table.typ b/tests/typ/meta/footnote-table.typ
deleted file mode 100644
index a52d28ba..00000000
--- a/tests/typ/meta/footnote-table.typ
+++ /dev/null
@@ -1,23 +0,0 @@
-// Test footnotes in tables. When the table spans multiple pages, the footnotes
-// will all be after the table, but it shouldn't create any empty pages.
----
-
-#set page(height: 100pt)
-
-= Tables
-#table(
- columns: 2,
- [Hello footnote #footnote[This is a footnote.]],
- [This is more text],
- [This cell
- #footnote[This footnote is not on the same page]
- breaks over multiple pages.],
- image("/assets/images/tiger.jpg"),
-)
-
-#table(
- columns: 3,
- ..range(1, 10)
- .map(numbering.with("a"))
- .map(v => upper(v) + footnote(v))
-)
diff --git a/tests/typ/meta/footnote.typ b/tests/typ/meta/footnote.typ
deleted file mode 100644
index 8f56fea2..00000000
--- a/tests/typ/meta/footnote.typ
+++ /dev/null
@@ -1,34 +0,0 @@
-// Test footnotes.
-
----
-#footnote[Hi]
-
----
-// Test space collapsing before footnote.
-A#footnote[A] \
-A #footnote[A]
-
----
-// Test nested footnotes.
-First \
-Second #footnote[A, #footnote[B, #footnote[C]]] \
-Third #footnote[D, #footnote[E]] \
-Fourth
-
----
-// Currently, numbers a bit out of order if a nested footnote ends up in the
-// same frame as another one. :(
-#footnote[A, #footnote[B]], #footnote[C]
-
----
-// Test customization.
-#show footnote: set text(red)
-#show footnote.entry: set text(8pt, style: "italic")
-#set footnote.entry(
- indent: 0pt,
- gap: 0.6em,
- clearance: 0.3em,
- separator: repeat[.],
-)
-
-Beautiful footnotes. #footnote[Wonderful, aren't they?]
diff --git a/tests/typ/meta/heading.typ b/tests/typ/meta/heading.typ
deleted file mode 100644
index a253913e..00000000
--- a/tests/typ/meta/heading.typ
+++ /dev/null
@@ -1,73 +0,0 @@
-// Test headings.
-
----
-// Different number of equals signs.
-
-= Level 1
-== Level 2
-=== Level 3
-
-// After three, it stops shrinking.
-=========== Level 11
-
----
-// Heading vs. no heading.
-
-// Parsed as headings if at start of the context.
-/**/ = Level 1
-#[== Level 2]
-#box[=== Level 3]
-
-// Not at the start of the context.
-No = heading
-
-// Escaped.
-\= No heading
-
----
-// Blocks can continue the heading.
-
-= #[This
-is
-multiline.
-]
-
-= This
- is not.
-
----
-// Test styling.
-#show heading.where(level: 5): it => block(
- text(font: "Roboto", fill: eastern, it.body + [!])
-)
-
-= Heading
-===== Heading 🌍
-#heading(level: 5)[Heading]
-
----
-// Test setting the starting offset.
-#set heading(numbering: "1.1")
-#show heading.where(level: 2): set text(blue)
-= Level 1
-
-#heading(depth: 1)[We're twins]
-#heading(level: 1)[We're twins]
-
-== Real level 2
-
-#set heading(offset: 1)
-= Fake level 2
-== Fake level 3
-
----
-// Passing level directly still overrides all other set values
-#set heading(numbering: "1.1", offset: 1)
-#heading(level: 1)[Still level 1]
-
----
-// Edge cases.
-#set heading(numbering: "1.")
-=
-Not in heading
-=Nope
diff --git a/tests/typ/meta/link.typ b/tests/typ/meta/link.typ
deleted file mode 100644
index dd5bffa8..00000000
--- a/tests/typ/meta/link.typ
+++ /dev/null
@@ -1,77 +0,0 @@
-// Test hyperlinking.
-
----
-// Link syntax.
-https://example.com/
-
-// Link with body.
-#link("https://typst.org/")[Some text text text]
-
-// With line break.
-This link appears #link("https://google.com/")[in the middle of] a paragraph.
-
-// Certain prefixes are trimmed when using the `link` function.
-Contact #link("mailto:hi@typst.app") or
-call #link("tel:123") for more information.
-
----
-// Test that the period is trimmed.
-#show link: underline
-https://a.b.?q=%10#. \
-Wahttp://link \
-Nohttps:\//link \
-Nohttp\://comment
-
----
-// Verify that brackets are included in links.
-https://[::1]:8080/ \
-https://example.com/(paren) \
-https://example.com/#(((nested))) \
-
----
-// Check that unbalanced brackets are not included in links.
-#[https://example.com/] \
-https://example.com/)
-
----
-// Verify that opening brackets without closing brackets throw an error.
-// Error: 1-22 automatic links cannot contain unbalanced brackets, use the `link` function instead
-https://exam(ple.com/
-
----
-// Styled with underline and color.
-#show link: it => underline(text(fill: rgb("283663"), it))
-You could also make the
-#link("https://html5zombo.com/")[link look way more typical.]
-
----
-// Transformed link.
-#set page(height: 60pt)
-#let mylink = link("https://typst.org/")[LINK]
-My cool #box(move(dx: 0.7cm, dy: 0.7cm, rotate(10deg, scale(200%, mylink))))
-
----
-// Link containing a block.
-#link("https://example.com/", block[
- My cool rhino
- #box(move(dx: 10pt, image("/assets/images/rhino.png", width: 1cm)))
-])
-
----
-// Link to page one.
-#link((page: 1, x: 10pt, y: 20pt))[Back to the start]
-
----
-// Test link to label.
-Text <hey>
-#link(<hey>)[Go to text.]
-
----
-// Error: 2-20 label `<hey>` does not exist in the document
-#link(<hey>)[Nope.]
-
----
-Text <hey>
-Text <hey>
-// Error: 2-20 label `<hey>` occurs multiple times in the document
-#link(<hey>)[Nope.]
diff --git a/tests/typ/meta/numbering.typ b/tests/typ/meta/numbering.typ
deleted file mode 100644
index 9c0c9b66..00000000
--- a/tests/typ/meta/numbering.typ
+++ /dev/null
@@ -1,103 +0,0 @@
-// Test integrated numbering patterns.
-
----
-#for i in range(0, 9) {
- numbering("*", i)
- [ and ]
- numbering("I.a", i, i)
- [ for #i \ ]
-}
-
----
-#for i in range(0, 4) {
- numbering("A", i)
- [ for #i \ ]
-}
-... \
-#for i in range(26, 30) {
- numbering("A", i)
- [ for #i \ ]
-}
-... \
-#for i in range(702, 706) {
- numbering("A", i)
- [ for #i \ ]
-}
-
----
-#set text(lang: "he")
-#for i in range(9, 21, step: 2) {
- numbering("א.", i)
- [ עבור #i \ ]
-}
-
----
-#set text(lang: "zh", font: ("Linux Libertine", "Noto Serif CJK SC"))
-#for i in range(9, 21, step: 2){
- numbering("一", i)
- [ and ]
- numbering("壹", i)
- [ for #i \ ]
-}
-
----
-#set text(lang: "ja", font: ("Linux Libertine", "Noto Serif CJK JP"))
-#for i in range(0, 4) {
- numbering("イ", i)
- [ (or ]
- numbering("い", i)
- [) for #i \ ]
-}
-... \
-#for i in range(47, 51) {
- numbering("イ", i)
- [ (or ]
- numbering("い", i)
- [) for #i \ ]
-}
-... \
-#for i in range(2256, 2260) {
- numbering("イ", i)
- [ for #i \ ]
-}
-
----
-#set text(lang: "ko", font: ("Linux Libertine", "Noto Serif CJK KR"))
-#for i in range(0, 4) {
- numbering("가", i)
- [ (or ]
- numbering("ㄱ", i)
- [) for #i \ ]
-}
-... \
-#for i in range(47, 51) {
- numbering("가", i)
- [ (or ]
- numbering("ㄱ", i)
- [) for #i \ ]
-}
-... \
-#for i in range(2256, 2260) {
- numbering("ㄱ", i)
- [ for #i \ ]
-}
-
----
-#set text(lang: "jp", font: ("Linux Libertine", "Noto Serif CJK JP"))
-#for i in range(0, 9) {
- numbering("あ", i)
- [ and ]
- numbering("I.あ", i, i)
- [ for #i \ ]
-}
-
-#for i in range(0, 9) {
- numbering("ア", i)
- [ and ]
- numbering("I.ア", i, i)
- [ for #i \ ]
-}
-
----
-// Error: 17-19 number must be at least zero
-#numbering("1", -1)
diff --git a/tests/typ/meta/outline-entry.typ b/tests/typ/meta/outline-entry.typ
deleted file mode 100644
index 74a785a9..00000000
--- a/tests/typ/meta/outline-entry.typ
+++ /dev/null
@@ -1,59 +0,0 @@
-// Tests outline entry.
-
----
-#set page(width: 150pt)
-#set heading(numbering: "1.")
-
-#show outline.entry.where(
- level: 1
-): it => {
- v(12pt, weak: true)
- strong(it)
-}
-
-#outline(indent: auto)
-
-#set text(8pt)
-#show heading: set block(spacing: 0.65em)
-
-= Introduction
-= Background
-== History
-== State of the Art
-= Analysis
-== Setup
-
----
-#set page(width: 150pt, numbering: "I", margin: (bottom: 20pt))
-#set heading(numbering: "1.")
-#show outline.entry.where(level: 1): it => [
- #let loc = it.element.location()
- #let num = numbering(loc.page-numbering(), ..counter(page).at(loc))
- #emph(link(loc, it.body))
- #text(luma(100), box(width: 1fr, repeat[#it.fill.body;·]))
- #link(loc, num)
-]
-
-#counter(page).update(3)
-#outline(indent: auto, fill: repeat[--])
-
-#set text(8pt)
-#show heading: set block(spacing: 0.65em)
-
-= Top heading
-== Not top heading
-=== Lower heading
-=== Lower too
-== Also not top
-
-#pagebreak()
-#set page(numbering: "1")
-
-= Another top heading
-== Middle heading
-=== Lower heading
-
----
-// Error: 2-27 cannot outline metadata
-#outline(target: metadata)
-#metadata("hello")
diff --git a/tests/typ/meta/outline-first-par-indent.typ b/tests/typ/meta/outline-first-par-indent.typ
deleted file mode 100644
index 77396634..00000000
--- a/tests/typ/meta/outline-first-par-indent.typ
+++ /dev/null
@@ -1,15 +0,0 @@
-#set par(first-line-indent: 1.5em)
-#set heading(numbering: "1.1.a.")
-#show outline.entry.where(level: 1): it => {
- v(0.5em, weak: true)
- strong(it)
-}
-
-#outline()
-
-= Introduction
-= Background
-== History
-== State of the Art
-= Analysis
-== Setup
diff --git a/tests/typ/meta/outline-indent.typ b/tests/typ/meta/outline-indent.typ
deleted file mode 100644
index b0132d43..00000000
--- a/tests/typ/meta/outline-indent.typ
+++ /dev/null
@@ -1,60 +0,0 @@
-// Tests outline 'indent' option.
-
----
-// With heading numbering
-#set page(width: 200pt)
-#set heading(numbering: "1.a.")
-#outline()
-#outline(indent: false)
-#outline(indent: true)
-#outline(indent: none)
-#outline(indent: auto)
-#outline(indent: 2em)
-#outline(indent: n => ([-], [], [==], [====]).at(n))
-#outline(indent: n => "!" * calc.pow(2, n))
-
-= About ACME Corp.
-
-== History
-#lorem(10)
-
-== Products
-#lorem(10)
-
-=== Categories
-#lorem(10)
-
-==== General
-#lorem(10)
-
----
-// Without heading numbering
-#set page(width: 200pt)
-#outline()
-#outline(indent: false)
-#outline(indent: true)
-#outline(indent: none)
-#outline(indent: auto)
-#outline(indent: n => 2em * n)
-#outline(indent: n => ([-], [], [==], [====]).at(n))
-#outline(indent: n => "!" * calc.pow(2, n))
-
-= About ACME Corp.
-
-== History
-#lorem(10)
-
-== Products
-#lorem(10)
-
-=== Categories
-#lorem(10)
-
-==== General
-#lorem(10)
-
----
-// Error: 2-35 expected relative length or content, found dictionary
-#outline(indent: n => (a: "dict"))
-
-= Heading
diff --git a/tests/typ/meta/outline.typ b/tests/typ/meta/outline.typ
deleted file mode 100644
index 1d0bcf75..00000000
--- a/tests/typ/meta/outline.typ
+++ /dev/null
@@ -1,45 +0,0 @@
-#set page("a7", margin: 20pt, numbering: "1")
-#set heading(numbering: "(1/a)")
-#show heading.where(level: 1): set text(12pt)
-#show heading.where(level: 2): set text(10pt)
-#set math.equation(numbering: "1")
-
-#outline()
-#outline(title: [Figures], target: figure)
-#outline(title: [Equations], target: math.equation)
-
-= Introduction
-#lorem(12)
-
-= Analysis
-#lorem(10)
-
-#[
- #set heading(outlined: false)
- == Methodology
- #lorem(6)
-]
-
-== Math
-$x$ is a very useful constant. See it in action:
-$ x = x $
-
-== Interesting figures
-#figure(rect[CENSORED], kind: image, caption: [A picture showing a programmer at work.])
-#figure(table[1x1], caption: [A very small table.])
-
-== Programming
-```rust
-fn main() {
- panic!("in the disco");
-}
-```
-
-==== Deep Stuff
-Ok ...
-
-// Ensure 'bookmarked' option doesn't affect the outline
-#set heading(numbering: "(I)", bookmarked: false)
-
-= #text(blue)[Sum]mary
-#lorem(10)
diff --git a/tests/typ/meta/page-label.typ b/tests/typ/meta/page-label.typ
deleted file mode 100644
index 8d12fb13..00000000
--- a/tests/typ/meta/page-label.typ
+++ /dev/null
@@ -1,47 +0,0 @@
-#set page(margin: (bottom: 20pt, rest: 10pt))
-#let filler = lorem(20)
-
-// (i) - (ii). No style opt. because of suffix.
-#set page(numbering: "(i)")
-#filler
-#pagebreak()
-#filler
-
-// 3 - 4. Style opt. Page Label should use /D style.
-#set page(numbering: "1")
-#filler
-#pagebreak()
-#filler
-
-// I - IV. Style opt. Page Label should use /R style and start at 1 again.
-#set page(numbering: "I / I")
-#counter(page).update(1)
-#filler
-#pagebreak()
-#filler
-#pagebreak()
-#filler
-#pagebreak()
-#filler
-
-// Pre: ほ, Pre: ろ, Pre: は, Pre: に. No style opt. Uses prefix field entirely.
-// Counter update without numbering change.
-#set page(numbering: "Pre: い")
-#filler
-#pagebreak()
-#filler
-#counter(page).update(2)
-#filler
-#pagebreak()
-#filler
-#pagebreak()
-#filler
-
-// aa & ba. Style opt only for values <= 26. Page Label uses lower alphabet style.
-// Repeats letter each 26 pages or uses numbering directly as prefix.
-#set page(numbering: "a")
-#counter(page).update(27)
-#filler
-#pagebreak()
-#counter(page).update(53)
-#filler
diff --git a/tests/typ/meta/query-before-after.typ b/tests/typ/meta/query-before-after.typ
deleted file mode 100644
index 5f134093..00000000
--- a/tests/typ/meta/query-before-after.typ
+++ /dev/null
@@ -1,69 +0,0 @@
-
----
-#set page(
- paper: "a7",
- numbering: "1 / 1",
- margin: (bottom: 1cm, rest: 0.5cm),
-)
-
-#show heading.where(level: 1, outlined: true): it => [
- #it
-
- #set text(size: 12pt, weight: "regular")
- #outline(
- title: "Chapter outline",
- indent: true,
- target: heading
- .where(level: 1)
- .or(heading.where(level: 2))
- .after(it.location(), inclusive: true)
- .before(
- heading
- .where(level: 1, outlined: true)
- .after(it.location(), inclusive: false),
- inclusive: false,
- )
- )
-]
-
-#set heading(outlined: true, numbering: "1.")
-
-= Section 1
-== Subsection 1
-== Subsection 2
-=== Subsubsection 1
-=== Subsubsection 2
-== Subsection 3
-
-= Section 2
-== Subsection 1
-== Subsection 2
-
-= Section 3
-== Subsection 1
-== Subsection 2
-=== Subsubsection 1
-=== Subsubsection 2
-=== Subsubsection 3
-== Subsection 3
-
----
-
-#set page(
- paper: "a7",
- numbering: "1 / 1",
- margin: (bottom: 1cm, rest: 0.5cm),
-)
-
-#set heading(outlined: true, numbering: "1.")
-
-#context [
- Non-outlined elements:
- #(query(selector(heading).and(heading.where(outlined: false)))
- .map(it => it.body).join(", "))
-]
-
-#heading("A", outlined: false)
-#heading("B", outlined: true)
-#heading("C", outlined: true)
-#heading("D", outlined: false)
diff --git a/tests/typ/meta/query-figure.typ b/tests/typ/meta/query-figure.typ
deleted file mode 100644
index 1fe82372..00000000
--- a/tests/typ/meta/query-figure.typ
+++ /dev/null
@@ -1,41 +0,0 @@
-// Test a list of figures.
-
----
-#set page(
- paper: "a7",
- numbering: "1 / 1",
- margin: (bottom: 1cm, rest: 0.5cm),
-)
-
-#set figure(numbering: "I")
-#show figure: set image(width: 80%)
-
-= List of Figures
-#context {
- let elements = query(selector(figure).after(here()))
- for it in elements [
- Figure
- #numbering(it.numbering,
- ..counter(figure).at(it.location())):
- #it.caption.body
- #box(width: 1fr, repeat[.])
- #counter(page).at(it.location()).first() \
- ]
-}
-
-#figure(
- image("/assets/images/glacier.jpg"),
- caption: [Glacier melting],
-)
-
-#figure(
- rect[Just some stand-in text],
- kind: image,
- supplement: "Figure",
- caption: [Stand-in text],
-)
-
-#figure(
- image("/assets/images/tiger.jpg"),
- caption: [Tiger world],
-)
diff --git a/tests/typ/meta/query-header.typ b/tests/typ/meta/query-header.typ
deleted file mode 100644
index 5cbaa995..00000000
--- a/tests/typ/meta/query-header.typ
+++ /dev/null
@@ -1,30 +0,0 @@
-// Test creating a header with the query function.
-
----
-#set page(
- paper: "a7",
- margin: (y: 1cm, x: 0.5cm),
- header: context {
- smallcaps[Typst Academy]
- h(1fr)
- let after = query(selector(heading).after(here()))
- let before = query(selector(heading).before(here()))
- let elem = if before.len() != 0 {
- before.last()
- } else if after.len() != 0 {
- after.first()
- }
- emph(elem.body)
- }
-)
-
-#outline()
-
-= Introduction
-#lorem(35)
-
-= Background
-#lorem(35)
-
-= Approach
-#lorem(60)
diff --git a/tests/typ/meta/ref.typ b/tests/typ/meta/ref.typ
deleted file mode 100644
index 4cc824d7..00000000
--- a/tests/typ/meta/ref.typ
+++ /dev/null
@@ -1,48 +0,0 @@
-// Test references.
-
----
-#set heading(numbering: "1.")
-
-= Introduction <intro>
-See @setup.
-
-== Setup <setup>
-As seen in @intro, we proceed.
-
----
-// Error: 1-5 label `<foo>` does not exist in the document
-@foo
-
----
-= First <foo>
-= Second <foo>
-
-// Error: 1-5 label `<foo>` occurs multiple times in the document
-@foo
-
----
-#set heading(numbering: "1.", supplement: [Chapter])
-#set math.equation(numbering: "(1)", supplement: [Eq.])
-
-= Intro
-#figure(
- image("/assets/images/cylinder.svg", height: 1cm),
- caption: [A cylinder.],
- supplement: "Fig",
-) <fig1>
-
-#figure(
- image("/assets/images/tiger.jpg", height: 1cm),
- caption: [A tiger.],
- supplement: "Tig",
-) <fig2>
-
-$ A = 1 $ <eq1>
-
-#set math.equation(supplement: none)
-$ A = 1 $ <eq2>
-
-@fig1, @fig2, @eq1, (@eq2)
-
-#set ref(supplement: none)
-@fig1, @fig2, @eq1, @eq2
diff --git a/tests/typ/meta/state.typ b/tests/typ/meta/state.typ
deleted file mode 100644
index 3fa8ece7..00000000
--- a/tests/typ/meta/state.typ
+++ /dev/null
@@ -1,56 +0,0 @@
-// Test state.
-
----
-#let s = state("hey", "a")
-#let double(it) = 2 * it
-
-#s.update(double)
-#s.update(double)
-$ 2 + 3 $
-#s.update(double)
-
-Is: #context s.get(),
-Was: #context {
- let it = query(math.equation).first()
- s.at(it.location())
-}.
-
----
-// Try same key with different initial value.
-#context state("key", 2).get()
-#state("key").update(x => x + 1)
-#context state("key", 2).get()
-#context state("key", 3).get()
-#state("key").update(x => x + 1)
-#context state("key", 2).get()
-
----
-#set page(width: 200pt)
-#set text(8pt)
-
-#let ls = state("lorem", lorem(1000).split("."))
-#let loremum(count) = {
- context ls.get().slice(0, count).join(".").trim() + "."
- ls.update(list => list.slice(count))
-}
-
-#let fs = state("fader", red)
-#let trait(title) = block[
- #context text(fill: fs.get())[
- *#title:* #loremum(1)
- ]
- #fs.update(color => color.lighten(30%))
-]
-
-#trait[Boldness]
-#trait[Adventure]
-#trait[Fear]
-#trait[Anger]
-
----
-// Make sure that a warning is produced if the layout fails to converge.
-// Warning: layout did not converge within 5 attempts
-// Hint: check if any states or queries are updating themselves
-#let s = state("s", 1)
-#context s.update(s.final() + 1)
-#context s.get()
diff --git a/tests/typ/text/baseline.typ b/tests/typ/text/baseline.typ
deleted file mode 100644
index c8fbb19d..00000000
--- a/tests/typ/text/baseline.typ
+++ /dev/null
@@ -1,12 +0,0 @@
-// Test baseline handling.
-
----
-Hi #text(1.5em)[You], #text(0.75em)[how are you?]
-
-Our cockatoo was one of the
-#text(baseline: -0.2em)[#box(circle(radius: 2pt)) first]
-#text(baseline: 0.2em)[birds #box(circle(radius: 2pt))]
-that ever learned to mimic a human voice.
-
----
-Hey #box(baseline: 40%, image("/assets/images/tiger.jpg", width: 1.5cm)) there!
diff --git a/tests/typ/text/case.typ b/tests/typ/text/case.typ
deleted file mode 100644
index 75574f21..00000000
--- a/tests/typ/text/case.typ
+++ /dev/null
@@ -1,12 +0,0 @@
-// Test the `upper` and `lower` functions.
-// Ref: false
-
----
-#let memes = "ArE mEmEs gReAt?";
-#test(lower(memes), "are memes great?")
-#test(upper(memes), "ARE MEMES GREAT?")
-#test(upper("Ελλάδα"), "ΕΛΛΆΔΑ")
-
----
-// Error: 8-9 expected string or content, found integer
-#upper(1)
diff --git a/tests/typ/text/chinese.typ b/tests/typ/text/chinese.typ
deleted file mode 100644
index 258d72fd..00000000
--- a/tests/typ/text/chinese.typ
+++ /dev/null
@@ -1,9 +0,0 @@
-// Test chinese text from Wikipedia.
-
----
-#set text(font: "Noto Serif CJK SC")
-
-是美国广播公司电视剧《迷失》第3季的第22和23集,也是全剧的第71集和72集
-由执行制作人戴蒙·林道夫和卡尔顿·库斯编剧,导演则是另一名执行制作人杰克·本德
-节目于2007年5月23日在美国和加拿大首播,共计吸引了1400万美国观众收看
-本集加上插播广告一共也持续有两个小时
diff --git a/tests/typ/text/copy-paste.typ b/tests/typ/text/copy-paste.typ
deleted file mode 100644
index 5d826482..00000000
--- a/tests/typ/text/copy-paste.typ
+++ /dev/null
@@ -1,8 +0,0 @@
-// Test copy-paste and search in PDF with ligatures
-// and Arabic test. Must be tested manually!
-
----
-The after fira 🏳️‍🌈!
-
-#set text(lang: "ar", font: "Noto Sans Arabic")
-مرحبًا
diff --git a/tests/typ/text/deco.typ b/tests/typ/text/deco.typ
deleted file mode 100644
index cc9b9b3a..00000000
--- a/tests/typ/text/deco.typ
+++ /dev/null
@@ -1,85 +0,0 @@
-// Test text decorations.
-
----
-#let red = rgb("fc0030")
-
-// Basic strikethrough.
-#strike[Statements dreamt up by the utterly deranged.]
-
-// Move underline down.
-#underline(offset: 5pt)[Further below.]
-
-// Different color.
-#underline(stroke: red, evade: false)[Critical information is conveyed here.]
-
-// Inherits font color.
-#text(fill: red, underline[Change with the wind.])
-
-// Both over- and underline.
-#overline(underline[Running amongst the wolves.])
-
----
-#let redact = strike.with(stroke: 10pt, extent: 0.05em)
-#let highlight-custom = strike.with(stroke: 10pt + rgb("abcdef88"), extent: 0.05em)
-
-// Abuse thickness and transparency for redacting and highlighting stuff.
-Sometimes, we work #redact[in secret].
-There might be #highlight-custom[redacted] things.
-
----
-// Test stroke folding.
-#set underline(stroke: 2pt, offset: 2pt)
-#underline(text(red, [DANGER!]))
-
----
-// Test highlight.
-This is the built-in #highlight[highlight with default color].
-We can also specify a customized value
-#highlight(fill: green.lighten(80%))[to highlight].
-
----
-// Test default highlight bounds.
-#highlight[ace],
-#highlight[base],
-#highlight[super],
-#highlight[phone #sym.integral]
-
----
-// Test a tighter highlight.
-#set highlight(top-edge: "x-height", bottom-edge: "baseline")
-#highlight[ace],
-#highlight[base],
-#highlight[super],
-#highlight[phone #sym.integral]
-
----
-// Test a bounds highlight.
-#set highlight(top-edge: "bounds", bottom-edge: "bounds")
-#highlight[abc]
-#highlight[abc #sym.integral]
-
----
-// Test highlight radius
-#highlight(radius: 3pt)[abc],
-#highlight(radius: 1em)[#lorem(5)]
-
----
-// Test highlight stroke
-#highlight(stroke: 2pt + blue)[abc]
-#highlight(stroke: (top: blue, left: red, bottom: green, right: orange))[abc]
-#highlight(stroke: 1pt, radius: 3pt)[#lorem(5)]
-
----
-// Test underline background
-#set underline(background: true, stroke: (thickness: 0.5em, paint: red, cap: "round"))
-#underline[This is in the background]
-
----
-// Test overline background
-#set overline(background: true, stroke: (thickness: 0.5em, paint: red, cap: "round"))
-#overline[This is in the background]
-
----
-// Test strike background
-#set strike(background: true, stroke: 5pt + red)
-#strike[This is in the background]
diff --git a/tests/typ/text/edge.typ b/tests/typ/text/edge.typ
deleted file mode 100644
index 053576e8..00000000
--- a/tests/typ/text/edge.typ
+++ /dev/null
@@ -1,39 +0,0 @@
-// Test top and bottom text edge.
-
----
-#set page(width: 160pt)
-#set text(size: 8pt)
-
-#let try(top, bottom) = rect(inset: 0pt, fill: conifer)[
- #set text(font: "IBM Plex Mono", top-edge: top, bottom-edge: bottom)
- From #top to #bottom
-]
-
-#let try-bounds(top, bottom) = rect(inset: 0pt, fill: conifer)[
- #set text(font: "IBM Plex Mono", top-edge: top, bottom-edge: bottom)
- #top to #bottom: "yay, Typst"
-]
-
-#try("ascender", "descender")
-#try("ascender", "baseline")
-#try("cap-height", "baseline")
-#try("x-height", "baseline")
-#try-bounds("cap-height", "baseline")
-#try-bounds("bounds", "baseline")
-#try-bounds("bounds", "bounds")
-#try-bounds("x-height", "bounds")
-
-#try(4pt, -2pt)
-#try(1pt + 0.3em, -0.15em)
-
----
-// Error: 21-23 expected "ascender", "cap-height", "x-height", "baseline", "bounds", or length, found array
-#set text(top-edge: ())
-
----
-// Error: 24-26 expected "baseline", "descender", "bounds", or length
-#set text(bottom-edge: "")
-
----
-// Error: 24-36 expected "baseline", "descender", "bounds", or length
-#set text(bottom-edge: "cap-height")
diff --git a/tests/typ/text/em.typ b/tests/typ/text/em.typ
deleted file mode 100644
index bf191c1f..00000000
--- a/tests/typ/text/em.typ
+++ /dev/null
@@ -1,33 +0,0 @@
-// Test font-relative sizing.
-
----
-#set text(size: 5pt)
-A // 5pt
-#[
- #set text(size: 2em)
- B // 10pt
- #[
- #set text(size: 1.5em + 1pt)
- C // 16pt
- #text(size: 2em)[D] // 32pt
- E // 16pt
- ]
- F // 10pt
-]
-G // 5pt
-
----
-// Test using ems in arbitrary places.
-#set text(size: 5pt)
-#set text(size: 2em)
-#set square(fill: red)
-
-#let size = {
- let size = 0.25em + 1pt
- for _ in range(3) {
- size *= 2
- }
- size - 3pt
-}
-
-#stack(dir: ltr, spacing: 1fr, square(size: size), square(size: 25pt))
diff --git a/tests/typ/text/emoji.typ b/tests/typ/text/emoji.typ
deleted file mode 100644
index f8e0d5b6..00000000
--- a/tests/typ/text/emoji.typ
+++ /dev/null
@@ -1,18 +0,0 @@
-// Test emoji shaping.
-
----
-// This should form a three-member family.
-👩‍👩‍👦
-
-// This should form a pride flag.
-🏳️‍🌈
-
-// Skin tone modifier should be applied.
-👍🏿
-
-// This should be a 1 in a box.
-1️⃣
-
----
-// These two shouldn't be affected by a zero-width joiner.
-🏞‍🌋
diff --git a/tests/typ/text/emphasis.typ b/tests/typ/text/emphasis.typ
deleted file mode 100644
index 93913dcf..00000000
--- a/tests/typ/text/emphasis.typ
+++ /dev/null
@@ -1,50 +0,0 @@
-// Test emph and strong.
-
----
-// Basic.
-_Emphasized and *strong* words!_
-
-// Inside of a word it's a normal underscore or star.
-hello_world Nutzer*innen
-
-// CJK characters will not need spaces.
-中文一般使用*粗体*或者_楷体_来表示强调。
-
-日本語では、*太字*や_斜体_を使って強調します。
-
-中文中混有*Strong*和_Empasis_。
-
-// Can contain paragraph in nested content block.
-_Still #[
-
-] emphasized._
-
----
-// Inside of words can still use the functions.
-P#strong[art]ly em#emph[phas]ized.
-
----
-// Adjusting the delta that strong applies on the weight.
-Normal
-
-#set strong(delta: 300)
-*Bold*
-
-#set strong(delta: 150)
-*Medium* and *#[*Bold*]*
-
----
-// Error: 6-7 unclosed delimiter
-#box[_Scoped] to body.
-
----
-// Ends at paragraph break.
-// Error: 1-2 unclosed delimiter
-_Hello
-
-World
-
----
-// Error: 11-12 unclosed delimiter
-// Error: 3-4 unclosed delimiter
-#[_Cannot *be interleaved]
diff --git a/tests/typ/text/escape.typ b/tests/typ/text/escape.typ
deleted file mode 100644
index 901632ba..00000000
--- a/tests/typ/text/escape.typ
+++ /dev/null
@@ -1,36 +0,0 @@
-// Test escape sequences.
-
----
-// Escapable symbols.
-\\ \/ \[ \] \{ \} \# \* \_ \+ \= \~ \
-\` \$ \" \' \< \> \@ \( \) \A
-
-// No need to escape.
-( ) ;
-
-// Escaped comments.
-\//
-\/\* \*\/
-\/* \*/ *
-
-// Unicode escape sequence.
-\u{1F3D5} == 🏕
-
-// Escaped escape sequence.
-\u{41} vs. \\u\{41\}
-
-// Some code stuff in text.
-let f() , ; : | + - /= == 12 "string"
-
-// Escaped dot.
-10\. May
-
----
-// Unicode codepoint does not exist.
-// Error: 1-11 invalid Unicode codepoint: FFFFFF
-\u{FFFFFF}
-
----
-// Unterminated.
-// Error: 1-6 unclosed Unicode escape sequence
-\u{41[*Bold*]
diff --git a/tests/typ/text/fallback.typ b/tests/typ/text/fallback.typ
deleted file mode 100644
index 1db85945..00000000
--- a/tests/typ/text/fallback.typ
+++ /dev/null
@@ -1,20 +0,0 @@
-// Test font fallback.
-
----
-// Font fallback for emoji.
-A😀B
-
-// Font fallback for entire text.
-دع النص يمطر عليك
-
-// Font fallback in right-to-left text.
-ب🐈😀سم
-
-// Multi-layer font fallback.
-Aب😀🏞سمB
-
-// Font fallback with composed emojis and multiple fonts.
-01️⃣2
-
-// Tofus are rendered with the first font.
-A🐈ዲሞB
diff --git a/tests/typ/text/features.typ b/tests/typ/text/features.typ
deleted file mode 100644
index 69a7064f..00000000
--- a/tests/typ/text/features.typ
+++ /dev/null
@@ -1,63 +0,0 @@
-// Test OpenType features.
-
----
-// Test turning kerning off.
-#text(kerning: true)[Tq] \
-#text(kerning: false)[Tq]
-
----
-// Test smallcaps.
-#smallcaps[Smallcaps]
-
----
-// Test alternates and stylistic sets.
-#set text(font: "IBM Plex Serif")
-a vs #text(alternates: true)[a] \
-ß vs #text(stylistic-set: 5)[ß]
-
----
-// Test ligatures.
-fi vs. #text(ligatures: false)[No fi]
-
----
-// Test number type.
-#set text(number-type: "old-style")
-0123456789 \
-#text(number-type: auto)[0123456789]
-
----
-// Test number width.
-#text(number-width: "proportional")[0123456789] \
-#text(number-width: "tabular")[3456789123] \
-#text(number-width: "tabular")[0123456789]
-
----
-// Test extra number stuff.
-#set text(font: "IBM Plex Serif")
-0 vs. #text(slashed-zero: true)[0] \
-1/2 vs. #text(fractions: true)[1/2]
-
----
-// Test raw features.
-#text(features: ("smcp",))[Smcp] \
-fi vs. #text(features: (liga: 0))[No fi]
-
----
-// Error: 26-31 expected integer or none, found boolean
-#set text(stylistic-set: false)
-
----
-// Error: 26-28 stylistic set must be between 1 and 20
-#set text(stylistic-set: 25)
-
----
-// Error: 24-25 expected "lining", "old-style", or auto, found integer
-#set text(number-type: 2)
-
----
-// Error: 21-26 expected array or dictionary, found boolean
-#set text(features: false)
-
----
-// Error: 21-35 expected string, found boolean
-#set text(features: ("tag", false))
diff --git a/tests/typ/text/font.typ b/tests/typ/text/font.typ
deleted file mode 100644
index 736ded41..00000000
--- a/tests/typ/text/font.typ
+++ /dev/null
@@ -1,66 +0,0 @@
-// Test configuring font properties.
-
----
-// Set same font size in three different ways.
-#text(20pt)[A]
-#text(2em)[A]
-#text(size: 15pt + 0.5em)[A]
-
-// Do nothing.
-#text()[Normal]
-
-// Set style (is available).
-#text(style: "italic")[Italic]
-
-// Set weight (is available).
-#text(weight: "bold")[Bold]
-
-// Set stretch (not available, matching closest).
-#text(stretch: 50%)[Condensed]
-
-// Set font family.
-#text(font: "IBM Plex Serif")[Serif]
-
-// Emoji.
-Emoji: 🐪, 🌋, 🏞
-
-// Colors.
-#[
- #set text(fill: eastern)
- This is #text(rgb("FA644B"))[way more] colorful.
-]
-
-// Transparency.
-#block(fill: green)[
- #set text(fill: rgb("FF000080"))
- This text is transparent.
-]
-
-// Disable font fallback beyond the user-specified list.
-// Without disabling, New Computer Modern Math would come to the rescue.
-#set text(font: ("PT Sans", "Twitter Color Emoji"), fallback: false)
-2π = 𝛼 + 𝛽. ✅
-
----
-// Test string body.
-#text("Text") \
-#text(red, "Text") \
-#text(font: "Ubuntu", blue, "Text") \
-#text([Text], teal, font: "IBM Plex Serif") \
-#text(forest, font: "New Computer Modern", [Text]) \
-
----
-// Error: 11-16 unexpected argument
-#set text(false)
-
----
-// Error: 18-24 expected "normal", "italic", or "oblique"
-#set text(style: "bold", weight: "thin")
-
----
-// Error: 23-27 unexpected argument
-#set text(size: 10pt, 12pt)
-
----
-// Error: 11-31 unexpected argument: something
-#set text(something: "invalid")
diff --git a/tests/typ/text/hyphenate.typ b/tests/typ/text/hyphenate.typ
deleted file mode 100644
index 42946a88..00000000
--- a/tests/typ/text/hyphenate.typ
+++ /dev/null
@@ -1,51 +0,0 @@
-// Test hyphenation.
-
----
-// Test hyphenating english and greek.
-#set text(hyphenate: true)
-#set page(width: auto)
-#grid(
- columns: (50pt, 50pt),
- [Warm welcomes to Typst.],
- text(lang: "el")[διαμερίσματα. \ λατρευτός],
-)
-
----
-// Test disabling hyphenation for short passages.
-#set page(width: 110pt)
-#set text(hyphenate: true)
-
-Welcome to wonderful experiences. \
-Welcome to `wonderful` experiences. \
-Welcome to #text(hyphenate: false)[wonderful] experiences. \
-Welcome to wonde#text(hyphenate: false)[rf]ul experiences. \
-
-// Test enabling hyphenation for short passages.
-#set text(hyphenate: false)
-Welcome to wonderful experiences. \
-Welcome to wo#text(hyphenate: true)[nd]erful experiences. \
-
----
-// Hyphenate between shape runs.
-#set page(width: 80pt)
-#set text(hyphenate: true)
-It's a #emph[Tree]beard.
-
----
-// Test shy hyphens.
-#set text(lang: "de", hyphenate: true)
-#grid(
- columns: 2 * (20pt,),
- gutter: 20pt,
- [Barankauf],
- [Bar-?ankauf],
-)
-
----
-// This sequence would confuse hypher if we passed trailing / leading
-// punctuation instead of just the words. So this tests that we don't
-// do that. The test passes if there's just one hyphenation between
-// "net" and "works".
-#set page(width: 60pt)
-#set text(hyphenate: true)
-#h(6pt) networks, the rest.
diff --git a/tests/typ/text/lang-with-region.typ b/tests/typ/text/lang-with-region.typ
deleted file mode 100644
index f890a00f..00000000
--- a/tests/typ/text/lang-with-region.typ
+++ /dev/null
@@ -1,16 +0,0 @@
-// Test if text with region works
-
----
-// without any region
-#set text(font: "Noto Serif CJK TC", lang: "zh")
-#outline()
-
----
-// with unknown region configured
-#set text(font: "Noto Serif CJK TC", lang: "zh", region: "XX")
-#outline()
-
----
-// with region configured
-#set text(font: "Noto Serif CJK TC", lang: "zh", region: "TW")
-#outline()
diff --git a/tests/typ/text/lang.typ b/tests/typ/text/lang.typ
deleted file mode 100644
index 7f1ae1fc..00000000
--- a/tests/typ/text/lang.typ
+++ /dev/null
@@ -1,59 +0,0 @@
-// Test setting the document language.
-
----
-// Ensure that setting the language does have effects.
-#set text(hyphenate: true)
-#grid(
- columns: 2 * (20pt,),
- gutter: 1fr,
- text(lang: "en")["Eingabeaufforderung"],
- text(lang: "de")["Eingabeaufforderung"],
-)
-
----
-// Test that the language passed to the shaper has an effect.
-#set text(font: "Ubuntu")
-
-// Some lowercase letters are different in Serbian Cyrillic compared to other
-// Cyrillic languages. Since there is only one set of Unicode codepoints for
-// Cyrillic, these can only be seen when setting the language to Serbian and
-// selecting one of the few fonts that support these letterforms.
-Бб
-#text(lang: "uk")[Бб]
-#text(lang: "sr")[Бб]
-
----
-// Verify that writing script/language combination has an effect
-#{
- set text(size:20pt)
- set text(script: "latn", lang: "en")
- [Ş ]
- set text(script: "latn", lang: "ro")
- [Ş ]
- set text(script: "grek", lang: "ro")
- [Ş ]
-}
-
----
-// Error: 19-23 expected string or auto, found none
-#set text(script: none)
-
----
-// Error: 19-23 expected three or four letter script code (ISO 15924 or 'math')
-#set text(script: "ab")
-
----
-// Error: 17-21 expected string, found none
-#set text(lang: none)
-
----
-// Error: 17-20 expected two or three letter language code (ISO 639-1/2/3)
-#set text(lang: "ӛ")
-
----
-// Error: 17-20 expected two or three letter language code (ISO 639-1/2/3)
-#set text(lang: "😃")
-
----
-// Error: 19-24 expected two letter region code (ISO 3166-1 alpha-2)
-#set text(region: "hey")
diff --git a/tests/typ/text/linebreak-link.typ b/tests/typ/text/linebreak-link.typ
deleted file mode 100644
index 9f6407fa..00000000
--- a/tests/typ/text/linebreak-link.typ
+++ /dev/null
@@ -1,23 +0,0 @@
-// Test linebreaking of links.
-
----
-#link("https://example.com/(ab") \
-#link("https://example.com/(ab)") \
-#link("https://example.com/(paren)") \
-#link("https://example.com/paren)") \
-#link("https://hi.com/%%%%%%%%abcdef") \
-
----
-#set page(width: 240pt)
-#set par(justify: true)
-
-Here's a link https://url.com/data/extern12840%data_urlenc and then there are more
-links #link("www.url.com/data/extern12840%data_urlenc") in my text of links
-http://mydataurl/hash/12098541029831025981024980124124214/incremental/progress%linkdata_information_setup_my_link_just_never_stops_going/on?query=false
-
----
-// Ensure that there's no unconditional break at the end of a link.
-#set page(width: 180pt, height: auto, margin: auto)
-#set text(11pt)
-
-For info see #link("https://myhost.tld").
diff --git a/tests/typ/text/linebreak-obj.typ b/tests/typ/text/linebreak-obj.typ
deleted file mode 100644
index ebf55f15..00000000
--- a/tests/typ/text/linebreak-obj.typ
+++ /dev/null
@@ -1,24 +0,0 @@
-// Test linebreaks with after inline elements.
-
----
-// Test punctuation after citations.
-#set page(width: 162pt)
-
-They can look for the details in @netwok,
-which is the authoritative source.
-
-#bibliography("/assets/bib/works.bib")
-
----
-// Test punctuation after math equations.
-#set page(width: 85pt)
-
-We prove $1 < 2$. \
-We prove $1 < 2$! \
-We prove $1 < 2$? \
-We prove $1 < 2$, \
-We prove $1 < 2$; \
-We prove $1 < 2$: \
-We prove $1 < 2$- \
-We prove $1 < 2$– \
-We prove $1 < 2$— \
diff --git a/tests/typ/text/linebreak.typ b/tests/typ/text/linebreak.typ
deleted file mode 100644
index 2ddeb9ed..00000000
--- a/tests/typ/text/linebreak.typ
+++ /dev/null
@@ -1,60 +0,0 @@
-// Test line breaks.
-
----
-// Test overlong word that is not directly after a hard break.
-This is a spaceexceedinglylongy.
-
----
-// Test two overlong words in a row.
-Supercalifragilisticexpialidocious Expialigoricmetrioxidation.
-
----
-// Test for non-breaking space and hyphen.
-There are non\u{2011}breaking~characters.
-
----
-// Test for narrow non-breaking space.
-#show "_": sym.space.nobreak.narrow
-0.1_g, 1_g, 10_g, 100_g, 1_000_g, 10_000_g, 100_000_g, 1_000_000_g
-
----
-// Test that there are no unwanted line break opportunities on run change.
-This is partly emp#emph[has]ized.
-
----
-Hard #linebreak() break.
-
----
-// Test hard break directly after normal break.
-Hard break directly after \ normal break.
-
----
-// Test consecutive breaks.
-Two consecutive \ \ breaks and three \ \ more.
-
----
-// Test forcing an empty trailing line.
-Trailing break \ \
-
----
-// Test justified breaks.
-#set par(justify: true)
-With a soft #linebreak(justify: true)
-break you can force a break without #linebreak(justify: true)
-breaking justification. #linebreak(justify: false)
-Nice!
-
----
-// Test comments at the end of a line
-First part//
-Second part
-
-// Test comments at the end of a line with pre-spacing
-First part //
-Second part
-
----
-// Test linebreak for East Asian languages
-ทีวีตรวจทานนอร์ทแฟรีเลคเชอร์โกลด์อัลบัมเชอร์รี่เย้วสโตร์กฤษณ์เคลมเยอบีร่าพ่อค้าบลูเบอร์รี่สหัสวรรษโฮปแคนูโยโย่จูนสตรอว์เบอร์รีซื่อบื้อเยนแบ็กโฮเป็นไงโดนัททอมสเตริโอแคนูวิทย์แดรี่โดนัทวิทย์แอปพริคอทเซอร์ไพรส์ไฮบริดกิฟท์อินเตอร์โซนเซอร์วิสเทียมทานโคโยตี้ม็อบเที่ยงคืนบุญคุณ
-
-
diff --git a/tests/typ/text/lorem.typ b/tests/typ/text/lorem.typ
deleted file mode 100644
index 804f804d..00000000
--- a/tests/typ/text/lorem.typ
+++ /dev/null
@@ -1,32 +0,0 @@
-// Test blind text.
-
----
-// Test basic call.
-#lorem(19)
-
----
-// Test custom paragraphs with user code.
-#set text(8pt)
-
-#{
- let sentences = lorem(59)
- .split(".")
- .filter(s => s != "")
- .map(s => s + ".")
-
- let used = 0
- for s in sentences {
- if used < 2 {
- used += 1
- } else {
- parbreak()
- used = 0
- }
- s.trim()
- [ ]
- }
-}
-
----
-// Error: 2-9 missing argument: words
-#lorem()
diff --git a/tests/typ/text/microtype.typ b/tests/typ/text/microtype.typ
deleted file mode 100644
index 4c2b4d0b..00000000
--- a/tests/typ/text/microtype.typ
+++ /dev/null
@@ -1,23 +0,0 @@
-// Test micro-typographical shenanigans.
-
----
-// Test hanging punctuation.
-#set page(width: 130pt, margin: 15pt)
-#set par(justify: true, linebreaks: "simple")
-#set text(size: 9pt)
-#rect(inset: 0pt, fill: rgb(0, 0, 0, 0), width: 100%)[
- This is a little bit of text that builds up to
- hang-ing hyphens and dash---es and then, you know,
- some punctuation in the margin.
-]
-
-// Test hanging punctuation with RTL.
-#set text(lang: "he", font: ("PT Sans", "Noto Serif Hebrew"))
-בנייה נכונה של משפטים ארוכים דורשת ידע בשפה. אז בואו נדבר על מזג האוויר.
-
----
-// Test that lone punctuation doesn't overhang into the margin.
-#set page(margin: 0pt)
-#set align(end)
-#set text(dir: rtl)
-:
diff --git a/tests/typ/text/numbers.typ b/tests/typ/text/numbers.typ
deleted file mode 100644
index a35798d1..00000000
--- a/tests/typ/text/numbers.typ
+++ /dev/null
@@ -1,110 +0,0 @@
-// Test how numbers are displayed.
-
----
-// Test numbers in text mode.
-12 \
-12.0 \
-3.14 \
-1234567890 \
-0123456789 \
-0 \
-0.0 \
-+0 \
-+0.0 \
--0 \
--0.0 \
--1 \
--3.14 \
--9876543210 \
--0987654321 \
-٣٫١٤ \
--٣٫١٤ \
--¾ \
-#text(fractions: true)[-3/2] \
-2022 - 2023 \
-2022 -- 2023 \
-2022--2023 \
-2022-2023 \
-٢٠٢٢ - ٢٠٢٣ \
-٢٠٢٢ -- ٢٠٢٣ \
-٢٠٢٢--٢٠٢٣ \
-٢٠٢٢-٢٠٢٣ \
--500 -- -400
-
----
-// Test integers.
-#12 \
-#1234567890 \
-#0123456789 \
-#0 \
-#(-0) \
-#(-1) \
-#(-9876543210) \
-#(-0987654321) \
-#(4 - 8)
-
----
-// Test floats.
-#12.0 \
-#3.14 \
-#1234567890.0 \
-#0123456789.0 \
-#0.0 \
-#(-0.0) \
-#(-1.0) \
-#(-9876543210.0) \
-#(-0987654321.0) \
-#(-3.14) \
-#(4.0 - 8.0)
-
----
-// Test the `str` function with integers.
-#str(12) \
-#str(1234567890) \
-#str(0123456789) \
-#str(0) \
-#str(-0) \
-#str(-1) \
-#str(-9876543210) \
-#str(-0987654321) \
-#str(4 - 8)
-
----
-// Test the `str` function with floats.
-#str(12.0) \
-#str(3.14) \
-#str(1234567890.0) \
-#str(0123456789.0) \
-#str(0.0) \
-#str(-0.0) \
-#str(-1.0) \
-#str(-9876543210.0) \
-#str(-0987654321.0) \
-#str(-3.14) \
-#str(4.0 - 8.0)
-
----
-// Test the `repr` function with integers.
-#repr(12) \
-#repr(1234567890) \
-#repr(0123456789) \
-#repr(0) \
-#repr(-0) \
-#repr(-1) \
-#repr(-9876543210) \
-#repr(-0987654321) \
-#repr(4 - 8)
-
----
-// Test the `repr` function with floats.
-#repr(12.0) \
-#repr(3.14) \
-#repr(1234567890.0) \
-#repr(0123456789.0) \
-#repr(0.0) \
-#repr(-0.0) \
-#repr(-1.0) \
-#repr(-9876543210.0) \
-#repr(-0987654321.0) \
-#repr(-3.14) \
-#repr(4.0 - 8.0)
diff --git a/tests/typ/text/quote-nesting.typ b/tests/typ/text/quote-nesting.typ
deleted file mode 100644
index 381aaa56..00000000
--- a/tests/typ/text/quote-nesting.typ
+++ /dev/null
@@ -1,27 +0,0 @@
-// Test quote nesting.
-
----
-// Test quote selection.
-#set page(width: auto)
-#set text(lang: "en")
-=== EN
-#quote[An apostroph'] \
-#quote[A #quote[nested] quote] \
-#quote[A #quote[very #quote[nested]] quote]
-
-#set text(lang: "de")
-=== DE
-#quote[Satz mit Apostroph'] \
-#quote[Satz mit #quote[Zitat]] \
-#quote[A #quote[very #quote[nested]] quote]
-
-#set smartquote(alternative: true)
-=== DE Alternative
-#quote[Satz mit Apostroph'] \
-#quote[Satz mit #quote[Zitat]] \
-#quote[A #quote[very #quote[nested]] quote]
-
----
-// With custom quotes.
-#set smartquote(quotes: (single: ("<", ">"), double: ("(", ")")))
-#quote[A #quote[nested] quote]
diff --git a/tests/typ/text/quote.typ b/tests/typ/text/quote.typ
deleted file mode 100644
index 1573438c..00000000
--- a/tests/typ/text/quote.typ
+++ /dev/null
@@ -1,60 +0,0 @@
-// Test the quote element.
-
----
-// Text direction affects author positioning
-And I quote: #quote(attribution: [René Descartes])[cogito, ergo sum].
-
-#set text(lang: "ar")
-#quote(attribution: [عالم])[مرحبًا]
-
----
-// Text direction affects block alignment
-#set quote(block: true)
-#quote(attribution: [René Descartes])[cogito, ergo sum]
-
-#set text(lang: "ar")
-#quote(attribution: [عالم])[مرحبًا]
-
----
-// Spacing with other blocks
-#set quote(block: true)
-#set text(8pt)
-
-#lorem(10)
-#quote(lorem(10))
-#lorem(10)
-
----
-// Inline citation
-#set text(8pt)
-#quote(attribution: <tolkien54>)[In a hole in the ground there lived a hobbit.]
-
-#set text(0pt)
-#bibliography("/assets/bib/works.bib")
-
----
-// Citation-format: label or numeric
-#set text(8pt)
-#set quote(block: true)
-#quote(attribution: <tolkien54>)[In a hole in the ground there lived a hobbit.]
-
-#set text(0pt)
-#bibliography("/assets/bib/works.bib", style: "ieee")
-
----
-// Citation-format: note
-#set text(8pt)
-#set quote(block: true)
-#quote(attribution: <tolkien54>)[In a hole in the ground there lived a hobbit.]
-
-#set text(0pt)
-#bibliography("/assets/bib/works.bib", style: "chicago-notes")
-
----
-// Citation-format: author-date or author
-#set text(8pt)
-#set quote(block: true)
-#quote(attribution: <tolkien54>)[In a hole in the ground there lived a hobbit.]
-
-#set text(0pt)
-#bibliography("/assets/bib/works.bib", style: "apa")
diff --git a/tests/typ/text/quotes.typ b/tests/typ/text/quotes.typ
deleted file mode 100644
index 3b4eb6ba..00000000
--- a/tests/typ/text/quotes.typ
+++ /dev/null
@@ -1,71 +0,0 @@
-// Test smart quotes.
-
----
-#set page(width: 250pt)
-
-// Test simple quotations in various languages.
-#set text(lang: "en")
-"The horse eats no cucumber salad" was the first sentence ever uttered on the 'telephone.'
-
-#set text(lang: "de")
-"Das Pferd frisst keinen Gurkensalat" war der erste jemals am 'Fernsprecher' gesagte Satz.
-
-#set text(lang: "de", region: "CH")
-"Das Pferd frisst keinen Gurkensalat" war der erste jemals am 'Fernsprecher' gesagte Satz.
-
-#set text(lang: "es", region: none)
-"El caballo no come ensalada de pepino" fue la primera frase pronunciada por 'teléfono'.
-
-#set text(lang: "es", region: "MX")
-"El caballo no come ensalada de pepino" fue la primera frase pronunciada por 'teléfono'.
-
-#set text(lang: "fr", region: none)
-"Le cheval ne mange pas de salade de concombres" est la première phrase jamais prononcée au 'téléphone'.
-
-#set text(lang: "fi")
-"Hevonen ei syö kurkkusalaattia" oli ensimmäinen koskaan 'puhelimessa' lausuttu lause.
-
-#set text(lang: "gr")
-"Το άλογο δεν τρώει αγγουροσαλάτα" ήταν η πρώτη πρόταση που ειπώθηκε στο 'τηλέφωνο'.
-
-#set text(lang: "he")
-"הסוס לא אוכל סלט מלפפונים" היה המשפט ההראשון שנאמר ב 'טלפון'.
-
-#set text(lang: "ro")
-"Calul nu mănâncă salată de castraveți" a fost prima propoziție rostită vreodată la 'telefon'.
-
-#set text(lang: "ru")
-"Лошадь не ест салат из огурцов" - это была первая фраза, сказанная по 'телефону'.
-
----
-// Test single pair of quotes.
-""
-
----
-// Test sentences with numbers and apostrophes.
-The 5'11" 'quick' brown fox jumps over the "lazy" dog's ear.
-
-He said "I'm a big fella."
-
----
-// Test escape sequences.
-The 5\'11\" 'quick\' brown fox jumps over the \"lazy" dog\'s ear.
-
----
-// Test turning smart quotes off.
-He's told some books contain questionable "example text".
-
-#set smartquote(enabled: false)
-He's told some books contain questionable "example text".
-
----
-// Test changing properties within text.
-"She suddenly started speaking french: #text(lang: "fr")['Je suis une banane.']" Roman told me.
-
-Some people's thought on this would be #[#set smartquote(enabled: false); "strange."]
-
----
-// Test nested double and single quotes.
-"'test statement'" \
-"'test' statement" \
-"statement 'test'"
diff --git a/tests/typ/text/raw-align.typ b/tests/typ/text/raw-align.typ
deleted file mode 100644
index 1dbaad16..00000000
--- a/tests/typ/text/raw-align.typ
+++ /dev/null
@@ -1,37 +0,0 @@
-// Test the alignment of text inside of raw blocks.
-
----
-// Text inside raw block should be unaffected by outer alignment by default.
-#set align(center)
-#set page(width: 180pt)
-#set text(6pt)
-
-#lorem(20)
-
-```py
-def something(x):
- return x
-
-a = 342395823859823958329
-b = 324923
-```
-
-#lorem(20)
-
----
-// Text inside raw block should follow the specified alignment.
-#set page(width: 180pt)
-#set text(6pt)
-
-#lorem(20)
-#align(center, raw(
- lang: "typ",
- block: true,
- align: right,
- "#let f(x) = x\n#align(center, line(length: 1em))",
-))
-#lorem(20)
-
----
-// Error: 17-20 expected `start`, `left`, `center`, `right`, or `end`, found top
-#set raw(align: top)
diff --git a/tests/typ/text/raw-code.typ b/tests/typ/text/raw-code.typ
deleted file mode 100644
index 3ac72a05..00000000
--- a/tests/typ/text/raw-code.typ
+++ /dev/null
@@ -1,86 +0,0 @@
-// Test code highlighting.
-
----
-#set page(width: 180pt)
-#set text(6pt)
-#show raw: it => rect(
- width: 100%,
- inset: (x: 4pt, y: 5pt),
- radius: 4pt,
- fill: rgb(239, 241, 243),
- place(right, text(luma(110), it.lang)) + it,
-)
-
-```typ
-= Chapter 1
-#lorem(100)
-
-#let hi = "Hello World"
-#show heading: emph
-```
-
-```rust
-/// A carefully designed state machine.
-#[derive(Debug)]
-enum State<'a> { A(u8), B(&'a str) }
-
-fn advance(state: State<'_>) -> State<'_> {
- unimplemented!("state machine")
-}
-```
-
-```py
-import this
-
-def hi():
- print("Hi!")
-```
-
-```cpp
-#include <iostream>
-
-int main() {
- std::cout << "Hello, world!";
-}
-```
-
-```julia
-# Add two numbers
-function add(x, y)
- return x * y
-end
-```
-
- // Try with some indent.
- ```html
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- </head>
- <body>
- <h1>Topic</h1>
- <p>The Hypertext Markup Language.</p>
- <script>
- function foo(a, b) {
- return a + b + "string";
- }
- </script>
- </body>
- </html>
- ```
-
----
-#set page(width: 180pt)
-#set text(6pt)
-#set raw(lang:"python")
-
-Inline raws, multiline e.g. `for i in range(10):
- # Only this line is a comment.
- print(i)` or otherwise e.g. `print(j)`, are colored properly.
-
-Inline raws, multiline e.g. `
-# Appears blocky due to linebreaks at the boundary.
-for i in range(10):
- print(i)
-` or otherwise e.g. `print(j)`, are colored properly.
diff --git a/tests/typ/text/raw-line.typ b/tests/typ/text/raw-line.typ
deleted file mode 100644
index 19e64fac..00000000
--- a/tests/typ/text/raw-line.typ
+++ /dev/null
@@ -1,109 +0,0 @@
-// Test line in raw code.
-
----
-#set page(width: 200pt)
-
-```rs
-fn main() {
- println!("Hello, world!");
-}
-```
-
-#show raw.line: it => {
- box(stack(
- dir: ltr,
- box(width: 15pt)[#it.number],
- it.body,
- ))
- linebreak()
-}
-
-```rs
-fn main() {
- println!("Hello, world!");
-}
-```
-
----
-#set page(width: 200pt)
-#show raw: it => stack(dir: ttb, ..it.lines)
-#show raw.line: it => {
- box(
- width: 100%,
- height: 1.75em,
- inset: 0.25em,
- fill: if calc.rem(it.number, 2) == 0 {
- luma(90%)
- } else {
- white
- },
- align(horizon, stack(
- dir: ltr,
- box(width: 15pt)[#it.number],
- it.body,
- ))
- )
-}
-
-```typ
-#show raw.line: block.with(
- fill: luma(60%)
-);
-
-Hello, world!
-
-= A heading for good measure
-```
-
----
-#set page(width: 200pt)
-#show raw.line: set text(fill: red)
-
-```py
-import numpy as np
-
-def f(x):
- return x**2
-
-x = np.linspace(0, 10, 100)
-y = f(x)
-
-print(x)
-print(y)
-```
-
----
-// Ref: false
-
-// Test line extraction works.
-
-#show raw: code => {
- for i in code.lines {
- test(i.count, 10)
- }
-
- test(code.lines.at(0).text, "import numpy as np")
- test(code.lines.at(1).text, "")
- test(code.lines.at(2).text, "def f(x):")
- test(code.lines.at(3).text, " return x**2")
- test(code.lines.at(4).text, "")
- test(code.lines.at(5).text, "x = np.linspace(0, 10, 100)")
- test(code.lines.at(6).text, "y = f(x)")
- test(code.lines.at(7).text, "")
- test(code.lines.at(8).text, "print(x)")
- test(code.lines.at(9).text, "print(y)")
- test(code.lines.at(10, default: none), none)
-}
-
-```py
-import numpy as np
-
-def f(x):
- return x**2
-
-x = np.linspace(0, 10, 100)
-y = f(x)
-
-print(x)
-print(y)
-```
diff --git a/tests/typ/text/raw-syntaxes.typ b/tests/typ/text/raw-syntaxes.typ
deleted file mode 100644
index e6c46924..00000000
--- a/tests/typ/text/raw-syntaxes.typ
+++ /dev/null
@@ -1,14 +0,0 @@
-// Test code highlighting with custom syntaxes.
-
----
-#set page(width: 180pt)
-#set text(6pt)
-#set raw(syntaxes: "/assets/syntaxes/SExpressions.sublime-syntax")
-
-```sexp
-(defun factorial (x)
- (if (zerop x)
- ; with a comment
- 1
- (* x (factorial (- x 1)))))
-```
diff --git a/tests/typ/text/raw-tabs.typ b/tests/typ/text/raw-tabs.typ
deleted file mode 100644
index d31326c0..00000000
--- a/tests/typ/text/raw-tabs.typ
+++ /dev/null
@@ -1,11 +0,0 @@
-// Test tabs in raw code.
-
----
-#set raw(tab-size: 8)
-
-```tsv
-Year Month Day
-2000 2 3
-2001 2 1
-2002 3 10
-```
diff --git a/tests/typ/text/raw-theme.typ b/tests/typ/text/raw-theme.typ
deleted file mode 100644
index d6cda221..00000000
--- a/tests/typ/text/raw-theme.typ
+++ /dev/null
@@ -1,24 +0,0 @@
-// Test code highlighting with custom theme.
-
----
-#set page(width: 180pt)
-#set text(6pt)
-#set raw(theme: "/assets/themes/halcyon.tmTheme")
-#show raw: it => {
- set text(fill: rgb("a2aabc"))
- rect(
- width: 100%,
- inset: (x: 4pt, y: 5pt),
- radius: 4pt,
- fill: rgb("1d2433"),
- place(right, text(luma(240), it.lang)) + it,
- )
-}
-
-```typ
-= Chapter 1
-#lorem(100)
-
-#let hi = "Hello World"
-#show heading: emph
-```
diff --git a/tests/typ/text/raw.typ b/tests/typ/text/raw.typ
deleted file mode 100644
index 525988ec..00000000
--- a/tests/typ/text/raw.typ
+++ /dev/null
@@ -1,79 +0,0 @@
-// Test raw blocks.
-
----
-// No extra space.
-`A``B`
-
----
-// Empty raw block.
-Empty raw block:``.
-
----
-// Typst syntax inside.
-```typ #let x = 1``` \
-```typ #f(1)```
-
----
-// Multiline block splits paragraphs.
-
-Text
-```rust
-fn code() {}
-```
-Text
-
----
-// Lots of backticks inside.
-````
-```backticks```
-````
-
----
-// Trimming.
-
-// Space between "rust" and "let" is trimmed.
-The keyword ```rust let```.
-
-// Trimming depends on number backticks.
-(``) \
-(` untrimmed `) \
-(``` trimmed` ```) \
-(``` trimmed ```) \
-(``` trimmed```) \
-
----
-// Single ticks should not have a language.
-`rust let`
-
----
-// First line is not dedented and leading space is still possible.
- ``` A
- B
- C
- ```
-
----
-// Do not take empty lines into account when computing dedent.
-```
- A
-
- B
-```
-
----
-// Take last line into account when computing dedent.
-```
- A
-
- B
- ```
-
----
-// Text show rule
-#show raw: set text(font: "Roboto")
-`Roboto`
-
----
-// Unterminated.
-// Error: 1-2:1 unclosed raw text
-`endless
diff --git a/tests/typ/text/shaping.typ b/tests/typ/text/shaping.typ
deleted file mode 100644
index 4a2b4f1e..00000000
--- a/tests/typ/text/shaping.typ
+++ /dev/null
@@ -1,29 +0,0 @@
-// Test shaping quirks.
-
----
-// Test separation by script.
-#set text(font: ("Linux Libertine", "IBM Plex Sans Devanagari"))
-ABCअपार्टमेंट
-
-// This is how it should look like.
-अपार्टमेंट
-
-// This (without the spaces) is how it would look
-// if we didn't separate by script.
-अ पा र् ट में ट
-
----
-// A forced `latn` script inhibits Devanagari font features.
-#set text(font: ("Linux Libertine", "IBM Plex Sans Devanagari"), script: "latn")
-ABCअपार्टमेंट
-
----
-// A forced `deva` script enables Devanagari font features.
-#set text(font: ("Linux Libertine", "IBM Plex Sans Devanagari"), script: "deva")
-ABCअपार्टमेंट
-
----
-// Test that RTL safe-to-break doesn't panic even though newline
-// doesn't exist in shaping output.
-#set text(dir: rtl, font: "Noto Serif Hebrew")
-\ ט
diff --git a/tests/typ/text/shift.typ b/tests/typ/text/shift.typ
deleted file mode 100644
index 2b1b8984..00000000
--- a/tests/typ/text/shift.typ
+++ /dev/null
@@ -1,19 +0,0 @@
-// Test sub- and superscipt shifts.
-
----
-#table(
- columns: 3,
- [Typo.], [Fallb.], [Synth],
- [x#super[1]], [x#super[5n]], [x#super[2 #box(square(size: 6pt))]],
- [x#sub[1]], [x#sub[5n]], [x#sub[2 #box(square(size: 6pt))]],
-)
-
----
-#set super(typographic: false, baseline: -0.25em, size: 0.7em)
-n#super[1], n#sub[2], ... n#super[N]
-
----
-#set underline(stroke: 0.5pt, offset: 0.15em)
-#underline[The claim#super[\[4\]]] has been disputed. \
-The claim#super[#underline[\[4\]]] has been disputed. \
-It really has been#super(box(text(baseline: 0pt, underline[\[4\]]))) \
diff --git a/tests/typ/text/smartquotes.typ b/tests/typ/text/smartquotes.typ
deleted file mode 100644
index da31866e..00000000
--- a/tests/typ/text/smartquotes.typ
+++ /dev/null
@@ -1,29 +0,0 @@
-// Test setting custom smartquotes
-
----
-// Use language quotes for missing keys, allow partial reset
-#set smartquote(quotes: "«»")
-"Double and 'Single' Quotes"
-
-#set smartquote(quotes: (double: auto, single: "«»"))
-"Double and 'Single' Quotes"
-
----
-// Allow 2 graphemes
-#set smartquote(quotes: "a\u{0301}a\u{0301}")
-"Double and 'Single' Quotes"
-
-#set smartquote(quotes: (single: "a\u{0301}a\u{0301}"))
-"Double and 'Single' Quotes"
-
----
-// Error: 25-28 expected 2 characters, found 1 character
-#set smartquote(quotes: "'")
-
----
-// Error: 25-35 expected 2 quotes, found 4 quotes
-#set smartquote(quotes: ("'",) * 4)
-
----
-// Error: 25-45 expected 2 quotes, found 4 quotes
-#set smartquote(quotes: (single: ("'",) * 4))
diff --git a/tests/typ/text/space.typ b/tests/typ/text/space.typ
deleted file mode 100644
index 9d29f347..00000000
--- a/tests/typ/text/space.typ
+++ /dev/null
@@ -1,53 +0,0 @@
-// Test whitespace handling.
-
----
-// Spacing around code constructs.
-A#let x = 1;B #test(x, 1) \
-C #let x = 2;D #test(x, 2) \
-E#if true [F]G \
-H #if true{"I"} J \
-K #if true [L] else []M \
-#let c = true; N#while c [#(c = false)O] P \
-#let c = true; Q #while c { c = false; "R" } S \
-T#for _ in (none,) {"U"}V
-#let foo = "A" ; \
-#foo;B \
-#foo; B \
-#foo ;B
-
----
-// Test spacing with comments.
-A/**/B/**/C \
-A /**/ B/**/C \
-A /**/B/**/ C
-
----
-// Test that a run consisting only of whitespace isn't trimmed.
-A#text(font: "IBM Plex Serif")[ ]B
-
----
-// Test font change after space.
-Left #text(font: "IBM Plex Serif")[Right].
-
----
-// Test that linebreak consumed surrounding spaces.
-#align(center)[A \ B \ C]
-
----
-// Test that space at start of non-backslash-linebreak line isn't trimmed.
-A#"\n" B
-
----
-// Test that trailing space does not force a line break.
-LLLLLLLLLLLLLLLLLL R _L_
-
----
-// Test that ideographic spaces are preserved.
-#set text(lang: "ja", font: "Noto Serif CJK JP")
-
-だろうか? 何のために! 私は、
-
----
-// Test that thin spaces are preserved.
-| | U+0020 regular space \
-| | U+2009 thin space
diff --git a/tests/typ/text/stroke.typ b/tests/typ/text/stroke.typ
deleted file mode 100644
index 713bbe2f..00000000
--- a/tests/typ/text/stroke.typ
+++ /dev/null
@@ -1,21 +0,0 @@
-#set text(size: 20pt)
-#set page(width: auto)
-测试字体 #lorem(5)
-
-#text(stroke: 0.3pt + red)[测试字体#lorem(5)]
-
-#text(stroke: 0.5pt + red)[测试字体#lorem(5)]
-
-#text(stroke: 0.7pt + red)[测试字体#lorem(5)]
-
-#text(stroke: 1pt + red)[测试字体#lorem(5)]
-
-#text(stroke: 2pt + red)[测试字体#lorem(5)]
-
-#text(stroke: 5pt + red)[测试字体#lorem(5)]
-
-#text(stroke: 7pt + red)[测试字体#lorem(5)]
-
-#text(stroke: (paint: blue, thickness: 1pt, dash: "dashed"))[测试字体#lorem(5)]
-
-#text(stroke: 1pt + gradient.linear(..color.map.rainbow))[测试字体#lorem(5)] // gradient doesn't work now
diff --git a/tests/typ/text/symbol.typ b/tests/typ/text/symbol.typ
deleted file mode 100644
index f7179449..00000000
--- a/tests/typ/text/symbol.typ
+++ /dev/null
@@ -1,18 +0,0 @@
-// Test symbols.
-
----
-#emoji.face
-#emoji.woman.old
-#emoji.turtle
-
-#set text(font: "New Computer Modern Math")
-#sym.arrow
-#sym.arrow.l
-#sym.arrow.r.squiggly
-#sym.arrow.tr.hook
-
-#sym.arrow.r;this and this#sym.arrow.l;
-
----
-// Error: 13-20 unknown symbol modifier
-#emoji.face.garbage
diff --git a/tests/typ/text/tracking-spacing.typ b/tests/typ/text/tracking-spacing.typ
deleted file mode 100644
index a89b991f..00000000
--- a/tests/typ/text/tracking-spacing.typ
+++ /dev/null
@@ -1,31 +0,0 @@
-// Test tracking characters apart or together.
-
----
-// Test tracking.
-#set text(tracking: -0.01em)
-I saw Zoe yӛsterday, on the tram.
-
----
-// Test tracking for only part of paragraph.
-I'm in#text(tracking: 0.15em + 1.5pt)[ spaace]!
-
----
-// Test that tracking doesn't disrupt mark placement.
-#set text(font: ("PT Sans", "Noto Serif Hebrew"))
-#set text(tracking: 0.3em)
-טֶקסט
-
----
-// Test tracking in arabic text (makes no sense whatsoever)
-#set text(tracking: 0.3em)
-النص
-
----
-// Test word spacing.
-#set text(spacing: 1em)
-My text has spaces.
-
----
-// Test word spacing relative to the font's space width.
-#set text(spacing: 50% + 1pt)
-This is tight.
diff --git a/tests/typ/visualize/gradient-conic.typ b/tests/typ/visualize/gradient-conic.typ
deleted file mode 100644
index 83fdb07c..00000000
--- a/tests/typ/visualize/gradient-conic.typ
+++ /dev/null
@@ -1,25 +0,0 @@
-// Test conic gradients
-
----
-#square(
- size: 50pt,
- fill: gradient.conic(..color.map.rainbow, space: color.hsv),
-)
-
----
-#square(
- size: 50pt,
- fill: gradient.conic(..color.map.rainbow, space: color.hsv, center: (10%, 10%)),
-)
-
----
-#square(
- size: 50pt,
- fill: gradient.conic(..color.map.rainbow, space: color.hsv, center: (90%, 90%)),
-)
-
----
-#square(
- size: 50pt,
- fill: gradient.conic(..color.map.rainbow, space: color.hsv, angle: 90deg),
-)
diff --git a/tests/typ/visualize/gradient-dir.typ b/tests/typ/visualize/gradient-dir.typ
deleted file mode 100644
index 92e00393..00000000
--- a/tests/typ/visualize/gradient-dir.typ
+++ /dev/null
@@ -1,13 +0,0 @@
-// Test gradients with direction.
-
----
-#set page(width: 900pt)
-#for i in range(0, 360, step: 15){
- box(
- height: 100pt,
- width: 100pt,
- fill: gradient.linear(angle: i * 1deg, (red, 0%), (blue, 100%)),
- align(center + horizon)[Angle: #i degrees],
- )
- h(30pt)
-}
diff --git a/tests/typ/visualize/gradient-hue-rotation.typ b/tests/typ/visualize/gradient-hue-rotation.typ
deleted file mode 100644
index 2cc6f9a6..00000000
--- a/tests/typ/visualize/gradient-hue-rotation.typ
+++ /dev/null
@@ -1,66 +0,0 @@
-// Tests whether hue rotation works correctly.
-
----
-// Test in Oklab space for reference.
-#set page(
- width: 100pt,
- height: 30pt,
- fill: gradient.linear(red, purple, space: oklab)
-)
-
----
-// Test in OkLCH space.
-#set page(
- width: 100pt,
- height: 30pt,
- fill: gradient.linear(red, purple, space: oklch)
-)
-
----
-// Test in HSV space.
-#set page(
- width: 100pt,
- height: 30pt,
- fill: gradient.linear(red, purple, space: color.hsv)
-)
-
----
-// Test in HSL space.
-#set page(
- width: 100pt,
- height: 30pt,
- fill: gradient.linear(red, purple, space: color.hsl)
-)
-
-
----
-// Test in Oklab space for reference.
-#set page(
- width: 100pt,
- height: 100pt,
- fill: gradient.conic(red, purple, space: oklab)
-)
-
----
-// Test in OkLCH space.
-#set page(
- width: 100pt,
- height: 100pt,
- fill: gradient.conic(red, purple, space: oklch)
-)
-
----
-// Test in HSV space.
-#set page(
- width: 100pt,
- height: 100pt,
- fill: gradient.conic(red, purple, space: color.hsv)
-)
-
----
-// Test in HSL space.
-#set page(
- width: 100pt,
- height: 100pt,
- fill: gradient.conic(red, purple, space: color.hsl)
-)
diff --git a/tests/typ/visualize/gradient-math.typ b/tests/typ/visualize/gradient-math.typ
deleted file mode 100644
index f16e5c58..00000000
--- a/tests/typ/visualize/gradient-math.typ
+++ /dev/null
@@ -1,89 +0,0 @@
-// Test that gradients are applied correctly on equations.
-
----
-// Test on cancel
-#show math.equation: set text(fill: gradient.linear(..color.map.rainbow))
-#show math.equation: box
-
-$ a dot cancel(5) = cancel(25) 5 x + cancel(5) 1 $
-
----
-// Test on frac
-#show math.equation: set text(fill: gradient.linear(..color.map.rainbow))
-#show math.equation: box
-
-$ nabla dot bold(E) = frac(rho, epsilon_0) $
-
----
-// Test on root
-#show math.equation: set text(fill: gradient.linear(..color.map.rainbow))
-#show math.equation: box
-
-$ x_"1,2" = frac(-b +- sqrt(b^2 - 4 a c), 2 a) $
-
----
-// Test on matrix
-#show math.equation: set text(fill: gradient.linear(..color.map.rainbow))
-#show math.equation: box
-
-$ A = mat(
- 1, 2, 3;
- 4, 5, 6;
- 7, 8, 9
-) $
-
----
-// Test on underover
-#show math.equation: set text(fill: gradient.linear(..color.map.rainbow))
-#show math.equation: box
-
-$ underline(X^2) $
-$ overline("hello, world!") $
-
----
-// Test a different direction
-#show math.equation: set text(fill: gradient.linear(..color.map.rainbow, dir: ttb))
-#show math.equation: box
-
-$ A = mat(
- 1, 2, 3;
- 4, 5, 6;
- 7, 8, 9
-) $
-
-$ x_"1,2" = frac(-b +- sqrt(b^2 - 4 a c), 2 a) $
-
----
-// Test miscellaneous
-#show math.equation: set text(fill: gradient.linear(..color.map.rainbow))
-#show math.equation: box
-
-$ hat(x) = bar x bar = vec(x, y, z) = tilde(x) = dot(x) $
-$ x prime = vec(1, 2, delim: "[") $
-$ sum_(i in NN) 1 + i $
-$ attach(
- Pi, t: alpha, b: beta,
- tl: 1, tr: 2+3, bl: 4+5, br: 6,
-) $
-
----
-// Test radial gradient
-#show math.equation: set text(fill: gradient.radial(..color.map.rainbow, center: (30%, 30%)))
-#show math.equation: box
-
-$ A = mat(
- 1, 2, 3;
- 4, 5, 6;
- 7, 8, 9
-) $
-
----
-// Test conic gradient
-#show math.equation: set text(fill: gradient.conic(red, blue, angle: 45deg))
-#show math.equation: box
-
-$ A = mat(
- 1, 2, 3;
- 4, 5, 6;
- 7, 8, 9
-) $
diff --git a/tests/typ/visualize/gradient-presets.typ b/tests/typ/visualize/gradient-presets.typ
deleted file mode 100644
index ca1a7007..00000000
--- a/tests/typ/visualize/gradient-presets.typ
+++ /dev/null
@@ -1,33 +0,0 @@
-// Test all gradient presets.
-
----
-#set page(width: 200pt, height: auto, margin: 0pt)
-#set text(fill: white, size: 18pt)
-#set text(top-edge: "bounds", bottom-edge: "bounds")
-
-#let presets = (
- ("turbo", color.map.turbo),
- ("cividis", color.map.cividis),
- ("rainbow", color.map.rainbow),
- ("spectral", color.map.spectral),
- ("viridis", color.map.viridis),
- ("inferno", color.map.inferno),
- ("magma", color.map.magma),
- ("plasma", color.map.plasma),
- ("rocket", color.map.rocket),
- ("mako", color.map.mako),
- ("vlag", color.map.vlag),
- ("icefire", color.map.icefire),
- ("flare", color.map.flare),
- ("crest", color.map.crest),
-)
-
-#stack(
- spacing: 3pt,
- ..presets.map(((name, preset)) => block(
- width: 100%,
- height: 20pt,
- fill: gradient.linear(..preset),
- align(center + horizon, smallcaps(name)),
- ))
-)
diff --git a/tests/typ/visualize/gradient-radial.typ b/tests/typ/visualize/gradient-radial.typ
deleted file mode 100644
index c0d1b249..00000000
--- a/tests/typ/visualize/gradient-radial.typ
+++ /dev/null
@@ -1,49 +0,0 @@
-// Test the different radial gradient features.
----
-
-#square(
- size: 100pt,
- fill: gradient.radial(..color.map.rainbow, space: color.hsl),
-)
----
-
-#grid(
- columns: 2,
- square(
- size: 50pt,
- fill: gradient.radial(..color.map.rainbow, space: color.hsl, center: (0%, 0%)),
- ),
- square(
- size: 50pt,
- fill: gradient.radial(..color.map.rainbow, space: color.hsl, center: (0%, 100%)),
- ),
- square(
- size: 50pt,
- fill: gradient.radial(..color.map.rainbow, space: color.hsl, center: (100%, 0%)),
- ),
- square(
- size: 50pt,
- fill: gradient.radial(..color.map.rainbow, space: color.hsl, center: (100%, 100%)),
- ),
-)
-
----
-
-#square(
- size: 50pt,
- fill: gradient.radial(..color.map.rainbow, space: color.hsl, radius: 10%),
-)
-#square(
- size: 50pt,
- fill: gradient.radial(..color.map.rainbow, space: color.hsl, radius: 72%),
-)
-
----
-#circle(
- radius: 25pt,
- fill: gradient.radial(white, rgb("#8fbc8f"), focal-center: (35%, 35%), focal-radius: 5%),
-)
-#circle(
- radius: 25pt,
- fill: gradient.radial(white, rgb("#8fbc8f"), focal-center: (75%, 35%), focal-radius: 5%),
-)
diff --git a/tests/typ/visualize/gradient-relative-conic.typ b/tests/typ/visualize/gradient-relative-conic.typ
deleted file mode 100644
index 26b509af..00000000
--- a/tests/typ/visualize/gradient-relative-conic.typ
+++ /dev/null
@@ -1,29 +0,0 @@
-// Test whether `relative: "parent"` works correctly on conic gradients.
-
----
-// The image should look as if there is a single gradient that is being used for
-// both the page and the rectangles.
-#let grad = gradient.conic(red, blue, green, purple, relative: "parent");
-#let my-rect = rect(width: 50%, height: 50%, fill: grad)
-#set page(
- height: 200pt,
- width: 200pt,
- fill: grad,
- background: place(top + left, my-rect),
-)
-#place(top + right, my-rect)
-#place(bottom + center, rotate(45deg, my-rect))
-
----
-// The image should look as if there are multiple gradients, one for each
-// rectangle.
-#let grad = gradient.conic(red, blue, green, purple, relative: "self");
-#let my-rect = rect(width: 50%, height: 50%, fill: grad)
-#set page(
- height: 200pt,
- width: 200pt,
- fill: grad,
- background: place(top + left, my-rect),
-)
-#place(top + right, my-rect)
-#place(bottom + center, rotate(45deg, my-rect))
diff --git a/tests/typ/visualize/gradient-relative-linear.typ b/tests/typ/visualize/gradient-relative-linear.typ
deleted file mode 100644
index 8e1d04dc..00000000
--- a/tests/typ/visualize/gradient-relative-linear.typ
+++ /dev/null
@@ -1,29 +0,0 @@
-// Test whether `relative: "parent"` works correctly on linear gradients.
-
----
-// The image should look as if there is a single gradient that is being used for
-// both the page and the rectangles.
-#let grad = gradient.linear(red, blue, green, purple, relative: "parent");
-#let my-rect = rect(width: 50%, height: 50%, fill: grad)
-#set page(
- height: 200pt,
- width: 200pt,
- fill: grad,
- background: place(top + left, my-rect),
-)
-#place(top + right, my-rect)
-#place(bottom + center, rotate(45deg, my-rect))
-
----
-// The image should look as if there are multiple gradients, one for each
-// rectangle.
-#let grad = gradient.linear(red, blue, green, purple, relative: "self");
-#let my-rect = rect(width: 50%, height: 50%, fill: grad)
-#set page(
- height: 200pt,
- width: 200pt,
- fill: grad,
- background: place(top + left, my-rect),
-)
-#place(top + right, my-rect)
-#place(bottom + center, rotate(45deg, my-rect))
diff --git a/tests/typ/visualize/gradient-relative-radial.typ b/tests/typ/visualize/gradient-relative-radial.typ
deleted file mode 100644
index 87686896..00000000
--- a/tests/typ/visualize/gradient-relative-radial.typ
+++ /dev/null
@@ -1,29 +0,0 @@
-// Test whether `relative: "parent"` works correctly on radial gradients.
-
----
-// The image should look as if there is a single gradient that is being used for
-// both the page and the rectangles.
-#let grad = gradient.radial(red, blue, green, purple, relative: "parent");
-#let my-rect = rect(width: 50%, height: 50%, fill: grad)
-#set page(
- height: 200pt,
- width: 200pt,
- fill: grad,
- background: place(top + left, my-rect),
-)
-#place(top + right, my-rect)
-#place(bottom + center, rotate(45deg, my-rect))
-
----
-// The image should look as if there are multiple gradients, one for each
-// rectangle.
-#let grad = gradient.radial(red, blue, green, purple, relative: "self");
-#let my-rect = rect(width: 50%, height: 50%, fill: grad)
-#set page(
- height: 200pt,
- width: 200pt,
- fill: grad,
- background: place(top + left, my-rect),
-)
-#place(top + right, my-rect)
-#place(bottom + center, rotate(45deg, my-rect))
diff --git a/tests/typ/visualize/gradient-repeat.typ b/tests/typ/visualize/gradient-repeat.typ
deleted file mode 100644
index fd4ea279..00000000
--- a/tests/typ/visualize/gradient-repeat.typ
+++ /dev/null
@@ -1,36 +0,0 @@
-// Test repeated gradients.
-
----
-#rect(
- height: 40pt,
- width: 100%,
- fill: gradient.linear(..color.map.inferno).repeat(2, mirror: true)
-)
-
----
-#rect(
- height: 40pt,
- width: 100%,
- fill: gradient.linear(..color.map.rainbow).repeat(2, mirror: true),
-)
-
----
-#rect(
- height: 40pt,
- width: 100%,
- fill: gradient.linear(..color.map.rainbow).repeat(5, mirror: true)
-)
-
----
-#rect(
- height: 40pt,
- width: 100%,
- fill: gradient.linear(..color.map.rainbow).sharp(10).repeat(5, mirror: false)
-)
-
----
-#rect(
- height: 40pt,
- width: 100%,
- fill: gradient.linear(..color.map.rainbow).sharp(10).repeat(5, mirror: true)
-)
diff --git a/tests/typ/visualize/gradient-sharp.typ b/tests/typ/visualize/gradient-sharp.typ
deleted file mode 100644
index 89841efd..00000000
--- a/tests/typ/visualize/gradient-sharp.typ
+++ /dev/null
@@ -1,29 +0,0 @@
-// Test sharp gradients.
-
----
-#square(
- size: 100pt,
- fill: gradient.linear(..color.map.rainbow, space: color.hsl).sharp(10),
-)
-#square(
- size: 100pt,
- fill: gradient.radial(..color.map.rainbow, space: color.hsl).sharp(10),
-)
-#square(
- size: 100pt,
- fill: gradient.conic(..color.map.rainbow, space: color.hsl).sharp(10),
-)
-
----
-#square(
- size: 100pt,
- fill: gradient.linear(..color.map.rainbow, space: color.hsl).sharp(10, smoothness: 40%),
-)
-#square(
- size: 100pt,
- fill: gradient.radial(..color.map.rainbow, space: color.hsl).sharp(10, smoothness: 40%),
-)
-#square(
- size: 100pt,
- fill: gradient.conic(..color.map.rainbow, space: color.hsl).sharp(10, smoothness: 40%),
-)
diff --git a/tests/typ/visualize/gradient-stroke.typ b/tests/typ/visualize/gradient-stroke.typ
deleted file mode 100644
index d4461497..00000000
--- a/tests/typ/visualize/gradient-stroke.typ
+++ /dev/null
@@ -1,31 +0,0 @@
-// Test gradients on strokes.
-
----
-#align(center + top, square(size: 50pt, fill: black, stroke: 5pt + gradient.linear(red, blue)))
-
----
-#align(
- center + bottom,
- square(
- size: 50pt,
- fill: gradient.radial(red, blue, radius: 70.7%, focal-center: (10%, 10%)),
- stroke: 10pt + gradient.radial(red, blue, radius: 70.7%, focal-center: (10%, 10%))
- )
-)
-
----
-#align(
- center + bottom,
- square(
- size: 50pt,
- fill: black,
- stroke: 10pt + gradient.conic(red, blue)
- )
-)
-
----
-// Test gradient on lines
-#set page(width: 100pt, height: 100pt)
-#line(length: 100%, stroke: 1pt + gradient.linear(red, blue))
-#line(length: 100%, angle: 10deg, stroke: 1pt + gradient.linear(red, blue))
-#line(length: 100%, angle: 10deg, stroke: 1pt + gradient.linear(red, blue, relative: "parent"))
diff --git a/tests/typ/visualize/gradient-text-decorations.typ b/tests/typ/visualize/gradient-text-decorations.typ
deleted file mode 100644
index a4e861d8..00000000
--- a/tests/typ/visualize/gradient-text-decorations.typ
+++ /dev/null
@@ -1,9 +0,0 @@
-// Tests gradients on text decorations.
-
----
-
-#set text(fill: gradient.linear(red, blue))
-
-Hello #underline[World]! \
-Hello #overline[World]! \
-Hello #strike[World]! \
diff --git a/tests/typ/visualize/gradient-text-other.typ b/tests/typ/visualize/gradient-text-other.typ
deleted file mode 100644
index 04b84cb6..00000000
--- a/tests/typ/visualize/gradient-text-other.typ
+++ /dev/null
@@ -1,14 +0,0 @@
-// Test text gradients with radial and conic gradients.
-
----
-#set page(width: 200pt, height: auto, margin: 10pt)
-#set par(justify: true)
-#set text(fill: gradient.radial(red, blue))
-#lorem(30)
-
-
----
-#set page(width: 200pt, height: auto, margin: 10pt)
-#set par(justify: true)
-#set text(fill: gradient.conic(red, blue, angle: 45deg))
-#lorem(30)
diff --git a/tests/typ/visualize/gradient-text.typ b/tests/typ/visualize/gradient-text.typ
deleted file mode 100644
index 671172e1..00000000
--- a/tests/typ/visualize/gradient-text.typ
+++ /dev/null
@@ -1,49 +0,0 @@
-// Test that gradient fills on text.
-// The solid bar gradients are used to make sure that all transforms are
-// correct: if you can see the text through the bar, then the gradient is
-// misaligned to its reference container.
-// Ref: true
-
----
-// Ref: false
-// Make sure they don't work when `relative: "self"`.
-
-// Hint: 17-61 make sure to set `relative: auto` on your text fill
-// Error: 17-61 gradients and patterns on text must be relative to the parent
-#set text(fill: gradient.linear(red, blue, relative: "self"))
-
----
-// Test that gradient fills on text work for globally defined gradients.
-
-#set page(width: 200pt, height: auto, margin: 10pt, background: {
- rect(width: 100%, height: 30pt, fill: gradient.linear(red, blue))
-})
-#set par(justify: true)
-#set text(fill: gradient.linear(red, blue))
-#lorem(30)
-
----
-// Sanity check that the direction works on text.
-
-#set page(width: 200pt, height: auto, margin: 10pt, background: {
- rect(height: 100%, width: 30pt, fill: gradient.linear(dir: btt, red, blue))
-})
-#set par(justify: true)
-#set text(fill: gradient.linear(dir: btt, red, blue))
-#lorem(30)
-
----
-// Test that gradient fills on text work for locally defined gradients.
-
-#set page(width: auto, height: auto, margin: 10pt)
-#show box: set text(fill: gradient.linear(..color.map.rainbow))
-
-Hello, #box[World]!
-
----
-// Test that gradients fills on text work with transforms.
-
-#set page(width: auto, height: auto, margin: 10pt)
-#show box: set text(fill: gradient.linear(..color.map.rainbow))
-
-#rotate(45deg, box[World])
diff --git a/tests/typ/visualize/gradient-transform.typ b/tests/typ/visualize/gradient-transform.typ
deleted file mode 100644
index d33c4ac2..00000000
--- a/tests/typ/visualize/gradient-transform.typ
+++ /dev/null
@@ -1,12 +0,0 @@
-// Test whether gradients work well when they are contained within a transform.
-
----
-#let grad = gradient.linear(red, blue, green, purple, relative: "parent");
-#let my-rect = rect(width: 50pt, height: 50pt, fill: grad)
-#set page(
- height: 200pt,
- width: 200pt,
-)
-#place(top + right, scale(x: 200%, y: 130%, my-rect))
-#place(bottom + center, rotate(45deg, my-rect))
-#place(horizon + center, scale(x: 200%, y: 130%, rotate(45deg, my-rect)))
diff --git a/tests/typ/visualize/image-scale.typ b/tests/typ/visualize/image-scale.typ
deleted file mode 100644
index 7ddf435e..00000000
--- a/tests/typ/visualize/image-scale.typ
+++ /dev/null
@@ -1,6 +0,0 @@
-// Test that images aren't upscaled.
-
----
-// Image is just 48x80 at 220dpi. It should not be scaled to fit the page
-// width, but rather max out at its natural size.
-#image("/assets/images/f2t.jpg")
diff --git a/tests/typ/visualize/image.typ b/tests/typ/visualize/image.typ
deleted file mode 100644
index a7965b2d..00000000
--- a/tests/typ/visualize/image.typ
+++ /dev/null
@@ -1,82 +0,0 @@
-// Test the `image` function.
-
----
-// Test loading different image formats.
-
-// Load an RGBA PNG image.
-#image("/assets/images/rhino.png")
-
-// Load an RGB JPEG image.
-#set page(height: 60pt)
-#image("../../assets/images/tiger.jpg")
-
----
-// Test configuring the size and fitting behaviour of images.
-
-// Set width and height explicitly.
-#box(image("/assets/images/rhino.png", width: 30pt))
-#box(image("/assets/images/rhino.png", height: 30pt))
-
-// Set width and height explicitly and force stretching.
-#image("/assets/images/monkey.svg", width: 100%, height: 20pt, fit: "stretch")
-
-// Make sure the bounding-box of the image is correct.
-#align(bottom + right, image("/assets/images/tiger.jpg", width: 40pt, alt: "A tiger"))
-
----
-// Test all three fit modes.
-#set page(height: 50pt, margin: 0pt)
-#grid(
- columns: (1fr, 1fr, 1fr),
- rows: 100%,
- gutter: 3pt,
- image("/assets/images/tiger.jpg", width: 100%, height: 100%, fit: "contain"),
- image("/assets/images/tiger.jpg", width: 100%, height: 100%, fit: "cover"),
- image("/assets/images/monkey.svg", width: 100%, height: 100%, fit: "stretch"),
-)
-
----
-// Does not fit to remaining height of page.
-#set page(height: 60pt)
-Stuff
-#image("/assets/images/rhino.png")
-
----
-// Test baseline.
-A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
-
----
-// Test advanced SVG features.
-#image("/assets/images/pattern.svg")
-
----
-// Error: 8-29 file not found (searched at typ/visualize/path/does/not/exist)
-#image("path/does/not/exist")
-
----
-// Error: 2-22 unknown image format
-#image("./image.typ")
-
----
-// Error: 2-33 failed to parse SVG (found closing tag 'g' instead of 'style' in line 4)
-#image("/assets/images/bad.svg")
-
----
-// Test parsing from svg data
-#image.decode(`<svg xmlns="http://www.w3.org/2000/svg" height="140" width="500"><ellipse cx="200" cy="80" rx="100" ry="50" style="fill:yellow;stroke:purple;stroke-width:2" /></svg>`.text, format: "svg")
-
----
-// Error: 2-168 failed to parse SVG (missing root node)
-#image.decode(`<svg height="140" width="500"><ellipse cx="200" cy="80" rx="100" ry="50" style="fill:yellow;stroke:purple;stroke-width:2" /></svg>`.text, format: "svg")
-
----
-// Test format auto detect
-#image.decode(read("/assets/images/tiger.jpg", encoding: none), width: 80%)
-
----
-// Test format manual
-#image.decode(read("/assets/images/tiger.jpg", encoding: none), format: "jpg", width: 80%)
-
----
-// Error: 2-91 failed to decode image (Format error decoding Png: Invalid PNG signature.)
-#image.decode(read("/assets/images/tiger.jpg", encoding: none), format: "png", width: 80%)
diff --git a/tests/typ/visualize/line.typ b/tests/typ/visualize/line.typ
deleted file mode 100644
index 8c405932..00000000
--- a/tests/typ/visualize/line.typ
+++ /dev/null
@@ -1,51 +0,0 @@
-// Test lines.
-
----
-#set page(height: 60pt)
-#box({
- set line(stroke: 0.75pt)
- place(line(end: (0.4em, 0pt)))
- place(line(start: (0pt, 0.4em), end: (0pt, 0pt)))
- line(end: (0.6em, 0.6em))
-}) Hello #box(line(length: 1cm))!
-
-#line(end: (70%, 50%))
-
----
-// Test the angle argument and positioning.
-
-#set page(fill: rgb("0B1026"))
-#set line(stroke: white)
-
-#let star(size, ..args) = box(width: size, height: size)[
- #set text(spacing: 0%)
- #set line(..args)
- #set align(left)
- #v(30%)
- #place(line(length: +30%, start: (09.0%, 02%)))
- #place(line(length: +30%, start: (38.7%, 02%), angle: -72deg))
- #place(line(length: +30%, start: (57.5%, 02%), angle: 252deg))
- #place(line(length: +30%, start: (57.3%, 02%)))
- #place(line(length: -30%, start: (88.0%, 02%), angle: -36deg))
- #place(line(length: +30%, start: (73.3%, 48%), angle: 252deg))
- #place(line(length: -30%, start: (73.5%, 48%), angle: 36deg))
- #place(line(length: +30%, start: (25.4%, 48%), angle: -36deg))
- #place(line(length: +30%, start: (25.6%, 48%), angle: -72deg))
- #place(line(length: +32%, start: (8.50%, 02%), angle: 34deg))
-]
-
-#align(center, grid(
- columns: 3,
- column-gutter: 10pt,
- ..((star(20pt, stroke: 0.5pt),) * 9)
-))
-
----
-// Test errors.
-
-// Error: 12-19 point array must contain exactly two entries
-#line(end: (50pt,))
-
----
-// Error: 14-26 expected relative length, found angle
-#line(start: (3deg, 10pt), length: 5cm)
diff --git a/tests/typ/visualize/path.typ b/tests/typ/visualize/path.typ
deleted file mode 100644
index d475811f..00000000
--- a/tests/typ/visualize/path.typ
+++ /dev/null
@@ -1,52 +0,0 @@
-// Test paths.
-
----
-#set page(height: 200pt, width: 200pt)
-#table(
- columns: (1fr, 1fr),
- rows: (1fr, 1fr),
- align: center + horizon,
- path(
- fill: red,
- closed: true,
- ((0%, 0%), (4%, -4%)),
- ((50%, 50%), (4%, -4%)),
- ((0%, 50%), (4%, 4%)),
- ((50%, 0%), (4%, 4%)),
- ),
- path(
- fill: purple,
- stroke: 1pt,
- (0pt, 0pt),
- (30pt, 30pt),
- (0pt, 30pt),
- (30pt, 0pt),
- ),
- path(
- fill: blue,
- stroke: 1pt,
- closed: true,
- ((30%, 0%), (35%, 30%), (-20%, 0%)),
- ((30%, 60%), (-20%, 0%), (0%, 0%)),
- ((50%, 30%), (60%, -30%), (60%, 0%)),
- ),
- path(
- stroke: 5pt,
- closed: true,
- (0pt, 30pt),
- (30pt, 30pt),
- (15pt, 0pt),
- ),
-)
-
----
-// Error: 7-9 path vertex must have 1, 2, or 3 points
-#path(())
-
----
-// Error: 7-47 path vertex must have 1, 2, or 3 points
-#path(((0%, 0%), (0%, 0%), (0%, 0%), (0%, 0%)))
-
----
-// Error: 7-31 point array must contain exactly two entries
-#path(((0%, 0%), (0%, 0%, 0%)))
diff --git a/tests/typ/visualize/pattern-relative.typ b/tests/typ/visualize/pattern-relative.typ
deleted file mode 100644
index 78517e1e..00000000
--- a/tests/typ/visualize/pattern-relative.typ
+++ /dev/null
@@ -1,23 +0,0 @@
-// Test pattern with different `relative`.
-
----
-// Test with relative set to `"self"`
-#let pat(..args) = pattern(size: (30pt, 30pt), ..args)[
- #place(top + left, line(start: (0%, 0%), end: (100%, 100%), stroke: 1pt))
- #place(top + left, line(start: (0%, 100%), end: (100%, 0%), stroke: 1pt))
-]
-
-#set page(fill: pat(), width: 100pt, height: 100pt)
-
-#rect(fill: pat(relative: "self"), width: 100%, height: 100%, stroke: 1pt)
-
----
-// Test with relative set to `"parent"`
-#let pat(..args) = pattern(size: (30pt, 30pt), ..args)[
- #place(top + left, line(start: (0%, 0%), end: (100%, 100%), stroke: 1pt))
- #place(top + left, line(start: (0%, 100%), end: (100%, 0%), stroke: 1pt))
-]
-
-#set page(fill: pat(), width: 100pt, height: 100pt)
-
-#rect(fill: pat(relative: "parent"), width: 100%, height: 100%, stroke: 1pt)
diff --git a/tests/typ/visualize/pattern-simple.typ b/tests/typ/visualize/pattern-simple.typ
deleted file mode 100644
index 9c41067d..00000000
--- a/tests/typ/visualize/pattern-simple.typ
+++ /dev/null
@@ -1,18 +0,0 @@
-// Tests that simple patterns work.
-
----
-#set page(width: auto, height: auto, margin: 0pt)
-#let pat = pattern(size: (10pt, 10pt), line(stroke: 4pt, start: (0%, 0%), end: (100%, 100%)))
-#rect(width: 50pt, height: 50pt, fill: pat)
-
----
-#set page(width: auto, height: auto, margin: 0pt)
-
-#let pat = pattern(size: (10pt, 10pt), {
- place(line(stroke: 4pt, start: (0%, 0%), end: (100%, 100%)))
- place(line(stroke: 4pt, start: (100%,0%), end: (200%, 100%)))
- place(line(stroke: 4pt, start: (0%,100%), end: (100%, 200%)))
- place(line(stroke: 4pt, start: (-100%,0%), end: (0%, 100%)))
- place(line(stroke: 4pt, start: (0%,-100%), end: (100%, 0%)))
-})
-#rect(width: 50pt, height: 50pt, fill: pat)
diff --git a/tests/typ/visualize/pattern-small.typ b/tests/typ/visualize/pattern-small.typ
deleted file mode 100644
index 1d289f92..00000000
--- a/tests/typ/visualize/pattern-small.typ
+++ /dev/null
@@ -1,19 +0,0 @@
-// Tests small patterns for pixel accuracy.
-
----
-#box(
- width: 8pt,
- height: 1pt,
- fill: pattern(size: (1pt, 1pt), square(size: 1pt, fill: black))
-)
-#v(-1em)
-#box(
- width: 8pt,
- height: 1pt,
- fill: pattern(size: (2pt, 1pt), square(size: 1pt, fill: black))
-)
-
----
-// Error: 15-52 pattern tile size must be non-zero
-// Hint: 15-52 try setting the size manually
-#line(stroke: pattern(path((0pt, 0pt), (1em, 0pt))))
diff --git a/tests/typ/visualize/pattern-spacing.typ b/tests/typ/visualize/pattern-spacing.typ
deleted file mode 100644
index f8f5f9fd..00000000
--- a/tests/typ/visualize/pattern-spacing.typ
+++ /dev/null
@@ -1,31 +0,0 @@
-// Test pattern with different `spacing`.
-
----
-// Test with spacing set to `(-10pt, -10pt)`
-#let pat(..args) = pattern(size: (30pt, 30pt), ..args)[
- #square(width: 100%, height: 100%, stroke: 1pt, fill: blue)
-]
-
-#set page(width: 100pt, height: 100pt)
-
-#rect(fill: pat(spacing: (-10pt, -10pt)), width: 100%, height: 100%, stroke: 1pt)
-
----
-// Test with spacing set to `(0pt, 0pt)`
-#let pat(..args) = pattern(size: (30pt, 30pt), ..args)[
- #square(width: 100%, height: 100%, stroke: 1pt, fill: blue)
-]
-
-#set page(width: 100pt, height: 100pt)
-
-#rect(fill: pat(spacing: (0pt, 0pt)), width: 100%, height: 100%, stroke: 1pt)
-
----
-// Test with spacing set to `(10pt, 10pt)`
-#let pat(..args) = pattern(size: (30pt, 30pt), ..args)[
- #square(width: 100%, height: 100%, stroke: 1pt, fill: blue)
-]
-
-#set page(width: 100pt, height: 100pt)
-
-#rect(fill: pat(spacing: (10pt, 10pt,)), width: 100%, height: 100%, stroke: 1pt)
diff --git a/tests/typ/visualize/pattern-stroke.typ b/tests/typ/visualize/pattern-stroke.typ
deleted file mode 100644
index 3cc43a70..00000000
--- a/tests/typ/visualize/pattern-stroke.typ
+++ /dev/null
@@ -1,13 +0,0 @@
-// Test pattern on strokes
-
----
-#align(
- center + top,
- square(
- size: 50pt,
- stroke: 5pt + pattern(
- size: (5pt, 5pt),
- align(horizon + center, circle(fill: blue, radius: 2.5pt))
- )
- )
-)
diff --git a/tests/typ/visualize/pattern-text.typ b/tests/typ/visualize/pattern-text.typ
deleted file mode 100644
index a9fbfb37..00000000
--- a/tests/typ/visualize/pattern-text.typ
+++ /dev/null
@@ -1,28 +0,0 @@
-// Test a pattern on some text
-
----
-// You shouldn't be able to see the text, if you can then
-// that means that the transform matrices are not being
-// applied to the text correctly.
-#let pat = pattern(
- size: (30pt, 30pt),
- relative: "parent",
- square(size: 30pt, fill: gradient.conic(..color.map.rainbow))
-);
-
-#set page(
- width: 140pt,
- height: 140pt,
- fill: pat
-)
-
-#rotate(45deg, scale(x: 50%, y: 70%, rect(
- width: 100%,
- height: 100%,
- stroke: 1pt,
-)[
- #lorem(10)
-
- #set text(fill: pat)
- #lorem(10)
-]))
diff --git a/tests/typ/visualize/polygon.typ b/tests/typ/visualize/polygon.typ
deleted file mode 100644
index cad62497..00000000
--- a/tests/typ/visualize/polygon.typ
+++ /dev/null
@@ -1,36 +0,0 @@
-// Test polygons.
-
----
-#set page(width: 50pt)
-#set polygon(stroke: 0.75pt, fill: blue)
-
-// These are not visible, but should also not give an error
-#polygon()
-#polygon((0em, 0pt))
-#polygon((0pt, 0pt), (10pt, 0pt))
-#polygon.regular(size: 0pt, vertices: 9)
-
-#polygon((5pt, 0pt), (0pt, 10pt), (10pt, 10pt))
-#polygon(
- (0pt, 0pt), (5pt, 5pt), (10pt, 0pt),
- (15pt, 5pt),
- (5pt, 10pt)
-)
-#polygon(stroke: none, (5pt, 0pt), (0pt, 10pt), (10pt, 10pt))
-#polygon(stroke: 3pt, fill: none, (5pt, 0pt), (0pt, 10pt), (10pt, 10pt))
-
-// Relative size
-#polygon((0pt, 0pt), (100%, 5pt), (50%, 10pt))
-
-// Antiparallelogram
-#polygon((0pt, 5pt), (5pt, 0pt), (0pt, 10pt), (5pt, 15pt))
-
-// Self-intersections
-#polygon((0pt, 10pt), (30pt, 20pt), (0pt, 30pt), (20pt, 0pt), (20pt, 35pt))
-
-// Regular polygon; should have equal side lengths
-#for k in range(3, 9) {polygon.regular(size: 30pt, vertices: k,)}
-
----
-// Error: 10-17 point array must contain exactly two entries
-#polygon((50pt,))
diff --git a/tests/typ/visualize/shape-aspect.typ b/tests/typ/visualize/shape-aspect.typ
deleted file mode 100644
index d3606808..00000000
--- a/tests/typ/visualize/shape-aspect.typ
+++ /dev/null
@@ -1,63 +0,0 @@
-// Test that squares and circles respect their 1-1 aspect ratio.
-
----
-// Test relative width and height and size that is smaller
-// than default size.
-#set page(width: 120pt, height: 70pt)
-#set align(bottom)
-#let centered = align.with(center + horizon)
-#stack(
- dir: ltr,
- spacing: 1fr,
- square(width: 50%, centered[A]),
- square(height: 50%),
- stack(
- square(size: 10pt),
- square(size: 20pt, centered[B])
- ),
-)
-
----
-// Test alignment in automatically sized square and circle.
-#set text(8pt)
-#box(square(inset: 4pt)[
- Hey there, #align(center + bottom, rotate(180deg, [you!]))
-])
-#box(circle(align(center + horizon, [Hey.])))
-
----
-// Test that minimum wins if both width and height are given.
-#stack(
- dir: ltr,
- spacing: 2pt,
- square(width: 20pt, height: 40pt),
- circle(width: 20%, height: 100pt),
-)
-
----
-// Test square that is limited by region size.
-#set page(width: 20pt, height: 10pt, margin: 0pt)
-#stack(dir: ltr, square(fill: forest), square(fill: conifer))
-
----
-// Test different ways of sizing.
-#set page(width: 120pt, height: 40pt)
-#stack(
- dir: ltr,
- spacing: 2pt,
- circle(radius: 5pt),
- circle(width: 10%),
- circle(height: 50%),
-)
-
----
-// Test that square doesn't overflow due to its aspect ratio.
-#set page(width: 40pt, height: 25pt, margin: 5pt)
-#square(width: 100%)
-#square(width: 100%)[Hello there]
-
----
-// Size cannot be relative because we wouldn't know
-// relative to which axis.
-// Error: 15-18 expected length or auto, found ratio
-#square(size: 50%)
diff --git a/tests/typ/visualize/shape-circle.typ b/tests/typ/visualize/shape-circle.typ
deleted file mode 100644
index 34238d9a..00000000
--- a/tests/typ/visualize/shape-circle.typ
+++ /dev/null
@@ -1,58 +0,0 @@
-// Test the `circle` function.
-
----
-// Default circle.
-#box(circle())
-#box(circle[Hey])
-
----
-// Test auto sizing.
-#set circle(inset: 0pt)
-
-Auto-sized circle.
-#circle(fill: rgb("eb5278"), stroke: 2pt + black,
- align(center + horizon)[But, soft!]
-)
-
-Center-aligned rect in auto-sized circle.
-#circle(fill: forest, stroke: conifer,
- align(center + horizon,
- rect(fill: conifer, inset: 5pt)[But, soft!]
- )
-)
-
-Rect in auto-sized circle.
-#circle(fill: forest,
- rect(fill: conifer, stroke: white, inset: 4pt)[
- #set text(8pt)
- But, soft! what light through yonder window breaks?
- ]
-)
-
-Expanded by height.
-#circle(stroke: black, align(center)[A \ B \ C])
-
----
-// Ensure circle directly in rect works.
-#rect(width: 40pt, height: 30pt, fill: forest,
- circle(fill: conifer))
-
----
-// Test relative sizing.
-#set text(fill: white)
-#show: rect.with(width: 100pt, height: 50pt, inset: 0pt, fill: rgb("aaa"))
-#set align(center + horizon)
-#stack(
- dir: ltr,
- spacing: 1fr,
- 1fr,
- circle(radius: 10pt, fill: eastern, [A]), // D=20pt
- circle(height: 60%, fill: eastern, [B]), // D=30pt
- circle(width: 20% + 20pt, fill: eastern, [C]), // D=40pt
- 1fr,
-)
-
----
-// Radius wins over width and height.
-// Error: 23-34 unexpected argument: width
-#circle(radius: 10pt, width: 50pt, height: 100pt, fill: eastern)
diff --git a/tests/typ/visualize/shape-ellipse.typ b/tests/typ/visualize/shape-ellipse.typ
deleted file mode 100644
index 2fd4acd9..00000000
--- a/tests/typ/visualize/shape-ellipse.typ
+++ /dev/null
@@ -1,31 +0,0 @@
-// Test the `ellipse` function.
-
----
-// Default ellipse.
-#ellipse()
-
----
-#set rect(inset: 0pt)
-#set ellipse(inset: 0pt)
-
-Rect in ellipse in fixed rect.
-#rect(width: 3cm, height: 2cm, fill: rgb("2a631a"),
- ellipse(fill: forest, width: 100%, height: 100%,
- rect(fill: conifer, width: 100%, height: 100%,
- align(center + horizon)[
- Stuff inside an ellipse!
- ]
- )
- )
-)
-
-Auto-sized ellipse.
-#ellipse(fill: conifer, stroke: 3pt + forest, inset: 3pt)[
- #set text(8pt)
- But, soft! what light through yonder window breaks?
-]
-
-
-An inline
-#box(ellipse(width: 8pt, height: 6pt, outset: (top: 3pt, rest: 5.5pt)))
-ellipse.
diff --git a/tests/typ/visualize/shape-fill-stroke.typ b/tests/typ/visualize/shape-fill-stroke.typ
deleted file mode 100644
index 8d187400..00000000
--- a/tests/typ/visualize/shape-fill-stroke.typ
+++ /dev/null
@@ -1,93 +0,0 @@
-// Test shape fill & stroke.
-
----
-#let variant = rect.with(width: 20pt, height: 10pt)
-#let items = for (i, item) in (
- variant(stroke: none),
- variant(),
- variant(fill: none),
- variant(stroke: 2pt),
- variant(stroke: eastern),
- variant(stroke: eastern + 2pt),
- variant(fill: eastern),
- variant(fill: eastern, stroke: none),
- variant(fill: forest, stroke: none),
- variant(fill: forest, stroke: conifer),
- variant(fill: forest, stroke: black + 2pt),
- variant(fill: forest, stroke: conifer + 2pt),
-).enumerate() {
- (align(horizon)[#(i + 1).], item, [])
-}
-
-#grid(
- columns: (auto, auto, 1fr, auto, auto, 0fr),
- gutter: 5pt,
- ..items,
-)
-
----
-// Test stroke folding.
-#let sq(..args) = box(square(size: 10pt, ..args))
-
-#set square(stroke: none)
-#sq()
-#set square(stroke: auto)
-#sq()
-#sq(fill: teal)
-#sq(stroke: 2pt)
-#sq(stroke: blue)
-#sq(fill: teal, stroke: blue)
-#sq(fill: teal, stroke: 2pt + blue)
-
----
-// Test stroke composition.
-#set square(stroke: 4pt)
-#set text(font: "Roboto")
-#stack(
- dir: ltr,
- square(
- stroke: (left: red, top: yellow, right: green, bottom: blue),
- radius: 50%, align(center+horizon)[*G*],
- inset: 8pt
- ),
- h(0.5cm),
- square(
- stroke: (left: red, top: yellow + 8pt, right: green, bottom: blue + 2pt),
- radius: 50%, align(center+horizon)[*G*],
- inset: 8pt
- ),
- h(0.5cm),
- square(
- stroke: (left: red, top: yellow, right: green, bottom: blue),
- radius: 100%, align(center+horizon)[*G*],
- inset: 8pt
- ),
-)
-
-// Join between different solid strokes
-#set square(size: 20pt, stroke: 2pt)
-#set square(stroke: (left: green + 4pt, top: black + 2pt, right: blue, bottom: black + 2pt))
-#stack(
- dir: ltr,
- square(),
- h(0.2cm),
- square(radius: (top-left: 0pt, rest: 1pt)),
- h(0.2cm),
- square(radius: (top-left: 0pt, rest: 8pt)),
- h(0.2cm),
- square(radius: (top-left: 0pt, rest: 100pt)),
-)
-
-
-// Join between solid and dotted strokes
-#set square(stroke: (left: green + 4pt, top: black + 2pt, right: (paint: blue, dash: "dotted"), bottom: (paint: black, dash: "dotted")))
-#stack(
- dir: ltr,
- square(),
- h(0.2cm),
- square(radius: (top-left: 0pt, rest: 1pt)),
- h(0.2cm),
- square(radius: (top-left: 0pt, rest: 8pt)),
- h(0.2cm),
- square(radius: (top-left: 0pt, rest: 100pt)),
-)
diff --git a/tests/typ/visualize/shape-rect.typ b/tests/typ/visualize/shape-rect.typ
deleted file mode 100644
index bd9cf1ae..00000000
--- a/tests/typ/visualize/shape-rect.typ
+++ /dev/null
@@ -1,78 +0,0 @@
-// Test the `rect` function.
-
----
-// Default rectangle.
-#rect()
-
----
-#set page(width: 150pt)
-
-// Fit to text.
-#rect(fill: conifer)[Textbox]
-
-// Empty with fixed width and height.
-#block(rect(
- height: 15pt,
- fill: rgb("46b3c2"),
- stroke: 2pt + rgb("234994"),
-))
-
-// Fixed width, text height.
-#rect(width: 2cm, fill: rgb("9650d6"))[Fixed and padded]
-
-// Page width, fixed height.
-#rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft]
-
-// These are inline with text.
-{#box(rect(width: 0.5in, height: 7pt, fill: rgb("d6cd67")))
- #box(rect(width: 0.5in, height: 7pt, fill: rgb("edd466")))
- #box(rect(width: 0.5in, height: 7pt, fill: rgb("e3be62")))}
-
-// Rounded corners.
-#stack(
- dir: ltr,
- spacing: 1fr,
- rect(width: 2cm, radius: 30%),
- rect(width: 1cm, radius: (left: 10pt, right: 5pt)),
- rect(width: 1.25cm, radius: (
- top-left: 2pt,
- top-right: 5pt,
- bottom-right: 8pt,
- bottom-left: 11pt
- )),
-)
-
-// Different strokes.
-#set rect(stroke: (right: red))
-#rect(width: 100%, fill: lime, stroke: (x: 5pt, y: 1pt))
-
----
-// Error: 15-38 unexpected key "cake", valid keys are "top-left", "top-right", "bottom-right", "bottom-left", "left", "top", "right", "bottom", and "rest"
-#rect(radius: (left: 10pt, cake: 5pt))
-
----
-// Error: 15-21 expected length, color, gradient, pattern, dictionary, stroke, none, or auto, found array
-#rect(stroke: (1, 2))
-
----
-#set page(width: 17.8cm)
-#lorem(100)
-#rect(lorem(100))
-#set par(justify: true)
-#lorem(100)
-#rect(lorem(100))
-
----
-// Negative dimensions
-#rect(width: -1cm, fill: gradient.linear(red, blue))[Reverse left]
-
-#rect(width: 1cm, fill: gradient.linear(red, blue))[Left]
-
-#align(center, rect(width: -1cm, fill: gradient.linear(red, blue))[Reverse center])
-
-#align(center, rect(width: 1cm, fill: gradient.linear(red, blue))[Center])
-
-#align(right, rect(width: -1cm, fill: gradient.linear(red, blue))[Reverse right])
-
-#align(right, rect(width: 1cm, fill: gradient.linear(red, blue))[Right])
-
diff --git a/tests/typ/visualize/shape-rounded.typ b/tests/typ/visualize/shape-rounded.typ
deleted file mode 100644
index 42432dc9..00000000
--- a/tests/typ/visualize/shape-rounded.typ
+++ /dev/null
@@ -1,53 +0,0 @@
-// Test rounded rectangles and squares.
-
----
-#set square(size: 20pt, stroke: 4pt)
-
-// no radius for non-rounded corners
-#stack(
- dir: ltr,
- square(),
- h(10pt),
- square(radius: 0pt),
- h(10pt),
- square(radius: -10pt),
-)
-
-#stack(
- dir: ltr,
- square(),
- h(10pt),
- square(radius: 0%),
- h(10pt),
- square(radius: -10%),
-)
-
-
-// small values for small radius
-#stack(
- dir: ltr,
- square(radius: 1pt),
- h(10pt),
- square(radius: 5%),
- h(10pt),
- square(radius: 2pt),
-)
-
-// large values for large radius or circle
-#stack(
- dir: ltr,
- square(radius: 8pt),
- h(10pt),
- square(radius: 10pt),
- h(10pt),
- square(radius: 12pt),
-)
-
-#stack(
- dir: ltr,
- square(radius: 45%),
- h(10pt),
- square(radius: 50%),
- h(10pt),
- square(radius: 55%),
-)
diff --git a/tests/typ/visualize/shape-square.typ b/tests/typ/visualize/shape-square.typ
deleted file mode 100644
index a321dc4a..00000000
--- a/tests/typ/visualize/shape-square.typ
+++ /dev/null
@@ -1,39 +0,0 @@
-// Test the `square` function.
-
----
-// Default square.
-#box(square())
-#box(square[hey!])
-
----
-// Test auto-sized square.
-#square(fill: eastern)[
- #set text(fill: white, weight: "bold")
- Typst
-]
-
----
-// Test relative-sized child.
-#square(fill: eastern)[
- #rect(width: 10pt, height: 5pt, fill: conifer)
- #rect(width: 40%, height: 5pt, stroke: conifer)
-]
-
----
-// Test text overflowing height.
-#set page(width: 75pt, height: 100pt)
-#square(fill: conifer)[
- But, soft! what light through yonder window breaks?
-]
-
----
-// Test that square does not overflow page.
-#set page(width: 100pt, height: 75pt)
-#square(fill: conifer)[
- But, soft! what light through yonder window breaks?
-]
-
----
-// Size wins over width and height.
-// Error: 09-20 unexpected argument: width
-#square(width: 10cm, height: 20cm, size: 1cm, fill: rgb("eb5278"))
diff --git a/tests/typ/visualize/stroke.typ b/tests/typ/visualize/stroke.typ
deleted file mode 100644
index cf91dcc3..00000000
--- a/tests/typ/visualize/stroke.typ
+++ /dev/null
@@ -1,115 +0,0 @@
-// Test lines.
-
----
-// Some simple test lines
-#line(length: 60pt, stroke: red)
-#v(3pt)
-#line(length: 60pt, stroke: 2pt)
-#v(3pt)
-#line(length: 60pt, stroke: blue + 1.5pt)
-#v(3pt)
-#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: "dashed"))
-#v(3pt)
-#line(length: 60pt, stroke: (paint: red, thickness: 4pt, cap: "round"))
-
----
-// Set rules with stroke
-#set line(stroke: (paint: red, thickness: 1pt, cap: "butt", dash: "dash-dotted"))
-#line(length: 60pt)
-#v(3pt)
-#line(length: 60pt, stroke: blue)
-#v(3pt)
-#line(length: 60pt, stroke: (dash: none))
-
----
-// Rectangle strokes
-#rect(width: 20pt, height: 20pt, stroke: red)
-#v(3pt)
-#rect(width: 20pt, height: 20pt, stroke: (rest: red, top: (paint: blue, dash: "dashed")))
-#v(3pt)
-#rect(width: 20pt, height: 20pt, stroke: (thickness: 5pt, join: "round"))
-
----
-// Dashing
-#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: ("dot", 1pt)))
-#v(3pt)
-#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: ("dot", 1pt, 4pt, 2pt)))
-#v(3pt)
-#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: (array: ("dot", 1pt, 4pt, 2pt), phase: 5pt)))
-#v(3pt)
-#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: ()))
-#v(3pt)
-#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: (1pt, 3pt, 9pt)))
-
----
-// Line joins
-#stack(
- dir: ltr,
- spacing: 1em,
- polygon(stroke: (thickness: 4pt, paint: blue, join: "round"),
- (0pt, 20pt), (15pt, 0pt), (0pt, 40pt), (15pt, 45pt)),
- polygon(stroke: (thickness: 4pt, paint: blue, join: "bevel"),
- (0pt, 20pt), (15pt, 0pt), (0pt, 40pt), (15pt, 45pt)),
- polygon(stroke: (thickness: 4pt, paint: blue, join: "miter"),
- (0pt, 20pt), (15pt, 0pt), (0pt, 40pt), (15pt, 45pt)),
- polygon(stroke: (thickness: 4pt, paint: blue, join: "miter", miter-limit: 20.0),
- (0pt, 20pt), (15pt, 0pt), (0pt, 40pt), (15pt, 45pt)),
-)
----
-// Error: 29-56 unexpected key "thicknes", valid keys are "paint", "thickness", "cap", "join", "dash", and "miter-limit"
-#line(length: 60pt, stroke: (paint: red, thicknes: 1pt))
-
----
-// Error: 29-55 expected "solid", "dotted", "densely-dotted", "loosely-dotted", "dashed", "densely-dashed", "loosely-dashed", "dash-dotted", "densely-dash-dotted", "loosely-dash-dotted", array, dictionary, none, or auto
-#line(length: 60pt, stroke: (paint: red, dash: "dash"))
-
----
-// 0pt strokes must function exactly like 'none' strokes and not draw anything
-#rect(width: 10pt, height: 10pt, stroke: none)
-#rect(width: 10pt, height: 10pt, stroke: 0pt)
-#rect(width: 10pt, height: 10pt, stroke: none, fill: blue)
-#rect(width: 10pt, height: 10pt, stroke: 0pt + red, fill: blue)
-
-#line(length: 30pt, stroke: 0pt)
-#line(length: 30pt, stroke: (paint: red, thickness: 0pt, dash: ("dot", 1pt)))
-
-#table(columns: 2, stroke: none)[A][B]
-#table(columns: 2, stroke: 0pt)[A][B]
-
-#path(
- fill: red,
- stroke: none,
- closed: true,
- ((0%, 0%), (4%, -4%)),
- ((50%, 50%), (4%, -4%)),
- ((0%, 50%), (4%, 4%)),
- ((50%, 0%), (4%, 4%)),
-)
-
-#path(
- fill: red,
- stroke: 0pt,
- closed: true,
- ((0%, 0%), (4%, -4%)),
- ((50%, 50%), (4%, -4%)),
- ((0%, 50%), (4%, 4%)),
- ((50%, 0%), (4%, 4%)),
-)
-
----
-// Converting to stroke
-#assert.eq(stroke(red).paint, red)
-#assert.eq(stroke(red).thickness, auto)
-#assert.eq(stroke(2pt).paint, auto)
-#assert.eq(stroke((cap: "round", paint: blue)).cap, "round")
-#assert.eq(stroke((cap: auto, paint: blue)).cap, auto)
-#assert.eq(stroke((cap: auto, paint: blue)).thickness, auto)
-
-// Error: 9-21 unexpected key "foo", valid keys are "paint", "thickness", "cap", "join", "dash", and "miter-limit"
-#stroke((foo: "bar"))
-
-// Constructing with named arguments
-#assert.eq(stroke(paint: blue, thickness: 8pt), 8pt + blue)
-#assert.eq(stroke(thickness: 2pt), stroke(2pt))
-#assert.eq(stroke(cap: "round").thickness, auto)
-#assert.eq(stroke(cap: "round", thickness: auto).thickness, auto)
diff --git a/tests/typ/visualize/svg-text.typ b/tests/typ/visualize/svg-text.typ
deleted file mode 100644
index 6f0a758e..00000000
--- a/tests/typ/visualize/svg-text.typ
+++ /dev/null
@@ -1,18 +0,0 @@
-// Test SVG with text.
-
----
-#set page(width: 250pt)
-
-#figure(
- image("/assets/images/diagram.svg"),
- caption: [A textful diagram],
-)
-
----
-#set page(width: 250pt)
-#show image: set text(font: ("Roboto", "Noto Serif CJK SC"))
-
-#figure(
- image("/assets/images/chinese.svg"),
- caption: [Bilingual text]
-)
diff --git a/tools/test-helper/README.md b/tools/test-helper/README.md
index 0b10c454..f95b8d73 100644
--- a/tools/test-helper/README.md
+++ b/tools/test-helper/README.md
@@ -1,23 +1,29 @@
# Test helper
This is a small VS Code extension that helps with managing Typst's test suite.
-When installed, three new buttons appear in the menubar for all `.typ` files in
-the `tests` folder.
+When installed, a new Code Lens appears in all `.typ` files in the `tests`
+folder. It provides the following actions:
-- Open: Opens the output and reference images of a test to the side.
-- Refresh: Refreshes the preview.
-- Rerun: Re-runs the test.
-- Update: Copies the output into the reference folder and optimizes
- it with `oxipng`.
+- View: Opens the output and reference image of a test to the side.
+- Run: Runs the test and shows the results to the side.
+- Terminal: Runs the test in the integrated terminal.
-For the test helper to work correctly, you also need to install `oxipng`, for
-example with `cargo install oxipng`. Make sure that the version of oxipng you
-install is the same as the one in the root `Cargo.toml` so that the results are
-the same as when using the test CLI.
+In the side panel, there are a few menu actions at the top right:
+
+- Refresh: Reloads the panel to reflect changes to the images
+- Run: Runs the test and shows the results
+- Save: Runs the test with `--update` to save the reference image
## Installation
-The simplest way to install this extension (and keep it up-to-date) is to use VSCode's UI:
-* Go to View > Command Palette,
-* In the drop down list, pick command "Developer: Install extension from location",
-* Select this `test-helper` directory in the file explorer dialogue box. VSCode will add
-the extension's path to `~/.vscode/extensions/extensions.json`.
+First, you need to build the extension:
+```bash
+npm i
+npm run build
+```
+
+Then, you can easily install and (and keep it up-to-date) via VS Code's UI:
+- Go to View > Command Palette or press Cmd/Ctrl+P,
+- In the drop down list, pick command "Developer: Install extension from
+ location",
+- Select this `test-helper` directory in the file explorer dialogue box. VS Code
+ will add the extension's path to `~/.vscode/extensions/extensions.json`.
diff --git a/tools/test-helper/extension.js b/tools/test-helper/extension.js
deleted file mode 100644
index 7c3fa418..00000000
--- a/tools/test-helper/extension.js
+++ /dev/null
@@ -1,363 +0,0 @@
-const vscode = require('vscode')
-const cp = require('child_process')
-const {clearInterval} = require('timers')
-
-class Handler {
- constructor() {
- /** @type {vscode.Uri?} */ this.sourceUriOfActivePanel = null
- /** @type {Map<vscode.Uri, vscode.WebviewPanel>} */ this.panels = new Map()
-
- /** @type {vscode.StatusBarItem} */ this.testRunningStatusBarItem =
- vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right)
- this.testRunningStatusBarItem.text = "$(loading~spin) Running"
- this.testRunningStatusBarItem.backgroundColor =
- new vscode.ThemeColor('statusBarItem.warningBackground')
- this.testRunningStatusBarItem.tooltip =
- "test-helper rebuilds crates if necessary, so it may take some time."
- this.testRunningStatusBarItem.command =
- "Typst.test-helper.showTestProgress"
- /** @type {string|undefined} */ this.testRunningLatestMessage = undefined
- this.testRunningProgressShown = false
-
- Handler.enableRunTestButton_(true)
- }
-
- /**
- * The caller should ensure when this function is called, a text editor is active.
- * Note the fake "editor" for the extension's WebView panel is not one.
- * @returns {vscode.Uri}
- */
- static getActiveDocumentUri() {
- const editor = vscode.window.activeTextEditor
- if (!editor) {
- throw new Error('vscode.window.activeTextEditor is undefined.')
- }
- return editor.document.uri
- }
-
- /**
- * The caller should ensure when this function is called, a WebView panel is active.
- * @returns {vscode.Uri}
- */
- getSourceUriOfActivePanel() {
- // If this function is invoked when user clicks the button from within a WebView
- // panel, then the active panel is this panel, and sourceUriOfActivePanel is
- // guaranteed to have been updated by that panel's onDidChangeViewState listener.
- if (!this.sourceUriOfActivePanel) {
- throw new Error('sourceUriOfActivePanel is falsy; is there a focused panel?')
- }
- return this.sourceUriOfActivePanel
- }
-
- /** @param {vscode.Uri} uri */
- static getImageUris(uri) {
- const png = vscode.Uri.file(uri.path
- .replace("tests/typ", "tests/png")
- .replace(".typ", ".png"))
-
- const ref = vscode.Uri.file(uri.path
- .replace("tests/typ", "tests/ref")
- .replace(".typ", ".png"))
-
- return {png, ref}
- }
-
- /** @param {boolean} enable */
- static enableRunTestButton_(enable) {
- // Need to flip the value here, i.e. "disableRunTestButton" rather than
- // "enableRunTestButton", because default values of custom context keys
- // before extension activation are falsy. The extension isn't activated
- // until a button is clicked.
- //
- // Note: at the time of this writing, VSCode doesn't support activating
- // on path patterns. Alternatively one may try activating the extension
- // using the activation event "onLanguage:typst", but this idea in fact
- // doesn't work perperly as we would like, since (a) we do not want the
- // extension to be enabled on every Typst file, e.g. the thesis you are
- // working on, and (b) VSCode does not know the language ID "typst" out
- // of box.
- vscode.commands.executeCommand(
- "setContext", "Typst.test-helper.disableRunTestButton", !enable)
- }
-
- /**
- * @param {vscode.Uri} uri
- * @param {string} stdout
- * @param {string} stderr
- */
- refreshTestPreviewImpl_(uri, stdout, stderr) {
- const {png, ref} = Handler.getImageUris(uri)
-
- const panel = this.panels.get(uri)
- if (panel && panel.visible) {
- console.log(`Refreshing WebView for ${uri.fsPath}`)
- const webViewSrcs = {
- png: panel.webview.asWebviewUri(png),
- ref: panel.webview.asWebviewUri(ref),
- }
- panel.webview.html = ''
-
- // Make refresh notable.
- setTimeout(() => {
- if (!panel) {
- throw new Error('panel to refresh is falsy after waiting')
- }
- panel.webview.html = getWebviewContent(webViewSrcs, stdout, stderr)
- }, 50)
- }
- }
-
- /** @param {vscode.Uri} uri */
- openTestPreview(uri) {
- if (this.panels.has(uri)) {
- this.panels.get(uri)?.reveal()
- return
- }
-
- const newPanel = vscode.window.createWebviewPanel(
- 'Typst.test-helper.preview',
- uri.path.split('/').pop()?.replace('.typ', '.png') ?? 'Test output',
- vscode.ViewColumn.Beside,
- {enableFindWidget: true},
- )
- newPanel.onDidChangeViewState(() => {
- if (newPanel && newPanel.active && newPanel.visible) {
- console.log(`Set sourceUriOfActivePanel to ${uri}`)
- this.sourceUriOfActivePanel = uri
- } else {
- console.log(`Set sourceUriOfActivePanel to null`)
- this.sourceUriOfActivePanel = null
- }
- })
- newPanel.onDidDispose(() => {
- console.log(`Delete panel ${uri}`)
- this.panels.delete(uri)
- if (this.sourceUriOfActivePanel === uri) {
- this.sourceUriOfActivePanel = null
- }
- })
- this.panels.set(uri, newPanel)
-
- this.refreshTestPreviewImpl_(uri, "", "")
- }
-
- showTestProgress() {
- if (this.testRunningLatestMessage === undefined
- || this.testRunningProgressShown) {
- return
- }
- this.testRunningProgressShown = true
- vscode.window.withProgress({
- location: vscode.ProgressLocation.Notification,
- title: "test-helper",
- }, (progress) => {
- /** @type {!Promise<void>} */
- const closeWhenResolved = new Promise((resolve) => {
- // This progress bar intends to relieve the developer's doubt
- // during the possibly long waiting because of the rebuilding
- // phase before actually running the test. Therefore, a naive
- // polling (updates every few millisec) should be sufficient.
- const timerId = setInterval(() => {
- if (this.testRunningLatestMessage === undefined) {
- this.testRunningProgressShown = false
- clearInterval(timerId)
- resolve()
- }
- progress.report({message: this.testRunningLatestMessage})
- }, 100)
- })
-
- return closeWhenResolved
- })
- }
-
- /** @param {vscode.Uri} uri */
- runTest(uri) {
- const components = uri.fsPath.split(/tests[\/\\]/)
- const [dir, subPath] = components
-
- Handler.enableRunTestButton_(false)
- this.testRunningStatusBarItem.show()
-
- const proc = cp.spawn(
- "cargo",
- ["test", "--manifest-path", `${dir}/Cargo.toml`, "--workspace", "--test", "tests", "--", `${subPath}`])
- let outs = {stdout: "", stderr: ""}
- if (!proc.stdout || !proc.stderr) {
- throw new Error('Child process was not spawned successfully.')
- }
- proc.stdout.setEncoding('utf8')
- proc.stdout.on("data", (data) => {
- outs.stdout += data.toString()
- })
- proc.stderr.setEncoding('utf8')
- proc.stderr.on("data", (data) => {
- let s = data.toString()
- outs.stderr += s
- s = s.replace(/\(.+?\)/, "")
- this.testRunningLatestMessage = s.length > 50 ? (s.slice(0, 50) + "...") : s
- })
- proc.on("close", (exitCode) => {
- Handler.enableRunTestButton_(true)
- this.testRunningStatusBarItem.hide()
- this.testRunningLatestMessage = undefined
- console.log(`Ran tests ${uri.fsPath}, exit = ${exitCode}`)
- this.refreshTestPreviewImpl_(uri, outs.stdout, outs.stderr)
- })
- }
-
- /** @param {vscode.Uri} uri */
- refreshTestPreview(uri) {
- const panel = this.panels.get(uri)
- if (panel) {
- panel.reveal()
- this.refreshTestPreviewImpl_(uri, "", "")
- }
- }
-
- /** @param {vscode.Uri} uri */
- updateTestReference(uri) {
- const {png, ref} = Handler.getImageUris(uri)
-
- vscode.workspace.fs.copy(png, ref, {overwrite: true})
- .then(() => {
- cp.exec(`oxipng -o max -a ${ref.fsPath}`, (err, stdout, stderr) => {
- console.log(`Copied to reference file for ${uri.fsPath}`)
- this.refreshTestPreviewImpl_(uri, stdout, stderr)
- })
- })
- }
-
- /**
- * @param {vscode.Uri} uri
- * @param {string} webviewSection
- */
- copyFilePathToClipboard(uri, webviewSection) {
- const {png, ref} = Handler.getImageUris(uri)
- switch (webviewSection) {
- case 'png':
- vscode.env.clipboard.writeText(png.fsPath)
- break
- case 'ref':
- vscode.env.clipboard.writeText(ref.fsPath)
- break
- default:
- break
- }
- }
-}
-
-/** @param {vscode.ExtensionContext} context */
-function activate(context) {
- const handler = new Handler()
- context.subscriptions.push(handler.testRunningStatusBarItem)
-
- context.subscriptions.push(vscode.commands.registerCommand(
- "Typst.test-helper.showTestProgress", () => {
- handler.showTestProgress()
- }))
- context.subscriptions.push(vscode.commands.registerCommand(
- "Typst.test-helper.openFromSource", () => {
- handler.openTestPreview(Handler.getActiveDocumentUri())
- }))
- context.subscriptions.push(vscode.commands.registerCommand(
- "Typst.test-helper.refreshFromSource", () => {
- handler.refreshTestPreview(Handler.getActiveDocumentUri())
- }))
- context.subscriptions.push(vscode.commands.registerCommand(
- "Typst.test-helper.refreshFromPreview", () => {
- handler.refreshTestPreview(handler.getSourceUriOfActivePanel())
- }))
- context.subscriptions.push(vscode.commands.registerCommand(
- "Typst.test-helper.runFromSource", () => {
- handler.runTest(Handler.getActiveDocumentUri())
- }))
- context.subscriptions.push(vscode.commands.registerCommand(
- "Typst.test-helper.runFromPreview", () => {
- handler.runTest(handler.getSourceUriOfActivePanel())
- }))
- context.subscriptions.push(vscode.commands.registerCommand(
- "Typst.test-helper.updateFromSource", () => {
- handler.updateTestReference(Handler.getActiveDocumentUri())
- }))
- context.subscriptions.push(vscode.commands.registerCommand(
- "Typst.test-helper.updateFromPreview", () => {
- handler.updateTestReference(handler.getSourceUriOfActivePanel())
- }))
- context.subscriptions.push(vscode.commands.registerCommand(
- // The context menu (the "right-click menu") in the preview tab.
- "Typst.test-helper.copyImageFilePathFromPreviewContext", (e) => {
- handler.copyFilePathToClipboard(
- handler.getSourceUriOfActivePanel(), e.webviewSection)
- }))
-}
-
-/**
- * @param {{png: vscode.Uri, ref: vscode.Uri}} webViewSrcs
- * @param {string} stdout
- * @param {string} stderr
- * @returns {string}
- */
-function getWebviewContent(webViewSrcs, stdout, stderr) {
- const escape = (/**@type{string}*/text) => text.replace(/</g, "&lt;").replace(/>/g, "&gt;")
- return `
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Test output</title>
- <style>
- body, html {
- width: 100%;
- margin: 0;
- padding: 0;
- text-align: center;
- }
- img {
- width: 80%;
- object-fit: contain;
- }
- pre {
- display: inline-block;
- font-family: var(--vscode-editor-font-family);
- text-align: left;
- width: 80%;
- }
- .flex {
- display: flex;
- flex-wrap: wrap;
- }
- .flex > * {
- flex-grow: 1;
- flex-shrink: 0;
- max-width: 100%;
- }
- </style>
- </head>
- <body>
- <div class="flex" data-vscode-context='{"preventDefaultContextMenuItems": true}'>
- <div>
- <h1>Output</h1>
- <img data-vscode-context='{"webviewSection":"png"}' src="${webViewSrcs.png}"/>
- </div>
-
- <div>
- <h1>Reference</h1>
- <img data-vscode-context='{"webviewSection":"ref"}' src="${webViewSrcs.ref}"/>
- </div>
- </div>
-
- <h1>Standard output</h1>
- <pre>${escape(stdout)}</pre>
-
- <h1>Standard error</h1>
- <pre>${escape(stderr)}</pre>
- </body>
- </html>
- `
-}
-
-function deactivate() {}
-
-module.exports = {activate, deactivate}
diff --git a/tools/test-helper/package.json b/tools/test-helper/package.json
index 0f806eb9..5da2fe5a 100644
--- a/tools/test-helper/package.json
+++ b/tools/test-helper/package.json
@@ -1,5 +1,5 @@
{
- "name": "test-helper",
+ "name": "typst-test-helper",
"publisher": "typst",
"displayName": "Typst Test Helper",
"description": "Helps to run, compare and update Typst tests.",
@@ -11,115 +11,94 @@
"Other"
],
"activationEvents": [
- "onCommand:Typst.test-helper.openFromSource",
- "onCommand:Typst.test-helper.refreshFromSource",
- "onCommand:Typst.test-helper.refreshFromPreview",
- "onCommand:Typst.test-helper.runFromSource",
- "onCommand:Typst.test-helper.runFromPreview",
- "onCommand:Typst.test-helper.updateFromSource",
- "onCommand:Typst.test-helper.updateFromPreview",
- "onCommand:Typst.test-helper.copyImageFilePathFromPreviewContext"
+ "workspaceContains:tests/suite/playground.typ"
],
- "main": "./extension.js",
+ "main": "./dist/extension.js",
"contributes": {
"commands": [
{
- "command": "Typst.test-helper.openFromSource",
- "title": "Open test output",
- "category": "Typst.test-helper",
- "icon": "$(plus)"
- },
- {
- "command": "Typst.test-helper.refreshFromSource",
+ "command": "typst-test-helper.refreshFromPreview",
"title": "Refresh preview",
- "category": "Typst.test-helper",
+ "category": "Typst Test Helper",
"icon": "$(refresh)"
},
{
- "command": "Typst.test-helper.refreshFromPreview",
- "title": "Refresh preview",
- "category": "Typst.test-helper",
- "icon": "$(refresh)"
- },
- {
- "command": "Typst.test-helper.runFromSource",
+ "command": "typst-test-helper.runFromPreview",
"title": "Run test",
- "category": "Typst.test-helper",
+ "category": "Typst Test Helper",
"icon": "$(debug-start)",
- "enablement": "!Typst.test-helper.disableRunTestButton"
+ "enablement": "typst-test-helper.runButtonEnabled"
},
{
- "command": "Typst.test-helper.runFromPreview",
- "title": "Run test",
- "category": "Typst.test-helper",
- "icon": "$(debug-start)",
- "enablement": "!Typst.test-helper.disableRunTestButton"
+ "command": "typst-test-helper.saveFromPreview",
+ "title": "Run and save reference image",
+ "category": "Typst Test Helper",
+ "icon": "$(save)",
+ "enablement": "typst-test-helper.runButtonEnabled"
},
{
- "command": "Typst.test-helper.updateFromSource",
- "title": "Update reference image",
- "category": "Typst.test-helper",
- "icon": "$(save)"
+ "command": "typst-test-helper.copyImageFilePathFromPreviewContext",
+ "title": "Copy image file path",
+ "category": "Typst Test Helper"
},
{
- "command": "Typst.test-helper.updateFromPreview",
- "title": "Update reference image",
- "category": "Typst.test-helper",
- "icon": "$(save)"
+ "command": "typst-test-helper.increaseResolution",
+ "title": "Render at higher resolution",
+ "category": "Typst Test Helper",
+ "icon": "$(zoom-in)",
+ "enablement": "typst-test-helper.runButtonEnabled"
},
{
- "command": "Typst.test-helper.copyImageFilePathFromPreviewContext",
- "title": "Copy image file path"
+ "command": "typst-test-helper.decreaseResolution",
+ "title": "Render at lower resolution",
+ "category": "Typst Test Helper",
+ "icon": "$(zoom-out)",
+ "enablement": "typst-test-helper.runButtonEnabled"
}
],
"menus": {
"editor/title": [
{
- "when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
- "command": "Typst.test-helper.openFromSource",
+ "when": "activeWebviewPanelId == typst-test-helper.preview",
+ "command": "typst-test-helper.refreshFromPreview",
"group": "navigation@1"
},
{
- "when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
- "command": "Typst.test-helper.refreshFromSource",
+ "when": "activeWebviewPanelId == typst-test-helper.preview",
+ "command": "typst-test-helper.runFromPreview",
"group": "navigation@2"
},
{
- "when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
- "command": "Typst.test-helper.runFromSource",
+ "when": "activeWebviewPanelId == typst-test-helper.preview",
+ "command": "typst-test-helper.saveFromPreview",
"group": "navigation@3"
},
{
- "when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
- "command": "Typst.test-helper.updateFromSource",
+ "when": "activeWebviewPanelId == typst-test-helper.preview",
+ "command": "typst-test-helper.increaseResolution",
"group": "navigation@4"
},
{
- "when": "activeWebviewPanelId == Typst.test-helper.preview",
- "command": "Typst.test-helper.refreshFromPreview",
- "group": "navigation@1"
- },
- {
- "when": "activeWebviewPanelId == Typst.test-helper.preview",
- "command": "Typst.test-helper.runFromPreview",
- "group": "navigation@2"
- },
- {
- "when": "activeWebviewPanelId == Typst.test-helper.preview",
- "command": "Typst.test-helper.updateFromPreview",
- "group": "navigation@3"
+ "when": "activeWebviewPanelId == typst-test-helper.preview",
+ "command": "typst-test-helper.decreaseResolution",
+ "group": "navigation@4"
}
],
"webview/context": [
{
- "command": "Typst.test-helper.copyImageFilePathFromPreviewContext",
- "when": "webviewId == Typst.test-helper.preview && (webviewSection == png || webviewSection == ref)"
+ "command": "typst-test-helper.copyImageFilePathFromPreviewContext",
+ "when": "webviewId == typst-test-helper.preview && (webviewSection == png || webviewSection == ref)"
}
]
}
},
+ "scripts": {
+ "build": "tsc -p ./",
+ "watch": "tsc -watch -p ./"
+ },
"devDependencies": {
- "@types/vscode": "^1.53.0",
- "@types/node": "^12.11.7"
+ "@types/vscode": "^1.88.0",
+ "@types/node": "18.x",
+ "typescript": "^5.3.3"
}
-} \ No newline at end of file
+}
diff --git a/tools/test-helper/src/extension.ts b/tools/test-helper/src/extension.ts
new file mode 100644
index 00000000..6c44f75e
--- /dev/null
+++ b/tools/test-helper/src/extension.ts
@@ -0,0 +1,492 @@
+import * as vscode from "vscode";
+import * as cp from "child_process";
+import { clearInterval } from "timers";
+
+// Called when an activation event is triggered. Our activation event is the
+// presence of "tests/suite/playground.typ".
+export function activate(context: vscode.ExtensionContext) {
+ new TestHelper(context);
+}
+
+export function deactivate() {}
+
+class TestHelper {
+ // The currently active view for a test or `null` if none.
+ active?: {
+ // The tests's name.
+ name: string;
+ // The WebView panel that displays the test images and output.
+ panel: vscode.WebviewPanel;
+ } | null;
+
+ // The current zoom scale.
+ scale = 1.0;
+
+ // The extention's status bar item.
+ statusItem: vscode.StatusBarItem;
+
+ // The active message of the status item.
+ statusMessage?: string;
+
+ // Whether the status item is currently in spinning state.
+ statusSpinning = false;
+
+ // Sets the extension up.
+ constructor(private readonly context: vscode.ExtensionContext) {
+ this.active = null;
+
+ // Code lens that displays commands inline with the tests.
+ this.context.subscriptions.push(
+ vscode.languages.registerCodeLensProvider(
+ { pattern: "**/*.typ" },
+ { provideCodeLenses: (document) => this.lens(document) }
+ )
+ );
+
+ // Triggered when clicking "View" in the lens.
+ this.registerCommand("typst-test-helper.viewFromLens", (name) =>
+ this.viewFromLens(name)
+ );
+
+ // Triggered when clicking "Run" in the lens.
+ this.registerCommand("typst-test-helper.runFromLens", (name) =>
+ this.runFromLens(name)
+ );
+
+ // Triggered when clicking "Save" in the lens.
+ this.registerCommand("typst-test-helper.saveFromLens", (name) =>
+ this.saveFromLens(name)
+ );
+
+ // Triggered when clicking "Terminal" in the lens.
+ this.registerCommand("typst-test-helper.runInTerminal", (name) =>
+ this.runInTerminal(name)
+ );
+
+ // Triggered when clicking the "Refresh" button in the WebView toolbar.
+ this.registerCommand("typst-test-helper.refreshFromPreview", () =>
+ this.refreshFromPreview()
+ );
+
+ // Triggered when clicking the "Run" button in the WebView toolbar.
+ this.registerCommand("typst-test-helper.runFromPreview", () =>
+ this.runFromPreview()
+ );
+
+ // Triggered when clicking the "Save" button in the WebView toolbar.
+ this.registerCommand("typst-test-helper.saveFromPreview", () =>
+ this.saveFromPreview()
+ );
+
+ // Triggered when clicking the "Increase Resolution" button in the WebView
+ // toolbar.
+ this.registerCommand("typst-test-helper.increaseResolution", () =>
+ this.adjustResolution(2.0)
+ );
+
+ // Triggered when clicking the "Decrease Resolution" button in the WebView
+ // toolbar.
+ this.registerCommand("typst-test-helper.decreaseResolution", () =>
+ this.adjustResolution(0.5)
+ );
+
+ // Triggered when performing a right-click on an image in the WebView.
+ this.registerCommand(
+ "typst-test-helper.copyImageFilePathFromPreviewContext",
+ (e) => this.copyImageFilePathFromPreviewContext(e.webviewSection)
+ );
+
+ // Set's up the status bar item that shows a spinner while running a test.
+ this.statusItem = this.createStatusItem();
+ this.context.subscriptions.push(this.statusItem);
+
+ // Triggered when clicking on the status item.
+ this.registerCommand("typst-test-helper.showTestProgress", () =>
+ this.showTestProgress()
+ );
+
+ this.setRunButtonEnabled(true);
+ }
+
+ // Register a command with VS Code.
+ private registerCommand(id: string, callback: (...args: any[]) => any) {
+ this.context.subscriptions.push(
+ vscode.commands.registerCommand(id, callback)
+ );
+ }
+
+ // The test lens that provides "View | Run | Save | Terminal" commands inline
+ // with the test sources.
+ private lens(document: vscode.TextDocument) {
+ const lenses = [];
+ for (let nr = 0; nr < document.lineCount; nr++) {
+ const line = document.lineAt(nr);
+ const re = /^--- ([\d\w-]+) ---$/;
+ const m = line.text.match(re);
+ if (!m) {
+ continue;
+ }
+
+ const name = m[1];
+ lenses.push(
+ new vscode.CodeLens(line.range, {
+ title: "View",
+ tooltip: "View the test output and reference in a new tab",
+ command: "typst-test-helper.viewFromLens",
+ arguments: [name],
+ }),
+ new vscode.CodeLens(line.range, {
+ title: "Run",
+ tooltip: "Run the test and view the results in a new tab",
+ command: "typst-test-helper.runFromLens",
+ arguments: [name],
+ }),
+ new vscode.CodeLens(line.range, {
+ title: "Save",
+ tooltip: "Run and view the test and save the reference image",
+ command: "typst-test-helper.saveFromLens",
+ arguments: [name],
+ }),
+ new vscode.CodeLens(line.range, {
+ title: "Terminal",
+ tooltip: "Run the test in the integrated terminal",
+ command: "typst-test-helper.runInTerminal",
+ arguments: [name],
+ })
+ );
+ }
+ return lenses;
+ }
+
+ // Triggered when clicking "View" in the lens.
+ private viewFromLens(name: string) {
+ if (this.active) {
+ if (this.active.name == name) {
+ this.active.panel.reveal();
+ return;
+ }
+
+ this.active.name = name;
+ this.active.panel.title = name;
+ } else {
+ const panel = vscode.window.createWebviewPanel(
+ "typst-test-helper.preview",
+ name,
+ vscode.ViewColumn.Beside,
+ { enableFindWidget: true }
+ );
+
+ panel.onDidDispose(() => (this.active = null));
+
+ this.active = { name, panel };
+ }
+
+ this.refreshWebView();
+ }
+
+ // Triggered when clicking "Run" in the lens.
+ private runFromLens(name: string) {
+ this.viewFromLens(name);
+ this.runFromPreview();
+ }
+
+ // Triggered when clicking "Run" in the lens.
+ private saveFromLens(name: string) {
+ this.viewFromLens(name);
+ this.saveFromPreview();
+ }
+
+ // Triggered when clicking "Terminal" in the lens.
+ private runInTerminal(name: string) {
+ const TESTIT = "cargo test --workspace --test tests --";
+
+ if (vscode.window.terminals.length === 0) {
+ vscode.window.createTerminal();
+ }
+
+ const terminal = vscode.window.terminals[0];
+ terminal.show(true);
+ terminal.sendText(`${TESTIT} --exact ${name}`, true);
+ }
+
+ // Triggered when clicking the "Refresh" button in the WebView toolbar.
+ private refreshFromPreview() {
+ if (this.active) {
+ this.active.panel.reveal();
+ this.refreshWebView();
+ }
+ }
+
+ // Triggered when clicking the "Run" button in the WebView toolbar.
+ private runFromPreview() {
+ if (this.active) {
+ this.runCargoTest(this.active.name);
+ }
+ }
+
+ // Triggered when clicking the "Save" button in the WebView toolbar.
+ private saveFromPreview() {
+ if (this.active) {
+ this.runCargoTest(this.active.name, true);
+ }
+ }
+
+ // Triggered when clicking the one of the resolution buttons in the WebView
+ // toolbar.
+ private adjustResolution(factor: number) {
+ this.scale = Math.round(Math.min(Math.max(this.scale * factor, 1.0), 8.0));
+ this.runFromPreview();
+ }
+
+ // Runs a single test with cargo, optionally with `--update`.
+ private runCargoTest(name: string, update?: boolean) {
+ this.setRunButtonEnabled(false);
+ this.statusItem.show();
+
+ const dir = getWorkspaceRoot().path;
+ const proc = cp.spawn("cargo", [
+ "test",
+ "--manifest-path",
+ `${dir}/Cargo.toml`,
+ "--workspace",
+ "--test",
+ "tests",
+ "--",
+ ...(this.scale != 1.0 ? ["--scale", `${this.scale}`] : []),
+ "--exact",
+ "--verbose",
+ name,
+ ...(update ? ["--update"] : []),
+ ]);
+ let outs = { stdout: "", stderr: "" };
+ if (!proc.stdout || !proc.stderr) {
+ throw new Error("child process was not spawned successfully.");
+ }
+ proc.stdout.setEncoding("utf8");
+ proc.stdout.on("data", (data) => {
+ outs.stdout += data.toString();
+ });
+ proc.stderr.setEncoding("utf8");
+ proc.stderr.on("data", (data) => {
+ let s = data.toString();
+ outs.stderr += s;
+ s = s.replace(/\(.+?\)/, "");
+ this.statusMessage = s.length > 50 ? s.slice(0, 50) + "..." : s;
+ });
+ proc.on("close", (exitCode) => {
+ this.setRunButtonEnabled(true);
+ this.statusItem.hide();
+ this.statusMessage = undefined;
+ console.log(`Ran test ${name}, exit = ${exitCode}`);
+ setTimeout(() => {
+ this.refreshWebView(outs);
+ }, 50);
+ });
+ }
+
+ // Triggered when performing a right-click on an image in the WebView.
+ private copyImageFilePathFromPreviewContext(webviewSection: string) {
+ if (!this.active) return;
+ const { name } = this.active;
+ const { png, ref } = getImageUris(name);
+ switch (webviewSection) {
+ case "png":
+ vscode.env.clipboard.writeText(png.fsPath);
+ break;
+ case "ref":
+ vscode.env.clipboard.writeText(ref.fsPath);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Reloads the web view.
+ private refreshWebView(output?: { stdout: string; stderr: string }) {
+ if (!this.active) return;
+
+ const { name, panel } = this.active;
+ const { png, ref } = getImageUris(name);
+
+ if (panel && panel.visible) {
+ console.log(`Refreshing WebView for ${name}`);
+ const webViewSrcs = {
+ png: panel.webview.asWebviewUri(png),
+ ref: panel.webview.asWebviewUri(ref),
+ };
+ panel.webview.html = "";
+
+ // Make refresh notable.
+ setTimeout(() => {
+ if (!panel) {
+ throw new Error("panel to refresh is falsy after waiting");
+ }
+ panel.webview.html = getWebviewContent(webViewSrcs, output);
+ }, 50);
+ }
+ }
+
+ // Creates an item for the bottom status bar.
+ private createStatusItem(): vscode.StatusBarItem {
+ const item = vscode.window.createStatusBarItem(
+ vscode.StatusBarAlignment.Right
+ );
+ item.text = "$(loading~spin) Running";
+ item.backgroundColor = new vscode.ThemeColor(
+ "statusBarItem.warningBackground"
+ );
+ item.tooltip =
+ "test-helper rebuilds crates if necessary, so it may take some time.";
+ item.command = "typst-test-helper.showTestProgress";
+ return item;
+ }
+
+ // Triggered when clicking on the status bar item.
+ private showTestProgress() {
+ if (this.statusMessage === undefined || this.statusSpinning) {
+ return;
+ }
+ this.statusSpinning = true;
+ vscode.window.withProgress(
+ {
+ location: vscode.ProgressLocation.Notification,
+ title: "test-helper",
+ },
+ (progress) =>
+ new Promise<void>((resolve) => {
+ // This progress bar intends to relieve the developer's doubt during
+ // the possibly long waiting because of the rebuilding phase before
+ // actually running the test. Therefore, a naive polling (updates
+ // every few millisec) should be sufficient.
+ const timerId = setInterval(() => {
+ if (this.statusMessage === undefined) {
+ this.statusSpinning = false;
+ clearInterval(timerId);
+ resolve();
+ }
+ progress.report({ message: this.statusMessage });
+ }, 100);
+ })
+ );
+ }
+
+ // Confgiures whether the run and save buttons are enabled.
+ private setRunButtonEnabled(enabled: boolean) {
+ vscode.commands.executeCommand(
+ "setContext",
+ "typst-test-helper.runButtonEnabled",
+ enabled
+ );
+ }
+}
+
+// Returns the root folder of the workspace.
+function getWorkspaceRoot() {
+ return vscode.workspace.workspaceFolders![0].uri;
+}
+
+// Returns the URIs for a test's images.
+function getImageUris(name: string) {
+ const root = getWorkspaceRoot();
+ const png = vscode.Uri.joinPath(root, `tests/store/render/${name}.png`);
+ const ref = vscode.Uri.joinPath(root, `tests/ref/${name}.png`);
+ return { png, ref };
+}
+
+// Produces the content of the WebView.
+function getWebviewContent(
+ webViewSrcs: { png: vscode.Uri; ref: vscode.Uri },
+ output?: {
+ stdout: string;
+ stderr: string;
+ }
+): string {
+ const escape = (text: string) =>
+ text.replace(/</g, "&lt;").replace(/>/g, "&gt;");
+ return `
+ <!DOCTYPE html>
+ <html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>Test output</title>
+ <style>
+ body,
+ html {
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ text-align: center;
+ }
+ img {
+ position: relative;
+ object-fit: contain;
+ border: 1px solid black;
+ max-width: 80%;
+ image-rendering: pixelated;
+ zoom: 2;
+ }
+ img[alt]:after {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: black;
+ font-size: 10px;
+ line-height: 1.6;
+ text-align: center;
+ color: #bebebe;
+ content: "Not present";
+ }
+ pre {
+ display: inline-block;
+ font-family: var(--vscode-editor-font-family);
+ text-align: left;
+ width: 80%;
+ }
+ .flex {
+ display: flex;
+ flex-direction: column;
+ }
+ </style>
+ </head>
+ <body>
+ <div
+ class="flex"
+ data-vscode-context='{"preventDefaultContextMenuItems": true}'
+ >
+ <div>
+ <h1>Output</h1>
+ <img
+ class="output"
+ data-vscode-context='{"webviewSection":"png"}'
+ src="${webViewSrcs.png}"
+ alt="Placeholder"
+ />
+ </div>
+
+ <div>
+ <h1>Reference</h1>
+ <img
+ class="ref"
+ data-vscode-context='{"webviewSection":"ref"}'
+ src="${webViewSrcs.ref}"
+ alt="Placeholder"
+ />
+ </div>
+ </div>
+ ${
+ output?.stdout
+ ? `<h1>Standard output</h1><pre>${escape(output.stdout)}</pre>`
+ : ""
+ }
+ ${
+ output?.stderr
+ ? `<h1>Standard error</h1><pre>${escape(output.stderr)}</pre>`
+ : ""
+ }
+ </body>
+ </html>`;
+}
diff --git a/tools/test-helper/tsconfig.json b/tools/test-helper/tsconfig.json
new file mode 100644
index 00000000..45e37455
--- /dev/null
+++ b/tools/test-helper/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "module": "Node16",
+ "target": "ES2022",
+ "outDir": "dist",
+ "lib": ["ES2022"],
+ "sourceMap": true,
+ "rootDir": "src",
+ "strict": true
+ }
+}