diff --git a/crates/render/src/morph_text.rs b/crates/render/src/morph_text.rs index 391ec0ba3..19c83cdb2 100644 --- a/crates/render/src/morph_text.rs +++ b/crates/render/src/morph_text.rs @@ -76,16 +76,12 @@ fn tokens(l: &TextLayout, chars: bool) -> (Vec, Vec<(usize, usize)>) { /// For each token of `b`, the token of `a` with the same text, in reading order, each used once. fn matches(a: &[Token], b: &[Token]) -> Vec> { - let mut used = vec![false; a.len()]; - b.iter() - .map(|tb| { - let i = a.iter().enumerate().position(|(i, ta)| !used.get(i).copied().unwrap_or(true) && ta.key == tb.key)?; - if let Some(u) = used.get_mut(i) { - *u = true; - } - Some(i) - }) - .collect() + // Unused tokens of `a` by text, in reading order, so each lookup is a pop rather than a scan. + let mut free: std::collections::HashMap<&str, std::collections::VecDeque> = std::collections::HashMap::new(); + for (i, ta) in a.iter().enumerate() { + free.entry(ta.key.as_str()).or_default().push_back(i); + } + b.iter().map(|tb| free.get_mut(tb.key.as_str())?.pop_front()).collect() } fn layout(ctx: &Ctx, s: &Shape, x: Xfrm, fields: &dyn Fields) -> Option { @@ -228,3 +224,19 @@ pub(crate) fn draw( pub(crate) fn token_keys(l: &TextLayout, chars: bool) -> Vec { tokens(l, chars).0.into_iter().map(|t| t.key).collect() } + +#[cfg(test)] +mod match_tests { + use super::*; + + fn tok(k: &str) -> Token { + Token { key: k.into(), glyphs: Vec::new(), origin: Point::ZERO, run: 0 } + } + + #[test] + fn each_source_token_is_used_once_in_reading_order() { + let a: Vec = ["a", "b", "a"].map(tok).into(); + let b: Vec = ["a", "a", "a", "b", "c"].map(tok).into(); + assert_eq!(matches(&a, &b), vec![Some(0), Some(2), None, Some(1), None]); + } +} diff --git a/crates/text/src/run.rs b/crates/text/src/run.rs index d7e9dbeea..56e6ead7c 100644 --- a/crates/text/src/run.rs +++ b/crates/text/src/run.rs @@ -22,7 +22,7 @@ pub struct GlyphRun { pub link: bool, /// Opacity of the fill on top of `color`'s own alpha (0 for no fill). pub alpha: f64, - pub gradient: Option, + pub gradient: Option>, /// Shadow and glow behind the glyphs (`a:rPr/a:effectLst`). pub effects: Option, /// East Asian vertical text (`eaVert`): each glyph stands upright, turned a quarter turn diff --git a/crates/text/src/style.rs b/crates/text/src/style.rs index e1289c520..b61359faf 100644 --- a/crates/text/src/style.rs +++ b/crates/text/src/style.rs @@ -46,7 +46,7 @@ pub(crate) struct Style { pub(crate) highlight: Option, pub(crate) outline: Option<(Rgba, f64)>, pub(crate) link: bool, - pub(crate) gradient: Option, + pub(crate) gradient: Option>, pub(crate) effects: Option, /// "Regular", "Bold"… for the slot and fallback faces. pub(crate) style_name: &'static str, @@ -136,7 +136,7 @@ pub(crate) fn make_style(ctx: &Ctx, r: &RunProps, scale: f64, prompt: Option (Rgba::TRANSPARENT, 0.0, None), _ => (resolve::text_color(ctx, r), 1.0, None),