From 58b4bdb81193c26c121d7f834365640b3e94da85 Mon Sep 17 00:00:00 2001 From: +merlan #flirora Date: Sat, 15 Feb 2025 00:50:02 -0500 Subject: [PATCH] Extract Run struct --- crates/typst-layout/src/inline/line.rs | 9 +++--- crates/typst-layout/src/inline/linebreak.rs | 22 +++++++------- crates/typst-layout/src/inline/prepare.rs | 32 ++++++++++++--------- crates/typst-layout/src/inline/shaping.rs | 5 ++-- 4 files changed, 38 insertions(+), 30 deletions(-) diff --git a/crates/typst-layout/src/inline/line.rs b/crates/typst-layout/src/inline/line.rs index 6d0d463be..2e0e537cd 100644 --- a/crates/typst-layout/src/inline/line.rs +++ b/crates/typst-layout/src/inline/line.rs @@ -270,12 +270,13 @@ fn collect_range<'a>( items: &mut Items<'a>, fallback: &mut Option>, ) { - for (subrange, item) in p.slice(range.clone()) { + for run 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); + let Item::Text(shaped) = &run.item else { + items.push(&run.item); continue; }; + let subrange = &run.range; // The intersection range of the item, the subrange, and the line's // trimming. @@ -296,7 +297,7 @@ fn collect_range<'a>( items.push(Item::Text(reshaped)); } else { // When the item is fully contained, just keep it. - items.push(item); + items.push(&run.item); } } } diff --git a/crates/typst-layout/src/inline/linebreak.rs b/crates/typst-layout/src/inline/linebreak.rs index 31512604f..5e900da0d 100644 --- a/crates/typst-layout/src/inline/linebreak.rs +++ b/crates/typst-layout/src/inline/linebreak.rs @@ -823,8 +823,8 @@ fn linebreak_link(link: &str, mut f: impl FnMut(usize)) { /// Whether hyphenation is enabled at the given offset. fn hyphenate_at(p: &Preparation, offset: usize) -> bool { p.config.hyphenate.unwrap_or_else(|| { - let (_, item) = p.get(offset); - match item.text() { + let run = p.get(offset); + match run.item.text() { Some(text) => TextElem::hyphenate_in(text.styles).unwrap_or(p.config.justify), None => false, } @@ -834,8 +834,8 @@ 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.config.lang.or_else(|| { - let (_, item) = p.get(offset); - let styles = item.text()?.styles; + let run = p.get(offset); + let styles = run.item.text()?.styles; Some(TextElem::lang_in(styles)) })?; @@ -900,8 +900,8 @@ impl Estimates { let mut shrinkability = CumulativeVec::with_capacity(cap); let mut justifiables = CumulativeVec::with_capacity(cap); - for (range, item) in p.items.iter() { - if let Item::Text(shaped) = item { + for run in p.items.iter() { + if let Item::Text(shaped) = &run.item { for g in shaped.glyphs.iter() { let byte_len = g.range.len(); let stretch = g.stretchability().0 + g.stretchability().1; @@ -912,13 +912,13 @@ impl Estimates { justifiables.push(byte_len, g.is_justifiable() as usize); } } else { - widths.push(range.len(), item.natural_width()); + widths.push(run.range.len(), run.item.natural_width()); } - widths.adjust(range.end); - stretchability.adjust(range.end); - shrinkability.adjust(range.end); - justifiables.adjust(range.end); + widths.adjust(run.range.end); + stretchability.adjust(run.range.end); + shrinkability.adjust(run.range.end); + justifiables.adjust(run.range.end); } Self { diff --git a/crates/typst-layout/src/inline/prepare.rs b/crates/typst-layout/src/inline/prepare.rs index 5d7fcd7cb..e7bef812c 100644 --- a/crates/typst-layout/src/inline/prepare.rs +++ b/crates/typst-layout/src/inline/prepare.rs @@ -3,6 +3,12 @@ use unicode_bidi::{BidiInfo, Level as BidiLevel}; use super::*; +#[derive(Debug)] +pub struct Run<'a> { + pub item: Item<'a>, + pub range: Range, +} + /// A representation in which children are already layouted and text is already /// preshaped. /// @@ -20,7 +26,7 @@ pub struct Preparation<'a> { /// direction). pub bidi: Option>, /// Text runs, spacing and layouted elements. - pub items: Vec<(Range, Item<'a>)>, + pub items: Vec>, /// Maps from byte indices to item indices. pub indices: Vec, /// The span mapper. @@ -29,13 +35,13 @@ pub struct Preparation<'a> { impl<'a> Preparation<'a> { /// Get the item that contains the given `text_offset`. - pub fn get(&self, offset: usize) -> &(Range, Item<'a>) { + pub fn get(&self, offset: usize) -> &Run { let idx = self.indices.get(offset).copied().unwrap_or(0); &self.items[idx] } /// Iterate over the items that intersect the given `sliced` range. - pub fn slice(&self, sliced: Range) -> impl Iterator)> { + pub fn slice(&self, sliced: Range) -> impl Iterator { // Usually, we don't want empty-range items at the start of the line // (because they will be part of the previous line), but for the first // line, we need to keep them. @@ -43,8 +49,8 @@ impl<'a> Preparation<'a> { 0 => 0, n => self.indices.get(n).copied().unwrap_or(0), }; - self.items[start..].iter().take_while(move |(range, _)| { - range.start < sliced.end || range.end <= sliced.end + self.items[start..].iter().take_while(move |run| { + run.range.start < sliced.end || run.range.end <= sliced.end }) } } @@ -84,7 +90,7 @@ pub fn prepare<'a>( Segment::Text(_, styles) => { shape_range(&mut items, engine, text, &bidi, range, styles); } - Segment::Item(item) => items.push((range, item)), + Segment::Item(item) => items.push(Run { range, item }), } cursor = end; @@ -92,8 +98,8 @@ pub fn prepare<'a>( // 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)); + for (i, run) in items.iter().enumerate() { + indices.extend(run.range.clone().map(|_| i)); } if config.cjk_latin_spacing { @@ -113,15 +119,15 @@ 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 [(Range, Item)]) { +fn add_cjk_latin_spacing(items: &mut [Run]) { let mut items = items .iter_mut() - .filter(|(_, x)| !matches!(x, Item::Tag(_))) + .filter(|run| !matches!(run.item, Item::Tag(_))) .peekable(); let mut prev: Option<&ShapedGlyph> = None; - while let Some((_, item)) = items.next() { - let Some(text) = item.text_mut() else { + while let Some(run) = items.next() { + let Some(text) = run.item.text_mut() else { prev = None; continue; }; @@ -135,7 +141,7 @@ fn add_cjk_latin_spacing(items: &mut [(Range, Item)]) { let next = glyphs.peek().map(|n| n as _).or_else(|| { items .peek() - .and_then(|(_, i)| i.text()) + .and_then(|run| run.item.text()) .and_then(|shaped| shaped.glyphs.first()) }); diff --git a/crates/typst-layout/src/inline/shaping.rs b/crates/typst-layout/src/inline/shaping.rs index 159619eb3..3f8bf140e 100644 --- a/crates/typst-layout/src/inline/shaping.rs +++ b/crates/typst-layout/src/inline/shaping.rs @@ -19,6 +19,7 @@ use typst_utils::SliceExt; use unicode_bidi::{BidiInfo, Level as BidiLevel}; use unicode_script::{Script, UnicodeScript}; +use super::prepare::Run; use super::{decorate, Item, Range, SpanMapper}; use crate::modifiers::{FrameModifiers, FrameModify}; @@ -592,7 +593,7 @@ 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<(Range, Item<'a>)>, + items: &mut Vec>, engine: &Engine, text: &'a str, bidi: &BidiInfo<'a>, @@ -606,7 +607,7 @@ pub fn shape_range<'a>( let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL }; let shaped = shape(engine, range.start, &text[range.clone()], styles, dir, lang, region); - items.push((range, Item::Text(shaped))); + items.push(Run { range, item: Item::Text(shaped) }); }; let mut prev_level = BidiLevel::ltr();