From 6d835ecb9278490f645ad0a1b86ec84e752d3220 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 1 Jul 2024 15:04:58 +0200 Subject: [PATCH 01/34] Optimize optimized paragraph layout (#4483) --- crates/typst/src/layout/inline/line.rs | 63 +- crates/typst/src/layout/inline/linebreak.rs | 679 +++++++++++++++----- crates/typst/src/layout/inline/mod.rs | 2 +- 3 files changed, 591 insertions(+), 153 deletions(-) diff --git a/crates/typst/src/layout/inline/line.rs b/crates/typst/src/layout/inline/line.rs index 2473f958c..232a1c6b7 100644 --- a/crates/typst/src/layout/inline/line.rs +++ b/crates/typst/src/layout/inline/line.rs @@ -3,7 +3,7 @@ use unicode_bidi::BidiInfo; use super::*; use crate::engine::Engine; use crate::layout::{Abs, Em, Fr, Frame, FrameItem, Point}; -use crate::text::TextElem; +use crate::text::{Lang, TextElem}; use crate::utils::Numeric; /// A layouted line, consisting of a sequence of layouted paragraph items that @@ -99,6 +99,15 @@ impl<'a> Line<'a> { self.items().filter_map(Item::text).map(|s| s.shrinkability()).sum() } + /// Whether the line has items with negative width. + pub fn has_negative_width_items(&self) -> bool { + self.items().any(|item| match item { + Item::Absolute(amount, _) => *amount < Abs::zero(), + Item::Frame(frame, _) => frame.width() < Abs::zero(), + _ => false, + }) + } + /// The sum of fractions in the line. pub fn fr(&self) -> Fr { self.items() @@ -129,7 +138,7 @@ pub fn line<'a>( p: &'a Preparation, mut range: Range, breakpoint: Breakpoint, - prepend_hyphen: bool, + pred: Option<&Line>, ) -> Line<'a> { let end = range.end; let mut justify = @@ -149,6 +158,8 @@ pub fn line<'a>( }; } + let prepend_hyphen = pred.map_or(false, should_insert_hyphen); + // Slice out the relevant items. let (mut expanded, mut inner) = p.slice(range.clone()); let mut width = Abs::zero(); @@ -528,6 +539,54 @@ fn reorder<'a>(line: &'a Line<'a>) -> (Vec<&Item<'a>>, bool) { (reordered, starts_rtl) } +/// Whether a hyphen should be inserted at the start of the next line. +fn should_insert_hyphen(pred_line: &Line) -> bool { + // If the predecessor line does not end with a Dash::HardHyphen, we shall + // not place a hyphen at the start of the next line. + if pred_line.dash != Some(Dash::HardHyphen) { + return false; + } + + // If there's a trimmed out space, we needn't repeat the hyphen. That's the + // case of a text like "...kebab é a -melhor- comida que existe", where the + // hyphens are a kind of emphasis marker. + if pred_line.trimmed.end != pred_line.end { + return false; + } + + // The hyphen should repeat only in the languages that require that feature. + // For more information see the discussion at https://github.com/typst/typst/issues/3235 + let Some(Item::Text(shape)) = pred_line.last.as_ref() else { return false }; + + match shape.lang { + // - Lower Sorbian: see https://dolnoserbski.de/ortografija/psawidla/K3 + // - Czech: see https://prirucka.ujc.cas.cz/?id=164 + // - Croatian: see http://pravopis.hr/pravilo/spojnica/68/ + // - Polish: see https://www.ortograf.pl/zasady-pisowni/lacznik-zasady-pisowni + // - Portuguese: see https://www2.senado.leg.br/bdsf/bitstream/handle/id/508145/000997415.pdf (Base XX) + // - Slovak: see https://www.zones.sk/studentske-prace/gramatika/10620-pravopis-rozdelovanie-slov/ + Lang::LOWER_SORBIAN + | Lang::CZECH + | Lang::CROATIAN + | Lang::POLISH + | Lang::PORTUGUESE + | Lang::SLOVAK => true, + + // In Spanish the hyphen is required only if the word next to hyphen is + // not capitalized. Otherwise, the hyphen must not be repeated. + // + // See § 4.1.1.1.2.e on the "Ortografía de la lengua española" + // https://www.rae.es/ortografía/como-signo-de-división-de-palabras-a-final-de-línea + Lang::SPANISH => pred_line.bidi.text[pred_line.end..] + .chars() + .next() + .map(|c| !c.is_uppercase()) + .unwrap_or(false), + + _ => false, + } +} + /// How much a character should hang into the end margin. /// /// For more discussion, see: diff --git a/crates/typst/src/layout/inline/linebreak.rs b/crates/typst/src/layout/inline/linebreak.rs index ddf7937bf..0555c1890 100644 --- a/crates/typst/src/layout/inline/linebreak.rs +++ b/crates/typst/src/layout/inline/linebreak.rs @@ -1,3 +1,5 @@ +use std::ops::{Add, Sub}; + use icu_properties::maps::CodePointMapData; use icu_properties::LineBreak; use icu_provider::AsDeserializingBufferProvider; @@ -8,11 +10,23 @@ use once_cell::sync::Lazy; use super::*; use crate::engine::Engine; -use crate::layout::Abs; +use crate::layout::{Abs, Em}; use crate::model::Linebreaks; use crate::syntax::link_prefix; use crate::text::{Lang, TextElem}; +/// The cost of a line or paragraph layout. +type Cost = f64; + +// Cost parameters. +const DEFAULT_HYPH_COST: Cost = 0.5; +const DEFAULT_RUNT_COST: Cost = 0.5; +const CONSECUTIVE_DASH_COST: Cost = 0.3; +const MAX_COST: Cost = 1_000_000.0; +const MIN_RATIO: f64 = -1.0; +const MIN_APPROX_RATIO: f64 = -0.5; +const BOUND_EPS: f64 = 1e-3; + /// The general line break segmenter. static SEGMENTER: Lazy = Lazy::new(|| { let provider = @@ -84,10 +98,8 @@ fn linebreak_simple<'a>( let mut last = None; breakpoints(p, |end, breakpoint| { - let prepend_hyphen = lines.last().map(should_repeat_hyphen).unwrap_or(false); - // Compute the line and its size. - let mut attempt = line(engine, p, start..end, breakpoint, prepend_hyphen); + let mut attempt = line(engine, p, start..end, breakpoint, lines.last()); // If the line doesn't fit anymore, we push the last fitting attempt // into the stack and rebuild the line from the attempt's end. The @@ -96,7 +108,7 @@ fn linebreak_simple<'a>( if let Some((last_attempt, last_end)) = last.take() { lines.push(last_attempt); start = last_end; - attempt = line(engine, p, start..end, breakpoint, prepend_hyphen); + attempt = line(engine, p, start..end, breakpoint, lines.last()); } } @@ -142,144 +154,142 @@ fn linebreak_optimized<'a>( p: &'a Preparation<'a>, width: Abs, ) -> Vec> { - /// The cost of a line or paragraph layout. - type Cost = f64; + let metrics = CostMetrics::compute(p); - /// An entry in the dynamic programming table. + // Determines the exact costs of a likely good layout through Knuth-Plass + // with approximate metrics. We can use this cost as an upper bound to prune + // the search space in our proper optimization pass below. + let upper_bound = linebreak_optimized_approximate(engine, p, width, &metrics); + + // Using the upper bound, perform exact optimized linebreaking. + linebreak_optimized_bounded(engine, p, width, &metrics, upper_bound) +} + +/// Performs line breaking in optimized Knuth-Plass style, but with an upper +/// bound on the cost. This allows us to skip many parts of the search space. +#[typst_macros::time] +fn linebreak_optimized_bounded<'a>( + engine: &Engine, + p: &'a Preparation<'a>, + width: Abs, + metrics: &CostMetrics, + upper_bound: Cost, +) -> Vec> { + /// An entry in the dynamic programming table for paragraph optimization. struct Entry<'a> { pred: usize, total: Cost, line: Line<'a>, } - // Cost parameters. - const DEFAULT_HYPH_COST: Cost = 0.5; - const DEFAULT_RUNT_COST: Cost = 0.5; - const CONSECUTIVE_DASH_COST: Cost = 0.3; - const MAX_COST: Cost = 1_000_000.0; - const MIN_RATIO: f64 = -1.0; - - let hyph_cost = DEFAULT_HYPH_COST * p.costs.hyphenation().get(); - let runt_cost = DEFAULT_RUNT_COST * p.costs.runt().get(); - // Dynamic programming table. - let mut active = 0; let mut table = vec![Entry { pred: 0, total: 0.0, - line: line(engine, p, 0..0, Breakpoint::Mandatory, false), + line: line(engine, p, 0..0, Breakpoint::Mandatory, None), }]; - let em = p.size; - let mut lines = Vec::with_capacity(16); + let mut active = 0; + let mut prev_end = 0; + breakpoints(p, |end, breakpoint| { - let k = table.len(); - let is_end = end == p.bidi.text.len(); + // Find the optimal predecessor. let mut best: Option = None; - // Find the optimal predecessor. - for (i, pred) in table.iter().enumerate().skip(active) { - // Layout the line. + // A lower bound for the cost of all following line attempts. + let mut line_lower_bound = None; + + for (pred_index, pred) in table.iter().enumerate().skip(active) { let start = pred.line.end; - let prepend_hyphen = should_repeat_hyphen(&pred.line); + let unbreakable = prev_end == start; - let attempt = line(engine, p, start..end, breakpoint, prepend_hyphen); - - // Determine how much the line's spaces would need to be stretched - // to make it the desired width. - let delta = width - attempt.width; - // Determine how much stretch are permitted. - let adjust = if delta >= Abs::zero() { - attempt.stretchability() - } else { - attempt.shrinkability() - }; - // Ideally, the ratio should between -1.0 and 1.0, but sometimes a - // value above 1.0 is possible, in which case the line is underfull. - let mut ratio = delta / adjust; - if ratio.is_nan() { - // The line is not stretchable, but it just fits. This often - // happens with monospace fonts and CJK texts. - ratio = 0.0; - } - if ratio > 1.0 { - // We should stretch the line above its stretchability. Now - // calculate the extra amount. Also, don't divide by zero. - let extra_stretch = - (delta - adjust) / attempt.justifiables().max(1) as f64; - // Normalize the amount by half Em size. - ratio = 1.0 + extra_stretch / (em / 2.0); + // If the minimum cost we've established for the line is already + // too much, skip this attempt. + if line_lower_bound + .is_some_and(|lower| pred.total + lower > upper_bound + BOUND_EPS) + { + continue; } - // Determine the cost of the line. - let min_ratio = if p.justify { MIN_RATIO } else { 0.0 }; - let mut cost = if ratio < min_ratio { - // The line is overfull. This is the case if - // - justification is on, but we'd need to shrink too much - // - justification is off and the line just doesn't fit - // - // If this is the earliest breakpoint in the active set - // (active == i), remove it from the active set. If there is an - // earlier one (active < i), then the logically shorter line was - // in fact longer (can happen with negative spacing) and we - // can't trim the active set just yet. - if active == i { - active += 1; - } - MAX_COST - } else if breakpoint == Breakpoint::Mandatory || is_end { - // This is a mandatory break and the line is not overfull, so - // all breakpoints before this one become inactive since no line - // can span above the mandatory break. - active = k; - // - If ratio > 0, we need to stretch the line only when justify - // is needed. - // - If ratio < 0, we always need to shrink the line. - if (ratio > 0.0 && attempt.justify) || ratio < 0.0 { - ratio.powi(3).abs() - } else { - 0.0 - } - } else { - // Normal line with cost of |ratio^3|. - ratio.powi(3).abs() - }; + // Build the line. + let attempt = line(engine, p, start..end, breakpoint, Some(&pred.line)); - // Penalize runts. - if k == i + 1 && is_end { - cost += runt_cost; - } + // Determine the cost of the line and its stretch ratio. + let (line_ratio, line_cost) = ratio_and_cost( + p, + metrics, + width, + &pred.line, + &attempt, + breakpoint, + unbreakable, + ); - // Penalize hyphens. - if breakpoint == Breakpoint::Hyphen { - cost += hyph_cost; - } - - // In Knuth paper, cost = (1 + 100|r|^3 + p)^2 + a, - // where r is the ratio, p=50 is the penalty, and a=3000 is - // consecutive the penalty. We divide the whole formula by 10, - // resulting (0.01 + |r|^3 + p)^2 + a, where p=0.5 and a=0.3 - cost = (0.01 + cost).powi(2); - - // Penalize two consecutive dashes (not necessarily hyphens) extra. - if attempt.dash.is_some() && pred.line.dash.is_some() { - cost += CONSECUTIVE_DASH_COST; + // If the line is overfull, we adjust the set of active candidate + // line starts. This is the case if + // - justification is on, but we'd need to shrink too much + // - justification is off and the line just doesn't fit + // + // If this is the earliest breakpoint in the active set + // (active == i), remove it from the active set. If there is an + // earlier one (active < i), then the logically shorter line was + // in fact longer (can happen with negative spacing) and we + // can't trim the active set just yet. + if line_ratio < metrics.min_ratio && active == pred_index { + active += 1; } // The total cost of this line and its chain of predecessors. - let total = pred.total + cost; + let total = pred.total + line_cost; + + // If the line is already underfull (`line_ratio > 0`), any shorter + // slice of the line will be even more underfull. So it'll only get + // worse from here and further attempts would also have a cost + // exceeding `bound`. There is one exception: When the line has + // negative spacing, we can't know for sure, so we don't assign the + // lower bound in that case. + if line_ratio > 0.0 + && line_lower_bound.is_none() + && !attempt.has_negative_width_items() + { + line_lower_bound = Some(line_cost); + } + + // If the cost already exceeds the upper bound, we don't need to + // integrate this result into the table. + if total > upper_bound + BOUND_EPS { + continue; + } // If this attempt is better than what we had before, take it! if best.as_ref().map_or(true, |best| best.total >= total) { - best = Some(Entry { pred: i, total, line: attempt }); + best = Some(Entry { pred: pred_index, total, line: attempt }); } } - table.push(best.unwrap()); + // If this is a mandatory break, all breakpoints before this one become + // inactive since no line can span over the mandatory break. + if breakpoint == Breakpoint::Mandatory { + active = table.len(); + } + + table.extend(best); + prev_end = end; }); // Retrace the best path. + let mut lines = Vec::with_capacity(16); let mut idx = table.len() - 1; + + // This should only happen if our bound was faulty. Which shouldn't happen! + if table[idx].line.end != p.bidi.text.len() { + #[cfg(debug_assertions)] + panic!("bounded paragraph layout is incomplete"); + + #[cfg(not(debug_assertions))] + return linebreak_optimized_bounded(engine, p, width, metrics, Cost::INFINITY); + } + while idx != 0 { table.truncate(idx + 1); let entry = table.pop().unwrap(); @@ -291,6 +301,282 @@ fn linebreak_optimized<'a>( lines } +/// Runs the normal Knuth-Plass algorithm, but instead of building proper lines +/// (which is costly) to determine costs, it determines approximate costs using +/// cummulative arrays. +/// +/// This results in a likely good paragraph layouts, for which we then compute +/// the exact cost. This cost is an upper bound for proper optimized +/// linebreaking. We can use it to heavily prune the search space. +#[typst_macros::time] +fn linebreak_optimized_approximate( + engine: &Engine, + p: &Preparation, + width: Abs, + metrics: &CostMetrics, +) -> Cost { + // Determine the cummulative estimation metrics. + let estimates = Estimates::compute(p); + + /// An entry in the dynamic programming table for paragraph optimization. + struct Entry { + pred: usize, + total: Cost, + end: usize, + unbreakable: bool, + breakpoint: Breakpoint, + } + + // Dynamic programming table. + let mut table = vec![Entry { + pred: 0, + total: 0.0, + end: 0, + unbreakable: false, + breakpoint: Breakpoint::Mandatory, + }]; + + let mut active = 0; + let mut prev_end = 0; + + breakpoints(p, |end, breakpoint| { + let at_end = end == p.bidi.text.len(); + + // Find the optimal predecessor. + let mut best: Option = None; + for (pred_index, pred) in table.iter().enumerate().skip(active) { + let start = pred.end; + let unbreakable = prev_end == start; + + // Whether the line is justified. This is not 100% accurate w.r.t + // to line()'s behaviour, but good enough. + let justify = p.justify && !at_end && breakpoint != Breakpoint::Mandatory; + + // We don't really know whether the line naturally ends with a dash + // here, so we can miss that case, but it's ok, since all of this + // just an estimate. + let consecutive_dash = + pred.breakpoint == Breakpoint::Hyphen && breakpoint == Breakpoint::Hyphen; + + // Estimate how much the line's spaces would need to be stretched to + // make it the desired width. We trim at the end to not take into + // account trailing spaces. This is, again, only an approximation of + // the real behaviour of `line`. + let trimmed_end = start + p.bidi.text[start..end].trim_end().len(); + let line_ratio = raw_ratio( + p, + width, + estimates.widths.estimate(start..trimmed_end) + + if breakpoint == Breakpoint::Hyphen { + metrics.approx_hyphen_width + } else { + Abs::zero() + }, + estimates.stretchability.estimate(start..trimmed_end), + estimates.shrinkability.estimate(start..trimmed_end), + estimates.justifiables.estimate(start..trimmed_end), + ); + + // Determine the line's cost. + let line_cost = raw_cost( + metrics, + breakpoint, + line_ratio, + at_end, + justify, + unbreakable, + consecutive_dash, + true, + ); + + // Adjust the set of active breakpoints. + // See `linebreak_optimized` for details. + if line_ratio < metrics.min_ratio && active == pred_index { + active += 1; + } + + // The total cost of this line and its chain of predecessors. + let total = pred.total + line_cost; + + // If this attempt is better than what we had before, take it! + if best.as_ref().map_or(true, |best| best.total >= total) { + best = Some(Entry { + pred: pred_index, + total, + end, + unbreakable, + breakpoint, + }); + } + } + + // If this is a mandatory break, all breakpoints before this one become + // inactive. + if breakpoint == Breakpoint::Mandatory { + active = table.len(); + } + + table.extend(best); + prev_end = end; + }); + + // Retrace the best path. + let mut indices = Vec::with_capacity(16); + let mut idx = table.len() - 1; + while idx != 0 { + indices.push(idx); + idx = table[idx].pred; + } + + let mut exact = 0.0; + let mut pred = line(engine, p, 0..0, Breakpoint::Mandatory, None); + + // The cost that we optimized was only an approximate cost, so the layout we + // got here is only likely to be good, not guaranteed to be the best. We now + // computes its exact cost as that gives us a sound upper bound for the + // proper optimization pass. + for idx in indices.into_iter().rev() { + let Entry { end, breakpoint, unbreakable, .. } = table[idx]; + + let start = pred.end; + let attempt = line(engine, p, start..end, breakpoint, Some(&pred)); + + let (_, line_cost) = + ratio_and_cost(p, metrics, width, &pred, &attempt, breakpoint, unbreakable); + + exact += line_cost; + pred = attempt; + } + + exact +} + +/// Compute the stretch ratio and cost of a line. +fn ratio_and_cost( + p: &Preparation, + metrics: &CostMetrics, + available_width: Abs, + pred: &Line, + attempt: &Line, + breakpoint: Breakpoint, + unbreakable: bool, +) -> (f64, Cost) { + let ratio = raw_ratio( + p, + available_width, + attempt.width, + attempt.stretchability(), + attempt.shrinkability(), + attempt.justifiables(), + ); + + let cost = raw_cost( + metrics, + breakpoint, + ratio, + attempt.end == p.bidi.text.len(), + attempt.justify, + unbreakable, + pred.dash.is_some() && attempt.dash.is_some(), + false, + ); + + (ratio, cost) +} + +/// Determine the stretch ratio for a line given raw metrics. +fn raw_ratio( + p: &Preparation, + available_width: Abs, + line_width: Abs, + stretchability: Abs, + shrinkability: Abs, + justifiables: usize, +) -> f64 { + // Determine how much the line's spaces would need to be stretched + // to make it the desired width. + let delta = available_width - line_width; + + // Determine how much stretch is permitted. + let adjust = if delta >= Abs::zero() { stretchability } else { shrinkability }; + + // Ideally, the ratio should between -1.0 and 1.0. + // + // A ratio above 1.0 is possible for an underfull line, but a ratio below + // -1.0 is forbidden because the line would overflow. + let mut ratio = delta / adjust; + + // The line is not stretchable, but it just fits. This often happens with + // monospace fonts and CJK texts. + if ratio.is_nan() { + ratio = 0.0; + } + + if ratio > 1.0 { + // We should stretch the line above its stretchability. Now + // calculate the extra amount. Also, don't divide by zero. + let extra_stretch = (delta - adjust) / justifiables.max(1) as f64; + // Normalize the amount by half the em size. + ratio = 1.0 + extra_stretch / (p.size / 2.0); + } + + ratio +} + +/// Compute the cost of a line given raw metrics. +#[allow(clippy::too_many_arguments)] +fn raw_cost( + metrics: &CostMetrics, + breakpoint: Breakpoint, + ratio: f64, + at_end: bool, + justify: bool, + unbreakable: bool, + consecutive_dash: bool, + approx: bool, +) -> Cost { + // Determine the cost of the line. + let mut cost = if ratio < metrics.min_ratio(approx) { + // Overfull line always has maximum cost. + MAX_COST + } else if breakpoint == Breakpoint::Mandatory || at_end { + // - If ratio < 0, we always need to shrink the line (even the last one). + // - If ratio > 0, we need to stretch the line only when it is justified + // (last line is not justified by default even if `p.justify` is true). + if ratio < 0.0 || (ratio > 0.0 && justify) { + ratio.powi(3).abs() + } else { + 0.0 + } + } else { + // Normal line with cost of |ratio^3|. + ratio.powi(3).abs() + }; + + // Penalize runts (lone words in the last line). + if unbreakable && at_end { + cost += metrics.runt_cost; + } + + // Penalize hyphenation. + if breakpoint == Breakpoint::Hyphen { + cost += metrics.hyph_cost; + } + + // In the Knuth paper, cost = (1 + 100|r|^3 + p)^2 + a, + // where r is the ratio, p=50 is the penalty, and a=3000 is + // consecutive the penalty. We divide the whole formula by 10, + // resulting (0.01 + |r|^3 + p)^2 + a, where p=0.5 and a=0.3 + let mut cost = (0.01 + cost).powi(2); + + // Penalize two consecutive dashes (not necessarily hyphens) extra. + if consecutive_dash { + cost += CONSECUTIVE_DASH_COST; + } + + cost +} + /// Calls `f` for all possible points in the text where lines can broken. /// /// Yields for each breakpoint the text index, whether the break is mandatory @@ -433,7 +719,7 @@ fn linebreak_link(link: &str, mut f: impl FnMut(usize)) { // - other -> other // - alphabetic -> numeric // - numeric -> alphabetic - // Never before after opening delimiters. + // Never before/after opening delimiters. if end > 0 && prev != Class::Open && if class == Class::Other { prev == Class::Other } else { class != prev } @@ -478,48 +764,141 @@ fn lang_at(p: &Preparation, offset: usize) -> Option { hypher::Lang::from_iso(bytes) } -/// Whether the hyphen should repeat at the start of the next line. -fn should_repeat_hyphen(pred_line: &Line) -> bool { - // If the predecessor line does not end with a Dash::HardHyphen, we shall - // not place a hyphen at the start of the next line. - if pred_line.dash != Some(Dash::HardHyphen) { - return false; +/// Resolved metrics relevant for cost computation. +struct CostMetrics { + min_ratio: f64, + min_approx_ratio: f64, + hyph_cost: Cost, + runt_cost: Cost, + approx_hyphen_width: Abs, +} + +impl CostMetrics { + /// Compute shared metrics for paragraph optimization. + fn compute(p: &Preparation) -> Self { + Self { + // When justifying, we may stretch spaces below their natural width. + min_ratio: if p.justify { MIN_RATIO } else { 0.0 }, + min_approx_ratio: if p.justify { MIN_APPROX_RATIO } else { 0.0 }, + hyph_cost: DEFAULT_HYPH_COST * p.costs.hyphenation().get(), + runt_cost: DEFAULT_RUNT_COST * p.costs.runt().get(), + // Approximate hyphen width for estimates. + approx_hyphen_width: Em::new(0.33).at(p.size), + } } - // If there's a trimmed out space, we needn't repeat the hyphen. That's the - // case of a text like "...kebab é a -melhor- comida que existe", where the - // hyphens are a kind of emphasis marker. - if pred_line.trimmed.end != pred_line.end { - return false; - } - - // The hyphen should repeat only in the languages that require that feature. - // For more information see the discussion at https://github.com/typst/typst/issues/3235 - let Some(Item::Text(shape)) = pred_line.last.as_ref() else { return false }; - - match shape.lang { - // - Lower Sorbian: see https://dolnoserbski.de/ortografija/psawidla/K3 - // - Czech: see https://prirucka.ujc.cas.cz/?id=164 - // - Croatian: see http://pravopis.hr/pravilo/spojnica/68/ - // - Polish: see https://www.ortograf.pl/zasady-pisowni/lacznik-zasady-pisowni - // - Portuguese: see https://www2.senado.leg.br/bdsf/bitstream/handle/id/508145/000997415.pdf (Base XX) - // - Slovak: see https://www.zones.sk/studentske-prace/gramatika/10620-pravopis-rozdelovanie-slov/ - Lang::LOWER_SORBIAN - | Lang::CZECH - | Lang::CROATIAN - | Lang::POLISH - | Lang::PORTUGUESE - | Lang::SLOVAK => true, - // In Spanish the hyphen is required only if the word next to hyphen is - // not capitalized. Otherwise, the hyphen must not be repeated. - // - // See § 4.1.1.1.2.e on the "Ortografía de la lengua española" - // https://www.rae.es/ortografía/como-signo-de-división-de-palabras-a-final-de-línea - Lang::SPANISH => pred_line.bidi.text[pred_line.end..] - .chars() - .next() - .map(|c| !c.is_uppercase()) - .unwrap_or(false), - _ => false, + /// The minimum line ratio we allow for shrinking. For approximate layout, + /// we allow less because otherwise we get an invalid layout fairly often, + /// which makes our bound useless. + fn min_ratio(&self, approx: bool) -> f64 { + if approx { + self.min_approx_ratio + } else { + self.min_ratio + } + } +} + +/// Estimated line metrics. +/// +/// Allows to get a quick estimate of a metric for a line between two byte +/// positions. +struct Estimates { + widths: CummulativeVec, + stretchability: CummulativeVec, + shrinkability: CummulativeVec, + justifiables: CummulativeVec, +} + +impl Estimates { + /// Compute estimations for approximate Knuth-Plass layout. + fn compute(p: &Preparation) -> Self { + let cap = p.bidi.text.len(); + + let mut widths = CummulativeVec::with_capacity(cap); + let mut stretchability = CummulativeVec::with_capacity(cap); + let mut shrinkability = CummulativeVec::with_capacity(cap); + let mut justifiables = CummulativeVec::with_capacity(cap); + + for item in &p.items { + let textual_len = item.textual_len(); + let after = widths.len() + textual_len; + + if let Item::Text(shaped) = item { + for g in shaped.glyphs.iter() { + let byte_len = g.range.len(); + let stretch = g.stretchability().0 + g.stretchability().1; + let shrink = g.shrinkability().0 + g.shrinkability().1; + widths.push(byte_len, g.x_advance.at(shaped.size)); + stretchability.push(byte_len, stretch.at(shaped.size)); + shrinkability.push(byte_len, shrink.at(shaped.size)); + justifiables.push(byte_len, g.is_justifiable() as usize); + } + } else { + widths.push(textual_len, item.width()); + } + + widths.adjust(after); + stretchability.adjust(after); + shrinkability.adjust(after); + justifiables.adjust(after); + } + + Self { + widths, + stretchability, + shrinkability, + justifiables, + } + } +} + +/// An accumulative array of a metric. +struct CummulativeVec { + total: T, + summed: Vec, +} + +impl CummulativeVec +where + T: Default + Copy + Add + Sub, +{ + /// Create a new instance with the given capacity. + fn with_capacity(capacity: usize) -> Self { + let total = T::default(); + let mut summed = Vec::with_capacity(capacity); + summed.push(total); + Self { total, summed } + } + + /// Get the covered byte length. + fn len(&self) -> usize { + self.summed.len() + } + + /// Adjust to cover the given byte length. + fn adjust(&mut self, len: usize) { + self.summed.resize(len, self.total); + } + + /// Adds a new segment with the given byte length and metric. + fn push(&mut self, byte_len: usize, metric: T) { + self.total = self.total + metric; + for _ in 0..byte_len { + self.summed.push(self.total); + } + } + + /// Estimates the metrics for the line spanned by the range. + fn estimate(&self, range: Range) -> T { + self.get(range.end) - self.get(range.start) + } + + /// Get the metric at the given byte position. + fn get(&self, index: usize) -> T { + match index.checked_sub(1) { + None => T::default(), + Some(i) => self.summed[i], + } } } diff --git a/crates/typst/src/layout/inline/mod.rs b/crates/typst/src/layout/inline/mod.rs index 94ac89f0d..f89de1690 100644 --- a/crates/typst/src/layout/inline/mod.rs +++ b/crates/typst/src/layout/inline/mod.rs @@ -9,7 +9,7 @@ use comemo::{Track, Tracked, TrackedMut}; use self::collect::{collect, Item, Segment, SpanMapper}; use self::finalize::finalize; -use self::line::{commit, line, Dash, Line}; +use self::line::{commit, line, Line}; use self::linebreak::{linebreak, Breakpoint}; use self::prepare::{prepare, Preparation}; use self::shaping::{ From aefc506424345d3e671df0aafe31330f8becdf6d Mon Sep 17 00:00:00 2001 From: Leedehai <18319900+Leedehai@users.noreply.github.com> Date: Tue, 2 Jul 2024 10:41:41 -0400 Subject: [PATCH 02/34] Use a clearer match pattern (#4437) --- crates/typst/src/math/fragment.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/typst/src/math/fragment.rs b/crates/typst/src/math/fragment.rs index 63c42cbce..da4cb0adc 100644 --- a/crates/typst/src/math/fragment.rs +++ b/crates/typst/src/math/fragment.rs @@ -120,17 +120,18 @@ impl MathFragment { } pub fn is_spaced(&self) -> bool { - self.class() == MathClass::Fence - || match self { - MathFragment::Frame(frame) => { - frame.spaced - && matches!( - frame.class, - MathClass::Normal | MathClass::Alphabetic - ) - } - _ => false, - } + if self.class() == MathClass::Fence { + return true; + } + + matches!( + self, + MathFragment::Frame(FrameFragment { + spaced: true, + class: MathClass::Normal | MathClass::Alphabetic, + .. + }) + ) } pub fn is_text_like(&self) -> bool { From 728fb7e475d024e7534aec1e47ea9b888671c1b3 Mon Sep 17 00:00:00 2001 From: Mattes Bieniarz <79235640+flauschpantoffel@users.noreply.github.com> Date: Tue, 2 Jul 2024 16:44:57 +0200 Subject: [PATCH 03/34] fix(array.slice): Clarify and correct function description (#4446) --- crates/typst/src/foundations/array.rs | 2 +- crates/typst/src/foundations/bytes.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/typst/src/foundations/array.rs b/crates/typst/src/foundations/array.rs index 2caefc133..bd7e7bca4 100644 --- a/crates/typst/src/foundations/array.rs +++ b/crates/typst/src/foundations/array.rs @@ -259,7 +259,7 @@ impl Array { .ok_or_else(|| out_of_bounds_no_default(index, self.len())) } - /// Extracts a subslice of the array. Fails with an error if the start or + /// Extracts a subslice of the array. Fails with an error if the start or end /// index is out of bounds. #[func] pub fn slice( diff --git a/crates/typst/src/foundations/bytes.rs b/crates/typst/src/foundations/bytes.rs index 1e7958595..5b77aa2a6 100644 --- a/crates/typst/src/foundations/bytes.rs +++ b/crates/typst/src/foundations/bytes.rs @@ -127,7 +127,7 @@ impl Bytes { .ok_or_else(|| out_of_bounds_no_default(index, self.len())) } - /// Extracts a subslice of the bytes. Fails with an error if the start or + /// Extracts a subslice of the bytes. Fails with an error if the start or end /// index is out of bounds. #[func] pub fn slice( From 75246f930b9041c206a8a3c87e6db03bfc9111fd Mon Sep 17 00:00:00 2001 From: Yip Coekjan <69834864+Coekjan@users.noreply.github.com> Date: Tue, 2 Jul 2024 22:46:25 +0800 Subject: [PATCH 04/34] Fix footnote-reference numbering (#4456) --- crates/typst/src/model/footnote.rs | 9 +++++++++ crates/typst/src/model/reference.rs | 4 ++-- tests/ref/issue-4454-footnote-ref-numbering.png | Bin 0 -> 830 bytes tests/suite/model/footnote.typ | 4 ++++ 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 tests/ref/issue-4454-footnote-ref-numbering.png diff --git a/crates/typst/src/model/footnote.rs b/crates/typst/src/model/footnote.rs index f8f36eb23..2aeaad1a7 100644 --- a/crates/typst/src/model/footnote.rs +++ b/crates/typst/src/model/footnote.rs @@ -93,6 +93,15 @@ impl FootnoteElem { Self::new(FootnoteBody::Reference(label)) } + /// Creates a new footnote referencing the footnote with the specified label, + /// with the other fields from the current footnote cloned. + pub fn into_ref(&self, label: Label) -> Self { + Self { + body: FootnoteBody::Reference(label), + ..self.clone() + } + } + /// Tests if this footnote is a reference to another footnote. pub fn is_ref(&self) -> bool { matches!(self.body(), FootnoteBody::Reference(_)) diff --git a/crates/typst/src/model/reference.rs b/crates/typst/src/model/reference.rs index 511ca3d9a..8194eec53 100644 --- a/crates/typst/src/model/reference.rs +++ b/crates/typst/src/model/reference.rs @@ -177,8 +177,8 @@ impl Show for Packed { let elem = elem.at(span)?; - if elem.func() == FootnoteElem::elem() { - return Ok(FootnoteElem::with_label(target).pack().spanned(span)); + if let Some(footnote) = elem.to_packed::() { + return Ok(footnote.into_ref(target).pack().spanned(span)); } let elem = elem.clone(); diff --git a/tests/ref/issue-4454-footnote-ref-numbering.png b/tests/ref/issue-4454-footnote-ref-numbering.png new file mode 100644 index 0000000000000000000000000000000000000000..0e89dbd918d21580cc3593b6f3626009409ae1a4 GIT binary patch literal 830 zcmV-E1Ht@>P)_{rvp=`}_R+`}_L(`}+F&`T6?!`T6+x`1kku_xJbq_V)Gl_4M@g^Yixd^78TV z@$m5S@bK{N@AL2P@9XR9>gw$3>g($2>gnm}=I8C_=jY|+=H=z(f z$jHaX$HvCS#l^+M#KgnH!@|PC!NI}4zP`P^y}Z1r=+B$qN1Xqp`oClpq`$doSdASo12=NnwgoIn3$NCmzS27mXni{ zl9G~0004PNkl1VE8V)Btm@1I$5rMYMxDB_ZIY#5eNX3`z-@kO533 zm15zpu)HG6hXz6^cyaW$+4kkI-}*gl3dX`YP!#bC=Nto?sFKoB0|0%r2HDdrH3;Uh z8F_atY#G747h`Oe(z-@)vF#~G-^r3BRbPXtcdsw1@+IPJ0*u~W^J*2?y3qm?r;fF= zGD#Yp1Yb47h_iq=X>;PE69nN0u7vP=auX7V`A_n!Su->J8Av+C04&a`TO^&DsHg1O zb0nQarK!N)WkE{9cDO952s?cZ7cD8cae8M*YnFK4f*|}=ANQ<8d|bF*{r~^~07*qo IM6N<$f*;(?TmS$7 literal 0 HcmV?d00001 diff --git a/tests/suite/model/footnote.typ b/tests/suite/model/footnote.typ index 34450ca4c..d72ca25a0 100644 --- a/tests/suite/model/footnote.typ +++ b/tests/suite/model/footnote.typ @@ -187,3 +187,7 @@ B #footnote[b] #set page(height: 50pt) #footnote[A] #footnote[B] + +--- issue-4454-footnote-ref-numbering --- +// Test that footnote references are numbered correctly. +A #footnote(numbering: "*")[B], C @fn, D @fn, E @fn. From 0ef672c347f368325313c8bccc4f70e3f1016b0a Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 4 Jul 2024 12:57:40 +0200 Subject: [PATCH 05/34] Refactor line building (#4497) --- Cargo.lock | 5 +- Cargo.toml | 4 +- crates/typst/src/introspection/mod.rs | 2 +- crates/typst/src/layout/inline/collect.rs | 2 +- crates/typst/src/layout/inline/line.rs | 805 ++++++++++-------- crates/typst/src/layout/inline/linebreak.rs | 141 +-- crates/typst/src/layout/inline/prepare.rs | 111 ++- crates/typst/src/layout/inline/shaping.rs | 55 +- crates/typst/src/model/par.rs | 5 +- docs/guides/guide-for-latex-users.md | 3 +- docs/reference/syntax.md | 2 +- tests/ref/bidi-whitespace-reset.png | Bin 378 -> 361 bytes tests/ref/context-compatibility-locate.png | Bin 1514 -> 1530 bytes tests/ref/eval-mode.png | Bin 850 -> 881 bytes tests/ref/issue-3601-empty-raw.png | Bin 0 -> 74 bytes .../issue-4278-par-trim-before-equation.png | Bin 0 -> 1138 bytes tests/ref/justify-basically-empty.png | Bin 0 -> 74 bytes .../ref/par-metadata-after-trimmed-space.png | Bin 0 -> 1030 bytes tests/ref/par-trailing-whitespace.png | Bin 0 -> 91 bytes tests/suite/foundations/version.typ | 2 +- tests/suite/layout/spacing.typ | 12 +- tests/suite/model/par.typ | 19 + 22 files changed, 651 insertions(+), 517 deletions(-) create mode 100644 tests/ref/issue-3601-empty-raw.png create mode 100644 tests/ref/issue-4278-par-trim-before-equation.png create mode 100644 tests/ref/justify-basically-empty.png create mode 100644 tests/ref/par-metadata-after-trimmed-space.png create mode 100644 tests/ref/par-trailing-whitespace.png diff --git a/Cargo.lock b/Cargo.lock index 3e99ea814..14dd36f11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2604,8 +2604,7 @@ dependencies = [ [[package]] name = "typst-assets" version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f13f85360328da54847dd7fefaf272dfa5b6d1fdeb53f32938924c39bf5b2c6c" +source = "git+https://github.com/typst/typst-assets?rev=4ee794c#4ee794cf8fb98eb67194e757c9820ab8562d853b" [[package]] name = "typst-cli" @@ -2656,7 +2655,7 @@ dependencies = [ [[package]] name = "typst-dev-assets" version = "0.11.0" -source = "git+https://github.com/typst/typst-dev-assets?rev=48a924d9de82b631bc775124a69384c8d860db04#48a924d9de82b631bc775124a69384c8d860db04" +source = "git+https://github.com/typst/typst-dev-assets?rev=48a924d#48a924d9de82b631bc775124a69384c8d860db04" [[package]] name = "typst-docs" diff --git a/Cargo.toml b/Cargo.toml index ee50b6667..1b5bf0f4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,8 +26,8 @@ typst-svg = { path = "crates/typst-svg", version = "0.11.0" } typst-syntax = { path = "crates/typst-syntax", version = "0.11.0" } typst-timing = { path = "crates/typst-timing", version = "0.11.0" } typst-utils = { path = "crates/typst-utils", version = "0.11.0" } -typst-assets = "0.11.0" -typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "48a924d9de82b631bc775124a69384c8d860db04" } +typst-assets = { git = "https://github.com/typst/typst-assets", rev = "4ee794c" } +typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "48a924d" } az = "1.2" base64 = "0.22" bitflags = { version = "2", features = ["serde"] } diff --git a/crates/typst/src/introspection/mod.rs b/crates/typst/src/introspection/mod.rs index c9dba244a..6c982afb4 100644 --- a/crates/typst/src/introspection/mod.rs +++ b/crates/typst/src/introspection/mod.rs @@ -116,6 +116,6 @@ impl Tag { impl Debug for Tag { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Tag({:?})", self.elem) + write!(f, "Tag({:?})", self.elem.elem().name()) } } diff --git a/crates/typst/src/layout/inline/collect.rs b/crates/typst/src/layout/inline/collect.rs index 79d0d59f6..f1607460b 100644 --- a/crates/typst/src/layout/inline/collect.rs +++ b/crates/typst/src/layout/inline/collect.rs @@ -79,7 +79,7 @@ impl<'a> Item<'a> { } /// The natural layouted width of the item. - pub fn width(&self) -> Abs { + pub fn natural_width(&self) -> Abs { match self { Self::Text(shaped) => shaped.width, Self::Absolute(v, _) => *v, diff --git a/crates/typst/src/layout/inline/line.rs b/crates/typst/src/layout/inline/line.rs index 232a1c6b7..12162ab16 100644 --- a/crates/typst/src/layout/inline/line.rs +++ b/crates/typst/src/layout/inline/line.rs @@ -1,11 +1,18 @@ -use unicode_bidi::BidiInfo; +use std::fmt::{self, Debug, Formatter}; +use std::ops::{Deref, DerefMut}; use super::*; use crate::engine::Engine; -use crate::layout::{Abs, Em, Fr, Frame, FrameItem, Point}; +use crate::layout::{Abs, Dir, Em, Fr, Frame, FrameItem, Point}; use crate::text::{Lang, TextElem}; use crate::utils::Numeric; +const SHY: char = '\u{ad}'; +const HYPHEN: char = '-'; +const EN_DASH: char = '–'; +const EM_DASH: char = '—'; +const LINE_SEPARATOR: char = '\u{2028}'; // We use LS to distinguish justified breaks. + /// A layouted line, consisting of a sequence of layouted paragraph items that /// are mostly borrowed from the preparation phase. This type enables you to /// measure the size of a line in a range before committing to building the @@ -16,20 +23,9 @@ use crate::utils::Numeric; /// line, respectively. But even those can partially reuse previous results when /// the break index is safe-to-break per rustybuzz. pub struct Line<'a> { - /// Bidi information about the paragraph. - pub bidi: &'a BidiInfo<'a>, - /// The trimmed range the line spans in the paragraph. - pub trimmed: Range, - /// The untrimmed end where the line ends. - pub end: usize, - /// A reshaped text item if the line sliced up a text item at the start. - pub first: Option>, - /// Inner items which don't need to be reprocessed. - pub inner: &'a [Item<'a>], - /// A reshaped text item if the line sliced up a text item at the end. If - /// there is only one text item, this takes precedence over `first`. - pub last: Option>, - /// The width of the line. + /// The items the line is made of. + pub items: Items<'a>, + /// The exact natural width of the line. pub width: Abs, /// Whether the line should be justified. pub justify: bool, @@ -39,45 +35,27 @@ pub struct Line<'a> { } impl<'a> Line<'a> { - /// Iterate over the line's items. - pub fn items(&self) -> impl Iterator> { - self.first.iter().chain(self.inner).chain(&self.last) - } - - /// Return items that intersect the given `text_range`. - pub fn slice(&self, text_range: Range) -> impl Iterator> { - let mut cursor = self.trimmed.start; - let mut start = 0; - let mut end = 0; - - for (i, item) in self.items().enumerate() { - if cursor <= text_range.start { - start = i; - } - - let len = item.textual_len(); - if cursor < text_range.end || cursor + len <= text_range.end { - end = i + 1; - } else { - break; - } - - cursor += len; + /// Create an empty line. + pub fn empty() -> Self { + Self { + items: Items::new(), + width: Abs::zero(), + justify: false, + dash: None, } - - self.items().skip(start).take(end - start) } /// How many glyphs are in the text where we can insert additional /// space when encountering underfull lines. pub fn justifiables(&self) -> usize { let mut count = 0; - for shaped in self.items().filter_map(Item::text) { + for shaped in self.items.iter().filter_map(Item::text) { count += shaped.justifiables(); } + // CJK character at line end should not be adjusted. if self - .items() + .items .last() .and_then(Item::text) .map(|s| s.cjk_justifiable_at_last()) @@ -89,19 +67,27 @@ impl<'a> Line<'a> { count } - /// How much can the line stretch + /// How much the line can stretch. pub fn stretchability(&self) -> Abs { - self.items().filter_map(Item::text).map(|s| s.stretchability()).sum() + self.items + .iter() + .filter_map(Item::text) + .map(|s| s.stretchability()) + .sum() } - /// How much can the line shrink + /// How much the line can shrink. pub fn shrinkability(&self) -> Abs { - self.items().filter_map(Item::text).map(|s| s.shrinkability()).sum() + self.items + .iter() + .filter_map(Item::text) + .map(|s| s.shrinkability()) + .sum() } /// Whether the line has items with negative width. pub fn has_negative_width_items(&self) -> bool { - self.items().any(|item| match item { + self.items.iter().any(|item| match item { Item::Absolute(amount, _) => *amount < Abs::zero(), Item::Frame(frame, _) => frame.width() < Abs::zero(), _ => false, @@ -110,7 +96,8 @@ impl<'a> Line<'a> { /// The sum of fractions in the line. pub fn fr(&self) -> Fr { - self.items() + self.items + .iter() .filter_map(|item| match item { Item::Fractional(fr, _) => Some(*fr), _ => None, @@ -122,234 +109,299 @@ impl<'a> Line<'a> { /// A dash at the end of a line. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Dash { - /// A hyphen added to break a word. - SoftHyphen, - /// Regular hyphen, present in a compound word, e.g. beija-flor. - HardHyphen, - /// An em dash. - Long, - /// An en dash. - Short, + /// A soft hyphen added to break a word. + Soft, + /// A regular hyphen, present in a compound word, e.g. beija-flor. + Hard, + /// Another kind of dash. Only relevant for cost computation. + Other, } /// Create a line which spans the given range. pub fn line<'a>( engine: &Engine, p: &'a Preparation, - mut range: Range, + range: Range, breakpoint: Breakpoint, pred: Option<&Line>, ) -> Line<'a> { - let end = range.end; - let mut justify = - p.justify && end < p.bidi.text.len() && breakpoint != Breakpoint::Mandatory; + // The line's full text. + let full = &p.text[range.clone()]; + // Whether the line is justified. + let justify = full.ends_with(LINE_SEPARATOR) + || (p.justify && breakpoint != Breakpoint::Mandatory); + + // Process dashes. + let dash = if breakpoint == Breakpoint::Hyphen || full.ends_with(SHY) { + Some(Dash::Soft) + } else if full.ends_with(HYPHEN) { + Some(Dash::Hard) + } else if full.ends_with([EN_DASH, EM_DASH]) { + Some(Dash::Other) + } else { + None + }; + + // Trim the line at the end, if necessary for this breakpoint. + let trim = range.start + breakpoint.trim(full).len(); + + // Collect the items for the line. + let mut items = collect_items(engine, p, range, trim); + + // Add a hyphen at the line start, if a previous dash should be repeated. + if pred.map_or(false, |pred| should_repeat_hyphen(pred, full)) { + if let Some(shaped) = items.first_text_mut() { + shaped.prepend_hyphen(engine, p.fallback); + } + } + + // Add a hyphen at the line end, if we ended on a soft hyphen. + if dash == Some(Dash::Soft) { + if let Some(shaped) = items.last_text_mut() { + shaped.push_hyphen(engine, p.fallback); + } + } + + // Deal with CJ characters at line boundaries. + adjust_cj_at_line_boundaries(p, full, &mut items); + + // Compute the line's width. + let width = items.iter().map(Item::natural_width).sum(); + + Line { items, width, justify, dash } +} + +/// Collects / reshapes all items for the line with the given `range`. +/// +/// The `trim` defines an end position to which text items are trimmed. For +/// example, the `range` may span "hello\n", but the `trim` specifies that the +/// linebreak is trimmed. +/// +/// We do not factor the `trim` diredctly into the `range` because we still want +/// to keep non-text items after the trim (e.g. tags). +fn collect_items<'a>( + engine: &Engine, + p: &'a Preparation, + range: Range, + trim: usize, +) -> Items<'a> { + let mut items = Items::new(); + let mut fallback = None; + + // Collect the items for each consecutively ordered run. + reorder(p, range.clone(), |subrange, rtl| { + let from = items.len(); + collect_range(engine, p, subrange, trim, &mut items, &mut fallback); + if rtl { + items.reorder(from); + } + }); + + // Trim weak spacing at the start of the line. + let prefix = items + .iter() + .take_while(|item| matches!(item, Item::Absolute(_, true))) + .count(); + if prefix > 0 { + items.drain(..prefix); + } + + // Trim weak spacing at the end of the line. + while matches!(items.last(), Some(Item::Absolute(_, true))) { + items.pop(); + } + + // Add fallback text to expand the line height, if necessary. + if !items.iter().any(|item| matches!(item, Item::Text(_))) { + if let Some(fallback) = fallback { + items.push(fallback); + } + } + + items +} + +/// Calls `f` for the the BiDi-reordered ranges of a line. +fn reorder(p: &Preparation, range: Range, mut f: F) +where + F: FnMut(Range, bool), +{ + // If there is nothing bidirectional going on, skip reordering. + let Some(bidi) = &p.bidi else { + f(range, p.dir == Dir::RTL); + return; + }; + + // The bidi crate panics for empty lines. if range.is_empty() { - return Line { - bidi: &p.bidi, - end, - trimmed: range, - first: None, - inner: &[], - last: None, - width: Abs::zero(), - justify, - dash: None, + f(range, p.dir == Dir::RTL); + return; + } + + // Find the paragraph that contains the line. + let para = bidi + .paragraphs + .iter() + .find(|para| para.range.contains(&range.start)) + .unwrap(); + + // Compute the reordered ranges in visual order (left to right). + let (levels, runs) = bidi.visual_runs(para, range.clone()); + + // Call `f` for each run. + for run in runs { + let rtl = levels[run.start].is_rtl(); + f(run, rtl) + } +} + +/// Collects / reshapes all items for the given `subrange` with continous +/// direction. +fn collect_range<'a>( + engine: &Engine, + p: &'a Preparation, + range: Range, + trim: usize, + items: &mut Items<'a>, + fallback: &mut Option>, +) { + for (subrange, item) in p.slice(range.clone()) { + // All non-text items are just kept, they can't be split. + let Item::Text(shaped) = item else { + items.push(item); + continue; }; - } - let prepend_hyphen = pred.map_or(false, should_insert_hyphen); + // The intersection range of the item, the subrange, and the line's + // trimming. + let sliced = + range.start.max(subrange.start)..range.end.min(subrange.end).min(trim); - // Slice out the relevant items. - let (mut expanded, mut inner) = p.slice(range.clone()); - let mut width = Abs::zero(); + // Whether the item is split by the line. + let split = subrange.start < sliced.start || sliced.end < subrange.end; - // Weak space (`Absolute(_, true)`) is removed at the end of the line - while let Some((Item::Absolute(_, true), before)) = inner.split_last() { - inner = before; - range.end -= 1; - expanded.end -= 1; - } - // Weak space (`Absolute(_, true)`) is removed at the beginning of the line - while let Some((Item::Absolute(_, true), after)) = inner.split_first() { - inner = after; - range.start += 1; - expanded.end += 1; - } - - // Reshape the last item if it's split in half or hyphenated. - let mut last = None; - let mut dash = None; - if let Some((Item::Text(shaped), before)) = inner.split_last() { - // Compute the range we want to shape, trimming whitespace at the - // end of the line. - let base = expanded.end - shaped.text.len(); - let start = range.start.max(base); - let text = &p.bidi.text[start..range.end]; - // U+200B ZERO WIDTH SPACE is used to provide a line break opportunity, - // we want to trim it too. - let trimmed = text.trim_end().trim_end_matches('\u{200B}'); - range.end = start + trimmed.len(); - - // Deal with hyphens, dashes and justification. - let shy = trimmed.ends_with('\u{ad}'); - let hyphen = breakpoint == Breakpoint::Hyphen; - dash = if hyphen || shy { - Some(Dash::SoftHyphen) - } else if trimmed.ends_with('-') { - Some(Dash::HardHyphen) - } else if trimmed.ends_with('–') { - Some(Dash::Short) - } else if trimmed.ends_with('—') { - Some(Dash::Long) + if sliced.is_empty() { + // When there is no text, still keep this as a fallback item, which + // we can use to force a non-zero line-height when the line doesn't + // contain any other text. + *fallback = Some(ItemEntry::from(Item::Text(shaped.empty()))); + } else if split { + // When the item is split in half, reshape it. + let reshaped = shaped.reshape(engine, sliced); + items.push(Item::Text(reshaped)); } else { - None - }; - justify |= text.ends_with('\u{2028}'); + // When the item is fully contained, just keep it. + items.push(item); + } + } +} - // Deal with CJK punctuation at line ends. - let gb_style = cjk_punct_style(shaped.lang, shaped.region); - let maybe_adjust_last_glyph = trimmed.ends_with(END_PUNCT_PAT) - || (p.cjk_latin_spacing && trimmed.ends_with(is_of_cj_script)); +/// Add spacing around punctuation marks for CJ glyphs at line boundaries. +/// +/// See Requirements for Chinese Text Layout, Section 3.1.6.3 Compression of +/// punctuation marks at line start or line end. +fn adjust_cj_at_line_boundaries(p: &Preparation, text: &str, items: &mut Items) { + if text.starts_with(BEGIN_PUNCT_PAT) + || (p.cjk_latin_spacing && text.starts_with(is_of_cj_script)) + { + adjust_cj_at_line_start(p, items); + } - // Usually, we don't want to shape an empty string because: - // - We don't want the height of trimmed whitespace in a different font - // to be considered for the line height. - // - Even if it's in the same font, its unnecessary. + if text.ends_with(END_PUNCT_PAT) + || (p.cjk_latin_spacing && text.ends_with(is_of_cj_script)) + { + adjust_cj_at_line_end(p, items); + } +} + +/// Add spacing around punctuation marks for CJ glyphs at the line start. +fn adjust_cj_at_line_start(p: &Preparation, items: &mut Items) { + let Some(shaped) = items.first_text_mut() else { return }; + let Some(glyph) = shaped.glyphs.first() else { return }; + + if glyph.is_cjk_right_aligned_punctuation() { + // If the first glyph is a CJK punctuation, we want to + // shrink it. + let glyph = shaped.glyphs.to_mut().first_mut().unwrap(); + let shrink = glyph.shrinkability().0; + glyph.shrink_left(shrink); + shaped.width -= shrink.at(shaped.size); + } else if p.cjk_latin_spacing && glyph.is_cj_script() && glyph.x_offset > Em::zero() { + // If the first glyph is a CJK character adjusted by + // [`add_cjk_latin_spacing`], restore the original width. + let glyph = shaped.glyphs.to_mut().first_mut().unwrap(); + let shrink = glyph.x_offset; + glyph.x_advance -= shrink; + glyph.x_offset = Em::zero(); + glyph.adjustability.shrinkability.0 = Em::zero(); + shaped.width -= shrink.at(shaped.size); + } +} + +/// Add spacing around punctuation marks for CJ glyphs at the line end. +fn adjust_cj_at_line_end(p: &Preparation, items: &mut Items) { + let Some(shaped) = items.last_text_mut() else { return }; + let Some(glyph) = shaped.glyphs.last() else { return }; + + // Deal with CJK punctuation at line ends. + let style = cjk_punct_style(shaped.lang, shaped.region); + + if glyph.is_cjk_left_aligned_punctuation(style) { + // If the last glyph is a CJK punctuation, we want to + // shrink it. + let shrink = glyph.shrinkability().1; + let punct = shaped.glyphs.to_mut().last_mut().unwrap(); + punct.shrink_right(shrink); + shaped.width -= shrink.at(shaped.size); + } else if p.cjk_latin_spacing + && glyph.is_cj_script() + && (glyph.x_advance - glyph.x_offset) > Em::one() + { + // If the last glyph is a CJK character adjusted by + // [`add_cjk_latin_spacing`], restore the original width. + let shrink = glyph.x_advance - glyph.x_offset - Em::one(); + let glyph = shaped.glyphs.to_mut().last_mut().unwrap(); + glyph.x_advance -= shrink; + glyph.adjustability.shrinkability.1 = Em::zero(); + shaped.width -= shrink.at(shaped.size); + } +} + +/// Whether a hyphen should be inserted at the start of the next line. +fn should_repeat_hyphen(pred_line: &Line, text: &str) -> bool { + // If the predecessor line does not end with a `Dash::Hard`, we shall + // not place a hyphen at the start of the next line. + if pred_line.dash != Some(Dash::Hard) { + return false; + } + + // The hyphen should repeat only in the languages that require that feature. + // For more information see the discussion at https://github.com/typst/typst/issues/3235 + let Some(Item::Text(shaped)) = pred_line.items.last() else { return false }; + + match shaped.lang { + // - Lower Sorbian: see https://dolnoserbski.de/ortografija/psawidla/K3 + // - Czech: see https://prirucka.ujc.cas.cz/?id=164 + // - Croatian: see http://pravopis.hr/pravilo/spojnica/68/ + // - Polish: see https://www.ortograf.pl/zasady-pisowni/lacznik-zasady-pisowni + // - Portuguese: see https://www2.senado.leg.br/bdsf/bitstream/handle/id/508145/000997415.pdf (Base XX) + // - Slovak: see https://www.zones.sk/studentske-prace/gramatika/10620-pravopis-rozdelovanie-slov/ + Lang::LOWER_SORBIAN + | Lang::CZECH + | Lang::CROATIAN + | Lang::POLISH + | Lang::PORTUGUESE + | Lang::SLOVAK => true, + + // In Spanish the hyphen is required only if the word next to hyphen is + // not capitalized. Otherwise, the hyphen must not be repeated. // - // There is one exception though. When the whole line is empty, we need - // the shaped empty string to make the line the appropriate height. That - // is the case exactly if the string is empty and there are no other - // items in the line. - if hyphen - || start + shaped.text.len() > range.end - || maybe_adjust_last_glyph - || prepend_hyphen - { - if hyphen || start < range.end || before.is_empty() { - let mut reshaped = shaped.reshape(engine, &p.spans, start..range.end); - if hyphen || shy { - reshaped.push_hyphen(engine, p.fallback); - } + // See § 4.1.1.1.2.e on the "Ortografía de la lengua española" + // https://www.rae.es/ortografía/como-signo-de-división-de-palabras-a-final-de-línea + Lang::SPANISH => text.chars().next().map_or(false, |c| !c.is_uppercase()), - if let Some(last_glyph) = reshaped.glyphs.last() { - if last_glyph.is_cjk_left_aligned_punctuation(gb_style) { - // If the last glyph is a CJK punctuation, we want to - // shrink it. See Requirements for Chinese Text Layout, - // Section 3.1.6.3 Compression of punctuation marks at - // line start or line end - let shrink_amount = last_glyph.shrinkability().1; - let punct = reshaped.glyphs.to_mut().last_mut().unwrap(); - punct.shrink_right(shrink_amount); - reshaped.width -= shrink_amount.at(reshaped.size); - } else if p.cjk_latin_spacing - && last_glyph.is_cj_script() - && (last_glyph.x_advance - last_glyph.x_offset) > Em::one() - { - // If the last glyph is a CJK character adjusted by - // [`add_cjk_latin_spacing`], restore the original - // width. - let shrink_amount = - last_glyph.x_advance - last_glyph.x_offset - Em::one(); - let glyph = reshaped.glyphs.to_mut().last_mut().unwrap(); - glyph.x_advance -= shrink_amount; - glyph.adjustability.shrinkability.1 = Em::zero(); - reshaped.width -= shrink_amount.at(reshaped.size); - } - } - - width += reshaped.width; - last = Some(Item::Text(reshaped)); - } - - inner = before; - } - } - - // Deal with CJ characters at line starts. - let text = &p.bidi.text[range.start..end]; - let maybe_adjust_first_glyph = text.starts_with(BEGIN_PUNCT_PAT) - || (p.cjk_latin_spacing && text.starts_with(is_of_cj_script)); - - // Reshape the start item if it's split in half. - let mut first = None; - if let Some((Item::Text(shaped), after)) = inner.split_first() { - // Compute the range we want to shape. - let base = expanded.start; - let end = range.end.min(base + shaped.text.len()); - - // Reshape if necessary. - if range.start + shaped.text.len() > end - || maybe_adjust_first_glyph - || prepend_hyphen - { - // If the range is empty, we don't want to push an empty text item. - if range.start < end { - let reshaped = shaped.reshape(engine, &p.spans, range.start..end); - width += reshaped.width; - first = Some(Item::Text(reshaped)); - } - - inner = after; - } - } - - if prepend_hyphen { - let reshaped = first.as_mut().or(last.as_mut()).and_then(Item::text_mut); - if let Some(reshaped) = reshaped { - let width_before = reshaped.width; - reshaped.prepend_hyphen(engine, p.fallback); - width += reshaped.width - width_before; - } - } - - if maybe_adjust_first_glyph { - let reshaped = first.as_mut().or(last.as_mut()).and_then(Item::text_mut); - if let Some(reshaped) = reshaped { - if let Some(first_glyph) = reshaped.glyphs.first() { - if first_glyph.is_cjk_right_aligned_punctuation() { - // If the first glyph is a CJK punctuation, we want to - // shrink it. - let shrink_amount = first_glyph.shrinkability().0; - let glyph = reshaped.glyphs.to_mut().first_mut().unwrap(); - glyph.shrink_left(shrink_amount); - let amount_abs = shrink_amount.at(reshaped.size); - reshaped.width -= amount_abs; - width -= amount_abs; - } else if p.cjk_latin_spacing - && first_glyph.is_cj_script() - && first_glyph.x_offset > Em::zero() - { - // If the first glyph is a CJK character adjusted by - // [`add_cjk_latin_spacing`], restore the original width. - let shrink_amount = first_glyph.x_offset; - let glyph = reshaped.glyphs.to_mut().first_mut().unwrap(); - glyph.x_advance -= shrink_amount; - glyph.x_offset = Em::zero(); - glyph.adjustability.shrinkability.0 = Em::zero(); - let amount_abs = shrink_amount.at(reshaped.size); - reshaped.width -= amount_abs; - width -= amount_abs; - } - } - } - } - - // Measure the inner items. - for item in inner { - width += item.width(); - } - - Line { - bidi: &p.bidi, - trimmed: range, - end, - first, - inner, - last, - width, - justify, - dash, + _ => false, } } @@ -365,18 +417,19 @@ pub fn commit( let mut remaining = width - line.width - p.hang; let mut offset = Abs::zero(); - // Reorder the line from logical to visual order. - let (reordered, starts_rtl) = reorder(line); - if !starts_rtl { + // We always build the line from left to right. In an LTR paragraph, we must + // thus add the hanging indent to the offset. When the paragraph is RTL, the + // hanging indent arises naturally due to the line width. + if p.dir == Dir::LTR { offset += p.hang; } // Handle hanging punctuation to the left. - if let Some(Item::Text(text)) = reordered.first() { + if let Some(Item::Text(text)) = line.items.first() { if let Some(glyph) = text.glyphs.first() { if !text.dir.is_positive() && TextElem::overhang_in(text.styles) - && (reordered.len() > 1 || text.glyphs.len() > 1) + && (line.items.len() > 1 || text.glyphs.len() > 1) { let amount = overhang(glyph.c) * glyph.x_advance.at(text.size); offset -= amount; @@ -386,11 +439,11 @@ pub fn commit( } // Handle hanging punctuation to the right. - if let Some(Item::Text(text)) = reordered.last() { + if let Some(Item::Text(text)) = line.items.last() { if let Some(glyph) = text.glyphs.last() { if text.dir.is_positive() && TextElem::overhang_in(text.styles) - && (reordered.len() > 1 || text.glyphs.len() > 1) + && (line.items.len() > 1 || text.glyphs.len() > 1) { let amount = overhang(glyph.c) * glyph.x_advance.at(text.size); remaining += amount; @@ -408,16 +461,16 @@ pub fn commit( let mut extra_justification = Abs::zero(); let shrinkability = line.shrinkability(); - let stretch = line.stretchability(); + let stretchability = line.stretchability(); if remaining < Abs::zero() && shrinkability > Abs::zero() && shrink { // Attempt to reduce the length of the line, using shrinkability. justification_ratio = (remaining / shrinkability).max(-1.0); remaining = (remaining + shrinkability).min(Abs::zero()); } else if line.justify && fr.is_zero() { // Attempt to increase the length of the line, using stretchability. - if stretch > Abs::zero() { - justification_ratio = (remaining / stretch).min(1.0); - remaining = (remaining - stretch).max(Abs::zero()); + if stretchability > Abs::zero() { + justification_ratio = (remaining / stretchability).min(1.0); + remaining = (remaining - stretchability).max(Abs::zero()); } let justifiables = line.justifiables(); @@ -433,7 +486,7 @@ pub fn commit( // Build the frames and determine the height and baseline. let mut frames = vec![]; - for item in reordered { + for item in line.items.iter() { let mut push = |offset: &mut Abs, frame: Frame| { let width = frame.width(); top.set_max(frame.baseline()); @@ -460,8 +513,12 @@ pub fn commit( } } Item::Text(shaped) => { - let mut frame = - shaped.build(engine, justification_ratio, extra_justification); + let mut frame = shaped.build( + engine, + &p.spans, + justification_ratio, + extra_justification, + ); frame.post_process(shaped.styles); push(&mut offset, frame); } @@ -499,94 +556,6 @@ pub fn commit( Ok(output) } -/// Return a line's items in visual order. -fn reorder<'a>(line: &'a Line<'a>) -> (Vec<&Item<'a>>, bool) { - let mut reordered = vec![]; - - // The bidi crate doesn't like empty lines. - if line.trimmed.is_empty() { - return (line.slice(line.trimmed.clone()).collect(), false); - } - - // Find the paragraph that contains the line. - let para = line - .bidi - .paragraphs - .iter() - .find(|para| para.range.contains(&line.trimmed.start)) - .unwrap(); - - // Compute the reordered ranges in visual order (left to right). - let (levels, runs) = line.bidi.visual_runs(para, line.trimmed.clone()); - let starts_rtl = levels.first().is_some_and(|level| level.is_rtl()); - - // Collect the reordered items. - for run in runs { - // Skip reset L1 runs because handling them would require reshaping - // again in some cases. - if line.bidi.levels[run.start] != levels[run.start] { - continue; - } - - let prev = reordered.len(); - reordered.extend(line.slice(run.clone())); - - if levels[run.start].is_rtl() { - reordered[prev..].reverse(); - } - } - - (reordered, starts_rtl) -} - -/// Whether a hyphen should be inserted at the start of the next line. -fn should_insert_hyphen(pred_line: &Line) -> bool { - // If the predecessor line does not end with a Dash::HardHyphen, we shall - // not place a hyphen at the start of the next line. - if pred_line.dash != Some(Dash::HardHyphen) { - return false; - } - - // If there's a trimmed out space, we needn't repeat the hyphen. That's the - // case of a text like "...kebab é a -melhor- comida que existe", where the - // hyphens are a kind of emphasis marker. - if pred_line.trimmed.end != pred_line.end { - return false; - } - - // The hyphen should repeat only in the languages that require that feature. - // For more information see the discussion at https://github.com/typst/typst/issues/3235 - let Some(Item::Text(shape)) = pred_line.last.as_ref() else { return false }; - - match shape.lang { - // - Lower Sorbian: see https://dolnoserbski.de/ortografija/psawidla/K3 - // - Czech: see https://prirucka.ujc.cas.cz/?id=164 - // - Croatian: see http://pravopis.hr/pravilo/spojnica/68/ - // - Polish: see https://www.ortograf.pl/zasady-pisowni/lacznik-zasady-pisowni - // - Portuguese: see https://www2.senado.leg.br/bdsf/bitstream/handle/id/508145/000997415.pdf (Base XX) - // - Slovak: see https://www.zones.sk/studentske-prace/gramatika/10620-pravopis-rozdelovanie-slov/ - Lang::LOWER_SORBIAN - | Lang::CZECH - | Lang::CROATIAN - | Lang::POLISH - | Lang::PORTUGUESE - | Lang::SLOVAK => true, - - // In Spanish the hyphen is required only if the word next to hyphen is - // not capitalized. Otherwise, the hyphen must not be repeated. - // - // See § 4.1.1.1.2.e on the "Ortografía de la lengua española" - // https://www.rae.es/ortografía/como-signo-de-división-de-palabras-a-final-de-línea - Lang::SPANISH => pred_line.bidi.text[pred_line.end..] - .chars() - .next() - .map(|c| !c.is_uppercase()) - .unwrap_or(false), - - _ => false, - } -} - /// How much a character should hang into the end margin. /// /// For more discussion, see: @@ -607,3 +576,119 @@ fn overhang(c: char) -> f64 { _ => 0.0, } } + +/// A collection of owned or borrowed paragraph items. +pub struct Items<'a>(Vec>); + +impl<'a> Items<'a> { + /// Create empty items. + pub fn new() -> Self { + Self(vec![]) + } + + /// Push a new item. + pub fn push(&mut self, entry: impl Into>) { + self.0.push(entry.into()); + } + + /// Iterate over the items + pub fn iter(&self) -> impl Iterator> { + self.0.iter().map(|item| &**item) + } + + /// Access the first item. + pub fn first(&self) -> Option<&Item<'a>> { + self.0.first().map(|item| &**item) + } + + /// Access the last item. + pub fn last(&self) -> Option<&Item<'a>> { + self.0.last().map(|item| &**item) + } + + /// Access the first item mutably, if it is text. + pub fn first_text_mut(&mut self) -> Option<&mut ShapedText<'a>> { + self.0.first_mut()?.text_mut() + } + + /// Access the last item mutably, if it is text. + pub fn last_text_mut(&mut self) -> Option<&mut ShapedText<'a>> { + self.0.last_mut()?.text_mut() + } + + /// Reorder the items starting at the given index to RTL. + pub fn reorder(&mut self, from: usize) { + self.0[from..].reverse() + } +} + +impl<'a> FromIterator> for Items<'a> { + fn from_iter>>(iter: I) -> Self { + Self(iter.into_iter().collect()) + } +} + +impl<'a> Deref for Items<'a> { + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> DerefMut for Items<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A reference to or a boxed item. +pub enum ItemEntry<'a> { + Ref(&'a Item<'a>), + Box(Box>), +} + +impl<'a> ItemEntry<'a> { + fn text_mut(&mut self) -> Option<&mut ShapedText<'a>> { + match self { + Self::Ref(item) => { + let text = item.text()?; + *self = Self::Box(Box::new(Item::Text(text.clone()))); + match self { + Self::Box(item) => item.text_mut(), + _ => unreachable!(), + } + } + Self::Box(item) => item.text_mut(), + } + } +} + +impl<'a> Deref for ItemEntry<'a> { + type Target = Item<'a>; + + fn deref(&self) -> &Self::Target { + match self { + Self::Ref(item) => item, + Self::Box(item) => item, + } + } +} + +impl Debug for ItemEntry<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +impl<'a> From<&'a Item<'a>> for ItemEntry<'a> { + fn from(item: &'a Item<'a>) -> Self { + Self::Ref(item) + } +} + +impl<'a> From> for ItemEntry<'a> { + fn from(item: Item<'a>) -> Self { + Self::Box(Box::new(item)) + } +} diff --git a/crates/typst/src/layout/inline/linebreak.rs b/crates/typst/src/layout/inline/linebreak.rs index 0555c1890..dbaa9c59a 100644 --- a/crates/typst/src/layout/inline/linebreak.rs +++ b/crates/typst/src/layout/inline/linebreak.rs @@ -1,6 +1,7 @@ use std::ops::{Add, Sub}; use icu_properties::maps::CodePointMapData; +use icu_properties::sets::CodePointSetData; use icu_properties::LineBreak; use icu_provider::AsDeserializingBufferProvider; use icu_provider_adapters::fork::ForkByKeyProvider; @@ -27,30 +28,33 @@ const MIN_RATIO: f64 = -1.0; const MIN_APPROX_RATIO: f64 = -0.5; const BOUND_EPS: f64 = 1e-3; +/// The ICU blob data. +fn blob() -> BlobDataProvider { + BlobDataProvider::try_new_from_static_blob(typst_assets::icu::ICU).unwrap() +} + /// The general line break segmenter. -static SEGMENTER: Lazy = Lazy::new(|| { - let provider = - BlobDataProvider::try_new_from_static_blob(typst_assets::icu::ICU).unwrap(); - LineSegmenter::try_new_lstm_with_buffer_provider(&provider).unwrap() -}); +static SEGMENTER: Lazy = + Lazy::new(|| LineSegmenter::try_new_lstm_with_buffer_provider(&blob()).unwrap()); /// The line break segmenter for Chinese/Japanese text. static CJ_SEGMENTER: Lazy = Lazy::new(|| { - let provider = - BlobDataProvider::try_new_from_static_blob(typst_assets::icu::ICU).unwrap(); let cj_blob = BlobDataProvider::try_new_from_static_blob(typst_assets::icu::ICU_CJ_SEGMENT) .unwrap(); - let cj_provider = ForkByKeyProvider::new(cj_blob, provider); + let cj_provider = ForkByKeyProvider::new(cj_blob, blob()); LineSegmenter::try_new_lstm_with_buffer_provider(&cj_provider).unwrap() }); /// The Unicode line break properties for each code point. static LINEBREAK_DATA: Lazy> = Lazy::new(|| { - let provider = - BlobDataProvider::try_new_from_static_blob(typst_assets::icu::ICU).unwrap(); - let deser_provider = provider.as_deserializing(); - icu_properties::maps::load_line_break(&deser_provider).unwrap() + icu_properties::maps::load_line_break(&blob().as_deserializing()).unwrap() +}); + +/// The set of Unicode default ignorables. +static DEFAULT_IGNORABLE_DATA: Lazy = Lazy::new(|| { + icu_properties::sets::load_default_ignorable_code_point(&blob().as_deserializing()) + .unwrap() }); /// A line break opportunity. @@ -64,6 +68,37 @@ pub enum Breakpoint { Hyphen, } +impl Breakpoint { + /// Trim a line before this breakpoint. + pub fn trim(self, line: &str) -> &str { + // Trim default ignorables. + let ignorable = DEFAULT_IGNORABLE_DATA.as_borrowed(); + let line = line.trim_end_matches(|c| ignorable.contains(c)); + + match self { + // Trim whitespace. + Self::Normal => line.trim_end_matches(char::is_whitespace), + + // Trim linebreaks. + Self::Mandatory => { + let lb = LINEBREAK_DATA.as_borrowed(); + line.trim_end_matches(|c| { + matches!( + lb.get(c), + LineBreak::MandatoryBreak + | LineBreak::CarriageReturn + | LineBreak::LineFeed + | LineBreak::NextLine + ) + }) + } + + // Trim nothing further. + Self::Hyphen => line, + } + } +} + /// Breaks the paragraph into lines. pub fn linebreak<'a>( engine: &Engine, @@ -180,14 +215,11 @@ fn linebreak_optimized_bounded<'a>( pred: usize, total: Cost, line: Line<'a>, + end: usize, } // Dynamic programming table. - let mut table = vec![Entry { - pred: 0, - total: 0.0, - line: line(engine, p, 0..0, Breakpoint::Mandatory, None), - }]; + let mut table = vec![Entry { pred: 0, total: 0.0, line: Line::empty(), end: 0 }]; let mut active = 0; let mut prev_end = 0; @@ -200,7 +232,7 @@ fn linebreak_optimized_bounded<'a>( let mut line_lower_bound = None; for (pred_index, pred) in table.iter().enumerate().skip(active) { - let start = pred.line.end; + let start = pred.end; let unbreakable = prev_end == start; // If the minimum cost we've established for the line is already @@ -221,6 +253,7 @@ fn linebreak_optimized_bounded<'a>( width, &pred.line, &attempt, + end, breakpoint, unbreakable, ); @@ -263,7 +296,7 @@ fn linebreak_optimized_bounded<'a>( // If this attempt is better than what we had before, take it! if best.as_ref().map_or(true, |best| best.total >= total) { - best = Some(Entry { pred: pred_index, total, line: attempt }); + best = Some(Entry { pred: pred_index, total, line: attempt, end }); } } @@ -282,7 +315,7 @@ fn linebreak_optimized_bounded<'a>( let mut idx = table.len() - 1; // This should only happen if our bound was faulty. Which shouldn't happen! - if table[idx].line.end != p.bidi.text.len() { + if table[idx].end != p.text.len() { #[cfg(debug_assertions)] panic!("bounded paragraph layout is incomplete"); @@ -340,7 +373,7 @@ fn linebreak_optimized_approximate( let mut prev_end = 0; breakpoints(p, |end, breakpoint| { - let at_end = end == p.bidi.text.len(); + let at_end = end == p.text.len(); // Find the optimal predecessor. let mut best: Option = None; @@ -362,7 +395,7 @@ fn linebreak_optimized_approximate( // make it the desired width. We trim at the end to not take into // account trailing spaces. This is, again, only an approximation of // the real behaviour of `line`. - let trimmed_end = start + p.bidi.text[start..end].trim_end().len(); + let trimmed_end = start + p.text[start..end].trim_end().len(); let line_ratio = raw_ratio( p, width, @@ -428,8 +461,9 @@ fn linebreak_optimized_approximate( idx = table[idx].pred; } + let mut pred = Line::empty(); + let mut start = 0; let mut exact = 0.0; - let mut pred = line(engine, p, 0..0, Breakpoint::Mandatory, None); // The cost that we optimized was only an approximate cost, so the layout we // got here is only likely to be good, not guaranteed to be the best. We now @@ -438,26 +472,36 @@ fn linebreak_optimized_approximate( for idx in indices.into_iter().rev() { let Entry { end, breakpoint, unbreakable, .. } = table[idx]; - let start = pred.end; let attempt = line(engine, p, start..end, breakpoint, Some(&pred)); - let (_, line_cost) = - ratio_and_cost(p, metrics, width, &pred, &attempt, breakpoint, unbreakable); + let (_, line_cost) = ratio_and_cost( + p, + metrics, + width, + &pred, + &attempt, + end, + breakpoint, + unbreakable, + ); - exact += line_cost; pred = attempt; + start = end; + exact += line_cost; } exact } /// Compute the stretch ratio and cost of a line. +#[allow(clippy::too_many_arguments)] fn ratio_and_cost( p: &Preparation, metrics: &CostMetrics, available_width: Abs, pred: &Line, attempt: &Line, + end: usize, breakpoint: Breakpoint, unbreakable: bool, ) -> (f64, Cost) { @@ -474,7 +518,7 @@ fn ratio_and_cost( metrics, breakpoint, ratio, - attempt.end == p.bidi.text.len(), + end == p.text.len(), attempt.justify, unbreakable, pred.dash.is_some() && attempt.dash.is_some(), @@ -587,7 +631,14 @@ fn raw_cost( /// code much simpler and the consumers of this function don't need the /// composability and flexibility of external iteration anyway. fn breakpoints<'a>(p: &'a Preparation<'a>, mut f: impl FnMut(usize, Breakpoint)) { - let text = p.bidi.text; + let text = p.text; + + // Single breakpoint at the end for empty text. + if text.is_empty() { + f(0, Breakpoint::Mandatory); + return; + } + let hyphenate = p.hyphenate != Some(false); let lb = LINEBREAK_DATA.as_borrowed(); let segmenter = match p.lang { @@ -747,8 +798,9 @@ fn linebreak_link(link: &str, mut f: impl FnMut(usize)) { fn hyphenate_at(p: &Preparation, offset: usize) -> bool { p.hyphenate .or_else(|| { - let shaped = p.find(offset)?.text()?; - Some(TextElem::hyphenate_in(shaped.styles)) + let (_, item) = p.get(offset); + let styles = item.text()?.styles; + Some(TextElem::hyphenate_in(styles)) }) .unwrap_or(false) } @@ -756,8 +808,9 @@ fn hyphenate_at(p: &Preparation, offset: usize) -> bool { /// The text language at the given offset. fn lang_at(p: &Preparation, offset: usize) -> Option { let lang = p.lang.or_else(|| { - let shaped = p.find(offset)?.text()?; - Some(TextElem::lang_in(shaped.styles)) + let (_, item) = p.get(offset); + let styles = item.text()?.styles; + Some(TextElem::lang_in(styles)) })?; let bytes = lang.as_str().as_bytes().try_into().ok()?; @@ -813,17 +866,14 @@ struct Estimates { impl Estimates { /// Compute estimations for approximate Knuth-Plass layout. fn compute(p: &Preparation) -> Self { - let cap = p.bidi.text.len(); + let cap = p.text.len(); let mut widths = CummulativeVec::with_capacity(cap); let mut stretchability = CummulativeVec::with_capacity(cap); let mut shrinkability = CummulativeVec::with_capacity(cap); let mut justifiables = CummulativeVec::with_capacity(cap); - for item in &p.items { - let textual_len = item.textual_len(); - let after = widths.len() + textual_len; - + for (range, item) in p.items.iter() { if let Item::Text(shaped) = item { for g in shaped.glyphs.iter() { let byte_len = g.range.len(); @@ -835,13 +885,13 @@ impl Estimates { justifiables.push(byte_len, g.is_justifiable() as usize); } } else { - widths.push(textual_len, item.width()); + widths.push(range.len(), item.natural_width()); } - widths.adjust(after); - stretchability.adjust(after); - shrinkability.adjust(after); - justifiables.adjust(after); + widths.adjust(range.end); + stretchability.adjust(range.end); + shrinkability.adjust(range.end); + justifiables.adjust(range.end); } Self { @@ -871,11 +921,6 @@ where Self { total, summed } } - /// Get the covered byte length. - fn len(&self) -> usize { - self.summed.len() - } - /// Adjust to cover the given byte length. fn adjust(&mut self, len: usize) { self.summed.resize(len, self.total); diff --git a/crates/typst/src/layout/inline/prepare.rs b/crates/typst/src/layout/inline/prepare.rs index 90d8d5a47..59682b2c8 100644 --- a/crates/typst/src/layout/inline/prepare.rs +++ b/crates/typst/src/layout/inline/prepare.rs @@ -13,16 +13,24 @@ use crate::text::{Costs, Lang, TextElem}; /// Only when a line break falls onto a text index that is not safe-to-break per /// rustybuzz, we have to reshape that portion. pub struct Preparation<'a> { + /// The paragraph's full text. + pub text: &'a str, /// Bidirectional text embedding levels for the paragraph. - pub bidi: BidiInfo<'a>, + /// + /// This is `None` if the paragraph is BiDi-uniform (all the base direction). + pub bidi: Option>, /// Text runs, spacing and layouted elements. - pub items: Vec>, + pub items: Vec<(Range, Item<'a>)>, + /// Maps from byte indices to item indices. + pub indices: Vec, /// The span mapper. pub spans: SpanMapper, /// Whether to hyphenate if it's the same for all children. pub hyphenate: Option, /// Costs for various layout decisions. pub costs: Costs, + /// The dominant direction. + pub dir: Dir, /// The text language if it's the same for all children. pub lang: Option, /// The paragraph's resolved horizontal alignment. @@ -44,46 +52,18 @@ pub struct Preparation<'a> { } impl<'a> Preparation<'a> { - /// Find the item that contains the given `text_offset`. - pub fn find(&self, text_offset: usize) -> Option<&Item<'a>> { - let mut cursor = 0; - for item in &self.items { - let end = cursor + item.textual_len(); - if (cursor..end).contains(&text_offset) { - return Some(item); - } - cursor = end; - } - None + /// Get the item that contains the given `text_offset`. + pub fn get(&self, offset: usize) -> &(Range, Item<'a>) { + let idx = self.indices.get(offset).copied().unwrap_or(0); + &self.items[idx] } - /// Return the items that intersect the given `text_range`. - /// - /// Returns the expanded range around the items and the items. - pub fn slice(&self, text_range: Range) -> (Range, &[Item<'a>]) { - let mut cursor = 0; - let mut start = 0; - let mut end = 0; - let mut expanded = text_range.clone(); - - for (i, item) in self.items.iter().enumerate() { - if cursor <= text_range.start { - start = i; - expanded.start = cursor; - } - - let len = item.textual_len(); - if cursor < text_range.end || cursor + len <= text_range.end { - end = i + 1; - expanded.end = cursor + len; - } else { - break; - } - - cursor += len; - } - - (expanded, &self.items[start..end]) + /// Iterate over the items that intersect the given `sliced` range. + pub fn slice(&self, sliced: Range) -> impl Iterator)> { + let start = self.indices.get(sliced.start).copied().unwrap_or(0); + self.items[start..].iter().take_while(move |(range, _)| { + range.start < sliced.end || range.end <= sliced.end + }) } } @@ -99,42 +79,57 @@ pub fn prepare<'a>( spans: SpanMapper, styles: StyleChain<'a>, ) -> SourceResult> { - let bidi = BidiInfo::new( - text, - match TextElem::dir_in(styles) { - Dir::LTR => Some(BidiLevel::ltr()), - Dir::RTL => Some(BidiLevel::rtl()), - _ => None, - }, - ); + let dir = TextElem::dir_in(styles); + let default_level = match dir { + Dir::RTL => BidiLevel::rtl(), + _ => BidiLevel::ltr(), + }; + + let bidi = BidiInfo::new(text, Some(default_level)); + let is_bidi = bidi + .levels + .iter() + .any(|level| level.is_ltr() != default_level.is_ltr()); let mut cursor = 0; let mut items = Vec::with_capacity(segments.len()); // Shape the text to finalize the items. for segment in segments { - let end = cursor + segment.textual_len(); + let len = segment.textual_len(); + let end = cursor + len; + let range = cursor..end; + match segment { Segment::Text(_, styles) => { - shape_range(&mut items, engine, &bidi, cursor..end, &spans, styles); + shape_range(&mut items, engine, text, &bidi, range, styles); } - Segment::Item(item) => items.push(item), + Segment::Item(item) => items.push((range, item)), } cursor = end; } + // Build the mapping from byte to item indices. + let mut indices = Vec::with_capacity(text.len()); + for (i, (range, _)) in items.iter().enumerate() { + indices.extend(range.clone().map(|_| i)); + } + let cjk_latin_spacing = TextElem::cjk_latin_spacing_in(styles).is_auto(); if cjk_latin_spacing { add_cjk_latin_spacing(&mut items); } Ok(Preparation { - bidi, + text, + bidi: is_bidi.then_some(bidi), items, + indices, spans, hyphenate: children.shared_get(styles, TextElem::hyphenate_in), costs: TextElem::costs_in(styles), + dir, lang: children.shared_get(styles, TextElem::lang_in), align: AlignElem::alignment_in(styles).resolve(styles).x, justify: ParElem::justify_in(styles), @@ -150,10 +145,14 @@ pub fn prepare<'a>( /// Add some spacing between Han characters and western characters. See /// Requirements for Chinese Text Layout, Section 3.2.2 Mixed Text Composition /// in Horizontal Written Mode -fn add_cjk_latin_spacing(items: &mut [Item]) { - let mut items = items.iter_mut().filter(|x| !matches!(x, Item::Tag(_))).peekable(); +fn add_cjk_latin_spacing(items: &mut [(Range, Item)]) { + let mut items = items + .iter_mut() + .filter(|(_, x)| !matches!(x, Item::Tag(_))) + .peekable(); + let mut prev: Option<&ShapedGlyph> = None; - while let Some(item) = items.next() { + while let Some((_, item)) = items.next() { let Some(text) = item.text_mut() else { prev = None; continue; @@ -168,7 +167,7 @@ fn add_cjk_latin_spacing(items: &mut [Item]) { let next = glyphs.peek().map(|n| n as _).or_else(|| { items .peek() - .and_then(|i| i.text()) + .and_then(|(_, i)| i.text()) .and_then(|shaped| shaped.glyphs.first()) }); diff --git a/crates/typst/src/layout/inline/shaping.rs b/crates/typst/src/layout/inline/shaping.rs index 44b653917..43dc351a5 100644 --- a/crates/typst/src/layout/inline/shaping.rs +++ b/crates/typst/src/layout/inline/shaping.rs @@ -14,7 +14,6 @@ use super::{Item, Range, SpanMapper}; use crate::engine::Engine; use crate::foundations::{Smart, StyleChain}; use crate::layout::{Abs, Dir, Em, Frame, FrameItem, Point, Size}; -use crate::syntax::Span; use crate::text::{ decorate, families, features, variant, Font, FontVariant, Glyph, Lang, Region, TextElem, TextItem, @@ -27,6 +26,7 @@ use crate::World; /// This type contains owned or borrowed shaped text runs, which can be /// measured, used to reshape substrings more quickly and converted into a /// frame. +#[derive(Clone)] pub struct ShapedText<'a> { /// The start of the text in the full paragraph. pub base: usize, @@ -80,8 +80,6 @@ pub struct ShapedGlyph { pub safe_to_break: bool, /// The first char in this glyph's cluster. pub c: char, - /// The source code location of the glyph and its byte offset within it. - pub span: (Span, u16), /// Whether this glyph is justifiable for CJK scripts. pub is_justifiable: bool, /// The script of the glyph. @@ -214,6 +212,7 @@ impl<'a> ShapedText<'a> { pub fn build( &self, engine: &Engine, + spans: &SpanMapper, justification_ratio: f64, extra_justification: Abs, ) -> Frame { @@ -268,7 +267,7 @@ impl<'a> ShapedText<'a> { // We may not be able to reach the offset completely if // it exceeds u16, but better to have a roughly correct // span offset than nothing. - let mut span = shaped.span; + let mut span = spans.span_at(shaped.range.start); span.1 = span.1.saturating_add(span_offset.saturating_as()); // |<---- a Glyph ---->| @@ -331,7 +330,7 @@ impl<'a> ShapedText<'a> { } /// Measure the top and bottom extent of this text. - fn measure(&self, engine: &Engine) -> (Abs, Abs) { + pub fn measure(&self, engine: &Engine) -> (Abs, Abs) { let mut top = Abs::zero(); let mut bottom = Abs::zero(); @@ -409,12 +408,7 @@ impl<'a> ShapedText<'a> { /// shaping process if possible. /// /// The text `range` is relative to the whole paragraph. - pub fn reshape( - &'a self, - engine: &Engine, - spans: &SpanMapper, - text_range: Range, - ) -> ShapedText<'a> { + pub fn reshape(&'a self, engine: &Engine, text_range: Range) -> ShapedText<'a> { let text = &self.text[text_range.start - self.base..text_range.end - self.base]; if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) { #[cfg(debug_assertions)] @@ -436,7 +430,6 @@ impl<'a> ShapedText<'a> { engine, text_range.start, text, - spans, self.styles, self.dir, self.lang, @@ -445,6 +438,16 @@ impl<'a> ShapedText<'a> { } } + /// Derive an empty text run with the same properties as this one. + pub fn empty(&self) -> Self { + Self { + text: "", + width: Abs::zero(), + glyphs: Cow::Borrowed(&[]), + ..*self + } + } + /// Push a hyphen to end of the text. pub fn push_hyphen(&mut self, engine: &Engine, fallback: bool) { self.insert_hyphen(engine, fallback, Side::Right) @@ -493,7 +496,6 @@ impl<'a> ShapedText<'a> { range, safe_to_break: true, c: '-', - span: (Span::detached(), 0), is_justifiable: false, script: Script::Common, }; @@ -592,11 +594,11 @@ impl Debug for ShapedText<'_> { /// Group a range of text by BiDi level and script, shape the runs and generate /// items for them. pub fn shape_range<'a>( - items: &mut Vec>, + items: &mut Vec<(Range, Item<'a>)>, engine: &Engine, + text: &'a str, bidi: &BidiInfo<'a>, range: Range, - spans: &SpanMapper, styles: StyleChain<'a>, ) { let script = TextElem::script_in(styles); @@ -604,17 +606,9 @@ pub fn shape_range<'a>( let region = TextElem::region_in(styles); let mut process = |range: Range, level: BidiLevel| { let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL }; - let shaped = shape( - engine, - range.start, - &bidi.text[range], - spans, - styles, - dir, - lang, - region, - ); - items.push(Item::Text(shaped)); + let shaped = + shape(engine, range.start, &text[range.clone()], styles, dir, lang, region); + items.push((range, Item::Text(shaped))); }; let mut prev_level = BidiLevel::ltr(); @@ -625,14 +619,14 @@ pub fn shape_range<'a>( // set (rather than inferred from the glyphs), we keep the script at an // unchanging `Script::Unknown` so that only level changes cause breaks. for i in range.clone() { - if !bidi.text.is_char_boundary(i) { + if !text.is_char_boundary(i) { continue; } let level = bidi.levels[i]; let curr_script = match script { Smart::Auto => { - bidi.text[i..].chars().next().map_or(Script::Unknown, |c| c.script()) + text[i..].chars().next().map_or(Script::Unknown, |c| c.script()) } Smart::Custom(_) => Script::Unknown, }; @@ -668,7 +662,6 @@ fn shape<'a>( engine: &Engine, base: usize, text: &'a str, - spans: &SpanMapper, styles: StyleChain<'a>, dir: Dir, lang: Lang, @@ -677,7 +670,6 @@ fn shape<'a>( let size = TextElem::size_in(styles); let mut ctx = ShapingContext { engine, - spans, size, glyphs: vec![], used: vec![], @@ -717,7 +709,6 @@ fn shape<'a>( /// Holds shaping results and metadata common to all shaped segments. struct ShapingContext<'a, 'v> { engine: &'a Engine<'v>, - spans: &'a SpanMapper, glyphs: Vec, used: Vec, styles: StyleChain<'a>, @@ -830,7 +821,6 @@ fn shape_segment<'a>( range: start..end, safe_to_break: !info.unsafe_to_break(), c, - span: ctx.spans.span_at(start), is_justifiable: is_justifiable( c, script, @@ -921,7 +911,6 @@ fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, font: Font) { range: start..end, safe_to_break: true, c, - span: ctx.spans.span_at(start), is_justifiable: is_justifiable( c, script, diff --git a/crates/typst/src/model/par.rs b/crates/typst/src/model/par.rs index 7f65a00fb..2110995f3 100644 --- a/crates/typst/src/model/par.rs +++ b/crates/typst/src/model/par.rs @@ -18,9 +18,9 @@ use crate::realize::StyleVec; /// /// # Example /// ```example -/// #show par: set block(spacing: 0.65em) /// #set par( /// first-line-indent: 1em, +/// spacing: 0.65em, /// justify: true, /// ) /// @@ -115,8 +115,7 @@ pub struct ParElem { /// By typographic convention, paragraph breaks are indicated either by some /// space between paragraphs or by indented first lines. Consider reducing /// the [paragraph spacing]($block.spacing) to the [`leading`]($par.leading) - /// when using this property (e.g. using - /// `[#show par: set block(spacing: 0.65em)]`). + /// when using this property (e.g. using `[#set par(spacing: 0.65em)]`). #[ghost] pub first_line_indent: Length, diff --git a/docs/guides/guide-for-latex-users.md b/docs/guides/guide-for-latex-users.md index 1f3caef98..8c3b56013 100644 --- a/docs/guides/guide-for-latex-users.md +++ b/docs/guides/guide-for-latex-users.md @@ -593,10 +593,9 @@ The example below ```typ #set page(margin: 1.75in) -#set par(leading: 0.55em, first-line-indent: 1.8em, justify: true) +#set par(leading: 0.55em, spacing: 0.55em, first-line-indent: 1.8em, justify: true) #set text(font: "New Computer Modern") #show raw: set text(font: "New Computer Modern Mono") -#show par: set block(spacing: 0.55em) #show heading: set block(above: 1.4em, below: 1em) ``` diff --git a/docs/reference/syntax.md b/docs/reference/syntax.md index 9a7dc3733..b63d17760 100644 --- a/docs/reference/syntax.md +++ b/docs/reference/syntax.md @@ -120,7 +120,7 @@ a table listing all syntax that is available in code mode: | Named function | `{let f(x) = 2 * x}` | [Function]($function) | | Set rule | `{set text(14pt)}` | [Styling]($styling/#set-rules) | | Set-if rule | `{set text(..) if .. }` | [Styling]($styling/#set-rules) | -| Show-set rule | `{show par: set block(..)}` | [Styling]($styling/#show-rules) | +| Show-set rule | `{show heading: set block(..)}` | [Styling]($styling/#show-rules) | | Show rule with function | `{show raw: it => {..}}` | [Styling]($styling/#show-rules) | | Show-everything rule | `{show: columns.with(2)}` | [Styling]($styling/#show-rules) | | Context expression | `{context text.lang}` | [Context]($context) | diff --git a/tests/ref/bidi-whitespace-reset.png b/tests/ref/bidi-whitespace-reset.png index 7d64012f9e99f9e04ac28e706bddf896d77e1ded..e9973798b9f2db2e806abd82a775c1e3db12fe82 100644 GIT binary patch delta 347 zcmV-h0i^!=0_g&f7k?!P00000tRHeG0003nNklq|4QBe{~H?r8__YS4gW`6gRpj$)7;`WzYuJL|4C>5ud&tY z`F}W<<`(Cif-=7R|F8c)`_O;&2cPZ!pG~H@#jyv$)bq%o|9}6@{#S1Jf59q#{r?N; zG`IMWLV9CIiHrF%olyJC|NGQ~yqsMf1C7_yK5>0Nuxj)9w?M|-d!Q_J>dpWE?@oXE z|NlN6Qgz*r0V+W5jF#2I+u}19{{o>GU81waiH^S@h30qqTD-4( zWjB<$Y%M)4PE@@7BLGNk0^%#a^t5=YmEBBWooEYU8`3q7U)==~??4tmavy*KBr;>| tOtYf{QF3i;p7~(3tRA&^)Z!szF#wHc1@He=ygdK_002ovPDHLkV1m;=wGRLQ delta 364 zcmV-y0h9je0{Q}w7k?-S000005P@O&0003&Nkl%Nd#cMozvoc=w*Shz_m#7HAKY*9b!i`sQ}wk158Ke9xo=-~ zMa}yE|8RcPyyr`c;`aSNw~O}K>C58*DnKr;7%i(uEvCA~e=2ZB{XRV{t~~f59u(j}ygHYj7N<;Dl=T1qKL;T5rx`she$iO; z`v3oDULaqa4?sTFx%~fcRr3H?e9_Iotm*G)kvVGdsKuie69fQX3cl{7jqTL{0000< KMNUMnLSTYfdbsNV diff --git a/tests/ref/context-compatibility-locate.png b/tests/ref/context-compatibility-locate.png index 4c8944ab4dfc4c0543aa82c4eb88b59bb70736a3..32516c00f85ac240b0d83cb786192e7540981fa9 100644 GIT binary patch delta 1512 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 delta 827 zcmV-B1H}CC2GRzQB!3x6L_t(|+U?cbQ;cyO$8mpwduA}M7*{Tgj4777D94D#c}j$k znH**^gcVD)q*^wl6^l(t$#mWx8=bT$TAh$$msV}IQf)nNH_wbK7iON>CiA?1cb}Wr z-S;<%acYtRE3g9pSFk8OpvoX-R`F*BM3A~C01wQ3mkWM5wSNF%iU3d>MoghJznEM~ zTMs7RkJ4}=3pjnhYnoi}Pylpp1i+X^unVch{REVv>p*Jr7hwD6>QK4h+D(m(x(hrp zM6Tf zL;%DJ@p)=(Uw=XoKH`p~F~)^E=c+M@j|G=YR0DjEBXA&jxhL4&n`?dJ!D1Va+E{Uxq`E7Ne!#6yJ=EY; zn4sd5BNc*Me=}voyKsC`!$m=8)l_F_Meo96aTP~|?)Ub=X1ojIxO7GUCwLb=1?B{S z$25Dr443$~fTkLcw_&OAgGZvq3C=Otl;25#6?h`SzX4>*{~_Y8Ae8_B002ovPDHLk FV1gkWix~g_ diff --git a/tests/ref/issue-3601-empty-raw.png b/tests/ref/issue-3601-empty-raw.png new file mode 100644 index 0000000000000000000000000000000000000000..be5ea8fc263aa04c98606efd91f0ce0480b63c47 GIT binary patch literal 74 zcmeAS@N?(olHy`uVBq!ia0vp^6+kS-2qYLnl$YuODM3#c$B>F!$v^tJB;0;Bckwf9 WH*M+K^}0|Rq{!3N&t;ucLK6Trs1n2g literal 0 HcmV?d00001 diff --git a/tests/ref/issue-4278-par-trim-before-equation.png b/tests/ref/issue-4278-par-trim-before-equation.png new file mode 100644 index 0000000000000000000000000000000000000000..b055371906b62d657bc680ccb8e2a3e1ce17a5aa GIT binary patch literal 1138 zcmV-&1daQNP)rY#C9LMqf8+#~GnVU<5X*v@oaSARBL_nPwCNPFj3QR&33}EF}ZiAJ!P%H{vp>z~b z?q#ElyRc1QFxa3tYVAsjP!>k*1!zAurw`gD?S@O0WI69AzXvDZymEdgzhBNF98iZy zn1o4~gdNiLX^Hh=|G$G9yd-#PkeT*G(?PtSyBba9?o%zmz?Wtnc^ zBLL7_Q?&NS*>%1z7RyK*m^vq=wsF7Ig2+8LuRU5tv>X(S(qW4N5eI{`-5fvVR1(T1IEj@=utgw>9xmTE}1d)Qt zdtja3x^&}t&#{noFc*mNe7J_ZGD+AYumStd2qJ?=;QMjdZ+TJ>oqQg6r!+EEy1pzh z8WU8oxA`-79iwd$*YM=+#Z+V$!&|u=WBFh{gEtos$EK1{wc`)XDJ0b54Kx4hiiX~K zJ#Zr!E$Zxkq)59%Kmkr|@4f{|sAdoLI+aou*a*|Rgtu$!&LY1T6lg+~zIUhpa)!aG z0F;5iF>~r2oYrqdoY$WioRRm(0kFcy$7Ys60p8Prf(|Ae)9_1Da6{<7>=MrHQwJft z6Fz%?2I&v4LpvXB*;d=1_Xe8P#^VO_86=Fu?{O=>?fSS8TE#2+?Z@BsSVCH$g)b1M zSUX~oFz{&0J{q$$N?Q-bCvw+Yo%2iloz&umD494`AxrpS(-hORbOH8CQ#*0HpmJHA z$d!K~5%7z3tJKRgXo?qLB5_ugSU!h*rePvO6f>Naq7sbQC%cQ9lT`GE0{*_wv0|9K zxk&g$hovF^H{1-OnXmr~*wC}2gL7*gau6{=#Ht?lJiHsZkuC2Y|1-w-Z= zQ{0=~9gZ-V;&ON#h6%fFex(2jY?;WLi-bv-gby742VA+E{l3*fXaE2J07*qoM6N<$ Ef)WN9EC2ui literal 0 HcmV?d00001 diff --git a/tests/ref/justify-basically-empty.png b/tests/ref/justify-basically-empty.png new file mode 100644 index 0000000000000000000000000000000000000000..3d1b50c13013f1ae752d913b5cc10803da68b709 GIT binary patch literal 74 zcmeAS@N?(olHy`uVBq!ia0vp^6+kS_2qYNh*OzqzDM3#c$B>F!$v^tJB;0;B&*En& We=@oH1((zbkRnf4KbLh*2~7Z9YZJTx literal 0 HcmV?d00001 diff --git a/tests/ref/par-metadata-after-trimmed-space.png b/tests/ref/par-metadata-after-trimmed-space.png new file mode 100644 index 0000000000000000000000000000000000000000..b0de98eab1bd91b67fa5125d76b6307dc08ff3e4 GIT binary patch literal 1030 zcmV+h1o``kP)3lpqa#9_n^^2mg%jUhjy}!5`4Q^ihM|4KRZ*n}WoaVJdz2h4;PKCy@)vMTe)2@W z+f~u>@Y6lq_rSgGd2ZqjE4=HCAL;%`z^sYw1XSU!NGb2vW)3giY^b3Q#*3)rW9Xk|Jb?y$|>pSs-Sqoe{P0 zqpAo|Yh}`$Q_JKQO2mAtz^o5Odp6E(RkGV4E*f$&sr(TPFAY}&O5)>a%{VvnFjT_4 zII(RYtXv-0lXHBMS1S+c5k(gNMd`&{lQuptr{r?TkI#2G*>fYKStrFuH}K1=uY%g>$No z^-o${P0=@XwhQ-K0e{(5PwW>5t;RHIC-_`3=7EcG)*kO?dL53joU#gSNPNv)P+DVk07xgM-)d$zD8_C0f2S;-G07c%pEc- z_)4o2%xwa}=z4H`3Cnlh>(<g0_+*E(k(t~3?ujkHYC<^ zvLpWtg%$I582Xonr9R3me|6`XOy#hDcArl*F)8UMp`vMS6g5U{J(|&19D)QT5~2~m;e9(07*qoM6N<$f@UP) A761SM literal 0 HcmV?d00001 diff --git a/tests/ref/par-trailing-whitespace.png b/tests/ref/par-trailing-whitespace.png new file mode 100644 index 0000000000000000000000000000000000000000..10c22da5ae897d249c26d978d50935be4b392b8e GIT binary patch literal 91 zcmeAS@N?(olHy`uVBq!ia0vp^6+kS_$P6TVPu)xfQfvV}A+G=b|F6IO-*q9s7f?jN o)5S5Q;#RW6mjg}D{w!o+2wl(EsVpRu36x{-boFyt=akR{069Dw&;S4c literal 0 HcmV?d00001 diff --git a/tests/suite/foundations/version.typ b/tests/suite/foundations/version.typ index bf2cadb18..a4be7f13e 100644 --- a/tests/suite/foundations/version.typ +++ b/tests/suite/foundations/version.typ @@ -4,7 +4,7 @@ // Test version constructor. // Empty. -#version() +#test(array(version()), ()) // Plain. #test(version(1, 2).major, 1) diff --git a/tests/suite/layout/spacing.typ b/tests/suite/layout/spacing.typ index dd0fced55..c32e6c8f9 100644 --- a/tests/suite/layout/spacing.typ +++ b/tests/suite/layout/spacing.typ @@ -47,14 +47,14 @@ Totally #h() ignored Hello #h(2cm, weak: true) --- issue-4087 --- -// weak space at the end of the line would be removed. +// Weak space at the end of the line is removed. This is the first line #h(2cm, weak: true) A new line -// non-weak space would be consume a specified width and push next line. +// Non-weak space consumes a specified width and pushes to next line. This is the first line #h(2cm, weak: false) A new line -// similarly weak space at the beginning of the line would be removed. -This is the first line\ #h(2cm, weak: true) A new line +// Similarly, weak space at the beginning of the line is removed. +This is the first line \ #h(2cm, weak: true) A new line -// non-spacing, on the other hand, is not removed. -This is the first line\ #h(2cm, weak: false) A new line +// Non-weak-spacing, on the other hand, is not removed. +This is the first line \ #h(2cm, weak: false) A new line diff --git a/tests/suite/model/par.typ b/tests/suite/model/par.typ index f07c4c6ce..80bc9f3e1 100644 --- a/tests/suite/model/par.typ +++ b/tests/suite/model/par.typ @@ -78,3 +78,22 @@ Welcome \ here. Does this work well? #set text(dir: rtl) لآن وقد أظلم الليل وبدأت النجوم تنضخ وجه الطبيعة التي أعْيَتْ من طول ما انبعثت في النهار + +--- par-trailing-whitespace --- +// Ensure that trailing whitespace layouts as intended. +#box(fill: aqua, " ") + +--- par-empty-metadata --- +// Check that metadata still works in a zero length paragraph. +#block(height: 0pt)[#""#metadata(false)] +#context test(query().first().value, false) + +--- par-metadata-after-trimmed-space --- +// Ensure that metadata doesn't prevent trailing spaces from being trimmed. +#set par(justify: true, linebreaks: "simple") +#set text(hyphenate: false) +Lorem ipsum dolor #metadata(none) nonumy eirmod tempor. + +--- issue-4278-par-trim-before-equation --- +#set par(justify: true) +#lorem(6) aa $a = c + b$ From 129a4d600c1860beb7ec2ae52a9186088f6f043d Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 4 Jul 2024 15:27:43 +0200 Subject: [PATCH 06/34] Fix hyphenation outside of words (#4498) --- crates/typst/src/layout/inline/linebreak.rs | 88 +++++++++++--------- tests/ref/hyphenate-outside-of-words.png | Bin 0 -> 1011 bytes tests/suite/layout/inline/hyphenate.typ | 10 +++ 3 files changed, 57 insertions(+), 41 deletions(-) create mode 100644 tests/ref/hyphenate-outside-of-words.png diff --git a/crates/typst/src/layout/inline/linebreak.rs b/crates/typst/src/layout/inline/linebreak.rs index dbaa9c59a..9deaa92a8 100644 --- a/crates/typst/src/layout/inline/linebreak.rs +++ b/crates/typst/src/layout/inline/linebreak.rs @@ -1,6 +1,6 @@ use std::ops::{Add, Sub}; -use icu_properties::maps::CodePointMapData; +use icu_properties::maps::{CodePointMapData, CodePointMapDataBorrowed}; use icu_properties::sets::CodePointSetData; use icu_properties::LineBreak; use icu_provider::AsDeserializingBufferProvider; @@ -8,6 +8,7 @@ use icu_provider_adapters::fork::ForkByKeyProvider; use icu_provider_blob::BlobDataProvider; use icu_segmenter::LineSegmenter; use once_cell::sync::Lazy; +use unicode_segmentation::UnicodeSegmentation; use super::*; use crate::engine::Engine; @@ -630,7 +631,7 @@ fn raw_cost( /// This is an internal instead of an external iterator because it makes the /// code much simpler and the consumers of this function don't need the /// composability and flexibility of external iteration anyway. -fn breakpoints<'a>(p: &'a Preparation<'a>, mut f: impl FnMut(usize, Breakpoint)) { +fn breakpoints(p: &Preparation, mut f: impl FnMut(usize, Breakpoint)) { let text = p.text; // Single breakpoint at the end for empty text. @@ -661,7 +662,7 @@ fn breakpoints<'a>(p: &'a Preparation<'a>, mut f: impl FnMut(usize, Breakpoint)) } } - // Get the UAX #14 linebreak opportunities. + // Get the next UAX #14 linebreak opportunity. let Some(point) = iter.next() else { break }; // Skip breakpoint if there is no char before it. icu4x generates one @@ -686,46 +687,13 @@ fn breakpoints<'a>(p: &'a Preparation<'a>, mut f: impl FnMut(usize, Breakpoint)) }; // Hyphenate between the last and current breakpoint. - 'hyphenate: { - if !hyphenate { - break 'hyphenate; - } - - // Extract a hyphenatable "word". - let word = &text[last..point].trim_end_matches(|c: char| !c.is_alphabetic()); - if word.is_empty() { - break 'hyphenate; - } - - let end = last + word.len(); + if hyphenate { let mut offset = last; - - // Determine the language to hyphenate this word in. - let Some(lang) = lang_at(p, last) else { break 'hyphenate }; - - for syllable in hypher::hyphenate(word, lang) { - // Don't hyphenate after the final syllable. - offset += syllable.len(); - if offset == end { - continue; + for segment in text[last..point].split_word_bounds() { + if !segment.is_empty() && segment.chars().all(char::is_alphabetic) { + hyphenations(p, &lb, offset, segment, &mut f); } - - // Filter out hyphenation opportunities where hyphenation was - // actually disabled. - if !hyphenate_at(p, offset) { - continue; - } - - // Filter out forbidden hyphenation opportunities. - if matches!( - syllable.chars().next_back().map(|c| lb.get(c)), - Some(LineBreak::Glue | LineBreak::WordJoiner | LineBreak::ZWJ) - ) { - continue; - } - - // Call `f` for the word-internal hyphenation opportunity. - f(offset, Breakpoint::Hyphen); + offset += segment.len(); } } @@ -736,6 +704,44 @@ fn breakpoints<'a>(p: &'a Preparation<'a>, mut f: impl FnMut(usize, Breakpoint)) } } +/// Generate breakpoints for hyphenations within a word. +fn hyphenations( + p: &Preparation, + lb: &CodePointMapDataBorrowed, + mut offset: usize, + word: &str, + mut f: impl FnMut(usize, Breakpoint), +) { + let Some(lang) = lang_at(p, offset) else { return }; + let end = offset + word.len(); + + for syllable in hypher::hyphenate(word, lang) { + offset += syllable.len(); + + // Don't hyphenate after the final syllable. + if offset == end { + continue; + } + + // Filter out hyphenation opportunities where hyphenation was actually + // disabled. + if !hyphenate_at(p, offset) { + continue; + } + + // Filter out forbidden hyphenation opportunities. + if matches!( + syllable.chars().next_back().map(|c| lb.get(c)), + Some(LineBreak::Glue | LineBreak::WordJoiner | LineBreak::ZWJ) + ) { + continue; + } + + // Call `f` for the word-internal hyphenation opportunity. + f(offset, Breakpoint::Hyphen); + } +} + /// Produce linebreak opportunities for a link. fn linebreak_link(link: &str, mut f: impl FnMut(usize)) { #[derive(PartialEq)] diff --git a/tests/ref/hyphenate-outside-of-words.png b/tests/ref/hyphenate-outside-of-words.png new file mode 100644 index 0000000000000000000000000000000000000000..57b11ed84222d1dd8a66e118fb319069efce7c07 GIT binary patch literal 1011 zcmVQhYF`8{b3Gu=pU}Q^F3Y!Y(a4-`yL2xj@wKJSsCj$u^D?&MS3{qe~ z={A_0tgPd>L5gxHgo5BW&2cP+K5vaTF7##>efkjdy#Ik$@=Ly--ytOGkPOSP4FA92 z?Y4P|;LtAI)Az9~GEAZw_e#dvlf;D!3XYo5>ePGGSLb7{h+B)el^M&Tkbc}KCS0Q< z-fT*vSkoWT;WHI6{E*ZF0+yjY6*1wsW)jWml?+4{CeO{fCIS<=)QVzGbht3qrKoN@ zP2;r=zr81$I{eozG;w?qy5cv*gqC(z5gY%R=f&=?j@1GM$Sj zgKkxq!l8+JG^hMk!S<^jhLal!YXNdD+fGrZ9t^5FZy7S}zEzz;@C@tR5Xv61v z!+e%{Ikxp3Xcfh!HW7N8{)4#H3wK`x47W$kpyu;$a zF8L##hQ~{JjuvY1G|VQ?(YCzRGAzR~j61v}5j<^>1{R_PY2fx`iVVf7t$~ZiQlU<3 z^BSD3qy3%TTs4Qgf6u%#7$ht$xx4}=HDHNj>RsLdzuMOOHl=dOEAUSmEV11Gme)X0 z!=~4sdf^NfKfQX(99YeC`1fY7!F)GviIe1xUCl~gyA&|2t~%sTLJIh)RB++66mVA_ zo2R`5*I%8YqkL*M?GJS)31MwSjd}&XmuK&-seS4v1eC43PMrpFio61^1SD>Ss`Yh* z&GF{HaLEr|fqy}zjnU#FWQHTuWkHI;E3ocL>e$Q&SDP@V$5QJr)fNoDm(~`GhBis^91>FTV+9!D(Toh5SCzVN6Z9wQ?QtVJ2{E hM&4=}mf-^r{|!Jnes$8Jf@%N&002ovPDHLkV1kuJ<5vIx literal 0 HcmV?d00001 diff --git a/tests/suite/layout/inline/hyphenate.typ b/tests/suite/layout/inline/hyphenate.typ index c366b38f9..debce1da1 100644 --- a/tests/suite/layout/inline/hyphenate.typ +++ b/tests/suite/layout/inline/hyphenate.typ @@ -50,6 +50,16 @@ It's a #emph[Tree]beard. #set text(hyphenate: true) #h(6pt) networks, the rest. +--- hyphenate-outside-of-words --- +// More tests for hyphenation of non-words. +#set text(hyphenate: true) +#block(width: 0pt, "doesn't") +#block(width: 0pt, "(OneNote)") +#block(width: 0pt, "(present)") + +#set text(lang: "de") +#block(width: 0pt, "(bzw.)") + --- hyphenate-pt-repeat-hyphen-natural-word-breaking --- // The word breaker naturally breaks arco-da-velha at arco-/-da-velha, // so we shall repeat the hyphen, even that hyphenate is set to false. From 3b32aa7929e9e7cc411647c3e32f1538ba2bf5c2 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 4 Jul 2024 17:28:23 +0200 Subject: [PATCH 07/34] Add regression test for #3355 (#4499) --- tests/ref/issue-3355-metadata-weak-spacing.png | Bin 0 -> 327 bytes tests/suite/layout/flow/flow.typ | 7 +++++++ 2 files changed, 7 insertions(+) create mode 100644 tests/ref/issue-3355-metadata-weak-spacing.png diff --git a/tests/ref/issue-3355-metadata-weak-spacing.png b/tests/ref/issue-3355-metadata-weak-spacing.png new file mode 100644 index 0000000000000000000000000000000000000000..1ae8a104314732c8fd6b39323b8ced738d88bbfd GIT binary patch literal 327 zcmV-N0l5B&P)+9s?$@($CM&%F4>g$;rpZ$A8=ZtE;Q3s;Z}_ zr=z2zp`oFko}P(`iF9;yadB~OZfe>JR002ovPDHLkV1lMrnsfjF literal 0 HcmV?d00001 diff --git a/tests/suite/layout/flow/flow.typ b/tests/suite/layout/flow/flow.typ index 9c48c9acc..7c8ade141 100644 --- a/tests/suite/layout/flow/flow.typ +++ b/tests/suite/layout/flow/flow.typ @@ -65,3 +65,10 @@ = Heading #lorem(6) + +--- issue-3355-metadata-weak-spacing --- +#set page(height: 50pt) +#block(width: 100%, height: 30pt, fill: aqua) +#metadata(none) +#v(10pt, weak: true) +Hi From b847cccba41371a8ab485ed0f774890188c3ca98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20d=27Herbais=20de=20Thun?= Date: Thu, 4 Jul 2024 11:27:34 -0700 Subject: [PATCH 08/34] Go from `String` to `&str` when passing font names to SVG code (#4500) --- crates/typst/src/visualize/image/mod.rs | 4 ++-- crates/typst/src/visualize/image/svg.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/typst/src/visualize/image/mod.rs b/crates/typst/src/visualize/image/mod.rs index fbbedfb3e..5d952b1a3 100644 --- a/crates/typst/src/visualize/image/mod.rs +++ b/crates/typst/src/visualize/image/mod.rs @@ -196,7 +196,7 @@ fn layout_image( format, elem.alt(styles), engine.world, - &families(styles).map(|s| s.into()).collect::>(), + &families(styles).collect::>(), ) .at(span)?; @@ -360,7 +360,7 @@ impl Image { format: ImageFormat, alt: Option, world: Tracked, - families: &[String], + families: &[&str], ) -> StrResult { let kind = match format { ImageFormat::Raster(format) => { diff --git a/crates/typst/src/visualize/image/svg.rs b/crates/typst/src/visualize/image/svg.rs index 09319ccde..f7a498a83 100644 --- a/crates/typst/src/visualize/image/svg.rs +++ b/crates/typst/src/visualize/image/svg.rs @@ -40,7 +40,7 @@ impl SvgImage { pub fn with_fonts( data: Bytes, world: Tracked, - families: &[String], + families: &[&str], ) -> StrResult { let book = world.book(); let resolver = Mutex::new(FontResolver::new(world, book, families)); @@ -142,7 +142,7 @@ struct FontResolver<'a> { /// The world we use to load fonts. world: Tracked<'a, dyn World + 'a>, /// The active list of font families at the location of the SVG. - families: &'a [String], + families: &'a [&'a str], /// A mapping from Typst font indices to fontdb IDs. to_id: HashMap>, /// The reverse mapping. @@ -156,7 +156,7 @@ impl<'a> FontResolver<'a> { fn new( world: Tracked<'a, dyn World + 'a>, book: &'a FontBook, - families: &'a [String], + families: &'a [&'a str], ) -> Self { Self { book, @@ -191,11 +191,11 @@ impl FontResolver<'_> { font.families() .iter() .filter_map(|family| match family { - usvg::FontFamily::Named(named) => Some(named), + usvg::FontFamily::Named(named) => Some(named.as_str()), // We don't support generic families at the moment. _ => None, }) - .chain(self.families) + .chain(self.families.iter().copied()) .filter_map(|named| self.book.select(&named.to_lowercase(), variant)) .find_map(|index| self.get_or_load(index, db)) } From 906de589cef8184f5bc8b1526b1783bad614cd0e Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 5 Jul 2024 14:38:05 +0200 Subject: [PATCH 09/34] Clean up flow a bit (#4505) --- crates/typst/src/layout/flow.rs | 410 +++++++++++++++++--------------- 1 file changed, 222 insertions(+), 188 deletions(-) diff --git a/crates/typst/src/layout/flow.rs b/crates/typst/src/layout/flow.rs index cdf6034d5..a1f1402e4 100644 --- a/crates/typst/src/layout/flow.rs +++ b/crates/typst/src/layout/flow.rs @@ -47,51 +47,7 @@ impl Packed { styles: StyleChain, regions: Regions, ) -> SourceResult { - if !regions.size.x.is_finite() && regions.expand.x { - bail!(self.span(), "cannot expand into infinite width"); - } - if !regions.size.y.is_finite() && regions.expand.y { - bail!(self.span(), "cannot expand into infinite height"); - } - - // Check whether we have just a single multiple-layoutable element. In - // that case, we do not set `expand.y` to `false`, but rather keep it at - // its original value (since that element can take the full space). - // - // Consider the following code: `block(height: 5cm, pad(10pt, align(bottom, ..)))` - // Thanks to the code below, the expansion will be passed all the way - // through the block & pad and reach the innermost flow, so that things - // are properly bottom-aligned. - let mut alone = false; - if let [child] = self.children().elements() { - alone = child.is::(); - } - - let mut layouter = FlowLayouter::new(locator, styles, regions, alone); - for (child, styles) in self.children().chain(&styles) { - if let Some(elem) = child.to_packed::() { - layouter.layout_tag(elem); - } else if child.is::() { - layouter.flush(engine)?; - } else if let Some(elem) = child.to_packed::() { - layouter.layout_spacing(engine, elem, styles)?; - } else if let Some(elem) = child.to_packed::() { - layouter.layout_par(engine, elem, styles)?; - } else if let Some(elem) = child.to_packed::() { - layouter.layout_block(engine, elem, styles)?; - } else if let Some(placed) = child.to_packed::() { - layouter.layout_placed(engine, placed, styles)?; - } else if child.is::() { - if !layouter.regions.backlog.is_empty() || layouter.regions.last.is_some() - { - layouter.finish_region(engine, true)?; - } - } else { - bail!(child.span(), "unexpected flow child"); - } - } - - layouter.finish(engine) + FlowLayouter::new(engine, self, locator, &styles, regions).layout() } } @@ -103,13 +59,17 @@ impl Debug for FlowElem { } /// Performs flow layout. -struct FlowLayouter<'a> { +struct FlowLayouter<'a, 'e> { + /// The engine. + engine: &'a mut Engine<'e>, + /// The children that will be arranged into a flow. + flow: &'a Packed, /// Whether this is the root flow. root: bool, /// Provides unique locations to the flow's children. locator: SplitLocator<'a>, /// The shared styles. - styles: StyleChain<'a>, + styles: &'a StyleChain<'a>, /// The regions to layout children into. regions: Regions<'a>, /// Whether the flow should expand to fill the region. @@ -124,7 +84,7 @@ struct FlowLayouter<'a> { /// Spacing and layouted blocks for the current region. items: Vec, /// A queue of tags that will be attached to the next frame. - pending_tags: Vec, + pending_tags: Vec<&'a Tag>, /// A queue of floating elements. pending_floats: Vec, /// Whether we have any footnotes in the current region. @@ -157,18 +117,27 @@ enum FlowItem { align: Axes, /// Whether the frame sticks to the item after it (for orphan prevention). sticky: bool, - /// Whether the frame is movable; that is, kept together with its footnotes. + /// Whether the frame is movable; that is, kept together with its + /// footnotes. /// - /// This is true for frames created by paragraphs and [`LayoutSingle`] elements. + /// This is true for frames created by paragraphs and + /// [`BlockElem::single_layouter`] elements. movable: bool, }, /// An absolutely placed frame. Placed { + /// The layouted content. frame: Frame, + /// Where to place the content horizontally. x_align: FixedAlignment, + /// Where to place the content vertically. y_align: Smart>, + /// A translation to apply to the content. delta: Axes>, + /// Whether the content floats --- i.e. collides with in-flow content. float: bool, + /// The amount of space that needs to be kept between the placed content + /// and in-flow content. Only relevant if `float` is `true`. clearance: Abs, }, /// A footnote frame (can also be the separator). @@ -193,24 +162,41 @@ impl FlowItem { } } -impl<'a> FlowLayouter<'a> { +impl<'a, 'e> FlowLayouter<'a, 'e> { /// Create a new flow layouter. fn new( + engine: &'a mut Engine<'e>, + flow: &'a Packed, locator: Locator<'a>, - styles: StyleChain<'a>, + styles: &'a StyleChain<'a>, mut regions: Regions<'a>, - alone: bool, ) -> Self { - let expand = regions.expand; - let root = std::mem::replace(&mut regions.root, false); + // Check whether we have just a single multiple-layoutable element. In + // that case, we do not set `expand.y` to `false`, but rather keep it at + // its original value (since that element can take the full space). + // + // Consider the following code: `block(height: 5cm, pad(10pt, + // align(bottom, ..)))`. Thanks to the code below, the expansion will be + // passed all the way through the block & pad and reach the innermost + // flow, so that things are properly bottom-aligned. + let mut alone = false; + if let [child] = flow.children.elements() { + alone = child.is::(); + } // Disable vertical expansion when there are multiple or not directly // layoutable children. + let expand = regions.expand; if !alone { regions.expand.y = false; } + // The children aren't root. + let root = std::mem::replace(&mut regions.root, false); + Self { + engine, + flow, root, locator: locator.split(), styles, @@ -223,52 +209,84 @@ impl<'a> FlowLayouter<'a> { pending_floats: vec![], has_footnotes: false, footnote_config: FootnoteConfig { - separator: FootnoteEntry::separator_in(styles), - clearance: FootnoteEntry::clearance_in(styles), - gap: FootnoteEntry::gap_in(styles), + separator: FootnoteEntry::separator_in(*styles), + clearance: FootnoteEntry::clearance_in(*styles), + gap: FootnoteEntry::gap_in(*styles), }, finished: vec![], } } + /// Layout the flow. + fn layout(mut self) -> SourceResult { + for (child, styles) in self.flow.children.chain(self.styles) { + if let Some(elem) = child.to_packed::() { + self.handle_tag(elem); + } else if let Some(elem) = child.to_packed::() { + self.handle_v(elem, styles)?; + } else if let Some(elem) = child.to_packed::() { + self.handle_colbreak(elem)?; + } else if let Some(elem) = child.to_packed::() { + self.handle_par(elem, styles)?; + } else if let Some(elem) = child.to_packed::() { + self.handle_block(elem, styles)?; + } else if let Some(elem) = child.to_packed::() { + self.handle_place(elem, styles)?; + } else if let Some(elem) = child.to_packed::() { + self.handle_flush(elem)?; + } else { + bail!(child.span(), "unexpected flow child"); + } + } + + self.finish() + } + /// Place explicit metadata into the flow. - fn layout_tag(&mut self, elem: &Packed) { - self.pending_tags.push(elem.tag.clone()); + fn handle_tag(&mut self, elem: &'a Packed) { + self.pending_tags.push(&elem.tag); } /// Layout vertical spacing. - fn layout_spacing( - &mut self, - engine: &mut Engine, - v: &Packed, - styles: StyleChain, - ) -> SourceResult<()> { - self.layout_item( - engine, - match v.amount() { - Spacing::Rel(rel) => FlowItem::Absolute( - rel.resolve(styles).relative_to(self.initial.y), - v.weakness(styles) > 0, - ), - Spacing::Fr(fr) => FlowItem::Fractional(*fr), - }, - ) + fn handle_v(&mut self, v: &'a Packed, styles: StyleChain) -> SourceResult<()> { + self.handle_item(match v.amount { + Spacing::Rel(rel) => FlowItem::Absolute( + // Resolve the spacing relative to the current base height. + rel.resolve(styles).relative_to(self.initial.y), + v.weakness(styles) > 0, + ), + Spacing::Fr(fr) => FlowItem::Fractional(fr), + }) + } + + /// Layout a column break. + fn handle_colbreak(&mut self, _: &'a Packed) -> SourceResult<()> { + // If there is still an available region, skip to it. + // TODO: Turn this into a region abstraction. + if !self.regions.backlog.is_empty() || self.regions.last.is_some() { + self.finish_region(true)?; + } + Ok(()) } /// Layout a paragraph. - fn layout_par( + fn handle_par( &mut self, - engine: &mut Engine, - par: &Packed, + par: &'a Packed, styles: StyleChain, ) -> SourceResult<()> { + // Fetch properties. let align = AlignElem::alignment_in(styles).resolve(styles); let leading = ParElem::leading_in(styles); + + // Layout the paragraph into lines. This only depends on the base size, + // not on the Y position. let consecutive = self.last_was_par; + let locator = self.locator.next(&par.span()); let lines = par .layout( - engine, - self.locator.next(&par.span()), + self.engine, + locator, styles, consecutive, self.regions.base(), @@ -280,39 +298,26 @@ impl<'a> FlowLayouter<'a> { // previous sticky frame to the next region (if available) if let Some(first) = lines.first() { while !self.regions.size.y.fits(first.height()) && !self.regions.in_last() { - let mut sticky = self.items.len(); - for (i, item) in self.items.iter().enumerate().rev() { - match *item { - FlowItem::Absolute(_, _) => {} - FlowItem::Frame { sticky: true, .. } => sticky = i, - _ => break, - } - } - - let carry: Vec<_> = self.items.drain(sticky..).collect(); - self.finish_region(engine, false)?; - let in_last = self.regions.in_last(); - - for item in carry { - self.layout_item(engine, item)?; - } - + let in_last = self.finish_region_with_migration()?; if in_last { break; } } } + // Layout the lines. for (i, mut frame) in lines.into_iter().enumerate() { if i > 0 { - self.layout_item(engine, FlowItem::Absolute(leading, true))?; + self.handle_item(FlowItem::Absolute(leading, true))?; } self.drain_tag(&mut frame); - self.layout_item( - engine, - FlowItem::Frame { frame, align, sticky: false, movable: true }, - )?; + self.handle_item(FlowItem::Frame { + frame, + align, + sticky: false, + movable: true, + })?; } self.last_was_par = true; @@ -320,56 +325,54 @@ impl<'a> FlowLayouter<'a> { } /// Layout into multiple regions. - fn layout_block( + fn handle_block( &mut self, - engine: &mut Engine, block: &'a Packed, styles: StyleChain<'a>, ) -> SourceResult<()> { - // Temporarily delegate rootness to the columns. + // Fetch properties. + let sticky = block.sticky(styles); + let align = AlignElem::alignment_in(styles).resolve(styles); + + // If the block is "rootable" it may host footnotes. In that case, we + // defer rootness to it temporarily. We disable our own rootness to + // prevent duplicate footnotes. let is_root = self.root; if is_root && block.rootable(styles) { self.root = false; self.regions.root = true; } + // Skip directly if region is already full. if self.regions.is_full() { - // Skip directly if region is already full. - self.finish_region(engine, false)?; + self.finish_region(false)?; } // Layout the block itself. - let sticky = block.sticky(styles); let fragment = block.layout( - engine, + self.engine, self.locator.next(&block.span()), styles, self.regions, )?; - // How to align the block. - let align = AlignElem::alignment_in(styles).resolve(styles); - let mut notes = Vec::new(); for (i, mut frame) in fragment.into_iter().enumerate() { // Find footnotes in the frame. if self.root { - find_footnotes(&mut notes, &frame); + collect_footnotes(&mut notes, &frame); } if i > 0 { - self.finish_region(engine, false)?; + self.finish_region(false)?; } self.drain_tag(&mut frame); frame.post_process(styles); - self.layout_item( - engine, - FlowItem::Frame { frame, align, sticky, movable: false }, - )?; + self.handle_item(FlowItem::Frame { frame, align, sticky, movable: false })?; } - self.try_handle_footnotes(engine, notes)?; + self.try_handle_footnotes(notes)?; self.root = is_root; self.regions.root = false; @@ -379,50 +382,56 @@ impl<'a> FlowLayouter<'a> { } /// Layout a placed element. - fn layout_placed( + fn handle_place( &mut self, - engine: &mut Engine, - placed: &Packed, + placed: &'a Packed, styles: StyleChain, ) -> SourceResult<()> { + // Fetch properties. let float = placed.float(styles); let clearance = placed.clearance(styles); let alignment = placed.alignment(styles); let delta = Axes::new(placed.dx(styles), placed.dy(styles)).resolve(styles); + let x_align = alignment.map_or(FixedAlignment::Center, |align| { align.x().unwrap_or_default().resolve(styles) }); let y_align = alignment.map(|align| align.y().map(|y| y.resolve(styles))); + let mut frame = placed .layout( - engine, + self.engine, self.locator.next(&placed.span()), styles, self.regions.base(), )? .into_frame(); + frame.post_process(styles); - let item = FlowItem::Placed { frame, x_align, y_align, delta, float, clearance }; - self.layout_item(engine, item) + + self.handle_item(FlowItem::Placed { + frame, + x_align, + y_align, + delta, + float, + clearance, + }) } - /// Attach currently pending metadata to the frame. - fn drain_tag(&mut self, frame: &mut Frame) { - if !self.pending_tags.is_empty() && !frame.is_empty() { - frame.prepend_multiple( - self.pending_tags - .drain(..) - .map(|tag| (Point::zero(), FrameItem::Tag(tag))), - ); + /// Lays out all floating elements before continuing with other content. + fn handle_flush(&mut self, _: &'a Packed) -> SourceResult<()> { + for item in std::mem::take(&mut self.pending_floats) { + self.handle_item(item)?; } + while !self.pending_floats.is_empty() { + self.finish_region(false)?; + } + Ok(()) } /// Layout a finished frame. - fn layout_item( - &mut self, - engine: &mut Engine, - mut item: FlowItem, - ) -> SourceResult<()> { + fn handle_item(&mut self, mut item: FlowItem) -> SourceResult<()> { match item { FlowItem::Absolute(v, weak) => { if weak @@ -439,24 +448,24 @@ impl<'a> FlowLayouter<'a> { FlowItem::Frame { ref frame, movable, .. } => { let height = frame.height(); while !self.regions.size.y.fits(height) && !self.regions.in_last() { - self.finish_region(engine, false)?; + self.finish_region(false)?; } let in_last = self.regions.in_last(); self.regions.size.y -= height; if self.root && movable { let mut notes = Vec::new(); - find_footnotes(&mut notes, frame); + collect_footnotes(&mut notes, frame); self.items.push(item); // When we are already in_last, we can directly force the // footnotes. - if !self.handle_footnotes(engine, &mut notes, true, in_last)? { + if !self.handle_footnotes(&mut notes, true, in_last)? { let item = self.items.pop(); - self.finish_region(engine, false)?; + self.finish_region(false)?; self.items.extend(item); self.regions.size.y -= height; - self.handle_footnotes(engine, &mut notes, true, true)?; + self.handle_footnotes(&mut notes, true, true)?; } return Ok(()); } @@ -504,8 +513,8 @@ impl<'a> FlowLayouter<'a> { // Find footnotes in the frame. if self.root { let mut notes = vec![]; - find_footnotes(&mut notes, frame); - self.try_handle_footnotes(engine, notes)?; + collect_footnotes(&mut notes, frame); + self.try_handle_footnotes(notes)?; } } FlowItem::Footnote(_) => {} @@ -515,12 +524,49 @@ impl<'a> FlowLayouter<'a> { Ok(()) } + /// Attach currently pending metadata to the frame. + fn drain_tag(&mut self, frame: &mut Frame) { + if !self.pending_tags.is_empty() && !frame.is_empty() { + frame.prepend_multiple( + self.pending_tags + .drain(..) + .map(|tag| (Point::zero(), FrameItem::Tag(tag.clone()))), + ); + } + } + + /// Finisht the region, migrating all sticky items to the next one. + /// + /// Returns whether we migrated into a last region. + fn finish_region_with_migration(&mut self) -> SourceResult { + // Find the suffix of sticky items. + let mut sticky = self.items.len(); + for (i, item) in self.items.iter().enumerate().rev() { + match *item { + FlowItem::Absolute(_, _) => {} + FlowItem::Frame { sticky: true, .. } => sticky = i, + _ => break, + } + } + + let carry: Vec<_> = self.items.drain(sticky..).collect(); + self.finish_region(false)?; + + let in_last = self.regions.in_last(); + for item in carry { + self.handle_item(item)?; + } + + Ok(in_last) + } + /// Finish the frame for one region. /// /// Set `force` to `true` to allow creating a frame for out-of-flow elements /// only (this is used to force the creation of a frame in case the /// remaining elements are all out-of-flow). - fn finish_region(&mut self, engine: &mut Engine, force: bool) -> SourceResult<()> { + fn finish_region(&mut self, force: bool) -> SourceResult<()> { + // Early return if we don't have any relevant items. if !force && !self.items.is_empty() && self.items.iter().all(FlowItem::is_out_of_flow) @@ -585,6 +631,13 @@ impl<'a> FlowLayouter<'a> { size.y = self.initial.y; } + if !self.regions.size.x.is_finite() && self.expand.x { + bail!(self.flow.span(), "cannot expand into infinite width"); + } + if !self.regions.size.y.is_finite() && self.expand.y { + bail!(self.flow.span(), "cannot expand into infinite height"); + } + let mut output = Frame::soft(size); let mut ruler = FixedAlignment::Start; let mut float_top_offset = Abs::zero(); @@ -653,7 +706,9 @@ impl<'a> FlowLayouter<'a> { if force && !self.pending_tags.is_empty() { let pos = Point::with_y(offset); output.push_multiple( - self.pending_tags.drain(..).map(|tag| (pos, FrameItem::Tag(tag))), + self.pending_tags + .drain(..) + .map(|tag| (pos, FrameItem::Tag(tag.clone()))), ); } @@ -665,62 +720,42 @@ impl<'a> FlowLayouter<'a> { // Try to place floats into the next region. for item in std::mem::take(&mut self.pending_floats) { - self.layout_item(engine, item)?; - } - - Ok(()) - } - - /// Lays out all floating elements before continuing with other content. - fn flush(&mut self, engine: &mut Engine) -> SourceResult<()> { - for item in std::mem::take(&mut self.pending_floats) { - self.layout_item(engine, item)?; - } - while !self.pending_floats.is_empty() { - self.finish_region(engine, false)?; + self.handle_item(item)?; } Ok(()) } /// Finish layouting and return the resulting fragment. - fn finish(mut self, engine: &mut Engine) -> SourceResult { + fn finish(mut self) -> SourceResult { if self.expand.y { while !self.regions.backlog.is_empty() { - self.finish_region(engine, true)?; + self.finish_region(true)?; } } - self.finish_region(engine, true)?; + self.finish_region(true)?; while !self.items.is_empty() { - self.finish_region(engine, true)?; + self.finish_region(true)?; } Ok(Fragment::frames(self.finished)) } -} -impl FlowLayouter<'_> { /// Tries to process all footnotes in the frame, placing them /// in the next region if they could not be placed in the current /// one. fn try_handle_footnotes( &mut self, - engine: &mut Engine, mut notes: Vec>, ) -> SourceResult<()> { // When we are already in_last, we can directly force the // footnotes. if self.root - && !self.handle_footnotes( - engine, - &mut notes, - false, - self.regions.in_last(), - )? + && !self.handle_footnotes(&mut notes, false, self.regions.in_last())? { - self.finish_region(engine, false)?; - self.handle_footnotes(engine, &mut notes, false, true)?; + self.finish_region(false)?; + self.handle_footnotes(&mut notes, false, true)?; } Ok(()) } @@ -731,7 +766,6 @@ impl FlowLayouter<'_> { /// regions. fn handle_footnotes( &mut self, - engine: &mut Engine, notes: &mut Vec>, movable: bool, force: bool, @@ -750,16 +784,16 @@ impl FlowLayouter<'_> { } if !self.has_footnotes { - self.layout_footnote_separator(engine)?; + self.layout_footnote_separator()?; } self.regions.size.y -= self.footnote_config.gap; let frames = FootnoteEntry::new(notes[k].clone()) .pack() .layout( - engine, + self.engine, Locator::synthesize(notes[k].location().unwrap()), - self.styles, + *self.styles, self.regions.with_root(false), )? .into_frames(); @@ -780,10 +814,10 @@ impl FlowLayouter<'_> { let prev = notes.len(); for (i, frame) in frames.into_iter().enumerate() { - find_footnotes(notes, &frame); + collect_footnotes(notes, &frame); if i > 0 { - self.finish_region(engine, false)?; - self.layout_footnote_separator(engine)?; + self.finish_region(false)?; + self.layout_footnote_separator()?; self.regions.size.y -= self.footnote_config.gap; } self.regions.size.y -= frame.height(); @@ -804,14 +838,14 @@ impl FlowLayouter<'_> { } /// Layout and save the footnote separator, typically a line. - fn layout_footnote_separator(&mut self, engine: &mut Engine) -> SourceResult<()> { + fn layout_footnote_separator(&mut self) -> SourceResult<()> { let expand = Axes::new(self.regions.expand.x, false); let pod = Regions::one(self.regions.base(), expand); let separator = &self.footnote_config.separator; // FIXME: Shouldn't use `root()` here. let mut frame = separator - .layout(engine, Locator::root(), self.styles, pod)? + .layout(self.engine, Locator::root(), *self.styles, pod)? .into_frame(); frame.size_mut().y += self.footnote_config.clearance; frame.translate(Point::with_y(self.footnote_config.clearance)); @@ -824,11 +858,11 @@ impl FlowLayouter<'_> { } } -/// Finds all footnotes in the frame. -fn find_footnotes(notes: &mut Vec>, frame: &Frame) { +/// Collect all footnotes in a frame. +fn collect_footnotes(notes: &mut Vec>, frame: &Frame) { for (_, item) in frame.items() { match item { - FrameItem::Group(group) => find_footnotes(notes, &group.frame), + FrameItem::Group(group) => collect_footnotes(notes, &group.frame), FrameItem::Tag(tag) if !notes.iter().any(|note| note.location() == tag.elem.location()) => { From 4c22ffa61230466f8141471ef72e74451a9a09a8 Mon Sep 17 00:00:00 2001 From: Adrian Freund Date: Sat, 6 Jul 2024 10:33:29 +0200 Subject: [PATCH 10/34] Statically link xz2 (#4463) --- crates/typst-cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/typst-cli/Cargo.toml b/crates/typst-cli/Cargo.toml index 9f90f430d..25bcabf0c 100644 --- a/crates/typst-cli/Cargo.toml +++ b/crates/typst-cli/Cargo.toml @@ -53,7 +53,7 @@ tar = { workspace = true } tempfile = { workspace = true } toml = { workspace = true } ureq = { workspace = true } -xz2 = { workspace = true, optional = true } +xz2 = { workspace = true, optional = true, features = ["static"] } zip = { workspace = true, optional = true } # Explicitly depend on OpenSSL if applicable, so that we can add the From 8c3002897a70ec8e06c4d2518549f6315e538184 Mon Sep 17 00:00:00 2001 From: Yip Coekjan <69834864+Coekjan@users.noreply.github.com> Date: Sat, 6 Jul 2024 16:36:24 +0800 Subject: [PATCH 11/34] Add metadata for `cargo-binstall` (#4458) --- crates/typst-cli/Cargo.toml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/typst-cli/Cargo.toml b/crates/typst-cli/Cargo.toml index 25bcabf0c..1bfd73585 100644 --- a/crates/typst-cli/Cargo.toml +++ b/crates/typst-cli/Cargo.toml @@ -85,3 +85,13 @@ vendor-openssl = ["openssl/vendored"] [lints] workspace = true + +# The following metadata is used by `cargo-binstall`, and should be synchronized +# with `.github/workflows/release.yml`. +[package.metadata.binstall] +pkg-url = "{ repo }/releases/download/v{ version }/typst-{ target }{ archive-suffix }" +bin-dir = "typst-{ target }/typst{ binary-ext }" +pkg-fmt = "txz" + +[package.metadata.binstall.overrides.x86_64-pc-windows-msvc] +pkg-fmt = "zip" From 82f13d9a38ae9b6486a4fe3c7503dc8d75bd39fa Mon Sep 17 00:00:00 2001 From: AnarchistHoneybun <74085528+AnarchistHoneybun@users.noreply.github.com> Date: Sat, 6 Jul 2024 15:10:12 +0530 Subject: [PATCH 12/34] Reword docs for `math.cancel` (#4444) --- crates/typst/src/math/cancel.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/typst/src/math/cancel.rs b/crates/typst/src/math/cancel.rs index 405b03513..ef07a1c8e 100644 --- a/crates/typst/src/math/cancel.rs +++ b/crates/typst/src/math/cancel.rs @@ -65,13 +65,13 @@ pub struct CancelElem { /// How much to rotate the cancel line. /// + /// - If given an angle, the line is rotated by that angle clockwise with + /// respect to the y-axis. /// - If `{auto}`, the line assumes the default angle; that is, along the - /// diagonal line of the content box. - /// - If given an angle, the line is rotated by that angle clockwise w.r.t - /// the y-axis. - /// - If given a function `angle => angle`, the line is rotated by the angle - /// returned by that function. The function receives the default angle as - /// its input. + /// rising diagonal of the content box. + /// - If given a function `angle => angle`, the line is rotated, with + /// respect to the y-axis, by the angle returned by that function. The + /// function receives the default angle as its input. /// /// ```example /// >>> #set page(width: 140pt) From 2df138a507860dc0cb610943ad6e608a33d03d7d Mon Sep 17 00:00:00 2001 From: Yip Coekjan <69834864+Coekjan@users.noreply.github.com> Date: Sat, 6 Jul 2024 17:54:12 +0800 Subject: [PATCH 13/34] Open with (detached) custom viewers and raise error on failure (#4430) --- crates/typst-cli/src/args.rs | 8 +++++--- crates/typst-cli/src/compile.rs | 24 ++++++++++++++++++++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/crates/typst-cli/src/args.rs b/crates/typst-cli/src/args.rs index 9648d8efa..ef12c48ac 100644 --- a/crates/typst-cli/src/args.rs +++ b/crates/typst-cli/src/args.rs @@ -100,9 +100,11 @@ pub struct CompileCommand { #[arg(long = "format", short = 'f')] pub format: Option, - /// Opens the output file using the default viewer after compilation. - /// Ignored if output is stdout - #[arg(long = "open")] + /// Opens the output file with the default viewer or a specific program after + /// compilation + /// + /// Ignored if output is stdout. + #[arg(long = "open", value_name = "VIEWER")] pub open: Option>, /// The PPI (pixels per inch) to use for PNG export diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index ae712a85e..4b87c3bd3 100644 --- a/crates/typst-cli/src/compile.rs +++ b/crates/typst-cli/src/compile.rs @@ -472,14 +472,30 @@ fn write_make_deps(world: &mut SystemWorld, command: &CompileCommand) -> StrResu /// Opens the given file using: /// - The default file viewer if `open` is `None`. /// - The given viewer provided by `open` if it is `Some`. +/// +/// If the file could not be opened, an error is returned. fn open_file(open: Option<&str>, path: &Path) -> StrResult<()> { + // Some resource openers require the path to be canonicalized. + let path = path + .canonicalize() + .map_err(|err| eco_format!("failed to canonicalize path ({err})"))?; if let Some(app) = open { - open::with_in_background(path, app); + open::with_detached(&path, app) + .map_err(|err| eco_format!("failed to open file with {} ({})", app, err)) } else { - open::that_in_background(path); + open::that_detached(&path).map_err(|err| { + let openers = open::commands(path) + .iter() + .map(|command| command.get_program().to_string_lossy()) + .collect::>() + .join(", "); + eco_format!( + "failed to open file with any of these resource openers: {} ({})", + openers, + err, + ) + }) } - - Ok(()) } /// Adds useful hints when the main source file couldn't be read From 394ba50fa612c36d7443094e21430aed6f533149 Mon Sep 17 00:00:00 2001 From: Yip Coekjan <69834864+Coekjan@users.noreply.github.com> Date: Sat, 6 Jul 2024 21:44:14 +0800 Subject: [PATCH 14/34] Rename `notes.*` to `note.*` & Add some music symbols (#4488) --- crates/typst/src/symbols/sym.rs | 46 ++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/crates/typst/src/symbols/sym.rs b/crates/typst/src/symbols/sym.rs index 1dec0039e..802b9a7a7 100644 --- a/crates/typst/src/symbols/sym.rs +++ b/crates/typst/src/symbols/sym.rs @@ -480,7 +480,6 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! { checkmark: ['✓', light: '🗸', heavy: '✔'], crossmark: ['✗', heavy: '✘'], floral: ['❦', l: '☙', r: '❧'], - notes: [up: '🎜', down: '🎝'], refmark: '※', servicemark: '℠', maltese: '✠', @@ -495,6 +494,51 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! { spade.stroked: '♤', ], + // Music. + note: [ + up: '🎜', + down: '🎝', + whole: '𝅝', + half: '𝅗𝅥', + quarter: '𝅘𝅥', + quarter.alt: '♩', + eighth: '𝅘𝅥𝅮', + eighth.alt: '♪', + eighth.beamed: '♫', + sixteenth: '𝅘𝅥𝅯', + sixteenth.beamed: '♬', + grace: '𝆕', + grace.slash: '𝆔', + ], + rest: [ + whole: '𝄻', + multiple: '𝄺', + multiple.measure: '𝄩', + half: '𝄼', + quarter: '𝄽', + eighth: '𝄾', + sixteenth: '𝄿', + ], + natural: [ + '♮', + t: '𝄮', + b: '𝄯', + ], + flat: [ + '♭', + t: '𝄬', + b: '𝄭', + double: '𝄫', + quarter: '𝄳', + ], + sharp: [ + '♯', + t: '𝄰', + b: '𝄱', + double: '𝄪', + quarter: '𝄲', + ], + // Shapes. bullet: '•', circle: [ From 59374f737079472aa5694b69ebced8c4cd5f9dfc Mon Sep 17 00:00:00 2001 From: Eric Biedert Date: Sat, 6 Jul 2024 15:45:19 +0200 Subject: [PATCH 15/34] Avoid spaces around ignorant and invisible elements in math (#4348) --- crates/typst/src/math/fragment.rs | 13 +++++++++++++ crates/typst/src/math/mod.rs | 13 +++++++++++-- crates/typst/src/math/row.rs | 13 ++++++++----- tests/ref/math-spacing-ignorant.png | Bin 0 -> 686 bytes tests/suite/math/spacing.typ | 10 ++++++++++ 5 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 tests/ref/math-spacing-ignorant.png diff --git a/crates/typst/src/math/fragment.rs b/crates/typst/src/math/fragment.rs index da4cb0adc..c6de24863 100644 --- a/crates/typst/src/math/fragment.rs +++ b/crates/typst/src/math/fragment.rs @@ -71,6 +71,13 @@ impl MathFragment { } } + pub fn is_ignorant(&self) -> bool { + match self { + Self::Frame(fragment) => fragment.ignorant, + _ => false, + } + } + pub fn class(&self) -> MathClass { match self { Self::Glyph(glyph) => glyph.class, @@ -441,6 +448,7 @@ pub struct FrameFragment { pub italics_correction: Abs, pub accent_attach: Abs, pub text_like: bool, + pub ignorant: bool, } impl FrameFragment { @@ -459,6 +467,7 @@ impl FrameFragment { italics_correction: Abs::zero(), accent_attach, text_like: false, + ignorant: false, } } @@ -489,6 +498,10 @@ impl FrameFragment { pub fn with_text_like(self, text_like: bool) -> Self { Self { text_like, ..self } } + + pub fn with_ignorant(self, ignorant: bool) -> Self { + Self { ignorant, ..self } + } } #[derive(Debug, Clone)] diff --git a/crates/typst/src/math/mod.rs b/crates/typst/src/math/mod.rs index dc79f48b4..04db9efb1 100644 --- a/crates/typst/src/math/mod.rs +++ b/crates/typst/src/math/mod.rs @@ -49,6 +49,7 @@ use crate::foundations::{ }; use crate::introspection::TagElem; use crate::layout::{BoxElem, Frame, FrameItem, HElem, Point, Size, Spacing, VAlignment}; +use crate::realize::Behaviour; use crate::realize::{process, BehavedBuilder}; use crate::text::{LinebreakElem, SpaceElem, TextElem}; @@ -299,7 +300,7 @@ impl LayoutMath for Content { if let Some(elem) = self.to_packed::() { let mut frame = Frame::soft(Size::zero()); frame.push(Point::zero(), FrameItem::Tag(elem.tag.clone())); - ctx.push(FrameFragment::new(ctx, styles, frame)); + ctx.push(FrameFragment::new(ctx, styles, frame).with_ignorant(true)); return Ok(()); } @@ -312,7 +313,15 @@ impl LayoutMath for Content { let axis = scaled!(ctx, styles, axis_height); frame.set_baseline(frame.height() / 2.0 + axis); } - ctx.push(FrameFragment::new(ctx, styles, frame).with_spaced(true)); + + ctx.push( + FrameFragment::new(ctx, styles, frame) + .with_spaced(true) + .with_ignorant(matches!( + self.behaviour(), + Behaviour::Invisible | Behaviour::Ignorant + )), + ); Ok(()) } diff --git a/crates/typst/src/math/row.rs b/crates/typst/src/math/row.rs index cb909b0bc..5234ca2cd 100644 --- a/crates/typst/src/math/row.rs +++ b/crates/typst/src/math/row.rs @@ -77,14 +77,17 @@ impl MathRun { fragment.set_class(MathClass::Binary); } - // Insert spacing between the last and this item. - if let Some(i) = last { - if let Some(s) = spacing(&resolved[i], space.take(), &fragment) { - resolved.insert(i + 1, s); + // Insert spacing between the last and this non-ignorant item. + if !fragment.is_ignorant() { + if let Some(i) = last { + if let Some(s) = spacing(&resolved[i], space.take(), &fragment) { + resolved.insert(i + 1, s); + } } + + last = Some(resolved.len()); } - last = Some(resolved.len()); resolved.push(fragment); } diff --git a/tests/ref/math-spacing-ignorant.png b/tests/ref/math-spacing-ignorant.png new file mode 100644 index 0000000000000000000000000000000000000000..6fead62e3ddd4b6d94806163e1449108ca7dc2e7 GIT binary patch literal 686 zcmV;f0#W^mP)00000a_RRq!CI(CPiUWyPO52Q3KOy{FS^Jh0j8cGa01vX;lCS6!gCLwNlMKP?NWkHu%Mq$h{6fls}&eYn2Ty-@X|t;Y4SA%ojhJp%0d z;!cNgJR3N;1o$jbW-0v3;fFnx5o{=qreL|E(TDK{@%y!#!w)gSR~F><9amqIS%DPX z$vo>q{%^bbmLhP$wGSxDF{}dq!X2&mJEHjjEsl?X6vjJqYruo)7DwB)V4ir*>`|A@$ZW?e)ul}Hj28>mAFwE5=2X8m{550WD UMs>5TMgRZ+07*qoM6N<$f?+H|K>z>% literal 0 HcmV?d00001 diff --git a/tests/suite/math/spacing.typ b/tests/suite/math/spacing.typ index 2a387f929..707c09bb8 100644 --- a/tests/suite/math/spacing.typ +++ b/tests/suite/math/spacing.typ @@ -49,6 +49,16 @@ $integral f(x) thin dif x$, // Both are weak, collide $integral f(x) #h(0.166em, weak: true)dif x$ +--- math-spacing-ignorant --- +// Test spacing with ignorant elements +$#metadata(none) "text"$ \ +$#place(dx: 5em)[Placed] "text"$ \ +// Operator spacing +$#counter("test").update(3) + b$ \ +$#place(dx: 5em)[a] + b$ +// Validate that ignorant elements are layouted +#context test(counter("test").get(), (3,)) + --- issue-1052-math-number-spacing --- // Test spacing after numbers in math. $ From 86af5b5f61d4cbfdf0c40a84784b5c890c8b9a45 Mon Sep 17 00:00:00 2001 From: T0mstone <39707032+T0mstone@users.noreply.github.com> Date: Mon, 8 Jul 2024 21:32:35 +0200 Subject: [PATCH 16/34] Allow non-utf8 values for `input` and `output` (#4517) --- crates/typst-cli/src/args.rs | 44 ++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/crates/typst-cli/src/args.rs b/crates/typst-cli/src/args.rs index ef12c48ac..09189de81 100644 --- a/crates/typst-cli/src/args.rs +++ b/crates/typst-cli/src/args.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use std::str::FromStr; use chrono::{DateTime, Utc}; -use clap::builder::ValueParser; +use clap::builder::{TypedValueParser, ValueParser}; use clap::{ArgAction, Args, ColorChoice, Parser, Subcommand, ValueEnum}; use semver::Version; @@ -77,7 +77,7 @@ pub struct CompileCommand { /// must be present if the source document renders to multiple pages. Use `{p}` for page /// numbers, `{0p}` for zero padded page numbers and `{t}` for page count. For example, /// `page-{0p}-of-{t}.png` creates `page-01-of-10.png`, `page-02-of-10.png` and so on. - #[clap(required_if_eq("input", "-"), value_parser = ValueParser::new(output_value_parser))] + #[clap(required_if_eq("input", "-"), value_parser = make_output_value_parser())] pub output: Option, /// Which pages to export. When unspecified, all document pages are exported. @@ -177,7 +177,7 @@ pub enum SerializationFormat { #[derive(Debug, Clone, Args)] pub struct SharedArgs { /// Path to input Typst file. Use `-` to read input from stdin - #[clap(value_parser = input_value_parser)] + #[clap(value_parser = make_input_value_parser())] pub input: Input, /// Configures the project root (for absolute paths) @@ -279,26 +279,30 @@ impl Display for Output { } /// The clap value parser used by `SharedArgs.input` -fn input_value_parser(value: &str) -> Result { - if value.is_empty() { - Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)) - } else if value == "-" { - Ok(Input::Stdin) - } else { - Ok(Input::Path(value.into())) - } +fn make_input_value_parser() -> impl TypedValueParser { + clap::builder::OsStringValueParser::new().try_map(|value| { + if value.is_empty() { + Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)) + } else if value == "-" { + Ok(Input::Stdin) + } else { + Ok(Input::Path(value.into())) + } + }) } /// The clap value parser used by `CompileCommand.output` -fn output_value_parser(value: &str) -> Result { - // Empty value also handled by clap for `Option` - if value.is_empty() { - Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)) - } else if value == "-" { - Ok(Output::Stdout) - } else { - Ok(Output::Path(value.into())) - } +fn make_output_value_parser() -> impl TypedValueParser { + clap::builder::OsStringValueParser::new().try_map(|value| { + // Empty value also handled by clap for `Option` + if value.is_empty() { + Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)) + } else if value == "-" { + Ok(Output::Stdout) + } else { + Ok(Output::Path(value.into())) + } + }) } /// Parses key/value pairs split by the first equal sign. From d1c7d08893ef293e74ac0763005e1dd3f46e6495 Mon Sep 17 00:00:00 2001 From: Leedehai <18319900+Leedehai@users.noreply.github.com> Date: Mon, 8 Jul 2024 15:32:58 -0400 Subject: [PATCH 17/34] Primes should not further raise next superscript's position (#4492) Co-authored-by: Ian Wrzesinski <133046678+wrzian@users.noreply.github.com> --- crates/typst-syntax/src/parser.rs | 7 +-- crates/typst/src/eval/math.rs | 6 ++- crates/typst/src/math/attach.rs | 50 ++++++++++++++------- tests/ref/math-primes-complex.png | Bin 1274 -> 1652 bytes tests/ref/math-primes-with-superscript.png | Bin 0 -> 956 bytes tests/suite/math/primes.typ | 15 ++++++- 6 files changed, 53 insertions(+), 25 deletions(-) create mode 100644 tests/ref/math-primes-with-superscript.png diff --git a/crates/typst-syntax/src/parser.rs b/crates/typst-syntax/src/parser.rs index 7e7eeea5f..ff01bacfd 100644 --- a/crates/typst-syntax/src/parser.rs +++ b/crates/typst-syntax/src/parser.rs @@ -393,11 +393,6 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { continue; } - // Separate primes and superscripts to different attachments. - if primed && p.current() == SyntaxKind::Hat { - p.wrap(m, SyntaxKind::MathAttach); - } - let Some((kind, stop, assoc, mut prec)) = math_op(p.current()) else { // No attachments, so we need to wrap primes as attachment. if primed { @@ -429,7 +424,7 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { math_expr_prec(p, prec, stop); math_unparen(p, m2); - if p.eat_if(SyntaxKind::Underscore) || (!primed && p.eat_if(SyntaxKind::Hat)) { + if p.eat_if(SyntaxKind::Underscore) || p.eat_if(SyntaxKind::Hat) { let m3 = p.marker(); math_expr_prec(p, prec, SyntaxKind::End); math_unparen(p, m3); diff --git a/crates/typst/src/eval/math.rs b/crates/typst/src/eval/math.rs index 0e5e0eef0..548c935dc 100644 --- a/crates/typst/src/eval/math.rs +++ b/crates/typst/src/eval/math.rs @@ -54,7 +54,11 @@ impl Eval for ast::MathAttach<'_> { if let Some(expr) = self.top() { elem.push_t(Some(expr.eval_display(vm)?)); - } else if let Some(primes) = self.primes() { + } + + // Always attach primes in scripts style (not limits style), + // i.e. at the top-right corner. + if let Some(primes) = self.primes() { elem.push_tr(Some(primes.eval(vm)?)); } diff --git a/crates/typst/src/math/attach.rs b/crates/typst/src/math/attach.rs index 7cc03bbab..8d85f8c41 100644 --- a/crates/typst/src/math/attach.rs +++ b/crates/typst/src/math/attach.rs @@ -52,31 +52,47 @@ pub struct AttachElem { impl LayoutMath for Packed { #[typst_macros::time(name = "math.attach", span = self.span())] fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> { - type GetAttachment = fn(&AttachElem, styles: StyleChain) -> Option; - - let layout_attachment = - |ctx: &mut MathContext, styles: StyleChain, getter: GetAttachment| { - getter(self, styles) - .map(|elem| ctx.layout_into_fragment(&elem, styles)) - .transpose() - }; - let base = ctx.layout_into_fragment(self.base(), styles)?; let sup_style = style_for_superscript(styles); - let tl = layout_attachment(ctx, styles.chain(&sup_style), AttachElem::tl)?; - let tr = layout_attachment(ctx, styles.chain(&sup_style), AttachElem::tr)?; - let t = layout_attachment(ctx, styles.chain(&sup_style), AttachElem::t)?; + let sup_style_chain = styles.chain(&sup_style); + let tl = self.tl(sup_style_chain); + let tr = self.tr(sup_style_chain); + let primed = tr.as_ref().is_some_and(|content| content.is::()); + let t = self.t(sup_style_chain); let sub_style = style_for_subscript(styles); - let bl = layout_attachment(ctx, styles.chain(&sub_style), AttachElem::bl)?; - let br = layout_attachment(ctx, styles.chain(&sub_style), AttachElem::br)?; - let b = layout_attachment(ctx, styles.chain(&sub_style), AttachElem::b)?; + let sub_style_chain = styles.chain(&sub_style); + let bl = self.bl(sub_style_chain); + let br = self.br(sub_style_chain); + let b = self.b(sub_style_chain); let limits = base.limits().active(styles); - let (t, tr) = if limits || tr.is_some() { (t, tr) } else { (None, t) }; + let (t, tr) = match (t, tr) { + (Some(t), Some(tr)) if primed && !limits => (None, Some(tr + t)), + (Some(t), None) if !limits => (None, Some(t)), + (t, tr) => (t, tr), + }; let (b, br) = if limits || br.is_some() { (b, br) } else { (None, b) }; - layout_attachments(ctx, styles, base, [tl, t, tr, bl, b, br]) + + macro_rules! layout { + ($content:ident, $style_chain:ident) => { + $content + .map(|elem| ctx.layout_into_fragment(&elem, $style_chain)) + .transpose() + }; + } + + let fragments = [ + layout!(tl, sup_style_chain)?, + layout!(t, sup_style_chain)?, + layout!(tr, sup_style_chain)?, + layout!(bl, sub_style_chain)?, + layout!(b, sub_style_chain)?, + layout!(br, sub_style_chain)?, + ]; + + layout_attachments(ctx, styles, base, fragments) } } diff --git a/tests/ref/math-primes-complex.png b/tests/ref/math-primes-complex.png index 5f5558eb214d68ffe35bf8455ffc4ea857c4540d..0e85d08d36563c74ed8a41070bdcc02f6289c71b 100644 GIT binary patch delta 1648 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 diff --git a/tests/ref/math-primes-with-superscript.png b/tests/ref/math-primes-with-superscript.png new file mode 100644 index 0000000000000000000000000000000000000000..88a892b9772223f3b18a8468ec09666ccc2c2ee9 GIT binary patch literal 956 zcmV;t14I0YP)`EWjb-@`xkTK;7x?LwDAKrw%!D}x_J?2JVvl4W?mOSXt2)aX%2DC!qH8Dc0- zjk#2W?7}}>1Oi_NQMCGizel*;oty?bIA_ryN+= zi(;`2;Q0i>Dv0htAsqnN?LshI>4w`!$S!d8fFN?drmenas|2)I~a!yF{q zac7B?feFA-U}1Rn1UQGr?|I_HO<=|h(BFZ~I|XznYmL{CO3U8_5Ql-~&jDtP5Q2SW zFaHH_L%Ay91xTuW<8AI0{%Rx8kN`Mv10dYIEj2k_TOq~z6e!`P+?u-XBp zYjW9R!zC6@BHI=s>6uyjC(vGc^I_HwA=~dDlkF`qAv2!=fq0hT=^~n<$X-efp%xRe zl`_&+pAe5PA$ui;%2NTm6OspFLN+lllvB8&`dieD#ojL{M5Cwr7$$!wduC9Zs@jF+ z${wMUqV-(D&&E*;ydHy~0ga-tuvP(LsfgNpH)E7ey$_)ZatL=l?LZ|`_o|DPHaBXQ z9=Fl+2V(nt5EaKpGvl`AFyZ5~@f^aoPtiDZ8EI?zcGTu}fgj!}-V#7jCCAuP4t3-S|qcxG-!u_?&s2% Date: Mon, 8 Jul 2024 22:41:38 +0200 Subject: [PATCH 18/34] Bump dependencies (#4523) --- .github/workflows/ci.yml | 4 +- Cargo.lock | 855 ++++++++++-------- Cargo.toml | 14 +- crates/typst-cli/Cargo.toml | 2 +- crates/typst/src/foundations/plugin.rs | 12 +- crates/typst/src/visualize/image/raster.rs | 4 +- tests/ref/baseline-box.png | Bin 3908 -> 3909 bytes .../closure-path-resolve-in-layout-phase.png | Bin 2179 -> 2178 bytes tests/ref/eval-path-resolve-in-show-rule.png | Bin 4379 -> 4376 bytes tests/ref/eval-path-resolve.png | Bin 4379 -> 4376 bytes tests/ref/footnote-in-table.png | Bin 12304 -> 12311 bytes tests/ref/hide-image.png | Bin 8838 -> 8864 bytes tests/ref/image-baseline-with-box.png | Bin 6375 -> 6379 bytes tests/ref/image-decode-detect-format.png | Bin 10628 -> 10648 bytes tests/ref/image-decode-specify-format.png | Bin 10628 -> 10648 bytes tests/ref/image-fit.png | Bin 10287 -> 10302 bytes tests/ref/image-rgba-png-and-jpeg.png | Bin 18076 -> 18075 bytes tests/ref/image-sizing.png | Bin 8625 -> 8662 bytes tests/ref/pad-followed-by-content.png | Bin 11851 -> 11853 bytes tests/ref/place-background.png | Bin 19865 -> 19859 bytes tests/ref/place-basic.png | Bin 11811 -> 11668 bytes tests/ref/place-float-figure.png | Bin 35400 -> 35339 bytes tests/ref/ref-supplements.png | Bin 8267 -> 8268 bytes tests/ref/transform-rotate-and-scale.png | Bin 7901 -> 7900 bytes tests/ref/transform-rotate-origin.png | Bin 4753 -> 4756 bytes 25 files changed, 493 insertions(+), 398 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7c6214ff..3dca52497 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@1.74.0 + - uses: dtolnay/rust-toolchain@1.77.0 - uses: Swatinem/rust-cache@v2 - run: cargo check --workspace @@ -64,7 +64,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: nightly-2023-09-13 + toolchain: nightly-2024-06-01 - uses: Swatinem/rust-cache@v2 - run: cargo install --locked cargo-fuzz@0.12.0 - run: cd tests/fuzz && cargo fuzz build --dev diff --git a/Cargo.lock b/Cargo.lock index 14dd36f11..9f9fd4c50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,10 +9,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "aho-corasick" -version = "1.1.2" +name = "ahash" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -34,47 +46,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -94,6 +107,9 @@ name = "arbitrary" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "arrayref" @@ -109,9 +125,9 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "az" @@ -121,15 +137,9 @@ checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" [[package]] name = "base64" -version = "0.21.7" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "biblatex" @@ -176,9 +186,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" dependencies = [ "serde", ] @@ -197,15 +207,21 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" -version = "1.14.3" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" [[package]] name = "byteorder" @@ -215,12 +231,13 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.0.90" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "066fce287b1d4eafef758e89e09d724a24808a9196fe9756b8ca90e86d0719a2" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -249,14 +266,14 @@ checksum = "7588475145507237ded760e52bf2f1085495245502033756d28ea72ade0e498b" [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets 0.52.4", + "windows-targets 0.52.6", ] [[package]] @@ -292,15 +309,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d259fe9fd78ffa05a119581d20fddb50bfba428311057b12741ffb9015123d0b" dependencies = [ - "quick-xml", + "quick-xml 0.31.0", "serde", ] [[package]] name = "clap" -version = "4.5.2" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" +checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" dependencies = [ "clap_builder", "clap_derive", @@ -308,9 +325,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" dependencies = [ "anstream", "anstyle", @@ -320,18 +337,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.1" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" +checksum = "1d598e88f6874d4b888ed40c71efbcbf4076f1dfbae128a08a8c9e45f710605d" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ "heck", "proc-macro2", @@ -341,15 +358,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "clap_mangen" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1dd95b5ebb5c1c54581dd6346f3ed6a79a3eef95dd372fc2ac13d535535300e" +checksum = "f50dde5bc0c853d6248de457e5eb6e5a674a54b93810a34ded88d882ca1fe2de" dependencies = [ "clap", "roff", @@ -379,9 +396,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "comemo" @@ -433,18 +450,18 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] @@ -470,9 +487,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -516,6 +533,17 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dirs" version = "5.0.1" @@ -539,9 +567,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", @@ -550,24 +578,24 @@ dependencies = [ [[package]] name = "downcast-rs" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "ecow" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba31a30727c42ff5e60468d695c7f21e43a6db2808b7195adcab908fbd9f794" +checksum = "54bfbb1708988623190a6c4dbedaeaf0f53c20c6395abd6a01feb327b3146f4b" dependencies = [ "serde", ] [[package]] name = "either" -version = "1.10.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "embedded-io" @@ -613,9 +641,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -648,9 +676,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fdeflate" @@ -669,15 +697,15 @@ checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "windows-sys 0.52.0", ] [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -774,9 +802,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -795,9 +823,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if", "crunchy", @@ -811,9 +839,12 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] [[package]] name = "hayagriva" @@ -824,11 +855,11 @@ dependencies = [ "biblatex", "ciborium", "citationberg", - "indexmap 2.2.5", + "indexmap 2.2.6", "numerals", "paste", "serde", - "serde_yaml 0.9.32", + "serde_yaml 0.9.34+deprecated", "thiserror", "unic-langid", "unicode-segmentation", @@ -838,9 +869,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hypher" @@ -873,9 +904,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "137d96353afc8544d437e8a99eceb10ab291352699573b0de5b08bda38c78c60" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", "serde", @@ -886,9 +917,9 @@ dependencies = [ [[package]] name = "icu_locid" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c0aa2536adc14c07e2a521e95512b75ed8ef832f0fdf9299d4a0a45d2be2a9d" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", @@ -899,9 +930,9 @@ dependencies = [ [[package]] name = "icu_locid_transform" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c17d8f6524fdca4471101dd71f0a132eb6382b5d6d7f2970441cb25f6f435a" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ "displaydoc", "icu_locid", @@ -913,15 +944,15 @@ dependencies = [ [[package]] name = "icu_locid_transform_data" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545c6c3e8bf9580e2dafee8de6f9ec14826aaf359787789c7724f1f85f47d3dc" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" [[package]] name = "icu_properties" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976e296217453af983efa25f287a4c1da04b9a63bf1ed63719455068e4453eb5" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", "icu_collections", @@ -935,15 +966,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6a86c0e384532b06b6c104814f9c1b13bcd5b64409001c0d05713a1f3529d99" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" [[package]] name = "icu_provider" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba58e782287eb6950247abbf11719f83f5d4e4a5c1f2cd490d30a334bc47c2f4" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", "icu_locid", @@ -960,9 +991,9 @@ dependencies = [ [[package]] name = "icu_provider_adapters" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a229f978260da7c3aabb68cb7dc7316589936680570fe55e50fdd3f97711a4dd" +checksum = "d6324dfd08348a8e0374a447ebd334044d766b1839bb8d5ccf2482a99a77c0bc" dependencies = [ "icu_locid", "icu_locid_transform", @@ -973,9 +1004,9 @@ dependencies = [ [[package]] name = "icu_provider_blob" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a7202cddda672db167c6352719959e9b01cb1ca576d32fa79103f61b5a73601" +checksum = "c24b98d1365f55d78186c205817631a4acf08d7a45bdf5dc9dcf9c5d54dccf51" dependencies = [ "icu_provider", "postcard", @@ -987,9 +1018,9 @@ dependencies = [ [[package]] name = "icu_provider_macros" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2abdd3a62551e8337af119c5899e600ca0c88ec8f23a46c60ba216c803dcf1a" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", @@ -998,9 +1029,9 @@ dependencies = [ [[package]] name = "icu_segmenter" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2dc1e8f4ba33a6a4956770ac5c08570f255d6605519fb3a859a0c0a270a2f8f" +checksum = "a717725612346ffc2d7b42c94b820db6908048f39434504cb130e8b46256b0de" dependencies = [ "core_maths", "displaydoc", @@ -1015,9 +1046,9 @@ dependencies = [ [[package]] name = "icu_segmenter_data" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3673d6698dcffce08cfe8fc5da3c11c3f2c663d5d6137fd58ab2cbf44235ab46" +checksum = "f739ee737260d955e330bc83fdeaaf1631f7fb7ed218761d3c04bb13bb7d79df" [[package]] name = "idna" @@ -1035,21 +1066,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" -[[package]] -name = "image" -version = "0.24.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "gif", - "jpeg-decoder", - "num-traits", - "png", -] - [[package]] name = "image" version = "0.25.1" @@ -1084,12 +1100,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "rayon", "serde", ] @@ -1122,9 +1138,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] @@ -1149,16 +1165,22 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.10" +name = "is_terminal_polyfill" +version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] @@ -1217,12 +1239,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" version = "0.2.155" @@ -1231,18 +1247,18 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libdeflate-sys" -version = "1.19.3" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9caa76c8cc6ee8c4efcf8f4514a812ebcad3aa7d3b548efe4d26da1203f177" +checksum = "669ea17f9257bcb48c09c7ee4bef3957777504acffac557263e20c11001977bc" dependencies = [ "cc", ] [[package]] name = "libdeflater" -version = "1.19.3" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "265a985bd31e5f22e2b2ac107cbed44c6ccf40ae236e46963cd00dd213e4bd03" +checksum = "8dfd6424f7010ee0a3416f1d796d0450e3ad3ac237a237644f728277c4ded016" dependencies = [ "libdeflate-sys", ] @@ -1266,22 +1282,12 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "libc", - "redox_syscall", -] - -[[package]] -name = "line-wrap" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" -dependencies = [ - "safemem", ] [[package]] @@ -1292,15 +1298,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lipsum" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c5e9ef2d2ad6fe67a59ace27c203c8d3a71d195532ee82e3bbe0d5f9a9ca541" +checksum = "636860251af8963cc40f6b4baadee105f02e21b28131d76eba8e40ce84ab8064" dependencies = [ "rand", "rand_chacha", @@ -1308,28 +1314,34 @@ dependencies = [ [[package]] name = "litemap" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d642685b028806386b2b6e75685faadd3eb65a85fff7df711ce18446a422da" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" dependencies = [ "serde", ] [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] -name = "log" -version = "0.4.21" +name = "lockfree-object-pool" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lzma-sys" @@ -1344,9 +1356,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" @@ -1359,9 +1371,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", "simd-adler32", @@ -1379,6 +1391,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "multi-stash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685a9ac4b61f4e728e1d2c6a7844609c16527aeb5e6c865915c08e619c16410f" + [[package]] name = "mutate_once" version = "0.1.1" @@ -1387,11 +1405,10 @@ checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -1409,7 +1426,7 @@ version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "crossbeam-channel", "filetime", "fsevent-sys", @@ -1424,11 +1441,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -1439,6 +1455,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -1450,9 +1477,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1471,9 +1498,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "open" -version = "5.1.1" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b3fbb0d52bf0cbb5225ba3d2c303aa136031d43abff98284332a9981ecddec" +checksum = "9d2c909a3fce3bd80efef4cd1c6c056bd9376a8fe06fcfdbebaf32cb485a7e37" dependencies = [ "is-wsl", "libc", @@ -1486,7 +1513,7 @@ version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -1514,18 +1541,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.2.3+3.2.1" +version = "300.3.1+3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" +checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.101" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -1542,14 +1569,16 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "oxipng" -version = "9.0.0" +version = "9.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28e5c341ef78a228e47a551bfd15ff885d8c501af49f953358763a538c01f14d" +checksum = "3f398c53eb34e0cf71d9e0bc676cfa7c611e3844dd14ab05e92fb7b423c98ecf" dependencies = [ "bitvec", + "clap", + "clap_mangen", "crossbeam-channel", "filetime", - "indexmap 2.2.5", + "indexmap 2.2.6", "libdeflater", "log", "rayon", @@ -1561,9 +1590,9 @@ dependencies = [ [[package]] name = "palette" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebfc23a4b76642983d57e4ad00bb4504eb30a8ce3c70f4aee1f725610e36d97a" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" dependencies = [ "approx", "fast-srgb8", @@ -1573,10 +1602,11 @@ dependencies = [ [[package]] name = "palette_derive" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8890702dbec0bad9116041ae586f84805b13eecd1d8b1df27c29998a9969d6d" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" dependencies = [ + "by_address", "proc-macro2", "quote", "syn", @@ -1584,9 +1614,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1594,22 +1624,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.2", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pathdiff" @@ -1623,7 +1653,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af6a7882fda7808481d43c51cadfc3ec934c6af72612a1fe6985ce329a2f0469" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "itoa", "memchr", "ryu", @@ -1700,14 +1730,13 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plist" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef" +checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ - "base64 0.21.7", - "indexmap 2.2.5", - "line-wrap", - "quick-xml", + "base64", + "indexmap 2.2.6", + "quick-xml 0.32.0", "serde", "time", ] @@ -1756,9 +1785,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -1778,7 +1807,7 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "getopts", "memchr", "unicase", @@ -1801,10 +1830,19 @@ dependencies = [ ] [[package]] -name = "quote" -version = "1.0.35" +name = "quick-xml" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1842,9 +1880,9 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "rayon" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -1870,10 +1908,19 @@ dependencies = [ ] [[package]] -name = "redox_users" -version = "0.4.4" +name = "redox_syscall" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", "libredox", @@ -1882,9 +1929,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", @@ -1894,9 +1941,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -1905,9 +1952,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "resvg" @@ -1927,9 +1974,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.37" +version = "0.8.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +checksum = "1aee83dc281d5a3200d37b299acd13b81066ea126a7f16f0eae70fc9aed241d9" dependencies = [ "bytemuck", ] @@ -1969,11 +2016,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -1982,17 +2029,17 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "rustybuzz" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7730060ad401b0d1807c904ea56735288af101430aa0d2ab8358b789f5f37002" +checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "bytemuck", "smallvec", "ttf-parser", @@ -2004,15 +2051,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" - -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -2040,11 +2081,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -2053,9 +2094,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -2074,24 +2115,24 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", @@ -2100,9 +2141,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -2111,9 +2152,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -2132,11 +2173,11 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.32" +version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -2187,9 +2228,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "spin" @@ -2226,25 +2267,36 @@ dependencies = [ ] [[package]] -name = "strsim" -version = "0.11.0" +name = "string-interner" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "1c6a0d765f5807e98a091107bae0a56ea3799f66a5de47b2c84c94a39c09974e" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "serde", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.26.1" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.26.1" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck", "proc-macro2", @@ -2264,7 +2316,7 @@ version = "0.11.0" source = "git+https://github.com/typst/svg2pdf?rev=39f8ad3#39f8ad3b35e14cfcabf3d5d916899f7ac78790f7" dependencies = [ "fontdb", - "image 0.25.1", + "image", "log", "miniz_oxide", "once_cell", @@ -2290,9 +2342,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" dependencies = [ "proc-macro2", "quote", @@ -2340,9 +2392,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" dependencies = [ "filetime", "libc", @@ -2356,7 +2408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", - "fastrand 2.0.1", + "fastrand 2.1.0", "rustix", "windows-sys 0.52.0", ] @@ -2378,18 +2430,18 @@ checksum = "a38c90d48152c236a3ab59271da4f4ae63d678c5d7ad6b7714d7cb9760be5e4b" [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", @@ -2455,9 +2507,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c02bf3c538ab32ba913408224323915f4ef9a6d61c0e85d493f355921c0ece" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "serde", @@ -2466,9 +2518,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -2481,9 +2533,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" -version = "0.8.10" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" dependencies = [ "serde", "serde_spanned", @@ -2493,20 +2545,20 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.6" +version = "0.22.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +checksum = "d59a3a72298453f564e2b111fa896f8d07fabb36f51f06d7e875fc5e0b5a3ef1" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", @@ -2521,9 +2573,9 @@ checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" [[package]] name = "two-face" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37bed2135b2459c7eefba72c906d374697eb15949c205f2f124e3636a46b5eeb" +checksum = "0ccd4843ea031c609fe9c16cae00e9657bad8a9f735a3cc2e420955d802b4268" dependencies = [ "once_cell", "serde", @@ -2541,7 +2593,7 @@ name = "typst" version = "0.11.0" dependencies = [ "az", - "bitflags 2.4.2", + "bitflags 2.6.0", "chinese-number", "ciborium", "comemo", @@ -2557,8 +2609,8 @@ dependencies = [ "icu_provider_blob", "icu_segmenter", "if_chain", - "image 0.24.9", - "indexmap 2.2.5", + "image", + "indexmap 2.2.6", "kamadak-exif", "kurbo", "lipsum", @@ -2575,7 +2627,7 @@ dependencies = [ "rustybuzz", "serde", "serde_json", - "serde_yaml 0.9.32", + "serde_yaml 0.9.34+deprecated", "siphasher 1.0.1", "smallvec", "stacker", @@ -2635,7 +2687,7 @@ dependencies = [ "semver", "serde", "serde_json", - "serde_yaml 0.9.32", + "serde_yaml 0.9.34+deprecated", "shell-escape", "tar", "tempfile", @@ -2669,7 +2721,7 @@ dependencies = [ "pulldown-cmark", "serde", "serde_json", - "serde_yaml 0.9.32", + "serde_yaml 0.9.34+deprecated", "syntect", "typed-arena", "typst", @@ -2722,12 +2774,12 @@ dependencies = [ name = "typst-pdf" version = "0.11.0" dependencies = [ - "base64 0.22.0", + "base64", "bytemuck", "comemo", "ecow", - "image 0.24.9", - "indexmap 2.2.5", + "image", + "indexmap 2.2.6", "miniz_oxide", "once_cell", "pdf-writer", @@ -2749,7 +2801,7 @@ version = "0.11.0" dependencies = [ "bytemuck", "comemo", - "image 0.24.9", + "image", "pixglyph", "resvg", "roxmltree 0.20.0", @@ -2765,7 +2817,7 @@ dependencies = [ name = "typst-svg" version = "0.11.0" dependencies = [ - "base64 0.22.0", + "base64", "comemo", "ecow", "flate2", @@ -2839,18 +2891,18 @@ dependencies = [ [[package]] name = "unic-langid" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "238722e6d794ed130f91f4ea33e01fcff4f188d92337a21297892521c72df516" +checksum = "23dd9d1e72a73b25e07123a80776aae3e7b0ec461ef94f9151eed6ec88005a44" dependencies = [ "unic-langid-impl", ] [[package]] name = "unic-langid-impl" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd55a2063fdea4ef1f8633243a7b0524cbeef1905ae04c31a1c9b9775c55bc6" +checksum = "0a5422c1f65949306c99240b81de9f3f15929f5a8bfe05bb44b034cc8bf593e5" dependencies = [ "serde", "tinystr", @@ -2930,15 +2982,15 @@ checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unsafe-libyaml" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "unscanny" @@ -2948,11 +3000,11 @@ checksum = "e9df2af067a7953e9c3831320f35c1cc0600c30d44d9f7a12b01db1cd88d6b47" [[package]] name = "ureq" -version = "2.9.6" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35" +checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea" dependencies = [ - "base64 0.21.7", + "base64", "flate2", "log", "native-tls", @@ -2964,9 +3016,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -2980,7 +3032,7 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b84ea542ae85c715f07b082438a4231c3760539d902e11d093847a0b22963032" dependencies = [ - "base64 0.22.0", + "base64", "data-url", "flate2", "fontdb", @@ -3009,9 +3061,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "vcpkg" @@ -3097,28 +3149,37 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasmi" -version = "0.31.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8281d1d660cdf54c76a3efa9ddd0c270cada1383a995db3ccb43d166456c7" +checksum = "dae81666d9dc76cb125fe16fa3e2f0fe08d15cd7b2ad9a5013cadf31826edee5" dependencies = [ + "arrayvec", + "multi-stash", + "num-derive", + "num-traits", "smallvec", "spin", - "wasmi_arena", + "wasmi_collections", "wasmi_core", "wasmparser-nostd", ] [[package]] -name = "wasmi_arena" -version = "0.4.1" +name = "wasmi_collections" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" +checksum = "b422568fe52d13bc889033799e548c43032ee71836c6e9bb7261c1fc039327b9" +dependencies = [ + "ahash", + "hashbrown 0.14.5", + "string-interner", +] [[package]] name = "wasmi_core" -version = "0.13.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" +checksum = "5fa2efecce566705221bc05f518b124ceb7d94b78f4e9035cb0572d9f369982e" dependencies = [ "downcast-rs", "libm", @@ -3128,9 +3189,9 @@ dependencies = [ [[package]] name = "wasmparser-nostd" -version = "0.100.1" +version = "0.100.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9157cab83003221bfd385833ab587a039f5d6fa7304854042ba358a3b09e0724" +checksum = "d5a015fe95f3504a94bb1462c717aae75253e39b9dd6c3fb1062c934535c64aa" dependencies = [ "indexmap-nostd", ] @@ -3159,11 +3220,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -3178,7 +3239,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.6", ] [[package]] @@ -3196,7 +3257,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.6", ] [[package]] @@ -3216,17 +3277,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -3237,9 +3299,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -3249,9 +3311,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -3261,9 +3323,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -3273,9 +3341,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -3285,9 +3353,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -3297,9 +3365,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -3309,24 +3377,24 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.5" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" dependencies = [ "memchr", ] [[package]] name = "writeable" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad7bb64b8ef9c0aa27b6da38b452b0ee9fd82beaf276a87dd796fb55cbae14e" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "wyz" @@ -3396,9 +3464,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e71b2e4f287f467794c671e2b8f8a5f3716b3c829079a1c44740148eff07e4" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" dependencies = [ "serde", "stable_deref_trait", @@ -3408,9 +3476,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e6936f0cce458098a201c245a11bef556c6a0181129c7034d10d76d1ec3a2b8" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", @@ -3419,19 +3487,39 @@ dependencies = [ ] [[package]] -name = "zerofrom" -version = "0.1.3" +name = "zerocopy" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655b0814c5c0b19ade497851070c640773304939a6c0fd5f5fb43da0696d05b7" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6a647510471d372f2e6c2e6b7219e44d8c574d24fdc11c610a61455782f18c3" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", @@ -3441,9 +3529,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0594125a0574fb93059c92c588ab209cc036a23d1baeb3410fa9181bea551a0" +checksum = "fb594dd55d87335c5f60177cee24f19457a5ec10a065e0a3014722ad252d0a1f" dependencies = [ "displaydoc", "litemap", @@ -3453,9 +3541,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.10.1" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff4439ae91fb5c72b8abc12f3f2dbf51bd27e6eadb9f8a5bc8898dddb0e27ea" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "serde", "yoke", @@ -3465,9 +3553,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4e5997cbf58990550ef1f0e5124a05e47e1ebd33a84af25739be6031a62c20" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", @@ -3476,26 +3564,33 @@ dependencies = [ [[package]] name = "zip" -version = "0.6.6" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +checksum = "775a2b471036342aa69bc5a602bc889cb0a06cda00477d0c69566757d5553d39" dependencies = [ - "byteorder", + "arbitrary", "crc32fast", "crossbeam-utils", + "displaydoc", "flate2", + "indexmap 2.2.6", + "memchr", + "thiserror", + "zopfli", ] [[package]] name = "zopfli" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1f48f3508a3a3f2faee01629564400bc12260f6214a056d06a3aaaa6ef0736" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" dependencies = [ + "bumpalo", "crc32fast", + "lockfree-object-pool", "log", + "once_cell", "simd-adler32", - "typed-arena", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1b5bf0f4d..e26f058ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "2" [workspace.package] version = "0.11.0" -rust-version = "1.74" # also change in ci.yml +rust-version = "1.77" # also change in ci.yml authors = ["The Typst Project Developers"] edition = "2021" homepage = "https://typst.app" @@ -49,7 +49,7 @@ flate2 = "1" fontdb = { version = "0.18", default-features = false } fs_extra = "1.3" hayagriva = "0.5.3" -heck = "0.4" +heck = "0.5" hypher = "0.1.4" icu_properties = { version = "1.4", features = ["serde"] } icu_provider = { version = "1.4", features = ["sync"] } @@ -57,7 +57,7 @@ icu_provider_adapters = "1.4" icu_provider_blob = "1.4" icu_segmenter = { version = "1.4", features = ["serde"] } if_chain = "1" -image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] } +image = { version = "0.25", default-features = false, features = ["png", "jpeg", "gif"] } indexmap = { version = "2", features = ["serde"] } kamadak-exif = "0.5" kurbo = "0.11" @@ -109,7 +109,7 @@ time = { version = "0.3.20", features = ["formatting", "macros", "parsing"] } tiny-skia = "0.11" toml = { version = "0.8", default-features = false, features = ["parse", "display"] } ttf-parser = "0.21.0" -two-face = { version = "0.3.0", default-features = false, features = ["syntect-fancy"] } +two-face = { version = "0.4.0", default-features = false, features = ["syntect-fancy"] } typed-arena = "2" unicode-bidi = "0.3.13" unicode-ident = "1.0" @@ -121,13 +121,13 @@ unscanny = "0.1" ureq = { version = "2", default-features = false, features = ["native-tls", "gzip", "json"] } usvg = { version = "0.42", default-features = false, features = ["text"] } walkdir = "2" -wasmi = "0.31.0" +wasmi = "0.34.0" xmlparser = "0.13.5" xmlwriter = "0.1.0" xmp-writer = "0.2" -xz2 = "0.1" +xz2 = { version = "0.1", features = ["static"] } yaml-front-matter = "0.1" -zip = { version = "0.6", default-features = false, features = ["deflate"] } +zip = { version = "2", default-features = false, features = ["deflate"] } [profile.dev.package."*"] opt-level = 2 diff --git a/crates/typst-cli/Cargo.toml b/crates/typst-cli/Cargo.toml index 1bfd73585..7ada123cb 100644 --- a/crates/typst-cli/Cargo.toml +++ b/crates/typst-cli/Cargo.toml @@ -53,7 +53,7 @@ tar = { workspace = true } tempfile = { workspace = true } toml = { workspace = true } ureq = { workspace = true } -xz2 = { workspace = true, optional = true, features = ["static"] } +xz2 = { workspace = true, optional = true } zip = { workspace = true, optional = true } # Explicitly depend on OpenSSL if applicable, so that we can add the diff --git a/crates/typst/src/foundations/plugin.rs b/crates/typst/src/foundations/plugin.rs index 80644ea53..57dea827e 100644 --- a/crates/typst/src/foundations/plugin.rs +++ b/crates/typst/src/foundations/plugin.rs @@ -234,12 +234,12 @@ impl Plugin { let ty = func.ty(store.as_context()); // Check function signature. - if ty.params().iter().any(|&v| v != wasmi::core::ValueType::I32) { + if ty.params().iter().any(|&v| v != wasmi::core::ValType::I32) { bail!( "plugin function `{name}` has a parameter that is not a 32-bit integer" ); } - if ty.results() != [wasmi::core::ValueType::I32] { + if ty.results() != [wasmi::core::ValType::I32] { bail!("plugin function `{name}` does not return exactly one 32-bit integer"); } @@ -257,14 +257,14 @@ impl Plugin { // Collect the lengths of the argument buffers. let lengths = args .iter() - .map(|a| wasmi::Value::I32(a.len() as i32)) + .map(|a| wasmi::Val::I32(a.len() as i32)) .collect::>(); // Store the input data. store.data_mut().args = args; // Call the function. - let mut code = wasmi::Value::I32(-1); + let mut code = wasmi::Val::I32(-1); func.call(store.as_context_mut(), &lengths, std::slice::from_mut(&mut code)) .map_err(|err| eco_format!("plugin panicked: {err}"))?; if let Some(MemoryError { offset, length, write }) = @@ -281,8 +281,8 @@ impl Plugin { // Parse the functions return value. match code { - wasmi::Value::I32(0) => {} - wasmi::Value::I32(1) => match std::str::from_utf8(&output) { + wasmi::Val::I32(0) => {} + wasmi::Val::I32(1) => match std::str::from_utf8(&output) { Ok(message) => bail!("plugin errored with: {message}"), Err(_) => { bail!("plugin errored, but did not return a valid error message") diff --git a/crates/typst/src/visualize/image/raster.rs b/crates/typst/src/visualize/image/raster.rs index 995e34837..ff15432b6 100644 --- a/crates/typst/src/visualize/image/raster.rs +++ b/crates/typst/src/visualize/image/raster.rs @@ -30,11 +30,11 @@ impl RasterImage { /// Decode a raster image. #[comemo::memoize] pub fn new(data: Bytes, format: RasterFormat) -> StrResult { - fn decode_with<'a, T: ImageDecoder<'a>>( + fn decode_with( decoder: ImageResult, ) -> ImageResult<(image::DynamicImage, Option>)> { let mut decoder = decoder?; - let icc = decoder.icc_profile().filter(|icc| !icc.is_empty()); + let icc = decoder.icc_profile().ok().flatten().filter(|icc| !icc.is_empty()); decoder.set_limits(Limits::default())?; let dynamic = image::DynamicImage::from_decoder(decoder)?; Ok((dynamic, icc)) diff --git a/tests/ref/baseline-box.png b/tests/ref/baseline-box.png index 8d7627c60918a2addde0d9ead7bffd6bd23a7516..b85d0715d1b657550dafd61ddc823b7ef730636d 100644 GIT binary patch literal 3909 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 Tvxm|#00000NkvXXu0mjf+ZC#+ literal 3908 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 S@qRb}0000jPCW>^ML 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 delta 2166 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 SWZH=U0000Hqm+Wne*1Bk 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 diff --git a/tests/ref/eval-path-resolve.png b/tests/ref/eval-path-resolve.png index cf5c183ad8d3f9ba2173d21afaf2f4c3b0a2aed8..4d695d91a9780ac0212b9fa0f5e7d79a05a341c3 100644 GIT binary patch literal 4376 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 SWZH=U0000Hqm+Wne*1Bk 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 diff --git a/tests/ref/footnote-in-table.png b/tests/ref/footnote-in-table.png index 0fd0acc7f01562fcf1cc679d319e3aa0c02b9e3c..3f8f50ca1027d75a74d7e7d2040dd9abae710710 100644 GIT binary patch delta 9842 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

a2i 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 Vr?Lc6E5N)DMOihOYALgz{{h9<^~?YO delta 9835 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 NSzbe~Ue+?~e*iLu#JB(e diff --git a/tests/ref/hide-image.png b/tests/ref/hide-image.png index 78bc690c8efc6827b60b485c676e2b773c7eb853..36dbf6a818205ffff07d59f93eb7be825c35faaa 100644 GIT binary patch delta 8376 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 F{twUVZMOgb delta 8350 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 diff --git a/tests/ref/image-baseline-with-box.png b/tests/ref/image-baseline-with-box.png index 41128069820bb5eb61278d3e32e00ec5622c714c..492570d0171440b2eeea7b02a5a85f23389e3736 100644 GIT binary patch literal 6379 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 literal 6375 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 diff --git a/tests/ref/image-decode-detect-format.png b/tests/ref/image-decode-detect-format.png index 6f12e8b474b974e648bd9f0ee4ec7c532977d0bc..6ecb7dcdaeed44fe450d98c7f2efcefdd98f47ab 100644 GIT binary patch literal 10648 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 yJ?$yCY>%qdpx_GcG%b=(IzYlv# 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 yJ?$yCY>%qdpx_GcG%b=(IzYlv# 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 edkX#kMgJG!$&FJ8#$KiX0000P)001ZjNkl0F& zj*WVr2mVUzIdm68Mis9n*v%uu&!HZHBWftRFdFP-h#-M6mR&~?4}xjKFiDhyAR2;2 z%drZTSsKlbPVQ|;jq%BWRZII$j*oYyb2O#3q`TW=7gn_CgU6nMVQG2i>KL7XFb;wU zQ&AW7d=~tbK*3)RP1$8>0VtG82YcW)qAr$a>L;4AMxkZGgAFw=SPcw@NP>W1-E-oW z%_DfTDNQa+zl%WNMIEa(Lme-?5G43qa#6D}#!=~dy;>KZ$Y$4vgXxKx!thwlX<=1o z2a1JFnOW2W{wIn)G`O!Nz=n{;9Kp9FB0)ZojE6A{NODuskCT8bi+f3&b8AfuWlWoI zjc>*bh=%#W(ckbKO%pA;QYG-1W2-c7pdl)oSdp&}3yv{W*++3?Jjzrgu{pla)sL~^ z9Pnb`|HbI&$Sb>Eo;ZF;)%5Nq%e#7)t>19LTV6Cw_BmL<)VMtZzoocJ1l>rJaTTe; zh|Ta!L&(Qyz_OVzcTJ;t7>Cj@O0ZOt_i~E%3WkJ8IDpZLpbrvowjpb^=4?&a+f)@D zB5}+TVbij-SiBu_by?sg!(x#*__GWgIrOi0+`hauoa9Is#^G z7Cn#;1}T9mR)O!mpuF{9yhE!gM+)kRz!hoj+6! zc1ws1F!TxQ@mL@VAv8s>1V+`o7fnNRfK-!99szUJs%egK!3;r=JPD&Qu2?yUp-6jf zH9-WMqF1Poz?2FjqpnHf4zZ#Wydm3u|C<9lcYOU_{eEnkhOTLTfa3&7QD3@#<+q-E z_22LM{Oxys{jDncy;rs#AA>5hCKm`*$Euz}UiU#3MMZS+4Auo@U~zR)IXog99Atu^}%K#T&XPR%=`OmxJ^0 z!Oh(q0Trh66Lmd6BL3xXPBl#eh2ZIYESdb^=B~G}=z6_5oF9WQJP=HKsI_rPpf+{% zh;VYYCM$+RAzFfS4I729;8?zLV9;3J(f+B9C9^X#h*oHCBKRaCi!1dSOaOuVV{=>h2@~#c*lF^7Cf8pEr zHmAxP;NmwQ{`%I(A6d12)AjFoC&RFBCDDMYMTn5)Y7PV^GM&M-;Zs%Zz|m8q1pz_f z8!uQuK;E6dJh84TLUT+(w8TPv{f5PhR`*gQ_k^B*-<1nOM9hR5Lz4~9jF_&$1~gqk z6bn>qA`0Uu4yu@g;ymK1EQKLR0G!PiKlAtRTGMu2XU{_~ow)JsZ=191^ZskE0`q+C z6CeNBm%iK`)xZ9(HC5So;+e-sM@H`a%GcgXqHWC4ZI8erNTj1VRv3Bi#PG<}OkMiu zd#@gvR<`cjiNX*?;&sW4@b=nun@zQ>&K%qJ

    e{DNB=+`MAN;jyhK+PLE4Es72h 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
  1. *0(e4;uyus>g;>D&4hJw#O5wK8e9ws?!xHdd+`7~7MU(s7|V00$c!VfU`*1E(R(vxZ!m!nfbrl-1F78c19-i zUbq-xIgkIWL)|md*{Dbsnjk!Q-Cxq@dnCV*7*i872tYI!!YWmya^=21qquWm!<=d> zpPy=sqRP?1DJmsjebd37A^bcce{AOOwavP_R(}XLPx8Xaj38QsW1|&9cv>0>3RjVi z+Rqw&1A_r{a4-9E*ni^i^yCZk*!ac$$P1PG7|pu~$z6}HCG0si*H&g{4SR{TF{;K%YU2+3$%!0UJ^M} zl-G`zkcG;~VVbXPL-xLvFcLzSn_+97L{xh#O6_S((e2G6CS5K%+E$x)s0AzyCTiM7 zSGPJ{^CeAw38jp-@q+(<(Ay1hpVZt8xspwB5HOD$s7R4Lp9~DH!l27v;iiETuuA(8!5}JRH9li9kpnZ`qOS1Iw-*BTGakdG(QOS}X zU9tTIJ3r8{{X`m7q2QQ7#RU?P`3DI>Jn^+KT!9aZ!?EP?UmrGOOl~3f)jEi43@{*>!;mOr4^cA^f~+bp9{@(M_U8*gx(n`$k~0_8eP- z@dy)1bBv{Dw)_`4HoT6r-KYO$(hE3P;0{E?x zJhpq&BDL*dSv7+N>m4|<`P6gfX&Ndh?#POtn>lH(Myq_|?Sg>@<3&25gCWy7dzs(y z=fH|3>S1i`xPAzCTAv|VK==Boa&*SAEsPM+c0u3o^Yy<*uqh>-vRFzXJSqxWjd@x* zU!89%RSzQuZZCyfbyj*GUIkh~ zRdhSprN8-?!KtFgGlmo4Ai`;JV4wLqth~?m8!L@H9&W_uvS28Ci6PQj?l=4|jxll0 zsW3BtDq|v4l9^sy%tRy!oq+>|E0JPJki;|+TDj97hsK4t)@b)Wxx2w+#w4mw5dVL= zgC~_H#oItfG5@3+p&Lb}ZVxA#EEfV1$E9lq z;oPlv^QIIx6sFY;MQ*j)vM@4b8I&e@)XPVVvA66&(;oc2Z+-csm~gAyqImE*ZV?4? zJn$z}UJRj7?lGV+9=JkiY?>yb*yDc(ccsKkL^YO6MT&uHwb^urKfb^F2k+md6>b`_ zBWF~%huL)MnWYjPH9!yk$!Kv7^QfT@k9yy)hy&YweNJC2X7;Txb2;$X6?1}L4?;r9 zR5eLtDg!jrrAXS;DC3&W*cyy_j+Zi^7^F5CWrX(b_T%;QYpaZRo>@JeN~VACLZ2Bk z$C^vWCbktfL`6?@oAr9__(7#TYlvDZ3CoFTQ5q{(XA-ZUFDbG{TUb-nT+CR2hvo zxGdvLF*O>4Or!Zs9O*Ka%Dl2xI_@#FJuDc~a5BENS6$2K%YyyfjIyzNcx|PmnK6_} zdRyeJFZS!S>uP?KDsv^F4D3GB)4Tm4qPa85`ASU%iKPb0I9f{{r}jznx8bHATEVvp{U>@5-p*A)zS}}rd*n) zNlj|vsXf`F|AhVpCK_W&gHE|y(Zb{AIZ z&vG#)a?yh!I|qOAcyD%Ivh&`&otfY6)KL!_YQJo3;l>+oR^og!5K3z`5zC$+9*l@$ zAiNX&&34?WQxzooh_8pILa4sQ_R>s7;1C$k^8%kd_=DHsf{`RA2<>exH8n;q2V2bp z@1`V4oksimh5g##NO@8Hzt$4Uog(cG6hbP?15Sc2_y&JCw0>^>K)$!B*~+o$6irjN zZ4o9{$V=IrLS1D@a{1UHZ#C*^Ui8H>7PI+{y`@T#tFI)hNhMMls={$-m*zwXwii1a z6FqBhQ;_5~n{U(;FxRF^Qb3sqF{3^fPmzP{A%Hd=O{ORH0BCbnHzfF%d+;S<2Pgbr zk}}3K`NMy$??)QFnW}l(YOU1}2=l(YYcW&$R!UQ$*6c9Z27|Uxp$crq;UEFuwY{6F z)+%8wA6w0JI&CV2+##*eDH~134O%#27=$B`wbv<5@Ht@w2C$59e(Q~<`;85^Ow^;c zrhCoS1SkEXGY^~bSVj<9)vM=MphCH#wpVkElv&guH>|60x}wApOL}zE`x&HnAeHaEv8yXslL?VBEeSJk0uB@!g%*@2&akR0{rl+SdJ0vVG zFOQ6j6gzPtRw46`y>odn58R6jIAltFBvQj7%069NDPvha?VLqR?TCEz5hU(GL z5kx6WOiU1;7}nR<7Yc=Px!mUFW~EZm>-Fd7=k3!PjYdHTFH@Im9p7vSR2)|vop8bO<{I+wpOb#4AKIl zx3||~v6#)~cs%|`gii&ly|}p0>2wDN2UuHs7Yf1TAVfb}2q|FqCX;D&bX0$I4x5wl zqE)=ZjA>a$AP}HBHa6CBr1I)!g#G>f?FooDHxeAoE*K0-J3Gz}%6>71L_>xlJv}`H zwx@|7zD_I{ak*TNB3vusl)Y{VL>&%CB9Wjh2Iu$t(IoMf7@WN0z$pM5Q0uD*1oK%*+fK4G4F4cX5DZs@!Z4%r-VQKths&ZE3aIsi~=O zILt@zcr;#LU#HV)sv`L0_4RdfUBVTP5e;Z{G0EE6TDe@NjN#7B&9!Ge$H&Kb!mX_> z65~>-w7k4bOEenY+1Wvd78e)!2woH)KzPn%GBi$3PR3#}jCE;g3DSS`LLIJHBpGS> z=P!A^yWfTT`})6>%= zz+@uWkUE`tmVk|%zFMtz_{k4`O{uJewWV|8;u6n=v?VNXAxZA$OR$yz)Z#E_}>pU6}A^Z z$kT4GG+LYe>u>h%SS79?3gdXnfQ>I;Cs>G$g_ZaKqGBUhh_zs0r-h`#w1WZDb zg%pv1!Upykw%AA5avMpVA0Akib-`qHQ^PMCnSWB*fZiG#*wHMC1yu5FpYa_Y4R>e*XUD+m8x%4=y8>#ry;x1X_h^ z#l)3GClFXU8SvOoL7;Z*FcnmI%{*@AE!AJuLxQ8EFhHj5(H$4K0J1_{ABITPnr+ zs84e*x<)5yVGxdE)LJO|Gf>=I*vy}%wNfZiGt`R1!o(xc>{^Av=OwO&qobJ*-FPH!_m86k zFWz2SU!0$xKPAX#ktr|9Ca<<1_J5o;0rNjL6|(5wU4Bxwigv#Ws1_C$xILx0%+Air z=_PvSKeHKMUtg~}_JR;fWI@jGou8lQD4&{|8tg}2wdQMUYrYfaSd(of(*)S0e(Pu5G=xpK1r}FGXsn~7?bD9$_nqAjP1H4H(IWm zDO5i~6OhXSP;g7FcyR_&*C?ZgDsYcG!}%hAPmA-5XOQq2y+X9FcyTdAPmCTANI~| zr>Q6i!>{5S@e00x@m`<9YZATi65=Up)EZBy@jyA1qd=k9mex&esfB{$xAr6}O)!5U zHWoT7^QT#zuHCEsW!B6(J3?1V2sz^3->G4QlexGz#9ymnFVu5IP?lw|EaWoY{afGCuT0)M@lTzU!l2vs_3`tJAncSF?TLrm2V;mv7>ETBWMObxG3zZYX(ENW>acYhJ!8Ae|Nyy=ggTb$o$&*EbUlKDFN=s9isO!0S zq)ur)y8#~gClpnSd-7pZ&XM(WMxjcIdK-gZk_gWZu%*p-A~zN4PXZ@G~^PM%n@)WyXI`wMk^OUb9nW zDK_NsLgq_)4!vbahRmB_F8N zj!_CZCwy_ePc)Wmm13I0wMU&1y&lnOqdE*!*qc(z(k;(Sg^s&VSjM(0FjL`hv7aP- z(}cxPg}n(Y)NkHQg@e!Z!6qQpPuwz6Op4gIBm;qr1mJ(;9lS~v{zZj+_NzZ9AfQl< z!L|AfF7f6TE~U1ON3tYT2%{6V83L~GN41+O3|^r%-~Szh`WUvtZVpRI8SY0ch;{Dg zf04CP5iYSR3|Qg#3-zXSvS+9W#7>M17~50L;|-bGhQL45d9eeIg-i23<>ACyGI-vkJtK6 zGx)2d&_UXT6dJje+_Ky-g^o+l2fzM8kz`7hAuqxW`mqYJ+{KMSKL+q3kVU*QVktz$ z91%cy_zIC=BgVTz)@F>tON+ek1OVmp{Bl__zz?S?PWU`L| zSS!lWL!p3=U#r!)Z8RDja4RuByDCD**R~vfyKD7A*T7TeM6`rl|(xSDeslE?p>_}_R-t=cG%O4yL|Mf?WJbCP+y5j6Vbw2;6wNgsZvs6th!s6s^*DymQwDymR1;z6*k>)!l?W1K*^2!+jN z^P49$^bwQ~(QdaRkw~djDi({WRLb+bbUICc%zQp?Hk2eIvsit*L8z> z+}C6>nNFv4iq&d091acJkw&A@-EPOhU@(86BW&At9EZ*%lgVtiTeg_ZX74+LRVWm4 zxg3Q>KA&&3T6BtPwOXlE3{x13#k$>Yxm>m^t6s0i<8dPT!QPp(v=&5hy!8{<*^1V# zxPXX?3ko7CXk{aUl`Af2p@O(zAy{ZoaK)`Lkc1>vAVqevkQCV zz%BCL2QYA_xSW}LX8oV@KV#QGv)N{5X1>3_Iq++1YfC1RUteD% zBO`or!h~He7jxd;-lh?y%~GkvlM`^NSF2T)ucp(>%S)ae9UUNzz;)e&gM)W>ceAsz zG_I)0cq*03+}zy${{H#-`RM2<_k4eI&*H_!1uo7l(zmy_TU%R8OH1feg4)Bw!|v{` z95~P*e*E+EGd@0!8m415o9SYGeLa)O;Bd{&&7Ys2G`O*`K^1Hc9@ncEXHvfRYHN=H_N+XD9X!KcfZ@E3XQLLNFKvc;wdB zRvd-B4~5b%PeQMvOfO_*U@Wv$Ct)Ir8d*#*91af*4B(5eudiYXU_*7tK-3x<8W=pW z0B@e3pQoHgs7O0>3Zs8844$arln_$g>_|-}6Gr#;_9j`#hb*2)yuoaVa4ZCj3ZbE+ z*Xs!+yyZ(T)T~ykI2=>^Ll^1vLnTwz?&?iMS(!> zbUIOBn*$poK*Asj!{g|6|EE2{Us*(1g5Q}I7Z-g#pZe>}e-d`erk@W3vD06>A`l}G z9v>g85UNRqP)#a?DuilMAym^JcJ5v^u^oLgP^ona=zM#Ko^aEHbiI%>P! zKGP1%icLdcU^p5NBa~N;^tbJHJJP6z=%&I@I0>_ia-DwxS65eQAPp(KBzWzMiwl3H z2${PGa>yt$FR>m-fzR-f4Nx~T33CLK_>^R}-|xxGltrqw ztISR^I9Gq?_++*1c1yT6J{vx}{l$f?RtwanfhD9|BOC7T?^UYD$H&G$G6tQ-bHwz^%S%9*%SB+)>s>TrY%my2P{0Gd$&3~&WaN8bW!Asun3(d zX#%%NZu6tLw0T03d3OwlLtV_VIrH3ZHP9pmzrVk0123F9+?!Y_8#}wTzIU)XsR(E| z8*8BK>3UIV^!t6s0FR{p#N?S|LU#M_L-&8?=H}tyA>X>bzE075d3nJXWqiqrx3{-W zr}HOx9^Hf%%$7`^yf2eyKk`XFMcdxm+eqRUX%*M(MIdH)v527V^5vKv^*unY{e`{Kd{4 z<0cG4VI02%69WSWK;kYe%*?Q|X35qgbnY#3iyWvw@kv&cMi7-&&|(i&5*)|&d(ZLv z=e@oO{uJS#WkZD)xVDao+dvBpKwSs1+?#6q4EDQrw>L=lf052vm^EXFF~Q?p-fg&r~IXH208pS%i3g7^UB4qPjs60Z#I9Mwxf)=0=8PJ2y3Y#d_@sL z754Yp=*PMKPeq0XPxK*a0=&=nOX0klWbQSmg%|KERtSZFFAk8TY@MIEU?s`+n_2jR zb5berze&6O4$vQ3o{Owo`UbPo9y3v>z0wGIWqzxazuL2q*!jVVIIYjm3X|4?tmp1)$fY zQ=u7>;`BjHr#hcBD85JE_sC+phdCs{YE+O1-FA?Bp<~UaK?A=_goeT3C=nGAFl zHYy@?1w>5-D|AkZz|F^{_y}s$rW^F}EVDuU$K%mmPB)v4Qx&vEmCX#GgZQB2CJn*C zs^}Sc4|yUMr+@TEU`NdJ$m~saYYYom4-PBSQQz{Fi@{>Uv%<#2-dXm(eK) z{_5@s`F9M&l^zE~0Y zKZz)Xa0P!gK-+(2QPbA=5g`?!8D$QfvoFDrxnN z27n|(BK7gO0eGmY@u##h^>GTks|aC+RuZTfK=MR{44g2kI1%y~48mV44Dbu;|KrCG zN&rYqgg7qIQ^T5wSM2VHFm2^{V}W1dp~%<=Q^m)5*0O&_V;oPlV1gI6>yz zO5RGV0WoVt2&-$c!i$^D{@oq8m=jht^J>k+pkPf(d+JVF3%D=;s9++b)`{?ZqdDk~ zMwXV1&#%CH5TWgWS_}_l)C`U%;n0Zc!-o&fd!Wv)urquiLee5y48lhZZBfl#ixniq zy|}1{ycvI|C;#i$ua6rIW%ee<_a;IHZK;ydz@62oKZ@d1mBWR8|NcG6u|jT>Kfo8+ zVAGTUD$RyeL3=DxR(@oWK`o^z4Q=x($jxY&#YNS*t`Nfq39%O-X{NW)idnw#7K}CN!d8-1NM511cj&UTIqeq-SFWp5v zcjc~QVV$PyMyP|nnAtuy}Xo~G(Pw5eF zKd*mItT_(UYdNR;y3m`^iHGpsppYY;rwxxZwKqid#&vgM5RSEwxDvuy%Y^v;oY5KU zFFY;lxqDF*Q&A}Gz6LUUXpew|1=yBWgi9;J72(Z63E-$pHQlK^rith8&n6h1p!UcV z#^lZ5DTxrjiekD5fsI6;o?kb!gl=4_NhW`GS9dC~`3+Mm0#OsaG*UDZFMnUWc;U!D z2^fUAj%kfINfWh9^H_+x$5`gD?9!%6VIuOlSWq@l#>{L&4(Uqip-yg_z-?($F^O7L zA}68z{%W>amxRfdS**JP?@~nVaot4I)Vso6tOO8$Eg3QoHpeLN5SUP$bhW&D_s)ML zvJmx-aATKl$c4_&>y@X9OEvO!G;HSDL#bLb;yfXaL5SY zV+z3q2#8Q!xBLZsPz4xAVfIiMoQ1G>3KK>175@(wmjIXGCYQ(XV5$s7e zz?)Efg2xu;x9PYOP2B}XLU2s{VI6=ib7h!5z)Z(IC2ouqUi z-HuHL1j84!27~<7N%4~s(8XX5JUWgLY|EUz1%z+kzUdWJ=sd4ZdDQbtpv8Z|o0LHL zkW$DN*|cvV5Hif2tpK_dK3F0okCT9Saiq9rnF0U?>w@yIBm*R%6;?g~EVXdAmASTC zfYv&h5)LOpFjS2h9dQ{V6vQ1a3S&FsnPk=hs){wCoa>a>f(ADrfA0{Q`bg2-dgX{_ z%8O=~MN!@bO-FN`XY2PExlDiRn8kvz28+o6s3@x1Ep~8H&irlhk1?G@UW1+Tne$4; zyt>uCi)s1Ww{M}f$X~Jg2(n5Z$JQ$0*x-IAHkI~hAw>aPRII;%QyR>Lo_9Q|gp=z9 zzUqG5W89DANVT4$fM~E;SVCIBYm0v@vds=7Jpue1 zMU05#$C`(`SK;yA0}?WgbJ78>({SB>v>}jTJd11d2i(d zn)wFBYM9*`N{|N;n?*Ra8*~{8Gh)G7qAxr!o$v@7wcmwJL$vvPHR_tGq0d74havEK zVo*L-D90GAv$$c{L#}`82qqY1tvh1ERK)Xt{`{F%DVY!FqVdy`*@HXMFkC&P>_!o- zG-l|t1j$KDU}7DB7yD&o`XF&!QqkMCDEMs!Kad(>y53?|jueA>2T@26C8ltLC$ZQy zNdx9p5XU8IBq~j@f}8ORqt%6>Z9h|H?RU#BURScF5~Mt;RxE#wWI)HN<#a`LIO9R> zRFp);#*i4*b*fxNYF7g3YQr2R{(PqPZB&r>dx*v}(`O<=o$+?ii^HNV3$U#SmsW&J zE5bh^@3&sl&o_*jAr!=R?_`&nz%>9xfhoTt^IC7DE5hGB*4~Tz`HaY{@!sn)sSv#{ zA(TzD)$Ni7d|!W9gqmC6a{)`uvEU}XNt$(;|MezmYX7TeCn(5dZg8?#Y$7-zRmbCO z&T}?RG}$_@#Y`&^mZwK*v(Glo1hi&X{xnz@^r(Inzr?UyoT&bj7^JX0V4?2IId$QK zr)q$bRfjE}WrdeqsZf1UhXR~TjUjt3f~SFvnh4~zkpO>!3iSq;06b0a=2_iwn$5e45Y>_If2>$9XKS^1_uQa@>gI|%<}6NpLtw2fb{?e zARzvdjaYJ(YGLqD6jB2^7jmu^=yL&&A`L;c5M62nwWkL)KzAgdCmH}HmOhd;D4YrM zrGg?|0S|xq3kU@R=?idGQyHknz`if}{X*x+=yM7i2AwgHs6-qoE<5|^dZg#Tq@D#| z!AMhov8F|h7Htmnf}w{66E;Z{PpmY>wq>N%D?;tNR=~5Vg-&!N2rQ5lVjK>(p@KeT zh2h$;b!do-{@Ov{S5&c)vXV&!&Qi&sQL?FD#1Ma~GBgN#N1y=HnVQH|3s%PZxz5v41){Rhf70v{=SQ;eiojteYiGsnT;iX#L9_sA z(Z7G`+By7vfzzKr(pI)OG)^WiGc4mX#|5QiMsQwS!$*qguQx*g<2)-E*+vkHe;k*b z9-*ohVn{Ubx15}nLB(p60J(TA0iOMPx;NK7>fgJ!`3F18NDDZ3% z%6_pXmx!%$5=M^HtAWLVc6r^B6XEqVW{5t?j8nbeYmd_M{9tY^3|3Z1@&9smkvM-9 z&ba+byH(mar#m`p<$)?lgyK$(uy5H^q`*pG#1=7dU@E24?ZK0Qx;*De1u#TL{H7{YYX=rY4XPj=iZ3dY^KgvRS5^t|Q)Wq`fhqgFwD#G6A805<}oL1?-L z<$=6|*DU~O-p`*uJNp_=1jGS(Uy%q0`6WW21|&M@8I;^%NBACE9eC};0ldKC&Wa$y zKw;UAV2Zf+VxL_HaESlJH4=Z4#vm*!Cj>drL>aXJd3w zJF!9GWmX-i#nA&tL)Q_=5$v1NkU^Rut`PP_HKqGzyDAh;{1nJ7q3a+dtN<}W@-T-X zYP01C#us#6P`SFUPK4kI?8CTDAfcWO0C31e*dm5d*Q0|<-n!ix?I?fziNzG$ZczsM z+R{YSspKO73c;i<{?a6kkr|f>ICmGZU?yvfuvTTA zWR`7}<6Mf_x8-z-qw}X;0_*{{(m23Bsm@;_qzX>9C?}X#!>(m#lelqGq<~s4ZSg_J z_$~mW-~1R9Sc_%TF>HV36=6|y-wGVG*F|iS=NO{jUqCjusl#Eda959zH)=rjpz?vS z6<#?8JheOL8bpbM#ACG&`-r(cQ?$*qsz%;5{0h^SnSl^H+a(C(562@{%yOMF1X# z#{k~rg6`?I^$N}hEB$IWsM_83u(_iDn8;{4n??o~=OC?4Y%$8>+C;t(r^h`tB@>v0 z>abfm_(+Xhp+gdUDN---Thz3xhAmp>D-S6;)kT7Fz^UmI&slX5Icc7f1HM_3h0nlf z?bH}OZK21kc=6qzn44o&G-mv$qf%%Q}nBEUgHaR)kA;m{eSn3Oc%(y!{&LyerNI;6EUfp zt|I(bzL?dugZCElCh1-L##>J0^&Ss}K?R5X0f~_FqHlj%@;jN$earbcWa4WAb2kgP z3k7}MN@N4Wyh{}T(jvI;LO|5tJZc>d+-LqChk&}^x(9TmP+f?&{sX8oCLRfL4WI&8 z&{pw%P+ik)yW`=U!ne)JK>^TSL7V2(W`W-OLEa}N!_@#EfH(Hx7gj#EKmjJvs^}D$ z$gCL*E1`c(Cy;1{W0|>D^FsKtnKdv^y^Ix3OCsPzB(Z8Xw9ERp6|P0-pi-PzfRHK^1?N0zxlCF*;oYW{({Z4M-!11UTXp zg8=apGBv~nLdrylv+Kdx5WCnnRycJD{--x{9*7kWcGDbRtCZQQ2;Q-Nm>ID9+zNFE zVh6w0R1goSOnzuPR%(ed`-U)-Rk(ij>bR$RytBXIw=jx;VqvJE{C-7HhLuxjsxWH+ z0}Ou&T2~#bAb7K6z|aG}kpi)54VMiSgq+zn(8?K&i>*nt)}!t+O+xiD@+%QC5ys^3 zD4FLgoRM0r3{V9xGUp<+dT_+hWlMz6ZOveU{8D;AjTozsfu4(yQEH(DBhms`w^|e> z!s_jiok*l4lz*zO*C}*|B2No}f}M)X99w@)>w3iB99O;`LxfmU$Gmh@=gk|wQj%!)uK#lp+L9##3dw}p^#I1l(JxkZtX=Q-GxvSPr-l0 zeL0hTz+HJDomFOMt0xaHXkWg3X~n82Na>j2ki^$4F#sZMyCFPdbPOKD5hh8sc%GXv}=uqkugVW1^m+SHHVMN zXJS1nMmrGMAjj(V&ABdP>9mb=_S|FnG(K z_#>`cb+wiTs}9WjP$FbE7VRYNPC!w;lDIG8$#-7=?;_;QxFEKFZS7_;*3f^Ank&Ku z*j9u~E5fCViO^dzh{2quiT3qV@6TRe|1J^2`1|VBIhp!+$rOQNT6SXwp>IjLGYbDJ znH6+U;C)ee3J7Mc@c)rO-rvb)7FEpd#i>ac%;oP|F9hkr)g(j5(tq6%3y z#|KD~r3^4nn0OgXRLy6@eC2;M3pTAb+m*+a`KlO9&g_^GgE7GZz^daa4~PUZXIc$Y z=Kbga)C%+k%~GZbtAIh60|3RZ{N~GM(DHOnwY|!pk3GdvS1{fu#bBZ5-h(%y}>Kc7JycNK|dU|1jt5v$PzYP#5WAgErX+Bdq*GGh` zHWA`oA_Uz5yWwi=DJ_2~LOdF63I+^MZr~YpWVkI*xSU z)}ijAj~_o0|Fd$W(CSvy@Ve7T{7Oq;hu=OQcw+`^b=OxTys|=d9mRr`Z9?E$J1H7I zi8>(0Z;8m=$|;+Kipo|+it1o5e0MO7-!Nq^TK=9bv%qb*IrM*$2i6jK90x-cdes8b zqAInD6y!^3_*nk-$vaXp(J(eIhc+=W5mK>?XT_3da~-m53&dn#rZV`2;kx2Fd*$oc zWhee~uXA|++=*Vt+8!Vn;{9;Fb{24+I_xR{Tf+CLy!INW*t z6BZ$(F&286ToEq7wv!QxACugPRFm9^9~kr`=pQ1au>*+}JRSf5002ovPDHLkV1k@@ BymbHo delta 29232 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 KMNUMnLSTZNmi&tV literal 8267 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 A9smFU diff --git a/tests/ref/transform-rotate-and-scale.png b/tests/ref/transform-rotate-and-scale.png index 0dcf67ed2e729e95bd4fd746c7936fb0996bd79c..2487d4b50bc0ae96b214a0bc0c2c1de57f8794c5 100644 GIT binary patch literal 7900 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 Hu0mjfH{cdK diff --git a/tests/ref/transform-rotate-origin.png b/tests/ref/transform-rotate-origin.png index 152b1e1f823e9241e571b733aadc56f7a9529c26..5292d300ce6d7af695aa483f4bf564c257d984a5 100644 GIT binary patch literal 4756 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 Date: Tue, 9 Jul 2024 17:22:08 +0200 Subject: [PATCH 19/34] Fix suggestions for '.' after code mode has exited. (#4470) --- crates/typst-ide/src/complete.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index cdcac956d..90c8b4a1f 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -334,6 +334,13 @@ fn math_completions(ctx: &mut CompletionContext) { /// Complete field accesses. fn complete_field_accesses(ctx: &mut CompletionContext) -> bool { + // Used to determine whether trivia nodes are allowed before '.'. + // During an inline expression in markup mode trivia nodes exit the inline expression. + let in_markup: bool = matches!( + ctx.leaf.parent_kind(), + None | Some(SyntaxKind::Markup) | Some(SyntaxKind::Ref) + ); + // Behind an expression plus dot: "emoji.|". if_chain! { if ctx.leaf.kind() == SyntaxKind::Dot @@ -341,6 +348,7 @@ fn complete_field_accesses(ctx: &mut CompletionContext) -> bool { && ctx.leaf.text() == "."); if ctx.leaf.range().end == ctx.cursor; if let Some(prev) = ctx.leaf.prev_sibling(); + if !in_markup || prev.range().end == ctx.leaf.range().start; if prev.is::(); if prev.parent_kind() != Some(SyntaxKind::Markup) || prev.prev_sibling_kind() == Some(SyntaxKind::Hash); @@ -1433,6 +1441,16 @@ mod tests { test("#().", 4, &["insert", "remove", "len", "all"], &["foo"]); } + #[test] + fn test_whitespace_in_autocomplete() { + //Check that extra space before '.' is handled correctly. + test("#() .", 5, &[], &["insert", "remove", "len", "all"]); + test("#{() .}", 6, &["insert", "remove", "len", "all"], &["foo"]); + + test("#() .a", 6, &[], &["insert", "remove", "len", "all"]); + test("#{() .a}", 7, &["at", "any", "all"], &["foo"]); + } + #[test] fn test_before_window_char_boundary() { // Check that the `before_window` doesn't slice into invalid byte From 79b77d566973eb698a0e2f9afb64ed728c459963 Mon Sep 17 00:00:00 2001 From: Leedehai <18319900+Leedehai@users.noreply.github.com> Date: Tue, 9 Jul 2024 11:23:44 -0400 Subject: [PATCH 20/34] Mark synthesized prime symbol frame text-like (#4525) --- crates/typst/src/math/attach.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/typst/src/math/attach.rs b/crates/typst/src/math/attach.rs index 8d85f8c41..0e19d1277 100644 --- a/crates/typst/src/math/attach.rs +++ b/crates/typst/src/math/attach.rs @@ -143,7 +143,7 @@ impl LayoutMath for Packed { prime.clone(), ) } - ctx.push(FrameFragment::new(ctx, styles, frame)); + ctx.push(FrameFragment::new(ctx, styles, frame).with_text_like(true)); } } Ok(()) From 46ab4edea63e38685a57e37cfc665b8b2ef05400 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Tue, 9 Jul 2024 23:46:38 +0800 Subject: [PATCH 21/34] Basic Definition Finder for IDE (#4309) Co-authored-by: Laurenz --- crates/typst-ide/src/complete.rs | 66 +----- crates/typst-ide/src/definition.rs | 264 ++++++++++++++++++++++++ crates/typst-ide/src/lib.rs | 6 +- crates/typst-ide/src/matchers.rs | 266 +++++++++++++++++++++++++ crates/typst-ide/src/tooltip.rs | 7 +- crates/typst/src/eval/mod.rs | 2 +- crates/typst/src/foundations/module.rs | 18 +- 7 files changed, 565 insertions(+), 64 deletions(-) create mode 100644 crates/typst-ide/src/definition.rs create mode 100644 crates/typst-ide/src/matchers.rs diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index 90c8b4a1f..c4f86d04a 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -17,8 +17,10 @@ use typst::visualize::Color; use typst::World; use unscanny::Scanner; -use crate::analyze::{analyze_expr, analyze_import, analyze_labels}; -use crate::{plain_docs_sentence, summarize_font_family}; +use crate::{ + analyze_expr, analyze_import, analyze_labels, named_items, plain_docs_sentence, + summarize_font_family, +}; /// Autocomplete a cursor position in a source file. /// @@ -1327,62 +1329,12 @@ impl<'a> CompletionContext<'a> { /// Filters the global/math scope with the given filter. fn scope_completions(&mut self, parens: bool, filter: impl Fn(&Value) -> bool) { let mut defined = BTreeSet::new(); - - let mut ancestor = Some(self.leaf.clone()); - while let Some(node) = &ancestor { - let mut sibling = Some(node.clone()); - while let Some(node) = &sibling { - if let Some(v) = node.cast::() { - for ident in v.kind().bindings() { - defined.insert(ident.get().clone()); - } - } - - if let Some(v) = node.cast::() { - let imports = v.imports(); - match imports { - None | Some(ast::Imports::Wildcard) => { - if let Some(value) = node - .children() - .find(|child| child.is::()) - .and_then(|source| analyze_import(self.world, &source)) - { - if imports.is_none() { - defined.extend(value.name().map(Into::into)); - } else if let Some(scope) = value.scope() { - for (name, _) in scope.iter() { - defined.insert(name.clone()); - } - } - } - } - Some(ast::Imports::Items(items)) => { - for item in items.iter() { - defined.insert(item.bound_name().get().clone()); - } - } - } - } - - sibling = node.prev_sibling(); + named_items(self.world, self.leaf.clone(), |name| { + if name.value().as_ref().map_or(true, &filter) { + defined.insert(name.name().clone()); } - - if let Some(parent) = node.parent() { - if let Some(v) = parent.cast::() { - if node.prev_sibling_kind() != Some(SyntaxKind::In) { - let pattern = v.pattern(); - for ident in pattern.bindings() { - defined.insert(ident.get().clone()); - } - } - } - - ancestor = Some(parent.clone()); - continue; - } - - break; - } + None::<()> + }); let in_math = matches!( self.leaf.parent_kind(), diff --git a/crates/typst-ide/src/definition.rs b/crates/typst-ide/src/definition.rs new file mode 100644 index 000000000..452627816 --- /dev/null +++ b/crates/typst-ide/src/definition.rs @@ -0,0 +1,264 @@ +use ecow::EcoString; +use typst::foundations::{Label, Module, Selector, Value}; +use typst::model::Document; +use typst::syntax::ast::AstNode; +use typst::syntax::{ast, LinkedNode, Side, Source, Span, SyntaxKind}; +use typst::World; + +use crate::{analyze_import, deref_target, named_items, DerefTarget, NamedItem}; + +/// Find the definition of the item under the cursor. +/// +/// Passing a `document` (from a previous compilation) is optional, but enhances +/// the definition search. Label definitions, for instance, are only generated +/// when the document is available. +pub fn definition( + world: &dyn World, + document: Option<&Document>, + source: &Source, + cursor: usize, + side: Side, +) -> Option { + let root = LinkedNode::new(source.root()); + let leaf = root.leaf_at(cursor, side)?; + + let target = deref_target(leaf.clone())?; + + let mut use_site = match target { + DerefTarget::VarAccess(node) | DerefTarget::Callee(node) => node, + DerefTarget::IncludePath(path) | DerefTarget::ImportPath(path) => { + let import_item = + analyze_import(world, &path).and_then(|v| v.cast::().ok())?; + return Some(Definition::module(&import_item, path.span(), Span::detached())); + } + DerefTarget::Ref(r) => { + let label = Label::new(r.cast::()?.target()); + let sel = Selector::Label(label); + let elem = document?.introspector.query_first(&sel)?; + let span = elem.span(); + return Some(Definition { + kind: DefinitionKind::Label, + name: label.as_str().into(), + value: Some(Value::Label(label)), + span, + name_span: Span::detached(), + }); + } + DerefTarget::Label(..) | DerefTarget::Code(..) => { + return None; + } + }; + + let mut has_path = false; + while let Some(node) = use_site.cast::() { + has_path = true; + use_site = use_site.find(node.target().span())?; + } + + let name = use_site.cast::()?.get().clone(); + let src = named_items(world, use_site, |item: NamedItem| { + if *item.name() != name { + return None; + } + + match item { + NamedItem::Var(name) => { + let name_span = name.span(); + let span = find_let_binding(source, name_span); + Some(Definition::item(name.get().clone(), span, name_span, None)) + } + NamedItem::Fn(name) => { + let name_span = name.span(); + let span = find_let_binding(source, name_span); + Some( + Definition::item(name.get().clone(), span, name_span, None) + .with_kind(DefinitionKind::Function), + ) + } + NamedItem::Module(item, site) => Some(Definition::module( + item, + site.span(), + matches!(site.kind(), SyntaxKind::Ident) + .then_some(site.span()) + .unwrap_or_else(Span::detached), + )), + NamedItem::Import(name, span, value) => Some(Definition::item( + name.clone(), + Span::detached(), + span, + value.cloned(), + )), + } + }); + + let src = src.or_else(|| { + let in_math = matches!( + leaf.parent_kind(), + Some(SyntaxKind::Equation) + | Some(SyntaxKind::Math) + | Some(SyntaxKind::MathFrac) + | Some(SyntaxKind::MathAttach) + ); + let library = world.library(); + + let scope = if in_math { library.math.scope() } else { library.global.scope() }; + for (item_name, value) in scope.iter() { + if *item_name == name { + return Some(Definition::item( + name, + Span::detached(), + Span::detached(), + Some(value.clone()), + )); + } + } + + None + })?; + + (!has_path).then_some(src) +} + +/// A definition of some item. +#[derive(Debug, Clone)] +pub struct Definition { + /// The name of the definition. + pub name: EcoString, + /// The kind of the definition. + pub kind: DefinitionKind, + /// An instance of the definition, if available. + pub value: Option, + /// The source span of the entire definition. May be detached if unknown. + pub span: Span, + /// The span of the definition's name. May be detached if unknown. + pub name_span: Span, +} + +impl Definition { + fn item(name: EcoString, span: Span, name_span: Span, value: Option) -> Self { + Self { + name, + kind: match value { + Some(Value::Func(_)) => DefinitionKind::Function, + _ => DefinitionKind::Variable, + }, + value, + span, + name_span, + } + } + + fn module(module: &Module, span: Span, name_span: Span) -> Self { + Definition { + name: module.name().clone(), + kind: DefinitionKind::Module, + value: Some(Value::Module(module.clone())), + span, + name_span, + } + } + + fn with_kind(self, kind: DefinitionKind) -> Self { + Self { kind, ..self } + } +} + +/// A kind of item that is definition. +#[derive(Debug, Clone, PartialEq, Hash)] +pub enum DefinitionKind { + /// ```plain + /// let foo; + /// ^^^^^^^^ span + /// ^^^ name_span + /// ``` + Variable, + /// ```plain + /// let foo(it) = it; + /// ^^^^^^^^^^^^^^^^^ span + /// ^^^ name_span + /// ``` + Function, + /// Case 1 + /// ```plain + /// import "foo.typ": * + /// ^^^^^^^^^ span + /// name_span is detached + /// ``` + /// + /// Case 2 + /// ```plain + /// import "foo.typ" as bar: * + /// span ^^^ + /// name_span ^^^ + /// ``` + Module, + /// ```plain + /// + /// ^^^^^ span + /// name_span is detached + /// ``` + Label, +} + +fn find_let_binding(source: &Source, name_span: Span) -> Span { + let node = LinkedNode::new(source.root()); + std::iter::successors(node.find(name_span).as_ref(), |n| n.parent()) + .find(|n| matches!(n.kind(), SyntaxKind::LetBinding)) + .map(|s| s.span()) + .unwrap_or_else(Span::detached) +} + +#[cfg(test)] +mod tests { + use std::ops::Range; + + use typst::foundations::{IntoValue, Label, NativeElement, Value}; + use typst::syntax::Side; + use typst::WorldExt; + + use super::{definition, DefinitionKind as Kind}; + use crate::tests::TestWorld; + + #[track_caller] + fn test( + text: &str, + cursor: usize, + name: &str, + kind: Kind, + value: Option, + range: Option>, + ) where + T: IntoValue, + { + let world = TestWorld::new(text); + let doc = typst::compile(&world).output.ok(); + let actual = definition(&world, doc.as_ref(), &world.main, cursor, Side::After) + .map(|d| (d.kind, d.name, world.range(d.span), d.value)); + assert_eq!( + actual, + Some((kind, name.into(), range, value.map(IntoValue::into_value))) + ); + } + + #[test] + fn test_definition() { + test("#let x; #x", 9, "x", Kind::Variable, None::, Some(1..6)); + test("#let x() = {}; #x", 16, "x", Kind::Function, None::, Some(1..13)); + test( + "#table", + 1, + "table", + Kind::Function, + Some(typst::model::TableElem::elem()), + None, + ); + test( + "#figure[] See @hi", + 21, + "hi", + Kind::Label, + Some(Label::new("hi")), + Some(1..9), + ); + } +} diff --git a/crates/typst-ide/src/lib.rs b/crates/typst-ide/src/lib.rs index 1f8562fd2..c4a88085d 100644 --- a/crates/typst-ide/src/lib.rs +++ b/crates/typst-ide/src/lib.rs @@ -2,12 +2,16 @@ mod analyze; mod complete; +mod definition; mod jump; +mod matchers; mod tooltip; -pub use self::analyze::analyze_labels; +pub use self::analyze::{analyze_expr, analyze_import, analyze_labels}; pub use self::complete::{autocomplete, Completion, CompletionKind}; +pub use self::definition::{definition, Definition, DefinitionKind}; pub use self::jump::{jump_from_click, jump_from_cursor, Jump}; +pub use self::matchers::{deref_target, named_items, DerefTarget, NamedItem}; pub use self::tooltip::{tooltip, Tooltip}; use std::fmt::Write; diff --git a/crates/typst-ide/src/matchers.rs b/crates/typst-ide/src/matchers.rs new file mode 100644 index 000000000..757e5ab65 --- /dev/null +++ b/crates/typst-ide/src/matchers.rs @@ -0,0 +1,266 @@ +use ecow::EcoString; +use typst::foundations::{Module, Value}; +use typst::syntax::ast::AstNode; +use typst::syntax::{ast, LinkedNode, Span, SyntaxKind, SyntaxNode}; +use typst::World; + +use crate::analyze_import; + +/// Find the named items starting from the given position. +pub fn named_items( + world: &dyn World, + position: LinkedNode, + mut recv: impl FnMut(NamedItem) -> Option, +) -> Option { + let mut ancestor = Some(position); + while let Some(node) = &ancestor { + let mut sibling = Some(node.clone()); + while let Some(node) = &sibling { + if let Some(v) = node.cast::() { + let kind = if matches!(v.kind(), ast::LetBindingKind::Closure(..)) { + NamedItem::Fn + } else { + NamedItem::Var + }; + for ident in v.kind().bindings() { + if let Some(res) = recv(kind(ident)) { + return Some(res); + } + } + } + + if let Some(v) = node.cast::() { + let imports = v.imports(); + let source = node + .children() + .find(|child| child.is::()) + .and_then(|source: LinkedNode| { + Some((analyze_import(world, &source)?, source)) + }); + let source = source.as_ref(); + + // Seeing the module itself. + if let Some((value, source)) = source { + let site = match (imports, v.new_name()) { + // ```plain + // import "foo" as name; + // import "foo" as name: ..; + // ``` + (_, Some(name)) => Some(name.to_untyped()), + // ```plain + // import "foo"; + // ``` + (None, None) => Some(source.get()), + // ```plain + // import "foo": ..; + // ``` + (Some(..), None) => None, + }; + + if let Some((site, value)) = + site.zip(value.clone().cast::().ok()) + { + if let Some(res) = recv(NamedItem::Module(&value, site)) { + return Some(res); + } + } + } + + // Seeing the imported items. + match imports { + // ```plain + // import "foo"; + // ``` + None => {} + // ```plain + // import "foo": *; + // ``` + Some(ast::Imports::Wildcard) => { + if let Some(scope) = source.and_then(|(value, _)| value.scope()) { + for (name, value) in scope.iter() { + let item = NamedItem::Import( + name, + Span::detached(), + Some(value), + ); + if let Some(res) = recv(item) { + return Some(res); + } + } + } + } + // ```plain + // import "foo": items; + // ``` + Some(ast::Imports::Items(items)) => { + for item in items.iter() { + let name = item.bound_name(); + if let Some(res) = + recv(NamedItem::Import(name.get(), name.span(), None)) + { + return Some(res); + } + } + } + } + } + + sibling = node.prev_sibling(); + } + + if let Some(parent) = node.parent() { + if let Some(v) = parent.cast::() { + if node.prev_sibling_kind() != Some(SyntaxKind::In) { + let pattern = v.pattern(); + for ident in pattern.bindings() { + if let Some(res) = recv(NamedItem::Var(ident)) { + return Some(res); + } + } + } + } + + ancestor = Some(parent.clone()); + continue; + } + + break; + } + + None +} + +/// An item that is named. +pub enum NamedItem<'a> { + /// A variable item. + Var(ast::Ident<'a>), + /// A function item. + Fn(ast::Ident<'a>), + /// A (imported) module item. + Module(&'a Module, &'a SyntaxNode), + /// An imported item. + Import(&'a EcoString, Span, Option<&'a Value>), +} + +impl<'a> NamedItem<'a> { + pub(crate) fn name(&self) -> &'a EcoString { + match self { + NamedItem::Var(ident) => ident.get(), + NamedItem::Fn(ident) => ident.get(), + NamedItem::Module(value, _) => value.name(), + NamedItem::Import(name, _, _) => name, + } + } + + pub(crate) fn value(&self) -> Option { + match self { + NamedItem::Var(..) | NamedItem::Fn(..) => None, + NamedItem::Module(value, _) => Some(Value::Module((*value).clone())), + NamedItem::Import(_, _, value) => value.cloned(), + } + } +} + +/// Categorize an expression into common classes IDE functionality can operate +/// on. +pub fn deref_target(node: LinkedNode) -> Option> { + // Move to the first ancestor that is an expression. + let mut ancestor = node; + while !ancestor.is::() { + ancestor = ancestor.parent()?.clone(); + } + + // Identify convenient expression kinds. + let expr_node = ancestor; + let expr = expr_node.cast::()?; + Some(match expr { + ast::Expr::Label(..) => DerefTarget::Label(expr_node), + ast::Expr::Ref(..) => DerefTarget::Ref(expr_node), + ast::Expr::FuncCall(call) => { + DerefTarget::Callee(expr_node.find(call.callee().span())?) + } + ast::Expr::Set(set) => DerefTarget::Callee(expr_node.find(set.target().span())?), + ast::Expr::Ident(..) | ast::Expr::MathIdent(..) | ast::Expr::FieldAccess(..) => { + DerefTarget::VarAccess(expr_node) + } + ast::Expr::Str(..) => { + let parent = expr_node.parent()?; + if parent.kind() == SyntaxKind::ModuleImport { + DerefTarget::ImportPath(expr_node) + } else if parent.kind() == SyntaxKind::ModuleInclude { + DerefTarget::IncludePath(expr_node) + } else { + DerefTarget::Code(expr_node.kind(), expr_node) + } + } + _ if expr.hash() + || matches!(expr_node.kind(), SyntaxKind::MathIdent | SyntaxKind::Error) => + { + DerefTarget::Code(expr_node.kind(), expr_node) + } + _ => return None, + }) +} + +/// Classes of expressions that can be operated on by IDE functionality. +#[derive(Debug, Clone)] +pub enum DerefTarget<'a> { + /// A label expression. + Label(LinkedNode<'a>), + /// A reference expression. + Ref(LinkedNode<'a>), + /// A variable access expression. + /// + /// It can be either an identifier or a field access. + VarAccess(LinkedNode<'a>), + /// A function call expression. + Callee(LinkedNode<'a>), + /// An import path expression. + ImportPath(LinkedNode<'a>), + /// An include path expression. + IncludePath(LinkedNode<'a>), + /// Any code expression. + Code(SyntaxKind, LinkedNode<'a>), +} + +#[cfg(test)] +mod tests { + use typst::syntax::{LinkedNode, Side}; + + use crate::{named_items, tests::TestWorld}; + + #[track_caller] + fn has_named_items(text: &str, cursor: usize, containing: &str) -> bool { + let world = TestWorld::new(text); + + let src = world.main.clone(); + let node = LinkedNode::new(src.root()); + let leaf = node.leaf_at(cursor, Side::After).unwrap(); + + let res = named_items(&world, leaf, |s| { + if containing == s.name() { + return Some(true); + } + + None + }); + + res.unwrap_or_default() + } + + #[test] + fn test_simple_named_items() { + // Has named items + assert!(has_named_items(r#"#let a = 1;#let b = 2;"#, 8, "a")); + assert!(has_named_items(r#"#let a = 1;#let b = 2;"#, 15, "a")); + + // Doesn't have named items + assert!(!has_named_items(r#"#let a = 1;#let b = 2;"#, 8, "b")); + } + + #[test] + fn test_import_named_items() { + // Cannot test much. + assert!(has_named_items(r#"#import "foo.typ": a; #(a);"#, 24, "a")); + } +} diff --git a/crates/typst-ide/src/tooltip.rs b/crates/typst-ide/src/tooltip.rs index 3bf8bb14a..c78c02d82 100644 --- a/crates/typst-ide/src/tooltip.rs +++ b/crates/typst-ide/src/tooltip.rs @@ -11,14 +11,13 @@ use typst::syntax::{ast, LinkedNode, Side, Source, SyntaxKind}; use typst::utils::{round_2, Numeric}; use typst::World; -use crate::analyze::{analyze_expr, analyze_labels}; -use crate::{plain_docs_sentence, summarize_font_family}; +use crate::{analyze_expr, analyze_labels, plain_docs_sentence, summarize_font_family}; /// Describe the item under the cursor. /// /// Passing a `document` (from a previous compilation) is optional, but enhances -/// the autocompletions. Label completions, for instance, are only generated -/// when the document is available. +/// the tooltips. Label tooltips, for instance, are only generated when the +/// document is available. pub fn tooltip( world: &dyn World, document: Option<&Document>, diff --git a/crates/typst/src/eval/mod.rs b/crates/typst/src/eval/mod.rs index e4221df77..dc8a18020 100644 --- a/crates/typst/src/eval/mod.rs +++ b/crates/typst/src/eval/mod.rs @@ -86,7 +86,7 @@ pub fn eval( .unwrap_or_default() .to_string_lossy(); - Ok(Module::new(name, vm.scopes.top).with_content(output)) + Ok(Module::new(name, vm.scopes.top).with_content(output).with_file_id(id)) } /// Evaluate a string as code and return the resulting value. diff --git a/crates/typst/src/foundations/module.rs b/crates/typst/src/foundations/module.rs index 580d09ef1..91b508554 100644 --- a/crates/typst/src/foundations/module.rs +++ b/crates/typst/src/foundations/module.rs @@ -5,6 +5,7 @@ use ecow::{eco_format, EcoString}; use crate::diag::StrResult; use crate::foundations::{repr, ty, Content, Scope, Value}; +use crate::syntax::FileId; /// An evaluated module, either built-in or resulting from a file. /// @@ -43,6 +44,8 @@ struct Repr { scope: Scope, /// The module's layoutable contents. content: Content, + /// The id of the file which defines the module, if any. + file_id: Option, } impl Module { @@ -50,7 +53,7 @@ impl Module { pub fn new(name: impl Into, scope: Scope) -> Self { Self { name: name.into(), - inner: Arc::new(Repr { scope, content: Content::empty() }), + inner: Arc::new(Repr { scope, content: Content::empty(), file_id: None }), } } @@ -72,6 +75,12 @@ impl Module { self } + /// Update the module's file id. + pub fn with_file_id(mut self, file_id: FileId) -> Self { + Arc::make_mut(&mut self.inner).file_id = Some(file_id); + self + } + /// Get the module's name. pub fn name(&self) -> &EcoString { &self.name @@ -82,6 +91,13 @@ impl Module { &self.inner.scope } + /// Access the module's file id. + /// + /// Some modules are not associated with a file, like the built-in modules. + pub fn file_id(&self) -> Option { + self.inner.file_id + } + /// Access the module's scope, mutably. pub fn scope_mut(&mut self) -> &mut Scope { &mut Arc::make_mut(&mut self.inner).scope From 3b382cbd4524484671018f1a66c085ede9f7dd19 Mon Sep 17 00:00:00 2001 From: Yip Coekjan <69834864+Coekjan@users.noreply.github.com> Date: Wed, 10 Jul 2024 17:43:46 +0800 Subject: [PATCH 22/34] Refactor `impl Eval for ast::FuncCall<'_>` (#4435) Co-authored-by: Ian Wrzesinski <133046678+wrzian@users.noreply.github.com> --- crates/typst/src/eval/call.rs | 273 +++++++++++++++------------ crates/typst/src/foundations/args.rs | 12 ++ 2 files changed, 166 insertions(+), 119 deletions(-) diff --git a/crates/typst/src/eval/call.rs b/crates/typst/src/eval/call.rs index a143c8ac2..ed972c005 100644 --- a/crates/typst/src/eval/call.rs +++ b/crates/typst/src/eval/call.rs @@ -1,7 +1,10 @@ use comemo::{Tracked, TrackedMut}; use ecow::{eco_format, EcoVec}; -use crate::diag::{bail, error, At, HintedStrResult, SourceResult, Trace, Tracepoint}; +use crate::diag::{ + bail, error, At, HintedStrResult, HintedString, SourceDiagnostic, SourceResult, + Trace, Tracepoint, +}; use crate::engine::{Engine, Sink, Traced}; use crate::eval::{Access, Eval, FlowEvent, Route, Vm}; use crate::foundations::{ @@ -10,7 +13,7 @@ use crate::foundations::{ }; use crate::introspection::Introspector; use crate::math::LrElem; -use crate::syntax::ast::{self, AstNode}; +use crate::syntax::ast::{self, AstNode, Ident}; use crate::syntax::{Span, Spanned, SyntaxNode}; use crate::text::TextElem; use crate::utils::LazyHash; @@ -32,135 +35,25 @@ impl Eval for ast::FuncCall<'_> { } // Try to evaluate as a call to an associated function or field. - let (callee, mut args) = if let ast::Expr::FieldAccess(access) = callee { + let (callee, args) = if let ast::Expr::FieldAccess(access) = callee { let target = access.target(); - let target_span = target.span(); let field = access.field(); - let field_span = field.span(); - - let target = if is_mutating_method(&field) { - let mut args = args.eval(vm)?.spanned(span); - let target = target.access(vm)?; - - // Only arrays and dictionaries have mutable methods. - if matches!(target, Value::Array(_) | Value::Dict(_)) { - args.span = span; - let point = || Tracepoint::Call(Some(field.get().clone())); - return call_method_mut(target, &field, args, span).trace( - vm.world(), - point, - span, - ); - } - - target.clone() - } else { - access.target().eval(vm)? - }; - - let mut args = args.eval(vm)?.spanned(span); - - // Handle plugins. - if let Value::Plugin(plugin) = &target { - let bytes = args.all::()?; - args.finish()?; - return Ok(plugin.call(&field, bytes).at(span)?.into_value()); - } - - // Prioritize associated functions on the value's type (i.e., - // methods) over its fields. A function call on a field is only - // allowed for functions, types, modules (because they are scopes), - // and symbols (because they have modifiers). - // - // For dictionaries, it is not allowed because it would be ambiguous - // (prioritizing associated functions would make an addition of a - // new associated function a breaking change and prioritizing fields - // would break associated functions for certain dictionaries). - if let Some(callee) = target.ty().scope().get(&field) { - let this = Arg { - span: target_span, - name: None, - value: Spanned::new(target, target_span), - }; - args.span = span; - args.items.insert(0, this); - (callee.clone(), args) - } else if matches!( - target, - Value::Symbol(_) | Value::Func(_) | Value::Type(_) | Value::Module(_) - ) { - (target.field(&field).at(field_span)?, args) - } else { - let mut error = error!( - field_span, - "type {} has no method `{}`", - target.ty(), - field.as_str() - ); - - let mut field_hint = || { - if target.field(&field).is_ok() { - error.hint(eco_format!( - "did you mean to access the field `{}`?", - field.as_str() - )); - } - }; - - match target { - Value::Dict(ref dict) => { - if matches!(dict.get(&field), Ok(Value::Func(_))) { - error.hint(eco_format!( - "to call the function stored in the dictionary, surround \ - the field access with parentheses, e.g. `(dict.{})(..)`", - field.as_str(), - )); - } else { - field_hint(); - } - } - _ => field_hint(), - } - - bail!(error); + match eval_field_call(target, field, args, span, vm)? { + FieldCall::Normal(callee, args) => (callee, args), + FieldCall::Resolved(value) => return Ok(value), } } else { + // Function call order: we evaluate the callee before the arguments. (callee.eval(vm)?, args.eval(vm)?.spanned(span)) }; let func_result = callee.clone().cast::(); if in_math && func_result.is_err() { - // For non-functions in math, we wrap the arguments in parentheses. - let mut body = Content::empty(); - for (i, arg) in args.all::()?.into_iter().enumerate() { - if i > 0 { - body += TextElem::packed(','); - } - body += arg; - } - if trailing_comma { - body += TextElem::packed(','); - } - return Ok(Value::Content( - callee.display().spanned(callee_span) - + LrElem::new(TextElem::packed('(') + body + TextElem::packed(')')) - .pack(), - )); + return wrap_args_in_math(callee, callee_span, args, trailing_comma); } let func = func_result - .map_err(|mut err| { - if let ast::Expr::Ident(ident) = self.callee() { - let ident = ident.get(); - if vm.scopes.check_std_shadowed(ident) { - err.hint(eco_format!( - "use `std.{}` to access the shadowed standard library function", - ident, - )); - } - } - err - }) + .map_err(|err| hint_if_shadowed_std(vm, &self.callee(), err)) .at(callee_span)?; let point = || Tracepoint::Call(func.name().map(Into::into)); @@ -368,6 +261,108 @@ pub(crate) fn call_closure( Ok(output) } +/// This used only as the return value of `eval_field_call`. +/// - `Normal` means that we have a function to call and the arguments to call it with. +/// - `Resolved` means that we have already resolved the call and have the value. +enum FieldCall { + Normal(Value, Args), + Resolved(Value), +} + +/// Evaluate a field call's callee and arguments. +/// +/// This follows the normal function call order: we evaluate the callee before the +/// arguments. +/// +/// Prioritize associated functions on the value's type (e.g., methods) over its fields. +/// A function call on a field is only allowed for functions, types, modules (because +/// they are scopes), and symbols (because they have modifiers or associated functions). +/// +/// For dictionaries, it is not allowed because it would be ambiguous - prioritizing +/// associated functions would make an addition of a new associated function a breaking +/// change and prioritizing fields would break associated functions for certain +/// dictionaries. +fn eval_field_call( + target_expr: ast::Expr, + field: Ident, + args: ast::Args, + span: Span, + vm: &mut Vm, +) -> SourceResult { + // Evaluate the field-call's target and overall arguments. + let (target, mut args) = if is_mutating_method(&field) { + // If `field` looks like a mutating method, we evaluate the arguments first, + // because `target_expr.access(vm)` mutably borrows the `vm`, so that we can't + // evaluate the arguments after it. + let args = args.eval(vm)?.spanned(span); + // However, this difference from the normal call order is not observable because + // expressions like `(1, arr.len(), 2, 3).push(arr.pop())` evaluate the target to + // a temporary which we disallow mutation on (returning an error). + // Theoretically this could be observed if a method matching `is_mutating_method` + // was added to some type in the future and we didn't update this function. + match target_expr.access(vm)? { + // Only arrays and dictionaries have mutable methods. + target @ (Value::Array(_) | Value::Dict(_)) => { + let value = call_method_mut(target, &field, args, span); + let point = || Tracepoint::Call(Some(field.get().clone())); + return Ok(FieldCall::Resolved(value.trace(vm.world(), point, span)?)); + } + target => (target.clone(), args), + } + } else { + let target = target_expr.eval(vm)?; + let args = args.eval(vm)?.spanned(span); + (target, args) + }; + + if let Value::Plugin(plugin) = &target { + // Call plugins by converting args to bytes. + let bytes = args.all::()?; + args.finish()?; + let value = plugin.call(&field, bytes).at(span)?.into_value(); + Ok(FieldCall::Resolved(value)) + } else if let Some(callee) = target.ty().scope().get(&field) { + args.insert(0, target_expr.span(), target); + Ok(FieldCall::Normal(callee.clone(), args)) + } else if matches!( + target, + Value::Symbol(_) | Value::Func(_) | Value::Type(_) | Value::Module(_) + ) { + // Certain value types may have their own ways to access method fields. + // e.g. `$arrow.r(v)$`, `table.cell[..]` + let value = target.field(&field).at(field.span())?; + Ok(FieldCall::Normal(value, args)) + } else { + // Otherwise we cannot call this field. + bail!(missing_field_call_error(target, field)) + } +} + +/// Produce an error when we cannot call the field. +fn missing_field_call_error(target: Value, field: Ident) -> SourceDiagnostic { + let mut error = + error!(field.span(), "type {} has no method `{}`", target.ty(), field.as_str()); + + match target { + Value::Dict(ref dict) if matches!(dict.get(&field), Ok(Value::Func(_))) => { + error.hint(eco_format!( + "to call the function stored in the dictionary, surround \ + the field access with parentheses, e.g. `(dict.{})(..)`", + field.as_str(), + )); + } + _ if target.field(&field).is_ok() => { + error.hint(eco_format!( + "did you mean to access the field `{}`?", + field.as_str(), + )); + } + _ => {} + } + error +} + +/// Check if the expression is in a math context. fn in_math(expr: ast::Expr) -> bool { match expr { ast::Expr::MathIdent(_) => true, @@ -376,6 +371,46 @@ fn in_math(expr: ast::Expr) -> bool { } } +/// For non-functions in math, we wrap the arguments in parentheses. +fn wrap_args_in_math( + callee: Value, + callee_span: Span, + mut args: Args, + trailing_comma: bool, +) -> SourceResult { + let mut body = Content::empty(); + for (i, arg) in args.all::()?.into_iter().enumerate() { + if i > 0 { + body += TextElem::packed(','); + } + body += arg; + } + if trailing_comma { + body += TextElem::packed(','); + } + Ok(Value::Content( + callee.display().spanned(callee_span) + + LrElem::new(TextElem::packed('(') + body + TextElem::packed(')')).pack(), + )) +} + +/// Provide a hint if the callee is a shadowed standard library function. +fn hint_if_shadowed_std( + vm: &mut Vm, + callee: &ast::Expr, + mut err: HintedString, +) -> HintedString { + if let ast::Expr::Ident(ident) = callee { + let ident = ident.get(); + if vm.scopes.check_std_shadowed(ident) { + err.hint(eco_format!( + "use `std.{ident}` to access the shadowed standard library function", + )); + } + } + err +} + /// A visitor that determines which variables to capture for a closure. pub struct CapturesVisitor<'a> { external: Option<&'a Scopes<'a>>, diff --git a/crates/typst/src/foundations/args.rs b/crates/typst/src/foundations/args.rs index c59e49855..d580be3ce 100644 --- a/crates/typst/src/foundations/args.rs +++ b/crates/typst/src/foundations/args.rs @@ -76,6 +76,18 @@ impl Args { self.items.iter().filter(|slot| slot.name.is_none()).count() } + /// Insert a positional argument at a specific index. + pub fn insert(&mut self, index: usize, span: Span, value: Value) { + self.items.insert( + index, + Arg { + span: self.span, + name: None, + value: Spanned::new(value, span), + }, + ) + } + /// Push a positional argument. pub fn push(&mut self, span: Span, value: Value) { self.items.push(Arg { From 3c22902d6cd99de6717fd2dad0a1fcca48039225 Mon Sep 17 00:00:00 2001 From: tingerrr Date: Wed, 10 Jul 2024 11:44:13 +0200 Subject: [PATCH 23/34] Add missing keys to manifest types (#4494) --- Cargo.lock | 1 + crates/typst-syntax/Cargo.toml | 1 + crates/typst-syntax/src/package.rs | 205 ++++++++++++++++++++++++++++- 3 files changed, 200 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f9fd4c50..a92c0d228 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2836,6 +2836,7 @@ dependencies = [ "ecow", "once_cell", "serde", + "toml", "typst-utils", "unicode-ident", "unicode-math-class", diff --git a/crates/typst-syntax/Cargo.toml b/crates/typst-syntax/Cargo.toml index 816f0d340..e9c399920 100644 --- a/crates/typst-syntax/Cargo.toml +++ b/crates/typst-syntax/Cargo.toml @@ -17,6 +17,7 @@ typst-utils = { workspace = true } ecow = { workspace = true } once_cell = { workspace = true } serde = { workspace = true } +toml = { workspace = true } unicode-ident = { workspace = true } unicode-math-class = { workspace = true } unicode-script = { workspace = true } diff --git a/crates/typst-syntax/src/package.rs b/crates/typst-syntax/src/package.rs index fb0031c85..c96aebe01 100644 --- a/crates/typst-syntax/src/package.rs +++ b/crates/typst-syntax/src/package.rs @@ -1,37 +1,101 @@ //! Package manifest parsing. +use std::collections::BTreeMap; use std::fmt::{self, Debug, Display, Formatter}; use std::str::FromStr; use ecow::{eco_format, EcoString}; +use serde::de::IgnoredAny; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use unscanny::Scanner; use crate::is_ident; +/// A type alias for a map of key-value pairs used to collect unknown fields +/// where values are completely discarded. +pub type UnknownFields = BTreeMap; + /// A parsed package manifest. -#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +/// +/// The `unknown_fields` contains fields which were found but not expected. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PackageManifest { /// Details about the package itself. pub package: PackageInfo, /// Details about the template, if the package is one. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub template: Option, + /// The tools section for third-party configuration. + #[serde(default)] + pub tool: ToolInfo, + /// All parsed but unknown fields, this can be used for validation. + #[serde(flatten, skip_serializing)] + pub unknown_fields: UnknownFields, +} + +/// The `[tool]` key in the manifest. This field can be used to retrieve +/// 3rd-party tool configuration. +/// +// # Examples +/// ``` +/// # use serde::{Deserialize, Serialize}; +/// # use ecow::EcoString; +/// # use typst_syntax::package::PackageManifest; +/// #[derive(Debug, PartialEq, Serialize, Deserialize)] +/// struct MyTool { +/// key: EcoString, +/// } +/// +/// let mut manifest: PackageManifest = toml::from_str(r#" +/// [package] +/// name = "package" +/// version = "0.1.0" +/// entrypoint = "src/lib.typ" +/// +/// [tool.my-tool] +/// key = "value" +/// "#)?; +/// +/// let my_tool = manifest +/// .tool +/// .sections +/// .remove("my-tool") +/// .ok_or("tool.my-tool section missing")?; +/// let my_tool = MyTool::deserialize(my_tool)?; +/// +/// assert_eq!(my_tool, MyTool { key: "value".into() }); +/// # Ok::<_, Box>(()) +/// ``` +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +pub struct ToolInfo { + /// Any fields parsed in the tool section. + #[serde(flatten)] + pub sections: BTreeMap, } /// The `[template]` key in the manifest. -#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +/// +/// The `unknown_fields` contains fields which were found but not expected. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct TemplateInfo { - /// The path of the starting point within the package. + /// The directory within the package that contains the files that should be + /// copied into the user's new project directory. pub path: EcoString, - /// The path of the entrypoint relative to the starting point's `path`. + /// A path relative to the template's path that points to the file serving + /// as the compilation target. pub entrypoint: EcoString, + /// A path relative to the package's root that points to a PNG or lossless + /// WebP thumbnail for the template. + pub thumbnail: EcoString, + /// All parsed but unknown fields, this can be used for validation. + #[serde(flatten, skip_serializing)] + pub unknown_fields: UnknownFields, } /// The `[package]` key in the manifest. /// -/// More fields are specified, but they are not relevant to the compiler. -#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +/// The `unknown_fields` contains fields which were found but not expected. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PackageInfo { /// The name of the package within its namespace. pub name: EcoString, @@ -39,8 +103,42 @@ pub struct PackageInfo { pub version: PackageVersion, /// The path of the entrypoint into the package. pub entrypoint: EcoString, + /// A list of the package's authors. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub authors: Vec, + /// The package's license. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub license: Option, + /// A short description of the package. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + /// A link to the package's web presence. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub homepage: Option, + /// A link to the repository where this package is developed. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub repository: Option, + /// An array of search keywords for the package. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub keywords: Vec, + /// An array with up to three of the predefined categories to help users + /// discover the package. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub categories: Vec, + /// An array of disciplines defining the target audience for which the + /// package is useful. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub disciplines: Vec, /// The minimum required compiler version for the package. + #[serde(default, skip_serializing_if = "Option::is_none")] pub compiler: Option, + /// An array of globs specifying files that should not be part of the + /// published bundle. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub exclude: Vec, + /// All parsed but unknown fields, this can be used for validation. + #[serde(flatten, skip_serializing)] + pub unknown_fields: UnknownFields, } impl PackageManifest { @@ -423,4 +521,97 @@ mod tests { assert!(!v1_1_1.matches_lt(&VersionBound::from_str("1.1").unwrap())); assert!(v1_1_1.matches_lt(&VersionBound::from_str("1.2").unwrap())); } + + #[test] + fn minimal_manifest() { + assert_eq!( + toml::from_str::( + r#" + [package] + name = "package" + version = "0.1.0" + entrypoint = "src/lib.typ" + "# + ), + Ok(PackageManifest { + package: PackageInfo { + name: "package".into(), + version: PackageVersion { major: 0, minor: 1, patch: 0 }, + entrypoint: "src/lib.typ".into(), + authors: vec![], + license: None, + description: None, + homepage: None, + repository: None, + keywords: vec![], + categories: vec![], + disciplines: vec![], + compiler: None, + exclude: vec![], + unknown_fields: BTreeMap::new(), + }, + template: None, + tool: ToolInfo { sections: BTreeMap::new() }, + unknown_fields: BTreeMap::new(), + }) + ); + } + + #[test] + fn tool_section() { + // NOTE: tool section must be table of tables, but we can't easily + // compare the error structurally + assert!(toml::from_str::( + r#" + [package] + name = "package" + version = "0.1.0" + entrypoint = "src/lib.typ" + + [tool] + not-table = "str" + "# + ) + .is_err()); + + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct MyTool { + key: EcoString, + } + + let mut manifest: PackageManifest = toml::from_str( + r#" + [package] + name = "package" + version = "0.1.0" + entrypoint = "src/lib.typ" + + [tool.my-tool] + key = "value" + "#, + ) + .unwrap(); + + let my_tool = manifest.tool.sections.remove("my-tool").unwrap(); + let my_tool = MyTool::deserialize(my_tool).unwrap(); + + assert_eq!(my_tool, MyTool { key: "value".into() }); + } + + #[test] + fn unknown_keys() { + let manifest: PackageManifest = toml::from_str( + r#" + [package] + name = "package" + version = "0.1.0" + entrypoint = "src/lib.typ" + + [unknown] + "#, + ) + .unwrap(); + + assert!(manifest.unknown_fields.contains_key("unknown")); + } } From 36042ff222d70349f4c459384790ec40e1103bf9 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 11 Jul 2024 11:30:06 +0200 Subject: [PATCH 24/34] Remove `place.flush` from global scope (#4537) --- crates/typst/src/layout/mod.rs | 1 - crates/typst/src/layout/place.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/typst/src/layout/mod.rs b/crates/typst/src/layout/mod.rs index 85cdbae7a..739d09224 100644 --- a/crates/typst/src/layout/mod.rs +++ b/crates/typst/src/layout/mod.rs @@ -106,7 +106,6 @@ pub fn define(global: &mut Scope) { global.define_elem::(); global.define_elem::(); global.define_elem::(); - global.define_elem::(); global.define_elem::(); global.define_elem::(); global.define_elem::(); diff --git a/crates/typst/src/layout/place.rs b/crates/typst/src/layout/place.rs index 78922c1bb..be211c150 100644 --- a/crates/typst/src/layout/place.rs +++ b/crates/typst/src/layout/place.rs @@ -46,7 +46,7 @@ pub struct PlaceElem { /// Floating elements are positioned at the top or bottom of the page, /// displacing in-flow content. They are always placed in the in-flow /// order relative to each other, as well as before any content following - /// a later [`flush`] element. + /// a later [`place.flush`] element. /// /// ```example /// #set page(height: 150pt) From be516867c834d1172e17b3b2b25d70428c3d918b Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 11 Jul 2024 16:24:28 +0200 Subject: [PATCH 25/34] Spans for cross-file go-to-definition (#4539) --- crates/typst-ide/src/analyze.rs | 2 +- crates/typst-ide/src/complete.rs | 8 ++-- crates/typst-ide/src/definition.rs | 14 +++--- crates/typst-ide/src/matchers.rs | 20 ++++---- crates/typst-ide/src/tooltip.rs | 2 +- crates/typst-syntax/src/span.rs | 9 ++++ crates/typst/src/eval/call.rs | 22 ++++++--- crates/typst/src/eval/import.rs | 8 ++-- crates/typst/src/eval/vm.rs | 2 +- crates/typst/src/foundations/dict.rs | 2 +- crates/typst/src/foundations/mod.rs | 2 +- crates/typst/src/foundations/scope.rs | 67 +++++++++++++++++++-------- docs/src/lib.rs | 10 ++-- 13 files changed, 108 insertions(+), 60 deletions(-) diff --git a/crates/typst-ide/src/analyze.rs b/crates/typst-ide/src/analyze.rs index ecb73dcec..c37795562 100644 --- a/crates/typst-ide/src/analyze.rs +++ b/crates/typst-ide/src/analyze.rs @@ -49,7 +49,6 @@ pub fn analyze_expr( pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option { // Use span in the node for resolving imports with relative paths. let source_span = source.span(); - let (source, _) = analyze_expr(world, source).into_iter().next()?; if source.scope().is_some() { return Some(source); @@ -73,6 +72,7 @@ pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option { Scopes::new(Some(world.library())), Span::detached(), ); + typst::eval::import(&mut vm, source, source_span, true) .ok() .map(Value::Module) diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index c4f86d04a..f6c96d001 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -386,12 +386,12 @@ fn field_access_completions( value: &Value, styles: &Option, ) { - for (name, value) in value.ty().scope().iter() { + for (name, value, _) in value.ty().scope().iter() { ctx.value_completion(Some(name.clone()), value, true, None); } if let Some(scope) = value.scope() { - for (name, value) in scope.iter() { + for (name, value, _) in scope.iter() { ctx.value_completion(Some(name.clone()), value, true, None); } } @@ -557,7 +557,7 @@ fn import_item_completions<'a>( ctx.snippet_completion("*", "*", "Import everything."); } - for (name, value) in scope.iter() { + for (name, value, _) in scope.iter() { if existing.iter().all(|item| item.original_name().as_str() != name) { ctx.value_completion(Some(name.clone()), value, false, None); } @@ -1345,7 +1345,7 @@ impl<'a> CompletionContext<'a> { ); let scope = if in_math { self.math } else { self.global }; - for (name, value) in scope.iter() { + for (name, value, _) in scope.iter() { if filter(value) && !defined.contains(name) { self.value_completion(Some(name.clone()), value, parens, None); } diff --git a/crates/typst-ide/src/definition.rs b/crates/typst-ide/src/definition.rs index 452627816..4323226d3 100644 --- a/crates/typst-ide/src/definition.rs +++ b/crates/typst-ide/src/definition.rs @@ -22,9 +22,7 @@ pub fn definition( let root = LinkedNode::new(source.root()); let leaf = root.leaf_at(cursor, side)?; - let target = deref_target(leaf.clone())?; - - let mut use_site = match target { + let mut use_site = match deref_target(leaf.clone())? { DerefTarget::VarAccess(node) | DerefTarget::Callee(node) => node, DerefTarget::IncludePath(path) | DerefTarget::ImportPath(path) => { let import_item = @@ -82,10 +80,10 @@ pub fn definition( .then_some(site.span()) .unwrap_or_else(Span::detached), )), - NamedItem::Import(name, span, value) => Some(Definition::item( + NamedItem::Import(name, name_span, value) => Some(Definition::item( name.clone(), Span::detached(), - span, + name_span, value.cloned(), )), } @@ -99,14 +97,14 @@ pub fn definition( | Some(SyntaxKind::MathFrac) | Some(SyntaxKind::MathAttach) ); - let library = world.library(); + let library = world.library(); let scope = if in_math { library.math.scope() } else { library.global.scope() }; - for (item_name, value) in scope.iter() { + for (item_name, value, span) in scope.iter() { if *item_name == name { return Some(Definition::item( name, - Span::detached(), + span, Span::detached(), Some(value.clone()), )); diff --git a/crates/typst-ide/src/matchers.rs b/crates/typst-ide/src/matchers.rs index 757e5ab65..1daec8193 100644 --- a/crates/typst-ide/src/matchers.rs +++ b/crates/typst-ide/src/matchers.rs @@ -77,12 +77,8 @@ pub fn named_items( // ``` Some(ast::Imports::Wildcard) => { if let Some(scope) = source.and_then(|(value, _)| value.scope()) { - for (name, value) in scope.iter() { - let item = NamedItem::Import( - name, - Span::detached(), - Some(value), - ); + for (name, value, span) in scope.iter() { + let item = NamedItem::Import(name, span, Some(value)); if let Some(res) = recv(item) { return Some(res); } @@ -94,9 +90,17 @@ pub fn named_items( // ``` Some(ast::Imports::Items(items)) => { for item in items.iter() { - let name = item.bound_name(); + let original = item.original_name(); + let bound = item.bound_name(); + let scope = source.and_then(|(value, _)| value.scope()); + let span = scope + .and_then(|s| s.get_span(&original)) + .unwrap_or(Span::detached()) + .or(bound.span()); + + let value = scope.and_then(|s| s.get(&original)); if let Some(res) = - recv(NamedItem::Import(name.get(), name.span(), None)) + recv(NamedItem::Import(bound.get(), span, value)) { return Some(res); } diff --git a/crates/typst-ide/src/tooltip.rs b/crates/typst-ide/src/tooltip.rs index c78c02d82..02fb3ec62 100644 --- a/crates/typst-ide/src/tooltip.rs +++ b/crates/typst-ide/src/tooltip.rs @@ -126,7 +126,7 @@ fn closure_tooltip(leaf: &LinkedNode) -> Option { let captures = visitor.finish(); let mut names: Vec<_> = - captures.iter().map(|(name, _)| eco_format!("`{name}`")).collect(); + captures.iter().map(|(name, ..)| eco_format!("`{name}`")).collect(); if names.is_empty() { return None; } diff --git a/crates/typst-syntax/src/span.rs b/crates/typst-syntax/src/span.rs index 8138a3166..4d5dd9c37 100644 --- a/crates/typst-syntax/src/span.rs +++ b/crates/typst-syntax/src/span.rs @@ -83,6 +83,15 @@ impl Span { self.0.get() & ((1 << Self::BITS) - 1) } + /// Return `other` if `self` is detached and `self` otherwise. + pub fn or(self, other: Self) -> Self { + if self.is_detached() { + other + } else { + self + } + } + /// Resolve a file location relative to this span's source. pub fn resolve_path(self, path: &str) -> Result { let Some(file) = self.id() else { diff --git a/crates/typst/src/eval/call.rs b/crates/typst/src/eval/call.rs index ed972c005..ee4c4787c 100644 --- a/crates/typst/src/eval/call.rs +++ b/crates/typst/src/eval/call.rs @@ -1,5 +1,5 @@ use comemo::{Tracked, TrackedMut}; -use ecow::{eco_format, EcoVec}; +use ecow::{eco_format, EcoString, EcoVec}; use crate::diag::{ bail, error, At, HintedStrResult, HintedString, SourceDiagnostic, SourceResult, @@ -442,9 +442,11 @@ impl<'a> CapturesVisitor<'a> { // Identifiers that shouldn't count as captures because they // actually bind a new name are handled below (individually through // the expressions that contain them). - Some(ast::Expr::Ident(ident)) => self.capture(&ident, Scopes::get), + Some(ast::Expr::Ident(ident)) => { + self.capture(ident.get(), ident.span(), Scopes::get) + } Some(ast::Expr::MathIdent(ident)) => { - self.capture(&ident, Scopes::get_in_math) + self.capture(ident.get(), ident.span(), Scopes::get_in_math) } // Code and content blocks create a scope. @@ -552,13 +554,14 @@ impl<'a> CapturesVisitor<'a> { /// Bind a new internal variable. fn bind(&mut self, ident: ast::Ident) { - self.internal.top.define(ident.get().clone(), Value::None); + self.internal.top.define_ident(ident, Value::None); } /// Capture a variable if it isn't internal. fn capture( &mut self, - ident: &str, + ident: &EcoString, + span: Span, getter: impl FnOnce(&'a Scopes<'a>, &str) -> HintedStrResult<&'a Value>, ) { if self.internal.get(ident).is_err() { @@ -570,7 +573,12 @@ impl<'a> CapturesVisitor<'a> { return; }; - self.captures.define_captured(ident, value.clone(), self.capturer); + self.captures.define_captured( + ident.clone(), + value.clone(), + self.capturer, + span, + ); } } } @@ -593,7 +601,7 @@ mod tests { visitor.visit(&root); let captures = visitor.finish(); - let mut names: Vec<_> = captures.iter().map(|(k, _)| k).collect(); + let mut names: Vec<_> = captures.iter().map(|(k, ..)| k).collect(); names.sort(); assert_eq!(names, result); diff --git a/crates/typst/src/eval/import.rs b/crates/typst/src/eval/import.rs index da07e6664..68187a966 100644 --- a/crates/typst/src/eval/import.rs +++ b/crates/typst/src/eval/import.rs @@ -31,7 +31,7 @@ impl Eval for ast::ModuleImport<'_> { } } - if let Some(new_name) = &new_name { + if let Some(new_name) = new_name { if let ast::Expr::Ident(ident) = self.source() { if ident.as_str() == new_name.as_str() { // Warn on `import x as x` @@ -43,7 +43,7 @@ impl Eval for ast::ModuleImport<'_> { } // Define renamed module on the scope. - vm.scopes.top.define(new_name.as_str(), source.clone()); + vm.scopes.top.define_ident(new_name, source.clone()); } let scope = source.scope().unwrap(); @@ -56,8 +56,8 @@ impl Eval for ast::ModuleImport<'_> { } } Some(ast::Imports::Wildcard) => { - for (var, value) in scope.iter() { - vm.scopes.top.define(var.clone(), value.clone()); + for (var, value, span) in scope.iter() { + vm.scopes.top.define_spanned(var.clone(), value.clone(), span); } } Some(ast::Imports::Items(items)) => { diff --git a/crates/typst/src/eval/vm.rs b/crates/typst/src/eval/vm.rs index 27960eb6d..4d346870c 100644 --- a/crates/typst/src/eval/vm.rs +++ b/crates/typst/src/eval/vm.rs @@ -47,7 +47,7 @@ impl<'a> Vm<'a> { if self.inspected == Some(var.span()) { self.trace(value.clone()); } - self.scopes.top.define(var.get().clone(), value); + self.scopes.top.define_ident(var, value); } /// Trace a value. diff --git a/crates/typst/src/foundations/dict.rs b/crates/typst/src/foundations/dict.rs index 991f3f7af..6e61838eb 100644 --- a/crates/typst/src/foundations/dict.rs +++ b/crates/typst/src/foundations/dict.rs @@ -261,7 +261,7 @@ pub struct ToDict(Dict); cast! { ToDict, - v: Module => Self(v.scope().iter().map(|(k, v)| (Str::from(k.clone()), v.clone())).collect()), + v: Module => Self(v.scope().iter().map(|(k, v, _)| (Str::from(k.clone()), v.clone())).collect()), } impl Debug for Dict { diff --git a/crates/typst/src/foundations/mod.rs b/crates/typst/src/foundations/mod.rs index eb9f6d661..b7783dda9 100644 --- a/crates/typst/src/foundations/mod.rs +++ b/crates/typst/src/foundations/mod.rs @@ -290,7 +290,7 @@ pub fn eval( let dict = scope; let mut scope = Scope::new(); for (key, value) in dict { - scope.define(key, value); + scope.define_spanned(key, value, span); } crate::eval::eval_string(engine.world, &text, span, mode, scope) } diff --git a/crates/typst/src/foundations/scope.rs b/crates/typst/src/foundations/scope.rs index 0313df7a9..b118540e1 100644 --- a/crates/typst/src/foundations/scope.rs +++ b/crates/typst/src/foundations/scope.rs @@ -9,6 +9,8 @@ use crate::foundations::{ Element, Func, IntoValue, Module, NativeElement, NativeFunc, NativeFuncData, NativeType, Type, Value, }; +use crate::syntax::ast::{self, AstNode}; +use crate::syntax::Span; use crate::utils::Static; use crate::Library; @@ -152,6 +154,23 @@ impl Scope { /// Bind a value to a name. #[track_caller] pub fn define(&mut self, name: impl Into, value: impl IntoValue) { + self.define_spanned(name, value, Span::detached()) + } + + /// Bind a value to a name defined by an identifier. + #[track_caller] + pub fn define_ident(&mut self, ident: ast::Ident, value: impl IntoValue) { + self.define_spanned(ident.get().clone(), value, ident.span()) + } + + /// Bind a value to a name. + #[track_caller] + pub fn define_spanned( + &mut self, + name: impl Into, + value: impl IntoValue, + span: Span, + ) { let name = name.into(); #[cfg(debug_assertions)] @@ -159,8 +178,24 @@ impl Scope { panic!("duplicate definition: {name}"); } - self.map - .insert(name, Slot::new(value.into_value(), Kind::Normal, self.category)); + self.map.insert( + name, + Slot::new(value.into_value(), span, Kind::Normal, self.category), + ); + } + + /// Define a captured, immutable binding. + pub fn define_captured( + &mut self, + name: EcoString, + value: Value, + capturer: Capturer, + span: Span, + ) { + self.map.insert( + name, + Slot::new(value.into_value(), span, Kind::Captured(capturer), self.category), + ); } /// Define a native function through a Rust type that shadows the function. @@ -191,19 +226,6 @@ impl Scope { self.define(module.name().clone(), module); } - /// Define a captured, immutable binding. - pub fn define_captured( - &mut self, - var: impl Into, - value: impl IntoValue, - capturer: Capturer, - ) { - self.map.insert( - var.into(), - Slot::new(value.into_value(), Kind::Captured(capturer), self.category), - ); - } - /// Try to access a variable immutably. pub fn get(&self, var: &str) -> Option<&Value> { self.map.get(var).map(Slot::read) @@ -217,14 +239,19 @@ impl Scope { .map(|res| res.map_err(HintedString::from)) } + /// Get the span of a definition. + pub fn get_span(&self, var: &str) -> Option { + Some(self.map.get(var)?.span) + } + /// Get the category of a definition. pub fn get_category(&self, var: &str) -> Option { self.map.get(var)?.category } /// Iterate over all definitions. - pub fn iter(&self) -> impl Iterator { - self.map.iter().map(|(k, v)| (k, v.read())) + pub fn iter(&self) -> impl Iterator { + self.map.iter().map(|(k, v)| (k, v.read(), v.span)) } } @@ -264,6 +291,8 @@ struct Slot { value: Value, /// The kind of slot, determines how the value can be accessed. kind: Kind, + /// A span associated with the stored value. + span: Span, /// The category of the slot. category: Option, } @@ -288,8 +317,8 @@ pub enum Capturer { impl Slot { /// Create a new slot. - fn new(value: Value, kind: Kind, category: Option) -> Self { - Self { value, kind, category } + fn new(value: Value, span: Span, kind: Kind, category: Option) -> Self { + Self { value, span, kind, category } } /// Read the value. diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 7575817a7..030cb0a97 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -44,8 +44,8 @@ static GROUPS: Lazy> = Lazy::new(|| { .module() .scope() .iter() - .filter(|(_, v)| matches!(v, Value::Func(_))) - .map(|(k, _)| k.clone()) + .filter(|(_, v, _)| matches!(v, Value::Func(_))) + .map(|(k, _, _)| k.clone()) .collect(); } } @@ -249,7 +249,7 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel { // Add values and types. let scope = module.scope(); - for (name, value) in scope.iter() { + for (name, value, _) in scope.iter() { if scope.get_category(name) != Some(category) { continue; } @@ -463,7 +463,7 @@ fn casts( fn scope_models(resolver: &dyn Resolver, name: &str, scope: &Scope) -> Vec { scope .iter() - .filter_map(|(_, value)| { + .filter_map(|(_, value, _)| { let Value::Func(func) = value else { return None }; Some(func_model(resolver, func, &[name], true)) }) @@ -649,7 +649,7 @@ fn symbols_page(resolver: &dyn Resolver, parent: &str, group: &GroupData) -> Pag /// Produce a symbol list's model. fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel { let mut list = vec![]; - for (name, value) in group.module().scope().iter() { + for (name, value, _) in group.module().scope().iter() { let Value::Symbol(symbol) = value else { continue }; let complete = |variant: &str| { if variant.is_empty() { From ab5cebc57c8345f397c3f0a1ed0c024dc6811e2f Mon Sep 17 00:00:00 2001 From: Leedehai <18319900+Leedehai@users.noreply.github.com> Date: Thu, 11 Jul 2024 10:43:16 -0400 Subject: [PATCH 26/34] Let test error hide line number if it's zero (#4535) --- tests/src/collect.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/src/collect.rs b/tests/src/collect.rs index ee4f9db99..f10f4a2e3 100644 --- a/tests/src/collect.rs +++ b/tests/src/collect.rs @@ -48,7 +48,11 @@ impl FilePos { impl Display for FilePos { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}:{}", self.path.display(), self.line) + if self.line > 0 { + write!(f, "{}:{}", self.path.display(), self.line) + } else { + write!(f, "{}", self.path.display()) + } } } From 5c71ad7fe733664d5dcec1a36dcbf205b203d86c Mon Sep 17 00:00:00 2001 From: Matthias Vogelgesang Date: Sun, 14 Jul 2024 11:53:32 +0200 Subject: [PATCH 27/34] Do not conflate Archivo Narrow and Archivo (Black) fonts (#4478) --- crates/typst/src/text/font/exceptions.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/typst/src/text/font/exceptions.rs b/crates/typst/src/text/font/exceptions.rs index 2e5e3edaf..642b6ea72 100644 --- a/crates/typst/src/text/font/exceptions.rs +++ b/crates/typst/src/text/font/exceptions.rs @@ -48,6 +48,16 @@ static EXCEPTION_MAP: phf::Map<&'static str, Exception> = phf::phf_map! { // See https://corefonts.sourceforge.net/. "Arial-Black" => Exception::new() .weight(900), + // Archivo Narrow is different from Archivo and Archivo Black. Since Archivo Black seems + // identical to Archivo weight 900, only differentiate between Archivo and Archivo Narrow. + "ArchivoNarrow-Regular" => Exception::new() + .family("Archivo Narrow"), + "ArchivoNarrow-Italic" => Exception::new() + .family("Archivo Narrow"), + "ArchivoNarrow-Bold" => Exception::new() + .family("Archivo Narrow"), + "ArchivoNarrow-BoldItalic" => Exception::new() + .family("Archivo Narrow"), // Fandol fonts designed for Chinese typesetting. // See https://ctan.org/tex-archive/fonts/fandol/. "FandolHei-Bold" => Exception::new() From 98d98a4bfd4222de1f1ac4acc9793b9783a95397 Mon Sep 17 00:00:00 2001 From: Leedehai <18319900+Leedehai@users.noreply.github.com> Date: Sun, 14 Jul 2024 05:55:23 -0400 Subject: [PATCH 28/34] Short-circuit on None's when laying out AttachElem (#4546) --- crates/typst/src/math/attach.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/typst/src/math/attach.rs b/crates/typst/src/math/attach.rs index 0e19d1277..4d5bf2994 100644 --- a/crates/typst/src/math/attach.rs +++ b/crates/typst/src/math/attach.rs @@ -261,8 +261,11 @@ fn layout_attachments( base: MathFragment, [tl, t, tr, bl, b, br]: [Option; 6], ) -> SourceResult<()> { - let (shift_up, shift_down) = - compute_shifts_up_and_down(ctx, styles, &base, [&tl, &tr, &bl, &br]); + let (shift_up, shift_down) = if [&tl, &tr, &bl, &br].iter().all(|e| e.is_none()) { + (Abs::zero(), Abs::zero()) + } else { + compute_shifts_up_and_down(ctx, styles, &base, [&tl, &tr, &bl, &br]) + }; let sup_delta = Abs::zero(); let sub_delta = -base.italics_correction(); @@ -287,7 +290,11 @@ fn layout_attachments( let post_width_max = (sup_delta + measure!(tr, width)).max(sub_delta + measure!(br, width)); - let (center_frame, base_offset) = attach_top_and_bottom(ctx, styles, base, t, b); + let (center_frame, base_offset) = if t.is_none() && b.is_none() { + (base.into_frame(), Abs::zero()) + } else { + attach_top_and_bottom(ctx, styles, base, t, b) + }; if [&tl, &bl, &tr, &br].iter().all(|&e| e.is_none()) { ctx.push(FrameFragment::new(ctx, styles, center_frame).with_class(base_class)); return Ok(()); From 4d8976b619fbb2ab19c1e46fccbca4294c0c2d0b Mon Sep 17 00:00:00 2001 From: Yip Coekjan <69834864+Coekjan@users.noreply.github.com> Date: Sun, 14 Jul 2024 20:54:51 +0800 Subject: [PATCH 29/34] Fix lexer behavior on non-whitespace before ref (#4553) --- crates/typst-syntax/src/lexer.rs | 2 +- tests/suite/model/ref.typ | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/typst-syntax/src/lexer.rs b/crates/typst-syntax/src/lexer.rs index dd05e73f2..993af0806 100644 --- a/crates/typst-syntax/src/lexer.rs +++ b/crates/typst-syntax/src/lexer.rs @@ -474,7 +474,7 @@ impl Lexer<'_> { Some('-') if !s.at(['-', '?']) => {} Some('.') if !s.at("..") => {} Some('h') if !s.at("ttp://") && !s.at("ttps://") => {} - Some('@') if !s.at(is_id_start) => {} + Some('@') if !s.at(is_valid_in_label_literal) => {} _ => break, } diff --git a/tests/suite/model/ref.typ b/tests/suite/model/ref.typ index 200f40aaa..d0881202c 100644 --- a/tests/suite/model/ref.typ +++ b/tests/suite/model/ref.typ @@ -54,3 +54,8 @@ $ A = 1 $ // Error: 1-7 label occurs in the document and its bibliography @arrgh #bibliography("/assets/bib/works.bib") + +--- issue-4536-non-whitespace-before-ref --- +// Test reference with non-whitespace before it. +#figure[] <1> +#test([(#ref(<1>))], [(@1)]) From a3f3a1a83330e3fe9a686fbe4c0eda9f1e9e99b2 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Sun, 14 Jul 2024 21:14:21 +0800 Subject: [PATCH 30/34] Change the signature of `World::main` (#4531) Co-authored-by: Laurenz --- crates/typst-cli/src/compile.rs | 67 ++------------------------------- crates/typst-cli/src/world.rs | 4 +- crates/typst-ide/src/lib.rs | 4 +- crates/typst-syntax/src/file.rs | 5 +++ crates/typst-syntax/src/path.rs | 5 +++ crates/typst/src/lib.rs | 65 +++++++++++++++++++++++++++++--- docs/src/html.rs | 14 ++++--- tests/fuzz/src/compile.rs | 16 +++++--- tests/src/world.rs | 4 +- 9 files changed, 97 insertions(+), 87 deletions(-) diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index 4b87c3bd3..d15264255 100644 --- a/crates/typst-cli/src/compile.rs +++ b/crates/typst-cli/src/compile.rs @@ -5,16 +5,16 @@ use std::path::{Path, PathBuf}; use chrono::{Datelike, Timelike}; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::term; -use ecow::{eco_format, eco_vec, EcoString, EcoVec}; +use ecow::{eco_format, EcoString}; use parking_lot::RwLock; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; -use typst::diag::{bail, FileError, Severity, SourceDiagnostic, StrResult, Warned}; +use typst::diag::{bail, Severity, SourceDiagnostic, StrResult, Warned}; use typst::foundations::{Datetime, Smart}; use typst::layout::{Frame, PageRanges}; use typst::model::Document; use typst::syntax::{FileId, Source, Span}; use typst::visualize::Color; -use typst::{World, WorldExt}; +use typst::WorldExt; use crate::args::{ CompileCommand, DiagnosticFormat, Input, Output, OutputFormat, PageRangeArgument, @@ -96,21 +96,6 @@ pub fn compile_once( Status::Compiling.print(command).unwrap(); } - if let Err(errors) = world - .source(world.main()) - .map_err(|err| hint_invalid_main_file(err, &command.common.input)) - { - set_failed(); - if watching { - Status::Error.print(command).unwrap(); - } - - print_diagnostics(world, &errors, &[], command.common.diagnostic_format) - .map_err(|err| eco_format!("failed to print diagnostics ({err})"))?; - - return Ok(()); - } - let Warned { output, warnings } = typst::compile(world); match output { @@ -498,52 +483,6 @@ fn open_file(open: Option<&str>, path: &Path) -> StrResult<()> { } } -/// Adds useful hints when the main source file couldn't be read -/// and returns the final diagnostic. -fn hint_invalid_main_file( - file_error: FileError, - input: &Input, -) -> EcoVec { - let is_utf8_error = matches!(file_error, FileError::InvalidUtf8); - let mut diagnostic = - SourceDiagnostic::error(Span::detached(), EcoString::from(file_error)); - - // Attempt to provide helpful hints for UTF-8 errors. - // Perhaps the user mistyped the filename. - // For example, they could have written "file.pdf" instead of - // "file.typ". - if is_utf8_error { - if let Input::Path(path) = input { - let extension = path.extension(); - if extension.is_some_and(|extension| extension == "typ") { - // No hints if the file is already a .typ file. - // The file is indeed just invalid. - return eco_vec![diagnostic]; - } - - match extension { - Some(extension) => { - diagnostic.hint(eco_format!( - "a file with the `.{}` extension is not usually a Typst file", - extension.to_string_lossy() - )); - } - - None => { - diagnostic - .hint("a file without an extension is not usually a Typst file"); - } - }; - - if path.with_extension("typ").exists() { - diagnostic.hint("check if you meant to use the `.typ` extension instead"); - } - } - } - - eco_vec![diagnostic] -} - /// Print diagnostic messages to the terminal. pub fn print_diagnostics( world: &SystemWorld, diff --git a/crates/typst-cli/src/world.rs b/crates/typst-cli/src/world.rs index 8e8b305f5..5a0814a86 100644 --- a/crates/typst-cli/src/world.rs +++ b/crates/typst-cli/src/world.rs @@ -192,8 +192,8 @@ impl World for SystemWorld { &self.book } - fn main(&self) -> Source { - self.source(self.main).unwrap() + fn main(&self) -> FileId { + self.main } fn source(&self, id: FileId) -> FileResult { diff --git a/crates/typst-ide/src/lib.rs b/crates/typst-ide/src/lib.rs index c4a88085d..403a36ba0 100644 --- a/crates/typst-ide/src/lib.rs +++ b/crates/typst-ide/src/lib.rs @@ -139,8 +139,8 @@ mod tests { &self.base.book } - fn main(&self) -> Source { - self.main.clone() + fn main(&self) -> FileId { + self.main.id() } fn source(&self, id: FileId) -> FileResult { diff --git a/crates/typst-syntax/src/file.rs b/crates/typst-syntax/src/file.rs index 356337f3f..89aaa55e1 100644 --- a/crates/typst-syntax/src/file.rs +++ b/crates/typst-syntax/src/file.rs @@ -91,6 +91,11 @@ impl FileId { Self::new(self.package().cloned(), self.vpath().join(path)) } + /// The same file location, but with a different extension. + pub fn with_extension(&self, extension: &str) -> Self { + Self::new(self.package().cloned(), self.vpath().with_extension(extension)) + } + /// Construct from a raw number. pub(crate) const fn from_raw(v: u16) -> Self { Self(v) diff --git a/crates/typst-syntax/src/path.rs b/crates/typst-syntax/src/path.rs index b561128c1..6c625642a 100644 --- a/crates/typst-syntax/src/path.rs +++ b/crates/typst-syntax/src/path.rs @@ -85,6 +85,11 @@ impl VirtualPath { Self::new(path) } } + + /// The same path, but with a different extension. + pub fn with_extension(&self, extension: &str) -> Self { + Self(self.0.with_extension(extension)) + } } impl Debug for VirtualPath { diff --git a/crates/typst/src/lib.rs b/crates/typst/src/lib.rs index 50575d129..cfcfd757a 100644 --- a/crates/typst/src/lib.rs +++ b/crates/typst/src/lib.rs @@ -60,10 +60,12 @@ use std::collections::HashSet; use std::ops::{Deref, Range}; use comemo::{Track, Tracked, Validate}; -use ecow::{EcoString, EcoVec}; +use ecow::{eco_format, eco_vec, EcoString, EcoVec}; use typst_timing::{timed, TimingScope}; -use crate::diag::{warning, FileResult, SourceDiagnostic, SourceResult, Warned}; +use crate::diag::{ + warning, FileError, FileResult, SourceDiagnostic, SourceResult, Warned, +}; use crate::engine::{Engine, Route, Sink, Traced}; use crate::foundations::{ Array, Bytes, Datetime, Dict, Module, Scope, StyleChain, Styles, Value, @@ -108,13 +110,19 @@ fn compile_inner( let library = world.library(); let styles = StyleChain::new(&library.styles); + // Fetch the main source file once. + let main = world.main(); + let main = world + .source(main) + .map_err(|err| hint_invalid_main_file(world, err, main))?; + // First evaluate the main source file into a module. let content = crate::eval::eval( world, traced, sink.track_mut(), Route::default().track(), - &world.main(), + &main, )? .content(); @@ -203,8 +211,8 @@ pub trait World: Send + Sync { /// Metadata about all known fonts. fn book(&self) -> &LazyHash; - /// Access the main source file. - fn main(&self) -> Source; + /// Get the file id of the main source file. + fn main(&self) -> FileId; /// Try to access the specified source file. fn source(&self, id: FileId) -> FileResult; @@ -246,7 +254,7 @@ macro_rules! delegate_for_ptr { self.deref().book() } - fn main(&self) -> Source { + fn main(&self) -> FileId { self.deref().main() } @@ -402,3 +410,48 @@ fn prelude(global: &mut Scope) { global.define("horizon", Alignment::HORIZON); global.define("bottom", Alignment::BOTTOM); } + +/// Adds useful hints when the main source file couldn't be read +/// and returns the final diagnostic. +fn hint_invalid_main_file( + world: Tracked, + file_error: FileError, + input: FileId, +) -> EcoVec { + let is_utf8_error = matches!(file_error, FileError::InvalidUtf8); + let mut diagnostic = + SourceDiagnostic::error(Span::detached(), EcoString::from(file_error)); + + // Attempt to provide helpful hints for UTF-8 errors. Perhaps the user + // mistyped the filename. For example, they could have written "file.pdf" + // instead of "file.typ". + if is_utf8_error { + let path = input.vpath(); + let extension = path.as_rootless_path().extension(); + if extension.is_some_and(|extension| extension == "typ") { + // No hints if the file is already a .typ file. + // The file is indeed just invalid. + return eco_vec![diagnostic]; + } + + match extension { + Some(extension) => { + diagnostic.hint(eco_format!( + "a file with the `.{}` extension is not usually a Typst file", + extension.to_string_lossy() + )); + } + + None => { + diagnostic + .hint("a file without an extension is not usually a Typst file"); + } + }; + + if world.source(input.with_extension("typ")).is_ok() { + diagnostic.hint("check if you meant to use the `.typ` extension instead"); + } + } + + eco_vec![diagnostic] +} diff --git a/docs/src/html.rs b/docs/src/html.rs index ab140a902..58c8e54c2 100644 --- a/docs/src/html.rs +++ b/docs/src/html.rs @@ -6,7 +6,7 @@ use heck::{ToKebabCase, ToTitleCase}; use pulldown_cmark as md; use serde::{Deserialize, Serialize}; use typed_arena::Arena; -use typst::diag::{FileResult, StrResult}; +use typst::diag::{FileError, FileResult, StrResult}; use typst::foundations::{Bytes, Datetime}; use typst::layout::{Abs, Point, Size}; use typst::syntax::{FileId, Source, VirtualPath}; @@ -463,12 +463,16 @@ impl World for DocWorld { &FONTS.0 } - fn main(&self) -> Source { - self.0.clone() + fn main(&self) -> FileId { + self.0.id() } - fn source(&self, _: FileId) -> FileResult { - Ok(self.0.clone()) + fn source(&self, id: FileId) -> FileResult { + if id == self.0.id() { + Ok(self.0.clone()) + } else { + Err(FileError::NotFound(id.vpath().as_rootless_path().into())) + } } fn file(&self, id: FileId) -> FileResult { diff --git a/tests/fuzz/src/compile.rs b/tests/fuzz/src/compile.rs index 98c300ce5..c9536150f 100644 --- a/tests/fuzz/src/compile.rs +++ b/tests/fuzz/src/compile.rs @@ -39,16 +39,20 @@ impl World for FuzzWorld { &self.book } - fn main(&self) -> Source { - self.source.clone() + fn main(&self) -> FileId { + self.source.id() } - fn source(&self, src: FileId) -> FileResult { - Err(FileError::NotFound(src.vpath().as_rootless_path().into())) + fn source(&self, id: FileId) -> FileResult { + if id == self.source.id() { + Ok(self.source.clone()) + } else { + Err(FileError::NotFound(id.vpath().as_rootless_path().into())) + } } - fn file(&self, src: FileId) -> FileResult { - Err(FileError::NotFound(src.vpath().as_rootless_path().into())) + fn file(&self, id: FileId) -> FileResult { + Err(FileError::NotFound(id.vpath().as_rootless_path().into())) } fn font(&self, _: usize) -> Option { diff --git a/tests/src/world.rs b/tests/src/world.rs index ad925a538..47b77d7e5 100644 --- a/tests/src/world.rs +++ b/tests/src/world.rs @@ -43,8 +43,8 @@ impl World for TestWorld { &self.base.book } - fn main(&self) -> Source { - self.main.clone() + fn main(&self) -> FileId { + self.main.id() } fn source(&self, id: FileId) -> FileResult { From 17ee3df1ba99183fc074e91dfba3e9189dae1c0c Mon Sep 17 00:00:00 2001 From: +merlan #flirora Date: Sun, 14 Jul 2024 09:48:40 -0400 Subject: [PATCH 31/34] Wrap outline entry body in LRE/RLE + make smart quotes ignore directional control characters (#4491) Co-authored-by: Laurenz --- crates/typst/src/layout/inline/collect.rs | 4 ++-- crates/typst/src/layout/inline/linebreak.rs | 5 +++++ crates/typst/src/layout/inline/mod.rs | 2 +- crates/typst/src/model/outline.rs | 8 ++++++-- crates/typst/src/text/mod.rs | 10 ++++++++++ crates/typst/src/text/smartquote.rs | 6 +++--- .../issue-4476-rtl-title-ending-in-ltr-text.png | Bin 0 -> 6312 bytes tests/ref/smartquote-with-embedding-chars.png | Bin 0 -> 571 bytes tests/suite/model/outline.typ | 7 +++++++ tests/suite/text/smartquote.typ | 5 +++++ 10 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 tests/ref/issue-4476-rtl-title-ending-in-ltr-text.png create mode 100644 tests/ref/smartquote-with-embedding-chars.png diff --git a/crates/typst/src/layout/inline/collect.rs b/crates/typst/src/layout/inline/collect.rs index f1607460b..b6a847f57 100644 --- a/crates/typst/src/layout/inline/collect.rs +++ b/crates/typst/src/layout/inline/collect.rs @@ -201,7 +201,7 @@ pub fn collect<'a>( ); let peeked = iter.peek().and_then(|(child, _)| { if let Some(elem) = child.to_packed::() { - elem.text().chars().next() + elem.text().chars().find(|c| !is_default_ignorable(*c)) } else if child.is::() { Some('"') } else if child.is::() @@ -302,7 +302,7 @@ impl<'a> Collector<'a> { } fn push_segment(&mut self, segment: Segment<'a>, is_quote: bool) { - if let Some(last) = self.full.chars().last() { + if let Some(last) = self.full.chars().rev().find(|c| !is_default_ignorable(*c)) { self.quoter.last(last, is_quote); } diff --git a/crates/typst/src/layout/inline/linebreak.rs b/crates/typst/src/layout/inline/linebreak.rs index 9deaa92a8..075d24b33 100644 --- a/crates/typst/src/layout/inline/linebreak.rs +++ b/crates/typst/src/layout/inline/linebreak.rs @@ -953,3 +953,8 @@ where } } } + +/// Whether a codepoint is Unicode `Default_Ignorable`. +pub fn is_default_ignorable(c: char) -> bool { + DEFAULT_IGNORABLE_DATA.as_borrowed().contains(c) +} diff --git a/crates/typst/src/layout/inline/mod.rs b/crates/typst/src/layout/inline/mod.rs index f89de1690..821b4f57e 100644 --- a/crates/typst/src/layout/inline/mod.rs +++ b/crates/typst/src/layout/inline/mod.rs @@ -10,7 +10,7 @@ use comemo::{Track, Tracked, TrackedMut}; use self::collect::{collect, Item, Segment, SpanMapper}; use self::finalize::finalize; use self::line::{commit, line, Line}; -use self::linebreak::{linebreak, Breakpoint}; +use self::linebreak::{is_default_ignorable, linebreak, Breakpoint}; use self::prepare::{prepare, Preparation}; use self::shaping::{ cjk_punct_style, is_of_cj_script, shape_range, ShapedGlyph, ShapedText, diff --git a/crates/typst/src/model/outline.rs b/crates/typst/src/model/outline.rs index 090472850..ec1e5f1b8 100644 --- a/crates/typst/src/model/outline.rs +++ b/crates/typst/src/model/outline.rs @@ -483,7 +483,7 @@ impl OutlineEntry { impl Show for Packed { #[typst_macros::time(name = "outline.entry", span = self.span())] - fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult { + fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult { let mut seq = vec![]; let elem = self.element(); @@ -500,7 +500,11 @@ impl Show for Packed { }; // The body text remains overridable. - seq.push(self.body().clone().linked(Destination::Location(location))); + crate::text::isolate( + self.body().clone().linked(Destination::Location(location)), + styles, + &mut seq, + ); // Add filler symbols between the section name and page number. if let Some(filler) = self.fill() { diff --git a/crates/typst/src/text/mod.rs b/crates/typst/src/text/mod.rs index 7648f08fa..d42e4df8b 100644 --- a/crates/typst/src/text/mod.rs +++ b/crates/typst/src/text/mod.rs @@ -1299,3 +1299,13 @@ cast! { ret }, } + +/// Pushes `text` wrapped in LRE/RLE + PDF to `out`. +pub(crate) fn isolate(text: Content, styles: StyleChain, out: &mut Vec) { + out.push(TextElem::packed(match TextElem::dir_in(styles) { + Dir::RTL => "\u{202B}", + _ => "\u{202A}", + })); + out.push(text); + out.push(TextElem::packed("\u{202C}")); +} diff --git a/crates/typst/src/text/smartquote.rs b/crates/typst/src/text/smartquote.rs index 236d06363..797f0804b 100644 --- a/crates/typst/src/text/smartquote.rs +++ b/crates/typst/src/text/smartquote.rs @@ -123,7 +123,7 @@ impl SmartQuoter { /// Process the last seen character. pub fn last(&mut self, c: char, is_quote: bool) { - self.expect_opening = is_ignorable(c) || is_opening_bracket(c); + self.expect_opening = is_exterior_to_quote(c) || is_opening_bracket(c); self.last_num = c.is_numeric(); if !is_quote { self.prev_quote_type = None; @@ -150,7 +150,7 @@ impl SmartQuoter { self.prev_quote_type = Some(double); quotes.open(double) } else if self.quote_depth > 0 - && (peeked.is_ascii_punctuation() || is_ignorable(peeked)) + && (peeked.is_ascii_punctuation() || is_exterior_to_quote(peeked)) { self.quote_depth -= 1; quotes.close(double) @@ -168,7 +168,7 @@ impl Default for SmartQuoter { } } -fn is_ignorable(c: char) -> bool { +fn is_exterior_to_quote(c: char) -> bool { c.is_whitespace() || is_newline(c) } diff --git a/tests/ref/issue-4476-rtl-title-ending-in-ltr-text.png b/tests/ref/issue-4476-rtl-title-ending-in-ltr-text.png new file mode 100644 index 0000000000000000000000000000000000000000..09506966ee8e2ae3b038e9fedb6b89c46b844e0b GIT binary patch literal 6312 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 Date: Sun, 14 Jul 2024 16:02:50 +0200 Subject: [PATCH 32/34] Save and restore graphics state for every frame (#4496) Co-authored-by: Laurenz --- crates/typst-pdf/src/content.rs | 49 +++++++++++---------- tests/ref/issue-4361-transparency-leak.png | Bin 0 -> 3515 bytes tests/suite/visualize/color.typ | 7 +++ 3 files changed, 33 insertions(+), 23 deletions(-) create mode 100644 tests/ref/issue-4361-transparency-leak.png diff --git a/crates/typst-pdf/src/content.rs b/crates/typst-pdf/src/content.rs index 8ae2c424d..da9e4ed44 100644 --- a/crates/typst-pdf/src/content.rs +++ b/crates/typst-pdf/src/content.rs @@ -92,7 +92,7 @@ pub struct Builder<'a, R = ()> { state: State, /// Stack of saved graphic states. saves: Vec, - /// Wheter any stroke or fill was not totally opaque. + /// Whether any stroke or fill was not totally opaque. uses_opacities: bool, /// All clickable links that are present in this content. links: Vec<(Destination, Rect)>, @@ -129,7 +129,7 @@ struct State { /// The color space of the current fill paint. fill_space: Option>, /// The current external graphic state. - external_graphics_state: Option, + external_graphics_state: ExtGState, /// The current stroke paint. stroke: Option, /// The color space of the current stroke paint. @@ -148,7 +148,7 @@ impl State { font: None, fill: None, fill_space: None, - external_graphics_state: None, + external_graphics_state: ExtGState::default(), stroke: None, stroke_space: None, text_rendering_mode: TextRenderingMode::Fill, @@ -191,12 +191,13 @@ impl Builder<'_, ()> { } fn set_external_graphics_state(&mut self, graphics_state: &ExtGState) { - let current_state = self.state.external_graphics_state.as_ref(); - if current_state != Some(graphics_state) { + let current_state = &self.state.external_graphics_state; + if current_state != graphics_state { let index = self.resources.ext_gs.insert(*graphics_state); let name = eco_format!("Gs{index}"); self.content.set_parameters(Name(name.as_bytes())); + self.state.external_graphics_state = *graphics_state; if graphics_state.uses_opacities() { self.uses_opacities = true; } @@ -204,29 +205,27 @@ impl Builder<'_, ()> { } fn set_opacities(&mut self, stroke: Option<&FixedStroke>, fill: Option<&Paint>) { - let stroke_opacity = stroke - .map(|stroke| { - let color = match &stroke.paint { - Paint::Solid(color) => *color, - Paint::Gradient(_) | Paint::Pattern(_) => return 255, - }; + let get_opacity = |paint: &Paint| { + let color = match paint { + Paint::Solid(color) => *color, + Paint::Gradient(_) | Paint::Pattern(_) => return 255, + }; - color.alpha().map_or(255, |v| (v * 255.0).round() as u8) - }) - .unwrap_or(255); - let fill_opacity = fill - .map(|paint| { - let color = match paint { - Paint::Solid(color) => *color, - Paint::Gradient(_) | Paint::Pattern(_) => return 255, - }; + color.alpha().map_or(255, |v| (v * 255.0).round() as u8) + }; - color.alpha().map_or(255, |v| (v * 255.0).round() as u8) - }) - .unwrap_or(255); + let stroke_opacity = stroke.map_or(255, |stroke| get_opacity(&stroke.paint)); + let fill_opacity = fill.map_or(255, get_opacity); self.set_external_graphics_state(&ExtGState { stroke_opacity, fill_opacity }); } + fn reset_opacities(&mut self) { + self.set_external_graphics_state(&ExtGState { + stroke_opacity: 255, + fill_opacity: 255, + }); + } + pub fn transform(&mut self, transform: Transform) { let Transform { sx, ky, kx, sy, tx, ty } = transform; self.state.transform = self.state.transform.pre_concat(transform); @@ -542,6 +541,8 @@ fn write_color_glyphs(ctx: &mut Builder, pos: Point, text: TextItemView) { let mut last_font = None; + ctx.reset_opacities(); + ctx.content.begin_text(); ctx.content.set_text_matrix([1.0, 0.0, 0.0, -1.0, x, y]); // So that the next call to ctx.set_font() will change the font to one that @@ -671,6 +672,8 @@ fn write_image(ctx: &mut Builder, x: f32, y: f32, image: &Image, size: Size) { image }); + ctx.reset_opacities(); + let name = eco_format!("Im{index}"); let w = size.x.to_f32(); let h = size.y.to_f32(); diff --git a/tests/ref/issue-4361-transparency-leak.png b/tests/ref/issue-4361-transparency-leak.png new file mode 100644 index 0000000000000000000000000000000000000000..4060d43ac442e67e674245685f3fbfbe1d555157 GIT binary patch literal 3515 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 literal 0 HcmV?d00001 diff --git a/tests/suite/visualize/color.typ b/tests/suite/visualize/color.typ index 45000ab24..bc8f8be5b 100644 --- a/tests/suite/visualize/color.typ +++ b/tests/suite/visualize/color.typ @@ -333,3 +333,10 @@ --- issue-color-mix-luma --- // When mixing luma colors, we accidentally used the wrong component. #rect(fill: gradient.linear(black, silver, space: luma)) + +--- issue-4361-transparency-leak --- +// Ensure that transparency doesn't leak from shapes to images in PDF. The PNG +// test doesn't validate it, but at least we can discover regressions on the PDF +// output with a PDF comparison script. +#rect(fill: red.transparentize(50%)) +#image("/assets/images/tiger.jpg", width: 45pt) From f0407d4949cce8014e2a4fdef8c00c955d870127 Mon Sep 17 00:00:00 2001 From: Leedehai <18319900+Leedehai@users.noreply.github.com> Date: Mon, 15 Jul 2024 05:54:54 -0400 Subject: [PATCH 33/34] Wrapping with parens should not push superscripts higher (#4545) Co-authored-by: Laurenz --- crates/typst/src/math/row.rs | 15 ++++++++++++--- tests/ref/math-attach-horizontal-align.png | Bin 2001 -> 1900 bytes tests/ref/math-attach-to-group.png | Bin 651 -> 649 bytes tests/ref/math-root-large-body.png | Bin 1614 -> 1549 bytes tests/suite/math/attach.typ | 4 ++-- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/crates/typst/src/math/row.rs b/crates/typst/src/math/row.rs index 5234ca2cd..60afe64e5 100644 --- a/crates/typst/src/math/row.rs +++ b/crates/typst/src/math/row.rs @@ -159,10 +159,19 @@ impl MathRun { pub fn into_fragment(self, ctx: &MathContext, styles: StyleChain) -> MathFragment { if self.0.len() == 1 { - self.0.into_iter().next().unwrap() - } else { - FrameFragment::new(ctx, styles, self.into_frame(ctx, styles)).into() + return self.0.into_iter().next().unwrap(); } + + // Fragments without a math_size are ignored: the notion of size do not + // apply to them, so their text-likeness is meaningless. + let text_like = self + .iter() + .filter(|e| e.math_size().is_some()) + .all(|e| e.is_text_like()); + + FrameFragment::new(ctx, styles, self.into_frame(ctx, styles)) + .with_text_like(text_like) + .into() } /// Returns a builder that lays out the [`MathFragment`]s into a possibly diff --git a/tests/ref/math-attach-horizontal-align.png b/tests/ref/math-attach-horizontal-align.png index 507cb0ffda282b96b37398a6dd3ac0a4f3b0cbcf..8aa2a376715c2799a726202d820972b84cb78907 100644 GIT binary patch delta 1885 zcmV-j2cr1V59|(*B!4qWL_t(|+U?idPn+i*!0}%157^EAfo+;JRob+5yXjr3wwtuA zIV@T?wP~m_CnPKjgp{HbUJ>FzIK+@Z2nokPAmvb?90Mc-6OQI+Ae_v}5HJQCV|@H> ze#9$dlvxM{T-4tS`?X(NAL-T4&-?ftj;MD6Z~zX#0r+5HeSaT54d#NJ3Q?H}0M~gN zz$)HW_W-XTnrbY+iBAfU`v8%t08z1kOPt8LN_GL8>e}i6MjATC0Q-lDApn!TYk$P2 zU^G=VGXR8uC=D;X5$nG9qHGC(fdaYLNw%(VZ~@X zqI3{d&U#@kxqoshY+oWeZ~cPPR{t<}2F$kPS#&qdVN(fkoKrNGLTi_9UAZRUk-G>u z`pGCIO@NN}JOD=~OOglHB|wNPx0z3@32%Vlfp55CeUp>&WeRs1*-Z+>wy z8HbX=-yRR=fgX_rj2v4GihROMHt-N6`C1DYiS>`Wa(|Uo4=EUj!DDM-nvbkV{$Lvp zJg~y*1_?f};DG^rVCXF(Z9u@tZ>jr{VIa5Jihu#E7dQOEBBzwI6r~6K=cA|q^*J-Z zW>_TvQ=jVqoMlblxl6Cv0Q%1u0LWSWqQ`aK=J_I|6@opo2$3o7<$pgESh)i5QNiwe z{Qy*#0)JqOp8;S(iKw;&Ia8DcfOHLD1qMBS?tM$v|!m0I4gm6oa}!UaBQllR$#r#SjojEX~7kG{o!%Mj>G1uMzNlTWq5b z+?Rn2zX?LXjjs_Ql`6bEA;eXun(k4>~TG{J_K7 z*m;1I-fT5sIpO*f0PB%1L=^xYxFt7X1W^_~cd0x8f;q!b0sw9Kj~h4x;0Jc5cL+F? z%YU1I{?uj=fb)^FAJLi&FC>)tZWicM`@m;;9km7DY3D5dVM$pz0?=4p46yND#tcB( zpZ`+F_VT})J|0y7fUSV7(!B8KXUi!?Hx~iOyt#U#^P_41@Xp-)uariv;9I6czz4j5 z*q$}%@NKpim`(~TnF08R1XpAp65N^rY=5%V2FBDwfj2+`Ud#ek;PD~BDgQFaurVn~ zz)i}H1A{y2LF78;MiXKbFkJ#Z*x`~u^gI#KLBjPv3%`B)wleJG&-1--bn9MVW2l3- zwQ^aUz2c-V27tAYP2l4X5eBrXraQ+f6Y@eJm?E*Hv^a~p!WSCB!Kb^*y}+XJz62w{n1X+b*Qp)i zb-0n-&mh|F#`|QxiMo2L`CWZGUBZkFwCe zg4yfi_#}o4s1V~nAG+mUwzua1P?)$K&eL^;9>mIJd0&;0k&zao`0*wAN<^r)!;tml zAmHhIxj7j=;qZZVIR}1|=s#-!9G|gT?1fh%Tk;PY7L`=iuo>lrJCl;SJn+o)0mF5r zfCM{#P92A`1Bq}V8eRiY8-K>bfDNs~?97;QMB7_+`9|QML;&7%)v8_)*;7=GLxivC zHv!P5TNb`vKSVeZL>GDtD31VMz5qu87iH}aHsrCD`{@F{>B>OPq1^!o_w5gMNB}I2 zLX8?M8cPu(wOc;t3U@(#`SRC3@NXRZgOLywxp=0A0yu-nS}vb+7Jq{M0_1InSAWIc z;q^}Kv^#7A08J-lwUB}BOgGv&7P*fj)GouL*LT3RLHmFk66&i~dnN#|-J!dOu~M4j zYOAWQ52;9UEcDFDaGh!gtW4hrjKb-bhl`u;;sv^EQO?}e6h2SwM`h8KA;Vck9(be} z89v>;cX;~yQ)9yp34baUVCs8n)Yvn4IC&Y>#3zs{hAZ1H;J6kKjD!ro>)1Q&>N6~Y z1h0pn%p`^@2ZkIpHGd@zC1_X|aM+?Ryzx8s4vRd))eELZD6NGNEkJs?nU~9AxO%}E zW1frzdkp4a^A_BGe!uXlW&*$tboYn@kvLe;%mFZHDe4F3Fjgy*#w|GW5gf^q89aVO z@LxF+*s2dlhBvzo5x%NiL5B6=hXvob)&?Sb#HUt}j%KrWbnx8MvB17M00-az{D1F1 XoEJ~MyM`}#8;it8@V5rE&DwjnAQK=jP*gJlz$ zh^`{l@G<~%%zs@{Z$F6Wko9x+Si{47Gh(*qzGU{iIcX*fcCedvHFOWmt|?3c9=|NX zsZ|rGI)GXF$^e|#(aXK?Y!)QAOFQ|iHR}~{WaJzttPjh>!KwQ-L#Ur}GcR)rIj?@X zD;KANr@!5CiffCAf#&2(SVp`Iu+uxd~FU=ka&H{QF?KIx!@IDGy8Y zJp6kU+r4&>BQoa#rFQ`+cmw)0dRhS`fSi>akb`zP0mKOX0*s|XS|l(s0ULjVfD7&E z?IG}!B0v1G`2?L+2+m*xN&;o+s`a(^WSd5p+v9PNpgwYa}$y3#8cbzLi& zkDmgd^Mu6_z*7j%;}F`{aiAsNycj;nh`yRP;N zIDe7jHehkL?SPp6munc2$pLYW z-azba9kYb?QVZyEVq0bm{xQMz*Eb37E`I`SAhnLMAo{R?kON&W0V`0kNpSvG1|Mw9 z$q{geVm)ebe+yFXcn?DfMgcP*;OH(cIa2qt5k(Vj`T40!mo6!icKxC}0H=1p6}Y+{ zKvfHX@q~*fX#kiKY`{k!A{zS`!!bLeICB8D7aukLKzObgj=f&G23a;xI6w|F`+u9r zwgCE{Wg(h3El#Zg;QAE-*fBk``hUPOys)X;4-P{*NiF4JsW#ehY!b;@V^2 zmKrIAhLGMf1;I-T3k&WraAu)ulHociINpUEfSNIKiYvwd>hBPooc`Uz%$6c*s30Ww zuZjGp7J1?FnKK|~My1l%jD_7OeDKgkL^cajqkjvAU)@cg`ChnM>DG69SbzT7OF9eG z{LjSV0_rpDz(OLDd)(1i0zhSUFkCh}kQj}f%d#!6sHjMpCj03zkOOeGCm4pCRndTT z<-X>9@J?q4JX;d=o5b*b1K|9WmFfWeCRtM+ZCIpgxQi_%06)sf8Suh$x+ufVYC!(& zzf=FFoIuXH5RI9T>czMjuz#U@jEx!dx9E7=Q@0*_l8C`?qr$v5NWI9ry_xXwMMTZF zor_7E2`3}6oNR&QEr6?Qh#K7cAPjtPK2q3KW#HS@&G4PM<@htqZ-BQEDU4c+sNXU0 z1b~&N@URi72LMbhO?L#|%cW5b*mJ8UF9*Z=_alevC0O4^TNxs?jeo~JxDbFURud3C zp2U>s252%sb2XF&!>Yu{;9i}U3Jn0o4$|%|IqtK}Y%`rzh`L7rMtW(@o;c1#8=u@L1C(Zx2fyyQ-kCc_ z27nJfIl2LU|3ySF#eWYxu1W&{>2R1YkP#2*)J+5Cj*b%@8X&{PA-;4Uw|@!g!SKgM zFWjl|WA73XT)uZwgIdc}J6wi~HTO8(dWn?^X>mEH3kIfjK>H|}BRQOzI|u~;6iM(O z@xl&;51WsR41TuyTr4hkJq(Rt)bZlcS2|i2eTz$p$XbJA6n}G9ZUf8rc#s9P61-Qy zPCwjmCNfx5p+Ua@-0=YYDh<}V&{+v!sX^+#5qN#AL5Rpf3r)QfmUE!bN0;bC!p^HB zhdqN@L~b`y4nRBTAvi~1AYfo^`kBfAD|5`hupwc4UPAP?%)x(}2q}PV2iSs}gz1Z| zfvx4u{;_^y^E_bNQ3ZZ@Ds{8qeWzc05RIQs0_0nA*xN!_t9lbVSI6KO9E0CZ{{h5^ VH(293FKhq+002ovPDHLkV1lkltnB~* diff --git a/tests/ref/math-attach-to-group.png b/tests/ref/math-attach-to-group.png index a3d1923eb0c83bc5a01a929a2f70af5cef36de30..ea4115fceadcf5245cab765fd9b465b7549513cf 100644 GIT binary patch delta 624 zcmV-$0+0QR1&IZaB!5szL_t(|+U?ifOH*+i$8rCKAiC?KZi5JjQoVkz8X9(&0ikgu z-rYx7?u-lC+u8<<3`dW9Vd6$9GM#-S4ZXm~aKjsbkMb}jsy2UpvN;S4$BM}oe)H^63{;f8=Vy$;7`s8vbwwUE$$ z0fZ5Il!ST`#%#*~%Y{0_Y71!sPmpmQL1=PXi;?=2D5&{}em7hSS!kx45 zTqV%o-TexQu}kDEOzQv}2^@>$Fib}binJn0000< KMNUMnLSTYFd?~vC delta 626 zcmV-&0*(EN1&alcB!5y#L_t(|+U?iROVe>2$8rCKAi8y^YY>5S=@j&fJaniCp@M1> zD0Ju&hCxvI)e=oe%2p`Yq*`hkQdyvsrj@B{uF{N@hAy`DBbVEE(DrN}Cf~8~+29-B zv54>E^twHHe-2;xmQW-mX4njyVKe+shdb>>3NP4-6rMLa9Dg7eKalp~iE3!rT?K?E zk$8Oz!PAqpJZ85K85xcr^uhZ}mB{oAkT(1jBf~8(0p7{ORH)kgHDz-c7*3RvFZ|#R z8ei+Of#LqfykWIWn_)|}%fN7%J8zf@r`|7DBJ*8>z|E_V2FX&2;J>l=6H?Y@y{gT}gg|Ks{b4NG7k{nI zeQ{(dDVT9_!!Ud9{Z04;nqb?m8R+Yig&XRSKlSC&#Z7L2wK~HsA#M5`jt|hNk{0M7 zrRx+3qhoPWnn{_kc>p}6I>TxQ8AA7waqdHCb6U%hdV5%Bm^m+y_6n)!7KCjJtXn~* z>!OaZ9&1~F1H@U{jMRnh0)^`zgK%;NsqN7Mg}uiRtY5xGkZ_%)Kw&Ilp!G=2A!N?E z3l^TQ1qOS2pCd7GhJuCZ93q#{foK84bSFS=AYlG2nqf0+hX23tA9ASvzzXEx3IG5A M07*qoM6N<$f~CbUQ2+n{ diff --git a/tests/ref/math-root-large-body.png b/tests/ref/math-root-large-body.png index 3dd4d848e412b9469e08e70d42e0982f91592a9d..b8d9edfbdc956975e564aca682eba207c2ee27ab 100644 GIT binary patch delta 1545 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 IM6N<$g5-z=82|tP diff --git a/tests/suite/math/attach.typ b/tests/suite/math/attach.typ index c9510c6a5..c5ca57357 100644 --- a/tests/suite/math/attach.typ +++ b/tests/suite/math/attach.typ @@ -84,9 +84,9 @@ $ (-1)^n + (1/2 + 3)^(-1/2) $ // Test that the attachments are aligned horizontally. $ x_1 p_1 frak(p)_1 2_1 dot_1 lg_1 !_1 \\_1 ]_1 "ip"_1 op("iq")_1 \ x^1 b^1 frak(b)^1 2^1 dot^1 lg^1 !^1 \\^1 ]^1 "ib"^1 op("id")^1 \ - x_1 y_1 "_"_1 x^1 l^1 "`"^1 attach(I,tl:1,bl:1,tr:1,br:1) + "_"_1 "`"^1 x_1 y_1 x^1 l^1 attach(I,tl:1,bl:1,tr:1,br:1) scripts(sum)_1^1 integral_1^1 abs(1/2)_1^1 \ - x^1_1, "("b y")"^1_1 != (b y)^1_1, "[∫]"_1 [integral]_1 $ + x^1_1, ")"^1_1 (b y)^1_1, "[∫]"_1 [integral]_1 $ --- math-attach-limit --- // Test limit. From f3863f14aff07ec2d0260e0edf9fe3f4f7205d8e Mon Sep 17 00:00:00 2001 From: Leedehai <18319900+Leedehai@users.noreply.github.com> Date: Mon, 15 Jul 2024 06:00:37 -0400 Subject: [PATCH 34/34] Remove an unnecessary mutable specifier (#4557) --- crates/typst/src/math/attach.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/typst/src/math/attach.rs b/crates/typst/src/math/attach.rs index 4d5bf2994..035b78125 100644 --- a/crates/typst/src/math/attach.rs +++ b/crates/typst/src/math/attach.rs @@ -350,7 +350,7 @@ fn layout_attachments( } fn attach_top_and_bottom( - ctx: &mut MathContext, + ctx: &MathContext, styles: StyleChain, base: MathFragment, t: Option,
  2. p@w4C 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 B&RPHf diff --git a/tests/ref/image-rgba-png-and-jpeg.png b/tests/ref/image-rgba-png-and-jpeg.png index 601271705e0029bfb97bd54076d487316f974bc2..083722819839f4778dff1897eb6e0743cb5b7879 100644 GIT binary patch delta 6571 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 delta 6589 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 diff --git a/tests/ref/image-sizing.png b/tests/ref/image-sizing.png index 7419de141b01f438e6103c7c5b6f9f9c67b49c1c..e1201cf223d71d39a065b655098bd1e564c8dd08 100644 GIT binary patch literal 8662 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 z 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 literal 8625 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 literal 11851 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 QJOBUy07*qoM6N<$g3jd1-~a#s diff --git a/tests/ref/place-basic.png b/tests/ref/place-basic.png index 07642c347d8da5bc8a57d42833a769422c0f6355..07845b7dd5d5a91186d43c1984d704c5770be80c 100644 GIT binary patch delta 10761 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 M5(?s#qDBG#3;G0DXaE2J diff --git a/tests/ref/place-float-figure.png b/tests/ref/place-float-figure.png index 5411178a0aedf6d92e38fa215fcf8506ce24f00d..be2071542de06aa67d89b0ec352385e1b42ad5cc 100644 GIT binary patch delta 29163 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