diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dfa836d18..33c5343c5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,8 +37,8 @@ Below are some signs of a good PR: - Adds/changes as little code and as few interfaces as possible. Should changes to larger-scale abstractions be necessary, these should be discussed throughout the implementation process. -- Adds tests if appropriate (with reference images for visual tests). See the - [testing] readme for more details. +- Adds tests if appropriate (with reference output for visual/HTML tests). See + the [testing] readme for more details. - Contains documentation comments on all new Rust types. - Comes with brief documentation for all new Typst definitions (elements/functions), ideally with a concise example that fits into ~5-10 diff --git a/Cargo.lock b/Cargo.lock index be5117da2..8aa7c0ec1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3076,6 +3076,7 @@ dependencies = [ "typst", "typst-assets", "typst-dev-assets", + "typst-html", "typst-library", "typst-pdf", "typst-render", @@ -3092,6 +3093,7 @@ dependencies = [ "parking_lot", "serde", "serde_json", + "web-sys", ] [[package]] @@ -3417,6 +3419,16 @@ dependencies = [ "indexmap-nostd", ] +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "weezl" version = "0.1.8" diff --git a/Cargo.toml b/Cargo.toml index b4f704f80..1be7816a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -134,6 +134,7 @@ ureq = { version = "2", default-features = false, features = ["native-tls", "gzi usvg = { version = "0.43", default-features = false, features = ["text"] } walkdir = "2" wasmi = "0.39.0" +web-sys = "0.3" xmlparser = "0.13.5" xmlwriter = "0.1.0" xmp-writer = "0.3" diff --git a/README.md b/README.md index 5d5c4798a..a5d20d2e6 100644 --- a/README.md +++ b/README.md @@ -5,19 +5,19 @@

Documentation + > Typst App + > Discord Server + > Apache-2 License + > Jobs at Typst + >

Typst is a new markup-based typesetting system that is designed to be as powerful @@ -39,7 +39,7 @@ A [gentle introduction][tutorial] to Typst is available in our documentation. However, if you want to see the power of Typst encapsulated in one image, here it is:

- Example + Example

diff --git a/crates/typst-cli/src/args.rs b/crates/typst-cli/src/args.rs index 83c4c8f9e..d6855d100 100644 --- a/crates/typst-cli/src/args.rs +++ b/crates/typst-cli/src/args.rs @@ -473,6 +473,9 @@ pub enum PdfStandard { /// PDF/A-2b. #[value(name = "a-2b")] A_2b, + /// PDF/A-3b. + #[value(name = "a-3b")] + A_3b, } display_possible_values!(PdfStandard); diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index adeef0f2d..515a777a2 100644 --- a/crates/typst-cli/src/compile.rs +++ b/crates/typst-cli/src/compile.rs @@ -136,6 +136,7 @@ impl CompileConfig { .map(|standard| match standard { PdfStandard::V_1_7 => typst_pdf::PdfStandard::V_1_7, PdfStandard::A_2b => typst_pdf::PdfStandard::A_2b, + PdfStandard::A_3b => typst_pdf::PdfStandard::A_3b, }) .collect::>(); PdfStandards::new(&list)? diff --git a/crates/typst-cli/src/world.rs b/crates/typst-cli/src/world.rs index af6cf228f..12e80d273 100644 --- a/crates/typst-cli/src/world.rs +++ b/crates/typst-cli/src/world.rs @@ -305,7 +305,7 @@ impl FileSlot { ) -> FileResult { self.file.get_or_init( || read(self.id, project_root, package_storage), - |data, _| Ok(data.into()), + |data, _| Ok(Bytes::new(data)), ) } } diff --git a/crates/typst-eval/src/call.rs b/crates/typst-eval/src/call.rs index fc934cef5..0a9e1c486 100644 --- a/crates/typst-eval/src/call.rs +++ b/crates/typst-eval/src/call.rs @@ -685,8 +685,7 @@ mod tests { // Named-params. test(s, "$ foo(bar: y) $", &["foo"]); - // This should be updated when we improve named-param parsing: - test(s, "$ foo(x-y: 1, bar-z: 2) $", &["bar", "foo"]); + test(s, "$ foo(x-y: 1, bar-z: 2) $", &["foo"]); // Field access in math. test(s, "$ foo.bar $", &["foo"]); diff --git a/crates/typst-eval/src/import.rs b/crates/typst-eval/src/import.rs index 5b67c0608..2060d25f1 100644 --- a/crates/typst-eval/src/import.rs +++ b/crates/typst-eval/src/import.rs @@ -211,7 +211,7 @@ fn resolve_package( // Evaluate the manifest. let manifest_id = FileId::new(Some(spec.clone()), VirtualPath::new("typst.toml")); let bytes = engine.world.file(manifest_id).at(span)?; - let string = std::str::from_utf8(&bytes).map_err(FileError::from).at(span)?; + let string = bytes.as_str().map_err(FileError::from).at(span)?; let manifest: PackageManifest = toml::from_str(string) .map_err(|err| eco_format!("package manifest is malformed ({})", err.message())) .at(span)?; diff --git a/crates/typst-html/src/encode.rs b/crates/typst-html/src/encode.rs index b87b0e1d6..62146f867 100644 --- a/crates/typst-html/src/encode.rs +++ b/crates/typst-html/src/encode.rs @@ -12,6 +12,9 @@ pub fn html(document: &HtmlDocument) -> SourceResult { w.buf.push_str(""); write_indent(&mut w); write_element(&mut w, &document.root)?; + if w.pretty { + w.buf.push('\n'); + } Ok(w.buf) } diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index 5c2b500a0..0f8abddb7 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -521,11 +521,13 @@ fn complete_imports(ctx: &mut CompletionContext) -> bool { if_chain! { if ctx.leaf.kind() == SyntaxKind::Ident; if let Some(parent) = ctx.leaf.parent(); - if parent.kind() == SyntaxKind::ImportItems; + if parent.kind() == SyntaxKind::ImportItemPath; if let Some(grand) = parent.parent(); - if let Some(ast::Expr::Import(import)) = grand.get().cast(); + if grand.kind() == SyntaxKind::ImportItems; + if let Some(great) = grand.parent(); + if let Some(ast::Expr::Import(import)) = great.get().cast(); if let Some(ast::Imports::Items(items)) = import.imports(); - if let Some(source) = grand.children().find(|child| child.is::()); + if let Some(source) = great.children().find(|child| child.is::()); then { ctx.from = ctx.leaf.offset(); import_item_completions(ctx, items, &source); @@ -815,19 +817,8 @@ fn param_value_completions<'a>( ) { if param.name == "font" { ctx.font_completions(); - } else if param.name == "path" { - ctx.file_completions_with_extensions(match func.name() { - Some("image") => &["png", "jpg", "jpeg", "gif", "svg", "svgz"], - Some("csv") => &["csv"], - Some("plugin") => &["wasm"], - Some("cbor") => &["cbor"], - Some("json") => &["json"], - Some("toml") => &["toml"], - Some("xml") => &["xml"], - Some("yaml") => &["yml", "yaml"], - Some("bibliography") => &["bib", "yml", "yaml"], - _ => &[], - }); + } else if let Some(extensions) = path_completion(func, param) { + ctx.file_completions_with_extensions(extensions); } else if func.name() == Some("figure") && param.name == "body" { ctx.snippet_completion("image", "image(\"${}\"),", "An image in a figure."); ctx.snippet_completion("table", "table(\n ${}\n),", "A table in a figure."); @@ -836,6 +827,28 @@ fn param_value_completions<'a>( ctx.cast_completions(¶m.input); } +/// Returns which file extensions to complete for the given parameter if any. +fn path_completion(func: &Func, param: &ParamInfo) -> Option<&'static [&'static str]> { + Some(match (func.name(), param.name) { + (Some("image"), "source") => &["png", "jpg", "jpeg", "gif", "svg", "svgz"], + (Some("csv"), "source") => &["csv"], + (Some("plugin"), "source") => &["wasm"], + (Some("cbor"), "source") => &["cbor"], + (Some("json"), "source") => &["json"], + (Some("toml"), "source") => &["toml"], + (Some("xml"), "source") => &["xml"], + (Some("yaml"), "source") => &["yml", "yaml"], + (Some("bibliography"), "sources") => &["bib", "yml", "yaml"], + (Some("bibliography"), "style") => &["csl"], + (Some("cite"), "style") => &["csl"], + (Some("raw"), "syntaxes") => &["sublime-syntax"], + (Some("raw"), "theme") => &["tmtheme"], + (Some("embed"), "path") => &[], + (None, "path") => &[], + _ => return None, + }) +} + /// Resolve a callee expression to a global function. fn resolve_global_callee<'a>( ctx: &CompletionContext<'a>, @@ -1504,14 +1517,13 @@ impl BracketMode { #[cfg(test)] mod tests { + use std::borrow::Borrow; use std::collections::BTreeSet; use typst::layout::PagedDocument; - use typst::syntax::{FileId, Source, VirtualPath}; - use typst::World; use super::{autocomplete, Completion}; - use crate::tests::{SourceExt, TestWorld}; + use crate::tests::{FilePos, TestWorld, WorldLike}; /// Quote a string. macro_rules! q { @@ -1583,60 +1595,50 @@ mod tests { } #[track_caller] - fn test(text: &str, cursor: isize) -> Response { - let world = TestWorld::new(text); - test_with_world(&world, cursor) + fn test(world: impl WorldLike, pos: impl FilePos) -> Response { + let world = world.acquire(); + let world = world.borrow(); + let doc = typst::compile(world).output.ok(); + test_with_doc(world, pos, doc.as_ref()) } #[track_caller] - fn test_with_world(world: &TestWorld, cursor: isize) -> Response { - let doc = typst::compile(&world).output.ok(); - test_full(world, &world.main, doc.as_ref(), cursor) - } - - #[track_caller] - fn test_with_path(world: &TestWorld, path: &str, cursor: isize) -> Response { - let doc = typst::compile(&world).output.ok(); - let id = FileId::new(None, VirtualPath::new(path)); - let source = world.source(id).unwrap(); - test_full(world, &source, doc.as_ref(), cursor) - } - - #[track_caller] - fn test_full( - world: &TestWorld, - source: &Source, + fn test_with_doc( + world: impl WorldLike, + pos: impl FilePos, doc: Option<&PagedDocument>, - cursor: isize, ) -> Response { - autocomplete(world, doc, source, source.cursor(cursor), true) + let world = world.acquire(); + let world = world.borrow(); + let (source, cursor) = pos.resolve(world); + autocomplete(world, doc, &source, cursor, true) } #[test] fn test_autocomplete_hash_expr() { - test("#i", 2).must_include(["int", "if conditional"]); + test("#i", -1).must_include(["int", "if conditional"]); } #[test] fn test_autocomplete_array_method() { - test("#().", 4).must_include(["insert", "remove", "len", "all"]); - test("#{ let x = (1, 2, 3); x. }", -2).must_include(["at", "push", "pop"]); + test("#().", -1).must_include(["insert", "remove", "len", "all"]); + test("#{ let x = (1, 2, 3); x. }", -3).must_include(["at", "push", "pop"]); } /// Test that extra space before '.' is handled correctly. #[test] fn test_autocomplete_whitespace() { - test("#() .", 5).must_exclude(["insert", "remove", "len", "all"]); - test("#{() .}", 6).must_include(["insert", "remove", "len", "all"]); - test("#() .a", 6).must_exclude(["insert", "remove", "len", "all"]); - test("#{() .a}", 7).must_include(["at", "any", "all"]); + test("#() .", -1).must_exclude(["insert", "remove", "len", "all"]); + test("#{() .}", -2).must_include(["insert", "remove", "len", "all"]); + test("#() .a", -1).must_exclude(["insert", "remove", "len", "all"]); + test("#{() .a}", -2).must_include(["at", "any", "all"]); } /// Test that the `before_window` doesn't slice into invalid byte /// boundaries. #[test] fn test_autocomplete_before_window_char_boundary() { - test("πŸ˜€πŸ˜€ #text(font: \"\")", -2); + test("πŸ˜€πŸ˜€ #text(font: \"\")", -3); } /// Ensure that autocompletion for `#cite(|)` completes bibligraphy labels, @@ -1653,7 +1655,7 @@ mod tests { let end = world.main.len_bytes(); world.main.edit(end..end, " #cite()"); - test_full(&world, &world.main, doc.as_ref(), -1) + test_with_doc(&world, -2, doc.as_ref()) .must_include(["netwok", "glacier-melt", "supplement"]) .must_exclude(["bib"]); } @@ -1677,13 +1679,13 @@ mod tests { #[test] fn test_autocomplete_positional_param() { // No string given yet. - test("#numbering()", -1).must_include(["string", "integer"]); + test("#numbering()", -2).must_include(["string", "integer"]); // String is already given. - test("#numbering(\"foo\", )", -1) + test("#numbering(\"foo\", )", -2) .must_include(["integer"]) .must_exclude(["string"]); // Integer is already given, but numbering is variadic. - test("#numbering(\"foo\", 1, )", -1) + test("#numbering(\"foo\", 1, )", -2) .must_include(["integer"]) .must_exclude(["string"]); } @@ -1698,14 +1700,14 @@ mod tests { "#let clrs = (a: red, b: blue); #let nums = (a: 1, b: 2)", ); - test_with_world(&world, -1) + test(&world, -2) .must_include(["clrs", "aqua"]) .must_exclude(["nums", "a", "b"]); } #[test] fn test_autocomplete_packages() { - test("#import \"@\"", -1).must_include([q!("@preview/example:0.1.0")]); + test("#import \"@\"", -2).must_include([q!("@preview/example:0.1.0")]); } #[test] @@ -1719,28 +1721,41 @@ mod tests { .with_asset_at("assets/rhino.png", "rhino.png") .with_asset_at("data/example.csv", "example.csv"); - test_with_path(&world, "main.typ", -1) + test(&world, -2) .must_include([q!("content/a.typ"), q!("content/b.typ"), q!("utils.typ")]) .must_exclude([q!("assets/tiger.jpg")]); - test_with_path(&world, "content/c.typ", -1) + test(&world, ("content/c.typ", -2)) .must_include([q!("../main.typ"), q!("a.typ"), q!("b.typ")]) .must_exclude([q!("c.typ")]); - test_with_path(&world, "content/a.typ", -1) + test(&world, ("content/a.typ", -2)) .must_include([q!("../assets/tiger.jpg"), q!("../assets/rhino.png")]) .must_exclude([q!("../data/example.csv"), q!("b.typ")]); - test_with_path(&world, "content/b.typ", -2) - .must_include([q!("../data/example.csv")]); + test(&world, ("content/b.typ", -3)).must_include([q!("../data/example.csv")]); } #[test] fn test_autocomplete_figure_snippets() { - test("#figure()", -1) + test("#figure()", -2) .must_apply("image", "image(\"${}\"),") .must_apply("table", "table(\n ${}\n),"); - test("#figure(cap)", -1).must_apply("caption", "caption: [${}]"); + test("#figure(cap)", -2).must_apply("caption", "caption: [${}]"); + } + + #[test] + fn test_autocomplete_import_items() { + let world = TestWorld::new("#import \"other.typ\": ") + .with_source("second.typ", "#import \"other.typ\": th") + .with_source("other.typ", "#let this = 1; #let that = 2"); + + test(&world, ("main.typ", 21)) + .must_include(["*", "this", "that"]) + .must_exclude(["figure"]); + test(&world, ("second.typ", 23)) + .must_include(["this", "that"]) + .must_exclude(["*", "figure"]); } } diff --git a/crates/typst-ide/src/definition.rs b/crates/typst-ide/src/definition.rs index c789430a2..31fb9e34e 100644 --- a/crates/typst-ide/src/definition.rs +++ b/crates/typst-ide/src/definition.rs @@ -86,6 +86,7 @@ pub fn definition( #[cfg(test)] mod tests { + use std::borrow::Borrow; use std::ops::Range; use typst::foundations::{IntoValue, NativeElement}; @@ -93,7 +94,7 @@ mod tests { use typst::WorldExt; use super::{definition, Definition}; - use crate::tests::{SourceExt, TestWorld}; + use crate::tests::{FilePos, TestWorld, WorldLike}; type Response = (TestWorld, Option); @@ -132,23 +133,19 @@ mod tests { } #[track_caller] - fn test(text: &str, cursor: isize, side: Side) -> Response { - let world = TestWorld::new(text); - test_with_world(world, cursor, side) - } - - #[track_caller] - fn test_with_world(world: TestWorld, cursor: isize, side: Side) -> Response { - let doc = typst::compile(&world).output.ok(); - let source = &world.main; - let def = definition(&world, doc.as_ref(), source, source.cursor(cursor), side); - (world, def) + fn test(world: impl WorldLike, pos: impl FilePos, side: Side) -> Response { + let world = world.acquire(); + let world = world.borrow(); + let doc = typst::compile(world).output.ok(); + let (source, cursor) = pos.resolve(world); + let def = definition(world, doc.as_ref(), &source, cursor, side); + (world.clone(), def) } #[test] fn test_definition_let() { - test("#let x; #x", 9, Side::After).must_be_at("main.typ", 5..6); - test("#let x() = {}; #x", 16, Side::After).must_be_at("main.typ", 5..6); + test("#let x; #x", -2, Side::After).must_be_at("main.typ", 5..6); + test("#let x() = {}; #x", -2, Side::After).must_be_at("main.typ", 5..6); } #[test] @@ -158,33 +155,33 @@ mod tests { // The span is at the args here because that's what the function value's // span is. Not ideal, but also not too big of a big deal. - test_with_world(world, -1, Side::Before).must_be_at("other.typ", 8..11); + test(&world, -2, Side::Before).must_be_at("other.typ", 8..11); } #[test] fn test_definition_cross_file() { let world = TestWorld::new("#import \"other.typ\": x; #x") .with_source("other.typ", "#let x = 1"); - test_with_world(world, -1, Side::After).must_be_at("other.typ", 5..6); + test(&world, -2, Side::After).must_be_at("other.typ", 5..6); } #[test] fn test_definition_import() { let world = TestWorld::new("#import \"other.typ\" as o: x") .with_source("other.typ", "#let x = 1"); - test_with_world(world, 14, Side::Before).must_be_at("other.typ", 0..0); + test(&world, 14, Side::Before).must_be_at("other.typ", 0..0); } #[test] fn test_definition_include() { let world = TestWorld::new("#include \"other.typ\"") .with_source("other.typ", "Hello there"); - test_with_world(world, 14, Side::Before).must_be_at("other.typ", 0..0); + test(&world, 14, Side::Before).must_be_at("other.typ", 0..0); } #[test] fn test_definition_ref() { - test("#figure[] See @hi", 21, Side::After).must_be_at("main.typ", 1..9); + test("#figure[] See @hi", -2, Side::After).must_be_at("main.typ", 1..9); } #[test] diff --git a/crates/typst-ide/src/jump.rs b/crates/typst-ide/src/jump.rs index ba62b0ab9..ed74df226 100644 --- a/crates/typst-ide/src/jump.rs +++ b/crates/typst-ide/src/jump.rs @@ -182,12 +182,13 @@ mod tests { //! )) //! ``` + use std::borrow::Borrow; use std::num::NonZeroUsize; use typst::layout::{Abs, Point, Position}; use super::{jump_from_click, jump_from_cursor, Jump}; - use crate::tests::TestWorld; + use crate::tests::{FilePos, TestWorld, WorldLike}; fn point(x: f64, y: f64) -> Point { Point::new(Abs::pt(x), Abs::pt(y)) @@ -211,10 +212,11 @@ mod tests { } #[track_caller] - fn test_click(text: &str, click: Point, expected: Option) { - let world = TestWorld::new(text); - let doc = typst::compile(&world).output.unwrap(); - let jump = jump_from_click(&world, &doc, &doc.pages[0].frame, click); + fn test_click(world: impl WorldLike, click: Point, expected: Option) { + let world = world.acquire(); + let world = world.borrow(); + let doc = typst::compile(world).output.unwrap(); + let jump = jump_from_click(world, &doc, &doc.pages[0].frame, click); if let (Some(Jump::Position(pos)), Some(Jump::Position(expected))) = (&jump, &expected) { @@ -227,10 +229,12 @@ mod tests { } #[track_caller] - fn test_cursor(text: &str, cursor: usize, expected: Option) { - let world = TestWorld::new(text); - let doc = typst::compile(&world).output.unwrap(); - let pos = jump_from_cursor(&doc, &world.main, cursor); + fn test_cursor(world: impl WorldLike, pos: impl FilePos, expected: Option) { + let world = world.acquire(); + let world = world.borrow(); + let doc = typst::compile(world).output.unwrap(); + let (source, cursor) = pos.resolve(world); + let pos = jump_from_cursor(&doc, &source, cursor); assert_eq!(!pos.is_empty(), expected.is_some()); if let (Some(pos), Some(expected)) = (pos.first(), expected) { assert_eq!(pos.page, expected.page); diff --git a/crates/typst-ide/src/matchers.rs b/crates/typst-ide/src/matchers.rs index 18262f701..b92cbf557 100644 --- a/crates/typst-ide/src/matchers.rs +++ b/crates/typst-ide/src/matchers.rs @@ -89,15 +89,21 @@ pub fn named_items( // ``` Some(ast::Imports::Items(items)) => { for item in items.iter() { - let original = item.original_name(); let bound = item.bound_name(); - let scope = source.and_then(|(value, _)| value.scope()); - let span = scope - .and_then(|s| s.get_span(&original)) - .unwrap_or(Span::detached()) - .or(bound.span()); - let value = scope.and_then(|s| s.get(&original)); + let (span, value) = item.path().iter().fold( + (bound.span(), source.map(|(value, _)| value)), + |(span, value), path_ident| { + let scope = value.and_then(|v| v.scope()); + let span = scope + .and_then(|s| s.get_span(&path_ident)) + .unwrap_or(Span::detached()) + .or(span); + let value = scope.and_then(|s| s.get(&path_ident)); + (span, value) + }, + ); + if let Some(res) = recv(NamedItem::Import(bound.get(), span, value)) { @@ -266,53 +272,95 @@ pub enum DerefTarget<'a> { #[cfg(test)] mod tests { + use std::borrow::Borrow; + + use ecow::EcoString; + use typst::foundations::Value; use typst::syntax::{LinkedNode, Side}; - use crate::{named_items, tests::TestWorld}; + use super::named_items; + use crate::tests::{FilePos, TestWorld, WorldLike}; + + type Response = Vec<(EcoString, Option)>; + + trait ResponseExt { + fn must_include<'a>(&self, includes: impl IntoIterator) -> &Self; + fn must_exclude<'a>(&self, excludes: impl IntoIterator) -> &Self; + fn must_include_value(&self, name_value: (&str, Option<&Value>)) -> &Self; + } + + impl ResponseExt for Response { + #[track_caller] + fn must_include<'a>(&self, includes: impl IntoIterator) -> &Self { + for item in includes { + assert!( + self.iter().any(|v| v.0 == item), + "{item:?} was not contained in {self:?}", + ); + } + self + } + + #[track_caller] + fn must_exclude<'a>(&self, excludes: impl IntoIterator) -> &Self { + for item in excludes { + assert!( + !self.iter().any(|v| v.0 == item), + "{item:?} was wrongly contained in {self:?}", + ); + } + self + } + + #[track_caller] + fn must_include_value(&self, name_value: (&str, Option<&Value>)) -> &Self { + assert!( + self.iter().any(|v| (v.0.as_str(), v.1.as_ref()) == name_value), + "{name_value:?} was not contained in {self:?}", + ); + self + } + } #[track_caller] - fn has_named_items(text: &str, cursor: usize, containing: &str) -> bool { - let world = TestWorld::new(text); - - let src = world.main.clone(); - let node = LinkedNode::new(src.root()); + fn test(world: impl WorldLike, pos: impl FilePos) -> Response { + let world = world.acquire(); + let world = world.borrow(); + let (source, cursor) = pos.resolve(world); + let node = LinkedNode::new(source.root()); let leaf = node.leaf_at(cursor, Side::After).unwrap(); - - let res = named_items(&world, leaf, |s| { - if containing == s.name() { - return Some(true); - } - - None + let mut items = vec![]; + named_items(world, leaf, |s| { + items.push((s.name().clone(), s.value().clone())); + None::<()> }); - - res.unwrap_or_default() + items } #[test] - fn test_simple_named_items() { - // Has named items - assert!(has_named_items(r#"#let a = 1;#let b = 2;"#, 8, "a")); - assert!(has_named_items(r#"#let a = 1;#let b = 2;"#, 15, "a")); - - // Doesn't have named items - assert!(!has_named_items(r#"#let a = 1;#let b = 2;"#, 8, "b")); + fn test_named_items_simple() { + let s = "#let a = 1;#let b = 2;"; + test(s, 8).must_include(["a"]).must_exclude(["b"]); + test(s, 15).must_include(["b"]); } #[test] - fn test_param_named_items() { - // Has named items - assert!(has_named_items(r#"#let f(a) = 1;#let b = 2;"#, 12, "a")); - assert!(has_named_items(r#"#let f(a: b) = 1;#let b = 2;"#, 15, "a")); + fn test_named_items_param() { + let pos = "#let f(a) = 1;#let b = 2;"; + test(pos, 12).must_include(["a"]); + test(pos, 19).must_include(["b", "f"]).must_exclude(["a"]); - // Doesn't have named items - assert!(!has_named_items(r#"#let f(a) = 1;#let b = 2;"#, 19, "a")); - assert!(!has_named_items(r#"#let f(a: b) = 1;#let b = 2;"#, 15, "b")); + let named = "#let f(a: b) = 1;#let b = 2;"; + test(named, 15).must_include(["a", "f"]).must_exclude(["b"]); } #[test] - fn test_import_named_items() { - // Cannot test much. - assert!(has_named_items(r#"#import "foo.typ": a; #(a);"#, 24, "a")); + fn test_named_items_import() { + test("#import \"foo.typ\": a; #(a);", 2).must_include(["a"]); + + let world = TestWorld::new("#import \"foo.typ\": a.b; #(b);") + .with_source("foo.typ", "#import \"a.typ\"") + .with_source("a.typ", "#let b = 1;"); + test(&world, 2).must_include_value(("b", Some(&Value::Int(1)))); } } diff --git a/crates/typst-ide/src/tests.rs b/crates/typst-ide/src/tests.rs index 5a73fa375..6678ab841 100644 --- a/crates/typst-ide/src/tests.rs +++ b/crates/typst-ide/src/tests.rs @@ -1,4 +1,6 @@ +use std::borrow::Borrow; use std::collections::HashMap; +use std::sync::Arc; use ecow::EcoString; use typst::diag::{FileError, FileResult}; @@ -13,10 +15,10 @@ use typst::{Library, World}; use crate::IdeWorld; /// A world for IDE testing. +#[derive(Clone)] pub struct TestWorld { pub main: Source, - assets: HashMap, - sources: HashMap, + files: Arc, base: &'static TestBase, } @@ -29,8 +31,7 @@ impl TestWorld { let main = Source::new(Self::main_id(), text.into()); Self { main, - assets: HashMap::new(), - sources: HashMap::new(), + files: Arc::new(TestFiles::default()), base: singleton!(TestBase, TestBase::default()), } } @@ -39,7 +40,7 @@ impl TestWorld { pub fn with_source(mut self, path: &str, text: &str) -> Self { let id = FileId::new(None, VirtualPath::new(path)); let source = Source::new(id, text.into()); - self.sources.insert(id, source); + Arc::make_mut(&mut self.files).sources.insert(id, source); self } @@ -54,8 +55,8 @@ impl TestWorld { pub fn with_asset_at(mut self, path: &str, filename: &str) -> Self { let id = FileId::new(None, VirtualPath::new(path)); let data = typst_dev_assets::get_by_name(filename).unwrap(); - let bytes = Bytes::from_static(data); - self.assets.insert(id, bytes); + let bytes = Bytes::new(data); + Arc::make_mut(&mut self.files).assets.insert(id, bytes); self } @@ -81,7 +82,7 @@ impl World for TestWorld { fn source(&self, id: FileId) -> FileResult { if id == self.main.id() { Ok(self.main.clone()) - } else if let Some(source) = self.sources.get(&id) { + } else if let Some(source) = self.files.sources.get(&id) { Ok(source.clone()) } else { Err(FileError::NotFound(id.vpath().as_rootless_path().into())) @@ -89,7 +90,7 @@ impl World for TestWorld { } fn file(&self, id: FileId) -> FileResult { - match self.assets.get(&id) { + match self.files.assets.get(&id) { Some(bytes) => Ok(bytes.clone()), None => Err(FileError::NotFound(id.vpath().as_rootless_path().into())), } @@ -111,8 +112,8 @@ impl IdeWorld for TestWorld { fn files(&self) -> Vec { std::iter::once(self.main.id()) - .chain(self.sources.keys().copied()) - .chain(self.assets.keys().copied()) + .chain(self.files.sources.keys().copied()) + .chain(self.files.assets.keys().copied()) .collect() } @@ -133,20 +134,11 @@ impl IdeWorld for TestWorld { } } -/// Extra methods for [`Source`]. -pub trait SourceExt { - /// Negative cursors index from the back. - fn cursor(&self, cursor: isize) -> usize; -} - -impl SourceExt for Source { - fn cursor(&self, cursor: isize) -> usize { - if cursor < 0 { - self.len_bytes().checked_add_signed(cursor).unwrap() - } else { - cursor as usize - } - } +/// Test-specific files. +#[derive(Default, Clone)] +struct TestFiles { + assets: HashMap, + sources: HashMap, } /// Shared foundation of all test worlds. @@ -160,7 +152,7 @@ impl Default for TestBase { fn default() -> Self { let fonts: Vec<_> = typst_assets::fonts() .chain(typst_dev_assets::fonts()) - .flat_map(|data| Font::iter(Bytes::from_static(data))) + .flat_map(|data| Font::iter(Bytes::new(data))) .collect(); Self { @@ -186,3 +178,58 @@ fn library() -> Library { lib.styles.set(TextElem::set_size(TextSize(Abs::pt(10.0).into()))); lib } + +/// The input to a test: Either just a string or a full `TestWorld`. +pub trait WorldLike { + type World: Borrow; + + fn acquire(self) -> Self::World; +} + +impl<'a> WorldLike for &'a TestWorld { + type World = &'a TestWorld; + + fn acquire(self) -> Self::World { + self + } +} + +impl WorldLike for &str { + type World = TestWorld; + + fn acquire(self) -> Self::World { + TestWorld::new(self) + } +} + +/// Specifies a position in a file for a test. +pub trait FilePos { + fn resolve(self, world: &TestWorld) -> (Source, usize); +} + +impl FilePos for isize { + #[track_caller] + fn resolve(self, world: &TestWorld) -> (Source, usize) { + (world.main.clone(), cursor(&world.main, self)) + } +} + +impl FilePos for (&str, isize) { + #[track_caller] + fn resolve(self, world: &TestWorld) -> (Source, usize) { + let id = FileId::new(None, VirtualPath::new(self.0)); + let source = world.source(id).unwrap(); + let cursor = cursor(&source, self.1); + (source, cursor) + } +} + +/// Resolve a signed index (negative from the back) to a unsigned index. +#[track_caller] +fn cursor(source: &Source, cursor: isize) -> usize { + if cursor < 0 { + source.len_bytes().checked_add_signed(cursor + 1).unwrap() + } else { + cursor as usize + } +} diff --git a/crates/typst-ide/src/tooltip.rs b/crates/typst-ide/src/tooltip.rs index 4eaaeda1f..99ae0620b 100644 --- a/crates/typst-ide/src/tooltip.rs +++ b/crates/typst-ide/src/tooltip.rs @@ -274,10 +274,12 @@ fn font_tooltip(world: &dyn IdeWorld, leaf: &LinkedNode) -> Option { #[cfg(test)] mod tests { + use std::borrow::Borrow; + use typst::syntax::Side; use super::{tooltip, Tooltip}; - use crate::tests::{SourceExt, TestWorld}; + use crate::tests::{FilePos, TestWorld, WorldLike}; type Response = Option; @@ -308,21 +310,17 @@ mod tests { } #[track_caller] - fn test(text: &str, cursor: isize, side: Side) -> Response { - let world = TestWorld::new(text); - test_with_world(&world, cursor, side) - } - - #[track_caller] - fn test_with_world(world: &TestWorld, cursor: isize, side: Side) -> Response { - let source = &world.main; - let doc = typst::compile(&world).output.ok(); - tooltip(world, doc.as_ref(), source, source.cursor(cursor), side) + fn test(world: impl WorldLike, pos: impl FilePos, side: Side) -> Response { + let world = world.acquire(); + let world = world.borrow(); + let (source, cursor) = pos.resolve(world); + let doc = typst::compile(world).output.ok(); + tooltip(world, doc.as_ref(), &source, cursor, side) } #[test] fn test_tooltip() { - test("#let x = 1 + 2", 14, Side::After).must_be_none(); + test("#let x = 1 + 2", -1, Side::After).must_be_none(); test("#let x = 1 + 2", 5, Side::After).must_be_code("3"); test("#let x = 1 + 2", 6, Side::Before).must_be_code("3"); test("#let x = 1 + 2", 6, Side::Before).must_be_code("3"); @@ -330,7 +328,7 @@ mod tests { #[test] fn test_tooltip_empty_contextual() { - test("#{context}", 10, Side::Before).must_be_code("context()"); + test("#{context}", -1, Side::Before).must_be_code("context()"); } #[test] @@ -358,8 +356,7 @@ mod tests { fn test_tooltip_star_import() { let world = TestWorld::new("#import \"other.typ\": *") .with_source("other.typ", "#let (a, b, c) = (1, 2, 3)"); - test_with_world(&world, 21, Side::Before).must_be_none(); - test_with_world(&world, 21, Side::After) - .must_be_text("This star imports `a`, `b`, and `c`"); + test(&world, -2, Side::Before).must_be_none(); + test(&world, -2, Side::After).must_be_text("This star imports `a`, `b`, and `c`"); } } diff --git a/crates/typst-kit/src/fonts.rs b/crates/typst-kit/src/fonts.rs index 83e13fd8f..c15d739ec 100644 --- a/crates/typst-kit/src/fonts.rs +++ b/crates/typst-kit/src/fonts.rs @@ -13,6 +13,7 @@ use std::path::{Path, PathBuf}; use std::sync::OnceLock; use fontdb::{Database, Source}; +use typst_library::foundations::Bytes; use typst_library::text::{Font, FontBook, FontInfo}; use typst_timing::TimingScope; @@ -52,9 +53,8 @@ impl FontSlot { .as_ref() .expect("`path` is not `None` if `font` is uninitialized"), ) - .ok()? - .into(); - Font::new(data, self.index) + .ok()?; + Font::new(Bytes::new(data), self.index) }) .clone() } @@ -196,7 +196,7 @@ impl FontSearcher { #[cfg(feature = "embed-fonts")] fn add_embedded(&mut self) { for data in typst_assets::fonts() { - let buffer = typst_library::foundations::Bytes::from_static(data); + let buffer = Bytes::new(data); for (i, font) in Font::iter(buffer).enumerate() { self.book.push(font.info().clone()); self.fonts.push(FontSlot { diff --git a/crates/typst-layout/src/flow/block.rs b/crates/typst-layout/src/flow/block.rs index 71eacc1ce..6c2c3923d 100644 --- a/crates/typst-layout/src/flow/block.rs +++ b/crates/typst-layout/src/flow/block.rs @@ -364,6 +364,12 @@ fn breakable_pod<'a>( /// Distribute a fixed height spread over existing regions into a new first /// height and a new backlog. +/// +/// Note that, if the given height fits within the first region, no backlog is +/// generated and the first region's height shrinks to fit exactly the given +/// height. In particular, negative and zero heights always fit in any region, +/// so such heights are always directly returned as the new first region +/// height. fn distribute<'a>( height: Abs, mut regions: Regions, @@ -371,7 +377,19 @@ fn distribute<'a>( ) -> (Abs, &'a mut [Abs]) { // Build new region heights from old regions. let mut remaining = height; + + // Negative and zero heights always fit, so just keep them. + // No backlog is generated. + if remaining <= Abs::zero() { + buf.push(remaining); + return (buf[0], &mut buf[1..]); + } + loop { + // This clamp is safe (min <= max), as 'remaining' won't be negative + // due to the initial check above (on the first iteration) and due to + // stopping on 'remaining.approx_empty()' below (for the second + // iteration onwards). let limited = regions.size.y.clamp(Abs::zero(), remaining); buf.push(limited); remaining -= limited; diff --git a/crates/typst-layout/src/flow/compose.rs b/crates/typst-layout/src/flow/compose.rs index 326456752..3cf66f9e3 100644 --- a/crates/typst-layout/src/flow/compose.rs +++ b/crates/typst-layout/src/flow/compose.rs @@ -15,7 +15,7 @@ use typst_library::model::{ FootnoteElem, FootnoteEntry, LineNumberingScope, Numbering, ParLineMarker, }; use typst_syntax::Span; -use typst_utils::NonZeroExt; +use typst_utils::{NonZeroExt, Numeric}; use super::{distribute, Config, FlowResult, LineNumberConfig, PlacedChild, Stop, Work}; @@ -374,7 +374,11 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { let mut relayout = false; let mut regions = *regions; - let mut migratable = migratable && !breakable && regions.may_progress(); + + // The first footnote's origin frame should be migratable if the region + // may progress (already checked by the footnote function) and if the + // origin frame isn't breakable (checked here). + let mut migratable = migratable && !breakable; for (y, elem) in notes { // The amount of space used by the in-flow content that contains the @@ -464,11 +468,35 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { // If the first frame is empty, then none of its content fit. If // possible, we then migrate the origin frame to the next region to // uphold the footnote invariant (that marker and entry are on the same - // page). If not, we just queue the footnote for the next page. + // page). If not, we just queue the footnote for the next page, but + // only if that would actually make a difference (that is, if the + // footnote isn't alone in the page after not fitting in any previous + // pages, as it probably won't ever fit then). + // + // Note that a non-zero flow need also indicates that queueing would + // make a difference, because the flow need is subtracted from the + // available height in the entry's pod even if what caused that need + // wasn't considered for the input `regions`. For example, floats just + // pass the `regions` they received along to their footnotes, which + // don't take into account the space occupied by the floats themselves, + // but they do indicate their footnotes have a non-zero flow need, so + // queueing them can matter as, in the following pages, the flow need + // will be set to zero and the footnote will be alone in the page. + // Then, `may_progress()` will also be false (this time, correctly) and + // the footnote is laid out, as queueing wouldn't improve the lack of + // space anymore and would result in an infinite loop. + // + // However, it is worth noting that migration does take into account + // the original region, before inserting what prompted the flow need. + // Logically, if moving the original frame can't improve the lack of + // space, then migration should be inhibited. The space occupied by the + // original frame is not relevant for that check. Therefore, + // `regions.may_progress()` must still be checked separately for + // migration, regardless of the presence of flow need. if first.is_empty() && exist_non_empty_frame { - if migratable { + if migratable && regions.may_progress() { return Err(Stop::Finish(false)); - } else { + } else if regions.may_progress() || !flow_need.is_zero() { self.footnote_queue.push(elem); return Ok(()); } diff --git a/crates/typst-layout/src/flow/mod.rs b/crates/typst-layout/src/flow/mod.rs index df716b338..2f0ec39a9 100644 --- a/crates/typst-layout/src/flow/mod.rs +++ b/crates/typst-layout/src/flow/mod.rs @@ -203,8 +203,14 @@ pub(crate) fn layout_flow( } else { PageElem::width_in(shared) }; - (0.026 * width.unwrap_or_default()) - .clamp(Em::new(0.75).resolve(shared), Em::new(2.5).resolve(shared)) + + // Clamp below is safe (min <= max): if the font size is + // negative, we set min = max = 0; otherwise, + // `0.75 * size <= 2.5 * size` for zero and positive sizes. + (0.026 * width.unwrap_or_default()).clamp( + Em::new(0.75).resolve(shared).max(Abs::zero()), + Em::new(2.5).resolve(shared).max(Abs::zero()), + ) }, }), }; @@ -354,6 +360,16 @@ struct LineNumberConfig { /// Where line numbers are reset. scope: LineNumberingScope, /// The default clearance for `auto`. + /// + /// This value should be relative to the page's width, such that the + /// clearance between line numbers and text is small when the page is, + /// itself, small. However, that could cause the clearance to be too small + /// or too large when considering the current text size; in particular, a + /// larger text size would require more clearance to be able to tell line + /// numbers apart from text, whereas a smaller text size requires less + /// clearance so they aren't way too far apart. Therefore, the default + /// value is a percentage of the page width clamped between `0.75em` and + /// `2.5em`. default_clearance: Abs, } diff --git a/crates/typst-layout/src/grid/layouter.rs b/crates/typst-layout/src/grid/layouter.rs index 7c94617dc..1f9cf6796 100644 --- a/crates/typst-layout/src/grid/layouter.rs +++ b/crates/typst-layout/src/grid/layouter.rs @@ -3,6 +3,7 @@ use std::fmt::Debug; use typst_library::diag::{bail, SourceResult}; use typst_library::engine::Engine; use typst_library::foundations::{Resolve, StyleChain}; +use typst_library::layout::grid::resolve::{Cell, CellGrid, LinePosition, Repeatable}; use typst_library::layout::{ Abs, Axes, Dir, Fr, Fragment, Frame, FrameItem, Length, Point, Region, Regions, Rel, Size, Sizing, @@ -13,8 +14,8 @@ 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, + generate_line_segments, hline_stroke_at_column, layout_cell, vline_stroke_at_row, + LineSegment, Rowspan, UnbreakableRowGroup, }; /// Performs grid layout. @@ -843,7 +844,8 @@ impl<'a> GridLayouter<'a> { let size = Size::new(available, height); let pod = Region::new(size, Axes::splat(false)); - let frame = cell.layout(engine, 0, self.styles, pod.into())?.into_frame(); + let frame = + layout_cell(cell, engine, 0, self.styles, pod.into())?.into_frame(); resolved.set_max(frame.width() - already_covered_width); } @@ -1086,7 +1088,7 @@ impl<'a> GridLayouter<'a> { }; let frames = - cell.layout(engine, disambiguator, self.styles, pod)?.into_frames(); + layout_cell(cell, engine, disambiguator, self.styles, pod)?.into_frames(); // Skip the first region if one cell in it is empty. Then, // remeasure. @@ -1252,9 +1254,9 @@ impl<'a> GridLayouter<'a> { // rows. pod.full = self.regions.full; } - let frame = cell - .layout(engine, disambiguator, self.styles, pod)? - .into_frame(); + let frame = + layout_cell(cell, engine, disambiguator, self.styles, pod)? + .into_frame(); let mut pos = pos; if self.is_rtl { // In the grid, cell colspans expand to the right, @@ -1310,7 +1312,7 @@ impl<'a> GridLayouter<'a> { // Push the layouted frames into the individual output frames. let fragment = - cell.layout(engine, disambiguator, self.styles, pod)?; + layout_cell(cell, engine, disambiguator, self.styles, pod)?; for (output, frame) in outputs.iter_mut().zip(fragment) { let mut pos = pos; if self.is_rtl { diff --git a/crates/typst-layout/src/grid/lines.rs b/crates/typst-layout/src/grid/lines.rs index 3e89612a1..1227953d1 100644 --- a/crates/typst-layout/src/grid/lines.rs +++ b/crates/typst-layout/src/grid/lines.rs @@ -1,41 +1,11 @@ -use std::num::NonZeroUsize; use std::sync::Arc; use typst_library::foundations::{AlternativeFold, Fold}; +use typst_library::layout::grid::resolve::{CellGrid, Line, Repeatable}; 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. -pub struct Line { - /// The index of the track after this line. This will be the index of the - /// row a horizontal line is above of, or of the column right after a - /// vertical line. - /// - /// Must be within `0..=tracks.len()` (where `tracks` is either `grid.cols` - /// or `grid.rows`, ignoring gutter tracks, as appropriate). - pub index: usize, - /// The index of the track at which this line starts being drawn. - /// This is the first column a horizontal line appears in, or the first row - /// a vertical line appears in. - /// - /// Must be within `0..tracks.len()` minus gutter tracks. - pub start: usize, - /// The index after the last track through which the line is drawn. - /// Thus, the line is drawn through tracks `start..end` (note that `end` is - /// exclusive). - /// - /// Must be within `1..=tracks.len()` minus gutter tracks. - /// `None` indicates the line should go all the way to the end. - pub end: Option, - /// The line's stroke. This is `None` when the line is explicitly used to - /// override a previously specified line. - pub stroke: Option>>, - /// The line's position in relation to the track with its index. - pub position: LinePosition, -} +use super::RowPiece; /// Indicates which priority a particular grid line segment should have, based /// on the highest priority configuration that defined the segment's stroke. @@ -588,13 +558,13 @@ pub fn hline_stroke_at_column( #[cfg(test)] mod test { + use std::num::NonZeroUsize; use typst_library::foundations::Content; use typst_library::introspection::Locator; + use typst_library::layout::grid::resolve::{Cell, Entry, LinePosition}; use typst_library::layout::{Axes, Sides, Sizing}; use typst_utils::NonZeroExt; - use super::super::cells::Entry; - use super::super::Cell; use super::*; fn sample_cell() -> Cell<'static> { diff --git a/crates/typst-layout/src/grid/mod.rs b/crates/typst-layout/src/grid/mod.rs index 769bef8c5..1b4380f0a 100644 --- a/crates/typst-layout/src/grid/mod.rs +++ b/crates/typst-layout/src/grid/mod.rs @@ -1,40 +1,44 @@ -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::diag::SourceResult; use typst_library::engine::Engine; -use typst_library::foundations::{Fold, Packed, Smart, StyleChain}; +use typst_library::foundations::{Packed, 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 typst_library::layout::grid::resolve::{grid_to_cellgrid, table_to_cellgrid, Cell}; +use typst_library::layout::{Fragment, GridElem, Regions}; +use typst_library::model::TableElem; -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, + generate_line_segments, hline_stroke_at_column, vline_stroke_at_row, LineSegment, }; -use self::repeated::{Footer, Header, Repeatable}; use self::rowspans::{Rowspan, UnbreakableRowGroup}; +/// Layout the cell into the given regions. +/// +/// The `disambiguator` indicates which instance of this cell this should be +/// layouted as. For normal cells, it is always `0`, but for headers and +/// footers, it indicates the index of the header/footer among all. See the +/// [`Locator`] docs for more details on the concepts behind this. +pub fn layout_cell( + cell: &Cell, + engine: &mut Engine, + disambiguator: usize, + styles: StyleChain, + regions: Regions, +) -> SourceResult { + let mut locator = cell.locator.relayout(); + if disambiguator > 0 { + locator = locator.split().next_inner(disambiguator as u128); + } + crate::layout_fragment(engine, &cell.body, locator, styles, regions) +} + /// Layout the grid. #[typst_macros::time(span = elem.span())] pub fn layout_grid( @@ -44,54 +48,8 @@ pub fn layout_grid( styles: StyleChain, regions: Regions, ) -> SourceResult { - 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) + let grid = grid_to_cellgrid(elem, engine, locator, styles)?; + GridLayouter::new(&grid, regions, styles, elem.span()).layout(engine) } /// Layout the table. @@ -103,314 +61,6 @@ pub fn layout_table( styles: StyleChain, regions: Regions, ) -> SourceResult { - 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> { - 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> { - 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 { - fn resolve_cell<'a>( - mut self, - x: usize, - y: usize, - fill: &Option, - align: Smart, - inset: Sides>>, - stroke: Sides>>>>, - 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 { - (**self).x(styles) - } - - fn y(&self, styles: StyleChain) -> Smart { - (**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 { - fn resolve_cell<'a>( - mut self, - x: usize, - y: usize, - fill: &Option, - align: Smart, - inset: Sides>>, - stroke: Sides>>>>, - 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 { - (**self).x(styles) - } - - fn y(&self, styles: StyleChain) -> Smart { - (**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) - } + let grid = table_to_cellgrid(elem, engine, locator, styles)?; + GridLayouter::new(&grid, regions, styles, elem.span()).layout(engine) } diff --git a/crates/typst-layout/src/grid/repeated.rs b/crates/typst-layout/src/grid/repeated.rs index 8d08d56db..22d2a09ef 100644 --- a/crates/typst-layout/src/grid/repeated.rs +++ b/crates/typst-layout/src/grid/repeated.rs @@ -1,50 +1,11 @@ use typst_library::diag::SourceResult; use typst_library::engine::Engine; +use typst_library::layout::grid::resolve::{Footer, Header, Repeatable}; use typst_library::layout::{Abs, Axes, Frame, Regions}; use super::layouter::GridLayouter; use super::rowspans::UnbreakableRowGroup; -/// A repeatable grid header. Starts at the first row. -pub struct Header { - /// The index after the last row included in this header. - pub end: usize, -} - -/// A repeatable grid footer. Stops at the last row. -pub struct Footer { - /// The first row included in this footer. - 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 enum Repeatable { - Repeated(T), - NotRepeated(T), -} - -impl Repeatable { - /// Gets the value inside this repeatable, regardless of whether - /// it repeats. - pub fn unwrap(&self) -> &T { - match self { - Self::Repeated(repeated) => repeated, - Self::NotRepeated(not_repeated) => not_repeated, - } - } - - /// Returns `Some` if the value is repeated, `None` otherwise. - pub fn as_repeated(&self) -> Option<&T> { - match self { - Self::Repeated(repeated) => Some(repeated), - Self::NotRepeated(_) => None, - } - } -} - impl GridLayouter<'_> { /// Layouts the header's rows. /// Skips regions as necessary. diff --git a/crates/typst-layout/src/grid/rowspans.rs b/crates/typst-layout/src/grid/rowspans.rs index 93d4c960d..5039695d8 100644 --- a/crates/typst-layout/src/grid/rowspans.rs +++ b/crates/typst-layout/src/grid/rowspans.rs @@ -1,12 +1,12 @@ use typst_library::diag::SourceResult; use typst_library::engine::Engine; use typst_library::foundations::Resolve; +use typst_library::layout::grid::resolve::Repeatable; 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 super::{Cell, GridLayouter}; +use super::{layout_cell, Cell, GridLayouter}; /// All information needed to layout a single rowspan. pub struct Rowspan { @@ -141,7 +141,7 @@ impl GridLayouter<'_> { } // Push the layouted frames directly into the finished frames. - let fragment = cell.layout(engine, disambiguator, self.styles, pod)?; + let fragment = layout_cell(cell, engine, disambiguator, self.styles, pod)?; let (current_region, current_rrows) = current_region_data.unzip(); for ((i, finished), frame) in self .finished diff --git a/crates/typst-layout/src/image.rs b/crates/typst-layout/src/image.rs index f44d68873..e521b993f 100644 --- a/crates/typst-layout/src/image.rs +++ b/crates/typst-layout/src/image.rs @@ -1,16 +1,16 @@ use std::ffi::OsStr; -use typst_library::diag::{bail, warning, At, SourceResult, StrResult}; +use typst_library::diag::{warning, At, SourceResult, StrResult}; use typst_library::engine::Engine; -use typst_library::foundations::{Packed, Smart, StyleChain}; +use typst_library::foundations::{Bytes, Derived, 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::loading::DataSource; use typst_library::text::families; use typst_library::visualize::{ - Image, ImageElem, ImageFit, ImageFormat, Path, RasterFormat, VectorFormat, + Curve, Image, ImageElem, ImageFit, ImageFormat, RasterFormat, VectorFormat, }; /// Layout the image. @@ -26,17 +26,17 @@ pub fn layout_image( // Take the format that was explicitly defined, or parse the extension, // or try to detect the format. - let data = elem.data(); + let Derived { source, derived: data } = &elem.source; let format = match elem.format(styles) { Smart::Custom(v) => v, - Smart::Auto => determine_format(elem.path().as_str(), data).at(span)?, + Smart::Auto => determine_format(source, 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(" StrResult { - let ext = std::path::Path::new(path) - .extension() - .and_then(OsStr::to_str) - .unwrap_or_default() - .to_lowercase(); +/// Try to determine the image format based on the data. +fn determine_format(source: &DataSource, data: &Bytes) -> StrResult { + if let DataSource::Path(path) = source { + let ext = std::path::Path::new(path.as_str()) + .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"), - }, - }, - }) + match ext.as_str() { + "png" => return Ok(ImageFormat::Raster(RasterFormat::Png)), + "jpg" | "jpeg" => return Ok(ImageFormat::Raster(RasterFormat::Jpg)), + "gif" => return Ok(ImageFormat::Raster(RasterFormat::Gif)), + "svg" | "svgz" => return Ok(ImageFormat::Vector(VectorFormat::Svg)), + _ => {} + } + } + + Ok(ImageFormat::detect(data).ok_or("unknown image format")?) } diff --git a/crates/typst-layout/src/inline/collect.rs b/crates/typst-layout/src/inline/collect.rs index 23e82c417..fcf7508e9 100644 --- a/crates/typst-layout/src/inline/collect.rs +++ b/crates/typst-layout/src/inline/collect.rs @@ -161,9 +161,9 @@ pub fn collect<'a>( } if let Some(case) = TextElem::case_in(styles) { - full.push_str(&case.apply(elem.text())); + full.push_str(&case.apply(&elem.text)); } else { - full.push_str(elem.text()); + full.push_str(&elem.text); } if dir != outer_dir { @@ -172,13 +172,12 @@ pub fn collect<'a>( } }); } else if let Some(elem) = child.to_packed::() { - let amount = elem.amount(); - if amount.is_zero() { + if elem.amount.is_zero() { continue; } - collector.push_item(match amount { - Spacing::Fr(fr) => Item::Fractional(*fr, None), + collector.push_item(match elem.amount { + Spacing::Fr(fr) => Item::Fractional(fr, None), Spacing::Rel(rel) => Item::Absolute( rel.resolve(styles).relative_to(region.x), elem.weak(styles), diff --git a/crates/typst-layout/src/lib.rs b/crates/typst-layout/src/lib.rs index 7069fc4dd..2e8c1129b 100644 --- a/crates/typst-layout/src/lib.rs +++ b/crates/typst-layout/src/lib.rs @@ -23,8 +23,8 @@ 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, + layout_circle, layout_curve, 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 index 0d51a1e4e..63127474b 100644 --- a/crates/typst-layout/src/lists.rs +++ b/crates/typst-layout/src/lists.rs @@ -4,11 +4,12 @@ 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::grid::resolve::{Cell, CellGrid}; 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}; +use crate::grid::GridLayouter; /// Layout the list. #[typst_macros::time(span = elem.span())] @@ -39,7 +40,7 @@ pub fn layout_list( let mut cells = vec![]; let mut locator = locator.split(); - for item in elem.children() { + 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(&()))); @@ -100,7 +101,7 @@ pub fn layout_enum( // relation to the item it refers to. let number_align = elem.number_align(styles); - for item in elem.children() { + for item in &elem.children { number = item.number(styles).unwrap_or(number); let context = Context::new(None, Some(styles)); diff --git a/crates/typst-layout/src/math/accent.rs b/crates/typst-layout/src/math/accent.rs index 4e26502e3..951870d68 100644 --- a/crates/typst-layout/src/math/accent.rs +++ b/crates/typst-layout/src/math/accent.rs @@ -1,12 +1,9 @@ use typst_library::diag::SourceResult; use typst_library::foundations::{Packed, StyleChain}; -use typst_library::layout::{Em, Frame, Point, Rel, Size}; +use typst_library::layout::{Em, Frame, Point, Size}; use typst_library::math::{Accent, AccentElem}; -use super::{ - scaled_font_size, style_cramped, FrameFragment, GlyphFragment, MathContext, - MathFragment, -}; +use super::{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); @@ -19,7 +16,7 @@ pub fn layout_accent( styles: StyleChain, ) -> SourceResult<()> { let cramped = style_cramped(); - let mut base = ctx.layout_into_fragment(elem.base(), styles.chain(&cramped))?; + let mut base = ctx.layout_into_fragment(&elem.base, styles.chain(&cramped))?; // Try to replace a glyph with its dotless variant. if let MathFragment::Glyph(glyph) = &mut base { @@ -30,14 +27,10 @@ pub fn layout_accent( 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()); + let width = elem.size(styles).relative_to(base.width()); - let Accent(c) = elem.accent(); - let mut glyph = GlyphFragment::new(ctx, styles, *c, elem.span()); + let Accent(c) = elem.accent; + let mut glyph = GlyphFragment::new(ctx, styles, c, elem.span()); // Try to replace accent glyph with flattened variant. let flattened_base_height = scaled!(ctx, styles, flattened_accent_base_height); @@ -75,7 +68,7 @@ pub fn layout_accent( frame.push_frame(accent_pos, accent); frame.push_frame(base_pos, base.into_frame()); ctx.push( - FrameFragment::new(ctx, styles, frame) + FrameFragment::new(styles, frame) .with_class(base_class) .with_base_ascent(base_ascent) .with_italics_correction(base_italics_correction) diff --git a/crates/typst-layout/src/math/attach.rs b/crates/typst-layout/src/math/attach.rs index 0f9090f77..8a67d53b3 100644 --- a/crates/typst-layout/src/math/attach.rs +++ b/crates/typst-layout/src/math/attach.rs @@ -1,6 +1,6 @@ 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::foundations::{Packed, StyleChain}; +use typst_library::layout::{Abs, Axis, Corner, Frame, Point, Rel, Size}; use typst_library::math::{ AttachElem, EquationElem, LimitsElem, PrimesElem, ScriptsElem, StretchElem, }; @@ -29,7 +29,7 @@ pub fn layout_attach( 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 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); @@ -95,7 +95,7 @@ pub fn layout_primes( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult<()> { - match *elem.count() { + match elem.count { count @ 1..=4 => { let c = match count { 1 => 'β€²', @@ -121,7 +121,7 @@ pub fn layout_primes( prime.clone(), ) } - ctx.push(FrameFragment::new(ctx, styles, frame).with_text_like(true)); + ctx.push(FrameFragment::new(styles, frame).with_text_like(true)); } } Ok(()) @@ -134,7 +134,7 @@ pub fn layout_scripts( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult<()> { - let mut fragment = ctx.layout_into_fragment(elem.body(), styles)?; + let mut fragment = ctx.layout_into_fragment(&elem.body, styles)?; fragment.set_limits(Limits::Never); ctx.push(fragment); Ok(()) @@ -148,21 +148,18 @@ pub fn layout_limits( styles: StyleChain, ) -> SourceResult<()> { let limits = if elem.inline(styles) { Limits::Always } else { Limits::Display }; - let mut fragment = ctx.layout_into_fragment(elem.body(), styles)?; + 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. -fn stretch_size( - styles: StyleChain, - elem: &Packed, -) -> Option>> { +/// Get the size to stretch the base to. +fn stretch_size(styles: StyleChain, elem: &Packed) -> Option> { // Extract from an EquationElem. - let mut base = elem.base(); - if let Some(equation) = base.to_packed::() { - base = equation.body(); + let mut base = &elem.base; + while let Some(equation) = base.to_packed::() { + base = &equation.body; } base.to_packed::().map(|stretch| stretch.size(styles)) @@ -277,7 +274,7 @@ fn layout_attachments( layout!(b, b_x, b_y); // lower-limit // Done! Note that we retain the class of the base. - ctx.push(FrameFragment::new(ctx, styles, frame).with_class(base_class)); + ctx.push(FrameFragment::new(styles, frame).with_class(base_class)); Ok(()) } diff --git a/crates/typst-layout/src/math/cancel.rs b/crates/typst-layout/src/math/cancel.rs index 994e0e2f7..9826397fa 100644 --- a/crates/typst-layout/src/math/cancel.rs +++ b/crates/typst-layout/src/math/cancel.rs @@ -7,7 +7,7 @@ use typst_library::text::TextElem; use typst_library::visualize::{FixedStroke, Geometry}; use typst_syntax::Span; -use super::{scaled_font_size, FrameFragment, MathContext}; +use super::{FrameFragment, MathContext}; /// Lays out a [`CancelElem`]. #[typst_macros::time(name = "math.cancel", span = elem.span())] @@ -16,7 +16,7 @@ pub fn layout_cancel( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult<()> { - let body = ctx.layout_into_fragment(elem.body(), styles)?; + let body = ctx.layout_into_fragment(&elem.body, styles)?; // Preserve properties of body. let body_class = body.class(); @@ -27,7 +27,7 @@ pub fn layout_cancel( 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 length = elem.length(styles); let stroke = elem.stroke(styles).unwrap_or(FixedStroke { paint: TextElem::fill_in(styles).as_decoration(), @@ -63,7 +63,7 @@ pub fn layout_cancel( } ctx.push( - FrameFragment::new(ctx, styles, body) + FrameFragment::new(styles, body) .with_class(body_class) .with_italics_correction(body_italics) .with_accent_attach(body_attach) diff --git a/crates/typst-layout/src/math/frac.rs b/crates/typst-layout/src/math/frac.rs index 50686333f..63463d761 100644 --- a/crates/typst-layout/src/math/frac.rs +++ b/crates/typst-layout/src/math/frac.rs @@ -1,5 +1,5 @@ use typst_library::diag::SourceResult; -use typst_library::foundations::{Content, Packed, StyleChain}; +use typst_library::foundations::{Content, Packed, Resolve, StyleChain}; use typst_library::layout::{Em, Frame, FrameItem, Point, Size}; use typst_library::math::{BinomElem, FracElem}; use typst_library::text::TextElem; @@ -7,8 +7,8 @@ use typst_library::visualize::{FixedStroke, Geometry}; use typst_syntax::Span; use super::{ - scaled_font_size, style_for_denominator, style_for_numerator, FrameFragment, - GlyphFragment, MathContext, DELIM_SHORT_FALL, + style_for_denominator, style_for_numerator, FrameFragment, GlyphFragment, + MathContext, DELIM_SHORT_FALL, }; const FRAC_AROUND: Em = Em::new(0.1); @@ -23,8 +23,8 @@ pub fn layout_frac( layout_frac_like( ctx, styles, - elem.num(), - std::slice::from_ref(elem.denom()), + &elem.num, + std::slice::from_ref(&elem.denom), false, elem.span(), ) @@ -37,7 +37,7 @@ pub fn layout_binom( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult<()> { - layout_frac_like(ctx, styles, elem.upper(), elem.lower(), true, elem.span()) + layout_frac_like(ctx, styles, &elem.upper, &elem.lower, true, elem.span()) } /// Layout a fraction or binomial. @@ -49,8 +49,7 @@ fn layout_frac_like( binom: bool, span: Span, ) -> SourceResult<()> { - let font_size = scaled_font_size(ctx, styles); - let short_fall = DELIM_SHORT_FALL.at(font_size); + let short_fall = DELIM_SHORT_FALL.resolve(styles); let axis = scaled!(ctx, styles, axis_height); let thickness = scaled!(ctx, styles, fraction_rule_thickness); let shift_up = scaled!( @@ -86,7 +85,7 @@ fn layout_frac_like( styles.chain(&denom_style), )?; - let around = FRAC_AROUND.at(font_size); + let around = FRAC_AROUND.resolve(styles); let num_gap = (shift_up - (axis + thickness / 2.0) - num.descent()).max(num_min); let denom_gap = (shift_down + (axis - thickness / 2.0) - denom.ascent()).max(denom_min); @@ -111,7 +110,7 @@ fn layout_frac_like( .stretch_vertical(ctx, height, short_fall); left.center_on_axis(ctx); ctx.push(left); - ctx.push(FrameFragment::new(ctx, styles, frame)); + ctx.push(FrameFragment::new(styles, frame)); let mut right = GlyphFragment::new(ctx, styles, ')', span) .stretch_vertical(ctx, height, short_fall); right.center_on_axis(ctx); @@ -129,7 +128,7 @@ fn layout_frac_like( span, ), ); - ctx.push(FrameFragment::new(ctx, styles, frame)); + ctx.push(FrameFragment::new(styles, frame)); } Ok(()) diff --git a/crates/typst-layout/src/math/fragment.rs b/crates/typst-layout/src/math/fragment.rs index ac8946681..a0453c14f 100644 --- a/crates/typst-layout/src/math/fragment.rs +++ b/crates/typst-layout/src/math/fragment.rs @@ -17,7 +17,7 @@ use typst_library::visualize::Paint; use typst_syntax::Span; use unicode_math_class::MathClass; -use super::{scaled_font_size, stretch_glyph, MathContext, Scaled}; +use super::{stretch_glyph, MathContext, Scaled}; #[derive(Debug, Clone)] pub enum MathFragment { @@ -292,7 +292,7 @@ impl GlyphFragment { region: TextElem::region_in(styles), fill: TextElem::fill_in(styles).as_decoration(), shift: TextElem::baseline_in(styles), - font_size: scaled_font_size(ctx, styles), + font_size: TextElem::size_in(styles), math_size: EquationElem::size_in(styles), width: Abs::zero(), ascent: Abs::zero(), @@ -512,12 +512,12 @@ pub struct FrameFragment { } impl FrameFragment { - pub fn new(ctx: &MathContext, styles: StyleChain, frame: Frame) -> Self { + pub fn new(styles: StyleChain, frame: Frame) -> Self { let base_ascent = frame.ascent(); let accent_attach = frame.width() / 2.0; Self { frame: frame.post_processed(styles), - font_size: scaled_font_size(ctx, styles), + font_size: TextElem::size_in(styles), class: EquationElem::class_in(styles).unwrap_or(MathClass::Normal), math_size: EquationElem::size_in(styles), limits: Limits::Never, diff --git a/crates/typst-layout/src/math/lr.rs b/crates/typst-layout/src/math/lr.rs index 01a7f4ccd..19176ee88 100644 --- a/crates/typst-layout/src/math/lr.rs +++ b/crates/typst-layout/src/math/lr.rs @@ -1,6 +1,6 @@ use typst_library::diag::SourceResult; -use typst_library::foundations::{Packed, Smart, StyleChain}; -use typst_library::layout::{Abs, Axis, Length, Rel}; +use typst_library::foundations::{Packed, StyleChain}; +use typst_library::layout::{Abs, Axis, Rel}; use typst_library::math::{EquationElem, LrElem, MidElem}; use unicode_math_class::MathClass; @@ -13,17 +13,16 @@ pub fn layout_lr( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult<()> { - let mut body = elem.body(); - // Extract from an EquationElem. + let mut body = &elem.body; if let Some(equation) = body.to_packed::() { - body = equation.body(); + body = &equation.body; } // Extract implicit LrElem. if let Some(lr) = body.to_packed::() { - if lr.size(styles).is_auto() { - body = lr.body(); + if lr.size(styles).is_one() { + body = &lr.body; } } @@ -100,7 +99,7 @@ pub fn layout_mid( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult<()> { - let mut fragments = ctx.layout_into_fragments(elem.body(), styles)?; + let mut fragments = ctx.layout_into_fragments(&elem.body, styles)?; for fragment in &mut fragments { match fragment { @@ -128,7 +127,7 @@ fn scale( styles: StyleChain, fragment: &mut MathFragment, relative_to: Abs, - height: Smart>, + height: Rel, apply: Option, ) { if matches!( diff --git a/crates/typst-layout/src/math/mat.rs b/crates/typst-layout/src/math/mat.rs index 24104f4ee..bf4929026 100644 --- a/crates/typst-layout/src/math/mat.rs +++ b/crates/typst-layout/src/math/mat.rs @@ -1,5 +1,5 @@ use typst_library::diag::{bail, SourceResult}; -use typst_library::foundations::{Content, Packed, StyleChain}; +use typst_library::foundations::{Content, Packed, Resolve, StyleChain}; use typst_library::layout::{ Abs, Axes, Em, FixedAlignment, Frame, FrameItem, Point, Ratio, Rel, Size, }; @@ -9,9 +9,8 @@ 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, + alignments, delimiter_alignment, stack, style_for_denominator, AlignmentResult, + FrameFragment, GlyphFragment, LeftRightAlternator, MathContext, DELIM_SHORT_FALL, }; const VERTICAL_PADDING: Ratio = Ratio::new(0.1); @@ -28,9 +27,9 @@ pub fn layout_vec( let frame = layout_vec_body( ctx, styles, - elem.children(), + &elem.children, elem.align(styles), - elem.gap(styles).at(scaled_font_size(ctx, styles)), + elem.gap(styles), LeftRightAlternator::Right, )?; @@ -45,7 +44,7 @@ pub fn layout_mat( styles: StyleChain, ) -> SourceResult<()> { let augment = elem.augment(styles); - let rows = elem.rows(); + let rows = &elem.rows; if let Some(aug) = &augment { for &offset in &aug.hline.0 { @@ -59,7 +58,7 @@ pub fn layout_mat( } } - let ncols = elem.rows().first().map_or(0, |row| row.len()); + let ncols = rows.first().map_or(0, |row| row.len()); for &offset in &aug.vline.0 { if offset == 0 || offset.unsigned_abs() >= ncols { @@ -73,9 +72,6 @@ pub fn layout_mat( } } - 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, @@ -83,7 +79,7 @@ pub fn layout_mat( rows, elem.align(styles), augment, - Axes::new(column_gap, row_gap), + Axes::new(elem.column_gap(styles), elem.row_gap(styles)), elem.span(), )?; @@ -101,9 +97,9 @@ pub fn layout_cases( let frame = layout_vec_body( ctx, styles, - elem.children(), + &elem.children, FixedAlignment::Start, - elem.gap(styles).at(scaled_font_size(ctx, styles)), + elem.gap(styles), LeftRightAlternator::None, )?; @@ -162,8 +158,7 @@ fn layout_mat_body( // 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_thickness = DEFAULT_STROKE_THICKNESS.resolve(styles); let default_stroke = FixedStroke { thickness: default_stroke_thickness, paint: TextElem::fill_in(styles).as_decoration(), @@ -308,9 +303,8 @@ fn layout_delimiters( right: Option, 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 short_fall = DELIM_SHORT_FALL.resolve(styles); + let axis = scaled!(ctx, styles, axis_height); let height = frame.height(); let target = height + VERTICAL_PADDING.of(height); frame.set_baseline(height / 2.0 + axis); @@ -322,7 +316,7 @@ fn layout_delimiters( ctx.push(left); } - ctx.push(FrameFragment::new(ctx, styles, frame)); + ctx.push(FrameFragment::new(styles, frame)); if let Some(right) = right { let mut right = GlyphFragment::new(ctx, styles, right, span) diff --git a/crates/typst-layout/src/math/mod.rs b/crates/typst-layout/src/math/mod.rs index e642f6338..06dc6653b 100644 --- a/crates/typst-layout/src/math/mod.rs +++ b/crates/typst-layout/src/math/mod.rs @@ -28,8 +28,7 @@ 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, + families, features, variant, Font, LinebreakElem, SpaceElem, TextEdgeBounds, TextElem, }; use typst_library::World; use typst_syntax::Span; @@ -58,12 +57,16 @@ pub fn layout_equation_inline( let mut locator = locator.split(); let mut ctx = MathContext::new(engine, &mut locator, styles, region, &font); + + let scale_style = style_for_script_scale(&ctx); + let styles = styles.chain(&scale_style); + 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())] + vec![InlineItem::Frame(run.into_fragment(styles).into_frame())] }; // An empty equation should have a height, so we still create a frame @@ -75,13 +78,12 @@ pub fn layout_equation_inline( 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, + TextElem::size_in(styles), TextEdgeBounds::Frame(frame), ); @@ -110,9 +112,13 @@ pub fn layout_equation_block( let mut locator = locator.split(); let mut ctx = MathContext::new(engine, &mut locator, styles, regions.base(), &font); + + let scale_style = style_for_script_scale(&ctx); + let styles = styles.chain(&scale_style); + let full_equation_builder = ctx .layout_into_run(&elem.body, styles)? - .multiline_frame_builder(&ctx, styles); + .multiline_frame_builder(styles); let width = full_equation_builder.size.x; let equation_builders = if BlockElem::breakable_in(styles) { @@ -469,7 +475,7 @@ impl<'a, 'v, 'e> MathContext<'a, 'v, 'e> { elem: &Content, styles: StyleChain, ) -> SourceResult { - Ok(self.layout_into_run(elem, styles)?.into_fragment(self, styles)) + Ok(self.layout_into_run(elem, styles)?.into_fragment(styles)) } /// Layout the given element and return the result as a [`Frame`]. @@ -502,7 +508,7 @@ impl<'a, 'v, 'e> MathContext<'a, 'v, 'e> { // 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)); + self.push(FrameFragment::new(styles, frame).with_spaced(true)); continue; } @@ -522,8 +528,7 @@ fn layout_realized( if let Some(elem) = elem.to_packed::() { ctx.push(MathFragment::Tag(elem.tag.clone())); } else if elem.is::() { - let font_size = scaled_font_size(ctx, styles); - ctx.push(MathFragment::Space(ctx.space_width.at(font_size))); + ctx.push(MathFragment::Space(ctx.space_width.resolve(styles))); } else if elem.is::() { ctx.push(MathFragment::Linebreak); } else if let Some(elem) = elem.to_packed::() { @@ -595,7 +600,7 @@ fn layout_realized( frame.set_baseline(frame.height() / 2.0 + axis); } ctx.push( - FrameFragment::new(ctx, styles, frame) + FrameFragment::new(styles, frame) .with_spaced(true) .with_ignorant(elem.is::()), ); @@ -610,15 +615,14 @@ fn layout_box( 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), + styles, ctx.region.size, )?; - ctx.push(FrameFragment::new(ctx, styles, frame).with_spaced(true)); + ctx.push(FrameFragment::new(styles, frame).with_spaced(true)); Ok(()) } @@ -628,12 +632,9 @@ fn layout_h( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult<()> { - if let Spacing::Rel(rel) = elem.amount() { + 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), - )); + ctx.push(MathFragment::Spacing(rel.abs.resolve(styles), elem.weak(styles))); } } Ok(()) @@ -646,11 +647,10 @@ fn layout_class( 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)); + let style = EquationElem::set_class(Some(elem.class)).wrap(); + let mut fragment = ctx.layout_into_fragment(&elem.body, styles.chain(&style))?; + fragment.set_class(elem.class); + fragment.set_limits(Limits::for_class(elem.class)); ctx.push(fragment); Ok(()) } @@ -662,13 +662,13 @@ fn layout_op( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult<()> { - let fragment = ctx.layout_into_fragment(elem.text(), styles)?; + 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()) + FrameFragment::new(styles, fragment.into_frame()) .with_class(MathClass::Large) .with_italics_correction(italics) .with_accent_attach(accent_attach) @@ -688,12 +688,11 @@ fn layout_external( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult { - 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), + styles, ctx.region, ) } diff --git a/crates/typst-layout/src/math/root.rs b/crates/typst-layout/src/math/root.rs index 0bb2f5393..a6b5c03d0 100644 --- a/crates/typst-layout/src/math/root.rs +++ b/crates/typst-layout/src/math/root.rs @@ -18,7 +18,6 @@ pub fn layout_root( styles: StyleChain, ) -> SourceResult<()> { let index = elem.index(styles); - let radicand = elem.radicand(); let span = elem.span(); let gap = scaled!( @@ -36,9 +35,9 @@ pub fn layout_root( let radicand = { let cramped = style_cramped(); let styles = styles.chain(&cramped); - let run = ctx.layout_into_run(radicand, styles)?; + let run = ctx.layout_into_run(&elem.radicand, styles)?; let multiline = run.is_multiline(); - let mut radicand = run.into_fragment(ctx, styles).into_frame(); + let mut radicand = run.into_fragment(styles).into_frame(); if multiline { // Align the frame center line with the math axis. radicand.set_baseline( @@ -120,7 +119,7 @@ pub fn layout_root( ); frame.push_frame(radicand_pos, radicand); - ctx.push(FrameFragment::new(ctx, styles, frame)); + ctx.push(FrameFragment::new(styles, frame)); Ok(()) } diff --git a/crates/typst-layout/src/math/run.rs b/crates/typst-layout/src/math/run.rs index b07f5893a..ae64368d6 100644 --- a/crates/typst-layout/src/math/run.rs +++ b/crates/typst-layout/src/math/run.rs @@ -6,7 +6,7 @@ use typst_library::math::{EquationElem, MathSize, MEDIUM, THICK, THIN}; use typst_library::model::ParElem; use unicode_math_class::MathClass; -use super::{alignments, scaled_font_size, FrameFragment, MathContext, MathFragment}; +use super::{alignments, FrameFragment, MathFragment}; const TIGHT_LEADING: Em = Em::new(0.25); @@ -161,15 +161,15 @@ impl MathRun { } } - pub fn into_frame(self, ctx: &MathContext, styles: StyleChain) -> Frame { + pub fn into_frame(self, styles: StyleChain) -> Frame { if !self.is_multiline() { self.into_line_frame(&[], LeftRightAlternator::Right) } else { - self.multiline_frame_builder(ctx, styles).build() + self.multiline_frame_builder(styles).build() } } - pub fn into_fragment(self, ctx: &MathContext, styles: StyleChain) -> MathFragment { + pub fn into_fragment(self, styles: StyleChain) -> MathFragment { if self.0.len() == 1 { return self.0.into_iter().next().unwrap(); } @@ -181,7 +181,7 @@ impl MathRun { .filter(|e| e.math_size().is_some()) .all(|e| e.is_text_like()); - FrameFragment::new(ctx, styles, self.into_frame(ctx, styles)) + FrameFragment::new(styles, self.into_frame(styles)) .with_text_like(text_like) .into() } @@ -189,11 +189,7 @@ impl MathRun { /// Returns a builder that lays out the [`MathFragment`]s into a possibly /// multi-row [`Frame`]. The rows are aligned using the same set of alignment /// points computed from them as a whole. - pub fn multiline_frame_builder( - self, - ctx: &MathContext, - styles: StyleChain, - ) -> MathRunFrameBuilder { + pub fn multiline_frame_builder(self, styles: StyleChain) -> MathRunFrameBuilder { let rows: Vec<_> = self.rows(); let row_count = rows.len(); let alignments = alignments(&rows); @@ -201,8 +197,7 @@ impl MathRun { let leading = if EquationElem::size_in(styles) >= MathSize::Text { ParElem::leading_in(styles) } else { - let font_size = scaled_font_size(ctx, styles); - TIGHT_LEADING.at(font_size) + TIGHT_LEADING.resolve(styles) }; let align = AlignElem::alignment_in(styles).resolve(styles).x; diff --git a/crates/typst-layout/src/math/shared.rs b/crates/typst-layout/src/math/shared.rs index 74e62e8f0..5aebdacac 100644 --- a/crates/typst-layout/src/math/shared.rs +++ b/crates/typst-layout/src/math/shared.rs @@ -2,7 +2,6 @@ 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}; @@ -18,7 +17,7 @@ macro_rules! scaled { $crate::math::Scaled::scaled( $ctx.constants.$name(), $ctx, - $crate::math::scaled_font_size($ctx, $styles), + typst_library::text::TextElem::size_in($styles), ) }; } @@ -55,16 +54,6 @@ impl Scaled for MathValue<'_> { } } -/// 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