From f11d28e8eee73e47288bf641430a5edde464f299 Mon Sep 17 00:00:00 2001 From: +merlan #flirora Date: Wed, 5 Feb 2025 13:25:52 -0500 Subject: [PATCH 1/9] Add failing test for #5775 --- tests/suite/model/cite.typ | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/suite/model/cite.typ b/tests/suite/model/cite.typ index b328dda49..8c9748150 100644 --- a/tests/suite/model/cite.typ +++ b/tests/suite/model/cite.typ @@ -147,3 +147,13 @@ B #cite() #cite(). // Error: 7-17 expected label, found string // Hint: 7-17 use `label("%@&#*!\\")` to create a label #cite("%@&#*!\\") + +--- issue-5775-cite-order-rtl --- +// Test citation order in RTL text. +#set page(width: 300pt) +این است +@tolkien54 +و این یکی هست +@arrgh + +#bibliography("/assets/bib/works.bib") From 6ceb25db8287aa737c2f1f7ce668d5c5ea283b59 Mon Sep 17 00:00:00 2001 From: +merlan #flirora Date: Thu, 6 Feb 2025 12:59:48 -0500 Subject: [PATCH 2/9] Add minor fixes to doc-comments --- crates/typst-layout/src/inline/line.rs | 4 ++++ crates/typst-library/src/model/bibliography.rs | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/typst-layout/src/inline/line.rs b/crates/typst-layout/src/inline/line.rs index 659d33f4a..6d0d463be 100644 --- a/crates/typst-layout/src/inline/line.rs +++ b/crates/typst-layout/src/inline/line.rs @@ -698,6 +698,10 @@ impl Debug for Items<'_> { } /// A reference to or a boxed item. +/// +/// This is conceptually similar to a [`Cow<'a, Item<'a>>`][std::borrow::Cow], +/// but we box owned items since an [`Item`] is much bigger than +/// a box. pub enum ItemEntry<'a> { Ref(&'a Item<'a>), Box(Box>), diff --git a/crates/typst-library/src/model/bibliography.rs b/crates/typst-library/src/model/bibliography.rs index a391e5804..4ea7b535d 100644 --- a/crates/typst-library/src/model/bibliography.rs +++ b/crates/typst-library/src/model/bibliography.rs @@ -571,7 +571,7 @@ impl Works { /// Context for generating the bibliography. struct Generator<'a> { - /// The routines that is used to evaluate mathematical material in citations. + /// The routines that are used to evaluate mathematical material in citations. routines: &'a Routines, /// The world that is used to evaluate mathematical material in citations. world: Tracked<'a, dyn World + 'a>, @@ -588,7 +588,7 @@ struct Generator<'a> { /// Details about a group of merged citations. All citations are put into groups /// of adjacent ones (e.g., `@foo @bar` will merge into a group of length two). -/// Even single citations will be put into groups of length ones. +/// Even single citations will be put into groups of length one. struct GroupInfo { /// The group's location. location: Location, From 58b4bdb81193c26c121d7f834365640b3e94da85 Mon Sep 17 00:00:00 2001 From: +merlan #flirora Date: Sat, 15 Feb 2025 00:50:02 -0500 Subject: [PATCH 3/9] 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(); From 662bccc42d4a6ebfb6ff3a75b34900aedd414064 Mon Sep 17 00:00:00 2001 From: +merlan #flirora Date: Sat, 15 Feb 2025 01:00:00 -0500 Subject: [PATCH 4/9] Add `idx` field to `Run` --- crates/typst-layout/src/inline/prepare.rs | 18 ++++++++++++++++-- crates/typst-layout/src/inline/shaping.rs | 4 +++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/crates/typst-layout/src/inline/prepare.rs b/crates/typst-layout/src/inline/prepare.rs index e7bef812c..85461c45a 100644 --- a/crates/typst-layout/src/inline/prepare.rs +++ b/crates/typst-layout/src/inline/prepare.rs @@ -7,6 +7,7 @@ use super::*; pub struct Run<'a> { pub item: Item<'a>, pub range: Range, + pub idx: usize, } /// A representation in which children are already layouted and text is already @@ -79,6 +80,7 @@ pub fn prepare<'a>( let mut cursor = 0; let mut items = Vec::with_capacity(segments.len()); + let mut next_idx = 0; // Shape the text to finalize the items. for segment in segments { @@ -88,9 +90,20 @@ pub fn prepare<'a>( match segment { Segment::Text(_, styles) => { - shape_range(&mut items, engine, text, &bidi, range, styles); + shape_range( + &mut items, + engine, + text, + &bidi, + range, + styles, + &mut next_idx, + ); + } + Segment::Item(item) => { + items.push(Run { range, item, idx: next_idx }); + next_idx += 1; } - Segment::Item(item) => items.push(Run { range, item }), } cursor = end; @@ -105,6 +118,7 @@ pub fn prepare<'a>( if config.cjk_latin_spacing { add_cjk_latin_spacing(&mut items); } + dbg!(&items); Ok(Preparation { config, diff --git a/crates/typst-layout/src/inline/shaping.rs b/crates/typst-layout/src/inline/shaping.rs index 3f8bf140e..78c5789a0 100644 --- a/crates/typst-layout/src/inline/shaping.rs +++ b/crates/typst-layout/src/inline/shaping.rs @@ -599,6 +599,7 @@ pub fn shape_range<'a>( bidi: &BidiInfo<'a>, range: Range, styles: StyleChain<'a>, + next_idx: &mut usize, ) { let script = TextElem::script_in(styles); let lang = TextElem::lang_in(styles); @@ -607,7 +608,8 @@ 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(Run { range, item: Item::Text(shaped) }); + items.push(Run { range, item: Item::Text(shaped), idx: *next_idx }); + *next_idx += 1; }; let mut prev_level = BidiLevel::ltr(); From fa76ea63106a36d46abe5050b3017cbdf5845497 Mon Sep 17 00:00:00 2001 From: +merlan #flirora Date: Sat, 15 Feb 2025 01:03:38 -0500 Subject: [PATCH 5/9] Associate each item in Items with a logical index --- crates/typst-layout/src/inline/line.rs | 41 ++++++++++++------- crates/typst-layout/src/inline/linebreak.rs | 4 +- crates/typst-layout/src/inline/prepare.rs | 1 - .../typst-library/src/model/bibliography.rs | 1 + 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/crates/typst-layout/src/inline/line.rs b/crates/typst-layout/src/inline/line.rs index 2e0e537cd..7ba1e5e2a 100644 --- a/crates/typst-layout/src/inline/line.rs +++ b/crates/typst-layout/src/inline/line.rs @@ -26,6 +26,7 @@ const LINE_SEPARATOR: char = '\u{2028}'; // We use LS to distinguish justified b /// first and last one since they may be broken apart by the start or end of the /// line, respectively. But even those can partially reuse previous results when /// the break index is safe-to-break per rustybuzz. +#[derive(Debug)] pub struct Line<'a> { /// The items the line is made of. pub items: Items<'a>, @@ -219,7 +220,7 @@ fn collect_items<'a>( // 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.push(fallback, usize::MAX); } } @@ -273,7 +274,7 @@ fn collect_range<'a>( for run in p.slice(range.clone()) { // All non-text items are just kept, they can't be split. let Item::Text(shaped) = &run.item else { - items.push(&run.item); + items.push(&run.item, run.idx); continue; }; let subrange = &run.range; @@ -294,10 +295,10 @@ fn collect_range<'a>( } else if split { // When the item is split in half, reshape it. let reshaped = shaped.reshape(engine, sliced); - items.push(Item::Text(reshaped)); + items.push(Item::Text(reshaped), run.idx); } else { // When the item is fully contained, just keep it. - items.push(&run.item); + items.push(&run.item, run.idx); } } } @@ -628,7 +629,7 @@ fn overhang(c: char) -> f64 { } /// A collection of owned or borrowed inline items. -pub struct Items<'a>(Vec>); +pub struct Items<'a>(Vec>); impl<'a> Items<'a> { /// Create empty items. @@ -637,33 +638,33 @@ impl<'a> Items<'a> { } /// Push a new item. - pub fn push(&mut self, entry: impl Into>) { - self.0.push(entry.into()); + pub fn push(&mut self, entry: impl Into>, idx: usize) { + self.0.push(IndexedItemEntry { item: entry.into(), idx }); } /// Iterate over the items pub fn iter(&self) -> impl Iterator> { - self.0.iter().map(|item| &**item) + self.0.iter().map(|item| &*item.item) } /// Access the first item. pub fn first(&self) -> Option<&Item<'a>> { - self.0.first().map(|item| &**item) + self.0.first().map(|item| &*item.item) } /// Access the last item. pub fn last(&self) -> Option<&Item<'a>> { - self.0.last().map(|item| &**item) + self.0.last().map(|item| &*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() + self.0.first_mut()?.item.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() + self.0.last_mut()?.item.text_mut() } /// Reorder the items starting at the given index to RTL. @@ -674,12 +675,17 @@ impl<'a> Items<'a> { impl<'a> FromIterator> for Items<'a> { fn from_iter>>(iter: I) -> Self { - Self(iter.into_iter().collect()) + Self( + iter.into_iter() + .enumerate() + .map(|(idx, item)| IndexedItemEntry { item, idx }) + .collect(), + ) } } impl<'a> Deref for Items<'a> { - type Target = Vec>; + type Target = Vec>; fn deref(&self) -> &Self::Target { &self.0 @@ -698,6 +704,13 @@ impl Debug for Items<'_> { } } +/// An item accompanied by its position within a line. +#[derive(Debug)] +pub struct IndexedItemEntry<'a> { + pub item: ItemEntry<'a>, + pub idx: usize, +} + /// A reference to or a boxed item. /// /// This is conceptually similar to a [`Cow<'a, Item<'a>>`][std::borrow::Cow], diff --git a/crates/typst-layout/src/inline/linebreak.rs b/crates/typst-layout/src/inline/linebreak.rs index 5e900da0d..14ff37937 100644 --- a/crates/typst-layout/src/inline/linebreak.rs +++ b/crates/typst-layout/src/inline/linebreak.rs @@ -110,10 +110,10 @@ pub fn linebreak<'a>( p: &'a Preparation<'a>, width: Abs, ) -> Vec> { - match p.config.linebreaks { + dbg!(match p.config.linebreaks { Linebreaks::Simple => linebreak_simple(engine, p, width), Linebreaks::Optimized => linebreak_optimized(engine, p, width), - } + }) } /// Performs line breaking in simple first-fit style. This means that we build diff --git a/crates/typst-layout/src/inline/prepare.rs b/crates/typst-layout/src/inline/prepare.rs index 85461c45a..aec90c501 100644 --- a/crates/typst-layout/src/inline/prepare.rs +++ b/crates/typst-layout/src/inline/prepare.rs @@ -118,7 +118,6 @@ pub fn prepare<'a>( if config.cjk_latin_spacing { add_cjk_latin_spacing(&mut items); } - dbg!(&items); Ok(Preparation { config, diff --git a/crates/typst-library/src/model/bibliography.rs b/crates/typst-library/src/model/bibliography.rs index 4ea7b535d..a1a7363fe 100644 --- a/crates/typst-library/src/model/bibliography.rs +++ b/crates/typst-library/src/model/bibliography.rs @@ -539,6 +539,7 @@ impl IntoValue for CslSource { /// memoization) for the whole document. This setup is necessary because /// citation formatting is inherently stateful and we need access to all /// citations to do it. +#[derive(Debug)] pub(super) struct Works { /// Maps from the location of a citation group to its rendered content. pub citations: HashMap>, From 7e69ee0942551904e82418d141b0fce4c86819fe Mon Sep 17 00:00:00 2001 From: +merlan #flirora Date: Tue, 18 Feb 2025 15:37:36 -0500 Subject: [PATCH 6/9] Sort frame items by logical index --- crates/typst-layout/src/inline/line.rs | 24 +++++++++++++++--------- tests/ref/issue-5775-cite-order-rtl.png | Bin 0 -> 8218 bytes 2 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 tests/ref/issue-5775-cite-order-rtl.png diff --git a/crates/typst-layout/src/inline/line.rs b/crates/typst-layout/src/inline/line.rs index 7ba1e5e2a..7fcc344f7 100644 --- a/crates/typst-layout/src/inline/line.rs +++ b/crates/typst-layout/src/inline/line.rs @@ -501,16 +501,16 @@ pub fn commit( // Build the frames and determine the height and baseline. let mut frames = vec![]; - for item in line.items.iter() { - let mut push = |offset: &mut Abs, frame: Frame| { + for item in line.items.indexed_iter() { + let mut push = |offset: &mut Abs, frame: Frame, idx: usize| { let width = frame.width(); top.set_max(frame.baseline()); bottom.set_max(frame.size().y - frame.baseline()); - frames.push((*offset, frame)); + frames.push((*offset, frame, idx)); *offset += width; }; - match item { + match &*item.item { Item::Absolute(v, _) => { offset += *v; } @@ -522,7 +522,7 @@ pub fn commit( layout_box(elem, engine, loc.relayout(), styles, region) })?; apply_baseline_shift(&mut frame, *styles); - push(&mut offset, frame); + push(&mut offset, frame, item.idx); } else { offset += amount; } @@ -534,15 +534,15 @@ pub fn commit( justification_ratio, extra_justification, ); - push(&mut offset, frame); + push(&mut offset, frame, item.idx); } Item::Frame(frame) => { - push(&mut offset, frame.clone()); + push(&mut offset, frame.clone(), item.idx); } Item::Tag(tag) => { let mut frame = Frame::soft(Size::zero()); frame.push(Point::zero(), FrameItem::Tag((*tag).clone())); - frames.push((offset, frame)); + frames.push((offset, frame, item.idx)); } Item::Skip(_) => {} } @@ -561,8 +561,9 @@ pub fn commit( add_par_line_marker(&mut output, marker, engine, locator, top); } + frames.sort_by_key(|(_, _, idx)| *idx); // Construct the line's frame. - for (offset, frame) in frames { + for (offset, frame, _) in frames { let x = offset + p.config.align.position(remaining); let y = top - frame.baseline(); output.push_frame(Point::new(x, y), frame); @@ -647,6 +648,11 @@ impl<'a> Items<'a> { self.0.iter().map(|item| &*item.item) } + /// Iterate over the items with indices + pub fn indexed_iter(&self) -> impl Iterator> { + self.0.iter() + } + /// Access the first item. pub fn first(&self) -> Option<&Item<'a>> { self.0.first().map(|item| &*item.item) diff --git a/tests/ref/issue-5775-cite-order-rtl.png b/tests/ref/issue-5775-cite-order-rtl.png new file mode 100644 index 0000000000000000000000000000000000000000..dbb1e86f83225757e6cf69c462b53d67f9875411 GIT binary patch literal 8218 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 Date: Tue, 18 Feb 2025 16:00:14 -0500 Subject: [PATCH 7/9] Remove `idx` field from `Run` --- crates/typst-layout/src/inline/line.rs | 8 ++++---- crates/typst-layout/src/inline/prepare.rs | 15 ++------------- crates/typst-layout/src/inline/shaping.rs | 4 +--- 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/crates/typst-layout/src/inline/line.rs b/crates/typst-layout/src/inline/line.rs index 7fcc344f7..cf788b15b 100644 --- a/crates/typst-layout/src/inline/line.rs +++ b/crates/typst-layout/src/inline/line.rs @@ -271,10 +271,10 @@ fn collect_range<'a>( items: &mut Items<'a>, fallback: &mut Option>, ) { - for run in p.slice(range.clone()) { + for (idx, run) in p.slice(range.clone()).enumerate() { // All non-text items are just kept, they can't be split. let Item::Text(shaped) = &run.item else { - items.push(&run.item, run.idx); + items.push(&run.item, idx); continue; }; let subrange = &run.range; @@ -295,10 +295,10 @@ fn collect_range<'a>( } else if split { // When the item is split in half, reshape it. let reshaped = shaped.reshape(engine, sliced); - items.push(Item::Text(reshaped), run.idx); + items.push(Item::Text(reshaped), idx); } else { // When the item is fully contained, just keep it. - items.push(&run.item, run.idx); + items.push(&run.item, idx); } } } diff --git a/crates/typst-layout/src/inline/prepare.rs b/crates/typst-layout/src/inline/prepare.rs index aec90c501..45585496b 100644 --- a/crates/typst-layout/src/inline/prepare.rs +++ b/crates/typst-layout/src/inline/prepare.rs @@ -7,7 +7,6 @@ use super::*; pub struct Run<'a> { pub item: Item<'a>, pub range: Range, - pub idx: usize, } /// A representation in which children are already layouted and text is already @@ -80,7 +79,6 @@ pub fn prepare<'a>( let mut cursor = 0; let mut items = Vec::with_capacity(segments.len()); - let mut next_idx = 0; // Shape the text to finalize the items. for segment in segments { @@ -90,19 +88,10 @@ pub fn prepare<'a>( match segment { Segment::Text(_, styles) => { - shape_range( - &mut items, - engine, - text, - &bidi, - range, - styles, - &mut next_idx, - ); + shape_range(&mut items, engine, text, &bidi, range, styles); } Segment::Item(item) => { - items.push(Run { range, item, idx: next_idx }); - next_idx += 1; + items.push(Run { range, item }); } } diff --git a/crates/typst-layout/src/inline/shaping.rs b/crates/typst-layout/src/inline/shaping.rs index 78c5789a0..3f8bf140e 100644 --- a/crates/typst-layout/src/inline/shaping.rs +++ b/crates/typst-layout/src/inline/shaping.rs @@ -599,7 +599,6 @@ pub fn shape_range<'a>( bidi: &BidiInfo<'a>, range: Range, styles: StyleChain<'a>, - next_idx: &mut usize, ) { let script = TextElem::script_in(styles); let lang = TextElem::lang_in(styles); @@ -608,8 +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(Run { range, item: Item::Text(shaped), idx: *next_idx }); - *next_idx += 1; + items.push(Run { range, item: Item::Text(shaped) }); }; let mut prev_level = BidiLevel::ltr(); From e48811a6d4808275434e9a0729db1cec48c6a993 Mon Sep 17 00:00:00 2001 From: +merlan #flirora Date: Tue, 18 Feb 2025 16:00:46 -0500 Subject: [PATCH 8/9] Remove debug statements --- crates/typst-layout/src/inline/linebreak.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/typst-layout/src/inline/linebreak.rs b/crates/typst-layout/src/inline/linebreak.rs index 14ff37937..5e900da0d 100644 --- a/crates/typst-layout/src/inline/linebreak.rs +++ b/crates/typst-layout/src/inline/linebreak.rs @@ -110,10 +110,10 @@ pub fn linebreak<'a>( p: &'a Preparation<'a>, width: Abs, ) -> Vec> { - dbg!(match p.config.linebreaks { + match p.config.linebreaks { Linebreaks::Simple => linebreak_simple(engine, p, width), Linebreaks::Optimized => linebreak_optimized(engine, p, width), - }) + } } /// Performs line breaking in simple first-fit style. This means that we build From 0ed8280e3e4c1d34648543ea65cdb2f38d1ef1b1 Mon Sep 17 00:00:00 2001 From: +merlan #flirora Date: Tue, 18 Feb 2025 16:32:38 -0500 Subject: [PATCH 9/9] Fix Clippy errors --- crates/typst-layout/src/inline/prepare.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/typst-layout/src/inline/prepare.rs b/crates/typst-layout/src/inline/prepare.rs index 45585496b..ac834e3cb 100644 --- a/crates/typst-layout/src/inline/prepare.rs +++ b/crates/typst-layout/src/inline/prepare.rs @@ -33,7 +33,7 @@ pub struct Preparation<'a> { pub spans: SpanMapper, } -impl<'a> Preparation<'a> { +impl Preparation<'_> { /// Get the item that contains the given `text_offset`. pub fn get(&self, offset: usize) -> &Run { let idx = self.indices.get(offset).copied().unwrap_or(0);