summaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/typst-cli/Cargo.toml4
-rw-r--r--crates/typst-cli/src/main.rs3
-rw-r--r--crates/typst-cli/src/query.rs3
-rw-r--r--crates/typst-cli/src/timings.rs3
-rw-r--r--crates/typst-cli/src/world.rs4
-rw-r--r--crates/typst-eval/Cargo.toml32
-rw-r--r--crates/typst-eval/src/access.rs (renamed from crates/typst/src/eval/access.rs)10
-rw-r--r--crates/typst-eval/src/binding.rs (renamed from crates/typst/src/eval/binding.rs)8
-rw-r--r--crates/typst-eval/src/call.rs (renamed from crates/typst/src/eval/call.rs)38
-rw-r--r--crates/typst-eval/src/code.rs (renamed from crates/typst/src/eval/code.rs)13
-rw-r--r--crates/typst-eval/src/flow.rs (renamed from crates/typst/src/eval/flow.rs)10
-rw-r--r--crates/typst-eval/src/import.rs (renamed from crates/typst/src/eval/import.rs)29
-rw-r--r--crates/typst-eval/src/lib.rs (renamed from crates/typst/src/eval/mod.rs)42
-rw-r--r--crates/typst-eval/src/markup.rs (renamed from crates/typst/src/eval/markup.rs)18
-rw-r--r--crates/typst-eval/src/math.rs (renamed from crates/typst/src/eval/math.rs)17
-rw-r--r--crates/typst-eval/src/methods.rs (renamed from crates/typst/src/foundations/methods.rs)6
-rw-r--r--crates/typst-eval/src/ops.rs91
-rw-r--r--crates/typst-eval/src/rules.rs (renamed from crates/typst/src/eval/rules.rs)13
-rw-r--r--crates/typst-eval/src/vm.rs (renamed from crates/typst/src/eval/vm.rs)14
-rw-r--r--crates/typst-ide/Cargo.toml1
-rw-r--r--crates/typst-ide/src/analyze.rs5
-rw-r--r--crates/typst-ide/src/complete.rs1
-rw-r--r--crates/typst-ide/src/lib.rs2
-rw-r--r--crates/typst-ide/src/tooltip.rs2
-rw-r--r--crates/typst-kit/Cargo.toml5
-rw-r--r--crates/typst-kit/src/fonts.rs10
-rw-r--r--crates/typst-kit/src/package.rs4
-rw-r--r--crates/typst-layout/Cargo.toml43
-rw-r--r--crates/typst-layout/src/flow/block.rs401
-rw-r--r--crates/typst-layout/src/flow/collect.rs (renamed from crates/typst/src/layout/flow/collect.rs)54
-rw-r--r--crates/typst-layout/src/flow/compose.rs (renamed from crates/typst/src/layout/flow/compose.rs)31
-rw-r--r--crates/typst-layout/src/flow/distribute.rs (renamed from crates/typst/src/layout/flow/distribute.rs)11
-rw-r--r--crates/typst-layout/src/flow/mod.rs (renamed from crates/typst/src/layout/flow/mod.rs)78
-rw-r--r--crates/typst-layout/src/grid/cells.rs (renamed from crates/typst/src/layout/grid/cells.rs)332
-rw-r--r--crates/typst-layout/src/grid/layouter.rs (renamed from crates/typst/src/layout/grid/layout.rs)29
-rw-r--r--crates/typst-layout/src/grid/lines.rs (renamed from crates/typst/src/layout/grid/lines.rs)57
-rw-r--r--crates/typst-layout/src/grid/mod.rs416
-rw-r--r--crates/typst-layout/src/grid/repeated.rs (renamed from crates/typst/src/layout/grid/repeated.rs)32
-rw-r--r--crates/typst-layout/src/grid/rowspans.rs (renamed from crates/typst/src/layout/grid/rowspans.rs)80
-rw-r--r--crates/typst-layout/src/image.rs142
-rw-r--r--crates/typst-layout/src/inline/box.rs87
-rw-r--r--crates/typst-layout/src/inline/collect.rs (renamed from crates/typst/src/layout/inline/collect.rs)19
-rw-r--r--crates/typst-layout/src/inline/deco.rs213
-rw-r--r--crates/typst-layout/src/inline/finalize.rs (renamed from crates/typst/src/layout/inline/finalize.rs)5
-rw-r--r--crates/typst-layout/src/inline/line.rs (renamed from crates/typst/src/layout/inline/line.rs)19
-rw-r--r--crates/typst-layout/src/inline/linebreak.rs (renamed from crates/typst/src/layout/inline/linebreak.rs)10
-rw-r--r--crates/typst-layout/src/inline/mod.rs (renamed from crates/typst/src/layout/inline/mod.rs)26
-rw-r--r--crates/typst-layout/src/inline/prepare.rs (renamed from crates/typst/src/layout/inline/prepare.rs)8
-rw-r--r--crates/typst-layout/src/inline/shaping.rs (renamed from crates/typst/src/layout/inline/shaping.rs)20
-rw-r--r--crates/typst-layout/src/lib.rs30
-rw-r--r--crates/typst-layout/src/lists.rs146
-rw-r--r--crates/typst-layout/src/math/accent.rs75
-rw-r--r--crates/typst-layout/src/math/attach.rs (renamed from crates/typst/src/math/attach.rs)427
-rw-r--r--crates/typst-layout/src/math/cancel.rs144
-rw-r--r--crates/typst-layout/src/math/frac.rs (renamed from crates/typst/src/math/frac.rs)111
-rw-r--r--crates/typst-layout/src/math/fragment.rs (renamed from crates/typst/src/math/fragment.rs)150
-rw-r--r--crates/typst-layout/src/math/lr.rs135
-rw-r--r--crates/typst-layout/src/math/mat.rs333
-rw-r--r--crates/typst-layout/src/math/mod.rs703
-rw-r--r--crates/typst-layout/src/math/root.rs (renamed from crates/typst/src/math/root.rs)68
-rw-r--r--crates/typst-layout/src/math/run.rs (renamed from crates/typst/src/math/row.rs)60
-rw-r--r--crates/typst-layout/src/math/shared.rs207
-rw-r--r--crates/typst-layout/src/math/stretch.rs (renamed from crates/typst/src/math/stretch.rs)171
-rw-r--r--crates/typst-layout/src/math/text.rs344
-rw-r--r--crates/typst-layout/src/math/underover.rs327
-rw-r--r--crates/typst-layout/src/pad.rs (renamed from crates/typst/src/layout/pad.rs)86
-rw-r--r--crates/typst-layout/src/pages/collect.rs (renamed from crates/typst/src/layout/pages/collect.rs)8
-rw-r--r--crates/typst-layout/src/pages/finalize.rs (renamed from crates/typst/src/layout/pages/finalize.rs)9
-rw-r--r--crates/typst-layout/src/pages/mod.rs (renamed from crates/typst/src/layout/pages/mod.rs)28
-rw-r--r--crates/typst-layout/src/pages/run.rs (renamed from crates/typst/src/layout/pages/run.rs)38
-rw-r--r--crates/typst-layout/src/repeat.rs60
-rw-r--r--crates/typst-layout/src/shapes.rs (renamed from crates/typst/src/visualize/shape.rs)897
-rw-r--r--crates/typst-layout/src/stack.rs (renamed from crates/typst/src/layout/stack.rs)104
-rw-r--r--crates/typst-layout/src/transforms.rs246
-rw-r--r--crates/typst-library/Cargo.toml72
-rw-r--r--crates/typst-library/src/diag.rs (renamed from crates/typst/src/diag.rs)4
-rw-r--r--crates/typst-library/src/engine.rs (renamed from crates/typst/src/engine.rs)13
-rw-r--r--crates/typst-library/src/foundations/args.rs (renamed from crates/typst/src/foundations/args.rs)2
-rw-r--r--crates/typst-library/src/foundations/array.rs (renamed from crates/typst/src/foundations/array.rs)7
-rw-r--r--crates/typst-library/src/foundations/auto.rs (renamed from crates/typst/src/foundations/auto.rs)3
-rw-r--r--crates/typst-library/src/foundations/bool.rs (renamed from crates/typst/src/foundations/bool.rs)0
-rw-r--r--crates/typst-library/src/foundations/bytes.rs (renamed from crates/typst/src/foundations/bytes.rs)2
-rw-r--r--crates/typst-library/src/foundations/calc.rs (renamed from crates/typst/src/foundations/calc.rs)7
-rw-r--r--crates/typst-library/src/foundations/cast.rs (renamed from crates/typst/src/foundations/cast.rs)10
-rw-r--r--crates/typst-library/src/foundations/content.rs (renamed from crates/typst/src/foundations/content.rs)4
-rw-r--r--crates/typst-library/src/foundations/context.rs (renamed from crates/typst/src/foundations/context.rs)0
-rw-r--r--crates/typst-library/src/foundations/datetime.rs (renamed from crates/typst/src/foundations/datetime.rs)0
-rw-r--r--crates/typst-library/src/foundations/decimal.rs (renamed from crates/typst/src/foundations/decimal.rs)8
-rw-r--r--crates/typst-library/src/foundations/dict.rs (renamed from crates/typst/src/foundations/dict.rs)4
-rw-r--r--crates/typst-library/src/foundations/duration.rs (renamed from crates/typst/src/foundations/duration.rs)0
-rw-r--r--crates/typst-library/src/foundations/element.rs (renamed from crates/typst/src/foundations/element.rs)7
-rw-r--r--crates/typst-library/src/foundations/fields.rs (renamed from crates/typst/src/foundations/fields.rs)0
-rw-r--r--crates/typst-library/src/foundations/float.rs (renamed from crates/typst/src/foundations/float.rs)4
-rw-r--r--crates/typst-library/src/foundations/func.rs (renamed from crates/typst/src/foundations/func.rs)13
-rw-r--r--crates/typst-library/src/foundations/int.rs (renamed from crates/typst/src/foundations/int.rs)21
-rw-r--r--crates/typst-library/src/foundations/label.rs (renamed from crates/typst/src/foundations/label.rs)2
-rw-r--r--crates/typst-library/src/foundations/mod.rs (renamed from crates/typst/src/foundations/mod.rs)15
-rw-r--r--crates/typst-library/src/foundations/module.rs (renamed from crates/typst/src/foundations/module.rs)2
-rw-r--r--crates/typst-library/src/foundations/none.rs (renamed from crates/typst/src/foundations/none.rs)0
-rw-r--r--crates/typst-library/src/foundations/ops.rs (renamed from crates/typst/src/eval/ops.rs)92
-rw-r--r--crates/typst-library/src/foundations/plugin.rs (renamed from crates/typst/src/foundations/plugin.rs)2
-rw-r--r--crates/typst-library/src/foundations/repr.rs (renamed from crates/typst/src/foundations/repr.rs)2
-rw-r--r--crates/typst-library/src/foundations/scope.rs (renamed from crates/typst/src/foundations/scope.rs)12
-rw-r--r--crates/typst-library/src/foundations/selector.rs (renamed from crates/typst/src/foundations/selector.rs)3
-rw-r--r--crates/typst-library/src/foundations/str.rs (renamed from crates/typst/src/foundations/str.rs)10
-rw-r--r--crates/typst-library/src/foundations/styles.rs (renamed from crates/typst/src/foundations/styles.rs)4
-rw-r--r--crates/typst-library/src/foundations/symbol.rs (renamed from crates/typst/src/symbols/symbol.rs)8
-rw-r--r--crates/typst-library/src/foundations/sys.rs (renamed from crates/typst/src/foundations/sys.rs)0
-rw-r--r--crates/typst-library/src/foundations/ty.rs (renamed from crates/typst/src/foundations/ty.rs)9
-rw-r--r--crates/typst-library/src/foundations/value.rs (renamed from crates/typst/src/foundations/value.rs)14
-rw-r--r--crates/typst-library/src/foundations/version.rs (renamed from crates/typst/src/foundations/version.rs)0
-rw-r--r--crates/typst-library/src/introspection/counter.rs (renamed from crates/typst/src/introspection/counter.rs)8
-rw-r--r--crates/typst-library/src/introspection/here.rs (renamed from crates/typst/src/introspection/here.rs)0
-rw-r--r--crates/typst-library/src/introspection/introspector.rs (renamed from crates/typst/src/introspection/introspector.rs)4
-rw-r--r--crates/typst-library/src/introspection/locate.rs (renamed from crates/typst/src/introspection/locate.rs)2
-rw-r--r--crates/typst-library/src/introspection/location.rs (renamed from crates/typst/src/introspection/location.rs)2
-rw-r--r--crates/typst-library/src/introspection/locator.rs (renamed from crates/typst/src/introspection/locator.rs)6
-rw-r--r--crates/typst-library/src/introspection/metadata.rs (renamed from crates/typst/src/introspection/metadata.rs)0
-rw-r--r--crates/typst-library/src/introspection/mod.rs (renamed from crates/typst/src/introspection/mod.rs)0
-rw-r--r--crates/typst-library/src/introspection/query.rs (renamed from crates/typst/src/introspection/query.rs)2
-rw-r--r--crates/typst-library/src/introspection/state.rs (renamed from crates/typst/src/introspection/state.rs)6
-rw-r--r--crates/typst-library/src/introspection/tag.rs (renamed from crates/typst/src/introspection/tag.rs)0
-rw-r--r--crates/typst-library/src/layout/abs.rs (renamed from crates/typst/src/layout/abs.rs)2
-rw-r--r--crates/typst-library/src/layout/align.rs (renamed from crates/typst/src/layout/align.rs)0
-rw-r--r--crates/typst-library/src/layout/angle.rs (renamed from crates/typst/src/layout/angle.rs)2
-rw-r--r--crates/typst-library/src/layout/axes.rs (renamed from crates/typst/src/layout/axes.rs)3
-rw-r--r--crates/typst-library/src/layout/columns.rs (renamed from crates/typst/src/layout/columns.rs)29
-rw-r--r--crates/typst-library/src/layout/container.rs563
-rw-r--r--crates/typst-library/src/layout/corners.rs (renamed from crates/typst/src/layout/corners.rs)3
-rw-r--r--crates/typst-library/src/layout/dir.rs (renamed from crates/typst/src/layout/dir.rs)0
-rw-r--r--crates/typst-library/src/layout/em.rs (renamed from crates/typst/src/layout/em.rs)2
-rw-r--r--crates/typst-library/src/layout/fr.rs (renamed from crates/typst/src/layout/fr.rs)2
-rw-r--r--crates/typst-library/src/layout/fragment.rs (renamed from crates/typst/src/layout/fragment.rs)0
-rw-r--r--crates/typst-library/src/layout/frame.rs (renamed from crates/typst/src/layout/frame.rs)37
-rw-r--r--crates/typst-library/src/layout/grid.rs (renamed from crates/typst/src/layout/grid/mod.rs)491
-rw-r--r--crates/typst-library/src/layout/hide.rs (renamed from crates/typst/src/layout/hide.rs)0
-rw-r--r--crates/typst-library/src/layout/layout.rs (renamed from crates/typst/src/layout/layout.rs)8
-rw-r--r--crates/typst-library/src/layout/length.rs (renamed from crates/typst/src/layout/length.rs)4
-rw-r--r--crates/typst-library/src/layout/measure.rs (renamed from crates/typst/src/layout/measure.rs)6
-rw-r--r--crates/typst-library/src/layout/mod.rs (renamed from crates/typst/src/layout/mod.rs)7
-rw-r--r--crates/typst-library/src/layout/pad.rs65
-rw-r--r--crates/typst-library/src/layout/page.rs (renamed from crates/typst/src/layout/page.rs)2
-rw-r--r--crates/typst-library/src/layout/place.rs (renamed from crates/typst/src/layout/place.rs)0
-rw-r--r--crates/typst-library/src/layout/point.rs (renamed from crates/typst/src/layout/point.rs)3
-rw-r--r--crates/typst-library/src/layout/ratio.rs (renamed from crates/typst/src/layout/ratio.rs)2
-rw-r--r--crates/typst-library/src/layout/regions.rs (renamed from crates/typst/src/layout/regions.rs)0
-rw-r--r--crates/typst-library/src/layout/rel.rs (renamed from crates/typst/src/layout/rel.rs)2
-rw-r--r--crates/typst-library/src/layout/repeat.rs49
-rw-r--r--crates/typst-library/src/layout/sides.rs (renamed from crates/typst/src/layout/sides.rs)3
-rw-r--r--crates/typst-library/src/layout/size.rs (renamed from crates/typst/src/layout/size.rs)3
-rw-r--r--crates/typst-library/src/layout/spacing.rs (renamed from crates/typst/src/layout/spacing.rs)3
-rw-r--r--crates/typst-library/src/layout/stack.rs84
-rw-r--r--crates/typst-library/src/layout/transform.rs (renamed from crates/typst/src/layout/transform.rs)268
-rw-r--r--crates/typst-library/src/lib.rs264
-rw-r--r--crates/typst-library/src/loading/cbor.rs (renamed from crates/typst/src/loading/cbor.rs)2
-rw-r--r--crates/typst-library/src/loading/csv.rs (renamed from crates/typst/src/loading/csv.rs)2
-rw-r--r--crates/typst-library/src/loading/json.rs (renamed from crates/typst/src/loading/json.rs)2
-rw-r--r--crates/typst-library/src/loading/mod.rs (renamed from crates/typst/src/loading/mod.rs)4
-rw-r--r--crates/typst-library/src/loading/read.rs (renamed from crates/typst/src/loading/read.rs)2
-rw-r--r--crates/typst-library/src/loading/toml.rs (renamed from crates/typst/src/loading/toml.rs)2
-rw-r--r--crates/typst-library/src/loading/xml.rs (renamed from crates/typst/src/loading/xml.rs)2
-rw-r--r--crates/typst-library/src/loading/yaml.rs (renamed from crates/typst/src/loading/yaml.rs)2
-rw-r--r--crates/typst-library/src/math/accent.rs (renamed from crates/typst/src/math/accent.rs)174
-rw-r--r--crates/typst-library/src/math/attach.rs156
-rw-r--r--crates/typst-library/src/math/cancel.rs116
-rw-r--r--crates/typst-library/src/math/equation.rs256
-rw-r--r--crates/typst-library/src/math/frac.rs56
-rw-r--r--crates/typst-library/src/math/lr.rs135
-rw-r--r--crates/typst-library/src/math/matrix.rs (renamed from crates/typst/src/math/matrix.rs)358
-rw-r--r--crates/typst-library/src/math/mod.rs (renamed from crates/typst/src/math/mod.rs)89
-rw-r--r--crates/typst-library/src/math/op.rs (renamed from crates/typst/src/math/op.rs)32
-rw-r--r--crates/typst-library/src/math/root.rs35
-rw-r--r--crates/typst-library/src/math/style.rs254
-rw-r--r--crates/typst-library/src/math/underover.rs156
-rw-r--r--crates/typst-library/src/model/bibliography.rs (renamed from crates/typst/src/model/bibliography.rs)42
-rw-r--r--crates/typst-library/src/model/cite.rs (renamed from crates/typst/src/model/cite.rs)2
-rw-r--r--crates/typst-library/src/model/document.rs (renamed from crates/typst/src/model/document.rs)0
-rw-r--r--crates/typst-library/src/model/emph.rs (renamed from crates/typst/src/model/emph.rs)0
-rw-r--r--crates/typst-library/src/model/enum.rs (renamed from crates/typst/src/model/enum.rs)106
-rw-r--r--crates/typst-library/src/model/figure.rs (renamed from crates/typst/src/model/figure.rs)2
-rw-r--r--crates/typst-library/src/model/footnote.rs (renamed from crates/typst/src/model/footnote.rs)3
-rw-r--r--crates/typst-library/src/model/heading.rs (renamed from crates/typst/src/model/heading.rs)18
-rw-r--r--crates/typst-library/src/model/link.rs (renamed from crates/typst/src/model/link.rs)0
-rw-r--r--crates/typst-library/src/model/list.rs (renamed from crates/typst/src/model/list.rs)73
-rw-r--r--crates/typst-library/src/model/mod.rs (renamed from crates/typst/src/model/mod.rs)0
-rw-r--r--crates/typst-library/src/model/numbering.rs (renamed from crates/typst/src/model/numbering.rs)0
-rw-r--r--crates/typst-library/src/model/outline.rs (renamed from crates/typst/src/model/outline.rs)4
-rw-r--r--crates/typst-library/src/model/par.rs (renamed from crates/typst/src/model/par.rs)3
-rw-r--r--crates/typst-library/src/model/quote.rs (renamed from crates/typst/src/model/quote.rs)0
-rw-r--r--crates/typst-library/src/model/reference.rs (renamed from crates/typst/src/model/reference.rs)0
-rw-r--r--crates/typst-library/src/model/strong.rs (renamed from crates/typst/src/model/strong.rs)0
-rw-r--r--crates/typst-library/src/model/table.rs (renamed from crates/typst/src/model/table.rs)224
-rw-r--r--crates/typst-library/src/model/terms.rs (renamed from crates/typst/src/model/terms.rs)3
-rw-r--r--crates/typst-library/src/routines.rs368
-rw-r--r--crates/typst-library/src/symbols/emoji.rs (renamed from crates/typst/src/symbols/emoji.rs)5
-rw-r--r--crates/typst-library/src/symbols/mod.rs (renamed from crates/typst/src/symbols/mod.rs)3
-rw-r--r--crates/typst-library/src/symbols/sym.rs (renamed from crates/typst/src/symbols/sym.rs)5
-rw-r--r--crates/typst-library/src/text/case.rs (renamed from crates/typst/src/text/case.rs)0
-rw-r--r--crates/typst-library/src/text/deco.rs (renamed from crates/typst/src/text/deco.rs)223
-rw-r--r--crates/typst-library/src/text/font/book.rs (renamed from crates/typst/src/text/font/book.rs)0
-rw-r--r--crates/typst-library/src/text/font/color.rs (renamed from crates/typst/src/text/font/color.rs)28
-rw-r--r--crates/typst-library/src/text/font/exceptions.rs (renamed from crates/typst/src/text/font/exceptions.rs)10
-rw-r--r--crates/typst-library/src/text/font/mod.rs (renamed from crates/typst/src/text/font/mod.rs)0
-rw-r--r--crates/typst-library/src/text/font/variant.rs (renamed from crates/typst/src/text/font/variant.rs)0
-rw-r--r--crates/typst-library/src/text/item.rs (renamed from crates/typst/src/text/item.rs)2
-rw-r--r--crates/typst-library/src/text/lang.rs (renamed from crates/typst/src/text/lang.rs)5
-rw-r--r--crates/typst-library/src/text/linebreak.rs (renamed from crates/typst/src/text/linebreak.rs)3
-rw-r--r--crates/typst-library/src/text/lorem.rs (renamed from crates/typst/src/text/lorem.rs)0
-rw-r--r--crates/typst-library/src/text/mod.rs (renamed from crates/typst/src/text/mod.rs)10
-rw-r--r--crates/typst-library/src/text/raw.rs (renamed from crates/typst/src/text/raw.rs)12
-rw-r--r--crates/typst-library/src/text/shift.rs (renamed from crates/typst/src/text/shift.rs)0
-rw-r--r--crates/typst-library/src/text/smallcaps.rs (renamed from crates/typst/src/text/smallcaps.rs)0
-rw-r--r--crates/typst-library/src/text/smartquote.rs (renamed from crates/typst/src/text/smartquote.rs)2
-rw-r--r--crates/typst-library/src/text/space.rs (renamed from crates/typst/src/text/space.rs)2
-rw-r--r--crates/typst-library/src/visualize/color.rs (renamed from crates/typst/src/visualize/color.rs)4
-rw-r--r--crates/typst-library/src/visualize/gradient.rs (renamed from crates/typst/src/visualize/gradient.rs)2
-rw-r--r--crates/typst-library/src/visualize/image/mod.rs (renamed from crates/typst/src/visualize/image/mod.rs)148
-rw-r--r--crates/typst-library/src/visualize/image/raster.rs (renamed from crates/typst/src/visualize/image/raster.rs)3
-rw-r--r--crates/typst-library/src/visualize/image/svg.rs (renamed from crates/typst/src/visualize/image/svg.rs)0
-rw-r--r--crates/typst-library/src/visualize/line.rs (renamed from crates/typst/src/visualize/line.rs)46
-rw-r--r--crates/typst-library/src/visualize/mod.rs (renamed from crates/typst/src/visualize/mod.rs)0
-rw-r--r--crates/typst-library/src/visualize/paint.rs (renamed from crates/typst/src/visualize/paint.rs)0
-rw-r--r--crates/typst-library/src/visualize/path.rs (renamed from crates/typst/src/visualize/path.rs)126
-rw-r--r--crates/typst-library/src/visualize/pattern.rs (renamed from crates/typst/src/visualize/pattern.rs)9
-rw-r--r--crates/typst-library/src/visualize/polygon.rs (renamed from crates/typst/src/visualize/polygon.rs)71
-rw-r--r--crates/typst-library/src/visualize/shape.rs448
-rw-r--r--crates/typst-library/src/visualize/stroke.rs (renamed from crates/typst/src/visualize/stroke.rs)2
-rw-r--r--crates/typst-library/translations/ar.txt (renamed from crates/typst/translations/ar.txt)0
-rw-r--r--crates/typst-library/translations/ca.txt (renamed from crates/typst/translations/ca.txt)0
-rw-r--r--crates/typst-library/translations/cs.txt (renamed from crates/typst/translations/cs.txt)0
-rw-r--r--crates/typst-library/translations/da.txt (renamed from crates/typst/translations/da.txt)0
-rw-r--r--crates/typst-library/translations/de.txt (renamed from crates/typst/translations/de.txt)0
-rw-r--r--crates/typst-library/translations/en.txt (renamed from crates/typst/translations/en.txt)0
-rw-r--r--crates/typst-library/translations/es.txt (renamed from crates/typst/translations/es.txt)0
-rw-r--r--crates/typst-library/translations/et.txt (renamed from crates/typst/translations/et.txt)0
-rw-r--r--crates/typst-library/translations/fi.txt (renamed from crates/typst/translations/fi.txt)0
-rw-r--r--crates/typst-library/translations/fr.txt (renamed from crates/typst/translations/fr.txt)0
-rw-r--r--crates/typst-library/translations/gl.txt (renamed from crates/typst/translations/gl.txt)0
-rw-r--r--crates/typst-library/translations/gr.txt (renamed from crates/typst/translations/gr.txt)0
-rw-r--r--crates/typst-library/translations/he.txt (renamed from crates/typst/translations/he.txt)0
-rw-r--r--crates/typst-library/translations/hu.txt (renamed from crates/typst/translations/hu.txt)0
-rw-r--r--crates/typst-library/translations/is.txt (renamed from crates/typst/translations/is.txt)0
-rw-r--r--crates/typst-library/translations/it.txt (renamed from crates/typst/translations/it.txt)0
-rw-r--r--crates/typst-library/translations/ja.txt (renamed from crates/typst/translations/ja.txt)0
-rw-r--r--crates/typst-library/translations/la.txt (renamed from crates/typst/translations/la.txt)0
-rw-r--r--crates/typst-library/translations/nb.txt (renamed from crates/typst/translations/nb.txt)0
-rw-r--r--crates/typst-library/translations/nl.txt (renamed from crates/typst/translations/nl.txt)0
-rw-r--r--crates/typst-library/translations/nn.txt (renamed from crates/typst/translations/nn.txt)0
-rw-r--r--crates/typst-library/translations/pl.txt (renamed from crates/typst/translations/pl.txt)0
-rw-r--r--crates/typst-library/translations/pt-PT.txt (renamed from crates/typst/translations/pt-PT.txt)0
-rw-r--r--crates/typst-library/translations/pt.txt (renamed from crates/typst/translations/pt.txt)0
-rw-r--r--crates/typst-library/translations/ro.txt (renamed from crates/typst/translations/ro.txt)0
-rw-r--r--crates/typst-library/translations/ru.txt (renamed from crates/typst/translations/ru.txt)0
-rw-r--r--crates/typst-library/translations/sl.txt (renamed from crates/typst/translations/sl.txt)0
-rw-r--r--crates/typst-library/translations/sq.txt (renamed from crates/typst/translations/sq.txt)0
-rw-r--r--crates/typst-library/translations/sr.txt (renamed from crates/typst/translations/sr.txt)0
-rw-r--r--crates/typst-library/translations/sv.txt (renamed from crates/typst/translations/sv.txt)0
-rw-r--r--crates/typst-library/translations/tl.txt (renamed from crates/typst/translations/tl.txt)0
-rw-r--r--crates/typst-library/translations/tr.txt (renamed from crates/typst/translations/tr.txt)0
-rw-r--r--crates/typst-library/translations/ua.txt (renamed from crates/typst/translations/ua.txt)0
-rw-r--r--crates/typst-library/translations/vi.txt (renamed from crates/typst/translations/vi.txt)0
-rw-r--r--crates/typst-library/translations/zh-TW.txt (renamed from crates/typst/translations/zh-TW.txt)0
-rw-r--r--crates/typst-library/translations/zh.txt (renamed from crates/typst/translations/zh.txt)0
-rw-r--r--crates/typst-macros/src/cast.rs2
-rw-r--r--crates/typst-macros/src/elem.rs22
-rw-r--r--crates/typst-macros/src/func.rs1
-rw-r--r--crates/typst-macros/src/symbols.rs12
-rw-r--r--crates/typst-macros/src/time.rs12
-rw-r--r--crates/typst-macros/src/util.rs4
-rw-r--r--crates/typst-pdf/Cargo.toml7
-rw-r--r--crates/typst-pdf/src/catalog.rs10
-rw-r--r--crates/typst-pdf/src/color.rs6
-rw-r--r--crates/typst-pdf/src/color_font.rs13
-rw-r--r--crates/typst-pdf/src/content.rs18
-rw-r--r--crates/typst-pdf/src/extg.rs2
-rw-r--r--crates/typst-pdf/src/font.rs10
-rw-r--r--crates/typst-pdf/src/gradient.rs11
-rw-r--r--crates/typst-pdf/src/image.rs6
-rw-r--r--crates/typst-pdf/src/lib.rs18
-rw-r--r--crates/typst-pdf/src/named_destination.rs10
-rw-r--r--crates/typst-pdf/src/outline.rs6
-rw-r--r--crates/typst-pdf/src/page.rs17
-rw-r--r--crates/typst-pdf/src/pattern.rs11
-rw-r--r--crates/typst-pdf/src/resources.rs10
-rw-r--r--crates/typst-realize/Cargo.toml29
-rw-r--r--crates/typst-realize/src/lib.rs (renamed from crates/typst/src/realize.rs)64
-rw-r--r--crates/typst-render/Cargo.toml4
-rw-r--r--crates/typst-render/src/image.rs4
-rw-r--r--crates/typst-render/src/lib.rs6
-rw-r--r--crates/typst-render/src/paint.rs4
-rw-r--r--crates/typst-render/src/shape.rs4
-rw-r--r--crates/typst-render/src/text.rs8
-rw-r--r--crates/typst-svg/Cargo.toml3
-rw-r--r--crates/typst-svg/src/image.rs4
-rw-r--r--crates/typst-svg/src/lib.rs8
-rw-r--r--crates/typst-svg/src/paint.rs8
-rw-r--r--crates/typst-svg/src/shape.rs4
-rw-r--r--crates/typst-svg/src/text.rs8
-rw-r--r--crates/typst-syntax/Cargo.toml1
-rw-r--r--crates/typst-syntax/src/file.rs8
-rw-r--r--crates/typst-syntax/src/highlight.rs3
-rw-r--r--crates/typst-syntax/src/parser.rs3
-rw-r--r--crates/typst-syntax/src/source.rs2
-rw-r--r--crates/typst-syntax/src/span.rs14
-rw-r--r--crates/typst-timing/Cargo.toml1
-rw-r--r--crates/typst-timing/src/lib.rs41
-rw-r--r--crates/typst-utils/src/lib.rs6
-rw-r--r--crates/typst/Cargo.toml64
-rw-r--r--crates/typst/src/layout/container.rs1046
-rw-r--r--crates/typst/src/layout/repeat.rs107
-rw-r--r--crates/typst/src/lib.rs345
-rw-r--r--crates/typst/src/math/align.rs70
-rw-r--r--crates/typst/src/math/cancel.rs254
-rw-r--r--crates/typst/src/math/class.rs46
-rw-r--r--crates/typst/src/math/ctx.rs469
-rw-r--r--crates/typst/src/math/equation.rs576
-rw-r--r--crates/typst/src/math/lr.rs272
-rw-r--r--crates/typst/src/math/spacing.rs66
-rw-r--r--crates/typst/src/math/style.rs518
-rw-r--r--crates/typst/src/math/underover.rs496
320 files changed, 9599 insertions, 8728 deletions
diff --git a/crates/typst-cli/Cargo.toml b/crates/typst-cli/Cargo.toml
index 1c622d1e..855d7bee 100644
--- a/crates/typst-cli/Cargo.toml
+++ b/crates/typst-cli/Cargo.toml
@@ -19,7 +19,7 @@ doc = false
[dependencies]
typst = { workspace = true }
-typst-assets = { workspace = true, features = ["fonts"] }
+typst-eval = { workspace = true }
typst-kit = { workspace = true }
typst-macros = { workspace = true }
typst-pdf = { workspace = true }
@@ -28,8 +28,8 @@ typst-svg = { workspace = true }
typst-timing = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true }
-color-print = { workspace = true }
codespan-reporting = { workspace = true }
+color-print = { workspace = true }
comemo = { workspace = true }
dirs = { workspace = true }
ecow = { workspace = true }
diff --git a/crates/typst-cli/src/main.rs b/crates/typst-cli/src/main.rs
index b14e687e..464ad624 100644
--- a/crates/typst-cli/src/main.rs
+++ b/crates/typst-cli/src/main.rs
@@ -94,9 +94,10 @@ fn print_error(msg: &str) -> io::Result<()> {
#[cfg(not(feature = "self-update"))]
mod update {
- use crate::args::UpdateCommand;
use typst::diag::{bail, StrResult};
+ use crate::args::UpdateCommand;
+
pub fn update(_: &UpdateCommand) -> StrResult<()> {
bail!(
"self-updating is not enabled for this executable, \
diff --git a/crates/typst-cli/src/query.rs b/crates/typst-cli/src/query.rs
index ef3c7951..90d99a5a 100644
--- a/crates/typst-cli/src/query.rs
+++ b/crates/typst-cli/src/query.rs
@@ -2,11 +2,11 @@ use comemo::Track;
use ecow::{eco_format, EcoString};
use serde::Serialize;
use typst::diag::{bail, HintedStrResult, StrResult, Warned};
-use typst::eval::{eval_string, EvalMode};
use typst::foundations::{Content, IntoValue, LocatableSelector, Scope};
use typst::model::Document;
use typst::syntax::Span;
use typst::World;
+use typst_eval::{eval_string, EvalMode};
use crate::args::{QueryCommand, SerializationFormat};
use crate::compile::print_diagnostics;
@@ -56,6 +56,7 @@ fn retrieve(
document: &Document,
) -> HintedStrResult<Vec<Content>> {
let selector = eval_string(
+ &typst::ROUTINES,
world.track(),
&command.selector,
Span::detached(),
diff --git a/crates/typst-cli/src/timings.rs b/crates/typst-cli/src/timings.rs
index df7665b7..4446bbf9 100644
--- a/crates/typst-cli/src/timings.rs
+++ b/crates/typst-cli/src/timings.rs
@@ -72,7 +72,8 @@ impl Timer {
let writer = BufWriter::with_capacity(1 << 20, file);
typst_timing::export_json(writer, |span| {
- resolve_span(world, span).unwrap_or_else(|| ("unknown".to_string(), 0))
+ resolve_span(world, Span::from_raw(span))
+ .unwrap_or_else(|| ("unknown".to_string(), 0))
})?;
Ok(output)
diff --git a/crates/typst-cli/src/world.rs b/crates/typst-cli/src/world.rs
index bb491c02..b790f41a 100644
--- a/crates/typst-cli/src/world.rs
+++ b/crates/typst-cli/src/world.rs
@@ -16,7 +16,7 @@ use typst::utils::LazyHash;
use typst::{Library, World};
use typst_kit::fonts::{FontSlot, Fonts};
use typst_kit::package::PackageStorage;
-use typst_timing::{timed, TimingScope};
+use typst_timing::timed;
use crate::args::{Input, SharedArgs};
use crate::compile::ExportCache;
@@ -285,8 +285,6 @@ impl FileSlot {
self.source.get_or_init(
|| read(self.id, project_root, package_storage),
|data, prev| {
- let name = if prev.is_some() { "reparsing file" } else { "parsing file" };
- let _scope = TimingScope::new(name, None);
let text = decode_utf8(&data)?;
if let Some(mut prev) = prev {
prev.replace(text);
diff --git a/crates/typst-eval/Cargo.toml b/crates/typst-eval/Cargo.toml
new file mode 100644
index 00000000..12a6a6a4
--- /dev/null
+++ b/crates/typst-eval/Cargo.toml
@@ -0,0 +1,32 @@
+[package]
+name = "typst-eval"
+description = "Typst's code interpreter."
+version = { workspace = true }
+rust-version = { workspace = true }
+authors = { workspace = true }
+edition = { workspace = true }
+homepage = { workspace = true }
+repository = { workspace = true }
+license = { workspace = true }
+categories = { workspace = true }
+keywords = { workspace = true }
+readme = { workspace = true }
+
+[dependencies]
+typst-library = { workspace = true }
+typst-macros = { workspace = true }
+typst-syntax = { workspace = true }
+typst-timing = { workspace = true }
+typst-utils = { workspace = true }
+comemo = { workspace = true }
+ecow = { workspace = true }
+if_chain = { workspace = true }
+indexmap = { workspace = true }
+toml = { workspace = true }
+unicode-segmentation = { workspace = true }
+
+[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
+stacker = { workspace = true }
+
+[lints]
+workspace = true
diff --git a/crates/typst/src/eval/access.rs b/crates/typst-eval/src/access.rs
index ab0a6491..9bcac4d6 100644
--- a/crates/typst/src/eval/access.rs
+++ b/crates/typst-eval/src/access.rs
@@ -1,9 +1,9 @@
use ecow::eco_format;
+use typst_library::diag::{bail, At, Hint, SourceResult, Trace, Tracepoint};
+use typst_library::foundations::{Dict, Value};
+use typst_syntax::ast::{self, AstNode};
-use crate::diag::{bail, At, Hint, SourceResult, Trace, Tracepoint};
-use crate::eval::{Eval, Vm};
-use crate::foundations::{call_method_access, is_accessor_method, Dict, Value};
-use crate::syntax::ast::{self, AstNode};
+use crate::{call_method_access, is_accessor_method, Eval, Vm};
/// Access an expression mutably.
pub(crate) trait Access {
@@ -85,7 +85,7 @@ pub(crate) fn access_dict<'a>(
Value::Symbol(_) | Value::Content(_) | Value::Module(_) | Value::Func(_)
) {
bail!(span, "cannot mutate fields on {ty}");
- } else if crate::foundations::fields_on(ty).is_empty() {
+ } else if typst_library::foundations::fields_on(ty).is_empty() {
bail!(span, "{ty} does not have accessible fields");
} else {
// type supports static fields, which don't yet have
diff --git a/crates/typst/src/eval/binding.rs b/crates/typst-eval/src/binding.rs
index 18468b9e..f3802f07 100644
--- a/crates/typst/src/eval/binding.rs
+++ b/crates/typst-eval/src/binding.rs
@@ -1,11 +1,11 @@
use std::collections::HashSet;
use ecow::eco_format;
+use typst_library::diag::{bail, error, At, SourceDiagnostic, SourceResult};
+use typst_library::foundations::{Array, Dict, Value};
+use typst_syntax::ast::{self, AstNode};
-use crate::diag::{bail, error, At, SourceDiagnostic, SourceResult};
-use crate::eval::{Access, Eval, Vm};
-use crate::foundations::{Array, Dict, Value};
-use crate::syntax::ast::{self, AstNode};
+use crate::{Access, Eval, Vm};
impl Eval for ast::LetBinding<'_> {
type Output = Value;
diff --git a/crates/typst/src/eval/call.rs b/crates/typst-eval/src/call.rs
index 4331e187..9dfb7693 100644
--- a/crates/typst/src/eval/call.rs
+++ b/crates/typst-eval/src/call.rs
@@ -1,23 +1,24 @@
use comemo::{Tracked, TrackedMut};
use ecow::{eco_format, EcoString, EcoVec};
-
-use crate::diag::{
+use typst_library::diag::{
bail, error, At, HintedStrResult, HintedString, SourceDiagnostic, SourceResult,
Trace, Tracepoint,
};
-use crate::engine::{Engine, Sink, Traced};
-use crate::eval::{Access, Eval, FlowEvent, Route, Vm};
-use crate::foundations::{
- call_method_mut, is_mutating_method, Arg, Args, Bytes, Capturer, Closure, Content,
- Context, Func, IntoValue, NativeElement, Scope, Scopes, Value,
+use typst_library::engine::{Engine, Sink, Traced};
+use typst_library::foundations::{
+ Arg, Args, Bytes, Capturer, Closure, Content, Context, Func, IntoValue,
+ NativeElement, Scope, Scopes, Value,
};
-use crate::introspection::Introspector;
-use crate::math::LrElem;
-use crate::syntax::ast::{self, AstNode, Ident};
-use crate::syntax::{Span, Spanned, SyntaxNode};
-use crate::text::TextElem;
-use crate::utils::LazyHash;
-use crate::World;
+use typst_library::introspection::Introspector;
+use typst_library::math::LrElem;
+use typst_library::routines::Routines;
+use typst_library::text::TextElem;
+use typst_library::World;
+use typst_syntax::ast::{self, AstNode, Ident};
+use typst_syntax::{Span, Spanned, SyntaxNode};
+use typst_utils::LazyHash;
+
+use crate::{call_method_mut, is_mutating_method, Access, Eval, FlowEvent, Route, Vm};
impl Eval for ast::FuncCall<'_> {
type Output = Value;
@@ -159,9 +160,10 @@ impl Eval for ast::Closure<'_> {
/// Call the function in the context with the arguments.
#[comemo::memoize]
#[allow(clippy::too_many_arguments)]
-pub(crate) fn call_closure(
+pub fn eval_closure(
func: &Func,
closure: &LazyHash<Closure>,
+ routines: &Routines,
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
@@ -182,6 +184,7 @@ pub(crate) fn call_closure(
// Prepare the engine.
let engine = Engine {
+ routines,
world,
introspector,
traced,
@@ -210,7 +213,7 @@ pub(crate) fn call_closure(
vm.define(ident, args.expect::<Value>(&ident)?)
}
pattern => {
- crate::eval::destructure(
+ crate::destructure(
&mut vm,
pattern,
args.expect::<Value>("pattern parameter")?,
@@ -583,8 +586,9 @@ impl<'a> CapturesVisitor<'a> {
#[cfg(test)]
mod tests {
+ use typst_syntax::parse;
+
use super::*;
- use crate::syntax::parse;
#[track_caller]
fn test(text: &str, result: &[&str]) {
diff --git a/crates/typst/src/eval/code.rs b/crates/typst-eval/src/code.rs
index 7cfb7f59..918d9d2a 100644
--- a/crates/typst/src/eval/code.rs
+++ b/crates/typst-eval/src/code.rs
@@ -1,11 +1,12 @@
use ecow::{eco_vec, EcoVec};
-
-use crate::diag::{bail, error, At, SourceResult};
-use crate::eval::{ops, CapturesVisitor, Eval, Vm};
-use crate::foundations::{
- Array, Capturer, Closure, Content, ContextElem, Dict, Func, NativeElement, Str, Value,
+use typst_library::diag::{bail, error, At, SourceResult};
+use typst_library::foundations::{
+ ops, Array, Capturer, Closure, Content, ContextElem, Dict, Func, NativeElement, Str,
+ Value,
};
-use crate::syntax::ast::{self, AstNode};
+use typst_syntax::ast::{self, AstNode};
+
+use crate::{CapturesVisitor, Eval, Vm};
impl Eval for ast::Code<'_> {
type Output = Value;
diff --git a/crates/typst/src/eval/flow.rs b/crates/typst-eval/src/flow.rs
index a68be95b..5c9d2a00 100644
--- a/crates/typst/src/eval/flow.rs
+++ b/crates/typst-eval/src/flow.rs
@@ -1,10 +1,10 @@
+use typst_library::diag::{bail, error, At, SourceDiagnostic, SourceResult};
+use typst_library::foundations::{ops, IntoValue, Value};
+use typst_syntax::ast::{self, AstNode};
+use typst_syntax::{Span, SyntaxKind, SyntaxNode};
use unicode_segmentation::UnicodeSegmentation;
-use crate::diag::{bail, error, At, SourceDiagnostic, SourceResult};
-use crate::eval::{destructure, ops, Eval, Vm};
-use crate::foundations::{IntoValue, Value};
-use crate::syntax::ast::{self, AstNode};
-use crate::syntax::{Span, SyntaxKind, SyntaxNode};
+use crate::{destructure, Eval, Vm};
/// The maximum number of loop iterations.
const MAX_ITERATIONS: usize = 10_000;
diff --git a/crates/typst/src/eval/import.rs b/crates/typst-eval/src/import.rs
index d02938cc..316fbf87 100644
--- a/crates/typst/src/eval/import.rs
+++ b/crates/typst-eval/src/import.rs
@@ -1,13 +1,15 @@
use comemo::TrackedMut;
use ecow::{eco_format, eco_vec, EcoString};
+use typst_library::diag::{
+ bail, error, warning, At, FileError, SourceResult, Trace, Tracepoint,
+};
+use typst_library::foundations::{Content, Module, Value};
+use typst_library::World;
+use typst_syntax::ast::{self, AstNode};
+use typst_syntax::package::{PackageManifest, PackageSpec};
+use typst_syntax::{FileId, Span, VirtualPath};
-use crate::diag::{bail, error, warning, At, FileError, SourceResult, Trace, Tracepoint};
-use crate::eval::{eval, Eval, Vm};
-use crate::foundations::{Content, Module, Value};
-use crate::syntax::ast::{self, AstNode};
-use crate::syntax::package::{PackageManifest, PackageSpec};
-use crate::syntax::{FileId, Span, VirtualPath};
-use crate::World;
+use crate::{eval, Eval, Vm};
impl Eval for ast::ModuleImport<'_> {
type Output = Value;
@@ -171,8 +173,9 @@ pub fn import(
/// Import an external package.
fn import_package(vm: &mut Vm, spec: PackageSpec, span: Span) -> SourceResult<Module> {
// Evaluate the manifest.
+ let world = vm.world();
let manifest_id = FileId::new(Some(spec.clone()), VirtualPath::new("typst.toml"));
- let bytes = vm.world().file(manifest_id).at(span)?;
+ let bytes = world.file(manifest_id).at(span)?;
let string = std::str::from_utf8(&bytes).map_err(FileError::from).at(span)?;
let manifest: PackageManifest = toml::from_str(string)
.map_err(|err| eco_format!("package manifest is malformed ({})", err.message()))
@@ -181,7 +184,7 @@ fn import_package(vm: &mut Vm, spec: PackageSpec, span: Span) -> SourceResult<Mo
// Evaluate the entry point.
let entrypoint_id = manifest_id.join(&manifest.package.entrypoint);
- let source = vm.world().source(entrypoint_id).at(span)?;
+ let source = world.source(entrypoint_id).at(span)?;
// Prevent cyclic importing.
if vm.engine.route.contains(source.id()) {
@@ -190,13 +193,14 @@ fn import_package(vm: &mut Vm, spec: PackageSpec, span: Span) -> SourceResult<Mo
let point = || Tracepoint::Import;
Ok(eval(
- vm.world(),
+ vm.engine.routines,
+ vm.engine.world,
vm.engine.traced,
TrackedMut::reborrow_mut(&mut vm.engine.sink),
vm.engine.route.track(),
&source,
)
- .trace(vm.world(), point, span)?
+ .trace(world, point, span)?
.with_name(manifest.package.name))
}
@@ -215,7 +219,8 @@ fn import_file(vm: &mut Vm, path: &str, span: Span) -> SourceResult<Module> {
// Evaluate the file.
let point = || Tracepoint::Import;
eval(
- world,
+ vm.engine.routines,
+ vm.engine.world,
vm.engine.traced,
TrackedMut::reborrow_mut(&mut vm.engine.sink),
vm.engine.route.track(),
diff --git a/crates/typst/src/eval/mod.rs b/crates/typst-eval/src/lib.rs
index dc8a1802..a5c0c7e3 100644
--- a/crates/typst/src/eval/mod.rs
+++ b/crates/typst-eval/src/lib.rs
@@ -1,4 +1,4 @@
-//! Evaluation of markup and code.
+//! Typst's code interpreter.
pub(crate) mod ops;
@@ -10,31 +10,35 @@ mod flow;
mod import;
mod markup;
mod math;
+mod methods;
mod rules;
mod vm;
pub use self::call::*;
pub use self::import::*;
pub use self::vm::*;
+pub use typst_library::routines::EvalMode;
-pub(crate) use self::access::*;
-pub(crate) use self::binding::*;
-pub(crate) use self::flow::*;
+use self::access::*;
+use self::binding::*;
+use self::flow::*;
+use self::methods::*;
use comemo::{Track, Tracked, TrackedMut};
-
-use crate::diag::{bail, SourceResult};
-use crate::engine::{Engine, Route, Sink, Traced};
-use crate::foundations::{Cast, Context, Module, NativeElement, Scope, Scopes, Value};
-use crate::introspection::Introspector;
-use crate::math::EquationElem;
-use crate::syntax::{ast, parse, parse_code, parse_math, Source, Span};
-use crate::World;
+use typst_library::diag::{bail, SourceResult};
+use typst_library::engine::{Engine, Route, Sink, Traced};
+use typst_library::foundations::{Context, Module, NativeElement, Scope, Scopes, Value};
+use typst_library::introspection::Introspector;
+use typst_library::math::EquationElem;
+use typst_library::routines::Routines;
+use typst_library::World;
+use typst_syntax::{ast, parse, parse_code, parse_math, Source, Span};
/// Evaluate a source file and return the resulting module.
#[comemo::memoize]
#[typst_macros::time(name = "eval", span = source.root().span())]
pub fn eval(
+ routines: &Routines,
world: Tracked<dyn World + '_>,
traced: Tracked<Traced>,
sink: TrackedMut<Sink>,
@@ -50,6 +54,7 @@ pub fn eval(
// Prepare the engine.
let introspector = Introspector::default();
let engine = Engine {
+ routines,
world,
introspector: introspector.track(),
traced,
@@ -94,6 +99,7 @@ pub fn eval(
/// Everything in the output is associated with the given `span`.
#[comemo::memoize]
pub fn eval_string(
+ routines: &Routines,
world: Tracked<dyn World + '_>,
string: &str,
span: Span,
@@ -119,6 +125,7 @@ pub fn eval_string(
let introspector = Introspector::default();
let traced = Traced::default();
let engine = Engine {
+ routines,
world,
introspector: introspector.track(),
traced: traced.track(),
@@ -153,17 +160,6 @@ pub fn eval_string(
Ok(output)
}
-/// In which mode to evaluate a string.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
-pub enum EvalMode {
- /// Evaluate as code, as after a hash.
- Code,
- /// Evaluate as markup, like in a Typst file.
- Markup,
- /// Evaluate as math, as in an equation.
- Math,
-}
-
/// Evaluate an expression.
pub trait Eval {
/// The output of evaluating the expression.
diff --git a/crates/typst/src/eval/markup.rs b/crates/typst-eval/src/markup.rs
index 42fede1c..e28eb9dd 100644
--- a/crates/typst/src/eval/markup.rs
+++ b/crates/typst-eval/src/markup.rs
@@ -1,18 +1,18 @@
-use crate::diag::{warning, At, SourceResult};
-use crate::eval::{Eval, Vm};
-use crate::foundations::{
- Content, Label, NativeElement, Repr, Smart, Unlabellable, Value,
+use typst_library::diag::{warning, At, SourceResult};
+use typst_library::foundations::{
+ Content, Label, NativeElement, Repr, Smart, Symbol, Unlabellable, Value,
};
-use crate::math::EquationElem;
-use crate::model::{
+use typst_library::math::EquationElem;
+use typst_library::model::{
EmphElem, EnumItem, HeadingElem, LinkElem, ListItem, ParbreakElem, RefElem,
StrongElem, Supplement, TermItem, Url,
};
-use crate::symbols::Symbol;
-use crate::syntax::ast::{self, AstNode};
-use crate::text::{
+use typst_library::text::{
LinebreakElem, RawContent, RawElem, SmartQuoteElem, SpaceElem, TextElem,
};
+use typst_syntax::ast::{self, AstNode};
+
+use crate::{Eval, Vm};
impl Eval for ast::Markup<'_> {
type Output = Content;
diff --git a/crates/typst/src/eval/math.rs b/crates/typst-eval/src/math.rs
index d1a667a5..c61a3251 100644
--- a/crates/typst/src/eval/math.rs
+++ b/crates/typst-eval/src/math.rs
@@ -1,12 +1,13 @@
use ecow::eco_format;
-
-use crate::diag::{At, SourceResult};
-use crate::eval::{Eval, Vm};
-use crate::foundations::{Content, NativeElement, Value};
-use crate::math::{AlignPointElem, AttachElem, FracElem, LrElem, PrimesElem, RootElem};
-use crate::symbols::Symbol;
-use crate::syntax::ast::{self, AstNode};
-use crate::text::TextElem;
+use typst_library::diag::{At, SourceResult};
+use typst_library::foundations::{Content, NativeElement, Symbol, Value};
+use typst_library::math::{
+ AlignPointElem, AttachElem, FracElem, LrElem, PrimesElem, RootElem,
+};
+use typst_library::text::TextElem;
+use typst_syntax::ast::{self, AstNode};
+
+use crate::{Eval, Vm};
impl Eval for ast::Math<'_> {
type Output = Content;
diff --git a/crates/typst/src/foundations/methods.rs b/crates/typst-eval/src/methods.rs
index 945b7c50..7cb36a00 100644
--- a/crates/typst/src/foundations/methods.rs
+++ b/crates/typst-eval/src/methods.rs
@@ -1,8 +1,8 @@
//! Handles special built-in methods on values.
-use crate::diag::{At, SourceResult};
-use crate::foundations::{Args, Str, Type, Value};
-use crate::syntax::Span;
+use typst_library::diag::{At, SourceResult};
+use typst_library::foundations::{Args, Str, Type, Value};
+use typst_syntax::Span;
/// Whether a specific method is mutating.
pub(crate) fn is_mutating_method(method: &str) -> bool {
diff --git a/crates/typst-eval/src/ops.rs b/crates/typst-eval/src/ops.rs
new file mode 100644
index 00000000..ebbd6743
--- /dev/null
+++ b/crates/typst-eval/src/ops.rs
@@ -0,0 +1,91 @@
+use typst_library::diag::{At, HintedStrResult, SourceResult};
+use typst_library::foundations::{ops, IntoValue, Value};
+use typst_syntax::ast::{self, AstNode};
+
+use crate::{access_dict, Access, Eval, Vm};
+
+impl Eval for ast::Unary<'_> {
+ type Output = Value;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let value = self.expr().eval(vm)?;
+ let result = match self.op() {
+ ast::UnOp::Pos => ops::pos(value),
+ ast::UnOp::Neg => ops::neg(value),
+ ast::UnOp::Not => ops::not(value),
+ };
+ result.at(self.span())
+ }
+}
+
+impl Eval for ast::Binary<'_> {
+ type Output = Value;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ match self.op() {
+ ast::BinOp::Add => apply_binary(self, vm, ops::add),
+ ast::BinOp::Sub => apply_binary(self, vm, ops::sub),
+ ast::BinOp::Mul => apply_binary(self, vm, ops::mul),
+ ast::BinOp::Div => apply_binary(self, vm, ops::div),
+ ast::BinOp::And => apply_binary(self, vm, ops::and),
+ ast::BinOp::Or => apply_binary(self, vm, ops::or),
+ ast::BinOp::Eq => apply_binary(self, vm, ops::eq),
+ ast::BinOp::Neq => apply_binary(self, vm, ops::neq),
+ ast::BinOp::Lt => apply_binary(self, vm, ops::lt),
+ ast::BinOp::Leq => apply_binary(self, vm, ops::leq),
+ ast::BinOp::Gt => apply_binary(self, vm, ops::gt),
+ ast::BinOp::Geq => apply_binary(self, vm, ops::geq),
+ ast::BinOp::In => apply_binary(self, vm, ops::in_),
+ ast::BinOp::NotIn => apply_binary(self, vm, ops::not_in),
+ ast::BinOp::Assign => apply_assignment(self, vm, |_, b| Ok(b)),
+ ast::BinOp::AddAssign => apply_assignment(self, vm, ops::add),
+ ast::BinOp::SubAssign => apply_assignment(self, vm, ops::sub),
+ ast::BinOp::MulAssign => apply_assignment(self, vm, ops::mul),
+ ast::BinOp::DivAssign => apply_assignment(self, vm, ops::div),
+ }
+ }
+}
+
+/// Apply a basic binary operation.
+fn apply_binary(
+ binary: ast::Binary,
+ vm: &mut Vm,
+ op: fn(Value, Value) -> HintedStrResult<Value>,
+) -> SourceResult<Value> {
+ let lhs = binary.lhs().eval(vm)?;
+
+ // Short-circuit boolean operations.
+ if (binary.op() == ast::BinOp::And && lhs == false.into_value())
+ || (binary.op() == ast::BinOp::Or && lhs == true.into_value())
+ {
+ return Ok(lhs);
+ }
+
+ let rhs = binary.rhs().eval(vm)?;
+ op(lhs, rhs).at(binary.span())
+}
+
+/// Apply an assignment operation.
+fn apply_assignment(
+ binary: ast::Binary,
+ vm: &mut Vm,
+ op: fn(Value, Value) -> HintedStrResult<Value>,
+) -> SourceResult<Value> {
+ let rhs = binary.rhs().eval(vm)?;
+ let lhs = binary.lhs();
+
+ // An assignment to a dictionary field is different from a normal access
+ // since it can create the field instead of just modifying it.
+ if binary.op() == ast::BinOp::Assign {
+ if let ast::Expr::FieldAccess(access) = lhs {
+ let dict = access_dict(vm, access)?;
+ dict.insert(access.field().get().clone().into(), rhs);
+ return Ok(Value::None);
+ }
+ }
+
+ let location = binary.lhs().access(vm)?;
+ let lhs = std::mem::take(&mut *location);
+ *location = op(lhs, rhs).at(binary.span())?;
+ Ok(Value::None)
+}
diff --git a/crates/typst/src/eval/rules.rs b/crates/typst-eval/src/rules.rs
index 1748bbd7..646354d4 100644
--- a/crates/typst/src/eval/rules.rs
+++ b/crates/typst-eval/src/rules.rs
@@ -1,11 +1,12 @@
-use crate::diag::{warning, At, SourceResult};
-use crate::eval::{Eval, Vm};
-use crate::foundations::{
+use typst_library::diag::{warning, At, SourceResult};
+use typst_library::foundations::{
Element, Fields, Func, Recipe, Selector, ShowableSelector, Styles, Transformation,
};
-use crate::layout::BlockElem;
-use crate::model::ParElem;
-use crate::syntax::ast::{self, AstNode};
+use typst_library::layout::BlockElem;
+use typst_library::model::ParElem;
+use typst_syntax::ast::{self, AstNode};
+
+use crate::{Eval, Vm};
impl Eval for ast::SetRule<'_> {
type Output = Styles;
diff --git a/crates/typst/src/eval/vm.rs b/crates/typst-eval/src/vm.rs
index 4d346870..1c8331b6 100644
--- a/crates/typst/src/eval/vm.rs
+++ b/crates/typst-eval/src/vm.rs
@@ -1,15 +1,15 @@
use comemo::Tracked;
+use typst_library::engine::Engine;
+use typst_library::foundations::{Context, IntoValue, Scopes, Value};
+use typst_library::World;
+use typst_syntax::ast::{self, AstNode};
+use typst_syntax::Span;
-use crate::engine::Engine;
-use crate::eval::FlowEvent;
-use crate::foundations::{Context, IntoValue, Scopes, Value};
-use crate::syntax::ast::{self, AstNode};
-use crate::syntax::Span;
-use crate::World;
+use crate::FlowEvent;
/// A virtual machine.
///
-/// Holds the state needed to [evaluate](crate::eval::eval()) Typst sources. A
+/// Holds the state needed to [evaluate](crate::eval()) Typst sources. A
/// new virtual machine is created for each module evaluation and function call.
pub struct Vm<'a> {
/// The underlying virtual typesetter.
diff --git a/crates/typst-ide/Cargo.toml b/crates/typst-ide/Cargo.toml
index 4e87f99b..3c98e9b8 100644
--- a/crates/typst-ide/Cargo.toml
+++ b/crates/typst-ide/Cargo.toml
@@ -14,6 +14,7 @@ readme = { workspace = true }
[dependencies]
typst = { workspace = true }
+typst-eval = { workspace = true }
comemo = { workspace = true }
ecow = { workspace = true }
if_chain = { workspace = true }
diff --git a/crates/typst-ide/src/analyze.rs b/crates/typst-ide/src/analyze.rs
index c3779556..75ffaede 100644
--- a/crates/typst-ide/src/analyze.rs
+++ b/crates/typst-ide/src/analyze.rs
@@ -1,12 +1,12 @@
use comemo::Track;
use ecow::{eco_vec, EcoString, EcoVec};
use typst::engine::{Engine, Route, Sink, Traced};
-use typst::eval::Vm;
use typst::foundations::{Context, Label, Scopes, Styles, Value};
use typst::introspection::Introspector;
use typst::model::{BibliographyElem, Document};
use typst::syntax::{ast, LinkedNode, Span, SyntaxKind};
use typst::World;
+use typst_eval::Vm;
/// Try to determine a set of possible values for an expression.
pub fn analyze_expr(
@@ -58,6 +58,7 @@ pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option<Value> {
let traced = Traced::default();
let mut sink = Sink::new();
let engine = Engine {
+ routines: &typst::ROUTINES,
world: world.track(),
introspector: introspector.track(),
traced: traced.track(),
@@ -73,7 +74,7 @@ pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option<Value> {
Span::detached(),
);
- typst::eval::import(&mut vm, source, source_span, true)
+ typst_eval::import(&mut vm, source, source_span, true)
.ok()
.map(Value::Module)
}
diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs
index d534f55c..cde4d1e3 100644
--- a/crates/typst-ide/src/complete.rs
+++ b/crates/typst-ide/src/complete.rs
@@ -1353,7 +1353,6 @@ impl<'a> CompletionContext<'a> {
#[cfg(test)]
mod tests {
-
use super::autocomplete;
use crate::tests::TestWorld;
diff --git a/crates/typst-ide/src/lib.rs b/crates/typst-ide/src/lib.rs
index 63ba6f75..f09e6ac5 100644
--- a/crates/typst-ide/src/lib.rs
+++ b/crates/typst-ide/src/lib.rs
@@ -191,7 +191,7 @@ mod tests {
// 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();
+ let mut lib = typst::Library::default();
lib.styles
.set(PageElem::set_width(Smart::Custom(Abs::pt(120.0).into())));
lib.styles.set(PageElem::set_height(Smart::Auto));
diff --git a/crates/typst-ide/src/tooltip.rs b/crates/typst-ide/src/tooltip.rs
index 532cda39..df93d1dc 100644
--- a/crates/typst-ide/src/tooltip.rs
+++ b/crates/typst-ide/src/tooltip.rs
@@ -3,13 +3,13 @@ use std::fmt::Write;
use ecow::{eco_format, EcoString};
use if_chain::if_chain;
use typst::engine::Sink;
-use typst::eval::CapturesVisitor;
use typst::foundations::{repr, Capturer, CastInfo, Repr, Value};
use typst::layout::Length;
use typst::model::Document;
use typst::syntax::{ast, LinkedNode, Side, Source, SyntaxKind};
use typst::utils::{round_with_precision, Numeric};
use typst::World;
+use typst_eval::CapturesVisitor;
use crate::{analyze_expr, analyze_labels, plain_docs_sentence, summarize_font_family};
diff --git a/crates/typst-kit/Cargo.toml b/crates/typst-kit/Cargo.toml
index 13d0a34b..266eba0b 100644
--- a/crates/typst-kit/Cargo.toml
+++ b/crates/typst-kit/Cargo.toml
@@ -11,13 +11,14 @@ license = { workspace = true }
readme = { workspace = true }
[dependencies]
-typst = { workspace = true }
typst-assets = { workspace = true, optional = true }
+typst-library = { workspace = true }
+typst-syntax = { workspace = true }
typst-timing = { workspace = true }
typst-utils = { workspace = true }
+dirs = { workspace = true, optional = true }
ecow = { workspace = true }
env_proxy = { workspace = true, optional = true }
-dirs = { workspace = true, optional = true }
flate2 = { workspace = true, optional = true }
fontdb = { workspace = true, optional = true }
native-tls = { workspace = true, optional = true }
diff --git a/crates/typst-kit/src/fonts.rs b/crates/typst-kit/src/fonts.rs
index 8c8981a1..83e13fd8 100644
--- a/crates/typst-kit/src/fonts.rs
+++ b/crates/typst-kit/src/fonts.rs
@@ -8,12 +8,12 @@
//! - For math: New Computer Modern Math
//! - For code: Deja Vu Sans Mono
-use std::path::PathBuf;
+use std::fs;
+use std::path::{Path, PathBuf};
use std::sync::OnceLock;
-use std::{fs, path::Path};
use fontdb::{Database, Source};
-use typst::text::{Font, FontBook, FontInfo};
+use typst_library::text::{Font, FontBook, FontInfo};
use typst_timing::TimingScope;
/// Holds details about the location of a font and lazily the font itself.
@@ -46,7 +46,7 @@ impl FontSlot {
pub fn get(&self) -> Option<Font> {
self.font
.get_or_init(|| {
- let _scope = TimingScope::new("load font", None);
+ let _scope = TimingScope::new("load font");
let data = fs::read(
self.path
.as_ref()
@@ -196,7 +196,7 @@ impl FontSearcher {
#[cfg(feature = "embed-fonts")]
fn add_embedded(&mut self) {
for data in typst_assets::fonts() {
- let buffer = typst::foundations::Bytes::from_static(data);
+ let buffer = typst_library::foundations::Bytes::from_static(data);
for (i, font) in Font::iter(buffer).enumerate() {
self.book.push(font.info().clone());
self.fonts.push(FontSlot {
diff --git a/crates/typst-kit/src/package.rs b/crates/typst-kit/src/package.rs
index 83978010..412c7982 100644
--- a/crates/typst-kit/src/package.rs
+++ b/crates/typst-kit/src/package.rs
@@ -5,8 +5,8 @@ use std::path::{Path, PathBuf};
use ecow::eco_format;
use once_cell::sync::OnceCell;
-use typst::diag::{bail, PackageError, PackageResult, StrResult};
-use typst::syntax::package::{
+use typst_library::diag::{bail, PackageError, PackageResult, StrResult};
+use typst_syntax::package::{
PackageInfo, PackageSpec, PackageVersion, VersionlessPackageSpec,
};
diff --git a/crates/typst-layout/Cargo.toml b/crates/typst-layout/Cargo.toml
new file mode 100644
index 00000000..4c133a4e
--- /dev/null
+++ b/crates/typst-layout/Cargo.toml
@@ -0,0 +1,43 @@
+[package]
+name = "typst-layout"
+description = "Typst's layout engine."
+version = { workspace = true }
+rust-version = { workspace = true }
+authors = { workspace = true }
+edition = { workspace = true }
+homepage = { workspace = true }
+repository = { workspace = true }
+license = { workspace = true }
+categories = { workspace = true }
+keywords = { workspace = true }
+readme = { workspace = true }
+
+[dependencies]
+typst-assets = { workspace = true }
+typst-library = { workspace = true }
+typst-macros = { workspace = true }
+typst-syntax = { workspace = true }
+typst-timing = { workspace = true }
+typst-utils = { workspace = true }
+az = { workspace = true }
+bumpalo = { workspace = true }
+comemo = { workspace = true }
+ecow = { workspace = true }
+hypher = { workspace = true }
+icu_properties = { workspace = true }
+icu_provider = { workspace = true }
+icu_provider_adapters = { workspace = true }
+icu_provider_blob = { workspace = true }
+icu_segmenter = { workspace = true }
+kurbo = { workspace = true }
+once_cell = { workspace = true }
+rustybuzz = { workspace = true }
+smallvec = { workspace = true }
+ttf-parser = { workspace = true }
+unicode-bidi = { workspace = true }
+unicode-math-class = { workspace = true }
+unicode-script = { workspace = true }
+unicode-segmentation = { workspace = true }
+
+[lints]
+workspace = true
diff --git a/crates/typst-layout/src/flow/block.rs b/crates/typst-layout/src/flow/block.rs
new file mode 100644
index 00000000..1dd98812
--- /dev/null
+++ b/crates/typst-layout/src/flow/block.rs
@@ -0,0 +1,401 @@
+use once_cell::unsync::Lazy;
+use smallvec::SmallVec;
+use typst_library::diag::SourceResult;
+use typst_library::engine::Engine;
+use typst_library::foundations::{Packed, Resolve, StyleChain};
+use typst_library::introspection::Locator;
+use typst_library::layout::{
+ Abs, Axes, BlockBody, BlockElem, Fragment, Frame, FrameKind, Region, Regions, Rel,
+ Sides, Size, Sizing,
+};
+use typst_library::visualize::Stroke;
+use typst_utils::Numeric;
+
+use crate::shapes::{clip_rect, fill_and_stroke};
+
+/// Lay this out as an unbreakable block.
+#[typst_macros::time(name = "block", span = elem.span())]
+pub fn layout_single_block(
+ elem: &Packed<BlockElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ // Fetch sizing properties.
+ let width = elem.width(styles);
+ let height = elem.height(styles);
+ let inset = elem.inset(styles).unwrap_or_default();
+
+ // Build the pod regions.
+ let pod = unbreakable_pod(&width.into(), &height, &inset, styles, region.size);
+
+ // Layout the body.
+ let body = elem.body(styles);
+ let mut frame = match body {
+ // If we have no body, just create one frame. Its size will be
+ // adjusted below.
+ None => Frame::hard(Size::zero()),
+
+ // If we have content as our body, just layout it.
+ Some(BlockBody::Content(body)) => {
+ crate::layout_frame(engine, body, locator.relayout(), styles, pod)?
+ }
+
+ // If we have a child that wants to layout with just access to the
+ // base region, give it that.
+ Some(BlockBody::SingleLayouter(callback)) => {
+ callback.call(engine, locator, styles, pod)?
+ }
+
+ // If we have a child that wants to layout with full region access,
+ // we layout it.
+ Some(BlockBody::MultiLayouter(callback)) => {
+ let expand = (pod.expand | region.expand) & pod.size.map(Abs::is_finite);
+ let pod = Region { expand, ..pod };
+ callback.call(engine, locator, styles, pod.into())?.into_frame()
+ }
+ };
+
+ // Explicit blocks are boundaries for gradient relativeness.
+ if matches!(body, None | Some(BlockBody::Content(_))) {
+ frame.set_kind(FrameKind::Hard);
+ }
+
+ // Enforce a correct frame size on the expanded axes. Do this before
+ // applying the inset, since the pod shrunk.
+ frame.set_size(pod.expand.select(pod.size, frame.size()));
+
+ // Apply the inset.
+ if !inset.is_zero() {
+ crate::pad::grow(&mut frame, &inset);
+ }
+
+ // Prepare fill and stroke.
+ let fill = elem.fill(styles);
+ let stroke = elem
+ .stroke(styles)
+ .unwrap_or_default()
+ .map(|s| s.map(Stroke::unwrap_or_default));
+
+ // Only fetch these if necessary (for clipping or filling/stroking).
+ let outset = Lazy::new(|| elem.outset(styles).unwrap_or_default());
+ let radius = Lazy::new(|| elem.radius(styles).unwrap_or_default());
+
+ // Clip the contents, if requested.
+ if elem.clip(styles) {
+ let size = frame.size() + outset.relative_to(frame.size()).sum_by_axis();
+ frame.clip(clip_rect(size, &radius, &stroke));
+ }
+
+ // Add fill and/or stroke.
+ if fill.is_some() || stroke.iter().any(Option::is_some) {
+ fill_and_stroke(&mut frame, fill, &stroke, &outset, &radius, elem.span());
+ }
+
+ // Assign label to each frame in the fragment.
+ if let Some(label) = elem.label() {
+ frame.label(label);
+ }
+
+ Ok(frame)
+}
+
+/// Lay this out as a breakable block.
+#[typst_macros::time(name = "block", span = elem.span())]
+pub fn layout_multi_block(
+ elem: &Packed<BlockElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+) -> SourceResult<Fragment> {
+ // Fetch sizing properties.
+ let width = elem.width(styles);
+ let height = elem.height(styles);
+ let inset = elem.inset(styles).unwrap_or_default();
+
+ // Allocate a small vector for backlogs.
+ let mut buf = SmallVec::<[Abs; 2]>::new();
+
+ // Build the pod regions.
+ let pod = breakable_pod(&width.into(), &height, &inset, styles, regions, &mut buf);
+
+ // Layout the body.
+ let body = elem.body(styles);
+ let mut fragment = match body {
+ // If we have no body, just create one frame plus one per backlog
+ // region. We create them zero-sized; if necessary, their size will
+ // be adjusted below.
+ None => {
+ let mut frames = vec![];
+ frames.push(Frame::hard(Size::zero()));
+ if pod.expand.y {
+ let mut iter = pod;
+ while !iter.backlog.is_empty() {
+ frames.push(Frame::hard(Size::zero()));
+ iter.next();
+ }
+ }
+ Fragment::frames(frames)
+ }
+
+ // If we have content as our body, just layout it.
+ Some(BlockBody::Content(body)) => {
+ let mut fragment =
+ crate::layout_fragment(engine, body, locator.relayout(), styles, pod)?;
+
+ // If the body is automatically sized and produced more than one
+ // fragment, ensure that the width was consistent across all
+ // regions. If it wasn't, we need to relayout with expansion.
+ if !pod.expand.x
+ && fragment
+ .as_slice()
+ .windows(2)
+ .any(|w| !w[0].width().approx_eq(w[1].width()))
+ {
+ let max_width =
+ fragment.iter().map(|frame| frame.width()).max().unwrap_or_default();
+ let pod = Regions {
+ size: Size::new(max_width, pod.size.y),
+ expand: Axes::new(true, pod.expand.y),
+ ..pod
+ };
+ fragment = crate::layout_fragment(engine, body, locator, styles, pod)?;
+ }
+
+ fragment
+ }
+
+ // If we have a child that wants to layout with just access to the
+ // base region, give it that.
+ Some(BlockBody::SingleLayouter(callback)) => {
+ let pod = Region::new(pod.base(), pod.expand);
+ callback.call(engine, locator, styles, pod).map(Fragment::frame)?
+ }
+
+ // If we have a child that wants to layout with full region access,
+ // we layout it.
+ //
+ // For auto-sized multi-layouters, we propagate the outer expansion
+ // so that they can decide for themselves. We also ensure again to
+ // only expand if the size is finite.
+ Some(BlockBody::MultiLayouter(callback)) => {
+ let expand = (pod.expand | regions.expand) & pod.size.map(Abs::is_finite);
+ let pod = Regions { expand, ..pod };
+ callback.call(engine, locator, styles, pod)?
+ }
+ };
+
+ // Prepare fill and stroke.
+ let fill = elem.fill(styles);
+ let stroke = elem
+ .stroke(styles)
+ .unwrap_or_default()
+ .map(|s| s.map(Stroke::unwrap_or_default));
+
+ // Only fetch these if necessary (for clipping or filling/stroking).
+ let outset = Lazy::new(|| elem.outset(styles).unwrap_or_default());
+ let radius = Lazy::new(|| elem.radius(styles).unwrap_or_default());
+
+ // Fetch/compute these outside of the loop.
+ let clip = elem.clip(styles);
+ let has_fill_or_stroke = fill.is_some() || stroke.iter().any(Option::is_some);
+ let has_inset = !inset.is_zero();
+ let is_explicit = matches!(body, None | Some(BlockBody::Content(_)));
+
+ // Skip filling/stroking the first frame if it is empty and a non-empty
+ // one follows.
+ let mut skip_first = false;
+ if let [first, rest @ ..] = fragment.as_slice() {
+ skip_first = has_fill_or_stroke
+ && first.is_empty()
+ && rest.iter().any(|frame| !frame.is_empty());
+ }
+
+ // Post-process to apply insets, clipping, fills, and strokes.
+ for (i, (frame, region)) in fragment.iter_mut().zip(pod.iter()).enumerate() {
+ // Explicit blocks are boundaries for gradient relativeness.
+ if is_explicit {
+ frame.set_kind(FrameKind::Hard);
+ }
+
+ // Enforce a correct frame size on the expanded axes. Do this before
+ // applying the inset, since the pod shrunk.
+ frame.set_size(pod.expand.select(region, frame.size()));
+
+ // Apply the inset.
+ if has_inset {
+ crate::pad::grow(frame, &inset);
+ }
+
+ // Clip the contents, if requested.
+ if clip {
+ let size = frame.size() + outset.relative_to(frame.size()).sum_by_axis();
+ frame.clip(clip_rect(size, &radius, &stroke));
+ }
+
+ // Add fill and/or stroke.
+ if has_fill_or_stroke && (i > 0 || !skip_first) {
+ fill_and_stroke(frame, fill.clone(), &stroke, &outset, &radius, elem.span());
+ }
+ }
+
+ // Assign label to each frame in the fragment.
+ if let Some(label) = elem.label() {
+ for frame in fragment.iter_mut() {
+ frame.label(label);
+ }
+ }
+
+ Ok(fragment)
+}
+
+/// Builds the pod region for an unbreakable sized container.
+pub(crate) fn unbreakable_pod(
+ width: &Sizing,
+ height: &Sizing,
+ inset: &Sides<Rel<Abs>>,
+ styles: StyleChain,
+ base: Size,
+) -> Region {
+ // Resolve the size.
+ let mut size = Size::new(
+ match width {
+ // - For auto, the whole region is available.
+ // - Fr is handled outside and already factored into the `region`,
+ // so we can treat it equivalently to 100%.
+ Sizing::Auto | Sizing::Fr(_) => base.x,
+ // Resolve the relative sizing.
+ Sizing::Rel(rel) => rel.resolve(styles).relative_to(base.x),
+ },
+ match height {
+ Sizing::Auto | Sizing::Fr(_) => base.y,
+ Sizing::Rel(rel) => rel.resolve(styles).relative_to(base.y),
+ },
+ );
+
+ // Take the inset, if any, into account.
+ if !inset.is_zero() {
+ size = crate::pad::shrink(size, inset);
+ }
+
+ // If the child is manually, the size is forced and we should enable
+ // expansion.
+ let expand = Axes::new(
+ *width != Sizing::Auto && size.x.is_finite(),
+ *height != Sizing::Auto && size.y.is_finite(),
+ );
+
+ Region::new(size, expand)
+}
+
+/// Builds the pod regions for a breakable sized container.
+fn breakable_pod<'a>(
+ width: &Sizing,
+ height: &Sizing,
+ inset: &Sides<Rel<Abs>>,
+ styles: StyleChain,
+ regions: Regions,
+ buf: &'a mut SmallVec<[Abs; 2]>,
+) -> Regions<'a> {
+ let base = regions.base();
+
+ // The vertical region sizes we're about to build.
+ let first;
+ let full;
+ let backlog: &mut [Abs];
+ let last;
+
+ // If the block has a fixed height, things are very different, so we
+ // handle that case completely separately.
+ match height {
+ Sizing::Auto | Sizing::Fr(_) => {
+ // If the block is automatically sized, we can just inherit the
+ // regions.
+ first = regions.size.y;
+ full = regions.full;
+ buf.extend_from_slice(regions.backlog);
+ backlog = buf;
+ last = regions.last;
+ }
+
+ Sizing::Rel(rel) => {
+ // Resolve the sizing to a concrete size.
+ let resolved = rel.resolve(styles).relative_to(base.y);
+
+ // Since we're manually sized, the resolved size is the base height.
+ full = resolved;
+
+ // Distribute the fixed height across a start region and a backlog.
+ (first, backlog) = distribute(resolved, regions, buf);
+
+ // If the height is manually sized, we don't want a final repeatable
+ // region.
+ last = None;
+ }
+ };
+
+ // Resolve the horizontal sizing to a concrete width and combine
+ // `width` and `first` into `size`.
+ let mut size = Size::new(
+ match width {
+ Sizing::Auto | Sizing::Fr(_) => regions.size.x,
+ Sizing::Rel(rel) => rel.resolve(styles).relative_to(base.x),
+ },
+ first,
+ );
+
+ // Take the inset, if any, into account, applying it to the
+ // individual region components.
+ let (mut full, mut last) = (full, last);
+ if !inset.is_zero() {
+ crate::pad::shrink_multiple(&mut size, &mut full, backlog, &mut last, inset);
+ }
+
+ // If the child is manually, the size is forced and we should enable
+ // expansion.
+ let expand = Axes::new(
+ *width != Sizing::Auto && size.x.is_finite(),
+ *height != Sizing::Auto && size.y.is_finite(),
+ );
+
+ Regions { size, full, backlog, last, expand }
+}
+
+/// Distribute a fixed height spread over existing regions into a new first
+/// height and a new backlog.
+fn distribute<'a>(
+ height: Abs,
+ mut regions: Regions,
+ buf: &'a mut SmallVec<[Abs; 2]>,
+) -> (Abs, &'a mut [Abs]) {
+ // Build new region heights from old regions.
+ let mut remaining = height;
+ loop {
+ let limited = regions.size.y.clamp(Abs::zero(), remaining);
+ buf.push(limited);
+ remaining -= limited;
+ if remaining.approx_empty()
+ || !regions.may_break()
+ || (!regions.may_progress() && limited.approx_empty())
+ {
+ break;
+ }
+ regions.next();
+ }
+
+ // If there is still something remaining, apply it to the
+ // last region (it will overflow, but there's nothing else
+ // we can do).
+ if !remaining.approx_empty() {
+ if let Some(last) = buf.last_mut() {
+ *last += remaining;
+ }
+ }
+
+ // Distribute the heights to the first region and the
+ // backlog. There is no last region, since the height is
+ // fixed.
+ (buf[0], &mut buf[1..])
+}
diff --git a/crates/typst/src/layout/flow/collect.rs b/crates/typst-layout/src/flow/collect.rs
index ffb45fda..aee5d508 100644
--- a/crates/typst/src/layout/flow/collect.rs
+++ b/crates/typst-layout/src/flow/collect.rs
@@ -6,22 +6,23 @@ use bumpalo::boxed::Box as BumpBox;
use bumpalo::Bump;
use comemo::{Track, Tracked, TrackedMut};
use once_cell::unsync::Lazy;
-
-use crate::diag::{bail, SourceResult};
-use crate::engine::{Engine, Route, Sink, Traced};
-use crate::foundations::{Packed, Resolve, Smart, StyleChain};
-use crate::introspection::{
+use typst_library::diag::{bail, SourceResult};
+use typst_library::engine::{Engine, Route, Sink, Traced};
+use typst_library::foundations::{Packed, Resolve, Smart, StyleChain};
+use typst_library::introspection::{
Introspector, Location, Locator, LocatorLink, SplitLocator, Tag, TagElem,
};
-use crate::layout::{
- layout_frame, Abs, AlignElem, Alignment, Axes, BlockElem, ColbreakElem,
- FixedAlignment, FlushElem, Fr, Fragment, Frame, PagebreakElem, PlaceElem,
- PlacementScope, Ratio, Region, Regions, Rel, Size, Sizing, Spacing, VElem,
+use typst_library::layout::{
+ Abs, AlignElem, Alignment, Axes, BlockElem, ColbreakElem, FixedAlignment, FlushElem,
+ Fr, Fragment, Frame, PagebreakElem, PlaceElem, PlacementScope, Ratio, Region,
+ Regions, Rel, Size, Sizing, Spacing, VElem,
};
-use crate::model::ParElem;
-use crate::realize::Pair;
-use crate::text::TextElem;
-use crate::World;
+use typst_library::model::ParElem;
+use typst_library::routines::{Pair, Routines};
+use typst_library::text::TextElem;
+use typst_library::World;
+
+use super::{layout_multi_block, layout_single_block};
/// Collects all elements of the flow into prepared children. These are much
/// simpler to handle than the raw elements.
@@ -110,7 +111,7 @@ impl<'a> Collector<'a, '_, '_> {
let spacing = ParElem::spacing_in(styles);
let costs = TextElem::costs_in(styles);
- let lines = crate::layout::layout_inline(
+ let lines = crate::layout_inline(
self.engine,
&elem.children,
self.locator.next(&elem.span()),
@@ -332,6 +333,7 @@ impl SingleChild<'_> {
// Vertical expansion is only kept if this block is the only child.
region.expand.y &= self.alone;
layout_single_impl(
+ engine.routines,
engine.world,
engine.introspector,
engine.traced,
@@ -350,6 +352,7 @@ impl SingleChild<'_> {
#[comemo::memoize]
#[allow(clippy::too_many_arguments)]
fn layout_single_impl(
+ routines: &Routines,
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
@@ -363,6 +366,7 @@ fn layout_single_impl(
let link = LocatorLink::new(locator);
let locator = Locator::link(&link);
let mut engine = Engine {
+ routines,
world,
introspector,
traced,
@@ -370,7 +374,7 @@ fn layout_single_impl(
route: Route::extend(route),
};
- elem.layout_single(&mut engine, locator, styles, region)
+ layout_single_block(elem, &mut engine, locator, styles, region)
.map(|frame| frame.post_processed(styles))
}
@@ -425,6 +429,7 @@ impl<'a> MultiChild<'a> {
// Vertical expansion is only kept if this block is the only child.
regions.expand.y &= self.alone;
layout_multi_impl(
+ engine.routines,
engine.world,
engine.introspector,
engine.traced,
@@ -443,6 +448,7 @@ impl<'a> MultiChild<'a> {
#[comemo::memoize]
#[allow(clippy::too_many_arguments)]
fn layout_multi_impl(
+ routines: &Routines,
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
@@ -456,6 +462,7 @@ fn layout_multi_impl(
let link = LocatorLink::new(locator);
let locator = Locator::link(&link);
let mut engine = Engine {
+ routines,
world,
introspector,
traced,
@@ -463,13 +470,12 @@ fn layout_multi_impl(
route: Route::extend(route),
};
- elem.layout_multiple(&mut engine, locator, styles, regions)
- .map(|mut fragment| {
- for frame in &mut fragment {
- frame.post_process(styles);
- }
- fragment
- })
+ layout_multi_block(elem, &mut engine, locator, styles, regions).map(|mut fragment| {
+ for frame in &mut fragment {
+ frame.post_process(styles);
+ }
+ fragment
+ })
}
/// The spilled remains of a `MultiChild` that broke across two regions.
@@ -571,7 +577,7 @@ impl PlacedChild<'_> {
let align = self.alignment.unwrap_or_else(|| Alignment::CENTER);
let aligned = AlignElem::set_alignment(align).wrap();
- let mut frame = layout_frame(
+ let mut frame = crate::layout_frame(
engine,
&self.elem.body,
self.locator.relayout(),
@@ -614,7 +620,7 @@ impl<T> CachedCell<T> {
T: Clone,
F: FnOnce(I) -> T,
{
- let input_hash = crate::utils::hash128(&input);
+ let input_hash = typst_utils::hash128(&input);
let mut slot = self.0.borrow_mut();
if let Some((hash, output)) = &*slot {
diff --git a/crates/typst/src/layout/flow/compose.rs b/crates/typst-layout/src/flow/compose.rs
index a262d5c1..932ccc9a 100644
--- a/crates/typst/src/layout/flow/compose.rs
+++ b/crates/typst-layout/src/flow/compose.rs
@@ -1,22 +1,23 @@
use std::num::NonZeroUsize;
-use super::{distribute, Config, FlowResult, LineNumberConfig, PlacedChild, Stop, Work};
-use crate::diag::SourceResult;
-use crate::engine::Engine;
-use crate::foundations::{Content, NativeElement, Packed, Resolve, Smart};
-use crate::introspection::{
+use typst_library::diag::SourceResult;
+use typst_library::engine::Engine;
+use typst_library::foundations::{Content, NativeElement, Packed, Resolve, Smart};
+use typst_library::introspection::{
Counter, CounterDisplayElem, CounterState, CounterUpdate, Location, Locator,
SplitLocator, Tag,
};
-use crate::layout::{
- layout_fragment, layout_frame, Abs, Axes, Dir, FixedAlignment, Fragment, Frame,
- FrameItem, OuterHAlignment, PlacementScope, Point, Region, Regions, Rel, Size,
+use typst_library::layout::{
+ Abs, Axes, Dir, FixedAlignment, Fragment, Frame, FrameItem, OuterHAlignment,
+ PlacementScope, Point, Region, Regions, Rel, Size,
};
-use crate::model::{
+use typst_library::model::{
FootnoteElem, FootnoteEntry, LineNumberingScope, Numbering, ParLineMarker,
};
-use crate::syntax::Span;
-use crate::utils::NonZeroExt;
+use typst_syntax::Span;
+use typst_utils::NonZeroExt;
+
+use super::{distribute, Config, FlowResult, LineNumberConfig, PlacedChild, Stop, Work};
/// Composes the contents of a single page/region. A region can have multiple
/// columns/subregions.
@@ -517,7 +518,7 @@ fn layout_footnote_separator(
config: &Config,
base: Size,
) -> SourceResult<Frame> {
- layout_frame(
+ crate::layout_frame(
engine,
&config.footnote.separator,
Locator::root(),
@@ -534,7 +535,7 @@ fn layout_footnote(
pod: Regions,
) -> SourceResult<Fragment> {
let loc = elem.location().unwrap();
- layout_fragment(
+ crate::layout_fragment(
engine,
&FootnoteEntry::new(elem.clone()).pack(),
Locator::synthesize(loc),
@@ -785,7 +786,7 @@ fn layout_line_number_reset(
let counter = Counter::of(ParLineMarker::elem());
let update = CounterUpdate::Set(CounterState::init(false));
let content = counter.update(Span::detached(), update);
- layout_frame(
+ crate::layout_frame(
engine,
&content,
locator.next(&()),
@@ -821,7 +822,7 @@ fn layout_line_number(
]);
// Layout the number.
- let mut frame = layout_frame(
+ let mut frame = crate::layout_frame(
engine,
&content,
locator.next(&()),
diff --git a/crates/typst/src/layout/flow/distribute.rs b/crates/typst-layout/src/flow/distribute.rs
index 3fa16626..1852f7ca 100644
--- a/crates/typst/src/layout/flow/distribute.rs
+++ b/crates/typst-layout/src/flow/distribute.rs
@@ -1,12 +1,13 @@
+use typst_library::introspection::Tag;
+use typst_library::layout::{
+ Abs, Axes, FixedAlignment, Fr, Frame, FrameItem, Point, Region, Regions, Rel, Size,
+};
+use typst_utils::Numeric;
+
use super::{
Child, Composer, FlowResult, LineChild, MultiChild, MultiSpill, PlacedChild,
SingleChild, Stop, Work,
};
-use crate::introspection::Tag;
-use crate::layout::{
- Abs, Axes, FixedAlignment, Fr, Frame, FrameItem, Point, Region, Regions, Rel, Size,
-};
-use crate::utils::Numeric;
/// Distributes as many children as fit from `composer.work` into the first
/// region and returns the resulting frame.
diff --git a/crates/typst/src/layout/flow/mod.rs b/crates/typst-layout/src/flow/mod.rs
index 66ec8e97..7cbec59a 100644
--- a/crates/typst/src/layout/flow/mod.rs
+++ b/crates/typst-layout/src/flow/mod.rs
@@ -1,9 +1,12 @@
//! Layout of content into a [`Frame`] or [`Fragment`].
+mod block;
mod collect;
mod compose;
mod distribute;
+pub(crate) use self::block::unbreakable_pod;
+
use std::collections::HashSet;
use std::num::NonZeroUsize;
use std::rc::Rc;
@@ -11,26 +14,40 @@ use std::rc::Rc;
use bumpalo::Bump;
use comemo::{Track, Tracked, TrackedMut};
use ecow::EcoVec;
+use typst_library::diag::{bail, At, SourceDiagnostic, SourceResult};
+use typst_library::engine::{Engine, Route, Sink, Traced};
+use typst_library::foundations::{Content, Packed, Resolve, StyleChain};
+use typst_library::introspection::{
+ Introspector, Location, Locator, LocatorLink, SplitLocator, Tag,
+};
+use typst_library::layout::{
+ Abs, ColumnsElem, Dir, Em, Fragment, Frame, PageElem, PlacementScope, Region,
+ Regions, Rel, Size,
+};
+use typst_library::model::{FootnoteElem, FootnoteEntry, LineNumberingScope, ParLine};
+use typst_library::routines::{Arenas, Pair, RealizationKind, Routines};
+use typst_library::text::TextElem;
+use typst_library::World;
+use typst_utils::{NonZeroExt, Numeric};
+use self::block::{layout_multi_block, layout_single_block};
use self::collect::{
collect, Child, LineChild, MultiChild, MultiSpill, PlacedChild, SingleChild,
};
use self::compose::{compose, Composer};
use self::distribute::distribute;
-use crate::diag::{bail, At, SourceDiagnostic, SourceResult};
-use crate::engine::{Engine, Route, Sink, Traced};
-use crate::foundations::{Content, Packed, Resolve, StyleChain};
-use crate::introspection::{
- Introspector, Location, Locator, LocatorLink, SplitLocator, Tag,
-};
-use crate::layout::{
- Abs, Dir, Em, Fragment, Frame, PageElem, PlacementScope, Region, Regions, Rel, Size,
-};
-use crate::model::{FootnoteElem, FootnoteEntry, LineNumberingScope, ParLine};
-use crate::realize::{realize, Arenas, Pair, RealizationKind};
-use crate::text::TextElem;
-use crate::utils::{NonZeroExt, Numeric};
-use crate::World;
+
+/// Lays out content into a single region, producing a single frame.
+pub fn layout_frame(
+ engine: &mut Engine,
+ content: &Content,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ layout_fragment(engine, content, locator, styles, region.into())
+ .map(Fragment::into_frame)
+}
/// Lays out content into multiple regions.
///
@@ -43,6 +60,7 @@ pub fn layout_fragment(
regions: Regions,
) -> SourceResult<Fragment> {
layout_fragment_impl(
+ engine.routines,
engine.world,
engine.introspector,
engine.traced,
@@ -57,50 +75,39 @@ pub fn layout_fragment(
)
}
-/// Lays out content into regions with columns.
+/// Layout the columns.
///
/// This is different from just laying out into column-sized regions as the
/// columns can interact due to parent-scoped placed elements.
-pub fn layout_fragment_with_columns(
+#[typst_macros::time(span = elem.span())]
+pub fn layout_columns(
+ elem: &Packed<ColumnsElem>,
engine: &mut Engine,
- content: &Content,
locator: Locator,
styles: StyleChain,
regions: Regions,
- count: NonZeroUsize,
- gutter: Rel<Abs>,
) -> SourceResult<Fragment> {
layout_fragment_impl(
+ engine.routines,
engine.world,
engine.introspector,
engine.traced,
TrackedMut::reborrow_mut(&mut engine.sink),
engine.route.track(),
- content,
+ &elem.body,
locator.track(),
styles,
regions,
- count,
- gutter,
+ elem.count(styles),
+ elem.gutter(styles),
)
}
-/// Lays out content into a single region, producing a single frame.
-pub fn layout_frame(
- engine: &mut Engine,
- content: &Content,
- locator: Locator,
- styles: StyleChain,
- region: Region,
-) -> SourceResult<Frame> {
- layout_fragment(engine, content, locator, styles, region.into())
- .map(Fragment::into_frame)
-}
-
/// The cached, internal implementation of [`layout_fragment`].
#[comemo::memoize]
#[allow(clippy::too_many_arguments)]
fn layout_fragment_impl(
+ routines: &Routines,
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
@@ -123,6 +130,7 @@ fn layout_fragment_impl(
let link = LocatorLink::new(locator);
let mut locator = Locator::link(&link).split();
let mut engine = Engine {
+ routines,
world,
introspector,
traced,
@@ -133,7 +141,7 @@ fn layout_fragment_impl(
engine.route.check_layout_depth().at(content.span())?;
let arenas = Arenas::default();
- let children = realize(
+ let children = (engine.routines.realize)(
RealizationKind::Container,
&mut engine,
&mut locator,
diff --git a/crates/typst/src/layout/grid/cells.rs b/crates/typst-layout/src/grid/cells.rs
index 64234aaf..175e2183 100644
--- a/crates/typst/src/layout/grid/cells.rs
+++ b/crates/typst-layout/src/grid/cells.rs
@@ -1,161 +1,89 @@
use std::num::NonZeroUsize;
use std::sync::Arc;
-use comemo::Track;
use ecow::eco_format;
-
-use super::lines::Line;
-use super::repeated::{Footer, Header, Repeatable};
-use crate::diag::{bail, At, Hint, HintedStrResult, HintedString, SourceResult};
-use crate::engine::Engine;
-use crate::foundations::{
- Array, CastInfo, Content, Context, Fold, FromValue, Func, IntoValue, Reflect,
- Resolve, Smart, StyleChain, Value,
-};
-use crate::introspection::Locator;
-use crate::layout::{
- layout_fragment, Abs, Alignment, Axes, Fragment, Length, LinePosition, Regions, Rel,
- Sides, Sizing,
+use typst_library::diag::{bail, At, Hint, HintedStrResult, HintedString, SourceResult};
+use typst_library::engine::Engine;
+use typst_library::foundations::{Content, Smart, StyleChain};
+use typst_library::introspection::Locator;
+use typst_library::layout::{
+ Abs, Alignment, Axes, Celled, Fragment, Length, Regions, Rel, ResolvedCelled, Sides,
+ Sizing,
};
-use crate::syntax::Span;
-use crate::utils::NonZeroExt;
-use crate::visualize::{Paint, Stroke};
-
-/// A value that can be configured per cell.
-#[derive(Debug, Clone, PartialEq, Hash)]
-pub enum Celled<T> {
- /// A bare value, the same for all cells.
- Value(T),
- /// A closure mapping from cell coordinates to a value.
- Func(Func),
- /// An array of alignment values corresponding to each column.
- Array(Vec<T>),
-}
+use typst_library::visualize::{Paint, Stroke};
+use typst_syntax::Span;
+use typst_utils::NonZeroExt;
-impl<T: Default + Clone + FromValue> Celled<T> {
- /// Resolve the value based on the cell position.
- pub fn resolve(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
+use super::{Footer, Header, Line, Repeatable};
+
+/// Used for cell-like elements which are aware of their final properties in
+/// the table, and may have property overrides.
+pub trait ResolvableCell {
+ /// Resolves the cell's fields, given its coordinates and default grid-wide
+ /// fill, align, inset and stroke properties, plus the expected value of
+ /// the `breakable` field.
+ /// Returns a final Cell.
+ #[allow(clippy::too_many_arguments)]
+ fn resolve_cell<'a>(
+ self,
x: usize,
y: usize,
- ) -> SourceResult<T> {
- Ok(match self {
- Self::Value(value) => value.clone(),
- Self::Func(func) => func
- .call(engine, Context::new(None, Some(styles)).track(), [x, y])?
- .cast()
- .at(func.span())?,
- Self::Array(array) => x
- .checked_rem(array.len())
- .and_then(|i| array.get(i))
- .cloned()
- .unwrap_or_default(),
- })
- }
-}
-
-impl<T: Default> Default for Celled<T> {
- fn default() -> Self {
- Self::Value(T::default())
- }
-}
-
-impl<T: Reflect> Reflect for Celled<T> {
- fn input() -> CastInfo {
- T::input() + Array::input() + Func::input()
- }
-
- fn output() -> CastInfo {
- T::output() + Array::output() + Func::output()
- }
-
- fn castable(value: &Value) -> bool {
- Array::castable(value) || Func::castable(value) || T::castable(value)
- }
-}
+ fill: &Option<Paint>,
+ align: Smart<Alignment>,
+ inset: Sides<Option<Rel<Length>>>,
+ stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
+ breakable: bool,
+ locator: Locator<'a>,
+ styles: StyleChain,
+ ) -> Cell<'a>;
-impl<T: IntoValue> IntoValue for Celled<T> {
- fn into_value(self) -> Value {
- match self {
- Self::Value(value) => value.into_value(),
- Self::Func(func) => func.into_value(),
- Self::Array(arr) => arr.into_value(),
- }
- }
-}
+ /// Returns this cell's column override.
+ fn x(&self, styles: StyleChain) -> Smart<usize>;
-impl<T: FromValue> FromValue for Celled<T> {
- fn from_value(value: Value) -> HintedStrResult<Self> {
- match value {
- Value::Func(v) => Ok(Self::Func(v)),
- Value::Array(array) => Ok(Self::Array(
- array.into_iter().map(T::from_value).collect::<HintedStrResult<_>>()?,
- )),
- v if T::castable(&v) => Ok(Self::Value(T::from_value(v)?)),
- v => Err(Self::error(&v)),
- }
- }
-}
+ /// Returns this cell's row override.
+ fn y(&self, styles: StyleChain) -> Smart<usize>;
-impl<T: Fold> Fold for Celled<T> {
- fn fold(self, outer: Self) -> Self {
- match (self, outer) {
- (Self::Value(inner), Self::Value(outer)) => Self::Value(inner.fold(outer)),
- (self_, _) => self_,
- }
- }
-}
+ /// The amount of columns spanned by this cell.
+ fn colspan(&self, styles: StyleChain) -> NonZeroUsize;
-impl<T: Resolve> Resolve for Celled<T> {
- type Output = ResolvedCelled<T>;
+ /// The amount of rows spanned by this cell.
+ fn rowspan(&self, styles: StyleChain) -> NonZeroUsize;
- fn resolve(self, styles: StyleChain) -> Self::Output {
- match self {
- Self::Value(value) => ResolvedCelled(Celled::Value(value.resolve(styles))),
- Self::Func(func) => ResolvedCelled(Celled::Func(func)),
- Self::Array(values) => ResolvedCelled(Celled::Array(
- values.into_iter().map(|value| value.resolve(styles)).collect(),
- )),
- }
- }
+ /// The cell's span, for errors.
+ fn span(&self) -> Span;
}
-/// The result of resolving a Celled's value according to styles.
-/// Holds resolved values which depend on each grid cell's position.
-/// When it is a closure, however, it is only resolved when the closure is
-/// called.
-#[derive(Default, Clone)]
-pub struct ResolvedCelled<T: Resolve>(Celled<T::Output>);
-
-impl<T> ResolvedCelled<T>
-where
- T: FromValue + Resolve,
- <T as Resolve>::Output: Default + Clone,
-{
- /// Resolve the value based on the cell position.
- pub fn resolve(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- x: usize,
- y: usize,
- ) -> SourceResult<T::Output> {
- Ok(match &self.0 {
- Celled::Value(value) => value.clone(),
- Celled::Func(func) => func
- .call(engine, Context::new(None, Some(styles)).track(), [x, y])?
- .cast::<T>()
- .at(func.span())?
- .resolve(styles),
- Celled::Array(array) => x
- .checked_rem(array.len())
- .and_then(|i| array.get(i))
- .cloned()
- .unwrap_or_default(),
- })
- }
+/// A grid item, possibly affected by automatic cell positioning. Can be either
+/// a line or a cell.
+pub enum ResolvableGridItem<T: ResolvableCell> {
+ /// A horizontal line in the grid.
+ HLine {
+ /// The row above which the horizontal line is drawn.
+ y: Smart<usize>,
+ start: usize,
+ end: Option<NonZeroUsize>,
+ stroke: Option<Arc<Stroke<Abs>>>,
+ /// The span of the corresponding line element.
+ span: Span,
+ /// The line's position. "before" here means on top of row `y`, while
+ /// "after" means below it.
+ position: LinePosition,
+ },
+ /// A vertical line in the grid.
+ VLine {
+ /// The column before which the vertical line is drawn.
+ x: Smart<usize>,
+ start: usize,
+ end: Option<NonZeroUsize>,
+ stroke: Option<Arc<Stroke<Abs>>>,
+ /// The span of the corresponding line element.
+ span: Span,
+ /// The line's position. "before" here means to the left of column `x`,
+ /// while "after" means to its right (both considering LTR).
+ position: LinePosition,
+ },
+ /// A cell in the grid.
+ Cell(T),
}
/// Represents a cell in CellGrid, to be laid out by GridLayouter.
@@ -221,12 +149,24 @@ impl<'a> Cell<'a> {
if disambiguator > 0 {
locator = locator.split().next_inner(disambiguator as u128);
}
- layout_fragment(engine, &self.body, locator, styles, regions)
+ crate::layout_fragment(engine, &self.body, locator, styles, regions)
}
}
+/// Indicates whether the line should be drawn before or after the track with
+/// its index. This is mostly only relevant when gutter is used, since, then,
+/// the position after a track is not the same as before the next
+/// non-gutter track.
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub enum LinePosition {
+ /// The line should be drawn before its track (e.g. hline on top of a row).
+ Before,
+ /// The line should be drawn after its track (e.g. hline below a row).
+ After,
+}
+
/// A grid entry.
-pub(super) enum Entry<'a> {
+pub enum Entry<'a> {
/// An entry which holds a cell.
Cell(Cell<'a>),
/// An entry which is merged with another cell.
@@ -246,39 +186,6 @@ impl<'a> Entry<'a> {
}
}
-/// A grid item, possibly affected by automatic cell positioning. Can be either
-/// a line or a cell.
-pub enum ResolvableGridItem<T: ResolvableCell> {
- /// A horizontal line in the grid.
- HLine {
- /// The row above which the horizontal line is drawn.
- y: Smart<usize>,
- start: usize,
- end: Option<NonZeroUsize>,
- stroke: Option<Arc<Stroke<Abs>>>,
- /// The span of the corresponding line element.
- span: Span,
- /// The line's position. "before" here means on top of row `y`, while
- /// "after" means below it.
- position: LinePosition,
- },
- /// A vertical line in the grid.
- VLine {
- /// The column before which the vertical line is drawn.
- x: Smart<usize>,
- start: usize,
- end: Option<NonZeroUsize>,
- stroke: Option<Arc<Stroke<Abs>>>,
- /// The span of the corresponding line element.
- span: Span,
- /// The line's position. "before" here means to the left of column `x`,
- /// while "after" means to its right (both considering LTR).
- position: LinePosition,
- },
- /// A cell in the grid.
- Cell(T),
-}
-
/// Any grid child, which can be either a header or an item.
pub enum ResolvableGridChild<T: ResolvableCell, I> {
Header { repeat: bool, span: Span, items: I },
@@ -286,65 +193,28 @@ pub enum ResolvableGridChild<T: ResolvableCell, I> {
Item(ResolvableGridItem<T>),
}
-/// Used for cell-like elements which are aware of their final properties in
-/// the table, and may have property overrides.
-pub trait ResolvableCell {
- /// Resolves the cell's fields, given its coordinates and default grid-wide
- /// fill, align, inset and stroke properties, plus the expected value of
- /// the `breakable` field.
- /// Returns a final Cell.
- #[allow(clippy::too_many_arguments)]
- fn resolve_cell<'a>(
- self,
- x: usize,
- y: usize,
- fill: &Option<Paint>,
- align: Smart<Alignment>,
- inset: Sides<Option<Rel<Length>>>,
- stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
- breakable: bool,
- locator: Locator<'a>,
- styles: StyleChain,
- ) -> Cell<'a>;
-
- /// Returns this cell's column override.
- fn x(&self, styles: StyleChain) -> Smart<usize>;
-
- /// Returns this cell's row override.
- fn y(&self, styles: StyleChain) -> Smart<usize>;
-
- /// The amount of columns spanned by this cell.
- fn colspan(&self, styles: StyleChain) -> NonZeroUsize;
-
- /// The amount of rows spanned by this cell.
- fn rowspan(&self, styles: StyleChain) -> NonZeroUsize;
-
- /// The cell's span, for errors.
- fn span(&self) -> Span;
-}
-
/// A grid of cells, including the columns, rows, and cell data.
pub struct CellGrid<'a> {
/// The grid cells.
- pub(super) entries: Vec<Entry<'a>>,
+ pub entries: Vec<Entry<'a>>,
/// The column tracks including gutter tracks.
- pub(super) cols: Vec<Sizing>,
+ pub cols: Vec<Sizing>,
/// The row tracks including gutter tracks.
- pub(super) rows: Vec<Sizing>,
+ pub rows: Vec<Sizing>,
/// The vertical lines before each column, or on the end border.
/// Gutter columns are not included.
/// Contains up to 'cols_without_gutter.len() + 1' vectors of lines.
- pub(super) vlines: Vec<Vec<Line>>,
+ pub vlines: Vec<Vec<Line>>,
/// The horizontal lines on top of each row, or on the bottom border.
/// Gutter rows are not included.
/// Contains up to 'rows_without_gutter.len() + 1' vectors of lines.
- pub(super) hlines: Vec<Vec<Line>>,
+ pub hlines: Vec<Vec<Line>>,
/// The repeatable header of this grid.
- pub(super) header: Option<Repeatable<Header>>,
+ pub header: Option<Repeatable<Header>>,
/// The repeatable footer of this grid.
- pub(super) footer: Option<Repeatable<Footer>>,
+ pub footer: Option<Repeatable<Footer>>,
/// Whether this grid has gutters.
- pub(super) has_gutter: bool,
+ pub has_gutter: bool,
}
impl<'a> CellGrid<'a> {
@@ -1125,7 +995,7 @@ impl<'a> CellGrid<'a> {
}
/// Generates the cell grid, given the tracks and resolved entries.
- pub(super) fn new_internal(
+ pub fn new_internal(
tracks: Axes<&[Sizing]>,
gutter: Axes<&[Sizing]>,
vlines: Vec<Vec<Line>>,
@@ -1194,7 +1064,7 @@ impl<'a> CellGrid<'a> {
///
/// Returns `None` if it's a gutter cell.
#[track_caller]
- pub(super) fn entry(&self, x: usize, y: usize) -> Option<&Entry<'a>> {
+ pub fn entry(&self, x: usize, y: usize) -> Option<&Entry<'a>> {
assert!(x < self.cols.len());
assert!(y < self.rows.len());
@@ -1216,7 +1086,7 @@ impl<'a> CellGrid<'a> {
///
/// Returns `None` if it's a gutter cell or merged position.
#[track_caller]
- pub(super) fn cell(&self, x: usize, y: usize) -> Option<&Cell<'a>> {
+ pub fn cell(&self, x: usize, y: usize) -> Option<&Cell<'a>> {
self.entry(x, y).and_then(Entry::as_cell)
}
@@ -1228,7 +1098,7 @@ impl<'a> CellGrid<'a> {
/// - If it is a merged cell, returns the parent cell's position.
/// - If it is a gutter cell, returns None.
#[track_caller]
- pub(super) fn parent_cell_position(&self, x: usize, y: usize) -> Option<Axes<usize>> {
+ pub fn parent_cell_position(&self, x: usize, y: usize) -> Option<Axes<usize>> {
self.entry(x, y).map(|entry| match entry {
Entry::Cell(_) => Axes::new(x, y),
Entry::Merged { parent } => {
@@ -1260,7 +1130,7 @@ impl<'a> CellGrid<'a> {
/// position, if it is gutter), if it exists; otherwise returns None (it's
/// gutter and no cell spans it).
#[track_caller]
- pub(super) fn effective_parent_cell_position(
+ pub fn effective_parent_cell_position(
&self,
x: usize,
y: usize,
@@ -1283,14 +1153,14 @@ impl<'a> CellGrid<'a> {
/// Checks if the track with the given index is gutter.
/// Does not check if the index is a valid track.
#[inline]
- pub(super) fn is_gutter_track(&self, index: usize) -> bool {
+ pub fn is_gutter_track(&self, index: usize) -> bool {
self.has_gutter && index % 2 == 1
}
/// Returns the effective colspan of a cell, considering the gutters it
/// might span if the grid has gutters.
#[inline]
- pub(super) fn effective_colspan_of_cell(&self, cell: &Cell) -> usize {
+ pub fn effective_colspan_of_cell(&self, cell: &Cell) -> usize {
if self.has_gutter {
2 * cell.colspan.get() - 1
} else {
@@ -1301,7 +1171,7 @@ impl<'a> CellGrid<'a> {
/// Returns the effective rowspan of a cell, considering the gutters it
/// might span if the grid has gutters.
#[inline]
- pub(super) fn effective_rowspan_of_cell(&self, cell: &Cell) -> usize {
+ pub fn effective_rowspan_of_cell(&self, cell: &Cell) -> usize {
if self.has_gutter {
2 * cell.rowspan.get() - 1
} else {
diff --git a/crates/typst/src/layout/grid/layout.rs b/crates/typst-layout/src/grid/layouter.rs
index 53eda0f0..7c94617d 100644
--- a/crates/typst/src/layout/grid/layout.rs
+++ b/crates/typst-layout/src/grid/layouter.rs
@@ -1,22 +1,21 @@
use std::fmt::Debug;
-use super::lines::{
- generate_line_segments, hline_stroke_at_column, vline_stroke_at_row, LinePosition,
- LineSegment,
+use typst_library::diag::{bail, SourceResult};
+use typst_library::engine::Engine;
+use typst_library::foundations::{Resolve, StyleChain};
+use typst_library::layout::{
+ Abs, Axes, Dir, Fr, Fragment, Frame, FrameItem, Length, Point, Region, Regions, Rel,
+ Size, Sizing,
};
-use super::repeated::Repeatable;
-use super::rowspans::{Rowspan, UnbreakableRowGroup};
-use crate::diag::{bail, SourceResult};
-use crate::engine::Engine;
-use crate::foundations::{Resolve, StyleChain};
-use crate::layout::{
- Abs, Axes, Cell, CellGrid, Dir, Fr, Fragment, Frame, FrameItem, Length, Point,
- Region, Regions, Rel, Size, Sizing,
+use typst_library::text::TextElem;
+use typst_library::visualize::Geometry;
+use typst_syntax::Span;
+use typst_utils::{MaybeReverseIter, Numeric};
+
+use super::{
+ generate_line_segments, hline_stroke_at_column, vline_stroke_at_row, Cell, CellGrid,
+ LinePosition, LineSegment, Repeatable, Rowspan, UnbreakableRowGroup,
};
-use crate::syntax::Span;
-use crate::text::TextElem;
-use crate::utils::{MaybeReverseIter, Numeric};
-use crate::visualize::Geometry;
/// Performs grid layout.
pub struct GridLayouter<'a> {
diff --git a/crates/typst/src/layout/grid/lines.rs b/crates/typst-layout/src/grid/lines.rs
index 660811c7..3e89612a 100644
--- a/crates/typst/src/layout/grid/lines.rs
+++ b/crates/typst-layout/src/grid/lines.rs
@@ -1,12 +1,11 @@
use std::num::NonZeroUsize;
use std::sync::Arc;
-use super::cells::CellGrid;
-use super::layout::RowPiece;
-use super::repeated::Repeatable;
-use crate::foundations::{AlternativeFold, Fold};
-use crate::layout::Abs;
-use crate::visualize::Stroke;
+use typst_library::foundations::{AlternativeFold, Fold};
+use typst_library::layout::Abs;
+use typst_library::visualize::Stroke;
+
+use super::{CellGrid, LinePosition, Repeatable, RowPiece};
/// Represents an explicit grid line (horizontal or vertical) specified by the
/// user.
@@ -38,22 +37,10 @@ pub struct Line {
pub position: LinePosition,
}
-/// Indicates whether the line should be drawn before or after the track with
-/// its index. This is mostly only relevant when gutter is used, since, then,
-/// the position after a track is not the same as before the next
-/// non-gutter track.
-#[derive(Copy, Clone, PartialEq, Eq)]
-pub enum LinePosition {
- /// The line should be drawn before its track (e.g. hline on top of a row).
- Before,
- /// The line should be drawn after its track (e.g. hline below a row).
- After,
-}
-
/// Indicates which priority a particular grid line segment should have, based
/// on the highest priority configuration that defined the segment's stroke.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
-pub(super) enum StrokePriority {
+pub enum StrokePriority {
/// The stroke of the segment was derived solely from the grid's global
/// stroke setting, so it should have the lowest priority.
GridStroke = 0,
@@ -71,19 +58,19 @@ pub(super) enum StrokePriority {
/// Data for a particular line segment in the grid as generated by
/// `generate_line_segments`.
#[derive(Debug, PartialEq, Eq)]
-pub(super) struct LineSegment {
+pub struct LineSegment {
/// The stroke with which to draw this segment.
- pub(super) stroke: Arc<Stroke<Abs>>,
+ pub stroke: Arc<Stroke<Abs>>,
/// The offset of this segment since the beginning of its axis.
/// For a vertical line segment, this is the offset since the top of the
/// table in the current page; for a horizontal line segment, this is the
/// offset since the start border of the table.
- pub(super) offset: Abs,
+ pub offset: Abs,
/// The length of this segment.
- pub(super) length: Abs,
+ pub length: Abs,
/// The segment's drawing priority, indicating on top of which other
/// segments this one should be drawn.
- pub(super) priority: StrokePriority,
+ pub priority: StrokePriority,
}
/// Generates the segments of lines that should be drawn alongside a certain
@@ -119,7 +106,7 @@ pub(super) struct LineSegment {
/// number, and they must be iterable over pairs of (number, size). For
/// vertical lines, for instance, `tracks` would describe the rows in the
/// current region, as pairs (row index, row height).
-pub(super) fn generate_line_segments<'grid, F, I, L>(
+pub fn generate_line_segments<'grid, F, I, L>(
grid: &'grid CellGrid,
tracks: I,
index: usize,
@@ -269,7 +256,7 @@ where
current_segment =
Some(LineSegment { stroke, offset, length: size, priority });
}
- } else if let Some(old_segment) = current_segment.take() {
+ } else if let Some(old_segment) = Option::take(&mut current_segment) {
// We shouldn't draw here (stroke of None), so we yield the
// current segment, as it was interrupted.
offset += size;
@@ -289,7 +276,9 @@ where
// closure, the current segment will necessarily be 'None',
// so the iterator will necessarily end (that is, we will return None)
// after this.
- current_segment.take()
+ //
+ // Note: Fully-qualified notation because rust-analyzer is confused.
+ Option::take(&mut current_segment)
})
}
@@ -312,7 +301,7 @@ where
///
/// The priority associated with the returned stroke follows the rules
/// described in the docs for `generate_line_segment`.
-pub(super) fn vline_stroke_at_row(
+pub fn vline_stroke_at_row(
grid: &CellGrid,
x: usize,
y: usize,
@@ -432,7 +421,7 @@ pub(super) fn vline_stroke_at_row(
///
/// This function assumes columns are sorted by increasing `x`, and rows are
/// sorted by increasing `y`.
-pub(super) fn hline_stroke_at_column(
+pub fn hline_stroke_at_column(
grid: &CellGrid,
rows: &[RowPiece],
local_top_y: Option<usize>,
@@ -599,12 +588,14 @@ pub(super) fn hline_stroke_at_column(
#[cfg(test)]
mod test {
+ use typst_library::foundations::Content;
+ use typst_library::introspection::Locator;
+ use typst_library::layout::{Axes, Sides, Sizing};
+ use typst_utils::NonZeroExt;
+
use super::super::cells::Entry;
+ use super::super::Cell;
use super::*;
- use crate::foundations::Content;
- use crate::introspection::Locator;
- use crate::layout::{Axes, Cell, Sides, Sizing};
- use crate::utils::NonZeroExt;
fn sample_cell() -> Cell<'static> {
Cell {
diff --git a/crates/typst-layout/src/grid/mod.rs b/crates/typst-layout/src/grid/mod.rs
new file mode 100644
index 00000000..769bef8c
--- /dev/null
+++ b/crates/typst-layout/src/grid/mod.rs
@@ -0,0 +1,416 @@
+mod cells;
+mod layouter;
+mod lines;
+mod repeated;
+mod rowspans;
+
+pub use self::cells::{Cell, CellGrid};
+pub use self::layouter::GridLayouter;
+
+use std::num::NonZeroUsize;
+use std::sync::Arc;
+
+use ecow::eco_format;
+use typst_library::diag::{SourceResult, Trace, Tracepoint};
+use typst_library::engine::Engine;
+use typst_library::foundations::{Fold, Packed, Smart, StyleChain};
+use typst_library::introspection::Locator;
+use typst_library::layout::{
+ Abs, Alignment, Axes, Dir, Fragment, GridCell, GridChild, GridElem, GridItem, Length,
+ OuterHAlignment, OuterVAlignment, Regions, Rel, Sides,
+};
+use typst_library::model::{TableCell, TableChild, TableElem, TableItem};
+use typst_library::text::TextElem;
+use typst_library::visualize::{Paint, Stroke};
+use typst_syntax::Span;
+
+use self::cells::{
+ LinePosition, ResolvableCell, ResolvableGridChild, ResolvableGridItem,
+};
+use self::layouter::RowPiece;
+use self::lines::{
+ generate_line_segments, hline_stroke_at_column, vline_stroke_at_row, Line,
+ LineSegment,
+};
+use self::repeated::{Footer, Header, Repeatable};
+use self::rowspans::{Rowspan, UnbreakableRowGroup};
+
+/// Layout the grid.
+#[typst_macros::time(span = elem.span())]
+pub fn layout_grid(
+ elem: &Packed<GridElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+) -> SourceResult<Fragment> {
+ let inset = elem.inset(styles);
+ let align = elem.align(styles);
+ let columns = elem.columns(styles);
+ let rows = elem.rows(styles);
+ let column_gutter = elem.column_gutter(styles);
+ let row_gutter = elem.row_gutter(styles);
+ let fill = elem.fill(styles);
+ let stroke = elem.stroke(styles);
+
+ let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice());
+ let gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice());
+ // Use trace to link back to the grid when a specific cell errors
+ let tracepoint = || Tracepoint::Call(Some(eco_format!("grid")));
+ let resolve_item = |item: &GridItem| grid_item_to_resolvable(item, styles);
+ let children = elem.children().iter().map(|child| match child {
+ GridChild::Header(header) => ResolvableGridChild::Header {
+ repeat: header.repeat(styles),
+ span: header.span(),
+ items: header.children().iter().map(resolve_item),
+ },
+ GridChild::Footer(footer) => ResolvableGridChild::Footer {
+ repeat: footer.repeat(styles),
+ span: footer.span(),
+ items: footer.children().iter().map(resolve_item),
+ },
+ GridChild::Item(item) => {
+ ResolvableGridChild::Item(grid_item_to_resolvable(item, styles))
+ }
+ });
+ let grid = CellGrid::resolve(
+ tracks,
+ gutter,
+ locator,
+ children,
+ fill,
+ align,
+ &inset,
+ &stroke,
+ engine,
+ styles,
+ elem.span(),
+ )
+ .trace(engine.world, tracepoint, elem.span())?;
+
+ let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
+
+ // Measure the columns and layout the grid row-by-row.
+ layouter.layout(engine)
+}
+
+/// Layout the table.
+#[typst_macros::time(span = elem.span())]
+pub fn layout_table(
+ elem: &Packed<TableElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+) -> SourceResult<Fragment> {
+ let inset = elem.inset(styles);
+ let align = elem.align(styles);
+ let columns = elem.columns(styles);
+ let rows = elem.rows(styles);
+ let column_gutter = elem.column_gutter(styles);
+ let row_gutter = elem.row_gutter(styles);
+ let fill = elem.fill(styles);
+ let stroke = elem.stroke(styles);
+
+ let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice());
+ let gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice());
+ // Use trace to link back to the table when a specific cell errors
+ let tracepoint = || Tracepoint::Call(Some(eco_format!("table")));
+ let resolve_item = |item: &TableItem| table_item_to_resolvable(item, styles);
+ let children = elem.children().iter().map(|child| match child {
+ TableChild::Header(header) => ResolvableGridChild::Header {
+ repeat: header.repeat(styles),
+ span: header.span(),
+ items: header.children().iter().map(resolve_item),
+ },
+ TableChild::Footer(footer) => ResolvableGridChild::Footer {
+ repeat: footer.repeat(styles),
+ span: footer.span(),
+ items: footer.children().iter().map(resolve_item),
+ },
+ TableChild::Item(item) => {
+ ResolvableGridChild::Item(table_item_to_resolvable(item, styles))
+ }
+ });
+ let grid = CellGrid::resolve(
+ tracks,
+ gutter,
+ locator,
+ children,
+ fill,
+ align,
+ &inset,
+ &stroke,
+ engine,
+ styles,
+ elem.span(),
+ )
+ .trace(engine.world, tracepoint, elem.span())?;
+
+ let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
+ layouter.layout(engine)
+}
+
+fn grid_item_to_resolvable(
+ item: &GridItem,
+ styles: StyleChain,
+) -> ResolvableGridItem<Packed<GridCell>> {
+ match item {
+ GridItem::HLine(hline) => ResolvableGridItem::HLine {
+ y: hline.y(styles),
+ start: hline.start(styles),
+ end: hline.end(styles),
+ stroke: hline.stroke(styles),
+ span: hline.span(),
+ position: match hline.position(styles) {
+ OuterVAlignment::Top => LinePosition::Before,
+ OuterVAlignment::Bottom => LinePosition::After,
+ },
+ },
+ GridItem::VLine(vline) => ResolvableGridItem::VLine {
+ x: vline.x(styles),
+ start: vline.start(styles),
+ end: vline.end(styles),
+ stroke: vline.stroke(styles),
+ span: vline.span(),
+ position: match vline.position(styles) {
+ OuterHAlignment::Left if TextElem::dir_in(styles) == Dir::RTL => {
+ LinePosition::After
+ }
+ OuterHAlignment::Right if TextElem::dir_in(styles) == Dir::RTL => {
+ LinePosition::Before
+ }
+ OuterHAlignment::Start | OuterHAlignment::Left => LinePosition::Before,
+ OuterHAlignment::End | OuterHAlignment::Right => LinePosition::After,
+ },
+ },
+ GridItem::Cell(cell) => ResolvableGridItem::Cell(cell.clone()),
+ }
+}
+
+fn table_item_to_resolvable(
+ item: &TableItem,
+ styles: StyleChain,
+) -> ResolvableGridItem<Packed<TableCell>> {
+ match item {
+ TableItem::HLine(hline) => ResolvableGridItem::HLine {
+ y: hline.y(styles),
+ start: hline.start(styles),
+ end: hline.end(styles),
+ stroke: hline.stroke(styles),
+ span: hline.span(),
+ position: match hline.position(styles) {
+ OuterVAlignment::Top => LinePosition::Before,
+ OuterVAlignment::Bottom => LinePosition::After,
+ },
+ },
+ TableItem::VLine(vline) => ResolvableGridItem::VLine {
+ x: vline.x(styles),
+ start: vline.start(styles),
+ end: vline.end(styles),
+ stroke: vline.stroke(styles),
+ span: vline.span(),
+ position: match vline.position(styles) {
+ OuterHAlignment::Left if TextElem::dir_in(styles) == Dir::RTL => {
+ LinePosition::After
+ }
+ OuterHAlignment::Right if TextElem::dir_in(styles) == Dir::RTL => {
+ LinePosition::Before
+ }
+ OuterHAlignment::Start | OuterHAlignment::Left => LinePosition::Before,
+ OuterHAlignment::End | OuterHAlignment::Right => LinePosition::After,
+ },
+ },
+ TableItem::Cell(cell) => ResolvableGridItem::Cell(cell.clone()),
+ }
+}
+
+impl ResolvableCell for Packed<TableCell> {
+ fn resolve_cell<'a>(
+ mut self,
+ x: usize,
+ y: usize,
+ fill: &Option<Paint>,
+ align: Smart<Alignment>,
+ inset: Sides<Option<Rel<Length>>>,
+ stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
+ breakable: bool,
+ locator: Locator<'a>,
+ styles: StyleChain,
+ ) -> Cell<'a> {
+ let cell = &mut *self;
+ let colspan = cell.colspan(styles);
+ let rowspan = cell.rowspan(styles);
+ let breakable = cell.breakable(styles).unwrap_or(breakable);
+ let fill = cell.fill(styles).unwrap_or_else(|| fill.clone());
+
+ let cell_stroke = cell.stroke(styles);
+ let stroke_overridden =
+ cell_stroke.as_ref().map(|side| matches!(side, Some(Some(_))));
+
+ // Using a typical 'Sides' fold, an unspecified side loses to a
+ // specified side. Additionally, when both are specified, an inner
+ // None wins over the outer Some, and vice-versa. When both are
+ // specified and Some, fold occurs, which, remarkably, leads to an Arc
+ // clone.
+ //
+ // In the end, we flatten because, for layout purposes, an unspecified
+ // cell stroke is the same as specifying 'none', so we equate the two
+ // concepts.
+ let stroke = cell_stroke.fold(stroke).map(Option::flatten);
+ cell.push_x(Smart::Custom(x));
+ cell.push_y(Smart::Custom(y));
+ cell.push_fill(Smart::Custom(fill.clone()));
+ cell.push_align(match align {
+ Smart::Custom(align) => {
+ Smart::Custom(cell.align(styles).map_or(align, |inner| inner.fold(align)))
+ }
+ // Don't fold if the table is using outer alignment. Use the
+ // cell's alignment instead (which, in the end, will fold with
+ // the outer alignment when it is effectively displayed).
+ Smart::Auto => cell.align(styles),
+ });
+ cell.push_inset(Smart::Custom(
+ cell.inset(styles).map_or(inset, |inner| inner.fold(inset)),
+ ));
+ cell.push_stroke(
+ // Here we convert the resolved stroke to a regular stroke, however
+ // with resolved units (that is, 'em' converted to absolute units).
+ // We also convert any stroke unspecified by both the cell and the
+ // outer stroke ('None' in the folded stroke) to 'none', that is,
+ // all sides are present in the resulting Sides object accessible
+ // by show rules on table cells.
+ stroke.as_ref().map(|side| {
+ Some(side.as_ref().map(|cell_stroke| {
+ Arc::new((**cell_stroke).clone().map(Length::from))
+ }))
+ }),
+ );
+ cell.push_breakable(Smart::Custom(breakable));
+ Cell {
+ body: self.pack(),
+ locator,
+ fill,
+ colspan,
+ rowspan,
+ stroke,
+ stroke_overridden,
+ breakable,
+ }
+ }
+
+ fn x(&self, styles: StyleChain) -> Smart<usize> {
+ (**self).x(styles)
+ }
+
+ fn y(&self, styles: StyleChain) -> Smart<usize> {
+ (**self).y(styles)
+ }
+
+ fn colspan(&self, styles: StyleChain) -> NonZeroUsize {
+ (**self).colspan(styles)
+ }
+
+ fn rowspan(&self, styles: StyleChain) -> NonZeroUsize {
+ (**self).rowspan(styles)
+ }
+
+ fn span(&self) -> Span {
+ Packed::span(self)
+ }
+}
+
+impl ResolvableCell for Packed<GridCell> {
+ fn resolve_cell<'a>(
+ mut self,
+ x: usize,
+ y: usize,
+ fill: &Option<Paint>,
+ align: Smart<Alignment>,
+ inset: Sides<Option<Rel<Length>>>,
+ stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
+ breakable: bool,
+ locator: Locator<'a>,
+ styles: StyleChain,
+ ) -> Cell<'a> {
+ let cell = &mut *self;
+ let colspan = cell.colspan(styles);
+ let rowspan = cell.rowspan(styles);
+ let breakable = cell.breakable(styles).unwrap_or(breakable);
+ let fill = cell.fill(styles).unwrap_or_else(|| fill.clone());
+
+ let cell_stroke = cell.stroke(styles);
+ let stroke_overridden =
+ cell_stroke.as_ref().map(|side| matches!(side, Some(Some(_))));
+
+ // Using a typical 'Sides' fold, an unspecified side loses to a
+ // specified side. Additionally, when both are specified, an inner
+ // None wins over the outer Some, and vice-versa. When both are
+ // specified and Some, fold occurs, which, remarkably, leads to an Arc
+ // clone.
+ //
+ // In the end, we flatten because, for layout purposes, an unspecified
+ // cell stroke is the same as specifying 'none', so we equate the two
+ // concepts.
+ let stroke = cell_stroke.fold(stroke).map(Option::flatten);
+ cell.push_x(Smart::Custom(x));
+ cell.push_y(Smart::Custom(y));
+ cell.push_fill(Smart::Custom(fill.clone()));
+ cell.push_align(match align {
+ Smart::Custom(align) => {
+ Smart::Custom(cell.align(styles).map_or(align, |inner| inner.fold(align)))
+ }
+ // Don't fold if the grid is using outer alignment. Use the
+ // cell's alignment instead (which, in the end, will fold with
+ // the outer alignment when it is effectively displayed).
+ Smart::Auto => cell.align(styles),
+ });
+ cell.push_inset(Smart::Custom(
+ cell.inset(styles).map_or(inset, |inner| inner.fold(inset)),
+ ));
+ cell.push_stroke(
+ // Here we convert the resolved stroke to a regular stroke, however
+ // with resolved units (that is, 'em' converted to absolute units).
+ // We also convert any stroke unspecified by both the cell and the
+ // outer stroke ('None' in the folded stroke) to 'none', that is,
+ // all sides are present in the resulting Sides object accessible
+ // by show rules on grid cells.
+ stroke.as_ref().map(|side| {
+ Some(side.as_ref().map(|cell_stroke| {
+ Arc::new((**cell_stroke).clone().map(Length::from))
+ }))
+ }),
+ );
+ cell.push_breakable(Smart::Custom(breakable));
+ Cell {
+ body: self.pack(),
+ locator,
+ fill,
+ colspan,
+ rowspan,
+ stroke,
+ stroke_overridden,
+ breakable,
+ }
+ }
+
+ fn x(&self, styles: StyleChain) -> Smart<usize> {
+ (**self).x(styles)
+ }
+
+ fn y(&self, styles: StyleChain) -> Smart<usize> {
+ (**self).y(styles)
+ }
+
+ fn colspan(&self, styles: StyleChain) -> NonZeroUsize {
+ (**self).colspan(styles)
+ }
+
+ fn rowspan(&self, styles: StyleChain) -> NonZeroUsize {
+ (**self).rowspan(styles)
+ }
+
+ fn span(&self) -> Span {
+ Packed::span(self)
+ }
+}
diff --git a/crates/typst/src/layout/grid/repeated.rs b/crates/typst-layout/src/grid/repeated.rs
index f187d0bc..972179da 100644
--- a/crates/typst/src/layout/grid/repeated.rs
+++ b/crates/typst-layout/src/grid/repeated.rs
@@ -1,25 +1,27 @@
+use typst_library::diag::SourceResult;
+use typst_library::engine::Engine;
+use typst_library::layout::{Abs, Axes, Frame, Regions};
+
+use super::layouter::GridLayouter;
use super::rowspans::UnbreakableRowGroup;
-use crate::diag::SourceResult;
-use crate::engine::Engine;
-use crate::layout::{Abs, Axes, Frame, GridLayouter, Regions};
/// A repeatable grid header. Starts at the first row.
-pub(super) struct Header {
+pub struct Header {
/// The index after the last row included in this header.
- pub(super) end: usize,
+ pub end: usize,
}
/// A repeatable grid footer. Stops at the last row.
-pub(super) struct Footer {
+pub struct Footer {
/// The first row included in this footer.
- pub(super) start: usize,
+ pub start: usize,
}
/// A possibly repeatable grid object.
/// It still exists even when not repeatable, but must not have additional
/// considerations by grid layout, other than for consistency (such as making
/// a certain group of rows unbreakable).
-pub(super) enum Repeatable<T> {
+pub enum Repeatable<T> {
Repeated(T),
NotRepeated(T),
}
@@ -27,7 +29,7 @@ pub(super) enum Repeatable<T> {
impl<T> Repeatable<T> {
/// Gets the value inside this repeatable, regardless of whether
/// it repeats.
- pub(super) fn unwrap(&self) -> &T {
+ pub fn unwrap(&self) -> &T {
match self {
Self::Repeated(repeated) => repeated,
Self::NotRepeated(not_repeated) => not_repeated,
@@ -35,7 +37,7 @@ impl<T> Repeatable<T> {
}
/// Returns `Some` if the value is repeated, `None` otherwise.
- pub(super) fn as_repeated(&self) -> Option<&T> {
+ pub fn as_repeated(&self) -> Option<&T> {
match self {
Self::Repeated(repeated) => Some(repeated),
Self::NotRepeated(_) => None,
@@ -46,7 +48,7 @@ impl<T> Repeatable<T> {
impl<'a> GridLayouter<'a> {
/// Layouts the header's rows.
/// Skips regions as necessary.
- pub(super) fn layout_header(
+ pub fn layout_header(
&mut self,
header: &Header,
engine: &mut Engine,
@@ -90,7 +92,7 @@ impl<'a> GridLayouter<'a> {
}
/// Simulate the header's group of rows.
- pub(super) fn simulate_header(
+ pub fn simulate_header(
&self,
header: &Header,
regions: &Regions<'_>,
@@ -112,7 +114,7 @@ impl<'a> GridLayouter<'a> {
}
/// Updates `self.footer_height` by simulating the footer, and skips to fitting region.
- pub(super) fn prepare_footer(
+ pub fn prepare_footer(
&mut self,
footer: &Footer,
engine: &mut Engine,
@@ -146,7 +148,7 @@ impl<'a> GridLayouter<'a> {
/// Lays out all rows in the footer.
/// They are unbreakable.
- pub(super) fn layout_footer(
+ pub fn layout_footer(
&mut self,
footer: &Footer,
engine: &mut Engine,
@@ -167,7 +169,7 @@ impl<'a> GridLayouter<'a> {
}
// Simulate the footer's group of rows.
- pub(super) fn simulate_footer(
+ pub fn simulate_footer(
&self,
footer: &Footer,
regions: &Regions<'_>,
diff --git a/crates/typst/src/layout/grid/rowspans.rs b/crates/typst-layout/src/grid/rowspans.rs
index 6381ba66..03b4103f 100644
--- a/crates/typst/src/layout/grid/rowspans.rs
+++ b/crates/typst-layout/src/grid/rowspans.rs
@@ -1,88 +1,88 @@
-use super::layout::{in_last_with_offset, points, Row, RowPiece};
+use typst_library::diag::SourceResult;
+use typst_library::engine::Engine;
+use typst_library::foundations::Resolve;
+use typst_library::layout::{Abs, Axes, Frame, Point, Region, Regions, Size, Sizing};
+use typst_utils::MaybeReverseIter;
+
+use super::layouter::{in_last_with_offset, points, Row, RowPiece};
use super::repeated::Repeatable;
-use crate::diag::SourceResult;
-use crate::engine::Engine;
-use crate::foundations::Resolve;
-use crate::layout::{
- Abs, Axes, Cell, Frame, GridLayouter, Point, Region, Regions, Size, Sizing,
-};
-use crate::utils::MaybeReverseIter;
+use super::{Cell, GridLayouter};
/// All information needed to layout a single rowspan.
-pub(super) struct Rowspan {
+pub struct Rowspan {
/// First column of this rowspan.
- pub(super) x: usize,
+ pub x: usize,
/// First row of this rowspan.
- pub(super) y: usize,
+ pub y: usize,
/// The disambiguator for laying out the cells.
- pub(super) disambiguator: usize,
+ pub disambiguator: usize,
/// Amount of rows spanned by the cell at (x, y).
- pub(super) rowspan: usize,
+ pub rowspan: usize,
/// Whether all rows of the rowspan are part of an unbreakable row group.
/// This is true e.g. in headers and footers, regardless of what the user
/// specified for the parent cell's `breakable` field.
- pub(super) is_effectively_unbreakable: bool,
+ pub is_effectively_unbreakable: bool,
/// The horizontal offset of this rowspan in all regions.
- pub(super) dx: Abs,
+ pub dx: Abs,
/// The vertical offset of this rowspan in the first region.
- pub(super) dy: Abs,
+ pub dy: Abs,
/// The index of the first region this rowspan appears in.
- pub(super) first_region: usize,
+ pub first_region: usize,
/// The full height in the first region this rowspan appears in, for
/// relative sizing.
- pub(super) region_full: Abs,
+ pub region_full: Abs,
/// The vertical space available for this rowspan in each region.
- pub(super) heights: Vec<Abs>,
+ pub heights: Vec<Abs>,
/// The index of the largest resolved spanned row so far.
/// Once a spanned row is resolved and its height added to `heights`, this
/// number is increased. Older rows, even if repeated through e.g. a
/// header, will no longer contribute height to this rowspan.
///
/// This is `None` if no spanned rows were resolved in `finish_region` yet.
- pub(super) max_resolved_row: Option<usize>,
+ pub max_resolved_row: Option<usize>,
}
/// The output of the simulation of an unbreakable row group.
#[derive(Default)]
-pub(super) struct UnbreakableRowGroup {
+pub struct UnbreakableRowGroup {
/// The rows in this group of unbreakable rows.
/// Includes their indices and their predicted heights.
- pub(super) rows: Vec<(usize, Abs)>,
+ pub rows: Vec<(usize, Abs)>,
/// The total height of this row group.
- pub(super) height: Abs,
+ pub height: Abs,
}
/// Data used to measure a cell in an auto row.
-pub(super) struct CellMeasurementData<'layouter> {
+pub struct CellMeasurementData<'layouter> {
/// The available width for the cell across all regions.
- pub(super) width: Abs,
+ pub width: Abs,
/// The available height for the cell in its first region.
/// Infinite when the auto row is unbreakable.
- pub(super) height: Abs,
+ pub height: Abs,
/// The backlog of heights available for the cell in later regions.
///
/// When this is `None`, the `custom_backlog` field should be used instead.
/// That's because, otherwise, this field would have to contain a reference
/// to the `custom_backlog` field, which isn't possible in Rust without
/// resorting to unsafe hacks.
- pub(super) backlog: Option<&'layouter [Abs]>,
+ pub backlog: Option<&'layouter [Abs]>,
/// If the backlog needs to be built from scratch instead of reusing the
/// one at the current region, which is the case of a multi-region rowspan
/// (needs to join its backlog of already laid out heights with the current
/// backlog), then this vector will store the new backlog.
- pub(super) custom_backlog: Vec<Abs>,
+ pub custom_backlog: Vec<Abs>,
/// The full height of the first region of the cell.
/// Infinite when the auto row is unbreakable.
- pub(super) full: Abs,
+ pub full: Abs,
/// The height of the last repeated region to use in the measurement pod,
/// if any.
- pub(super) last: Option<Abs>,
+ pub last: Option<Abs>,
/// The total height of previous rows spanned by the cell in the current
/// region (so far).
- pub(super) height_in_this_region: Abs,
+ pub height_in_this_region: Abs,
/// The amount of previous regions spanned by the cell.
/// They are skipped for measurement purposes.
- pub(super) frames_in_previous_regions: usize,
+ pub frames_in_previous_regions: usize,
}
impl<'a> GridLayouter<'a> {
@@ -95,7 +95,7 @@ impl<'a> GridLayouter<'a> {
/// We need to do this only once we already know the heights of all
/// spanned rows, which is only possible after laying out the last row
/// spanned by the rowspan (or some row immediately after the last one).
- pub(super) fn layout_rowspan(
+ pub fn layout_rowspan(
&mut self,
rowspan_data: Rowspan,
current_region_data: Option<(&mut Frame, &[RowPiece])>,
@@ -184,7 +184,7 @@ impl<'a> GridLayouter<'a> {
/// Checks if a row contains the beginning of one or more rowspan cells.
/// If so, adds them to the rowspans vector.
- pub(super) fn check_for_rowspans(&mut self, disambiguator: usize, y: usize) {
+ pub fn check_for_rowspans(&mut self, disambiguator: usize, y: usize) {
// We will compute the horizontal offset of each rowspan in advance.
// For that reason, we must reverse the column order when using RTL.
let offsets = points(self.rcols.iter().copied().rev_if(self.is_rtl));
@@ -219,7 +219,7 @@ impl<'a> GridLayouter<'a> {
/// unbreakable row group, and, if so, advances regions until there is
/// enough space for them. This can be needed, for example, if there's an
/// unbreakable rowspan crossing those rows.
- pub(super) fn check_for_unbreakable_rows(
+ pub fn check_for_unbreakable_rows(
&mut self,
current_row: usize,
engine: &mut Engine,
@@ -292,7 +292,7 @@ impl<'a> GridLayouter<'a> {
///
/// This is used to figure out how much height the next unbreakable row
/// group (if any) needs.
- pub(super) fn simulate_unbreakable_row_group(
+ pub fn simulate_unbreakable_row_group(
&self,
first_row: usize,
amount_unbreakable_rows: Option<usize>,
@@ -359,7 +359,7 @@ impl<'a> GridLayouter<'a> {
/// Checks if one or more of the cells at the given row are unbreakable.
/// If so, returns the largest rowspan among the unbreakable cells;
/// the spanned rows must, as a result, be laid out in the same region.
- pub(super) fn check_for_unbreakable_cells(&self, y: usize) -> usize {
+ pub fn check_for_unbreakable_cells(&self, y: usize) -> usize {
(0..self.grid.cols.len())
.filter_map(|x| self.grid.cell(x, y))
.filter(|cell| !cell.breakable)
@@ -369,7 +369,7 @@ impl<'a> GridLayouter<'a> {
}
/// Used by `measure_auto_row` to gather data needed to measure the cell.
- pub(super) fn prepare_auto_row_cell_measurement(
+ pub fn prepare_auto_row_cell_measurement(
&self,
parent: Axes<usize>,
cell: &Cell,
@@ -577,7 +577,7 @@ impl<'a> GridLayouter<'a> {
/// expand the auto row based on the rowspan's demanded size, or `false`
/// otherwise.
#[allow(clippy::too_many_arguments)]
- pub(super) fn prepare_rowspan_sizes(
+ pub fn prepare_rowspan_sizes(
&self,
auto_row_y: usize,
sizes: &mut Vec<Abs>,
@@ -667,7 +667,7 @@ impl<'a> GridLayouter<'a> {
/// in each region and the pending rowspans' data (parent Y, rowspan amount
/// and vector of requested sizes).
#[allow(clippy::too_many_arguments)]
- pub(super) fn simulate_and_measure_rowspans_in_auto_row(
+ pub fn simulate_and_measure_rowspans_in_auto_row(
&self,
y: usize,
resolved: &mut Vec<Abs>,
diff --git a/crates/typst-layout/src/image.rs b/crates/typst-layout/src/image.rs
new file mode 100644
index 00000000..84a60282
--- /dev/null
+++ b/crates/typst-layout/src/image.rs
@@ -0,0 +1,142 @@
+use std::ffi::OsStr;
+
+use typst_library::diag::{bail, warning, At, SourceResult, StrResult};
+use typst_library::engine::Engine;
+use typst_library::foundations::{Packed, Smart, StyleChain};
+use typst_library::introspection::Locator;
+use typst_library::layout::{
+ Abs, Axes, FixedAlignment, Frame, FrameItem, Point, Region, Size,
+};
+use typst_library::loading::Readable;
+use typst_library::text::families;
+use typst_library::visualize::{
+ Image, ImageElem, ImageFit, ImageFormat, Path, RasterFormat, VectorFormat,
+};
+
+/// Layout the image.
+#[typst_macros::time(span = elem.span())]
+pub fn layout_image(
+ elem: &Packed<ImageElem>,
+ engine: &mut Engine,
+ _: Locator,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ let span = elem.span();
+
+ // Take the format that was explicitly defined, or parse the extension,
+ // or try to detect the format.
+ let data = elem.data();
+ let format = match elem.format(styles) {
+ Smart::Custom(v) => v,
+ Smart::Auto => determine_format(elem.path().as_str(), data).at(span)?,
+ };
+
+ // Warn the user if the image contains a foreign object. Not perfect
+ // because the svg could also be encoded, but that's an edge case.
+ if format == ImageFormat::Vector(VectorFormat::Svg) {
+ let has_foreign_object =
+ data.as_str().is_some_and(|s| s.contains("<foreignObject"));
+
+ if has_foreign_object {
+ engine.sink.warn(warning!(
+ span,
+ "image contains foreign object";
+ hint: "SVG images with foreign objects might render incorrectly in typst";
+ hint: "see https://github.com/typst/typst/issues/1421 for more information"
+ ));
+ }
+ }
+
+ // Construct the image itself.
+ let image = Image::with_fonts(
+ data.clone().into(),
+ format,
+ elem.alt(styles),
+ engine.world,
+ &families(styles).collect::<Vec<_>>(),
+ )
+ .at(span)?;
+
+ // Determine the image's pixel aspect ratio.
+ let pxw = image.width();
+ let pxh = image.height();
+ let px_ratio = pxw / pxh;
+
+ // Determine the region's aspect ratio.
+ let region_ratio = region.size.x / region.size.y;
+
+ // Find out whether the image is wider or taller than the region.
+ let wide = px_ratio > region_ratio;
+
+ // The space into which the image will be placed according to its fit.
+ let target = if region.expand.x && region.expand.y {
+ // If both width and height are forced, take them.
+ region.size
+ } else if region.expand.x {
+ // If just width is forced, take it.
+ Size::new(region.size.x, region.size.y.min(region.size.x / px_ratio))
+ } else if region.expand.y {
+ // If just height is forced, take it.
+ Size::new(region.size.x.min(region.size.y * px_ratio), region.size.y)
+ } else {
+ // If neither is forced, take the natural image size at the image's
+ // DPI bounded by the available space.
+ let dpi = image.dpi().unwrap_or(Image::DEFAULT_DPI);
+ let natural = Axes::new(pxw, pxh).map(|v| Abs::inches(v / dpi));
+ Size::new(
+ natural.x.min(region.size.x).min(region.size.y * px_ratio),
+ natural.y.min(region.size.y).min(region.size.x / px_ratio),
+ )
+ };
+
+ // Compute the actual size of the fitted image.
+ let fit = elem.fit(styles);
+ let fitted = match fit {
+ ImageFit::Cover | ImageFit::Contain => {
+ if wide == (fit == ImageFit::Contain) {
+ Size::new(target.x, target.x / px_ratio)
+ } else {
+ Size::new(target.y * px_ratio, target.y)
+ }
+ }
+ ImageFit::Stretch => target,
+ };
+
+ // First, place the image in a frame of exactly its size and then resize
+ // the frame to the target size, center aligning the image in the
+ // process.
+ let mut frame = Frame::soft(fitted);
+ frame.push(Point::zero(), FrameItem::Image(image, fitted, span));
+ frame.resize(target, Axes::splat(FixedAlignment::Center));
+
+ // Create a clipping group if only part of the image should be visible.
+ if fit == ImageFit::Cover && !target.fits(fitted) {
+ frame.clip(Path::rect(frame.size()));
+ }
+
+ Ok(frame)
+}
+
+/// Determine the image format based on path and data.
+fn determine_format(path: &str, data: &Readable) -> StrResult<ImageFormat> {
+ let ext = std::path::Path::new(path)
+ .extension()
+ .and_then(OsStr::to_str)
+ .unwrap_or_default()
+ .to_lowercase();
+
+ Ok(match ext.as_str() {
+ "png" => ImageFormat::Raster(RasterFormat::Png),
+ "jpg" | "jpeg" => ImageFormat::Raster(RasterFormat::Jpg),
+ "gif" => ImageFormat::Raster(RasterFormat::Gif),
+ "svg" | "svgz" => ImageFormat::Vector(VectorFormat::Svg),
+ _ => match &data {
+ Readable::Str(_) => ImageFormat::Vector(VectorFormat::Svg),
+ Readable::Bytes(bytes) => match RasterFormat::detect(bytes) {
+ Some(f) => ImageFormat::Raster(f),
+ None => bail!("unknown image format"),
+ },
+ },
+ })
+}
diff --git a/crates/typst-layout/src/inline/box.rs b/crates/typst-layout/src/inline/box.rs
new file mode 100644
index 00000000..30572e4e
--- /dev/null
+++ b/crates/typst-layout/src/inline/box.rs
@@ -0,0 +1,87 @@
+use once_cell::unsync::Lazy;
+use typst_library::diag::SourceResult;
+use typst_library::engine::Engine;
+use typst_library::foundations::{Packed, StyleChain};
+use typst_library::introspection::Locator;
+use typst_library::layout::{BoxElem, Frame, FrameKind, Size};
+use typst_library::visualize::Stroke;
+use typst_utils::Numeric;
+
+use crate::flow::unbreakable_pod;
+use crate::shapes::{clip_rect, fill_and_stroke};
+
+/// Lay out a box as part of a paragraph.
+#[typst_macros::time(name = "box", span = elem.span())]
+pub fn layout_box(
+ elem: &Packed<BoxElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Size,
+) -> SourceResult<Frame> {
+ // Fetch sizing properties.
+ let width = elem.width(styles);
+ let height = elem.height(styles);
+ let inset = elem.inset(styles).unwrap_or_default();
+
+ // Build the pod region.
+ let pod = unbreakable_pod(&width, &height.into(), &inset, styles, region);
+
+ // Layout the body.
+ let mut frame = match elem.body(styles) {
+ // If we have no body, just create an empty frame. If necessary,
+ // its size will be adjusted below.
+ None => Frame::hard(Size::zero()),
+
+ // If we have a child, layout it into the body. Boxes are boundaries
+ // for gradient relativeness, so we set the `FrameKind` to `Hard`.
+ Some(body) => crate::layout_frame(engine, body, locator, styles, pod)?
+ .with_kind(FrameKind::Hard),
+ };
+
+ // Enforce a correct frame size on the expanded axes. Do this before
+ // applying the inset, since the pod shrunk.
+ frame.set_size(pod.expand.select(pod.size, frame.size()));
+
+ // Apply the inset.
+ if !inset.is_zero() {
+ crate::pad::grow(&mut frame, &inset);
+ }
+
+ // Prepare fill and stroke.
+ let fill = elem.fill(styles);
+ let stroke = elem
+ .stroke(styles)
+ .unwrap_or_default()
+ .map(|s| s.map(Stroke::unwrap_or_default));
+
+ // Only fetch these if necessary (for clipping or filling/stroking).
+ let outset = Lazy::new(|| elem.outset(styles).unwrap_or_default());
+ let radius = Lazy::new(|| elem.radius(styles).unwrap_or_default());
+
+ // Clip the contents, if requested.
+ if elem.clip(styles) {
+ let size = frame.size() + outset.relative_to(frame.size()).sum_by_axis();
+ frame.clip(clip_rect(size, &radius, &stroke));
+ }
+
+ // Add fill and/or stroke.
+ if fill.is_some() || stroke.iter().any(Option::is_some) {
+ fill_and_stroke(&mut frame, fill, &stroke, &outset, &radius, elem.span());
+ }
+
+ // Assign label to the frame.
+ if let Some(label) = elem.label() {
+ frame.label(label);
+ }
+
+ // Apply baseline shift. Do this after setting the size and applying the
+ // inset, so that a relative shift is resolved relative to the final
+ // height.
+ let shift = elem.baseline(styles).relative_to(frame.height());
+ if !shift.is_zero() {
+ frame.set_baseline(frame.baseline() - shift);
+ }
+
+ Ok(frame)
+}
diff --git a/crates/typst/src/layout/inline/collect.rs b/crates/typst-layout/src/inline/collect.rs
index af14b152..fbcddee5 100644
--- a/crates/typst/src/layout/inline/collect.rs
+++ b/crates/typst-layout/src/inline/collect.rs
@@ -1,17 +1,18 @@
-use super::*;
-use crate::diag::bail;
-use crate::foundations::{Packed, Resolve};
-use crate::introspection::{SplitLocator, Tag, TagElem};
-use crate::layout::{
+use typst_library::diag::bail;
+use typst_library::foundations::{Packed, Resolve};
+use typst_library::introspection::{SplitLocator, Tag, TagElem};
+use typst_library::layout::{
Abs, AlignElem, BoxElem, Dir, Fr, Frame, HElem, InlineElem, InlineItem, Sizing,
Spacing,
};
-use crate::syntax::Span;
-use crate::text::{
+use typst_library::text::{
is_default_ignorable, LinebreakElem, SmartQuoteElem, SmartQuoter, SmartQuotes,
SpaceElem, TextElem,
};
-use crate::utils::Numeric;
+use typst_syntax::Span;
+use typst_utils::Numeric;
+
+use super::*;
// The characters by which spacing, inline content and pins are replaced in the
// paragraph's full text.
@@ -222,7 +223,7 @@ pub fn collect<'a>(
if let Sizing::Fr(v) = elem.width(styles) {
collector.push_item(Item::Fractional(v, Some((elem, loc, styles))));
} else {
- let frame = elem.layout(engine, loc, styles, region)?;
+ let frame = layout_box(elem, engine, loc, styles, region)?;
collector.push_item(Item::Frame(frame, styles));
}
} else if let Some(elem) = child.to_packed::<TagElem>() {
diff --git a/crates/typst-layout/src/inline/deco.rs b/crates/typst-layout/src/inline/deco.rs
new file mode 100644
index 00000000..c01b369b
--- /dev/null
+++ b/crates/typst-layout/src/inline/deco.rs
@@ -0,0 +1,213 @@
+use kurbo::{BezPath, Line, ParamCurve};
+use ttf_parser::{GlyphId, OutlineBuilder};
+use typst_library::layout::{Abs, Em, Frame, FrameItem, Point, Size};
+use typst_library::text::{
+ BottomEdge, DecoLine, Decoration, TextEdgeBounds, TextItem, TopEdge,
+};
+use typst_library::visualize::{FixedStroke, Geometry};
+use typst_syntax::Span;
+
+use crate::shapes::styled_rect;
+
+/// Add line decorations to a single run of shaped text.
+pub fn decorate(
+ frame: &mut Frame,
+ deco: &Decoration,
+ text: &TextItem,
+ width: Abs,
+ shift: Abs,
+ pos: Point,
+) {
+ let font_metrics = text.font.metrics();
+
+ if let DecoLine::Highlight { fill, stroke, top_edge, bottom_edge, radius } =
+ &deco.line
+ {
+ let (top, bottom) = determine_edges(text, *top_edge, *bottom_edge);
+ let size = Size::new(width + 2.0 * deco.extent, top + bottom);
+ let rects = styled_rect(size, radius, fill.clone(), stroke);
+ let origin = Point::new(pos.x - deco.extent, pos.y - top - shift);
+ frame.prepend_multiple(
+ rects
+ .into_iter()
+ .map(|shape| (origin, FrameItem::Shape(shape, Span::detached()))),
+ );
+ return;
+ }
+
+ let (stroke, metrics, offset, evade, background) = match &deco.line {
+ DecoLine::Strikethrough { stroke, offset, background } => {
+ (stroke, font_metrics.strikethrough, offset, false, *background)
+ }
+ DecoLine::Overline { stroke, offset, evade, background } => {
+ (stroke, font_metrics.overline, offset, *evade, *background)
+ }
+ DecoLine::Underline { stroke, offset, evade, background } => {
+ (stroke, font_metrics.underline, offset, *evade, *background)
+ }
+ _ => return,
+ };
+
+ let offset = offset.unwrap_or(-metrics.position.at(text.size)) - shift;
+ let stroke = stroke.clone().unwrap_or(FixedStroke::from_pair(
+ text.fill.as_decoration(),
+ metrics.thickness.at(text.size),
+ ));
+
+ let gap_padding = 0.08 * text.size;
+ let min_width = 0.162 * text.size;
+
+ let start = pos.x - deco.extent;
+ let end = pos.x + width + deco.extent;
+
+ let mut push_segment = |from: Abs, to: Abs, prepend: bool| {
+ let origin = Point::new(from, pos.y + offset);
+ let target = Point::new(to - from, Abs::zero());
+
+ if target.x >= min_width || !evade {
+ let shape = Geometry::Line(target).stroked(stroke.clone());
+
+ if prepend {
+ frame.prepend(origin, FrameItem::Shape(shape, Span::detached()));
+ } else {
+ frame.push(origin, FrameItem::Shape(shape, Span::detached()));
+ }
+ }
+ };
+
+ if !evade {
+ push_segment(start, end, background);
+ return;
+ }
+
+ let line = Line::new(
+ kurbo::Point::new(pos.x.to_raw(), offset.to_raw()),
+ kurbo::Point::new((pos.x + width).to_raw(), offset.to_raw()),
+ );
+
+ let mut x = pos.x;
+ let mut intersections = vec![];
+
+ for glyph in text.glyphs.iter() {
+ let dx = glyph.x_offset.at(text.size) + x;
+ let mut builder =
+ BezPathBuilder::new(font_metrics.units_per_em, text.size, dx.to_raw());
+
+ let bbox = text.font.ttf().outline_glyph(GlyphId(glyph.id), &mut builder);
+ let path = builder.finish();
+
+ x += glyph.x_advance.at(text.size);
+
+ // Only do the costly segments intersection test if the line
+ // intersects the bounding box.
+ let intersect = bbox.is_some_and(|bbox| {
+ let y_min = -text.font.to_em(bbox.y_max).at(text.size);
+ let y_max = -text.font.to_em(bbox.y_min).at(text.size);
+ offset >= y_min && offset <= y_max
+ });
+
+ if intersect {
+ // Find all intersections of segments with the line.
+ intersections.extend(
+ path.segments()
+ .flat_map(|seg| seg.intersect_line(line))
+ .map(|is| Abs::raw(line.eval(is.line_t).x)),
+ );
+ }
+ }
+
+ // Add start and end points, taking padding into account.
+ intersections.push(start - gap_padding);
+ intersections.push(end + gap_padding);
+ // When emitting the decorative line segments, we move from left to
+ // right. The intersections are not necessarily in this order, yet.
+ intersections.sort();
+
+ for edge in intersections.windows(2) {
+ let l = edge[0];
+ let r = edge[1];
+
+ // If we are too close, don't draw the segment
+ if r - l < gap_padding {
+ continue;
+ } else {
+ push_segment(l + gap_padding, r - gap_padding, background);
+ }
+ }
+}
+
+// Return the top/bottom edge of the text given the metric of the font.
+fn determine_edges(
+ text: &TextItem,
+ top_edge: TopEdge,
+ bottom_edge: BottomEdge,
+) -> (Abs, Abs) {
+ let mut top = Abs::zero();
+ let mut bottom = Abs::zero();
+
+ for g in text.glyphs.iter() {
+ let (t, b) = text.font.edges(
+ top_edge,
+ bottom_edge,
+ text.size,
+ TextEdgeBounds::Glyph(g.id),
+ );
+ top.set_max(t);
+ bottom.set_max(b);
+ }
+
+ (top, bottom)
+}
+
+/// Builds a kurbo [`BezPath`] for a glyph.
+struct BezPathBuilder {
+ path: BezPath,
+ units_per_em: f64,
+ font_size: Abs,
+ x_offset: f64,
+}
+
+impl BezPathBuilder {
+ fn new(units_per_em: f64, font_size: Abs, x_offset: f64) -> Self {
+ Self {
+ path: BezPath::new(),
+ units_per_em,
+ font_size,
+ x_offset,
+ }
+ }
+
+ fn finish(self) -> BezPath {
+ self.path
+ }
+
+ fn p(&self, x: f32, y: f32) -> kurbo::Point {
+ kurbo::Point::new(self.s(x) + self.x_offset, -self.s(y))
+ }
+
+ fn s(&self, v: f32) -> f64 {
+ Em::from_units(v, self.units_per_em).at(self.font_size).to_raw()
+ }
+}
+
+impl OutlineBuilder for BezPathBuilder {
+ fn move_to(&mut self, x: f32, y: f32) {
+ self.path.move_to(self.p(x, y));
+ }
+
+ fn line_to(&mut self, x: f32, y: f32) {
+ self.path.line_to(self.p(x, y));
+ }
+
+ fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
+ self.path.quad_to(self.p(x1, y1), self.p(x, y));
+ }
+
+ fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
+ self.path.curve_to(self.p(x1, y1), self.p(x2, y2), self.p(x, y));
+ }
+
+ fn close(&mut self) {
+ self.path.close_path();
+ }
+}
diff --git a/crates/typst/src/layout/inline/finalize.rs b/crates/typst-layout/src/inline/finalize.rs
index 082e3613..599ace9d 100644
--- a/crates/typst/src/layout/inline/finalize.rs
+++ b/crates/typst-layout/src/inline/finalize.rs
@@ -1,6 +1,7 @@
+use typst_library::introspection::SplitLocator;
+use typst_utils::Numeric;
+
use super::*;
-use crate::introspection::SplitLocator;
-use crate::utils::Numeric;
/// Turns the selected lines into frames.
#[typst_macros::time]
diff --git a/crates/typst/src/layout/inline/line.rs b/crates/typst-layout/src/inline/line.rs
index a512c32d..596e109e 100644
--- a/crates/typst/src/layout/inline/line.rs
+++ b/crates/typst-layout/src/inline/line.rs
@@ -1,14 +1,15 @@
use std::fmt::{self, Debug, Formatter};
use std::ops::{Deref, DerefMut};
+use typst_library::engine::Engine;
+use typst_library::foundations::NativeElement;
+use typst_library::introspection::{SplitLocator, Tag};
+use typst_library::layout::{Abs, Dir, Em, Fr, Frame, FrameItem, Point};
+use typst_library::model::{ParLine, ParLineMarker};
+use typst_library::text::{Lang, TextElem};
+use typst_utils::Numeric;
+
use super::*;
-use crate::engine::Engine;
-use crate::foundations::NativeElement;
-use crate::introspection::{SplitLocator, Tag};
-use crate::layout::{Abs, Dir, Em, Fr, Frame, FrameItem, Point};
-use crate::model::{ParLine, ParLineMarker};
-use crate::text::{Lang, TextElem};
-use crate::utils::Numeric;
const SHY: char = '\u{ad}';
const HYPHEN: char = '-';
@@ -510,7 +511,7 @@ pub fn commit(
if let Some((elem, loc, styles)) = elem {
let region = Size::new(amount, full);
let mut frame =
- elem.layout(engine, loc.relayout(), *styles, region)?;
+ layout_box(elem, engine, loc.relayout(), *styles, region)?;
frame.translate(Point::with_y(TextElem::baseline_in(*styles)));
push(&mut offset, frame.post_processed(*styles));
} else {
@@ -590,7 +591,7 @@ fn add_par_line_marker(
// where line numbers can be displayed), so we just need it to be in a tag
// and to be valid (to have a location).
let mut marker = ParLineMarker::new(numbering, align, margin, clearance).pack();
- let key = crate::utils::hash128(&marker);
+ let key = typst_utils::hash128(&marker);
let loc = locator.next_location(engine.introspector, key);
marker.set_location(loc);
diff --git a/crates/typst/src/layout/inline/linebreak.rs b/crates/typst-layout/src/inline/linebreak.rs
index aa62d487..7fc8b368 100644
--- a/crates/typst/src/layout/inline/linebreak.rs
+++ b/crates/typst-layout/src/inline/linebreak.rs
@@ -8,14 +8,14 @@ use icu_provider_adapters::fork::ForkByKeyProvider;
use icu_provider_blob::BlobDataProvider;
use icu_segmenter::LineSegmenter;
use once_cell::sync::Lazy;
+use typst_library::engine::Engine;
+use typst_library::layout::{Abs, Em};
+use typst_library::model::Linebreaks;
+use typst_library::text::{is_default_ignorable, Lang, TextElem};
+use typst_syntax::link_prefix;
use unicode_segmentation::UnicodeSegmentation;
use super::*;
-use crate::engine::Engine;
-use crate::layout::{Abs, Em};
-use crate::model::Linebreaks;
-use crate::syntax::link_prefix;
-use crate::text::{is_default_ignorable, Lang, TextElem};
/// The cost of a line or paragraph layout.
type Cost = f64;
diff --git a/crates/typst/src/layout/inline/mod.rs b/crates/typst-layout/src/inline/mod.rs
index 275cb332..658e3084 100644
--- a/crates/typst/src/layout/inline/mod.rs
+++ b/crates/typst-layout/src/inline/mod.rs
@@ -1,13 +1,27 @@
+#[path = "box.rs"]
+mod box_;
mod collect;
+mod deco;
mod finalize;
mod line;
mod linebreak;
mod prepare;
mod shaping;
+pub use self::box_::layout_box;
+
use comemo::{Track, Tracked, TrackedMut};
+use typst_library::diag::SourceResult;
+use typst_library::engine::{Engine, Route, Sink, Traced};
+use typst_library::foundations::{StyleChain, StyleVec};
+use typst_library::introspection::{Introspector, Locator, LocatorLink};
+use typst_library::layout::{Fragment, Size};
+use typst_library::model::ParElem;
+use typst_library::routines::Routines;
+use typst_library::World;
use self::collect::{collect, Item, Segment, SpanMapper};
+use self::deco::decorate;
use self::finalize::finalize;
use self::line::{commit, line, Line};
use self::linebreak::{linebreak, Breakpoint};
@@ -16,19 +30,12 @@ use self::shaping::{
cjk_punct_style, is_of_cj_script, shape_range, ShapedGlyph, ShapedText,
BEGIN_PUNCT_PAT, END_PUNCT_PAT,
};
-use crate::diag::SourceResult;
-use crate::engine::{Engine, Route, Sink, Traced};
-use crate::foundations::{StyleChain, StyleVec};
-use crate::introspection::{Introspector, Locator, LocatorLink};
-use crate::layout::{Fragment, Size};
-use crate::model::ParElem;
-use crate::World;
/// Range of a substring of text.
type Range = std::ops::Range<usize>;
/// Layouts content inline.
-pub(crate) fn layout_inline(
+pub fn layout_inline(
engine: &mut Engine,
children: &StyleVec,
locator: Locator,
@@ -39,6 +46,7 @@ pub(crate) fn layout_inline(
) -> SourceResult<Fragment> {
layout_inline_impl(
children,
+ engine.routines,
engine.world,
engine.introspector,
engine.traced,
@@ -57,6 +65,7 @@ pub(crate) fn layout_inline(
#[allow(clippy::too_many_arguments)]
fn layout_inline_impl(
children: &StyleVec,
+ routines: &Routines,
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
@@ -71,6 +80,7 @@ fn layout_inline_impl(
let link = LocatorLink::new(locator);
let locator = Locator::link(&link);
let mut engine = Engine {
+ routines,
world,
introspector,
traced,
diff --git a/crates/typst/src/layout/inline/prepare.rs b/crates/typst-layout/src/inline/prepare.rs
index 3ac155b5..2dd79aec 100644
--- a/crates/typst/src/layout/inline/prepare.rs
+++ b/crates/typst-layout/src/inline/prepare.rs
@@ -1,10 +1,10 @@
+use typst_library::foundations::{Resolve, Smart};
+use typst_library::layout::{Abs, AlignElem, Dir, Em, FixedAlignment};
+use typst_library::model::Linebreaks;
+use typst_library::text::{Costs, Lang, TextElem};
use unicode_bidi::{BidiInfo, Level as BidiLevel};
use super::*;
-use crate::foundations::{Resolve, Smart};
-use crate::layout::{Abs, AlignElem, Dir, Em, FixedAlignment};
-use crate::model::Linebreaks;
-use crate::text::{Costs, Lang, TextElem};
/// A paragraph representation in which children are already layouted and text
/// is already preshaped.
diff --git a/crates/typst/src/layout/inline/shaping.rs b/crates/typst-layout/src/inline/shaping.rs
index 5b95dadb..bd803b52 100644
--- a/crates/typst/src/layout/inline/shaping.rs
+++ b/crates/typst-layout/src/inline/shaping.rs
@@ -7,19 +7,19 @@ use az::SaturatingAs;
use ecow::EcoString;
use rustybuzz::{BufferFlags, ShapePlan, UnicodeBuffer};
use ttf_parser::Tag;
+use typst_library::engine::Engine;
+use typst_library::foundations::{Smart, StyleChain};
+use typst_library::layout::{Abs, Dir, Em, Frame, FrameItem, Point, Size};
+use typst_library::text::{
+ families, features, is_default_ignorable, variant, Font, FontVariant, Glyph, Lang,
+ Region, TextEdgeBounds, TextElem, TextItem,
+};
+use typst_library::World;
+use typst_utils::SliceExt;
use unicode_bidi::{BidiInfo, Level as BidiLevel};
use unicode_script::{Script, UnicodeScript};
-use super::{Item, Range, SpanMapper};
-use crate::engine::Engine;
-use crate::foundations::{Smart, StyleChain};
-use crate::layout::{Abs, Dir, Em, Frame, FrameItem, Point, Size};
-use crate::text::{
- decorate, families, features, is_default_ignorable, variant, Font, FontVariant,
- Glyph, Lang, Region, TextEdgeBounds, TextElem, TextItem,
-};
-use crate::utils::SliceExt;
-use crate::World;
+use super::{decorate, Item, Range, SpanMapper};
/// The result of shaping text.
///
diff --git a/crates/typst-layout/src/lib.rs b/crates/typst-layout/src/lib.rs
new file mode 100644
index 00000000..7069fc4d
--- /dev/null
+++ b/crates/typst-layout/src/lib.rs
@@ -0,0 +1,30 @@
+//! Typst's layout engine.
+
+mod flow;
+mod grid;
+mod image;
+mod inline;
+mod lists;
+mod math;
+mod pad;
+mod pages;
+mod repeat;
+mod shapes;
+mod stack;
+mod transforms;
+
+pub use self::flow::{layout_columns, layout_fragment, layout_frame};
+pub use self::grid::{layout_grid, layout_table};
+pub use self::image::layout_image;
+pub use self::inline::{layout_box, layout_inline};
+pub use self::lists::{layout_enum, layout_list};
+pub use self::math::{layout_equation_block, layout_equation_inline};
+pub use self::pad::layout_pad;
+pub use self::pages::layout_document;
+pub use self::repeat::layout_repeat;
+pub use self::shapes::{
+ layout_circle, layout_ellipse, layout_line, layout_path, layout_polygon, layout_rect,
+ layout_square,
+};
+pub use self::stack::layout_stack;
+pub use self::transforms::{layout_move, layout_rotate, layout_scale, layout_skew};
diff --git a/crates/typst-layout/src/lists.rs b/crates/typst-layout/src/lists.rs
new file mode 100644
index 00000000..08c2a2f4
--- /dev/null
+++ b/crates/typst-layout/src/lists.rs
@@ -0,0 +1,146 @@
+use comemo::Track;
+use smallvec::smallvec;
+use typst_library::diag::SourceResult;
+use typst_library::engine::Engine;
+use typst_library::foundations::{Content, Context, Depth, Packed, StyleChain};
+use typst_library::introspection::Locator;
+use typst_library::layout::{Axes, Fragment, HAlignment, Regions, Sizing, VAlignment};
+use typst_library::model::{EnumElem, ListElem, Numbering, ParElem};
+use typst_library::text::TextElem;
+
+use crate::grid::{Cell, CellGrid, GridLayouter};
+
+/// Layout the list.
+#[typst_macros::time(span = elem.span())]
+pub fn layout_list(
+ elem: &Packed<ListElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+) -> SourceResult<Fragment> {
+ let indent = elem.indent(styles);
+ let body_indent = elem.body_indent(styles);
+ let gutter = elem.spacing(styles).unwrap_or_else(|| {
+ if elem.tight(styles) {
+ ParElem::leading_in(styles).into()
+ } else {
+ ParElem::spacing_in(styles).into()
+ }
+ });
+
+ let Depth(depth) = ListElem::depth_in(styles);
+ let marker = elem
+ .marker(styles)
+ .resolve(engine, styles, depth)?
+ // avoid '#set align' interference with the list
+ .aligned(HAlignment::Start + VAlignment::Top);
+
+ let mut cells = vec![];
+ let mut locator = locator.split();
+
+ for item in elem.children() {
+ cells.push(Cell::new(Content::empty(), locator.next(&())));
+ cells.push(Cell::new(marker.clone(), locator.next(&marker.span())));
+ cells.push(Cell::new(Content::empty(), locator.next(&())));
+ cells.push(Cell::new(
+ item.body.clone().styled(ListElem::set_depth(Depth(1))),
+ locator.next(&item.body.span()),
+ ));
+ }
+
+ let grid = CellGrid::new(
+ Axes::with_x(&[
+ Sizing::Rel(indent.into()),
+ Sizing::Auto,
+ Sizing::Rel(body_indent.into()),
+ Sizing::Auto,
+ ]),
+ Axes::with_y(&[gutter.into()]),
+ cells,
+ );
+ let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
+
+ layouter.layout(engine)
+}
+
+/// Layout the enumeration.
+#[typst_macros::time(span = elem.span())]
+pub fn layout_enum(
+ elem: &Packed<EnumElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+) -> SourceResult<Fragment> {
+ let numbering = elem.numbering(styles);
+ let indent = elem.indent(styles);
+ let body_indent = elem.body_indent(styles);
+ let gutter = elem.spacing(styles).unwrap_or_else(|| {
+ if elem.tight(styles) {
+ ParElem::leading_in(styles).into()
+ } else {
+ ParElem::spacing_in(styles).into()
+ }
+ });
+
+ let mut cells = vec![];
+ let mut locator = locator.split();
+ let mut number = elem.start(styles);
+ let mut parents = EnumElem::parents_in(styles);
+
+ let full = elem.full(styles);
+
+ // Horizontally align based on the given respective parameter.
+ // Vertically align to the top to avoid inheriting `horizon` or `bottom`
+ // alignment from the context and having the number be displaced in
+ // relation to the item it refers to.
+ let number_align = elem.number_align(styles);
+
+ for item in elem.children() {
+ number = item.number(styles).unwrap_or(number);
+
+ let context = Context::new(None, Some(styles));
+ let resolved = if full {
+ parents.push(number);
+ let content = numbering.apply(engine, context.track(), &parents)?.display();
+ parents.pop();
+ content
+ } else {
+ match numbering {
+ Numbering::Pattern(pattern) => {
+ TextElem::packed(pattern.apply_kth(parents.len(), number))
+ }
+ other => other.apply(engine, context.track(), &[number])?.display(),
+ }
+ };
+
+ // Disable overhang as a workaround to end-aligned dots glitching
+ // and decreasing spacing between numbers and items.
+ let resolved =
+ resolved.aligned(number_align).styled(TextElem::set_overhang(false));
+
+ cells.push(Cell::new(Content::empty(), locator.next(&())));
+ cells.push(Cell::new(resolved, locator.next(&())));
+ cells.push(Cell::new(Content::empty(), locator.next(&())));
+ cells.push(Cell::new(
+ item.body.clone().styled(EnumElem::set_parents(smallvec![number])),
+ locator.next(&item.body.span()),
+ ));
+ number = number.saturating_add(1);
+ }
+
+ let grid = CellGrid::new(
+ Axes::with_x(&[
+ Sizing::Rel(indent.into()),
+ Sizing::Auto,
+ Sizing::Rel(body_indent.into()),
+ Sizing::Auto,
+ ]),
+ Axes::with_y(&[gutter.into()]),
+ cells,
+ );
+ let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
+
+ layouter.layout(engine)
+}
diff --git a/crates/typst-layout/src/math/accent.rs b/crates/typst-layout/src/math/accent.rs
new file mode 100644
index 00000000..9fa7a5a0
--- /dev/null
+++ b/crates/typst-layout/src/math/accent.rs
@@ -0,0 +1,75 @@
+use typst_library::diag::SourceResult;
+use typst_library::foundations::{Packed, StyleChain};
+use typst_library::layout::{Em, Frame, Point, Rel, Size};
+use typst_library::math::{Accent, AccentElem};
+
+use super::{
+ scaled_font_size, style_cramped, FrameFragment, GlyphFragment, MathContext,
+ MathFragment,
+};
+
+/// How much the accent can be shorter than the base.
+const ACCENT_SHORT_FALL: Em = Em::new(0.5);
+
+/// Lays out an [`AccentElem`].
+#[typst_macros::time(name = "math.accent", span = elem.span())]
+pub fn layout_accent(
+ elem: &Packed<AccentElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ let cramped = style_cramped();
+ let base = ctx.layout_into_fragment(elem.base(), styles.chain(&cramped))?;
+
+ // Preserve class to preserve automatic spacing.
+ let base_class = base.class();
+ let base_attach = base.accent_attach();
+
+ let width = elem
+ .size(styles)
+ .unwrap_or(Rel::one())
+ .at(scaled_font_size(ctx, styles))
+ .relative_to(base.width());
+
+ // Forcing the accent to be at least as large as the base makes it too
+ // wide in many case.
+ let Accent(c) = elem.accent();
+ let glyph = GlyphFragment::new(ctx, styles, *c, elem.span());
+ let short_fall = ACCENT_SHORT_FALL.at(glyph.font_size);
+ let variant = glyph.stretch_horizontal(ctx, width, short_fall);
+ let accent = variant.frame;
+ let accent_attach = variant.accent_attach;
+
+ // Descent is negative because the accent's ink bottom is above the
+ // baseline. Therefore, the default gap is the accent's negated descent
+ // minus the accent base height. Only if the base is very small, we need
+ // a larger gap so that the accent doesn't move too low.
+ let accent_base_height = scaled!(ctx, styles, accent_base_height);
+ let gap = -accent.descent() - base.height().min(accent_base_height);
+ let size = Size::new(base.width(), accent.height() + gap + base.height());
+ let accent_pos = Point::with_x(base_attach - accent_attach);
+ let base_pos = Point::with_y(accent.height() + gap);
+ let baseline = base_pos.y + base.ascent();
+ let base_italics_correction = base.italics_correction();
+ let base_text_like = base.is_text_like();
+
+ let base_ascent = match &base {
+ MathFragment::Frame(frame) => frame.base_ascent,
+ _ => base.ascent(),
+ };
+
+ let mut frame = Frame::soft(size);
+ frame.set_baseline(baseline);
+ frame.push_frame(accent_pos, accent);
+ frame.push_frame(base_pos, base.into_frame());
+ ctx.push(
+ FrameFragment::new(ctx, styles, frame)
+ .with_class(base_class)
+ .with_base_ascent(base_ascent)
+ .with_italics_correction(base_italics_correction)
+ .with_accent_attach(base_attach)
+ .with_text_like(base_text_like),
+ );
+
+ Ok(())
+}
diff --git a/crates/typst/src/math/attach.rs b/crates/typst-layout/src/math/attach.rs
index 9eb0c824..0f9090f7 100644
--- a/crates/typst/src/math/attach.rs
+++ b/crates/typst-layout/src/math/attach.rs
@@ -1,14 +1,16 @@
-use unicode_math_class::MathClass;
-
-use crate::diag::SourceResult;
-use crate::foundations::{elem, Content, Packed, Smart, StyleChain};
-use crate::layout::{Abs, Axis, Corner, Frame, Length, Point, Rel, Size};
-use crate::math::{
- stretch_fragment, style_for_subscript, style_for_superscript, EquationElem,
- FrameFragment, LayoutMath, MathContext, MathFragment, MathSize, Scaled, StretchElem,
+use typst_library::diag::SourceResult;
+use typst_library::foundations::{Packed, Smart, StyleChain};
+use typst_library::layout::{Abs, Axis, Corner, Frame, Length, Point, Rel, Size};
+use typst_library::math::{
+ AttachElem, EquationElem, LimitsElem, PrimesElem, ScriptsElem, StretchElem,
+};
+use typst_library::text::TextElem;
+use typst_utils::OptionExt;
+
+use super::{
+ stretch_fragment, style_for_subscript, style_for_superscript, FrameFragment, Limits,
+ MathContext, MathFragment,
};
-use crate::text::TextElem;
-use crate::utils::OptionExt;
macro_rules! measure {
($e: ident, $attr: ident) => {
@@ -16,300 +18,140 @@ macro_rules! measure {
};
}
-/// A base with optional attachments.
-///
-/// ```example
-/// $ attach(
-/// Pi, t: alpha, b: beta,
-/// tl: 1, tr: 2+3, bl: 4+5, br: 6,
-/// ) $
-/// ```
-#[elem(LayoutMath)]
-pub struct AttachElem {
- /// The base to which things are attached.
- #[required]
- pub base: Content,
-
- /// The top attachment, smartly positioned at top-right or above the base.
- ///
- /// You can wrap the base in `{limits()}` or `{scripts()}` to override the
- /// smart positioning.
- pub t: Option<Content>,
-
- /// The bottom attachment, smartly positioned at the bottom-right or below
- /// the base.
- ///
- /// You can wrap the base in `{limits()}` or `{scripts()}` to override the
- /// smart positioning.
- pub b: Option<Content>,
-
- /// The top-left attachment (before the base).
- pub tl: Option<Content>,
-
- /// The bottom-left attachment (before base).
- pub bl: Option<Content>,
-
- /// The top-right attachment (after the base).
- pub tr: Option<Content>,
-
- /// The bottom-right attachment (after the base).
- pub br: Option<Content>,
-}
+/// Lays out an [`AttachElem`].
+#[typst_macros::time(name = "math.attach", span = elem.span())]
+pub fn layout_attach(
+ elem: &Packed<AttachElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ let merged = elem.merge_base();
+ let elem = merged.as_ref().unwrap_or(elem);
+ let stretch = stretch_size(styles, elem);
+
+ let mut base = ctx.layout_into_fragment(elem.base(), styles)?;
+ let sup_style = style_for_superscript(styles);
+ let sup_style_chain = styles.chain(&sup_style);
+ let tl = elem.tl(sup_style_chain);
+ let tr = elem.tr(sup_style_chain);
+ let primed = tr.as_ref().is_some_and(|content| content.is::<PrimesElem>());
+ let t = elem.t(sup_style_chain);
+
+ let sub_style = style_for_subscript(styles);
+ let sub_style_chain = styles.chain(&sub_style);
+ let bl = elem.bl(sub_style_chain);
+ let br = elem.br(sub_style_chain);
+ let b = elem.b(sub_style_chain);
+
+ let limits = base.limits().active(styles);
+ let (t, tr) = match (t, tr) {
+ (Some(t), Some(tr)) if primed && !limits => (None, Some(tr + t)),
+ (Some(t), None) if !limits => (None, Some(t)),
+ (t, tr) => (t, tr),
+ };
+ let (b, br) = if limits || br.is_some() { (b, br) } else { (None, b) };
-impl LayoutMath for Packed<AttachElem> {
- #[typst_macros::time(name = "math.attach", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- let new_elem = merge_base(self);
- let elem = new_elem.as_ref().unwrap_or(self);
- let stretch = stretch_size(styles, elem);
-
- let mut base = ctx.layout_into_fragment(elem.base(), styles)?;
- let sup_style = style_for_superscript(styles);
- let sup_style_chain = styles.chain(&sup_style);
- let tl = elem.tl(sup_style_chain);
- let tr = elem.tr(sup_style_chain);
- let primed = tr.as_ref().is_some_and(|content| content.is::<PrimesElem>());
- let t = elem.t(sup_style_chain);
-
- let sub_style = style_for_subscript(styles);
- let sub_style_chain = styles.chain(&sub_style);
- let bl = elem.bl(sub_style_chain);
- let br = elem.br(sub_style_chain);
- let b = elem.b(sub_style_chain);
-
- let limits = base.limits().active(styles);
- let (t, tr) = match (t, tr) {
- (Some(t), Some(tr)) if primed && !limits => (None, Some(tr + t)),
- (Some(t), None) if !limits => (None, Some(t)),
- (t, tr) => (t, tr),
+ macro_rules! layout {
+ ($content:ident, $style_chain:ident) => {
+ $content
+ .map(|elem| ctx.layout_into_fragment(&elem, $style_chain))
+ .transpose()
};
- let (b, br) = if limits || br.is_some() { (b, br) } else { (None, b) };
-
- macro_rules! layout {
- ($content:ident, $style_chain:ident) => {
- $content
- .map(|elem| ctx.layout_into_fragment(&elem, $style_chain))
- .transpose()
- };
- }
-
- // Layout the top and bottom attachments early so we can measure their
- // widths, in order to calculate what the stretch size is relative to.
- let t = layout!(t, sup_style_chain)?;
- let b = layout!(b, sub_style_chain)?;
- if let Some(stretch) = stretch {
- let relative_to_width = measure!(t, width).max(measure!(b, width));
- stretch_fragment(
- ctx,
- styles,
- &mut base,
- Some(Axis::X),
- Some(relative_to_width),
- stretch,
- Abs::zero(),
- );
- }
-
- let fragments = [
- layout!(tl, sup_style_chain)?,
- t,
- layout!(tr, sup_style_chain)?,
- layout!(bl, sub_style_chain)?,
- b,
- layout!(br, sub_style_chain)?,
- ];
-
- layout_attachments(ctx, styles, base, fragments)
}
-}
-/// Grouped primes.
-///
-/// ```example
-/// $ a'''_b = a^'''_b $
-/// ```
-///
-/// # Syntax
-/// This function has dedicated syntax: use apostrophes instead of primes. They
-/// will automatically attach to the previous element, moving superscripts to
-/// the next level.
-#[elem(LayoutMath)]
-pub struct PrimesElem {
- /// The number of grouped primes.
- #[required]
- pub count: usize,
-}
-
-impl LayoutMath for Packed<PrimesElem> {
- #[typst_macros::time(name = "math.primes", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- match *self.count() {
- count @ 1..=4 => {
- let c = match count {
- 1 => '′',
- 2 => '″',
- 3 => '‴',
- 4 => '⁗',
- _ => unreachable!(),
- };
- let f = ctx.layout_into_fragment(&TextElem::packed(c), styles)?;
- ctx.push(f);
- }
- count => {
- // Custom amount of primes
- let prime = ctx
- .layout_into_fragment(&TextElem::packed('′'), styles)?
- .into_frame();
- let width = prime.width() * (count + 1) as f64 / 2.0;
- let mut frame = Frame::soft(Size::new(width, prime.height()));
- frame.set_baseline(prime.ascent());
-
- for i in 0..count {
- frame.push_frame(
- Point::new(prime.width() * (i as f64 / 2.0), Abs::zero()),
- prime.clone(),
- )
- }
- ctx.push(FrameFragment::new(ctx, styles, frame).with_text_like(true));
- }
- }
- Ok(())
- }
-}
-
-/// Forces a base to display attachments as scripts.
-///
-/// ```example
-/// $ scripts(sum)_1^2 != sum_1^2 $
-/// ```
-#[elem(LayoutMath)]
-pub struct ScriptsElem {
- /// The base to attach the scripts to.
- #[required]
- pub body: Content,
-}
-
-impl LayoutMath for Packed<ScriptsElem> {
- #[typst_macros::time(name = "math.scripts", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- let mut fragment = ctx.layout_into_fragment(self.body(), styles)?;
- fragment.set_limits(Limits::Never);
- ctx.push(fragment);
- Ok(())
+ // Layout the top and bottom attachments early so we can measure their
+ // widths, in order to calculate what the stretch size is relative to.
+ let t = layout!(t, sup_style_chain)?;
+ let b = layout!(b, sub_style_chain)?;
+ if let Some(stretch) = stretch {
+ let relative_to_width = measure!(t, width).max(measure!(b, width));
+ stretch_fragment(
+ ctx,
+ styles,
+ &mut base,
+ Some(Axis::X),
+ Some(relative_to_width),
+ stretch,
+ Abs::zero(),
+ );
}
-}
-
-/// Forces a base to display attachments as limits.
-///
-/// ```example
-/// $ limits(A)_1^2 != A_1^2 $
-/// ```
-#[elem(LayoutMath)]
-pub struct LimitsElem {
- /// The base to attach the limits to.
- #[required]
- pub body: Content,
-
- /// Whether to also force limits in inline equations.
- ///
- /// When applying limits globally (e.g., through a show rule), it is
- /// typically a good idea to disable this.
- #[default(true)]
- pub inline: bool,
-}
-impl LayoutMath for Packed<LimitsElem> {
- #[typst_macros::time(name = "math.limits", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- let limits = if self.inline(styles) { Limits::Always } else { Limits::Display };
- let mut fragment = ctx.layout_into_fragment(self.body(), styles)?;
- fragment.set_limits(limits);
- ctx.push(fragment);
- Ok(())
- }
-}
+ let fragments = [
+ layout!(tl, sup_style_chain)?,
+ t,
+ layout!(tr, sup_style_chain)?,
+ layout!(bl, sub_style_chain)?,
+ b,
+ layout!(br, sub_style_chain)?,
+ ];
-/// Describes in which situation a frame should use limits for attachments.
-#[derive(Debug, Copy, Clone)]
-pub enum Limits {
- /// Always scripts.
- Never,
- /// Display limits only in `display` math.
- Display,
- /// Always limits.
- Always,
+ layout_attachments(ctx, styles, base, fragments)
}
-impl Limits {
- /// The default limit configuration if the given character is the base.
- pub fn for_char(c: char) -> Self {
- match unicode_math_class::class(c) {
- Some(MathClass::Large) => {
- if is_integral_char(c) {
- Limits::Never
- } else {
- Limits::Display
- }
- }
- Some(MathClass::Relation) => Limits::Always,
- _ => Limits::Never,
- }
- }
-
- /// The default limit configuration for a math class.
- pub fn for_class(class: MathClass) -> Self {
- match class {
- MathClass::Large => Self::Display,
- MathClass::Relation => Self::Always,
- _ => Self::Never,
+/// Lays out a [`PrimeElem`].
+#[typst_macros::time(name = "math.primes", span = elem.span())]
+pub fn layout_primes(
+ elem: &Packed<PrimesElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ match *elem.count() {
+ count @ 1..=4 => {
+ let c = match count {
+ 1 => '′',
+ 2 => '″',
+ 3 => '‴',
+ 4 => '⁗',
+ _ => unreachable!(),
+ };
+ let f = ctx.layout_into_fragment(&TextElem::packed(c), styles)?;
+ ctx.push(f);
}
- }
-
- /// Whether limits should be displayed in this context.
- pub fn active(&self, styles: StyleChain) -> bool {
- match self {
- Self::Always => true,
- Self::Display => EquationElem::size_in(styles) == MathSize::Display,
- Self::Never => false,
+ count => {
+ // Custom amount of primes
+ let prime =
+ ctx.layout_into_fragment(&TextElem::packed('′'), styles)?.into_frame();
+ let width = prime.width() * (count + 1) as f64 / 2.0;
+ let mut frame = Frame::soft(Size::new(width, prime.height()));
+ frame.set_baseline(prime.ascent());
+
+ for i in 0..count {
+ frame.push_frame(
+ Point::new(prime.width() * (i as f64 / 2.0), Abs::zero()),
+ prime.clone(),
+ )
+ }
+ ctx.push(FrameFragment::new(ctx, styles, frame).with_text_like(true));
}
}
+ Ok(())
}
-/// If an AttachElem's base is also an AttachElem, merge attachments into the
-/// base AttachElem where possible.
-fn merge_base(elem: &Packed<AttachElem>) -> Option<Packed<AttachElem>> {
- // Extract from an EquationElem.
- let mut base = elem.base();
- if let Some(equation) = base.to_packed::<EquationElem>() {
- base = equation.body();
- }
-
- // Move attachments from elem into base where possible.
- if let Some(base) = base.to_packed::<AttachElem>() {
- let mut elem = elem.clone();
- let mut base = base.clone();
-
- macro_rules! merge {
- ($content:ident) => {
- if base.$content.is_none() && elem.$content.is_some() {
- base.$content = elem.$content.clone();
- elem.$content = None;
- }
- };
- }
-
- merge!(t);
- merge!(b);
- merge!(tl);
- merge!(tr);
- merge!(bl);
- merge!(br);
-
- elem.base = base.pack();
- return Some(elem);
- }
+/// Lays out a [`ScriptsElem`].
+#[typst_macros::time(name = "math.scripts", span = elem.span())]
+pub fn layout_scripts(
+ elem: &Packed<ScriptsElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ let mut fragment = ctx.layout_into_fragment(elem.body(), styles)?;
+ fragment.set_limits(Limits::Never);
+ ctx.push(fragment);
+ Ok(())
+}
- None
+/// Lays out a [`LimitsElem`].
+#[typst_macros::time(name = "math.limits", span = elem.span())]
+pub fn layout_limits(
+ elem: &Packed<LimitsElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ let limits = if elem.inline(styles) { Limits::Always } else { Limits::Display };
+ let mut fragment = ctx.layout_into_fragment(elem.body(), styles)?;
+ fragment.set_limits(limits);
+ ctx.push(fragment);
+ Ok(())
}
/// Get the size to stretch the base to, if the attach argument is true.
@@ -326,7 +168,7 @@ fn stretch_size(
base.to_packed::<StretchElem>().map(|stretch| stretch.size(styles))
}
-/// Layout the attachments.
+/// Lay out the attachments.
fn layout_attachments(
ctx: &mut MathContext,
styles: StyleChain,
@@ -671,8 +513,3 @@ fn math_kern(
// result in glyphs colliding.
summed_kern(corr_height_top).max(summed_kern(corr_height_bot))
}
-
-/// Determines if the character is one of a variety of integral signs.
-fn is_integral_char(c: char) -> bool {
- ('∫'..='∳').contains(&c) || ('⨋'..='⨜').contains(&c)
-}
diff --git a/crates/typst-layout/src/math/cancel.rs b/crates/typst-layout/src/math/cancel.rs
new file mode 100644
index 00000000..994e0e2f
--- /dev/null
+++ b/crates/typst-layout/src/math/cancel.rs
@@ -0,0 +1,144 @@
+use comemo::Track;
+use typst_library::diag::{At, SourceResult};
+use typst_library::foundations::{Context, Packed, Smart, StyleChain};
+use typst_library::layout::{Abs, Angle, Frame, FrameItem, Point, Rel, Size, Transform};
+use typst_library::math::{CancelAngle, CancelElem};
+use typst_library::text::TextElem;
+use typst_library::visualize::{FixedStroke, Geometry};
+use typst_syntax::Span;
+
+use super::{scaled_font_size, FrameFragment, MathContext};
+
+/// Lays out a [`CancelElem`].
+#[typst_macros::time(name = "math.cancel", span = elem.span())]
+pub fn layout_cancel(
+ elem: &Packed<CancelElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ let body = ctx.layout_into_fragment(elem.body(), styles)?;
+
+ // Preserve properties of body.
+ let body_class = body.class();
+ let body_italics = body.italics_correction();
+ let body_attach = body.accent_attach();
+ let body_text_like = body.is_text_like();
+
+ let mut body = body.into_frame();
+ let body_size = body.size();
+ let span = elem.span();
+ let length = elem.length(styles).at(scaled_font_size(ctx, styles));
+
+ let stroke = elem.stroke(styles).unwrap_or(FixedStroke {
+ paint: TextElem::fill_in(styles).as_decoration(),
+ ..Default::default()
+ });
+
+ let invert = elem.inverted(styles);
+ let cross = elem.cross(styles);
+ let angle = elem.angle(styles);
+
+ let invert_first_line = !cross && invert;
+ let first_line = draw_cancel_line(
+ ctx,
+ length,
+ stroke.clone(),
+ invert_first_line,
+ &angle,
+ body_size,
+ styles,
+ span,
+ )?;
+
+ // The origin of our line is the very middle of the element.
+ let center = body_size.to_point() / 2.0;
+ body.push_frame(center, first_line);
+
+ if cross {
+ // Draw the second line.
+ let second_line =
+ draw_cancel_line(ctx, length, stroke, true, &angle, body_size, styles, span)?;
+
+ body.push_frame(center, second_line);
+ }
+
+ ctx.push(
+ FrameFragment::new(ctx, styles, body)
+ .with_class(body_class)
+ .with_italics_correction(body_italics)
+ .with_accent_attach(body_attach)
+ .with_text_like(body_text_like),
+ );
+
+ Ok(())
+}
+
+/// Draws a cancel line.
+#[allow(clippy::too_many_arguments)]
+fn draw_cancel_line(
+ ctx: &mut MathContext,
+ length_scale: Rel<Abs>,
+ stroke: FixedStroke,
+ invert: bool,
+ angle: &Smart<CancelAngle>,
+ body_size: Size,
+ styles: StyleChain,
+ span: Span,
+) -> SourceResult<Frame> {
+ let default = default_angle(body_size);
+ let mut angle = match angle {
+ // Non specified angle defaults to the diagonal
+ Smart::Auto => default,
+ Smart::Custom(angle) => match angle {
+ // This specifies the absolute angle w.r.t y-axis clockwise.
+ CancelAngle::Angle(v) => *v,
+ // This specifies a function that takes the default angle as input.
+ CancelAngle::Func(func) => func
+ .call(ctx.engine, Context::new(None, Some(styles)).track(), [default])?
+ .cast()
+ .at(span)?,
+ },
+ };
+
+ // invert means flipping along the y-axis
+ if invert {
+ angle *= -1.0;
+ }
+
+ // same as above, the default length is the diagonal of the body box.
+ let default_length = body_size.to_point().hypot();
+ let length = length_scale.relative_to(default_length);
+
+ // Draw a vertical line of length and rotate it by angle
+ let start = Point::new(Abs::zero(), length / 2.0);
+ let delta = Point::new(Abs::zero(), -length);
+
+ let mut frame = Frame::soft(body_size);
+ frame.push(start, FrameItem::Shape(Geometry::Line(delta).stroked(stroke), span));
+
+ // Having the middle of the line at the origin is convenient here.
+ frame.transform(Transform::rotate(angle));
+ Ok(frame)
+}
+
+/// The default line angle for a body of the given size.
+fn default_angle(body: Size) -> Angle {
+ // The default cancel line is the diagonal.
+ // We infer the default angle from
+ // the diagonal w.r.t to the body box.
+ //
+ // The returned angle is in the range of [0, Pi/2]
+ //
+ // Note that the angle is computed w.r.t to the y-axis
+ //
+ // B
+ // /|
+ // diagonal / | height
+ // / |
+ // / |
+ // O ----
+ // width
+ let (width, height) = (body.x, body.y);
+ let default_angle = (width / height).atan(); // arctangent (in the range [0, Pi/2])
+ Angle::rad(default_angle)
+}
diff --git a/crates/typst/src/math/frac.rs b/crates/typst-layout/src/math/frac.rs
index 744a15c5..50686333 100644
--- a/crates/typst/src/math/frac.rs
+++ b/crates/typst-layout/src/math/frac.rs
@@ -1,90 +1,47 @@
-use crate::diag::{bail, SourceResult};
-use crate::foundations::{elem, Content, Packed, StyleChain, Value};
-use crate::layout::{Em, Frame, FrameItem, Point, Size};
-use crate::math::{
+use typst_library::diag::SourceResult;
+use typst_library::foundations::{Content, Packed, StyleChain};
+use typst_library::layout::{Em, Frame, FrameItem, Point, Size};
+use typst_library::math::{BinomElem, FracElem};
+use typst_library::text::TextElem;
+use typst_library::visualize::{FixedStroke, Geometry};
+use typst_syntax::Span;
+
+use super::{
scaled_font_size, style_for_denominator, style_for_numerator, FrameFragment,
- GlyphFragment, LayoutMath, MathContext, Scaled, DELIM_SHORT_FALL,
+ GlyphFragment, MathContext, DELIM_SHORT_FALL,
};
-use crate::syntax::{Span, Spanned};
-use crate::text::TextElem;
-use crate::visualize::{FixedStroke, Geometry};
const FRAC_AROUND: Em = Em::new(0.1);
-/// A mathematical fraction.
-///
-/// # Example
-/// ```example
-/// $ 1/2 < (x+1)/2 $
-/// $ ((x+1)) / 2 = frac(a, b) $
-/// ```
-///
-/// # Syntax
-/// This function also has dedicated syntax: Use a slash to turn neighbouring
-/// expressions into a fraction. Multiple atoms can be grouped into a single
-/// expression using round grouping parenthesis. Such parentheses are removed
-/// from the output, but you can nest multiple to force them.
-#[elem(title = "Fraction", LayoutMath)]
-pub struct FracElem {
- /// The fraction's numerator.
- #[required]
- pub num: Content,
-
- /// The fraction's denominator.
- #[required]
- pub denom: Content,
-}
-
-impl LayoutMath for Packed<FracElem> {
- #[typst_macros::time(name = "math.frac", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- layout(
- ctx,
- styles,
- self.num(),
- std::slice::from_ref(self.denom()),
- false,
- self.span(),
- )
- }
-}
-
-/// A binomial expression.
-///
-/// # Example
-/// ```example
-/// $ binom(n, k) $
-/// $ binom(n, k_1, k_2, k_3, ..., k_m) $
-/// ```
-#[elem(title = "Binomial", LayoutMath)]
-pub struct BinomElem {
- /// The binomial's upper index.
- #[required]
- pub upper: Content,
-
- /// The binomial's lower index.
- #[required]
- #[variadic]
- #[parse(
- let values = args.all::<Spanned<Value>>()?;
- if values.is_empty() {
- // Prevents one element binomials
- bail!(args.span, "missing argument: lower");
- }
- values.into_iter().map(|spanned| spanned.v.display()).collect()
- )]
- pub lower: Vec<Content>,
+/// Lays out a [`FracElem`].
+#[typst_macros::time(name = "math.frac", span = elem.span())]
+pub fn layout_frac(
+ elem: &Packed<FracElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_frac_like(
+ ctx,
+ styles,
+ elem.num(),
+ std::slice::from_ref(elem.denom()),
+ false,
+ elem.span(),
+ )
}
-impl LayoutMath for Packed<BinomElem> {
- #[typst_macros::time(name = "math.binom", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- layout(ctx, styles, self.upper(), self.lower(), true, self.span())
- }
+/// Lays out a [`BinomElem`].
+#[typst_macros::time(name = "math.binom", span = elem.span())]
+pub fn layout_binom(
+ elem: &Packed<BinomElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_frac_like(ctx, styles, elem.upper(), elem.lower(), true, elem.span())
}
/// Layout a fraction or binomial.
-fn layout(
+fn layout_frac_like(
ctx: &mut MathContext,
styles: StyleChain,
num: &Content,
diff --git a/crates/typst/src/math/fragment.rs b/crates/typst-layout/src/math/fragment.rs
index a3fcc9c6..19a4494e 100644
--- a/crates/typst/src/math/fragment.rs
+++ b/crates/typst-layout/src/math/fragment.rs
@@ -1,22 +1,25 @@
use std::fmt::{self, Debug, Formatter};
+use rustybuzz::Feature;
use smallvec::SmallVec;
-use ttf_parser::gsub::AlternateSet;
+use ttf_parser::gsub::{
+ AlternateSet, AlternateSubstitution, SingleSubstitution, SubstitutionSubtable,
+};
+use ttf_parser::opentype_layout::LayoutTable;
use ttf_parser::{GlyphId, Rect};
+use typst_library::foundations::StyleChain;
+use typst_library::introspection::Tag;
+use typst_library::layout::{
+ Abs, Axis, Corner, Em, Frame, FrameItem, HideElem, Point, Size, VAlignment,
+};
+use typst_library::math::{EquationElem, MathSize};
+use typst_library::model::{Destination, LinkElem};
+use typst_library::text::{Font, Glyph, Lang, Region, TextElem, TextItem};
+use typst_library::visualize::Paint;
+use typst_syntax::Span;
use unicode_math_class::MathClass;
-use crate::foundations::StyleChain;
-use crate::introspection::Tag;
-use crate::layout::{
- Abs, Corner, Em, Frame, FrameItem, HideElem, Point, Size, VAlignment,
-};
-use crate::math::{
- scaled_font_size, EquationElem, Limits, MathContext, MathSize, Scaled,
-};
-use crate::model::{Destination, LinkElem};
-use crate::syntax::Span;
-use crate::text::{Font, Glyph, Lang, Region, TextElem, TextItem};
-use crate::visualize::Paint;
+use super::{scaled_font_size, stretch_glyph, MathContext, Scaled};
#[derive(Debug, Clone)]
pub enum MathFragment {
@@ -350,7 +353,6 @@ impl GlyphFragment {
pub fn into_variant(self) -> VariantFragment {
VariantFragment {
c: self.c,
- id: Some(self.id),
font_size: self.font_size,
italics_correction: self.italics_correction,
accent_attach: self.accent_attach,
@@ -406,6 +408,26 @@ impl GlyphFragment {
self.set_id(ctx, alt_id);
}
}
+
+ /// Try to stretch a glyph to a desired height.
+ pub fn stretch_vertical(
+ self,
+ ctx: &MathContext,
+ height: Abs,
+ short_fall: Abs,
+ ) -> VariantFragment {
+ stretch_glyph(ctx, self, height, short_fall, Axis::Y)
+ }
+
+ /// Try to stretch a glyph to a desired width.
+ pub fn stretch_horizontal(
+ self,
+ ctx: &MathContext,
+ width: Abs,
+ short_fall: Abs,
+ ) -> VariantFragment {
+ stretch_glyph(ctx, self, width, short_fall, Axis::X)
+ }
}
impl Debug for GlyphFragment {
@@ -417,7 +439,6 @@ impl Debug for GlyphFragment {
#[derive(Clone)]
pub struct VariantFragment {
pub c: char,
- pub id: Option<GlyphId>,
pub italics_correction: Abs,
pub accent_attach: Abs,
pub frame: Frame,
@@ -582,3 +603,102 @@ fn kern_at_height(
Some(kern.kern(i)?.scaled(ctx, font_size))
}
+
+/// Describes in which situation a frame should use limits for attachments.
+#[derive(Debug, Copy, Clone)]
+pub enum Limits {
+ /// Always scripts.
+ Never,
+ /// Display limits only in `display` math.
+ Display,
+ /// Always limits.
+ Always,
+}
+
+impl Limits {
+ /// The default limit configuration if the given character is the base.
+ pub fn for_char(c: char) -> Self {
+ match unicode_math_class::class(c) {
+ Some(MathClass::Large) => {
+ if is_integral_char(c) {
+ Limits::Never
+ } else {
+ Limits::Display
+ }
+ }
+ Some(MathClass::Relation) => Limits::Always,
+ _ => Limits::Never,
+ }
+ }
+
+ /// The default limit configuration for a math class.
+ pub fn for_class(class: MathClass) -> Self {
+ match class {
+ MathClass::Large => Self::Display,
+ MathClass::Relation => Self::Always,
+ _ => Self::Never,
+ }
+ }
+
+ /// Whether limits should be displayed in this context.
+ pub fn active(&self, styles: StyleChain) -> bool {
+ match self {
+ Self::Always => true,
+ Self::Display => EquationElem::size_in(styles) == MathSize::Display,
+ Self::Never => false,
+ }
+ }
+}
+
+/// Determines if the character is one of a variety of integral signs.
+fn is_integral_char(c: char) -> bool {
+ ('∫'..='∳').contains(&c) || ('⨋'..='⨜').contains(&c)
+}
+
+/// An OpenType substitution table that is applicable to glyph-wise substitutions.
+pub enum GlyphwiseSubsts<'a> {
+ Single(SingleSubstitution<'a>),
+ Alternate(AlternateSubstitution<'a>, u32),
+}
+
+impl<'a> GlyphwiseSubsts<'a> {
+ pub fn new(gsub: LayoutTable<'a>, feature: Feature) -> Option<Self> {
+ let table = gsub
+ .features
+ .find(ttf_parser::Tag(feature.tag.0))
+ .and_then(|feature| feature.lookup_indices.get(0))
+ .and_then(|index| gsub.lookups.get(index))?;
+ let table = table.subtables.get::<SubstitutionSubtable>(0)?;
+ match table {
+ SubstitutionSubtable::Single(single_glyphs) => {
+ Some(Self::Single(single_glyphs))
+ }
+ SubstitutionSubtable::Alternate(alt_glyphs) => {
+ Some(Self::Alternate(alt_glyphs, feature.value))
+ }
+ _ => None,
+ }
+ }
+
+ pub fn try_apply(&self, glyph_id: GlyphId) -> Option<GlyphId> {
+ match self {
+ Self::Single(single) => match single {
+ SingleSubstitution::Format1 { coverage, delta } => coverage
+ .get(glyph_id)
+ .map(|_| GlyphId(glyph_id.0.wrapping_add(*delta as u16))),
+ SingleSubstitution::Format2 { coverage, substitutes } => {
+ coverage.get(glyph_id).and_then(|idx| substitutes.get(idx))
+ }
+ },
+ Self::Alternate(alternate, value) => alternate
+ .coverage
+ .get(glyph_id)
+ .and_then(|idx| alternate.alternate_sets.get(idx))
+ .and_then(|set| set.alternates.get(*value as u16)),
+ }
+ }
+
+ pub fn apply(&self, glyph_id: GlyphId) -> GlyphId {
+ self.try_apply(glyph_id).unwrap_or(glyph_id)
+ }
+}
diff --git a/crates/typst-layout/src/math/lr.rs b/crates/typst-layout/src/math/lr.rs
new file mode 100644
index 00000000..aba9012f
--- /dev/null
+++ b/crates/typst-layout/src/math/lr.rs
@@ -0,0 +1,135 @@
+use typst_library::diag::SourceResult;
+use typst_library::foundations::{Packed, Smart, StyleChain};
+use typst_library::layout::{Abs, Axis, Length, Rel};
+use typst_library::math::{EquationElem, LrElem, MidElem};
+use unicode_math_class::MathClass;
+
+use super::{stretch_fragment, MathContext, MathFragment, DELIM_SHORT_FALL};
+
+/// Lays out an [`LrElem`].
+#[typst_macros::time(name = "math.lr", span = elem.span())]
+pub fn layout_lr(
+ elem: &Packed<LrElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ let mut body = elem.body();
+
+ // Extract from an EquationElem.
+ if let Some(equation) = body.to_packed::<EquationElem>() {
+ body = equation.body();
+ }
+
+ // Extract implicit LrElem.
+ if let Some(lr) = body.to_packed::<LrElem>() {
+ if lr.size(styles).is_auto() {
+ body = lr.body();
+ }
+ }
+
+ let mut fragments = ctx.layout_into_fragments(body, styles)?;
+ let axis = scaled!(ctx, styles, axis_height);
+ let max_extent = fragments
+ .iter()
+ .map(|fragment| (fragment.ascent() - axis).max(fragment.descent() + axis))
+ .max()
+ .unwrap_or_default();
+
+ let relative_to = 2.0 * max_extent;
+ let height = elem.size(styles);
+
+ // Scale up fragments at both ends.
+ match fragments.as_mut_slice() {
+ [one] => scale(ctx, styles, one, relative_to, height, None),
+ [first, .., last] => {
+ scale(ctx, styles, first, relative_to, height, Some(MathClass::Opening));
+ scale(ctx, styles, last, relative_to, height, Some(MathClass::Closing));
+ }
+ _ => {}
+ }
+
+ // Handle MathFragment::Variant fragments that should be scaled up.
+ for fragment in &mut fragments {
+ if let MathFragment::Variant(ref mut variant) = fragment {
+ if variant.mid_stretched == Some(false) {
+ variant.mid_stretched = Some(true);
+ scale(ctx, styles, fragment, relative_to, height, Some(MathClass::Large));
+ }
+ }
+ }
+
+ // Remove weak SpacingFragment immediately after the opening or immediately
+ // before the closing.
+ let original_len = fragments.len();
+ let mut index = 0;
+ fragments.retain(|fragment| {
+ index += 1;
+ (index != 2 && index + 1 != original_len)
+ || !matches!(fragment, MathFragment::Spacing(_, true))
+ });
+
+ ctx.extend(fragments);
+
+ Ok(())
+}
+
+/// Lays out a [`MidElem`].
+#[typst_macros::time(name = "math.mid", span = elem.span())]
+pub fn layout_mid(
+ elem: &Packed<MidElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ let mut fragments = ctx.layout_into_fragments(elem.body(), styles)?;
+
+ for fragment in &mut fragments {
+ match fragment {
+ MathFragment::Glyph(glyph) => {
+ let mut new = glyph.clone().into_variant();
+ new.mid_stretched = Some(false);
+ new.class = MathClass::Fence;
+ *fragment = MathFragment::Variant(new);
+ }
+ MathFragment::Variant(variant) => {
+ variant.mid_stretched = Some(false);
+ variant.class = MathClass::Fence;
+ }
+ _ => {}
+ }
+ }
+
+ ctx.extend(fragments);
+ Ok(())
+}
+
+/// Scale a math fragment to a height.
+fn scale(
+ ctx: &mut MathContext,
+ styles: StyleChain,
+ fragment: &mut MathFragment,
+ relative_to: Abs,
+ height: Smart<Rel<Length>>,
+ apply: Option<MathClass>,
+) {
+ if matches!(
+ fragment.class(),
+ MathClass::Opening | MathClass::Closing | MathClass::Fence
+ ) {
+ // This unwrap doesn't really matter. If it is None, then the fragment
+ // won't be stretchable anyways.
+ let short_fall = DELIM_SHORT_FALL.at(fragment.font_size().unwrap_or_default());
+ stretch_fragment(
+ ctx,
+ styles,
+ fragment,
+ Some(Axis::Y),
+ Some(relative_to),
+ height,
+ short_fall,
+ );
+
+ if let Some(class) = apply {
+ fragment.set_class(class);
+ }
+ }
+}
diff --git a/crates/typst-layout/src/math/mat.rs b/crates/typst-layout/src/math/mat.rs
new file mode 100644
index 00000000..6c8b0455
--- /dev/null
+++ b/crates/typst-layout/src/math/mat.rs
@@ -0,0 +1,333 @@
+use typst_library::diag::{bail, SourceResult};
+use typst_library::foundations::{Content, Packed, StyleChain};
+use typst_library::layout::{
+ Abs, Axes, Em, FixedAlignment, Frame, FrameItem, Point, Ratio, Rel, Size,
+};
+use typst_library::math::{Augment, AugmentOffsets, CasesElem, MatElem, VecElem};
+use typst_library::text::TextElem;
+use typst_library::visualize::{FillRule, FixedStroke, Geometry, LineCap, Shape};
+use typst_syntax::Span;
+
+use super::{
+ alignments, delimiter_alignment, scaled_font_size, stack, style_for_denominator,
+ AlignmentResult, FrameFragment, GlyphFragment, LeftRightAlternator, MathContext,
+ Scaled, DELIM_SHORT_FALL,
+};
+
+const VERTICAL_PADDING: Ratio = Ratio::new(0.1);
+const DEFAULT_STROKE_THICKNESS: Em = Em::new(0.05);
+
+/// Lays out a [`VecElem`].
+#[typst_macros::time(name = "math.vec", span = elem.span())]
+pub fn layout_vec(
+ elem: &Packed<VecElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ let delim = elem.delim(styles);
+ let frame = layout_vec_body(
+ ctx,
+ styles,
+ elem.children(),
+ elem.align(styles),
+ elem.gap(styles).at(scaled_font_size(ctx, styles)),
+ LeftRightAlternator::Right,
+ )?;
+
+ layout_delimiters(ctx, styles, frame, delim.open(), delim.close(), elem.span())
+}
+
+/// Lays out a [`MatElem`].
+#[typst_macros::time(name = "math.mat", span = elem.span())]
+pub fn layout_mat(
+ elem: &Packed<MatElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ let augment = elem.augment(styles);
+ let rows = elem.rows();
+
+ if let Some(aug) = &augment {
+ for &offset in &aug.hline.0 {
+ if offset == 0 || offset.unsigned_abs() >= rows.len() {
+ bail!(
+ elem.span(),
+ "cannot draw a horizontal line after row {} of a matrix with {} rows",
+ if offset < 0 { rows.len() as isize + offset } else { offset },
+ rows.len()
+ );
+ }
+ }
+
+ let ncols = elem.rows().first().map_or(0, |row| row.len());
+
+ for &offset in &aug.vline.0 {
+ if offset == 0 || offset.unsigned_abs() >= ncols {
+ bail!(
+ elem.span(),
+ "cannot draw a vertical line after column {} of a matrix with {} columns",
+ if offset < 0 { ncols as isize + offset } else { offset },
+ ncols
+ );
+ }
+ }
+ }
+
+ let font_size = scaled_font_size(ctx, styles);
+ let column_gap = elem.column_gap(styles).at(font_size);
+ let row_gap = elem.row_gap(styles).at(font_size);
+ let delim = elem.delim(styles);
+ let frame = layout_mat_body(
+ ctx,
+ styles,
+ rows,
+ elem.align(styles),
+ augment,
+ Axes::new(column_gap, row_gap),
+ elem.span(),
+ )?;
+
+ layout_delimiters(ctx, styles, frame, delim.open(), delim.close(), elem.span())
+}
+
+/// Lays out a [`CasesElem`].
+#[typst_macros::time(name = "math.cases", span = elem.span())]
+pub fn layout_cases(
+ elem: &Packed<CasesElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ let delim = elem.delim(styles);
+ let frame = layout_vec_body(
+ ctx,
+ styles,
+ elem.children(),
+ FixedAlignment::Start,
+ elem.gap(styles).at(scaled_font_size(ctx, styles)),
+ LeftRightAlternator::None,
+ )?;
+
+ let (open, close) =
+ if elem.reverse(styles) { (None, delim.close()) } else { (delim.open(), None) };
+
+ layout_delimiters(ctx, styles, frame, open, close, elem.span())
+}
+
+/// Layout the inner contents of a vector.
+fn layout_vec_body(
+ ctx: &mut MathContext,
+ styles: StyleChain,
+ column: &[Content],
+ align: FixedAlignment,
+ row_gap: Rel<Abs>,
+ alternator: LeftRightAlternator,
+) -> SourceResult<Frame> {
+ let gap = row_gap.relative_to(ctx.region.size.y);
+
+ let denom_style = style_for_denominator(styles);
+ let mut flat = vec![];
+ for child in column {
+ flat.push(ctx.layout_into_run(child, styles.chain(&denom_style))?);
+ }
+ // We pad ascent and descent with the ascent and descent of the paren
+ // to ensure that normal vectors are aligned with others unless they are
+ // way too big.
+ let paren =
+ GlyphFragment::new(ctx, styles.chain(&denom_style), '(', Span::detached());
+ Ok(stack(flat, align, gap, 0, alternator, Some((paren.ascent, paren.descent))))
+}
+
+/// Layout the inner contents of a matrix.
+fn layout_mat_body(
+ ctx: &mut MathContext,
+ styles: StyleChain,
+ rows: &[Vec<Content>],
+ align: FixedAlignment,
+ augment: Option<Augment<Abs>>,
+ gap: Axes<Rel<Abs>>,
+ span: Span,
+) -> SourceResult<Frame> {
+ let ncols = rows.first().map_or(0, |row| row.len());
+ let nrows = rows.len();
+ if ncols == 0 || nrows == 0 {
+ return Ok(Frame::soft(Size::zero()));
+ }
+
+ let gap = gap.zip_map(ctx.region.size, Rel::relative_to);
+ let half_gap = gap * 0.5;
+
+ // We provide a default stroke thickness that scales
+ // with font size to ensure that augmentation lines
+ // look correct by default at all matrix sizes.
+ // The line cap is also set to square because it looks more "correct".
+ let font_size = scaled_font_size(ctx, styles);
+ let default_stroke_thickness = DEFAULT_STROKE_THICKNESS.at(font_size);
+ let default_stroke = FixedStroke {
+ thickness: default_stroke_thickness,
+ paint: TextElem::fill_in(styles).as_decoration(),
+ cap: LineCap::Square,
+ ..Default::default()
+ };
+
+ let (hline, vline, stroke) = match augment {
+ Some(augment) => {
+ // We need to get stroke here for ownership.
+ let stroke = augment.stroke.unwrap_or_default().unwrap_or(default_stroke);
+ (augment.hline, augment.vline, stroke)
+ }
+ _ => (AugmentOffsets::default(), AugmentOffsets::default(), default_stroke),
+ };
+
+ // Before the full matrix body can be laid out, the
+ // individual cells must first be independently laid out
+ // so we can ensure alignment across rows and columns.
+
+ // This variable stores the maximum ascent and descent for each row.
+ let mut heights = vec![(Abs::zero(), Abs::zero()); nrows];
+
+ // We want to transpose our data layout to columns
+ // before final layout. For efficiency, the columns
+ // variable is set up here and newly generated
+ // individual cells are then added to it.
+ let mut cols = vec![vec![]; ncols];
+
+ let denom_style = style_for_denominator(styles);
+ // We pad ascent and descent with the ascent and descent of the paren
+ // to ensure that normal matrices are aligned with others unless they are
+ // way too big.
+ let paren =
+ GlyphFragment::new(ctx, styles.chain(&denom_style), '(', Span::detached());
+
+ for (row, (ascent, descent)) in rows.iter().zip(&mut heights) {
+ for (cell, col) in row.iter().zip(&mut cols) {
+ let cell = ctx.layout_into_run(cell, styles.chain(&denom_style))?;
+
+ ascent.set_max(cell.ascent().max(paren.ascent));
+ descent.set_max(cell.descent().max(paren.descent));
+
+ col.push(cell);
+ }
+ }
+
+ // For each row, combine maximum ascent and descent into a row height.
+ // Sum the row heights, then add the total height of the gaps between rows.
+ let total_height =
+ heights.iter().map(|&(a, b)| a + b).sum::<Abs>() + gap.y * (nrows - 1) as f64;
+
+ // Width starts at zero because it can't be calculated until later
+ let mut frame = Frame::soft(Size::new(Abs::zero(), total_height));
+
+ let mut x = Abs::zero();
+
+ for (index, col) in cols.into_iter().enumerate() {
+ let AlignmentResult { points, width: rcol } = alignments(&col);
+
+ let mut y = Abs::zero();
+
+ for (cell, &(ascent, descent)) in col.into_iter().zip(&heights) {
+ let cell = cell.into_line_frame(&points, LeftRightAlternator::Right);
+ let pos = Point::new(
+ if points.is_empty() {
+ x + align.position(rcol - cell.width())
+ } else {
+ x
+ },
+ y + ascent - cell.ascent(),
+ );
+
+ frame.push_frame(pos, cell);
+
+ y += ascent + descent + gap.y;
+ }
+
+ // Advance to the end of the column
+ x += rcol;
+
+ // If a vertical line should be inserted after this column
+ if vline.0.contains(&(index as isize + 1))
+ || vline.0.contains(&(1 - ((ncols - index) as isize)))
+ {
+ frame.push(
+ Point::with_x(x + half_gap.x),
+ line_item(total_height, true, stroke.clone(), span),
+ );
+ }
+
+ // Advance to the start of the next column
+ x += gap.x;
+ }
+
+ // Once all the columns are laid out, the total width can be calculated
+ let total_width = x - gap.x;
+
+ // This allows the horizontal lines to be laid out
+ for line in hline.0 {
+ let real_line =
+ if line < 0 { nrows - line.unsigned_abs() } else { line as usize };
+ let offset = (heights[0..real_line].iter().map(|&(a, b)| a + b).sum::<Abs>()
+ + gap.y * (real_line - 1) as f64)
+ + half_gap.y;
+
+ frame.push(
+ Point::with_y(offset),
+ line_item(total_width, false, stroke.clone(), span),
+ );
+ }
+
+ frame.size_mut().x = total_width;
+
+ Ok(frame)
+}
+
+fn line_item(length: Abs, vertical: bool, stroke: FixedStroke, span: Span) -> FrameItem {
+ let line_geom = if vertical {
+ Geometry::Line(Point::with_y(length))
+ } else {
+ Geometry::Line(Point::with_x(length))
+ };
+
+ FrameItem::Shape(
+ Shape {
+ geometry: line_geom,
+ fill: None,
+ fill_rule: FillRule::default(),
+ stroke: Some(stroke),
+ },
+ span,
+ )
+}
+
+/// Layout the outer wrapper around the body of a vector or matrix.
+fn layout_delimiters(
+ ctx: &mut MathContext,
+ styles: StyleChain,
+ mut frame: Frame,
+ left: Option<char>,
+ right: Option<char>,
+ span: Span,
+) -> SourceResult<()> {
+ let font_size = scaled_font_size(ctx, styles);
+ let short_fall = DELIM_SHORT_FALL.at(font_size);
+ let axis = ctx.constants.axis_height().scaled(ctx, font_size);
+ let height = frame.height();
+ let target = height + VERTICAL_PADDING.of(height);
+ frame.set_baseline(height / 2.0 + axis);
+
+ if let Some(left) = left {
+ let mut left = GlyphFragment::new(ctx, styles, left, span)
+ .stretch_vertical(ctx, target, short_fall);
+ left.align_on_axis(ctx, delimiter_alignment(left.c));
+ ctx.push(left);
+ }
+
+ ctx.push(FrameFragment::new(ctx, styles, frame));
+
+ if let Some(right) = right {
+ let mut right = GlyphFragment::new(ctx, styles, right, span)
+ .stretch_vertical(ctx, target, short_fall);
+ right.align_on_axis(ctx, delimiter_alignment(right.c));
+ ctx.push(right);
+ }
+
+ Ok(())
+}
diff --git a/crates/typst-layout/src/math/mod.rs b/crates/typst-layout/src/math/mod.rs
new file mode 100644
index 00000000..b3dde977
--- /dev/null
+++ b/crates/typst-layout/src/math/mod.rs
@@ -0,0 +1,703 @@
+#[macro_use]
+mod shared;
+mod accent;
+mod attach;
+mod cancel;
+mod frac;
+mod fragment;
+mod lr;
+mod mat;
+mod root;
+mod run;
+mod stretch;
+mod text;
+mod underover;
+
+use ttf_parser::gsub::SubstitutionSubtable;
+use typst_library::diag::{bail, SourceResult};
+use typst_library::engine::Engine;
+use typst_library::foundations::{Content, NativeElement, Packed, Resolve, StyleChain};
+use typst_library::introspection::{Counter, Locator, SplitLocator, TagElem};
+use typst_library::layout::{
+ Abs, AlignElem, Axes, BlockElem, BoxElem, Em, FixedAlignment, Fragment, Frame, HElem,
+ InlineItem, OuterHAlignment, PlaceElem, Point, Region, Regions, Size, Spacing,
+ SpecificAlignment, VAlignment,
+};
+use typst_library::math::*;
+use typst_library::model::ParElem;
+use typst_library::routines::{Arenas, RealizationKind};
+use typst_library::text::{
+ families, features, variant, Font, LinebreakElem, SpaceElem, TextEdgeBounds,
+ TextElem, TextSize,
+};
+use typst_library::World;
+use typst_syntax::Span;
+use typst_utils::Numeric;
+use unicode_math_class::MathClass;
+
+use self::fragment::{
+ FrameFragment, GlyphFragment, GlyphwiseSubsts, Limits, MathFragment, VariantFragment,
+};
+use self::run::{LeftRightAlternator, MathRun, MathRunFrameBuilder};
+use self::shared::*;
+use self::stretch::{stretch_fragment, stretch_glyph};
+
+/// Layout an inline equation (in a paragraph).
+#[typst_macros::time(span = elem.span())]
+pub fn layout_equation_inline(
+ elem: &Packed<EquationElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Size,
+) -> SourceResult<Vec<InlineItem>> {
+ assert!(!elem.block(styles));
+
+ let font = find_math_font(engine, styles, elem.span())?;
+
+ let mut locator = locator.split();
+ let mut ctx = MathContext::new(engine, &mut locator, styles, region, &font);
+ let run = ctx.layout_into_run(&elem.body, styles)?;
+
+ let mut items = if run.row_count() == 1 {
+ run.into_par_items()
+ } else {
+ vec![InlineItem::Frame(run.into_fragment(&ctx, styles).into_frame())]
+ };
+
+ // An empty equation should have a height, so we still create a frame
+ // (which is then resized in the loop).
+ if items.is_empty() {
+ items.push(InlineItem::Frame(Frame::soft(Size::zero())));
+ }
+
+ for item in &mut items {
+ let InlineItem::Frame(frame) = item else { continue };
+
+ let font_size = scaled_font_size(&ctx, styles);
+ let slack = ParElem::leading_in(styles) * 0.7;
+
+ let (t, b) = font.edges(
+ TextElem::top_edge_in(styles),
+ TextElem::bottom_edge_in(styles),
+ font_size,
+ TextEdgeBounds::Frame(frame),
+ );
+
+ let ascent = t.max(frame.ascent() - slack);
+ let descent = b.max(frame.descent() - slack);
+ frame.translate(Point::with_y(ascent - frame.baseline()));
+ frame.size_mut().y = ascent + descent;
+ }
+
+ Ok(items)
+}
+
+/// Layout a block-level equation (in a flow).
+#[typst_macros::time(span = elem.span())]
+pub fn layout_equation_block(
+ elem: &Packed<EquationElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+) -> SourceResult<Fragment> {
+ assert!(elem.block(styles));
+
+ let span = elem.span();
+ let font = find_math_font(engine, styles, span)?;
+
+ let mut locator = locator.split();
+ let mut ctx = MathContext::new(engine, &mut locator, styles, regions.base(), &font);
+ let full_equation_builder = ctx
+ .layout_into_run(&elem.body, styles)?
+ .multiline_frame_builder(&ctx, styles);
+ let width = full_equation_builder.size.x;
+
+ let equation_builders = if BlockElem::breakable_in(styles) {
+ let mut rows = full_equation_builder.frames.into_iter().peekable();
+ let mut equation_builders = vec![];
+ let mut last_first_pos = Point::zero();
+ let mut regions = regions;
+
+ loop {
+ // Keep track of the position of the first row in this region,
+ // so that the offset can be reverted later.
+ let Some(&(_, first_pos)) = rows.peek() else { break };
+ last_first_pos = first_pos;
+
+ let mut frames = vec![];
+ let mut height = Abs::zero();
+ while let Some((sub, pos)) = rows.peek() {
+ let mut pos = *pos;
+ pos.y -= first_pos.y;
+
+ // Finish this region if the line doesn't fit. Only do it if
+ // we placed at least one line _or_ we still have non-last
+ // regions. Crucially, we don't want to infinitely create
+ // new regions which are too small.
+ if !regions.size.y.fits(sub.height() + pos.y)
+ && (regions.may_progress()
+ || (regions.may_break() && !frames.is_empty()))
+ {
+ break;
+ }
+
+ let (sub, _) = rows.next().unwrap();
+ height = height.max(pos.y + sub.height());
+ frames.push((sub, pos));
+ }
+
+ equation_builders
+ .push(MathRunFrameBuilder { frames, size: Size::new(width, height) });
+ regions.next();
+ }
+
+ // Append remaining rows to the equation builder of the last region.
+ if let Some(equation_builder) = equation_builders.last_mut() {
+ equation_builder.frames.extend(rows.map(|(frame, mut pos)| {
+ pos.y -= last_first_pos.y;
+ (frame, pos)
+ }));
+
+ let height = equation_builder
+ .frames
+ .iter()
+ .map(|(frame, pos)| frame.height() + pos.y)
+ .max()
+ .unwrap_or(equation_builder.size.y);
+
+ equation_builder.size.y = height;
+ }
+
+ // Ensure that there is at least one frame, even for empty equations.
+ if equation_builders.is_empty() {
+ equation_builders
+ .push(MathRunFrameBuilder { frames: vec![], size: Size::zero() });
+ }
+
+ equation_builders
+ } else {
+ vec![full_equation_builder]
+ };
+
+ let Some(numbering) = (**elem).numbering(styles) else {
+ let frames = equation_builders
+ .into_iter()
+ .map(MathRunFrameBuilder::build)
+ .collect();
+ return Ok(Fragment::frames(frames));
+ };
+
+ let pod = Region::new(regions.base(), Axes::splat(false));
+ let counter = Counter::of(EquationElem::elem())
+ .display_at_loc(engine, elem.location().unwrap(), styles, numbering)?
+ .spanned(span);
+ let number =
+ (engine.routines.layout_frame)(engine, &counter, locator.next(&()), styles, pod)?;
+
+ static NUMBER_GUTTER: Em = Em::new(0.5);
+ let full_number_width = number.width() + NUMBER_GUTTER.resolve(styles);
+
+ let number_align = match elem.number_align(styles) {
+ SpecificAlignment::H(h) => SpecificAlignment::Both(h, VAlignment::Horizon),
+ SpecificAlignment::V(v) => SpecificAlignment::Both(OuterHAlignment::End, v),
+ SpecificAlignment::Both(h, v) => SpecificAlignment::Both(h, v),
+ };
+
+ // Add equation numbers to each equation region.
+ let region_count = equation_builders.len();
+ let frames = equation_builders
+ .into_iter()
+ .map(|builder| {
+ if builder.frames.is_empty() && region_count > 1 {
+ // Don't number empty regions, but do number empty equations.
+ return builder.build();
+ }
+ add_equation_number(
+ builder,
+ number.clone(),
+ number_align.resolve(styles),
+ AlignElem::alignment_in(styles).resolve(styles).x,
+ regions.size.x,
+ full_number_width,
+ )
+ })
+ .collect();
+
+ Ok(Fragment::frames(frames))
+}
+
+fn find_math_font(
+ engine: &mut Engine<'_>,
+ styles: StyleChain,
+ span: Span,
+) -> SourceResult<Font> {
+ let variant = variant(styles);
+ let world = engine.world;
+ let Some(font) = families(styles).find_map(|family| {
+ let id = world.book().select(family, variant)?;
+ let font = world.font(id)?;
+ let _ = font.ttf().tables().math?.constants?;
+ Some(font)
+ }) else {
+ bail!(span, "current font does not support math");
+ };
+ Ok(font)
+}
+
+fn add_equation_number(
+ equation_builder: MathRunFrameBuilder,
+ number: Frame,
+ number_align: Axes<FixedAlignment>,
+ equation_align: FixedAlignment,
+ region_size_x: Abs,
+ full_number_width: Abs,
+) -> Frame {
+ let first =
+ equation_builder.frames.first().map_or(
+ (equation_builder.size, Point::zero(), Abs::zero()),
+ |(frame, pos)| (frame.size(), *pos, frame.baseline()),
+ );
+ let last =
+ equation_builder.frames.last().map_or(
+ (equation_builder.size, Point::zero(), Abs::zero()),
+ |(frame, pos)| (frame.size(), *pos, frame.baseline()),
+ );
+ let line_count = equation_builder.frames.len();
+ let mut equation = equation_builder.build();
+
+ let width = if region_size_x.is_finite() {
+ region_size_x
+ } else {
+ equation.width() + 2.0 * full_number_width
+ };
+
+ let is_multiline = line_count >= 2;
+ let resizing_offset = resize_equation(
+ &mut equation,
+ &number,
+ number_align,
+ equation_align,
+ width,
+ is_multiline,
+ [first, last],
+ );
+ equation.translate(Point::with_x(match (equation_align, number_align.x) {
+ (FixedAlignment::Start, FixedAlignment::Start) => full_number_width,
+ (FixedAlignment::End, FixedAlignment::End) => -full_number_width,
+ _ => Abs::zero(),
+ }));
+
+ let x = match number_align.x {
+ FixedAlignment::Start => Abs::zero(),
+ FixedAlignment::End => equation.width() - number.width(),
+ _ => unreachable!(),
+ };
+ let y = {
+ let align_baselines = |(_, pos, baseline): (_, Point, Abs), number: &Frame| {
+ resizing_offset.y + pos.y + baseline - number.baseline()
+ };
+ match number_align.y {
+ FixedAlignment::Start => align_baselines(first, &number),
+ FixedAlignment::Center if !is_multiline => align_baselines(first, &number),
+ // In this case, the center lines (not baselines) of the number frame
+ // and the equation frame shall be aligned.
+ FixedAlignment::Center => (equation.height() - number.height()) / 2.0,
+ FixedAlignment::End => align_baselines(last, &number),
+ }
+ };
+
+ equation.push_frame(Point::new(x, y), number);
+ equation
+}
+
+/// Resize the equation's frame accordingly so that it encompasses the number.
+fn resize_equation(
+ equation: &mut Frame,
+ number: &Frame,
+ number_align: Axes<FixedAlignment>,
+ equation_align: FixedAlignment,
+ width: Abs,
+ is_multiline: bool,
+ [first, last]: [(Axes<Abs>, Point, Abs); 2],
+) -> Point {
+ if matches!(number_align.y, FixedAlignment::Center if is_multiline) {
+ // In this case, the center lines (not baselines) of the number frame
+ // and the equation frame shall be aligned.
+ return equation.resize(
+ Size::new(width, equation.height().max(number.height())),
+ Axes::<FixedAlignment>::new(equation_align, FixedAlignment::Center),
+ );
+ }
+
+ let excess_above = Abs::zero().max({
+ if !is_multiline || matches!(number_align.y, FixedAlignment::Start) {
+ let (.., baseline) = first;
+ number.baseline() - baseline
+ } else {
+ Abs::zero()
+ }
+ });
+ let excess_below = Abs::zero().max({
+ if !is_multiline || matches!(number_align.y, FixedAlignment::End) {
+ let (size, .., baseline) = last;
+ (number.height() - number.baseline()) - (size.y - baseline)
+ } else {
+ Abs::zero()
+ }
+ });
+
+ // The vertical expansion is asymmetric on the top and bottom edges, so we
+ // first align at the top then translate the content downward later.
+ let resizing_offset = equation.resize(
+ Size::new(width, equation.height() + excess_above + excess_below),
+ Axes::<FixedAlignment>::new(equation_align, FixedAlignment::Start),
+ );
+ equation.translate(Point::with_y(excess_above));
+ resizing_offset + Point::with_y(excess_above)
+}
+
+/// The context for math layout.
+struct MathContext<'a, 'v, 'e> {
+ // External.
+ engine: &'v mut Engine<'e>,
+ locator: &'v mut SplitLocator<'a>,
+ region: Region,
+ // Font-related.
+ font: &'a Font,
+ ttf: &'a ttf_parser::Face<'a>,
+ table: ttf_parser::math::Table<'a>,
+ constants: ttf_parser::math::Constants<'a>,
+ ssty_table: Option<ttf_parser::gsub::AlternateSubstitution<'a>>,
+ glyphwise_tables: Option<Vec<GlyphwiseSubsts<'a>>>,
+ space_width: Em,
+ // Mutable.
+ fragments: Vec<MathFragment>,
+}
+
+impl<'a, 'v, 'e> MathContext<'a, 'v, 'e> {
+ /// Create a new math context.
+ fn new(
+ engine: &'v mut Engine<'e>,
+ locator: &'v mut SplitLocator<'a>,
+ styles: StyleChain<'a>,
+ base: Size,
+ font: &'a Font,
+ ) -> Self {
+ let math_table = font.ttf().tables().math.unwrap();
+ let gsub_table = font.ttf().tables().gsub;
+ let constants = math_table.constants.unwrap();
+
+ let ssty_table = gsub_table
+ .and_then(|gsub| {
+ gsub.features
+ .find(ttf_parser::Tag::from_bytes(b"ssty"))
+ .and_then(|feature| feature.lookup_indices.get(0))
+ .and_then(|index| gsub.lookups.get(index))
+ })
+ .and_then(|ssty| ssty.subtables.get::<SubstitutionSubtable>(0))
+ .and_then(|ssty| match ssty {
+ SubstitutionSubtable::Alternate(alt_glyphs) => Some(alt_glyphs),
+ _ => None,
+ });
+
+ let features = features(styles);
+ let glyphwise_tables = gsub_table.map(|gsub| {
+ features
+ .into_iter()
+ .filter_map(|feature| GlyphwiseSubsts::new(gsub, feature))
+ .collect()
+ });
+
+ let ttf = font.ttf();
+ let space_width = ttf
+ .glyph_index(' ')
+ .and_then(|id| ttf.glyph_hor_advance(id))
+ .map(|advance| font.to_em(advance))
+ .unwrap_or(THICK);
+
+ Self {
+ engine,
+ locator,
+ region: Region::new(base, Axes::splat(false)),
+ font,
+ ttf: font.ttf(),
+ table: math_table,
+ constants,
+ ssty_table,
+ glyphwise_tables,
+ space_width,
+ fragments: vec![],
+ }
+ }
+
+ /// Push a fragment.
+ fn push(&mut self, fragment: impl Into<MathFragment>) {
+ self.fragments.push(fragment.into());
+ }
+
+ /// Push multiple fragments.
+ fn extend(&mut self, fragments: impl IntoIterator<Item = MathFragment>) {
+ self.fragments.extend(fragments);
+ }
+
+ /// Layout the given element and return the result as a [`MathRun`].
+ fn layout_into_run(
+ &mut self,
+ elem: &Content,
+ styles: StyleChain,
+ ) -> SourceResult<MathRun> {
+ Ok(MathRun::new(self.layout_into_fragments(elem, styles)?))
+ }
+
+ /// Layout the given element and return the resulting [`MathFragment`]s.
+ fn layout_into_fragments(
+ &mut self,
+ elem: &Content,
+ styles: StyleChain,
+ ) -> SourceResult<Vec<MathFragment>> {
+ // The element's layout_math() changes the fragments held in this
+ // MathContext object, but for convenience this function shouldn't change
+ // them, so we restore the MathContext's fragments after obtaining the
+ // layout result.
+ let prev = std::mem::take(&mut self.fragments);
+ self.layout_into_self(elem, styles)?;
+ Ok(std::mem::replace(&mut self.fragments, prev))
+ }
+
+ /// Layout the given element and return the result as a
+ /// unified [`MathFragment`].
+ fn layout_into_fragment(
+ &mut self,
+ elem: &Content,
+ styles: StyleChain,
+ ) -> SourceResult<MathFragment> {
+ Ok(self.layout_into_run(elem, styles)?.into_fragment(self, styles))
+ }
+
+ /// Layout the given element and return the result as a [`Frame`].
+ fn layout_into_frame(
+ &mut self,
+ elem: &Content,
+ styles: StyleChain,
+ ) -> SourceResult<Frame> {
+ Ok(self.layout_into_fragment(elem, styles)?.into_frame())
+ }
+
+ /// Layout arbitrary content.
+ fn layout_into_self(
+ &mut self,
+ content: &Content,
+ styles: StyleChain,
+ ) -> SourceResult<()> {
+ let arenas = Arenas::default();
+ let pairs = (self.engine.routines.realize)(
+ RealizationKind::Math,
+ self.engine,
+ self.locator,
+ &arenas,
+ content,
+ styles,
+ )?;
+
+ let outer = styles;
+ for (elem, styles) in pairs {
+ // Hack because the font is fixed in math.
+ if styles != outer && TextElem::font_in(styles) != TextElem::font_in(outer) {
+ let frame = layout_external(elem, self, styles)?;
+ self.push(FrameFragment::new(self, styles, frame).with_spaced(true));
+ continue;
+ }
+
+ layout_realized(elem, self, styles)?;
+ }
+
+ Ok(())
+ }
+}
+
+/// Lays out a leaf element resulting from realization.
+fn layout_realized(
+ elem: &Content,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ if let Some(elem) = elem.to_packed::<TagElem>() {
+ ctx.push(MathFragment::Tag(elem.tag.clone()));
+ } else if elem.is::<SpaceElem>() {
+ let font_size = scaled_font_size(ctx, styles);
+ ctx.push(MathFragment::Space(ctx.space_width.at(font_size)));
+ } else if elem.is::<LinebreakElem>() {
+ ctx.push(MathFragment::Linebreak);
+ } else if let Some(elem) = elem.to_packed::<HElem>() {
+ layout_h(elem, ctx, styles)?;
+ } else if let Some(elem) = elem.to_packed::<TextElem>() {
+ self::text::layout_text(elem, ctx, styles)?;
+ } else if let Some(elem) = elem.to_packed::<BoxElem>() {
+ layout_box(elem, ctx, styles)?;
+ } else if elem.is::<AlignPointElem>() {
+ ctx.push(MathFragment::Align);
+ } else if let Some(elem) = elem.to_packed::<ClassElem>() {
+ layout_class(elem, ctx, styles)?;
+ } else if let Some(elem) = elem.to_packed::<AccentElem>() {
+ self::accent::layout_accent(elem, ctx, styles)?;
+ } else if let Some(elem) = elem.to_packed::<AttachElem>() {
+ self::attach::layout_attach(elem, ctx, styles)?;
+ } else if let Some(elem) = elem.to_packed::<PrimesElem>() {
+ self::attach::layout_primes(elem, ctx, styles)?;
+ } else if let Some(elem) = elem.to_packed::<ScriptsElem>() {
+ self::attach::layout_scripts(elem, ctx, styles)?;
+ } else if let Some(elem) = elem.to_packed::<LimitsElem>() {
+ self::attach::layout_limits(elem, ctx, styles)?;
+ } else if let Some(elem) = elem.to_packed::<CancelElem>() {
+ self::cancel::layout_cancel(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<FracElem>() {
+ self::frac::layout_frac(elem, ctx, styles)?;
+ } else if let Some(elem) = elem.to_packed::<BinomElem>() {
+ self::frac::layout_binom(elem, ctx, styles)?;
+ } else if let Some(elem) = elem.to_packed::<LrElem>() {
+ self::lr::layout_lr(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<MidElem>() {
+ self::lr::layout_mid(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<VecElem>() {
+ self::mat::layout_vec(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<MatElem>() {
+ self::mat::layout_mat(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<CasesElem>() {
+ self::mat::layout_cases(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<OpElem>() {
+ layout_op(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<RootElem>() {
+ self::root::layout_root(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<StretchElem>() {
+ self::stretch::layout_stretch(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<UnderlineElem>() {
+ self::underover::layout_underline(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<OverlineElem>() {
+ self::underover::layout_overline(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<UnderbraceElem>() {
+ self::underover::layout_underbrace(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<OverbraceElem>() {
+ self::underover::layout_overbrace(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<UnderbracketElem>() {
+ self::underover::layout_underbracket(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<OverbracketElem>() {
+ self::underover::layout_overbracket(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<UnderparenElem>() {
+ self::underover::layout_underparen(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<OverparenElem>() {
+ self::underover::layout_overparen(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<UndershellElem>() {
+ self::underover::layout_undershell(elem, ctx, styles)?
+ } else if let Some(elem) = elem.to_packed::<OvershellElem>() {
+ self::underover::layout_overshell(elem, ctx, styles)?
+ } else {
+ let mut frame = layout_external(elem, ctx, styles)?;
+ if !frame.has_baseline() {
+ let axis = scaled!(ctx, styles, axis_height);
+ frame.set_baseline(frame.height() / 2.0 + axis);
+ }
+ ctx.push(
+ FrameFragment::new(ctx, styles, frame)
+ .with_spaced(true)
+ .with_ignorant(elem.is::<PlaceElem>()),
+ );
+ }
+
+ Ok(())
+}
+
+/// Lays out an [`BoxElem`].
+fn layout_box(
+ elem: &Packed<BoxElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ let local = TextElem::set_size(TextSize(scaled_font_size(ctx, styles).into())).wrap();
+ let frame = (ctx.engine.routines.layout_box)(
+ elem,
+ ctx.engine,
+ ctx.locator.next(&elem.span()),
+ styles.chain(&local),
+ ctx.region.size,
+ )?;
+ ctx.push(FrameFragment::new(ctx, styles, frame).with_spaced(true));
+ Ok(())
+}
+
+/// Lays out an [`HElem`].
+fn layout_h(
+ elem: &Packed<HElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ if let Spacing::Rel(rel) = elem.amount() {
+ if rel.rel.is_zero() {
+ ctx.push(MathFragment::Spacing(
+ rel.abs.at(scaled_font_size(ctx, styles)),
+ elem.weak(styles),
+ ));
+ }
+ }
+ Ok(())
+}
+
+/// Lays out a [`ClassElem`].
+#[typst_macros::time(name = "math.op", span = elem.span())]
+fn layout_class(
+ elem: &Packed<ClassElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ let class = *elem.class();
+ let style = EquationElem::set_class(Some(class)).wrap();
+ let mut fragment = ctx.layout_into_fragment(elem.body(), styles.chain(&style))?;
+ fragment.set_class(class);
+ fragment.set_limits(Limits::for_class(class));
+ ctx.push(fragment);
+ Ok(())
+}
+
+/// Lays out an [`OpElem`].
+#[typst_macros::time(name = "math.op", span = elem.span())]
+fn layout_op(
+ elem: &Packed<OpElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ let fragment = ctx.layout_into_fragment(elem.text(), styles)?;
+ let italics = fragment.italics_correction();
+ let accent_attach = fragment.accent_attach();
+ let text_like = fragment.is_text_like();
+
+ ctx.push(
+ FrameFragment::new(ctx, styles, fragment.into_frame())
+ .with_class(MathClass::Large)
+ .with_italics_correction(italics)
+ .with_accent_attach(accent_attach)
+ .with_text_like(text_like)
+ .with_limits(if elem.limits(styles) {
+ Limits::Display
+ } else {
+ Limits::Never
+ }),
+ );
+ Ok(())
+}
+
+/// Layout into a frame with normal layout.
+fn layout_external(
+ content: &Content,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<Frame> {
+ let local = TextElem::set_size(TextSize(scaled_font_size(ctx, styles).into())).wrap();
+ (ctx.engine.routines.layout_frame)(
+ ctx.engine,
+ content,
+ ctx.locator.next(&content.span()),
+ styles.chain(&local),
+ ctx.region,
+ )
+}
diff --git a/crates/typst/src/math/root.rs b/crates/typst-layout/src/math/root.rs
index 66707c59..0bb2f539 100644
--- a/crates/typst/src/math/root.rs
+++ b/crates/typst-layout/src/math/root.rs
@@ -1,63 +1,26 @@
-use crate::diag::SourceResult;
-use crate::foundations::{elem, func, Content, NativeElement, Packed, StyleChain};
-use crate::layout::{Abs, Frame, FrameItem, Point, Size};
-use crate::math::{
- style_cramped, EquationElem, FrameFragment, GlyphFragment, LayoutMath, MathContext,
- MathSize, Scaled,
-};
-use crate::syntax::Span;
-use crate::text::TextElem;
-use crate::visualize::{FixedStroke, Geometry};
-
-/// A square root.
-///
-/// ```example
-/// $ sqrt(3 - 2 sqrt(2)) = sqrt(2) - 1 $
-/// ```
-#[func(title = "Square Root")]
-pub fn sqrt(
- /// The call span of this function.
- span: Span,
- /// The expression to take the square root of.
- radicand: Content,
-) -> Content {
- RootElem::new(radicand).pack().spanned(span)
-}
-
-/// A general root.
-///
-/// ```example
-/// $ root(3, x) $
-/// ```
-#[elem(LayoutMath)]
-pub struct RootElem {
- /// Which root of the radicand to take.
- #[positional]
- pub index: Option<Content>,
-
- /// The expression to take the root of.
- #[required]
- pub radicand: Content,
-}
+use typst_library::diag::SourceResult;
+use typst_library::foundations::{Packed, StyleChain};
+use typst_library::layout::{Abs, Frame, FrameItem, Point, Size};
+use typst_library::math::{EquationElem, MathSize, RootElem};
+use typst_library::text::TextElem;
+use typst_library::visualize::{FixedStroke, Geometry};
-impl LayoutMath for Packed<RootElem> {
- #[typst_macros::time(name = "math.root", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- layout(ctx, styles, self.index(styles).as_ref(), self.radicand(), self.span())
- }
-}
+use super::{style_cramped, FrameFragment, GlyphFragment, MathContext};
-/// Layout a root.
+/// Lays out a [`RootElem`].
///
/// TeXbook page 443, page 360
/// See also: <https://www.w3.org/TR/mathml-core/#radicals-msqrt-mroot>
-fn layout(
+#[typst_macros::time(name = "math.root", span = elem.span())]
+pub fn layout_root(
+ elem: &Packed<RootElem>,
ctx: &mut MathContext,
styles: StyleChain,
- index: Option<&Content>,
- radicand: &Content,
- span: Span,
) -> SourceResult<()> {
+ let index = elem.index(styles);
+ let radicand = elem.radicand();
+ let span = elem.span();
+
let gap = scaled!(
ctx, styles,
text: radical_vertical_gap,
@@ -94,6 +57,7 @@ fn layout(
// Layout the index.
let sscript = EquationElem::set_size(MathSize::ScriptScript).wrap();
let index = index
+ .as_ref()
.map(|elem| ctx.layout_into_frame(elem, styles.chain(&sscript)))
.transpose()?;
diff --git a/crates/typst/src/math/row.rs b/crates/typst-layout/src/math/run.rs
index a8422b1e..8f12c509 100644
--- a/crates/typst/src/math/row.rs
+++ b/crates/typst-layout/src/math/run.rs
@@ -1,16 +1,14 @@
use std::iter::once;
+use typst_library::foundations::{Resolve, StyleChain};
+use typst_library::layout::{Abs, AlignElem, Em, Frame, InlineItem, Point, Size};
+use typst_library::math::{EquationElem, MathSize, MEDIUM, THICK, THIN};
+use typst_library::model::ParElem;
use unicode_math_class::MathClass;
-use crate::foundations::{Resolve, StyleChain};
-use crate::layout::{Abs, AlignElem, Em, Frame, InlineItem, Point, Size};
-use crate::math::{
- alignments, scaled_font_size, spacing, EquationElem, FrameFragment, MathContext,
- MathFragment, MathSize,
-};
-use crate::model::ParElem;
+use super::{alignments, scaled_font_size, FrameFragment, MathContext, MathFragment};
-pub const TIGHT_LEADING: Em = Em::new(0.25);
+const TIGHT_LEADING: Em = Em::new(0.25);
/// A linear collection of [`MathFragment`]s.
#[derive(Debug, Default, Clone)]
@@ -423,3 +421,49 @@ impl MathRunFrameBuilder {
fn affects_row_height(fragment: &MathFragment) -> bool {
!matches!(fragment, MathFragment::Align | MathFragment::Linebreak)
}
+
+/// Create the spacing between two fragments in a given style.
+fn spacing(
+ l: &MathFragment,
+ space: Option<MathFragment>,
+ r: &MathFragment,
+) -> Option<MathFragment> {
+ use MathClass::*;
+
+ let resolve = |v: Em, size_ref: &MathFragment| -> Option<MathFragment> {
+ let width = size_ref.font_size().map_or(Abs::zero(), |size| v.at(size));
+ Some(MathFragment::Spacing(width, false))
+ };
+ let script = |f: &MathFragment| f.math_size().is_some_and(|s| s <= MathSize::Script);
+
+ match (l.class(), r.class()) {
+ // No spacing before punctuation; thin spacing after punctuation, unless
+ // in script size.
+ (_, Punctuation) => None,
+ (Punctuation, _) if !script(l) => resolve(THIN, l),
+
+ // No spacing after opening delimiters and before closing delimiters.
+ (Opening, _) | (_, Closing) => None,
+
+ // Thick spacing around relations, unless followed by a another relation
+ // or in script size.
+ (Relation, Relation) => None,
+ (Relation, _) if !script(l) => resolve(THICK, l),
+ (_, Relation) if !script(r) => resolve(THICK, r),
+
+ // Medium spacing around binary operators, unless in script size.
+ (Binary, _) if !script(l) => resolve(MEDIUM, l),
+ (_, Binary) if !script(r) => resolve(MEDIUM, r),
+
+ // Thin spacing around large operators, unless to the left of
+ // an opening delimiter. TeXBook, p170
+ (Large, Opening | Fence) => None,
+ (Large, _) => resolve(THIN, l),
+ (_, Large) => resolve(THIN, r),
+
+ // Spacing around spaced frames.
+ _ if (l.is_spaced() || r.is_spaced()) => space,
+
+ _ => None,
+ }
+}
diff --git a/crates/typst-layout/src/math/shared.rs b/crates/typst-layout/src/math/shared.rs
new file mode 100644
index 00000000..13477c10
--- /dev/null
+++ b/crates/typst-layout/src/math/shared.rs
@@ -0,0 +1,207 @@
+use ttf_parser::math::MathValue;
+use typst_library::foundations::{Style, StyleChain};
+use typst_library::layout::{Abs, Em, FixedAlignment, Frame, Point, Size, VAlignment};
+use typst_library::math::{EquationElem, MathSize};
+use typst_library::text::TextElem;
+use typst_utils::LazyHash;
+
+use super::{LeftRightAlternator, MathContext, MathFragment, MathRun};
+
+macro_rules! scaled {
+ ($ctx:expr, $styles:expr, text: $text:ident, display: $display:ident $(,)?) => {
+ match typst_library::math::EquationElem::size_in($styles) {
+ typst_library::math::MathSize::Display => scaled!($ctx, $styles, $display),
+ _ => scaled!($ctx, $styles, $text),
+ }
+ };
+ ($ctx:expr, $styles:expr, $name:ident) => {
+ $crate::math::Scaled::scaled(
+ $ctx.constants.$name(),
+ $ctx,
+ $crate::math::scaled_font_size($ctx, $styles),
+ )
+ };
+}
+
+macro_rules! percent {
+ ($ctx:expr, $name:ident) => {
+ $ctx.constants.$name() as f64 / 100.0
+ };
+}
+
+/// How much less high scaled delimiters can be than what they wrap.
+pub const DELIM_SHORT_FALL: Em = Em::new(0.1);
+
+/// Converts some unit to an absolute length with the current font & font size.
+pub trait Scaled {
+ fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs;
+}
+
+impl Scaled for i16 {
+ fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
+ ctx.font.to_em(self).at(font_size)
+ }
+}
+
+impl Scaled for u16 {
+ fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
+ ctx.font.to_em(self).at(font_size)
+ }
+}
+
+impl Scaled for MathValue<'_> {
+ fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
+ self.value.scaled(ctx, font_size)
+ }
+}
+
+/// Get the font size scaled with the `MathSize`.
+pub fn scaled_font_size(ctx: &MathContext, styles: StyleChain) -> Abs {
+ let factor = match EquationElem::size_in(styles) {
+ MathSize::Display | MathSize::Text => 1.0,
+ MathSize::Script => percent!(ctx, script_percent_scale_down),
+ MathSize::ScriptScript => percent!(ctx, script_script_percent_scale_down),
+ };
+ factor * TextElem::size_in(styles)
+}
+
+/// Styles something as cramped.
+pub fn style_cramped() -> LazyHash<Style> {
+ EquationElem::set_cramped(true).wrap()
+}
+
+/// The style for subscripts in the current style.
+pub fn style_for_subscript(styles: StyleChain) -> [LazyHash<Style>; 2] {
+ [style_for_superscript(styles), EquationElem::set_cramped(true).wrap()]
+}
+
+/// The style for superscripts in the current style.
+pub fn style_for_superscript(styles: StyleChain) -> LazyHash<Style> {
+ EquationElem::set_size(match EquationElem::size_in(styles) {
+ MathSize::Display | MathSize::Text => MathSize::Script,
+ MathSize::Script | MathSize::ScriptScript => MathSize::ScriptScript,
+ })
+ .wrap()
+}
+
+/// The style for numerators in the current style.
+pub fn style_for_numerator(styles: StyleChain) -> LazyHash<Style> {
+ EquationElem::set_size(match EquationElem::size_in(styles) {
+ MathSize::Display => MathSize::Text,
+ MathSize::Text => MathSize::Script,
+ MathSize::Script | MathSize::ScriptScript => MathSize::ScriptScript,
+ })
+ .wrap()
+}
+
+/// The style for denominators in the current style.
+pub fn style_for_denominator(styles: StyleChain) -> [LazyHash<Style>; 2] {
+ [style_for_numerator(styles), EquationElem::set_cramped(true).wrap()]
+}
+
+/// How a delimieter should be aligned when scaling.
+pub fn delimiter_alignment(delimiter: char) -> VAlignment {
+ match delimiter {
+ '⌜' | '⌝' => VAlignment::Top,
+ '⌞' | '⌟' => VAlignment::Bottom,
+ _ => VAlignment::Horizon,
+ }
+}
+
+/// Stack rows on top of each other.
+///
+/// Add a `gap` between each row and uses the baseline of the `baseline`-th
+/// row for the whole frame. `alternator` controls the left/right alternating
+/// alignment behavior of `AlignPointElem` in the rows.
+pub fn stack(
+ rows: Vec<MathRun>,
+ align: FixedAlignment,
+ gap: Abs,
+ baseline: usize,
+ alternator: LeftRightAlternator,
+ minimum_ascent_descent: Option<(Abs, Abs)>,
+) -> Frame {
+ let rows: Vec<_> = rows.into_iter().flat_map(|r| r.rows()).collect();
+ let AlignmentResult { points, width } = alignments(&rows);
+ let rows: Vec<_> = rows
+ .into_iter()
+ .map(|row| row.into_line_frame(&points, alternator))
+ .collect();
+
+ let padded_height = |height: Abs| {
+ height.max(minimum_ascent_descent.map_or(Abs::zero(), |(a, d)| a + d))
+ };
+
+ let mut frame = Frame::soft(Size::new(
+ width,
+ rows.iter().map(|row| padded_height(row.height())).sum::<Abs>()
+ + rows.len().saturating_sub(1) as f64 * gap,
+ ));
+
+ let mut y = Abs::zero();
+ for (i, row) in rows.into_iter().enumerate() {
+ let x = if points.is_empty() {
+ align.position(width - row.width())
+ } else {
+ Abs::zero()
+ };
+ let ascent_padded_part = minimum_ascent_descent
+ .map_or(Abs::zero(), |(a, _)| (a - row.ascent()))
+ .max(Abs::zero());
+ let pos = Point::new(x, y + ascent_padded_part);
+ if i == baseline {
+ frame.set_baseline(y + row.baseline() + ascent_padded_part);
+ }
+ y += padded_height(row.height()) + gap;
+ frame.push_frame(pos, row);
+ }
+
+ frame
+}
+
+/// Determine the positions of the alignment points, according to the input rows combined.
+pub fn alignments(rows: &[MathRun]) -> AlignmentResult {
+ let mut widths = Vec::<Abs>::new();
+
+ let mut pending_width = Abs::zero();
+ for row in rows {
+ let mut width = Abs::zero();
+ let mut alignment_index = 0;
+
+ for fragment in row.iter() {
+ if matches!(fragment, MathFragment::Align) {
+ if alignment_index < widths.len() {
+ widths[alignment_index].set_max(width);
+ } else {
+ widths.push(width.max(pending_width));
+ }
+ width = Abs::zero();
+ alignment_index += 1;
+ } else {
+ width += fragment.width();
+ }
+ }
+ if widths.is_empty() {
+ pending_width.set_max(width);
+ } else if alignment_index < widths.len() {
+ widths[alignment_index].set_max(width);
+ } else {
+ widths.push(width.max(pending_width));
+ }
+ }
+
+ let mut points = widths;
+ for i in 1..points.len() {
+ let prev = points[i - 1];
+ points[i] += prev;
+ }
+ AlignmentResult {
+ width: points.last().copied().unwrap_or(pending_width),
+ points,
+ }
+}
+
+pub struct AlignmentResult {
+ pub points: Vec<Abs>,
+ pub width: Abs,
+}
diff --git a/crates/typst/src/math/stretch.rs b/crates/typst-layout/src/math/stretch.rs
index 18a42b18..6dc82014 100644
--- a/crates/typst/src/math/stretch.rs
+++ b/crates/typst-layout/src/math/stretch.rs
@@ -1,64 +1,42 @@
use ttf_parser::math::{GlyphAssembly, GlyphConstruction, GlyphPart};
use ttf_parser::LazyArray16;
-
-use crate::diag::SourceResult;
-use crate::foundations::{elem, Content, Packed, Smart, StyleChain};
-use crate::layout::{Abs, Axis, Frame, Length, Point, Rel, Size, VAlignment};
-use crate::math::{
- scaled_font_size, GlyphFragment, LayoutMath, MathContext, MathFragment, Scaled,
- VariantFragment,
+use typst_library::diag::SourceResult;
+use typst_library::foundations::{Packed, Smart, StyleChain};
+use typst_library::layout::{Abs, Axis, Frame, Length, Point, Rel, Size};
+use typst_library::math::StretchElem;
+use typst_utils::Get;
+
+use super::{
+ delimiter_alignment, scaled_font_size, GlyphFragment, MathContext, MathFragment,
+ Scaled, VariantFragment,
};
-use crate::utils::Get;
/// Maximum number of times extenders can be repeated.
const MAX_REPEATS: usize = 1024;
-/// Stretches a glyph.
-///
-/// This function can also be used to automatically stretch the base of an
-/// attachment, so that it fits the top and bottom attachments.
-///
-/// Note that only some glyphs can be stretched, and which ones can depend on
-/// the math font being used. However, most math fonts are the same in this
-/// regard.
-///
-/// ```example
-/// $ H stretch(=)^"define" U + p V $
-/// $ f : X stretch(->>, size: #150%)_"surjective" Y $
-/// $ x stretch(harpoons.ltrb, size: #3em) y
-/// stretch(\[, size: #150%) z $
-/// ```
-#[elem(LayoutMath)]
-pub struct StretchElem {
- /// The glyph to stretch.
- #[required]
- pub body: Content,
-
- /// The size to stretch to, relative to the maximum size of the glyph and
- /// its attachments.
- pub size: Smart<Rel<Length>>,
-}
-
-impl LayoutMath for Packed<StretchElem> {
- #[typst_macros::time(name = "math.stretch", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- let mut fragment = ctx.layout_into_fragment(self.body(), styles)?;
- stretch_fragment(
- ctx,
- styles,
- &mut fragment,
- None,
- None,
- self.size(styles),
- Abs::zero(),
- );
- ctx.push(fragment);
- Ok(())
- }
+/// Lays out a [`StretchElem`].
+#[typst_macros::time(name = "math.stretch", span = elem.span())]
+pub fn layout_stretch(
+ elem: &Packed<StretchElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ let mut fragment = ctx.layout_into_fragment(elem.body(), styles)?;
+ stretch_fragment(
+ ctx,
+ styles,
+ &mut fragment,
+ None,
+ None,
+ elem.size(styles),
+ Abs::zero(),
+ );
+ ctx.push(fragment);
+ Ok(())
}
/// Attempts to stretch the given fragment by/to the amount given in stretch.
-pub(super) fn stretch_fragment(
+pub fn stretch_fragment(
ctx: &mut MathContext,
styles: StyleChain,
fragment: &mut MathFragment,
@@ -77,9 +55,7 @@ pub(super) fn stretch_fragment(
// Return if we attempt to stretch along an axis which isn't stretchable,
// so that the original fragment isn't modified.
- let Some(stretch_axis) = stretch_axis(ctx, &glyph) else {
- return;
- };
+ let Some(stretch_axis) = stretch_axis(ctx, &glyph) else { return };
let axis = axis.unwrap_or(stretch_axis);
if axis != stretch_axis {
return;
@@ -105,67 +81,10 @@ pub(super) fn stretch_fragment(
*fragment = MathFragment::Variant(variant);
}
-pub(super) fn delimiter_alignment(delimiter: char) -> VAlignment {
- match delimiter {
- '\u{231c}' | '\u{231d}' => VAlignment::Top,
- '\u{231e}' | '\u{231f}' => VAlignment::Bottom,
- _ => VAlignment::Horizon,
- }
-}
-
-/// Return whether the glyph is stretchable and if it is, along which axis it
-/// can be stretched.
-fn stretch_axis(ctx: &MathContext, base: &GlyphFragment) -> Option<Axis> {
- let base_id = base.id;
- let vertical = ctx
- .table
- .variants
- .and_then(|variants| variants.vertical_constructions.get(base_id))
- .map(|_| Axis::Y);
- let horizontal = ctx
- .table
- .variants
- .and_then(|variants| variants.horizontal_constructions.get(base_id))
- .map(|_| Axis::X);
-
- match (vertical, horizontal) {
- (vertical, None) => vertical,
- (None, horizontal) => horizontal,
- _ => {
- // As far as we know, there aren't any glyphs that have both
- // vertical and horizontal constructions. So for the time being, we
- // will assume that a glyph cannot have both.
- panic!("glyph {:?} has both vertical and horizontal constructions", base.c);
- }
- }
-}
-
-impl GlyphFragment {
- /// Try to stretch a glyph to a desired height.
- pub fn stretch_vertical(
- self,
- ctx: &MathContext,
- height: Abs,
- short_fall: Abs,
- ) -> VariantFragment {
- stretch_glyph(ctx, self, height, short_fall, Axis::Y)
- }
-
- /// Try to stretch a glyph to a desired width.
- pub fn stretch_horizontal(
- self,
- ctx: &MathContext,
- width: Abs,
- short_fall: Abs,
- ) -> VariantFragment {
- stretch_glyph(ctx, self, width, short_fall, Axis::X)
- }
-}
-
/// Try to stretch a glyph to a desired width or height.
///
/// The resulting frame may not have the exact desired width.
-fn stretch_glyph(
+pub fn stretch_glyph(
ctx: &MathContext,
mut base: GlyphFragment,
target: Abs,
@@ -218,6 +137,33 @@ fn stretch_glyph(
assemble(ctx, base, assembly, min_overlap, target, axis)
}
+/// Return whether the glyph is stretchable and if it is, along which axis it
+/// can be stretched.
+fn stretch_axis(ctx: &MathContext, base: &GlyphFragment) -> Option<Axis> {
+ let base_id = base.id;
+ let vertical = ctx
+ .table
+ .variants
+ .and_then(|variants| variants.vertical_constructions.get(base_id))
+ .map(|_| Axis::Y);
+ let horizontal = ctx
+ .table
+ .variants
+ .and_then(|variants| variants.horizontal_constructions.get(base_id))
+ .map(|_| Axis::X);
+
+ match (vertical, horizontal) {
+ (vertical, None) => vertical,
+ (None, horizontal) => horizontal,
+ _ => {
+ // As far as we know, there aren't any glyphs that have both
+ // vertical and horizontal constructions. So for the time being, we
+ // will assume that a glyph cannot have both.
+ panic!("glyph {:?} has both vertical and horizontal constructions", base.c);
+ }
+ }
+}
+
/// Assemble a glyph from parts.
fn assemble(
ctx: &MathContext,
@@ -323,7 +269,6 @@ fn assemble(
VariantFragment {
c: base.c,
- id: None,
frame,
font_size: base.font_size,
italics_correction: Abs::zero(),
diff --git a/crates/typst-layout/src/math/text.rs b/crates/typst-layout/src/math/text.rs
new file mode 100644
index 00000000..df80b45a
--- /dev/null
+++ b/crates/typst-layout/src/math/text.rs
@@ -0,0 +1,344 @@
+use std::f64::consts::SQRT_2;
+
+use ecow::{eco_vec, EcoString};
+use typst_library::diag::SourceResult;
+use typst_library::foundations::{Packed, StyleChain, StyleVec};
+use typst_library::layout::{Abs, Size};
+use typst_library::math::{EquationElem, MathSize, MathVariant};
+use typst_library::text::{
+ BottomEdge, BottomEdgeMetric, TextElem, TextSize, TopEdge, TopEdgeMetric,
+};
+use typst_syntax::{is_newline, Span};
+use unicode_math_class::MathClass;
+use unicode_segmentation::UnicodeSegmentation;
+
+use super::{
+ scaled_font_size, FrameFragment, GlyphFragment, MathContext, MathFragment, MathRun,
+};
+
+/// Lays out a [`TextElem`].
+pub fn layout_text(
+ elem: &Packed<TextElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ let text = elem.text();
+ let span = elem.span();
+ let mut chars = text.chars();
+ let math_size = EquationElem::size_in(styles);
+
+ let fragment: MathFragment = if let Some(mut glyph) = chars
+ .next()
+ .filter(|_| chars.next().is_none())
+ .map(|c| styled_char(styles, c, true))
+ .and_then(|c| GlyphFragment::try_new(ctx, styles, c, span))
+ {
+ // A single letter that is available in the math font.
+ match math_size {
+ MathSize::Script => {
+ glyph.make_scriptsize(ctx);
+ }
+ MathSize::ScriptScript => {
+ glyph.make_scriptscriptsize(ctx);
+ }
+ _ => (),
+ }
+
+ if glyph.class == MathClass::Large {
+ let mut variant = if math_size == MathSize::Display {
+ let height = scaled!(ctx, styles, display_operator_min_height)
+ .max(SQRT_2 * glyph.height());
+ glyph.stretch_vertical(ctx, height, Abs::zero())
+ } else {
+ glyph.into_variant()
+ };
+ // TeXbook p 155. Large operators are always vertically centered on the axis.
+ variant.center_on_axis(ctx);
+ variant.into()
+ } else {
+ glyph.into()
+ }
+ } else if text.chars().all(|c| c.is_ascii_digit() || c == '.') {
+ // Numbers aren't that difficult.
+ let mut fragments = vec![];
+ for c in text.chars() {
+ let c = styled_char(styles, c, false);
+ fragments.push(GlyphFragment::new(ctx, styles, c, span).into());
+ }
+ let frame = MathRun::new(fragments).into_frame(ctx, styles);
+ FrameFragment::new(ctx, styles, frame).with_text_like(true).into()
+ } else {
+ let local = [
+ TextElem::set_top_edge(TopEdge::Metric(TopEdgeMetric::Bounds)),
+ TextElem::set_bottom_edge(BottomEdge::Metric(BottomEdgeMetric::Bounds)),
+ TextElem::set_size(TextSize(scaled_font_size(ctx, styles).into())),
+ ]
+ .map(|p| p.wrap());
+
+ // Anything else is handled by Typst's standard text layout.
+ let styles = styles.chain(&local);
+ let text: EcoString =
+ text.chars().map(|c| styled_char(styles, c, false)).collect();
+ if text.contains(is_newline) {
+ let mut fragments = vec![];
+ for (i, piece) in text.split(is_newline).enumerate() {
+ if i != 0 {
+ fragments.push(MathFragment::Linebreak);
+ }
+ if !piece.is_empty() {
+ fragments.push(layout_complex_text(piece, ctx, span, styles)?.into());
+ }
+ }
+ let mut frame = MathRun::new(fragments).into_frame(ctx, styles);
+ let axis = scaled!(ctx, styles, axis_height);
+ frame.set_baseline(frame.height() / 2.0 + axis);
+ FrameFragment::new(ctx, styles, frame).into()
+ } else {
+ layout_complex_text(&text, ctx, span, styles)?.into()
+ }
+ };
+
+ ctx.push(fragment);
+ Ok(())
+}
+
+/// Layout the given text string into a [`FrameFragment`].
+fn layout_complex_text(
+ text: &str,
+ ctx: &mut MathContext,
+ span: Span,
+ styles: StyleChain,
+) -> SourceResult<FrameFragment> {
+ // There isn't a natural width for a paragraph in a math environment;
+ // because it will be placed somewhere probably not at the left margin
+ // it will overflow. So emulate an `hbox` instead and allow the paragraph
+ // to extend as far as needed.
+ let spaced = text.graphemes(true).nth(1).is_some();
+ let elem = TextElem::packed(text).spanned(span);
+ let frame = (ctx.engine.routines.layout_inline)(
+ ctx.engine,
+ &StyleVec::wrap(eco_vec![elem]),
+ ctx.locator.next(&span),
+ styles,
+ false,
+ Size::splat(Abs::inf()),
+ false,
+ )?
+ .into_frame();
+
+ Ok(FrameFragment::new(ctx, styles, frame)
+ .with_class(MathClass::Alphabetic)
+ .with_text_like(true)
+ .with_spaced(spaced))
+}
+
+/// Select the correct styled math letter.
+///
+/// <https://www.w3.org/TR/mathml-core/#new-text-transform-mappings>
+/// <https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols>
+fn styled_char(styles: StyleChain, c: char, auto_italic: bool) -> char {
+ use MathVariant::*;
+
+ let variant = EquationElem::variant_in(styles);
+ let bold = EquationElem::bold_in(styles);
+ let italic = EquationElem::italic_in(styles).unwrap_or(
+ auto_italic
+ && matches!(
+ c,
+ 'a'..='z' | 'ı' | 'ȷ' | 'A'..='Z' | 'α'..='ω' |
+ '∂' | 'ϵ' | 'ϑ' | 'ϰ' | 'ϕ' | 'ϱ' | 'ϖ'
+ )
+ && matches!(variant, Sans | Serif),
+ );
+
+ if let Some(c) = basic_exception(c) {
+ return c;
+ }
+
+ if let Some(c) = latin_exception(c, variant, bold, italic) {
+ return c;
+ }
+
+ if let Some(c) = greek_exception(c, variant, bold, italic) {
+ return c;
+ }
+
+ let base = match c {
+ 'A'..='Z' => 'A',
+ 'a'..='z' => 'a',
+ 'Α'..='Ω' => 'Α',
+ 'α'..='ω' => 'α',
+ '0'..='9' => '0',
+ // Hebrew Alef -> Dalet.
+ '\u{05D0}'..='\u{05D3}' => '\u{05D0}',
+ _ => return c,
+ };
+
+ let tuple = (variant, bold, italic);
+ let start = match c {
+ // Latin upper.
+ 'A'..='Z' => match tuple {
+ (Serif, false, false) => 0x0041,
+ (Serif, true, false) => 0x1D400,
+ (Serif, false, true) => 0x1D434,
+ (Serif, true, true) => 0x1D468,
+ (Sans, false, false) => 0x1D5A0,
+ (Sans, true, false) => 0x1D5D4,
+ (Sans, false, true) => 0x1D608,
+ (Sans, true, true) => 0x1D63C,
+ (Cal, false, _) => 0x1D49C,
+ (Cal, true, _) => 0x1D4D0,
+ (Frak, false, _) => 0x1D504,
+ (Frak, true, _) => 0x1D56C,
+ (Mono, _, _) => 0x1D670,
+ (Bb, _, _) => 0x1D538,
+ },
+
+ // Latin lower.
+ 'a'..='z' => match tuple {
+ (Serif, false, false) => 0x0061,
+ (Serif, true, false) => 0x1D41A,
+ (Serif, false, true) => 0x1D44E,
+ (Serif, true, true) => 0x1D482,
+ (Sans, false, false) => 0x1D5BA,
+ (Sans, true, false) => 0x1D5EE,
+ (Sans, false, true) => 0x1D622,
+ (Sans, true, true) => 0x1D656,
+ (Cal, false, _) => 0x1D4B6,
+ (Cal, true, _) => 0x1D4EA,
+ (Frak, false, _) => 0x1D51E,
+ (Frak, true, _) => 0x1D586,
+ (Mono, _, _) => 0x1D68A,
+ (Bb, _, _) => 0x1D552,
+ },
+
+ // Greek upper.
+ 'Α'..='Ω' => match tuple {
+ (Serif, false, false) => 0x0391,
+ (Serif, true, false) => 0x1D6A8,
+ (Serif, false, true) => 0x1D6E2,
+ (Serif, true, true) => 0x1D71C,
+ (Sans, _, false) => 0x1D756,
+ (Sans, _, true) => 0x1D790,
+ (Cal | Frak | Mono | Bb, _, _) => return c,
+ },
+
+ // Greek lower.
+ 'α'..='ω' => match tuple {
+ (Serif, false, false) => 0x03B1,
+ (Serif, true, false) => 0x1D6C2,
+ (Serif, false, true) => 0x1D6FC,
+ (Serif, true, true) => 0x1D736,
+ (Sans, _, false) => 0x1D770,
+ (Sans, _, true) => 0x1D7AA,
+ (Cal | Frak | Mono | Bb, _, _) => return c,
+ },
+
+ // Hebrew Alef -> Dalet.
+ '\u{05D0}'..='\u{05D3}' => 0x2135,
+
+ // Numbers.
+ '0'..='9' => match tuple {
+ (Serif, false, _) => 0x0030,
+ (Serif, true, _) => 0x1D7CE,
+ (Bb, _, _) => 0x1D7D8,
+ (Sans, false, _) => 0x1D7E2,
+ (Sans, true, _) => 0x1D7EC,
+ (Mono, _, _) => 0x1D7F6,
+ (Cal | Frak, _, _) => return c,
+ },
+
+ _ => unreachable!(),
+ };
+
+ std::char::from_u32(start + (c as u32 - base as u32)).unwrap()
+}
+
+fn basic_exception(c: char) -> Option<char> {
+ Some(match c {
+ '〈' => '⟨',
+ '〉' => '⟩',
+ '《' => '⟪',
+ '》' => '⟫',
+ _ => return None,
+ })
+}
+
+fn latin_exception(
+ c: char,
+ variant: MathVariant,
+ bold: bool,
+ italic: bool,
+) -> Option<char> {
+ use MathVariant::*;
+ Some(match (c, variant, bold, italic) {
+ ('B', Cal, false, _) => 'ℬ',
+ ('E', Cal, false, _) => 'ℰ',
+ ('F', Cal, false, _) => 'ℱ',
+ ('H', Cal, false, _) => 'ℋ',
+ ('I', Cal, false, _) => 'ℐ',
+ ('L', Cal, false, _) => 'ℒ',
+ ('M', Cal, false, _) => 'ℳ',
+ ('R', Cal, false, _) => 'ℛ',
+ ('C', Frak, false, _) => 'ℭ',
+ ('H', Frak, false, _) => 'ℌ',
+ ('I', Frak, false, _) => 'ℑ',
+ ('R', Frak, false, _) => 'ℜ',
+ ('Z', Frak, false, _) => 'ℨ',
+ ('C', Bb, ..) => 'ℂ',
+ ('H', Bb, ..) => 'ℍ',
+ ('N', Bb, ..) => 'ℕ',
+ ('P', Bb, ..) => 'ℙ',
+ ('Q', Bb, ..) => 'ℚ',
+ ('R', Bb, ..) => 'ℝ',
+ ('Z', Bb, ..) => 'ℤ',
+ ('D', Bb, _, true) => 'ⅅ',
+ ('d', Bb, _, true) => 'ⅆ',
+ ('e', Bb, _, true) => 'ⅇ',
+ ('i', Bb, _, true) => 'ⅈ',
+ ('j', Bb, _, true) => 'ⅉ',
+ ('h', Serif, false, true) => 'ℎ',
+ ('e', Cal, false, _) => 'ℯ',
+ ('g', Cal, false, _) => 'ℊ',
+ ('o', Cal, false, _) => 'ℴ',
+ ('ı', Serif, .., true) => '𝚤',
+ ('ȷ', Serif, .., true) => '𝚥',
+ _ => return None,
+ })
+}
+
+fn greek_exception(
+ c: char,
+ variant: MathVariant,
+ bold: bool,
+ italic: bool,
+) -> Option<char> {
+ use MathVariant::*;
+ let list = match c {
+ 'ϴ' => ['𝚹', '𝛳', '𝜭', '𝝧', '𝞡', 'ϴ'],
+ '∇' => ['𝛁', '𝛻', '𝜵', '𝝯', '𝞩', '∇'],
+ '∂' => ['𝛛', '𝜕', '𝝏', '𝞉', '𝟃', '∂'],
+ 'ϵ' => ['𝛜', '𝜖', '𝝐', '𝞊', '𝟄', 'ϵ'],
+ 'ϑ' => ['𝛝', '𝜗', '𝝑', '𝞋', '𝟅', 'ϑ'],
+ 'ϰ' => ['𝛞', '𝜘', '𝝒', '𝞌', '𝟆', 'ϰ'],
+ 'ϕ' => ['𝛟', '𝜙', '𝝓', '𝞍', '𝟇', 'ϕ'],
+ 'ϱ' => ['𝛠', '𝜚', '𝝔', '𝞎', '𝟈', 'ϱ'],
+ 'ϖ' => ['𝛡', '𝜛', '𝝕', '𝞏', '𝟉', 'ϖ'],
+ 'Γ' => ['𝚪', '𝛤', '𝜞', '𝝘', '𝞒', 'ℾ'],
+ 'γ' => ['𝛄', '𝛾', '𝜸', '𝝲', '𝞬', 'ℽ'],
+ 'Π' => ['𝚷', '𝛱', '𝜫', '𝝥', '𝞟', 'ℿ'],
+ 'π' => ['𝛑', '𝜋', '𝝅', '𝝿', '𝞹', 'ℼ'],
+ '∑' => ['∑', '∑', '∑', '∑', '∑', '⅀'],
+ _ => return None,
+ };
+
+ Some(match (variant, bold, italic) {
+ (Serif, true, false) => list[0],
+ (Serif, false, true) => list[1],
+ (Serif, true, true) => list[2],
+ (Sans, _, false) => list[3],
+ (Sans, _, true) => list[4],
+ (Bb, ..) => list[5],
+ _ => return None,
+ })
+}
diff --git a/crates/typst-layout/src/math/underover.rs b/crates/typst-layout/src/math/underover.rs
new file mode 100644
index 00000000..b1d4825b
--- /dev/null
+++ b/crates/typst-layout/src/math/underover.rs
@@ -0,0 +1,327 @@
+use typst_library::diag::SourceResult;
+use typst_library::foundations::{Content, Packed, StyleChain};
+use typst_library::layout::{Abs, Em, FixedAlignment, Frame, FrameItem, Point, Size};
+use typst_library::math::{
+ OverbraceElem, OverbracketElem, OverlineElem, OverparenElem, OvershellElem,
+ UnderbraceElem, UnderbracketElem, UnderlineElem, UnderparenElem, UndershellElem,
+};
+use typst_library::text::TextElem;
+use typst_library::visualize::{FixedStroke, Geometry};
+use typst_syntax::Span;
+
+use super::{
+ scaled_font_size, stack, style_cramped, style_for_subscript, style_for_superscript,
+ FrameFragment, GlyphFragment, LeftRightAlternator, MathContext, MathRun,
+};
+
+const BRACE_GAP: Em = Em::new(0.25);
+const BRACKET_GAP: Em = Em::new(0.25);
+const PAREN_GAP: Em = Em::new(0.25);
+const SHELL_GAP: Em = Em::new(0.25);
+
+/// A marker to distinguish under- and overlines.
+enum Position {
+ Under,
+ Over,
+}
+
+/// Lays out an [`UnderlineElem`].
+#[typst_macros::time(name = "math.underline", span = elem.span())]
+pub fn layout_underline(
+ elem: &Packed<UnderlineElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverline(ctx, styles, elem.body(), elem.span(), Position::Under)
+}
+
+/// Lays out an [`OverlineElem`].
+#[typst_macros::time(name = "math.overline", span = elem.span())]
+pub fn layout_overline(
+ elem: &Packed<OverlineElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverline(ctx, styles, elem.body(), elem.span(), Position::Over)
+}
+
+/// Lays out an [`UnderbraceElem`].
+#[typst_macros::time(name = "math.underbrace", span = elem.span())]
+pub fn layout_underbrace(
+ elem: &Packed<UnderbraceElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverspreader(
+ ctx,
+ styles,
+ elem.body(),
+ &elem.annotation(styles),
+ '⏟',
+ BRACE_GAP,
+ Position::Under,
+ elem.span(),
+ )
+}
+
+/// Lays out an [`OverbraceElem`].
+#[typst_macros::time(name = "math.overbrace", span = elem.span())]
+pub fn layout_overbrace(
+ elem: &Packed<OverbraceElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverspreader(
+ ctx,
+ styles,
+ elem.body(),
+ &elem.annotation(styles),
+ '⏞',
+ BRACE_GAP,
+ Position::Over,
+ elem.span(),
+ )
+}
+
+/// Lays out an [`UnderbracketElem`].
+#[typst_macros::time(name = "math.underbracket", span = elem.span())]
+pub fn layout_underbracket(
+ elem: &Packed<UnderbracketElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverspreader(
+ ctx,
+ styles,
+ elem.body(),
+ &elem.annotation(styles),
+ '⎵',
+ BRACKET_GAP,
+ Position::Under,
+ elem.span(),
+ )
+}
+
+/// Lays out an [`OverbracketElem`].
+#[typst_macros::time(name = "math.overbracket", span = elem.span())]
+pub fn layout_overbracket(
+ elem: &Packed<OverbracketElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverspreader(
+ ctx,
+ styles,
+ elem.body(),
+ &elem.annotation(styles),
+ '⎴',
+ BRACKET_GAP,
+ Position::Over,
+ elem.span(),
+ )
+}
+
+/// Lays out an [`UnderparenElem`].
+#[typst_macros::time(name = "math.underparen", span = elem.span())]
+pub fn layout_underparen(
+ elem: &Packed<UnderparenElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverspreader(
+ ctx,
+ styles,
+ elem.body(),
+ &elem.annotation(styles),
+ '⏝',
+ PAREN_GAP,
+ Position::Under,
+ elem.span(),
+ )
+}
+
+/// Lays out an [`OverparenElem`].
+#[typst_macros::time(name = "math.overparen", span = elem.span())]
+pub fn layout_overparen(
+ elem: &Packed<OverparenElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverspreader(
+ ctx,
+ styles,
+ elem.body(),
+ &elem.annotation(styles),
+ '⏜',
+ PAREN_GAP,
+ Position::Over,
+ elem.span(),
+ )
+}
+
+/// Lays out an [`UndershellElem`].
+#[typst_macros::time(name = "math.undershell", span = elem.span())]
+pub fn layout_undershell(
+ elem: &Packed<UndershellElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverspreader(
+ ctx,
+ styles,
+ elem.body(),
+ &elem.annotation(styles),
+ '⏡',
+ SHELL_GAP,
+ Position::Under,
+ elem.span(),
+ )
+}
+
+/// Lays out an [`OvershellElem`].
+#[typst_macros::time(name = "math.overshell", span = elem.span())]
+pub fn layout_overshell(
+ elem: &Packed<OvershellElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverspreader(
+ ctx,
+ styles,
+ elem.body(),
+ &elem.annotation(styles),
+ '⏠',
+ SHELL_GAP,
+ Position::Over,
+ elem.span(),
+ )
+}
+
+/// layout under- or overlined content.
+fn layout_underoverline(
+ ctx: &mut MathContext,
+ styles: StyleChain,
+ body: &Content,
+ span: Span,
+ position: Position,
+) -> SourceResult<()> {
+ let (extra_height, content, line_pos, content_pos, baseline, bar_height, line_adjust);
+ match position {
+ Position::Under => {
+ let sep = scaled!(ctx, styles, underbar_extra_descender);
+ bar_height = scaled!(ctx, styles, underbar_rule_thickness);
+ let gap = scaled!(ctx, styles, underbar_vertical_gap);
+ extra_height = sep + bar_height + gap;
+
+ content = ctx.layout_into_fragment(body, styles)?;
+
+ line_pos = Point::with_y(content.height() + gap + bar_height / 2.0);
+ content_pos = Point::zero();
+ baseline = content.ascent();
+ line_adjust = -content.italics_correction();
+ }
+ Position::Over => {
+ let sep = scaled!(ctx, styles, overbar_extra_ascender);
+ bar_height = scaled!(ctx, styles, overbar_rule_thickness);
+ let gap = scaled!(ctx, styles, overbar_vertical_gap);
+ extra_height = sep + bar_height + gap;
+
+ let cramped = style_cramped();
+ content = ctx.layout_into_fragment(body, styles.chain(&cramped))?;
+
+ line_pos = Point::with_y(sep + bar_height / 2.0);
+ content_pos = Point::with_y(extra_height);
+ baseline = content.ascent() + extra_height;
+ line_adjust = Abs::zero();
+ }
+ }
+
+ let width = content.width();
+ let height = content.height() + extra_height;
+ let size = Size::new(width, height);
+ let line_width = width + line_adjust;
+
+ let content_class = content.class();
+ let content_is_text_like = content.is_text_like();
+ let content_italics_correction = content.italics_correction();
+ let mut frame = Frame::soft(size);
+ frame.set_baseline(baseline);
+ frame.push_frame(content_pos, content.into_frame());
+ frame.push(
+ line_pos,
+ FrameItem::Shape(
+ Geometry::Line(Point::with_x(line_width)).stroked(FixedStroke {
+ paint: TextElem::fill_in(styles).as_decoration(),
+ thickness: bar_height,
+ ..FixedStroke::default()
+ }),
+ span,
+ ),
+ );
+
+ ctx.push(
+ FrameFragment::new(ctx, styles, frame)
+ .with_class(content_class)
+ .with_text_like(content_is_text_like)
+ .with_italics_correction(content_italics_correction),
+ );
+
+ Ok(())
+}
+
+/// Layout an over- or underbrace-like object.
+#[allow(clippy::too_many_arguments)]
+fn layout_underoverspreader(
+ ctx: &mut MathContext,
+ styles: StyleChain,
+ body: &Content,
+ annotation: &Option<Content>,
+ c: char,
+ gap: Em,
+ position: Position,
+ span: Span,
+) -> SourceResult<()> {
+ let font_size = scaled_font_size(ctx, styles);
+ let gap = gap.at(font_size);
+ let body = ctx.layout_into_run(body, styles)?;
+ let body_class = body.class();
+ let body = body.into_fragment(ctx, styles);
+ let glyph = GlyphFragment::new(ctx, styles, c, span);
+ let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero());
+
+ let mut rows = vec![];
+ let baseline = match position {
+ Position::Under => {
+ rows.push(MathRun::new(vec![body]));
+ rows.push(stretched.into());
+ if let Some(annotation) = annotation {
+ let under_style = style_for_subscript(styles);
+ let annotation_styles = styles.chain(&under_style);
+ rows.push(ctx.layout_into_run(annotation, annotation_styles)?);
+ }
+ 0
+ }
+ Position::Over => {
+ if let Some(annotation) = annotation {
+ let over_style = style_for_superscript(styles);
+ let annotation_styles = styles.chain(&over_style);
+ rows.push(ctx.layout_into_run(annotation, annotation_styles)?);
+ }
+ rows.push(stretched.into());
+ rows.push(MathRun::new(vec![body]));
+ rows.len() - 1
+ }
+ };
+
+ let frame = stack(
+ rows,
+ FixedAlignment::Center,
+ gap,
+ baseline,
+ LeftRightAlternator::Right,
+ None,
+ );
+ ctx.push(FrameFragment::new(ctx, styles, frame).with_class(body_class));
+
+ Ok(())
+}
diff --git a/crates/typst/src/layout/pad.rs b/crates/typst-layout/src/pad.rs
index ff5d4e69..00badcdb 100644
--- a/crates/typst/src/layout/pad.rs
+++ b/crates/typst-layout/src/pad.rs
@@ -1,78 +1,14 @@
-use crate::diag::SourceResult;
-use crate::engine::Engine;
-use crate::foundations::{
- elem, Content, NativeElement, Packed, Resolve, Show, StyleChain,
+use typst_library::diag::SourceResult;
+use typst_library::engine::Engine;
+use typst_library::foundations::{Packed, Resolve, StyleChain};
+use typst_library::introspection::Locator;
+use typst_library::layout::{
+ Abs, Fragment, Frame, PadElem, Point, Regions, Rel, Sides, Size,
};
-use crate::introspection::Locator;
-use crate::layout::{
- layout_fragment, Abs, BlockElem, Fragment, Frame, Length, Point, Regions, Rel, Sides,
- Size,
-};
-
-/// Adds spacing around content.
-///
-/// The spacing can be specified for each side individually, or for all sides at
-/// once by specifying a positional argument.
-///
-/// # Example
-/// ```example
-/// #set align(center)
-///
-/// #pad(x: 16pt, image("typing.jpg"))
-/// _Typing speeds can be
-/// measured in words per minute._
-/// ```
-#[elem(title = "Padding", Show)]
-pub struct PadElem {
- /// The padding at the left side.
- #[parse(
- let all = args.named("rest")?.or(args.find()?);
- let x = args.named("x")?.or(all);
- let y = args.named("y")?.or(all);
- args.named("left")?.or(x)
- )]
- pub left: Rel<Length>,
-
- /// The padding at the top side.
- #[parse(args.named("top")?.or(y))]
- pub top: Rel<Length>,
-
- /// The padding at the right side.
- #[parse(args.named("right")?.or(x))]
- pub right: Rel<Length>,
-
- /// The padding at the bottom side.
- #[parse(args.named("bottom")?.or(y))]
- pub bottom: Rel<Length>,
-
- /// A shorthand to set `left` and `right` to the same value.
- #[external]
- pub x: Rel<Length>,
-
- /// A shorthand to set `top` and `bottom` to the same value.
- #[external]
- pub y: Rel<Length>,
-
- /// A shorthand to set all four sides to the same value.
- #[external]
- pub rest: Rel<Length>,
-
- /// The content to pad at the sides.
- #[required]
- pub body: Content,
-}
-
-impl Show for Packed<PadElem> {
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::multi_layouter(self.clone(), layout_pad)
- .pack()
- .spanned(self.span()))
- }
-}
/// Layout the padded content.
#[typst_macros::time(span = elem.span())]
-fn layout_pad(
+pub fn layout_pad(
elem: &Packed<PadElem>,
engine: &mut Engine,
locator: Locator,
@@ -90,7 +26,7 @@ fn layout_pad(
let pod = regions.map(&mut backlog, |size| shrink(size, &padding));
// Layout child into padded regions.
- let mut fragment = layout_fragment(engine, &elem.body, locator, styles, pod)?;
+ let mut fragment = crate::layout_fragment(engine, &elem.body, locator, styles, pod)?;
for frame in &mut fragment {
grow(frame, &padding);
@@ -100,13 +36,13 @@ fn layout_pad(
}
/// Shrink a region size by an inset relative to the size itself.
-pub(crate) fn shrink(size: Size, inset: &Sides<Rel<Abs>>) -> Size {
+pub fn shrink(size: Size, inset: &Sides<Rel<Abs>>) -> Size {
size - inset.sum_by_axis().relative_to(size)
}
/// Shrink the components of possibly multiple `Regions` by an inset relative to
/// the regions themselves.
-pub(crate) fn shrink_multiple(
+pub fn shrink_multiple(
size: &mut Size,
full: &mut Abs,
backlog: &mut [Abs],
@@ -141,7 +77,7 @@ pub(crate) fn shrink_multiple(
/// <=> w - p.rel * w - p.abs = s
/// <=> (1 - p.rel) * w = s + p.abs
/// <=> w = (s + p.abs) / (1 - p.rel)
-pub(crate) fn grow(frame: &mut Frame, inset: &Sides<Rel<Abs>>) {
+pub fn grow(frame: &mut Frame, inset: &Sides<Rel<Abs>>) {
// Apply the padding inversely such that the grown size padded
// yields the frame's size.
let padded = frame
diff --git a/crates/typst/src/layout/pages/collect.rs b/crates/typst-layout/src/pages/collect.rs
index 2e7201e2..1903d6ac 100644
--- a/crates/typst/src/layout/pages/collect.rs
+++ b/crates/typst-layout/src/pages/collect.rs
@@ -1,9 +1,9 @@
use std::collections::HashSet;
-use crate::foundations::StyleChain;
-use crate::introspection::{Locator, SplitLocator, Tag, TagElem};
-use crate::layout::{PagebreakElem, Parity};
-use crate::realize::Pair;
+use typst_library::foundations::StyleChain;
+use typst_library::introspection::{Locator, SplitLocator, Tag, TagElem};
+use typst_library::layout::{PagebreakElem, Parity};
+use typst_library::routines::Pair;
/// An item in page layout.
pub enum Item<'a> {
diff --git a/crates/typst/src/layout/pages/finalize.rs b/crates/typst-layout/src/pages/finalize.rs
index d1842613..8df6cb16 100644
--- a/crates/typst/src/layout/pages/finalize.rs
+++ b/crates/typst-layout/src/pages/finalize.rs
@@ -1,8 +1,9 @@
+use typst_library::diag::SourceResult;
+use typst_library::engine::Engine;
+use typst_library::introspection::{ManualPageCounter, Tag};
+use typst_library::layout::{Frame, FrameItem, Page, Point};
+
use super::LayoutedPage;
-use crate::diag::SourceResult;
-use crate::engine::Engine;
-use crate::introspection::{ManualPageCounter, Tag};
-use crate::layout::{Frame, FrameItem, Page, Point};
/// Piece together the inner page frame and the marginals. We can only do this
/// at the very end because inside/outside margins require knowledge of the
diff --git a/crates/typst/src/layout/pages/mod.rs b/crates/typst-layout/src/pages/mod.rs
index 6833d535..b969749a 100644
--- a/crates/typst/src/layout/pages/mod.rs
+++ b/crates/typst-layout/src/pages/mod.rs
@@ -5,25 +5,25 @@ mod finalize;
mod run;
use comemo::{Tracked, TrackedMut};
+use typst_library::diag::SourceResult;
+use typst_library::engine::{Engine, Route, Sink, Traced};
+use typst_library::foundations::{Content, StyleChain};
+use typst_library::introspection::{
+ Introspector, Locator, ManualPageCounter, SplitLocator, TagElem,
+};
+use typst_library::layout::{FrameItem, Page, Point};
+use typst_library::model::{Document, DocumentInfo};
+use typst_library::routines::{Arenas, Pair, RealizationKind, Routines};
+use typst_library::World;
use self::collect::{collect, Item};
use self::finalize::finalize;
use self::run::{layout_blank_page, layout_page_run, LayoutedPage};
-use crate::diag::SourceResult;
-use crate::engine::{Engine, Route, Sink, Traced};
-use crate::foundations::{Content, StyleChain};
-use crate::introspection::{
- Introspector, Locator, ManualPageCounter, SplitLocator, TagElem,
-};
-use crate::layout::{FrameItem, Page, Point};
-use crate::model::{Document, DocumentInfo};
-use crate::realize::{realize, Arenas, Pair, RealizationKind};
-use crate::World;
/// Layout content into a document.
///
/// This first performs root-level realization and then lays out the resulting
-/// elements. In contrast to [`layout_fragment`](crate::layout::layout_fragment),
+/// elements. In contrast to [`layout_fragment`](crate::layout_fragment),
/// this does not take regions since the regions are defined by the page
/// configuration in the content and style chain.
#[typst_macros::time(name = "document")]
@@ -33,6 +33,7 @@ pub fn layout_document(
styles: StyleChain,
) -> SourceResult<Document> {
layout_document_impl(
+ engine.routines,
engine.world,
engine.introspector,
engine.traced,
@@ -45,7 +46,9 @@ pub fn layout_document(
/// The internal implementation of `layout_document`.
#[comemo::memoize]
+#[allow(clippy::too_many_arguments)]
fn layout_document_impl(
+ routines: &Routines,
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
@@ -56,6 +59,7 @@ fn layout_document_impl(
) -> SourceResult<Document> {
let mut locator = Locator::root().split();
let mut engine = Engine {
+ routines,
world,
introspector,
traced,
@@ -70,7 +74,7 @@ fn layout_document_impl(
let arenas = Arenas::default();
let mut info = DocumentInfo::default();
- let mut children = realize(
+ let mut children = (engine.routines.realize)(
RealizationKind::Root(&mut info),
&mut engine,
&mut locator,
diff --git a/crates/typst/src/layout/pages/run.rs b/crates/typst-layout/src/pages/run.rs
index b5c2834f..15735faa 100644
--- a/crates/typst/src/layout/pages/run.rs
+++ b/crates/typst-layout/src/pages/run.rs
@@ -1,22 +1,25 @@
use comemo::{Track, Tracked, TrackedMut};
-
-use crate::diag::SourceResult;
-use crate::engine::{Engine, Route, Sink, Traced};
-use crate::foundations::{Content, NativeElement, Resolve, Smart, StyleChain, Styles};
-use crate::introspection::{
+use typst_library::diag::SourceResult;
+use typst_library::engine::{Engine, Route, Sink, Traced};
+use typst_library::foundations::{
+ Content, NativeElement, Resolve, Smart, StyleChain, Styles,
+};
+use typst_library::introspection::{
Counter, CounterDisplayElem, CounterKey, Introspector, Locator, LocatorLink, TagElem,
};
-use crate::layout::{
- layout_flow, layout_frame, Abs, AlignElem, Alignment, Axes, Binding, ColumnsElem,
- Dir, Frame, HAlignment, Length, OuterVAlignment, PageElem, Paper, Region, Regions,
- Rel, Sides, Size, VAlignment,
+use typst_library::layout::{
+ Abs, AlignElem, Alignment, Axes, Binding, ColumnsElem, Dir, Frame, HAlignment,
+ Length, OuterVAlignment, PageElem, Paper, Region, Regions, Rel, Sides, Size,
+ VAlignment,
};
-use crate::model::Numbering;
-use crate::realize::Pair;
-use crate::text::TextElem;
-use crate::utils::Numeric;
-use crate::visualize::Paint;
-use crate::World;
+use typst_library::model::Numbering;
+use typst_library::routines::{Pair, Routines};
+use typst_library::text::TextElem;
+use typst_library::visualize::Paint;
+use typst_library::World;
+use typst_utils::Numeric;
+
+use crate::flow::layout_flow;
/// A mostly finished layout for one page. Needs only knowledge of its exact
/// page number to be finalized into a `Page`. (Because the margins can depend
@@ -54,6 +57,7 @@ pub fn layout_page_run(
initial: StyleChain,
) -> SourceResult<Vec<LayoutedPage>> {
layout_page_run_impl(
+ engine.routines,
engine.world,
engine.introspector,
engine.traced,
@@ -69,6 +73,7 @@ pub fn layout_page_run(
#[comemo::memoize]
#[allow(clippy::too_many_arguments)]
fn layout_page_run_impl(
+ routines: &Routines,
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
@@ -81,6 +86,7 @@ fn layout_page_run_impl(
let link = LocatorLink::new(locator);
let mut locator = Locator::link(&link).split();
let mut engine = Engine {
+ routines,
world,
introspector,
traced,
@@ -177,7 +183,7 @@ fn layout_page_run_impl(
let mut layout_marginal = |content: &Option<Content>, area, align| {
let Some(content) = content else { return Ok(None) };
let aligned = content.clone().styled(AlignElem::set_alignment(align));
- layout_frame(
+ crate::layout_frame(
&mut engine,
&aligned,
locator.next(&content.span()),
diff --git a/crates/typst-layout/src/repeat.rs b/crates/typst-layout/src/repeat.rs
new file mode 100644
index 00000000..b761438c
--- /dev/null
+++ b/crates/typst-layout/src/repeat.rs
@@ -0,0 +1,60 @@
+use typst_library::diag::{bail, SourceResult};
+use typst_library::engine::Engine;
+use typst_library::foundations::{Packed, Resolve, StyleChain};
+use typst_library::introspection::Locator;
+use typst_library::layout::{
+ Abs, AlignElem, Axes, Frame, Point, Region, RepeatElem, Size,
+};
+use typst_utils::Numeric;
+
+/// Layout the repeated content.
+#[typst_macros::time(span = elem.span())]
+pub fn layout_repeat(
+ elem: &Packed<RepeatElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ let pod = Region::new(region.size, Axes::new(false, false));
+ let piece = crate::layout_frame(engine, &elem.body, locator, styles, pod)?;
+ let size = Size::new(region.size.x, piece.height());
+
+ if !size.is_finite() {
+ bail!(elem.span(), "repeat with no size restrictions");
+ }
+
+ let mut frame = Frame::soft(size);
+ if piece.has_baseline() {
+ frame.set_baseline(piece.baseline());
+ }
+
+ let mut gap = elem.gap(styles).resolve(styles);
+ let fill = region.size.x;
+ let width = piece.width();
+
+ // count * width + (count - 1) * gap = fill, but count is an integer so
+ // we need to round down and get the remainder.
+ let count = ((fill + gap) / (width + gap)).floor();
+ let remaining = (fill + gap) % (width + gap);
+
+ let justify = elem.justify(styles);
+ if justify {
+ gap += remaining / (count - 1.0);
+ }
+
+ let align = AlignElem::alignment_in(styles).resolve(styles);
+ let mut offset = Abs::zero();
+ if count == 1.0 || !justify {
+ offset += align.x.position(remaining);
+ }
+
+ if width > Abs::zero() {
+ for _ in 0..(count as usize).min(1000) {
+ frame.push_frame(Point::with_x(offset), piece.clone());
+ offset += piece.width() + gap;
+ }
+ }
+
+ Ok(frame)
+}
diff --git a/crates/typst/src/visualize/shape.rs b/crates/typst-layout/src/shapes.rs
index 56ecd029..31f8c42b 100644
--- a/crates/typst/src/visualize/shape.rs
+++ b/crates/typst-layout/src/shapes.rs
@@ -1,469 +1,319 @@
use std::f64::consts::SQRT_2;
-use crate::diag::SourceResult;
-use crate::engine::Engine;
-use crate::foundations::{
- elem, Cast, Content, NativeElement, Packed, Show, Smart, StyleChain,
+use kurbo::ParamCurveExtrema;
+use typst_library::diag::{bail, SourceResult};
+use typst_library::engine::Engine;
+use typst_library::foundations::{Content, Packed, Resolve, Smart, StyleChain};
+use typst_library::introspection::Locator;
+use typst_library::layout::{
+ Abs, Axes, Corner, Corners, Frame, FrameItem, Length, Point, Ratio, Region, Rel,
+ Sides, Size,
};
-use crate::introspection::Locator;
-use crate::layout::{
- layout_frame, Abs, Axes, BlockElem, Corner, Corners, Frame, FrameItem, Length, Point,
- Ratio, Region, Rel, Sides, Size, Sizing,
+use typst_library::visualize::{
+ CircleElem, EllipseElem, FillRule, FixedStroke, Geometry, LineElem, Paint, Path,
+ PathElem, PathVertex, PolygonElem, RectElem, Shape, SquareElem, Stroke,
};
-use crate::syntax::Span;
-use crate::utils::Get;
-use crate::visualize::{FixedStroke, Paint, Path, Stroke};
-
-/// A rectangle with optional content.
-///
-/// # Example
-/// ```example
-/// // Without content.
-/// #rect(width: 35%, height: 30pt)
-///
-/// // With content.
-/// #rect[
-/// Automatically sized \
-/// to fit the content.
-/// ]
-/// ```
-#[elem(title = "Rectangle", Show)]
-pub struct RectElem {
- /// The rectangle's width, relative to its parent container.
- pub width: Smart<Rel<Length>>,
-
- /// The rectangle's height, relative to its parent container.
- pub height: Sizing,
-
- /// How to fill the rectangle.
- ///
- /// When setting a fill, the default stroke disappears. To create a
- /// rectangle with both fill and stroke, you have to configure both.
- ///
- /// ```example
- /// #rect(fill: blue)
- /// ```
- pub fill: Option<Paint>,
-
- /// How to stroke the rectangle. This can be:
- ///
- /// - `{none}` to disable stroking
- /// - `{auto}` for a stroke of `{1pt + black}` if and if only if no fill is
- /// given.
- /// - Any kind of [stroke]
- /// - A dictionary describing the stroke for each side individually. The
- /// dictionary can contain the following keys in order of precedence:
- /// - `top`: The top stroke.
- /// - `right`: The right stroke.
- /// - `bottom`: The bottom stroke.
- /// - `left`: The left stroke.
- /// - `x`: The horizontal stroke.
- /// - `y`: The vertical stroke.
- /// - `rest`: The stroke on all sides except those for which the
- /// dictionary explicitly sets a size.
- ///
- /// ```example
- /// #stack(
- /// dir: ltr,
- /// spacing: 1fr,
- /// rect(stroke: red),
- /// rect(stroke: 2pt),
- /// rect(stroke: 2pt + red),
- /// )
- /// ```
- #[resolve]
- #[fold]
- pub stroke: Smart<Sides<Option<Option<Stroke>>>>,
-
- /// How much to round the rectangle's corners, relative to the minimum of
- /// the width and height divided by two. This can be:
- ///
- /// - A relative length for a uniform corner radius.
- /// - A dictionary: With a dictionary, the stroke for each side can be set
- /// individually. The dictionary can contain the following keys in order
- /// of precedence:
- /// - `top-left`: The top-left corner radius.
- /// - `top-right`: The top-right corner radius.
- /// - `bottom-right`: The bottom-right corner radius.
- /// - `bottom-left`: The bottom-left corner radius.
- /// - `left`: The top-left and bottom-left corner radii.
- /// - `top`: The top-left and top-right corner radii.
- /// - `right`: The top-right and bottom-right corner radii.
- /// - `bottom`: The bottom-left and bottom-right corner radii.
- /// - `rest`: The radii for all corners except those for which the
- /// dictionary explicitly sets a size.
- ///
- /// ```example
- /// #set rect(stroke: 4pt)
- /// #rect(
- /// radius: (
- /// left: 5pt,
- /// top-right: 20pt,
- /// bottom-right: 10pt,
- /// ),
- /// stroke: (
- /// left: red,
- /// top: yellow,
- /// right: green,
- /// bottom: blue,
- /// ),
- /// )
- /// ```
- #[resolve]
- #[fold]
- pub radius: Corners<Option<Rel<Length>>>,
-
- /// How much to pad the rectangle's content.
- /// See the [box's documentation]($box.outset) for more details.
- #[resolve]
- #[fold]
- #[default(Sides::splat(Some(Abs::pt(5.0).into())))]
- pub inset: Sides<Option<Rel<Length>>>,
-
- /// How much to expand the rectangle's size without affecting the layout.
- /// See the [box's documentation]($box.outset) for more details.
- #[resolve]
- #[fold]
- pub outset: Sides<Option<Rel<Length>>>,
-
- /// The content to place into the rectangle.
- ///
- /// When this is omitted, the rectangle takes on a default size of at most
- /// `{45pt}` by `{30pt}`.
- #[positional]
- #[borrowed]
- pub body: Option<Content>,
+use typst_syntax::Span;
+use typst_utils::{Get, Numeric};
+
+/// Layout the line.
+#[typst_macros::time(span = elem.span())]
+pub fn layout_line(
+ elem: &Packed<LineElem>,
+ _: &mut Engine,
+ _: Locator,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ let resolve = |axes: Axes<Rel<Abs>>| axes.zip_map(region.size, Rel::relative_to);
+ let start = resolve(elem.start(styles));
+ let delta = elem.end(styles).map(|end| resolve(end) - start).unwrap_or_else(|| {
+ let length = elem.length(styles);
+ let angle = elem.angle(styles);
+ let x = angle.cos() * length;
+ let y = angle.sin() * length;
+ resolve(Axes::new(x, y))
+ });
+
+ let stroke = elem.stroke(styles).unwrap_or_default();
+ let size = start.max(start + delta).max(Size::zero());
+
+ if !size.is_finite() {
+ bail!(elem.span(), "cannot create line with infinite length");
+ }
+
+ let mut frame = Frame::soft(size);
+ let shape = Geometry::Line(delta.to_point()).stroked(stroke);
+ frame.push(start.to_point(), FrameItem::Shape(shape, elem.span()));
+ Ok(frame)
}
-impl Show for Packed<RectElem> {
- fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(
- self.clone(),
- |elem, engine, locator, styles, region| {
- layout_shape(
- engine,
- locator,
- styles,
- region,
- ShapeKind::Rect,
- elem.body(styles),
- elem.fill(styles),
- elem.stroke(styles),
- elem.inset(styles),
- elem.outset(styles),
- elem.radius(styles),
- elem.span(),
- )
- },
- )
- .with_width(self.width(styles))
- .with_height(self.height(styles))
- .pack()
- .spanned(self.span()))
+/// Layout the path.
+#[typst_macros::time(span = elem.span())]
+pub fn layout_path(
+ elem: &Packed<PathElem>,
+ _: &mut Engine,
+ _: Locator,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ let resolve = |axes: Axes<Rel<Length>>| {
+ axes.resolve(styles).zip_map(region.size, Rel::relative_to).to_point()
+ };
+
+ let vertices = elem.vertices();
+ let points: Vec<Point> = vertices.iter().map(|c| resolve(c.vertex())).collect();
+
+ let mut size = Size::zero();
+ if points.is_empty() {
+ return Ok(Frame::soft(size));
}
-}
-/// A square with optional content.
-///
-/// # Example
-/// ```example
-/// // Without content.
-/// #square(size: 40pt)
-///
-/// // With content.
-/// #square[
-/// Automatically \
-/// sized to fit.
-/// ]
-/// ```
-#[elem(Show)]
-pub struct SquareElem {
- /// The square's side length. This is mutually exclusive with `width` and
- /// `height`.
- #[external]
- pub size: Smart<Length>,
-
- /// The square's width. This is mutually exclusive with `size` and `height`.
- ///
- /// In contrast to `size`, this can be relative to the parent container's
- /// width.
- #[parse(
- let size = args.named::<Smart<Length>>("size")?.map(|s| s.map(Rel::from));
- match size {
- None => args.named("width")?,
- size => size,
- }
- )]
- pub width: Smart<Rel<Length>>,
-
- /// The square's height. This is mutually exclusive with `size` and `width`.
- ///
- /// In contrast to `size`, this can be relative to the parent container's
- /// height.
- #[parse(match size {
- None => args.named("height")?,
- size => size.map(Into::into),
- })]
- pub height: Sizing,
-
- /// How to fill the square. See the [rectangle's documentation]($rect.fill)
- /// for more details.
- pub fill: Option<Paint>,
-
- /// How to stroke the square. See the
- /// [rectangle's documentation]($rect.stroke) for more details.
- #[resolve]
- #[fold]
- pub stroke: Smart<Sides<Option<Option<Stroke>>>>,
-
- /// How much to round the square's corners. See the
- /// [rectangle's documentation]($rect.radius) for more details.
- #[resolve]
- #[fold]
- pub radius: Corners<Option<Rel<Length>>>,
-
- /// How much to pad the square's content. See the
- /// [box's documentation]($box.inset) for more details.
- #[resolve]
- #[fold]
- #[default(Sides::splat(Some(Abs::pt(5.0).into())))]
- pub inset: Sides<Option<Rel<Length>>>,
-
- /// How much to expand the square's size without affecting the layout. See
- /// the [box's documentation]($box.outset) for more details.
- #[resolve]
- #[fold]
- pub outset: Sides<Option<Rel<Length>>>,
-
- /// The content to place into the square. The square expands to fit this
- /// content, keeping the 1-1 aspect ratio.
- ///
- /// When this is omitted, the square takes on a default size of at most
- /// `{30pt}`.
- #[positional]
- #[borrowed]
- pub body: Option<Content>,
+ // Only create a path if there are more than zero points.
+ // Construct a closed path given all points.
+ let mut path = Path::new();
+ path.move_to(points[0]);
+
+ let mut add_cubic = |from_point: Point,
+ to_point: Point,
+ from: PathVertex,
+ to: PathVertex| {
+ let from_control_point = resolve(from.control_point_from()) + from_point;
+ let to_control_point = resolve(to.control_point_to()) + to_point;
+ path.cubic_to(from_control_point, to_control_point, to_point);
+
+ let p0 = kurbo::Point::new(from_point.x.to_raw(), from_point.y.to_raw());
+ let p1 = kurbo::Point::new(
+ from_control_point.x.to_raw(),
+ from_control_point.y.to_raw(),
+ );
+ let p2 =
+ kurbo::Point::new(to_control_point.x.to_raw(), to_control_point.y.to_raw());
+ let p3 = kurbo::Point::new(to_point.x.to_raw(), to_point.y.to_raw());
+ let extrema = kurbo::CubicBez::new(p0, p1, p2, p3).bounding_box();
+ size.x.set_max(Abs::raw(extrema.x1));
+ size.y.set_max(Abs::raw(extrema.y1));
+ };
+
+ for (vertex_window, point_window) in vertices.windows(2).zip(points.windows(2)) {
+ let from = vertex_window[0];
+ let to = vertex_window[1];
+ let from_point = point_window[0];
+ let to_point = point_window[1];
+
+ add_cubic(from_point, to_point, from, to);
+ }
+
+ if elem.closed(styles) {
+ let from = *vertices.last().unwrap(); // We checked that we have at least one element.
+ let to = vertices[0];
+ let from_point = *points.last().unwrap();
+ let to_point = points[0];
+
+ add_cubic(from_point, to_point, from, to);
+ path.close_path();
+ }
+
+ // Prepare fill and stroke.
+ let fill = elem.fill(styles);
+ let fill_rule = elem.fill_rule(styles);
+ let stroke = match elem.stroke(styles) {
+ Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
+ Smart::Auto => None,
+ Smart::Custom(stroke) => stroke.map(Stroke::unwrap_or_default),
+ };
+
+ let mut frame = Frame::soft(size);
+ let shape = Shape {
+ geometry: Geometry::Path(path),
+ stroke,
+ fill,
+ fill_rule,
+ };
+ frame.push(Point::zero(), FrameItem::Shape(shape, elem.span()));
+ Ok(frame)
}
-impl Show for Packed<SquareElem> {
- fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(
- self.clone(),
- |elem, engine, locator, styles, regions| {
- layout_shape(
- engine,
- locator,
- styles,
- regions,
- ShapeKind::Square,
- elem.body(styles),
- elem.fill(styles),
- elem.stroke(styles),
- elem.inset(styles),
- elem.outset(styles),
- elem.radius(styles),
- elem.span(),
- )
- },
- )
- .with_width(self.width(styles))
- .with_height(self.height(styles))
- .pack()
- .spanned(self.span()))
+/// Layout the polygon.
+#[typst_macros::time(span = elem.span())]
+pub fn layout_polygon(
+ elem: &Packed<PolygonElem>,
+ _: &mut Engine,
+ _: Locator,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ let points: Vec<Point> = elem
+ .vertices()
+ .iter()
+ .map(|c| c.resolve(styles).zip_map(region.size, Rel::relative_to).to_point())
+ .collect();
+
+ let size = points.iter().fold(Point::zero(), |max, c| c.max(max)).to_size();
+ if !size.is_finite() {
+ bail!(elem.span(), "cannot create polygon with infinite size");
+ }
+
+ let mut frame = Frame::hard(size);
+
+ // Only create a path if there are more than zero points.
+ if points.is_empty() {
+ return Ok(frame);
}
+
+ // Prepare fill and stroke.
+ let fill = elem.fill(styles);
+ let fill_rule = elem.fill_rule(styles);
+ let stroke = match elem.stroke(styles) {
+ Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
+ Smart::Auto => None,
+ Smart::Custom(stroke) => stroke.map(Stroke::unwrap_or_default),
+ };
+
+ // Construct a closed path given all points.
+ let mut path = Path::new();
+ path.move_to(points[0]);
+ for &point in &points[1..] {
+ path.line_to(point);
+ }
+ path.close_path();
+
+ let shape = Shape {
+ geometry: Geometry::Path(path),
+ stroke,
+ fill,
+ fill_rule,
+ };
+ frame.push(Point::zero(), FrameItem::Shape(shape, elem.span()));
+ Ok(frame)
}
-/// An ellipse with optional content.
-///
-/// # Example
-/// ```example
-/// // Without content.
-/// #ellipse(width: 35%, height: 30pt)
-///
-/// // With content.
-/// #ellipse[
-/// #set align(center)
-/// Automatically sized \
-/// to fit the content.
-/// ]
-/// ```
-#[elem(Show)]
-pub struct EllipseElem {
- /// The ellipse's width, relative to its parent container.
- pub width: Smart<Rel<Length>>,
-
- /// The ellipse's height, relative to its parent container.
- pub height: Sizing,
-
- /// How to fill the ellipse. See the [rectangle's documentation]($rect.fill)
- /// for more details.
- pub fill: Option<Paint>,
-
- /// How to stroke the ellipse. See the
- /// [rectangle's documentation]($rect.stroke) for more details.
- #[resolve]
- #[fold]
- pub stroke: Smart<Option<Stroke>>,
-
- /// How much to pad the ellipse's content. See the
- /// [box's documentation]($box.inset) for more details.
- #[resolve]
- #[fold]
- #[default(Sides::splat(Some(Abs::pt(5.0).into())))]
- pub inset: Sides<Option<Rel<Length>>>,
-
- /// How much to expand the ellipse's size without affecting the layout. See
- /// the [box's documentation]($box.outset) for more details.
- #[resolve]
- #[fold]
- pub outset: Sides<Option<Rel<Length>>>,
-
- /// The content to place into the ellipse.
- ///
- /// When this is omitted, the ellipse takes on a default size of at most
- /// `{45pt}` by `{30pt}`.
- #[positional]
- #[borrowed]
- pub body: Option<Content>,
+/// Lay out the rectangle.
+#[typst_macros::time(span = elem.span())]
+pub fn layout_rect(
+ elem: &Packed<RectElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ layout_shape(
+ engine,
+ locator,
+ styles,
+ region,
+ ShapeKind::Rect,
+ elem.body(styles),
+ elem.fill(styles),
+ elem.stroke(styles),
+ elem.inset(styles),
+ elem.outset(styles),
+ elem.radius(styles),
+ elem.span(),
+ )
}
-impl Show for Packed<EllipseElem> {
- fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(
- self.clone(),
- |elem, engine, locator, styles, regions| {
- layout_shape(
- engine,
- locator,
- styles,
- regions,
- ShapeKind::Ellipse,
- elem.body(styles),
- elem.fill(styles),
- elem.stroke(styles).map(|s| Sides::splat(Some(s))),
- elem.inset(styles),
- elem.outset(styles),
- Corners::splat(None),
- elem.span(),
- )
- },
- )
- .with_width(self.width(styles))
- .with_height(self.height(styles))
- .pack()
- .spanned(self.span()))
- }
+/// Lay out the square.
+#[typst_macros::time(span = elem.span())]
+pub fn layout_square(
+ elem: &Packed<SquareElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ layout_shape(
+ engine,
+ locator,
+ styles,
+ region,
+ ShapeKind::Square,
+ elem.body(styles),
+ elem.fill(styles),
+ elem.stroke(styles),
+ elem.inset(styles),
+ elem.outset(styles),
+ elem.radius(styles),
+ elem.span(),
+ )
}
-/// A circle with optional content.
-///
-/// # Example
-/// ```example
-/// // Without content.
-/// #circle(radius: 25pt)
-///
-/// // With content.
-/// #circle[
-/// #set align(center + horizon)
-/// Automatically \
-/// sized to fit.
-/// ]
-/// ```
-#[elem(Show)]
-pub struct CircleElem {
- /// The circle's radius. This is mutually exclusive with `width` and
- /// `height`.
- #[external]
- pub radius: Length,
-
- /// The circle's width. This is mutually exclusive with `radius` and
- /// `height`.
- ///
- /// In contrast to `radius`, this can be relative to the parent container's
- /// width.
- #[parse(
- let size = args
- .named::<Smart<Length>>("radius")?
- .map(|s| s.map(|r| 2.0 * Rel::from(r)));
- match size {
- None => args.named("width")?,
- size => size,
- }
- )]
- pub width: Smart<Rel<Length>>,
-
- /// The circle's height. This is mutually exclusive with `radius` and
- /// `width`.
- ///
- /// In contrast to `radius`, this can be relative to the parent container's
- /// height.
- #[parse(match size {
- None => args.named("height")?,
- size => size.map(Into::into),
- })]
- pub height: Sizing,
-
- /// How to fill the circle. See the [rectangle's documentation]($rect.fill)
- /// for more details.
- pub fill: Option<Paint>,
-
- /// How to stroke the circle. See the
- /// [rectangle's documentation]($rect.stroke) for more details.
- #[resolve]
- #[fold]
- #[default(Smart::Auto)]
- pub stroke: Smart<Option<Stroke>>,
-
- /// How much to pad the circle's content. See the
- /// [box's documentation]($box.inset) for more details.
- #[resolve]
- #[fold]
- #[default(Sides::splat(Some(Abs::pt(5.0).into())))]
- pub inset: Sides<Option<Rel<Length>>>,
-
- /// How much to expand the circle's size without affecting the layout. See
- /// the [box's documentation]($box.outset) for more details.
- #[resolve]
- #[fold]
- pub outset: Sides<Option<Rel<Length>>>,
-
- /// The content to place into the circle. The circle expands to fit this
- /// content, keeping the 1-1 aspect ratio.
- #[positional]
- #[borrowed]
- pub body: Option<Content>,
+/// Lay out the ellipse.
+#[typst_macros::time(span = elem.span())]
+pub fn layout_ellipse(
+ elem: &Packed<EllipseElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ layout_shape(
+ engine,
+ locator,
+ styles,
+ region,
+ ShapeKind::Ellipse,
+ elem.body(styles),
+ elem.fill(styles),
+ elem.stroke(styles).map(|s| Sides::splat(Some(s))),
+ elem.inset(styles),
+ elem.outset(styles),
+ Corners::splat(None),
+ elem.span(),
+ )
+}
+
+/// Lay out the circle.
+#[typst_macros::time(span = elem.span())]
+pub fn layout_circle(
+ elem: &Packed<CircleElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ layout_shape(
+ engine,
+ locator,
+ styles,
+ region,
+ ShapeKind::Circle,
+ elem.body(styles),
+ elem.fill(styles),
+ elem.stroke(styles).map(|s| Sides::splat(Some(s))),
+ elem.inset(styles),
+ elem.outset(styles),
+ Corners::splat(None),
+ elem.span(),
+ )
+}
+
+/// A category of shape.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+enum ShapeKind {
+ /// A rectangle with equal side lengths.
+ Square,
+ /// A quadrilateral with four right angles.
+ Rect,
+ /// An ellipse with coinciding foci.
+ Circle,
+ /// A curve around two focal points.
+ Ellipse,
}
-impl Show for Packed<CircleElem> {
- fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(
- self.clone(),
- |elem, engine, locator, styles, regions| {
- layout_shape(
- engine,
- locator,
- styles,
- regions,
- ShapeKind::Circle,
- elem.body(styles),
- elem.fill(styles),
- elem.stroke(styles).map(|s| Sides::splat(Some(s))),
- elem.inset(styles),
- elem.outset(styles),
- Corners::splat(None),
- elem.span(),
- )
- },
- )
- .with_width(self.width(styles))
- .with_height(self.height(styles))
- .pack()
- .spanned(self.span()))
+impl ShapeKind {
+ /// Whether this shape kind is curvy.
+ fn is_round(self) -> bool {
+ matches!(self, Self::Circle | Self::Ellipse)
+ }
+
+ /// Whether this shape kind has equal side length.
+ fn is_quadratic(self) -> bool {
+ matches!(self, Self::Square | Self::Circle)
}
}
/// Layout a shape.
-#[typst_macros::time(span = span)]
#[allow(clippy::too_many_arguments)]
fn layout_shape(
engine: &mut Engine,
@@ -491,23 +341,23 @@ fn layout_shape(
// Take the inset, if any, into account.
let mut pod = region;
if has_inset {
- pod.size = crate::layout::shrink(region.size, &inset);
+ pod.size = crate::pad::shrink(region.size, &inset);
}
// Layout the child.
- frame = layout_frame(engine, child, locator.relayout(), styles, pod)?;
+ frame = crate::layout_frame(engine, child, locator.relayout(), styles, pod)?;
// If the child is a square or circle, relayout with full expansion into
// square region to make sure the result is really quadratic.
if kind.is_quadratic() {
let length = frame.size().max_by_side().min(pod.size.min_by_side());
let quad_pod = Region::new(Size::splat(length), Axes::splat(true));
- frame = layout_frame(engine, child, locator, styles, quad_pod)?;
+ frame = crate::layout_frame(engine, child, locator, styles, quad_pod)?;
}
// Apply the inset.
if has_inset {
- crate::layout::grow(&mut frame, &inset);
+ crate::pad::grow(&mut frame, &inset);
}
} else {
// The default size that a shape takes on if it has no child and
@@ -535,10 +385,16 @@ fn layout_shape(
let outset = outset.unwrap_or_default().relative_to(frame.size());
let size = frame.size() + outset.sum_by_axis();
let pos = Point::new(-outset.left, -outset.top);
- let shape = ellipse(size, fill, stroke.left);
+ let shape = Shape {
+ geometry: Geometry::Path(Path::ellipse(size)),
+ fill,
+ stroke: stroke.left,
+ fill_rule: FillRule::default(),
+ };
frame.prepend(pos, FrameItem::Shape(shape, span));
} else {
- frame.fill_and_stroke(
+ fill_and_stroke(
+ &mut frame,
fill,
&stroke,
&outset.unwrap_or_default(),
@@ -551,128 +407,8 @@ fn layout_shape(
Ok(frame)
}
-/// A category of shape.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum ShapeKind {
- /// A rectangle with equal side lengths.
- Square,
- /// A quadrilateral with four right angles.
- Rect,
- /// An ellipse with coinciding foci.
- Circle,
- /// A curve around two focal points.
- Ellipse,
-}
-
-impl ShapeKind {
- /// Whether this shape kind is curvy.
- fn is_round(self) -> bool {
- matches!(self, Self::Circle | Self::Ellipse)
- }
-
- /// Whether this shape kind has equal side length.
- fn is_quadratic(self) -> bool {
- matches!(self, Self::Square | Self::Circle)
- }
-}
-
-/// A geometric shape with optional fill and stroke.
-#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-pub struct Shape {
- /// The shape's geometry.
- pub geometry: Geometry,
- /// The shape's background fill.
- pub fill: Option<Paint>,
- /// The shape's fill rule.
- pub fill_rule: FillRule,
- /// The shape's border stroke.
- pub stroke: Option<FixedStroke>,
-}
-
-/// A path filling rule.
-#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)]
-pub enum FillRule {
- /// Specifies that "inside" is computed by a non-zero sum of signed edge crossings.
- #[default]
- NonZero,
- /// Specifies that "inside" is computed by an odd number of edge crossings.
- EvenOdd,
-}
-
-/// A shape's geometry.
-#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-pub enum Geometry {
- /// A line to a point (relative to its position).
- Line(Point),
- /// A rectangle with its origin in the topleft corner.
- Rect(Size),
- /// A bezier path.
- Path(Path),
-}
-
-impl Geometry {
- /// Fill the geometry without a stroke.
- pub fn filled(self, fill: Paint) -> Shape {
- Shape {
- geometry: self,
- fill: Some(fill),
- fill_rule: FillRule::default(),
- stroke: None,
- }
- }
-
- /// Stroke the geometry without a fill.
- pub fn stroked(self, stroke: FixedStroke) -> Shape {
- Shape {
- geometry: self,
- fill: None,
- fill_rule: FillRule::default(),
- stroke: Some(stroke),
- }
- }
-
- /// The bounding box of the geometry.
- pub fn bbox_size(&self) -> Size {
- match self {
- Self::Line(line) => Size::new(line.x, line.y),
- Self::Rect(s) => *s,
- Self::Path(p) => p.bbox_size(),
- }
- }
-}
-
-/// Produce a shape that approximates an axis-aligned ellipse.
-pub(crate) fn ellipse(
- size: Size,
- fill: Option<Paint>,
- stroke: Option<FixedStroke>,
-) -> Shape {
- // https://stackoverflow.com/a/2007782
- let z = Abs::zero();
- let rx = size.x / 2.0;
- let ry = size.y / 2.0;
- let m = 0.551784;
- let mx = m * rx;
- let my = m * ry;
- let point = |x, y| Point::new(x + rx, y + ry);
-
- let mut path = Path::new();
- path.move_to(point(-rx, z));
- path.cubic_to(point(-rx, -my), point(-mx, -ry), point(z, -ry));
- path.cubic_to(point(mx, -ry), point(rx, -my), point(rx, z));
- path.cubic_to(point(rx, my), point(mx, ry), point(z, ry));
- path.cubic_to(point(-mx, ry), point(-rx, my), point(-rx, z));
-
- Shape {
- geometry: Geometry::Path(path),
- stroke,
- fill,
- fill_rule: FillRule::default(),
- }
-}
-
/// Creates a new rectangle as a path.
-pub(crate) fn clip_rect(
+pub fn clip_rect(
size: Size,
radius: &Corners<Rel<Abs>>,
stroke: &Sides<Option<FixedStroke>>,
@@ -708,11 +444,30 @@ pub(crate) fn clip_rect(
path
}
+/// Add a fill and stroke with optional radius and outset to the frame.
+pub fn fill_and_stroke(
+ frame: &mut Frame,
+ fill: Option<Paint>,
+ stroke: &Sides<Option<FixedStroke>>,
+ outset: &Sides<Rel<Abs>>,
+ radius: &Corners<Rel<Abs>>,
+ span: Span,
+) {
+ let outset = outset.relative_to(frame.size());
+ let size = frame.size() + outset.sum_by_axis();
+ let pos = Point::new(-outset.left, -outset.top);
+ frame.prepend_multiple(
+ styled_rect(size, radius, fill, stroke)
+ .into_iter()
+ .map(|x| (pos, FrameItem::Shape(x, span))),
+ );
+}
+
/// Create a styled rectangle with shapes.
/// - use rect primitive for simple rectangles
/// - stroke sides if possible
/// - use fill for sides for best looks
-pub(crate) fn styled_rect(
+pub fn styled_rect(
size: Size,
radius: &Corners<Rel<Abs>>,
fill: Option<Paint>,
diff --git a/crates/typst/src/layout/stack.rs b/crates/typst-layout/src/stack.rs
index 6f1aa534..a3ebc9f3 100644
--- a/crates/typst/src/layout/stack.rs
+++ b/crates/typst-layout/src/stack.rs
@@ -1,99 +1,17 @@
-use std::fmt::{self, Debug, Formatter};
-use typst_syntax::Span;
-
-use crate::diag::{bail, SourceResult};
-use crate::engine::Engine;
-use crate::foundations::{
- cast, elem, Content, NativeElement, Packed, Resolve, Show, StyleChain, StyledElem,
+use typst_library::diag::{bail, SourceResult};
+use typst_library::engine::Engine;
+use typst_library::foundations::{Content, Packed, Resolve, StyleChain, StyledElem};
+use typst_library::introspection::{Locator, SplitLocator};
+use typst_library::layout::{
+ Abs, AlignElem, Axes, Axis, Dir, FixedAlignment, Fr, Fragment, Frame, HElem, Point,
+ Regions, Size, Spacing, StackChild, StackElem, VElem,
};
-use crate::introspection::{Locator, SplitLocator};
-use crate::layout::{
- layout_fragment, Abs, AlignElem, Axes, Axis, BlockElem, Dir, FixedAlignment, Fr,
- Fragment, Frame, HElem, Point, Regions, Size, Spacing, VElem,
-};
-use crate::utils::{Get, Numeric};
-
-/// Arranges content and spacing horizontally or vertically.
-///
-/// The stack places a list of items along an axis, with optional spacing
-/// between each item.
-///
-/// # Example
-/// ```example
-/// #stack(
-/// dir: ttb,
-/// rect(width: 40pt),
-/// rect(width: 120pt),
-/// rect(width: 90pt),
-/// )
-/// ```
-#[elem(Show)]
-pub struct StackElem {
- /// The direction along which the items are stacked. Possible values are:
- ///
- /// - `{ltr}`: Left to right.
- /// - `{rtl}`: Right to left.
- /// - `{ttb}`: Top to bottom.
- /// - `{btt}`: Bottom to top.
- ///
- /// You can use the `start` and `end` methods to obtain the initial and
- /// final points (respectively) of a direction, as `alignment`. You can also
- /// use the `axis` method to determine whether a direction is
- /// `{"horizontal"}` or `{"vertical"}`. The `inv` method returns a
- /// direction's inverse direction.
- ///
- /// For example, `{ttb.start()}` is `top`, `{ttb.end()}` is `bottom`,
- /// `{ttb.axis()}` is `{"vertical"}` and `{ttb.inv()}` is equal to `btt`.
- #[default(Dir::TTB)]
- pub dir: Dir,
-
- /// Spacing to insert between items where no explicit spacing was provided.
- pub spacing: Option<Spacing>,
-
- /// The children to stack along the axis.
- #[variadic]
- pub children: Vec<StackChild>,
-}
-
-impl Show for Packed<StackElem> {
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::multi_layouter(self.clone(), layout_stack)
- .pack()
- .spanned(self.span()))
- }
-}
-
-/// A child of a stack element.
-#[derive(Clone, PartialEq, Hash)]
-pub enum StackChild {
- /// Spacing between other children.
- Spacing(Spacing),
- /// Arbitrary block-level content.
- Block(Content),
-}
-
-impl Debug for StackChild {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self {
- Self::Spacing(kind) => kind.fmt(f),
- Self::Block(block) => block.fmt(f),
- }
- }
-}
-
-cast! {
- StackChild,
- self => match self {
- Self::Spacing(spacing) => spacing.into_value(),
- Self::Block(content) => content.into_value(),
- },
- v: Spacing => Self::Spacing(v),
- v: Content => Self::Block(v),
-}
+use typst_syntax::Span;
+use typst_utils::{Get, Numeric};
/// Layout the stack.
#[typst_macros::time(span = elem.span())]
-fn layout_stack(
+pub fn layout_stack(
elem: &Packed<StackElem>,
engine: &mut Engine,
locator: Locator,
@@ -257,7 +175,7 @@ impl<'a> StackLayouter<'a> {
}
.resolve(styles);
- let fragment = layout_fragment(
+ let fragment = crate::layout_fragment(
engine,
block,
self.locator.next(&block.span()),
diff --git a/crates/typst-layout/src/transforms.rs b/crates/typst-layout/src/transforms.rs
new file mode 100644
index 00000000..5ac9f777
--- /dev/null
+++ b/crates/typst-layout/src/transforms.rs
@@ -0,0 +1,246 @@
+use once_cell::unsync::Lazy;
+use typst_library::diag::{bail, SourceResult};
+use typst_library::engine::Engine;
+use typst_library::foundations::{Content, Packed, Resolve, Smart, StyleChain};
+use typst_library::introspection::Locator;
+use typst_library::layout::{
+ Abs, Axes, FixedAlignment, Frame, MoveElem, Point, Ratio, Region, Rel, RotateElem,
+ ScaleAmount, ScaleElem, Size, SkewElem, Transform,
+};
+use typst_utils::Numeric;
+
+/// Layout the moved content.
+#[typst_macros::time(span = elem.span())]
+pub fn layout_move(
+ elem: &Packed<MoveElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ let mut frame = crate::layout_frame(engine, &elem.body, locator, styles, region)?;
+ let delta = Axes::new(elem.dx(styles), elem.dy(styles)).resolve(styles);
+ let delta = delta.zip_map(region.size, Rel::relative_to);
+ frame.translate(delta.to_point());
+ Ok(frame)
+}
+
+/// Layout the rotated content.
+#[typst_macros::time(span = elem.span())]
+pub fn layout_rotate(
+ elem: &Packed<RotateElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ let angle = elem.angle(styles);
+ let align = elem.origin(styles).resolve(styles);
+
+ // Compute the new region's approximate size.
+ let is_finite = region.size.is_finite();
+ let size = if is_finite {
+ compute_bounding_box(region.size, Transform::rotate(-angle)).1
+ } else {
+ Size::splat(Abs::inf())
+ };
+
+ measure_and_layout(
+ engine,
+ locator,
+ region,
+ size,
+ styles,
+ elem.body(),
+ Transform::rotate(angle),
+ align,
+ elem.reflow(styles),
+ )
+}
+
+/// Layout the scaled content.
+#[typst_macros::time(span = elem.span())]
+pub fn layout_scale(
+ elem: &Packed<ScaleElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ // Compute the new region's approximate size.
+ let scale = resolve_scale(elem, engine, locator.relayout(), region.size, styles)?;
+ let size = region
+ .size
+ .zip_map(scale, |r, s| if r.is_finite() { Ratio::new(1.0 / s).of(r) } else { r })
+ .map(Abs::abs);
+
+ measure_and_layout(
+ engine,
+ locator,
+ region,
+ size,
+ styles,
+ elem.body(),
+ Transform::scale(scale.x, scale.y),
+ elem.origin(styles).resolve(styles),
+ elem.reflow(styles),
+ )
+}
+
+/// Resolves scale parameters, preserving aspect ratio if one of the scales
+/// is set to `auto`.
+fn resolve_scale(
+ elem: &Packed<ScaleElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ container: Size,
+ styles: StyleChain,
+) -> SourceResult<Axes<Ratio>> {
+ fn resolve_axis(
+ axis: Smart<ScaleAmount>,
+ body: impl Fn() -> SourceResult<Abs>,
+ styles: StyleChain,
+ ) -> SourceResult<Smart<Ratio>> {
+ Ok(match axis {
+ Smart::Auto => Smart::Auto,
+ Smart::Custom(amt) => Smart::Custom(match amt {
+ ScaleAmount::Ratio(ratio) => ratio,
+ ScaleAmount::Length(length) => {
+ let length = length.resolve(styles);
+ Ratio::new(length / body()?)
+ }
+ }),
+ })
+ }
+
+ let size = Lazy::new(|| {
+ let pod = Region::new(container, Axes::splat(false));
+ let frame = crate::layout_frame(engine, &elem.body, locator, styles, pod)?;
+ SourceResult::Ok(frame.size())
+ });
+
+ let x = resolve_axis(
+ elem.x(styles),
+ || size.as_ref().map(|size| size.x).map_err(Clone::clone),
+ styles,
+ )?;
+
+ let y = resolve_axis(
+ elem.y(styles),
+ || size.as_ref().map(|size| size.y).map_err(Clone::clone),
+ styles,
+ )?;
+
+ match (x, y) {
+ (Smart::Auto, Smart::Auto) => {
+ bail!(elem.span(), "x and y cannot both be auto")
+ }
+ (Smart::Custom(x), Smart::Custom(y)) => Ok(Axes::new(x, y)),
+ (Smart::Auto, Smart::Custom(v)) | (Smart::Custom(v), Smart::Auto) => {
+ Ok(Axes::splat(v))
+ }
+ }
+}
+
+/// Layout the skewed content.
+#[typst_macros::time(span = elem.span())]
+pub fn layout_skew(
+ elem: &Packed<SkewElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ let ax = elem.ax(styles);
+ let ay = elem.ay(styles);
+ let align = elem.origin(styles).resolve(styles);
+
+ // Compute the new region's approximate size.
+ let size = if region.size.is_finite() {
+ compute_bounding_box(region.size, Transform::skew(ax, ay)).1
+ } else {
+ Size::splat(Abs::inf())
+ };
+
+ measure_and_layout(
+ engine,
+ locator,
+ region,
+ size,
+ styles,
+ elem.body(),
+ Transform::skew(ax, ay),
+ align,
+ elem.reflow(styles),
+ )
+}
+
+/// Applies a transformation to a frame, reflowing the layout if necessary.
+#[allow(clippy::too_many_arguments)]
+fn measure_and_layout(
+ engine: &mut Engine,
+ locator: Locator,
+ region: Region,
+ size: Size,
+ styles: StyleChain,
+ body: &Content,
+ transform: Transform,
+ align: Axes<FixedAlignment>,
+ reflow: bool,
+) -> SourceResult<Frame> {
+ if reflow {
+ // Measure the size of the body.
+ let pod = Region::new(size, Axes::splat(false));
+ let frame = crate::layout_frame(engine, body, locator.relayout(), styles, pod)?;
+
+ // Actually perform the layout.
+ let pod = Region::new(frame.size(), Axes::splat(true));
+ let mut frame = crate::layout_frame(engine, body, locator, styles, pod)?;
+ let Axes { x, y } = align.zip_map(frame.size(), FixedAlignment::position);
+
+ // Compute the transform.
+ let ts = Transform::translate(x, y)
+ .pre_concat(transform)
+ .pre_concat(Transform::translate(-x, -y));
+
+ // Compute the bounding box and offset and wrap in a new frame.
+ let (offset, size) = compute_bounding_box(frame.size(), ts);
+ frame.transform(ts);
+ frame.translate(offset);
+ frame.set_size(size);
+ Ok(frame)
+ } else {
+ // Layout the body.
+ let mut frame = crate::layout_frame(engine, body, locator, styles, region)?;
+ let Axes { x, y } = align.zip_map(frame.size(), FixedAlignment::position);
+
+ // Compute the transform.
+ let ts = Transform::translate(x, y)
+ .pre_concat(transform)
+ .pre_concat(Transform::translate(-x, -y));
+
+ // Apply the transform.
+ frame.transform(ts);
+ Ok(frame)
+ }
+}
+
+/// Computes the bounding box and offset of a transformed area.
+fn compute_bounding_box(size: Size, ts: Transform) -> (Point, Size) {
+ let top_left = Point::zero().transform_inf(ts);
+ let top_right = Point::with_x(size.x).transform_inf(ts);
+ let bottom_left = Point::with_y(size.y).transform_inf(ts);
+ let bottom_right = size.to_point().transform_inf(ts);
+
+ // We first compute the new bounding box of the rotated area.
+ let min_x = top_left.x.min(top_right.x).min(bottom_left.x).min(bottom_right.x);
+ let min_y = top_left.y.min(top_right.y).min(bottom_left.y).min(bottom_right.y);
+ let max_x = top_left.x.max(top_right.x).max(bottom_left.x).max(bottom_right.x);
+ let max_y = top_left.y.max(top_right.y).max(bottom_left.y).max(bottom_right.y);
+
+ // Then we compute the new size of the area.
+ let width = max_x - min_x;
+ let height = max_y - min_y;
+
+ (Point::new(-min_x, -min_y), Size::new(width.abs(), height.abs()))
+}
diff --git a/crates/typst-library/Cargo.toml b/crates/typst-library/Cargo.toml
new file mode 100644
index 00000000..de28f001
--- /dev/null
+++ b/crates/typst-library/Cargo.toml
@@ -0,0 +1,72 @@
+[package]
+name = "typst-library"
+description = "Typst's standard library."
+version = { workspace = true }
+rust-version = { workspace = true }
+authors = { workspace = true }
+edition = { workspace = true }
+homepage = { workspace = true }
+repository = { workspace = true }
+license = { workspace = true }
+categories = { workspace = true }
+keywords = { workspace = true }
+readme = { workspace = true }
+
+[dependencies]
+typst-assets = { workspace = true }
+typst-macros = { workspace = true }
+typst-syntax = { workspace = true }
+typst-timing = { workspace = true }
+typst-utils = { workspace = true }
+az = { workspace = true }
+bitflags = { workspace = true }
+bumpalo = { workspace = true }
+chinese-number = { workspace = true }
+ciborium = { workspace = true }
+comemo = { workspace = true }
+csv = { workspace = true }
+ecow = { workspace = true }
+flate2 = { workspace = true }
+fontdb = { workspace = true }
+hayagriva = { workspace = true }
+icu_properties = { workspace = true }
+icu_provider = { workspace = true }
+icu_provider_blob = { workspace = true }
+image = { workspace = true }
+indexmap = { workspace = true }
+kamadak-exif = { workspace = true }
+kurbo = { workspace = true }
+lipsum = { workspace = true }
+once_cell = { workspace = true }
+palette = { workspace = true }
+phf = { workspace = true }
+png = { workspace = true }
+qcms = { workspace = true }
+rayon = { workspace = true }
+regex = { workspace = true }
+roxmltree = { workspace = true }
+rust_decimal = { workspace = true }
+rustybuzz = { workspace = true }
+serde = { workspace = true }
+serde_json = { workspace = true }
+serde_yaml = { workspace = true }
+siphasher = { workspace = true }
+smallvec = { workspace = true }
+syntect = { workspace = true }
+time = { workspace = true }
+toml = { workspace = true }
+ttf-parser = { workspace = true }
+two-face = { workspace = true }
+typed-arena = { workspace = true }
+unicode-math-class = { workspace = true }
+unicode-segmentation = { workspace = true }
+unscanny = { workspace = true }
+usvg = { workspace = true }
+wasmi = { workspace = true }
+xmlwriter = { workspace = true }
+
+[dev-dependencies]
+typst-dev-assets = { workspace = true }
+
+[lints]
+workspace = true
diff --git a/crates/typst/src/diag.rs b/crates/typst-library/src/diag.rs
index ad3f3e4a..bd4c90a1 100644
--- a/crates/typst/src/diag.rs
+++ b/crates/typst-library/src/diag.rs
@@ -8,9 +8,9 @@ use std::string::FromUtf8Error;
use comemo::Tracked;
use ecow::{eco_vec, EcoVec};
+use typst_syntax::package::{PackageSpec, PackageVersion};
+use typst_syntax::{Span, Spanned, SyntaxError};
-use crate::syntax::package::{PackageSpec, PackageVersion};
-use crate::syntax::{Span, Spanned, SyntaxError};
use crate::{World, WorldExt};
/// Early-return with a [`StrResult`] or [`SourceResult`].
diff --git a/crates/typst/src/engine.rs b/crates/typst-library/src/engine.rs
index c3a862f8..e532172e 100644
--- a/crates/typst/src/engine.rs
+++ b/crates/typst-library/src/engine.rs
@@ -6,15 +6,19 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use comemo::{Track, Tracked, TrackedMut, Validate};
use ecow::EcoVec;
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
+use typst_syntax::{FileId, Span};
use crate::diag::{bail, HintedStrResult, SourceDiagnostic, SourceResult, StrResult};
use crate::foundations::{Styles, Value};
use crate::introspection::Introspector;
-use crate::syntax::{FileId, Span};
+use crate::routines::Routines;
use crate::World;
/// Holds all data needed during compilation.
pub struct Engine<'a> {
+ /// Defines implementation of various Typst compiler routines as a table of
+ /// function pointers.
+ pub routines: &'a Routines,
/// The compilation environment.
pub world: Tracked<'a, dyn World + 'a>,
/// Provides access to information about the document.
@@ -51,7 +55,9 @@ impl Engine<'_> {
U: Send,
F: Fn(&mut Engine, T) -> U + Send + Sync,
{
- let Engine { world, introspector, traced, ref route, .. } = *self;
+ let Engine {
+ world, introspector, traced, ref route, routines, ..
+ } = *self;
// We collect into a vector and then call `into_par_iter` instead of
// using `par_bridge` because it does not retain the ordering.
@@ -68,6 +74,7 @@ impl Engine<'_> {
traced,
sink: sink.track_mut(),
route: route.clone(),
+ routines,
};
(f(&mut engine, value), sink)
})
@@ -173,7 +180,7 @@ impl Sink {
/// Add a warning.
pub fn warn(&mut self, warning: SourceDiagnostic) {
// Check if warning is a duplicate.
- let hash = crate::utils::hash128(&(&warning.span, &warning.message));
+ let hash = typst_utils::hash128(&(&warning.span, &warning.message));
if self.warnings_set.insert(hash) {
self.warnings.push(warning);
}
diff --git a/crates/typst/src/foundations/args.rs b/crates/typst-library/src/foundations/args.rs
index db443648..ee282a87 100644
--- a/crates/typst/src/foundations/args.rs
+++ b/crates/typst-library/src/foundations/args.rs
@@ -1,12 +1,12 @@
use std::fmt::{self, Debug, Formatter};
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
+use typst_syntax::{Span, Spanned};
use crate::diag::{bail, error, At, SourceDiagnostic, SourceResult, StrResult};
use crate::foundations::{
cast, func, repr, scope, ty, Array, Dict, FromValue, IntoValue, Repr, Str, Value,
};
-use crate::syntax::{Span, Spanned};
/// Captured arguments to a function.
///
diff --git a/crates/typst/src/foundations/array.rs b/crates/typst-library/src/foundations/array.rs
index 77d8829d..9c8aecac 100644
--- a/crates/typst/src/foundations/array.rs
+++ b/crates/typst-library/src/foundations/array.rs
@@ -7,15 +7,14 @@ use comemo::Tracked;
use ecow::{eco_format, EcoString, EcoVec};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
+use typst_syntax::{Span, Spanned};
use crate::diag::{bail, At, HintedStrResult, SourceDiagnostic, SourceResult, StrResult};
use crate::engine::Engine;
-use crate::eval::ops;
use crate::foundations::{
- cast, func, repr, scope, ty, Args, Bytes, CastInfo, Context, Dict, FromValue, Func,
- IntoValue, Reflect, Repr, Str, Value, Version,
+ cast, func, ops, repr, scope, ty, Args, Bytes, CastInfo, Context, Dict, FromValue,
+ Func, IntoValue, Reflect, Repr, Str, Value, Version,
};
-use crate::syntax::{Span, Spanned};
/// Create a new [`Array`] from values.
#[macro_export]
diff --git a/crates/typst/src/foundations/auto.rs b/crates/typst-library/src/foundations/auto.rs
index cb9d999e..8237b450 100644
--- a/crates/typst/src/foundations/auto.rs
+++ b/crates/typst-library/src/foundations/auto.rs
@@ -1,6 +1,7 @@
-use ecow::EcoString;
use std::fmt::{self, Debug, Formatter};
+use ecow::EcoString;
+
use crate::diag::HintedStrResult;
use crate::foundations::{
ty, CastInfo, Fold, FromValue, IntoValue, Reflect, Repr, Resolve, StyleChain, Type,
diff --git a/crates/typst/src/foundations/bool.rs b/crates/typst-library/src/foundations/bool.rs
index e88c8c6f..e88c8c6f 100644
--- a/crates/typst/src/foundations/bool.rs
+++ b/crates/typst-library/src/foundations/bool.rs
diff --git a/crates/typst/src/foundations/bytes.rs b/crates/typst-library/src/foundations/bytes.rs
index 5b77aa2a..05fe4763 100644
--- a/crates/typst/src/foundations/bytes.rs
+++ b/crates/typst-library/src/foundations/bytes.rs
@@ -5,10 +5,10 @@ use std::sync::Arc;
use ecow::{eco_format, EcoString};
use serde::{Serialize, Serializer};
+use typst_utils::LazyHash;
use crate::diag::{bail, StrResult};
use crate::foundations::{cast, func, scope, ty, Array, Reflect, Repr, Str, Value};
-use crate::utils::LazyHash;
/// A sequence of bytes.
///
diff --git a/crates/typst/src/foundations/calc.rs b/crates/typst-library/src/foundations/calc.rs
index a4dbab88..f12ca74c 100644
--- a/crates/typst/src/foundations/calc.rs
+++ b/crates/typst-library/src/foundations/calc.rs
@@ -4,13 +4,12 @@ use std::cmp;
use std::cmp::Ordering;
use az::SaturatingAs;
+use typst_syntax::{Span, Spanned};
+use typst_utils::{round_int_with_precision, round_with_precision};
use crate::diag::{bail, At, HintedString, SourceResult, StrResult};
-use crate::eval::ops;
-use crate::foundations::{cast, func, Decimal, IntoValue, Module, Scope, Value};
+use crate::foundations::{cast, func, ops, Decimal, IntoValue, Module, Scope, Value};
use crate::layout::{Angle, Fr, Length, Ratio};
-use crate::syntax::{Span, Spanned};
-use crate::utils::{round_int_with_precision, round_with_precision};
/// A module with calculation definitions.
pub fn module() -> Module {
diff --git a/crates/typst/src/foundations/cast.rs b/crates/typst-library/src/foundations/cast.rs
index 2dc69236..84f38f36 100644
--- a/crates/typst/src/foundations/cast.rs
+++ b/crates/typst-library/src/foundations/cast.rs
@@ -1,3 +1,7 @@
+#[rustfmt::skip]
+#[doc(inline)]
+pub use typst_macros::{cast, Cast};
+
use std::borrow::Cow;
use std::fmt::Write;
use std::hash::Hash;
@@ -5,15 +9,11 @@ use std::ops::Add;
use ecow::eco_format;
use smallvec::SmallVec;
+use typst_syntax::{Span, Spanned};
use unicode_math_class::MathClass;
use crate::diag::{At, HintedStrResult, HintedString, SourceResult, StrResult};
use crate::foundations::{array, repr, NativeElement, Packed, Repr, Str, Type, Value};
-use crate::syntax::{Span, Spanned};
-
-#[rustfmt::skip]
-#[doc(inline)]
-pub use typst_macros::{cast, Cast};
/// Determine details of a type.
///
diff --git a/crates/typst/src/foundations/content.rs b/crates/typst-library/src/foundations/content.rs
index 0103ff9b..a274b8bf 100644
--- a/crates/typst/src/foundations/content.rs
+++ b/crates/typst-library/src/foundations/content.rs
@@ -10,6 +10,8 @@ use comemo::Tracked;
use ecow::{eco_format, EcoString};
use serde::{Serialize, Serializer};
use smallvec::smallvec;
+use typst_syntax::Span;
+use typst_utils::{fat, singleton, LazyHash, SmallBitSet};
use crate::diag::{SourceResult, StrResult};
use crate::engine::Engine;
@@ -21,9 +23,7 @@ use crate::foundations::{
use crate::introspection::Location;
use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides};
use crate::model::{Destination, EmphElem, LinkElem, StrongElem};
-use crate::syntax::Span;
use crate::text::UnderlineElem;
-use crate::utils::{fat, singleton, LazyHash, SmallBitSet};
/// A piece of document content.
///
diff --git a/crates/typst/src/foundations/context.rs b/crates/typst-library/src/foundations/context.rs
index bf4bdcd2..bf4bdcd2 100644
--- a/crates/typst/src/foundations/context.rs
+++ b/crates/typst-library/src/foundations/context.rs
diff --git a/crates/typst/src/foundations/datetime.rs b/crates/typst-library/src/foundations/datetime.rs
index d15cd417..d15cd417 100644
--- a/crates/typst/src/foundations/datetime.rs
+++ b/crates/typst-library/src/foundations/datetime.rs
diff --git a/crates/typst/src/foundations/decimal.rs b/crates/typst-library/src/foundations/decimal.rs
index 809b298a..cf11e1dd 100644
--- a/crates/typst/src/foundations/decimal.rs
+++ b/crates/typst-library/src/foundations/decimal.rs
@@ -5,10 +5,11 @@ use std::str::FromStr;
use ecow::{eco_format, EcoString};
use rust_decimal::MathematicalOps;
+use typst_syntax::{ast, Span, Spanned};
use crate::diag::{warning, At, SourceResult};
-use crate::foundations::{cast, func, repr, scope, ty, Engine, Repr, Str};
-use crate::syntax::{ast, Span, Spanned};
+use crate::engine::Engine;
+use crate::foundations::{cast, func, repr, scope, ty, Repr, Str};
use crate::World;
/// A fixed-point decimal number type.
@@ -447,8 +448,9 @@ cast! {
mod tests {
use std::str::FromStr;
+ use typst_utils::hash128;
+
use super::Decimal;
- use crate::utils::hash128;
#[test]
fn test_decimals_with_equal_scales_hash_identically() {
diff --git a/crates/typst/src/foundations/dict.rs b/crates/typst-library/src/foundations/dict.rs
index 6e61838e..e4ab54e7 100644
--- a/crates/typst/src/foundations/dict.rs
+++ b/crates/typst-library/src/foundations/dict.rs
@@ -6,13 +6,13 @@ use std::sync::Arc;
use ecow::{eco_format, EcoString};
use indexmap::IndexMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
+use typst_syntax::is_ident;
+use typst_utils::ArcExt;
use crate::diag::{Hint, HintedStrResult, StrResult};
use crate::foundations::{
array, cast, func, repr, scope, ty, Array, Module, Repr, Str, Value,
};
-use crate::syntax::is_ident;
-use crate::utils::ArcExt;
/// Create a new [`Dict`] from key-value pairs.
#[macro_export]
diff --git a/crates/typst/src/foundations/duration.rs b/crates/typst-library/src/foundations/duration.rs
index 94d44fb2..94d44fb2 100644
--- a/crates/typst/src/foundations/duration.rs
+++ b/crates/typst-library/src/foundations/duration.rs
diff --git a/crates/typst/src/foundations/element.rs b/crates/typst-library/src/foundations/element.rs
index e578d209..8da71965 100644
--- a/crates/typst/src/foundations/element.rs
+++ b/crates/typst-library/src/foundations/element.rs
@@ -7,6 +7,9 @@ use std::ptr::NonNull;
use ecow::EcoString;
use once_cell::sync::Lazy;
use smallvec::SmallVec;
+#[doc(inline)]
+pub use typst_macros::elem;
+use typst_utils::Static;
use crate::diag::SourceResult;
use crate::engine::Engine;
@@ -15,10 +18,6 @@ use crate::foundations::{
StyleChain, Styles, Value,
};
use crate::text::{Lang, Region};
-use crate::utils::Static;
-
-#[doc(inline)]
-pub use typst_macros::elem;
/// A document element.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
diff --git a/crates/typst/src/foundations/fields.rs b/crates/typst-library/src/foundations/fields.rs
index 422f30b8..422f30b8 100644
--- a/crates/typst/src/foundations/fields.rs
+++ b/crates/typst-library/src/foundations/fields.rs
diff --git a/crates/typst/src/foundations/float.rs b/crates/typst-library/src/foundations/float.rs
index 752b01d6..bb3232ee 100644
--- a/crates/typst/src/foundations/float.rs
+++ b/crates/typst-library/src/foundations/float.rs
@@ -2,9 +2,9 @@ use std::num::ParseFloatError;
use ecow::{eco_format, EcoString};
-use crate::diag::StrResult;
+use crate::diag::{bail, StrResult};
use crate::foundations::{
- bail, cast, func, repr, scope, ty, Bytes, Decimal, Endianness, Repr, Str,
+ cast, func, repr, scope, ty, Bytes, Decimal, Endianness, Repr, Str,
};
use crate::layout::Ratio;
diff --git a/crates/typst/src/foundations/func.rs b/crates/typst-library/src/foundations/func.rs
index 7ba7496f..1b40714b 100644
--- a/crates/typst/src/foundations/func.rs
+++ b/crates/typst-library/src/foundations/func.rs
@@ -1,9 +1,14 @@
+#[doc(inline)]
+pub use typst_macros::func;
+
use std::fmt::{self, Debug, Formatter};
use std::sync::Arc;
use comemo::{Tracked, TrackedMut};
use ecow::{eco_format, EcoString};
use once_cell::sync::Lazy;
+use typst_syntax::{ast, Span, SyntaxNode};
+use typst_utils::{singleton, LazyHash, Static};
use crate::diag::{bail, SourceResult, StrResult};
use crate::engine::Engine;
@@ -11,11 +16,6 @@ use crate::foundations::{
cast, repr, scope, ty, Args, CastInfo, Content, Context, Element, IntoArgs, Scope,
Selector, Type, Value,
};
-use crate::syntax::{ast, Span, SyntaxNode};
-use crate::utils::{singleton, LazyHash, Static};
-
-#[doc(inline)]
-pub use typst_macros::func;
/// A mapping from argument values to a return value.
///
@@ -296,9 +296,10 @@ impl Func {
args.finish()?;
Ok(Value::Content(value))
}
- Repr::Closure(closure) => crate::eval::call_closure(
+ Repr::Closure(closure) => (engine.routines.eval_closure)(
self,
closure,
+ engine.routines,
engine.world,
engine.introspector,
engine.traced,
diff --git a/crates/typst/src/foundations/int.rs b/crates/typst-library/src/foundations/int.rs
index 2832be91..e936353c 100644
--- a/crates/typst/src/foundations/int.rs
+++ b/crates/typst-library/src/foundations/int.rs
@@ -2,9 +2,9 @@ use std::num::{NonZeroI64, NonZeroIsize, NonZeroU64, NonZeroUsize, ParseIntError
use ecow::{eco_format, EcoString};
-use crate::diag::StrResult;
+use crate::diag::{bail, StrResult};
use crate::foundations::{
- bail, cast, func, repr, scope, ty, Bytes, Cast, Decimal, Repr, Str, Value,
+ cast, func, repr, scope, ty, Bytes, Cast, Decimal, Repr, Str, Value,
};
/// A whole number.
@@ -402,13 +402,16 @@ macro_rules! unsigned_int {
($($ty:ty)*) => {
$(cast! {
$ty,
- self => if let Ok(int) = i64::try_from(self) {
- Value::Int(int)
- } else {
- // Some u64 are too large to be cast as i64
- // In that case, we accept that there may be a
- // precision loss, and use a floating point number
- Value::Float(self as _)
+ self => {
+ #[allow(irrefutable_let_patterns)]
+ if let Ok(int) = i64::try_from(self) {
+ Value::Int(int)
+ } else {
+ // Some u64 are too large to be cast as i64
+ // In that case, we accept that there may be a
+ // precision loss, and use a floating point number
+ Value::Float(self as _)
+ }
},
v: i64 => v.try_into().map_err(|_| {
if v < 0 {
diff --git a/crates/typst/src/foundations/label.rs b/crates/typst-library/src/foundations/label.rs
index 6b8f6798..726958df 100644
--- a/crates/typst/src/foundations/label.rs
+++ b/crates/typst-library/src/foundations/label.rs
@@ -1,7 +1,7 @@
use ecow::{eco_format, EcoString};
+use typst_utils::PicoStr;
use crate::foundations::{func, scope, ty, Repr};
-use crate::utils::PicoStr;
/// A label for an element.
///
diff --git a/crates/typst/src/foundations/mod.rs b/crates/typst-library/src/foundations/mod.rs
index dc939932..a6d6c253 100644
--- a/crates/typst/src/foundations/mod.rs
+++ b/crates/typst-library/src/foundations/mod.rs
@@ -1,11 +1,10 @@
//! Foundational types and functions.
pub mod calc;
+pub mod ops;
pub mod repr;
pub mod sys;
-pub use typst_macros::{scope, ty};
-
mod args;
mod array;
mod auto;
@@ -24,7 +23,6 @@ mod float;
mod func;
mod int;
mod label;
-mod methods;
mod module;
mod none;
mod plugin;
@@ -32,6 +30,7 @@ mod scope;
mod selector;
mod str;
mod styles;
+mod symbol;
mod ty;
mod value;
mod version;
@@ -53,7 +52,6 @@ pub use self::float::*;
pub use self::func::*;
pub use self::int::*;
pub use self::label::*;
-pub(crate) use self::methods::*;
pub use self::module::*;
pub use self::none::*;
pub use self::plugin::*;
@@ -62,9 +60,11 @@ pub use self::scope::*;
pub use self::selector::*;
pub use self::str::*;
pub use self::styles::*;
+pub use self::symbol::*;
pub use self::ty::*;
pub use self::value::*;
pub use self::version::*;
+pub use typst_macros::{scope, ty};
#[rustfmt::skip]
#[doc(hidden)]
@@ -75,11 +75,11 @@ pub use {
};
use ecow::EcoString;
+use typst_syntax::Spanned;
use crate::diag::{bail, SourceResult, StrResult};
use crate::engine::Engine;
-use crate::eval::EvalMode;
-use crate::syntax::Spanned;
+use crate::routines::EvalMode;
/// Foundational types and functions.
///
@@ -108,6 +108,7 @@ pub(super) fn define(global: &mut Scope, inputs: Dict) {
global.define_type::<Selector>();
global.define_type::<Datetime>();
global.define_type::<Decimal>();
+ global.define_type::<Symbol>();
global.define_type::<Duration>();
global.define_type::<Version>();
global.define_type::<Plugin>();
@@ -297,5 +298,5 @@ pub fn eval(
for (key, value) in dict {
scope.define_spanned(key, value, span);
}
- crate::eval::eval_string(engine.world, &text, span, mode, scope)
+ (engine.routines.eval_string)(engine.routines, engine.world, &text, span, mode, scope)
}
diff --git a/crates/typst/src/foundations/module.rs b/crates/typst-library/src/foundations/module.rs
index 91b50855..a476d6af 100644
--- a/crates/typst/src/foundations/module.rs
+++ b/crates/typst-library/src/foundations/module.rs
@@ -2,10 +2,10 @@ use std::fmt::{self, Debug, Formatter};
use std::sync::Arc;
use ecow::{eco_format, EcoString};
+use typst_syntax::FileId;
use crate::diag::StrResult;
use crate::foundations::{repr, ty, Content, Scope, Value};
-use crate::syntax::FileId;
/// An evaluated module, either built-in or resulting from a file.
///
diff --git a/crates/typst/src/foundations/none.rs b/crates/typst-library/src/foundations/none.rs
index d376c0c5..d376c0c5 100644
--- a/crates/typst/src/foundations/none.rs
+++ b/crates/typst-library/src/foundations/none.rs
diff --git a/crates/typst/src/eval/ops.rs b/crates/typst-library/src/foundations/ops.rs
index 52032ae9..ba36137f 100644
--- a/crates/typst/src/eval/ops.rs
+++ b/crates/typst-library/src/foundations/ops.rs
@@ -3,102 +3,14 @@
use std::cmp::Ordering;
use ecow::eco_format;
+use typst_utils::Numeric;
-use crate::diag::{bail, At, HintedStrResult, SourceResult, StrResult};
-use crate::eval::{access_dict, Access, Eval, Vm};
+use crate::diag::{bail, HintedStrResult, StrResult};
use crate::foundations::{format_str, Datetime, IntoValue, Regex, Repr, Value};
use crate::layout::{Alignment, Length, Rel};
-use crate::syntax::ast::{self, AstNode};
use crate::text::TextElem;
-use crate::utils::Numeric;
use crate::visualize::Stroke;
-impl Eval for ast::Unary<'_> {
- type Output = Value;
-
- fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
- let value = self.expr().eval(vm)?;
- let result = match self.op() {
- ast::UnOp::Pos => pos(value),
- ast::UnOp::Neg => neg(value),
- ast::UnOp::Not => not(value),
- };
- result.at(self.span())
- }
-}
-
-impl Eval for ast::Binary<'_> {
- type Output = Value;
-
- fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
- match self.op() {
- ast::BinOp::Add => apply_binary(self, vm, add),
- ast::BinOp::Sub => apply_binary(self, vm, sub),
- ast::BinOp::Mul => apply_binary(self, vm, mul),
- ast::BinOp::Div => apply_binary(self, vm, div),
- ast::BinOp::And => apply_binary(self, vm, and),
- ast::BinOp::Or => apply_binary(self, vm, or),
- ast::BinOp::Eq => apply_binary(self, vm, eq),
- ast::BinOp::Neq => apply_binary(self, vm, neq),
- ast::BinOp::Lt => apply_binary(self, vm, lt),
- ast::BinOp::Leq => apply_binary(self, vm, leq),
- ast::BinOp::Gt => apply_binary(self, vm, gt),
- ast::BinOp::Geq => apply_binary(self, vm, geq),
- ast::BinOp::In => apply_binary(self, vm, in_),
- ast::BinOp::NotIn => apply_binary(self, vm, not_in),
- ast::BinOp::Assign => apply_assignment(self, vm, |_, b| Ok(b)),
- ast::BinOp::AddAssign => apply_assignment(self, vm, add),
- ast::BinOp::SubAssign => apply_assignment(self, vm, sub),
- ast::BinOp::MulAssign => apply_assignment(self, vm, mul),
- ast::BinOp::DivAssign => apply_assignment(self, vm, div),
- }
- }
-}
-
-/// Apply a basic binary operation.
-fn apply_binary(
- binary: ast::Binary,
- vm: &mut Vm,
- op: fn(Value, Value) -> HintedStrResult<Value>,
-) -> SourceResult<Value> {
- let lhs = binary.lhs().eval(vm)?;
-
- // Short-circuit boolean operations.
- if (binary.op() == ast::BinOp::And && lhs == false.into_value())
- || (binary.op() == ast::BinOp::Or && lhs == true.into_value())
- {
- return Ok(lhs);
- }
-
- let rhs = binary.rhs().eval(vm)?;
- op(lhs, rhs).at(binary.span())
-}
-
-/// Apply an assignment operation.
-fn apply_assignment(
- binary: ast::Binary,
- vm: &mut Vm,
- op: fn(Value, Value) -> HintedStrResult<Value>,
-) -> SourceResult<Value> {
- let rhs = binary.rhs().eval(vm)?;
- let lhs = binary.lhs();
-
- // An assignment to a dictionary field is different from a normal access
- // since it can create the field instead of just modifying it.
- if binary.op() == ast::BinOp::Assign {
- if let ast::Expr::FieldAccess(access) = lhs {
- let dict = access_dict(vm, access)?;
- dict.insert(access.field().get().clone().into(), rhs);
- return Ok(Value::None);
- }
- }
-
- let location = binary.lhs().access(vm)?;
- let lhs = std::mem::take(&mut *location);
- *location = op(lhs, rhs).at(binary.span())?;
- Ok(Value::None)
-}
-
/// Bail with a type mismatch error.
macro_rules! mismatch {
($fmt:expr, $($value:expr),* $(,)?) => {
diff --git a/crates/typst/src/foundations/plugin.rs b/crates/typst-library/src/foundations/plugin.rs
index 4dc03014..31107dc3 100644
--- a/crates/typst/src/foundations/plugin.rs
+++ b/crates/typst-library/src/foundations/plugin.rs
@@ -3,12 +3,12 @@ use std::hash::{Hash, Hasher};
use std::sync::{Arc, Mutex};
use ecow::{eco_format, EcoString};
+use typst_syntax::Spanned;
use wasmi::{AsContext, AsContextMut};
use crate::diag::{bail, At, SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{func, repr, scope, ty, Bytes};
-use crate::syntax::Spanned;
use crate::World;
/// A WebAssembly plugin.
diff --git a/crates/typst/src/foundations/repr.rs b/crates/typst-library/src/foundations/repr.rs
index 14510968..e219b0f1 100644
--- a/crates/typst/src/foundations/repr.rs
+++ b/crates/typst-library/src/foundations/repr.rs
@@ -1,9 +1,9 @@
//! Debug representation of values.
use ecow::{eco_format, EcoString};
+use typst_utils::round_with_precision;
use crate::foundations::{func, Str, Value};
-use crate::utils::round_with_precision;
/// The Unicode minus sign.
pub const MINUS_SIGN: &str = "\u{2212}";
diff --git a/crates/typst/src/foundations/scope.rs b/crates/typst-library/src/foundations/scope.rs
index c5c65485..b51f8caa 100644
--- a/crates/typst/src/foundations/scope.rs
+++ b/crates/typst-library/src/foundations/scope.rs
@@ -1,22 +1,22 @@
+#[doc(inline)]
+pub use typst_macros::category;
+
use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};
use ecow::{eco_format, EcoString};
use indexmap::IndexMap;
+use typst_syntax::ast::{self, AstNode};
+use typst_syntax::Span;
+use typst_utils::Static;
use crate::diag::{bail, HintedStrResult, HintedString, StrResult};
use crate::foundations::{
Element, Func, IntoValue, Module, NativeElement, NativeFunc, NativeFuncData,
NativeType, Type, Value,
};
-use crate::syntax::ast::{self, AstNode};
-use crate::syntax::Span;
-use crate::utils::Static;
use crate::Library;
-#[doc(inline)]
-pub use typst_macros::category;
-
/// A stack of scopes.
#[derive(Debug, Default, Clone)]
pub struct Scopes<'a> {
diff --git a/crates/typst/src/foundations/selector.rs b/crates/typst-library/src/foundations/selector.rs
index 575baa13..bf5449d9 100644
--- a/crates/typst/src/foundations/selector.rs
+++ b/crates/typst-library/src/foundations/selector.rs
@@ -8,10 +8,9 @@ use smallvec::SmallVec;
use crate::diag::{bail, HintedStrResult, StrResult};
use crate::foundations::{
cast, func, repr, scope, ty, CastInfo, Content, Context, Dict, Element, FromValue,
- Func, Label, Reflect, Regex, Repr, Str, StyleChain, Type, Value,
+ Func, Label, Reflect, Regex, Repr, Str, StyleChain, Symbol, Type, Value,
};
use crate::introspection::{Introspector, Locatable, Location, Unqueriable};
-use crate::symbols::Symbol;
/// A helper macro to create a field selector used in [`Selector::Elem`]
#[macro_export]
diff --git a/crates/typst/src/foundations/str.rs b/crates/typst-library/src/foundations/str.rs
index d90b6f20..fc221c49 100644
--- a/crates/typst/src/foundations/str.rs
+++ b/crates/typst-library/src/foundations/str.rs
@@ -6,6 +6,8 @@ use std::ops::{Add, AddAssign, Deref, Range};
use comemo::Tracked;
use ecow::EcoString;
use serde::{Deserialize, Serialize};
+use typst_syntax::{Span, Spanned};
+use typst_utils::PicoStr;
use unicode_segmentation::UnicodeSegmentation;
use crate::diag::{bail, At, SourceResult, StrResult};
@@ -15,8 +17,6 @@ use crate::foundations::{
IntoValue, Label, Repr, Type, Value, Version,
};
use crate::layout::Alignment;
-use crate::syntax::{Span, Spanned};
-use crate::utils::PicoStr;
/// Create a new [`Str`] from a format string.
#[macro_export]
@@ -27,12 +27,12 @@ macro_rules! __format_str {
}};
}
-#[doc(inline)]
-pub use crate::__format_str as format_str;
-
#[doc(hidden)]
pub use ecow::eco_format;
+#[doc(inline)]
+pub use crate::__format_str as format_str;
+
/// A sequence of Unicode codepoints.
///
/// You can iterate over the grapheme clusters of the string using a [for
diff --git a/crates/typst/src/foundations/styles.rs b/crates/typst-library/src/foundations/styles.rs
index a3c8e0c4..af4909e5 100644
--- a/crates/typst/src/foundations/styles.rs
+++ b/crates/typst-library/src/foundations/styles.rs
@@ -6,6 +6,8 @@ use std::{mem, ptr};
use comemo::{Track, Tracked};
use ecow::{eco_vec, EcoString, EcoVec};
use smallvec::SmallVec;
+use typst_syntax::Span;
+use typst_utils::LazyHash;
use crate::diag::{warning, SourceResult, Trace, Tracepoint};
use crate::engine::Engine;
@@ -14,9 +16,7 @@ use crate::foundations::{
Selector, Show,
};
use crate::introspection::Locatable;
-use crate::syntax::Span;
use crate::text::{FontFamily, FontList, TextElem};
-use crate::utils::LazyHash;
/// Provides access to active styles.
///
diff --git a/crates/typst/src/symbols/symbol.rs b/crates/typst-library/src/foundations/symbol.rs
index 0be4adc4..86676fa2 100644
--- a/crates/typst/src/symbols/symbol.rs
+++ b/crates/typst-library/src/foundations/symbol.rs
@@ -1,3 +1,6 @@
+#[doc(inline)]
+pub use typst_macros::symbols;
+
use std::cmp::Reverse;
use std::collections::BTreeSet;
use std::fmt::{self, Debug, Display, Formatter, Write};
@@ -5,13 +8,10 @@ use std::sync::Arc;
use ecow::{eco_format, EcoString};
use serde::{Serialize, Serializer};
+use typst_syntax::{Span, Spanned};
use crate::diag::{bail, SourceResult, StrResult};
use crate::foundations::{cast, func, scope, ty, Array, Func};
-use crate::syntax::{Span, Spanned};
-
-#[doc(inline)]
-pub use typst_macros::symbols;
/// A Unicode symbol.
///
diff --git a/crates/typst/src/foundations/sys.rs b/crates/typst-library/src/foundations/sys.rs
index 7c128104..7c128104 100644
--- a/crates/typst/src/foundations/sys.rs
+++ b/crates/typst-library/src/foundations/sys.rs
diff --git a/crates/typst/src/foundations/ty.rs b/crates/typst-library/src/foundations/ty.rs
index 3bcde61f..70845dd2 100644
--- a/crates/typst/src/foundations/ty.rs
+++ b/crates/typst-library/src/foundations/ty.rs
@@ -1,18 +1,17 @@
+#[doc(inline)]
+pub use typst_macros::{scope, ty};
+
use std::cmp::Ordering;
use std::fmt::{self, Debug, Display, Formatter};
use ecow::{eco_format, EcoString};
use once_cell::sync::Lazy;
+use typst_utils::Static;
use crate::diag::StrResult;
use crate::foundations::{
cast, func, AutoValue, Func, NativeFuncData, NoneValue, Repr, Scope, Value,
};
-use crate::utils::Static;
-
-#[rustfmt::skip]
-#[doc(inline)]
-pub use typst_macros::{scope, ty};
/// Describes a kind of value.
///
diff --git a/crates/typst/src/foundations/value.rs b/crates/typst-library/src/foundations/value.rs
index cabe5647..fbfa5a0e 100644
--- a/crates/typst/src/foundations/value.rs
+++ b/crates/typst-library/src/foundations/value.rs
@@ -8,20 +8,18 @@ use ecow::{eco_format, EcoString};
use serde::de::value::{MapAccessDeserializer, SeqAccessDeserializer};
use serde::de::{Error, MapAccess, SeqAccess, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
+use typst_syntax::{ast, Span};
+use typst_utils::ArcExt;
use crate::diag::{HintedStrResult, HintedString, StrResult};
-use crate::eval::ops;
use crate::foundations::{
- fields, repr, Args, Array, AutoValue, Bytes, CastInfo, Content, Datetime, Decimal,
- Dict, Duration, Fold, FromValue, Func, IntoValue, Label, Module, NativeElement,
- NativeType, NoneValue, Plugin, Reflect, Repr, Resolve, Scope, Str, Styles, Type,
- Version,
+ fields, ops, repr, Args, Array, AutoValue, Bytes, CastInfo, Content, Datetime,
+ Decimal, Dict, Duration, Fold, FromValue, Func, IntoValue, Label, Module,
+ NativeElement, NativeType, NoneValue, Plugin, Reflect, Repr, Resolve, Scope, Str,
+ Styles, Symbol, Type, Version,
};
use crate::layout::{Abs, Angle, Em, Fr, Length, Ratio, Rel};
-use crate::symbols::Symbol;
-use crate::syntax::{ast, Span};
use crate::text::{RawContent, RawElem, TextElem};
-use crate::utils::ArcExt;
use crate::visualize::{Color, Gradient, Pattern};
/// A computational value.
diff --git a/crates/typst/src/foundations/version.rs b/crates/typst-library/src/foundations/version.rs
index 62c02917..62c02917 100644
--- a/crates/typst/src/foundations/version.rs
+++ b/crates/typst-library/src/foundations/version.rs
diff --git a/crates/typst/src/introspection/counter.rs b/crates/typst-library/src/introspection/counter.rs
index 38da6363..2e7180c6 100644
--- a/crates/typst/src/introspection/counter.rs
+++ b/crates/typst-library/src/introspection/counter.rs
@@ -4,6 +4,8 @@ use std::str::FromStr;
use comemo::{Track, Tracked, TrackedMut};
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
use smallvec::{smallvec, SmallVec};
+use typst_syntax::Span;
+use typst_utils::NonZeroExt;
use crate::diag::{bail, warning, At, HintedStrResult, SourceResult};
use crate::engine::{Engine, Route, Sink, Traced};
@@ -16,8 +18,7 @@ use crate::introspection::{Introspector, Locatable, Location, Tag};
use crate::layout::{Frame, FrameItem, PageElem};
use crate::math::EquationElem;
use crate::model::{FigureElem, FootnoteElem, HeadingElem, Numbering, NumberingPattern};
-use crate::syntax::Span;
-use crate::utils::NonZeroExt;
+use crate::routines::Routines;
use crate::World;
/// Counts through pages, elements, and more.
@@ -278,6 +279,7 @@ impl Counter {
engine: &mut Engine,
) -> SourceResult<EcoVec<(CounterState, NonZeroUsize)>> {
self.sequence_impl(
+ engine.routines,
engine.world,
engine.introspector,
engine.traced,
@@ -290,6 +292,7 @@ impl Counter {
#[comemo::memoize]
fn sequence_impl(
&self,
+ routines: &Routines,
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
@@ -297,6 +300,7 @@ impl Counter {
route: Tracked<Route>,
) -> SourceResult<EcoVec<(CounterState, NonZeroUsize)>> {
let mut engine = Engine {
+ routines,
world,
introspector,
traced,
diff --git a/crates/typst/src/introspection/here.rs b/crates/typst-library/src/introspection/here.rs
index 9d613381..9d613381 100644
--- a/crates/typst/src/introspection/here.rs
+++ b/crates/typst-library/src/introspection/here.rs
diff --git a/crates/typst/src/introspection/introspector.rs b/crates/typst-library/src/introspection/introspector.rs
index 113f9afe..f4eaea30 100644
--- a/crates/typst/src/introspection/introspector.rs
+++ b/crates/typst-library/src/introspection/introspector.rs
@@ -6,13 +6,13 @@ use std::sync::RwLock;
use ecow::EcoVec;
use smallvec::SmallVec;
+use typst_utils::NonZeroExt;
use crate::diag::{bail, StrResult};
use crate::foundations::{Content, Label, Repr, Selector};
use crate::introspection::{Location, Tag};
use crate::layout::{Frame, FrameItem, Page, Point, Position, Transform};
use crate::model::Numbering;
-use crate::utils::NonZeroExt;
/// Can be queried for elements and their positions.
#[derive(Default, Clone)]
@@ -97,7 +97,7 @@ impl Introspector {
impl Introspector {
/// Query for all matching elements.
pub fn query(&self, selector: &Selector) -> EcoVec<Content> {
- let hash = crate::utils::hash128(selector);
+ let hash = typst_utils::hash128(selector);
if let Some(output) = self.queries.get(hash) {
return output;
}
diff --git a/crates/typst/src/introspection/locate.rs b/crates/typst-library/src/introspection/locate.rs
index 8991ae9b..9a0e28e2 100644
--- a/crates/typst/src/introspection/locate.rs
+++ b/crates/typst-library/src/introspection/locate.rs
@@ -1,4 +1,5 @@
use comemo::{Track, Tracked};
+use typst_syntax::Span;
use crate::diag::{warning, HintedStrResult, SourceResult};
use crate::engine::Engine;
@@ -7,7 +8,6 @@ use crate::foundations::{
Show, StyleChain, Value,
};
use crate::introspection::{Locatable, Location};
-use crate::syntax::Span;
/// Determines the location of an element in the document.
///
diff --git a/crates/typst/src/introspection/location.rs b/crates/typst-library/src/introspection/location.rs
index 5c520d25..8f4caaec 100644
--- a/crates/typst/src/introspection/location.rs
+++ b/crates/typst-library/src/introspection/location.rs
@@ -42,7 +42,7 @@ impl Location {
/// example, in bibliography management to create individual linkable
/// locations for reference entries from the bibliography's location.
pub fn variant(self, n: usize) -> Self {
- Self(crate::utils::hash128(&(self.0, n)))
+ Self(typst_utils::hash128(&(self.0, n)))
}
}
diff --git a/crates/typst/src/introspection/locator.rs b/crates/typst-library/src/introspection/locator.rs
index 3e9e5c89..4045aa9c 100644
--- a/crates/typst/src/introspection/locator.rs
+++ b/crates/typst-library/src/introspection/locator.rs
@@ -210,7 +210,7 @@ impl<'a> Locator<'a> {
None => Resolved::Hash(self.local),
Some(outer) => match outer.resolve() {
Resolved::Hash(outer) => {
- Resolved::Hash(crate::utils::hash128(&(self.local, outer)))
+ Resolved::Hash(typst_utils::hash128(&(self.local, outer)))
}
Resolved::Measure(anchor) => Resolved::Measure(anchor),
},
@@ -257,7 +257,7 @@ impl<'a> SplitLocator<'a> {
/// A common choice for a key is the span of the content that will be
/// layouted with this locator.
pub fn next<K: Hash>(&mut self, key: &K) -> Locator<'a> {
- self.next_inner(crate::utils::hash128(key))
+ self.next_inner(typst_utils::hash128(key))
}
/// Produces a sublocator for a subtree.
@@ -272,7 +272,7 @@ impl<'a> SplitLocator<'a> {
// Combine the key, disambiguator and local hash into a sub-local hash.
// The outer information is not yet merged into this, it is added
// on-demand in `Locator::resolve`.
- let local = crate::utils::hash128(&(key, disambiguator, self.local));
+ let local = typst_utils::hash128(&(key, disambiguator, self.local));
Locator { outer: self.outer, local }
}
diff --git a/crates/typst/src/introspection/metadata.rs b/crates/typst-library/src/introspection/metadata.rs
index 06000174..06000174 100644
--- a/crates/typst/src/introspection/metadata.rs
+++ b/crates/typst-library/src/introspection/metadata.rs
diff --git a/crates/typst/src/introspection/mod.rs b/crates/typst-library/src/introspection/mod.rs
index b1ff2e08..b1ff2e08 100644
--- a/crates/typst/src/introspection/mod.rs
+++ b/crates/typst-library/src/introspection/mod.rs
diff --git a/crates/typst/src/introspection/query.rs b/crates/typst-library/src/introspection/query.rs
index 07f761a8..7b106bf0 100644
--- a/crates/typst/src/introspection/query.rs
+++ b/crates/typst-library/src/introspection/query.rs
@@ -1,10 +1,10 @@
use comemo::Tracked;
+use typst_syntax::Span;
use crate::diag::{warning, HintedStrResult};
use crate::engine::Engine;
use crate::foundations::{func, Array, Context, LocatableSelector, Value};
use crate::introspection::Location;
-use crate::syntax::Span;
/// Finds elements in the document.
///
diff --git a/crates/typst/src/introspection/state.rs b/crates/typst-library/src/introspection/state.rs
index 6a3d68e2..13c96b50 100644
--- a/crates/typst/src/introspection/state.rs
+++ b/crates/typst-library/src/introspection/state.rs
@@ -1,5 +1,6 @@
use comemo::{Track, Tracked, TrackedMut};
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
+use typst_syntax::Span;
use crate::diag::{bail, warning, At, SourceResult};
use crate::engine::{Engine, Route, Sink, Traced};
@@ -9,7 +10,7 @@ use crate::foundations::{
Value,
};
use crate::introspection::{Introspector, Locatable, Location};
-use crate::syntax::Span;
+use crate::routines::Routines;
use crate::World;
/// Manages stateful parts of your document.
@@ -211,6 +212,7 @@ impl State {
/// of state updates from quadratic to linear.
fn sequence(&self, engine: &mut Engine) -> SourceResult<EcoVec<Value>> {
self.sequence_impl(
+ engine.routines,
engine.world,
engine.introspector,
engine.traced,
@@ -223,6 +225,7 @@ impl State {
#[comemo::memoize]
fn sequence_impl(
&self,
+ routines: &Routines,
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
@@ -230,6 +233,7 @@ impl State {
route: Tracked<Route>,
) -> SourceResult<EcoVec<Value>> {
let mut engine = Engine {
+ routines,
world,
introspector,
traced,
diff --git a/crates/typst/src/introspection/tag.rs b/crates/typst-library/src/introspection/tag.rs
index b2bae28e..b2bae28e 100644
--- a/crates/typst/src/introspection/tag.rs
+++ b/crates/typst-library/src/introspection/tag.rs
diff --git a/crates/typst/src/layout/abs.rs b/crates/typst-library/src/layout/abs.rs
index 981a64ff..e19b037a 100644
--- a/crates/typst/src/layout/abs.rs
+++ b/crates/typst-library/src/layout/abs.rs
@@ -3,9 +3,9 @@ use std::iter::Sum;
use std::ops::{Add, Div, Mul, Neg, Rem};
use ecow::EcoString;
+use typst_utils::{Numeric, Scalar};
use crate::foundations::{cast, repr, Fold, Repr, Value};
-use crate::utils::{Numeric, Scalar};
/// An absolute length.
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
diff --git a/crates/typst/src/layout/align.rs b/crates/typst-library/src/layout/align.rs
index e8ba4d7c..e8ba4d7c 100644
--- a/crates/typst/src/layout/align.rs
+++ b/crates/typst-library/src/layout/align.rs
diff --git a/crates/typst/src/layout/angle.rs b/crates/typst-library/src/layout/angle.rs
index 63e30186..d1410786 100644
--- a/crates/typst/src/layout/angle.rs
+++ b/crates/typst-library/src/layout/angle.rs
@@ -4,9 +4,9 @@ use std::iter::Sum;
use std::ops::{Add, Div, Mul, Neg};
use ecow::EcoString;
+use typst_utils::{Numeric, Scalar};
use crate::foundations::{func, repr, scope, ty, Repr};
-use crate::utils::{Numeric, Scalar};
/// An angle describing a rotation.
///
diff --git a/crates/typst/src/layout/axes.rs b/crates/typst-library/src/layout/axes.rs
index a96fa850..7a73ba79 100644
--- a/crates/typst/src/layout/axes.rs
+++ b/crates/typst-library/src/layout/axes.rs
@@ -2,10 +2,11 @@ use std::any::Any;
use std::fmt::{self, Debug, Formatter};
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Deref, Not};
+use typst_utils::Get;
+
use crate::diag::bail;
use crate::foundations::{array, cast, Array, Resolve, Smart, StyleChain};
use crate::layout::{Abs, Dir, Length, Ratio, Rel, Size};
-use crate::utils::Get;
/// A container with a horizontal and vertical component.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
diff --git a/crates/typst/src/layout/columns.rs b/crates/typst-library/src/layout/columns.rs
index 7678e03f..f2f36441 100644
--- a/crates/typst/src/layout/columns.rs
+++ b/crates/typst-library/src/layout/columns.rs
@@ -3,10 +3,7 @@ use std::num::NonZeroUsize;
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain};
-use crate::introspection::Locator;
-use crate::layout::{
- layout_fragment_with_columns, BlockElem, Fragment, Length, Ratio, Regions, Rel,
-};
+use crate::layout::{BlockElem, Length, Ratio, Rel};
/// Separates a region into multiple equally sized columns.
///
@@ -62,33 +59,13 @@ pub struct ColumnsElem {
}
impl Show for Packed<ColumnsElem> {
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::multi_layouter(self.clone(), layout_columns)
+ fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::multi_layouter(self.clone(), engine.routines.layout_columns)
.pack()
.spanned(self.span()))
}
}
-/// Layout the columns.
-#[typst_macros::time(span = elem.span())]
-fn layout_columns(
- elem: &Packed<ColumnsElem>,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- regions: Regions,
-) -> SourceResult<Fragment> {
- layout_fragment_with_columns(
- engine,
- &elem.body,
- locator,
- styles,
- regions,
- elem.count(styles),
- elem.gutter(styles),
- )
-}
-
/// Forces a column break.
///
/// The function will behave like a [page break]($pagebreak) when used in a
diff --git a/crates/typst-library/src/layout/container.rs b/crates/typst-library/src/layout/container.rs
new file mode 100644
index 00000000..266d1d88
--- /dev/null
+++ b/crates/typst-library/src/layout/container.rs
@@ -0,0 +1,563 @@
+use crate::diag::{bail, SourceResult};
+use crate::engine::Engine;
+use crate::foundations::{
+ cast, elem, Args, AutoValue, Construct, Content, NativeElement, Packed, Smart,
+ StyleChain, Value,
+};
+use crate::introspection::Locator;
+use crate::layout::{
+ Abs, Corners, Em, Fr, Fragment, Frame, Length, Region, Regions, Rel, Sides, Size,
+ Spacing,
+};
+use crate::visualize::{Paint, Stroke};
+
+/// An inline-level container that sizes content.
+///
+/// All elements except inline math, text, and boxes are block-level and cannot
+/// occur inside of a paragraph. The box function can be used to integrate such
+/// elements into a paragraph. Boxes take the size of their contents by default
+/// but can also be sized explicitly.
+///
+/// # Example
+/// ```example
+/// Refer to the docs
+/// #box(
+/// height: 9pt,
+/// image("docs.svg")
+/// )
+/// for more information.
+/// ```
+#[elem]
+pub struct BoxElem {
+ /// The width of the box.
+ ///
+ /// Boxes can have [fractional]($fraction) widths, as the example below
+ /// demonstrates.
+ ///
+ /// _Note:_ Currently, only boxes and only their widths might be fractionally
+ /// sized within paragraphs. Support for fractionally sized images, shapes,
+ /// and more might be added in the future.
+ ///
+ /// ```example
+ /// Line in #box(width: 1fr, line(length: 100%)) between.
+ /// ```
+ pub width: Sizing,
+
+ /// The height of the box.
+ pub height: Smart<Rel<Length>>,
+
+ /// An amount to shift the box's baseline by.
+ ///
+ /// ```example
+ /// Image: #box(baseline: 40%, image("tiger.jpg", width: 2cm)).
+ /// ```
+ #[resolve]
+ pub baseline: Rel<Length>,
+
+ /// The box's background color. See the
+ /// [rectangle's documentation]($rect.fill) for more details.
+ pub fill: Option<Paint>,
+
+ /// The box's border color. See the
+ /// [rectangle's documentation]($rect.stroke) for more details.
+ #[resolve]
+ #[fold]
+ pub stroke: Sides<Option<Option<Stroke>>>,
+
+ /// How much to round the box's corners. See the
+ /// [rectangle's documentation]($rect.radius) for more details.
+ #[resolve]
+ #[fold]
+ pub radius: Corners<Option<Rel<Length>>>,
+
+ /// How much to pad the box's content.
+ ///
+ /// _Note:_ When the box contains text, its exact size depends on the
+ /// current [text edges]($text.top-edge).
+ ///
+ /// ```example
+ /// #rect(inset: 0pt)[Tight]
+ /// ```
+ #[resolve]
+ #[fold]
+ pub inset: Sides<Option<Rel<Length>>>,
+
+ /// How much to expand the box's size without affecting the layout.
+ ///
+ /// This is useful to prevent padding from affecting line layout. For a
+ /// generalized version of the example below, see the documentation for the
+ /// [raw text's block parameter]($raw.block).
+ ///
+ /// ```example
+ /// An inline
+ /// #box(
+ /// fill: luma(235),
+ /// inset: (x: 3pt, y: 0pt),
+ /// outset: (y: 3pt),
+ /// radius: 2pt,
+ /// )[rectangle].
+ /// ```
+ #[resolve]
+ #[fold]
+ pub outset: Sides<Option<Rel<Length>>>,
+
+ /// Whether to clip the content inside the box.
+ ///
+ /// Clipping is useful when the box's content is larger than the box itself,
+ /// as any content that exceeds the box's bounds will be hidden.
+ ///
+ /// ```example
+ /// #box(
+ /// width: 50pt,
+ /// height: 50pt,
+ /// clip: true,
+ /// image("tiger.jpg", width: 100pt, height: 100pt)
+ /// )
+ /// ```
+ #[default(false)]
+ pub clip: bool,
+
+ /// The contents of the box.
+ #[positional]
+ #[borrowed]
+ pub body: Option<Content>,
+}
+
+/// An inline-level container that can produce arbitrary items that can break
+/// across lines.
+#[elem(Construct)]
+pub struct InlineElem {
+ /// A callback that is invoked with the regions to produce arbitrary
+ /// inline items.
+ #[required]
+ #[internal]
+ body: callbacks::InlineCallback,
+}
+
+impl Construct for InlineElem {
+ fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
+ bail!(args.span, "cannot be constructed manually");
+ }
+}
+
+impl InlineElem {
+ /// Create an inline-level item with a custom layouter.
+ #[allow(clippy::type_complexity)]
+ pub fn layouter<T: NativeElement>(
+ captured: Packed<T>,
+ callback: fn(
+ content: &Packed<T>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Size,
+ ) -> SourceResult<Vec<InlineItem>>,
+ ) -> Self {
+ Self::new(callbacks::InlineCallback::new(captured, callback))
+ }
+}
+
+impl Packed<InlineElem> {
+ /// Layout the element.
+ pub fn layout(
+ &self,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Size,
+ ) -> SourceResult<Vec<InlineItem>> {
+ self.body().call(engine, locator, styles, region)
+ }
+}
+
+/// Layouted items suitable for placing in a paragraph.
+#[derive(Debug, Clone)]
+pub enum InlineItem {
+ /// Absolute spacing between other items, and whether it is weak.
+ Space(Abs, bool),
+ /// Layouted inline-level content.
+ Frame(Frame),
+}
+
+/// A block-level container.
+///
+/// Such a container can be used to separate content, size it, and give it a
+/// background or border.
+///
+/// # Examples
+/// With a block, you can give a background to content while still allowing it
+/// to break across multiple pages.
+/// ```example
+/// #set page(height: 100pt)
+/// #block(
+/// fill: luma(230),
+/// inset: 8pt,
+/// radius: 4pt,
+/// lorem(30),
+/// )
+/// ```
+///
+/// Blocks are also useful to force elements that would otherwise be inline to
+/// become block-level, especially when writing show rules.
+/// ```example
+/// #show heading: it => it.body
+/// = Blockless
+/// More text.
+///
+/// #show heading: it => block(it.body)
+/// = Blocky
+/// More text.
+/// ```
+#[elem]
+pub struct BlockElem {
+ /// The block's width.
+ ///
+ /// ```example
+ /// #set align(center)
+ /// #block(
+ /// width: 60%,
+ /// inset: 8pt,
+ /// fill: silver,
+ /// lorem(10),
+ /// )
+ /// ```
+ pub width: Smart<Rel<Length>>,
+
+ /// The block's height. When the height is larger than the remaining space
+ /// on a page and [`breakable`]($block.breakable) is `{true}`, the
+ /// block will continue on the next page with the remaining height.
+ ///
+ /// ```example
+ /// #set page(height: 80pt)
+ /// #set align(center)
+ /// #block(
+ /// width: 80%,
+ /// height: 150%,
+ /// fill: aqua,
+ /// )
+ /// ```
+ pub height: Sizing,
+
+ /// Whether the block can be broken and continue on the next page.
+ ///
+ /// ```example
+ /// #set page(height: 80pt)
+ /// The following block will
+ /// jump to its own page.
+ /// #block(
+ /// breakable: false,
+ /// lorem(15),
+ /// )
+ /// ```
+ #[default(true)]
+ pub breakable: bool,
+
+ /// The block's background color. See the
+ /// [rectangle's documentation]($rect.fill) for more details.
+ pub fill: Option<Paint>,
+
+ /// The block's border color. See the
+ /// [rectangle's documentation]($rect.stroke) for more details.
+ #[resolve]
+ #[fold]
+ pub stroke: Sides<Option<Option<Stroke>>>,
+
+ /// How much to round the block's corners. See the
+ /// [rectangle's documentation]($rect.radius) for more details.
+ #[resolve]
+ #[fold]
+ pub radius: Corners<Option<Rel<Length>>>,
+
+ /// How much to pad the block's content. See the
+ /// [box's documentation]($box.inset) for more details.
+ #[resolve]
+ #[fold]
+ pub inset: Sides<Option<Rel<Length>>>,
+
+ /// How much to expand the block's size without affecting the layout. See
+ /// the [box's documentation]($box.outset) for more details.
+ #[resolve]
+ #[fold]
+ pub outset: Sides<Option<Rel<Length>>>,
+
+ /// The spacing around the block. When `{auto}`, inherits the paragraph
+ /// [`spacing`]($par.spacing).
+ ///
+ /// For two adjacent blocks, the larger of the first block's `above` and the
+ /// second block's `below` spacing wins. Moreover, block spacing takes
+ /// precedence over paragraph [`spacing`]($par.spacing).
+ ///
+ /// Note that this is only a shorthand to set `above` and `below` to the
+ /// same value. Since the values for `above` and `below` might differ, a
+ /// [context] block only provides access to `{block.above}` and
+ /// `{block.below}`, not to `{block.spacing}` directly.
+ ///
+ /// This property can be used in combination with a show rule to adjust the
+ /// spacing around arbitrary block-level elements.
+ ///
+ /// ```example
+ /// #set align(center)
+ /// #show math.equation: set block(above: 8pt, below: 16pt)
+ ///
+ /// This sum of $x$ and $y$:
+ /// $ x + y = z $
+ /// A second paragraph.
+ /// ```
+ #[external]
+ #[default(Em::new(1.2).into())]
+ pub spacing: Spacing,
+
+ /// The spacing between this block and its predecessor.
+ #[parse(
+ let spacing = args.named("spacing")?;
+ args.named("above")?.or(spacing)
+ )]
+ pub above: Smart<Spacing>,
+
+ /// The spacing between this block and its successor.
+ #[parse(args.named("below")?.or(spacing))]
+ pub below: Smart<Spacing>,
+
+ /// Whether to clip the content inside the block.
+ ///
+ /// Clipping is useful when the block's content is larger than the block itself,
+ /// as any content that exceeds the block's bounds will be hidden.
+ ///
+ /// ```example
+ /// #block(
+ /// width: 50pt,
+ /// height: 50pt,
+ /// clip: true,
+ /// image("tiger.jpg", width: 100pt, height: 100pt)
+ /// )
+ /// ```
+ #[default(false)]
+ pub clip: bool,
+
+ /// Whether this block must stick to the following one, with no break in
+ /// between.
+ ///
+ /// This is, by default, set on heading blocks to prevent orphaned headings
+ /// at the bottom of the page.
+ ///
+ /// ```example
+ /// >>> #set page(height: 140pt)
+ /// // Disable stickiness of headings.
+ /// #show heading: set block(sticky: false)
+ /// #lorem(20)
+ ///
+ /// = Chapter
+ /// #lorem(10)
+ /// ```
+ #[default(false)]
+ pub sticky: bool,
+
+ /// The contents of the block.
+ #[positional]
+ #[borrowed]
+ pub body: Option<BlockBody>,
+}
+
+impl BlockElem {
+ /// Create a block with a custom single-region layouter.
+ ///
+ /// Such a block must have `breakable: false` (which is set by this
+ /// constructor).
+ pub fn single_layouter<T: NativeElement>(
+ captured: Packed<T>,
+ f: fn(
+ content: &Packed<T>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>,
+ ) -> Self {
+ Self::new()
+ .with_breakable(false)
+ .with_body(Some(BlockBody::SingleLayouter(
+ callbacks::BlockSingleCallback::new(captured, f),
+ )))
+ }
+
+ /// Create a block with a custom multi-region layouter.
+ pub fn multi_layouter<T: NativeElement>(
+ captured: Packed<T>,
+ f: fn(
+ content: &Packed<T>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment>,
+ ) -> Self {
+ Self::new().with_body(Some(BlockBody::MultiLayouter(
+ callbacks::BlockMultiCallback::new(captured, f),
+ )))
+ }
+}
+
+/// The contents of a block.
+#[derive(Debug, Clone, PartialEq, Hash)]
+pub enum BlockBody {
+ /// The block contains normal content.
+ Content(Content),
+ /// The block contains a layout callback that needs access to just one
+ /// base region.
+ SingleLayouter(callbacks::BlockSingleCallback),
+ /// The block contains a layout callback that needs access to the exact
+ /// regions.
+ MultiLayouter(callbacks::BlockMultiCallback),
+}
+
+impl Default for BlockBody {
+ fn default() -> Self {
+ Self::Content(Content::default())
+ }
+}
+
+cast! {
+ BlockBody,
+ self => match self {
+ Self::Content(content) => content.into_value(),
+ _ => Value::Auto,
+ },
+ v: Content => Self::Content(v),
+}
+
+/// Defines how to size something along an axis.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum Sizing {
+ /// A track that fits its item's contents.
+ Auto,
+ /// A size specified in absolute terms and relative to the parent's size.
+ Rel(Rel),
+ /// A size specified as a fraction of the remaining free space in the
+ /// parent.
+ Fr(Fr),
+}
+
+impl Sizing {
+ /// Whether this is an automatic sizing.
+ pub fn is_auto(self) -> bool {
+ matches!(self, Self::Auto)
+ }
+
+ /// Whether this is fractional sizing.
+ pub fn is_fractional(self) -> bool {
+ matches!(self, Self::Fr(_))
+ }
+}
+
+impl Default for Sizing {
+ fn default() -> Self {
+ Self::Auto
+ }
+}
+
+impl From<Smart<Rel>> for Sizing {
+ fn from(smart: Smart<Rel>) -> Self {
+ match smart {
+ Smart::Auto => Self::Auto,
+ Smart::Custom(rel) => Self::Rel(rel),
+ }
+ }
+}
+
+impl<T: Into<Spacing>> From<T> for Sizing {
+ fn from(spacing: T) -> Self {
+ match spacing.into() {
+ Spacing::Rel(rel) => Self::Rel(rel),
+ Spacing::Fr(fr) => Self::Fr(fr),
+ }
+ }
+}
+
+cast! {
+ Sizing,
+ self => match self {
+ Self::Auto => Value::Auto,
+ Self::Rel(rel) => rel.into_value(),
+ Self::Fr(fr) => fr.into_value(),
+ },
+ _: AutoValue => Self::Auto,
+ v: Rel<Length> => Self::Rel(v),
+ v: Fr => Self::Fr(v),
+}
+
+/// Manual closure implementations for layout callbacks.
+///
+/// Normal closures are not `Hash`, so we can't use them.
+mod callbacks {
+ use super::*;
+
+ macro_rules! callback {
+ ($name:ident = ($($param:ident: $param_ty:ty),* $(,)?) -> $ret:ty) => {
+ #[derive(Debug, Clone, PartialEq, Hash)]
+ pub struct $name {
+ captured: Content,
+ f: fn(&Content, $($param_ty),*) -> $ret,
+ }
+
+ impl $name {
+ pub fn new<T: NativeElement>(
+ captured: Packed<T>,
+ f: fn(&Packed<T>, $($param_ty),*) -> $ret,
+ ) -> Self {
+ Self {
+ // Type-erased the content.
+ captured: captured.pack(),
+ // Safety: The only difference between the two function
+ // pointer types is the type of the first parameter,
+ // which changes from `&Packed<T>` to `&Content`. This
+ // is safe because:
+ // - `Packed<T>` is a transparent wrapper around
+ // `Content`, so for any `T` it has the same memory
+ // representation as `Content`.
+ // - While `Packed<T>` imposes the additional constraint
+ // that the content is of type `T`, this constraint is
+ // upheld: It is initially the case because we store a
+ // `Packed<T>` above. It keeps being the case over the
+ // lifetime of the closure because `capture` is a
+ // private field and `Content`'s `Clone` impl is
+ // guaranteed to retain the type (if it didn't,
+ // literally everything would break).
+ #[allow(clippy::missing_transmute_annotations)]
+ f: unsafe { std::mem::transmute(f) },
+ }
+ }
+
+ pub fn call(&self, $($param: $param_ty),*) -> $ret {
+ (self.f)(&self.captured, $($param),*)
+ }
+ }
+ };
+ }
+
+ callback! {
+ InlineCallback = (
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Size,
+ ) -> SourceResult<Vec<InlineItem>>
+ }
+
+ callback! {
+ BlockSingleCallback = (
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>
+ }
+
+ callback! {
+ BlockMultiCallback = (
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment>
+ }
+}
diff --git a/crates/typst/src/layout/corners.rs b/crates/typst-library/src/layout/corners.rs
index 075f51bc..fe69126a 100644
--- a/crates/typst/src/layout/corners.rs
+++ b/crates/typst-library/src/layout/corners.rs
@@ -1,12 +1,13 @@
use std::fmt::{self, Debug, Formatter};
+use typst_utils::Get;
+
use crate::diag::HintedStrResult;
use crate::foundations::{
AlternativeFold, CastInfo, Dict, Fold, FromValue, IntoValue, Reflect, Resolve,
StyleChain, Value,
};
use crate::layout::Side;
-use crate::utils::Get;
/// A container with components for the four corners of a rectangle.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
diff --git a/crates/typst/src/layout/dir.rs b/crates/typst-library/src/layout/dir.rs
index 9a2e7710..9a2e7710 100644
--- a/crates/typst/src/layout/dir.rs
+++ b/crates/typst-library/src/layout/dir.rs
diff --git a/crates/typst/src/layout/em.rs b/crates/typst-library/src/layout/em.rs
index 7aa5dfcf..e2d8b120 100644
--- a/crates/typst/src/layout/em.rs
+++ b/crates/typst-library/src/layout/em.rs
@@ -3,11 +3,11 @@ use std::iter::Sum;
use std::ops::{Add, Div, Mul, Neg};
use ecow::EcoString;
+use typst_utils::{Numeric, Scalar};
use crate::foundations::{cast, repr, Repr, Resolve, StyleChain, Value};
use crate::layout::Abs;
use crate::text::TextElem;
-use crate::utils::{Numeric, Scalar};
/// A length that is relative to the font size.
///
diff --git a/crates/typst/src/layout/fr.rs b/crates/typst-library/src/layout/fr.rs
index a61daf1e..ff8a6e6b 100644
--- a/crates/typst/src/layout/fr.rs
+++ b/crates/typst-library/src/layout/fr.rs
@@ -3,10 +3,10 @@ use std::iter::Sum;
use std::ops::{Add, Div, Mul, Neg};
use ecow::EcoString;
+use typst_utils::{Numeric, Scalar};
use crate::foundations::{repr, ty, Repr};
use crate::layout::Abs;
-use crate::utils::{Numeric, Scalar};
/// Defines how the remaining space in a layout is distributed.
///
diff --git a/crates/typst/src/layout/fragment.rs b/crates/typst-library/src/layout/fragment.rs
index c2666115..c2666115 100644
--- a/crates/typst/src/layout/fragment.rs
+++ b/crates/typst-library/src/layout/fragment.rs
diff --git a/crates/typst/src/layout/frame.rs b/crates/typst-library/src/layout/frame.rs
index cf4153d6..204584fc 100644
--- a/crates/typst/src/layout/frame.rs
+++ b/crates/typst-library/src/layout/frame.rs
@@ -5,20 +5,17 @@ use std::num::NonZeroUsize;
use std::sync::Arc;
use smallvec::SmallVec;
+use typst_syntax::Span;
+use typst_utils::{LazyHash, Numeric};
use crate::foundations::{cast, dict, Dict, Label, StyleChain, Value};
use crate::introspection::{Location, Tag};
use crate::layout::{
- Abs, Axes, Corners, FixedAlignment, HideElem, Length, Point, Rel, Sides, Size,
- Transform,
+ Abs, Axes, FixedAlignment, HideElem, Length, Point, Size, Transform,
};
use crate::model::{Destination, LinkElem};
-use crate::syntax::Span;
use crate::text::TextItem;
-use crate::utils::{LazyHash, Numeric};
-use crate::visualize::{
- ellipse, styled_rect, Color, FixedStroke, Geometry, Image, Paint, Path, Shape,
-};
+use crate::visualize::{Color, FixedStroke, Geometry, Image, Paint, Path, Shape};
/// A finished layout with items at fixed positions.
#[derive(Default, Clone, Hash)]
@@ -363,32 +360,13 @@ impl Frame {
}
/// Add a background fill.
- pub fn fill(&mut self, fill: Paint) {
+ pub fn fill(&mut self, fill: impl Into<Paint>) {
self.prepend(
Point::zero(),
FrameItem::Shape(Geometry::Rect(self.size()).filled(fill), Span::detached()),
);
}
- /// Add a fill and stroke with optional radius and outset to the frame.
- pub fn fill_and_stroke(
- &mut self,
- fill: Option<Paint>,
- stroke: &Sides<Option<FixedStroke>>,
- outset: &Sides<Rel<Abs>>,
- radius: &Corners<Rel<Abs>>,
- span: Span,
- ) {
- let outset = outset.relative_to(self.size());
- let size = self.size() + outset.sum_by_axis();
- let pos = Point::new(-outset.left, -outset.top);
- self.prepend_multiple(
- styled_rect(size, radius, fill, stroke)
- .into_iter()
- .map(|x| (pos, FrameItem::Shape(x, span))),
- );
- }
-
/// Arbitrarily transform the contents of the frame.
pub fn transform(&mut self, transform: Transform) {
if !self.is_empty() {
@@ -448,7 +426,7 @@ impl Frame {
0,
Point::zero(),
FrameItem::Shape(
- Geometry::Rect(self.size).filled(Color::TEAL.with_alpha(0.5).into()),
+ Geometry::Rect(self.size).filled(Color::TEAL.with_alpha(0.5)),
Span::detached(),
),
);
@@ -469,7 +447,8 @@ impl Frame {
self.push(
pos - Point::splat(radius),
FrameItem::Shape(
- ellipse(Size::splat(2.0 * radius), Some(Color::GREEN.into()), None),
+ Geometry::Path(Path::ellipse(Size::splat(2.0 * radius)))
+ .filled(Color::GREEN),
Span::detached(),
),
);
diff --git a/crates/typst/src/layout/grid/mod.rs b/crates/typst-library/src/layout/grid.rs
index ec0504f3..2e1e9abc 100644
--- a/crates/typst/src/layout/grid/mod.rs
+++ b/crates/typst-library/src/layout/grid.rs
@@ -1,36 +1,20 @@
-mod cells;
-mod layout;
-mod lines;
-mod repeated;
-mod rowspans;
-
-pub use self::cells::{
- Cell, CellGrid, Celled, ResolvableCell, ResolvableGridChild, ResolvableGridItem,
-};
-pub use self::layout::GridLayouter;
-pub use self::lines::LinePosition;
-
use std::num::NonZeroUsize;
use std::sync::Arc;
-use ecow::eco_format;
+use comemo::Track;
use smallvec::{smallvec, SmallVec};
+use typst_utils::NonZeroExt;
-use crate::diag::{bail, HintedStrResult, HintedString, SourceResult, Trace, Tracepoint};
+use crate::diag::{bail, At, HintedStrResult, HintedString, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, scope, Array, Content, Fold, NativeElement, Packed, Show, Smart,
- StyleChain, Value,
+ cast, elem, scope, Array, CastInfo, Content, Context, Fold, FromValue, Func,
+ IntoValue, NativeElement, Packed, Reflect, Resolve, Show, Smart, StyleChain, Value,
};
-use crate::introspection::Locator;
use crate::layout::{
- Abs, Alignment, Axes, BlockElem, Dir, Fragment, Length, OuterHAlignment,
- OuterVAlignment, Regions, Rel, Sides, Sizing,
+ Alignment, BlockElem, Length, OuterHAlignment, OuterVAlignment, Rel, Sides, Sizing,
};
use crate::model::{TableCell, TableFooter, TableHLine, TableHeader, TableVLine};
-use crate::syntax::Span;
-use crate::text::TextElem;
-use crate::utils::NonZeroExt;
use crate::visualize::{Paint, Stroke};
/// Arranges content in a grid.
@@ -241,67 +225,67 @@ pub struct GridElem {
/// #set page(height: 13em, width: 26em)
///
/// #let cv(..jobs) = grid(
- /// columns: 2,
- /// inset: 5pt,
- /// stroke: (x, y) => if x == 0 and y > 0 {
- /// (right: (
- /// paint: luma(180),
- /// thickness: 1.5pt,
- /// dash: "dotted"
- /// ))
- /// },
- /// grid.header(grid.cell(colspan: 2)[
- /// *Professional Experience*
- /// #box(width: 1fr, line(length: 100%, stroke: luma(180)))
- /// ]),
- /// ..{
- /// let last = none
- /// for job in jobs.pos() {
- /// (
- /// if job.year != last [*#job.year*],
- /// [
- /// *#job.company* - #job.role _(#job.timeframe)_ \
- /// #job.details
- /// ]
- /// )
- /// last = job.year
- /// }
+ /// columns: 2,
+ /// inset: 5pt,
+ /// stroke: (x, y) => if x == 0 and y > 0 {
+ /// (right: (
+ /// paint: luma(180),
+ /// thickness: 1.5pt,
+ /// dash: "dotted"
+ /// ))
+ /// },
+ /// grid.header(grid.cell(colspan: 2)[
+ /// *Professional Experience*
+ /// #box(width: 1fr, line(length: 100%, stroke: luma(180)))
+ /// ]),
+ /// ..{
+ /// let last = none
+ /// for job in jobs.pos() {
+ /// (
+ /// if job.year != last [*#job.year*],
+ /// [
+ /// *#job.company* - #job.role _(#job.timeframe)_ \
+ /// #job.details
+ /// ]
+ /// )
+ /// last = job.year
/// }
- /// )
- ///
- /// #cv(
- /// (
- /// year: 2012,
- /// company: [Pear Seed & Co.],
- /// role: [Lead Engineer],
- /// timeframe: [Jul - Dec],
- /// details: [
- /// - Raised engineers from 3x to 10x
- /// - Did a great job
- /// ],
- /// ),
- /// (
- /// year: 2012,
- /// company: [Mega Corp.],
- /// role: [VP of Sales],
- /// timeframe: [Mar - Jun],
- /// details: [- Closed tons of customers],
- /// ),
- /// (
- /// year: 2013,
- /// company: [Tiny Co.],
- /// role: [CEO],
- /// timeframe: [Jan - Dec],
- /// details: [- Delivered 4x more shareholder value],
- /// ),
- /// (
- /// year: 2014,
- /// company: [Glorbocorp Ltd],
- /// role: [CTO],
- /// timeframe: [Jan - Mar],
- /// details: [- Drove containerization forward],
- /// ),
- /// )
+ /// }
+ /// )
+ ///
+ /// #cv(
+ /// (
+ /// year: 2012,
+ /// company: [Pear Seed & Co.],
+ /// role: [Lead Engineer],
+ /// timeframe: [Jul - Dec],
+ /// details: [
+ /// - Raised engineers from 3x to 10x
+ /// - Did a great job
+ /// ],
+ /// ),
+ /// (
+ /// year: 2012,
+ /// company: [Mega Corp.],
+ /// role: [VP of Sales],
+ /// timeframe: [Mar - Jun],
+ /// details: [- Closed tons of customers],
+ /// ),
+ /// (
+ /// year: 2013,
+ /// company: [Tiny Co.],
+ /// role: [CEO],
+ /// timeframe: [Jan - Dec],
+ /// details: [- Delivered 4x more shareholder value],
+ /// ),
+ /// (
+ /// year: 2014,
+ /// company: [Glorbocorp Ltd],
+ /// role: [CTO],
+ /// timeframe: [Jan - Mar],
+ /// details: [- Drove containerization forward],
+ /// ),
+ /// )
/// ```
#[resolve]
#[fold]
@@ -342,70 +326,13 @@ impl GridElem {
}
impl Show for Packed<GridElem> {
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::multi_layouter(self.clone(), layout_grid)
+ fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::multi_layouter(self.clone(), engine.routines.layout_grid)
.pack()
.spanned(self.span()))
}
}
-/// Layout the grid.
-#[typst_macros::time(span = elem.span())]
-fn layout_grid(
- elem: &Packed<GridElem>,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- regions: Regions,
-) -> SourceResult<Fragment> {
- let inset = elem.inset(styles);
- let align = elem.align(styles);
- let columns = elem.columns(styles);
- let rows = elem.rows(styles);
- let column_gutter = elem.column_gutter(styles);
- let row_gutter = elem.row_gutter(styles);
- let fill = elem.fill(styles);
- let stroke = elem.stroke(styles);
-
- let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice());
- let gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice());
- // Use trace to link back to the grid when a specific cell errors
- let tracepoint = || Tracepoint::Call(Some(eco_format!("grid")));
- let resolve_item = |item: &GridItem| item.to_resolvable(styles);
- let children = elem.children().iter().map(|child| match child {
- GridChild::Header(header) => ResolvableGridChild::Header {
- repeat: header.repeat(styles),
- span: header.span(),
- items: header.children().iter().map(resolve_item),
- },
- GridChild::Footer(footer) => ResolvableGridChild::Footer {
- repeat: footer.repeat(styles),
- span: footer.span(),
- items: footer.children().iter().map(resolve_item),
- },
- GridChild::Item(item) => ResolvableGridChild::Item(item.to_resolvable(styles)),
- });
- let grid = CellGrid::resolve(
- tracks,
- gutter,
- locator,
- children,
- fill,
- align,
- &inset,
- &stroke,
- engine,
- styles,
- elem.span(),
- )
- .trace(engine.world, tracepoint, elem.span())?;
-
- let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
-
- // Measure the columns and layout the grid row-by-row.
- layouter.layout(engine)
-}
-
/// Track sizing definitions.
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct TrackSizings(pub SmallVec<[Sizing; 4]>);
@@ -470,44 +397,6 @@ pub enum GridItem {
Cell(Packed<GridCell>),
}
-impl GridItem {
- fn to_resolvable(&self, styles: StyleChain) -> ResolvableGridItem<Packed<GridCell>> {
- match self {
- Self::HLine(hline) => ResolvableGridItem::HLine {
- y: hline.y(styles),
- start: hline.start(styles),
- end: hline.end(styles),
- stroke: hline.stroke(styles),
- span: hline.span(),
- position: match hline.position(styles) {
- OuterVAlignment::Top => LinePosition::Before,
- OuterVAlignment::Bottom => LinePosition::After,
- },
- },
- Self::VLine(vline) => ResolvableGridItem::VLine {
- x: vline.x(styles),
- start: vline.start(styles),
- end: vline.end(styles),
- stroke: vline.stroke(styles),
- span: vline.span(),
- position: match vline.position(styles) {
- OuterHAlignment::Left if TextElem::dir_in(styles) == Dir::RTL => {
- LinePosition::After
- }
- OuterHAlignment::Right if TextElem::dir_in(styles) == Dir::RTL => {
- LinePosition::Before
- }
- OuterHAlignment::Start | OuterHAlignment::Left => {
- LinePosition::Before
- }
- OuterHAlignment::End | OuterHAlignment::Right => LinePosition::After,
- },
- },
- Self::Cell(cell) => ResolvableGridItem::Cell(cell.clone()),
- }
- }
-}
-
cast! {
GridItem,
self => match self {
@@ -856,113 +745,18 @@ cast! {
v: Content => v.into(),
}
-impl Default for Packed<GridCell> {
- fn default() -> Self {
- Packed::new(GridCell::new(Content::default()))
- }
-}
-
-impl ResolvableCell for Packed<GridCell> {
- fn resolve_cell<'a>(
- mut self,
- x: usize,
- y: usize,
- fill: &Option<Paint>,
- align: Smart<Alignment>,
- inset: Sides<Option<Rel<Length>>>,
- stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
- breakable: bool,
- locator: Locator<'a>,
- styles: StyleChain,
- ) -> Cell<'a> {
- let cell = &mut *self;
- let colspan = cell.colspan(styles);
- let rowspan = cell.rowspan(styles);
- let breakable = cell.breakable(styles).unwrap_or(breakable);
- let fill = cell.fill(styles).unwrap_or_else(|| fill.clone());
-
- let cell_stroke = cell.stroke(styles);
- let stroke_overridden =
- cell_stroke.as_ref().map(|side| matches!(side, Some(Some(_))));
-
- // Using a typical 'Sides' fold, an unspecified side loses to a
- // specified side. Additionally, when both are specified, an inner
- // None wins over the outer Some, and vice-versa. When both are
- // specified and Some, fold occurs, which, remarkably, leads to an Arc
- // clone.
- //
- // In the end, we flatten because, for layout purposes, an unspecified
- // cell stroke is the same as specifying 'none', so we equate the two
- // concepts.
- let stroke = cell_stroke.fold(stroke).map(Option::flatten);
- cell.push_x(Smart::Custom(x));
- cell.push_y(Smart::Custom(y));
- cell.push_fill(Smart::Custom(fill.clone()));
- cell.push_align(match align {
- Smart::Custom(align) => {
- Smart::Custom(cell.align(styles).map_or(align, |inner| inner.fold(align)))
- }
- // Don't fold if the grid is using outer alignment. Use the
- // cell's alignment instead (which, in the end, will fold with
- // the outer alignment when it is effectively displayed).
- Smart::Auto => cell.align(styles),
- });
- cell.push_inset(Smart::Custom(
- cell.inset(styles).map_or(inset, |inner| inner.fold(inset)),
- ));
- cell.push_stroke(
- // Here we convert the resolved stroke to a regular stroke, however
- // with resolved units (that is, 'em' converted to absolute units).
- // We also convert any stroke unspecified by both the cell and the
- // outer stroke ('None' in the folded stroke) to 'none', that is,
- // all sides are present in the resulting Sides object accessible
- // by show rules on grid cells.
- stroke.as_ref().map(|side| {
- Some(side.as_ref().map(|cell_stroke| {
- Arc::new((**cell_stroke).clone().map(Length::from))
- }))
- }),
- );
- cell.push_breakable(Smart::Custom(breakable));
- Cell {
- body: self.pack(),
- locator,
- fill,
- colspan,
- rowspan,
- stroke,
- stroke_overridden,
- breakable,
- }
- }
-
- fn x(&self, styles: StyleChain) -> Smart<usize> {
- (**self).x(styles)
- }
-
- fn y(&self, styles: StyleChain) -> Smart<usize> {
- (**self).y(styles)
- }
-
- fn colspan(&self, styles: StyleChain) -> NonZeroUsize {
- (**self).colspan(styles)
- }
-
- fn rowspan(&self, styles: StyleChain) -> NonZeroUsize {
- (**self).rowspan(styles)
- }
-
- fn span(&self) -> Span {
- Packed::span(self)
- }
-}
-
impl Show for Packed<GridCell> {
fn show(&self, _engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
show_grid_cell(self.body().clone(), self.inset(styles), self.align(styles))
}
}
+impl Default for Packed<GridCell> {
+ fn default() -> Self {
+ Packed::new(GridCell::new(Content::default()))
+ }
+}
+
impl From<Content> for GridCell {
fn from(value: Content) -> Self {
#[allow(clippy::unwrap_or_default)]
@@ -991,3 +785,140 @@ pub(crate) fn show_grid_cell(
Ok(body)
}
+
+/// A value that can be configured per cell.
+#[derive(Debug, Clone, PartialEq, Hash)]
+pub enum Celled<T> {
+ /// A bare value, the same for all cells.
+ Value(T),
+ /// A closure mapping from cell coordinates to a value.
+ Func(Func),
+ /// An array of alignment values corresponding to each column.
+ Array(Vec<T>),
+}
+
+impl<T: Default + Clone + FromValue> Celled<T> {
+ /// Resolve the value based on the cell position.
+ pub fn resolve(
+ &self,
+ engine: &mut Engine,
+ styles: StyleChain,
+ x: usize,
+ y: usize,
+ ) -> SourceResult<T> {
+ Ok(match self {
+ Self::Value(value) => value.clone(),
+ Self::Func(func) => func
+ .call(engine, Context::new(None, Some(styles)).track(), [x, y])?
+ .cast()
+ .at(func.span())?,
+ Self::Array(array) => x
+ .checked_rem(array.len())
+ .and_then(|i| array.get(i))
+ .cloned()
+ .unwrap_or_default(),
+ })
+ }
+}
+
+impl<T: Default> Default for Celled<T> {
+ fn default() -> Self {
+ Self::Value(T::default())
+ }
+}
+
+impl<T: Reflect> Reflect for Celled<T> {
+ fn input() -> CastInfo {
+ T::input() + Array::input() + Func::input()
+ }
+
+ fn output() -> CastInfo {
+ T::output() + Array::output() + Func::output()
+ }
+
+ fn castable(value: &Value) -> bool {
+ Array::castable(value) || Func::castable(value) || T::castable(value)
+ }
+}
+
+impl<T: IntoValue> IntoValue for Celled<T> {
+ fn into_value(self) -> Value {
+ match self {
+ Self::Value(value) => value.into_value(),
+ Self::Func(func) => func.into_value(),
+ Self::Array(arr) => arr.into_value(),
+ }
+ }
+}
+
+impl<T: FromValue> FromValue for Celled<T> {
+ fn from_value(value: Value) -> HintedStrResult<Self> {
+ match value {
+ Value::Func(v) => Ok(Self::Func(v)),
+ Value::Array(array) => Ok(Self::Array(
+ array.into_iter().map(T::from_value).collect::<HintedStrResult<_>>()?,
+ )),
+ v if T::castable(&v) => Ok(Self::Value(T::from_value(v)?)),
+ v => Err(Self::error(&v)),
+ }
+ }
+}
+
+impl<T: Fold> Fold for Celled<T> {
+ fn fold(self, outer: Self) -> Self {
+ match (self, outer) {
+ (Self::Value(inner), Self::Value(outer)) => Self::Value(inner.fold(outer)),
+ (self_, _) => self_,
+ }
+ }
+}
+
+impl<T: Resolve> Resolve for Celled<T> {
+ type Output = ResolvedCelled<T>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ match self {
+ Self::Value(value) => ResolvedCelled(Celled::Value(value.resolve(styles))),
+ Self::Func(func) => ResolvedCelled(Celled::Func(func)),
+ Self::Array(values) => ResolvedCelled(Celled::Array(
+ values.into_iter().map(|value| value.resolve(styles)).collect(),
+ )),
+ }
+ }
+}
+
+/// The result of resolving a Celled's value according to styles.
+/// Holds resolved values which depend on each grid cell's position.
+/// When it is a closure, however, it is only resolved when the closure is
+/// called.
+#[derive(Default, Clone)]
+pub struct ResolvedCelled<T: Resolve>(Celled<T::Output>);
+
+impl<T> ResolvedCelled<T>
+where
+ T: FromValue + Resolve,
+ <T as Resolve>::Output: Default + Clone,
+{
+ /// Resolve the value based on the cell position.
+ pub fn resolve(
+ &self,
+ engine: &mut Engine,
+ styles: StyleChain,
+ x: usize,
+ y: usize,
+ ) -> SourceResult<T::Output> {
+ Ok(match &self.0 {
+ Celled::Value(value) => value.clone(),
+ Celled::Func(func) => func
+ .call(engine, Context::new(None, Some(styles)).track(), [x, y])?
+ .cast::<T>()
+ .at(func.span())?
+ .resolve(styles),
+ Celled::Array(array) => x
+ .checked_rem(array.len())
+ .and_then(|i| array.get(i))
+ .cloned()
+ .unwrap_or_default(),
+ })
+ }
+}
diff --git a/crates/typst/src/layout/hide.rs b/crates/typst-library/src/layout/hide.rs
index 1b8b9bd5..1b8b9bd5 100644
--- a/crates/typst/src/layout/hide.rs
+++ b/crates/typst-library/src/layout/hide.rs
diff --git a/crates/typst/src/layout/layout.rs b/crates/typst-library/src/layout/layout.rs
index f69acca9..c3d112e1 100644
--- a/crates/typst/src/layout/layout.rs
+++ b/crates/typst-library/src/layout/layout.rs
@@ -1,4 +1,5 @@
use comemo::Track;
+use typst_syntax::Span;
use crate::diag::SourceResult;
use crate::engine::Engine;
@@ -6,8 +7,7 @@ use crate::foundations::{
dict, elem, func, Content, Context, Func, NativeElement, Packed, Show, StyleChain,
};
use crate::introspection::Locatable;
-use crate::layout::{layout_fragment, BlockElem, Size};
-use crate::syntax::Span;
+use crate::layout::{BlockElem, Size};
/// Provides access to the current outer container's (or page's, if none)
/// dimensions (width and height).
@@ -96,7 +96,9 @@ impl Show for Packed<LayoutElem> {
[dict! { "width" => x, "height" => y }],
)?
.display();
- layout_fragment(engine, &result, locator, styles, regions)
+ (engine.routines.layout_fragment)(
+ engine, &result, locator, styles, regions,
+ )
},
)
.pack()
diff --git a/crates/typst/src/layout/length.rs b/crates/typst-library/src/layout/length.rs
index 5e352f13..800140c4 100644
--- a/crates/typst/src/layout/length.rs
+++ b/crates/typst-library/src/layout/length.rs
@@ -4,12 +4,12 @@ use std::ops::{Add, Div, Mul, Neg};
use comemo::Tracked;
use ecow::{eco_format, EcoString};
+use typst_syntax::Span;
+use typst_utils::Numeric;
use crate::diag::{bail, HintedStrResult, SourceResult};
use crate::foundations::{func, scope, ty, Context, Fold, Repr, Resolve, StyleChain};
use crate::layout::{Abs, Em};
-use crate::syntax::Span;
-use crate::utils::Numeric;
/// A size or distance, possibly expressed with contextual units.
///
diff --git a/crates/typst/src/layout/measure.rs b/crates/typst-library/src/layout/measure.rs
index ad68b948..2fa51b2d 100644
--- a/crates/typst/src/layout/measure.rs
+++ b/crates/typst-library/src/layout/measure.rs
@@ -1,4 +1,5 @@
use comemo::Tracked;
+use typst_syntax::Span;
use crate::diag::{warning, At, SourceResult};
use crate::engine::Engine;
@@ -6,8 +7,7 @@ use crate::foundations::{
dict, func, Content, Context, Dict, Resolve, Smart, StyleChain, Styles,
};
use crate::introspection::{Locator, LocatorLink};
-use crate::layout::{layout_frame, Abs, Axes, Length, Region, Size};
-use crate::syntax::Span;
+use crate::layout::{Abs, Axes, Length, Region, Size};
/// Measures the layouted size of content.
///
@@ -109,7 +109,7 @@ pub fn measure(
let link = LocatorLink::measure(here);
let locator = Locator::link(&link);
- let frame = layout_frame(engine, &content, locator, styles, pod)?;
+ let frame = (engine.routines.layout_frame)(engine, &content, locator, styles, pod)?;
let Size { x, y } = frame.size();
Ok(dict! { "width" => x, "height" => y })
}
diff --git a/crates/typst/src/layout/mod.rs b/crates/typst-library/src/layout/mod.rs
index f43a7138..b54d6906 100644
--- a/crates/typst/src/layout/mod.rs
+++ b/crates/typst-library/src/layout/mod.rs
@@ -9,13 +9,11 @@ mod container;
mod corners;
mod dir;
mod em;
-mod flow;
mod fr;
mod fragment;
mod frame;
mod grid;
mod hide;
-mod inline;
#[path = "layout.rs"]
mod layout_;
mod length;
@@ -23,7 +21,6 @@ mod length;
mod measure_;
mod pad;
mod page;
-mod pages;
mod place;
mod point;
mod ratio;
@@ -45,7 +42,6 @@ pub use self::container::*;
pub use self::corners::*;
pub use self::dir::*;
pub use self::em::*;
-pub use self::flow::*;
pub use self::fr::*;
pub use self::fragment::*;
pub use self::frame::*;
@@ -56,7 +52,6 @@ pub use self::length::*;
pub use self::measure_::*;
pub use self::pad::*;
pub use self::page::*;
-pub use self::pages::*;
pub use self::place::*;
pub use self::point::*;
pub use self::ratio::*;
@@ -69,8 +64,6 @@ pub use self::spacing::*;
pub use self::stack::*;
pub use self::transform::*;
-pub(crate) use self::inline::layout_inline;
-
use crate::foundations::{category, Category, Scope};
/// Arranging elements on the page in different ways.
diff --git a/crates/typst-library/src/layout/pad.rs b/crates/typst-library/src/layout/pad.rs
new file mode 100644
index 00000000..1dc6d131
--- /dev/null
+++ b/crates/typst-library/src/layout/pad.rs
@@ -0,0 +1,65 @@
+use crate::diag::SourceResult;
+use crate::engine::Engine;
+use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain};
+use crate::layout::{BlockElem, Length, Rel};
+
+/// Adds spacing around content.
+///
+/// The spacing can be specified for each side individually, or for all sides at
+/// once by specifying a positional argument.
+///
+/// # Example
+/// ```example
+/// #set align(center)
+///
+/// #pad(x: 16pt, image("typing.jpg"))
+/// _Typing speeds can be
+/// measured in words per minute._
+/// ```
+#[elem(title = "Padding", Show)]
+pub struct PadElem {
+ /// The padding at the left side.
+ #[parse(
+ let all = args.named("rest")?.or(args.find()?);
+ let x = args.named("x")?.or(all);
+ let y = args.named("y")?.or(all);
+ args.named("left")?.or(x)
+ )]
+ pub left: Rel<Length>,
+
+ /// The padding at the top side.
+ #[parse(args.named("top")?.or(y))]
+ pub top: Rel<Length>,
+
+ /// The padding at the right side.
+ #[parse(args.named("right")?.or(x))]
+ pub right: Rel<Length>,
+
+ /// The padding at the bottom side.
+ #[parse(args.named("bottom")?.or(y))]
+ pub bottom: Rel<Length>,
+
+ /// A shorthand to set `left` and `right` to the same value.
+ #[external]
+ pub x: Rel<Length>,
+
+ /// A shorthand to set `top` and `bottom` to the same value.
+ #[external]
+ pub y: Rel<Length>,
+
+ /// A shorthand to set all four sides to the same value.
+ #[external]
+ pub rest: Rel<Length>,
+
+ /// The content to pad at the sides.
+ #[required]
+ pub body: Content,
+}
+
+impl Show for Packed<PadElem> {
+ fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::multi_layouter(self.clone(), engine.routines.layout_pad)
+ .pack()
+ .spanned(self.span()))
+ }
+}
diff --git a/crates/typst/src/layout/page.rs b/crates/typst-library/src/layout/page.rs
index 95b3cd1b..de278cb6 100644
--- a/crates/typst/src/layout/page.rs
+++ b/crates/typst-library/src/layout/page.rs
@@ -4,6 +4,7 @@ use std::ops::RangeInclusive;
use std::str::FromStr;
use comemo::Track;
+use typst_utils::{singleton, NonZeroExt, Scalar};
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
@@ -16,7 +17,6 @@ use crate::layout::{
Sides, SpecificAlignment,
};
use crate::model::Numbering;
-use crate::utils::{singleton, NonZeroExt, Scalar};
use crate::visualize::{Color, Paint};
/// Layouts its child onto one or multiple pages.
diff --git a/crates/typst/src/layout/place.rs b/crates/typst-library/src/layout/place.rs
index bedeb507..bedeb507 100644
--- a/crates/typst/src/layout/place.rs
+++ b/crates/typst-library/src/layout/place.rs
diff --git a/crates/typst/src/layout/point.rs b/crates/typst-library/src/layout/point.rs
index 172b6021..c3d1594e 100644
--- a/crates/typst/src/layout/point.rs
+++ b/crates/typst-library/src/layout/point.rs
@@ -1,8 +1,9 @@
use std::fmt::{self, Debug, Formatter};
use std::ops::{Add, Div, Mul, Neg};
+use typst_utils::{Get, Numeric};
+
use crate::layout::{Abs, Axis, Size, Transform};
-use crate::utils::{Get, Numeric};
/// A point in 2D.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
diff --git a/crates/typst/src/layout/ratio.rs b/crates/typst-library/src/layout/ratio.rs
index 020d689a..1c0dcd29 100644
--- a/crates/typst/src/layout/ratio.rs
+++ b/crates/typst-library/src/layout/ratio.rs
@@ -2,9 +2,9 @@ use std::fmt::{self, Debug, Formatter};
use std::ops::{Add, Div, Mul, Neg};
use ecow::EcoString;
+use typst_utils::{Numeric, Scalar};
use crate::foundations::{repr, ty, Repr};
-use crate::utils::{Numeric, Scalar};
/// A ratio of a whole.
///
diff --git a/crates/typst/src/layout/regions.rs b/crates/typst-library/src/layout/regions.rs
index 385664bb..385664bb 100644
--- a/crates/typst/src/layout/regions.rs
+++ b/crates/typst-library/src/layout/regions.rs
diff --git a/crates/typst/src/layout/rel.rs b/crates/typst-library/src/layout/rel.rs
index cce0ac86..30441a42 100644
--- a/crates/typst/src/layout/rel.rs
+++ b/crates/typst-library/src/layout/rel.rs
@@ -3,10 +3,10 @@ use std::fmt::{self, Debug, Formatter};
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use ecow::{eco_format, EcoString};
+use typst_utils::Numeric;
use crate::foundations::{cast, ty, Fold, Repr, Resolve, StyleChain};
use crate::layout::{Abs, Em, Length, Ratio};
-use crate::utils::Numeric;
/// A length in relation to some known length.
///
diff --git a/crates/typst-library/src/layout/repeat.rs b/crates/typst-library/src/layout/repeat.rs
new file mode 100644
index 00000000..e423410a
--- /dev/null
+++ b/crates/typst-library/src/layout/repeat.rs
@@ -0,0 +1,49 @@
+use crate::diag::SourceResult;
+use crate::engine::Engine;
+use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain};
+use crate::layout::{BlockElem, Length};
+
+/// Repeats content to the available space.
+///
+/// This can be useful when implementing a custom index, reference, or outline.
+///
+/// Space may be inserted between the instances of the body parameter, so be
+/// sure to adjust the [`justify`]($repeat.justify) parameter accordingly.
+///
+/// Errors if there no bounds on the available space, as it would create
+/// infinite content.
+///
+/// # Example
+/// ```example
+/// Sign on the dotted line:
+/// #box(width: 1fr, repeat[.])
+///
+/// #set text(10pt)
+/// #v(8pt, weak: true)
+/// #align(right)[
+/// Berlin, the 22nd of December, 2022
+/// ]
+/// ```
+#[elem(Show)]
+pub struct RepeatElem {
+ /// The content to repeat.
+ #[required]
+ pub body: Content,
+
+ /// The gap between each instance of the body.
+ #[default]
+ pub gap: Length,
+
+ /// Whether to increase the gap between instances to completely fill the
+ /// available space.
+ #[default(true)]
+ pub justify: bool,
+}
+
+impl Show for Packed<RepeatElem> {
+ fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_repeat)
+ .pack()
+ .spanned(self.span()))
+ }
+}
diff --git a/crates/typst/src/layout/sides.rs b/crates/typst-library/src/layout/sides.rs
index 61d6a1fd..e04b63d9 100644
--- a/crates/typst/src/layout/sides.rs
+++ b/crates/typst-library/src/layout/sides.rs
@@ -1,13 +1,14 @@
use std::fmt::{self, Debug, Formatter};
use std::ops::Add;
+use typst_utils::Get;
+
use crate::diag::{bail, HintedStrResult};
use crate::foundations::{
cast, AlternativeFold, CastInfo, Dict, Fold, FromValue, IntoValue, Reflect, Resolve,
StyleChain, Value,
};
use crate::layout::{Abs, Alignment, Axes, Axis, Corner, Rel, Size};
-use crate::utils::Get;
/// A container with left, top, right and bottom components.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
diff --git a/crates/typst/src/layout/size.rs b/crates/typst-library/src/layout/size.rs
index 8b5dc8fc..294eec66 100644
--- a/crates/typst/src/layout/size.rs
+++ b/crates/typst-library/src/layout/size.rs
@@ -1,7 +1,8 @@
use std::ops::{Add, Div, Mul, Neg};
+use typst_utils::Numeric;
+
use crate::layout::{Abs, Axes, Point, Ratio};
-use crate::utils::Numeric;
/// A size in 2D.
pub type Size = Axes<Abs>;
diff --git a/crates/typst/src/layout/spacing.rs b/crates/typst-library/src/layout/spacing.rs
index c7c0823c..b3ca1e81 100644
--- a/crates/typst/src/layout/spacing.rs
+++ b/crates/typst-library/src/layout/spacing.rs
@@ -1,6 +1,7 @@
+use typst_utils::Numeric;
+
use crate::foundations::{cast, elem, Content};
use crate::layout::{Abs, Em, Fr, Length, Ratio, Rel};
-use crate::utils::Numeric;
/// Inserts horizontal spacing into a paragraph.
///
diff --git a/crates/typst-library/src/layout/stack.rs b/crates/typst-library/src/layout/stack.rs
new file mode 100644
index 00000000..5fc78480
--- /dev/null
+++ b/crates/typst-library/src/layout/stack.rs
@@ -0,0 +1,84 @@
+use std::fmt::{self, Debug, Formatter};
+
+use crate::diag::SourceResult;
+use crate::engine::Engine;
+use crate::foundations::{cast, elem, Content, NativeElement, Packed, Show, StyleChain};
+use crate::layout::{BlockElem, Dir, Spacing};
+
+/// Arranges content and spacing horizontally or vertically.
+///
+/// The stack places a list of items along an axis, with optional spacing
+/// between each item.
+///
+/// # Example
+/// ```example
+/// #stack(
+/// dir: ttb,
+/// rect(width: 40pt),
+/// rect(width: 120pt),
+/// rect(width: 90pt),
+/// )
+/// ```
+#[elem(Show)]
+pub struct StackElem {
+ /// The direction along which the items are stacked. Possible values are:
+ ///
+ /// - `{ltr}`: Left to right.
+ /// - `{rtl}`: Right to left.
+ /// - `{ttb}`: Top to bottom.
+ /// - `{btt}`: Bottom to top.
+ ///
+ /// You can use the `start` and `end` methods to obtain the initial and
+ /// final points (respectively) of a direction, as `alignment`. You can also
+ /// use the `axis` method to determine whether a direction is
+ /// `{"horizontal"}` or `{"vertical"}`. The `inv` method returns a
+ /// direction's inverse direction.
+ ///
+ /// For example, `{ttb.start()}` is `top`, `{ttb.end()}` is `bottom`,
+ /// `{ttb.axis()}` is `{"vertical"}` and `{ttb.inv()}` is equal to `btt`.
+ #[default(Dir::TTB)]
+ pub dir: Dir,
+
+ /// Spacing to insert between items where no explicit spacing was provided.
+ pub spacing: Option<Spacing>,
+
+ /// The children to stack along the axis.
+ #[variadic]
+ pub children: Vec<StackChild>,
+}
+
+impl Show for Packed<StackElem> {
+ fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::multi_layouter(self.clone(), engine.routines.layout_stack)
+ .pack()
+ .spanned(self.span()))
+ }
+}
+
+/// A child of a stack element.
+#[derive(Clone, PartialEq, Hash)]
+pub enum StackChild {
+ /// Spacing between other children.
+ Spacing(Spacing),
+ /// Arbitrary block-level content.
+ Block(Content),
+}
+
+impl Debug for StackChild {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ match self {
+ Self::Spacing(kind) => kind.fmt(f),
+ Self::Block(block) => block.fmt(f),
+ }
+ }
+}
+
+cast! {
+ StackChild,
+ self => match self {
+ Self::Spacing(spacing) => spacing.into_value(),
+ Self::Block(content) => content.into_value(),
+ },
+ v: Spacing => Self::Spacing(v),
+ v: Content => Self::Block(v),
+}
diff --git a/crates/typst/src/layout/transform.rs b/crates/typst-library/src/layout/transform.rs
index 871bf5b7..183df609 100644
--- a/crates/typst/src/layout/transform.rs
+++ b/crates/typst-library/src/layout/transform.rs
@@ -1,18 +1,11 @@
-use std::ops::Div;
-
-use once_cell::unsync::Lazy;
-
-use crate::diag::{bail, SourceResult};
+use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, Content, NativeElement, Packed, Resolve, Show, Smart, StyleChain,
+ cast, elem, Content, NativeElement, Packed, Show, Smart, StyleChain,
};
-use crate::introspection::Locator;
use crate::layout::{
- layout_frame, Abs, Alignment, Angle, Axes, BlockElem, FixedAlignment, Frame,
- HAlignment, Length, Point, Ratio, Region, Rel, Size, VAlignment,
+ Abs, Alignment, Angle, BlockElem, HAlignment, Length, Ratio, Rel, VAlignment,
};
-use crate::utils::Numeric;
/// Moves content without affecting layout.
///
@@ -46,29 +39,13 @@ pub struct MoveElem {
}
impl Show for Packed<MoveElem> {
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), layout_move)
+ fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_move)
.pack()
.spanned(self.span()))
}
}
-/// Layout the moved content.
-#[typst_macros::time(span = elem.span())]
-fn layout_move(
- elem: &Packed<MoveElem>,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- region: Region,
-) -> SourceResult<Frame> {
- let mut frame = layout_frame(engine, &elem.body, locator, styles, region)?;
- let delta = Axes::new(elem.dx(styles), elem.dy(styles)).resolve(styles);
- let delta = delta.zip_map(region.size, Rel::relative_to);
- frame.translate(delta.to_point());
- Ok(frame)
-}
-
/// Rotates content without affecting layout.
///
/// Rotates an element by a given angle. The layout will act as if the element
@@ -131,45 +108,13 @@ pub struct RotateElem {
}
impl Show for Packed<RotateElem> {
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), layout_rotate)
+ fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_rotate)
.pack()
.spanned(self.span()))
}
}
-/// Layout the rotated content.
-#[typst_macros::time(span = elem.span())]
-fn layout_rotate(
- elem: &Packed<RotateElem>,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- region: Region,
-) -> SourceResult<Frame> {
- let angle = elem.angle(styles);
- let align = elem.origin(styles).resolve(styles);
-
- // Compute the new region's approximate size.
- let size = if region.size.is_finite() {
- compute_bounding_box(region.size, Transform::rotate(-angle)).1
- } else {
- Size::splat(Abs::inf())
- };
-
- measure_and_layout(
- engine,
- locator,
- region,
- size,
- styles,
- elem.body(),
- Transform::rotate(angle),
- align,
- elem.reflow(styles),
- )
-}
-
/// Scales content without affecting layout.
///
/// Lets you mirror content by specifying a negative scale on a single axis.
@@ -235,104 +180,20 @@ pub struct ScaleElem {
}
impl Show for Packed<ScaleElem> {
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), layout_scale)
+ fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_scale)
.pack()
.spanned(self.span()))
}
}
-/// Layout the scaled content.
-#[typst_macros::time(span = elem.span())]
-fn layout_scale(
- elem: &Packed<ScaleElem>,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- region: Region,
-) -> SourceResult<Frame> {
- // Compute the new region's approximate size.
- let scale = elem.resolve_scale(engine, locator.relayout(), region.size, styles)?;
- let size = region
- .size
- .zip_map(scale, |r, s| if r.is_finite() { Ratio::new(1.0 / s).of(r) } else { r })
- .map(Abs::abs);
-
- measure_and_layout(
- engine,
- locator,
- region,
- size,
- styles,
- elem.body(),
- Transform::scale(scale.x, scale.y),
- elem.origin(styles).resolve(styles),
- elem.reflow(styles),
- )
-}
-
+/// To what size something shall be scaled.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-enum ScaleAmount {
+pub enum ScaleAmount {
Ratio(Ratio),
Length(Length),
}
-impl Packed<ScaleElem> {
- /// Resolves scale parameters, preserving aspect ratio if one of the scales is set to `auto`.
- fn resolve_scale(
- &self,
- engine: &mut Engine,
- locator: Locator,
- container: Size,
- styles: StyleChain,
- ) -> SourceResult<Axes<Ratio>> {
- fn resolve_axis(
- axis: Smart<ScaleAmount>,
- body: impl Fn() -> SourceResult<Abs>,
- styles: StyleChain,
- ) -> SourceResult<Smart<Ratio>> {
- Ok(match axis {
- Smart::Auto => Smart::Auto,
- Smart::Custom(amt) => Smart::Custom(match amt {
- ScaleAmount::Ratio(ratio) => ratio,
- ScaleAmount::Length(length) => {
- let length = length.resolve(styles);
- Ratio::new(length.div(body()?))
- }
- }),
- })
- }
-
- let size = Lazy::new(|| {
- let pod = Region::new(container, Axes::splat(false));
- let frame = layout_frame(engine, &self.body, locator, styles, pod)?;
- SourceResult::Ok(frame.size())
- });
-
- let x = resolve_axis(
- self.x(styles),
- || size.as_ref().map(|size| size.x).map_err(Clone::clone),
- styles,
- )?;
-
- let y = resolve_axis(
- self.y(styles),
- || size.as_ref().map(|size| size.y).map_err(Clone::clone),
- styles,
- )?;
-
- match (x, y) {
- (Smart::Auto, Smart::Auto) => {
- bail!(self.span(), "x and y cannot both be auto")
- }
- (Smart::Custom(x), Smart::Custom(y)) => Ok(Axes::new(x, y)),
- (Smart::Auto, Smart::Custom(v)) | (Smart::Custom(v), Smart::Auto) => {
- Ok(Axes::splat(v))
- }
- }
- }
-}
-
cast! {
ScaleAmount,
self => match self {
@@ -405,46 +266,13 @@ pub struct SkewElem {
}
impl Show for Packed<SkewElem> {
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), layout_skew)
+ fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_skew)
.pack()
.spanned(self.span()))
}
}
-/// Layout the skewed content.
-#[typst_macros::time(span = elem.span())]
-fn layout_skew(
- elem: &Packed<SkewElem>,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- region: Region,
-) -> SourceResult<Frame> {
- let ax = elem.ax(styles);
- let ay = elem.ay(styles);
- let align = elem.origin(styles).resolve(styles);
-
- // Compute the new region's approximate size.
- let size = if region.size.is_finite() {
- compute_bounding_box(region.size, Transform::skew(ax, ay)).1
- } else {
- Size::splat(Abs::inf())
- };
-
- measure_and_layout(
- engine,
- locator,
- region,
- size,
- styles,
- elem.body(),
- Transform::skew(ax, ay),
- align,
- elem.reflow(styles),
- )
-}
-
/// A scale-skew-translate transformation.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Transform {
@@ -578,73 +406,3 @@ impl Default for Transform {
Self::identity()
}
}
-
-/// Applies a transformation to a frame, reflowing the layout if necessary.
-#[allow(clippy::too_many_arguments)]
-fn measure_and_layout(
- engine: &mut Engine,
- locator: Locator,
- region: Region,
- size: Size,
- styles: StyleChain,
- body: &Content,
- transform: Transform,
- align: Axes<FixedAlignment>,
- reflow: bool,
-) -> SourceResult<Frame> {
- if reflow {
- // Measure the size of the body.
- let pod = Region::new(size, Axes::splat(false));
- let frame = layout_frame(engine, body, locator.relayout(), styles, pod)?;
-
- // Actually perform the layout.
- let pod = Region::new(frame.size(), Axes::splat(true));
- let mut frame = layout_frame(engine, body, locator, styles, pod)?;
- let Axes { x, y } = align.zip_map(frame.size(), FixedAlignment::position);
-
- // Compute the transform.
- let ts = Transform::translate(x, y)
- .pre_concat(transform)
- .pre_concat(Transform::translate(-x, -y));
-
- // Compute the bounding box and offset and wrap in a new frame.
- let (offset, size) = compute_bounding_box(frame.size(), ts);
- frame.transform(ts);
- frame.translate(offset);
- frame.set_size(size);
- Ok(frame)
- } else {
- // Layout the body.
- let mut frame = layout_frame(engine, body, locator, styles, region)?;
- let Axes { x, y } = align.zip_map(frame.size(), FixedAlignment::position);
-
- // Compute the transform.
- let ts = Transform::translate(x, y)
- .pre_concat(transform)
- .pre_concat(Transform::translate(-x, -y));
-
- // Apply the transform.
- frame.transform(ts);
- Ok(frame)
- }
-}
-
-/// Computes the bounding box and offset of a transformed area.
-fn compute_bounding_box(size: Size, ts: Transform) -> (Point, Size) {
- let top_left = Point::zero().transform_inf(ts);
- let top_right = Point::with_x(size.x).transform_inf(ts);
- let bottom_left = Point::with_y(size.y).transform_inf(ts);
- let bottom_right = size.to_point().transform_inf(ts);
-
- // We first compute the new bounding box of the rotated area.
- let min_x = top_left.x.min(top_right.x).min(bottom_left.x).min(bottom_right.x);
- let min_y = top_left.y.min(top_right.y).min(bottom_left.y).min(bottom_right.y);
- let max_x = top_left.x.max(top_right.x).max(bottom_left.x).max(bottom_right.x);
- let max_y = top_left.y.max(top_right.y).max(bottom_left.y).max(bottom_right.y);
-
- // Then we compute the new size of the area.
- let width = max_x - min_x;
- let height = max_y - min_y;
-
- (Point::new(-min_x, -min_y), Size::new(width.abs(), height.abs()))
-}
diff --git a/crates/typst-library/src/lib.rs b/crates/typst-library/src/lib.rs
new file mode 100644
index 00000000..34c2e526
--- /dev/null
+++ b/crates/typst-library/src/lib.rs
@@ -0,0 +1,264 @@
+//! Typst's standard library.
+//!
+//! This crate also contains all of the compiler's central type definitions as
+//! these are interwoven with the standard library types.
+//!
+//! In contrast to the _types,_ most of the compilation _behaviour_ is split out
+//! into separate crates (`typst-eval`, `typst-realize`, `typst-layout`, etc.)
+//!
+//! Note that, unless you are working on the compiler itself, you will rarely
+//! need to interact with this crate, as it is fully reexported by the `typst`
+//! crate.
+
+extern crate self as typst_library;
+
+pub mod diag;
+pub mod engine;
+pub mod foundations;
+pub mod introspection;
+pub mod layout;
+pub mod loading;
+pub mod math;
+pub mod model;
+pub mod routines;
+pub mod symbols;
+pub mod text;
+pub mod visualize;
+
+use std::ops::{Deref, Range};
+
+use ecow::EcoString;
+use typst_syntax::package::PackageSpec;
+use typst_syntax::{FileId, Source, Span};
+use typst_utils::LazyHash;
+
+use crate::diag::FileResult;
+use crate::foundations::{Array, Bytes, Datetime, Dict, Module, Scope, Styles, Value};
+use crate::layout::{Alignment, Dir};
+use crate::text::{Font, FontBook};
+use crate::visualize::Color;
+
+/// The environment in which typesetting occurs.
+///
+/// All loading functions (`main`, `source`, `file`, `font`) should perform
+/// internal caching so that they are relatively cheap on repeated invocations
+/// with the same argument. [`Source`], [`Bytes`], and [`Font`] are
+/// all reference-counted and thus cheap to clone.
+///
+/// The compiler doesn't do the caching itself because the world has much more
+/// information on when something can change. For example, fonts typically don't
+/// change and can thus even be cached across multiple compilations (for
+/// long-running applications like `typst watch`). Source files on the other
+/// hand can change and should thus be cleared after each compilation. Advanced
+/// clients like language servers can also retain the source files and
+/// [edit](Source::edit) them in-place to benefit from better incremental
+/// performance.
+#[comemo::track]
+pub trait World: Send + Sync {
+ /// The standard library.
+ ///
+ /// Can be created through `Library::build()`.
+ fn library(&self) -> &LazyHash<Library>;
+
+ /// Metadata about all known fonts.
+ fn book(&self) -> &LazyHash<FontBook>;
+
+ /// Get the file id of the main source file.
+ fn main(&self) -> FileId;
+
+ /// Try to access the specified source file.
+ fn source(&self, id: FileId) -> FileResult<Source>;
+
+ /// Try to access the specified file.
+ fn file(&self, id: FileId) -> FileResult<Bytes>;
+
+ /// Try to access the font with the given index in the font book.
+ fn font(&self, index: usize) -> Option<Font>;
+
+ /// Get the current date.
+ ///
+ /// If no offset is specified, the local date should be chosen. Otherwise,
+ /// the UTC date should be chosen with the corresponding offset in hours.
+ ///
+ /// If this function returns `None`, Typst's `datetime` function will
+ /// return an error.
+ fn today(&self, offset: Option<i64>) -> Option<Datetime>;
+
+ /// A list of all available packages and optionally descriptions for them.
+ ///
+ /// This function is optional to implement. It enhances the user experience
+ /// by enabling autocompletion for packages. Details about packages from the
+ /// `@preview` namespace are available from
+ /// `https://packages.typst.org/preview/index.json`.
+ fn packages(&self) -> &[(PackageSpec, Option<EcoString>)] {
+ &[]
+ }
+}
+
+macro_rules! world_impl {
+ ($W:ident for $ptr:ty) => {
+ impl<$W: World> World for $ptr {
+ fn library(&self) -> &LazyHash<Library> {
+ self.deref().library()
+ }
+
+ fn book(&self) -> &LazyHash<FontBook> {
+ self.deref().book()
+ }
+
+ fn main(&self) -> FileId {
+ self.deref().main()
+ }
+
+ fn source(&self, id: FileId) -> FileResult<Source> {
+ self.deref().source(id)
+ }
+
+ fn file(&self, id: FileId) -> FileResult<Bytes> {
+ self.deref().file(id)
+ }
+
+ fn font(&self, index: usize) -> Option<Font> {
+ self.deref().font(index)
+ }
+
+ fn today(&self, offset: Option<i64>) -> Option<Datetime> {
+ self.deref().today(offset)
+ }
+
+ fn packages(&self) -> &[(PackageSpec, Option<EcoString>)] {
+ self.deref().packages()
+ }
+ }
+ };
+}
+
+world_impl!(W for std::boxed::Box<W>);
+world_impl!(W for std::sync::Arc<W>);
+world_impl!(W for &W);
+
+/// Helper methods on [`World`] implementations.
+pub trait WorldExt {
+ /// Get the byte range for a span.
+ ///
+ /// Returns `None` if the `Span` does not point into any source file.
+ fn range(&self, span: Span) -> Option<Range<usize>>;
+}
+
+impl<T: World> WorldExt for T {
+ fn range(&self, span: Span) -> Option<Range<usize>> {
+ self.source(span.id()?).ok()?.range(span)
+ }
+}
+
+/// Definition of Typst's standard library.
+#[derive(Debug, Clone, Hash)]
+pub struct Library {
+ /// The module that contains the definitions that are available everywhere.
+ pub global: Module,
+ /// The module that contains the definitions available in math mode.
+ pub math: Module,
+ /// The default style properties (for page size, font selection, and
+ /// everything else configurable via set and show rules).
+ pub styles: Styles,
+ /// The standard library as a value.
+ /// Used to provide the `std` variable.
+ pub std: Value,
+}
+
+impl Library {
+ /// Create a new builder for a library.
+ pub fn builder() -> LibraryBuilder {
+ LibraryBuilder::default()
+ }
+}
+
+impl Default for Library {
+ /// Constructs the standard library with the default configuration.
+ fn default() -> Self {
+ Self::builder().build()
+ }
+}
+
+/// Configurable builder for the standard library.
+///
+/// This struct is created by [`Library::builder`].
+#[derive(Debug, Clone, Default)]
+pub struct LibraryBuilder {
+ inputs: Option<Dict>,
+}
+
+impl LibraryBuilder {
+ /// Configure the inputs visible through `sys.inputs`.
+ pub fn with_inputs(mut self, inputs: Dict) -> Self {
+ self.inputs = Some(inputs);
+ self
+ }
+
+ /// Consumes the builder and returns a `Library`.
+ pub fn build(self) -> Library {
+ let math = math::module();
+ let inputs = self.inputs.unwrap_or_default();
+ let global = global(math.clone(), inputs);
+ let std = Value::Module(global.clone());
+ Library { global, math, styles: Styles::new(), std }
+ }
+}
+
+/// Construct the module with global definitions.
+fn global(math: Module, inputs: Dict) -> Module {
+ let mut global = Scope::deduplicating();
+ self::foundations::define(&mut global, inputs);
+ self::model::define(&mut global);
+ self::text::define(&mut global);
+ global.reset_category();
+ global.define_module(math);
+ self::layout::define(&mut global);
+ self::visualize::define(&mut global);
+ self::introspection::define(&mut global);
+ self::loading::define(&mut global);
+ self::symbols::define(&mut global);
+ prelude(&mut global);
+ Module::new("global", global)
+}
+
+/// Defines scoped values that are globally available, too.
+fn prelude(global: &mut Scope) {
+ global.reset_category();
+ global.define("black", Color::BLACK);
+ global.define("gray", Color::GRAY);
+ global.define("silver", Color::SILVER);
+ global.define("white", Color::WHITE);
+ global.define("navy", Color::NAVY);
+ global.define("blue", Color::BLUE);
+ global.define("aqua", Color::AQUA);
+ global.define("teal", Color::TEAL);
+ global.define("eastern", Color::EASTERN);
+ global.define("purple", Color::PURPLE);
+ global.define("fuchsia", Color::FUCHSIA);
+ global.define("maroon", Color::MAROON);
+ global.define("red", Color::RED);
+ global.define("orange", Color::ORANGE);
+ global.define("yellow", Color::YELLOW);
+ global.define("olive", Color::OLIVE);
+ global.define("green", Color::GREEN);
+ global.define("lime", Color::LIME);
+ global.define("luma", Color::luma_data());
+ global.define("oklab", Color::oklab_data());
+ global.define("oklch", Color::oklch_data());
+ global.define("rgb", Color::rgb_data());
+ global.define("cmyk", Color::cmyk_data());
+ global.define("range", Array::range_data());
+ global.define("ltr", Dir::LTR);
+ global.define("rtl", Dir::RTL);
+ global.define("ttb", Dir::TTB);
+ global.define("btt", Dir::BTT);
+ global.define("start", Alignment::START);
+ global.define("left", Alignment::LEFT);
+ global.define("center", Alignment::CENTER);
+ global.define("right", Alignment::RIGHT);
+ global.define("end", Alignment::END);
+ global.define("top", Alignment::TOP);
+ global.define("horizon", Alignment::HORIZON);
+ global.define("bottom", Alignment::BOTTOM);
+}
diff --git a/crates/typst/src/loading/cbor.rs b/crates/typst-library/src/loading/cbor.rs
index e2d0f7c7..977059c3 100644
--- a/crates/typst/src/loading/cbor.rs
+++ b/crates/typst-library/src/loading/cbor.rs
@@ -1,9 +1,9 @@
use ecow::{eco_format, EcoString};
+use typst_syntax::Spanned;
use crate::diag::{At, SourceResult};
use crate::engine::Engine;
use crate::foundations::{func, scope, Bytes, Value};
-use crate::syntax::Spanned;
use crate::World;
/// Reads structured data from a CBOR file.
diff --git a/crates/typst/src/loading/csv.rs b/crates/typst-library/src/loading/csv.rs
index 62a9bf69..6822505d 100644
--- a/crates/typst/src/loading/csv.rs
+++ b/crates/typst-library/src/loading/csv.rs
@@ -1,10 +1,10 @@
use ecow::{eco_format, EcoString};
+use typst_syntax::Spanned;
use crate::diag::{bail, At, SourceResult};
use crate::engine::Engine;
use crate::foundations::{cast, func, scope, Array, Dict, IntoValue, Type, Value};
use crate::loading::Readable;
-use crate::syntax::Spanned;
use crate::World;
/// Reads structured data from a CSV file.
diff --git a/crates/typst/src/loading/json.rs b/crates/typst-library/src/loading/json.rs
index d466d043..597cf4cc 100644
--- a/crates/typst/src/loading/json.rs
+++ b/crates/typst-library/src/loading/json.rs
@@ -1,10 +1,10 @@
use ecow::{eco_format, EcoString};
+use typst_syntax::Spanned;
use crate::diag::{At, SourceResult};
use crate::engine::Engine;
use crate::foundations::{func, scope, Str, Value};
use crate::loading::Readable;
-use crate::syntax::Spanned;
use crate::World;
/// Reads structured data from a JSON file.
diff --git a/crates/typst/src/loading/mod.rs b/crates/typst-library/src/loading/mod.rs
index a02fb9a9..ae74df86 100644
--- a/crates/typst/src/loading/mod.rs
+++ b/crates/typst-library/src/loading/mod.rs
@@ -54,14 +54,14 @@ pub enum Readable {
}
impl Readable {
- fn as_slice(&self) -> &[u8] {
+ pub fn as_slice(&self) -> &[u8] {
match self {
Readable::Bytes(v) => v,
Readable::Str(v) => v.as_bytes(),
}
}
- pub(crate) fn as_str(&self) -> Option<&str> {
+ pub fn as_str(&self) -> Option<&str> {
match self {
Readable::Str(v) => Some(v.as_str()),
Readable::Bytes(v) => std::str::from_utf8(v).ok(),
diff --git a/crates/typst/src/loading/read.rs b/crates/typst-library/src/loading/read.rs
index 3bec9bd7..23e6e27e 100644
--- a/crates/typst/src/loading/read.rs
+++ b/crates/typst-library/src/loading/read.rs
@@ -1,10 +1,10 @@
use ecow::EcoString;
+use typst_syntax::Spanned;
use crate::diag::{At, SourceResult};
use crate::engine::Engine;
use crate::foundations::{func, Cast};
use crate::loading::Readable;
-use crate::syntax::Spanned;
use crate::World;
/// Reads plain text or data from a file.
diff --git a/crates/typst/src/loading/toml.rs b/crates/typst-library/src/loading/toml.rs
index 17ab6b53..5167703e 100644
--- a/crates/typst/src/loading/toml.rs
+++ b/crates/typst-library/src/loading/toml.rs
@@ -1,10 +1,10 @@
use ecow::{eco_format, EcoString};
+use typst_syntax::{is_newline, Spanned};
use crate::diag::{At, SourceResult};
use crate::engine::Engine;
use crate::foundations::{func, scope, Str, Value};
use crate::loading::Readable;
-use crate::syntax::{is_newline, Spanned};
use crate::World;
/// Reads structured data from a TOML file.
diff --git a/crates/typst/src/loading/xml.rs b/crates/typst-library/src/loading/xml.rs
index fefb63c2..3b1a9674 100644
--- a/crates/typst/src/loading/xml.rs
+++ b/crates/typst-library/src/loading/xml.rs
@@ -1,11 +1,11 @@
use ecow::EcoString;
use roxmltree::ParsingOptions;
+use typst_syntax::Spanned;
use crate::diag::{format_xml_like_error, At, FileError, SourceResult};
use crate::engine::Engine;
use crate::foundations::{dict, func, scope, Array, Dict, IntoValue, Str, Value};
use crate::loading::Readable;
-use crate::syntax::Spanned;
use crate::World;
/// Reads structured data from an XML file.
diff --git a/crates/typst/src/loading/yaml.rs b/crates/typst-library/src/loading/yaml.rs
index 22311cf6..0e8ca3fb 100644
--- a/crates/typst/src/loading/yaml.rs
+++ b/crates/typst-library/src/loading/yaml.rs
@@ -1,10 +1,10 @@
use ecow::{eco_format, EcoString};
+use typst_syntax::Spanned;
use crate::diag::{At, SourceResult};
use crate::engine::Engine;
use crate::foundations::{func, scope, Str, Value};
use crate::loading::Readable;
-use crate::syntax::Spanned;
use crate::World;
/// Reads structured data from a YAML file.
diff --git a/crates/typst/src/math/accent.rs b/crates/typst-library/src/math/accent.rs
index 08ed7238..a1c70b4a 100644
--- a/crates/typst/src/math/accent.rs
+++ b/crates/typst-library/src/math/accent.rs
@@ -1,64 +1,9 @@
-use crate::diag::{bail, SourceResult};
-use crate::foundations::{
- cast, elem, func, Content, NativeElement, Packed, Smart, StyleChain, Value,
-};
-use crate::layout::{Em, Frame, Length, Point, Rel, Size};
-use crate::math::{
- scaled_font_size, style_cramped, FrameFragment, GlyphFragment, LayoutMath,
- MathContext, MathFragment, Scaled,
-};
+use crate::diag::bail;
+use crate::foundations::{cast, elem, func, Content, NativeElement, Smart, Value};
+use crate::layout::{Length, Rel};
+use crate::math::Mathy;
use crate::text::TextElem;
-/// How much the accent can be shorter than the base.
-const ACCENT_SHORT_FALL: Em = Em::new(0.5);
-
-/// This macro generates accent-related functions.
-///
-/// ```ignore
-/// accents! {
-/// '\u{0300}' | '`' => grave,
-/// // ^^^^^^^^^ ^^^ ^^^^^
-/// // | | |
-/// // | | +-- The name of the function.
-/// // | +--------- The alternative characters that represent the accent.
-/// // +---------------------- The primary character that represents the accent.
-/// }
-/// ```
-///
-/// When combined with the `Accent::combine` function, accent characters can be normalized
-/// to the primary character.
-macro_rules! accents {
- ($($primary:literal $(| $alt:literal)* => $name:ident),* $(,)?) => {
- impl Accent {
- /// Normalize an accent to a combining one.
- pub fn combine(c: char) -> Option<char> {
- Some(match c {
- $($primary $(| $alt)* => $primary,)*
- _ => return None,
- })
- }
- }
-
- $(
- /// The accent function for callable symbol definitions.
- #[func]
- pub fn $name(
- /// The base to which the accent is applied.
- base: Content,
- /// The size of the accent, relative to the width of the base.
- #[named]
- size: Option<Smart<Rel<Length>>>,
- ) -> Content {
- let mut accent = AccentElem::new(base, Accent::new($primary));
- if let Some(size) = size {
- accent = accent.with_size(size);
- }
- accent.pack()
- }
- )+
- };
-}
-
/// Attaches an accent to a base.
///
/// # Example
@@ -67,7 +12,7 @@ macro_rules! accents {
/// $arrow(a) = accent(a, arrow)$ \
/// $tilde(a) = accent(a, \u{0303})$
/// ```
-#[elem(LayoutMath)]
+#[elem(Mathy)]
pub struct AccentElem {
/// The base to which the accent is applied.
/// May consist of multiple letters.
@@ -110,69 +55,9 @@ pub struct AccentElem {
pub size: Smart<Rel<Length>>,
}
-impl LayoutMath for Packed<AccentElem> {
- #[typst_macros::time(name = "math.accent", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- let cramped = style_cramped();
- let base = ctx.layout_into_fragment(self.base(), styles.chain(&cramped))?;
-
- // Preserve class to preserve automatic spacing.
- let base_class = base.class();
- let base_attach = base.accent_attach();
-
- let width = self
- .size(styles)
- .unwrap_or(Rel::one())
- .at(scaled_font_size(ctx, styles))
- .relative_to(base.width());
-
- // Forcing the accent to be at least as large as the base makes it too
- // wide in many case.
- let Accent(c) = self.accent();
- let glyph = GlyphFragment::new(ctx, styles, *c, self.span());
- let short_fall = ACCENT_SHORT_FALL.at(glyph.font_size);
- let variant = glyph.stretch_horizontal(ctx, width, short_fall);
- let accent = variant.frame;
- let accent_attach = variant.accent_attach;
-
- // Descent is negative because the accent's ink bottom is above the
- // baseline. Therefore, the default gap is the accent's negated descent
- // minus the accent base height. Only if the base is very small, we need
- // a larger gap so that the accent doesn't move too low.
- let accent_base_height = scaled!(ctx, styles, accent_base_height);
- let gap = -accent.descent() - base.height().min(accent_base_height);
- let size = Size::new(base.width(), accent.height() + gap + base.height());
- let accent_pos = Point::with_x(base_attach - accent_attach);
- let base_pos = Point::with_y(accent.height() + gap);
- let baseline = base_pos.y + base.ascent();
- let base_italics_correction = base.italics_correction();
- let base_text_like = base.is_text_like();
-
- let base_ascent = match &base {
- MathFragment::Frame(frame) => frame.base_ascent,
- _ => base.ascent(),
- };
-
- let mut frame = Frame::soft(size);
- frame.set_baseline(baseline);
- frame.push_frame(accent_pos, accent);
- frame.push_frame(base_pos, base.into_frame());
- ctx.push(
- FrameFragment::new(ctx, styles, frame)
- .with_class(base_class)
- .with_base_ascent(base_ascent)
- .with_italics_correction(base_italics_correction)
- .with_accent_attach(base_attach)
- .with_text_like(base_text_like),
- );
-
- Ok(())
- }
-}
-
/// An accent character.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct Accent(char);
+pub struct Accent(pub char);
impl Accent {
/// Normalize a character into an accent.
@@ -181,6 +66,53 @@ impl Accent {
}
}
+/// This macro generates accent-related functions.
+///
+/// ```ignore
+/// accents! {
+/// '\u{0300}' | '`' => grave,
+/// // ^^^^^^^^^ ^^^ ^^^^^
+/// // | | |
+/// // | | +-- The name of the function.
+/// // | +--------- The alternative characters that represent the accent.
+/// // +---------------------- The primary character that represents the accent.
+/// }
+/// ```
+///
+/// When combined with the `Accent::combine` function, accent characters can be normalized
+/// to the primary character.
+macro_rules! accents {
+ ($($primary:literal $(| $alt:literal)* => $name:ident),* $(,)?) => {
+ impl Accent {
+ /// Normalize an accent to a combining one.
+ pub fn combine(c: char) -> Option<char> {
+ Some(match c {
+ $($primary $(| $alt)* => $primary,)*
+ _ => return None,
+ })
+ }
+ }
+
+ $(
+ /// The accent function for callable symbol definitions.
+ #[func]
+ pub fn $name(
+ /// The base to which the accent is applied.
+ base: Content,
+ /// The size of the accent, relative to the width of the base.
+ #[named]
+ size: Option<Smart<Rel<Length>>>,
+ ) -> Content {
+ let mut accent = AccentElem::new(base, Accent::new($primary));
+ if let Some(size) = size {
+ accent = accent.with_size(size);
+ }
+ accent.pack()
+ }
+ )+
+ };
+}
+
// Keep it synced with the documenting table above.
accents! {
'\u{0300}' | '`' => grave,
diff --git a/crates/typst-library/src/math/attach.rs b/crates/typst-library/src/math/attach.rs
new file mode 100644
index 00000000..74eabd10
--- /dev/null
+++ b/crates/typst-library/src/math/attach.rs
@@ -0,0 +1,156 @@
+use crate::foundations::{elem, Content, Packed, Smart};
+use crate::layout::{Length, Rel};
+use crate::math::{EquationElem, Mathy};
+
+/// A base with optional attachments.
+///
+/// ```example
+/// $ attach(
+/// Pi, t: alpha, b: beta,
+/// tl: 1, tr: 2+3, bl: 4+5, br: 6,
+/// ) $
+/// ```
+#[elem(Mathy)]
+pub struct AttachElem {
+ /// The base to which things are attached.
+ #[required]
+ pub base: Content,
+
+ /// The top attachment, smartly positioned at top-right or above the base.
+ ///
+ /// You can wrap the base in `{limits()}` or `{scripts()}` to override the
+ /// smart positioning.
+ pub t: Option<Content>,
+
+ /// The bottom attachment, smartly positioned at the bottom-right or below
+ /// the base.
+ ///
+ /// You can wrap the base in `{limits()}` or `{scripts()}` to override the
+ /// smart positioning.
+ pub b: Option<Content>,
+
+ /// The top-left attachment (before the base).
+ pub tl: Option<Content>,
+
+ /// The bottom-left attachment (before base).
+ pub bl: Option<Content>,
+
+ /// The top-right attachment (after the base).
+ pub tr: Option<Content>,
+
+ /// The bottom-right attachment (after the base).
+ pub br: Option<Content>,
+}
+
+impl Packed<AttachElem> {
+ /// If an AttachElem's base is also an AttachElem, merge attachments into the
+ /// base AttachElem where possible.
+ pub fn merge_base(&self) -> Option<Self> {
+ // Extract from an EquationElem.
+ let mut base = self.base();
+ if let Some(equation) = base.to_packed::<EquationElem>() {
+ base = equation.body();
+ }
+
+ // Move attachments from elem into base where possible.
+ if let Some(base) = base.to_packed::<AttachElem>() {
+ let mut elem = self.clone();
+ let mut base = base.clone();
+
+ macro_rules! merge {
+ ($content:ident) => {
+ if base.$content.is_none() && elem.$content.is_some() {
+ base.$content = elem.$content.clone();
+ elem.$content = None;
+ }
+ };
+ }
+
+ merge!(t);
+ merge!(b);
+ merge!(tl);
+ merge!(tr);
+ merge!(bl);
+ merge!(br);
+
+ elem.base = base.pack();
+ return Some(elem);
+ }
+
+ None
+ }
+}
+
+/// Grouped primes.
+///
+/// ```example
+/// $ a'''_b = a^'''_b $
+/// ```
+///
+/// # Syntax
+/// This function has dedicated syntax: use apostrophes instead of primes. They
+/// will automatically attach to the previous element, moving superscripts to
+/// the next level.
+#[elem(Mathy)]
+pub struct PrimesElem {
+ /// The number of grouped primes.
+ #[required]
+ pub count: usize,
+}
+
+/// Forces a base to display attachments as scripts.
+///
+/// ```example
+/// $ scripts(sum)_1^2 != sum_1^2 $
+/// ```
+#[elem(Mathy)]
+pub struct ScriptsElem {
+ /// The base to attach the scripts to.
+ #[required]
+ pub body: Content,
+}
+
+/// Forces a base to display attachments as limits.
+///
+/// ```example
+/// $ limits(A)_1^2 != A_1^2 $
+/// ```
+#[elem(Mathy)]
+pub struct LimitsElem {
+ /// The base to attach the limits to.
+ #[required]
+ pub body: Content,
+
+ /// Whether to also force limits in inline equations.
+ ///
+ /// When applying limits globally (e.g., through a show rule), it is
+ /// typically a good idea to disable this.
+ #[default(true)]
+ pub inline: bool,
+}
+
+/// Stretches a glyph.
+///
+/// This function can also be used to automatically stretch the base of an
+/// attachment, so that it fits the top and bottom attachments.
+///
+/// Note that only some glyphs can be stretched, and which ones can depend on
+/// the math font being used. However, most math fonts are the same in this
+/// regard.
+///
+/// ```example
+/// $ H stretch(=)^"define" U + p V $
+/// $ f : X stretch(->>, size: #150%)_"surjective" Y $
+/// $ x stretch(harpoons.ltrb, size: #3em) y
+/// stretch(\[, size: #150%) z $
+/// ```
+#[elem(Mathy)]
+pub struct StretchElem {
+ /// The glyph to stretch.
+ #[required]
+ pub body: Content,
+
+ /// The size to stretch to, relative to the maximum size of the glyph and
+ /// its attachments.
+ pub size: Smart<Rel<Length>>,
+}
diff --git a/crates/typst-library/src/math/cancel.rs b/crates/typst-library/src/math/cancel.rs
new file mode 100644
index 00000000..a72505c0
--- /dev/null
+++ b/crates/typst-library/src/math/cancel.rs
@@ -0,0 +1,116 @@
+use crate::foundations::{cast, elem, Content, Func, Smart};
+use crate::layout::{Abs, Angle, Length, Ratio, Rel};
+use crate::math::Mathy;
+use crate::visualize::Stroke;
+
+/// Displays a diagonal line over a part of an equation.
+///
+/// This is commonly used to show the elimination of a term.
+///
+/// # Example
+/// ```example
+/// >>> #set page(width: 140pt)
+/// Here, we can simplify:
+/// $ (a dot b dot cancel(x)) /
+/// cancel(x) $
+/// ```
+#[elem(Mathy)]
+pub struct CancelElem {
+ /// The content over which the line should be placed.
+ #[required]
+ pub body: Content,
+
+ /// The length of the line, relative to the length of the diagonal spanning
+ /// the whole element being "cancelled". A value of `{100%}` would then have
+ /// the line span precisely the element's diagonal.
+ ///
+ /// ```example
+ /// >>> #set page(width: 140pt)
+ /// $ a + cancel(x, length: #200%)
+ /// - cancel(x, length: #200%) $
+ /// ```
+ #[default(Rel::new(Ratio::one(), Abs::pt(3.0).into()))]
+ pub length: Rel<Length>,
+
+ /// Whether the cancel line should be inverted (flipped along the y-axis).
+ /// For the default angle setting, inverted means the cancel line
+ /// points to the top left instead of top right.
+ ///
+ /// ```example
+ /// >>> #set page(width: 140pt)
+ /// $ (a cancel((b + c), inverted: #true)) /
+ /// cancel(b + c, inverted: #true) $
+ /// ```
+ #[default(false)]
+ pub inverted: bool,
+
+ /// Whether two opposing cancel lines should be drawn, forming a cross over
+ /// the element. Overrides `inverted`.
+ ///
+ /// ```example
+ /// >>> #set page(width: 140pt)
+ /// $ cancel(Pi, cross: #true) $
+ /// ```
+ #[default(false)]
+ pub cross: bool,
+
+ /// How much to rotate the cancel line.
+ ///
+ /// - If given an angle, the line is rotated by that angle clockwise with
+ /// respect to the y-axis.
+ /// - If `{auto}`, the line assumes the default angle; that is, along the
+ /// rising diagonal of the content box.
+ /// - If given a function `angle => angle`, the line is rotated, with
+ /// respect to the y-axis, by the angle returned by that function. The
+ /// function receives the default angle as its input.
+ ///
+ /// ```example
+ /// >>> #set page(width: 140pt)
+ /// $ cancel(Pi)
+ /// cancel(Pi, angle: #0deg)
+ /// cancel(Pi, angle: #45deg)
+ /// cancel(Pi, angle: #90deg)
+ /// cancel(1/(1+x), angle: #(a => a + 45deg))
+ /// cancel(1/(1+x), angle: #(a => a + 90deg)) $
+ /// ```
+ pub angle: Smart<CancelAngle>,
+
+ /// How to [stroke]($stroke) the cancel line.
+ ///
+ /// ```example
+ /// >>> #set page(width: 140pt)
+ /// $ cancel(
+ /// sum x,
+ /// stroke: #(
+ /// paint: red,
+ /// thickness: 1.5pt,
+ /// dash: "dashed",
+ /// ),
+ /// ) $
+ /// ```
+ #[resolve]
+ #[fold]
+ #[default(Stroke {
+ // Default stroke has 0.5pt for better visuals.
+ thickness: Smart::Custom(Abs::pt(0.5).into()),
+ ..Default::default()
+ })]
+ pub stroke: Stroke,
+}
+
+/// Defines the cancel line.
+#[derive(Debug, Clone, PartialEq, Hash)]
+pub enum CancelAngle {
+ Angle(Angle),
+ Func(Func),
+}
+
+cast! {
+ CancelAngle,
+ self => match self {
+ Self::Angle(v) => v.into_value(),
+ Self::Func(v) => v.into_value()
+ },
+ v: Angle => CancelAngle::Angle(v),
+ v: Func => CancelAngle::Func(v),
+}
diff --git a/crates/typst-library/src/math/equation.rs b/crates/typst-library/src/math/equation.rs
new file mode 100644
index 00000000..4edafe5e
--- /dev/null
+++ b/crates/typst-library/src/math/equation.rs
@@ -0,0 +1,256 @@
+use std::num::NonZeroUsize;
+
+use typst_utils::NonZeroExt;
+use unicode_math_class::MathClass;
+
+use crate::diag::SourceResult;
+use crate::engine::Engine;
+use crate::foundations::{
+ elem, Content, NativeElement, Packed, Show, ShowSet, Smart, StyleChain, Styles,
+ Synthesize,
+};
+use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
+use crate::layout::{
+ AlignElem, Alignment, BlockElem, InlineElem, OuterHAlignment, SpecificAlignment,
+ VAlignment,
+};
+use crate::math::{MathSize, MathVariant};
+use crate::model::{Numbering, Outlinable, ParLine, Refable, Supplement};
+use crate::text::{FontFamily, FontList, FontWeight, LocalName, TextElem};
+
+/// A mathematical equation.
+///
+/// Can be displayed inline with text or as a separate block.
+///
+/// # Example
+/// ```example
+/// #set text(font: "New Computer Modern")
+///
+/// Let $a$, $b$, and $c$ be the side
+/// lengths of right-angled triangle.
+/// Then, we know that:
+/// $ a^2 + b^2 = c^2 $
+///
+/// Prove by induction:
+/// $ sum_(k=1)^n k = (n(n+1)) / 2 $
+/// ```
+///
+/// By default, block-level equations will not break across pages. This can be
+/// changed through `{show math.equation: set block(breakable: true)}`.
+///
+/// # Syntax
+/// This function also has dedicated syntax: Write mathematical markup within
+/// dollar signs to create an equation. Starting and ending the equation with at
+/// least one space lifts it into a separate block that is centered
+/// horizontally. For more details about math syntax, see the
+/// [main math page]($category/math).
+#[elem(Locatable, Synthesize, Show, ShowSet, Count, LocalName, Refable, Outlinable)]
+pub struct EquationElem {
+ /// Whether the equation is displayed as a separate block.
+ #[default(false)]
+ pub block: bool,
+
+ /// How to [number]($numbering) block-level equations.
+ ///
+ /// ```example
+ /// #set math.equation(numbering: "(1)")
+ ///
+ /// We define:
+ /// $ phi.alt := (1 + sqrt(5)) / 2 $ <ratio>
+ ///
+ /// With @ratio, we get:
+ /// $ F_n = floor(1 / sqrt(5) phi.alt^n) $
+ /// ```
+ #[borrowed]
+ pub numbering: Option<Numbering>,
+
+ /// The alignment of the equation numbering.
+ ///
+ /// By default, the alignment is `{end + horizon}`. For the horizontal
+ /// component, you can use `{right}`, `{left}`, or `{start}` and `{end}`
+ /// of the text direction; for the vertical component, you can use
+ /// `{top}`, `{horizon}`, or `{bottom}`.
+ ///
+ /// ```example
+ /// #set math.equation(numbering: "(1)", number-align: bottom)
+ ///
+ /// We can calculate:
+ /// $ E &= sqrt(m_0^2 + p^2) \
+ /// &approx 125 "GeV" $
+ /// ```
+ #[default(SpecificAlignment::Both(OuterHAlignment::End, VAlignment::Horizon))]
+ pub number_align: SpecificAlignment<OuterHAlignment, VAlignment>,
+
+ /// A supplement for the equation.
+ ///
+ /// For references to equations, this is added before the referenced number.
+ ///
+ /// If a function is specified, it is passed the referenced equation and
+ /// should return content.
+ ///
+ /// ```example
+ /// #set math.equation(numbering: "(1)", supplement: [Eq.])
+ ///
+ /// We define:
+ /// $ phi.alt := (1 + sqrt(5)) / 2 $ <ratio>
+ ///
+ /// With @ratio, we get:
+ /// $ F_n = floor(1 / sqrt(5) phi.alt^n) $
+ /// ```
+ pub supplement: Smart<Option<Supplement>>,
+
+ /// The contents of the equation.
+ #[required]
+ pub body: Content,
+
+ /// The size of the glyphs.
+ #[internal]
+ #[default(MathSize::Text)]
+ #[ghost]
+ pub size: MathSize,
+
+ /// The style variant to select.
+ #[internal]
+ #[ghost]
+ pub variant: MathVariant,
+
+ /// Affects the height of exponents.
+ #[internal]
+ #[default(false)]
+ #[ghost]
+ pub cramped: bool,
+
+ /// Whether to use bold glyphs.
+ #[internal]
+ #[default(false)]
+ #[ghost]
+ pub bold: bool,
+
+ /// Whether to use italic glyphs.
+ #[internal]
+ #[ghost]
+ pub italic: Smart<bool>,
+
+ /// A forced class to use for all fragment.
+ #[internal]
+ #[ghost]
+ pub class: Option<MathClass>,
+}
+
+impl Synthesize for Packed<EquationElem> {
+ fn synthesize(
+ &mut self,
+ engine: &mut Engine,
+ styles: StyleChain,
+ ) -> SourceResult<()> {
+ let supplement = match self.as_ref().supplement(styles) {
+ Smart::Auto => TextElem::packed(Self::local_name_in(styles)),
+ Smart::Custom(None) => Content::empty(),
+ Smart::Custom(Some(supplement)) => {
+ supplement.resolve(engine, styles, [self.clone().pack()])?
+ }
+ };
+
+ self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement))));
+ Ok(())
+ }
+}
+
+impl Show for Packed<EquationElem> {
+ fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ if self.block(styles) {
+ Ok(BlockElem::multi_layouter(
+ self.clone(),
+ engine.routines.layout_equation_block,
+ )
+ .pack()
+ .spanned(self.span()))
+ } else {
+ Ok(InlineElem::layouter(self.clone(), engine.routines.layout_equation_inline)
+ .pack()
+ .spanned(self.span()))
+ }
+ }
+}
+
+impl ShowSet for Packed<EquationElem> {
+ fn show_set(&self, styles: StyleChain) -> Styles {
+ let mut out = Styles::new();
+ if self.block(styles) {
+ out.set(AlignElem::set_alignment(Alignment::CENTER));
+ out.set(BlockElem::set_breakable(false));
+ out.set(ParLine::set_numbering(None));
+ out.set(EquationElem::set_size(MathSize::Display));
+ } else {
+ out.set(EquationElem::set_size(MathSize::Text));
+ }
+ out.set(TextElem::set_weight(FontWeight::from_number(450)));
+ out.set(TextElem::set_font(FontList(vec![FontFamily::new(
+ "New Computer Modern Math",
+ )])));
+ out
+ }
+}
+
+impl Count for Packed<EquationElem> {
+ fn update(&self) -> Option<CounterUpdate> {
+ (self.block(StyleChain::default()) && self.numbering().is_some())
+ .then(|| CounterUpdate::Step(NonZeroUsize::ONE))
+ }
+}
+
+impl LocalName for Packed<EquationElem> {
+ const KEY: &'static str = "equation";
+}
+
+impl Refable for Packed<EquationElem> {
+ fn supplement(&self) -> Content {
+ // After synthesis, this should always be custom content.
+ match (**self).supplement(StyleChain::default()) {
+ Smart::Custom(Some(Supplement::Content(content))) => content,
+ _ => Content::empty(),
+ }
+ }
+
+ fn counter(&self) -> Counter {
+ Counter::of(EquationElem::elem())
+ }
+
+ fn numbering(&self) -> Option<&Numbering> {
+ (**self).numbering(StyleChain::default()).as_ref()
+ }
+}
+
+impl Outlinable for Packed<EquationElem> {
+ fn outline(
+ &self,
+ engine: &mut Engine,
+ styles: StyleChain,
+ ) -> SourceResult<Option<Content>> {
+ if !self.block(StyleChain::default()) {
+ return Ok(None);
+ }
+ let Some(numbering) = self.numbering() else {
+ return Ok(None);
+ };
+
+ // After synthesis, this should always be custom content.
+ let mut supplement = match (**self).supplement(StyleChain::default()) {
+ Smart::Custom(Some(Supplement::Content(content))) => content,
+ _ => Content::empty(),
+ };
+
+ if !supplement.is_empty() {
+ supplement += TextElem::packed("\u{a0}");
+ }
+
+ let numbers = self.counter().display_at_loc(
+ engine,
+ self.location().unwrap(),
+ styles,
+ numbering,
+ )?;
+
+ Ok(Some(supplement + numbers))
+ }
+}
diff --git a/crates/typst-library/src/math/frac.rs b/crates/typst-library/src/math/frac.rs
new file mode 100644
index 00000000..f5c4514d
--- /dev/null
+++ b/crates/typst-library/src/math/frac.rs
@@ -0,0 +1,56 @@
+use typst_syntax::Spanned;
+
+use crate::diag::bail;
+use crate::foundations::{elem, Content, Value};
+use crate::math::Mathy;
+
+/// A mathematical fraction.
+///
+/// # Example
+/// ```example
+/// $ 1/2 < (x+1)/2 $
+/// $ ((x+1)) / 2 = frac(a, b) $
+/// ```
+///
+/// # Syntax
+/// This function also has dedicated syntax: Use a slash to turn neighbouring
+/// expressions into a fraction. Multiple atoms can be grouped into a single
+/// expression using round grouping parenthesis. Such parentheses are removed
+/// from the output, but you can nest multiple to force them.
+#[elem(title = "Fraction", Mathy)]
+pub struct FracElem {
+ /// The fraction's numerator.
+ #[required]
+ pub num: Content,
+
+ /// The fraction's denominator.
+ #[required]
+ pub denom: Content,
+}
+
+/// A binomial expression.
+///
+/// # Example
+/// ```example
+/// $ binom(n, k) $
+/// $ binom(n, k_1, k_2, k_3, ..., k_m) $
+/// ```
+#[elem(title = "Binomial", Mathy)]
+pub struct BinomElem {
+ /// The binomial's upper index.
+ #[required]
+ pub upper: Content,
+
+ /// The binomial's lower index.
+ #[required]
+ #[variadic]
+ #[parse(
+ let values = args.all::<Spanned<Value>>()?;
+ if values.is_empty() {
+ // Prevents one element binomials
+ bail!(args.span, "missing argument: lower");
+ }
+ values.into_iter().map(|spanned| spanned.v.display()).collect()
+ )]
+ pub lower: Vec<Content>,
+}
diff --git a/crates/typst-library/src/math/lr.rs b/crates/typst-library/src/math/lr.rs
new file mode 100644
index 00000000..07ab0dd4
--- /dev/null
+++ b/crates/typst-library/src/math/lr.rs
@@ -0,0 +1,135 @@
+use crate::foundations::{elem, func, Content, NativeElement, Smart};
+use crate::layout::{Length, Rel};
+use crate::math::Mathy;
+use crate::text::TextElem;
+
+/// Scales delimiters.
+///
+/// While matched delimiters scale by default, this can be used to scale
+/// unmatched delimiters and to control the delimiter scaling more precisely.
+#[elem(title = "Left/Right", Mathy)]
+pub struct LrElem {
+ /// The size of the brackets, relative to the height of the wrapped content.
+ pub size: Smart<Rel<Length>>,
+
+ /// The delimited content, including the delimiters.
+ #[required]
+ #[parse(
+ let mut arguments = args.all::<Content>()?.into_iter();
+ let mut body = arguments.next().unwrap_or_default();
+ arguments.for_each(|arg| body += TextElem::packed(',') + arg);
+ body
+ )]
+ pub body: Content,
+}
+
+/// Scales delimiters vertically to the nearest surrounding `{lr()}` group.
+///
+/// ```example
+/// $ { x mid(|) sum_(i=1)^n w_i|f_i (x)| < 1 } $
+/// ```
+#[elem(Mathy)]
+pub struct MidElem {
+ /// The content to be scaled.
+ #[required]
+ pub body: Content,
+}
+
+/// Floors an expression.
+///
+/// ```example
+/// $ floor(x/2) $
+/// ```
+#[func]
+pub fn floor(
+ /// The size of the brackets, relative to the height of the wrapped content.
+ #[named]
+ size: Option<Smart<Rel<Length>>>,
+ /// The expression to floor.
+ body: Content,
+) -> Content {
+ delimited(body, '⌊', '⌋', size)
+}
+
+/// Ceils an expression.
+///
+/// ```example
+/// $ ceil(x/2) $
+/// ```
+#[func]
+pub fn ceil(
+ /// The size of the brackets, relative to the height of the wrapped content.
+ #[named]
+ size: Option<Smart<Rel<Length>>>,
+ /// The expression to ceil.
+ body: Content,
+) -> Content {
+ delimited(body, '⌈', '⌉', size)
+}
+
+/// Rounds an expression.
+///
+/// ```example
+/// $ round(x/2) $
+/// ```
+#[func]
+pub fn round(
+ /// The size of the brackets, relative to the height of the wrapped content.
+ #[named]
+ size: Option<Smart<Rel<Length>>>,
+ /// The expression to round.
+ body: Content,
+) -> Content {
+ delimited(body, '⌊', '⌉', size)
+}
+
+/// Takes the absolute value of an expression.
+///
+/// ```example
+/// $ abs(x/2) $
+/// ```
+#[func]
+pub fn abs(
+ /// The size of the brackets, relative to the height of the wrapped content.
+ #[named]
+ size: Option<Smart<Rel<Length>>>,
+ /// The expression to take the absolute value of.
+ body: Content,
+) -> Content {
+ delimited(body, '|', '|', size)
+}
+
+/// Takes the norm of an expression.
+///
+/// ```example
+/// $ norm(x/2) $
+/// ```
+#[func]
+pub fn norm(
+ /// The size of the brackets, relative to the height of the wrapped content.
+ #[named]
+ size: Option<Smart<Rel<Length>>>,
+ /// The expression to take the norm of.
+ body: Content,
+) -> Content {
+ delimited(body, '‖', '‖', size)
+}
+
+fn delimited(
+ body: Content,
+ left: char,
+ right: char,
+ size: Option<Smart<Rel<Length>>>,
+) -> Content {
+ let span = body.span();
+ let mut elem = LrElem::new(Content::sequence([
+ TextElem::packed(left),
+ body,
+ TextElem::packed(right),
+ ]));
+ // Push size only if size is provided
+ if let Some(size) = size {
+ elem.push_size(size);
+ }
+ elem.pack().spanned(span)
+}
diff --git a/crates/typst/src/math/matrix.rs b/crates/typst-library/src/math/matrix.rs
index 66b83e20..1c788995 100644
--- a/crates/typst/src/math/matrix.rs
+++ b/crates/typst-library/src/math/matrix.rs
@@ -1,30 +1,19 @@
use smallvec::{smallvec, SmallVec};
+use typst_syntax::Spanned;
+use typst_utils::Numeric;
use unicode_math_class::MathClass;
-use crate::diag::{bail, At, HintedStrResult, SourceResult, StrResult};
+use crate::diag::{bail, At, HintedStrResult, StrResult};
use crate::foundations::{
- array, cast, dict, elem, Array, Content, Dict, Fold, NoneValue, Packed, Resolve,
- Smart, StyleChain, Value,
+ array, cast, dict, elem, Array, Content, Dict, Fold, NoneValue, Resolve, Smart,
+ StyleChain, Symbol, Value,
};
-use crate::layout::{
- Abs, Axes, Em, FixedAlignment, Frame, FrameItem, HAlignment, Length, Point, Ratio,
- Rel, Size,
-};
-use crate::math::{
- alignments, delimiter_alignment, scaled_font_size, stack, style_for_denominator,
- AlignmentResult, FrameFragment, GlyphFragment, LayoutMath, LeftRightAlternator,
- MathContext, Scaled, DELIM_SHORT_FALL,
-};
-use crate::symbols::Symbol;
-use crate::syntax::{Span, Spanned};
-use crate::text::TextElem;
-use crate::utils::Numeric;
-use crate::visualize::{FillRule, FixedStroke, Geometry, LineCap, Shape, Stroke};
+use crate::layout::{Abs, Em, HAlignment, Length, Rel};
+use crate::math::Mathy;
+use crate::visualize::Stroke;
const DEFAULT_ROW_GAP: Em = Em::new(0.2);
const DEFAULT_COL_GAP: Em = Em::new(0.5);
-const VERTICAL_PADDING: Ratio = Ratio::new(0.1);
-const DEFAULT_STROKE_THICKNESS: Em = Em::new(0.05);
/// A column vector.
///
@@ -36,7 +25,7 @@ const DEFAULT_STROKE_THICKNESS: Em = Em::new(0.05);
/// $ vec(a, b, c) dot vec(1, 2, 3)
/// = a + 2b + 3c $
/// ```
-#[elem(title = "Vector", LayoutMath)]
+#[elem(title = "Vector", Mathy)]
pub struct VecElem {
/// The delimiter to use.
///
@@ -75,23 +64,6 @@ pub struct VecElem {
pub children: Vec<Content>,
}
-impl LayoutMath for Packed<VecElem> {
- #[typst_macros::time(name = "math.vec", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- let delim = self.delim(styles);
- let frame = layout_vec_body(
- ctx,
- styles,
- self.children(),
- self.align(styles),
- self.gap(styles).at(scaled_font_size(ctx, styles)),
- LeftRightAlternator::Right,
- )?;
-
- layout_delimiters(ctx, styles, frame, delim.open(), delim.close(), self.span())
- }
-}
-
/// A matrix.
///
/// The elements of a row should be separated by commas, while the rows
@@ -113,7 +85,7 @@ impl LayoutMath for Packed<VecElem> {
/// 10, 10, ..., 10;
/// ) $
/// ```
-#[elem(title = "Matrix", LayoutMath)]
+#[elem(title = "Matrix", Mathy)]
pub struct MatElem {
/// The delimiter to use.
///
@@ -241,56 +213,6 @@ pub struct MatElem {
pub rows: Vec<Vec<Content>>,
}
-impl LayoutMath for Packed<MatElem> {
- #[typst_macros::time(name = "math.mat", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- let augment = self.augment(styles);
- let rows = self.rows();
-
- if let Some(aug) = &augment {
- for &offset in &aug.hline.0 {
- if offset == 0 || offset.unsigned_abs() >= rows.len() {
- bail!(
- self.span(),
- "cannot draw a horizontal line after row {} of a matrix with {} rows",
- if offset < 0 { rows.len() as isize + offset } else { offset },
- rows.len()
- );
- }
- }
-
- let ncols = self.rows().first().map_or(0, |row| row.len());
-
- for &offset in &aug.vline.0 {
- if offset == 0 || offset.unsigned_abs() >= ncols {
- bail!(
- self.span(),
- "cannot draw a vertical line after column {} of a matrix with {} columns",
- if offset < 0 { ncols as isize + offset } else { offset },
- ncols
- );
- }
- }
- }
-
- let font_size = scaled_font_size(ctx, styles);
- let column_gap = self.column_gap(styles).at(font_size);
- let row_gap = self.row_gap(styles).at(font_size);
- let delim = self.delim(styles);
- let frame = layout_mat_body(
- ctx,
- styles,
- rows,
- self.align(styles),
- augment,
- Axes::new(column_gap, row_gap),
- self.span(),
- )?;
-
- layout_delimiters(ctx, styles, frame, delim.open(), delim.close(), self.span())
- }
-}
-
/// A case distinction.
///
/// Content across different branches can be aligned with the `&` symbol.
@@ -304,7 +226,7 @@ impl LayoutMath for Packed<MatElem> {
/// 4 "else",
/// ) $
/// ```
-#[elem(LayoutMath)]
+#[elem(Mathy)]
pub struct CasesElem {
/// The delimiter to use.
///
@@ -342,29 +264,6 @@ pub struct CasesElem {
pub children: Vec<Content>,
}
-impl LayoutMath for Packed<CasesElem> {
- #[typst_macros::time(name = "math.cases", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- let delim = self.delim(styles);
- let frame = layout_vec_body(
- ctx,
- styles,
- self.children(),
- FixedAlignment::Start,
- self.gap(styles).at(scaled_font_size(ctx, styles)),
- LeftRightAlternator::None,
- )?;
-
- let (open, close) = if self.reverse(styles) {
- (None, delim.close())
- } else {
- (delim.open(), None)
- };
-
- layout_delimiters(ctx, styles, frame, open, close, self.span())
- }
-}
-
/// A delimiter is a single character that is used to delimit a matrix, vector
/// or cases. The character has to be a Unicode codepoint tagged as a math
/// "opening", "closing" or "fence".
@@ -372,7 +271,7 @@ impl LayoutMath for Packed<CasesElem> {
/// Typically, the delimiter is stretched to fit the height of whatever it
/// delimits.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
-struct Delimiter(Option<char>);
+pub struct Delimiter(Option<char>);
cast! {
Delimiter,
@@ -383,11 +282,11 @@ cast! {
}
impl Delimiter {
- fn none() -> Self {
+ pub fn none() -> Self {
Self(None)
}
- fn char(c: char) -> StrResult<Self> {
+ pub fn char(c: char) -> StrResult<Self> {
if !matches!(
unicode_math_class::class(c),
Some(MathClass::Opening | MathClass::Closing | MathClass::Fence),
@@ -397,11 +296,11 @@ impl Delimiter {
Ok(Self(Some(c)))
}
- fn get(self) -> Option<char> {
+ pub fn get(self) -> Option<char> {
self.0
}
- fn find_matching(self) -> Self {
+ pub fn find_matching(self) -> Self {
match self.0 {
None => Self::none(),
Some('[') => Self(Some(']')),
@@ -451,235 +350,16 @@ impl DelimiterPair {
};
/// The delimiter's opening character.
- fn open(self) -> Option<char> {
+ pub fn open(self) -> Option<char> {
self.open.get()
}
/// The delimiter's closing character.
- fn close(self) -> Option<char> {
+ pub fn close(self) -> Option<char> {
self.close.get()
}
}
-/// Layout the inner contents of a vector.
-fn layout_vec_body(
- ctx: &mut MathContext,
- styles: StyleChain,
- column: &[Content],
- align: FixedAlignment,
- row_gap: Rel<Abs>,
- alternator: LeftRightAlternator,
-) -> SourceResult<Frame> {
- let gap = row_gap.relative_to(ctx.region.size.y);
-
- let denom_style = style_for_denominator(styles);
- let mut flat = vec![];
- for child in column {
- flat.push(ctx.layout_into_run(child, styles.chain(&denom_style))?);
- }
- // We pad ascent and descent with the ascent and descent of the paren
- // to ensure that normal vectors are aligned with others unless they are
- // way too big.
- let paren =
- GlyphFragment::new(ctx, styles.chain(&denom_style), '(', Span::detached());
- Ok(stack(flat, align, gap, 0, alternator, Some((paren.ascent, paren.descent))))
-}
-
-/// Layout the inner contents of a matrix.
-fn layout_mat_body(
- ctx: &mut MathContext,
- styles: StyleChain,
- rows: &[Vec<Content>],
- align: FixedAlignment,
- augment: Option<Augment<Abs>>,
- gap: Axes<Rel<Abs>>,
- span: Span,
-) -> SourceResult<Frame> {
- let ncols = rows.first().map_or(0, |row| row.len());
- let nrows = rows.len();
- if ncols == 0 || nrows == 0 {
- return Ok(Frame::soft(Size::zero()));
- }
-
- let gap = gap.zip_map(ctx.region.size, Rel::relative_to);
- let half_gap = gap * 0.5;
-
- // We provide a default stroke thickness that scales
- // with font size to ensure that augmentation lines
- // look correct by default at all matrix sizes.
- // The line cap is also set to square because it looks more "correct".
- let font_size = scaled_font_size(ctx, styles);
- let default_stroke_thickness = DEFAULT_STROKE_THICKNESS.at(font_size);
- let default_stroke = FixedStroke {
- thickness: default_stroke_thickness,
- paint: TextElem::fill_in(styles).as_decoration(),
- cap: LineCap::Square,
- ..Default::default()
- };
-
- let (hline, vline, stroke) = match augment {
- Some(augment) => {
- // We need to get stroke here for ownership.
- let stroke = augment.stroke.unwrap_or_default().unwrap_or(default_stroke);
- (augment.hline, augment.vline, stroke)
- }
- _ => (AugmentOffsets::default(), AugmentOffsets::default(), default_stroke),
- };
-
- // Before the full matrix body can be laid out, the
- // individual cells must first be independently laid out
- // so we can ensure alignment across rows and columns.
-
- // This variable stores the maximum ascent and descent for each row.
- let mut heights = vec![(Abs::zero(), Abs::zero()); nrows];
-
- // We want to transpose our data layout to columns
- // before final layout. For efficiency, the columns
- // variable is set up here and newly generated
- // individual cells are then added to it.
- let mut cols = vec![vec![]; ncols];
-
- let denom_style = style_for_denominator(styles);
- // We pad ascent and descent with the ascent and descent of the paren
- // to ensure that normal matrices are aligned with others unless they are
- // way too big.
- let paren =
- GlyphFragment::new(ctx, styles.chain(&denom_style), '(', Span::detached());
-
- for (row, (ascent, descent)) in rows.iter().zip(&mut heights) {
- for (cell, col) in row.iter().zip(&mut cols) {
- let cell = ctx.layout_into_run(cell, styles.chain(&denom_style))?;
-
- ascent.set_max(cell.ascent().max(paren.ascent));
- descent.set_max(cell.descent().max(paren.descent));
-
- col.push(cell);
- }
- }
-
- // For each row, combine maximum ascent and descent into a row height.
- // Sum the row heights, then add the total height of the gaps between rows.
- let total_height =
- heights.iter().map(|&(a, b)| a + b).sum::<Abs>() + gap.y * (nrows - 1) as f64;
-
- // Width starts at zero because it can't be calculated until later
- let mut frame = Frame::soft(Size::new(Abs::zero(), total_height));
-
- let mut x = Abs::zero();
-
- for (index, col) in cols.into_iter().enumerate() {
- let AlignmentResult { points, width: rcol } = alignments(&col);
-
- let mut y = Abs::zero();
-
- for (cell, &(ascent, descent)) in col.into_iter().zip(&heights) {
- let cell = cell.into_line_frame(&points, LeftRightAlternator::Right);
- let pos = Point::new(
- if points.is_empty() {
- x + align.position(rcol - cell.width())
- } else {
- x
- },
- y + ascent - cell.ascent(),
- );
-
- frame.push_frame(pos, cell);
-
- y += ascent + descent + gap.y;
- }
-
- // Advance to the end of the column
- x += rcol;
-
- // If a vertical line should be inserted after this column
- if vline.0.contains(&(index as isize + 1))
- || vline.0.contains(&(1 - ((ncols - index) as isize)))
- {
- frame.push(
- Point::with_x(x + half_gap.x),
- line_item(total_height, true, stroke.clone(), span),
- );
- }
-
- // Advance to the start of the next column
- x += gap.x;
- }
-
- // Once all the columns are laid out, the total width can be calculated
- let total_width = x - gap.x;
-
- // This allows the horizontal lines to be laid out
- for line in hline.0 {
- let real_line =
- if line < 0 { nrows - line.unsigned_abs() } else { line as usize };
- let offset = (heights[0..real_line].iter().map(|&(a, b)| a + b).sum::<Abs>()
- + gap.y * (real_line - 1) as f64)
- + half_gap.y;
-
- frame.push(
- Point::with_y(offset),
- line_item(total_width, false, stroke.clone(), span),
- );
- }
-
- frame.size_mut().x = total_width;
-
- Ok(frame)
-}
-
-fn line_item(length: Abs, vertical: bool, stroke: FixedStroke, span: Span) -> FrameItem {
- let line_geom = if vertical {
- Geometry::Line(Point::with_y(length))
- } else {
- Geometry::Line(Point::with_x(length))
- };
-
- FrameItem::Shape(
- Shape {
- geometry: line_geom,
- fill: None,
- fill_rule: FillRule::default(),
- stroke: Some(stroke),
- },
- span,
- )
-}
-
-/// Layout the outer wrapper around the body of a vector or matrix.
-fn layout_delimiters(
- ctx: &mut MathContext,
- styles: StyleChain,
- mut frame: Frame,
- left: Option<char>,
- right: Option<char>,
- span: Span,
-) -> SourceResult<()> {
- let font_size = scaled_font_size(ctx, styles);
- let short_fall = DELIM_SHORT_FALL.at(font_size);
- let axis = ctx.constants.axis_height().scaled(ctx, font_size);
- let height = frame.height();
- let target = height + VERTICAL_PADDING.of(height);
- frame.set_baseline(height / 2.0 + axis);
-
- if let Some(left) = left {
- let mut left = GlyphFragment::new(ctx, styles, left, span)
- .stretch_vertical(ctx, target, short_fall);
- left.align_on_axis(ctx, delimiter_alignment(left.c));
- ctx.push(left);
- }
-
- ctx.push(FrameFragment::new(ctx, styles, frame));
-
- if let Some(right) = right {
- let mut right = GlyphFragment::new(ctx, styles, right, span)
- .stretch_vertical(ctx, target, short_fall);
- right.align_on_axis(ctx, delimiter_alignment(right.c));
- ctx.push(right);
- }
-
- Ok(())
-}
-
/// Parameters specifying how augmentation lines
/// should be drawn on a matrix.
#[derive(Debug, Default, Clone, PartialEq, Hash)]
@@ -758,7 +438,7 @@ cast! {
/// The offsets at which augmentation lines should be drawn on a matrix.
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
-pub struct AugmentOffsets(SmallVec<[isize; 1]>);
+pub struct AugmentOffsets(pub SmallVec<[isize; 1]>);
cast! {
AugmentOffsets,
diff --git a/crates/typst/src/math/mod.rs b/crates/typst-library/src/math/mod.rs
index 2c5c32cb..53b1f072 100644
--- a/crates/typst/src/math/mod.rs
+++ b/crates/typst-library/src/math/mod.rs
@@ -1,50 +1,36 @@
//! Mathematical formulas.
-#[macro_use]
-mod ctx;
-
pub mod accent;
-
-mod align;
mod attach;
mod cancel;
-#[path = "class.rs"]
-mod class_;
mod equation;
mod frac;
-mod fragment;
mod lr;
mod matrix;
mod op;
mod root;
-mod row;
-mod spacing;
-mod stretch;
mod style;
mod underover;
pub use self::accent::{Accent, AccentElem};
-pub use self::align::*;
pub use self::attach::*;
pub use self::cancel::*;
-pub use self::class_::*;
pub use self::equation::*;
pub use self::frac::*;
pub use self::lr::*;
pub use self::matrix::*;
pub use self::op::*;
pub use self::root::*;
-pub use self::stretch::*;
pub use self::style::*;
pub use self::underover::*;
-use self::ctx::*;
-use self::fragment::*;
-use self::row::*;
-use self::spacing::*;
+use typst_utils::singleton;
+use unicode_math_class::MathClass;
-use crate::diag::SourceResult;
-use crate::foundations::{category, Category, Module, Scope, StyleChain};
+use crate::foundations::{
+ category, elem, Category, Content, Module, NativeElement, Scope,
+};
+use crate::layout::{Em, HElem};
use crate::text::TextElem;
/// Typst has special [syntax]($syntax/#math) and library functions to typeset
@@ -152,6 +138,13 @@ use crate::text::TextElem;
#[category]
pub static MATH: Category;
+// Spacings.
+pub const THIN: Em = Em::new(1.0 / 6.0);
+pub const MEDIUM: Em = Em::new(2.0 / 9.0);
+pub const THICK: Em = Em::new(5.0 / 18.0);
+pub const QUAD: Em = Em::new(1.0);
+pub const WIDE: Em = Em::new(2.0);
+
/// Create a module with all math definitions.
pub fn module() -> Module {
let mut math = Scope::deduplicating();
@@ -203,9 +196,17 @@ pub fn module() -> Module {
math.define_func::<script>();
math.define_func::<sscript>();
- // Text operators, spacings, and symbols.
+ // Text operators.
op::define(&mut math);
- spacing::define(&mut math);
+
+ // Spacings.
+ math.define("thin", HElem::new(THIN.into()).pack());
+ math.define("med", HElem::new(MEDIUM.into()).pack());
+ math.define("thick", HElem::new(THICK.into()).pack());
+ math.define("quad", HElem::new(QUAD.into()).pack());
+ math.define("wide", HElem::new(WIDE.into()).pack());
+
+ // Symbols.
for (name, symbol) in crate::symbols::SYM {
math.define(*name, symbol.clone());
}
@@ -213,8 +214,44 @@ pub fn module() -> Module {
Module::new("math", math)
}
-/// Layout for math elements.
-pub trait LayoutMath {
- /// Layout the element, producing fragment in the context.
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()>;
+/// Trait for recognizing math elements and auto-wrapping them in equations.
+pub trait Mathy {}
+
+/// A math alignment point: `&`, `&&`.
+#[elem(title = "Alignment Point", Mathy)]
+pub struct AlignPointElem {}
+
+impl AlignPointElem {
+ /// Get the globally shared alignment point element.
+ pub fn shared() -> &'static Content {
+ singleton!(Content, AlignPointElem::new().pack())
+ }
+}
+
+/// Forced use of a certain math class.
+///
+/// This is useful to treat certain symbols as if they were of a different
+/// class, e.g. to make a symbol behave like a relation. The class of a symbol
+/// defines the way it is laid out, including spacing around it, and how its
+/// scripts are attached by default. Note that the latter can always be
+/// overridden using [`{limits}`](math.limits) and [`{scripts}`](math.scripts).
+///
+/// # Example
+/// ```example
+/// #let loves = math.class(
+/// "relation",
+/// sym.suit.heart,
+/// )
+///
+/// $x loves y and y loves 5$
+/// ```
+#[elem(Mathy)]
+pub struct ClassElem {
+ /// The class to apply to the content.
+ #[required]
+ pub class: MathClass,
+
+ /// The content to which the class is applied.
+ #[required]
+ pub body: Content,
}
diff --git a/crates/typst/src/math/op.rs b/crates/typst-library/src/math/op.rs
index 8e93b4e4..ef24705a 100644
--- a/crates/typst/src/math/op.rs
+++ b/crates/typst-library/src/math/op.rs
@@ -1,10 +1,8 @@
use ecow::EcoString;
-use unicode_math_class::MathClass;
-use crate::diag::SourceResult;
-use crate::foundations::{elem, Content, NativeElement, Packed, Scope, StyleChain};
+use crate::foundations::{elem, Content, NativeElement, Scope};
use crate::layout::HElem;
-use crate::math::{upright, FrameFragment, LayoutMath, Limits, MathContext, THIN};
+use crate::math::{upright, Mathy, THIN};
use crate::text::TextElem;
/// A text operator in an equation.
@@ -22,7 +20,7 @@ use crate::text::TextElem;
/// `gcd`, `hom`, `id`, `im`, `inf`, `ker`, `lg`, `lim`, `liminf`, `limsup`,
/// `ln`, `log`, `max`, `min`, `mod`, `Pr`, `sec`, `sech`, `sin`, `sinc`,
/// `sinh`, `sup`, `tan`, `tanh`, `tg` and `tr`.
-#[elem(title = "Text Operator", LayoutMath)]
+#[elem(title = "Text Operator", Mathy)]
pub struct OpElem {
/// The operator's text.
#[required]
@@ -33,30 +31,6 @@ pub struct OpElem {
pub limits: bool,
}
-impl LayoutMath for Packed<OpElem> {
- #[typst_macros::time(name = "math.op", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- let fragment = ctx.layout_into_fragment(self.text(), styles)?;
- let italics = fragment.italics_correction();
- let accent_attach = fragment.accent_attach();
- let text_like = fragment.is_text_like();
-
- ctx.push(
- FrameFragment::new(ctx, styles, fragment.into_frame())
- .with_class(MathClass::Large)
- .with_italics_correction(italics)
- .with_accent_attach(accent_attach)
- .with_text_like(text_like)
- .with_limits(if self.limits(styles) {
- Limits::Display
- } else {
- Limits::Never
- }),
- );
- Ok(())
- }
-}
-
macro_rules! ops {
($($name:ident $(: $value:literal)? $(($tts:tt))?),* $(,)?) => {
pub(super) fn define(math: &mut Scope) {
diff --git a/crates/typst-library/src/math/root.rs b/crates/typst-library/src/math/root.rs
new file mode 100644
index 00000000..e25c6d42
--- /dev/null
+++ b/crates/typst-library/src/math/root.rs
@@ -0,0 +1,35 @@
+use typst_syntax::Span;
+
+use crate::foundations::{elem, func, Content, NativeElement};
+use crate::math::Mathy;
+
+/// A square root.
+///
+/// ```example
+/// $ sqrt(3 - 2 sqrt(2)) = sqrt(2) - 1 $
+/// ```
+#[func(title = "Square Root")]
+pub fn sqrt(
+ /// The call span of this function.
+ span: Span,
+ /// The expression to take the square root of.
+ radicand: Content,
+) -> Content {
+ RootElem::new(radicand).pack().spanned(span)
+}
+
+/// A general root.
+///
+/// ```example
+/// $ root(3, x) $
+/// ```
+#[elem(Mathy)]
+pub struct RootElem {
+ /// Which root of the radicand to take.
+ #[positional]
+ pub index: Option<Content>,
+
+ /// The expression to take the root of.
+ #[required]
+ pub radicand: Content,
+}
diff --git a/crates/typst-library/src/math/style.rs b/crates/typst-library/src/math/style.rs
new file mode 100644
index 00000000..f3d28f2a
--- /dev/null
+++ b/crates/typst-library/src/math/style.rs
@@ -0,0 +1,254 @@
+use crate::foundations::{func, Cast, Content, Smart};
+use crate::math::EquationElem;
+
+/// Bold font style in math.
+///
+/// ```example
+/// $ bold(A) := B^+ $
+/// ```
+#[func(keywords = ["mathbf"])]
+pub fn bold(
+ /// The content to style.
+ body: Content,
+) -> Content {
+ body.styled(EquationElem::set_bold(true))
+}
+
+/// Upright (non-italic) font style in math.
+///
+/// ```example
+/// $ upright(A) != A $
+/// ```
+#[func(keywords = ["mathup"])]
+pub fn upright(
+ /// The content to style.
+ body: Content,
+) -> Content {
+ body.styled(EquationElem::set_italic(Smart::Custom(false)))
+}
+
+/// Italic font style in math.
+///
+/// For roman letters and greek lowercase letters, this is already the default.
+#[func(keywords = ["mathit"])]
+pub fn italic(
+ /// The content to style.
+ body: Content,
+) -> Content {
+ body.styled(EquationElem::set_italic(Smart::Custom(true)))
+}
+
+/// Serif (roman) font style in math.
+///
+/// This is already the default.
+#[func(keywords = ["mathrm"])]
+pub fn serif(
+ /// The content to style.
+ body: Content,
+) -> Content {
+ body.styled(EquationElem::set_variant(MathVariant::Serif))
+}
+
+/// Sans-serif font style in math.
+///
+/// ```example
+/// $ sans(A B C) $
+/// ```
+#[func(title = "Sans Serif", keywords = ["mathsf"])]
+pub fn sans(
+ /// The content to style.
+ body: Content,
+) -> Content {
+ body.styled(EquationElem::set_variant(MathVariant::Sans))
+}
+
+/// Calligraphic font style in math.
+///
+/// ```example
+/// Let $cal(P)$ be the set of ...
+/// ```
+///
+/// This corresponds both to LaTeX's `\mathcal` and `\mathscr` as both of these
+/// styles share the same Unicode codepoints. Switching between the styles is
+/// thus only possible if supported by the font via
+/// [font features]($text.features).
+///
+/// For the default math font, the roundhand style is available through the
+/// `ss01` feature. Therefore, you could define your own version of `\mathscr`
+/// like this:
+///
+/// ```example
+/// #let scr(it) = text(
+/// features: ("ss01",),
+/// box($cal(it)$),
+/// )
+///
+/// We establish $cal(P) != scr(P)$.
+/// ```
+///
+/// (The box is not conceptually necessary, but unfortunately currently needed
+/// due to limitations in Typst's text style handling in math.)
+#[func(title = "Calligraphic", keywords = ["mathcal", "mathscr"])]
+pub fn cal(
+ /// The content to style.
+ body: Content,
+) -> Content {
+ body.styled(EquationElem::set_variant(MathVariant::Cal))
+}
+
+/// Fraktur font style in math.
+///
+/// ```example
+/// $ frak(P) $
+/// ```
+#[func(title = "Fraktur", keywords = ["mathfrak"])]
+pub fn frak(
+ /// The content to style.
+ body: Content,
+) -> Content {
+ body.styled(EquationElem::set_variant(MathVariant::Frak))
+}
+
+/// Monospace font style in math.
+///
+/// ```example
+/// $ mono(x + y = z) $
+/// ```
+#[func(title = "Monospace", keywords = ["mathtt"])]
+pub fn mono(
+ /// The content to style.
+ body: Content,
+) -> Content {
+ body.styled(EquationElem::set_variant(MathVariant::Mono))
+}
+
+/// Blackboard bold (double-struck) font style in math.
+///
+/// For uppercase latin letters, blackboard bold is additionally available
+/// through [symbols]($category/symbols/sym) of the form `NN` and `RR`.
+///
+/// ```example
+/// $ bb(b) $
+/// $ bb(N) = NN $
+/// $ f: NN -> RR $
+/// ```
+#[func(title = "Blackboard Bold", keywords = ["mathbb"])]
+pub fn bb(
+ /// The content to style.
+ body: Content,
+) -> Content {
+ body.styled(EquationElem::set_variant(MathVariant::Bb))
+}
+
+/// Forced display style in math.
+///
+/// This is the normal size for block equations.
+///
+/// ```example
+/// $sum_i x_i/2 = display(sum_i x_i/2)$
+/// ```
+#[func(title = "Display Size", keywords = ["displaystyle"])]
+pub fn display(
+ /// The content to size.
+ body: Content,
+ /// Whether to impose a height restriction for exponents, like regular sub-
+ /// and superscripts do.
+ #[named]
+ #[default(false)]
+ cramped: bool,
+) -> Content {
+ body.styled(EquationElem::set_size(MathSize::Display))
+ .styled(EquationElem::set_cramped(cramped))
+}
+
+/// Forced inline (text) style in math.
+///
+/// This is the normal size for inline equations.
+///
+/// ```example
+/// $ sum_i x_i/2
+/// = inline(sum_i x_i/2) $
+/// ```
+#[func(title = "Inline Size", keywords = ["textstyle"])]
+pub fn inline(
+ /// The content to size.
+ body: Content,
+ /// Whether to impose a height restriction for exponents, like regular sub-
+ /// and superscripts do.
+ #[named]
+ #[default(false)]
+ cramped: bool,
+) -> Content {
+ body.styled(EquationElem::set_size(MathSize::Text))
+ .styled(EquationElem::set_cramped(cramped))
+}
+
+/// Forced script style in math.
+///
+/// This is the smaller size used in powers or sub- or superscripts.
+///
+/// ```example
+/// $sum_i x_i/2 = script(sum_i x_i/2)$
+/// ```
+#[func(title = "Script Size", keywords = ["scriptstyle"])]
+pub fn script(
+ /// The content to size.
+ body: Content,
+ /// Whether to impose a height restriction for exponents, like regular sub-
+ /// and superscripts do.
+ #[named]
+ #[default(true)]
+ cramped: bool,
+) -> Content {
+ body.styled(EquationElem::set_size(MathSize::Script))
+ .styled(EquationElem::set_cramped(cramped))
+}
+
+/// Forced second script style in math.
+///
+/// This is the smallest size, used in second-level sub- and superscripts
+/// (script of the script).
+///
+/// ```example
+/// $sum_i x_i/2 = sscript(sum_i x_i/2)$
+/// ```
+#[func(title = "Script-Script Size", keywords = ["scriptscriptstyle"])]
+pub fn sscript(
+ /// The content to size.
+ body: Content,
+ /// Whether to impose a height restriction for exponents, like regular sub-
+ /// and superscripts do.
+ #[named]
+ #[default(true)]
+ cramped: bool,
+) -> Content {
+ body.styled(EquationElem::set_size(MathSize::ScriptScript))
+ .styled(EquationElem::set_cramped(cramped))
+}
+
+/// The size of elements in an equation.
+///
+/// See the TeXbook p. 141.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Cast, Hash)]
+pub enum MathSize {
+ /// Second-level sub- and superscripts.
+ ScriptScript,
+ /// Sub- and superscripts.
+ Script,
+ /// Math in text.
+ Text,
+ /// Math on its own line.
+ Display,
+}
+
+/// A mathematical style variant, as defined by Unicode.
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Cast, Hash)]
+pub enum MathVariant {
+ #[default]
+ Serif,
+ Sans,
+ Cal,
+ Frak,
+ Mono,
+ Bb,
+}
diff --git a/crates/typst-library/src/math/underover.rs b/crates/typst-library/src/math/underover.rs
new file mode 100644
index 00000000..302c51af
--- /dev/null
+++ b/crates/typst-library/src/math/underover.rs
@@ -0,0 +1,156 @@
+use crate::foundations::{elem, Content};
+use crate::math::Mathy;
+
+/// A horizontal line under content.
+///
+/// ```example
+/// $ underline(1 + 2 + ... + 5) $
+/// ```
+#[elem(Mathy)]
+pub struct UnderlineElem {
+ /// The content above the line.
+ #[required]
+ pub body: Content,
+}
+
+/// A horizontal line over content.
+///
+/// ```example
+/// $ overline(1 + 2 + ... + 5) $
+/// ```
+#[elem(Mathy)]
+pub struct OverlineElem {
+ /// The content below the line.
+ #[required]
+ pub body: Content,
+}
+
+/// A horizontal brace under content, with an optional annotation below.
+///
+/// ```example
+/// $ underbrace(1 + 2 + ... + 5, "numbers") $
+/// ```
+#[elem(Mathy)]
+pub struct UnderbraceElem {
+ /// The content above the brace.
+ #[required]
+ pub body: Content,
+
+ /// The optional content below the brace.
+ #[positional]
+ pub annotation: Option<Content>,
+}
+
+/// A horizontal brace over content, with an optional annotation above.
+///
+/// ```example
+/// $ overbrace(1 + 2 + ... + 5, "numbers") $
+/// ```
+#[elem(Mathy)]
+pub struct OverbraceElem {
+ /// The content below the brace.
+ #[required]
+ pub body: Content,
+
+ /// The optional content above the brace.
+ #[positional]
+ pub annotation: Option<Content>,
+}
+
+/// A horizontal bracket under content, with an optional annotation below.
+///
+/// ```example
+/// $ underbracket(1 + 2 + ... + 5, "numbers") $
+/// ```
+#[elem(Mathy)]
+pub struct UnderbracketElem {
+ /// The content above the bracket.
+ #[required]
+ pub body: Content,
+
+ /// The optional content below the bracket.
+ #[positional]
+ pub annotation: Option<Content>,
+}
+
+/// A horizontal bracket over content, with an optional annotation above.
+///
+/// ```example
+/// $ overbracket(1 + 2 + ... + 5, "numbers") $
+/// ```
+#[elem(Mathy)]
+pub struct OverbracketElem {
+ /// The content below the bracket.
+ #[required]
+ pub body: Content,
+
+ /// The optional content above the bracket.
+ #[positional]
+ pub annotation: Option<Content>,
+}
+
+/// A horizontal parenthesis under content, with an optional annotation below.
+///
+/// ```example
+/// $ underparen(1 + 2 + ... + 5, "numbers") $
+/// ```
+#[elem(Mathy)]
+pub struct UnderparenElem {
+ /// The content above the parenthesis.
+ #[required]
+ pub body: Content,
+
+ /// The optional content below the parenthesis.
+ #[positional]
+ pub annotation: Option<Content>,
+}
+
+/// A horizontal parenthesis over content, with an optional annotation above.
+///
+/// ```example
+/// $ overparen(1 + 2 + ... + 5, "numbers") $
+/// ```
+#[elem(Mathy)]
+pub struct OverparenElem {
+ /// The content below the parenthesis.
+ #[required]
+ pub body: Content,
+
+ /// The optional content above the parenthesis.
+ #[positional]
+ pub annotation: Option<Content>,
+}
+
+/// A horizontal tortoise shell bracket under content, with an optional
+/// annotation below.
+///
+/// ```example
+/// $ undershell(1 + 2 + ... + 5, "numbers") $
+/// ```
+#[elem(Mathy)]
+pub struct UndershellElem {
+ /// The content above the tortoise shell bracket.
+ #[required]
+ pub body: Content,
+
+ /// The optional content below the tortoise shell bracket.
+ #[positional]
+ pub annotation: Option<Content>,
+}
+
+/// A horizontal tortoise shell bracket over content, with an optional
+/// annotation above.
+///
+/// ```example
+/// $ overshell(1 + 2 + ... + 5, "numbers") $
+/// ```
+#[elem(Mathy)]
+pub struct OvershellElem {
+ /// The content below the tortoise shell bracket.
+ #[required]
+ pub body: Content,
+
+ /// The optional content above the tortoise shell bracket.
+ #[positional]
+ pub annotation: Option<Content>,
+}
diff --git a/crates/typst/src/model/bibliography.rs b/crates/typst-library/src/model/bibliography.rs
index ebc2c003..d11055b9 100644
--- a/crates/typst/src/model/bibliography.rs
+++ b/crates/typst-library/src/model/bibliography.rs
@@ -18,10 +18,11 @@ use indexmap::IndexMap;
use once_cell::sync::Lazy;
use smallvec::{smallvec, SmallVec};
use typed_arena::Arena;
+use typst_syntax::{Span, Spanned};
+use typst_utils::{LazyHash, NonZeroExt, PicoStr};
use crate::diag::{bail, error, At, FileError, HintedStrResult, SourceResult, StrResult};
use crate::engine::Engine;
-use crate::eval::{eval_string, EvalMode};
use crate::foundations::{
cast, elem, ty, Args, Array, Bytes, CastInfo, Content, FromValue, IntoValue, Label,
NativeElement, Packed, Reflect, Repr, Scope, Show, ShowSet, Smart, Str, StyleChain,
@@ -36,12 +37,10 @@ use crate::model::{
CitationForm, CiteGroup, Destination, FootnoteElem, HeadingElem, LinkElem, ParElem,
Url,
};
-
-use crate::syntax::{Span, Spanned};
+use crate::routines::{EvalMode, Routines};
use crate::text::{
FontStyle, Lang, LocalName, Region, SubElem, SuperElem, TextElem, WeightDelta,
};
-use crate::utils::{LazyHash, NonZeroExt, PicoStr};
use crate::World;
/// A bibliography / reference listing.
@@ -225,7 +224,7 @@ impl Show for Packed<BibliographyElem> {
}
let span = self.span();
- let works = Works::generate(engine.world, engine.introspector).at(span)?;
+ let works = Works::generate(engine).at(span)?;
let references = works
.references
.as_ref()
@@ -360,7 +359,7 @@ impl Bibliography {
Ok(Bibliography {
map: Arc::new(map),
- hash: crate::utils::hash128(data),
+ hash: typst_utils::hash128(data),
})
}
@@ -553,12 +552,18 @@ pub(super) struct Works {
impl Works {
/// Generate all citations and the whole bibliography.
+ pub fn generate(engine: &Engine) -> StrResult<Arc<Works>> {
+ Self::generate_impl(engine.routines, engine.world, engine.introspector)
+ }
+
+ /// The internal implementation of [`Works::generate`].
#[comemo::memoize]
- pub fn generate(
+ fn generate_impl(
+ routines: &Routines,
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
) -> StrResult<Arc<Works>> {
- let mut generator = Generator::new(world, introspector)?;
+ let mut generator = Generator::new(routines, world, introspector)?;
let rendered = generator.drive();
let works = generator.display(&rendered)?;
Ok(Arc::new(works))
@@ -567,6 +572,8 @@ impl Works {
/// Context for generating the bibliography.
struct Generator<'a> {
+ /// The routines that is used to evaluate mathematical material in citations.
+ routines: &'a Routines,
/// The world that is used to evaluate mathematical material in citations.
world: Tracked<'a, dyn World + 'a>,
/// The document's bibliography.
@@ -607,6 +614,7 @@ struct CiteInfo {
impl<'a> Generator<'a> {
/// Create a new generator.
fn new(
+ routines: &'a Routines,
world: Tracked<'a, dyn World + 'a>,
introspector: Tracked<Introspector>,
) -> StrResult<Self> {
@@ -614,6 +622,7 @@ impl<'a> Generator<'a> {
let groups = introspector.query(&CiteGroup::elem().select());
let infos = Vec::with_capacity(groups.len());
Ok(Self {
+ routines,
world,
bibliography,
groups,
@@ -770,6 +779,7 @@ impl<'a> Generator<'a> {
let link = |i: usize| links.get(info.subinfos.get(i)?.key.as_str()).copied();
let renderer = ElemRenderer {
+ routines: self.routines,
world: self.world,
span: info.span,
supplement: &supplement,
@@ -822,6 +832,7 @@ impl<'a> Generator<'a> {
let mut output = vec![];
for (k, item) in rendered.items.iter().enumerate() {
let renderer = ElemRenderer {
+ routines: self.routines,
world: self.world,
span: self.bibliography.span(),
supplement: &|_| None,
@@ -865,6 +876,8 @@ impl<'a> Generator<'a> {
/// Renders hayagriva elements into content.
struct ElemRenderer<'a> {
+ /// The routines that is used to evaluate mathematical material in citations.
+ routines: &'a Routines,
/// The world that is used to evaluate mathematical material.
world: Tracked<'a, dyn World + 'a>,
/// The span that is attached to all of the resulting content.
@@ -984,9 +997,16 @@ impl ElemRenderer<'_> {
/// Display math.
fn display_math(&self, math: &str) -> Content {
- eval_string(self.world, math, self.span, EvalMode::Math, Scope::new())
- .map(Value::display)
- .unwrap_or_else(|_| TextElem::packed(math).spanned(self.span))
+ (self.routines.eval_string)(
+ self.routines,
+ self.world,
+ math,
+ self.span,
+ EvalMode::Math,
+ Scope::new(),
+ )
+ .map(Value::display)
+ .unwrap_or_else(|_| TextElem::packed(math).spanned(self.span))
}
/// Display a link.
diff --git a/crates/typst/src/model/cite.rs b/crates/typst-library/src/model/cite.rs
index e156e0ec..ac0cfa79 100644
--- a/crates/typst/src/model/cite.rs
+++ b/crates/typst-library/src/model/cite.rs
@@ -154,7 +154,7 @@ impl Show for Packed<CiteGroup> {
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
let location = self.location().unwrap();
let span = self.span();
- Works::generate(engine.world, engine.introspector)
+ Works::generate(engine)
.at(span)?
.citations
.get(&location)
diff --git a/crates/typst/src/model/document.rs b/crates/typst-library/src/model/document.rs
index b693d785..b693d785 100644
--- a/crates/typst/src/model/document.rs
+++ b/crates/typst-library/src/model/document.rs
diff --git a/crates/typst/src/model/emph.rs b/crates/typst-library/src/model/emph.rs
index e36e5ef7..e36e5ef7 100644
--- a/crates/typst/src/model/emph.rs
+++ b/crates/typst-library/src/model/emph.rs
diff --git a/crates/typst/src/model/enum.rs b/crates/typst-library/src/model/enum.rs
index fcb62f89..bac792d3 100644
--- a/crates/typst/src/model/enum.rs
+++ b/crates/typst-library/src/model/enum.rs
@@ -1,21 +1,15 @@
use std::str::FromStr;
-use comemo::Track;
-use smallvec::{smallvec, SmallVec};
+use smallvec::SmallVec;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, scope, Array, Content, Context, NativeElement, Packed, Show, Smart,
- StyleChain, Styles,
-};
-use crate::introspection::Locator;
-use crate::layout::{
- Alignment, Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlignment,
- Length, Regions, Sizing, VAlignment, VElem,
+ cast, elem, scope, Array, Content, NativeElement, Packed, Show, Smart, StyleChain,
+ Styles,
};
+use crate::layout::{Alignment, BlockElem, Em, HAlignment, Length, VAlignment, VElem};
use crate::model::{ListItemLike, ListLike, Numbering, NumberingPattern, ParElem};
-use crate::text::TextElem;
/// A numbered list.
///
@@ -209,7 +203,7 @@ pub struct EnumElem {
#[internal]
#[fold]
#[ghost]
- parents: SmallVec<[usize; 4]>,
+ pub parents: SmallVec<[usize; 4]>,
}
#[scope]
@@ -219,10 +213,11 @@ impl EnumElem {
}
impl Show for Packed<EnumElem> {
- fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- let mut realized = BlockElem::multi_layouter(self.clone(), layout_enum)
- .pack()
- .spanned(self.span());
+ fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ let mut realized =
+ BlockElem::multi_layouter(self.clone(), engine.routines.layout_enum)
+ .pack()
+ .spanned(self.span());
if self.tight(styles) {
let leading = ParElem::leading_in(styles);
@@ -235,87 +230,6 @@ impl Show for Packed<EnumElem> {
}
}
-/// Layout the enumeration.
-#[typst_macros::time(span = elem.span())]
-fn layout_enum(
- elem: &Packed<EnumElem>,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- regions: Regions,
-) -> SourceResult<Fragment> {
- let numbering = elem.numbering(styles);
- let indent = elem.indent(styles);
- let body_indent = elem.body_indent(styles);
- let gutter = elem.spacing(styles).unwrap_or_else(|| {
- if elem.tight(styles) {
- ParElem::leading_in(styles).into()
- } else {
- ParElem::spacing_in(styles).into()
- }
- });
-
- let mut cells = vec![];
- let mut locator = locator.split();
- let mut number = elem.start(styles);
- let mut parents = EnumElem::parents_in(styles);
-
- let full = elem.full(styles);
-
- // Horizontally align based on the given respective parameter.
- // Vertically align to the top to avoid inheriting `horizon` or `bottom`
- // alignment from the context and having the number be displaced in
- // relation to the item it refers to.
- let number_align = elem.number_align(styles);
-
- for item in elem.children() {
- number = item.number(styles).unwrap_or(number);
-
- let context = Context::new(None, Some(styles));
- let resolved = if full {
- parents.push(number);
- let content = numbering.apply(engine, context.track(), &parents)?.display();
- parents.pop();
- content
- } else {
- match numbering {
- Numbering::Pattern(pattern) => {
- TextElem::packed(pattern.apply_kth(parents.len(), number))
- }
- other => other.apply(engine, context.track(), &[number])?.display(),
- }
- };
-
- // Disable overhang as a workaround to end-aligned dots glitching
- // and decreasing spacing between numbers and items.
- let resolved =
- resolved.aligned(number_align).styled(TextElem::set_overhang(false));
-
- cells.push(Cell::new(Content::empty(), locator.next(&())));
- cells.push(Cell::new(resolved, locator.next(&())));
- cells.push(Cell::new(Content::empty(), locator.next(&())));
- cells.push(Cell::new(
- item.body.clone().styled(EnumElem::set_parents(smallvec![number])),
- locator.next(&item.body.span()),
- ));
- number = number.saturating_add(1);
- }
-
- let grid = CellGrid::new(
- Axes::with_x(&[
- Sizing::Rel(indent.into()),
- Sizing::Auto,
- Sizing::Rel(body_indent.into()),
- Sizing::Auto,
- ]),
- Axes::with_y(&[gutter.into()]),
- cells,
- );
- let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
-
- layouter.layout(engine)
-}
-
/// An enumeration item.
#[elem(name = "item", title = "Numbered List Item")]
pub struct EnumItem {
diff --git a/crates/typst/src/model/figure.rs b/crates/typst-library/src/model/figure.rs
index 1d3c9c95..abdf2a4e 100644
--- a/crates/typst/src/model/figure.rs
+++ b/crates/typst-library/src/model/figure.rs
@@ -3,6 +3,7 @@ use std::num::NonZeroUsize;
use std::str::FromStr;
use ecow::EcoString;
+use typst_utils::NonZeroExt;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
@@ -19,7 +20,6 @@ use crate::layout::{
};
use crate::model::{Numbering, NumberingPattern, Outlinable, Refable, Supplement};
use crate::text::{Lang, Region, TextElem};
-use crate::utils::NonZeroExt;
use crate::visualize::ImageElem;
/// A figure with an optional caption.
diff --git a/crates/typst/src/model/footnote.rs b/crates/typst-library/src/model/footnote.rs
index 5468b4f5..d9971dd1 100644
--- a/crates/typst/src/model/footnote.rs
+++ b/crates/typst-library/src/model/footnote.rs
@@ -1,6 +1,8 @@
use std::num::NonZeroUsize;
use std::str::FromStr;
+use typst_utils::NonZeroExt;
+
use crate::diag::{bail, At, SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
@@ -11,7 +13,6 @@ use crate::introspection::{Count, Counter, CounterUpdate, Locatable, Location};
use crate::layout::{Abs, Em, HElem, Length, Ratio};
use crate::model::{Destination, Numbering, NumberingPattern, ParElem};
use crate::text::{SuperElem, TextElem, TextSize};
-use crate::utils::NonZeroExt;
use crate::visualize::{LineElem, Stroke};
/// A footnote.
diff --git a/crates/typst/src/model/heading.rs b/crates/typst-library/src/model/heading.rs
index 57717f7d..269e95eb 100644
--- a/crates/typst/src/model/heading.rs
+++ b/crates/typst-library/src/model/heading.rs
@@ -1,5 +1,7 @@
use std::num::NonZeroUsize;
+use typst_utils::NonZeroExt;
+
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{
@@ -9,12 +11,9 @@ use crate::foundations::{
use crate::introspection::{
Count, Counter, CounterUpdate, Locatable, Locator, LocatorLink,
};
-use crate::layout::{
- layout_frame, Abs, Axes, BlockBody, BlockElem, Em, HElem, Length, Region,
-};
+use crate::layout::{Abs, Axes, BlockBody, BlockElem, Em, HElem, Length, Region};
use crate::model::{Numbering, Outlinable, ParElem, Refable, Supplement};
use crate::text::{FontWeight, LocalName, SpaceElem, TextElem, TextSize};
-use crate::utils::NonZeroExt;
/// A section heading.
///
@@ -240,9 +239,14 @@ impl Show for Packed<HeadingElem> {
// We don't have a locator for the numbering here, so we just
// use the measurement infrastructure for now.
let link = LocatorLink::measure(location);
- let size =
- layout_frame(engine, &numbering, Locator::link(&link), styles, pod)?
- .size();
+ let size = (engine.routines.layout_frame)(
+ engine,
+ &numbering,
+ Locator::link(&link),
+ styles,
+ pod,
+ )?
+ .size();
indent = size.x + SPACING_TO_NUMBERING.resolve(styles);
}
diff --git a/crates/typst/src/model/link.rs b/crates/typst-library/src/model/link.rs
index 31c65a1d..31c65a1d 100644
--- a/crates/typst/src/model/link.rs
+++ b/crates/typst-library/src/model/link.rs
diff --git a/crates/typst/src/model/list.rs b/crates/typst-library/src/model/list.rs
index 3a2bd651..18bddd10 100644
--- a/crates/typst/src/model/list.rs
+++ b/crates/typst-library/src/model/list.rs
@@ -6,11 +6,7 @@ use crate::foundations::{
cast, elem, scope, Array, Content, Context, Depth, Func, NativeElement, Packed, Show,
Smart, StyleChain, Styles, Value,
};
-use crate::introspection::Locator;
-use crate::layout::{
- Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlignment, Length,
- Regions, Sizing, VAlignment, VElem,
-};
+use crate::layout::{BlockElem, Em, Length, VElem};
use crate::model::ParElem;
use crate::text::TextElem;
@@ -133,7 +129,7 @@ pub struct ListElem {
#[internal]
#[fold]
#[ghost]
- depth: Depth,
+ pub depth: Depth,
}
#[scope]
@@ -143,10 +139,11 @@ impl ListElem {
}
impl Show for Packed<ListElem> {
- fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- let mut realized = BlockElem::multi_layouter(self.clone(), layout_list)
- .pack()
- .spanned(self.span());
+ fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ let mut realized =
+ BlockElem::multi_layouter(self.clone(), engine.routines.layout_list)
+ .pack()
+ .spanned(self.span());
if self.tight(styles) {
let leading = ParElem::leading_in(styles);
@@ -159,60 +156,6 @@ impl Show for Packed<ListElem> {
}
}
-/// Layout the list.
-#[typst_macros::time(span = elem.span())]
-fn layout_list(
- elem: &Packed<ListElem>,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- regions: Regions,
-) -> SourceResult<Fragment> {
- let indent = elem.indent(styles);
- let body_indent = elem.body_indent(styles);
- let gutter = elem.spacing(styles).unwrap_or_else(|| {
- if elem.tight(styles) {
- ParElem::leading_in(styles).into()
- } else {
- ParElem::spacing_in(styles).into()
- }
- });
-
- let Depth(depth) = ListElem::depth_in(styles);
- let marker = elem
- .marker(styles)
- .resolve(engine, styles, depth)?
- // avoid '#set align' interference with the list
- .aligned(HAlignment::Start + VAlignment::Top);
-
- let mut cells = vec![];
- let mut locator = locator.split();
-
- for item in elem.children() {
- cells.push(Cell::new(Content::empty(), locator.next(&())));
- cells.push(Cell::new(marker.clone(), locator.next(&marker.span())));
- cells.push(Cell::new(Content::empty(), locator.next(&())));
- cells.push(Cell::new(
- item.body.clone().styled(ListElem::set_depth(Depth(1))),
- locator.next(&item.body.span()),
- ));
- }
-
- let grid = CellGrid::new(
- Axes::with_x(&[
- Sizing::Rel(indent.into()),
- Sizing::Auto,
- Sizing::Rel(body_indent.into()),
- Sizing::Auto,
- ]),
- Axes::with_y(&[gutter.into()]),
- cells,
- );
- let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
-
- layouter.layout(engine)
-}
-
/// A bullet list item.
#[elem(name = "item", title = "Bullet List Item")]
pub struct ListItem {
@@ -235,7 +178,7 @@ pub enum ListMarker {
impl ListMarker {
/// Resolve the marker for the given depth.
- fn resolve(
+ pub fn resolve(
&self,
engine: &mut Engine,
styles: StyleChain,
diff --git a/crates/typst/src/model/mod.rs b/crates/typst-library/src/model/mod.rs
index 7dad51c3..7dad51c3 100644
--- a/crates/typst/src/model/mod.rs
+++ b/crates/typst-library/src/model/mod.rs
diff --git a/crates/typst/src/model/numbering.rs b/crates/typst-library/src/model/numbering.rs
index f0aa06e5..f0aa06e5 100644
--- a/crates/typst/src/model/numbering.rs
+++ b/crates/typst-library/src/model/numbering.rs
diff --git a/crates/typst/src/model/outline.rs b/crates/typst-library/src/model/outline.rs
index b89a1524..85257c2c 100644
--- a/crates/typst/src/model/outline.rs
+++ b/crates/typst-library/src/model/outline.rs
@@ -2,6 +2,8 @@ use std::num::NonZeroUsize;
use std::str::FromStr;
use comemo::Track;
+use typst_syntax::Span;
+use typst_utils::NonZeroExt;
use crate::diag::{bail, At, SourceResult};
use crate::engine::Engine;
@@ -16,9 +18,7 @@ use crate::layout::{
use crate::model::{
Destination, HeadingElem, NumberingPattern, ParElem, ParbreakElem, Refable,
};
-use crate::syntax::Span;
use crate::text::{LinebreakElem, LocalName, SpaceElem, TextElem};
-use crate::utils::NonZeroExt;
/// A table of contents, figures, or other elements.
///
diff --git a/crates/typst/src/model/par.rs b/crates/typst-library/src/model/par.rs
index 15bad61f..a43499d8 100644
--- a/crates/typst/src/model/par.rs
+++ b/crates/typst-library/src/model/par.rs
@@ -1,5 +1,7 @@
use std::fmt::{self, Debug, Formatter};
+use typst_utils::singleton;
+
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
@@ -9,7 +11,6 @@ use crate::foundations::{
use crate::introspection::{Count, CounterUpdate, Locatable};
use crate::layout::{Em, HAlignment, Length, OuterHAlignment};
use crate::model::Numbering;
-use crate::utils::singleton;
/// Arranges text, spacing and inline-level elements into a paragraph.
///
diff --git a/crates/typst/src/model/quote.rs b/crates/typst-library/src/model/quote.rs
index 110825f1..110825f1 100644
--- a/crates/typst/src/model/quote.rs
+++ b/crates/typst-library/src/model/quote.rs
diff --git a/crates/typst/src/model/reference.rs b/crates/typst-library/src/model/reference.rs
index bc1919d6..bc1919d6 100644
--- a/crates/typst/src/model/reference.rs
+++ b/crates/typst-library/src/model/reference.rs
diff --git a/crates/typst/src/model/strong.rs b/crates/typst-library/src/model/strong.rs
index 0e23179e..0e23179e 100644
--- a/crates/typst/src/model/strong.rs
+++ b/crates/typst-library/src/model/strong.rs
diff --git a/crates/typst/src/model/table.rs b/crates/typst-library/src/model/table.rs
index 74b12196..7dfaf45d 100644
--- a/crates/typst/src/model/table.rs
+++ b/crates/typst-library/src/model/table.rs
@@ -1,24 +1,20 @@
use std::num::NonZeroUsize;
use std::sync::Arc;
-use ecow::eco_format;
+use typst_utils::NonZeroExt;
-use crate::diag::{bail, HintedStrResult, HintedString, SourceResult, Trace, Tracepoint};
+use crate::diag::{bail, HintedStrResult, HintedString, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, scope, Content, Fold, NativeElement, Packed, Show, Smart, StyleChain,
+ cast, elem, scope, Content, NativeElement, Packed, Show, Smart, StyleChain,
};
-use crate::introspection::Locator;
use crate::layout::{
- show_grid_cell, Abs, Alignment, Axes, BlockElem, Cell, CellGrid, Celled, Dir,
- Fragment, GridCell, GridFooter, GridHLine, GridHeader, GridLayouter, GridVLine,
- Length, LinePosition, OuterHAlignment, OuterVAlignment, Regions, Rel, ResolvableCell,
- ResolvableGridChild, ResolvableGridItem, Sides, TrackSizings,
+ show_grid_cell, Abs, Alignment, BlockElem, Celled, GridCell, GridFooter, GridHLine,
+ GridHeader, GridVLine, Length, OuterHAlignment, OuterVAlignment, Rel, Sides,
+ TrackSizings,
};
use crate::model::Figurable;
-use crate::syntax::Span;
-use crate::text::{LocalName, TextElem};
-use crate::utils::NonZeroExt;
+use crate::text::LocalName;
use crate::visualize::{Paint, Stroke};
/// A table of items.
@@ -263,68 +259,13 @@ impl TableElem {
}
impl Show for Packed<TableElem> {
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::multi_layouter(self.clone(), layout_table)
+ fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::multi_layouter(self.clone(), engine.routines.layout_table)
.pack()
.spanned(self.span()))
}
}
-/// Layout the table.
-#[typst_macros::time(span = elem.span())]
-fn layout_table(
- elem: &Packed<TableElem>,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- regions: Regions,
-) -> SourceResult<Fragment> {
- let inset = elem.inset(styles);
- let align = elem.align(styles);
- let columns = elem.columns(styles);
- let rows = elem.rows(styles);
- let column_gutter = elem.column_gutter(styles);
- let row_gutter = elem.row_gutter(styles);
- let fill = elem.fill(styles);
- let stroke = elem.stroke(styles);
-
- let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice());
- let gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice());
- // Use trace to link back to the table when a specific cell errors
- let tracepoint = || Tracepoint::Call(Some(eco_format!("table")));
- let resolve_item = |item: &TableItem| item.to_resolvable(styles);
- let children = elem.children().iter().map(|child| match child {
- TableChild::Header(header) => ResolvableGridChild::Header {
- repeat: header.repeat(styles),
- span: header.span(),
- items: header.children().iter().map(resolve_item),
- },
- TableChild::Footer(footer) => ResolvableGridChild::Footer {
- repeat: footer.repeat(styles),
- span: footer.span(),
- items: footer.children().iter().map(resolve_item),
- },
- TableChild::Item(item) => ResolvableGridChild::Item(item.to_resolvable(styles)),
- });
- let grid = CellGrid::resolve(
- tracks,
- gutter,
- locator,
- children,
- fill,
- align,
- &inset,
- &stroke,
- engine,
- styles,
- elem.span(),
- )
- .trace(engine.world, tracepoint, elem.span())?;
-
- let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
- layouter.layout(engine)
-}
-
impl LocalName for Packed<TableElem> {
const KEY: &'static str = "table";
}
@@ -384,44 +325,6 @@ pub enum TableItem {
Cell(Packed<TableCell>),
}
-impl TableItem {
- fn to_resolvable(&self, styles: StyleChain) -> ResolvableGridItem<Packed<TableCell>> {
- match self {
- Self::HLine(hline) => ResolvableGridItem::HLine {
- y: hline.y(styles),
- start: hline.start(styles),
- end: hline.end(styles),
- stroke: hline.stroke(styles),
- span: hline.span(),
- position: match hline.position(styles) {
- OuterVAlignment::Top => LinePosition::Before,
- OuterVAlignment::Bottom => LinePosition::After,
- },
- },
- Self::VLine(vline) => ResolvableGridItem::VLine {
- x: vline.x(styles),
- start: vline.start(styles),
- end: vline.end(styles),
- stroke: vline.stroke(styles),
- span: vline.span(),
- position: match vline.position(styles) {
- OuterHAlignment::Left if TextElem::dir_in(styles) == Dir::RTL => {
- LinePosition::After
- }
- OuterHAlignment::Right if TextElem::dir_in(styles) == Dir::RTL => {
- LinePosition::Before
- }
- OuterHAlignment::Start | OuterHAlignment::Left => {
- LinePosition::Before
- }
- OuterHAlignment::End | OuterHAlignment::Right => LinePosition::After,
- },
- },
- Self::Cell(cell) => ResolvableGridItem::Cell(cell.clone()),
- }
- }
-}
-
cast! {
TableItem,
self => match self {
@@ -773,7 +676,7 @@ pub struct TableCell {
/// The amount of rows spanned by this cell.
#[default(NonZeroUsize::ONE)]
- rowspan: NonZeroUsize,
+ pub rowspan: NonZeroUsize,
/// The cell's [fill]($table.fill) override.
pub fill: Smart<Option<Paint>>,
@@ -801,113 +704,18 @@ cast! {
v: Content => v.into(),
}
-impl Default for Packed<TableCell> {
- fn default() -> Self {
- Packed::new(TableCell::new(Content::default()))
- }
-}
-
-impl ResolvableCell for Packed<TableCell> {
- fn resolve_cell<'a>(
- mut self,
- x: usize,
- y: usize,
- fill: &Option<Paint>,
- align: Smart<Alignment>,
- inset: Sides<Option<Rel<Length>>>,
- stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
- breakable: bool,
- locator: Locator<'a>,
- styles: StyleChain,
- ) -> Cell<'a> {
- let cell = &mut *self;
- let colspan = cell.colspan(styles);
- let rowspan = cell.rowspan(styles);
- let breakable = cell.breakable(styles).unwrap_or(breakable);
- let fill = cell.fill(styles).unwrap_or_else(|| fill.clone());
-
- let cell_stroke = cell.stroke(styles);
- let stroke_overridden =
- cell_stroke.as_ref().map(|side| matches!(side, Some(Some(_))));
-
- // Using a typical 'Sides' fold, an unspecified side loses to a
- // specified side. Additionally, when both are specified, an inner
- // None wins over the outer Some, and vice-versa. When both are
- // specified and Some, fold occurs, which, remarkably, leads to an Arc
- // clone.
- //
- // In the end, we flatten because, for layout purposes, an unspecified
- // cell stroke is the same as specifying 'none', so we equate the two
- // concepts.
- let stroke = cell_stroke.fold(stroke).map(Option::flatten);
- cell.push_x(Smart::Custom(x));
- cell.push_y(Smart::Custom(y));
- cell.push_fill(Smart::Custom(fill.clone()));
- cell.push_align(match align {
- Smart::Custom(align) => {
- Smart::Custom(cell.align(styles).map_or(align, |inner| inner.fold(align)))
- }
- // Don't fold if the table is using outer alignment. Use the
- // cell's alignment instead (which, in the end, will fold with
- // the outer alignment when it is effectively displayed).
- Smart::Auto => cell.align(styles),
- });
- cell.push_inset(Smart::Custom(
- cell.inset(styles).map_or(inset, |inner| inner.fold(inset)),
- ));
- cell.push_stroke(
- // Here we convert the resolved stroke to a regular stroke, however
- // with resolved units (that is, 'em' converted to absolute units).
- // We also convert any stroke unspecified by both the cell and the
- // outer stroke ('None' in the folded stroke) to 'none', that is,
- // all sides are present in the resulting Sides object accessible
- // by show rules on table cells.
- stroke.as_ref().map(|side| {
- Some(side.as_ref().map(|cell_stroke| {
- Arc::new((**cell_stroke).clone().map(Length::from))
- }))
- }),
- );
- cell.push_breakable(Smart::Custom(breakable));
- Cell {
- body: self.pack(),
- locator,
- fill,
- colspan,
- rowspan,
- stroke,
- stroke_overridden,
- breakable,
- }
- }
-
- fn x(&self, styles: StyleChain) -> Smart<usize> {
- (**self).x(styles)
- }
-
- fn y(&self, styles: StyleChain) -> Smart<usize> {
- (**self).y(styles)
- }
-
- fn colspan(&self, styles: StyleChain) -> NonZeroUsize {
- (**self).colspan(styles)
- }
-
- fn rowspan(&self, styles: StyleChain) -> NonZeroUsize {
- (**self).rowspan(styles)
- }
-
- fn span(&self) -> Span {
- Packed::span(self)
- }
-}
-
impl Show for Packed<TableCell> {
fn show(&self, _engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
show_grid_cell(self.body().clone(), self.inset(styles), self.align(styles))
}
}
+impl Default for Packed<TableCell> {
+ fn default() -> Self {
+ Packed::new(TableCell::new(Content::default()))
+ }
+}
+
impl From<Content> for TableCell {
fn from(value: Content) -> Self {
#[allow(clippy::unwrap_or_default)]
diff --git a/crates/typst/src/model/terms.rs b/crates/typst-library/src/model/terms.rs
index 557895f7..036a03e2 100644
--- a/crates/typst/src/model/terms.rs
+++ b/crates/typst-library/src/model/terms.rs
@@ -1,3 +1,5 @@
+use typst_utils::Numeric;
+
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
@@ -7,7 +9,6 @@ use crate::foundations::{
use crate::layout::{Dir, Em, HElem, Length, Sides, StackChild, StackElem, VElem};
use crate::model::{ListItemLike, ListLike, ParElem};
use crate::text::TextElem;
-use crate::utils::Numeric;
/// A list of terms and their descriptions.
///
diff --git a/crates/typst-library/src/routines.rs b/crates/typst-library/src/routines.rs
new file mode 100644
index 00000000..6b78b7fb
--- /dev/null
+++ b/crates/typst-library/src/routines.rs
@@ -0,0 +1,368 @@
+#![allow(unused)]
+
+use std::hash::{Hash, Hasher};
+use std::num::NonZeroUsize;
+
+use comemo::{Tracked, TrackedMut};
+use typst_syntax::Span;
+use typst_utils::LazyHash;
+
+use crate::diag::SourceResult;
+use crate::engine::{Engine, Route, Sink, Traced};
+use crate::foundations::{
+ Args, Cast, Closure, Content, Context, Func, Packed, Scope, StyleChain, StyleVec,
+ Styles, Value,
+};
+use crate::introspection::{Introspector, Locator, SplitLocator};
+use crate::layout::{
+ Abs, BoxElem, ColumnsElem, Fragment, Frame, GridElem, InlineItem, MoveElem, PadElem,
+ Region, Regions, Rel, RepeatElem, RotateElem, ScaleElem, Size, SkewElem, StackElem,
+};
+use crate::math::EquationElem;
+use crate::model::{Document, DocumentInfo, EnumElem, ListElem, TableElem};
+use crate::visualize::{
+ CircleElem, EllipseElem, ImageElem, LineElem, PathElem, PolygonElem, RectElem,
+ SquareElem,
+};
+use crate::World;
+
+/// Defines the `Routines` struct.
+macro_rules! routines {
+ ($(
+ $(#[$attr:meta])*
+ fn $name:ident $(<$($time:lifetime),*>)? ($($args:tt)*) -> $ret:ty
+ )*) => {
+ /// Defines implementation of various Typst compiler routines as a table
+ /// of function pointers.
+ ///
+ /// This is essentially dynamic linking and done to allow for crate
+ /// splitting.
+ pub struct Routines {
+ $(
+ $(#[$attr])*
+ pub $name: $(for<$($time),*>)? fn ($($args)*) -> $ret
+ ),*
+ }
+
+ impl Hash for Routines {
+ fn hash<H: Hasher>(&self, _: &mut H) {}
+ }
+ };
+}
+
+routines! {
+ /// Evaluates a string as code and return the resulting value.
+ fn eval_string(
+ routines: &Routines,
+ world: Tracked<dyn World + '_>,
+ string: &str,
+ span: Span,
+ mode: EvalMode,
+ scope: Scope,
+ ) -> SourceResult<Value>
+
+ /// Call the closure in the context with the arguments.
+ fn eval_closure(
+ func: &Func,
+ closure: &LazyHash<Closure>,
+ routines: &Routines,
+ world: Tracked<dyn World + '_>,
+ introspector: Tracked<Introspector>,
+ traced: Tracked<Traced>,
+ sink: TrackedMut<Sink>,
+ route: Tracked<Route>,
+ context: Tracked<Context>,
+ args: Args,
+ ) -> SourceResult<Value>
+
+ /// Realizes content into a flat list of well-known, styled items.
+ fn realize<'a>(
+ kind: RealizationKind,
+ engine: &mut Engine,
+ locator: &mut SplitLocator,
+ arenas: &'a Arenas,
+ content: &'a Content,
+ styles: StyleChain<'a>,
+ ) -> SourceResult<Vec<Pair<'a>>>
+
+ /// Layout content into a document.
+ fn layout_document(
+ engine: &mut Engine,
+ content: &Content,
+ styles: StyleChain,
+ ) -> SourceResult<Document>
+
+ /// Lays out content into multiple regions.
+ fn layout_fragment(
+ engine: &mut Engine,
+ content: &Content,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment>
+
+ /// Lays out content into a single region, producing a single frame.
+ fn layout_frame(
+ engine: &mut Engine,
+ content: &Content,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>
+
+ /// Lays out inline content.
+ fn layout_inline(
+ engine: &mut Engine,
+ children: &StyleVec,
+ locator: Locator,
+ styles: StyleChain,
+ consecutive: bool,
+ region: Size,
+ expand: bool,
+ ) -> SourceResult<Fragment>
+
+ /// Lays out a [`BoxElem`].
+ fn layout_box(
+ elem: &Packed<BoxElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Size,
+ ) -> SourceResult<Frame>
+
+ /// Lays out a [`ListElem`].
+ fn layout_list(
+ elem: &Packed<ListElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment>
+
+ /// Lays out an [`EnumElem`].
+ fn layout_enum(
+ elem: &Packed<EnumElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment>
+
+ /// Lays out a [`GridElem`].
+ fn layout_grid(
+ elem: &Packed<GridElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment>
+
+ /// Lays out a [`TableElem`].
+ fn layout_table(
+ elem: &Packed<TableElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment>
+
+ /// Lays out a [`StackElem`].
+ fn layout_stack(
+ elem: &Packed<StackElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment>
+
+ /// Lays out a [`ColumnsElem`].
+ fn layout_columns(
+ elem: &Packed<ColumnsElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment>
+
+ /// Lays out a [`MoveElem`].
+ fn layout_move(
+ elem: &Packed<MoveElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>
+
+ /// Lays out a [`RotateElem`].
+ fn layout_rotate(
+ elem: &Packed<RotateElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>
+
+ /// Lays out a [`ScaleElem`].
+ fn layout_scale(
+ elem: &Packed<ScaleElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>
+
+ /// Lays out a [`SkewElem`].
+ fn layout_skew(
+ elem: &Packed<SkewElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>
+
+ /// Lays out a [`RepeatElem`].
+ fn layout_repeat(
+ elem: &Packed<RepeatElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>
+
+ /// Lays out a [`PadElem`].
+ fn layout_pad(
+ elem: &Packed<PadElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment>
+
+ /// Lays out a [`LineElem`].
+ fn layout_line(
+ elem: &Packed<LineElem>,
+ _: &mut Engine,
+ _: Locator,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>
+
+ /// Lays out a [`PathElem`].
+ fn layout_path(
+ elem: &Packed<PathElem>,
+ _: &mut Engine,
+ _: Locator,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>
+
+ /// Lays out a [`PolygonElem`].
+ fn layout_polygon(
+ elem: &Packed<PolygonElem>,
+ _: &mut Engine,
+ _: Locator,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>
+
+ /// Lays out a [`RectElem`].
+ fn layout_rect(
+ elem: &Packed<RectElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>
+
+ /// Lays out a [`SquareElem`].
+ fn layout_square(
+ elem: &Packed<SquareElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>
+
+ /// Lays out a [`EllipseElem`].
+ fn layout_ellipse(
+ elem: &Packed<EllipseElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>
+
+ /// Lays out a [`CircleElem`].
+ fn layout_circle(
+ elem: &Packed<CircleElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>
+
+ /// Lays out an [`ImageElem`].
+ fn layout_image(
+ elem: &Packed<ImageElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+ ) -> SourceResult<Frame>
+
+ /// Lays out an [`EquationElem`] in a paragraph.
+ fn layout_equation_inline(
+ elem: &Packed<EquationElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Size,
+ ) -> SourceResult<Vec<InlineItem>>
+
+ /// Lays out an [`EquationElem`] in a flow.
+ fn layout_equation_block(
+ elem: &Packed<EquationElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment>
+}
+
+/// In which mode to evaluate a string.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
+pub enum EvalMode {
+ /// Evaluate as code, as after a hash.
+ Code,
+ /// Evaluate as markup, like in a Typst file.
+ Markup,
+ /// Evaluate as math, as in an equation.
+ Math,
+}
+
+/// Defines what kind of realization we are performing.
+pub enum RealizationKind<'a> {
+ /// This the root realization for the document. Requires a mutable reference
+ /// to document metadata that will be filled from `set document` rules.
+ Root(&'a mut DocumentInfo),
+ /// A nested realization in a container (e.g. a `block`).
+ Container,
+ /// A realization within math.
+ Math,
+}
+
+/// Temporary storage arenas for lifetime extension during realization.
+///
+/// Must be kept live while the content returned from realization is processed.
+#[derive(Default)]
+pub struct Arenas {
+ /// A typed arena for owned content.
+ pub content: typed_arena::Arena<Content>,
+ /// A typed arena for owned styles.
+ pub styles: typed_arena::Arena<Styles>,
+ /// An untyped arena for everything that is `Copy`.
+ pub bump: bumpalo::Bump,
+}
+
+/// A pair of content and a style chain that applies to it.
+pub type Pair<'a> = (&'a Content, StyleChain<'a>);
diff --git a/crates/typst/src/symbols/emoji.rs b/crates/typst-library/src/symbols/emoji.rs
index e6b2bbf9..1bd57ec0 100644
--- a/crates/typst/src/symbols/emoji.rs
+++ b/crates/typst-library/src/symbols/emoji.rs
@@ -1,5 +1,4 @@
-use crate::foundations::{Module, Scope};
-use crate::symbols::{symbols, Symbol};
+use crate::foundations::{Module, Scope, Symbol};
/// A module with all emoji.
pub fn emoji() -> Module {
@@ -11,7 +10,7 @@ pub fn emoji() -> Module {
}
/// A list of named emoji.
-const EMOJI: &[(&str, Symbol)] = symbols! {
+const EMOJI: &[(&str, Symbol)] = typst_macros::symbols! {
abacus: '🧮',
abc: '🔤',
abcd: '🔡',
diff --git a/crates/typst/src/symbols/mod.rs b/crates/typst-library/src/symbols/mod.rs
index 711ab149..7e0fb55b 100644
--- a/crates/typst/src/symbols/mod.rs
+++ b/crates/typst-library/src/symbols/mod.rs
@@ -2,11 +2,9 @@
mod emoji;
mod sym;
-mod symbol;
pub use self::emoji::*;
pub use self::sym::*;
-pub use self::symbol::*;
use crate::foundations::{category, Category, Scope};
@@ -21,7 +19,6 @@ pub static SYMBOLS: Category;
/// Hook up all `symbol` definitions.
pub(super) fn define(global: &mut Scope) {
global.category(SYMBOLS);
- global.define_type::<Symbol>();
global.define_module(sym());
global.define_module(emoji());
}
diff --git a/crates/typst/src/symbols/sym.rs b/crates/typst-library/src/symbols/sym.rs
index 7e957543..606e44ea 100644
--- a/crates/typst/src/symbols/sym.rs
+++ b/crates/typst-library/src/symbols/sym.rs
@@ -1,5 +1,4 @@
-use crate::foundations::{Module, Scope};
-use crate::symbols::{symbols, Symbol};
+use crate::foundations::{Module, Scope, Symbol};
/// A module with all general symbols.
pub fn sym() -> Module {
@@ -11,7 +10,7 @@ pub fn sym() -> Module {
}
/// The list of general symbols.
-pub(crate) const SYM: &[(&str, Symbol)] = symbols! {
+pub(crate) const SYM: &[(&str, Symbol)] = typst_macros::symbols! {
// Control.
wj: '\u{2060}',
zwj: '\u{200D}',
diff --git a/crates/typst/src/text/case.rs b/crates/typst-library/src/text/case.rs
index 69dbf5e1..69dbf5e1 100644
--- a/crates/typst/src/text/case.rs
+++ b/crates/typst-library/src/text/case.rs
diff --git a/crates/typst/src/text/deco.rs b/crates/typst-library/src/text/deco.rs
index ce98348d..5da7ecec 100644
--- a/crates/typst/src/text/deco.rs
+++ b/crates/typst-library/src/text/deco.rs
@@ -1,19 +1,11 @@
-use kurbo::{BezPath, Line, ParamCurve};
use smallvec::smallvec;
-use ttf_parser::{GlyphId, OutlineBuilder};
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{elem, Content, Packed, Show, Smart, StyleChain};
-use crate::layout::{
- Abs, Corners, Em, Frame, FrameItem, Length, Point, Rel, Sides, Size,
-};
-use crate::syntax::Span;
-use crate::text::{
- BottomEdge, BottomEdgeMetric, TextEdgeBounds, TextElem, TextItem, TopEdge,
- TopEdgeMetric,
-};
-use crate::visualize::{styled_rect, Color, FixedStroke, Geometry, Paint, Stroke};
+use crate::layout::{Abs, Corners, Length, Rel, Sides};
+use crate::text::{BottomEdge, BottomEdgeMetric, TextElem, TopEdge, TopEdgeMetric};
+use crate::visualize::{Color, FixedStroke, Paint, Stroke};
/// Underlines text.
///
@@ -375,13 +367,13 @@ impl Show for Packed<HighlightElem> {
/// a background.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Decoration {
- line: DecoLine,
- extent: Abs,
+ pub line: DecoLine,
+ pub extent: Abs,
}
/// A kind of decorative line.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-enum DecoLine {
+pub enum DecoLine {
Underline {
stroke: Stroke<Abs>,
offset: Smart<Abs>,
@@ -407,206 +399,3 @@ enum DecoLine {
radius: Corners<Rel<Abs>>,
},
}
-
-/// Add line decorations to a single run of shaped text.
-pub(crate) fn decorate(
- frame: &mut Frame,
- deco: &Decoration,
- text: &TextItem,
- width: Abs,
- shift: Abs,
- pos: Point,
-) {
- let font_metrics = text.font.metrics();
-
- if let DecoLine::Highlight { fill, stroke, top_edge, bottom_edge, radius } =
- &deco.line
- {
- let (top, bottom) = determine_edges(text, *top_edge, *bottom_edge);
- let size = Size::new(width + 2.0 * deco.extent, top + bottom);
- let rects = styled_rect(size, radius, fill.clone(), stroke);
- let origin = Point::new(pos.x - deco.extent, pos.y - top - shift);
- frame.prepend_multiple(
- rects
- .into_iter()
- .map(|shape| (origin, FrameItem::Shape(shape, Span::detached()))),
- );
- return;
- }
-
- let (stroke, metrics, offset, evade, background) = match &deco.line {
- DecoLine::Strikethrough { stroke, offset, background } => {
- (stroke, font_metrics.strikethrough, offset, false, *background)
- }
- DecoLine::Overline { stroke, offset, evade, background } => {
- (stroke, font_metrics.overline, offset, *evade, *background)
- }
- DecoLine::Underline { stroke, offset, evade, background } => {
- (stroke, font_metrics.underline, offset, *evade, *background)
- }
- _ => return,
- };
-
- let offset = offset.unwrap_or(-metrics.position.at(text.size)) - shift;
- let stroke = stroke.clone().unwrap_or(FixedStroke::from_pair(
- text.fill.as_decoration(),
- metrics.thickness.at(text.size),
- ));
-
- let gap_padding = 0.08 * text.size;
- let min_width = 0.162 * text.size;
-
- let start = pos.x - deco.extent;
- let end = pos.x + width + deco.extent;
-
- let mut push_segment = |from: Abs, to: Abs, prepend: bool| {
- let origin = Point::new(from, pos.y + offset);
- let target = Point::new(to - from, Abs::zero());
-
- if target.x >= min_width || !evade {
- let shape = Geometry::Line(target).stroked(stroke.clone());
-
- if prepend {
- frame.prepend(origin, FrameItem::Shape(shape, Span::detached()));
- } else {
- frame.push(origin, FrameItem::Shape(shape, Span::detached()));
- }
- }
- };
-
- if !evade {
- push_segment(start, end, background);
- return;
- }
-
- let line = Line::new(
- kurbo::Point::new(pos.x.to_raw(), offset.to_raw()),
- kurbo::Point::new((pos.x + width).to_raw(), offset.to_raw()),
- );
-
- let mut x = pos.x;
- let mut intersections = vec![];
-
- for glyph in text.glyphs.iter() {
- let dx = glyph.x_offset.at(text.size) + x;
- let mut builder =
- BezPathBuilder::new(font_metrics.units_per_em, text.size, dx.to_raw());
-
- let bbox = text.font.ttf().outline_glyph(GlyphId(glyph.id), &mut builder);
- let path = builder.finish();
-
- x += glyph.x_advance.at(text.size);
-
- // Only do the costly segments intersection test if the line
- // intersects the bounding box.
- let intersect = bbox.is_some_and(|bbox| {
- let y_min = -text.font.to_em(bbox.y_max).at(text.size);
- let y_max = -text.font.to_em(bbox.y_min).at(text.size);
- offset >= y_min && offset <= y_max
- });
-
- if intersect {
- // Find all intersections of segments with the line.
- intersections.extend(
- path.segments()
- .flat_map(|seg| seg.intersect_line(line))
- .map(|is| Abs::raw(line.eval(is.line_t).x)),
- );
- }
- }
-
- // Add start and end points, taking padding into account.
- intersections.push(start - gap_padding);
- intersections.push(end + gap_padding);
- // When emitting the decorative line segments, we move from left to
- // right. The intersections are not necessarily in this order, yet.
- intersections.sort();
-
- for edge in intersections.windows(2) {
- let l = edge[0];
- let r = edge[1];
-
- // If we are too close, don't draw the segment
- if r - l < gap_padding {
- continue;
- } else {
- push_segment(l + gap_padding, r - gap_padding, background);
- }
- }
-}
-
-// Return the top/bottom edge of the text given the metric of the font.
-fn determine_edges(
- text: &TextItem,
- top_edge: TopEdge,
- bottom_edge: BottomEdge,
-) -> (Abs, Abs) {
- let mut top = Abs::zero();
- let mut bottom = Abs::zero();
-
- for g in text.glyphs.iter() {
- let (t, b) = text.font.edges(
- top_edge,
- bottom_edge,
- text.size,
- TextEdgeBounds::Glyph(g.id),
- );
- top.set_max(t);
- bottom.set_max(b);
- }
-
- (top, bottom)
-}
-
-/// Builds a kurbo [`BezPath`] for a glyph.
-struct BezPathBuilder {
- path: BezPath,
- units_per_em: f64,
- font_size: Abs,
- x_offset: f64,
-}
-
-impl BezPathBuilder {
- fn new(units_per_em: f64, font_size: Abs, x_offset: f64) -> Self {
- Self {
- path: BezPath::new(),
- units_per_em,
- font_size,
- x_offset,
- }
- }
-
- fn finish(self) -> BezPath {
- self.path
- }
-
- fn p(&self, x: f32, y: f32) -> kurbo::Point {
- kurbo::Point::new(self.s(x) + self.x_offset, -self.s(y))
- }
-
- fn s(&self, v: f32) -> f64 {
- Em::from_units(v, self.units_per_em).at(self.font_size).to_raw()
- }
-}
-
-impl OutlineBuilder for BezPathBuilder {
- fn move_to(&mut self, x: f32, y: f32) {
- self.path.move_to(self.p(x, y));
- }
-
- fn line_to(&mut self, x: f32, y: f32) {
- self.path.line_to(self.p(x, y));
- }
-
- fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
- self.path.quad_to(self.p(x1, y1), self.p(x, y));
- }
-
- fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
- self.path.curve_to(self.p(x1, y1), self.p(x2, y2), self.p(x, y));
- }
-
- fn close(&mut self) {
- self.path.close_path();
- }
-}
diff --git a/crates/typst/src/text/font/book.rs b/crates/typst-library/src/text/font/book.rs
index 23e27f64..23e27f64 100644
--- a/crates/typst/src/text/font/book.rs
+++ b/crates/typst-library/src/text/font/book.rs
diff --git a/crates/typst/src/text/font/color.rs b/crates/typst-library/src/text/font/color.rs
index e26d323c..08f6fe0a 100644
--- a/crates/typst/src/text/font/color.rs
+++ b/crates/typst-library/src/text/font/color.rs
@@ -3,13 +3,13 @@
use std::io::Read;
use ttf_parser::{GlyphId, RgbaColor};
+use typst_syntax::Span;
use usvg::tiny_skia_path;
use xmlwriter::XmlWriter;
use crate::layout::{Abs, Frame, FrameItem, Point, Size};
-use crate::syntax::Span;
use crate::text::{Font, Glyph};
-use crate::visualize::{FixedStroke, Geometry, Image};
+use crate::visualize::{FixedStroke, Geometry, Image, RasterFormat, VectorFormat};
/// Whether this glyph should be rendered via simple outlining instead of via
/// `glyph_frame`.
@@ -101,12 +101,8 @@ fn draw_raster_glyph(
upem: Abs,
raster_image: ttf_parser::RasterGlyphImage,
) -> Option<()> {
- let image = Image::new(
- raster_image.data.into(),
- typst::visualize::ImageFormat::Raster(typst::visualize::RasterFormat::Png),
- None,
- )
- .ok()?;
+ let image =
+ Image::new(raster_image.data.into(), RasterFormat::Png.into(), None).ok()?;
// Apple Color emoji doesn't provide offset information (or at least
// not in a way ttf-parser understands), so we artificially shift their
@@ -179,12 +175,7 @@ fn draw_colr_glyph(
let data = svg.end_document().into_bytes();
- let image = Image::new(
- data.into(),
- typst::visualize::ImageFormat::Vector(typst::visualize::VectorFormat::Svg),
- None,
- )
- .ok()?;
+ let image = Image::new(data.into(), VectorFormat::Svg.into(), None).ok()?;
let y_shift = Abs::pt(upem.to_pt() - y_max);
let position = Point::new(Abs::pt(x_min), y_shift);
@@ -259,12 +250,9 @@ fn draw_svg_glyph(
ty = -top,
);
- let image = Image::new(
- wrapper_svg.into_bytes().into(),
- typst::visualize::ImageFormat::Vector(typst::visualize::VectorFormat::Svg),
- None,
- )
- .ok()?;
+ let image =
+ Image::new(wrapper_svg.into_bytes().into(), VectorFormat::Svg.into(), None)
+ .ok()?;
let position = Point::new(Abs::pt(left), Abs::pt(top) + upem);
let size = Size::new(Abs::pt(width), Abs::pt(height));
diff --git a/crates/typst/src/text/font/exceptions.rs b/crates/typst-library/src/text/font/exceptions.rs
index 40998bb9..6393df4b 100644
--- a/crates/typst/src/text/font/exceptions.rs
+++ b/crates/typst-library/src/text/font/exceptions.rs
@@ -2,6 +2,10 @@ use serde::Deserialize;
use super::{FontStretch, FontStyle, FontWeight};
+pub fn find_exception(postscript_name: &str) -> Option<&'static Exception> {
+ EXCEPTION_MAP.get(postscript_name)
+}
+
#[derive(Debug, Default, Deserialize)]
pub struct Exception {
pub family: Option<&'static str>,
@@ -11,7 +15,7 @@ pub struct Exception {
}
impl Exception {
- pub const fn new() -> Self {
+ const fn new() -> Self {
Self {
family: None,
style: None,
@@ -38,10 +42,6 @@ impl Exception {
}
}
-pub fn find_exception(postscript_name: &str) -> Option<&'static Exception> {
- EXCEPTION_MAP.get(postscript_name)
-}
-
/// A map which keys are PostScript name and values are override entries.
static EXCEPTION_MAP: phf::Map<&'static str, Exception> = phf::phf_map! {
// The old version of Arial-Black, published by Microsoft in 1996 in their
diff --git a/crates/typst/src/text/font/mod.rs b/crates/typst-library/src/text/font/mod.rs
index 09837312..09837312 100644
--- a/crates/typst/src/text/font/mod.rs
+++ b/crates/typst-library/src/text/font/mod.rs
diff --git a/crates/typst/src/text/font/variant.rs b/crates/typst-library/src/text/font/variant.rs
index c7a00fb3..c7a00fb3 100644
--- a/crates/typst/src/text/font/variant.rs
+++ b/crates/typst-library/src/text/font/variant.rs
diff --git a/crates/typst/src/text/item.rs b/crates/typst-library/src/text/item.rs
index b2f9e272..ed559aec 100644
--- a/crates/typst/src/text/item.rs
+++ b/crates/typst-library/src/text/item.rs
@@ -2,9 +2,9 @@ use std::fmt::{self, Debug, Formatter};
use std::ops::Range;
use ecow::EcoString;
+use typst_syntax::Span;
use crate::layout::{Abs, Em};
-use crate::syntax::Span;
use crate::text::{is_default_ignorable, Font, Lang, Region};
use crate::visualize::{FixedStroke, Paint};
diff --git a/crates/typst/src/text/lang.rs b/crates/typst-library/src/text/lang.rs
index 6c9c886a..64ab1a7c 100644
--- a/crates/typst/src/text/lang.rs
+++ b/crates/typst-library/src/text/lang.rs
@@ -1,9 +1,9 @@
use std::collections::HashMap;
use std::str::FromStr;
-use crate::diag::Hint;
use ecow::{eco_format, EcoString};
+use crate::diag::Hint;
use crate::foundations::{cast, StyleChain};
use crate::layout::Dir;
use crate::text::TextElem;
@@ -304,8 +304,9 @@ fn lang_str(lang: Lang, region: Option<Region>) -> EcoString {
#[cfg(test)]
mod tests {
+ use typst_utils::option_eq;
+
use super::*;
- use crate::utils::option_eq;
#[test]
fn test_region_option_eq() {
diff --git a/crates/typst/src/text/linebreak.rs b/crates/typst-library/src/text/linebreak.rs
index f32b9223..0519e1c4 100644
--- a/crates/typst/src/text/linebreak.rs
+++ b/crates/typst-library/src/text/linebreak.rs
@@ -1,5 +1,6 @@
+use typst_utils::singleton;
+
use crate::foundations::{elem, Content, NativeElement};
-use crate::utils::singleton;
/// Inserts a line break.
///
diff --git a/crates/typst/src/text/lorem.rs b/crates/typst-library/src/text/lorem.rs
index 5d01a550..5d01a550 100644
--- a/crates/typst/src/text/lorem.rs
+++ b/crates/typst-library/src/text/lorem.rs
diff --git a/crates/typst/src/text/mod.rs b/crates/typst-library/src/text/mod.rs
index 6f3f741a..acf100b5 100644
--- a/crates/typst/src/text/mod.rs
+++ b/crates/typst-library/src/text/mod.rs
@@ -38,6 +38,7 @@ use once_cell::sync::Lazy;
use rustybuzz::Feature;
use smallvec::SmallVec;
use ttf_parser::Tag;
+use typst_syntax::Spanned;
use crate::diag::{bail, warning, HintedStrResult, SourceResult};
use crate::engine::Engine;
@@ -48,7 +49,6 @@ use crate::foundations::{
};
use crate::layout::{Abs, Axis, Dir, Em, Length, Ratio, Rel};
use crate::model::ParElem;
-use crate::syntax::Spanned;
use crate::visualize::{Color, Paint, RelativeTo, Stroke};
use crate::World;
@@ -816,7 +816,7 @@ cast! {
}
/// Resolve a prioritized iterator over the font families.
-pub(crate) fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone {
+pub fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone {
const FALLBACKS: &[&str] = &[
"libertinus serif",
"twitter color emoji",
@@ -833,7 +833,7 @@ pub(crate) fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone
}
/// Resolve the font variant.
-pub(crate) fn variant(styles: StyleChain) -> FontVariant {
+pub fn variant(styles: StyleChain) -> FontVariant {
let mut variant = FontVariant::new(
TextElem::style_in(styles),
TextElem::weight_in(styles),
@@ -1121,7 +1121,7 @@ impl Fold for FontFeatures {
}
/// Collect the OpenType features to apply.
-pub(crate) fn features(styles: StyleChain) -> Vec<Feature> {
+pub fn features(styles: StyleChain) -> Vec<Feature> {
let mut tags = vec![];
let mut feat = |tag: &[u8; 4], value: u32| {
tags.push(Feature::new(Tag::from_bytes(tag), value, ..));
@@ -1273,7 +1273,7 @@ cast! {
}
/// Whether a codepoint is Unicode `Default_Ignorable`.
-pub(crate) fn is_default_ignorable(c: char) -> bool {
+pub fn is_default_ignorable(c: char) -> bool {
/// The set of Unicode default ignorables.
static DEFAULT_IGNORABLE_DATA: Lazy<CodePointSetData> = Lazy::new(|| {
icu_properties::sets::load_default_ignorable_code_point(
diff --git a/crates/typst/src/text/raw.rs b/crates/typst-library/src/text/raw.rs
index e669be38..5ce77348 100644
--- a/crates/typst/src/text/raw.rs
+++ b/crates/typst-library/src/text/raw.rs
@@ -7,6 +7,7 @@ use once_cell::sync::Lazy;
use once_cell::unsync::Lazy as UnsyncLazy;
use syntect::highlighting::{self as synt, Theme};
use syntect::parsing::{SyntaxDefinition, SyntaxSet, SyntaxSetBuilder};
+use typst_syntax::{split_newlines, LinkedNode, Span, Spanned};
use unicode_segmentation::UnicodeSegmentation;
use super::Lang;
@@ -18,12 +19,11 @@ use crate::foundations::{
};
use crate::layout::{BlockBody, BlockElem, Em, HAlignment};
use crate::model::{Figurable, ParElem};
-use crate::syntax::{split_newlines, LinkedNode, Span, Spanned};
use crate::text::{
FontFamily, FontList, Hyphenate, LinebreakElem, LocalName, TextElem, TextSize,
};
use crate::visualize::Color;
-use crate::{syntax, World};
+use crate::World;
// Shorthand for highlighter closures.
type StyleFn<'a> =
@@ -358,9 +358,9 @@ impl Packed<RawElem> {
let text =
lines.iter().map(|(s, _)| s.clone()).collect::<Vec<_>>().join("\n");
let root = match lang.as_deref() {
- Some("typc") => syntax::parse_code(&text),
- Some("typm") => syntax::parse_math(&text),
- _ => syntax::parse(&text),
+ Some("typc") => typst_syntax::parse_code(&text),
+ Some("typm") => typst_syntax::parse_math(&text),
+ _ => typst_syntax::parse(&text),
};
ThemedHighlighter::new(
@@ -654,7 +654,7 @@ impl<'a> ThemedHighlighter<'a> {
for child in self.node.children() {
let mut scopes = self.scopes.clone();
- if let Some(tag) = crate::syntax::highlight(&child) {
+ if let Some(tag) = typst_syntax::highlight(&child) {
scopes.push(syntect::parsing::Scope::new(tag.tm_scope()).unwrap())
}
diff --git a/crates/typst/src/text/shift.rs b/crates/typst-library/src/text/shift.rs
index 003ecf47..003ecf47 100644
--- a/crates/typst/src/text/shift.rs
+++ b/crates/typst-library/src/text/shift.rs
diff --git a/crates/typst/src/text/smallcaps.rs b/crates/typst-library/src/text/smallcaps.rs
index bf003bd1..bf003bd1 100644
--- a/crates/typst/src/text/smallcaps.rs
+++ b/crates/typst-library/src/text/smallcaps.rs
diff --git a/crates/typst/src/text/smartquote.rs b/crates/typst-library/src/text/smartquote.rs
index 467165b6..5917550d 100644
--- a/crates/typst/src/text/smartquote.rs
+++ b/crates/typst-library/src/text/smartquote.rs
@@ -1,4 +1,5 @@
use ecow::EcoString;
+use typst_syntax::is_newline;
use unicode_segmentation::UnicodeSegmentation;
use crate::diag::{bail, HintedStrResult, StrResult};
@@ -6,7 +7,6 @@ use crate::foundations::{
array, cast, dict, elem, Array, Dict, FromValue, Packed, PlainText, Smart, Str,
};
use crate::layout::Dir;
-use crate::syntax::is_newline;
use crate::text::{Lang, Region};
/// A language-aware quote that reacts to its context.
diff --git a/crates/typst/src/text/space.rs b/crates/typst-library/src/text/space.rs
index 2ebc3b33..38a55482 100644
--- a/crates/typst/src/text/space.rs
+++ b/crates/typst-library/src/text/space.rs
@@ -1,9 +1,9 @@
use ecow::EcoString;
+use typst_utils::singleton;
use crate::foundations::{
elem, Content, NativeElement, Packed, PlainText, Repr, Unlabellable,
};
-use crate::utils::singleton;
/// A text space.
#[elem(Unlabellable, PlainText, Repr)]
diff --git a/crates/typst/src/visualize/color.rs b/crates/typst-library/src/visualize/color.rs
index 6e05f0e3..1a279fbb 100644
--- a/crates/typst/src/visualize/color.rs
+++ b/crates/typst-library/src/visualize/color.rs
@@ -9,6 +9,7 @@ use palette::{
Alpha, Darken, Desaturate, FromColor, Lighten, OklabHue, RgbHue, Saturate, ShiftHue,
};
use qcms::Profile;
+use typst_syntax::{Span, Spanned};
use crate::diag::{bail, At, SourceResult, StrResult};
use crate::foundations::{
@@ -16,7 +17,6 @@ use crate::foundations::{
Value,
};
use crate::layout::{Angle, Ratio};
-use crate::syntax::{Span, Spanned};
// Type aliases for `palette` internal types in f32.
pub type Oklab = palette::oklab::Oklaba<f32>;
@@ -211,7 +211,7 @@ pub enum Color {
#[scope]
impl Color {
/// The module of preset color maps.
- pub const MAP: fn() -> Module = || crate::utils::singleton!(Module, map()).clone();
+ pub const MAP: fn() -> Module = || typst_utils::singleton!(Module, map()).clone();
pub const BLACK: Self = Self::Luma(Luma::new(0.0, 1.0));
pub const GRAY: Self = Self::Luma(Luma::new(0.6666666, 1.0));
diff --git a/crates/typst/src/visualize/gradient.rs b/crates/typst-library/src/visualize/gradient.rs
index 45281820..2be7e370 100644
--- a/crates/typst/src/visualize/gradient.rs
+++ b/crates/typst-library/src/visualize/gradient.rs
@@ -5,13 +5,13 @@ use std::sync::Arc;
use ecow::EcoString;
use kurbo::Vec2;
+use typst_syntax::{Span, Spanned};
use crate::diag::{bail, SourceResult};
use crate::foundations::{
array, cast, func, scope, ty, Args, Array, Cast, Func, IntoValue, Repr, Smart,
};
use crate::layout::{Angle, Axes, Dir, Quadrant, Ratio};
-use crate::syntax::{Span, Spanned};
use crate::visualize::{Color, ColorSpace, WeightedColor};
/// A color gradient.
diff --git a/crates/typst/src/visualize/image/mod.rs b/crates/typst-library/src/visualize/image/mod.rs
index 0bc2f3b1..359db252 100644
--- a/crates/typst/src/visualize/image/mod.rs
+++ b/crates/typst-library/src/visualize/image/mod.rs
@@ -6,30 +6,24 @@ mod svg;
pub use self::raster::{RasterFormat, RasterImage};
pub use self::svg::SvgImage;
-use std::ffi::OsStr;
use std::fmt::{self, Debug, Formatter};
use std::sync::Arc;
use comemo::Tracked;
use ecow::EcoString;
+use typst_syntax::{Span, Spanned};
+use typst_utils::LazyHash;
-use crate::diag::{bail, warning, At, SourceResult, StrResult};
+use crate::diag::{At, SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, func, scope, Bytes, Cast, Content, NativeElement, Packed, Show, Smart,
StyleChain,
};
-use crate::introspection::Locator;
-use crate::layout::{
- Abs, Axes, BlockElem, FixedAlignment, Frame, FrameItem, Length, Point, Region, Rel,
- Size, Sizing,
-};
+use crate::layout::{BlockElem, Length, Rel, Sizing};
use crate::loading::Readable;
use crate::model::Figurable;
-use crate::syntax::{Span, Spanned};
-use crate::text::{families, LocalName};
-use crate::utils::LazyHash;
-use crate::visualize::Path;
+use crate::text::LocalName;
use crate::World;
/// A raster or vector graphic.
@@ -159,8 +153,8 @@ impl ImageElem {
}
impl Show for Packed<ImageElem> {
- fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), layout_image)
+ fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_image)
.with_width(self.width(styles))
.with_height(self.height(styles))
.pack()
@@ -174,134 +168,6 @@ impl LocalName for Packed<ImageElem> {
impl Figurable for Packed<ImageElem> {}
-/// Layout the image.
-#[typst_macros::time(span = elem.span())]
-fn layout_image(
- elem: &Packed<ImageElem>,
- engine: &mut Engine,
- _: Locator,
- styles: StyleChain,
- region: Region,
-) -> SourceResult<Frame> {
- let span = elem.span();
-
- // Take the format that was explicitly defined, or parse the extension,
- // or try to detect the format.
- let data = elem.data();
- let format = match elem.format(styles) {
- Smart::Custom(v) => v,
- Smart::Auto => determine_format(elem.path().as_str(), data).at(span)?,
- };
-
- // Warn the user if the image contains a foreign object. Not perfect
- // because the svg could also be encoded, but that's an edge case.
- if format == ImageFormat::Vector(VectorFormat::Svg) {
- let has_foreign_object =
- data.as_str().is_some_and(|s| s.contains("<foreignObject"));
-
- if has_foreign_object {
- engine.sink.warn(warning!(
- span,
- "image contains foreign object";
- hint: "SVG images with foreign objects might render incorrectly in typst";
- hint: "see https://github.com/typst/typst/issues/1421 for more information"
- ));
- }
- }
-
- // Construct the image itself.
- let image = Image::with_fonts(
- data.clone().into(),
- format,
- elem.alt(styles),
- engine.world,
- &families(styles).collect::<Vec<_>>(),
- )
- .at(span)?;
-
- // Determine the image's pixel aspect ratio.
- let pxw = image.width();
- let pxh = image.height();
- let px_ratio = pxw / pxh;
-
- // Determine the region's aspect ratio.
- let region_ratio = region.size.x / region.size.y;
-
- // Find out whether the image is wider or taller than the region.
- let wide = px_ratio > region_ratio;
-
- // The space into which the image will be placed according to its fit.
- let target = if region.expand.x && region.expand.y {
- // If both width and height are forced, take them.
- region.size
- } else if region.expand.x {
- // If just width is forced, take it.
- Size::new(region.size.x, region.size.y.min(region.size.x / px_ratio))
- } else if region.expand.y {
- // If just height is forced, take it.
- Size::new(region.size.x.min(region.size.y * px_ratio), region.size.y)
- } else {
- // If neither is forced, take the natural image size at the image's
- // DPI bounded by the available space.
- let dpi = image.dpi().unwrap_or(Image::DEFAULT_DPI);
- let natural = Axes::new(pxw, pxh).map(|v| Abs::inches(v / dpi));
- Size::new(
- natural.x.min(region.size.x).min(region.size.y * px_ratio),
- natural.y.min(region.size.y).min(region.size.x / px_ratio),
- )
- };
-
- // Compute the actual size of the fitted image.
- let fit = elem.fit(styles);
- let fitted = match fit {
- ImageFit::Cover | ImageFit::Contain => {
- if wide == (fit == ImageFit::Contain) {
- Size::new(target.x, target.x / px_ratio)
- } else {
- Size::new(target.y * px_ratio, target.y)
- }
- }
- ImageFit::Stretch => target,
- };
-
- // First, place the image in a frame of exactly its size and then resize
- // the frame to the target size, center aligning the image in the
- // process.
- let mut frame = Frame::soft(fitted);
- frame.push(Point::zero(), FrameItem::Image(image, fitted, span));
- frame.resize(target, Axes::splat(FixedAlignment::Center));
-
- // Create a clipping group if only part of the image should be visible.
- if fit == ImageFit::Cover && !target.fits(fitted) {
- frame.clip(Path::rect(frame.size()));
- }
-
- Ok(frame)
-}
-
-/// Determine the image format based on path and data.
-fn determine_format(path: &str, data: &Readable) -> StrResult<ImageFormat> {
- let ext = std::path::Path::new(path)
- .extension()
- .and_then(OsStr::to_str)
- .unwrap_or_default()
- .to_lowercase();
-
- Ok(match ext.as_str() {
- "png" => ImageFormat::Raster(RasterFormat::Png),
- "jpg" | "jpeg" => ImageFormat::Raster(RasterFormat::Jpg),
- "gif" => ImageFormat::Raster(RasterFormat::Gif),
- "svg" | "svgz" => ImageFormat::Vector(VectorFormat::Svg),
- _ => match &data {
- Readable::Str(_) => ImageFormat::Vector(VectorFormat::Svg),
- Readable::Bytes(bytes) => match RasterFormat::detect(bytes) {
- Some(f) => ImageFormat::Raster(f),
- None => bail!("unknown image format"),
- },
- },
- })
-}
-
/// How an image should adjust itself to a given area,
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum ImageFit {
diff --git a/crates/typst/src/visualize/image/raster.rs b/crates/typst-library/src/visualize/image/raster.rs
index 3a57e491..829826c7 100644
--- a/crates/typst/src/visualize/image/raster.rs
+++ b/crates/typst-library/src/visualize/image/raster.rs
@@ -7,8 +7,7 @@ use ecow::{eco_format, EcoString};
use image::codecs::gif::GifDecoder;
use image::codecs::jpeg::JpegDecoder;
use image::codecs::png::PngDecoder;
-use image::Limits;
-use image::{guess_format, DynamicImage, ImageDecoder, ImageResult};
+use image::{guess_format, DynamicImage, ImageDecoder, ImageResult, Limits};
use crate::diag::{bail, StrResult};
use crate::foundations::{Bytes, Cast};
diff --git a/crates/typst/src/visualize/image/svg.rs b/crates/typst-library/src/visualize/image/svg.rs
index f7a498a8..f7a498a8 100644
--- a/crates/typst/src/visualize/image/svg.rs
+++ b/crates/typst-library/src/visualize/image/svg.rs
diff --git a/crates/typst/src/visualize/line.rs b/crates/typst-library/src/visualize/line.rs
index e00bf220..d9ddab84 100644
--- a/crates/typst/src/visualize/line.rs
+++ b/crates/typst-library/src/visualize/line.rs
@@ -1,12 +1,8 @@
-use crate::diag::{bail, SourceResult};
+use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain};
-use crate::introspection::Locator;
-use crate::layout::{
- Abs, Angle, Axes, BlockElem, Frame, FrameItem, Length, Region, Rel, Size,
-};
-use crate::utils::Numeric;
-use crate::visualize::{Geometry, Stroke};
+use crate::layout::{Abs, Angle, Axes, BlockElem, Length, Rel};
+use crate::visualize::Stroke;
/// A line from one point to another.
///
@@ -60,41 +56,9 @@ pub struct LineElem {
}
impl Show for Packed<LineElem> {
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), layout_line)
+ fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_line)
.pack()
.spanned(self.span()))
}
}
-
-/// Layout the line.
-#[typst_macros::time(span = elem.span())]
-fn layout_line(
- elem: &Packed<LineElem>,
- _: &mut Engine,
- _: Locator,
- styles: StyleChain,
- region: Region,
-) -> SourceResult<Frame> {
- let resolve = |axes: Axes<Rel<Abs>>| axes.zip_map(region.size, Rel::relative_to);
- let start = resolve(elem.start(styles));
- let delta = elem.end(styles).map(|end| resolve(end) - start).unwrap_or_else(|| {
- let length = elem.length(styles);
- let angle = elem.angle(styles);
- let x = angle.cos() * length;
- let y = angle.sin() * length;
- resolve(Axes::new(x, y))
- });
-
- let stroke = elem.stroke(styles).unwrap_or_default();
- let size = start.max(start + delta).max(Size::zero());
-
- if !size.is_finite() {
- bail!(elem.span(), "cannot create line with infinite length");
- }
-
- let mut frame = Frame::soft(size);
- let shape = Geometry::Line(delta.to_point()).stroked(stroke);
- frame.push(start.to_point(), FrameItem::Shape(shape, elem.span()));
- Ok(frame)
-}
diff --git a/crates/typst/src/visualize/mod.rs b/crates/typst-library/src/visualize/mod.rs
index 5c8bf646..5c8bf646 100644
--- a/crates/typst/src/visualize/mod.rs
+++ b/crates/typst-library/src/visualize/mod.rs
diff --git a/crates/typst/src/visualize/paint.rs b/crates/typst-library/src/visualize/paint.rs
index cd1006aa..cd1006aa 100644
--- a/crates/typst/src/visualize/paint.rs
+++ b/crates/typst-library/src/visualize/paint.rs
diff --git a/crates/typst/src/visualize/path.rs b/crates/typst-library/src/visualize/path.rs
index 035b76a5..76fd0df0 100644
--- a/crates/typst/src/visualize/path.rs
+++ b/crates/typst-library/src/visualize/path.rs
@@ -1,18 +1,14 @@
-use kurbo::{CubicBez, ParamCurveExtrema};
+use kurbo::ParamCurveExtrema;
+use self::PathVertex::{AllControlPoints, MirroredControlPoint, Vertex};
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- array, cast, elem, Array, Content, NativeElement, Packed, Reflect, Resolve, Show,
- Smart, StyleChain,
+ array, cast, elem, Array, Content, NativeElement, Packed, Reflect, Show, Smart,
+ StyleChain,
};
-use crate::introspection::Locator;
-use crate::layout::{
- Abs, Axes, BlockElem, Frame, FrameItem, Length, Point, Region, Rel, Size,
-};
-use crate::visualize::{FillRule, FixedStroke, Geometry, Paint, Shape, Stroke};
-
-use PathVertex::{AllControlPoints, MirroredControlPoint, Vertex};
+use crate::layout::{Abs, Axes, BlockElem, Length, Point, Rel, Size};
+use crate::visualize::{FillRule, Paint, Stroke};
/// A path through a list of points, connected by Bezier curves.
///
@@ -91,99 +87,13 @@ pub struct PathElem {
}
impl Show for Packed<PathElem> {
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), layout_path)
+ fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_path)
.pack()
.spanned(self.span()))
}
}
-/// Layout the path.
-#[typst_macros::time(span = elem.span())]
-fn layout_path(
- elem: &Packed<PathElem>,
- _: &mut Engine,
- _: Locator,
- styles: StyleChain,
- region: Region,
-) -> SourceResult<Frame> {
- let resolve = |axes: Axes<Rel<Length>>| {
- axes.resolve(styles).zip_map(region.size, Rel::relative_to).to_point()
- };
-
- let vertices = elem.vertices();
- let points: Vec<Point> = vertices.iter().map(|c| resolve(c.vertex())).collect();
-
- let mut size = Size::zero();
- if points.is_empty() {
- return Ok(Frame::soft(size));
- }
-
- // Only create a path if there are more than zero points.
- // Construct a closed path given all points.
- let mut path = Path::new();
- path.move_to(points[0]);
-
- let mut add_cubic = |from_point: Point,
- to_point: Point,
- from: PathVertex,
- to: PathVertex| {
- let from_control_point = resolve(from.control_point_from()) + from_point;
- let to_control_point = resolve(to.control_point_to()) + to_point;
- path.cubic_to(from_control_point, to_control_point, to_point);
-
- let p0 = kurbo::Point::new(from_point.x.to_raw(), from_point.y.to_raw());
- let p1 = kurbo::Point::new(
- from_control_point.x.to_raw(),
- from_control_point.y.to_raw(),
- );
- let p2 =
- kurbo::Point::new(to_control_point.x.to_raw(), to_control_point.y.to_raw());
- let p3 = kurbo::Point::new(to_point.x.to_raw(), to_point.y.to_raw());
- let extrema = CubicBez::new(p0, p1, p2, p3).bounding_box();
- size.x.set_max(Abs::raw(extrema.x1));
- size.y.set_max(Abs::raw(extrema.y1));
- };
-
- for (vertex_window, point_window) in vertices.windows(2).zip(points.windows(2)) {
- let from = vertex_window[0];
- let to = vertex_window[1];
- let from_point = point_window[0];
- let to_point = point_window[1];
-
- add_cubic(from_point, to_point, from, to);
- }
-
- if elem.closed(styles) {
- let from = *vertices.last().unwrap(); // We checked that we have at least one element.
- let to = vertices[0];
- let from_point = *points.last().unwrap();
- let to_point = points[0];
-
- add_cubic(from_point, to_point, from, to);
- path.close_path();
- }
-
- // Prepare fill and stroke.
- let fill = elem.fill(styles);
- let fill_rule = elem.fill_rule(styles);
- let stroke = match elem.stroke(styles) {
- Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
- Smart::Auto => None,
- Smart::Custom(stroke) => stroke.map(Stroke::unwrap_or_default),
- };
-
- let mut frame = Frame::soft(size);
- let shape = Shape {
- geometry: Geometry::Path(path),
- stroke,
- fill,
- fill_rule,
- };
- frame.push(Point::zero(), FrameItem::Shape(shape, elem.span()));
- Ok(frame)
-}
-
/// A component used for path creation.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum PathVertex {
@@ -278,6 +188,26 @@ impl Path {
path
}
+ /// Create a path that describes an axis-aligned ellipse.
+ pub fn ellipse(size: Size) -> Self {
+ // https://stackoverflow.com/a/2007782
+ let z = Abs::zero();
+ let rx = size.x / 2.0;
+ let ry = size.y / 2.0;
+ let m = 0.551784;
+ let mx = m * rx;
+ let my = m * ry;
+ let point = |x, y| Point::new(x + rx, y + ry);
+
+ let mut path = Path::new();
+ path.move_to(point(-rx, z));
+ path.cubic_to(point(-rx, -my), point(-mx, -ry), point(z, -ry));
+ path.cubic_to(point(mx, -ry), point(rx, -my), point(rx, z));
+ path.cubic_to(point(rx, my), point(mx, ry), point(z, ry));
+ path.cubic_to(point(-mx, ry), point(-rx, my), point(-rx, z));
+ path
+ }
+
/// Push a [`MoveTo`](PathItem::MoveTo) item.
pub fn move_to(&mut self, p: Point) {
self.0.push(PathItem::MoveTo(p));
diff --git a/crates/typst/src/visualize/pattern.rs b/crates/typst-library/src/visualize/pattern.rs
index ca4bb46f..2017ea65 100644
--- a/crates/typst/src/visualize/pattern.rs
+++ b/crates/typst-library/src/visualize/pattern.rs
@@ -2,14 +2,14 @@ use std::hash::Hash;
use std::sync::Arc;
use ecow::{eco_format, EcoString};
+use typst_syntax::{Span, Spanned};
+use typst_utils::{LazyHash, Numeric};
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{func, repr, scope, ty, Content, Smart, StyleChain};
use crate::introspection::Locator;
-use crate::layout::{layout_frame, Abs, Axes, Frame, Length, Region, Size};
-use crate::syntax::{Span, Spanned};
-use crate::utils::{LazyHash, Numeric};
+use crate::layout::{Abs, Axes, Frame, Length, Region, Size};
use crate::visualize::RelativeTo;
use crate::World;
@@ -193,7 +193,8 @@ impl Pattern {
let locator = Locator::root();
let styles = StyleChain::new(&library.styles);
let pod = Region::new(region, Axes::splat(false));
- let mut frame = layout_frame(engine, &body, locator, styles, pod)?;
+ let mut frame =
+ (engine.routines.layout_frame)(engine, &body, locator, styles, pod)?;
// Set the size of the frame if the size is enforced.
if let Smart::Custom(size) = size {
diff --git a/crates/typst/src/visualize/polygon.rs b/crates/typst-library/src/visualize/polygon.rs
index 19428204..33e4fd32 100644
--- a/crates/typst/src/visualize/polygon.rs
+++ b/crates/typst-library/src/visualize/polygon.rs
@@ -1,15 +1,14 @@
use std::f64::consts::PI;
-use crate::diag::{bail, SourceResult};
+use typst_syntax::Span;
+
+use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{
- elem, func, scope, Content, NativeElement, Packed, Resolve, Show, Smart, StyleChain,
+ elem, func, scope, Content, NativeElement, Packed, Show, Smart, StyleChain,
};
-use crate::introspection::Locator;
-use crate::layout::{Axes, BlockElem, Em, Frame, FrameItem, Length, Point, Region, Rel};
-use crate::syntax::Span;
-use crate::utils::Numeric;
-use crate::visualize::{FillRule, FixedStroke, Geometry, Paint, Path, Shape, Stroke};
+use crate::layout::{Axes, BlockElem, Em, Length, Rel};
+use crate::visualize::{FillRule, Paint, Stroke};
/// A closed polygon.
///
@@ -128,63 +127,9 @@ impl PolygonElem {
}
impl Show for Packed<PolygonElem> {
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), layout_polygon)
+ fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_polygon)
.pack()
.spanned(self.span()))
}
}
-
-/// Layout the polygon.
-#[typst_macros::time(span = elem.span())]
-fn layout_polygon(
- elem: &Packed<PolygonElem>,
- _: &mut Engine,
- _: Locator,
- styles: StyleChain,
- region: Region,
-) -> SourceResult<Frame> {
- let points: Vec<Point> = elem
- .vertices()
- .iter()
- .map(|c| c.resolve(styles).zip_map(region.size, Rel::relative_to).to_point())
- .collect();
-
- let size = points.iter().fold(Point::zero(), |max, c| c.max(max)).to_size();
- if !size.is_finite() {
- bail!(elem.span(), "cannot create polygon with infinite size");
- }
-
- let mut frame = Frame::hard(size);
-
- // Only create a path if there are more than zero points.
- if points.is_empty() {
- return Ok(frame);
- }
-
- // Prepare fill and stroke.
- let fill = elem.fill(styles);
- let fill_rule = elem.fill_rule(styles);
- let stroke = match elem.stroke(styles) {
- Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
- Smart::Auto => None,
- Smart::Custom(stroke) => stroke.map(Stroke::unwrap_or_default),
- };
-
- // Construct a closed path given all points.
- let mut path = Path::new();
- path.move_to(points[0]);
- for &point in &points[1..] {
- path.line_to(point);
- }
- path.close_path();
-
- let shape = Shape {
- geometry: Geometry::Path(path),
- stroke,
- fill,
- fill_rule,
- };
- frame.push(Point::zero(), FrameItem::Shape(shape, elem.span()));
- Ok(frame)
-}
diff --git a/crates/typst-library/src/visualize/shape.rs b/crates/typst-library/src/visualize/shape.rs
new file mode 100644
index 00000000..01e316a8
--- /dev/null
+++ b/crates/typst-library/src/visualize/shape.rs
@@ -0,0 +1,448 @@
+use crate::diag::SourceResult;
+use crate::engine::Engine;
+use crate::foundations::{
+ elem, Cast, Content, NativeElement, Packed, Show, Smart, StyleChain,
+};
+use crate::layout::{Abs, BlockElem, Corners, Length, Point, Rel, Sides, Size, Sizing};
+use crate::visualize::{FixedStroke, Paint, Path, Stroke};
+
+/// A rectangle with optional content.
+///
+/// # Example
+/// ```example
+/// // Without content.
+/// #rect(width: 35%, height: 30pt)
+///
+/// // With content.
+/// #rect[
+/// Automatically sized \
+/// to fit the content.
+/// ]
+/// ```
+#[elem(title = "Rectangle", Show)]
+pub struct RectElem {
+ /// The rectangle's width, relative to its parent container.
+ pub width: Smart<Rel<Length>>,
+
+ /// The rectangle's height, relative to its parent container.
+ pub height: Sizing,
+
+ /// How to fill the rectangle.
+ ///
+ /// When setting a fill, the default stroke disappears. To create a
+ /// rectangle with both fill and stroke, you have to configure both.
+ ///
+ /// ```example
+ /// #rect(fill: blue)
+ /// ```
+ pub fill: Option<Paint>,
+
+ /// How to stroke the rectangle. This can be:
+ ///
+ /// - `{none}` to disable stroking
+ /// - `{auto}` for a stroke of `{1pt + black}` if and if only if no fill is
+ /// given.
+ /// - Any kind of [stroke]
+ /// - A dictionary describing the stroke for each side individually. The
+ /// dictionary can contain the following keys in order of precedence:
+ /// - `top`: The top stroke.
+ /// - `right`: The right stroke.
+ /// - `bottom`: The bottom stroke.
+ /// - `left`: The left stroke.
+ /// - `x`: The horizontal stroke.
+ /// - `y`: The vertical stroke.
+ /// - `rest`: The stroke on all sides except those for which the
+ /// dictionary explicitly sets a size.
+ ///
+ /// ```example
+ /// #stack(
+ /// dir: ltr,
+ /// spacing: 1fr,
+ /// rect(stroke: red),
+ /// rect(stroke: 2pt),
+ /// rect(stroke: 2pt + red),
+ /// )
+ /// ```
+ #[resolve]
+ #[fold]
+ pub stroke: Smart<Sides<Option<Option<Stroke>>>>,
+
+ /// How much to round the rectangle's corners, relative to the minimum of
+ /// the width and height divided by two. This can be:
+ ///
+ /// - A relative length for a uniform corner radius.
+ /// - A dictionary: With a dictionary, the stroke for each side can be set
+ /// individually. The dictionary can contain the following keys in order
+ /// of precedence:
+ /// - `top-left`: The top-left corner radius.
+ /// - `top-right`: The top-right corner radius.
+ /// - `bottom-right`: The bottom-right corner radius.
+ /// - `bottom-left`: The bottom-left corner radius.
+ /// - `left`: The top-left and bottom-left corner radii.
+ /// - `top`: The top-left and top-right corner radii.
+ /// - `right`: The top-right and bottom-right corner radii.
+ /// - `bottom`: The bottom-left and bottom-right corner radii.
+ /// - `rest`: The radii for all corners except those for which the
+ /// dictionary explicitly sets a size.
+ ///
+ /// ```example
+ /// #set rect(stroke: 4pt)
+ /// #rect(
+ /// radius: (
+ /// left: 5pt,
+ /// top-right: 20pt,
+ /// bottom-right: 10pt,
+ /// ),
+ /// stroke: (
+ /// left: red,
+ /// top: yellow,
+ /// right: green,
+ /// bottom: blue,
+ /// ),
+ /// )
+ /// ```
+ #[resolve]
+ #[fold]
+ pub radius: Corners<Option<Rel<Length>>>,
+
+ /// How much to pad the rectangle's content.
+ /// See the [box's documentation]($box.outset) for more details.
+ #[resolve]
+ #[fold]
+ #[default(Sides::splat(Some(Abs::pt(5.0).into())))]
+ pub inset: Sides<Option<Rel<Length>>>,
+
+ /// How much to expand the rectangle's size without affecting the layout.
+ /// See the [box's documentation]($box.outset) for more details.
+ #[resolve]
+ #[fold]
+ pub outset: Sides<Option<Rel<Length>>>,
+
+ /// The content to place into the rectangle.
+ ///
+ /// When this is omitted, the rectangle takes on a default size of at most
+ /// `{45pt}` by `{30pt}`.
+ #[positional]
+ #[borrowed]
+ pub body: Option<Content>,
+}
+
+impl Show for Packed<RectElem> {
+ fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_rect)
+ .with_width(self.width(styles))
+ .with_height(self.height(styles))
+ .pack()
+ .spanned(self.span()))
+ }
+}
+
+/// A square with optional content.
+///
+/// # Example
+/// ```example
+/// // Without content.
+/// #square(size: 40pt)
+///
+/// // With content.
+/// #square[
+/// Automatically \
+/// sized to fit.
+/// ]
+/// ```
+#[elem(Show)]
+pub struct SquareElem {
+ /// The square's side length. This is mutually exclusive with `width` and
+ /// `height`.
+ #[external]
+ pub size: Smart<Length>,
+
+ /// The square's width. This is mutually exclusive with `size` and `height`.
+ ///
+ /// In contrast to `size`, this can be relative to the parent container's
+ /// width.
+ #[parse(
+ let size = args.named::<Smart<Length>>("size")?.map(|s| s.map(Rel::from));
+ match size {
+ None => args.named("width")?,
+ size => size,
+ }
+ )]
+ pub width: Smart<Rel<Length>>,
+
+ /// The square's height. This is mutually exclusive with `size` and `width`.
+ ///
+ /// In contrast to `size`, this can be relative to the parent container's
+ /// height.
+ #[parse(match size {
+ None => args.named("height")?,
+ size => size.map(Into::into),
+ })]
+ pub height: Sizing,
+
+ /// How to fill the square. See the [rectangle's documentation]($rect.fill)
+ /// for more details.
+ pub fill: Option<Paint>,
+
+ /// How to stroke the square. See the
+ /// [rectangle's documentation]($rect.stroke) for more details.
+ #[resolve]
+ #[fold]
+ pub stroke: Smart<Sides<Option<Option<Stroke>>>>,
+
+ /// How much to round the square's corners. See the
+ /// [rectangle's documentation]($rect.radius) for more details.
+ #[resolve]
+ #[fold]
+ pub radius: Corners<Option<Rel<Length>>>,
+
+ /// How much to pad the square's content. See the
+ /// [box's documentation]($box.inset) for more details.
+ #[resolve]
+ #[fold]
+ #[default(Sides::splat(Some(Abs::pt(5.0).into())))]
+ pub inset: Sides<Option<Rel<Length>>>,
+
+ /// How much to expand the square's size without affecting the layout. See
+ /// the [box's documentation]($box.outset) for more details.
+ #[resolve]
+ #[fold]
+ pub outset: Sides<Option<Rel<Length>>>,
+
+ /// The content to place into the square. The square expands to fit this
+ /// content, keeping the 1-1 aspect ratio.
+ ///
+ /// When this is omitted, the square takes on a default size of at most
+ /// `{30pt}`.
+ #[positional]
+ #[borrowed]
+ pub body: Option<Content>,
+}
+
+impl Show for Packed<SquareElem> {
+ fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_square)
+ .with_width(self.width(styles))
+ .with_height(self.height(styles))
+ .pack()
+ .spanned(self.span()))
+ }
+}
+
+/// An ellipse with optional content.
+///
+/// # Example
+/// ```example
+/// // Without content.
+/// #ellipse(width: 35%, height: 30pt)
+///
+/// // With content.
+/// #ellipse[
+/// #set align(center)
+/// Automatically sized \
+/// to fit the content.
+/// ]
+/// ```
+#[elem(Show)]
+pub struct EllipseElem {
+ /// The ellipse's width, relative to its parent container.
+ pub width: Smart<Rel<Length>>,
+
+ /// The ellipse's height, relative to its parent container.
+ pub height: Sizing,
+
+ /// How to fill the ellipse. See the [rectangle's documentation]($rect.fill)
+ /// for more details.
+ pub fill: Option<Paint>,
+
+ /// How to stroke the ellipse. See the
+ /// [rectangle's documentation]($rect.stroke) for more details.
+ #[resolve]
+ #[fold]
+ pub stroke: Smart<Option<Stroke>>,
+
+ /// How much to pad the ellipse's content. See the
+ /// [box's documentation]($box.inset) for more details.
+ #[resolve]
+ #[fold]
+ #[default(Sides::splat(Some(Abs::pt(5.0).into())))]
+ pub inset: Sides<Option<Rel<Length>>>,
+
+ /// How much to expand the ellipse's size without affecting the layout. See
+ /// the [box's documentation]($box.outset) for more details.
+ #[resolve]
+ #[fold]
+ pub outset: Sides<Option<Rel<Length>>>,
+
+ /// The content to place into the ellipse.
+ ///
+ /// When this is omitted, the ellipse takes on a default size of at most
+ /// `{45pt}` by `{30pt}`.
+ #[positional]
+ #[borrowed]
+ pub body: Option<Content>,
+}
+
+impl Show for Packed<EllipseElem> {
+ fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_ellipse)
+ .with_width(self.width(styles))
+ .with_height(self.height(styles))
+ .pack()
+ .spanned(self.span()))
+ }
+}
+
+/// A circle with optional content.
+///
+/// # Example
+/// ```example
+/// // Without content.
+/// #circle(radius: 25pt)
+///
+/// // With content.
+/// #circle[
+/// #set align(center + horizon)
+/// Automatically \
+/// sized to fit.
+/// ]
+/// ```
+#[elem(Show)]
+pub struct CircleElem {
+ /// The circle's radius. This is mutually exclusive with `width` and
+ /// `height`.
+ #[external]
+ pub radius: Length,
+
+ /// The circle's width. This is mutually exclusive with `radius` and
+ /// `height`.
+ ///
+ /// In contrast to `radius`, this can be relative to the parent container's
+ /// width.
+ #[parse(
+ let size = args
+ .named::<Smart<Length>>("radius")?
+ .map(|s| s.map(|r| 2.0 * Rel::from(r)));
+ match size {
+ None => args.named("width")?,
+ size => size,
+ }
+ )]
+ pub width: Smart<Rel<Length>>,
+
+ /// The circle's height. This is mutually exclusive with `radius` and
+ /// `width`.
+ ///
+ /// In contrast to `radius`, this can be relative to the parent container's
+ /// height.
+ #[parse(match size {
+ None => args.named("height")?,
+ size => size.map(Into::into),
+ })]
+ pub height: Sizing,
+
+ /// How to fill the circle. See the [rectangle's documentation]($rect.fill)
+ /// for more details.
+ pub fill: Option<Paint>,
+
+ /// How to stroke the circle. See the
+ /// [rectangle's documentation]($rect.stroke) for more details.
+ #[resolve]
+ #[fold]
+ #[default(Smart::Auto)]
+ pub stroke: Smart<Option<Stroke>>,
+
+ /// How much to pad the circle's content. See the
+ /// [box's documentation]($box.inset) for more details.
+ #[resolve]
+ #[fold]
+ #[default(Sides::splat(Some(Abs::pt(5.0).into())))]
+ pub inset: Sides<Option<Rel<Length>>>,
+
+ /// How much to expand the circle's size without affecting the layout. See
+ /// the [box's documentation]($box.outset) for more details.
+ #[resolve]
+ #[fold]
+ pub outset: Sides<Option<Rel<Length>>>,
+
+ /// The content to place into the circle. The circle expands to fit this
+ /// content, keeping the 1-1 aspect ratio.
+ #[positional]
+ #[borrowed]
+ pub body: Option<Content>,
+}
+
+impl Show for Packed<CircleElem> {
+ fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_circle)
+ .with_width(self.width(styles))
+ .with_height(self.height(styles))
+ .pack()
+ .spanned(self.span()))
+ }
+}
+
+/// A geometric shape with optional fill and stroke.
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct Shape {
+ /// The shape's geometry.
+ pub geometry: Geometry,
+ /// The shape's background fill.
+ pub fill: Option<Paint>,
+ /// The shape's fill rule.
+ pub fill_rule: FillRule,
+ /// The shape's border stroke.
+ pub stroke: Option<FixedStroke>,
+}
+
+/// A path filling rule.
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)]
+pub enum FillRule {
+ /// Specifies that "inside" is computed by a non-zero sum of signed edge crossings.
+ #[default]
+ NonZero,
+ /// Specifies that "inside" is computed by an odd number of edge crossings.
+ EvenOdd,
+}
+
+/// A shape's geometry.
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub enum Geometry {
+ /// A line to a point (relative to its position).
+ Line(Point),
+ /// A rectangle with its origin in the topleft corner.
+ Rect(Size),
+ /// A bezier path.
+ Path(Path),
+}
+
+impl Geometry {
+ /// Fill the geometry without a stroke.
+ pub fn filled(self, fill: impl Into<Paint>) -> Shape {
+ Shape {
+ geometry: self,
+ fill: Some(fill.into()),
+ fill_rule: FillRule::default(),
+ stroke: None,
+ }
+ }
+
+ /// Stroke the geometry without a fill.
+ pub fn stroked(self, stroke: FixedStroke) -> Shape {
+ Shape {
+ geometry: self,
+ fill: None,
+ fill_rule: FillRule::default(),
+ stroke: Some(stroke),
+ }
+ }
+
+ /// The bounding box of the geometry.
+ pub fn bbox_size(&self) -> Size {
+ match self {
+ Self::Line(line) => Size::new(line.x, line.y),
+ Self::Rect(s) => *s,
+ Self::Path(p) => p.bbox_size(),
+ }
+ }
+}
diff --git a/crates/typst/src/visualize/stroke.rs b/crates/typst-library/src/visualize/stroke.rs
index 234ad1da..4ca10920 100644
--- a/crates/typst/src/visualize/stroke.rs
+++ b/crates/typst-library/src/visualize/stroke.rs
@@ -1,4 +1,5 @@
use ecow::EcoString;
+use typst_utils::{Numeric, Scalar};
use crate::diag::{HintedStrResult, SourceResult};
use crate::foundations::{
@@ -6,7 +7,6 @@ use crate::foundations::{
Resolve, Smart, StyleChain, Value,
};
use crate::layout::{Abs, Length};
-use crate::utils::{Numeric, Scalar};
use crate::visualize::{Color, Gradient, Paint, Pattern};
/// Defines how to draw a line.
diff --git a/crates/typst/translations/ar.txt b/crates/typst-library/translations/ar.txt
index 5c17d379..5c17d379 100644
--- a/crates/typst/translations/ar.txt
+++ b/crates/typst-library/translations/ar.txt
diff --git a/crates/typst/translations/ca.txt b/crates/typst-library/translations/ca.txt
index b16d2f9f..b16d2f9f 100644
--- a/crates/typst/translations/ca.txt
+++ b/crates/typst-library/translations/ca.txt
diff --git a/crates/typst/translations/cs.txt b/crates/typst-library/translations/cs.txt
index 13670586..13670586 100644
--- a/crates/typst/translations/cs.txt
+++ b/crates/typst-library/translations/cs.txt
diff --git a/crates/typst/translations/da.txt b/crates/typst-library/translations/da.txt
index 9664c3b0..9664c3b0 100644
--- a/crates/typst/translations/da.txt
+++ b/crates/typst-library/translations/da.txt
diff --git a/crates/typst/translations/de.txt b/crates/typst-library/translations/de.txt
index 8ba72adb..8ba72adb 100644
--- a/crates/typst/translations/de.txt
+++ b/crates/typst-library/translations/de.txt
diff --git a/crates/typst/translations/en.txt b/crates/typst-library/translations/en.txt
index 02e7accd..02e7accd 100644
--- a/crates/typst/translations/en.txt
+++ b/crates/typst-library/translations/en.txt
diff --git a/crates/typst/translations/es.txt b/crates/typst-library/translations/es.txt
index b0e56642..b0e56642 100644
--- a/crates/typst/translations/es.txt
+++ b/crates/typst-library/translations/es.txt
diff --git a/crates/typst/translations/et.txt b/crates/typst-library/translations/et.txt
index 01f23b4b..01f23b4b 100644
--- a/crates/typst/translations/et.txt
+++ b/crates/typst-library/translations/et.txt
diff --git a/crates/typst/translations/fi.txt b/crates/typst-library/translations/fi.txt
index da516d24..da516d24 100644
--- a/crates/typst/translations/fi.txt
+++ b/crates/typst-library/translations/fi.txt
diff --git a/crates/typst/translations/fr.txt b/crates/typst-library/translations/fr.txt
index 6c5b60f6..6c5b60f6 100644
--- a/crates/typst/translations/fr.txt
+++ b/crates/typst-library/translations/fr.txt
diff --git a/crates/typst/translations/gl.txt b/crates/typst-library/translations/gl.txt
index 7c26953d..7c26953d 100644
--- a/crates/typst/translations/gl.txt
+++ b/crates/typst-library/translations/gl.txt
diff --git a/crates/typst/translations/gr.txt b/crates/typst-library/translations/gr.txt
index 34aa3c44..34aa3c44 100644
--- a/crates/typst/translations/gr.txt
+++ b/crates/typst-library/translations/gr.txt
diff --git a/crates/typst/translations/he.txt b/crates/typst-library/translations/he.txt
index 7c3705a4..7c3705a4 100644
--- a/crates/typst/translations/he.txt
+++ b/crates/typst-library/translations/he.txt
diff --git a/crates/typst/translations/hu.txt b/crates/typst-library/translations/hu.txt
index f1b616b7..f1b616b7 100644
--- a/crates/typst/translations/hu.txt
+++ b/crates/typst-library/translations/hu.txt
diff --git a/crates/typst/translations/is.txt b/crates/typst-library/translations/is.txt
index dcfabacd..dcfabacd 100644
--- a/crates/typst/translations/is.txt
+++ b/crates/typst-library/translations/is.txt
diff --git a/crates/typst/translations/it.txt b/crates/typst-library/translations/it.txt
index 4c1cd90c..4c1cd90c 100644
--- a/crates/typst/translations/it.txt
+++ b/crates/typst-library/translations/it.txt
diff --git a/crates/typst/translations/ja.txt b/crates/typst-library/translations/ja.txt
index 6c1f6f9c..6c1f6f9c 100644
--- a/crates/typst/translations/ja.txt
+++ b/crates/typst-library/translations/ja.txt
diff --git a/crates/typst/translations/la.txt b/crates/typst-library/translations/la.txt
index 3ec63fff..3ec63fff 100644
--- a/crates/typst/translations/la.txt
+++ b/crates/typst-library/translations/la.txt
diff --git a/crates/typst/translations/nb.txt b/crates/typst-library/translations/nb.txt
index f51bcd75..f51bcd75 100644
--- a/crates/typst/translations/nb.txt
+++ b/crates/typst-library/translations/nb.txt
diff --git a/crates/typst/translations/nl.txt b/crates/typst-library/translations/nl.txt
index ecd464b3..ecd464b3 100644
--- a/crates/typst/translations/nl.txt
+++ b/crates/typst-library/translations/nl.txt
diff --git a/crates/typst/translations/nn.txt b/crates/typst-library/translations/nn.txt
index b067bc56..b067bc56 100644
--- a/crates/typst/translations/nn.txt
+++ b/crates/typst-library/translations/nn.txt
diff --git a/crates/typst/translations/pl.txt b/crates/typst-library/translations/pl.txt
index c4934257..c4934257 100644
--- a/crates/typst/translations/pl.txt
+++ b/crates/typst-library/translations/pl.txt
diff --git a/crates/typst/translations/pt-PT.txt b/crates/typst-library/translations/pt-PT.txt
index 6fbb586a..6fbb586a 100644
--- a/crates/typst/translations/pt-PT.txt
+++ b/crates/typst-library/translations/pt-PT.txt
diff --git a/crates/typst/translations/pt.txt b/crates/typst-library/translations/pt.txt
index a8372e6d..a8372e6d 100644
--- a/crates/typst/translations/pt.txt
+++ b/crates/typst-library/translations/pt.txt
diff --git a/crates/typst/translations/ro.txt b/crates/typst-library/translations/ro.txt
index 44d6bc8b..44d6bc8b 100644
--- a/crates/typst/translations/ro.txt
+++ b/crates/typst-library/translations/ro.txt
diff --git a/crates/typst/translations/ru.txt b/crates/typst-library/translations/ru.txt
index fccf7bc9..fccf7bc9 100644
--- a/crates/typst/translations/ru.txt
+++ b/crates/typst-library/translations/ru.txt
diff --git a/crates/typst/translations/sl.txt b/crates/typst-library/translations/sl.txt
index 1375c4dd..1375c4dd 100644
--- a/crates/typst/translations/sl.txt
+++ b/crates/typst-library/translations/sl.txt
diff --git a/crates/typst/translations/sq.txt b/crates/typst-library/translations/sq.txt
index 3b429545..3b429545 100644
--- a/crates/typst/translations/sq.txt
+++ b/crates/typst-library/translations/sq.txt
diff --git a/crates/typst/translations/sr.txt b/crates/typst-library/translations/sr.txt
index 3a2f92b9..3a2f92b9 100644
--- a/crates/typst/translations/sr.txt
+++ b/crates/typst-library/translations/sr.txt
diff --git a/crates/typst/translations/sv.txt b/crates/typst-library/translations/sv.txt
index 61a39403..61a39403 100644
--- a/crates/typst/translations/sv.txt
+++ b/crates/typst-library/translations/sv.txt
diff --git a/crates/typst/translations/tl.txt b/crates/typst-library/translations/tl.txt
index a7b6b571..a7b6b571 100644
--- a/crates/typst/translations/tl.txt
+++ b/crates/typst-library/translations/tl.txt
diff --git a/crates/typst/translations/tr.txt b/crates/typst-library/translations/tr.txt
index a7068a4f..a7068a4f 100644
--- a/crates/typst/translations/tr.txt
+++ b/crates/typst-library/translations/tr.txt
diff --git a/crates/typst/translations/ua.txt b/crates/typst-library/translations/ua.txt
index 30b409a6..30b409a6 100644
--- a/crates/typst/translations/ua.txt
+++ b/crates/typst-library/translations/ua.txt
diff --git a/crates/typst/translations/vi.txt b/crates/typst-library/translations/vi.txt
index a65ac09a..a65ac09a 100644
--- a/crates/typst/translations/vi.txt
+++ b/crates/typst-library/translations/vi.txt
diff --git a/crates/typst/translations/zh-TW.txt b/crates/typst-library/translations/zh-TW.txt
index af6d5950..af6d5950 100644
--- a/crates/typst/translations/zh-TW.txt
+++ b/crates/typst-library/translations/zh-TW.txt
diff --git a/crates/typst/translations/zh.txt b/crates/typst-library/translations/zh.txt
index d4b86544..d4b86544 100644
--- a/crates/typst/translations/zh.txt
+++ b/crates/typst-library/translations/zh.txt
diff --git a/crates/typst-macros/src/cast.rs b/crates/typst-macros/src/cast.rs
index c732ce6b..9254cdb9 100644
--- a/crates/typst-macros/src/cast.rs
+++ b/crates/typst-macros/src/cast.rs
@@ -108,7 +108,7 @@ pub fn cast(stream: TokenStream) -> Result<TokenStream> {
let from_value = (!input.from_value.is_empty() || input.dynamic).then(|| {
quote! {
impl #foundations::FromValue for #ty {
- fn from_value(value: #foundations::Value) -> ::typst::diag::HintedStrResult<Self> {
+ fn from_value(value: #foundations::Value) -> ::typst_library::diag::HintedStrResult<Self> {
#from_value_body
}
}
diff --git a/crates/typst-macros/src/elem.rs b/crates/typst-macros/src/elem.rs
index a3666e1d..3b968f76 100644
--- a/crates/typst-macros/src/elem.rs
+++ b/crates/typst-macros/src/elem.rs
@@ -314,6 +314,7 @@ fn create(element: &Elem) -> Result<TokenStream> {
let fields_impl = create_fields_impl(element);
let repr_impl = element.cannot("Repr").then(|| create_repr_impl(element));
let locatable_impl = element.can("Locatable").then(|| create_locatable_impl(element));
+ let mathy_impl = element.can("Mathy").then(|| create_mathy_impl(element));
let into_value_impl = create_into_value_impl(element);
// We use a const block to create an anonymous scope, as to not leak any
@@ -333,6 +334,7 @@ fn create(element: &Elem) -> Result<TokenStream> {
#partial_eq_impl
#repr_impl
#locatable_impl
+ #mathy_impl
#into_value_impl
};
})
@@ -632,7 +634,7 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
let Elem { name, ident, title, scope, keywords, docs, .. } = element;
let local_name = if element.can("LocalName") {
- quote! { Some(<#foundations::Packed<#ident> as ::typst::text::LocalName>::local_name) }
+ quote! { Some(<#foundations::Packed<#ident> as ::typst_library::text::LocalName>::local_name) }
} else {
quote! { None }
};
@@ -761,9 +763,9 @@ fn create_construct_impl(element: &Elem) -> TokenStream {
quote! {
impl #foundations::Construct for #ident {
fn construct(
- engine: &mut ::typst::engine::Engine,
+ engine: &mut ::typst_library::engine::Engine,
args: &mut #foundations::Args,
- ) -> ::typst::diag::SourceResult<#foundations::Content> {
+ ) -> ::typst_library::diag::SourceResult<#foundations::Content> {
#(#setup)*
Ok(#foundations::Content::new(Self { #(#fields),* }))
}
@@ -788,9 +790,9 @@ fn create_set_impl(element: &Elem) -> TokenStream {
quote! {
impl #foundations::Set for #ident {
fn set(
- engine: &mut ::typst::engine::Engine,
+ engine: &mut ::typst_library::engine::Engine,
args: &mut #foundations::Args,
- ) -> ::typst::diag::SourceResult<#foundations::Styles> {
+ ) -> ::typst_library::diag::SourceResult<#foundations::Styles> {
let mut styles = #foundations::Styles::new();
#(#handlers)*
Ok(styles)
@@ -837,7 +839,7 @@ fn create_capable_impl(element: &Elem) -> TokenStream {
// Safety: The vtable function doesn't require initialized
// data, so it's fine to use a dangling pointer.
return Some(unsafe {
- ::typst::utils::fat::vtable(dangling as *const dyn #capability)
+ ::typst_utils::fat::vtable(dangling as *const dyn #capability)
});
}
}
@@ -1047,7 +1049,13 @@ fn create_repr_impl(element: &Elem) -> TokenStream {
/// Creates the element's `Locatable` implementation.
fn create_locatable_impl(element: &Elem) -> TokenStream {
let ident = &element.ident;
- quote! { impl ::typst::introspection::Locatable for #foundations::Packed<#ident> {} }
+ quote! { impl ::typst_library::introspection::Locatable for #foundations::Packed<#ident> {} }
+}
+
+/// Creates the element's `Mathy` implementation.
+fn create_mathy_impl(element: &Elem) -> TokenStream {
+ let ident = &element.ident;
+ quote! { impl ::typst_library::math::Mathy for #foundations::Packed<#ident> {} }
}
/// Creates the element's `IntoValue` implementation.
diff --git a/crates/typst-macros/src/func.rs b/crates/typst-macros/src/func.rs
index 8d3fd439..d402efd9 100644
--- a/crates/typst-macros/src/func.rs
+++ b/crates/typst-macros/src/func.rs
@@ -262,6 +262,7 @@ fn create(func: &Func, item: &syn::ItemFn) -> TokenStream {
let ident_data = quote::format_ident!("{ident}_data");
quote! {
#[doc(hidden)]
+ #[allow(non_snake_case)]
#vis fn #ident_data() -> &'static #foundations::NativeFuncData {
static DATA: #foundations::NativeFuncData = #data;
&DATA
diff --git a/crates/typst-macros/src/symbols.rs b/crates/typst-macros/src/symbols.rs
index 6b35f87f..9917f436 100644
--- a/crates/typst-macros/src/symbols.rs
+++ b/crates/typst-macros/src/symbols.rs
@@ -5,6 +5,8 @@ use syn::parse::{Parse, ParseStream, Parser};
use syn::punctuated::Punctuated;
use syn::{Ident, LitChar, Path, Result, Token};
+use crate::util::foundations;
+
/// Expand the `symbols!` macro.
pub fn symbols(stream: TokenStream) -> Result<TokenStream> {
let list: Punctuated<Symbol, Token![,]> =
@@ -14,7 +16,7 @@ pub fn symbols(stream: TokenStream) -> Result<TokenStream> {
let kind = match &symbol.kind {
Kind::Single(c, h) => {
let symbol = construct_sym_char(c, h);
- quote! { ::typst::symbols::Symbol::single(#symbol), }
+ quote! { #foundations::Symbol::single(#symbol), }
}
Kind::Multiple(variants) => {
let variants = variants.iter().map(|variant| {
@@ -24,7 +26,7 @@ pub fn symbols(stream: TokenStream) -> Result<TokenStream> {
quote! { (#name, #symbol) }
});
quote! {
- ::typst::symbols::Symbol::list(&[#(#variants),*])
+ #foundations::Symbol::list(&[#(#variants),*])
}
}
};
@@ -35,11 +37,11 @@ pub fn symbols(stream: TokenStream) -> Result<TokenStream> {
fn construct_sym_char(ch: &LitChar, handler: &Handler) -> TokenStream {
match &handler.0 {
- None => quote! { ::typst::symbols::SymChar::pure(#ch), },
+ None => quote! { #foundations::SymChar::pure(#ch), },
Some(path) => quote! {
- ::typst::symbols::SymChar::with_func(
+ #foundations::SymChar::with_func(
#ch,
- <#path as ::typst::foundations::NativeFunc>::func,
+ <#path as ::typst_library::foundations::NativeFunc>::func,
),
},
}
diff --git a/crates/typst-macros/src/time.rs b/crates/typst-macros/src/time.rs
index b0758dc2..94fa4e5b 100644
--- a/crates/typst-macros/src/time.rs
+++ b/crates/typst-macros/src/time.rs
@@ -28,17 +28,15 @@ impl Parse for Meta {
fn create(meta: Meta, mut item: syn::ItemFn) -> TokenStream {
let name = meta.name.unwrap_or_else(|| item.sig.ident.to_string());
- let span = meta
- .span
- .as_ref()
- .map(|span| quote! { Some(#span) })
- .unwrap_or_else(|| quote! { None });
+ let construct = match meta.span.as_ref() {
+ Some(span) => quote! { with_span(#name, Some(#span.into_raw())) },
+ None => quote! { new(#name) },
+ };
item.block.stmts.insert(
0,
parse_quote! {
- #[cfg(not(target_arch = "wasm32"))]
- let __scope = ::typst_timing::TimingScope::new(#name, #span);
+ let __scope = ::typst_timing::TimingScope::#construct;
},
);
diff --git a/crates/typst-macros/src/util.rs b/crates/typst-macros/src/util.rs
index 4d8255e0..e8c0910b 100644
--- a/crates/typst-macros/src/util.rs
+++ b/crates/typst-macros/src/util.rs
@@ -200,13 +200,13 @@ impl<T: Parse> Parse for Array<T> {
}
}
-/// Shorthand for `::typst::foundations`.
+/// Shorthand for `::typst_library::foundations`.
#[allow(non_camel_case_types)]
pub struct foundations;
impl quote::ToTokens for foundations {
fn to_tokens(&self, tokens: &mut TokenStream) {
- quote! { ::typst::foundations }.to_tokens(tokens);
+ quote! { ::typst_library::foundations }.to_tokens(tokens);
}
}
diff --git a/crates/typst-pdf/Cargo.toml b/crates/typst-pdf/Cargo.toml
index 64229945..4cef14bd 100644
--- a/crates/typst-pdf/Cargo.toml
+++ b/crates/typst-pdf/Cargo.toml
@@ -13,10 +13,13 @@ keywords = { workspace = true }
readme = { workspace = true }
[dependencies]
-typst = { workspace = true }
typst-assets = { workspace = true }
+typst-library = { workspace = true }
typst-macros = { workspace = true }
+typst-syntax = { workspace = true }
typst-timing = { workspace = true }
+typst-utils = { workspace = true }
+arrayvec = { workspace = true }
base64 = { workspace = true }
bytemuck = { workspace = true }
comemo = { workspace = true }
@@ -26,12 +29,10 @@ indexmap = { workspace = true }
miniz_oxide = { workspace = true }
once_cell = { workspace = true }
pdf-writer = { workspace = true }
-arrayvec = { workspace = true }
serde = { workspace = true }
subsetter = { workspace = true }
svg2pdf = { workspace = true }
ttf-parser = { workspace = true }
-unscanny = { workspace = true }
xmp-writer = { workspace = true }
[lints]
diff --git a/crates/typst-pdf/src/catalog.rs b/crates/typst-pdf/src/catalog.rs
index 7aba0eb6..1412afe6 100644
--- a/crates/typst-pdf/src/catalog.rs
+++ b/crates/typst-pdf/src/catalog.rs
@@ -4,11 +4,11 @@ use ecow::eco_format;
use pdf_writer::types::Direction;
use pdf_writer::writers::PageLabel;
use pdf_writer::{Finish, Name, Pdf, Ref, Str, TextStr};
-use typst::diag::{bail, SourceResult};
-use typst::foundations::{Datetime, Smart};
-use typst::layout::Dir;
-use typst::syntax::Span;
-use typst::text::Lang;
+use typst_library::diag::{bail, SourceResult};
+use typst_library::foundations::{Datetime, Smart};
+use typst_library::layout::Dir;
+use typst_library::text::Lang;
+use typst_syntax::Span;
use xmp_writer::{DateTime, LangId, RenditionClass, Timezone, XmpWriter};
use crate::page::PdfPageLabel;
diff --git a/crates/typst-pdf/src/color.rs b/crates/typst-pdf/src/color.rs
index a19d776a..26f2044c 100644
--- a/crates/typst-pdf/src/color.rs
+++ b/crates/typst-pdf/src/color.rs
@@ -1,9 +1,9 @@
use arrayvec::ArrayVec;
use once_cell::sync::Lazy;
use pdf_writer::{writers, Chunk, Dict, Filter, Name, Ref};
-use typst::diag::{bail, SourceResult};
-use typst::syntax::Span;
-use typst::visualize::{Color, ColorSpace, Paint};
+use typst_library::diag::{bail, SourceResult};
+use typst_library::visualize::{Color, ColorSpace, Paint};
+use typst_syntax::Span;
use crate::{content, deflate, PdfChunk, PdfOptions, Renumber, WithResources};
diff --git a/crates/typst-pdf/src/color_font.rs b/crates/typst-pdf/src/color_font.rs
index f6fea396..1183e966 100644
--- a/crates/typst-pdf/src/color_font.rs
+++ b/crates/typst-pdf/src/color_font.rs
@@ -12,16 +12,15 @@ use indexmap::IndexMap;
use pdf_writer::types::UnicodeCmap;
use pdf_writer::writers::WMode;
use pdf_writer::{Filter, Finish, Name, Rect, Ref};
-use typst::diag::{bail, error, SourceDiagnostic, SourceResult};
-use typst::foundations::Repr;
-use typst::layout::Em;
-use typst::text::color::glyph_frame;
-use typst::text::{Font, Glyph, TextItemView};
+use typst_library::diag::{bail, error, SourceDiagnostic, SourceResult};
+use typst_library::foundations::Repr;
+use typst_library::layout::Em;
+use typst_library::text::color::glyph_frame;
+use typst_library::text::{Font, Glyph, TextItemView};
-use crate::content;
use crate::font::{base_font_name, write_font_descriptor, CMAP_NAME, SYSTEM_INFO};
use crate::resources::{Resources, ResourcesRefs};
-use crate::{EmExt, PdfChunk, PdfOptions, WithGlobalRefs};
+use crate::{content, EmExt, PdfChunk, PdfOptions, WithGlobalRefs};
/// Write color fonts in the PDF document.
///
diff --git a/crates/typst-pdf/src/content.rs b/crates/typst-pdf/src/content.rs
index babb3e57..ce72365d 100644
--- a/crates/typst-pdf/src/content.rs
+++ b/crates/typst-pdf/src/content.rs
@@ -10,20 +10,20 @@ use pdf_writer::types::{
};
use pdf_writer::writers::PositionedItems;
use pdf_writer::{Content, Finish, Name, Rect, Str};
-use typst::diag::{bail, error, SourceDiagnostic, SourceResult};
-use typst::foundations::Repr;
-use typst::layout::{
+use typst_library::diag::{bail, error, SourceDiagnostic, SourceResult};
+use typst_library::foundations::Repr;
+use typst_library::layout::{
Abs, Em, Frame, FrameItem, GroupItem, Point, Ratio, Size, Transform,
};
-use typst::model::Destination;
-use typst::syntax::Span;
-use typst::text::color::should_outline;
-use typst::text::{Font, Glyph, TextItem, TextItemView};
-use typst::utils::{Deferred, Numeric, SliceExt};
-use typst::visualize::{
+use typst_library::model::Destination;
+use typst_library::text::color::should_outline;
+use typst_library::text::{Font, Glyph, TextItem, TextItemView};
+use typst_library::visualize::{
FillRule, FixedStroke, Geometry, Image, LineCap, LineJoin, Paint, Path, PathItem,
Shape,
};
+use typst_syntax::Span;
+use typst_utils::{Deferred, Numeric, SliceExt};
use crate::color::PaintEncode;
use crate::color_font::ColorFontMap;
diff --git a/crates/typst-pdf/src/extg.rs b/crates/typst-pdf/src/extg.rs
index 12bfa26a..06617d8d 100644
--- a/crates/typst-pdf/src/extg.rs
+++ b/crates/typst-pdf/src/extg.rs
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use pdf_writer::Ref;
-use typst::diag::SourceResult;
+use typst_library::diag::SourceResult;
use crate::{PdfChunk, WithGlobalRefs};
diff --git a/crates/typst-pdf/src/font.rs b/crates/typst-pdf/src/font.rs
index c870d32a..93d75e50 100644
--- a/crates/typst-pdf/src/font.rs
+++ b/crates/typst-pdf/src/font.rs
@@ -8,10 +8,10 @@ use pdf_writer::writers::{FontDescriptor, WMode};
use pdf_writer::{Chunk, Filter, Finish, Name, Rect, Ref, Str};
use subsetter::GlyphRemapper;
use ttf_parser::{name_id, GlyphId, Tag};
-use typst::diag::{At, SourceResult};
-use typst::syntax::Span;
-use typst::text::Font;
-use typst::utils::SliceExt;
+use typst_library::diag::{At, SourceResult};
+use typst_library::text::Font;
+use typst_syntax::Span;
+use typst_utils::SliceExt;
use crate::{deflate, EmExt, NameExt, PdfChunk, WithGlobalRefs};
@@ -249,7 +249,7 @@ pub(crate) fn base_font_name<T: Hash>(font: &Font, glyphs: &T) -> EcoString {
/// Produce a unique 6 letter tag for a glyph set.
pub(crate) fn subset_tag<T: Hash>(glyphs: &T) -> EcoString {
const BASE: u128 = 26;
- let mut hash = typst::utils::hash128(&glyphs);
+ let mut hash = typst_utils::hash128(&glyphs);
let mut letter = [b'A'; SUBSET_TAG_LEN];
for l in letter.iter_mut() {
*l = b'A' + (hash % BASE) as u8;
diff --git a/crates/typst-pdf/src/gradient.rs b/crates/typst-pdf/src/gradient.rs
index be0a3ea0..6cd4c1ae 100644
--- a/crates/typst-pdf/src/gradient.rs
+++ b/crates/typst-pdf/src/gradient.rs
@@ -6,18 +6,17 @@ use ecow::eco_format;
use pdf_writer::types::{ColorSpaceOperand, FunctionShadingType};
use pdf_writer::writers::StreamShadingType;
use pdf_writer::{Filter, Finish, Name, Ref};
-use typst::diag::SourceResult;
-use typst::layout::{Abs, Angle, Point, Quadrant, Ratio, Transform};
-use typst::utils::Numeric;
-use typst::visualize::{
+use typst_library::diag::SourceResult;
+use typst_library::layout::{Abs, Angle, Point, Quadrant, Ratio, Transform};
+use typst_library::visualize::{
Color, ColorSpace, Gradient, RatioOrAngle, RelativeTo, WeightedColor,
};
+use typst_utils::Numeric;
use crate::color::{
self, check_cmyk_allowed, ColorSpaceExt, PaintEncode, QuantizedColor,
};
-use crate::{content, WithGlobalRefs};
-use crate::{deflate, transform_to_array, AbsExt, PdfChunk};
+use crate::{content, deflate, transform_to_array, AbsExt, PdfChunk, WithGlobalRefs};
/// A unique-transform-aspect-ratio combination that will be encoded into the
/// PDF.
diff --git a/crates/typst-pdf/src/image.rs b/crates/typst-pdf/src/image.rs
index bff09e09..9651d31b 100644
--- a/crates/typst-pdf/src/image.rs
+++ b/crates/typst-pdf/src/image.rs
@@ -4,11 +4,11 @@ use std::io::Cursor;
use ecow::eco_format;
use image::{DynamicImage, GenericImageView, Rgba};
use pdf_writer::{Chunk, Filter, Finish, Ref};
-use typst::diag::{At, SourceResult, StrResult};
-use typst::utils::Deferred;
-use typst::visualize::{
+use typst_library::diag::{At, SourceResult, StrResult};
+use typst_library::visualize::{
ColorSpace, Image, ImageKind, RasterFormat, RasterImage, SvgImage,
};
+use typst_utils::Deferred;
use crate::{color, deflate, PdfChunk, WithGlobalRefs};
diff --git a/crates/typst-pdf/src/lib.rs b/crates/typst-pdf/src/lib.rs
index 7df77f10..efc99b74 100644
--- a/crates/typst-pdf/src/lib.rs
+++ b/crates/typst-pdf/src/lib.rs
@@ -22,14 +22,14 @@ use std::ops::{Deref, DerefMut};
use base64::Engine;
use pdf_writer::{Chunk, Name, Pdf, Ref, Str, TextStr};
use serde::{Deserialize, Serialize};
-use typst::diag::{bail, SourceResult, StrResult};
-use typst::foundations::{Datetime, Smart};
-use typst::layout::{Abs, Em, PageRanges, Transform};
-use typst::model::Document;
-use typst::syntax::Span;
-use typst::text::Font;
-use typst::utils::Deferred;
-use typst::visualize::Image;
+use typst_library::diag::{bail, SourceResult, StrResult};
+use typst_library::foundations::{Datetime, Smart};
+use typst_library::layout::{Abs, Em, PageRanges, Transform};
+use typst_library::model::Document;
+use typst_library::text::Font;
+use typst_library::visualize::Image;
+use typst_syntax::Span;
+use typst_utils::Deferred;
use crate::catalog::write_catalog;
use crate::color::{alloc_color_functions_refs, ColorFunctionRefs};
@@ -518,7 +518,7 @@ fn deflate_deferred(content: Vec<u8>) -> Deferred<Vec<u8>> {
/// Create a base64-encoded hash of the value.
fn hash_base64<T: Hash>(value: &T) -> String {
base64::engine::general_purpose::STANDARD
- .encode(typst::utils::hash128(value).to_be_bytes())
+ .encode(typst_utils::hash128(value).to_be_bytes())
}
/// Additional methods for [`Abs`].
diff --git a/crates/typst-pdf/src/named_destination.rs b/crates/typst-pdf/src/named_destination.rs
index 2d893526..90552335 100644
--- a/crates/typst-pdf/src/named_destination.rs
+++ b/crates/typst-pdf/src/named_destination.rs
@@ -2,11 +2,11 @@ use std::collections::{HashMap, HashSet};
use pdf_writer::writers::Destination;
use pdf_writer::{Ref, Str};
-use typst::diag::SourceResult;
-use typst::foundations::{Label, NativeElement};
-use typst::introspection::Location;
-use typst::layout::Abs;
-use typst::model::HeadingElem;
+use typst_library::diag::SourceResult;
+use typst_library::foundations::{Label, NativeElement};
+use typst_library::introspection::Location;
+use typst_library::layout::Abs;
+use typst_library::model::HeadingElem;
use crate::{AbsExt, PdfChunk, Renumber, StrExt, WithGlobalRefs};
diff --git a/crates/typst-pdf/src/outline.rs b/crates/typst-pdf/src/outline.rs
index 5c099b89..b9e71319 100644
--- a/crates/typst-pdf/src/outline.rs
+++ b/crates/typst-pdf/src/outline.rs
@@ -1,9 +1,9 @@
use std::num::NonZeroUsize;
use pdf_writer::{Finish, Pdf, Ref, TextStr};
-use typst::foundations::{NativeElement, Packed, StyleChain};
-use typst::layout::Abs;
-use typst::model::HeadingElem;
+use typst_library::foundations::{NativeElement, Packed, StyleChain};
+use typst_library::layout::Abs;
+use typst_library::model::HeadingElem;
use crate::{AbsExt, TextStrExt, WithEverything};
diff --git a/crates/typst-pdf/src/page.rs b/crates/typst-pdf/src/page.rs
index 631eec11..27daf6c9 100644
--- a/crates/typst-pdf/src/page.rs
+++ b/crates/typst-pdf/src/page.rs
@@ -4,15 +4,15 @@ use std::num::NonZeroUsize;
use ecow::EcoString;
use pdf_writer::types::{ActionType, AnnotationFlags, AnnotationType, NumberingStyle};
use pdf_writer::{Filter, Finish, Name, Rect, Ref, Str};
-use typst::diag::SourceResult;
-use typst::foundations::Label;
-use typst::introspection::Location;
-use typst::layout::{Abs, Page};
-use typst::model::{Destination, Numbering};
+use typst_library::diag::SourceResult;
+use typst_library::foundations::Label;
+use typst_library::introspection::Location;
+use typst_library::layout::{Abs, Page};
+use typst_library::model::{Destination, Numbering};
-use crate::content;
use crate::{
- AbsExt, PdfChunk, PdfOptions, Resources, WithDocument, WithRefs, WithResources,
+ content, AbsExt, PdfChunk, PdfOptions, Resources, WithDocument, WithRefs,
+ WithResources,
};
/// Construct page objects.
@@ -252,7 +252,8 @@ impl PdfPageLabel {
// If there is a suffix, we cannot use the common style optimisation,
// since PDF does not provide a suffix field.
let style = if pat.suffix.is_empty() {
- use {typst::model::NumberingKind as Kind, PdfPageLabelStyle as Style};
+ use typst_library::model::NumberingKind as Kind;
+ use PdfPageLabelStyle as Style;
match kind {
Kind::Arabic => Some(Style::Arabic),
Kind::LowerRoman => Some(Style::LowerRoman),
diff --git a/crates/typst-pdf/src/pattern.rs b/crates/typst-pdf/src/pattern.rs
index fd9d9dbb..ddc22fbd 100644
--- a/crates/typst-pdf/src/pattern.rs
+++ b/crates/typst-pdf/src/pattern.rs
@@ -3,15 +3,14 @@ use std::collections::HashMap;
use ecow::eco_format;
use pdf_writer::types::{ColorSpaceOperand, PaintType, TilingType};
use pdf_writer::{Filter, Name, Rect, Ref};
-use typst::diag::SourceResult;
-use typst::layout::{Abs, Ratio, Transform};
-use typst::utils::Numeric;
-use typst::visualize::{Pattern, RelativeTo};
+use typst_library::diag::SourceResult;
+use typst_library::layout::{Abs, Ratio, Transform};
+use typst_library::visualize::{Pattern, RelativeTo};
+use typst_utils::Numeric;
use crate::color::PaintEncode;
-use crate::content;
use crate::resources::{Remapper, ResourcesRefs};
-use crate::{transform_to_array, PdfChunk, Resources, WithGlobalRefs};
+use crate::{content, transform_to_array, PdfChunk, Resources, WithGlobalRefs};
/// Writes the actual patterns (tiling patterns) to the PDF.
/// This is performed once after writing all pages.
diff --git a/crates/typst-pdf/src/resources.rs b/crates/typst-pdf/src/resources.rs
index fabf0b3f..d3fde5dd 100644
--- a/crates/typst-pdf/src/resources.rs
+++ b/crates/typst-pdf/src/resources.rs
@@ -12,11 +12,11 @@ use std::hash::Hash;
use ecow::{eco_format, EcoString};
use pdf_writer::{Dict, Finish, Name, Ref};
use subsetter::GlyphRemapper;
-use typst::diag::{SourceResult, StrResult};
-use typst::syntax::Span;
-use typst::text::{Font, Lang};
-use typst::utils::Deferred;
-use typst::visualize::Image;
+use typst_library::diag::{SourceResult, StrResult};
+use typst_library::text::{Font, Lang};
+use typst_library::visualize::Image;
+use typst_syntax::Span;
+use typst_utils::Deferred;
use crate::color::ColorSpaces;
use crate::color_font::ColorFontMap;
diff --git a/crates/typst-realize/Cargo.toml b/crates/typst-realize/Cargo.toml
new file mode 100644
index 00000000..78bda259
--- /dev/null
+++ b/crates/typst-realize/Cargo.toml
@@ -0,0 +1,29 @@
+[package]
+name = "typst-realize"
+description = "Typst's realization subsystem."
+version = { workspace = true }
+rust-version = { workspace = true }
+authors = { workspace = true }
+edition = { workspace = true }
+homepage = { workspace = true }
+repository = { workspace = true }
+license = { workspace = true }
+categories = { workspace = true }
+keywords = { workspace = true }
+readme = { workspace = true }
+
+[dependencies]
+typst-library = { workspace = true }
+typst-macros = { workspace = true }
+typst-syntax = { workspace = true }
+typst-timing = { workspace = true }
+typst-utils = { workspace = true }
+arrayvec = { workspace = true }
+bumpalo = { workspace = true }
+comemo = { workspace = true }
+ecow = { workspace = true }
+once_cell = { workspace = true }
+regex = { workspace = true }
+
+[lints]
+workspace = true
diff --git a/crates/typst/src/realize.rs b/crates/typst-realize/src/lib.rs
index b6a969ba..58668962 100644
--- a/crates/typst/src/realize.rs
+++ b/crates/typst-realize/src/lib.rs
@@ -1,7 +1,8 @@
-//! Realization of content.
+//! Typst's realization subsystem.
//!
//! *Realization* is the process of recursively applying styling and, in
-//! particular, show rules to produce well-known elements that can be laid out.
+//! particular, show rules to produce well-known elements that can be processed
+//! further.
use std::borrow::Cow;
@@ -10,29 +11,26 @@ use bumpalo::collections::{String as BumpString, Vec as BumpVec};
use comemo::Track;
use ecow::EcoString;
use once_cell::unsync::Lazy;
-
-use crate::diag::{bail, At, SourceResult};
-use crate::engine::Engine;
-use crate::foundations::{
+use typst_library::diag::{bail, At, SourceResult};
+use typst_library::engine::Engine;
+use typst_library::foundations::{
Content, Context, ContextElem, Element, NativeElement, Recipe, RecipeIndex, Selector,
SequenceElem, Show, ShowSet, Style, StyleChain, StyleVec, StyledElem, Styles,
Synthesize, Transformation,
};
-use crate::introspection::{Locatable, SplitLocator, Tag, TagElem};
-use crate::layout::{
+use typst_library::introspection::{Locatable, SplitLocator, Tag, TagElem};
+use typst_library::layout::{
AlignElem, BoxElem, HElem, InlineElem, PageElem, PagebreakElem, VElem,
};
-use crate::math::{EquationElem, LayoutMath};
-use crate::model::{
- CiteElem, CiteGroup, DocumentElem, DocumentInfo, EnumElem, ListElem, ListItemLike,
- ListLike, ParElem, ParbreakElem, TermsElem,
+use typst_library::math::{EquationElem, Mathy};
+use typst_library::model::{
+ CiteElem, CiteGroup, DocumentElem, EnumElem, ListElem, ListItemLike, ListLike,
+ ParElem, ParbreakElem, TermsElem,
};
-use crate::syntax::Span;
-use crate::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem};
-use crate::utils::{SliceExt, SmallBitSet};
-
-/// A pair of content and a style chain that applies to it.
-pub type Pair<'a> = (&'a Content, StyleChain<'a>);
+use typst_library::routines::{Arenas, Pair, RealizationKind};
+use typst_library::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem};
+use typst_syntax::Span;
+use typst_utils::{SliceExt, SmallBitSet};
/// Realize content into a flat list of well-known, styled items.
#[typst_macros::time(name = "realize")]
@@ -65,30 +63,6 @@ pub fn realize<'a>(
Ok(s.sink)
}
-/// Defines what kind of realization we are performing.
-pub enum RealizationKind<'a> {
- /// This the root realization for the document. Requires a mutable reference
- /// to document metadata that will be filled from `set document` rules.
- Root(&'a mut DocumentInfo),
- /// A nested realization in a container (e.g. a `block`).
- Container,
- /// A realization within math.
- Math,
-}
-
-/// Temporary storage arenas for lifetime extension during realization.
-///
-/// Must be kept live while the content returned from realization is processed.
-#[derive(Default)]
-pub struct Arenas {
- /// A typed arena for owned content.
- pub content: typed_arena::Arena<Content>,
- /// A typed arena for owned styles.
- pub styles: typed_arena::Arena<Styles>,
- /// An untyped arena for everything that is `Copy`.
- pub bump: bumpalo::Bump,
-}
-
/// Mutable state for realization.
///
/// Sadly, we need that many lifetimes because &mut references are invariant and
@@ -323,8 +297,8 @@ fn visit_math_rules<'a>(
}
}
} else {
- // Transparently wrap math-y content into equations.
- if content.can::<dyn LayoutMath>() && !content.is::<EquationElem>() {
+ // Transparently wrap mathy content into equations.
+ if content.can::<dyn Mathy>() && !content.is::<EquationElem>() {
let eq = EquationElem::new(content.clone()).pack().spanned(content.span());
visit(s, s.store(eq), styles)?;
return Ok(true);
@@ -523,7 +497,7 @@ fn prepare(
//
// The element could already have a location even if it is not prepared
// when it stems from a query.
- let key = crate::utils::hash128(&target);
+ let key = typst_utils::hash128(&target);
if target.location().is_none()
&& (target.can::<dyn Locatable>() || target.label().is_some())
{
diff --git a/crates/typst-render/Cargo.toml b/crates/typst-render/Cargo.toml
index 56a18e80..7d01d7e3 100644
--- a/crates/typst-render/Cargo.toml
+++ b/crates/typst-render/Cargo.toml
@@ -13,7 +13,7 @@ keywords = { workspace = true }
readme = { workspace = true }
[dependencies]
-typst = { workspace = true }
+typst-library = { workspace = true }
typst-macros = { workspace = true }
typst-timing = { workspace = true }
bytemuck = { workspace = true }
@@ -21,10 +21,8 @@ comemo = { workspace = true }
image = { workspace = true }
pixglyph = { workspace = true }
resvg = { workspace = true }
-roxmltree = { workspace = true }
tiny-skia = { workspace = true }
ttf-parser = { workspace = true }
-usvg = { workspace = true }
[lints]
workspace = true
diff --git a/crates/typst-render/src/image.rs b/crates/typst-render/src/image.rs
index dcbf7982..27b03911 100644
--- a/crates/typst-render/src/image.rs
+++ b/crates/typst-render/src/image.rs
@@ -3,8 +3,8 @@ use std::sync::Arc;
use image::imageops::FilterType;
use image::{GenericImageView, Rgba};
use tiny_skia as sk;
-use typst::layout::Size;
-use typst::visualize::{Image, ImageKind};
+use typst_library::layout::Size;
+use typst_library::visualize::{Image, ImageKind};
use crate::{AbsExt, State};
diff --git a/crates/typst-render/src/lib.rs b/crates/typst-render/src/lib.rs
index d5eeacce..8de3852d 100644
--- a/crates/typst-render/src/lib.rs
+++ b/crates/typst-render/src/lib.rs
@@ -6,11 +6,11 @@ mod shape;
mod text;
use tiny_skia as sk;
-use typst::layout::{
+use typst_library::layout::{
Abs, Axes, Frame, FrameItem, FrameKind, GroupItem, Page, Point, Size, Transform,
};
-use typst::model::Document;
-use typst::visualize::{Color, Geometry, Paint};
+use typst_library::model::Document;
+use typst_library::visualize::{Color, Geometry, Paint};
/// Export a page into a raster image.
///
diff --git a/crates/typst-render/src/paint.rs b/crates/typst-render/src/paint.rs
index 3a507ca4..689f28a8 100644
--- a/crates/typst-render/src/paint.rs
+++ b/crates/typst-render/src/paint.rs
@@ -1,8 +1,8 @@
use std::sync::Arc;
use tiny_skia as sk;
-use typst::layout::{Axes, Point, Ratio, Size};
-use typst::visualize::{Color, Gradient, Paint, Pattern, RelativeTo};
+use typst_library::layout::{Axes, Point, Ratio, Size};
+use typst_library::visualize::{Color, Gradient, Paint, Pattern, RelativeTo};
use crate::{AbsExt, State};
diff --git a/crates/typst-render/src/shape.rs b/crates/typst-render/src/shape.rs
index f31262ef..f9ff7f3a 100644
--- a/crates/typst-render/src/shape.rs
+++ b/crates/typst-render/src/shape.rs
@@ -1,6 +1,6 @@
use tiny_skia as sk;
-use typst::layout::{Abs, Axes, Point, Ratio, Size};
-use typst::visualize::{
+use typst_library::layout::{Abs, Axes, Point, Ratio, Size};
+use typst_library::visualize::{
DashPattern, FillRule, FixedStroke, Geometry, LineCap, LineJoin, Path, PathItem,
Shape,
};
diff --git a/crates/typst-render/src/text.rs b/crates/typst-render/src/text.rs
index 1685e67d..c017b1ce 100644
--- a/crates/typst-render/src/text.rs
+++ b/crates/typst-render/src/text.rs
@@ -3,10 +3,10 @@ use std::sync::Arc;
use pixglyph::Bitmap;
use tiny_skia as sk;
use ttf_parser::{GlyphId, OutlineBuilder};
-use typst::layout::{Abs, Axes, Point, Size};
-use typst::text::color::{glyph_frame, should_outline};
-use typst::text::{Font, TextItem};
-use typst::visualize::{FixedStroke, Paint};
+use typst_library::layout::{Abs, Axes, Point, Size};
+use typst_library::text::color::{glyph_frame, should_outline};
+use typst_library::text::{Font, TextItem};
+use typst_library::visualize::{FixedStroke, Paint};
use crate::paint::{self, GradientSampler, PaintSampler, PatternSampler};
use crate::{shape, AbsExt, State};
diff --git a/crates/typst-svg/Cargo.toml b/crates/typst-svg/Cargo.toml
index df49a2b1..41d35565 100644
--- a/crates/typst-svg/Cargo.toml
+++ b/crates/typst-svg/Cargo.toml
@@ -13,9 +13,10 @@ keywords = { workspace = true }
readme = { workspace = true }
[dependencies]
-typst = { workspace = true }
+typst-library = { workspace = true }
typst-macros = { workspace = true }
typst-timing = { workspace = true }
+typst-utils = { workspace = true }
base64 = { workspace = true }
comemo = { workspace = true }
ecow = { workspace = true }
diff --git a/crates/typst-svg/src/image.rs b/crates/typst-svg/src/image.rs
index 87bfbd27..ede4e76e 100644
--- a/crates/typst-svg/src/image.rs
+++ b/crates/typst-svg/src/image.rs
@@ -1,7 +1,7 @@
use base64::Engine;
use ecow::{eco_format, EcoString};
-use typst::layout::{Abs, Axes};
-use typst::visualize::{Image, ImageFormat, RasterFormat, VectorFormat};
+use typst_library::layout::{Abs, Axes};
+use typst_library::visualize::{Image, ImageFormat, RasterFormat, VectorFormat};
use crate::SVGRenderer;
diff --git a/crates/typst-svg/src/lib.rs b/crates/typst-svg/src/lib.rs
index deb5fb00..0ae7b2da 100644
--- a/crates/typst-svg/src/lib.rs
+++ b/crates/typst-svg/src/lib.rs
@@ -10,12 +10,12 @@ use std::fmt::{self, Display, Formatter, Write};
use ecow::EcoString;
use ttf_parser::OutlineBuilder;
-use typst::layout::{
+use typst_library::layout::{
Abs, Frame, FrameItem, FrameKind, GroupItem, Page, Point, Ratio, Size, Transform,
};
-use typst::model::Document;
-use typst::utils::hash128;
-use typst::visualize::{Geometry, Gradient, Pattern};
+use typst_library::model::Document;
+use typst_library::visualize::{Geometry, Gradient, Pattern};
+use typst_utils::hash128;
use xmlwriter::XmlWriter;
use crate::paint::{GradientRef, PatternRef, SVGSubGradient};
diff --git a/crates/typst-svg/src/paint.rs b/crates/typst-svg/src/paint.rs
index 364cdd23..58348c94 100644
--- a/crates/typst-svg/src/paint.rs
+++ b/crates/typst-svg/src/paint.rs
@@ -2,10 +2,10 @@ use std::f32::consts::TAU;
use ecow::{eco_format, EcoString};
use ttf_parser::OutlineBuilder;
-use typst::foundations::Repr;
-use typst::layout::{Angle, Axes, Frame, Quadrant, Ratio, Size, Transform};
-use typst::utils::hash128;
-use typst::visualize::{Color, FillRule, Gradient, Paint, Pattern, RatioOrAngle};
+use typst_library::foundations::Repr;
+use typst_library::layout::{Angle, Axes, Frame, Quadrant, Ratio, Size, Transform};
+use typst_library::visualize::{Color, FillRule, Gradient, Paint, Pattern, RatioOrAngle};
+use typst_utils::hash128;
use xmlwriter::XmlWriter;
use crate::{Id, SVGRenderer, State, SvgMatrix, SvgPathBuilder};
diff --git a/crates/typst-svg/src/shape.rs b/crates/typst-svg/src/shape.rs
index 12be2e22..e188ed8b 100644
--- a/crates/typst-svg/src/shape.rs
+++ b/crates/typst-svg/src/shape.rs
@@ -1,7 +1,7 @@
use ecow::EcoString;
use ttf_parser::OutlineBuilder;
-use typst::layout::{Abs, Ratio, Size, Transform};
-use typst::visualize::{
+use typst_library::layout::{Abs, Ratio, Size, Transform};
+use typst_library::visualize::{
FixedStroke, Geometry, LineCap, LineJoin, Paint, Path, PathItem, RelativeTo, Shape,
};
diff --git a/crates/typst-svg/src/text.rs b/crates/typst-svg/src/text.rs
index 4a99b4b8..76cd53cb 100644
--- a/crates/typst-svg/src/text.rs
+++ b/crates/typst-svg/src/text.rs
@@ -3,10 +3,10 @@ use std::io::Read;
use base64::Engine;
use ecow::EcoString;
use ttf_parser::GlyphId;
-use typst::layout::{Abs, Point, Ratio, Size, Transform};
-use typst::text::{Font, TextItem};
-use typst::utils::hash128;
-use typst::visualize::{FillRule, Image, Paint, RasterFormat, RelativeTo};
+use typst_library::layout::{Abs, Point, Ratio, Size, Transform};
+use typst_library::text::{Font, TextItem};
+use typst_library::visualize::{FillRule, Image, Paint, RasterFormat, RelativeTo};
+use typst_utils::hash128;
use crate::{SVGRenderer, State, SvgMatrix, SvgPathBuilder};
diff --git a/crates/typst-syntax/Cargo.toml b/crates/typst-syntax/Cargo.toml
index e9c39992..3dc983eb 100644
--- a/crates/typst-syntax/Cargo.toml
+++ b/crates/typst-syntax/Cargo.toml
@@ -13,6 +13,7 @@ keywords = { workspace = true }
readme = { workspace = true }
[dependencies]
+typst-timing = { workspace = true }
typst-utils = { workspace = true }
ecow = { workspace = true }
once_cell = { workspace = true }
diff --git a/crates/typst-syntax/src/file.rs b/crates/typst-syntax/src/file.rs
index 89aaa55e..bc7dd314 100644
--- a/crates/typst-syntax/src/file.rs
+++ b/crates/typst-syntax/src/file.rs
@@ -97,12 +97,16 @@ impl FileId {
}
/// Construct from a raw number.
- pub(crate) const fn from_raw(v: u16) -> Self {
+ ///
+ /// Should only be used with numbers retrieved via
+ /// [`into_raw`](Self::into_raw). Misuse may results in panics, but no
+ /// unsafety.
+ pub const fn from_raw(v: u16) -> Self {
Self(v)
}
/// Extract the raw underlying number.
- pub(crate) const fn into_raw(self) -> u16 {
+ pub const fn into_raw(self) -> u16 {
self.0
}
diff --git a/crates/typst-syntax/src/highlight.rs b/crates/typst-syntax/src/highlight.rs
index ddd29326..de8ed65c 100644
--- a/crates/typst-syntax/src/highlight.rs
+++ b/crates/typst-syntax/src/highlight.rs
@@ -411,9 +411,10 @@ fn highlight_html_impl(html: &mut String, node: &LinkedNode) {
#[cfg(test)]
mod tests {
- use super::*;
use std::ops::Range;
+ use super::*;
+
#[test]
fn test_highlighting() {
use Tag::*;
diff --git a/crates/typst-syntax/src/parser.rs b/crates/typst-syntax/src/parser.rs
index dba6d69d..8c783ffe 100644
--- a/crates/typst-syntax/src/parser.rs
+++ b/crates/typst-syntax/src/parser.rs
@@ -12,6 +12,7 @@ use crate::{
/// Parses a source file.
pub fn parse(text: &str) -> SyntaxNode {
+ let _scope = typst_timing::TimingScope::new("parse");
let mut p = Parser::new(text, 0, LexMode::Markup);
markup(&mut p, true, 0, |_| false);
p.finish().into_iter().next().unwrap()
@@ -19,6 +20,7 @@ pub fn parse(text: &str) -> SyntaxNode {
/// Parses top-level code.
pub fn parse_code(text: &str) -> SyntaxNode {
+ let _scope = typst_timing::TimingScope::new("parse code");
let mut p = Parser::new(text, 0, LexMode::Code);
let m = p.marker();
p.skip();
@@ -29,6 +31,7 @@ pub fn parse_code(text: &str) -> SyntaxNode {
/// Parses top-level math.
pub fn parse_math(text: &str) -> SyntaxNode {
+ let _scope = typst_timing::TimingScope::new("parse math");
let mut p = Parser::new(text, 0, LexMode::Math);
math(&mut p, |_| false);
p.finish().into_iter().next().unwrap()
diff --git a/crates/typst-syntax/src/source.rs b/crates/typst-syntax/src/source.rs
index a2ccb5bb..3454a265 100644
--- a/crates/typst-syntax/src/source.rs
+++ b/crates/typst-syntax/src/source.rs
@@ -32,6 +32,7 @@ struct Repr {
impl Source {
/// Create a new source file.
pub fn new(id: FileId, text: String) -> Self {
+ let _scope = typst_timing::TimingScope::new("create source");
let mut root = parse(&text);
root.numberize(id, Span::FULL).unwrap();
Self(Arc::new(Repr {
@@ -75,6 +76,7 @@ impl Source {
///
/// Returns the range in the new source that was ultimately reparsed.
pub fn replace(&mut self, new: &str) -> Range<usize> {
+ let _scope = typst_timing::TimingScope::new("replace source");
let old = self.text();
let mut prefix =
diff --git a/crates/typst-syntax/src/span.rs b/crates/typst-syntax/src/span.rs
index 85745f60..0847ceea 100644
--- a/crates/typst-syntax/src/span.rs
+++ b/crates/typst-syntax/src/span.rs
@@ -83,6 +83,20 @@ impl Span {
self.0.get() & ((1 << Self::BITS) - 1)
}
+ /// Construct from a raw number.
+ ///
+ /// Should only be used with numbers retrieved via
+ /// [`into_raw`](Self::into_raw). Misuse may results in panics, but no
+ /// unsafety.
+ pub const fn from_raw(v: NonZeroU64) -> Self {
+ Self(v)
+ }
+
+ /// Extract the raw underlying number.
+ pub const fn into_raw(self) -> NonZeroU64 {
+ self.0
+ }
+
/// Return `other` if `self` is detached and `self` otherwise.
pub fn or(self, other: Self) -> Self {
if self.is_detached() {
diff --git a/crates/typst-timing/Cargo.toml b/crates/typst-timing/Cargo.toml
index 525d65c5..2d42269f 100644
--- a/crates/typst-timing/Cargo.toml
+++ b/crates/typst-timing/Cargo.toml
@@ -13,7 +13,6 @@ keywords = { workspace = true }
readme = { workspace = true }
[dependencies]
-typst-syntax = { workspace = true }
parking_lot = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
diff --git a/crates/typst-timing/src/lib.rs b/crates/typst-timing/src/lib.rs
index 4e711d5f..0df65443 100644
--- a/crates/typst-timing/src/lib.rs
+++ b/crates/typst-timing/src/lib.rs
@@ -2,14 +2,15 @@
use std::hash::Hash;
use std::io::Write;
-use std::sync::atomic::{AtomicBool, Ordering::Relaxed};
+use std::num::NonZeroU64;
+use std::sync::atomic::AtomicBool;
+use std::sync::atomic::Ordering::Relaxed;
use std::thread::ThreadId;
use std::time::{Duration, SystemTime};
use parking_lot::Mutex;
use serde::ser::SerializeSeq;
use serde::{Serialize, Serializer};
-use typst_syntax::Span;
/// Whether the timer is enabled. Defaults to `false`.
static ENABLED: AtomicBool = AtomicBool::new(false);
@@ -43,8 +44,8 @@ struct Event {
id: u64,
/// The name of this event.
name: &'static str,
- /// The span of code that this event was recorded in.
- span: Option<Span>,
+ /// The raw value of the span of code that this event was recorded in.
+ span: Option<NonZeroU64>,
/// The thread ID of this event.
thread_id: ThreadId,
}
@@ -79,18 +80,34 @@ pub fn clear() {
/// A scope that records an event when it is dropped.
pub struct TimingScope {
name: &'static str,
- span: Option<Span>,
+ span: Option<NonZeroU64>,
id: u64,
thread_id: ThreadId,
}
impl TimingScope {
/// Create a new scope if timing is enabled.
- pub fn new(name: &'static str, span: Option<Span>) -> Option<Self> {
- if !is_enabled() {
- return None;
+ #[inline]
+ pub fn new(name: &'static str) -> Option<Self> {
+ Self::with_span(name, None)
+ }
+
+ /// Create a new scope with a span if timing is enabled.
+ ///
+ /// The span is a raw number because `typst-timing` can't depend on
+ /// `typst-syntax` (or else `typst-syntax` couldn't depend on
+ /// `typst-timing`).
+ #[inline]
+ pub fn with_span(name: &'static str, span: Option<NonZeroU64>) -> Option<Self> {
+ #[cfg(not(target_arch = "wasm32"))]
+ if is_enabled() {
+ return Some(Self::new_impl(name, span));
}
+ None
+ }
+ /// Create a new scope without checking if timing is enabled.
+ fn new_impl(name: &'static str, span: Option<NonZeroU64>) -> Self {
let timestamp = SystemTime::now();
let thread_id = std::thread::current().id();
@@ -106,7 +123,7 @@ impl TimingScope {
thread_id,
});
- Some(TimingScope { name, span, id, thread_id })
+ Self { name, span, id, thread_id }
}
}
@@ -151,11 +168,11 @@ impl Drop for TimingScope {
#[macro_export]
macro_rules! timed {
($name:expr, span = $span:expr, $body:expr $(,)?) => {{
- let __scope = $crate::TimingScope::new($name, Some($span));
+ let __scope = $crate::TimingScope::with_span($name, Some($span));
$body
}};
($name:expr, $body:expr $(,)?) => {{
- let __scope = $crate::TimingScope::new($name, None);
+ let __scope = $crate::TimingScope::new($name);
$body
}};
}
@@ -167,7 +184,7 @@ macro_rules! timed {
/// the second element is the line number.
pub fn export_json<W: Write>(
writer: W,
- mut source: impl FnMut(Span) -> (String, u32),
+ mut source: impl FnMut(NonZeroU64) -> (String, u32),
) -> Result<(), String> {
#[derive(Serialize)]
struct Entry {
diff --git a/crates/typst-utils/src/lib.rs b/crates/typst-utils/src/lib.rs
index 8488b72d..e199e1bb 100644
--- a/crates/typst-utils/src/lib.rs
+++ b/crates/typst-utils/src/lib.rs
@@ -20,6 +20,9 @@ pub use self::pico::PicoStr;
pub use self::round::{round_int_with_precision, round_with_precision};
pub use self::scalar::Scalar;
+#[doc(hidden)]
+pub use once_cell;
+
use std::fmt::{Debug, Formatter};
use std::hash::Hash;
use std::iter::{Chain, Flatten, Rev};
@@ -29,9 +32,6 @@ use std::sync::Arc;
use siphasher::sip128::{Hasher128, SipHasher13};
-#[doc(hidden)]
-pub use once_cell;
-
/// Turn a closure into a struct implementing [`Debug`].
pub fn debug<F>(f: F) -> impl Debug
where
diff --git a/crates/typst/Cargo.toml b/crates/typst/Cargo.toml
index a3e21123..6158d791 100644
--- a/crates/typst/Cargo.toml
+++ b/crates/typst/Cargo.toml
@@ -13,72 +13,16 @@ license = { workspace = true }
readme = { workspace = true }
[dependencies]
-typst-assets = { workspace = true }
+typst-eval = { workspace = true }
+typst-layout = { workspace = true }
+typst-library = { workspace = true }
typst-macros = { workspace = true }
+typst-realize = { workspace = true }
typst-syntax = { workspace = true }
typst-timing = { workspace = true }
typst-utils = { workspace = true }
-arrayvec = { workspace = true }
-az = { workspace = true }
-bitflags = { workspace = true }
-bumpalo = { workspace = true }
-chinese-number = { workspace = true }
-ciborium = { workspace = true }
comemo = { workspace = true }
-csv = { workspace = true }
ecow = { workspace = true }
-flate2 = { workspace = true }
-fontdb = { workspace = true }
-hayagriva = { workspace = true }
-hypher = { workspace = true }
-icu_properties = { workspace = true }
-icu_provider = { workspace = true }
-icu_provider_adapters = { workspace = true }
-icu_provider_blob = { workspace = true }
-icu_segmenter = { workspace = true }
-if_chain = { workspace = true }
-image = { workspace = true }
-indexmap = { workspace = true }
-kamadak-exif = { workspace = true }
-kurbo = { workspace = true }
-lipsum = { workspace = true }
-log = { workspace = true }
-once_cell = { workspace = true }
-palette = { workspace = true }
-qcms = { workspace = true }
-phf = { workspace = true }
-png = { workspace = true }
-portable-atomic = { workspace = true }
-rayon = { workspace = true }
-regex = { workspace = true }
-roxmltree = { workspace = true }
-rust_decimal = { workspace = true }
-rustybuzz = { workspace = true }
-serde = { workspace = true }
-serde_json = { workspace = true }
-serde_yaml = { workspace = true }
-siphasher = { workspace = true }
-smallvec = { workspace = true }
-syntect = { workspace = true }
-time = { workspace = true }
-toml = { workspace = true }
-ttf-parser = { workspace = true }
-two-face = { workspace = true }
-typed-arena = { workspace = true }
-unicode-bidi = { workspace = true }
-unicode-math-class = { workspace = true }
-unicode-script = { workspace = true }
-unicode-segmentation = { workspace = true }
-unscanny = { workspace = true }
-usvg = { workspace = true }
-xmlwriter = { workspace = true }
-wasmi = { workspace = true }
-
-[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
-stacker = { workspace = true }
-
-[dev-dependencies]
-typst-dev-assets = { workspace = true }
[lints]
workspace = true
diff --git a/crates/typst/src/layout/container.rs b/crates/typst/src/layout/container.rs
deleted file mode 100644
index d97edd5a..00000000
--- a/crates/typst/src/layout/container.rs
+++ /dev/null
@@ -1,1046 +0,0 @@
-use once_cell::unsync::Lazy;
-use smallvec::SmallVec;
-
-use crate::diag::{bail, SourceResult};
-use crate::engine::Engine;
-use crate::foundations::{
- cast, elem, Args, AutoValue, Construct, Content, NativeElement, Packed, Resolve,
- Smart, StyleChain, Value,
-};
-use crate::introspection::Locator;
-use crate::layout::{
- layout_fragment, layout_frame, Abs, Axes, Corners, Em, Fr, Fragment, Frame,
- FrameKind, Length, Region, Regions, Rel, Sides, Size, Spacing,
-};
-use crate::utils::Numeric;
-use crate::visualize::{clip_rect, Paint, Stroke};
-
-/// An inline-level container that sizes content.
-///
-/// All elements except inline math, text, and boxes are block-level and cannot
-/// occur inside of a paragraph. The box function can be used to integrate such
-/// elements into a paragraph. Boxes take the size of their contents by default
-/// but can also be sized explicitly.
-///
-/// # Example
-/// ```example
-/// Refer to the docs
-/// #box(
-/// height: 9pt,
-/// image("docs.svg")
-/// )
-/// for more information.
-/// ```
-#[elem]
-pub struct BoxElem {
- /// The width of the box.
- ///
- /// Boxes can have [fractional]($fraction) widths, as the example below
- /// demonstrates.
- ///
- /// _Note:_ Currently, only boxes and only their widths might be fractionally
- /// sized within paragraphs. Support for fractionally sized images, shapes,
- /// and more might be added in the future.
- ///
- /// ```example
- /// Line in #box(width: 1fr, line(length: 100%)) between.
- /// ```
- pub width: Sizing,
-
- /// The height of the box.
- pub height: Smart<Rel<Length>>,
-
- /// An amount to shift the box's baseline by.
- ///
- /// ```example
- /// Image: #box(baseline: 40%, image("tiger.jpg", width: 2cm)).
- /// ```
- #[resolve]
- pub baseline: Rel<Length>,
-
- /// The box's background color. See the
- /// [rectangle's documentation]($rect.fill) for more details.
- pub fill: Option<Paint>,
-
- /// The box's border color. See the
- /// [rectangle's documentation]($rect.stroke) for more details.
- #[resolve]
- #[fold]
- pub stroke: Sides<Option<Option<Stroke>>>,
-
- /// How much to round the box's corners. See the
- /// [rectangle's documentation]($rect.radius) for more details.
- #[resolve]
- #[fold]
- pub radius: Corners<Option<Rel<Length>>>,
-
- /// How much to pad the box's content.
- ///
- /// _Note:_ When the box contains text, its exact size depends on the
- /// current [text edges]($text.top-edge).
- ///
- /// ```example
- /// #rect(inset: 0pt)[Tight]
- /// ```
- #[resolve]
- #[fold]
- pub inset: Sides<Option<Rel<Length>>>,
-
- /// How much to expand the box's size without affecting the layout.
- ///
- /// This is useful to prevent padding from affecting line layout. For a
- /// generalized version of the example below, see the documentation for the
- /// [raw text's block parameter]($raw.block).
- ///
- /// ```example
- /// An inline
- /// #box(
- /// fill: luma(235),
- /// inset: (x: 3pt, y: 0pt),
- /// outset: (y: 3pt),
- /// radius: 2pt,
- /// )[rectangle].
- /// ```
- #[resolve]
- #[fold]
- pub outset: Sides<Option<Rel<Length>>>,
-
- /// Whether to clip the content inside the box.
- ///
- /// Clipping is useful when the box's content is larger than the box itself,
- /// as any content that exceeds the box's bounds will be hidden.
- ///
- /// ```example
- /// #box(
- /// width: 50pt,
- /// height: 50pt,
- /// clip: true,
- /// image("tiger.jpg", width: 100pt, height: 100pt)
- /// )
- /// ```
- #[default(false)]
- pub clip: bool,
-
- /// The contents of the box.
- #[positional]
- #[borrowed]
- pub body: Option<Content>,
-}
-
-impl Packed<BoxElem> {
- /// Layout this box as part of a paragraph.
- #[typst_macros::time(name = "box", span = self.span())]
- pub fn layout(
- &self,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- region: Size,
- ) -> SourceResult<Frame> {
- // Fetch sizing properties.
- let width = self.width(styles);
- let height = self.height(styles);
- let inset = self.inset(styles).unwrap_or_default();
-
- // Build the pod region.
- let pod = unbreakable_pod(&width, &height.into(), &inset, styles, region);
-
- // Layout the body.
- let mut frame = match self.body(styles) {
- // If we have no body, just create an empty frame. If necessary,
- // its size will be adjusted below.
- None => Frame::hard(Size::zero()),
-
- // If we have a child, layout it into the body. Boxes are boundaries
- // for gradient relativeness, so we set the `FrameKind` to `Hard`.
- Some(body) => layout_frame(engine, body, locator, styles, pod)?
- .with_kind(FrameKind::Hard),
- };
-
- // Enforce a correct frame size on the expanded axes. Do this before
- // applying the inset, since the pod shrunk.
- frame.set_size(pod.expand.select(pod.size, frame.size()));
-
- // Apply the inset.
- if !inset.is_zero() {
- crate::layout::grow(&mut frame, &inset);
- }
-
- // Prepare fill and stroke.
- let fill = self.fill(styles);
- let stroke = self
- .stroke(styles)
- .unwrap_or_default()
- .map(|s| s.map(Stroke::unwrap_or_default));
-
- // Only fetch these if necessary (for clipping or filling/stroking).
- let outset = Lazy::new(|| self.outset(styles).unwrap_or_default());
- let radius = Lazy::new(|| self.radius(styles).unwrap_or_default());
-
- // Clip the contents, if requested.
- if self.clip(styles) {
- let size = frame.size() + outset.relative_to(frame.size()).sum_by_axis();
- frame.clip(clip_rect(size, &radius, &stroke));
- }
-
- // Add fill and/or stroke.
- if fill.is_some() || stroke.iter().any(Option::is_some) {
- frame.fill_and_stroke(fill, &stroke, &outset, &radius, self.span());
- }
-
- // Assign label to the frame.
- if let Some(label) = self.label() {
- frame.label(label);
- }
-
- // Apply baseline shift. Do this after setting the size and applying the
- // inset, so that a relative shift is resolved relative to the final
- // height.
- let shift = self.baseline(styles).relative_to(frame.height());
- if !shift.is_zero() {
- frame.set_baseline(frame.baseline() - shift);
- }
-
- Ok(frame)
- }
-}
-
-/// An inline-level container that can produce arbitrary items that can break
-/// across lines.
-#[elem(Construct)]
-pub struct InlineElem {
- /// A callback that is invoked with the regions to produce arbitrary
- /// inline items.
- #[required]
- #[internal]
- body: callbacks::InlineCallback,
-}
-
-impl Construct for InlineElem {
- fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
- bail!(args.span, "cannot be constructed manually");
- }
-}
-
-impl InlineElem {
- /// Create an inline-level item with a custom layouter.
- #[allow(clippy::type_complexity)]
- pub fn layouter<T: NativeElement>(
- captured: Packed<T>,
- callback: fn(
- content: &Packed<T>,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- region: Size,
- ) -> SourceResult<Vec<InlineItem>>,
- ) -> Self {
- Self::new(callbacks::InlineCallback::new(captured, callback))
- }
-}
-
-impl Packed<InlineElem> {
- /// Layout the element.
- pub fn layout(
- &self,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- region: Size,
- ) -> SourceResult<Vec<InlineItem>> {
- self.body().call(engine, locator, styles, region)
- }
-}
-
-/// Layouted items suitable for placing in a paragraph.
-#[derive(Debug, Clone)]
-pub enum InlineItem {
- /// Absolute spacing between other items, and whether it is weak.
- Space(Abs, bool),
- /// Layouted inline-level content.
- Frame(Frame),
-}
-
-/// A block-level container.
-///
-/// Such a container can be used to separate content, size it, and give it a
-/// background or border.
-///
-/// # Examples
-/// With a block, you can give a background to content while still allowing it
-/// to break across multiple pages.
-/// ```example
-/// #set page(height: 100pt)
-/// #block(
-/// fill: luma(230),
-/// inset: 8pt,
-/// radius: 4pt,
-/// lorem(30),
-/// )
-/// ```
-///
-/// Blocks are also useful to force elements that would otherwise be inline to
-/// become block-level, especially when writing show rules.
-/// ```example
-/// #show heading: it => it.body
-/// = Blockless
-/// More text.
-///
-/// #show heading: it => block(it.body)
-/// = Blocky
-/// More text.
-/// ```
-#[elem]
-pub struct BlockElem {
- /// The block's width.
- ///
- /// ```example
- /// #set align(center)
- /// #block(
- /// width: 60%,
- /// inset: 8pt,
- /// fill: silver,
- /// lorem(10),
- /// )
- /// ```
- pub width: Smart<Rel<Length>>,
-
- /// The block's height. When the height is larger than the remaining space
- /// on a page and [`breakable`]($block.breakable) is `{true}`, the
- /// block will continue on the next page with the remaining height.
- ///
- /// ```example
- /// #set page(height: 80pt)
- /// #set align(center)
- /// #block(
- /// width: 80%,
- /// height: 150%,
- /// fill: aqua,
- /// )
- /// ```
- pub height: Sizing,
-
- /// Whether the block can be broken and continue on the next page.
- ///
- /// ```example
- /// #set page(height: 80pt)
- /// The following block will
- /// jump to its own page.
- /// #block(
- /// breakable: false,
- /// lorem(15),
- /// )
- /// ```
- #[default(true)]
- pub breakable: bool,
-
- /// The block's background color. See the
- /// [rectangle's documentation]($rect.fill) for more details.
- pub fill: Option<Paint>,
-
- /// The block's border color. See the
- /// [rectangle's documentation]($rect.stroke) for more details.
- #[resolve]
- #[fold]
- pub stroke: Sides<Option<Option<Stroke>>>,
-
- /// How much to round the block's corners. See the
- /// [rectangle's documentation]($rect.radius) for more details.
- #[resolve]
- #[fold]
- pub radius: Corners<Option<Rel<Length>>>,
-
- /// How much to pad the block's content. See the
- /// [box's documentation]($box.inset) for more details.
- #[resolve]
- #[fold]
- pub inset: Sides<Option<Rel<Length>>>,
-
- /// How much to expand the block's size without affecting the layout. See
- /// the [box's documentation]($box.outset) for more details.
- #[resolve]
- #[fold]
- pub outset: Sides<Option<Rel<Length>>>,
-
- /// The spacing around the block. When `{auto}`, inherits the paragraph
- /// [`spacing`]($par.spacing).
- ///
- /// For two adjacent blocks, the larger of the first block's `above` and the
- /// second block's `below` spacing wins. Moreover, block spacing takes
- /// precedence over paragraph [`spacing`]($par.spacing).
- ///
- /// Note that this is only a shorthand to set `above` and `below` to the
- /// same value. Since the values for `above` and `below` might differ, a
- /// [context] block only provides access to `{block.above}` and
- /// `{block.below}`, not to `{block.spacing}` directly.
- ///
- /// This property can be used in combination with a show rule to adjust the
- /// spacing around arbitrary block-level elements.
- ///
- /// ```example
- /// #set align(center)
- /// #show math.equation: set block(above: 8pt, below: 16pt)
- ///
- /// This sum of $x$ and $y$:
- /// $ x + y = z $
- /// A second paragraph.
- /// ```
- #[external]
- #[default(Em::new(1.2).into())]
- pub spacing: Spacing,
-
- /// The spacing between this block and its predecessor.
- #[parse(
- let spacing = args.named("spacing")?;
- args.named("above")?.or(spacing)
- )]
- pub above: Smart<Spacing>,
-
- /// The spacing between this block and its successor.
- #[parse(args.named("below")?.or(spacing))]
- pub below: Smart<Spacing>,
-
- /// Whether to clip the content inside the block.
- ///
- /// Clipping is useful when the block's content is larger than the block itself,
- /// as any content that exceeds the block's bounds will be hidden.
- ///
- /// ```example
- /// #block(
- /// width: 50pt,
- /// height: 50pt,
- /// clip: true,
- /// image("tiger.jpg", width: 100pt, height: 100pt)
- /// )
- /// ```
- #[default(false)]
- pub clip: bool,
-
- /// Whether this block must stick to the following one, with no break in
- /// between.
- ///
- /// This is, by default, set on heading blocks to prevent orphaned headings
- /// at the bottom of the page.
- ///
- /// ```example
- /// >>> #set page(height: 140pt)
- /// // Disable stickiness of headings.
- /// #show heading: set block(sticky: false)
- /// #lorem(20)
- ///
- /// = Chapter
- /// #lorem(10)
- /// ```
- #[default(false)]
- pub sticky: bool,
-
- /// The contents of the block.
- #[positional]
- #[borrowed]
- pub body: Option<BlockBody>,
-}
-
-impl BlockElem {
- /// Create a block with a custom single-region layouter.
- ///
- /// Such a block must have `breakable: false` (which is set by this
- /// constructor).
- pub fn single_layouter<T: NativeElement>(
- captured: Packed<T>,
- f: fn(
- content: &Packed<T>,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- region: Region,
- ) -> SourceResult<Frame>,
- ) -> Self {
- Self::new()
- .with_breakable(false)
- .with_body(Some(BlockBody::SingleLayouter(
- callbacks::BlockSingleCallback::new(captured, f),
- )))
- }
-
- /// Create a block with a custom multi-region layouter.
- pub fn multi_layouter<T: NativeElement>(
- captured: Packed<T>,
- f: fn(
- content: &Packed<T>,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment>,
- ) -> Self {
- Self::new().with_body(Some(BlockBody::MultiLayouter(
- callbacks::BlockMultiCallback::new(captured, f),
- )))
- }
-}
-
-impl Packed<BlockElem> {
- /// Lay this out as an unbreakable block.
- #[typst_macros::time(name = "block", span = self.span())]
- pub fn layout_single(
- &self,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- region: Region,
- ) -> SourceResult<Frame> {
- // Fetch sizing properties.
- let width = self.width(styles);
- let height = self.height(styles);
- let inset = self.inset(styles).unwrap_or_default();
-
- // Build the pod regions.
- let pod = unbreakable_pod(&width.into(), &height, &inset, styles, region.size);
-
- // Layout the body.
- let body = self.body(styles);
- let mut frame = match body {
- // If we have no body, just create one frame. Its size will be
- // adjusted below.
- None => Frame::hard(Size::zero()),
-
- // If we have content as our body, just layout it.
- Some(BlockBody::Content(body)) => {
- layout_frame(engine, body, locator.relayout(), styles, pod)?
- }
-
- // If we have a child that wants to layout with just access to the
- // base region, give it that.
- Some(BlockBody::SingleLayouter(callback)) => {
- callback.call(engine, locator, styles, pod)?
- }
-
- // If we have a child that wants to layout with full region access,
- // we layout it.
- Some(BlockBody::MultiLayouter(callback)) => {
- let expand = (pod.expand | region.expand) & pod.size.map(Abs::is_finite);
- let pod = Region { expand, ..pod };
- callback.call(engine, locator, styles, pod.into())?.into_frame()
- }
- };
-
- // Explicit blocks are boundaries for gradient relativeness.
- if matches!(body, None | Some(BlockBody::Content(_))) {
- frame.set_kind(FrameKind::Hard);
- }
-
- // Enforce a correct frame size on the expanded axes. Do this before
- // applying the inset, since the pod shrunk.
- frame.set_size(pod.expand.select(pod.size, frame.size()));
-
- // Apply the inset.
- if !inset.is_zero() {
- crate::layout::grow(&mut frame, &inset);
- }
-
- // Prepare fill and stroke.
- let fill = self.fill(styles);
- let stroke = self
- .stroke(styles)
- .unwrap_or_default()
- .map(|s| s.map(Stroke::unwrap_or_default));
-
- // Only fetch these if necessary (for clipping or filling/stroking).
- let outset = Lazy::new(|| self.outset(styles).unwrap_or_default());
- let radius = Lazy::new(|| self.radius(styles).unwrap_or_default());
-
- // Clip the contents, if requested.
- if self.clip(styles) {
- let size = frame.size() + outset.relative_to(frame.size()).sum_by_axis();
- frame.clip(clip_rect(size, &radius, &stroke));
- }
-
- // Add fill and/or stroke.
- if fill.is_some() || stroke.iter().any(Option::is_some) {
- frame.fill_and_stroke(fill, &stroke, &outset, &radius, self.span());
- }
-
- // Assign label to each frame in the fragment.
- if let Some(label) = self.label() {
- frame.label(label);
- }
-
- Ok(frame)
- }
-}
-
-impl Packed<BlockElem> {
- /// Lay this out as a breakable block.
- #[typst_macros::time(name = "block", span = self.span())]
- pub fn layout_multiple(
- &self,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- // Fetch sizing properties.
- let width = self.width(styles);
- let height = self.height(styles);
- let inset = self.inset(styles).unwrap_or_default();
-
- // Allocate a small vector for backlogs.
- let mut buf = SmallVec::<[Abs; 2]>::new();
-
- // Build the pod regions.
- let pod =
- breakable_pod(&width.into(), &height, &inset, styles, regions, &mut buf);
-
- // Layout the body.
- let body = self.body(styles);
- let mut fragment = match body {
- // If we have no body, just create one frame plus one per backlog
- // region. We create them zero-sized; if necessary, their size will
- // be adjusted below.
- None => {
- let mut frames = vec![];
- frames.push(Frame::hard(Size::zero()));
- if pod.expand.y {
- let mut iter = pod;
- while !iter.backlog.is_empty() {
- frames.push(Frame::hard(Size::zero()));
- iter.next();
- }
- }
- Fragment::frames(frames)
- }
-
- // If we have content as our body, just layout it.
- Some(BlockBody::Content(body)) => {
- let mut fragment =
- layout_fragment(engine, body, locator.relayout(), styles, pod)?;
-
- // If the body is automatically sized and produced more than one
- // fragment, ensure that the width was consistent across all
- // regions. If it wasn't, we need to relayout with expansion.
- if !pod.expand.x
- && fragment
- .as_slice()
- .windows(2)
- .any(|w| !w[0].width().approx_eq(w[1].width()))
- {
- let max_width = fragment
- .iter()
- .map(|frame| frame.width())
- .max()
- .unwrap_or_default();
- let pod = Regions {
- size: Size::new(max_width, pod.size.y),
- expand: Axes::new(true, pod.expand.y),
- ..pod
- };
- fragment = layout_fragment(engine, body, locator, styles, pod)?;
- }
-
- fragment
- }
-
- // If we have a child that wants to layout with just access to the
- // base region, give it that.
- Some(BlockBody::SingleLayouter(callback)) => {
- let pod = Region::new(pod.base(), pod.expand);
- callback.call(engine, locator, styles, pod).map(Fragment::frame)?
- }
-
- // If we have a child that wants to layout with full region access,
- // we layout it.
- //
- // For auto-sized multi-layouters, we propagate the outer expansion
- // so that they can decide for themselves. We also ensure again to
- // only expand if the size is finite.
- Some(BlockBody::MultiLayouter(callback)) => {
- let expand = (pod.expand | regions.expand) & pod.size.map(Abs::is_finite);
- let pod = Regions { expand, ..pod };
- callback.call(engine, locator, styles, pod)?
- }
- };
-
- // Prepare fill and stroke.
- let fill = self.fill(styles);
- let stroke = self
- .stroke(styles)
- .unwrap_or_default()
- .map(|s| s.map(Stroke::unwrap_or_default));
-
- // Only fetch these if necessary (for clipping or filling/stroking).
- let outset = Lazy::new(|| self.outset(styles).unwrap_or_default());
- let radius = Lazy::new(|| self.radius(styles).unwrap_or_default());
-
- // Fetch/compute these outside of the loop.
- let clip = self.clip(styles);
- let has_fill_or_stroke = fill.is_some() || stroke.iter().any(Option::is_some);
- let has_inset = !inset.is_zero();
- let is_explicit = matches!(body, None | Some(BlockBody::Content(_)));
-
- // Skip filling/stroking the first frame if it is empty and a non-empty
- // one follows.
- let mut skip_first = false;
- if let [first, rest @ ..] = fragment.as_slice() {
- skip_first = has_fill_or_stroke
- && first.is_empty()
- && rest.iter().any(|frame| !frame.is_empty());
- }
-
- // Post-process to apply insets, clipping, fills, and strokes.
- for (i, (frame, region)) in fragment.iter_mut().zip(pod.iter()).enumerate() {
- // Explicit blocks are boundaries for gradient relativeness.
- if is_explicit {
- frame.set_kind(FrameKind::Hard);
- }
-
- // Enforce a correct frame size on the expanded axes. Do this before
- // applying the inset, since the pod shrunk.
- frame.set_size(pod.expand.select(region, frame.size()));
-
- // Apply the inset.
- if has_inset {
- crate::layout::grow(frame, &inset);
- }
-
- // Clip the contents, if requested.
- if clip {
- let size = frame.size() + outset.relative_to(frame.size()).sum_by_axis();
- frame.clip(clip_rect(size, &radius, &stroke));
- }
-
- // Add fill and/or stroke.
- if has_fill_or_stroke && (i > 0 || !skip_first) {
- frame.fill_and_stroke(
- fill.clone(),
- &stroke,
- &outset,
- &radius,
- self.span(),
- );
- }
- }
-
- // Assign label to each frame in the fragment.
- if let Some(label) = self.label() {
- for frame in fragment.iter_mut() {
- frame.label(label);
- }
- }
-
- Ok(fragment)
- }
-}
-
-/// The contents of a block.
-#[derive(Debug, Clone, PartialEq, Hash)]
-pub enum BlockBody {
- /// The block contains normal content.
- Content(Content),
- /// The block contains a layout callback that needs access to just one
- /// base region.
- SingleLayouter(callbacks::BlockSingleCallback),
- /// The block contains a layout callback that needs access to the exact
- /// regions.
- MultiLayouter(callbacks::BlockMultiCallback),
-}
-
-impl Default for BlockBody {
- fn default() -> Self {
- Self::Content(Content::default())
- }
-}
-
-cast! {
- BlockBody,
- self => match self {
- Self::Content(content) => content.into_value(),
- _ => Value::Auto,
- },
- v: Content => Self::Content(v),
-}
-
-/// Defines how to size something along an axis.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum Sizing {
- /// A track that fits its item's contents.
- Auto,
- /// A size specified in absolute terms and relative to the parent's size.
- Rel(Rel),
- /// A size specified as a fraction of the remaining free space in the
- /// parent.
- Fr(Fr),
-}
-
-impl Sizing {
- /// Whether this is an automatic sizing.
- pub fn is_auto(self) -> bool {
- matches!(self, Self::Auto)
- }
-
- /// Whether this is fractional sizing.
- pub fn is_fractional(self) -> bool {
- matches!(self, Self::Fr(_))
- }
-}
-
-impl Default for Sizing {
- fn default() -> Self {
- Self::Auto
- }
-}
-
-impl From<Smart<Rel>> for Sizing {
- fn from(smart: Smart<Rel>) -> Self {
- match smart {
- Smart::Auto => Self::Auto,
- Smart::Custom(rel) => Self::Rel(rel),
- }
- }
-}
-
-impl<T: Into<Spacing>> From<T> for Sizing {
- fn from(spacing: T) -> Self {
- match spacing.into() {
- Spacing::Rel(rel) => Self::Rel(rel),
- Spacing::Fr(fr) => Self::Fr(fr),
- }
- }
-}
-
-cast! {
- Sizing,
- self => match self {
- Self::Auto => Value::Auto,
- Self::Rel(rel) => rel.into_value(),
- Self::Fr(fr) => fr.into_value(),
- },
- _: AutoValue => Self::Auto,
- v: Rel<Length> => Self::Rel(v),
- v: Fr => Self::Fr(v),
-}
-
-/// Builds the pod region for an unbreakable sized container.
-fn unbreakable_pod(
- width: &Sizing,
- height: &Sizing,
- inset: &Sides<Rel<Abs>>,
- styles: StyleChain,
- base: Size,
-) -> Region {
- // Resolve the size.
- let mut size = Size::new(
- match width {
- // - For auto, the whole region is available.
- // - Fr is handled outside and already factored into the `region`,
- // so we can treat it equivalently to 100%.
- Sizing::Auto | Sizing::Fr(_) => base.x,
- // Resolve the relative sizing.
- Sizing::Rel(rel) => rel.resolve(styles).relative_to(base.x),
- },
- match height {
- Sizing::Auto | Sizing::Fr(_) => base.y,
- Sizing::Rel(rel) => rel.resolve(styles).relative_to(base.y),
- },
- );
-
- // Take the inset, if any, into account.
- if !inset.is_zero() {
- size = crate::layout::shrink(size, inset);
- }
-
- // If the child is manually, the size is forced and we should enable
- // expansion.
- let expand = Axes::new(
- *width != Sizing::Auto && size.x.is_finite(),
- *height != Sizing::Auto && size.y.is_finite(),
- );
-
- Region::new(size, expand)
-}
-
-/// Builds the pod regions for a breakable sized container.
-fn breakable_pod<'a>(
- width: &Sizing,
- height: &Sizing,
- inset: &Sides<Rel<Abs>>,
- styles: StyleChain,
- regions: Regions,
- buf: &'a mut SmallVec<[Abs; 2]>,
-) -> Regions<'a> {
- let base = regions.base();
-
- // The vertical region sizes we're about to build.
- let first;
- let full;
- let backlog: &mut [Abs];
- let last;
-
- // If the block has a fixed height, things are very different, so we
- // handle that case completely separately.
- match height {
- Sizing::Auto | Sizing::Fr(_) => {
- // If the block is automatically sized, we can just inherit the
- // regions.
- first = regions.size.y;
- full = regions.full;
- buf.extend_from_slice(regions.backlog);
- backlog = buf;
- last = regions.last;
- }
-
- Sizing::Rel(rel) => {
- // Resolve the sizing to a concrete size.
- let resolved = rel.resolve(styles).relative_to(base.y);
-
- // Since we're manually sized, the resolved size is the base height.
- full = resolved;
-
- // Distribute the fixed height across a start region and a backlog.
- (first, backlog) = distribute(resolved, regions, buf);
-
- // If the height is manually sized, we don't want a final repeatable
- // region.
- last = None;
- }
- };
-
- // Resolve the horizontal sizing to a concrete width and combine
- // `width` and `first` into `size`.
- let mut size = Size::new(
- match width {
- Sizing::Auto | Sizing::Fr(_) => regions.size.x,
- Sizing::Rel(rel) => rel.resolve(styles).relative_to(base.x),
- },
- first,
- );
-
- // Take the inset, if any, into account, applying it to the
- // individual region components.
- let (mut full, mut last) = (full, last);
- if !inset.is_zero() {
- crate::layout::shrink_multiple(&mut size, &mut full, backlog, &mut last, inset);
- }
-
- // If the child is manually, the size is forced and we should enable
- // expansion.
- let expand = Axes::new(
- *width != Sizing::Auto && size.x.is_finite(),
- *height != Sizing::Auto && size.y.is_finite(),
- );
-
- Regions { size, full, backlog, last, expand }
-}
-
-/// Distribute a fixed height spread over existing regions into a new first
-/// height and a new backlog.
-fn distribute<'a>(
- height: Abs,
- mut regions: Regions,
- buf: &'a mut SmallVec<[Abs; 2]>,
-) -> (Abs, &'a mut [Abs]) {
- // Build new region heights from old regions.
- let mut remaining = height;
- loop {
- let limited = regions.size.y.clamp(Abs::zero(), remaining);
- buf.push(limited);
- remaining -= limited;
- if remaining.approx_empty()
- || !regions.may_break()
- || (!regions.may_progress() && limited.approx_empty())
- {
- break;
- }
- regions.next();
- }
-
- // If there is still something remaining, apply it to the
- // last region (it will overflow, but there's nothing else
- // we can do).
- if !remaining.approx_empty() {
- if let Some(last) = buf.last_mut() {
- *last += remaining;
- }
- }
-
- // Distribute the heights to the first region and the
- // backlog. There is no last region, since the height is
- // fixed.
- (buf[0], &mut buf[1..])
-}
-
-/// Manual closure implementations for layout callbacks.
-///
-/// Normal closures are not `Hash`, so we can't use them.
-mod callbacks {
- use super::*;
-
- macro_rules! callback {
- ($name:ident = ($($param:ident: $param_ty:ty),* $(,)?) -> $ret:ty) => {
- #[derive(Debug, Clone, PartialEq, Hash)]
- pub struct $name {
- captured: Content,
- f: fn(&Content, $($param_ty),*) -> $ret,
- }
-
- impl $name {
- pub fn new<T: NativeElement>(
- captured: Packed<T>,
- f: fn(&Packed<T>, $($param_ty),*) -> $ret,
- ) -> Self {
- Self {
- // Type-erased the content.
- captured: captured.pack(),
- // Safety: The only difference between the two function
- // pointer types is the type of the first parameter,
- // which changes from `&Packed<T>` to `&Content`. This
- // is safe because:
- // - `Packed<T>` is a transparent wrapper around
- // `Content`, so for any `T` it has the same memory
- // representation as `Content`.
- // - While `Packed<T>` imposes the additional constraint
- // that the content is of type `T`, this constraint is
- // upheld: It is initially the case because we store a
- // `Packed<T>` above. It keeps being the case over the
- // lifetime of the closure because `capture` is a
- // private field and `Content`'s `Clone` impl is
- // guaranteed to retain the type (if it didn't,
- // literally everything would break).
- #[allow(clippy::missing_transmute_annotations)]
- f: unsafe { std::mem::transmute(f) },
- }
- }
-
- pub fn call(&self, $($param: $param_ty),*) -> $ret {
- (self.f)(&self.captured, $($param),*)
- }
- }
- };
- }
-
- callback! {
- InlineCallback = (
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- region: Size,
- ) -> SourceResult<Vec<InlineItem>>
- }
-
- callback! {
- BlockSingleCallback = (
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- region: Region,
- ) -> SourceResult<Frame>
- }
-
- callback! {
- BlockMultiCallback = (
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment>
- }
-}
diff --git a/crates/typst/src/layout/repeat.rs b/crates/typst/src/layout/repeat.rs
deleted file mode 100644
index 66a6f923..00000000
--- a/crates/typst/src/layout/repeat.rs
+++ /dev/null
@@ -1,107 +0,0 @@
-use crate::diag::{bail, SourceResult};
-use crate::engine::Engine;
-use crate::foundations::{
- elem, Content, NativeElement, Packed, Resolve, Show, StyleChain,
-};
-use crate::introspection::Locator;
-use crate::layout::{
- layout_frame, Abs, AlignElem, Axes, BlockElem, Frame, Length, Point, Region, Size,
-};
-use crate::utils::Numeric;
-
-/// Repeats content to the available space.
-///
-/// This can be useful when implementing a custom index, reference, or outline.
-///
-/// Space may be inserted between the instances of the body parameter, so be
-/// sure to adjust the [`justify`]($repeat.justify) parameter accordingly.
-///
-/// Errors if there no bounds on the available space, as it would create
-/// infinite content.
-///
-/// # Example
-/// ```example
-/// Sign on the dotted line:
-/// #box(width: 1fr, repeat[.])
-///
-/// #set text(10pt)
-/// #v(8pt, weak: true)
-/// #align(right)[
-/// Berlin, the 22nd of December, 2022
-/// ]
-/// ```
-#[elem(Show)]
-pub struct RepeatElem {
- /// The content to repeat.
- #[required]
- pub body: Content,
-
- /// The gap between each instance of the body.
- #[default]
- pub gap: Length,
-
- /// Whether to increase the gap between instances to completely fill the
- /// available space.
- #[default(true)]
- pub justify: bool,
-}
-
-impl Show for Packed<RepeatElem> {
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), layout_repeat)
- .pack()
- .spanned(self.span()))
- }
-}
-
-/// Layout the repeated content.
-#[typst_macros::time(span = elem.span())]
-fn layout_repeat(
- elem: &Packed<RepeatElem>,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- region: Region,
-) -> SourceResult<Frame> {
- let pod = Region::new(region.size, Axes::new(false, false));
- let piece = layout_frame(engine, &elem.body, locator, styles, pod)?;
- let size = Size::new(region.size.x, piece.height());
-
- if !size.is_finite() {
- bail!(elem.span(), "repeat with no size restrictions");
- }
-
- let mut frame = Frame::soft(size);
- if piece.has_baseline() {
- frame.set_baseline(piece.baseline());
- }
-
- let mut gap = elem.gap(styles).resolve(styles);
- let fill = region.size.x;
- let width = piece.width();
-
- // count * width + (count - 1) * gap = fill, but count is an integer so
- // we need to round down and get the remainder.
- let count = ((fill + gap) / (width + gap)).floor();
- let remaining = (fill + gap) % (width + gap);
-
- let justify = elem.justify(styles);
- if justify {
- gap += remaining / (count - 1.0);
- }
-
- let align = AlignElem::alignment_in(styles).resolve(styles);
- let mut offset = Abs::zero();
- if count == 1.0 || !justify {
- offset += align.x.position(remaining);
- }
-
- if width > Abs::zero() {
- for _ in 0..(count as usize).min(1000) {
- frame.push_frame(Point::with_x(offset), piece.clone());
- offset += piece.width() + gap;
- }
- }
-
- Ok(frame)
-}
diff --git a/crates/typst/src/lib.rs b/crates/typst/src/lib.rs
index 7f0b8e69..db1673d2 100644
--- a/crates/typst/src/lib.rs
+++ b/crates/typst/src/lib.rs
@@ -13,72 +13,45 @@
//! order-independent and thus much better suited for further processing than
//! the raw markup.
//! - **Layouting:**
-//! Next, the content is [layouted] into a [document] containing one [frame]
+//! Next, the content is [laid out] into a [document] containing one [frame]
//! per page with items at fixed positions.
//! - **Exporting:**
//! These frames can finally be exported into an output format (currently PDF,
//! PNG, or SVG).
//!
-//! [tokens]: syntax::SyntaxKind
-//! [parsed]: syntax::parse
-//! [syntax tree]: syntax::SyntaxNode
-//! [AST]: syntax::ast
-//! [evaluate]: eval::eval
-//! [module]: foundations::Module
-//! [content]: foundations::Content
-//! [layouted]: crate::layout::layout_document
-//! [document]: model::Document
-//! [frame]: layout::Frame
-
-#![recursion_limit = "1000"]
-#![allow(clippy::comparison_chain)]
-#![allow(clippy::wildcard_in_or_patterns)]
-#![allow(clippy::manual_range_contains)]
-
-extern crate self as typst;
-
-pub mod diag;
-pub mod engine;
-pub mod eval;
-pub mod foundations;
-pub mod introspection;
-pub mod layout;
-pub mod loading;
-pub mod math;
-pub mod model;
-pub mod realize;
-pub mod symbols;
-pub mod text;
-pub mod visualize;
-
+//! [tokens]: typst_syntax::SyntaxKind
+//! [parsed]: typst_syntax::parse
+//! [syntax tree]: typst_syntax::SyntaxNode
+//! [AST]: typst_syntax::ast
+//! [evaluate]: typst_eval::eval
+//! [module]: crate::foundations::Module
+//! [content]: crate::foundations::Content
+//! [laid out]: typst_layout::layout_document
+//! [document]: crate::model::Document
+//! [frame]: crate::layout::Frame
+
+pub extern crate comemo;
+pub extern crate ecow;
+
+pub use typst_library::*;
#[doc(inline)]
pub use typst_syntax as syntax;
#[doc(inline)]
pub use typst_utils as utils;
use std::collections::HashSet;
-use std::ops::{Deref, Range};
use comemo::{Track, Tracked, Validate};
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
+use typst_library::diag::{warning, FileError, SourceDiagnostic, SourceResult, Warned};
+use typst_library::engine::{Engine, Route, Sink, Traced};
+use typst_library::foundations::{StyleChain, Styles, Value};
+use typst_library::introspection::Introspector;
+use typst_library::model::Document;
+use typst_library::routines::Routines;
+use typst_syntax::{FileId, Span};
use typst_timing::{timed, TimingScope};
-use crate::diag::{
- warning, FileError, FileResult, SourceDiagnostic, SourceResult, Warned,
-};
-use crate::engine::{Engine, Route, Sink, Traced};
-use crate::foundations::{
- Array, Bytes, Datetime, Dict, Module, Scope, StyleChain, Styles, Value,
-};
-use crate::introspection::Introspector;
-use crate::layout::{Alignment, Dir};
-use crate::model::Document;
-use crate::syntax::package::PackageSpec;
-use crate::syntax::{FileId, Source, Span};
-use crate::text::{Font, FontBook};
-use crate::utils::LazyHash;
-use crate::visualize::Color;
-
/// Compile sources into a fully layouted document.
///
/// - Returns `Ok(document)` if there were no fatal errors.
@@ -118,7 +91,8 @@ fn compile_impl(
.map_err(|err| hint_invalid_main_file(world, err, main))?;
// First evaluate the main source file into a module.
- let content = crate::eval::eval(
+ let content = typst_eval::eval(
+ &ROUTINES,
world,
traced,
sink.track_mut(),
@@ -137,7 +111,7 @@ fn compile_impl(
// The name of the iterations for timing scopes.
const ITER_NAMES: &[&str] =
&["layout (1)", "layout (2)", "layout (3)", "layout (4)", "layout (5)"];
- let _scope = TimingScope::new(ITER_NAMES[iter], None);
+ let _scope = TimingScope::new(ITER_NAMES[iter]);
subsink = Sink::new();
@@ -148,10 +122,11 @@ fn compile_impl(
traced,
sink: subsink.track_mut(),
route: Route::default(),
+ routines: &ROUTINES,
};
// Layout!
- document = crate::layout::layout_document(&mut engine, &content, styles)?;
+ document = (engine.routines.layout_document)(&mut engine, &content, styles)?;
iter += 1;
if timed!("check stabilized", document.introspector.validate(&constraint)) {
@@ -182,237 +157,12 @@ fn compile_impl(
fn deduplicate(mut diags: EcoVec<SourceDiagnostic>) -> EcoVec<SourceDiagnostic> {
let mut unique = HashSet::new();
diags.retain(|diag| {
- let hash = crate::utils::hash128(&(&diag.span, &diag.message));
+ let hash = typst_utils::hash128(&(&diag.span, &diag.message));
unique.insert(hash)
});
diags
}
-/// The environment in which typesetting occurs.
-///
-/// All loading functions (`main`, `source`, `file`, `font`) should perform
-/// internal caching so that they are relatively cheap on repeated invocations
-/// with the same argument. [`Source`], [`Bytes`], and [`Font`] are
-/// all reference-counted and thus cheap to clone.
-///
-/// The compiler doesn't do the caching itself because the world has much more
-/// information on when something can change. For example, fonts typically don't
-/// change and can thus even be cached across multiple compilations (for
-/// long-running applications like `typst watch`). Source files on the other
-/// hand can change and should thus be cleared after each compilation. Advanced
-/// clients like language servers can also retain the source files and
-/// [edit](Source::edit) them in-place to benefit from better incremental
-/// performance.
-#[comemo::track]
-pub trait World: Send + Sync {
- /// The standard library.
- ///
- /// Can be created through `Library::build()`.
- fn library(&self) -> &LazyHash<Library>;
-
- /// Metadata about all known fonts.
- fn book(&self) -> &LazyHash<FontBook>;
-
- /// Get the file id of the main source file.
- fn main(&self) -> FileId;
-
- /// Try to access the specified source file.
- fn source(&self, id: FileId) -> FileResult<Source>;
-
- /// Try to access the specified file.
- fn file(&self, id: FileId) -> FileResult<Bytes>;
-
- /// Try to access the font with the given index in the font book.
- fn font(&self, index: usize) -> Option<Font>;
-
- /// Get the current date.
- ///
- /// If no offset is specified, the local date should be chosen. Otherwise,
- /// the UTC date should be chosen with the corresponding offset in hours.
- ///
- /// If this function returns `None`, Typst's `datetime` function will
- /// return an error.
- fn today(&self, offset: Option<i64>) -> Option<Datetime>;
-
- /// A list of all available packages and optionally descriptions for them.
- ///
- /// This function is optional to implement. It enhances the user experience
- /// by enabling autocompletion for packages. Details about packages from the
- /// `@preview` namespace are available from
- /// `https://packages.typst.org/preview/index.json`.
- fn packages(&self) -> &[(PackageSpec, Option<EcoString>)] {
- &[]
- }
-}
-
-macro_rules! delegate_for_ptr {
- ($W:ident for $ptr:ty) => {
- impl<$W: World> World for $ptr {
- fn library(&self) -> &LazyHash<Library> {
- self.deref().library()
- }
-
- fn book(&self) -> &LazyHash<FontBook> {
- self.deref().book()
- }
-
- fn main(&self) -> FileId {
- self.deref().main()
- }
-
- fn source(&self, id: FileId) -> FileResult<Source> {
- self.deref().source(id)
- }
-
- fn file(&self, id: FileId) -> FileResult<Bytes> {
- self.deref().file(id)
- }
-
- fn font(&self, index: usize) -> Option<Font> {
- self.deref().font(index)
- }
-
- fn today(&self, offset: Option<i64>) -> Option<Datetime> {
- self.deref().today(offset)
- }
-
- fn packages(&self) -> &[(PackageSpec, Option<EcoString>)] {
- self.deref().packages()
- }
- }
- };
-}
-
-delegate_for_ptr!(W for std::boxed::Box<W>);
-delegate_for_ptr!(W for std::sync::Arc<W>);
-delegate_for_ptr!(W for &W);
-
-/// Helper methods on [`World`] implementations.
-pub trait WorldExt {
- /// Get the byte range for a span.
- ///
- /// Returns `None` if the `Span` does not point into any source file.
- fn range(&self, span: Span) -> Option<Range<usize>>;
-}
-
-impl<T: World> WorldExt for T {
- fn range(&self, span: Span) -> Option<Range<usize>> {
- self.source(span.id()?).ok()?.range(span)
- }
-}
-
-/// Definition of Typst's standard library.
-#[derive(Debug, Clone, Hash)]
-pub struct Library {
- /// The module that contains the definitions that are available everywhere.
- pub global: Module,
- /// The module that contains the definitions available in math mode.
- pub math: Module,
- /// The default style properties (for page size, font selection, and
- /// everything else configurable via set and show rules).
- pub styles: Styles,
- /// The standard library as a value.
- /// Used to provide the `std` variable.
- pub std: Value,
-}
-
-impl Library {
- /// Create a new builder for a library.
- pub fn builder() -> LibraryBuilder {
- LibraryBuilder::default()
- }
-}
-
-impl Default for Library {
- /// Constructs the standard library with the default configuration.
- fn default() -> Self {
- Self::builder().build()
- }
-}
-
-/// Configurable builder for the standard library.
-///
-/// This struct is created by [`Library::builder`].
-#[derive(Debug, Clone, Default)]
-pub struct LibraryBuilder {
- inputs: Option<Dict>,
-}
-
-impl LibraryBuilder {
- /// Configure the inputs visible through `sys.inputs`.
- pub fn with_inputs(mut self, inputs: Dict) -> Self {
- self.inputs = Some(inputs);
- self
- }
-
- /// Consumes the builder and returns a `Library`.
- pub fn build(self) -> Library {
- let math = math::module();
- let inputs = self.inputs.unwrap_or_default();
- let global = global(math.clone(), inputs);
- let std = Value::Module(global.clone());
- Library { global, math, styles: Styles::new(), std }
- }
-}
-
-/// Construct the module with global definitions.
-fn global(math: Module, inputs: Dict) -> Module {
- let mut global = Scope::deduplicating();
- self::foundations::define(&mut global, inputs);
- self::model::define(&mut global);
- self::text::define(&mut global);
- global.reset_category();
- global.define_module(math);
- self::layout::define(&mut global);
- self::visualize::define(&mut global);
- self::introspection::define(&mut global);
- self::loading::define(&mut global);
- self::symbols::define(&mut global);
- prelude(&mut global);
- Module::new("global", global)
-}
-
-/// Defines scoped values that are globally available, too.
-fn prelude(global: &mut Scope) {
- global.reset_category();
- global.define("black", Color::BLACK);
- global.define("gray", Color::GRAY);
- global.define("silver", Color::SILVER);
- global.define("white", Color::WHITE);
- global.define("navy", Color::NAVY);
- global.define("blue", Color::BLUE);
- global.define("aqua", Color::AQUA);
- global.define("teal", Color::TEAL);
- global.define("eastern", Color::EASTERN);
- global.define("purple", Color::PURPLE);
- global.define("fuchsia", Color::FUCHSIA);
- global.define("maroon", Color::MAROON);
- global.define("red", Color::RED);
- global.define("orange", Color::ORANGE);
- global.define("yellow", Color::YELLOW);
- global.define("olive", Color::OLIVE);
- global.define("green", Color::GREEN);
- global.define("lime", Color::LIME);
- global.define("luma", Color::luma_data());
- global.define("oklab", Color::oklab_data());
- global.define("oklch", Color::oklch_data());
- global.define("rgb", Color::rgb_data());
- global.define("cmyk", Color::cmyk_data());
- global.define("range", Array::range_data());
- global.define("ltr", Dir::LTR);
- global.define("rtl", Dir::RTL);
- global.define("ttb", Dir::TTB);
- global.define("btt", Dir::BTT);
- global.define("start", Alignment::START);
- global.define("left", Alignment::LEFT);
- global.define("center", Alignment::CENTER);
- global.define("right", Alignment::RIGHT);
- global.define("end", Alignment::END);
- global.define("top", Alignment::TOP);
- global.define("horizon", Alignment::HORIZON);
- global.define("bottom", Alignment::BOTTOM);
-}
-
/// Adds useful hints when the main source file couldn't be read
/// and returns the final diagnostic.
fn hint_invalid_main_file(
@@ -457,3 +207,40 @@ fn hint_invalid_main_file(
eco_vec![diagnostic]
}
+
+/// Defines implementation of various Typst compiler routines as a table of
+/// function pointers.
+///
+/// This is essentially dynamic linking and done to allow for crate splitting.
+pub static ROUTINES: Routines = Routines {
+ eval_string: typst_eval::eval_string,
+ eval_closure: typst_eval::eval_closure,
+ realize: typst_realize::realize,
+ layout_document: typst_layout::layout_document,
+ layout_fragment: typst_layout::layout_fragment,
+ layout_frame: typst_layout::layout_frame,
+ layout_inline: typst_layout::layout_inline,
+ layout_box: typst_layout::layout_box,
+ layout_list: typst_layout::layout_list,
+ layout_enum: typst_layout::layout_enum,
+ layout_grid: typst_layout::layout_grid,
+ layout_table: typst_layout::layout_table,
+ layout_stack: typst_layout::layout_stack,
+ layout_columns: typst_layout::layout_columns,
+ layout_move: typst_layout::layout_move,
+ layout_rotate: typst_layout::layout_rotate,
+ layout_scale: typst_layout::layout_scale,
+ layout_skew: typst_layout::layout_skew,
+ layout_repeat: typst_layout::layout_repeat,
+ layout_pad: typst_layout::layout_pad,
+ layout_line: typst_layout::layout_line,
+ layout_path: typst_layout::layout_path,
+ layout_polygon: typst_layout::layout_polygon,
+ layout_rect: typst_layout::layout_rect,
+ layout_square: typst_layout::layout_square,
+ layout_ellipse: typst_layout::layout_ellipse,
+ layout_circle: typst_layout::layout_circle,
+ layout_image: typst_layout::layout_image,
+ layout_equation_block: typst_layout::layout_equation_block,
+ layout_equation_inline: typst_layout::layout_equation_inline,
+};
diff --git a/crates/typst/src/math/align.rs b/crates/typst/src/math/align.rs
deleted file mode 100644
index 41580bf9..00000000
--- a/crates/typst/src/math/align.rs
+++ /dev/null
@@ -1,70 +0,0 @@
-use crate::diag::SourceResult;
-use crate::foundations::{elem, Content, NativeElement, Packed, StyleChain};
-use crate::layout::Abs;
-use crate::math::{LayoutMath, MathContext, MathFragment, MathRun};
-use crate::utils::singleton;
-
-/// A math alignment point: `&`, `&&`.
-#[elem(title = "Alignment Point", LayoutMath)]
-pub struct AlignPointElem {}
-
-impl AlignPointElem {
- /// Get the globally shared alignment point element.
- pub fn shared() -> &'static Content {
- singleton!(Content, AlignPointElem::new().pack())
- }
-}
-
-impl LayoutMath for Packed<AlignPointElem> {
- fn layout_math(&self, ctx: &mut MathContext, _: StyleChain) -> SourceResult<()> {
- ctx.push(MathFragment::Align);
- Ok(())
- }
-}
-
-pub(super) struct AlignmentResult {
- pub points: Vec<Abs>,
- pub width: Abs,
-}
-
-/// Determine the positions of the alignment points, according to the input rows combined.
-pub(super) fn alignments(rows: &[MathRun]) -> AlignmentResult {
- let mut widths = Vec::<Abs>::new();
-
- let mut pending_width = Abs::zero();
- for row in rows {
- let mut width = Abs::zero();
- let mut alignment_index = 0;
-
- for fragment in row.iter() {
- if matches!(fragment, MathFragment::Align) {
- if alignment_index < widths.len() {
- widths[alignment_index].set_max(width);
- } else {
- widths.push(width.max(pending_width));
- }
- width = Abs::zero();
- alignment_index += 1;
- } else {
- width += fragment.width();
- }
- }
- if widths.is_empty() {
- pending_width.set_max(width);
- } else if alignment_index < widths.len() {
- widths[alignment_index].set_max(width);
- } else {
- widths.push(width.max(pending_width));
- }
- }
-
- let mut points = widths;
- for i in 1..points.len() {
- let prev = points[i - 1];
- points[i] += prev;
- }
- AlignmentResult {
- width: points.last().copied().unwrap_or(pending_width),
- points,
- }
-}
diff --git a/crates/typst/src/math/cancel.rs b/crates/typst/src/math/cancel.rs
deleted file mode 100644
index 0bc28f07..00000000
--- a/crates/typst/src/math/cancel.rs
+++ /dev/null
@@ -1,254 +0,0 @@
-use comemo::Track;
-
-use crate::diag::{At, SourceResult};
-use crate::foundations::{cast, elem, Content, Context, Func, Packed, Smart, StyleChain};
-use crate::layout::{
- Abs, Angle, Frame, FrameItem, Length, Point, Ratio, Rel, Size, Transform,
-};
-use crate::math::{scaled_font_size, FrameFragment, LayoutMath, MathContext};
-use crate::syntax::Span;
-use crate::text::TextElem;
-use crate::visualize::{FixedStroke, Geometry, Stroke};
-
-/// Displays a diagonal line over a part of an equation.
-///
-/// This is commonly used to show the elimination of a term.
-///
-/// # Example
-/// ```example
-/// >>> #set page(width: 140pt)
-/// Here, we can simplify:
-/// $ (a dot b dot cancel(x)) /
-/// cancel(x) $
-/// ```
-#[elem(LayoutMath)]
-pub struct CancelElem {
- /// The content over which the line should be placed.
- #[required]
- pub body: Content,
-
- /// The length of the line, relative to the length of the diagonal spanning
- /// the whole element being "cancelled". A value of `{100%}` would then have
- /// the line span precisely the element's diagonal.
- ///
- /// ```example
- /// >>> #set page(width: 140pt)
- /// $ a + cancel(x, length: #200%)
- /// - cancel(x, length: #200%) $
- /// ```
- #[default(Rel::new(Ratio::one(), Abs::pt(3.0).into()))]
- pub length: Rel<Length>,
-
- /// Whether the cancel line should be inverted (flipped along the y-axis).
- /// For the default angle setting, inverted means the cancel line
- /// points to the top left instead of top right.
- ///
- /// ```example
- /// >>> #set page(width: 140pt)
- /// $ (a cancel((b + c), inverted: #true)) /
- /// cancel(b + c, inverted: #true) $
- /// ```
- #[default(false)]
- pub inverted: bool,
-
- /// Whether two opposing cancel lines should be drawn, forming a cross over
- /// the element. Overrides `inverted`.
- ///
- /// ```example
- /// >>> #set page(width: 140pt)
- /// $ cancel(Pi, cross: #true) $
- /// ```
- #[default(false)]
- pub cross: bool,
-
- /// How much to rotate the cancel line.
- ///
- /// - If given an angle, the line is rotated by that angle clockwise with
- /// respect to the y-axis.
- /// - If `{auto}`, the line assumes the default angle; that is, along the
- /// rising diagonal of the content box.
- /// - If given a function `angle => angle`, the line is rotated, with
- /// respect to the y-axis, by the angle returned by that function. The
- /// function receives the default angle as its input.
- ///
- /// ```example
- /// >>> #set page(width: 140pt)
- /// $ cancel(Pi)
- /// cancel(Pi, angle: #0deg)
- /// cancel(Pi, angle: #45deg)
- /// cancel(Pi, angle: #90deg)
- /// cancel(1/(1+x), angle: #(a => a + 45deg))
- /// cancel(1/(1+x), angle: #(a => a + 90deg)) $
- /// ```
- pub angle: Smart<CancelAngle>,
-
- /// How to [stroke]($stroke) the cancel line.
- ///
- /// ```example
- /// >>> #set page(width: 140pt)
- /// $ cancel(
- /// sum x,
- /// stroke: #(
- /// paint: red,
- /// thickness: 1.5pt,
- /// dash: "dashed",
- /// ),
- /// ) $
- /// ```
- #[resolve]
- #[fold]
- #[default(Stroke {
- // Default stroke has 0.5pt for better visuals.
- thickness: Smart::Custom(Abs::pt(0.5).into()),
- ..Default::default()
- })]
- pub stroke: Stroke,
-}
-
-impl LayoutMath for Packed<CancelElem> {
- #[typst_macros::time(name = "math.cancel", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- let body = ctx.layout_into_fragment(self.body(), styles)?;
- // Preserve properties of body.
- let body_class = body.class();
- let body_italics = body.italics_correction();
- let body_attach = body.accent_attach();
- let body_text_like = body.is_text_like();
-
- let mut body = body.into_frame();
- let body_size = body.size();
- let span = self.span();
- let length = self.length(styles).at(scaled_font_size(ctx, styles));
-
- let stroke = self.stroke(styles).unwrap_or(FixedStroke {
- paint: TextElem::fill_in(styles).as_decoration(),
- ..Default::default()
- });
-
- let invert = self.inverted(styles);
- let cross = self.cross(styles);
- let angle = self.angle(styles);
-
- let invert_first_line = !cross && invert;
- let first_line = draw_cancel_line(
- ctx,
- length,
- stroke.clone(),
- invert_first_line,
- &angle,
- body_size,
- styles,
- span,
- )?;
-
- // The origin of our line is the very middle of the element.
- let center = body_size.to_point() / 2.0;
- body.push_frame(center, first_line);
-
- if cross {
- // Draw the second line.
- let second_line = draw_cancel_line(
- ctx, length, stroke, true, &angle, body_size, styles, span,
- )?;
-
- body.push_frame(center, second_line);
- }
-
- ctx.push(
- FrameFragment::new(ctx, styles, body)
- .with_class(body_class)
- .with_italics_correction(body_italics)
- .with_accent_attach(body_attach)
- .with_text_like(body_text_like),
- );
-
- Ok(())
- }
-}
-
-/// Defines the cancel line.
-#[derive(Debug, Clone, PartialEq, Hash)]
-pub enum CancelAngle {
- Angle(Angle),
- Func(Func),
-}
-
-cast! {
- CancelAngle,
- self => match self {
- Self::Angle(v) => v.into_value(),
- Self::Func(v) => v.into_value()
- },
- v: Angle => CancelAngle::Angle(v),
- v: Func => CancelAngle::Func(v),
-}
-
-/// Draws a cancel line.
-#[allow(clippy::too_many_arguments)]
-fn draw_cancel_line(
- ctx: &mut MathContext,
- length_scale: Rel<Abs>,
- stroke: FixedStroke,
- invert: bool,
- angle: &Smart<CancelAngle>,
- body_size: Size,
- styles: StyleChain,
- span: Span,
-) -> SourceResult<Frame> {
- let default = default_angle(body_size);
- let mut angle = match angle {
- // Non specified angle defaults to the diagonal
- Smart::Auto => default,
- Smart::Custom(angle) => match angle {
- // This specifies the absolute angle w.r.t y-axis clockwise.
- CancelAngle::Angle(v) => *v,
- // This specifies a function that takes the default angle as input.
- CancelAngle::Func(func) => func
- .call(ctx.engine, Context::new(None, Some(styles)).track(), [default])?
- .cast()
- .at(span)?,
- },
- };
-
- // invert means flipping along the y-axis
- if invert {
- angle *= -1.0;
- }
-
- // same as above, the default length is the diagonal of the body box.
- let default_length = body_size.to_point().hypot();
- let length = length_scale.relative_to(default_length);
-
- // Draw a vertical line of length and rotate it by angle
- let start = Point::new(Abs::zero(), length / 2.0);
- let delta = Point::new(Abs::zero(), -length);
-
- let mut frame = Frame::soft(body_size);
- frame.push(start, FrameItem::Shape(Geometry::Line(delta).stroked(stroke), span));
-
- // Having the middle of the line at the origin is convenient here.
- frame.transform(Transform::rotate(angle));
- Ok(frame)
-}
-
-/// The default line angle for a body of the given size.
-fn default_angle(body: Size) -> Angle {
- // The default cancel line is the diagonal.
- // We infer the default angle from
- // the diagonal w.r.t to the body box.
- //
- // The returned angle is in the range of [0, Pi/2]
- //
- // Note that the angle is computed w.r.t to the y-axis
- //
- // B
- // /|
- // diagonal / | height
- // / |
- // / |
- // O ----
- // width
- let (width, height) = (body.x, body.y);
- let default_angle = (width / height).atan(); // arctangent (in the range [0, Pi/2])
- Angle::rad(default_angle)
-}
diff --git a/crates/typst/src/math/class.rs b/crates/typst/src/math/class.rs
deleted file mode 100644
index 1110ebc9..00000000
--- a/crates/typst/src/math/class.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-use unicode_math_class::MathClass;
-
-use crate::diag::SourceResult;
-use crate::foundations::{elem, Content, Packed, StyleChain};
-use crate::math::{EquationElem, LayoutMath, Limits, MathContext};
-
-/// Forced use of a certain math class.
-///
-/// This is useful to treat certain symbols as if they were of a different
-/// class, e.g. to make a symbol behave like a relation. The class of a symbol
-/// defines the way it is laid out, including spacing around it, and how its
-/// scripts are attached by default. Note that the latter can always be
-/// overridden using [`{limits}`](math.limits) and [`{scripts}`](math.scripts).
-///
-/// # Example
-/// ```example
-/// #let loves = math.class(
-/// "relation",
-/// sym.suit.heart,
-/// )
-///
-/// $x loves y and y loves 5$
-/// ```
-#[elem(LayoutMath)]
-pub struct ClassElem {
- /// The class to apply to the content.
- #[required]
- pub class: MathClass,
-
- /// The content to which the class is applied.
- #[required]
- pub body: Content,
-}
-
-impl LayoutMath for Packed<ClassElem> {
- #[typst_macros::time(name = "math.class", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- let class = *self.class();
- let style = EquationElem::set_class(Some(class)).wrap();
- let mut fragment = ctx.layout_into_fragment(self.body(), styles.chain(&style))?;
- fragment.set_class(class);
- fragment.set_limits(Limits::for_class(class));
- ctx.push(fragment);
- Ok(())
- }
-}
diff --git a/crates/typst/src/math/ctx.rs b/crates/typst/src/math/ctx.rs
deleted file mode 100644
index 35eb665c..00000000
--- a/crates/typst/src/math/ctx.rs
+++ /dev/null
@@ -1,469 +0,0 @@
-use std::f64::consts::SQRT_2;
-
-use ecow::{eco_vec, EcoString};
-use rustybuzz::Feature;
-use ttf_parser::gsub::{AlternateSubstitution, SingleSubstitution, SubstitutionSubtable};
-use ttf_parser::math::MathValue;
-use ttf_parser::opentype_layout::LayoutTable;
-use ttf_parser::GlyphId;
-use unicode_math_class::MathClass;
-use unicode_segmentation::UnicodeSegmentation;
-
-use crate::diag::SourceResult;
-use crate::engine::Engine;
-use crate::foundations::{Content, Packed, StyleChain, StyleVec};
-use crate::introspection::{SplitLocator, TagElem};
-use crate::layout::{
- layout_frame, Abs, Axes, BoxElem, Em, Frame, HElem, PlaceElem, Region, Size, Spacing,
-};
-use crate::math::{
- scaled_font_size, styled_char, EquationElem, FrameFragment, GlyphFragment,
- LayoutMath, MathFragment, MathRun, MathSize, THICK,
-};
-use crate::realize::{realize, Arenas, RealizationKind};
-use crate::syntax::{is_newline, Span};
-use crate::text::{
- features, BottomEdge, BottomEdgeMetric, Font, LinebreakElem, SpaceElem, TextElem,
- TextSize, TopEdge, TopEdgeMetric,
-};
-
-macro_rules! scaled {
- ($ctx:expr, $styles:expr, text: $text:ident, display: $display:ident $(,)?) => {
- match $crate::math::EquationElem::size_in($styles) {
- $crate::math::MathSize::Display => scaled!($ctx, $styles, $display),
- _ => scaled!($ctx, $styles, $text),
- }
- };
- ($ctx:expr, $styles:expr, $name:ident) => {
- $ctx.constants
- .$name()
- .scaled($ctx, $crate::math::scaled_font_size($ctx, $styles))
- };
-}
-
-macro_rules! percent {
- ($ctx:expr, $name:ident) => {
- $ctx.constants.$name() as f64 / 100.0
- };
-}
-
-/// The context for math layout.
-pub struct MathContext<'a, 'v, 'e> {
- // External.
- pub engine: &'v mut Engine<'e>,
- pub locator: &'v mut SplitLocator<'a>,
- pub region: Region,
- // Font-related.
- pub font: &'a Font,
- pub ttf: &'a ttf_parser::Face<'a>,
- pub table: ttf_parser::math::Table<'a>,
- pub constants: ttf_parser::math::Constants<'a>,
- pub ssty_table: Option<ttf_parser::gsub::AlternateSubstitution<'a>>,
- pub glyphwise_tables: Option<Vec<GlyphwiseSubsts<'a>>>,
- pub space_width: Em,
- // Mutable.
- pub fragments: Vec<MathFragment>,
-}
-
-impl<'a, 'v, 'e> MathContext<'a, 'v, 'e> {
- /// Create a new math context.
- pub fn new(
- engine: &'v mut Engine<'e>,
- locator: &'v mut SplitLocator<'a>,
- styles: StyleChain<'a>,
- base: Size,
- font: &'a Font,
- ) -> Self {
- let math_table = font.ttf().tables().math.unwrap();
- let gsub_table = font.ttf().tables().gsub;
- let constants = math_table.constants.unwrap();
-
- let ssty_table = gsub_table
- .and_then(|gsub| {
- gsub.features
- .find(ttf_parser::Tag::from_bytes(b"ssty"))
- .and_then(|feature| feature.lookup_indices.get(0))
- .and_then(|index| gsub.lookups.get(index))
- })
- .and_then(|ssty| ssty.subtables.get::<SubstitutionSubtable>(0))
- .and_then(|ssty| match ssty {
- SubstitutionSubtable::Alternate(alt_glyphs) => Some(alt_glyphs),
- _ => None,
- });
-
- let features = features(styles);
- let glyphwise_tables = gsub_table.map(|gsub| {
- features
- .into_iter()
- .filter_map(|feature| GlyphwiseSubsts::new(gsub, feature))
- .collect()
- });
-
- let ttf = font.ttf();
- let space_width = ttf
- .glyph_index(' ')
- .and_then(|id| ttf.glyph_hor_advance(id))
- .map(|advance| font.to_em(advance))
- .unwrap_or(THICK);
-
- Self {
- engine,
- locator,
- region: Region::new(base, Axes::splat(false)),
- font,
- ttf: font.ttf(),
- table: math_table,
- constants,
- ssty_table,
- glyphwise_tables,
- space_width,
- fragments: vec![],
- }
- }
-
- /// Push a fragment.
- pub fn push(&mut self, fragment: impl Into<MathFragment>) {
- self.fragments.push(fragment.into());
- }
-
- /// Push multiple fragments.
- pub fn extend(&mut self, fragments: impl IntoIterator<Item = MathFragment>) {
- self.fragments.extend(fragments);
- }
-
- /// Layout the given element and return the result as a [`MathRun`].
- pub fn layout_into_run(
- &mut self,
- elem: &Content,
- styles: StyleChain,
- ) -> SourceResult<MathRun> {
- Ok(MathRun::new(self.layout_into_fragments(elem, styles)?))
- }
-
- /// Layout the given element and return the resulting [`MathFragment`]s.
- pub fn layout_into_fragments(
- &mut self,
- elem: &Content,
- styles: StyleChain,
- ) -> SourceResult<Vec<MathFragment>> {
- // The element's layout_math() changes the fragments held in this
- // MathContext object, but for convenience this function shouldn't change
- // them, so we restore the MathContext's fragments after obtaining the
- // layout result.
- let prev = std::mem::take(&mut self.fragments);
- self.layout(elem, styles)?;
- Ok(std::mem::replace(&mut self.fragments, prev))
- }
-
- /// Layout the given element and return the result as a
- /// unified [`MathFragment`].
- pub fn layout_into_fragment(
- &mut self,
- elem: &Content,
- styles: StyleChain,
- ) -> SourceResult<MathFragment> {
- Ok(self.layout_into_run(elem, styles)?.into_fragment(self, styles))
- }
-
- /// Layout the given element and return the result as a [`Frame`].
- pub fn layout_into_frame(
- &mut self,
- elem: &Content,
- styles: StyleChain,
- ) -> SourceResult<Frame> {
- Ok(self.layout_into_fragment(elem, styles)?.into_frame())
- }
-}
-
-impl MathContext<'_, '_, '_> {
- /// Layout arbitrary content.
- fn layout(&mut self, content: &Content, styles: StyleChain) -> SourceResult<()> {
- let arenas = Arenas::default();
- let pairs = realize(
- RealizationKind::Math,
- self.engine,
- self.locator,
- &arenas,
- content,
- styles,
- )?;
-
- let outer = styles;
- for (elem, styles) in pairs {
- // Hack because the font is fixed in math.
- if styles != outer && TextElem::font_in(styles) != TextElem::font_in(outer) {
- let frame = self.layout_external(elem, styles)?;
- self.push(FrameFragment::new(self, styles, frame).with_spaced(true));
- continue;
- }
-
- self.layout_realized(elem, styles)?;
- }
-
- Ok(())
- }
-
- /// Layout an element resulting from realization.
- fn layout_realized(
- &mut self,
- elem: &Content,
- styles: StyleChain,
- ) -> SourceResult<()> {
- if let Some(elem) = elem.to_packed::<TagElem>() {
- self.push(MathFragment::Tag(elem.tag.clone()));
- } else if elem.is::<SpaceElem>() {
- let font_size = scaled_font_size(self, styles);
- self.push(MathFragment::Space(self.space_width.at(font_size)));
- } else if elem.is::<LinebreakElem>() {
- self.push(MathFragment::Linebreak);
- } else if let Some(elem) = elem.to_packed::<HElem>() {
- if let Spacing::Rel(rel) = elem.amount() {
- if rel.rel.is_zero() {
- self.push(MathFragment::Spacing(
- rel.abs.at(scaled_font_size(self, styles)),
- elem.weak(styles),
- ));
- }
- }
- } else if let Some(elem) = elem.to_packed::<TextElem>() {
- let fragment = self.layout_text(elem, styles)?;
- self.push(fragment);
- } else if let Some(boxed) = elem.to_packed::<BoxElem>() {
- let frame = self.layout_box(boxed, styles)?;
- self.push(FrameFragment::new(self, styles, frame).with_spaced(true));
- } else if let Some(elem) = elem.with::<dyn LayoutMath>() {
- elem.layout_math(self, styles)?;
- } else {
- let mut frame = self.layout_external(elem, styles)?;
- if !frame.has_baseline() {
- let axis = scaled!(self, styles, axis_height);
- frame.set_baseline(frame.height() / 2.0 + axis);
- }
- self.push(
- FrameFragment::new(self, styles, frame)
- .with_spaced(true)
- .with_ignorant(elem.is::<PlaceElem>()),
- );
- }
-
- Ok(())
- }
-
- /// Layout a box into a frame.
- fn layout_box(
- &mut self,
- boxed: &Packed<BoxElem>,
- styles: StyleChain,
- ) -> SourceResult<Frame> {
- let local =
- TextElem::set_size(TextSize(scaled_font_size(self, styles).into())).wrap();
- boxed.layout(
- self.engine,
- self.locator.next(&boxed.span()),
- styles.chain(&local),
- self.region.size,
- )
- }
-
- /// Layout into a frame with normal layout.
- fn layout_external(
- &mut self,
- content: &Content,
- styles: StyleChain,
- ) -> SourceResult<Frame> {
- let local =
- TextElem::set_size(TextSize(scaled_font_size(self, styles).into())).wrap();
- layout_frame(
- self.engine,
- content,
- self.locator.next(&content.span()),
- styles.chain(&local),
- self.region,
- )
- }
-
- /// Layout the given [`TextElem`] into a [`MathFragment`].
- fn layout_text(
- &mut self,
- elem: &Packed<TextElem>,
- styles: StyleChain,
- ) -> SourceResult<MathFragment> {
- let text = elem.text();
- let span = elem.span();
- let mut chars = text.chars();
- let math_size = EquationElem::size_in(styles);
- let fragment = if let Some(mut glyph) = chars
- .next()
- .filter(|_| chars.next().is_none())
- .map(|c| styled_char(styles, c, true))
- .and_then(|c| GlyphFragment::try_new(self, styles, c, span))
- {
- // A single letter that is available in the math font.
- match math_size {
- MathSize::Script => {
- glyph.make_scriptsize(self);
- }
- MathSize::ScriptScript => {
- glyph.make_scriptscriptsize(self);
- }
- _ => (),
- }
-
- if glyph.class == MathClass::Large {
- let mut variant = if math_size == MathSize::Display {
- let height = scaled!(self, styles, display_operator_min_height)
- .max(SQRT_2 * glyph.height());
- glyph.stretch_vertical(self, height, Abs::zero())
- } else {
- glyph.into_variant()
- };
- // TeXbook p 155. Large operators are always vertically centered on the axis.
- variant.center_on_axis(self);
- variant.into()
- } else {
- glyph.into()
- }
- } else if text.chars().all(|c| c.is_ascii_digit() || c == '.') {
- // Numbers aren't that difficult.
- let mut fragments = vec![];
- for c in text.chars() {
- let c = styled_char(styles, c, false);
- fragments.push(GlyphFragment::new(self, styles, c, span).into());
- }
- let frame = MathRun::new(fragments).into_frame(self, styles);
- FrameFragment::new(self, styles, frame).with_text_like(true).into()
- } else {
- let local = [
- TextElem::set_top_edge(TopEdge::Metric(TopEdgeMetric::Bounds)),
- TextElem::set_bottom_edge(BottomEdge::Metric(BottomEdgeMetric::Bounds)),
- TextElem::set_size(TextSize(scaled_font_size(self, styles).into())),
- ]
- .map(|p| p.wrap());
-
- // Anything else is handled by Typst's standard text layout.
- let styles = styles.chain(&local);
- let text: EcoString =
- text.chars().map(|c| styled_char(styles, c, false)).collect();
- if text.contains(is_newline) {
- let mut fragments = vec![];
- for (i, piece) in text.split(is_newline).enumerate() {
- if i != 0 {
- fragments.push(MathFragment::Linebreak);
- }
- if !piece.is_empty() {
- fragments
- .push(self.layout_complex_text(piece, span, styles)?.into());
- }
- }
- let mut frame = MathRun::new(fragments).into_frame(self, styles);
- let axis = scaled!(self, styles, axis_height);
- frame.set_baseline(frame.height() / 2.0 + axis);
- FrameFragment::new(self, styles, frame).into()
- } else {
- self.layout_complex_text(&text, span, styles)?.into()
- }
- };
- Ok(fragment)
- }
-
- /// Layout the given text string into a [`FrameFragment`].
- fn layout_complex_text(
- &mut self,
- text: &str,
- span: Span,
- styles: StyleChain,
- ) -> SourceResult<FrameFragment> {
- // There isn't a natural width for a paragraph in a math environment;
- // because it will be placed somewhere probably not at the left margin
- // it will overflow. So emulate an `hbox` instead and allow the paragraph
- // to extend as far as needed.
- let spaced = text.graphemes(true).nth(1).is_some();
- let elem = TextElem::packed(text).spanned(span);
- let frame = crate::layout::layout_inline(
- self.engine,
- &StyleVec::wrap(eco_vec![elem]),
- self.locator.next(&span),
- styles,
- false,
- Size::splat(Abs::inf()),
- false,
- )?
- .into_frame();
-
- Ok(FrameFragment::new(self, styles, frame)
- .with_class(MathClass::Alphabetic)
- .with_text_like(true)
- .with_spaced(spaced))
- }
-}
-
-/// Converts some unit to an absolute length with the current font & font size.
-pub(super) trait Scaled {
- fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs;
-}
-
-impl Scaled for i16 {
- fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
- ctx.font.to_em(self).at(font_size)
- }
-}
-
-impl Scaled for u16 {
- fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
- ctx.font.to_em(self).at(font_size)
- }
-}
-
-impl Scaled for MathValue<'_> {
- fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
- self.value.scaled(ctx, font_size)
- }
-}
-
-/// An OpenType substitution table that is applicable to glyph-wise substitutions.
-pub enum GlyphwiseSubsts<'a> {
- Single(SingleSubstitution<'a>),
- Alternate(AlternateSubstitution<'a>, u32),
-}
-
-impl<'a> GlyphwiseSubsts<'a> {
- pub fn new(gsub: LayoutTable<'a>, feature: Feature) -> Option<Self> {
- let table = gsub
- .features
- .find(ttf_parser::Tag(feature.tag.0))
- .and_then(|feature| feature.lookup_indices.get(0))
- .and_then(|index| gsub.lookups.get(index))?;
- let table = table.subtables.get::<SubstitutionSubtable>(0)?;
- match table {
- SubstitutionSubtable::Single(single_glyphs) => {
- Some(Self::Single(single_glyphs))
- }
- SubstitutionSubtable::Alternate(alt_glyphs) => {
- Some(Self::Alternate(alt_glyphs, feature.value))
- }
- _ => None,
- }
- }
-
- pub fn try_apply(&self, glyph_id: GlyphId) -> Option<GlyphId> {
- match self {
- Self::Single(single) => match single {
- SingleSubstitution::Format1 { coverage, delta } => coverage
- .get(glyph_id)
- .map(|_| GlyphId(glyph_id.0.wrapping_add(*delta as u16))),
- SingleSubstitution::Format2 { coverage, substitutes } => {
- coverage.get(glyph_id).and_then(|idx| substitutes.get(idx))
- }
- },
- Self::Alternate(alternate, value) => alternate
- .coverage
- .get(glyph_id)
- .and_then(|idx| alternate.alternate_sets.get(idx))
- .and_then(|set| set.alternates.get(*value as u16)),
- }
- }
-
- pub fn apply(&self, glyph_id: GlyphId) -> GlyphId {
- self.try_apply(glyph_id).unwrap_or(glyph_id)
- }
-}
diff --git a/crates/typst/src/math/equation.rs b/crates/typst/src/math/equation.rs
deleted file mode 100644
index 19e3ce57..00000000
--- a/crates/typst/src/math/equation.rs
+++ /dev/null
@@ -1,576 +0,0 @@
-use std::num::NonZeroUsize;
-
-use unicode_math_class::MathClass;
-
-use crate::diag::{bail, SourceResult};
-use crate::engine::Engine;
-use crate::foundations::{
- elem, Content, NativeElement, Packed, Resolve, Show, ShowSet, Smart, StyleChain,
- Styles, Synthesize,
-};
-use crate::introspection::{Count, Counter, CounterUpdate, Locatable, Locator};
-use crate::layout::{
- layout_frame, Abs, AlignElem, Alignment, Axes, BlockElem, Em, FixedAlignment,
- Fragment, Frame, InlineElem, InlineItem, OuterHAlignment, Point, Region, Regions,
- Size, SpecificAlignment, VAlignment,
-};
-use crate::math::{
- scaled_font_size, MathContext, MathRunFrameBuilder, MathSize, MathVariant,
-};
-use crate::model::{Numbering, Outlinable, ParElem, ParLine, Refable, Supplement};
-use crate::syntax::Span;
-use crate::text::{
- families, variant, Font, FontFamily, FontList, FontWeight, LocalName, TextEdgeBounds,
- TextElem,
-};
-use crate::utils::{NonZeroExt, Numeric};
-use crate::World;
-
-/// A mathematical equation.
-///
-/// Can be displayed inline with text or as a separate block.
-///
-/// # Example
-/// ```example
-/// #set text(font: "New Computer Modern")
-///
-/// Let $a$, $b$, and $c$ be the side
-/// lengths of right-angled triangle.
-/// Then, we know that:
-/// $ a^2 + b^2 = c^2 $
-///
-/// Prove by induction:
-/// $ sum_(k=1)^n k = (n(n+1)) / 2 $
-/// ```
-///
-/// By default, block-level equations will not break across pages. This can be
-/// changed through `{show math.equation: set block(breakable: true)}`.
-///
-/// # Syntax
-/// This function also has dedicated syntax: Write mathematical markup within
-/// dollar signs to create an equation. Starting and ending the equation with at
-/// least one space lifts it into a separate block that is centered
-/// horizontally. For more details about math syntax, see the
-/// [main math page]($category/math).
-#[elem(Locatable, Synthesize, Show, ShowSet, Count, LocalName, Refable, Outlinable)]
-pub struct EquationElem {
- /// Whether the equation is displayed as a separate block.
- #[default(false)]
- pub block: bool,
-
- /// How to [number]($numbering) block-level equations.
- ///
- /// ```example
- /// #set math.equation(numbering: "(1)")
- ///
- /// We define:
- /// $ phi.alt := (1 + sqrt(5)) / 2 $ <ratio>
- ///
- /// With @ratio, we get:
- /// $ F_n = floor(1 / sqrt(5) phi.alt^n) $
- /// ```
- #[borrowed]
- pub numbering: Option<Numbering>,
-
- /// The alignment of the equation numbering.
- ///
- /// By default, the alignment is `{end + horizon}`. For the horizontal
- /// component, you can use `{right}`, `{left}`, or `{start}` and `{end}`
- /// of the text direction; for the vertical component, you can use
- /// `{top}`, `{horizon}`, or `{bottom}`.
- ///
- /// ```example
- /// #set math.equation(numbering: "(1)", number-align: bottom)
- ///
- /// We can calculate:
- /// $ E &= sqrt(m_0^2 + p^2) \
- /// &approx 125 "GeV" $
- /// ```
- #[default(SpecificAlignment::Both(OuterHAlignment::End, VAlignment::Horizon))]
- pub number_align: SpecificAlignment<OuterHAlignment, VAlignment>,
-
- /// A supplement for the equation.
- ///
- /// For references to equations, this is added before the referenced number.
- ///
- /// If a function is specified, it is passed the referenced equation and
- /// should return content.
- ///
- /// ```example
- /// #set math.equation(numbering: "(1)", supplement: [Eq.])
- ///
- /// We define:
- /// $ phi.alt := (1 + sqrt(5)) / 2 $ <ratio>
- ///
- /// With @ratio, we get:
- /// $ F_n = floor(1 / sqrt(5) phi.alt^n) $
- /// ```
- pub supplement: Smart<Option<Supplement>>,
-
- /// The contents of the equation.
- #[required]
- pub body: Content,
-
- /// The size of the glyphs.
- #[internal]
- #[default(MathSize::Text)]
- #[ghost]
- pub size: MathSize,
-
- /// The style variant to select.
- #[internal]
- #[ghost]
- pub variant: MathVariant,
-
- /// Affects the height of exponents.
- #[internal]
- #[default(false)]
- #[ghost]
- pub cramped: bool,
-
- /// Whether to use bold glyphs.
- #[internal]
- #[default(false)]
- #[ghost]
- pub bold: bool,
-
- /// Whether to use italic glyphs.
- #[internal]
- #[ghost]
- pub italic: Smart<bool>,
-
- /// A forced class to use for all fragment.
- #[internal]
- #[ghost]
- pub class: Option<MathClass>,
-}
-
-impl Synthesize for Packed<EquationElem> {
- fn synthesize(
- &mut self,
- engine: &mut Engine,
- styles: StyleChain,
- ) -> SourceResult<()> {
- let supplement = match self.as_ref().supplement(styles) {
- Smart::Auto => TextElem::packed(Self::local_name_in(styles)),
- Smart::Custom(None) => Content::empty(),
- Smart::Custom(Some(supplement)) => {
- supplement.resolve(engine, styles, [self.clone().pack()])?
- }
- };
-
- self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement))));
- Ok(())
- }
-}
-
-impl Show for Packed<EquationElem> {
- fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- if self.block(styles) {
- Ok(BlockElem::multi_layouter(self.clone(), layout_equation_block)
- .pack()
- .spanned(self.span()))
- } else {
- Ok(InlineElem::layouter(self.clone(), layout_equation_inline)
- .pack()
- .spanned(self.span()))
- }
- }
-}
-
-impl ShowSet for Packed<EquationElem> {
- fn show_set(&self, styles: StyleChain) -> Styles {
- let mut out = Styles::new();
- if self.block(styles) {
- out.set(AlignElem::set_alignment(Alignment::CENTER));
- out.set(BlockElem::set_breakable(false));
- out.set(ParLine::set_numbering(None));
- out.set(EquationElem::set_size(MathSize::Display));
- } else {
- out.set(EquationElem::set_size(MathSize::Text));
- }
- out.set(TextElem::set_weight(FontWeight::from_number(450)));
- out.set(TextElem::set_font(FontList(vec![FontFamily::new(
- "New Computer Modern Math",
- )])));
- out
- }
-}
-
-impl Count for Packed<EquationElem> {
- fn update(&self) -> Option<CounterUpdate> {
- (self.block(StyleChain::default()) && self.numbering().is_some())
- .then(|| CounterUpdate::Step(NonZeroUsize::ONE))
- }
-}
-
-impl LocalName for Packed<EquationElem> {
- const KEY: &'static str = "equation";
-}
-
-impl Refable for Packed<EquationElem> {
- fn supplement(&self) -> Content {
- // After synthesis, this should always be custom content.
- match (**self).supplement(StyleChain::default()) {
- Smart::Custom(Some(Supplement::Content(content))) => content,
- _ => Content::empty(),
- }
- }
-
- fn counter(&self) -> Counter {
- Counter::of(EquationElem::elem())
- }
-
- fn numbering(&self) -> Option<&Numbering> {
- (**self).numbering(StyleChain::default()).as_ref()
- }
-}
-
-impl Outlinable for Packed<EquationElem> {
- fn outline(
- &self,
- engine: &mut Engine,
- styles: StyleChain,
- ) -> SourceResult<Option<Content>> {
- if !self.block(StyleChain::default()) {
- return Ok(None);
- }
- let Some(numbering) = self.numbering() else {
- return Ok(None);
- };
-
- // After synthesis, this should always be custom content.
- let mut supplement = match (**self).supplement(StyleChain::default()) {
- Smart::Custom(Some(Supplement::Content(content))) => content,
- _ => Content::empty(),
- };
-
- if !supplement.is_empty() {
- supplement += TextElem::packed("\u{a0}");
- }
-
- let numbers = self.counter().display_at_loc(
- engine,
- self.location().unwrap(),
- styles,
- numbering,
- )?;
-
- Ok(Some(supplement + numbers))
- }
-}
-
-/// Layout an inline equation (in a paragraph).
-#[typst_macros::time(span = elem.span())]
-fn layout_equation_inline(
- elem: &Packed<EquationElem>,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- region: Size,
-) -> SourceResult<Vec<InlineItem>> {
- assert!(!elem.block(styles));
-
- let font = find_math_font(engine, styles, elem.span())?;
-
- let mut locator = locator.split();
- let mut ctx = MathContext::new(engine, &mut locator, styles, region, &font);
- let run = ctx.layout_into_run(&elem.body, styles)?;
-
- let mut items = if run.row_count() == 1 {
- run.into_par_items()
- } else {
- vec![InlineItem::Frame(run.into_fragment(&ctx, styles).into_frame())]
- };
-
- // An empty equation should have a height, so we still create a frame
- // (which is then resized in the loop).
- if items.is_empty() {
- items.push(InlineItem::Frame(Frame::soft(Size::zero())));
- }
-
- for item in &mut items {
- let InlineItem::Frame(frame) = item else { continue };
-
- let font_size = scaled_font_size(&ctx, styles);
- let slack = ParElem::leading_in(styles) * 0.7;
-
- let (t, b) = font.edges(
- TextElem::top_edge_in(styles),
- TextElem::bottom_edge_in(styles),
- font_size,
- TextEdgeBounds::Frame(frame),
- );
-
- let ascent = t.max(frame.ascent() - slack);
- let descent = b.max(frame.descent() - slack);
- frame.translate(Point::with_y(ascent - frame.baseline()));
- frame.size_mut().y = ascent + descent;
- }
-
- Ok(items)
-}
-
-/// Layout a block-level equation (in a flow).
-#[typst_macros::time(span = elem.span())]
-fn layout_equation_block(
- elem: &Packed<EquationElem>,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- regions: Regions,
-) -> SourceResult<Fragment> {
- assert!(elem.block(styles));
-
- let span = elem.span();
- let font = find_math_font(engine, styles, span)?;
-
- let mut locator = locator.split();
- let mut ctx = MathContext::new(engine, &mut locator, styles, regions.base(), &font);
- let full_equation_builder = ctx
- .layout_into_run(&elem.body, styles)?
- .multiline_frame_builder(&ctx, styles);
- let width = full_equation_builder.size.x;
-
- let equation_builders = if BlockElem::breakable_in(styles) {
- let mut rows = full_equation_builder.frames.into_iter().peekable();
- let mut equation_builders = vec![];
- let mut last_first_pos = Point::zero();
- let mut regions = regions;
-
- loop {
- // Keep track of the position of the first row in this region,
- // so that the offset can be reverted later.
- let Some(&(_, first_pos)) = rows.peek() else { break };
- last_first_pos = first_pos;
-
- let mut frames = vec![];
- let mut height = Abs::zero();
- while let Some((sub, pos)) = rows.peek() {
- let mut pos = *pos;
- pos.y -= first_pos.y;
-
- // Finish this region if the line doesn't fit. Only do it if
- // we placed at least one line _or_ we still have non-last
- // regions. Crucially, we don't want to infinitely create
- // new regions which are too small.
- if !regions.size.y.fits(sub.height() + pos.y)
- && (regions.may_progress()
- || (regions.may_break() && !frames.is_empty()))
- {
- break;
- }
-
- let (sub, _) = rows.next().unwrap();
- height = height.max(pos.y + sub.height());
- frames.push((sub, pos));
- }
-
- equation_builders
- .push(MathRunFrameBuilder { frames, size: Size::new(width, height) });
- regions.next();
- }
-
- // Append remaining rows to the equation builder of the last region.
- if let Some(equation_builder) = equation_builders.last_mut() {
- equation_builder.frames.extend(rows.map(|(frame, mut pos)| {
- pos.y -= last_first_pos.y;
- (frame, pos)
- }));
-
- let height = equation_builder
- .frames
- .iter()
- .map(|(frame, pos)| frame.height() + pos.y)
- .max()
- .unwrap_or(equation_builder.size.y);
-
- equation_builder.size.y = height;
- }
-
- // Ensure that there is at least one frame, even for empty equations.
- if equation_builders.is_empty() {
- equation_builders
- .push(MathRunFrameBuilder { frames: vec![], size: Size::zero() });
- }
-
- equation_builders
- } else {
- vec![full_equation_builder]
- };
-
- let Some(numbering) = (**elem).numbering(styles) else {
- let frames = equation_builders
- .into_iter()
- .map(MathRunFrameBuilder::build)
- .collect();
- return Ok(Fragment::frames(frames));
- };
-
- let pod = Region::new(regions.base(), Axes::splat(false));
- let counter = Counter::of(EquationElem::elem())
- .display_at_loc(engine, elem.location().unwrap(), styles, numbering)?
- .spanned(span);
- let number = layout_frame(engine, &counter, locator.next(&()), styles, pod)?;
-
- static NUMBER_GUTTER: Em = Em::new(0.5);
- let full_number_width = number.width() + NUMBER_GUTTER.resolve(styles);
-
- let number_align = match elem.number_align(styles) {
- SpecificAlignment::H(h) => SpecificAlignment::Both(h, VAlignment::Horizon),
- SpecificAlignment::V(v) => SpecificAlignment::Both(OuterHAlignment::End, v),
- SpecificAlignment::Both(h, v) => SpecificAlignment::Both(h, v),
- };
-
- // Add equation numbers to each equation region.
- let region_count = equation_builders.len();
- let frames = equation_builders
- .into_iter()
- .map(|builder| {
- if builder.frames.is_empty() && region_count > 1 {
- // Don't number empty regions, but do number empty equations.
- return builder.build();
- }
- add_equation_number(
- builder,
- number.clone(),
- number_align.resolve(styles),
- AlignElem::alignment_in(styles).resolve(styles).x,
- regions.size.x,
- full_number_width,
- )
- })
- .collect();
-
- Ok(Fragment::frames(frames))
-}
-
-fn find_math_font(
- engine: &mut Engine<'_>,
- styles: StyleChain,
- span: Span,
-) -> SourceResult<Font> {
- let variant = variant(styles);
- let world = engine.world;
- let Some(font) = families(styles).find_map(|family| {
- let id = world.book().select(family, variant)?;
- let font = world.font(id)?;
- let _ = font.ttf().tables().math?.constants?;
- Some(font)
- }) else {
- bail!(span, "current font does not support math");
- };
- Ok(font)
-}
-
-fn add_equation_number(
- equation_builder: MathRunFrameBuilder,
- number: Frame,
- number_align: Axes<FixedAlignment>,
- equation_align: FixedAlignment,
- region_size_x: Abs,
- full_number_width: Abs,
-) -> Frame {
- let first =
- equation_builder.frames.first().map_or(
- (equation_builder.size, Point::zero(), Abs::zero()),
- |(frame, pos)| (frame.size(), *pos, frame.baseline()),
- );
- let last =
- equation_builder.frames.last().map_or(
- (equation_builder.size, Point::zero(), Abs::zero()),
- |(frame, pos)| (frame.size(), *pos, frame.baseline()),
- );
- let line_count = equation_builder.frames.len();
- let mut equation = equation_builder.build();
-
- let width = if region_size_x.is_finite() {
- region_size_x
- } else {
- equation.width() + 2.0 * full_number_width
- };
-
- let is_multiline = line_count >= 2;
- let resizing_offset = resize_equation(
- &mut equation,
- &number,
- number_align,
- equation_align,
- width,
- is_multiline,
- [first, last],
- );
- equation.translate(Point::with_x(match (equation_align, number_align.x) {
- (FixedAlignment::Start, FixedAlignment::Start) => full_number_width,
- (FixedAlignment::End, FixedAlignment::End) => -full_number_width,
- _ => Abs::zero(),
- }));
-
- let x = match number_align.x {
- FixedAlignment::Start => Abs::zero(),
- FixedAlignment::End => equation.width() - number.width(),
- _ => unreachable!(),
- };
- let y = {
- let align_baselines = |(_, pos, baseline): (_, Point, Abs), number: &Frame| {
- resizing_offset.y + pos.y + baseline - number.baseline()
- };
- match number_align.y {
- FixedAlignment::Start => align_baselines(first, &number),
- FixedAlignment::Center if !is_multiline => align_baselines(first, &number),
- // In this case, the center lines (not baselines) of the number frame
- // and the equation frame shall be aligned.
- FixedAlignment::Center => (equation.height() - number.height()) / 2.0,
- FixedAlignment::End => align_baselines(last, &number),
- }
- };
-
- equation.push_frame(Point::new(x, y), number);
- equation
-}
-
-/// Resize the equation's frame accordingly so that it encompasses the number.
-fn resize_equation(
- equation: &mut Frame,
- number: &Frame,
- number_align: Axes<FixedAlignment>,
- equation_align: FixedAlignment,
- width: Abs,
- is_multiline: bool,
- [first, last]: [(Axes<Abs>, Point, Abs); 2],
-) -> Point {
- if matches!(number_align.y, FixedAlignment::Center if is_multiline) {
- // In this case, the center lines (not baselines) of the number frame
- // and the equation frame shall be aligned.
- return equation.resize(
- Size::new(width, equation.height().max(number.height())),
- Axes::<FixedAlignment>::new(equation_align, FixedAlignment::Center),
- );
- }
-
- let excess_above = Abs::zero().max({
- if !is_multiline || matches!(number_align.y, FixedAlignment::Start) {
- let (.., baseline) = first;
- number.baseline() - baseline
- } else {
- Abs::zero()
- }
- });
- let excess_below = Abs::zero().max({
- if !is_multiline || matches!(number_align.y, FixedAlignment::End) {
- let (size, .., baseline) = last;
- (number.height() - number.baseline()) - (size.y - baseline)
- } else {
- Abs::zero()
- }
- });
-
- // The vertical expansion is asymmetric on the top and bottom edges, so we
- // first align at the top then translate the content downward later.
- let resizing_offset = equation.resize(
- Size::new(width, equation.height() + excess_above + excess_below),
- Axes::<FixedAlignment>::new(equation_align, FixedAlignment::Start),
- );
- equation.translate(Point::with_y(excess_above));
- resizing_offset + Point::with_y(excess_above)
-}
diff --git a/crates/typst/src/math/lr.rs b/crates/typst/src/math/lr.rs
deleted file mode 100644
index ccaf2959..00000000
--- a/crates/typst/src/math/lr.rs
+++ /dev/null
@@ -1,272 +0,0 @@
-use unicode_math_class::MathClass;
-
-use crate::diag::SourceResult;
-use crate::foundations::{elem, func, Content, NativeElement, Packed, Smart, StyleChain};
-use crate::layout::{Abs, Axis, Em, Length, Rel};
-use crate::math::{
- stretch_fragment, EquationElem, LayoutMath, MathContext, MathFragment, Scaled,
-};
-use crate::text::TextElem;
-
-/// How much less high scaled delimiters can be than what they wrap.
-pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1);
-
-/// Scales delimiters.
-///
-/// While matched delimiters scale by default, this can be used to scale
-/// unmatched delimiters and to control the delimiter scaling more precisely.
-#[elem(title = "Left/Right", LayoutMath)]
-pub struct LrElem {
- /// The size of the brackets, relative to the height of the wrapped content.
- pub size: Smart<Rel<Length>>,
-
- /// The delimited content, including the delimiters.
- #[required]
- #[parse(
- let mut arguments = args.all::<Content>()?.into_iter();
- let mut body = arguments.next().unwrap_or_default();
- arguments.for_each(|arg| body += TextElem::packed(',') + arg);
- body
- )]
- pub body: Content,
-}
-
-impl LayoutMath for Packed<LrElem> {
- #[typst_macros::time(name = "math.lr", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- let mut body = self.body();
-
- // Extract from an EquationElem.
- if let Some(equation) = body.to_packed::<EquationElem>() {
- body = equation.body();
- }
-
- // Extract implicit LrElem.
- if let Some(lr) = body.to_packed::<LrElem>() {
- if lr.size(styles).is_auto() {
- body = lr.body();
- }
- }
-
- let mut fragments = ctx.layout_into_fragments(body, styles)?;
- let axis = scaled!(ctx, styles, axis_height);
- let max_extent = fragments
- .iter()
- .map(|fragment| (fragment.ascent() - axis).max(fragment.descent() + axis))
- .max()
- .unwrap_or_default();
-
- let relative_to = 2.0 * max_extent;
- let height = self.size(styles);
-
- // Scale up fragments at both ends.
- match fragments.as_mut_slice() {
- [one] => scale(ctx, styles, one, relative_to, height, None),
- [first, .., last] => {
- scale(ctx, styles, first, relative_to, height, Some(MathClass::Opening));
- scale(ctx, styles, last, relative_to, height, Some(MathClass::Closing));
- }
- _ => {}
- }
-
- // Handle MathFragment::Variant fragments that should be scaled up.
- for fragment in &mut fragments {
- if let MathFragment::Variant(ref mut variant) = fragment {
- if variant.mid_stretched == Some(false) {
- variant.mid_stretched = Some(true);
- scale(
- ctx,
- styles,
- fragment,
- relative_to,
- height,
- Some(MathClass::Large),
- );
- }
- }
- }
-
- // Remove weak SpacingFragment immediately after the opening or immediately
- // before the closing.
- let original_len = fragments.len();
- let mut index = 0;
- fragments.retain(|fragment| {
- index += 1;
- (index != 2 && index + 1 != original_len)
- || !matches!(fragment, MathFragment::Spacing(_, true))
- });
-
- ctx.extend(fragments);
-
- Ok(())
- }
-}
-
-/// Scales delimiters vertically to the nearest surrounding `{lr()}` group.
-///
-/// ```example
-/// $ { x mid(|) sum_(i=1)^n w_i|f_i (x)| < 1 } $
-/// ```
-#[elem(LayoutMath)]
-pub struct MidElem {
- /// The content to be scaled.
- #[required]
- pub body: Content,
-}
-
-impl LayoutMath for Packed<MidElem> {
- #[typst_macros::time(name = "math.mid", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- let mut fragments = ctx.layout_into_fragments(self.body(), styles)?;
-
- for fragment in &mut fragments {
- match fragment {
- MathFragment::Glyph(glyph) => {
- let mut new = glyph.clone().into_variant();
- new.mid_stretched = Some(false);
- new.class = MathClass::Fence;
- *fragment = MathFragment::Variant(new);
- }
- MathFragment::Variant(variant) => {
- variant.mid_stretched = Some(false);
- variant.class = MathClass::Fence;
- }
- _ => {}
- }
- }
-
- ctx.extend(fragments);
- Ok(())
- }
-}
-
-/// Scale a math fragment to a height.
-fn scale(
- ctx: &mut MathContext,
- styles: StyleChain,
- fragment: &mut MathFragment,
- relative_to: Abs,
- height: Smart<Rel<Length>>,
- apply: Option<MathClass>,
-) {
- if matches!(
- fragment.class(),
- MathClass::Opening | MathClass::Closing | MathClass::Fence
- ) {
- // This unwrap doesn't really matter. If it is None, then the fragment
- // won't be stretchable anyways.
- let short_fall = DELIM_SHORT_FALL.at(fragment.font_size().unwrap_or_default());
- stretch_fragment(
- ctx,
- styles,
- fragment,
- Some(Axis::Y),
- Some(relative_to),
- height,
- short_fall,
- );
-
- if let Some(class) = apply {
- fragment.set_class(class);
- }
- }
-}
-
-/// Floors an expression.
-///
-/// ```example
-/// $ floor(x/2) $
-/// ```
-#[func]
-pub fn floor(
- /// The size of the brackets, relative to the height of the wrapped content.
- #[named]
- size: Option<Smart<Rel<Length>>>,
- /// The expression to floor.
- body: Content,
-) -> Content {
- delimited(body, '⌊', '⌋', size)
-}
-
-/// Ceils an expression.
-///
-/// ```example
-/// $ ceil(x/2) $
-/// ```
-#[func]
-pub fn ceil(
- /// The size of the brackets, relative to the height of the wrapped content.
- #[named]
- size: Option<Smart<Rel<Length>>>,
- /// The expression to ceil.
- body: Content,
-) -> Content {
- delimited(body, '⌈', '⌉', size)
-}
-
-/// Rounds an expression.
-///
-/// ```example
-/// $ round(x/2) $
-/// ```
-#[func]
-pub fn round(
- /// The size of the brackets, relative to the height of the wrapped content.
- #[named]
- size: Option<Smart<Rel<Length>>>,
- /// The expression to round.
- body: Content,
-) -> Content {
- delimited(body, '⌊', '⌉', size)
-}
-
-/// Takes the absolute value of an expression.
-///
-/// ```example
-/// $ abs(x/2) $
-/// ```
-#[func]
-pub fn abs(
- /// The size of the brackets, relative to the height of the wrapped content.
- #[named]
- size: Option<Smart<Rel<Length>>>,
- /// The expression to take the absolute value of.
- body: Content,
-) -> Content {
- delimited(body, '|', '|', size)
-}
-
-/// Takes the norm of an expression.
-///
-/// ```example
-/// $ norm(x/2) $
-/// ```
-#[func]
-pub fn norm(
- /// The size of the brackets, relative to the height of the wrapped content.
- #[named]
- size: Option<Smart<Rel<Length>>>,
- /// The expression to take the norm of.
- body: Content,
-) -> Content {
- delimited(body, '‖', '‖', size)
-}
-
-fn delimited(
- body: Content,
- left: char,
- right: char,
- size: Option<Smart<Rel<Length>>>,
-) -> Content {
- let span = body.span();
- let mut elem = LrElem::new(Content::sequence([
- TextElem::packed(left),
- body,
- TextElem::packed(right),
- ]));
- // Push size only if size is provided
- if let Some(size) = size {
- elem.push_size(size);
- }
- elem.pack().spanned(span)
-}
diff --git a/crates/typst/src/math/spacing.rs b/crates/typst/src/math/spacing.rs
deleted file mode 100644
index 377a2248..00000000
--- a/crates/typst/src/math/spacing.rs
+++ /dev/null
@@ -1,66 +0,0 @@
-use unicode_math_class::MathClass;
-
-use crate::foundations::{NativeElement, Scope};
-use crate::layout::{Abs, Em, HElem};
-use crate::math::{MathFragment, MathSize};
-
-pub(super) const THIN: Em = Em::new(1.0 / 6.0);
-pub(super) const MEDIUM: Em = Em::new(2.0 / 9.0);
-pub(super) const THICK: Em = Em::new(5.0 / 18.0);
-pub(super) const QUAD: Em = Em::new(1.0);
-pub(super) const WIDE: Em = Em::new(2.0);
-
-/// Hook up all spacings.
-pub(super) fn define(math: &mut Scope) {
- math.define("thin", HElem::new(THIN.into()).pack());
- math.define("med", HElem::new(MEDIUM.into()).pack());
- math.define("thick", HElem::new(THICK.into()).pack());
- math.define("quad", HElem::new(QUAD.into()).pack());
- math.define("wide", HElem::new(WIDE.into()).pack());
-}
-
-/// Create the spacing between two fragments in a given style.
-pub(super) fn spacing(
- l: &MathFragment,
- space: Option<MathFragment>,
- r: &MathFragment,
-) -> Option<MathFragment> {
- use MathClass::*;
-
- let resolve = |v: Em, size_ref: &MathFragment| -> Option<MathFragment> {
- let width = size_ref.font_size().map_or(Abs::zero(), |size| v.at(size));
- Some(MathFragment::Spacing(width, false))
- };
- let script = |f: &MathFragment| f.math_size().is_some_and(|s| s <= MathSize::Script);
-
- match (l.class(), r.class()) {
- // No spacing before punctuation; thin spacing after punctuation, unless
- // in script size.
- (_, Punctuation) => None,
- (Punctuation, _) if !script(l) => resolve(THIN, l),
-
- // No spacing after opening delimiters and before closing delimiters.
- (Opening, _) | (_, Closing) => None,
-
- // Thick spacing around relations, unless followed by a another relation
- // or in script size.
- (Relation, Relation) => None,
- (Relation, _) if !script(l) => resolve(THICK, l),
- (_, Relation) if !script(r) => resolve(THICK, r),
-
- // Medium spacing around binary operators, unless in script size.
- (Binary, _) if !script(l) => resolve(MEDIUM, l),
- (_, Binary) if !script(r) => resolve(MEDIUM, r),
-
- // Thin spacing around large operators, unless to the left of
- // an opening delimiter. TeXBook, p170
- (Large, Opening | Fence) => None,
- (Large, _) => resolve(THIN, l),
- (_, Large) => resolve(THIN, r),
-
- // Spacing around spaced frames.
- _ if (l.is_spaced() || r.is_spaced()) => space,
-
- _ => None,
- }
-}
diff --git a/crates/typst/src/math/style.rs b/crates/typst/src/math/style.rs
deleted file mode 100644
index 67a198a7..00000000
--- a/crates/typst/src/math/style.rs
+++ /dev/null
@@ -1,518 +0,0 @@
-use crate::foundations::{func, Cast, Content, Smart, Style, StyleChain};
-use crate::layout::Abs;
-use crate::math::{EquationElem, MathContext};
-use crate::text::TextElem;
-use crate::utils::LazyHash;
-
-/// Bold font style in math.
-///
-/// ```example
-/// $ bold(A) := B^+ $
-/// ```
-#[func(keywords = ["mathbf"])]
-pub fn bold(
- /// The content to style.
- body: Content,
-) -> Content {
- body.styled(EquationElem::set_bold(true))
-}
-
-/// Upright (non-italic) font style in math.
-///
-/// ```example
-/// $ upright(A) != A $
-/// ```
-#[func(keywords = ["mathup"])]
-pub fn upright(
- /// The content to style.
- body: Content,
-) -> Content {
- body.styled(EquationElem::set_italic(Smart::Custom(false)))
-}
-
-/// Italic font style in math.
-///
-/// For roman letters and greek lowercase letters, this is already the default.
-#[func(keywords = ["mathit"])]
-pub fn italic(
- /// The content to style.
- body: Content,
-) -> Content {
- body.styled(EquationElem::set_italic(Smart::Custom(true)))
-}
-
-/// Serif (roman) font style in math.
-///
-/// This is already the default.
-#[func(keywords = ["mathrm"])]
-pub fn serif(
- /// The content to style.
- body: Content,
-) -> Content {
- body.styled(EquationElem::set_variant(MathVariant::Serif))
-}
-
-/// Sans-serif font style in math.
-///
-/// ```example
-/// $ sans(A B C) $
-/// ```
-#[func(title = "Sans Serif", keywords = ["mathsf"])]
-pub fn sans(
- /// The content to style.
- body: Content,
-) -> Content {
- body.styled(EquationElem::set_variant(MathVariant::Sans))
-}
-
-/// Calligraphic font style in math.
-///
-/// ```example
-/// Let $cal(P)$ be the set of ...
-/// ```
-///
-/// This corresponds both to LaTeX's `\mathcal` and `\mathscr` as both of these
-/// styles share the same Unicode codepoints. Switching between the styles is
-/// thus only possible if supported by the font via
-/// [font features]($text.features).
-///
-/// For the default math font, the roundhand style is available through the
-/// `ss01` feature. Therefore, you could define your own version of `\mathscr`
-/// like this:
-///
-/// ```example
-/// #let scr(it) = text(
-/// features: ("ss01",),
-/// box($cal(it)$),
-/// )
-///
-/// We establish $cal(P) != scr(P)$.
-/// ```
-///
-/// (The box is not conceptually necessary, but unfortunately currently needed
-/// due to limitations in Typst's text style handling in math.)
-#[func(title = "Calligraphic", keywords = ["mathcal", "mathscr"])]
-pub fn cal(
- /// The content to style.
- body: Content,
-) -> Content {
- body.styled(EquationElem::set_variant(MathVariant::Cal))
-}
-
-/// Fraktur font style in math.
-///
-/// ```example
-/// $ frak(P) $
-/// ```
-#[func(title = "Fraktur", keywords = ["mathfrak"])]
-pub fn frak(
- /// The content to style.
- body: Content,
-) -> Content {
- body.styled(EquationElem::set_variant(MathVariant::Frak))
-}
-
-/// Monospace font style in math.
-///
-/// ```example
-/// $ mono(x + y = z) $
-/// ```
-#[func(title = "Monospace", keywords = ["mathtt"])]
-pub fn mono(
- /// The content to style.
- body: Content,
-) -> Content {
- body.styled(EquationElem::set_variant(MathVariant::Mono))
-}
-
-/// Blackboard bold (double-struck) font style in math.
-///
-/// For uppercase latin letters, blackboard bold is additionally available
-/// through [symbols]($category/symbols/sym) of the form `NN` and `RR`.
-///
-/// ```example
-/// $ bb(b) $
-/// $ bb(N) = NN $
-/// $ f: NN -> RR $
-/// ```
-#[func(title = "Blackboard Bold", keywords = ["mathbb"])]
-pub fn bb(
- /// The content to style.
- body: Content,
-) -> Content {
- body.styled(EquationElem::set_variant(MathVariant::Bb))
-}
-
-/// Forced display style in math.
-///
-/// This is the normal size for block equations.
-///
-/// ```example
-/// $sum_i x_i/2 = display(sum_i x_i/2)$
-/// ```
-#[func(title = "Display Size", keywords = ["displaystyle"])]
-pub fn display(
- /// The content to size.
- body: Content,
- /// Whether to impose a height restriction for exponents, like regular sub-
- /// and superscripts do.
- #[named]
- #[default(false)]
- cramped: bool,
-) -> Content {
- body.styled(EquationElem::set_size(MathSize::Display))
- .styled(EquationElem::set_cramped(cramped))
-}
-
-/// Forced inline (text) style in math.
-///
-/// This is the normal size for inline equations.
-///
-/// ```example
-/// $ sum_i x_i/2
-/// = inline(sum_i x_i/2) $
-/// ```
-#[func(title = "Inline Size", keywords = ["textstyle"])]
-pub fn inline(
- /// The content to size.
- body: Content,
- /// Whether to impose a height restriction for exponents, like regular sub-
- /// and superscripts do.
- #[named]
- #[default(false)]
- cramped: bool,
-) -> Content {
- body.styled(EquationElem::set_size(MathSize::Text))
- .styled(EquationElem::set_cramped(cramped))
-}
-
-/// Forced script style in math.
-///
-/// This is the smaller size used in powers or sub- or superscripts.
-///
-/// ```example
-/// $sum_i x_i/2 = script(sum_i x_i/2)$
-/// ```
-#[func(title = "Script Size", keywords = ["scriptstyle"])]
-pub fn script(
- /// The content to size.
- body: Content,
- /// Whether to impose a height restriction for exponents, like regular sub-
- /// and superscripts do.
- #[named]
- #[default(true)]
- cramped: bool,
-) -> Content {
- body.styled(EquationElem::set_size(MathSize::Script))
- .styled(EquationElem::set_cramped(cramped))
-}
-
-/// Forced second script style in math.
-///
-/// This is the smallest size, used in second-level sub- and superscripts
-/// (script of the script).
-///
-/// ```example
-/// $sum_i x_i/2 = sscript(sum_i x_i/2)$
-/// ```
-#[func(title = "Script-Script Size", keywords = ["scriptscriptstyle"])]
-pub fn sscript(
- /// The content to size.
- body: Content,
- /// Whether to impose a height restriction for exponents, like regular sub-
- /// and superscripts do.
- #[named]
- #[default(true)]
- cramped: bool,
-) -> Content {
- body.styled(EquationElem::set_size(MathSize::ScriptScript))
- .styled(EquationElem::set_cramped(cramped))
-}
-
-/// The size of elements in an equation.
-///
-/// See the TeXbook p. 141.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Cast, Hash)]
-pub enum MathSize {
- /// Second-level sub- and superscripts.
- ScriptScript,
- /// Sub- and superscripts.
- Script,
- /// Math in text.
- Text,
- /// Math on its own line.
- Display,
-}
-
-impl MathSize {
- /// The scaling factor.
- pub fn factor(self, ctx: &MathContext) -> f64 {
- match self {
- Self::Display | Self::Text => 1.0,
- Self::Script => percent!(ctx, script_percent_scale_down),
- Self::ScriptScript => percent!(ctx, script_script_percent_scale_down),
- }
- }
-}
-
-/// A mathematical style variant, as defined by Unicode.
-#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Cast, Hash)]
-pub enum MathVariant {
- #[default]
- Serif,
- Sans,
- Cal,
- Frak,
- Mono,
- Bb,
-}
-
-/// Get the font size scaled with the `MathSize`.
-pub fn scaled_font_size(ctx: &MathContext, styles: StyleChain) -> Abs {
- EquationElem::size_in(styles).factor(ctx) * TextElem::size_in(styles)
-}
-
-/// Styles something as cramped.
-pub fn style_cramped() -> LazyHash<Style> {
- EquationElem::set_cramped(true).wrap()
-}
-
-/// The style for subscripts in the current style.
-pub fn style_for_subscript(styles: StyleChain) -> [LazyHash<Style>; 2] {
- [style_for_superscript(styles), EquationElem::set_cramped(true).wrap()]
-}
-
-/// The style for superscripts in the current style.
-pub fn style_for_superscript(styles: StyleChain) -> LazyHash<Style> {
- EquationElem::set_size(match EquationElem::size_in(styles) {
- MathSize::Display | MathSize::Text => MathSize::Script,
- MathSize::Script | MathSize::ScriptScript => MathSize::ScriptScript,
- })
- .wrap()
-}
-
-/// The style for numerators in the current style.
-pub fn style_for_numerator(styles: StyleChain) -> LazyHash<Style> {
- EquationElem::set_size(match EquationElem::size_in(styles) {
- MathSize::Display => MathSize::Text,
- MathSize::Text => MathSize::Script,
- MathSize::Script | MathSize::ScriptScript => MathSize::ScriptScript,
- })
- .wrap()
-}
-
-/// The style for denominators in the current style.
-pub fn style_for_denominator(styles: StyleChain) -> [LazyHash<Style>; 2] {
- [style_for_numerator(styles), EquationElem::set_cramped(true).wrap()]
-}
-
-/// Select the correct styled math letter.
-///
-/// <https://www.w3.org/TR/mathml-core/#new-text-transform-mappings>
-/// <https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols>
-pub fn styled_char(styles: StyleChain, c: char, auto_italic: bool) -> char {
- use MathVariant::*;
-
- let variant = EquationElem::variant_in(styles);
- let bold = EquationElem::bold_in(styles);
- let italic = EquationElem::italic_in(styles).unwrap_or(
- auto_italic
- && matches!(
- c,
- 'a'..='z' | 'ı' | 'ȷ' | 'A'..='Z' | 'α'..='ω' |
- '∂' | 'ϵ' | 'ϑ' | 'ϰ' | 'ϕ' | 'ϱ' | 'ϖ'
- )
- && matches!(variant, Sans | Serif),
- );
-
- if let Some(c) = basic_exception(c) {
- return c;
- }
-
- if let Some(c) = latin_exception(c, variant, bold, italic) {
- return c;
- }
-
- if let Some(c) = greek_exception(c, variant, bold, italic) {
- return c;
- }
-
- let base = match c {
- 'A'..='Z' => 'A',
- 'a'..='z' => 'a',
- 'Α'..='Ω' => 'Α',
- 'α'..='ω' => 'α',
- '0'..='9' => '0',
- // Hebrew Alef -> Dalet.
- '\u{05D0}'..='\u{05D3}' => '\u{05D0}',
- _ => return c,
- };
-
- let tuple = (variant, bold, italic);
- let start = match c {
- // Latin upper.
- 'A'..='Z' => match tuple {
- (Serif, false, false) => 0x0041,
- (Serif, true, false) => 0x1D400,
- (Serif, false, true) => 0x1D434,
- (Serif, true, true) => 0x1D468,
- (Sans, false, false) => 0x1D5A0,
- (Sans, true, false) => 0x1D5D4,
- (Sans, false, true) => 0x1D608,
- (Sans, true, true) => 0x1D63C,
- (Cal, false, _) => 0x1D49C,
- (Cal, true, _) => 0x1D4D0,
- (Frak, false, _) => 0x1D504,
- (Frak, true, _) => 0x1D56C,
- (Mono, _, _) => 0x1D670,
- (Bb, _, _) => 0x1D538,
- },
-
- // Latin lower.
- 'a'..='z' => match tuple {
- (Serif, false, false) => 0x0061,
- (Serif, true, false) => 0x1D41A,
- (Serif, false, true) => 0x1D44E,
- (Serif, true, true) => 0x1D482,
- (Sans, false, false) => 0x1D5BA,
- (Sans, true, false) => 0x1D5EE,
- (Sans, false, true) => 0x1D622,
- (Sans, true, true) => 0x1D656,
- (Cal, false, _) => 0x1D4B6,
- (Cal, true, _) => 0x1D4EA,
- (Frak, false, _) => 0x1D51E,
- (Frak, true, _) => 0x1D586,
- (Mono, _, _) => 0x1D68A,
- (Bb, _, _) => 0x1D552,
- },
-
- // Greek upper.
- 'Α'..='Ω' => match tuple {
- (Serif, false, false) => 0x0391,
- (Serif, true, false) => 0x1D6A8,
- (Serif, false, true) => 0x1D6E2,
- (Serif, true, true) => 0x1D71C,
- (Sans, _, false) => 0x1D756,
- (Sans, _, true) => 0x1D790,
- (Cal | Frak | Mono | Bb, _, _) => return c,
- },
-
- // Greek lower.
- 'α'..='ω' => match tuple {
- (Serif, false, false) => 0x03B1,
- (Serif, true, false) => 0x1D6C2,
- (Serif, false, true) => 0x1D6FC,
- (Serif, true, true) => 0x1D736,
- (Sans, _, false) => 0x1D770,
- (Sans, _, true) => 0x1D7AA,
- (Cal | Frak | Mono | Bb, _, _) => return c,
- },
-
- // Hebrew Alef -> Dalet.
- '\u{05D0}'..='\u{05D3}' => 0x2135,
-
- // Numbers.
- '0'..='9' => match tuple {
- (Serif, false, _) => 0x0030,
- (Serif, true, _) => 0x1D7CE,
- (Bb, _, _) => 0x1D7D8,
- (Sans, false, _) => 0x1D7E2,
- (Sans, true, _) => 0x1D7EC,
- (Mono, _, _) => 0x1D7F6,
- (Cal | Frak, _, _) => return c,
- },
-
- _ => unreachable!(),
- };
-
- std::char::from_u32(start + (c as u32 - base as u32)).unwrap()
-}
-
-fn basic_exception(c: char) -> Option<char> {
- Some(match c {
- '〈' => '⟨',
- '〉' => '⟩',
- '《' => '⟪',
- '》' => '⟫',
- _ => return None,
- })
-}
-
-fn latin_exception(
- c: char,
- variant: MathVariant,
- bold: bool,
- italic: bool,
-) -> Option<char> {
- use MathVariant::*;
- Some(match (c, variant, bold, italic) {
- ('B', Cal, false, _) => 'ℬ',
- ('E', Cal, false, _) => 'ℰ',
- ('F', Cal, false, _) => 'ℱ',
- ('H', Cal, false, _) => 'ℋ',
- ('I', Cal, false, _) => 'ℐ',
- ('L', Cal, false, _) => 'ℒ',
- ('M', Cal, false, _) => 'ℳ',
- ('R', Cal, false, _) => 'ℛ',
- ('C', Frak, false, _) => 'ℭ',
- ('H', Frak, false, _) => 'ℌ',
- ('I', Frak, false, _) => 'ℑ',
- ('R', Frak, false, _) => 'ℜ',
- ('Z', Frak, false, _) => 'ℨ',
- ('C', Bb, ..) => 'ℂ',
- ('H', Bb, ..) => 'ℍ',
- ('N', Bb, ..) => 'ℕ',
- ('P', Bb, ..) => 'ℙ',
- ('Q', Bb, ..) => 'ℚ',
- ('R', Bb, ..) => 'ℝ',
- ('Z', Bb, ..) => 'ℤ',
- ('D', Bb, _, true) => 'ⅅ',
- ('d', Bb, _, true) => 'ⅆ',
- ('e', Bb, _, true) => 'ⅇ',
- ('i', Bb, _, true) => 'ⅈ',
- ('j', Bb, _, true) => 'ⅉ',
- ('h', Serif, false, true) => 'ℎ',
- ('e', Cal, false, _) => 'ℯ',
- ('g', Cal, false, _) => 'ℊ',
- ('o', Cal, false, _) => 'ℴ',
- ('ı', Serif, .., true) => '𝚤',
- ('ȷ', Serif, .., true) => '𝚥',
- _ => return None,
- })
-}
-
-fn greek_exception(
- c: char,
- variant: MathVariant,
- bold: bool,
- italic: bool,
-) -> Option<char> {
- use MathVariant::*;
- let list = match c {
- 'ϴ' => ['𝚹', '𝛳', '𝜭', '𝝧', '𝞡', 'ϴ'],
- '∇' => ['𝛁', '𝛻', '𝜵', '𝝯', '𝞩', '∇'],
- '∂' => ['𝛛', '𝜕', '𝝏', '𝞉', '𝟃', '∂'],
- 'ϵ' => ['𝛜', '𝜖', '𝝐', '𝞊', '𝟄', 'ϵ'],
- 'ϑ' => ['𝛝', '𝜗', '𝝑', '𝞋', '𝟅', 'ϑ'],
- 'ϰ' => ['𝛞', '𝜘', '𝝒', '𝞌', '𝟆', 'ϰ'],
- 'ϕ' => ['𝛟', '𝜙', '𝝓', '𝞍', '𝟇', 'ϕ'],
- 'ϱ' => ['𝛠', '𝜚', '𝝔', '𝞎', '𝟈', 'ϱ'],
- 'ϖ' => ['𝛡', '𝜛', '𝝕', '𝞏', '𝟉', 'ϖ'],
- 'Γ' => ['𝚪', '𝛤', '𝜞', '𝝘', '𝞒', 'ℾ'],
- 'γ' => ['𝛄', '𝛾', '𝜸', '𝝲', '𝞬', 'ℽ'],
- 'Π' => ['𝚷', '𝛱', '𝜫', '𝝥', '𝞟', 'ℿ'],
- 'π' => ['𝛑', '𝜋', '𝝅', '𝝿', '𝞹', 'ℼ'],
- '∑' => ['∑', '∑', '∑', '∑', '∑', '⅀'],
- _ => return None,
- };
-
- Some(match (variant, bold, italic) {
- (Serif, true, false) => list[0],
- (Serif, false, true) => list[1],
- (Serif, true, true) => list[2],
- (Sans, _, false) => list[3],
- (Sans, _, true) => list[4],
- (Bb, ..) => list[5],
- _ => return None,
- })
-}
diff --git a/crates/typst/src/math/underover.rs b/crates/typst/src/math/underover.rs
deleted file mode 100644
index 5ad7f457..00000000
--- a/crates/typst/src/math/underover.rs
+++ /dev/null
@@ -1,496 +0,0 @@
-use crate::diag::SourceResult;
-use crate::foundations::{elem, Content, Packed, StyleChain};
-use crate::layout::{Abs, Em, FixedAlignment, Frame, FrameItem, Point, Size};
-use crate::math::{
- alignments, scaled_font_size, style_cramped, style_for_subscript,
- style_for_superscript, AlignmentResult, FrameFragment, GlyphFragment, LayoutMath,
- LeftRightAlternator, MathContext, MathRun, Scaled,
-};
-use crate::syntax::Span;
-use crate::text::TextElem;
-use crate::visualize::{FixedStroke, Geometry};
-
-const BRACE_GAP: Em = Em::new(0.25);
-const BRACKET_GAP: Em = Em::new(0.25);
-const PAREN_GAP: Em = Em::new(0.25);
-const SHELL_GAP: Em = Em::new(0.25);
-
-/// A marker to distinguish under- vs. overlines.
-enum Position {
- Under,
- Over,
-}
-
-/// A horizontal line under content.
-///
-/// ```example
-/// $ underline(1 + 2 + ... + 5) $
-/// ```
-#[elem(LayoutMath)]
-pub struct UnderlineElem {
- /// The content above the line.
- #[required]
- pub body: Content,
-}
-
-impl LayoutMath for Packed<UnderlineElem> {
- #[typst_macros::time(name = "math.underline", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- layout_underoverline(ctx, styles, self.body(), self.span(), Position::Under)
- }
-}
-
-/// A horizontal line over content.
-///
-/// ```example
-/// $ overline(1 + 2 + ... + 5) $
-/// ```
-#[elem(LayoutMath)]
-pub struct OverlineElem {
- /// The content below the line.
- #[required]
- pub body: Content,
-}
-
-impl LayoutMath for Packed<OverlineElem> {
- #[typst_macros::time(name = "math.overline", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- layout_underoverline(ctx, styles, self.body(), self.span(), Position::Over)
- }
-}
-
-/// layout under- or overlined content
-fn layout_underoverline(
- ctx: &mut MathContext,
- styles: StyleChain,
- body: &Content,
- span: Span,
- position: Position,
-) -> SourceResult<()> {
- let (extra_height, content, line_pos, content_pos, baseline, bar_height, line_adjust);
- match position {
- Position::Under => {
- let sep = scaled!(ctx, styles, underbar_extra_descender);
- bar_height = scaled!(ctx, styles, underbar_rule_thickness);
- let gap = scaled!(ctx, styles, underbar_vertical_gap);
- extra_height = sep + bar_height + gap;
-
- content = ctx.layout_into_fragment(body, styles)?;
-
- line_pos = Point::with_y(content.height() + gap + bar_height / 2.0);
- content_pos = Point::zero();
- baseline = content.ascent();
- line_adjust = -content.italics_correction();
- }
- Position::Over => {
- let sep = scaled!(ctx, styles, overbar_extra_ascender);
- bar_height = scaled!(ctx, styles, overbar_rule_thickness);
- let gap = scaled!(ctx, styles, overbar_vertical_gap);
- extra_height = sep + bar_height + gap;
-
- let cramped = style_cramped();
- content = ctx.layout_into_fragment(body, styles.chain(&cramped))?;
-
- line_pos = Point::with_y(sep + bar_height / 2.0);
- content_pos = Point::with_y(extra_height);
- baseline = content.ascent() + extra_height;
- line_adjust = Abs::zero();
- }
- }
-
- let width = content.width();
- let height = content.height() + extra_height;
- let size = Size::new(width, height);
- let line_width = width + line_adjust;
-
- let content_class = content.class();
- let content_is_text_like = content.is_text_like();
- let content_italics_correction = content.italics_correction();
- let mut frame = Frame::soft(size);
- frame.set_baseline(baseline);
- frame.push_frame(content_pos, content.into_frame());
- frame.push(
- line_pos,
- FrameItem::Shape(
- Geometry::Line(Point::with_x(line_width)).stroked(FixedStroke {
- paint: TextElem::fill_in(styles).as_decoration(),
- thickness: bar_height,
- ..FixedStroke::default()
- }),
- span,
- ),
- );
-
- ctx.push(
- FrameFragment::new(ctx, styles, frame)
- .with_class(content_class)
- .with_text_like(content_is_text_like)
- .with_italics_correction(content_italics_correction),
- );
-
- Ok(())
-}
-
-/// A horizontal brace under content, with an optional annotation below.
-///
-/// ```example
-/// $ underbrace(1 + 2 + ... + 5, "numbers") $
-/// ```
-#[elem(LayoutMath)]
-pub struct UnderbraceElem {
- /// The content above the brace.
- #[required]
- pub body: Content,
-
- /// The optional content below the brace.
- #[positional]
- pub annotation: Option<Content>,
-}
-
-impl LayoutMath for Packed<UnderbraceElem> {
- #[typst_macros::time(name = "math.underbrace", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- layout_underoverspreader(
- ctx,
- styles,
- self.body(),
- &self.annotation(styles),
- '⏟',
- BRACE_GAP,
- Position::Under,
- self.span(),
- )
- }
-}
-
-/// A horizontal brace over content, with an optional annotation above.
-///
-/// ```example
-/// $ overbrace(1 + 2 + ... + 5, "numbers") $
-/// ```
-#[elem(LayoutMath)]
-pub struct OverbraceElem {
- /// The content below the brace.
- #[required]
- pub body: Content,
-
- /// The optional content above the brace.
- #[positional]
- pub annotation: Option<Content>,
-}
-
-impl LayoutMath for Packed<OverbraceElem> {
- #[typst_macros::time(name = "math.overbrace", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- layout_underoverspreader(
- ctx,
- styles,
- self.body(),
- &self.annotation(styles),
- '⏞',
- BRACE_GAP,
- Position::Over,
- self.span(),
- )
- }
-}
-
-/// A horizontal bracket under content, with an optional annotation below.
-///
-/// ```example
-/// $ underbracket(1 + 2 + ... + 5, "numbers") $
-/// ```
-#[elem(LayoutMath)]
-pub struct UnderbracketElem {
- /// The content above the bracket.
- #[required]
- pub body: Content,
-
- /// The optional content below the bracket.
- #[positional]
- pub annotation: Option<Content>,
-}
-
-impl LayoutMath for Packed<UnderbracketElem> {
- #[typst_macros::time(name = "math.underbracket", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- layout_underoverspreader(
- ctx,
- styles,
- self.body(),
- &self.annotation(styles),
- '⎵',
- BRACKET_GAP,
- Position::Under,
- self.span(),
- )
- }
-}
-
-/// A horizontal bracket over content, with an optional annotation above.
-///
-/// ```example
-/// $ overbracket(1 + 2 + ... + 5, "numbers") $
-/// ```
-#[elem(LayoutMath)]
-pub struct OverbracketElem {
- /// The content below the bracket.
- #[required]
- pub body: Content,
-
- /// The optional content above the bracket.
- #[positional]
- pub annotation: Option<Content>,
-}
-
-impl LayoutMath for Packed<OverbracketElem> {
- #[typst_macros::time(name = "math.overbracket", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- layout_underoverspreader(
- ctx,
- styles,
- self.body(),
- &self.annotation(styles),
- '⎴',
- BRACKET_GAP,
- Position::Over,
- self.span(),
- )
- }
-}
-
-/// A horizontal parenthesis under content, with an optional annotation below.
-///
-/// ```example
-/// $ underparen(1 + 2 + ... + 5, "numbers") $
-/// ```
-#[elem(LayoutMath)]
-pub struct UnderparenElem {
- /// The content above the parenthesis.
- #[required]
- pub body: Content,
-
- /// The optional content below the parenthesis.
- #[positional]
- pub annotation: Option<Content>,
-}
-
-impl LayoutMath for Packed<UnderparenElem> {
- #[typst_macros::time(name = "math.underparen", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- layout_underoverspreader(
- ctx,
- styles,
- self.body(),
- &self.annotation(styles),
- '⏝',
- PAREN_GAP,
- Position::Under,
- self.span(),
- )
- }
-}
-
-/// A horizontal parenthesis over content, with an optional annotation above.
-///
-/// ```example
-/// $ overparen(1 + 2 + ... + 5, "numbers") $
-/// ```
-#[elem(LayoutMath)]
-pub struct OverparenElem {
- /// The content below the parenthesis.
- #[required]
- pub body: Content,
-
- /// The optional content above the parenthesis.
- #[positional]
- pub annotation: Option<Content>,
-}
-
-impl LayoutMath for Packed<OverparenElem> {
- #[typst_macros::time(name = "math.overparen", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- layout_underoverspreader(
- ctx,
- styles,
- self.body(),
- &self.annotation(styles),
- '⏜',
- PAREN_GAP,
- Position::Over,
- self.span(),
- )
- }
-}
-
-/// A horizontal tortoise shell bracket under content, with an optional annotation below.
-///
-/// ```example
-/// $ undershell(1 + 2 + ... + 5, "numbers") $
-/// ```
-#[elem(LayoutMath)]
-pub struct UndershellElem {
- /// The content above the tortoise shell bracket.
- #[required]
- pub body: Content,
-
- /// The optional content below the tortoise shell bracket.
- #[positional]
- pub annotation: Option<Content>,
-}
-
-impl LayoutMath for Packed<UndershellElem> {
- #[typst_macros::time(name = "math.undershell", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- layout_underoverspreader(
- ctx,
- styles,
- self.body(),
- &self.annotation(styles),
- '⏡',
- SHELL_GAP,
- Position::Under,
- self.span(),
- )
- }
-}
-
-/// A horizontal tortoise shell bracket over content, with an optional annotation above.
-///
-/// ```example
-/// $ overshell(1 + 2 + ... + 5, "numbers") $
-/// ```
-#[elem(LayoutMath)]
-pub struct OvershellElem {
- /// The content below the tortoise shell bracket.
- #[required]
- pub body: Content,
-
- /// The optional content above the tortoise shell bracket.
- #[positional]
- pub annotation: Option<Content>,
-}
-
-impl LayoutMath for Packed<OvershellElem> {
- #[typst_macros::time(name = "math.overshell", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
- layout_underoverspreader(
- ctx,
- styles,
- self.body(),
- &self.annotation(styles),
- '⏠',
- SHELL_GAP,
- Position::Over,
- self.span(),
- )
- }
-}
-
-/// Layout an over- or underbrace-like object.
-#[allow(clippy::too_many_arguments)]
-fn layout_underoverspreader(
- ctx: &mut MathContext,
- styles: StyleChain,
- body: &Content,
- annotation: &Option<Content>,
- c: char,
- gap: Em,
- position: Position,
- span: Span,
-) -> SourceResult<()> {
- let font_size = scaled_font_size(ctx, styles);
- let gap = gap.at(font_size);
- let body = ctx.layout_into_run(body, styles)?;
- let body_class = body.class();
- let body = body.into_fragment(ctx, styles);
- let glyph = GlyphFragment::new(ctx, styles, c, span);
- let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero());
-
- let mut rows = vec![];
- let baseline = match position {
- Position::Under => {
- rows.push(MathRun::new(vec![body]));
- rows.push(stretched.into());
- if let Some(annotation) = annotation {
- let under_style = style_for_subscript(styles);
- let annotation_styles = styles.chain(&under_style);
- rows.push(ctx.layout_into_run(annotation, annotation_styles)?);
- }
- 0
- }
- Position::Over => {
- if let Some(annotation) = annotation {
- let over_style = style_for_superscript(styles);
- let annotation_styles = styles.chain(&over_style);
- rows.push(ctx.layout_into_run(annotation, annotation_styles)?);
- }
- rows.push(stretched.into());
- rows.push(MathRun::new(vec![body]));
- rows.len() - 1
- }
- };
-
- let frame = stack(
- rows,
- FixedAlignment::Center,
- gap,
- baseline,
- LeftRightAlternator::Right,
- None,
- );
- ctx.push(FrameFragment::new(ctx, styles, frame).with_class(body_class));
-
- Ok(())
-}
-
-/// Stack rows on top of each other.
-///
-/// Add a `gap` between each row and uses the baseline of the `baseline`-th
-/// row for the whole frame. `alternator` controls the left/right alternating
-/// alignment behavior of `AlignPointElem` in the rows.
-pub(super) fn stack(
- rows: Vec<MathRun>,
- align: FixedAlignment,
- gap: Abs,
- baseline: usize,
- alternator: LeftRightAlternator,
- minimum_ascent_descent: Option<(Abs, Abs)>,
-) -> Frame {
- let rows: Vec<_> = rows.into_iter().flat_map(|r| r.rows()).collect();
- let AlignmentResult { points, width } = alignments(&rows);
- let rows: Vec<_> = rows
- .into_iter()
- .map(|row| row.into_line_frame(&points, alternator))
- .collect();
-
- let padded_height = |height: Abs| {
- height.max(minimum_ascent_descent.map_or(Abs::zero(), |(a, d)| a + d))
- };
-
- let mut frame = Frame::soft(Size::new(
- width,
- rows.iter().map(|row| padded_height(row.height())).sum::<Abs>()
- + rows.len().saturating_sub(1) as f64 * gap,
- ));
-
- let mut y = Abs::zero();
- for (i, row) in rows.into_iter().enumerate() {
- let x = if points.is_empty() {
- align.position(width - row.width())
- } else {
- Abs::zero()
- };
- let ascent_padded_part = minimum_ascent_descent
- .map_or(Abs::zero(), |(a, _)| (a - row.ascent()))
- .max(Abs::zero());
- let pos = Point::new(x, y + ascent_padded_part);
- if i == baseline {
- frame.set_baseline(y + row.baseline() + ascent_padded_part);
- }
- y += padded_height(row.height()) + gap;
- frame.push_frame(pos, row);
- }
-
- frame
-}