diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41f17d137..c5c81537b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: sudo dpkg --add-architecture i386 sudo apt update sudo apt install -y gcc-multilib libssl-dev:i386 pkg-config:i386 - - uses: dtolnay/rust-toolchain@1.85.0 + - uses: dtolnay/rust-toolchain@1.87.0 with: targets: ${{ matrix.bits == 32 && 'i686-unknown-linux-gnu' || '' }} - uses: Swatinem/rust-cache@v2 @@ -73,7 +73,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@1.85.0 + - uses: dtolnay/rust-toolchain@1.87.0 with: components: clippy, rustfmt - uses: Swatinem/rust-cache@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0d235aec5..ca317abd0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@1.85.0 + - uses: dtolnay/rust-toolchain@1.87.0 with: target: ${{ matrix.target }} diff --git a/Cargo.lock b/Cargo.lock index ab2d2cc83..a9b3756a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -508,9 +508,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -1215,6 +1215,7 @@ dependencies = [ "byteorder-lite", "color_quant", "gif", + "image-webp", "num-traits", "png", "zune-core", @@ -1259,6 +1260,12 @@ dependencies = [ "serde", ] +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" + [[package]] name = "inotify" version = "0.11.0" @@ -2857,7 +2864,7 @@ dependencies = [ [[package]] name = "typst-assets" version = "0.13.1" -source = "git+https://github.com/typst/typst-assets?rev=ab1295f#ab1295ff896444e51902e03c2669955e1d73604a" +source = "git+https://github.com/typst/typst-assets?rev=c74e539#c74e539b090070a0c66fd007c550f5b6d3b724bd" [[package]] name = "typst-cli" @@ -3127,6 +3134,7 @@ dependencies = [ "comemo", "ecow", "image", + "infer", "krilla", "krilla-svg", "serde", diff --git a/Cargo.toml b/Cargo.toml index 12870b809..b4890e3c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ typst-svg = { path = "crates/typst-svg", version = "0.13.1" } typst-syntax = { path = "crates/typst-syntax", version = "0.13.1" } typst-timing = { path = "crates/typst-timing", version = "0.13.1" } typst-utils = { path = "crates/typst-utils", version = "0.13.1" } -typst-assets = { git = "https://github.com/typst/typst-assets", rev = "ab1295f" } +typst-assets = { git = "https://github.com/typst/typst-assets", rev = "c74e539" } typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "fddbf8b" } arrayvec = "0.7.4" az = "1.2" @@ -69,8 +69,9 @@ icu_provider_adapters = "1.4" icu_provider_blob = "1.4" icu_segmenter = { version = "1.4", features = ["serde"] } if_chain = "1" -image = { version = "0.25.5", default-features = false, features = ["png", "jpeg", "gif"] } +image = { version = "0.25.5", default-features = false, features = ["png", "jpeg", "gif", "webp"] } indexmap = { version = "2", features = ["serde"] } +infer = { version = "0.19.0", default-features = false } kamadak-exif = "0.6" krilla = { version = "0.4.0", default-features = false, features = ["raster-images", "comemo", "rayon"] } krilla-svg = "0.1.0" diff --git a/crates/typst-eval/src/call.rs b/crates/typst-eval/src/call.rs index 1ca7b4b8f..eaeabbab3 100644 --- a/crates/typst-eval/src/call.rs +++ b/crates/typst-eval/src/call.rs @@ -25,19 +25,22 @@ impl Eval for ast::FuncCall<'_> { fn eval(self, vm: &mut Vm) -> SourceResult { let span = self.span(); let callee = self.callee(); - let in_math = in_math(callee); let callee_span = callee.span(); let args = self.args(); - let trailing_comma = args.trailing_comma(); vm.engine.route.check_call_depth().at(span)?; // Try to evaluate as a call to an associated function or field. - let (callee, args) = if let ast::Expr::FieldAccess(access) = callee { + let (callee_value, args_value) = if let ast::Expr::FieldAccess(access) = callee { let target = access.target(); let field = access.field(); match eval_field_call(target, field, args, span, vm)? { - FieldCall::Normal(callee, args) => (callee, args), + FieldCall::Normal(callee, args) => { + if vm.inspected == Some(callee_span) { + vm.trace(callee.clone()); + } + (callee, args) + } FieldCall::Resolved(value) => return Ok(value), } } else { @@ -45,9 +48,15 @@ impl Eval for ast::FuncCall<'_> { (callee.eval(vm)?, args.eval(vm)?.spanned(span)) }; - let func_result = callee.clone().cast::(); - if in_math && func_result.is_err() { - return wrap_args_in_math(callee, callee_span, args, trailing_comma); + let func_result = callee_value.clone().cast::(); + + if func_result.is_err() && in_math(callee) { + return wrap_args_in_math( + callee_value, + callee_span, + args_value, + args.trailing_comma(), + ); } let func = func_result @@ -56,8 +65,11 @@ impl Eval for ast::FuncCall<'_> { let point = || Tracepoint::Call(func.name().map(Into::into)); let f = || { - func.call(&mut vm.engine, vm.context, args) - .trace(vm.world(), point, span) + func.call(&mut vm.engine, vm.context, args_value).trace( + vm.world(), + point, + span, + ) }; // Stacker is broken on WASM. @@ -404,12 +416,14 @@ fn wrap_args_in_math( if trailing_comma { body += SymbolElem::packed(','); } - Ok(Value::Content( - callee.display().spanned(callee_span) - + LrElem::new(SymbolElem::packed('(') + body + SymbolElem::packed(')')) - .pack() - .spanned(args.span), - )) + + let formatted = callee.display().spanned(callee_span) + + LrElem::new(SymbolElem::packed('(') + body + SymbolElem::packed(')')) + .pack() + .spanned(args.span); + + args.finish()?; + Ok(Value::Content(formatted)) } /// Provide a hint if the callee is a shadowed standard library function. diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index 91fa53f9a..4a36045ae 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -15,7 +15,7 @@ use typst::syntax::{ ast, is_id_continue, is_id_start, is_ident, FileId, LinkedNode, Side, Source, SyntaxKind, }; -use typst::text::RawElem; +use typst::text::{FontFlags, RawElem}; use typst::visualize::Color; use unscanny::Scanner; @@ -841,7 +841,9 @@ fn param_value_completions<'a>( /// 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("image"), "source") => { + &["png", "jpg", "jpeg", "gif", "svg", "svgz", "webp"] + } (Some("csv"), "source") => &["csv"], (Some("plugin"), "source") => &["wasm"], (Some("cbor"), "source") => &["cbor"], @@ -1081,6 +1083,24 @@ fn code_completions(ctx: &mut CompletionContext, hash: bool) { } } +/// See if the AST node is somewhere within a show rule applying to equations +fn is_in_equation_show_rule(leaf: &LinkedNode<'_>) -> bool { + let mut node = leaf; + while let Some(parent) = node.parent() { + if_chain! { + if let Some(expr) = parent.get().cast::(); + if let ast::Expr::ShowRule(show) = expr; + if let Some(ast::Expr::FieldAccess(field)) = show.selector(); + if field.field().as_str() == "equation"; + then { + return true; + } + } + node = parent; + } + false +} + /// Context for autocompletion. struct CompletionContext<'a> { world: &'a (dyn IdeWorld + 'a), @@ -1152,10 +1172,12 @@ impl<'a> CompletionContext<'a> { /// Add completions for all font families. fn font_completions(&mut self) { - let equation = self.before_window(25).contains("equation"); + let equation = is_in_equation_show_rule(self.leaf); for (family, iter) in self.world.book().families() { - let detail = summarize_font_family(iter); - if !equation || family.contains("Math") { + let variants: Vec<_> = iter.collect(); + let is_math = variants.iter().any(|f| f.flags.contains(FontFlags::MATH)); + let detail = summarize_font_family(variants); + if !equation || is_math { self.str_completion( family, Some(CompletionKind::Font), @@ -1790,4 +1812,21 @@ mod tests { .must_include(["r", "dashed"]) .must_exclude(["cases"]); } + + #[test] + fn test_autocomplete_fonts() { + test("#text(font:)", -1) + .must_include(["\"Libertinus Serif\"", "\"New Computer Modern Math\""]); + + test("#show link: set text(font: )", -1) + .must_include(["\"Libertinus Serif\"", "\"New Computer Modern Math\""]); + + test("#show math.equation: set text(font: )", -1) + .must_include(["\"New Computer Modern Math\""]) + .must_exclude(["\"Libertinus Serif\""]); + + test("#show math.equation: it => { set text(font: )\nit }", -6) + .must_include(["\"New Computer Modern Math\""]) + .must_exclude(["\"Libertinus Serif\""]); + } } diff --git a/crates/typst-ide/src/tooltip.rs b/crates/typst-ide/src/tooltip.rs index cbfffe530..528f679cf 100644 --- a/crates/typst-ide/src/tooltip.rs +++ b/crates/typst-ide/src/tooltip.rs @@ -86,7 +86,7 @@ fn expr_tooltip(world: &dyn IdeWorld, leaf: &LinkedNode) -> Option { *count += 1; continue; } else if *count > 1 { - write!(pieces.last_mut().unwrap(), " (x{count})").unwrap(); + write!(pieces.last_mut().unwrap(), " (×{count})").unwrap(); } } pieces.push(value.repr()); @@ -95,7 +95,7 @@ fn expr_tooltip(world: &dyn IdeWorld, leaf: &LinkedNode) -> Option { if let Some((_, count)) = last { if count > 1 { - write!(pieces.last_mut().unwrap(), " (x{count})").unwrap(); + write!(pieces.last_mut().unwrap(), " (×{count})").unwrap(); } } @@ -269,7 +269,7 @@ fn font_tooltip(world: &dyn IdeWorld, leaf: &LinkedNode) -> Option { .find(|&(family, _)| family.to_lowercase().as_str() == lower.as_str()); then { - let detail = summarize_font_family(iter); + let detail = summarize_font_family(iter.collect()); return Some(Tooltip::Text(detail)); } }; @@ -371,4 +371,11 @@ mod tests { test(&world, -2, Side::Before).must_be_none(); test(&world, -2, Side::After).must_be_text("This star imports `a`, `b`, and `c`"); } + + #[test] + fn test_tooltip_field_call() { + let world = TestWorld::new("#import \"other.typ\"\n#other.f()") + .with_source("other.typ", "#let f = (x) => 1"); + test(&world, -4, Side::After).must_be_code("(..) => .."); + } } diff --git a/crates/typst-ide/src/utils.rs b/crates/typst-ide/src/utils.rs index d5d584e2b..887e851f9 100644 --- a/crates/typst-ide/src/utils.rs +++ b/crates/typst-ide/src/utils.rs @@ -77,23 +77,20 @@ pub fn plain_docs_sentence(docs: &str) -> EcoString { } /// Create a short description of a font family. -pub fn summarize_font_family<'a>( - variants: impl Iterator, -) -> EcoString { - let mut infos: Vec<_> = variants.collect(); - infos.sort_by_key(|info| info.variant); +pub fn summarize_font_family(mut variants: Vec<&FontInfo>) -> EcoString { + variants.sort_by_key(|info| info.variant); let mut has_italic = false; let mut min_weight = u16::MAX; let mut max_weight = 0; - for info in &infos { + for info in &variants { let weight = info.variant.weight.to_number(); has_italic |= info.variant.style == FontStyle::Italic; min_weight = min_weight.min(weight); max_weight = min_weight.max(weight); } - let count = infos.len(); + let count = variants.len(); let mut detail = eco_format!("{count} variant{}.", if count == 1 { "" } else { "s" }); if min_weight == max_weight { diff --git a/crates/typst-kit/src/download.rs b/crates/typst-kit/src/download.rs index 40084e51b..a4d49b4f3 100644 --- a/crates/typst-kit/src/download.rs +++ b/crates/typst-kit/src/download.rs @@ -128,8 +128,7 @@ impl Downloader { } // Configure native TLS. - let connector = - tls.build().map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; + let connector = tls.build().map_err(io::Error::other)?; builder = builder.tls_connector(Arc::new(connector)); builder.build().get(url).call() diff --git a/crates/typst-layout/src/grid/layouter.rs b/crates/typst-layout/src/grid/layouter.rs index dc9e2238d..99b85eddb 100644 --- a/crates/typst-layout/src/grid/layouter.rs +++ b/crates/typst-layout/src/grid/layouter.rs @@ -11,7 +11,7 @@ use typst_library::layout::{ use typst_library::text::TextElem; use typst_library::visualize::Geometry; use typst_syntax::Span; -use typst_utils::{MaybeReverseIter, Numeric}; +use typst_utils::Numeric; use super::{ generate_line_segments, hline_stroke_at_column, layout_cell, vline_stroke_at_row, @@ -574,7 +574,7 @@ impl<'a> GridLayouter<'a> { // Reverse with RTL so that later columns start first. let mut dx = Abs::zero(); - for (x, &col) in self.rcols.iter().enumerate().rev_if(self.is_rtl) { + for (x, &col) in self.rcols.iter().enumerate() { let mut dy = Abs::zero(); for row in rows { // We want to only draw the fill starting at the parent @@ -643,18 +643,13 @@ impl<'a> GridLayouter<'a> { .sum() }; let width = self.cell_spanned_width(cell, x); - // In the grid, cell colspans expand to the right, - // so we're at the leftmost (lowest 'x') column - // spanned by the cell. However, in RTL, cells - // expand to the left. Therefore, without the - // offset below, cell fills would start at the - // rightmost visual position of a cell and extend - // over to unrelated columns to the right in RTL. - // We avoid this by ensuring the fill starts at the - // very left of the cell, even with colspan > 1. - let offset = - if self.is_rtl { -width + col } else { Abs::zero() }; - let pos = Point::new(dx + offset, dy); + let mut pos = Point::new(dx, dy); + if self.is_rtl { + // In RTL cells expand to the left, thus the + // position must additionally be offset by the + // cell's width. + pos.x = self.width - (dx + width); + } let size = Size::new(width, height); let rect = Geometry::Rect(size).filled(fill); fills.push((pos, FrameItem::Shape(rect, self.span))); @@ -1236,10 +1231,9 @@ impl<'a> GridLayouter<'a> { } let mut output = Frame::soft(Size::new(self.width, height)); - let mut pos = Point::zero(); + let mut offset = Point::zero(); - // Reverse the column order when using RTL. - for (x, &rcol) in self.rcols.iter().enumerate().rev_if(self.is_rtl) { + for (x, &rcol) in self.rcols.iter().enumerate() { if let Some(cell) = self.grid.cell(x, y) { // Rowspans have a separate layout step if cell.rowspan.get() == 1 { @@ -1257,25 +1251,17 @@ impl<'a> GridLayouter<'a> { let frame = layout_cell(cell, engine, disambiguator, self.styles, pod)? .into_frame(); - let mut pos = pos; + let mut pos = offset; if self.is_rtl { - // In the grid, cell colspans expand to the right, - // so we're at the leftmost (lowest 'x') column - // spanned by the cell. However, in RTL, cells - // expand to the left. Therefore, without the - // offset below, the cell's contents would be laid out - // starting at its rightmost visual position and extend - // over to unrelated cells to its right in RTL. - // We avoid this by ensuring the rendered cell starts at - // the very left of the cell, even with colspan > 1. - let offset = -width + rcol; - pos.x += offset; + // In RTL cells expand to the left, thus the position + // must additionally be offset by the cell's width. + pos.x = self.width - (pos.x + width); } output.push_frame(pos, frame); } } - pos.x += rcol; + offset.x += rcol; } Ok(output) @@ -1302,8 +1288,8 @@ impl<'a> GridLayouter<'a> { pod.backlog = &heights[1..]; // Layout the row. - let mut pos = Point::zero(); - for (x, &rcol) in self.rcols.iter().enumerate().rev_if(self.is_rtl) { + let mut offset = Point::zero(); + for (x, &rcol) in self.rcols.iter().enumerate() { if let Some(cell) = self.grid.cell(x, y) { // Rowspans have a separate layout step if cell.rowspan.get() == 1 { @@ -1314,17 +1300,19 @@ impl<'a> GridLayouter<'a> { let fragment = layout_cell(cell, engine, disambiguator, self.styles, pod)?; for (output, frame) in outputs.iter_mut().zip(fragment) { - let mut pos = pos; + let mut pos = offset; if self.is_rtl { - let offset = -width + rcol; - pos.x += offset; + // In RTL cells expand to the left, thus the + // position must additionally be offset by the + // cell's width. + pos.x = self.width - (offset.x + width); } output.push_frame(pos, frame); } } } - pos.x += rcol; + offset.x += rcol; } Ok(Fragment::frames(outputs)) diff --git a/crates/typst-layout/src/grid/rowspans.rs b/crates/typst-layout/src/grid/rowspans.rs index 21992ed02..5ab0417d8 100644 --- a/crates/typst-layout/src/grid/rowspans.rs +++ b/crates/typst-layout/src/grid/rowspans.rs @@ -3,7 +3,6 @@ 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::{layout_cell, Cell, GridLayouter}; @@ -23,6 +22,10 @@ pub struct Rowspan { /// specified for the parent cell's `breakable` field. pub is_effectively_unbreakable: bool, /// The horizontal offset of this rowspan in all regions. + /// + /// This is the offset from the text direction start, meaning that, on RTL + /// grids, this is the offset from the right of the grid, whereas, on LTR + /// grids, it is the offset from the left. pub dx: Abs, /// The vertical offset of this rowspan in the first region. pub dy: Abs, @@ -118,10 +121,11 @@ impl GridLayouter<'_> { // Nothing to layout. return Ok(()); }; - let first_column = self.rcols[x]; let cell = self.grid.cell(x, y).unwrap(); let width = self.cell_spanned_width(cell, x); - let dx = if self.is_rtl { dx - width + first_column } else { dx }; + // In RTL cells expand to the left, thus the position + // must additionally be offset by the cell's width. + let dx = if self.is_rtl { self.width - (dx + width) } else { dx }; // Prepare regions. let size = Size::new(width, *first_height); @@ -185,10 +189,8 @@ impl GridLayouter<'_> { /// Checks if a row contains the beginning of one or more rowspan cells. /// If so, adds them to the rowspans vector. 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)); - for (x, dx) in (0..self.rcols.len()).rev_if(self.is_rtl).zip(offsets) { + let offsets = points(self.rcols.iter().copied()); + for (x, dx) in (0..self.rcols.len()).zip(offsets) { let Some(cell) = self.grid.cell(x, y) else { continue; }; diff --git a/crates/typst-layout/src/image.rs b/crates/typst-layout/src/image.rs index 3e5b7d8bd..8136a25a3 100644 --- a/crates/typst-layout/src/image.rs +++ b/crates/typst-layout/src/image.rs @@ -147,6 +147,7 @@ fn determine_format(source: &DataSource, data: &Bytes) -> StrResult "jpg" | "jpeg" => return Ok(ExchangeFormat::Jpg.into()), "gif" => return Ok(ExchangeFormat::Gif.into()), "svg" | "svgz" => return Ok(VectorFormat::Svg.into()), + "webp" => return Ok(ExchangeFormat::Webp.into()), _ => {} } } diff --git a/crates/typst-layout/src/inline/linebreak.rs b/crates/typst-layout/src/inline/linebreak.rs index 31512604f..ada048c7d 100644 --- a/crates/typst-layout/src/inline/linebreak.rs +++ b/crates/typst-layout/src/inline/linebreak.rs @@ -690,13 +690,34 @@ fn breakpoints(p: &Preparation, mut f: impl FnMut(usize, Breakpoint)) { let breakpoint = if point == text.len() { Breakpoint::Mandatory } else { + const OBJ_REPLACE: char = '\u{FFFC}'; match lb.get(c) { - // Fix for: https://github.com/unicode-org/icu4x/issues/4146 - LineBreak::Glue | LineBreak::WordJoiner | LineBreak::ZWJ => continue, LineBreak::MandatoryBreak | LineBreak::CarriageReturn | LineBreak::LineFeed | LineBreak::NextLine => Breakpoint::Mandatory, + + // https://github.com/typst/typst/issues/5489 + // + // OBJECT-REPLACEMENT-CHARACTERs provide Contingent Break + // opportunities before and after by default. This behaviour + // is however tailorable, see: + // https://www.unicode.org/reports/tr14/#CB + // https://www.unicode.org/reports/tr14/#TailorableBreakingRules + // https://www.unicode.org/reports/tr14/#LB20 + // + // Don't provide a line breaking opportunity between a LTR- + // ISOLATE (or any other Combining Mark) and an OBJECT- + // REPLACEMENT-CHARACTER representing an inline item, if the + // LTR-ISOLATE could end up as the only character on the + // previous line. + LineBreak::CombiningMark + if text[point..].starts_with(OBJ_REPLACE) + && last + c.len_utf8() == point => + { + continue; + } + _ => Breakpoint::Normal, } }; diff --git a/crates/typst-layout/src/inline/shaping.rs b/crates/typst-layout/src/inline/shaping.rs index 7f16bbd0d..dabf59e34 100644 --- a/crates/typst-layout/src/inline/shaping.rs +++ b/crates/typst-layout/src/inline/shaping.rs @@ -21,7 +21,7 @@ use unicode_bidi::{BidiInfo, Level as BidiLevel}; use unicode_script::{Script, UnicodeScript}; use super::{decorate, Item, Range, SpanMapper}; -use crate::modifiers::{FrameModifiers, FrameModify}; +use crate::modifiers::FrameModifyText; /// The result of shaping text. /// @@ -332,7 +332,7 @@ impl<'a> ShapedText<'a> { offset += width; } - frame.modify(&FrameModifiers::get_in(self.styles)); + frame.modify_text(self.styles); frame } diff --git a/crates/typst-layout/src/math/accent.rs b/crates/typst-layout/src/math/accent.rs index 73d821019..301606466 100644 --- a/crates/typst-layout/src/math/accent.rs +++ b/crates/typst-layout/src/math/accent.rs @@ -1,7 +1,7 @@ use typst_library::diag::SourceResult; use typst_library::foundations::{Packed, StyleChain}; use typst_library::layout::{Em, Frame, Point, Size}; -use typst_library::math::{Accent, AccentElem}; +use typst_library::math::AccentElem; use super::{style_cramped, FrameFragment, GlyphFragment, MathContext, MathFragment}; @@ -18,8 +18,11 @@ pub fn layout_accent( let cramped = style_cramped(); let mut base = ctx.layout_into_fragment(&elem.base, styles.chain(&cramped))?; - // Try to replace a glyph with its dotless variant. - if elem.dotless(styles) { + let accent = elem.accent; + let top_accent = !accent.is_bottom(); + + // Try to replace base glyph with its dotless variant. + if top_accent && elem.dotless(styles) { if let MathFragment::Glyph(glyph) = &mut base { glyph.make_dotless_form(ctx); } @@ -29,41 +32,54 @@ pub fn layout_accent( let base_class = base.class(); let base_attach = base.accent_attach(); - let width = elem.size(styles).relative_to(base.width()); + let mut glyph = GlyphFragment::new(ctx, styles, accent.0, 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); - if base.ascent() > flattened_base_height { - glyph.make_flattened_accent_form(ctx); + // Try to replace accent glyph with its flattened variant. + if top_accent { + let flattened_base_height = scaled!(ctx, styles, flattened_accent_base_height); + if base.ascent() > flattened_base_height { + glyph.make_flattened_accent_form(ctx); + } } // Forcing the accent to be at least as large as the base makes it too // wide in many case. + let width = elem.size(styles).relative_to(base.width()); let short_fall = ACCENT_SHORT_FALL.at(glyph.font_size); - let variant = glyph.stretch_horizontal(ctx, width, short_fall); + let variant = glyph.stretch_horizontal(ctx, width - short_fall); let accent = variant.frame; - let accent_attach = variant.accent_attach; + let accent_attach = variant.accent_attach.0; + + let (gap, accent_pos, base_pos) = if top_accent { + // 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.ascent().min(accent_base_height); + let accent_pos = Point::with_x(base_attach.0 - accent_attach); + let base_pos = Point::with_y(accent.height() + gap); + (gap, accent_pos, base_pos) + } else { + let gap = -accent.ascent(); + let accent_pos = Point::new(base_attach.1 - accent_attach, base.height() + gap); + let base_pos = Point::zero(); + (gap, accent_pos, base_pos) + }; - // 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.ascent().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 base_descent = match &base { + MathFragment::Frame(frame) => frame.base_descent, + _ => base.descent(), + }; let mut frame = Frame::soft(size); frame.set_baseline(baseline); @@ -73,6 +89,7 @@ pub fn layout_accent( FrameFragment::new(styles, frame) .with_class(base_class) .with_base_ascent(base_ascent) + .with_base_descent(base_descent) .with_italics_correction(base_italics_correction) .with_accent_attach(base_attach) .with_text_like(base_text_like), diff --git a/crates/typst-layout/src/math/attach.rs b/crates/typst-layout/src/math/attach.rs index e1d7d7c9d..90aad941e 100644 --- a/crates/typst-layout/src/math/attach.rs +++ b/crates/typst-layout/src/math/attach.rs @@ -434,9 +434,13 @@ fn compute_script_shifts( } if bl.is_some() || br.is_some() { + let descent = match &base { + MathFragment::Frame(frame) => frame.base_descent, + _ => base.descent(), + }; shift_down = shift_down .max(sub_shift_down) - .max(if is_text_like { Abs::zero() } else { base.descent() + sub_drop_min }) + .max(if is_text_like { Abs::zero() } else { descent + sub_drop_min }) .max(measure!(bl, ascent) - sub_top_max) .max(measure!(br, ascent) - sub_top_max); } diff --git a/crates/typst-layout/src/math/frac.rs b/crates/typst-layout/src/math/frac.rs index 6d3caac45..2567349d0 100644 --- a/crates/typst-layout/src/math/frac.rs +++ b/crates/typst-layout/src/math/frac.rs @@ -110,12 +110,12 @@ fn layout_frac_like( if binom { let mut left = GlyphFragment::new(ctx, styles, '(', span) - .stretch_vertical(ctx, height, short_fall); + .stretch_vertical(ctx, height - short_fall); left.center_on_axis(ctx); ctx.push(left); ctx.push(FrameFragment::new(styles, frame)); let mut right = GlyphFragment::new(ctx, styles, ')', span) - .stretch_vertical(ctx, height, short_fall); + .stretch_vertical(ctx, height - short_fall); right.center_on_axis(ctx); ctx.push(right); } else { diff --git a/crates/typst-layout/src/math/fragment.rs b/crates/typst-layout/src/math/fragment.rs index 1b508a349..01fa6be4b 100644 --- a/crates/typst-layout/src/math/fragment.rs +++ b/crates/typst-layout/src/math/fragment.rs @@ -11,7 +11,7 @@ use typst_library::layout::{ }; use typst_library::math::{EquationElem, MathSize}; use typst_library::text::{Font, Glyph, Lang, Region, TextElem, TextItem}; -use typst_library::visualize::Paint; +use typst_library::visualize::{FixedStroke, Paint}; use typst_syntax::Span; use typst_utils::default_math_class; use unicode_math_class::MathClass; @@ -164,12 +164,12 @@ impl MathFragment { } } - pub fn accent_attach(&self) -> Abs { + pub fn accent_attach(&self) -> (Abs, Abs) { match self { Self::Glyph(glyph) => glyph.accent_attach, Self::Variant(variant) => variant.accent_attach, Self::Frame(fragment) => fragment.accent_attach, - _ => self.width() / 2.0, + _ => (self.width() / 2.0, self.width() / 2.0), } } @@ -235,12 +235,13 @@ pub struct GlyphFragment { pub lang: Lang, pub region: Option, pub fill: Paint, + pub stroke: Option, pub shift: Abs, pub width: Abs, pub ascent: Abs, pub descent: Abs, pub italics_correction: Abs, - pub accent_attach: Abs, + pub accent_attach: (Abs, Abs), pub font_size: Abs, pub class: MathClass, pub math_size: MathSize, @@ -286,6 +287,7 @@ impl GlyphFragment { lang: TextElem::lang_in(styles), region: TextElem::region_in(styles), fill: TextElem::fill_in(styles).as_decoration(), + stroke: TextElem::stroke_in(styles).map(|s| s.unwrap_or_default()), shift: TextElem::baseline_in(styles), font_size: TextElem::size_in(styles), math_size: EquationElem::size_in(styles), @@ -294,7 +296,7 @@ impl GlyphFragment { descent: Abs::zero(), limits: Limits::for_char(c), italics_correction: Abs::zero(), - accent_attach: Abs::zero(), + accent_attach: (Abs::zero(), Abs::zero()), class, span, modifiers: FrameModifiers::get_in(styles), @@ -326,8 +328,14 @@ impl GlyphFragment { }); let mut width = advance.scaled(ctx, self.font_size); - let accent_attach = + + // The fallback for accents is half the width plus or minus the italics + // correction. This is similar to how top and bottom attachments are + // shifted. For bottom accents we do not use the accent attach of the + // base as it is meant for top acccents. + let top_accent_attach = accent_attach(ctx, id, self.font_size).unwrap_or((width + italics) / 2.0); + let bottom_accent_attach = (width - italics) / 2.0; let extended_shape = is_extended_shape(ctx, id); if !extended_shape { @@ -339,7 +347,7 @@ impl GlyphFragment { self.ascent = bbox.y_max.scaled(ctx, self.font_size); self.descent = -bbox.y_min.scaled(ctx, self.font_size); self.italics_correction = italics; - self.accent_attach = accent_attach; + self.accent_attach = (top_accent_attach, bottom_accent_attach); self.extended_shape = extended_shape; } @@ -368,10 +376,10 @@ impl GlyphFragment { font: self.font.clone(), size: self.font_size, fill: self.fill, + stroke: self.stroke, lang: self.lang, region: self.region, text: self.c.into(), - stroke: None, glyphs: vec![Glyph { id: self.id.0, x_advance: Em::from_length(self.width, self.font_size), @@ -427,13 +435,8 @@ impl GlyphFragment { } /// Try to stretch a glyph to a desired height. - pub fn stretch_vertical( - self, - ctx: &mut MathContext, - height: Abs, - short_fall: Abs, - ) -> VariantFragment { - stretch_glyph(ctx, self, height, short_fall, Axis::Y) + pub fn stretch_vertical(self, ctx: &mut MathContext, height: Abs) -> VariantFragment { + stretch_glyph(ctx, self, height, Axis::Y) } /// Try to stretch a glyph to a desired width. @@ -441,9 +444,8 @@ impl GlyphFragment { self, ctx: &mut MathContext, width: Abs, - short_fall: Abs, ) -> VariantFragment { - stretch_glyph(ctx, self, width, short_fall, Axis::X) + stretch_glyph(ctx, self, width, Axis::X) } } @@ -457,7 +459,7 @@ impl Debug for GlyphFragment { pub struct VariantFragment { pub c: char, pub italics_correction: Abs, - pub accent_attach: Abs, + pub accent_attach: (Abs, Abs), pub frame: Frame, pub font_size: Abs, pub class: MathClass, @@ -499,8 +501,9 @@ pub struct FrameFragment { pub limits: Limits, pub spaced: bool, pub base_ascent: Abs, + pub base_descent: Abs, pub italics_correction: Abs, - pub accent_attach: Abs, + pub accent_attach: (Abs, Abs), pub text_like: bool, pub ignorant: bool, } @@ -508,6 +511,7 @@ pub struct FrameFragment { impl FrameFragment { pub fn new(styles: StyleChain, frame: Frame) -> Self { let base_ascent = frame.ascent(); + let base_descent = frame.descent(); let accent_attach = frame.width() / 2.0; Self { frame: frame.modified(&FrameModifiers::get_in(styles)), @@ -517,8 +521,9 @@ impl FrameFragment { limits: Limits::Never, spaced: false, base_ascent, + base_descent, italics_correction: Abs::zero(), - accent_attach, + accent_attach: (accent_attach, accent_attach), text_like: false, ignorant: false, } @@ -540,11 +545,15 @@ impl FrameFragment { Self { base_ascent, ..self } } + pub fn with_base_descent(self, base_descent: Abs) -> Self { + Self { base_descent, ..self } + } + pub fn with_italics_correction(self, italics_correction: Abs) -> Self { Self { italics_correction, ..self } } - pub fn with_accent_attach(self, accent_attach: Abs) -> Self { + pub fn with_accent_attach(self, accent_attach: (Abs, Abs)) -> Self { Self { accent_attach, ..self } } diff --git a/crates/typst-layout/src/math/mat.rs b/crates/typst-layout/src/math/mat.rs index d678f8658..e509cecc7 100644 --- a/crates/typst-layout/src/math/mat.rs +++ b/crates/typst-layout/src/math/mat.rs @@ -314,7 +314,7 @@ fn layout_delimiters( if let Some(left) = left { let mut left = GlyphFragment::new(ctx, styles, left, span) - .stretch_vertical(ctx, target, short_fall); + .stretch_vertical(ctx, target - short_fall); left.align_on_axis(ctx, delimiter_alignment(left.c)); ctx.push(left); } @@ -323,7 +323,7 @@ fn layout_delimiters( if let Some(right) = right { let mut right = GlyphFragment::new(ctx, styles, right, span) - .stretch_vertical(ctx, target, short_fall); + .stretch_vertical(ctx, target - short_fall); right.align_on_axis(ctx, delimiter_alignment(right.c)); ctx.push(right); } diff --git a/crates/typst-layout/src/math/root.rs b/crates/typst-layout/src/math/root.rs index c7f41488e..32f527198 100644 --- a/crates/typst-layout/src/math/root.rs +++ b/crates/typst-layout/src/math/root.rs @@ -50,7 +50,7 @@ pub fn layout_root( // Layout root symbol. let target = radicand.height() + thickness + gap; let sqrt = GlyphFragment::new(ctx, styles, '√', span) - .stretch_vertical(ctx, target, Abs::zero()) + .stretch_vertical(ctx, target) .frame; // Layout the index. diff --git a/crates/typst-layout/src/math/run.rs b/crates/typst-layout/src/math/run.rs index ae64368d6..4ec76c253 100644 --- a/crates/typst-layout/src/math/run.rs +++ b/crates/typst-layout/src/math/run.rs @@ -278,6 +278,9 @@ impl MathRun { frame } + /// Convert this run of math fragments into a vector of inline items for + /// paragraph layout. Creates multiple fragments when relation or binary + /// operators are present to allow for line-breaking opportunities later. pub fn into_par_items(self) -> Vec { let mut items = vec![]; @@ -295,21 +298,24 @@ impl MathRun { let mut space_is_visible = false; - let is_relation = |f: &MathFragment| matches!(f.class(), MathClass::Relation); let is_space = |f: &MathFragment| { matches!(f, MathFragment::Space(_) | MathFragment::Spacing(_, _)) }; + let is_line_break_opportunity = |class, next_fragment| match class { + // Don't split when two relations are in a row or when preceding a + // closing parenthesis. + MathClass::Binary => next_fragment != Some(MathClass::Closing), + MathClass::Relation => { + !matches!(next_fragment, Some(MathClass::Relation | MathClass::Closing)) + } + _ => false, + }; let mut iter = self.0.into_iter().peekable(); while let Some(fragment) = iter.next() { - if space_is_visible { - match fragment { - MathFragment::Space(width) | MathFragment::Spacing(width, _) => { - items.push(InlineItem::Space(width, true)); - continue; - } - _ => {} - } + if space_is_visible && is_space(&fragment) { + items.push(InlineItem::Space(fragment.width(), true)); + continue; } let class = fragment.class(); @@ -323,10 +329,9 @@ impl MathRun { frame.push_frame(pos, fragment.into_frame()); empty = false; - if class == MathClass::Binary - || (class == MathClass::Relation - && !iter.peek().map(is_relation).unwrap_or_default()) - { + // Split our current frame when we encounter a binary operator or + // relation so that there is a line-breaking opportunity. + if is_line_break_opportunity(class, iter.peek().map(|f| f.class())) { let mut frame_prev = std::mem::replace(&mut frame, Frame::soft(Size::zero())); diff --git a/crates/typst-layout/src/math/stretch.rs b/crates/typst-layout/src/math/stretch.rs index f45035e27..40f76da59 100644 --- a/crates/typst-layout/src/math/stretch.rs +++ b/crates/typst-layout/src/math/stretch.rs @@ -67,8 +67,7 @@ pub fn stretch_fragment( let mut variant = stretch_glyph( ctx, glyph, - stretch.relative_to(relative_to_size), - short_fall, + stretch.relative_to(relative_to_size) - short_fall, axis, ); @@ -120,7 +119,6 @@ pub fn stretch_glyph( ctx: &mut MathContext, mut base: GlyphFragment, target: Abs, - short_fall: Abs, axis: Axis, ) -> VariantFragment { // If the base glyph is good enough, use it. @@ -128,8 +126,7 @@ pub fn stretch_glyph( Axis::X => base.width, Axis::Y => base.height(), }; - let short_target = target - short_fall; - if short_target <= advance { + if target <= advance { return base.into_variant(); } @@ -153,13 +150,13 @@ pub fn stretch_glyph( for variant in construction.variants { best_id = variant.variant_glyph; best_advance = base.font.to_em(variant.advance_measurement).at(base.font_size); - if short_target <= best_advance { + if target <= best_advance { break; } } // This is either good or the best we've got. - if short_target <= best_advance || construction.assembly.is_none() { + if target <= best_advance || construction.assembly.is_none() { base.set_id(ctx, best_id); return base.into_variant(); } @@ -278,7 +275,7 @@ fn assemble( } let accent_attach = match axis { - Axis::X => frame.width() / 2.0, + Axis::X => (frame.width() / 2.0, frame.width() / 2.0), Axis::Y => base.accent_attach, }; diff --git a/crates/typst-layout/src/math/text.rs b/crates/typst-layout/src/math/text.rs index 59ac5b089..e191ec170 100644 --- a/crates/typst-layout/src/math/text.rs +++ b/crates/typst-layout/src/math/text.rs @@ -65,18 +65,13 @@ fn layout_inline_text( // Small optimization for numbers. Note that this lays out slightly // differently to normal text and is worth re-evaluating in the future. let mut fragments = vec![]; - let is_single = text.chars().count() == 1; for unstyled_c in text.chars() { let c = styled_char(styles, unstyled_c, false); let mut glyph = GlyphFragment::new(ctx, styles, c, span); - if is_single { - // Duplicate what `layout_glyph` does exactly even if it's - // probably incorrect here. - match EquationElem::size_in(styles) { - MathSize::Script => glyph.make_script_size(ctx), - MathSize::ScriptScript => glyph.make_script_script_size(ctx), - _ => {} - } + match EquationElem::size_in(styles) { + MathSize::Script => glyph.make_script_size(ctx), + MathSize::ScriptScript => glyph.make_script_script_size(ctx), + _ => {} } fragments.push(glyph.into()); } @@ -164,7 +159,7 @@ fn layout_glyph( 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()) + glyph.stretch_vertical(ctx, height) } else { glyph.into_variant() }; diff --git a/crates/typst-layout/src/math/underover.rs b/crates/typst-layout/src/math/underover.rs index 5b6bd40eb..a24113c81 100644 --- a/crates/typst-layout/src/math/underover.rs +++ b/crates/typst-layout/src/math/underover.rs @@ -286,7 +286,7 @@ fn layout_underoverspreader( let body_class = body.class(); let body = body.into_fragment(styles); let glyph = GlyphFragment::new(ctx, styles, c, span); - let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero()); + let stretched = glyph.stretch_horizontal(ctx, body.width()); let mut rows = vec![]; let baseline = match position { diff --git a/crates/typst-layout/src/modifiers.rs b/crates/typst-layout/src/modifiers.rs index ac5f40b04..b0371d63e 100644 --- a/crates/typst-layout/src/modifiers.rs +++ b/crates/typst-layout/src/modifiers.rs @@ -1,6 +1,6 @@ use typst_library::foundations::StyleChain; -use typst_library::layout::{Fragment, Frame, FrameItem, HideElem, Point}; -use typst_library::model::{Destination, LinkElem}; +use typst_library::layout::{Abs, Fragment, Frame, FrameItem, HideElem, Point, Sides}; +use typst_library::model::{Destination, LinkElem, ParElem}; /// Frame-level modifications resulting from styles that do not impose any /// layout structure. @@ -52,14 +52,7 @@ pub trait FrameModify { impl FrameModify for Frame { fn modify(&mut self, modifiers: &FrameModifiers) { - if let Some(dest) = &modifiers.dest { - let size = self.size(); - self.push(Point::zero(), FrameItem::Link(dest.clone(), size)); - } - - if modifiers.hidden { - self.hide(); - } + modify_frame(self, modifiers, None); } } @@ -82,6 +75,41 @@ where } } +pub trait FrameModifyText { + /// Resolve and apply [`FrameModifiers`] for this text frame. + fn modify_text(&mut self, styles: StyleChain); +} + +impl FrameModifyText for Frame { + fn modify_text(&mut self, styles: StyleChain) { + let modifiers = FrameModifiers::get_in(styles); + let expand_y = 0.5 * ParElem::leading_in(styles); + let outset = Sides::new(Abs::zero(), expand_y, Abs::zero(), expand_y); + modify_frame(self, &modifiers, Some(outset)); + } +} + +fn modify_frame( + frame: &mut Frame, + modifiers: &FrameModifiers, + link_box_outset: Option>, +) { + if let Some(dest) = &modifiers.dest { + let mut pos = Point::zero(); + let mut size = frame.size(); + if let Some(outset) = link_box_outset { + pos.y -= outset.top; + pos.x -= outset.left; + size += outset.sum_by_axis(); + } + frame.push(pos, FrameItem::Link(dest.clone(), size)); + } + + if modifiers.hidden { + frame.hide(); + } +} + /// Performs layout and modification in one step. /// /// This just runs `layout(styles).modified(&FrameModifiers::get_in(styles))`, diff --git a/crates/typst-library/src/foundations/calc.rs b/crates/typst-library/src/foundations/calc.rs index a8e0eaeb3..7f481a23b 100644 --- a/crates/typst-library/src/foundations/calc.rs +++ b/crates/typst-library/src/foundations/calc.rs @@ -708,12 +708,13 @@ pub fn fract( } } -/// Rounds a number to the nearest integer away from zero. +/// Rounds a number to the nearest integer. /// -/// Optionally, a number of decimal places can be specified. +/// Half-integers are rounded away from zero. /// -/// If the number of digits is negative, its absolute value will indicate the -/// amount of significant integer digits to remove before the decimal point. +/// Optionally, a number of decimal places can be specified. If negative, its +/// absolute value will indicate the amount of significant integer digits to +/// remove before the decimal point. /// /// Note that this function will return the same type as the operand. That is, /// applying `round` to a [`float`] will return a `float`, and to a [`decimal`], diff --git a/crates/typst-library/src/foundations/content.rs b/crates/typst-library/src/foundations/content.rs index daf6c2dd9..1855bb70b 100644 --- a/crates/typst-library/src/foundations/content.rs +++ b/crates/typst-library/src/foundations/content.rs @@ -414,7 +414,7 @@ impl Content { /// Elements produced in `show` rules will not be included in the results. pub fn query(&self, selector: Selector) -> Vec { let mut results = Vec::new(); - self.traverse(&mut |element| -> ControlFlow<()> { + let _ = self.traverse(&mut |element| -> ControlFlow<()> { if selector.matches(&element, None) { results.push(element); } @@ -441,7 +441,7 @@ impl Content { /// Extracts the plain text of this content. pub fn plain_text(&self) -> EcoString { let mut text = EcoString::new(); - self.traverse(&mut |element| -> ControlFlow<()> { + let _ = self.traverse(&mut |element| -> ControlFlow<()> { if let Some(textable) = element.with::() { textable.plain_text(&mut text); } diff --git a/crates/typst-library/src/layout/layout.rs b/crates/typst-library/src/layout/layout.rs index 88252e5e3..46271ff22 100644 --- a/crates/typst-library/src/layout/layout.rs +++ b/crates/typst-library/src/layout/layout.rs @@ -41,8 +41,23 @@ use crate::layout::{BlockElem, Size}; /// receives the page's dimensions minus its margins. This is mostly useful in /// combination with [measurement]($measure). /// -/// You can also use this function to resolve [`ratio`] to fixed lengths. This -/// might come in handy if you're building your own layout abstractions. +/// To retrieve the _remaining_ height of the page rather than its full size, +/// you can wrap your `layout` call in a `{block(height: 1fr)}`. This works +/// because the block automatically grows to fill the remaining space (see the +/// [fraction] documentation for more details). +/// +/// ```example +/// #set page(height: 150pt) +/// +/// #lorem(20) +/// +/// #block(height: 1fr, layout(size => [ +/// Remaining height: #size.height +/// ])) +/// ``` +/// +/// You can also use this function to resolve a [`ratio`] to a fixed length. +/// This might come in handy if you're building your own layout abstractions. /// /// ```example /// #layout(size => { diff --git a/crates/typst-library/src/layout/page.rs b/crates/typst-library/src/layout/page.rs index 62e25278a..98afbd06f 100644 --- a/crates/typst-library/src/layout/page.rs +++ b/crates/typst-library/src/layout/page.rs @@ -1,16 +1,14 @@ -use std::borrow::Cow; use std::num::NonZeroUsize; 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; use crate::foundations::{ - cast, elem, Args, AutoValue, Cast, Construct, Content, Context, Dict, Fold, Func, - NativeElement, Set, Smart, StyleChain, Value, + cast, elem, Args, AutoValue, Cast, Construct, Content, Dict, Fold, NativeElement, + Set, Smart, Value, }; use crate::introspection::Introspector; use crate::layout::{ @@ -649,43 +647,6 @@ cast! { }, } -/// A header, footer, foreground or background definition. -#[derive(Debug, Clone, Hash)] -pub enum Marginal { - /// Bare content. - Content(Content), - /// A closure mapping from a page number to content. - Func(Func), -} - -impl Marginal { - /// Resolve the marginal based on the page number. - pub fn resolve( - &self, - engine: &mut Engine, - styles: StyleChain, - page: usize, - ) -> SourceResult> { - Ok(match self { - Self::Content(content) => Cow::Borrowed(content), - Self::Func(func) => Cow::Owned( - func.call(engine, Context::new(None, Some(styles)).track(), [page])? - .display(), - ), - }) - } -} - -cast! { - Marginal, - self => match self { - Self::Content(v) => v.into_value(), - Self::Func(v) => v.into_value(), - }, - v: Content => Self::Content(v), - v: Func => Self::Func(v), -} - /// A list of page ranges to be exported. #[derive(Debug, Clone)] pub struct PageRanges(Vec); diff --git a/crates/typst-library/src/math/accent.rs b/crates/typst-library/src/math/accent.rs index e62b63872..f2c9168c2 100644 --- a/crates/typst-library/src/math/accent.rs +++ b/crates/typst-library/src/math/accent.rs @@ -80,6 +80,19 @@ impl Accent { pub fn new(c: char) -> Self { Self(Self::combine(c).unwrap_or(c)) } + + /// List of bottom accents. Currently just a list of ones included in the + /// Unicode math class document. + const BOTTOM: &[char] = &[ + '\u{0323}', '\u{032C}', '\u{032D}', '\u{032E}', '\u{032F}', '\u{0330}', + '\u{0331}', '\u{0332}', '\u{0333}', '\u{033A}', '\u{20E8}', '\u{20EC}', + '\u{20ED}', '\u{20EE}', '\u{20EF}', + ]; + + /// Whether this accent is a bottom accent or not. + pub fn is_bottom(&self) -> bool { + Self::BOTTOM.contains(&self.0) + } } /// This macro generates accent-related functions. diff --git a/crates/typst-library/src/model/enum.rs b/crates/typst-library/src/model/enum.rs index 2d95996ab..f1f93702b 100644 --- a/crates/typst-library/src/model/enum.rs +++ b/crates/typst-library/src/model/enum.rs @@ -259,10 +259,11 @@ impl Show for Packed { .spanned(self.span()); if tight { - let leading = ParElem::leading_in(styles); - let spacing = - VElem::new(leading.into()).with_weak(true).with_attach(true).pack(); - realized = spacing + realized; + let spacing = self + .spacing(styles) + .unwrap_or_else(|| ParElem::leading_in(styles).into()); + let v = VElem::new(spacing.into()).with_weak(true).with_attach(true).pack(); + realized = v + realized; } Ok(realized) diff --git a/crates/typst-library/src/model/figure.rs b/crates/typst-library/src/model/figure.rs index 78a79a8e2..bec667d6e 100644 --- a/crates/typst-library/src/model/figure.rs +++ b/crates/typst-library/src/model/figure.rs @@ -125,6 +125,9 @@ pub struct FigureElem { /// /// ```example /// #set page(height: 200pt) + /// #show figure: set place( + /// clearance: 1em, + /// ) /// /// = Introduction /// #figure( @@ -457,7 +460,7 @@ impl Outlinable for Packed { /// customize the appearance of captions for all figures or figures of a /// specific kind. /// -/// In addition to its `pos` and `body`, the `caption` also provides the +/// In addition to its `position` and `body`, the `caption` also provides the /// figure's `kind`, `supplement`, `counter`, and `numbering` as fields. These /// parts can be used in [`where`]($function.where) selectors and show rules to /// build a completely custom caption. diff --git a/crates/typst-library/src/model/list.rs b/crates/typst-library/src/model/list.rs index d93ec9172..3c3afd338 100644 --- a/crates/typst-library/src/model/list.rs +++ b/crates/typst-library/src/model/list.rs @@ -166,10 +166,11 @@ impl Show for Packed { .spanned(self.span()); if tight { - let leading = ParElem::leading_in(styles); - let spacing = - VElem::new(leading.into()).with_weak(true).with_attach(true).pack(); - realized = spacing + realized; + let spacing = self + .spacing(styles) + .unwrap_or_else(|| ParElem::leading_in(styles).into()); + let v = VElem::new(spacing.into()).with_weak(true).with_attach(true).pack(); + realized = v + realized; } Ok(realized) diff --git a/crates/typst-library/src/model/numbering.rs b/crates/typst-library/src/model/numbering.rs index d82c3e4cd..236ced361 100644 --- a/crates/typst-library/src/model/numbering.rs +++ b/crates/typst-library/src/model/numbering.rs @@ -9,7 +9,6 @@ use ecow::{eco_format, EcoString, EcoVec}; use crate::diag::SourceResult; use crate::engine::Engine; use crate::foundations::{cast, func, Context, Func, Str, Value}; -use crate::text::Case; /// Applies a numbering to a sequence of numbers. /// @@ -261,9 +260,9 @@ pub enum NumberingKind { LowerRoman, /// Uppercase Roman numerals (I, II, III, etc.). UpperRoman, - /// Lowercase Greek numerals (Α, Β, Γ, etc.). + /// Lowercase Greek letters (α, β, γ, etc.). LowerGreek, - /// Uppercase Greek numerals (α, β, γ, etc.). + /// Uppercase Greek letters (Α, Β, Γ, etc.). UpperGreek, /// Paragraph/note-like symbols: *, †, ‡, §, ¶, and ‖. Further items use /// repeated symbols. @@ -381,40 +380,194 @@ impl NumberingKind { /// Apply the numbering to the given number. pub fn apply(self, n: u64) -> EcoString { match self { - Self::Arabic => eco_format!("{n}"), - Self::LowerRoman => roman_numeral(n, Case::Lower), - Self::UpperRoman => roman_numeral(n, Case::Upper), - Self::LowerGreek => greek_numeral(n, Case::Lower), - Self::UpperGreek => greek_numeral(n, Case::Upper), - Self::Symbol => { - if n == 0 { - return '-'.into(); - } - - const SYMBOLS: &[char] = &['*', '†', '‡', '§', '¶', '‖']; - let n_symbols = SYMBOLS.len() as u64; - let symbol = SYMBOLS[((n - 1) % n_symbols) as usize]; - let amount = ((n - 1) / n_symbols) + 1; - std::iter::repeat_n(symbol, amount.try_into().unwrap()).collect() + Self::Arabic => { + numeric(&['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], n) } - Self::Hebrew => hebrew_numeral(n), - - Self::LowerLatin => zeroless( - [ + Self::LowerRoman => additive( + &[ + ("m̅", 1000000), + ("d̅", 500000), + ("c̅", 100000), + ("l̅", 50000), + ("x̅", 10000), + ("v̅", 5000), + ("i̅v̅", 4000), + ("m", 1000), + ("cm", 900), + ("d", 500), + ("cd", 400), + ("c", 100), + ("xc", 90), + ("l", 50), + ("xl", 40), + ("x", 10), + ("ix", 9), + ("v", 5), + ("iv", 4), + ("i", 1), + ("n", 0), + ], + n, + ), + Self::UpperRoman => additive( + &[ + ("M̅", 1000000), + ("D̅", 500000), + ("C̅", 100000), + ("L̅", 50000), + ("X̅", 10000), + ("V̅", 5000), + ("I̅V̅", 4000), + ("M", 1000), + ("CM", 900), + ("D", 500), + ("CD", 400), + ("C", 100), + ("XC", 90), + ("L", 50), + ("XL", 40), + ("X", 10), + ("IX", 9), + ("V", 5), + ("IV", 4), + ("I", 1), + ("N", 0), + ], + n, + ), + Self::LowerGreek => additive( + &[ + ("͵θ", 9000), + ("͵η", 8000), + ("͵ζ", 7000), + ("͵ϛ", 6000), + ("͵ε", 5000), + ("͵δ", 4000), + ("͵γ", 3000), + ("͵β", 2000), + ("͵α", 1000), + ("ϡ", 900), + ("ω", 800), + ("ψ", 700), + ("χ", 600), + ("φ", 500), + ("υ", 400), + ("τ", 300), + ("σ", 200), + ("ρ", 100), + ("ϟ", 90), + ("π", 80), + ("ο", 70), + ("ξ", 60), + ("ν", 50), + ("μ", 40), + ("λ", 30), + ("κ", 20), + ("ι", 10), + ("θ", 9), + ("η", 8), + ("ζ", 7), + ("ϛ", 6), + ("ε", 5), + ("δ", 4), + ("γ", 3), + ("β", 2), + ("α", 1), + ("𐆊", 0), + ], + n, + ), + Self::UpperGreek => additive( + &[ + ("͵Θ", 9000), + ("͵Η", 8000), + ("͵Ζ", 7000), + ("͵Ϛ", 6000), + ("͵Ε", 5000), + ("͵Δ", 4000), + ("͵Γ", 3000), + ("͵Β", 2000), + ("͵Α", 1000), + ("Ϡ", 900), + ("Ω", 800), + ("Ψ", 700), + ("Χ", 600), + ("Φ", 500), + ("Υ", 400), + ("Τ", 300), + ("Σ", 200), + ("Ρ", 100), + ("Ϟ", 90), + ("Π", 80), + ("Ο", 70), + ("Ξ", 60), + ("Ν", 50), + ("Μ", 40), + ("Λ", 30), + ("Κ", 20), + ("Ι", 10), + ("Θ", 9), + ("Η", 8), + ("Ζ", 7), + ("Ϛ", 6), + ("Ε", 5), + ("Δ", 4), + ("Γ", 3), + ("Β", 2), + ("Α", 1), + ("𐆊", 0), + ], + n, + ), + Self::Hebrew => additive( + &[ + ("ת", 400), + ("ש", 300), + ("ר", 200), + ("ק", 100), + ("צ", 90), + ("פ", 80), + ("ע", 70), + ("ס", 60), + ("נ", 50), + ("מ", 40), + ("ל", 30), + ("כ", 20), + ("יט", 19), + ("יח", 18), + ("יז", 17), + ("טז", 16), + ("טו", 15), + ("י", 10), + ("ט", 9), + ("ח", 8), + ("ז", 7), + ("ו", 6), + ("ה", 5), + ("ד", 4), + ("ג", 3), + ("ב", 2), + ("א", 1), + ("-", 0), + ], + n, + ), + Self::LowerLatin => alphabetic( + &[ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', ], n, ), - Self::UpperLatin => zeroless( - [ + Self::UpperLatin => alphabetic( + &[ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ], n, ), - Self::HiraganaAiueo => zeroless( - [ + Self::HiraganaAiueo => alphabetic( + &[ 'あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ', 'し', 'す', 'せ', 'そ', 'た', 'ち', 'つ', 'て', 'と', 'な', 'に', 'ぬ', 'ね', 'の', 'は', 'ひ', 'ふ', 'へ', 'ほ', 'ま', 'み', 'む', @@ -423,8 +576,8 @@ impl NumberingKind { ], n, ), - Self::HiraganaIroha => zeroless( - [ + Self::HiraganaIroha => alphabetic( + &[ 'い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る', 'を', 'わ', 'か', 'よ', 'た', 'れ', 'そ', 'つ', 'ね', 'な', 'ら', 'む', 'う', 'ゐ', 'の', 'お', 'く', 'や', 'ま', 'け', 'ふ', 'こ', @@ -433,8 +586,8 @@ impl NumberingKind { ], n, ), - Self::KatakanaAiueo => zeroless( - [ + Self::KatakanaAiueo => alphabetic( + &[ 'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', 'シ', 'ス', 'セ', 'ソ', 'タ', 'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ', 'ヌ', 'ネ', 'ノ', 'ハ', 'ヒ', 'フ', 'ヘ', 'ホ', 'マ', 'ミ', 'ム', @@ -443,8 +596,8 @@ impl NumberingKind { ], n, ), - Self::KatakanaIroha => zeroless( - [ + Self::KatakanaIroha => alphabetic( + &[ 'イ', 'ロ', 'ハ', 'ニ', 'ホ', 'ヘ', 'ト', 'チ', 'リ', 'ヌ', 'ル', 'ヲ', 'ワ', 'カ', 'ヨ', 'タ', 'レ', 'ソ', 'ツ', 'ネ', 'ナ', 'ラ', 'ム', 'ウ', 'ヰ', 'ノ', 'オ', 'ク', 'ヤ', 'マ', 'ケ', 'フ', 'コ', @@ -453,40 +606,40 @@ impl NumberingKind { ], n, ), - Self::KoreanJamo => zeroless( - [ + Self::KoreanJamo => alphabetic( + &[ 'ㄱ', 'ㄴ', 'ㄷ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅅ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ', ], n, ), - Self::KoreanSyllable => zeroless( - [ + Self::KoreanSyllable => alphabetic( + &[ '가', '나', '다', '라', '마', '바', '사', '아', '자', '차', '카', '타', '파', '하', ], n, ), - Self::BengaliLetter => zeroless( - [ + Self::BengaliLetter => alphabetic( + &[ 'ক', 'খ', 'গ', 'ঘ', 'ঙ', 'চ', 'ছ', 'জ', 'ঝ', 'ঞ', 'ট', 'ঠ', 'ড', 'ঢ', 'ণ', 'ত', 'থ', 'দ', 'ধ', 'ন', 'প', 'ফ', 'ব', 'ভ', 'ম', 'য', 'র', 'ল', 'শ', 'ষ', 'স', 'হ', ], n, ), - Self::CircledNumber => zeroless( - [ - '①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩', '⑪', '⑫', '⑬', '⑭', - '⑮', '⑯', '⑰', '⑱', '⑲', '⑳', '㉑', '㉒', '㉓', '㉔', '㉕', '㉖', - '㉗', '㉘', '㉙', '㉚', '㉛', '㉜', '㉝', '㉞', '㉟', '㊱', '㊲', - '㊳', '㊴', '㊵', '㊶', '㊷', '㊸', '㊹', '㊺', '㊻', '㊼', '㊽', - '㊾', '㊿', + Self::CircledNumber => fixed( + &[ + '⓪', '①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩', '⑪', '⑫', '⑬', + '⑭', '⑮', '⑯', '⑰', '⑱', '⑲', '⑳', '㉑', '㉒', '㉓', '㉔', '㉕', + '㉖', '㉗', '㉘', '㉙', '㉚', '㉛', '㉜', '㉝', '㉞', '㉟', '㊱', + '㊲', '㊳', '㊴', '㊵', '㊶', '㊷', '㊸', '㊹', '㊺', '㊻', '㊼', + '㊽', '㊾', '㊿', ], n, ), Self::DoubleCircledNumber => { - zeroless(['⓵', '⓶', '⓷', '⓸', '⓹', '⓺', '⓻', '⓼', '⓽', '⓾'], n) + fixed(&['0', '⓵', '⓶', '⓷', '⓸', '⓹', '⓺', '⓻', '⓼', '⓽', '⓾'], n) } Self::LowerSimplifiedChinese => { @@ -502,306 +655,170 @@ impl NumberingKind { u64_to_chinese(ChineseVariant::Traditional, ChineseCase::Upper, n).into() } - Self::EasternArabic => decimal('\u{0660}', n), - Self::EasternArabicPersian => decimal('\u{06F0}', n), - Self::DevanagariNumber => decimal('\u{0966}', n), - Self::BengaliNumber => decimal('\u{09E6}', n), - } - } -} - -/// Stringify an integer to a Hebrew number. -fn hebrew_numeral(mut n: u64) -> EcoString { - if n == 0 { - return '-'.into(); - } - let mut fmt = EcoString::new(); - 'outer: for (name, value) in [ - ('ת', 400), - ('ש', 300), - ('ר', 200), - ('ק', 100), - ('צ', 90), - ('פ', 80), - ('ע', 70), - ('ס', 60), - ('נ', 50), - ('מ', 40), - ('ל', 30), - ('כ', 20), - ('י', 10), - ('ט', 9), - ('ח', 8), - ('ז', 7), - ('ו', 6), - ('ה', 5), - ('ד', 4), - ('ג', 3), - ('ב', 2), - ('א', 1), - ] { - while n >= value { - match n { - 15 => fmt.push_str("ט״ו"), - 16 => fmt.push_str("ט״ז"), - _ => { - let append_geresh = n == value && fmt.is_empty(); - if n == value && !fmt.is_empty() { - fmt.push('״'); - } - fmt.push(name); - if append_geresh { - fmt.push('׳'); - } - - n -= value; - continue; - } + Self::EasternArabic => { + numeric(&['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'], n) } - break 'outer; - } - } - fmt -} - -/// Stringify an integer to a Roman numeral. -fn roman_numeral(mut n: u64, case: Case) -> EcoString { - if n == 0 { - return match case { - Case::Lower => 'n'.into(), - Case::Upper => 'N'.into(), - }; - } - - // Adapted from Yann Villessuzanne's roman.rs under the - // Unlicense, at https://github.com/linfir/roman.rs/ - let mut fmt = EcoString::new(); - for &(name, value) in &[ - ("M̅", 1000000), - ("D̅", 500000), - ("C̅", 100000), - ("L̅", 50000), - ("X̅", 10000), - ("V̅", 5000), - ("I̅V̅", 4000), - ("M", 1000), - ("CM", 900), - ("D", 500), - ("CD", 400), - ("C", 100), - ("XC", 90), - ("L", 50), - ("XL", 40), - ("X", 10), - ("IX", 9), - ("V", 5), - ("IV", 4), - ("I", 1), - ] { - while n >= value { - n -= value; - for c in name.chars() { - match case { - Case::Lower => fmt.extend(c.to_lowercase()), - Case::Upper => fmt.push(c), - } + Self::EasternArabicPersian => { + numeric(&['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'], n) } + Self::DevanagariNumber => { + numeric(&['०', '१', '२', '३', '४', '५', '६', '७', '८', '९'], n) + } + Self::BengaliNumber => { + numeric(&['০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯'], n) + } + Self::Symbol => symbolic(&['*', '†', '‡', '§', '¶', '‖'], n), } } - - fmt } -/// Stringify an integer to Greek numbers. +/// Stringify a number using symbols representing values. The decimal +/// representation of the number is recovered by summing over the values of the +/// symbols present. /// -/// Greek numbers use the Greek Alphabet to represent numbers; it is based on 10 -/// (decimal). Here we implement the single digit M power representation from -/// [The Greek Number Converter][convert] and also described in -/// [Greek Numbers][numbers]. -/// -/// [converter]: https://www.russellcottrell.com/greek/utilities/GreekNumberConverter.htm -/// [numbers]: https://mathshistory.st-andrews.ac.uk/HistTopics/Greek_numbers/ -fn greek_numeral(n: u64, case: Case) -> EcoString { - let thousands = [ - ["͵α", "͵Α"], - ["͵β", "͵Β"], - ["͵γ", "͵Γ"], - ["͵δ", "͵Δ"], - ["͵ε", "͵Ε"], - ["͵ϛ", "͵Ϛ"], - ["͵ζ", "͵Ζ"], - ["͵η", "͵Η"], - ["͵θ", "͵Θ"], - ]; - let hundreds = [ - ["ρ", "Ρ"], - ["σ", "Σ"], - ["τ", "Τ"], - ["υ", "Υ"], - ["φ", "Φ"], - ["χ", "Χ"], - ["ψ", "Ψ"], - ["ω", "Ω"], - ["ϡ", "Ϡ"], - ]; - let tens = [ - ["ι", "Ι"], - ["κ", "Κ"], - ["λ", "Λ"], - ["μ", "Μ"], - ["ν", "Ν"], - ["ξ", "Ξ"], - ["ο", "Ο"], - ["π", "Π"], - ["ϙ", "Ϟ"], - ]; - let ones = [ - ["α", "Α"], - ["β", "Β"], - ["γ", "Γ"], - ["δ", "Δ"], - ["ε", "Ε"], - ["ϛ", "Ϛ"], - ["ζ", "Ζ"], - ["η", "Η"], - ["θ", "Θ"], - ]; - - if n == 0 { - // Greek Zero Sign - return '𐆊'.into(); - } - - let mut fmt = EcoString::new(); - let case = match case { - Case::Lower => 0, - Case::Upper => 1, - }; - - // Extract a list of decimal digits from the number - let mut decimal_digits: Vec = Vec::new(); - let mut n = n; - while n > 0 { - decimal_digits.push((n % 10) as usize); - n /= 10; - } - - // Pad the digits with leading zeros to ensure we can form groups of 4 - while decimal_digits.len() % 4 != 0 { - decimal_digits.push(0); - } - decimal_digits.reverse(); - - let mut m_power = decimal_digits.len() / 4; - - // M are used to represent 10000, M_power = 2 means 10000^2 = 10000 0000 - // The prefix of M is also made of Greek numerals but only be single digits, so it is 9 at max. This enables us - // to represent up to (10000)^(9 + 1) - 1 = 10^40 -1 (9,999,999,999,999,999,999,999,999,999,999,999,999,999) - let get_m_prefix = |m_power: usize| { - if m_power == 0 { - None - } else { - assert!(m_power <= 9); - // the prefix of M is a single digit lowercase - Some(ones[m_power - 1][0]) - } - }; - - let mut previous_has_number = false; - for chunk in decimal_digits.chunks_exact(4) { - // chunk must be exact 4 item - assert_eq!(chunk.len(), 4); - - m_power = m_power.saturating_sub(1); - - // `th`ousan, `h`undred, `t`en and `o`ne - let (th, h, t, o) = (chunk[0], chunk[1], chunk[2], chunk[3]); - if th + h + t + o == 0 { - continue; - } - - if previous_has_number { - fmt.push_str(", "); - } - - if let Some(m_prefix) = get_m_prefix(m_power) { - fmt.push_str(m_prefix); - fmt.push_str("Μ"); - } - if th != 0 { - let thousand_digit = thousands[th - 1][case]; - fmt.push_str(thousand_digit); - } - if h != 0 { - let hundred_digit = hundreds[h - 1][case]; - fmt.push_str(hundred_digit); - } - if t != 0 { - let ten_digit = tens[t - 1][case]; - fmt.push_str(ten_digit); - } - if o != 0 { - let one_digit = ones[o - 1][case]; - fmt.push_str(one_digit); - } - // if we do not have thousan, we need to append 'ʹ' at the end. - if th == 0 { - fmt.push_str("ʹ"); - } - previous_has_number = true; - } - fmt -} - -/// Stringify a number using a base-N counting system with no zero digit. -/// -/// This is best explained by example. Suppose our digits are 'A', 'B', and 'C'. -/// We would get the following: +/// Consider the situation where ['I': 1, 'IV': 4, 'V': 5], /// /// ```text -/// 1 => "A" -/// 2 => "B" -/// 3 => "C" -/// 4 => "AA" -/// 5 => "AB" -/// 6 => "AC" -/// 7 => "BA" -/// 8 => "BB" -/// 9 => "BC" -/// 10 => "CA" -/// 11 => "CB" -/// 12 => "CC" -/// 13 => "AAA" -/// etc. +/// 1 => 'I' +/// 2 => 'II' +/// 3 => 'III' +/// 4 => 'IV' +/// 5 => 'V' +/// 6 => 'VI' +/// 7 => 'VII' +/// 8 => 'VIII' /// ``` /// -/// You might be familiar with this scheme from the way spreadsheet software -/// tends to label its columns. -fn zeroless(alphabet: [char; N_DIGITS], mut n: u64) -> EcoString { +/// where this is the start of the familiar Roman numeral system. +fn additive(symbols: &[(&str, u64)], mut n: u64) -> EcoString { + if n == 0 { + if let Some(&(symbol, 0)) = symbols.last() { + return symbol.into(); + } + return '0'.into(); + } + + let mut s = EcoString::new(); + for (symbol, weight) in symbols { + if *weight == 0 || *weight > n { + continue; + } + let reps = n / weight; + for _ in 0..reps { + s.push_str(symbol); + } + + n -= weight * reps; + if n == 0 { + return s; + } + } + s +} + +/// Stringify a number using a base-n (where n is the number of provided +/// symbols) system without a zero symbol. +/// +/// Consider the situation where ['A', 'B', 'C'] are the provided symbols, +/// +/// ```text +/// 1 => 'A' +/// 2 => 'B' +/// 3 => 'C' +/// 4 => 'AA +/// 5 => 'AB' +/// 6 => 'AC' +/// 7 => 'BA' +/// ... +/// ``` +/// +/// This system is commonly used in spreadsheet software. +fn alphabetic(symbols: &[char], mut n: u64) -> EcoString { + let n_digits = symbols.len() as u64; if n == 0 { return '-'.into(); } - let n_digits = N_DIGITS as u64; - let mut cs = EcoString::new(); - while n > 0 { + let mut s = EcoString::new(); + while n != 0 { n -= 1; - cs.push(alphabet[(n % n_digits) as usize]); + s.push(symbols[(n % n_digits) as usize]); n /= n_digits; } - cs.chars().rev().collect() + s.chars().rev().collect() } -/// Stringify a number using a base-10 counting system with a zero digit. +/// Stringify a number using the symbols provided, defaulting to the arabic +/// representation when the number is greater than the number of symbols. /// -/// This function assumes that the digits occupy contiguous codepoints. -fn decimal(start: char, mut n: u64) -> EcoString { - if n == 0 { - return start.into(); +/// Consider the situation where ['0', 'A', 'B', 'C'] are the provided symbols, +/// +/// ```text +/// 0 => '0' +/// 1 => 'A' +/// 2 => 'B' +/// 3 => 'C' +/// 4 => '4' +/// ... +/// n => 'n' +/// ``` +fn fixed(symbols: &[char], n: u64) -> EcoString { + let n_digits = symbols.len() as u64; + if n < n_digits { + return symbols[(n) as usize].into(); } - let mut cs = EcoString::new(); - while n > 0 { - cs.push(char::from_u32((start as u32) + ((n % 10) as u32)).unwrap()); - n /= 10; - } - cs.chars().rev().collect() + eco_format!("{n}") +} + +/// Stringify a number using a base-n (where n is the number of provided +/// symbols) system with a zero symbol. +/// +/// Consider the situation where ['0', '1', '2'] are the provided symbols, +/// +/// ```text +/// 0 => '0' +/// 1 => '1' +/// 2 => '2' +/// 3 => '10' +/// 4 => '11' +/// 5 => '12' +/// 6 => '20' +/// ... +/// ``` +/// +/// which is the familiar trinary counting system. +fn numeric(symbols: &[char], mut n: u64) -> EcoString { + let n_digits = symbols.len() as u64; + if n == 0 { + return symbols[0].into(); + } + let mut s = EcoString::new(); + while n != 0 { + s.push(symbols[(n % n_digits) as usize]); + n /= n_digits; + } + s.chars().rev().collect() +} + +/// Stringify a number using repeating symbols. +/// +/// Consider the situation where ['A', 'B', 'C'] are the provided symbols, +/// +/// ```text +/// 0 => '-' +/// 1 => 'A' +/// 2 => 'B' +/// 3 => 'C' +/// 4 => 'AA' +/// 5 => 'BB' +/// 6 => 'CC' +/// 7 => 'AAA' +/// ... +/// ``` +fn symbolic(symbols: &[char], n: u64) -> EcoString { + let n_digits = symbols.len() as u64; + if n == 0 { + return '-'.into(); + } + EcoString::from(symbols[((n - 1) % n_digits) as usize]) + .repeat((n.div_ceil(n_digits)) as usize) } diff --git a/crates/typst-library/src/model/reference.rs b/crates/typst-library/src/model/reference.rs index 316617688..7d44cccc0 100644 --- a/crates/typst-library/src/model/reference.rs +++ b/crates/typst-library/src/model/reference.rs @@ -21,9 +21,10 @@ use crate::text::TextElem; /// /// The default, a `{"normal"}` reference, produces a textual reference to a /// label. For example, a reference to a heading will yield an appropriate -/// string such as "Section 1" for a reference to the first heading. The -/// references are also links to the respective element. Reference syntax can -/// also be used to [cite] from a bibliography. +/// string such as "Section 1" for a reference to the first heading. The word +/// "Section" depends on the [`lang`]($text.lang) setting and is localized +/// accordingly. The references are also links to the respective element. +/// Reference syntax can also be used to [cite] from a bibliography. /// /// As the default form requires a supplement and numbering, the label must be /// attached to a _referenceable element_. Referenceable elements include diff --git a/crates/typst-library/src/model/terms.rs b/crates/typst-library/src/model/terms.rs index e197ff318..3df74cd9e 100644 --- a/crates/typst-library/src/model/terms.rs +++ b/crates/typst-library/src/model/terms.rs @@ -189,13 +189,15 @@ impl Show for Packed { .styled(TermsElem::set_within(true)); if tight { - let leading = ParElem::leading_in(styles); - let spacing = VElem::new(leading.into()) + let spacing = self + .spacing(styles) + .unwrap_or_else(|| ParElem::leading_in(styles).into()); + let v = VElem::new(spacing.into()) .with_weak(true) .with_attach(true) .pack() .spanned(span); - realized = spacing + realized; + realized = v + realized; } Ok(realized) diff --git a/crates/typst-library/src/text/deco.rs b/crates/typst-library/src/text/deco.rs index 485d0edcf..7aa06e815 100644 --- a/crates/typst-library/src/text/deco.rs +++ b/crates/typst-library/src/text/deco.rs @@ -373,6 +373,7 @@ pub struct Decoration { /// A kind of decorative line. #[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[allow(clippy::large_enum_variant)] pub enum DecoLine { Underline { stroke: Stroke, diff --git a/crates/typst-library/src/text/font/book.rs b/crates/typst-library/src/text/font/book.rs index 9f8acce87..cd90a08fe 100644 --- a/crates/typst-library/src/text/font/book.rs +++ b/crates/typst-library/src/text/font/book.rs @@ -194,6 +194,8 @@ bitflags::bitflags! { const MONOSPACE = 1 << 0; /// Glyphs have short strokes at their stems. const SERIF = 1 << 1; + /// Font face has a MATH table + const MATH = 1 << 2; } } @@ -272,6 +274,7 @@ impl FontInfo { let mut flags = FontFlags::empty(); flags.set(FontFlags::MONOSPACE, ttf.is_monospaced()); + flags.set(FontFlags::MATH, ttf.tables().math.is_some()); // Determine whether this is a serif or sans-serif font. if let Some(panose) = ttf diff --git a/crates/typst-library/src/text/lang.rs b/crates/typst-library/src/text/lang.rs index 2cc66a261..f9f13c783 100644 --- a/crates/typst-library/src/text/lang.rs +++ b/crates/typst-library/src/text/lang.rs @@ -14,7 +14,7 @@ macro_rules! translation { }; } -const TRANSLATIONS: [(&str, &str); 39] = [ +const TRANSLATIONS: [(&str, &str); 40] = [ translation!("ar"), translation!("bg"), translation!("ca"), @@ -36,6 +36,7 @@ const TRANSLATIONS: [(&str, &str); 39] = [ translation!("it"), translation!("ja"), translation!("la"), + translation!("lv"), translation!("nb"), translation!("nl"), translation!("nn"), @@ -87,6 +88,7 @@ impl Lang { pub const ITALIAN: Self = Self(*b"it ", 2); pub const JAPANESE: Self = Self(*b"ja ", 2); pub const LATIN: Self = Self(*b"la ", 2); + pub const LATVIAN: Self = Self(*b"lv ", 2); pub const LOWER_SORBIAN: Self = Self(*b"dsb", 3); pub const NYNORSK: Self = Self(*b"nn ", 2); pub const POLISH: Self = Self(*b"pl ", 2); diff --git a/crates/typst-library/src/text/mod.rs b/crates/typst-library/src/text/mod.rs index 3eada5a76..0207c500b 100644 --- a/crates/typst-library/src/text/mod.rs +++ b/crates/typst-library/src/text/mod.rs @@ -348,15 +348,17 @@ pub struct TextElem { /// This can make justification visually more pleasing. /// /// ```example + /// #set page(width: 220pt) + /// /// #set par(justify: true) /// This justified text has a hyphen in - /// the paragraph's first line. Hanging + /// the paragraph's second line. Hanging /// the hyphen slightly into the margin /// results in a clearer paragraph edge. /// /// #set text(overhang: false) /// This justified text has a hyphen in - /// the paragraph's first line. Hanging + /// the paragraph's second line. Hanging /// the hyphen slightly into the margin /// results in a clearer paragraph edge. /// ``` diff --git a/crates/typst-library/src/text/smartquote.rs b/crates/typst-library/src/text/smartquote.rs index 4dda689df..09cefd013 100644 --- a/crates/typst-library/src/text/smartquote.rs +++ b/crates/typst-library/src/text/smartquote.rs @@ -237,7 +237,7 @@ impl<'s> SmartQuotes<'s> { "cs" | "da" | "de" | "sk" | "sl" if alternative => ("›", "‹", "»", "«"), "cs" | "de" | "et" | "is" | "lt" | "lv" | "sk" | "sl" => low_high, "da" => ("‘", "’", "“", "”"), - "fr" | "ru" if alternative => default, + "fr" if alternative => default, "fr" => ("“", "”", "«\u{202F}", "\u{202F}»"), "fi" | "sv" if alternative => ("’", "’", "»", "»"), "bs" | "fi" | "sv" => ("’", "’", "”", "”"), @@ -247,7 +247,9 @@ impl<'s> SmartQuotes<'s> { "es" if matches!(region, Some("ES") | None) => ("“", "”", "«", "»"), "hu" | "pl" | "ro" => ("’", "’", "„", "”"), "no" | "nb" | "nn" if alternative => low_high, - "ru" | "no" | "nb" | "nn" | "uk" => ("’", "’", "«", "»"), + "no" | "nb" | "nn" => ("’", "’", "«", "»"), + "ru" => ("„", "“", "«", "»"), + "uk" => ("“", "”", "«", "»"), "el" => ("‘", "’", "«", "»"), "he" => ("’", "’", "”", "”"), "hr" => ("‘", "’", "„", "”"), diff --git a/crates/typst-library/src/visualize/image/mod.rs b/crates/typst-library/src/visualize/image/mod.rs index 258eb96f3..f9e345e70 100644 --- a/crates/typst-library/src/visualize/image/mod.rs +++ b/crates/typst-library/src/visualize/image/mod.rs @@ -77,8 +77,8 @@ pub struct ImageElem { /// [`source`]($image.source) (even then, Typst will try to figure out the /// format automatically, but that's not always possible). /// - /// Supported formats are `{"png"}`, `{"jpg"}`, `{"gif"}`, `{"svg"}` as well - /// as raw pixel data. Embedding PDFs as images is + /// Supported formats are `{"png"}`, `{"jpg"}`, `{"gif"}`, `{"svg"}`, + /// `{"webp"}` as well as raw pixel data. Embedding PDFs as images is /// [not currently supported](https://github.com/typst/typst/issues/145). /// /// When providing raw pixel data as the `source`, you must specify a diff --git a/crates/typst-library/src/visualize/image/raster.rs b/crates/typst-library/src/visualize/image/raster.rs index 21d5b18fc..54f832bae 100644 --- a/crates/typst-library/src/visualize/image/raster.rs +++ b/crates/typst-library/src/visualize/image/raster.rs @@ -9,6 +9,7 @@ use ecow::{eco_format, EcoString}; use image::codecs::gif::GifDecoder; use image::codecs::jpeg::JpegDecoder; use image::codecs::png::PngDecoder; +use image::codecs::webp::WebPDecoder; use image::{ guess_format, DynamicImage, ImageBuffer, ImageDecoder, ImageResult, Limits, Pixel, }; @@ -77,6 +78,7 @@ impl RasterImage { ExchangeFormat::Jpg => decode(JpegDecoder::new(cursor), icc), ExchangeFormat::Png => decode(PngDecoder::new(cursor), icc), ExchangeFormat::Gif => decode(GifDecoder::new(cursor), icc), + ExchangeFormat::Webp => decode(WebPDecoder::new(cursor), icc), } .map_err(format_image_error)?; @@ -242,6 +244,8 @@ pub enum ExchangeFormat { /// Raster format that is typically used for short animated clips. Typst can /// load GIFs, but they will become static. Gif, + /// Raster format that supports both lossy and lossless compression. + Webp, } impl ExchangeFormat { @@ -257,6 +261,7 @@ impl From for image::ImageFormat { ExchangeFormat::Png => image::ImageFormat::Png, ExchangeFormat::Jpg => image::ImageFormat::Jpeg, ExchangeFormat::Gif => image::ImageFormat::Gif, + ExchangeFormat::Webp => image::ImageFormat::WebP, } } } @@ -269,6 +274,7 @@ impl TryFrom for ExchangeFormat { image::ImageFormat::Png => ExchangeFormat::Png, image::ImageFormat::Jpeg => ExchangeFormat::Jpg, image::ImageFormat::Gif => ExchangeFormat::Gif, + image::ImageFormat::WebP => ExchangeFormat::Webp, _ => bail!("format not yet supported"), }) } diff --git a/crates/typst-library/translations/lv.txt b/crates/typst-library/translations/lv.txt new file mode 100644 index 000000000..4c6b86841 --- /dev/null +++ b/crates/typst-library/translations/lv.txt @@ -0,0 +1,8 @@ +figure = Attēls +table = Tabula +equation = Vienādojums +bibliography = Literatūra +heading = Sadaļa +outline = Saturs +raw = Saraksts +page = lpp. diff --git a/crates/typst-macros/src/cast.rs b/crates/typst-macros/src/cast.rs index b90b78886..6f4b2b95c 100644 --- a/crates/typst-macros/src/cast.rs +++ b/crates/typst-macros/src/cast.rs @@ -185,6 +185,7 @@ struct Cast { } /// A pattern in a cast, e.g.`"ascender"` or `v: i64`. +#[allow(clippy::large_enum_variant)] enum Pattern { Str(syn::LitStr), Ty(syn::Pat, syn::Type), diff --git a/crates/typst-pdf/Cargo.toml b/crates/typst-pdf/Cargo.toml index f6f08b5bc..5745d0530 100644 --- a/crates/typst-pdf/Cargo.toml +++ b/crates/typst-pdf/Cargo.toml @@ -23,6 +23,7 @@ bytemuck = { workspace = true } comemo = { workspace = true } ecow = { workspace = true } image = { workspace = true } +infer = { workspace = true } krilla = { workspace = true } krilla-svg = { workspace = true } serde = { workspace = true } diff --git a/crates/typst-pdf/src/embed.rs b/crates/typst-pdf/src/embed.rs index 6ed65a2b6..f0cd9060a 100644 --- a/crates/typst-pdf/src/embed.rs +++ b/crates/typst-pdf/src/embed.rs @@ -34,6 +34,8 @@ pub(crate) fn embed_files( }, }; let data: Arc + Send + Sync> = Arc::new(embed.data.clone()); + // TODO: update when new krilla version lands (https://github.com/LaurenzV/krilla/pull/203) + let compress = should_compress(&embed.data).unwrap_or(true); let file = EmbeddedFile { path, @@ -41,7 +43,7 @@ pub(crate) fn embed_files( description, association_kind, data: data.into(), - compress: true, + compress, location: Some(span.into_raw().get()), }; @@ -52,3 +54,69 @@ pub(crate) fn embed_files( Ok(()) } + +fn should_compress(data: &[u8]) -> Option { + let ty = infer::get(data)?; + match ty.matcher_type() { + infer::MatcherType::App => None, + infer::MatcherType::Archive => match ty.mime_type() { + #[rustfmt::skip] + "application/zip" + | "application/vnd.rar" + | "application/gzip" + | "application/x-bzip2" + | "application/vnd.bzip3" + | "application/x-7z-compressed" + | "application/x-xz" + | "application/vnd.ms-cab-compressed" + | "application/vnd.debian.binary-package" + | "application/x-compress" + | "application/x-lzip" + | "application/x-rpm" + | "application/zstd" + | "application/x-lz4" + | "application/x-ole-storage" => Some(false), + _ => None, + }, + infer::MatcherType::Audio => match ty.mime_type() { + #[rustfmt::skip] + "audio/mpeg" + | "audio/m4a" + | "audio/opus" + | "audio/ogg" + | "audio/x-flac" + | "audio/amr" + | "audio/aac" + | "audio/x-ape" => Some(false), + _ => None, + }, + infer::MatcherType::Book => None, + infer::MatcherType::Doc => None, + infer::MatcherType::Font => None, + infer::MatcherType::Image => match ty.mime_type() { + #[rustfmt::skip] + "image/jpeg" + | "image/jp2" + | "image/png" + | "image/webp" + | "image/vnd.ms-photo" + | "image/heif" + | "image/avif" + | "image/jxl" + | "image/vnd.djvu" => None, + _ => None, + }, + infer::MatcherType::Text => None, + infer::MatcherType::Video => match ty.mime_type() { + #[rustfmt::skip] + "video/mp4" + | "video/x-m4v" + | "video/x-matroska" + | "video/webm" + | "video/quicktime" + | "video/x-flv" => Some(false), + _ => None, + }, + infer::MatcherType::Custom => None, + } +} diff --git a/crates/typst-realize/src/lib.rs b/crates/typst-realize/src/lib.rs index 151ae76ba..7d2460a89 100644 --- a/crates/typst-realize/src/lib.rs +++ b/crates/typst-realize/src/lib.rs @@ -655,6 +655,7 @@ fn visit_grouping_rules<'a>( let matching = s.rules.iter().find(|&rule| (rule.trigger)(content, &s.kind)); // Try to continue or finish an existing grouping. + let mut i = 0; while let Some(active) = s.groupings.last() { // Start a nested group if a rule with higher priority matches. if matching.is_some_and(|rule| rule.priority > active.rule.priority) { @@ -670,6 +671,16 @@ fn visit_grouping_rules<'a>( } finish_innermost_grouping(s)?; + i += 1; + if i > 512 { + // It seems like this case is only hit when there is a cycle between + // a show rule and a grouping rule. The show rule produces content + // that is matched by a grouping rule, which is then again processed + // by the show rule, and so on. The two must be at an equilibrium, + // otherwise either the "maximum show rule depth" or "maximum + // grouping depth" errors are triggered. + bail!(content.span(), "maximum grouping depth exceeded"); + } } // Start a new grouping. diff --git a/crates/typst-svg/src/image.rs b/crates/typst-svg/src/image.rs index d74432026..1868ca39b 100644 --- a/crates/typst-svg/src/image.rs +++ b/crates/typst-svg/src/image.rs @@ -45,6 +45,7 @@ pub fn convert_image_to_base64_url(image: &Image) -> EcoString { ExchangeFormat::Png => "png", ExchangeFormat::Jpg => "jpeg", ExchangeFormat::Gif => "gif", + ExchangeFormat::Webp => "webp", }, raster.data(), ), diff --git a/crates/typst/src/lib.rs b/crates/typst/src/lib.rs index 580ba9e80..a6bb4fe38 100644 --- a/crates/typst/src/lib.rs +++ b/crates/typst/src/lib.rs @@ -27,7 +27,6 @@ //! [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; diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index bbae06792..3620d4fda 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -12,6 +12,7 @@ Let's start with a broad overview of the directories in this repository: - `crates/typst-cli`: Typst's command line interface. This is a relatively small layer on top of the compiler and the exporters. - `crates/typst-eval`: The interpreter for the Typst language. +- `crates/typst-html`: The HTML exporter. - `crates/typst-ide`: Exposes IDE functionality. - `crates/typst-kit`: Contains various default implementation of functionality used in `typst-cli`. diff --git a/docs/tutorial/1-writing.md b/docs/tutorial/1-writing.md index acc257830..d505d2d03 100644 --- a/docs/tutorial/1-writing.md +++ b/docs/tutorial/1-writing.md @@ -69,7 +69,7 @@ the first item of the list above by indenting it. ## Adding a figure { #figure } You think that your report would benefit from a figure. Let's add one. Typst -supports images in the formats PNG, JPEG, GIF, and SVG. To add an image file to +supports images in the formats PNG, JPEG, GIF, SVG, and WebP. To add an image file to your project, first open the _file panel_ by clicking the box icon in the left sidebar. Here, you can see a list of all files in your project. Currently, there is only one: The main Typst file you are writing in. To upload another file, diff --git a/flake.lock b/flake.lock index ad47d29cd..dedfbb4e0 100644 --- a/flake.lock +++ b/flake.lock @@ -112,13 +112,13 @@ "rust-manifest": { "flake": false, "locked": { - "narHash": "sha256-irgHsBXecwlFSdmP9MfGP06Cbpca2QALJdbN4cymcko=", + "narHash": "sha256-BwfxWd/E8gpnXoKsucFXhMbevMlVgw3l0becLkIcWCU=", "type": "file", - "url": "https://static.rust-lang.org/dist/channel-rust-1.85.0.toml" + "url": "https://static.rust-lang.org/dist/channel-rust-1.87.0.toml" }, "original": { "type": "file", - "url": "https://static.rust-lang.org/dist/channel-rust-1.85.0.toml" + "url": "https://static.rust-lang.org/dist/channel-rust-1.87.0.toml" } }, "systems": { diff --git a/flake.nix b/flake.nix index 6938f6e57..1b2b3abc8 100644 --- a/flake.nix +++ b/flake.nix @@ -10,7 +10,7 @@ inputs.nixpkgs.follows = "nixpkgs"; }; rust-manifest = { - url = "https://static.rust-lang.org/dist/channel-rust-1.85.0.toml"; + url = "https://static.rust-lang.org/dist/channel-rust-1.87.0.toml"; flake = false; }; }; diff --git a/tests/ref/bibliography-basic.png b/tests/ref/bibliography-basic.png index 0844eaf81..86d02cc69 100644 Binary files a/tests/ref/bibliography-basic.png and b/tests/ref/bibliography-basic.png differ diff --git a/tests/ref/bibliography-before-content.png b/tests/ref/bibliography-before-content.png index eb9f26d83..a16ea1df5 100644 Binary files a/tests/ref/bibliography-before-content.png and b/tests/ref/bibliography-before-content.png differ diff --git a/tests/ref/bibliography-grid-par.png b/tests/ref/bibliography-grid-par.png index 5befbcc54..0757e92c5 100644 Binary files a/tests/ref/bibliography-grid-par.png and b/tests/ref/bibliography-grid-par.png differ diff --git a/tests/ref/bibliography-indent-par.png b/tests/ref/bibliography-indent-par.png index 02278124b..031e28974 100644 Binary files a/tests/ref/bibliography-indent-par.png and b/tests/ref/bibliography-indent-par.png differ diff --git a/tests/ref/bibliography-math.png b/tests/ref/bibliography-math.png index 6b60fef4a..6d6d88d53 100644 Binary files a/tests/ref/bibliography-math.png and b/tests/ref/bibliography-math.png differ diff --git a/tests/ref/bibliography-multiple-files.png b/tests/ref/bibliography-multiple-files.png index 3be3763f4..b2e7b0144 100644 Binary files a/tests/ref/bibliography-multiple-files.png and b/tests/ref/bibliography-multiple-files.png differ diff --git a/tests/ref/bibliography-ordering.png b/tests/ref/bibliography-ordering.png index c19b7e7d0..ad5e86019 100644 Binary files a/tests/ref/bibliography-ordering.png and b/tests/ref/bibliography-ordering.png differ diff --git a/tests/ref/block-consistent-width.png b/tests/ref/block-consistent-width.png index 045603cb8..f181bd335 100644 Binary files a/tests/ref/block-consistent-width.png and b/tests/ref/block-consistent-width.png differ diff --git a/tests/ref/cite-footnote.png b/tests/ref/cite-footnote.png index dd2cf8bdb..e89b1dc12 100644 Binary files a/tests/ref/cite-footnote.png and b/tests/ref/cite-footnote.png differ diff --git a/tests/ref/cite-form.png b/tests/ref/cite-form.png index a9fbe7515..91a6d2e26 100644 Binary files a/tests/ref/cite-form.png and b/tests/ref/cite-form.png differ diff --git a/tests/ref/cite-group.png b/tests/ref/cite-group.png index 02772f499..d512d07e0 100644 Binary files a/tests/ref/cite-group.png and b/tests/ref/cite-group.png differ diff --git a/tests/ref/cite-grouping-and-ordering.png b/tests/ref/cite-grouping-and-ordering.png index 8dea1b533..69c1db475 100644 Binary files a/tests/ref/cite-grouping-and-ordering.png and b/tests/ref/cite-grouping-and-ordering.png differ diff --git a/tests/ref/figure-basic.png b/tests/ref/figure-basic.png index 69388755f..eed77cc3a 100644 Binary files a/tests/ref/figure-basic.png and b/tests/ref/figure-basic.png differ diff --git a/tests/ref/footnote-basic.png b/tests/ref/footnote-basic.png index 44ace9035..7351bbb7f 100644 Binary files a/tests/ref/footnote-basic.png and b/tests/ref/footnote-basic.png differ diff --git a/tests/ref/footnote-block-at-end.png b/tests/ref/footnote-block-at-end.png index 09880aaba..15d0bb593 100644 Binary files a/tests/ref/footnote-block-at-end.png and b/tests/ref/footnote-block-at-end.png differ diff --git a/tests/ref/footnote-block-fr.png b/tests/ref/footnote-block-fr.png index 451e4d778..80781fd1d 100644 Binary files a/tests/ref/footnote-block-fr.png and b/tests/ref/footnote-block-fr.png differ diff --git a/tests/ref/footnote-break-across-pages-block.png b/tests/ref/footnote-break-across-pages-block.png index f2882dbcd..fd512950b 100644 Binary files a/tests/ref/footnote-break-across-pages-block.png and b/tests/ref/footnote-break-across-pages-block.png differ diff --git a/tests/ref/footnote-break-across-pages-float.png b/tests/ref/footnote-break-across-pages-float.png index 5d3ea1078..a343137b8 100644 Binary files a/tests/ref/footnote-break-across-pages-float.png and b/tests/ref/footnote-break-across-pages-float.png differ diff --git a/tests/ref/footnote-break-across-pages-nested.png b/tests/ref/footnote-break-across-pages-nested.png index f87658ce8..79b09cfb9 100644 Binary files a/tests/ref/footnote-break-across-pages-nested.png and b/tests/ref/footnote-break-across-pages-nested.png differ diff --git a/tests/ref/footnote-break-across-pages.png b/tests/ref/footnote-break-across-pages.png index 878227cb9..ba985d1eb 100644 Binary files a/tests/ref/footnote-break-across-pages.png and b/tests/ref/footnote-break-across-pages.png differ diff --git a/tests/ref/footnote-duplicate.png b/tests/ref/footnote-duplicate.png index b5a73f74b..e95228e48 100644 Binary files a/tests/ref/footnote-duplicate.png and b/tests/ref/footnote-duplicate.png differ diff --git a/tests/ref/footnote-entry.png b/tests/ref/footnote-entry.png index dd09acb92..9e0108f88 100644 Binary files a/tests/ref/footnote-entry.png and b/tests/ref/footnote-entry.png differ diff --git a/tests/ref/footnote-float-priority.png b/tests/ref/footnote-float-priority.png index 1017ed51c..7d649d55c 100644 Binary files a/tests/ref/footnote-float-priority.png and b/tests/ref/footnote-float-priority.png differ diff --git a/tests/ref/footnote-in-caption.png b/tests/ref/footnote-in-caption.png index 79b2b5d0f..cd4f837bb 100644 Binary files a/tests/ref/footnote-in-caption.png and b/tests/ref/footnote-in-caption.png differ diff --git a/tests/ref/footnote-in-columns.png b/tests/ref/footnote-in-columns.png index 281ec8836..8b5f1201d 100644 Binary files a/tests/ref/footnote-in-columns.png and b/tests/ref/footnote-in-columns.png differ diff --git a/tests/ref/footnote-in-list.png b/tests/ref/footnote-in-list.png index daf94e950..edb24156b 100644 Binary files a/tests/ref/footnote-in-list.png and b/tests/ref/footnote-in-list.png differ diff --git a/tests/ref/footnote-in-place.png b/tests/ref/footnote-in-place.png index b500ac80f..2f9681e23 100644 Binary files a/tests/ref/footnote-in-place.png and b/tests/ref/footnote-in-place.png differ diff --git a/tests/ref/footnote-in-table.png b/tests/ref/footnote-in-table.png index e110eac6d..2c7e42309 100644 Binary files a/tests/ref/footnote-in-table.png and b/tests/ref/footnote-in-table.png differ diff --git a/tests/ref/footnote-invariant.png b/tests/ref/footnote-invariant.png index a183ba7ab..23838c368 100644 Binary files a/tests/ref/footnote-invariant.png and b/tests/ref/footnote-invariant.png differ diff --git a/tests/ref/footnote-multiple-in-one-line.png b/tests/ref/footnote-multiple-in-one-line.png index 12def79ba..41faa1fb1 100644 Binary files a/tests/ref/footnote-multiple-in-one-line.png and b/tests/ref/footnote-multiple-in-one-line.png differ diff --git a/tests/ref/footnote-nested-break-across-pages.png b/tests/ref/footnote-nested-break-across-pages.png index 889fb0671..0903214c2 100644 Binary files a/tests/ref/footnote-nested-break-across-pages.png and b/tests/ref/footnote-nested-break-across-pages.png differ diff --git a/tests/ref/footnote-nested.png b/tests/ref/footnote-nested.png index 06617d984..365bef4e7 100644 Binary files a/tests/ref/footnote-nested.png and b/tests/ref/footnote-nested.png differ diff --git a/tests/ref/footnote-ref-call.png b/tests/ref/footnote-ref-call.png index afc103211..635865e41 100644 Binary files a/tests/ref/footnote-ref-call.png and b/tests/ref/footnote-ref-call.png differ diff --git a/tests/ref/footnote-ref-forward.png b/tests/ref/footnote-ref-forward.png index afb4d7cb5..f222b4be8 100644 Binary files a/tests/ref/footnote-ref-forward.png and b/tests/ref/footnote-ref-forward.png differ diff --git a/tests/ref/footnote-ref-in-footnote.png b/tests/ref/footnote-ref-in-footnote.png index 944985983..73901b479 100644 Binary files a/tests/ref/footnote-ref-in-footnote.png and b/tests/ref/footnote-ref-in-footnote.png differ diff --git a/tests/ref/footnote-ref-multiple.png b/tests/ref/footnote-ref-multiple.png index 899afca16..59e9fecef 100644 Binary files a/tests/ref/footnote-ref-multiple.png and b/tests/ref/footnote-ref-multiple.png differ diff --git a/tests/ref/footnote-ref.png b/tests/ref/footnote-ref.png index 812514eac..278f0e594 100644 Binary files a/tests/ref/footnote-ref.png and b/tests/ref/footnote-ref.png differ diff --git a/tests/ref/footnote-space-collapsing.png b/tests/ref/footnote-space-collapsing.png index bad0691fb..6936f3c13 100644 Binary files a/tests/ref/footnote-space-collapsing.png and b/tests/ref/footnote-space-collapsing.png differ diff --git a/tests/ref/footnote-styling.png b/tests/ref/footnote-styling.png index a68cca9dd..f9400ee7e 100644 Binary files a/tests/ref/footnote-styling.png and b/tests/ref/footnote-styling.png differ diff --git a/tests/ref/gradient-math-conic.png b/tests/ref/gradient-math-conic.png index ffd3e8068..9bac6c3d4 100644 Binary files a/tests/ref/gradient-math-conic.png and b/tests/ref/gradient-math-conic.png differ diff --git a/tests/ref/gradient-math-dir.png b/tests/ref/gradient-math-dir.png index 8d33f51f5..c2f5bcef5 100644 Binary files a/tests/ref/gradient-math-dir.png and b/tests/ref/gradient-math-dir.png differ diff --git a/tests/ref/gradient-math-mat.png b/tests/ref/gradient-math-mat.png index ecf953038..d003d6d03 100644 Binary files a/tests/ref/gradient-math-mat.png and b/tests/ref/gradient-math-mat.png differ diff --git a/tests/ref/gradient-math-misc.png b/tests/ref/gradient-math-misc.png index 13f5c27b3..419481384 100644 Binary files a/tests/ref/gradient-math-misc.png and b/tests/ref/gradient-math-misc.png differ diff --git a/tests/ref/gradient-math-radial.png b/tests/ref/gradient-math-radial.png index 8d0047bbe..97fb17e6f 100644 Binary files a/tests/ref/gradient-math-radial.png and b/tests/ref/gradient-math-radial.png differ diff --git a/tests/ref/grid-rtl-counter.png b/tests/ref/grid-rtl-counter.png new file mode 100644 index 000000000..fb0df44ad Binary files /dev/null and b/tests/ref/grid-rtl-counter.png differ diff --git a/tests/ref/grid-rtl-rowspan-counter-equal.png b/tests/ref/grid-rtl-rowspan-counter-equal.png new file mode 100644 index 000000000..fb0df44ad Binary files /dev/null and b/tests/ref/grid-rtl-rowspan-counter-equal.png differ diff --git a/tests/ref/grid-rtl-rowspan-counter-mixed-1.png b/tests/ref/grid-rtl-rowspan-counter-mixed-1.png new file mode 100644 index 000000000..fffccc566 Binary files /dev/null and b/tests/ref/grid-rtl-rowspan-counter-mixed-1.png differ diff --git a/tests/ref/grid-rtl-rowspan-counter-mixed-2.png b/tests/ref/grid-rtl-rowspan-counter-mixed-2.png new file mode 100644 index 000000000..c091f3a80 Binary files /dev/null and b/tests/ref/grid-rtl-rowspan-counter-mixed-2.png differ diff --git a/tests/ref/grid-rtl-rowspan-counter-unequal-1.png b/tests/ref/grid-rtl-rowspan-counter-unequal-1.png new file mode 100644 index 000000000..c091f3a80 Binary files /dev/null and b/tests/ref/grid-rtl-rowspan-counter-unequal-1.png differ diff --git a/tests/ref/grid-rtl-rowspan-counter-unequal-2.png b/tests/ref/grid-rtl-rowspan-counter-unequal-2.png new file mode 100644 index 000000000..fffccc566 Binary files /dev/null and b/tests/ref/grid-rtl-rowspan-counter-unequal-2.png differ diff --git a/tests/ref/issue-1433-footnote-in-list.png b/tests/ref/issue-1433-footnote-in-list.png index a012e2345..19934a709 100644 Binary files a/tests/ref/issue-1433-footnote-in-list.png and b/tests/ref/issue-1433-footnote-in-list.png differ diff --git a/tests/ref/issue-1597-cite-footnote.png b/tests/ref/issue-1597-cite-footnote.png index 6ec017c76..e7c076b14 100644 Binary files a/tests/ref/issue-1597-cite-footnote.png and b/tests/ref/issue-1597-cite-footnote.png differ diff --git a/tests/ref/issue-1617-mat-align.png b/tests/ref/issue-1617-mat-align.png index bd4ea16fe..73d8ae824 100644 Binary files a/tests/ref/issue-1617-mat-align.png and b/tests/ref/issue-1617-mat-align.png differ diff --git a/tests/ref/issue-2531-cite-show-set.png b/tests/ref/issue-2531-cite-show-set.png index 568c77e56..93b69b7d8 100644 Binary files a/tests/ref/issue-2531-cite-show-set.png and b/tests/ref/issue-2531-cite-show-set.png differ diff --git a/tests/ref/issue-3481-cite-location.png b/tests/ref/issue-3481-cite-location.png index 01139e25f..110ee4a24 100644 Binary files a/tests/ref/issue-3481-cite-location.png and b/tests/ref/issue-3481-cite-location.png differ diff --git a/tests/ref/issue-3699-cite-twice-et-al.png b/tests/ref/issue-3699-cite-twice-et-al.png index 62921dd64..46b98c39d 100644 Binary files a/tests/ref/issue-3699-cite-twice-et-al.png and b/tests/ref/issue-3699-cite-twice-et-al.png differ diff --git a/tests/ref/issue-3774-math-call-empty-2d-args.png b/tests/ref/issue-3774-math-call-empty-2d-args.png index c1bf52d00..52472d8db 100644 Binary files a/tests/ref/issue-3774-math-call-empty-2d-args.png and b/tests/ref/issue-3774-math-call-empty-2d-args.png differ diff --git a/tests/ref/issue-4618-bibliography-set-heading-level.png b/tests/ref/issue-4618-bibliography-set-heading-level.png index 3bf2096e3..6de2bed29 100644 Binary files a/tests/ref/issue-4618-bibliography-set-heading-level.png and b/tests/ref/issue-4618-bibliography-set-heading-level.png differ diff --git a/tests/ref/issue-4828-math-number-multi-char.png b/tests/ref/issue-4828-math-number-multi-char.png index ff0a9bab9..b365645d3 100644 Binary files a/tests/ref/issue-4828-math-number-multi-char.png and b/tests/ref/issue-4828-math-number-multi-char.png differ diff --git a/tests/ref/issue-5256-multiple-footnotes-in-footnote.png b/tests/ref/issue-5256-multiple-footnotes-in-footnote.png index f9c173351..f32d192e6 100644 Binary files a/tests/ref/issue-5256-multiple-footnotes-in-footnote.png and b/tests/ref/issue-5256-multiple-footnotes-in-footnote.png differ diff --git a/tests/ref/issue-5354-footnote-empty-frame-infinite-loop.png b/tests/ref/issue-5354-footnote-empty-frame-infinite-loop.png index acad56b68..99da601ae 100644 Binary files a/tests/ref/issue-5354-footnote-empty-frame-infinite-loop.png and b/tests/ref/issue-5354-footnote-empty-frame-infinite-loop.png differ diff --git a/tests/ref/issue-5435-footnote-migration-in-floats.png b/tests/ref/issue-5435-footnote-migration-in-floats.png index 672a5af83..58d71fc72 100644 Binary files a/tests/ref/issue-5435-footnote-migration-in-floats.png and b/tests/ref/issue-5435-footnote-migration-in-floats.png differ diff --git a/tests/ref/issue-5489-matrix-stray-linebreak.png b/tests/ref/issue-5489-matrix-stray-linebreak.png new file mode 100644 index 000000000..f4e41bf99 Binary files /dev/null and b/tests/ref/issue-5489-matrix-stray-linebreak.png differ diff --git a/tests/ref/issue-5496-footnote-in-float-never-fits.png b/tests/ref/issue-5496-footnote-in-float-never-fits.png index 4ae5903d8..85851f5ad 100644 Binary files a/tests/ref/issue-5496-footnote-in-float-never-fits.png and b/tests/ref/issue-5496-footnote-in-float-never-fits.png differ diff --git a/tests/ref/issue-5496-footnote-never-fits-multiple.png b/tests/ref/issue-5496-footnote-never-fits-multiple.png index 24fcf7098..000a10745 100644 Binary files a/tests/ref/issue-5496-footnote-never-fits-multiple.png and b/tests/ref/issue-5496-footnote-never-fits-multiple.png differ diff --git a/tests/ref/issue-5496-footnote-never-fits.png b/tests/ref/issue-5496-footnote-never-fits.png index 4ae5903d8..85851f5ad 100644 Binary files a/tests/ref/issue-5496-footnote-never-fits.png and b/tests/ref/issue-5496-footnote-never-fits.png differ diff --git a/tests/ref/issue-5496-footnote-separator-never-fits.png b/tests/ref/issue-5496-footnote-separator-never-fits.png index 4fe4fdee3..3b342619c 100644 Binary files a/tests/ref/issue-5496-footnote-separator-never-fits.png and b/tests/ref/issue-5496-footnote-separator-never-fits.png differ diff --git a/tests/ref/issue-5503-cite-group-interrupted-by-par-align.png b/tests/ref/issue-5503-cite-group-interrupted-by-par-align.png index 166331587..8c91ad552 100644 Binary files a/tests/ref/issue-5503-cite-group-interrupted-by-par-align.png and b/tests/ref/issue-5503-cite-group-interrupted-by-par-align.png differ diff --git a/tests/ref/issue-5503-cite-in-align.png b/tests/ref/issue-5503-cite-in-align.png index aeb72aa0d..eabc4dc3d 100644 Binary files a/tests/ref/issue-5503-cite-in-align.png and b/tests/ref/issue-5503-cite-in-align.png differ diff --git a/tests/ref/issue-6170-equation-stroke.png b/tests/ref/issue-6170-equation-stroke.png new file mode 100644 index 000000000..a375931b5 Binary files /dev/null and b/tests/ref/issue-6170-equation-stroke.png differ diff --git a/tests/ref/issue-622-hide-meta-cite.png b/tests/ref/issue-622-hide-meta-cite.png index c3c9b188a..1fe3f2519 100644 Binary files a/tests/ref/issue-622-hide-meta-cite.png and b/tests/ref/issue-622-hide-meta-cite.png differ diff --git a/tests/ref/issue-6242-tight-list-attach-spacing.png b/tests/ref/issue-6242-tight-list-attach-spacing.png new file mode 100644 index 000000000..48920008b Binary files /dev/null and b/tests/ref/issue-6242-tight-list-attach-spacing.png differ diff --git a/tests/ref/issue-758-link-repeat.png b/tests/ref/issue-758-link-repeat.png index aaec20d23..5db44c314 100644 Binary files a/tests/ref/issue-758-link-repeat.png and b/tests/ref/issue-758-link-repeat.png differ diff --git a/tests/ref/issue-785-cite-locate.png b/tests/ref/issue-785-cite-locate.png index d387ed0d5..c80314544 100644 Binary files a/tests/ref/issue-785-cite-locate.png and b/tests/ref/issue-785-cite-locate.png differ diff --git a/tests/ref/issue-footnotes-skip-first-page.png b/tests/ref/issue-footnotes-skip-first-page.png index b7a8ce62c..7e4dbd566 100644 Binary files a/tests/ref/issue-footnotes-skip-first-page.png and b/tests/ref/issue-footnotes-skip-first-page.png differ diff --git a/tests/ref/linebreak-cite-punctuation.png b/tests/ref/linebreak-cite-punctuation.png index f544aca4d..8e9e945d0 100644 Binary files a/tests/ref/linebreak-cite-punctuation.png and b/tests/ref/linebreak-cite-punctuation.png differ diff --git a/tests/ref/linebreak-link-end.png b/tests/ref/linebreak-link-end.png index bcc88751c..43c771a85 100644 Binary files a/tests/ref/linebreak-link-end.png and b/tests/ref/linebreak-link-end.png differ diff --git a/tests/ref/linebreak-link-justify.png b/tests/ref/linebreak-link-justify.png index a80e30743..3175f657c 100644 Binary files a/tests/ref/linebreak-link-justify.png and b/tests/ref/linebreak-link-justify.png differ diff --git a/tests/ref/linebreak-link.png b/tests/ref/linebreak-link.png index 19eba3054..2f6b77eaa 100644 Binary files a/tests/ref/linebreak-link.png and b/tests/ref/linebreak-link.png differ diff --git a/tests/ref/link-basic.png b/tests/ref/link-basic.png index 0d2bd7533..f53223ffd 100644 Binary files a/tests/ref/link-basic.png and b/tests/ref/link-basic.png differ diff --git a/tests/ref/link-bracket-balanced.png b/tests/ref/link-bracket-balanced.png index 8b7e02db2..01bfa8797 100644 Binary files a/tests/ref/link-bracket-balanced.png and b/tests/ref/link-bracket-balanced.png differ diff --git a/tests/ref/link-bracket-unbalanced-closing.png b/tests/ref/link-bracket-unbalanced-closing.png index f54ad32c4..dbd418a0a 100644 Binary files a/tests/ref/link-bracket-unbalanced-closing.png and b/tests/ref/link-bracket-unbalanced-closing.png differ diff --git a/tests/ref/link-show.png b/tests/ref/link-show.png index ac6df7fef..69e70ac98 100644 Binary files a/tests/ref/link-show.png and b/tests/ref/link-show.png differ diff --git a/tests/ref/link-to-label.png b/tests/ref/link-to-label.png index 633ee9881..39433c13c 100644 Binary files a/tests/ref/link-to-label.png and b/tests/ref/link-to-label.png differ diff --git a/tests/ref/link-to-page.png b/tests/ref/link-to-page.png index 2dbf76778..d618f066b 100644 Binary files a/tests/ref/link-to-page.png and b/tests/ref/link-to-page.png differ diff --git a/tests/ref/link-trailing-period.png b/tests/ref/link-trailing-period.png index b458d201a..80e0bd70a 100644 Binary files a/tests/ref/link-trailing-period.png and b/tests/ref/link-trailing-period.png differ diff --git a/tests/ref/link-transformed.png b/tests/ref/link-transformed.png index 4efa32f3c..c391f080b 100644 Binary files a/tests/ref/link-transformed.png and b/tests/ref/link-transformed.png differ diff --git a/tests/ref/math-accent-bottom-high-base.png b/tests/ref/math-accent-bottom-high-base.png new file mode 100644 index 000000000..4893575cb Binary files /dev/null and b/tests/ref/math-accent-bottom-high-base.png differ diff --git a/tests/ref/math-accent-bottom-sized.png b/tests/ref/math-accent-bottom-sized.png new file mode 100644 index 000000000..5455b2f5b Binary files /dev/null and b/tests/ref/math-accent-bottom-sized.png differ diff --git a/tests/ref/math-accent-bottom-subscript.png b/tests/ref/math-accent-bottom-subscript.png new file mode 100644 index 000000000..818544445 Binary files /dev/null and b/tests/ref/math-accent-bottom-subscript.png differ diff --git a/tests/ref/math-accent-bottom-wide-base.png b/tests/ref/math-accent-bottom-wide-base.png new file mode 100644 index 000000000..fb4a1169b Binary files /dev/null and b/tests/ref/math-accent-bottom-wide-base.png differ diff --git a/tests/ref/math-accent-bottom.png b/tests/ref/math-accent-bottom.png new file mode 100644 index 000000000..bd1b92146 Binary files /dev/null and b/tests/ref/math-accent-bottom.png differ diff --git a/tests/ref/math-accent-nested.png b/tests/ref/math-accent-nested.png new file mode 100644 index 000000000..4b3d58f38 Binary files /dev/null and b/tests/ref/math-accent-nested.png differ diff --git a/tests/ref/math-accent-wide-base.png b/tests/ref/math-accent-wide-base.png index af716bf45..793ab30bd 100644 Binary files a/tests/ref/math-accent-wide-base.png and b/tests/ref/math-accent-wide-base.png differ diff --git a/tests/ref/math-attach-kerning-mixed.png b/tests/ref/math-attach-kerning-mixed.png index 9d0bea27a..64a546869 100644 Binary files a/tests/ref/math-attach-kerning-mixed.png and b/tests/ref/math-attach-kerning-mixed.png differ diff --git a/tests/ref/math-attach-limit-long.png b/tests/ref/math-attach-limit-long.png index b79e6ed4a..555f9c66c 100644 Binary files a/tests/ref/math-attach-limit-long.png and b/tests/ref/math-attach-limit-long.png differ diff --git a/tests/ref/math-attach-prescripts.png b/tests/ref/math-attach-prescripts.png index f0d21cb8a..bcd60c0a0 100644 Binary files a/tests/ref/math-attach-prescripts.png and b/tests/ref/math-attach-prescripts.png differ diff --git a/tests/ref/math-call-symbol.png b/tests/ref/math-call-symbol.png new file mode 100644 index 000000000..8308bece1 Binary files /dev/null and b/tests/ref/math-call-symbol.png differ diff --git a/tests/ref/math-cases-gap.png b/tests/ref/math-cases-gap.png index 746572fac..6bd8e2055 100644 Binary files a/tests/ref/math-cases-gap.png and b/tests/ref/math-cases-gap.png differ diff --git a/tests/ref/math-cases-linebreaks.png b/tests/ref/math-cases-linebreaks.png index eb4971c46..65b4e4025 100644 Binary files a/tests/ref/math-cases-linebreaks.png and b/tests/ref/math-cases-linebreaks.png differ diff --git a/tests/ref/math-cases.png b/tests/ref/math-cases.png index ed0423def..34567837d 100644 Binary files a/tests/ref/math-cases.png and b/tests/ref/math-cases.png differ diff --git a/tests/ref/math-equation-numbering.png b/tests/ref/math-equation-numbering.png index b127a9a1d..9606b30dd 100644 Binary files a/tests/ref/math-equation-numbering.png and b/tests/ref/math-equation-numbering.png differ diff --git a/tests/ref/math-frac-precedence.png b/tests/ref/math-frac-precedence.png index fd16f2e6b..bddcb43c3 100644 Binary files a/tests/ref/math-frac-precedence.png and b/tests/ref/math-frac-precedence.png differ diff --git a/tests/ref/math-linebreaking-after-relation-without-space.png b/tests/ref/math-linebreaking-after-relation-without-space.png index 7c569ad1f..fb1413768 100644 Binary files a/tests/ref/math-linebreaking-after-relation-without-space.png and b/tests/ref/math-linebreaking-after-relation-without-space.png differ diff --git a/tests/ref/math-mat-align-explicit-alternating.png b/tests/ref/math-mat-align-explicit-alternating.png index 1ebcc7b68..52a51378b 100644 Binary files a/tests/ref/math-mat-align-explicit-alternating.png and b/tests/ref/math-mat-align-explicit-alternating.png differ diff --git a/tests/ref/math-mat-align-explicit-left.png b/tests/ref/math-mat-align-explicit-left.png index cb9819248..09c5cb3d4 100644 Binary files a/tests/ref/math-mat-align-explicit-left.png and b/tests/ref/math-mat-align-explicit-left.png differ diff --git a/tests/ref/math-mat-align-explicit-mixed.png b/tests/ref/math-mat-align-explicit-mixed.png index 88ccd6de7..f2b38b3eb 100644 Binary files a/tests/ref/math-mat-align-explicit-mixed.png and b/tests/ref/math-mat-align-explicit-mixed.png differ diff --git a/tests/ref/math-mat-align-explicit-right.png b/tests/ref/math-mat-align-explicit-right.png index b537e6571..5db5378f9 100644 Binary files a/tests/ref/math-mat-align-explicit-right.png and b/tests/ref/math-mat-align-explicit-right.png differ diff --git a/tests/ref/math-mat-align-implicit.png b/tests/ref/math-mat-align-implicit.png index b184d9140..cd6833155 100644 Binary files a/tests/ref/math-mat-align-implicit.png and b/tests/ref/math-mat-align-implicit.png differ diff --git a/tests/ref/math-mat-align-signed-numbers.png b/tests/ref/math-mat-align-signed-numbers.png index c92743797..4463e2fb1 100644 Binary files a/tests/ref/math-mat-align-signed-numbers.png and b/tests/ref/math-mat-align-signed-numbers.png differ diff --git a/tests/ref/math-mat-align.png b/tests/ref/math-mat-align.png index 66513dd53..eaa3233d8 100644 Binary files a/tests/ref/math-mat-align.png and b/tests/ref/math-mat-align.png differ diff --git a/tests/ref/math-mat-augment-set.png b/tests/ref/math-mat-augment-set.png index c5881b139..1a6676159 100644 Binary files a/tests/ref/math-mat-augment-set.png and b/tests/ref/math-mat-augment-set.png differ diff --git a/tests/ref/math-mat-augment.png b/tests/ref/math-mat-augment.png index 0e2a42a24..306c4b199 100644 Binary files a/tests/ref/math-mat-augment.png and b/tests/ref/math-mat-augment.png differ diff --git a/tests/ref/math-mat-baseline.png b/tests/ref/math-mat-baseline.png index d2f266213..01928f724 100644 Binary files a/tests/ref/math-mat-baseline.png and b/tests/ref/math-mat-baseline.png differ diff --git a/tests/ref/math-mat-gap.png b/tests/ref/math-mat-gap.png index e4f87b59b..90525ff2e 100644 Binary files a/tests/ref/math-mat-gap.png and b/tests/ref/math-mat-gap.png differ diff --git a/tests/ref/math-mat-gaps.png b/tests/ref/math-mat-gaps.png index 405358776..95cd6cf11 100644 Binary files a/tests/ref/math-mat-gaps.png and b/tests/ref/math-mat-gaps.png differ diff --git a/tests/ref/math-mat-linebreaks.png b/tests/ref/math-mat-linebreaks.png index 52ff0a8bb..6666749da 100644 Binary files a/tests/ref/math-mat-linebreaks.png and b/tests/ref/math-mat-linebreaks.png differ diff --git a/tests/ref/math-mat-sparse.png b/tests/ref/math-mat-sparse.png index e9f0d948c..c255fe3e5 100644 Binary files a/tests/ref/math-mat-sparse.png and b/tests/ref/math-mat-sparse.png differ diff --git a/tests/ref/math-mat-spread.png b/tests/ref/math-mat-spread.png index dc8b2bf7e..b8f539cca 100644 Binary files a/tests/ref/math-mat-spread.png and b/tests/ref/math-mat-spread.png differ diff --git a/tests/ref/math-root-frame-size-index.png b/tests/ref/math-root-frame-size-index.png index 41d4df2e9..f47049568 100644 Binary files a/tests/ref/math-root-frame-size-index.png and b/tests/ref/math-root-frame-size-index.png differ diff --git a/tests/ref/math-root-large-index.png b/tests/ref/math-root-large-index.png index 85689823d..29dd478fe 100644 Binary files a/tests/ref/math-root-large-index.png and b/tests/ref/math-root-large-index.png differ diff --git a/tests/ref/math-shorthands.png b/tests/ref/math-shorthands.png index 65b35acad..19bdb6be5 100644 Binary files a/tests/ref/math-shorthands.png and b/tests/ref/math-shorthands.png differ diff --git a/tests/ref/math-vec-align-explicit-alternating.png b/tests/ref/math-vec-align-explicit-alternating.png index 1ebcc7b68..52a51378b 100644 Binary files a/tests/ref/math-vec-align-explicit-alternating.png and b/tests/ref/math-vec-align-explicit-alternating.png differ diff --git a/tests/ref/math-vec-align.png b/tests/ref/math-vec-align.png index 680d0936d..07d58df72 100644 Binary files a/tests/ref/math-vec-align.png and b/tests/ref/math-vec-align.png differ diff --git a/tests/ref/math-vec-gap.png b/tests/ref/math-vec-gap.png index e48b3e902..ccfb21711 100644 Binary files a/tests/ref/math-vec-gap.png and b/tests/ref/math-vec-gap.png differ diff --git a/tests/ref/math-vec-linebreaks.png b/tests/ref/math-vec-linebreaks.png index 52ff0a8bb..6666749da 100644 Binary files a/tests/ref/math-vec-linebreaks.png and b/tests/ref/math-vec-linebreaks.png differ diff --git a/tests/ref/math-vec-wide.png b/tests/ref/math-vec-wide.png index 9dc887a8c..000e3cf2a 100644 Binary files a/tests/ref/math-vec-wide.png and b/tests/ref/math-vec-wide.png differ diff --git a/tests/ref/measure-citation-deeply-nested.png b/tests/ref/measure-citation-deeply-nested.png index 596c351eb..6711fc732 100644 Binary files a/tests/ref/measure-citation-deeply-nested.png and b/tests/ref/measure-citation-deeply-nested.png differ diff --git a/tests/ref/measure-citation-in-flow.png b/tests/ref/measure-citation-in-flow.png index 18617beda..83f92aac4 100644 Binary files a/tests/ref/measure-citation-in-flow.png and b/tests/ref/measure-citation-in-flow.png differ diff --git a/tests/ref/par-semantic-align.png b/tests/ref/par-semantic-align.png index eda496411..202236efe 100644 Binary files a/tests/ref/par-semantic-align.png and b/tests/ref/par-semantic-align.png differ diff --git a/tests/ref/quote-cite-format-author-date.png b/tests/ref/quote-cite-format-author-date.png index 4931969d5..dbd6a8d8f 100644 Binary files a/tests/ref/quote-cite-format-author-date.png and b/tests/ref/quote-cite-format-author-date.png differ diff --git a/tests/ref/quote-cite-format-label-or-numeric.png b/tests/ref/quote-cite-format-label-or-numeric.png index d1dadf0e2..22654a0d7 100644 Binary files a/tests/ref/quote-cite-format-label-or-numeric.png and b/tests/ref/quote-cite-format-label-or-numeric.png differ diff --git a/tests/ref/quote-cite-format-note.png b/tests/ref/quote-cite-format-note.png index 0cde539bd..bef078c9f 100644 Binary files a/tests/ref/quote-cite-format-note.png and b/tests/ref/quote-cite-format-note.png differ diff --git a/tests/ref/quote-inline.png b/tests/ref/quote-inline.png index c09faa3a8..9205d6839 100644 Binary files a/tests/ref/quote-inline.png and b/tests/ref/quote-inline.png differ diff --git a/tests/ref/ref-basic.png b/tests/ref/ref-basic.png index 79655eba9..d9068d93d 100644 Binary files a/tests/ref/ref-basic.png and b/tests/ref/ref-basic.png differ diff --git a/tests/ref/ref-form-page-unambiguous.png b/tests/ref/ref-form-page-unambiguous.png index e7baa2f2c..3b37f115b 100644 Binary files a/tests/ref/ref-form-page-unambiguous.png and b/tests/ref/ref-form-page-unambiguous.png differ diff --git a/tests/ref/ref-form-page.png b/tests/ref/ref-form-page.png index 52fde86d7..0cc29a4f1 100644 Binary files a/tests/ref/ref-form-page.png and b/tests/ref/ref-form-page.png differ diff --git a/tests/ref/ref-supplements.png b/tests/ref/ref-supplements.png index fd715339f..e400c1feb 100644 Binary files a/tests/ref/ref-supplements.png and b/tests/ref/ref-supplements.png differ diff --git a/tests/ref/show-text-citation-smartquote.png b/tests/ref/show-text-citation-smartquote.png index 604ecc24f..fc18b5a52 100644 Binary files a/tests/ref/show-text-citation-smartquote.png and b/tests/ref/show-text-citation-smartquote.png differ diff --git a/tests/ref/show-text-citation.png b/tests/ref/show-text-citation.png index a0e684935..e5ae8d317 100644 Binary files a/tests/ref/show-text-citation.png and b/tests/ref/show-text-citation.png differ diff --git a/tests/ref/show-text-in-citation.png b/tests/ref/show-text-in-citation.png index 392487bc8..6533a4f7c 100644 Binary files a/tests/ref/show-text-in-citation.png and b/tests/ref/show-text-in-citation.png differ diff --git a/tests/ref/smartquote-ru.png b/tests/ref/smartquote-ru.png index 05c79263f..867121d1e 100644 Binary files a/tests/ref/smartquote-ru.png and b/tests/ref/smartquote-ru.png differ diff --git a/tests/ref/smartquote-uk.png b/tests/ref/smartquote-uk.png new file mode 100644 index 000000000..7ac1c032e Binary files /dev/null and b/tests/ref/smartquote-uk.png differ diff --git a/tests/ref/table-header-citation.png b/tests/ref/table-header-citation.png index 462198078..b9463b044 100644 Binary files a/tests/ref/table-header-citation.png and b/tests/ref/table-header-citation.png differ diff --git a/tests/src/collect.rs b/tests/src/collect.rs index 33f4f7366..84af04d2d 100644 --- a/tests/src/collect.rs +++ b/tests/src/collect.rs @@ -30,7 +30,8 @@ pub struct Test { impl Display for Test { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{} ({})", self.name, self.pos) + // underline path + write!(f, "{} (\x1B[4m{}\x1B[0m)", self.name, self.pos) } } diff --git a/tests/src/tests.rs b/tests/src/tests.rs index 26eb63beb..0ed2fa469 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -59,7 +59,9 @@ fn main() { fn setup() { // Make all paths relative to the workspace. That's nicer for IDEs when // clicking on paths printed to the terminal. - std::env::set_current_dir("..").unwrap(); + let workspace_dir = + Path::new(env!("CARGO_MANIFEST_DIR")).join(std::path::Component::ParentDir); + std::env::set_current_dir(workspace_dir).unwrap(); // Create the storage. for ext in ["render", "html", "pdf", "svg"] { diff --git a/tests/suite/layout/grid/rtl.typ b/tests/suite/layout/grid/rtl.typ index 7c0e999a2..e79b465ab 100644 --- a/tests/suite/layout/grid/rtl.typ +++ b/tests/suite/layout/grid/rtl.typ @@ -193,3 +193,143 @@ ), ..range(0, 10).map(i => ([\##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten() ) + +--- grid-rtl-counter --- +// Test interaction between RTL and counters +#set text(dir: rtl) +#let test = counter("test") +#grid( + columns: (1fr, 1fr), + inset: 5pt, + align: center, + [ + a: // should produce 1 + #test.step() + #context test.get().first() + ], + [ + b: // should produce 2 + #test.step() + #context test.get().first() + ], +) + +--- grid-rtl-rowspan-counter-equal --- +// Test interaction between RTL and counters +#set text(dir: rtl) +#let test = counter("test") +#grid( + columns: (1fr, 1fr), + inset: 5pt, + align: center, + grid.cell(rowspan: 2, [ + a: // should produce 1 + #test.step() + #context test.get().first() + ]), + grid.cell(rowspan: 2, [ + b: // should produce 2 + #test.step() + #context test.get().first() + ]), +) + +--- grid-rtl-rowspan-counter-unequal-1 --- +// Test interaction between RTL and counters +#set text(dir: rtl) +#let test = counter("test") +#grid( + columns: (1fr, 1fr), + inset: 5pt, + align: center, + grid.cell(rowspan: 5, [ + b: // will produce 2 + #test.step() + #context test.get().first() + ]), + grid.cell(rowspan: 2, [ + a: // will produce 1 + #test.step() + #context test.get().first() + ]), + grid.cell(rowspan: 3, [ + c: // will produce 3 + #test.step() + #context test.get().first() + ]), +) + +--- grid-rtl-rowspan-counter-unequal-2 --- +// Test interaction between RTL and counters +#set text(dir: rtl) +#let test = counter("test") +#grid( + columns: (1fr, 1fr), + inset: 5pt, + align: center, + grid.cell(rowspan: 2, [ + a: // will produce 1 + #test.step() + #context test.get().first() + ]), + grid.cell(rowspan: 5, [ + b: // will produce 2 + #test.step() + #context test.get().first() + ]), + grid.cell(rowspan: 3, [ + c: // will produce 3 + #test.step() + #context test.get().first() + ]), +) + +--- grid-rtl-rowspan-counter-mixed-1 --- +// Test interaction between RTL and counters +#set text(dir: rtl) +#let test = counter("test") +#grid( + columns: (1fr, 1fr), + inset: 5pt, + align: center, + [ + a: // will produce 1 + #test.step() + #context test.get().first() + ], + grid.cell(rowspan: 2, [ + b: // will produce 2 + #test.step() + #context test.get().first() + ]), + [ + c: // will produce 3 + #test.step() + #context test.get().first() + ], +) + +--- grid-rtl-rowspan-counter-mixed-2 --- +// Test interaction between RTL and counters +#set text(dir: rtl) +#let test = counter("test") +#grid( + columns: (1fr, 1fr), + inset: 5pt, + align: center, + grid.cell(rowspan: 2, [ + b: // will produce 2 + #test.step() + #context test.get().first() + ]), + [ + a: // will produce 1 + #test.step() + #context test.get().first() + ], + [ + c: // will produce 3 + #test.step() + #context test.get().first() + ] +) diff --git a/tests/suite/layout/inline/linebreak.typ b/tests/suite/layout/inline/linebreak.typ index e4b04b245..86a900252 100644 --- a/tests/suite/layout/inline/linebreak.typ +++ b/tests/suite/layout/inline/linebreak.typ @@ -139,3 +139,11 @@ Some texts feature many longer words. Those are often exceedingly challenging to break in a visually pleasing way. + +--- issue-5489-matrix-stray-linebreak --- +#table( + columns: (70pt,) * 1, + align: horizon + center, + stroke: 0.6pt, + [$mat(2241/2210,-71/1105;-71/1105,147/1105)$], +) diff --git a/tests/suite/math/accent.typ b/tests/suite/math/accent.typ index ecc0588c5..2239d8975 100644 --- a/tests/suite/math/accent.typ +++ b/tests/suite/math/accent.typ @@ -58,3 +58,31 @@ $hat(a) hat(A)$ $tilde(w) tilde(W)$ $grave(i) grave(j)$ $grave(I) grave(J)$ + +--- math-accent-bottom --- +// Test bottom accents. +$accent(a, \u{20EE}), accent(T, \u{0323}), accent(xi, \u{0332}), + accent(f, \u{20ED}), accent(F, \u{20E8}), accent(y, \u{032E}), + accent(!, \u{032F}), accent(J, \u{0333}), accent(p, \u{0331})$ + +--- math-accent-bottom-wide-base --- +// Test wide base with bottom accents. +$accent(x + y, \u{20EF}), accent(sum, \u{032D})$ + +--- math-accent-bottom-subscript --- +// Test effect of bottom accent on subscript. +$q_x != accent(q, \u{032C})_x != accent(accent(q, \u{032C}), \u{032C})_x$ + +--- math-accent-bottom-high-base --- +// Test high base with bottom accents. +$ accent(integral, \u{20EC}), accent(integral, \u{20EC})_a^b, accent(integral_a^b, \u{20EC}) $ + +--- math-accent-bottom-sized --- +// Test bottom accent size. +$accent(sum, \u{0330}), accent(sum, \u{0330}, size: #50%), accent(H, \u{032D}, size: #200%)$ + +--- math-accent-nested --- +// Test nested top and bottom accents. +$hat(accent(L, \u{0330})), accent(circle(p), \u{0323}), + macron(accent(caron(accent(A, \u{20ED})), \u{0333})) \ + breve(accent(eta, \u{032E})) = accent(breve(eta), \u{032E})$ diff --git a/tests/suite/math/call.typ b/tests/suite/math/call.typ index 5caacfac6..54b97ceb0 100644 --- a/tests/suite/math/call.typ +++ b/tests/suite/math/call.typ @@ -221,6 +221,15 @@ $ // Hint: 4-6 or if you meant to display this as text, try placing it in quotes: `"ab"` $ 5ab $ +--- math-call-symbol --- +$ phi(x) $ +$ phi(x, y) $ +$ phi(1,2,,3,) $ + +--- math-call-symbol-named-argument --- +// Error: 10-18 unexpected argument: alpha +$ phi(x, alpha: y) $ + --- issue-3774-math-call-empty-2d-args --- $ mat(;,) $ // Add some whitespace/trivia: diff --git a/tests/suite/math/equation.typ b/tests/suite/math/equation.typ index 148a49d02..189f6e6db 100644 --- a/tests/suite/math/equation.typ +++ b/tests/suite/math/equation.typ @@ -297,3 +297,10 @@ Looks at the @quadratic formula. #set page(width: 150pt) #set text(lang: "he") תהא סדרה $a_n$: $[a_n: 1, 1/2, 1/3, dots]$ + +--- issue-6170-equation-stroke --- +// In this bug stroke settings did not apply to math content. +// We expect all of these to have a green stroke. +#set text(stroke: green + 0.5pt) + +A $B^2$ $ grave(C)' $ diff --git a/tests/suite/math/multiline.typ b/tests/suite/math/multiline.typ index 34e66b99c..70838dd8c 100644 --- a/tests/suite/math/multiline.typ +++ b/tests/suite/math/multiline.typ @@ -99,6 +99,9 @@ Multiple trailing line breaks. #let hrule(x) = box(line(length: x)) #hrule(90pt)$<;$\ #hrule(95pt)$<;$\ +// We don't linebreak before a closing paren, but do before an opening paren. +#hrule(90pt)$<($\ +#hrule(95pt)$<($ #hrule(90pt)$<)$\ #hrule(95pt)$<)$ diff --git a/tests/suite/model/list.typ b/tests/suite/model/list.typ index 9bed930bb..796a7b069 100644 --- a/tests/suite/model/list.typ +++ b/tests/suite/model/list.typ @@ -304,3 +304,11 @@ World - C - = D E + +--- issue-6242-tight-list-attach-spacing --- +// Nested tight lists should be uniformly spaced when list spacing is set. +#set list(spacing: 1.2em) +- A + - B + - C +- C diff --git a/tests/suite/model/numbering.typ b/tests/suite/model/numbering.typ index 6af989ff1..2d6a3d6a6 100644 --- a/tests/suite/model/numbering.typ +++ b/tests/suite/model/numbering.typ @@ -19,50 +19,32 @@ // Greek. #t( pat: "α", - "𐆊", "αʹ", "βʹ", "γʹ", "δʹ", "εʹ", "ϛʹ", "ζʹ", "ηʹ", "θʹ", "ιʹ", - "ιαʹ", "ιβʹ", "ιγʹ", "ιδʹ", "ιεʹ", "ιϛʹ", "ιζʹ", "ιηʹ", "ιθʹ", "κʹ", - 241, "σμαʹ", - 999, "ϡϙθʹ", + "𐆊", "α", "β", "γ", "δ", "ε", "ϛ", "ζ", "η", "θ", "ι", + "ια", "ιβ", "ιγ", "ιδ", "ιε", "ιϛ", "ιζ", "ιη", "ιθ", "κ", + 241, "σμα", + 999, "ϡϟθ", 1005, "͵αε", - 1999, "͵αϡϙθ", - 2999, "͵βϡϙθ", + 1999, "͵αϡϟθ", + 2999, "͵βϡϟθ", 3000, "͵γ", - 3398, "͵γτϙη", + 3398, "͵γτϟη", 4444, "͵δυμδ", 5683, "͵εχπγ", 9184, "͵θρπδ", - 9999, "͵θϡϙθ", - 20000, "αΜβʹ", - 20001, "αΜβʹ, αʹ", - 97554, "αΜθʹ, ͵ζφνδ", - 99999, "αΜθʹ, ͵θϡϙθ", - 1000000, "αΜρʹ", - 1000001, "αΜρʹ, αʹ", - 1999999, "αΜρϙθʹ, ͵θϡϙθ", - 2345678, "αΜσλδʹ, ͵εχοη", - 9999999, "αΜϡϙθʹ, ͵θϡϙθ", - 10000000, "αΜ͵α", - 90000001, "αΜ͵θ, αʹ", - 100000000, "βΜαʹ", - 1000000000, "βΜιʹ", - 2000000000, "βΜκʹ", - 2000000001, "βΜκʹ, αʹ", - 2000010001, "βΜκʹ, αΜαʹ, αʹ", - 2056839184, "βΜκʹ, αΜ͵εχπγ, ͵θρπδ", - 12312398676, "βΜρκγʹ, αΜ͵ασλθ, ͵ηχοϛ", + 9999, "͵θϡϟθ", ) #t( pat: sym.Alpha, - "𐆊", "Αʹ", "Βʹ", "Γʹ", "Δʹ", "Εʹ", "Ϛʹ", "Ζʹ", "Ηʹ", "Θʹ", "Ιʹ", - "ΙΑʹ", "ΙΒʹ", "ΙΓʹ", "ΙΔʹ", "ΙΕʹ", "ΙϚʹ", "ΙΖʹ", "ΙΗʹ", "ΙΘʹ", "Κʹ", - 241, "ΣΜΑʹ", + "𐆊", "Α", "Β", "Γ", "Δ", "Ε", "Ϛ", "Ζ", "Η", "Θ", "Ι", + "ΙΑ", "ΙΒ", "ΙΓ", "ΙΔ", "ΙΕ", "ΙϚ", "ΙΖ", "ΙΗ", "ΙΘ", "Κ", + 241, "ΣΜΑ", ) // Symbols. #t(pat: "*", "-", "*", "†", "‡", "§", "¶", "‖", "**") // Hebrew. -#t(pat: "א", step: 2, 9, "ט׳", "י״א", "י״ג") +#t(pat: "א", step: 2, 9, "ט", "יא", "יג", 15, "טו", 16, "טז") // Chinese. #t(pat: "一", step: 2, 9, "九", "十一", "十三", "十五", "十七", "十九") diff --git a/tests/suite/styling/show.typ b/tests/suite/styling/show.typ index e8ddf5534..f3d9efd55 100644 --- a/tests/suite/styling/show.typ +++ b/tests/suite/styling/show.typ @@ -258,3 +258,11 @@ I am *strong*, I am _emphasized_, and I am #[special]. = Hello *strong* + +--- issue-5690-oom-par-box --- +// Error: 3:6-5:1 maximum grouping depth exceeded +#show par: box + +Hello + +World diff --git a/tests/suite/text/smartquote.typ b/tests/suite/text/smartquote.typ index f2af93ceb..6eab35076 100644 --- a/tests/suite/text/smartquote.typ +++ b/tests/suite/text/smartquote.typ @@ -46,6 +46,10 @@ #set text(lang: "ru") "Лошадь не ест салат из огурцов" - это была первая фраза, сказанная по 'телефону'. +--- smartquote-uk --- +#set text(lang: "uk") +"Кінь не їсть огірковий салат" — перше речення, коли-небудь вимовлене по 'телефону'. + --- smartquote-it --- #set text(lang: "it") "Il cavallo non mangia insalata di cetrioli" è stata la prima frase pronunciata al 'telefono'. diff --git a/tests/suite/visualize/image.typ b/tests/suite/visualize/image.typ index 9a77870af..73c4feff8 100644 --- a/tests/suite/visualize/image.typ +++ b/tests/suite/visualize/image.typ @@ -243,7 +243,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B --- image-png-but-pixmap-format --- #image( read("/assets/images/tiger.jpg", encoding: none), - // Error: 11-18 expected "png", "jpg", "gif", dictionary, "svg", or auto + // Error: 11-18 expected "png", "jpg", "gif", "webp", dictionary, "svg", or auto format: "rgba8", )