Extract Run struct

This commit is contained in:
+merlan #flirora 2025-02-15 00:50:02 -05:00
parent 6ceb25db82
commit 58b4bdb811
4 changed files with 38 additions and 30 deletions

View File

@ -270,12 +270,13 @@ fn collect_range<'a>(
items: &mut Items<'a>, items: &mut Items<'a>,
fallback: &mut Option<ItemEntry<'a>>, fallback: &mut Option<ItemEntry<'a>>,
) { ) {
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. // All non-text items are just kept, they can't be split.
let Item::Text(shaped) = item else { let Item::Text(shaped) = &run.item else {
items.push(item); items.push(&run.item);
continue; continue;
}; };
let subrange = &run.range;
// The intersection range of the item, the subrange, and the line's // The intersection range of the item, the subrange, and the line's
// trimming. // trimming.
@ -296,7 +297,7 @@ fn collect_range<'a>(
items.push(Item::Text(reshaped)); items.push(Item::Text(reshaped));
} else { } else {
// When the item is fully contained, just keep it. // When the item is fully contained, just keep it.
items.push(item); items.push(&run.item);
} }
} }
} }

View File

@ -823,8 +823,8 @@ fn linebreak_link(link: &str, mut f: impl FnMut(usize)) {
/// Whether hyphenation is enabled at the given offset. /// Whether hyphenation is enabled at the given offset.
fn hyphenate_at(p: &Preparation, offset: usize) -> bool { fn hyphenate_at(p: &Preparation, offset: usize) -> bool {
p.config.hyphenate.unwrap_or_else(|| { p.config.hyphenate.unwrap_or_else(|| {
let (_, item) = p.get(offset); let run = p.get(offset);
match item.text() { match run.item.text() {
Some(text) => TextElem::hyphenate_in(text.styles).unwrap_or(p.config.justify), Some(text) => TextElem::hyphenate_in(text.styles).unwrap_or(p.config.justify),
None => false, None => false,
} }
@ -834,8 +834,8 @@ fn hyphenate_at(p: &Preparation, offset: usize) -> bool {
/// The text language at the given offset. /// The text language at the given offset.
fn lang_at(p: &Preparation, offset: usize) -> Option<hypher::Lang> { fn lang_at(p: &Preparation, offset: usize) -> Option<hypher::Lang> {
let lang = p.config.lang.or_else(|| { let lang = p.config.lang.or_else(|| {
let (_, item) = p.get(offset); let run = p.get(offset);
let styles = item.text()?.styles; let styles = run.item.text()?.styles;
Some(TextElem::lang_in(styles)) Some(TextElem::lang_in(styles))
})?; })?;
@ -900,8 +900,8 @@ impl Estimates {
let mut shrinkability = CumulativeVec::with_capacity(cap); let mut shrinkability = CumulativeVec::with_capacity(cap);
let mut justifiables = CumulativeVec::with_capacity(cap); let mut justifiables = CumulativeVec::with_capacity(cap);
for (range, item) in p.items.iter() { for run in p.items.iter() {
if let Item::Text(shaped) = item { if let Item::Text(shaped) = &run.item {
for g in shaped.glyphs.iter() { for g in shaped.glyphs.iter() {
let byte_len = g.range.len(); let byte_len = g.range.len();
let stretch = g.stretchability().0 + g.stretchability().1; let stretch = g.stretchability().0 + g.stretchability().1;
@ -912,13 +912,13 @@ impl Estimates {
justifiables.push(byte_len, g.is_justifiable() as usize); justifiables.push(byte_len, g.is_justifiable() as usize);
} }
} else { } else {
widths.push(range.len(), item.natural_width()); widths.push(run.range.len(), run.item.natural_width());
} }
widths.adjust(range.end); widths.adjust(run.range.end);
stretchability.adjust(range.end); stretchability.adjust(run.range.end);
shrinkability.adjust(range.end); shrinkability.adjust(run.range.end);
justifiables.adjust(range.end); justifiables.adjust(run.range.end);
} }
Self { Self {

View File

@ -3,6 +3,12 @@ use unicode_bidi::{BidiInfo, Level as BidiLevel};
use super::*; 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 /// A representation in which children are already layouted and text is already
/// preshaped. /// preshaped.
/// ///
@ -20,7 +26,7 @@ pub struct Preparation<'a> {
/// direction). /// direction).
pub bidi: Option<BidiInfo<'a>>, pub bidi: Option<BidiInfo<'a>>,
/// Text runs, spacing and layouted elements. /// Text runs, spacing and layouted elements.
pub items: Vec<(Range, Item<'a>)>, pub items: Vec<Run<'a>>,
/// Maps from byte indices to item indices. /// Maps from byte indices to item indices.
pub indices: Vec<usize>, pub indices: Vec<usize>,
/// The span mapper. /// The span mapper.
@ -29,13 +35,13 @@ pub struct Preparation<'a> {
impl<'a> Preparation<'a> { impl<'a> Preparation<'a> {
/// Get the item that contains the given `text_offset`. /// 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); let idx = self.indices.get(offset).copied().unwrap_or(0);
&self.items[idx] &self.items[idx]
} }
/// Iterate over the items that intersect the given `sliced` range. /// Iterate over the items that intersect the given `sliced` range.
pub fn slice(&self, sliced: Range) -> impl Iterator<Item = &(Range, Item<'a>)> { pub fn slice(&self, sliced: Range) -> impl Iterator<Item = &Run> {
// Usually, we don't want empty-range items at the start of the line // 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 // (because they will be part of the previous line), but for the first
// line, we need to keep them. // line, we need to keep them.
@ -43,8 +49,8 @@ impl<'a> Preparation<'a> {
0 => 0, 0 => 0,
n => self.indices.get(n).copied().unwrap_or(0), n => self.indices.get(n).copied().unwrap_or(0),
}; };
self.items[start..].iter().take_while(move |(range, _)| { self.items[start..].iter().take_while(move |run| {
range.start < sliced.end || range.end <= sliced.end run.range.start < sliced.end || run.range.end <= sliced.end
}) })
} }
} }
@ -84,7 +90,7 @@ pub fn prepare<'a>(
Segment::Text(_, styles) => { Segment::Text(_, styles) => {
shape_range(&mut items, engine, text, &bidi, range, 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; cursor = end;
@ -92,8 +98,8 @@ pub fn prepare<'a>(
// Build the mapping from byte to item indices. // Build the mapping from byte to item indices.
let mut indices = Vec::with_capacity(text.len()); let mut indices = Vec::with_capacity(text.len());
for (i, (range, _)) in items.iter().enumerate() { for (i, run) in items.iter().enumerate() {
indices.extend(range.clone().map(|_| i)); indices.extend(run.range.clone().map(|_| i));
} }
if config.cjk_latin_spacing { if config.cjk_latin_spacing {
@ -113,15 +119,15 @@ pub fn prepare<'a>(
/// Add some spacing between Han characters and western characters. See /// Add some spacing between Han characters and western characters. See
/// Requirements for Chinese Text Layout, Section 3.2.2 Mixed Text Composition /// Requirements for Chinese Text Layout, Section 3.2.2 Mixed Text Composition
/// in Horizontal Written Mode /// 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 let mut items = items
.iter_mut() .iter_mut()
.filter(|(_, x)| !matches!(x, Item::Tag(_))) .filter(|run| !matches!(run.item, Item::Tag(_)))
.peekable(); .peekable();
let mut prev: Option<&ShapedGlyph> = None; let mut prev: Option<&ShapedGlyph> = None;
while let Some((_, item)) = items.next() { while let Some(run) = items.next() {
let Some(text) = item.text_mut() else { let Some(text) = run.item.text_mut() else {
prev = None; prev = None;
continue; continue;
}; };
@ -135,7 +141,7 @@ fn add_cjk_latin_spacing(items: &mut [(Range, Item)]) {
let next = glyphs.peek().map(|n| n as _).or_else(|| { let next = glyphs.peek().map(|n| n as _).or_else(|| {
items items
.peek() .peek()
.and_then(|(_, i)| i.text()) .and_then(|run| run.item.text())
.and_then(|shaped| shaped.glyphs.first()) .and_then(|shaped| shaped.glyphs.first())
}); });

View File

@ -19,6 +19,7 @@ use typst_utils::SliceExt;
use unicode_bidi::{BidiInfo, Level as BidiLevel}; use unicode_bidi::{BidiInfo, Level as BidiLevel};
use unicode_script::{Script, UnicodeScript}; use unicode_script::{Script, UnicodeScript};
use super::prepare::Run;
use super::{decorate, Item, Range, SpanMapper}; use super::{decorate, Item, Range, SpanMapper};
use crate::modifiers::{FrameModifiers, FrameModify}; 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 /// Group a range of text by BiDi level and script, shape the runs and generate
/// items for them. /// items for them.
pub fn shape_range<'a>( pub fn shape_range<'a>(
items: &mut Vec<(Range, Item<'a>)>, items: &mut Vec<Run<'a>>,
engine: &Engine, engine: &Engine,
text: &'a str, text: &'a str,
bidi: &BidiInfo<'a>, bidi: &BidiInfo<'a>,
@ -606,7 +607,7 @@ pub fn shape_range<'a>(
let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL }; let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL };
let shaped = let shaped =
shape(engine, range.start, &text[range.clone()], styles, dir, lang, region); 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(); let mut prev_level = BidiLevel::ltr();