Skip to main content

fonts/
glyph.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::fmt;
6use std::ops::Range;
7use std::sync::Arc;
8use std::vec::Vec;
9
10use app_units::Au;
11use euclid::default::Point2D;
12use euclid::num::Zero;
13use itertools::Either;
14use log::{debug, error};
15use malloc_size_of_derive::MallocSizeOf;
16use servo_base::text::Utf32CodeUnits;
17
18use crate::{GlyphShapingResult, ShapedGlyph, ShapingOptions};
19
20/// GlyphEntry is a port of Gecko's CompressedGlyph scheme for storing glyph data compactly.
21///
22/// In the common case (reasonable glyph advances, no offsets from the font em-box, and one glyph
23/// per character), we pack glyph advance, glyph id, and some flags into a single u32.
24///
25/// In the uncommon case (multiple glyphs per unicode character, large glyph index/advance, or glyph
26/// offsets), we create a DetailedGlyphEntry for the glyph and pack its index into the GlyphEntry.
27#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
28pub struct GlyphEntry {
29    value: u32,
30}
31
32impl GlyphEntry {
33    fn new(value: u32) -> GlyphEntry {
34        GlyphEntry { value }
35    }
36
37    // Creates a GlyphEntry for the common case
38    fn simple(id: GlyphId, advance: Au) -> GlyphEntry {
39        assert!(is_simple_glyph_id(id));
40        assert!(is_simple_advance(advance));
41
42        let id_mask = id;
43        let Au(advance) = advance;
44        let advance_mask = (advance as u32) << GLYPH_ADVANCE_SHIFT;
45
46        GlyphEntry::new(id_mask | advance_mask | FLAG_IS_SIMPLE_GLYPH)
47    }
48
49    fn complex(detailed_glyph_index: usize) -> GlyphEntry {
50        assert!(detailed_glyph_index as u32 <= u32::MAX >> 1);
51        GlyphEntry::new(detailed_glyph_index as u32)
52    }
53}
54
55/// The id of a particular glyph within a font
56pub(crate) type GlyphId = u32;
57
58// TODO: make this more type-safe.
59
60const FLAG_CHAR_IS_WORD_SEPARATOR: u32 = 0x40000000;
61const FLAG_IS_SIMPLE_GLYPH: u32 = 0x80000000;
62
63// glyph advance; in Au's.
64const GLYPH_ADVANCE_MASK: u32 = 0x3FFF0000;
65const GLYPH_ADVANCE_SHIFT: u32 = 16;
66const GLYPH_ID_MASK: u32 = 0x0000FFFF;
67
68// Non-simple glyphs (more than one glyph per char; missing glyph,
69// newline, tab, large advance, or nonzero x/y offsets) may have one
70// or more detailed glyphs associated with them. They are stored in a
71// side array so that there is a 1:1 mapping of GlyphEntry to
72// unicode char.
73
74fn is_simple_glyph_id(id: GlyphId) -> bool {
75    (id & GLYPH_ID_MASK) == id
76}
77
78fn is_simple_advance(advance: Au) -> bool {
79    advance >= Au::zero() && {
80        let unsigned_au = advance.0 as u32;
81        (unsigned_au & (GLYPH_ADVANCE_MASK >> GLYPH_ADVANCE_SHIFT)) == unsigned_au
82    }
83}
84
85// Getters and setters for GlyphEntry. Setter methods are functional,
86// because GlyphEntry is immutable and only a u32 in size.
87impl GlyphEntry {
88    #[inline(always)]
89    fn advance(&self) -> Au {
90        Au::new(((self.value & GLYPH_ADVANCE_MASK) >> GLYPH_ADVANCE_SHIFT) as i32)
91    }
92
93    #[inline]
94    fn id(&self) -> GlyphId {
95        self.value & GLYPH_ID_MASK
96    }
97
98    /// True if the original character was a word separator. These include spaces
99    /// (U+0020), non-breaking spaces (U+00A0), and a few other characters
100    /// non-exhaustively listed in the specification. Other characters may map to the same
101    /// glyphs, but this function does not take mapping into account.
102    ///
103    /// See <https://drafts.csswg.org/css-text/#word-separator>.
104    fn char_is_word_separator(&self) -> bool {
105        self.has_flag(FLAG_CHAR_IS_WORD_SEPARATOR)
106    }
107
108    #[inline(always)]
109    fn set_char_is_word_separator(&mut self) {
110        self.value |= FLAG_CHAR_IS_WORD_SEPARATOR;
111    }
112
113    fn detailed_glyph_index(&self) -> usize {
114        self.value as usize
115    }
116
117    #[inline(always)]
118    fn is_simple(&self) -> bool {
119        self.has_flag(FLAG_IS_SIMPLE_GLYPH)
120    }
121
122    #[inline(always)]
123    fn has_flag(&self, flag: u32) -> bool {
124        (self.value & flag) != 0
125    }
126}
127
128#[derive(Clone, MallocSizeOf)]
129pub struct DetailedGlyphEntry {
130    /// The id of the this glyph within the font.
131    id: u32,
132    /// The advance that this glyphs needs ie the distance between where this
133    /// glyph is painted and the next is painted.
134    advance: Au,
135    /// The physical offset that this glyph should be painted with.
136    offset: Option<Point2D<Au>>,
137    /// The number of character this glyph corresponds to in the original string.
138    /// This might be zero and this might be more than one.
139    character_count: Utf32CodeUnits,
140    /// Whether or not the originating character for this glyph was a word separator
141    is_word_separator: bool,
142}
143
144// This enum is a proxy that's provided to ShapedText clients when iterating
145// through glyphs (either for a particular TextRun offset, or all glyphs).
146#[derive(Clone, Copy)]
147pub enum GlyphInfo<'a> {
148    Simple(&'a GlyphEntry),
149    Detail(&'a DetailedGlyphEntry),
150}
151
152impl GlyphInfo<'_> {
153    pub fn id(self) -> GlyphId {
154        match self {
155            GlyphInfo::Simple(entry) => entry.id(),
156            GlyphInfo::Detail(entry) => entry.id,
157        }
158    }
159
160    #[inline(always)]
161    pub fn advance(self) -> Au {
162        match self {
163            GlyphInfo::Simple(entry) => entry.advance(),
164            GlyphInfo::Detail(entry) => entry.advance,
165        }
166    }
167
168    #[inline]
169    pub fn offset(self) -> Option<Point2D<Au>> {
170        match self {
171            GlyphInfo::Simple(..) => None,
172            GlyphInfo::Detail(entry) => entry.offset,
173        }
174    }
175
176    #[inline]
177    pub fn char_is_word_separator(self) -> bool {
178        match self {
179            GlyphInfo::Simple(entry) => entry.char_is_word_separator(),
180            GlyphInfo::Detail(entry) => entry.is_word_separator,
181        }
182    }
183
184    /// The number of characters that this glyph corresponds to. This may be more
185    /// than one when a single glyph is produced for multiple characters. This may
186    /// be zero when multiple glyphs are produced for a single character.
187    #[inline]
188    pub fn character_count(self) -> Utf32CodeUnits {
189        match self {
190            GlyphInfo::Simple(..) => Utf32CodeUnits(1),
191            GlyphInfo::Detail(entry) => entry.character_count,
192        }
193    }
194}
195
196/// Stores the glyph data belonging to a text run.
197///
198/// Simple glyphs are stored inline in the `entry_buffer`, detailed glyphs are
199/// stored as pointers into the `detail_store`.
200///
201/// ~~~ascii
202/// +- ShapedText --------------------------------+
203/// |               +---+---+---+---+---+---+---+ |
204/// | entry_buffer: |   | s |   | s |   | s | s | |  d = detailed
205/// |               +-|-+---+-|-+---+-|-+---+---+ |  s = simple
206/// |                 |       |       |           |
207/// |                 |   +---+-------+           |
208/// |                 |   |                       |
209/// |               +-V-+-V-+                     |
210/// | detail_store: | d | d |                     |
211/// |               +---+---+                     |
212/// +---------------------------------------------+
213/// ~~~
214#[derive(Clone, MallocSizeOf)]
215pub struct ShapedText {
216    // TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector
217    // optimization.
218    /// A collection of [`GlyphEntry`]s within the [`ShapedText`]. Each [`GlyphEntry`]
219    /// maybe simple or detailed. When detailed, there will be a corresponding entry
220    /// in [`Self::detailed_glyphs`].
221    glyphs: Vec<GlyphEntry>,
222
223    /// A vector of glyphs that cannot fit within a single [`GlyphEntry`] or that
224    /// correspond to 0 or more than 1 character in the original string.
225    detailed_glyphs: Vec<DetailedGlyphEntry>,
226
227    /// A cache of the advance of the entire glyph store.
228    total_advance: Au,
229
230    /// The number of characters that correspond to the glyphs in this [`ShapedText`]
231    character_count: Utf32CodeUnits,
232
233    /// A cache of the number of word separators in the entire glyph store.
234    /// See <https://drafts.csswg.org/css-text/#word-separator>.
235    total_word_separators: usize,
236
237    /// Whether or not this [`ShapedText`] has right-to-left text, which has implications
238    /// about the order of the glyphs in the store.
239    is_rtl: bool,
240}
241
242impl ShapedText {
243    /// Initializes the glyph store with the given capacity, but doesn't actually add any glyphs.
244    ///
245    /// Use the `add_*` methods to store glyph data.
246    pub(crate) fn new(length: usize, is_rtl: bool) -> Self {
247        Self {
248            glyphs: Vec::with_capacity(length),
249            detailed_glyphs: Default::default(),
250            total_advance: Au::zero(),
251            character_count: Utf32CodeUnits(0),
252            total_word_separators: 0,
253            is_rtl,
254        }
255    }
256
257    /// This constructor turns shaping output from HarfBuzz into a glyph run to be
258    /// used by layout. The idea here is that we add each glyph to the [`ShapedText`]
259    /// and track to which characters from the original string each glyph
260    /// corresponds. HarfBuzz will either give us glyphs that correspond to
261    /// characters left-to-right or right-to-left. Each character can produce
262    /// multiple glyphs and multiple characters can produce one glyph. HarfBuzz just
263    /// guarantees that the resulting character offsets are in monotone order.
264    pub(crate) fn with_shaped_glyph_data(
265        text: &str,
266        options: &ShapingOptions,
267        shaped_glyph_data: &impl GlyphShapingResult,
268    ) -> Self {
269        debug!(
270            "Shaped: '{text:?}: {:?}",
271            shaped_glyph_data.iter().collect::<Vec<_>>()
272        );
273
274        // Note: Even if we set the `RTL_FLAG` in the options, Harfbuzz may still
275        // give us shaped glyphs in left-to-right order. We need to look at the
276        // actual cluster indices in the shaped run.
277        let shaped_run_is_rtl = shaped_glyph_data.is_rtl();
278        let mut characters = if !shaped_run_is_rtl {
279            Either::Left(text.char_indices())
280        } else {
281            Either::Right(text.char_indices().rev())
282        };
283
284        let mut previous_character_offset = None;
285        let mut glyph_store = ShapedText::new(shaped_glyph_data.len(), shaped_run_is_rtl);
286        if shaped_glyph_data.len() == 0 {
287            return glyph_store;
288        }
289
290        for mut shaped_glyph in shaped_glyph_data.iter() {
291            // The glyph "cluster" (HarfBuzz terminology) is the byte offset in the string that
292            // this glyph corresponds to. More than one glyph can share a cluster.
293            let glyph_cluster = shaped_glyph.string_byte_offset;
294
295            if let Some(previous_character_offset) = previous_character_offset &&
296                previous_character_offset == glyph_cluster
297            {
298                glyph_store.add_glyph_for_current_character(&shaped_glyph, options);
299                continue;
300            }
301
302            previous_character_offset = Some(glyph_cluster);
303            let mut characters_skipped = 0;
304            let Some(character) = characters.find_map(|(character_offset, character)| {
305                if glyph_cluster == character_offset {
306                    Some(character)
307                } else {
308                    characters_skipped += 1;
309                    None
310                }
311            }) else {
312                error!("HarfBuzz shaping results extended past character count");
313                return glyph_store;
314            };
315
316            shaped_glyph.adjust_for_character(character, options);
317
318            // If the we are working from the end of the string to the start and
319            // characters were skipped to produce this glyph, they belong to this
320            // glyph.
321            if shaped_run_is_rtl {
322                glyph_store.add_glyph(character, &shaped_glyph);
323            }
324
325            for _ in 0..characters_skipped {
326                glyph_store.extend_previous_glyph_by_character()
327            }
328
329            // If the we are working from the estart of the string to the end and
330            // characters were skipped to produce this glyph, they belong to the
331            // previous glyph.
332            if !shaped_run_is_rtl {
333                glyph_store.add_glyph(character, &shaped_glyph);
334            }
335        }
336
337        // Consume any remaining characters that belong to the more-recently added glyph.
338        for (_, _) in characters {
339            glyph_store.extend_previous_glyph_by_character();
340        }
341
342        glyph_store
343    }
344
345    /// Return the number of glyphs stored in this [`ShapedText`].
346    #[inline]
347    pub fn glyph_count(&self) -> usize {
348        self.glyphs.len()
349    }
350
351    /// Get the number of characters that were shaped to produce this [`ShapedText`].
352    pub fn character_count(&self) -> Utf32CodeUnits {
353        self.character_count
354    }
355
356    /// Adds glyph that corresponds to a single character (as far we know) in the originating string.
357    #[inline]
358    pub(crate) fn add_glyph(&mut self, character: char, glyph: &ShapedGlyph) {
359        if !glyph.can_be_simple_glyph() {
360            self.add_detailed_glyph(glyph, Some(character), Utf32CodeUnits(1));
361            return;
362        }
363
364        let mut simple_glyph_entry = GlyphEntry::simple(glyph.glyph_id, glyph.advance);
365        if character_is_word_separator(character) {
366            self.total_word_separators += 1;
367            simple_glyph_entry.set_char_is_word_separator();
368        }
369
370        self.character_count += Utf32CodeUnits(1);
371        self.total_advance += glyph.advance;
372        self.glyphs.push(simple_glyph_entry)
373    }
374
375    fn add_detailed_glyph(
376        &mut self,
377        shaped_glyph: &ShapedGlyph,
378        character: Option<char>,
379        character_count: Utf32CodeUnits,
380    ) {
381        let is_word_separator = character.is_some_and(character_is_word_separator);
382        if is_word_separator {
383            self.total_word_separators += 1;
384        }
385
386        self.character_count += character_count;
387        self.total_advance += shaped_glyph.advance;
388        self.detailed_glyphs.push(DetailedGlyphEntry {
389            id: shaped_glyph.glyph_id,
390            advance: shaped_glyph.advance,
391            offset: shaped_glyph.offset,
392            character_count,
393            is_word_separator,
394        });
395        self.glyphs
396            .push(GlyphEntry::complex(self.detailed_glyphs.len() - 1));
397    }
398
399    fn extend_previous_glyph_by_character(&mut self) {
400        let detailed_glyph_index = self.ensure_last_glyph_is_detailed();
401        let detailed_glyph = self
402            .detailed_glyphs
403            .get_mut(detailed_glyph_index)
404            .expect("GlyphEntry should have valid index to detailed glyph");
405        detailed_glyph.character_count += Utf32CodeUnits(1);
406        self.character_count += Utf32CodeUnits(1);
407    }
408
409    fn add_glyph_for_current_character(
410        &mut self,
411        shaped_glyph: &ShapedGlyph,
412        options: &ShapingOptions,
413    ) {
414        // If this glyph cluster is extending to include another glyph and we applied
415        // letter spacing to the previous glyph, ensure that the letter spacing is only
416        // applied to the last glyph in the cluster. Note that this is unconditionally
417        // converting the previous glyph to a detailed one because it's quite likely that
418        // the advance will not fit into the simple bitmask due to being negative.
419        if options.letter_spacing != Au::zero() {
420            let last_glyph_index = self.ensure_last_glyph_is_detailed();
421            self.detailed_glyphs[last_glyph_index].advance -= options.letter_spacing;
422        }
423
424        // Add a detailed glyph entry for this new glyph, but it corresponds to a character
425        // we have already started processing. It should not contribute any character count.
426        self.add_detailed_glyph(shaped_glyph, None, Utf32CodeUnits(0));
427    }
428
429    /// If the last glyph added to this [`ShapedText`] was a simple glyph, convert it to a
430    /// detailed one. In either case, return the index into [`Self::detailed_glyphs`] for
431    /// the most recently added glyph.
432    fn ensure_last_glyph_is_detailed(&mut self) -> usize {
433        let last_glyph = self
434            .glyphs
435            .last_mut()
436            .expect("Should never call this before any glyphs have been added.");
437        if !last_glyph.is_simple() {
438            return last_glyph.detailed_glyph_index();
439        }
440
441        self.detailed_glyphs.push(DetailedGlyphEntry {
442            id: last_glyph.id(),
443            advance: last_glyph.advance(),
444            offset: Default::default(),
445            character_count: Utf32CodeUnits(1),
446            is_word_separator: last_glyph.char_is_word_separator(),
447        });
448
449        let detailed_glyph_index = self.detailed_glyphs.len() - 1;
450        *last_glyph = GlyphEntry::complex(detailed_glyph_index);
451        detailed_glyph_index
452    }
453
454    pub fn glyphs(&self) -> impl DoubleEndedIterator<Item = GlyphInfo<'_>> + use<'_> {
455        self.glyph_slice(0..self.glyphs.len())
456    }
457
458    fn glyph_slice(
459        &self,
460        glyph_range: Range<usize>,
461    ) -> impl DoubleEndedIterator<Item = GlyphInfo<'_>> + use<'_> {
462        self.glyphs[glyph_range].iter().map(|entry| {
463            if entry.is_simple() {
464                GlyphInfo::Simple(entry)
465            } else {
466                GlyphInfo::Detail(&self.detailed_glyphs[entry.detailed_glyph_index()])
467            }
468        })
469    }
470}
471
472impl ShapedGlyph {
473    fn can_be_simple_glyph(&self) -> bool {
474        is_simple_glyph_id(self.glyph_id) &&
475            is_simple_advance(self.advance) &&
476            self.offset
477                .is_none_or(|offset| offset == Default::default())
478    }
479
480    /// After shaping is complete, some glyphs need their spacing adjusted to take into
481    /// account `letter-spacing` and `word-spacing`.
482    pub(crate) fn adjust_for_character(
483        &mut self,
484        character: char,
485        shaping_options: &ShapingOptions,
486    ) {
487        self.advance += shaping_options.letter_spacing_for_character(character);
488
489        // CSS 2.1 ยง 16.4 states that "word spacing affects each space (U+0020) and non-breaking
490        // space (U+00A0) left in the text after the white space processing rules have been
491        // applied. The effect of the property on other word-separator characters is undefined."
492        // We elect to only space the two required code points.
493        if character == ' ' || character == '\u{a0}' {
494            // https://drafts.csswg.org/css-text-3/#word-spacing-property
495            self.advance += shaping_options.word_spacing;
496        }
497    }
498}
499
500fn character_is_word_separator(character: char) -> bool {
501    // This list is taken from the non-exhaustive list of word separator characters in
502    // the CSS Text Module Level 3 Spec:
503    // See https://drafts.csswg.org/css-text/#word-separator
504    let is_word_separator = matches!(
505        character,
506        ' ' |
507                '\u{00A0}' | // non-breaking space
508                '\u{1361}' | // Ethiopic word space
509                '\u{10100}' | // Aegean word separator
510                '\u{10101}' | // Aegean word separator
511                '\u{1039F}' | // Ugartic word divider
512                '\u{1091F}' // Phoenician word separator
513    );
514    is_word_separator
515}
516
517impl fmt::Debug for ShapedText {
518    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
519        writeln!(formatter, "ShapedText:")?;
520        for entry in self.glyphs.iter() {
521            if entry.is_simple() {
522                writeln!(
523                    formatter,
524                    "  simple id={:?} advance={:?}",
525                    entry.id(),
526                    entry.advance()
527                )?;
528                continue;
529            } else {
530                let detailed_glyph = &self.detailed_glyphs[entry.detailed_glyph_index()];
531                writeln!(
532                    formatter,
533                    "  detailed id={:?} advance={:?} characters={:?}",
534                    detailed_glyph.id, detailed_glyph.advance, detailed_glyph.character_count,
535                )?;
536            }
537        }
538        Ok(())
539    }
540}
541
542#[derive(Clone, Debug, MallocSizeOf)]
543struct GlyphCountAndAdvance {
544    count: usize,
545    advance: Au,
546}
547
548impl GlyphCountAndAdvance {
549    fn new(count: usize, advance: Au) -> Self {
550        Self { count, advance }
551    }
552}
553
554/// A record of the trailing white space at the end of a run of text or glyphs.
555///
556/// See <https://drafts.csswg.org/css-text-3/#white-space-phase-2>
557#[derive(Clone, Debug, MallocSizeOf)]
558pub struct TrailingWhiteSpace<Unit> {
559    /// The part of the run that "hangs". This comes before the removable parts of the run.
560    pub hangable: Unit,
561    /// The part of the run that is removed at the end of a line. This comes after the
562    /// hangable parts of the run.
563    pub removable: Unit,
564}
565
566/// A slice of a [`ShapedText`] which allows having different views into a shaped
567/// text run. This is used for splitting up shaped text during layout, without
568/// duplicating the entire run.
569#[derive(Clone, Debug, MallocSizeOf)]
570pub struct ShapedTextSlice {
571    /// The [`ShapedText`] that this [`ShapedTextSlice`] refers to.
572    #[conditional_malloc_size_of]
573    shaped_text: Arc<ShapedText>,
574
575    /// The range of glyphs within the [`ShapedText`] that this [`ShapedTextSlice`] represents.
576    glyph_range: Range<usize>,
577
578    /// A cache of the advance of the entire [`ShapedTextSlice`].
579    total_advance: Au,
580
581    /// The number of characters that correspond to the glyphs in this [`ShapedTextSlice`]
582    character_count: Utf32CodeUnits,
583
584    /// A precomputed count of the number of word separators in the entire [`ShapedTextSlice`]. See
585    /// <https://drafts.csswg.org/css-text/#word-separator>.
586    total_word_separators: usize,
587
588    /// The number of glyphs and their advance of the trailing white space portion of this
589    /// slice during inline line layout.
590    trailing_white_space: TrailingWhiteSpace<GlyphCountAndAdvance>,
591
592    /// Whether or not the text that created this slice was entirely white space.
593    all_white_space: bool,
594}
595
596impl ShapedTextSlice {
597    /// Return the number of glyphs represented by this [`ShapedTextSlice`].
598    #[inline]
599    pub fn glyph_count(&self) -> usize {
600        self.glyph_range.len()
601    }
602
603    /// The number of characters that were consumed to produce this [`ShapedTextSlice`]. Some
604    /// characters correspond to more than one glyph and some glyphs correspond to more than
605    /// one character.
606    #[inline]
607    pub fn character_count(&self) -> Utf32CodeUnits {
608        self.character_count
609    }
610
611    /// The total advance of the characters represented by this [`ShapedTextSlice`].
612    #[inline]
613    pub fn total_advance(&self) -> Au {
614        self.total_advance
615    }
616
617    /// The number of word separators in this [`ShapedTextSlice`].
618    #[inline]
619    pub fn total_word_separators(&self) -> usize {
620        self.total_word_separators
621    }
622
623    /// The number of word separators in the hanging and removable portion of this [`ShapedTextSlice`].
624    pub fn hanging_and_removable_word_separators(&self) -> usize {
625        let discard_glyph_count =
626            self.trailing_white_space.removable.count + self.trailing_white_space.hangable.count;
627        let iterator = if self.shaped_text.is_rtl {
628            self.shaped_text
629                .glyph_slice(self.glyph_range.start..self.glyph_range.start + discard_glyph_count)
630        } else {
631            self.shaped_text
632                .glyph_slice(self.glyph_range.end - discard_glyph_count..self.glyph_range.end)
633        };
634        iterator
635            .map(|glyph| if glyph.char_is_word_separator() { 1 } else { 0 })
636            .sum()
637    }
638
639    /// Whether or not this [`ShapedTextSlice`] is composed of only removable characters.
640    pub fn all_removable(&self) -> bool {
641        self.trailing_white_space.removable.count >= self.glyph_count()
642    }
643
644    /// Whether or not this [`ShapedTextSlice`] has any non-hangable and non-removable glyphs.
645    pub fn has_non_hangable_non_removable_content(&self) -> bool {
646        self.trailing_white_space.removable.count + self.trailing_white_space.hangable.count <
647            self.glyph_count()
648    }
649
650    /// The advance of this [`ShapedTextSlice`] that is removable at the end of a line.
651    pub fn removable_advance(&self) -> Au {
652        self.trailing_white_space.removable.advance
653    }
654
655    /// The advance of this [`ShapedTextSlice`] that is hangable at the end of a line.
656    pub fn hangable_advance(&self) -> Au {
657        self.trailing_white_space.hangable.advance
658    }
659
660    /// Whether or not this [`ShapedTextSlice`] is entirely composed of white space.
661    pub fn all_white_space(&self) -> bool {
662        self.all_white_space
663    }
664
665    /// An iterator over the glyphs represented by this [`ShapedTextSlice`].
666    pub fn glyphs(&self) -> impl DoubleEndedIterator<Item = GlyphInfo<'_>> + use<'_> {
667        self.shaped_text.glyph_slice(self.glyph_range.clone())
668    }
669
670    fn split_off_count(&mut self, trimmed_glyph_count: usize) -> Arc<Self> {
671        let glyph_range = if self.shaped_text.is_rtl {
672            self.glyph_range.start + trimmed_glyph_count..self.glyph_range.end
673        } else {
674            self.glyph_range.start..self.glyph_range.end - trimmed_glyph_count
675        };
676
677        let split_glyph_range = if self.shaped_text.is_rtl {
678            self.glyph_range.start..glyph_range.start
679        } else {
680            glyph_range.end..self.glyph_range.end
681        };
682
683        let mut trimmed_characters = Utf32CodeUnits(0);
684        let mut trimmed_word_separators = 0;
685        let mut trimmed_advance = Au::zero();
686        let iterator = self.shaped_text.glyph_slice(split_glyph_range.clone());
687        for glyph in iterator {
688            trimmed_characters += glyph.character_count();
689            trimmed_advance += glyph.advance();
690            if glyph.char_is_word_separator() {
691                trimmed_word_separators += 1;
692            }
693        }
694
695        self.glyph_range = glyph_range;
696        self.character_count -= trimmed_characters;
697        self.total_word_separators -= trimmed_word_separators;
698        self.total_advance -= trimmed_advance;
699
700        let GlyphCountAndAdvance {
701            count: removable_glyphs,
702            advance: removable_advance,
703        } = self.trailing_white_space.removable;
704
705        let removable_glyphs_to_trim = trimmed_glyph_count.min(removable_glyphs);
706        let removable_advance_to_trim = trimmed_advance.min(removable_advance);
707        let hangable_glyphs_to_trim = trimmed_glyph_count - removable_glyphs_to_trim;
708        let hangable_advance_to_trim =
709            (trimmed_advance - removable_advance_to_trim).min(self.hangable_advance());
710
711        self.trailing_white_space.removable.count -= removable_glyphs_to_trim;
712        self.trailing_white_space.removable.advance -= removable_advance_to_trim;
713        self.trailing_white_space.hangable.count -= hangable_glyphs_to_trim;
714        self.trailing_white_space.hangable.advance -= hangable_advance_to_trim;
715
716        Arc::new(Self {
717            shaped_text: self.shaped_text.clone(),
718            glyph_range: split_glyph_range,
719            total_advance: trimmed_advance,
720            character_count: trimmed_characters,
721            total_word_separators: trimmed_word_separators,
722            trailing_white_space: TrailingWhiteSpace {
723                hangable: GlyphCountAndAdvance::new(
724                    hangable_glyphs_to_trim,
725                    hangable_advance_to_trim,
726                ),
727                removable: GlyphCountAndAdvance::new(
728                    removable_glyphs_to_trim,
729                    removable_advance_to_trim,
730                ),
731            },
732            // TODO: This is only correct because this function only splits off removable
733            // white space. Once that changes this should be calculated based on what is
734            // split off.
735            all_white_space: true,
736        })
737    }
738
739    /// Produce a new [`ShapedTextSlice`] that includes all of the glyphs that `self`
740    /// does except for the removable portion of the slice.
741    pub fn without_removable_white_space(&self) -> Arc<Self> {
742        let mut new = self.clone();
743        new.split_off_count(self.trailing_white_space.removable.count);
744        Arc::new(new)
745    }
746}
747
748/// A data structure used to efficiently slice up a [`ShapedText`] into [`ShapedTextSlice`]s.
749pub struct ShapedTextSlicer {
750    current_glyph_offset: usize,
751    current_character_offset: Utf32CodeUnits,
752    shaped_text: Arc<ShapedText>,
753}
754
755impl ShapedTextSlicer {
756    pub fn new(shaped_text: Arc<ShapedText>) -> Self {
757        let current_glyph_offset = if shaped_text.is_rtl {
758            shaped_text.glyph_count()
759        } else {
760            0
761        };
762
763        Self {
764            current_glyph_offset,
765            current_character_offset: Utf32CodeUnits(0),
766            shaped_text,
767        }
768    }
769
770    /// Given a desired character offset, consume glyphs until that character offset
771    /// is reached (inclusive of glyphs that come from zero characters). Return those
772    /// glyphs as a [`ShapedTextSlice`] tagged with the given whitespace-related
773    /// properties. Returns `None` if the resulting slice would not hold any glyphs.
774    pub fn slice_until_character_offset(
775        &mut self,
776        desired_character_offset: Utf32CodeUnits,
777        trailing_white_space: TrailingWhiteSpace<Utf32CodeUnits>,
778        all_white_space: bool,
779    ) -> Option<Arc<ShapedTextSlice>> {
780        let mut glyph_count = 0;
781        let mut total_word_separators = 0;
782        let mut total_advance = Au::zero();
783        let original_character_offset = self.current_character_offset;
784
785        if self.current_character_offset >= desired_character_offset {
786            return None;
787        }
788
789        let starting_removable_offset = desired_character_offset - trailing_white_space.removable;
790        let starting_hangable_offset = starting_removable_offset - trailing_white_space.hangable;
791        let mut hangable_glyph_count = 0;
792        let mut hangable_advance = Au::zero();
793        let mut removable_glyph_count = 0;
794        let mut removable_advance = Au::zero();
795
796        // In `ShapedText` glyphs are stored in physical left-to-right order, which means that the
797        // indices of the characters that they represent might decrease. Since we want to consume
798        // characters in memory order, we may need to walk the glyphs in the `ShapedText` from right
799        // to left.
800        let iterator = if self.shaped_text.is_rtl {
801            Either::Left(
802                self.shaped_text
803                    .glyph_slice(0..self.current_glyph_offset)
804                    .rev(),
805            )
806        } else {
807            Either::Right(
808                self.shaped_text
809                    .glyph_slice(self.current_glyph_offset..self.shaped_text.glyph_count()),
810            )
811        };
812
813        for glyph in iterator {
814            // When glyphs span two character slices, prioritize the first slice and also
815            // ensure that glyphs that span zero characters are also included there.
816            if self.current_character_offset >= desired_character_offset &&
817                glyph.character_count().0 > 0
818            {
819                break;
820            }
821
822            if self.current_character_offset >= starting_removable_offset {
823                removable_advance += glyph.advance();
824                removable_glyph_count += 1;
825            } else if self.current_character_offset >= starting_hangable_offset {
826                hangable_advance += glyph.advance();
827                hangable_glyph_count += 1;
828            }
829
830            glyph_count += 1;
831            self.current_character_offset += glyph.character_count();
832            total_advance += glyph.advance();
833            if glyph.char_is_word_separator() {
834                total_word_separators += 1;
835            }
836        }
837
838        let (new_glyph_offset, glyph_range) = if self.shaped_text.is_rtl {
839            assert!(self.current_glyph_offset >= glyph_count);
840            let new_glyph_offset = self.current_glyph_offset - glyph_count;
841            (
842                new_glyph_offset,
843                new_glyph_offset..self.current_glyph_offset,
844            )
845        } else {
846            let new_glyph_offset = self.current_glyph_offset + glyph_count;
847            (
848                new_glyph_offset,
849                self.current_glyph_offset..new_glyph_offset,
850            )
851        };
852
853        if glyph_count == 0 {
854            return None;
855        }
856
857        self.current_glyph_offset = new_glyph_offset;
858        Some(Arc::new(ShapedTextSlice {
859            shaped_text: self.shaped_text.clone(),
860            glyph_range,
861            total_advance,
862            character_count: self.current_character_offset - original_character_offset,
863            total_word_separators,
864            trailing_white_space: TrailingWhiteSpace {
865                hangable: GlyphCountAndAdvance::new(hangable_glyph_count, hangable_advance),
866                removable: GlyphCountAndAdvance::new(removable_glyph_count, removable_advance),
867            },
868            all_white_space,
869        }))
870    }
871}