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 serde::{Deserialize, Serialize};
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, Deserialize, MallocSizeOf, PartialEq, Serialize)]
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, Deserialize, MallocSizeOf, Serialize)]
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: usize,
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) -> usize {
189        match self {
190            GlyphInfo::Simple(..) => 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, Deserialize, MallocSizeOf, Serialize)]
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: usize,
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: 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) -> usize {
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), 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 += 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: usize,
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 += 1;
406        self.character_count += 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 let Some(letter_spacing) = options.letter_spacing &&
420            letter_spacing != Au::zero()
421        {
422            let last_glyph_index = self.ensure_last_glyph_is_detailed();
423            self.detailed_glyphs[last_glyph_index].advance -= letter_spacing;
424        }
425
426        // Add a detailed glyph entry for this new glyph, but it corresponds to a character
427        // we have already started processing. It should not contribute any character count.
428        self.add_detailed_glyph(shaped_glyph, None, 0);
429    }
430
431    /// If the last glyph added to this [`ShapedText`] was a simple glyph, convert it to a
432    /// detailed one. In either case, return the index into [`Self::detailed_glyphs`] for
433    /// the most recently added glyph.
434    fn ensure_last_glyph_is_detailed(&mut self) -> usize {
435        let last_glyph = self
436            .glyphs
437            .last_mut()
438            .expect("Should never call this before any glyphs have been added.");
439        if !last_glyph.is_simple() {
440            return last_glyph.detailed_glyph_index();
441        }
442
443        self.detailed_glyphs.push(DetailedGlyphEntry {
444            id: last_glyph.id(),
445            advance: last_glyph.advance(),
446            offset: Default::default(),
447            character_count: 1,
448            is_word_separator: last_glyph.char_is_word_separator(),
449        });
450
451        let detailed_glyph_index = self.detailed_glyphs.len() - 1;
452        *last_glyph = GlyphEntry::complex(detailed_glyph_index);
453        detailed_glyph_index
454    }
455
456    pub fn glyphs(&self) -> impl DoubleEndedIterator<Item = GlyphInfo<'_>> + use<'_> {
457        self.glyph_slice(0..self.glyphs.len())
458    }
459
460    fn glyph_slice(
461        &self,
462        glyph_range: Range<usize>,
463    ) -> impl DoubleEndedIterator<Item = GlyphInfo<'_>> + use<'_> {
464        self.glyphs[glyph_range].iter().map(|entry| {
465            if entry.is_simple() {
466                GlyphInfo::Simple(entry)
467            } else {
468                GlyphInfo::Detail(&self.detailed_glyphs[entry.detailed_glyph_index()])
469            }
470        })
471    }
472}
473
474impl ShapedGlyph {
475    fn can_be_simple_glyph(&self) -> bool {
476        is_simple_glyph_id(self.glyph_id) &&
477            is_simple_advance(self.advance) &&
478            self.offset
479                .is_none_or(|offset| offset == Default::default())
480    }
481
482    /// After shaping is complete, some glyphs need their spacing adjusted to take into
483    /// account `letter-spacing` and `word-spacing`.
484    pub(crate) fn adjust_for_character(
485        &mut self,
486        character: char,
487        shaping_options: &ShapingOptions,
488    ) {
489        if let Some(letter_spacing) = shaping_options.letter_spacing_for_character(character) {
490            self.advance += letter_spacing;
491        };
492
493        // CSS 2.1 ยง 16.4 states that "word spacing affects each space (U+0020) and non-breaking
494        // space (U+00A0) left in the text after the white space processing rules have been
495        // applied. The effect of the property on other word-separator characters is undefined."
496        // We elect to only space the two required code points.
497        if let Some(word_spacing) = shaping_options.word_spacing &&
498            (character == ' ' || character == '\u{a0}')
499        {
500            // https://drafts.csswg.org/css-text-3/#word-spacing-property
501            self.advance += word_spacing;
502        }
503    }
504}
505
506fn character_is_word_separator(character: char) -> bool {
507    // This list is taken from the non-exhaustive list of word separator characters in
508    // the CSS Text Module Level 3 Spec:
509    // See https://drafts.csswg.org/css-text/#word-separator
510    let is_word_separator = matches!(
511        character,
512        ' ' |
513                '\u{00A0}' | // non-breaking space
514                '\u{1361}' | // Ethiopic word space
515                '\u{10100}' | // Aegean word separator
516                '\u{10101}' | // Aegean word separator
517                '\u{1039F}' | // Ugartic word divider
518                '\u{1091F}' // Phoenician word separator
519    );
520    is_word_separator
521}
522
523impl fmt::Debug for ShapedText {
524    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
525        writeln!(formatter, "ShapedText:")?;
526        for entry in self.glyphs.iter() {
527            if entry.is_simple() {
528                writeln!(
529                    formatter,
530                    "  simple id={:?} advance={:?}",
531                    entry.id(),
532                    entry.advance()
533                )?;
534                continue;
535            } else {
536                let detailed_glyph = &self.detailed_glyphs[entry.detailed_glyph_index()];
537                writeln!(
538                    formatter,
539                    "  detailed id={:?} advance={:?} characters={:?}",
540                    detailed_glyph.id, detailed_glyph.advance, detailed_glyph.character_count,
541                )?;
542            }
543        }
544        Ok(())
545    }
546}
547
548#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
549pub enum ShapedTextSliceType {
550    /// A [`ShapedTextSlice`] that is a word that is not followed by white space.
551    Word,
552    /// A [`ShapedTextSlice`] composed of only white space glyphs.
553    WhiteSpace,
554    /// A [`ShapedTextSlice`] that is a word that ends with a white space glyphs.
555    /// Typically whitespace glyphs are placed in a separate slice, but that may not be
556    /// the case with `white-space: break-spaces`.
557    WordAndWhiteSpace,
558}
559
560/// A slice of a [`ShapedText`] which allows having different views into a shaped
561/// text run. This is used for splitting up shaped text during layout, without
562/// duplicating the entire run.
563#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
564pub struct ShapedTextSlice {
565    /// The [`ShapedText`] that this [`ShapedTextSlice`] refers to.
566    #[conditional_malloc_size_of]
567    shaped_text: Arc<ShapedText>,
568
569    /// The range of glyphs within the [`ShapedText`] that this [`ShapedTextSlice`] represents.
570    glyph_range: Range<usize>,
571
572    /// A cache of the advance of the entire [`ShapedTextSlice`].
573    total_advance: Au,
574
575    /// The number of characters that correspond to the glyphs in this [`ShapedTextSlice`]
576    character_count: usize,
577
578    /// A precomputed count of the number of word separators in the entire [`ShapedTextSlice`]. See
579    /// <https://drafts.csswg.org/css-text/#word-separator>.
580    total_word_separators: usize,
581
582    /// The [`ShapedTextSliceType`] of this [`ShapedTextSlice`].
583    slice_type: ShapedTextSliceType,
584}
585
586impl ShapedTextSlice {
587    /// Return the number of glyphs represented by this [`ShapedTextSlice`].
588    #[inline]
589    pub fn glyph_count(&self) -> usize {
590        self.glyph_range.len()
591    }
592
593    /// The number of characters that were consumed to produce this [`ShapedTextSlice`]. Some
594    /// characters correspond to more than one glyph and some glyphs correspond to more than
595    /// one character.
596    #[inline]
597    pub fn character_count(&self) -> usize {
598        self.character_count
599    }
600
601    /// The total advance of the characters represented by this [`ShapedTextSlice`].
602    #[inline]
603    pub fn total_advance(&self) -> Au {
604        self.total_advance
605    }
606
607    /// The number of word separators in this [`ShapedTextSlice`].
608    #[inline]
609    pub fn total_word_separators(&self) -> usize {
610        self.total_word_separators
611    }
612
613    /// Whether or not this [`ShapedTextSlice`] is entirely whitespace.
614    #[inline]
615    pub fn is_whitespace(&self) -> bool {
616        matches!(self.slice_type, ShapedTextSliceType::WhiteSpace)
617    }
618
619    /// Whether or not this [`ShapedTextSlice`] ends with whitespace.
620    #[inline]
621    pub fn ends_with_whitespace(&self) -> bool {
622        match self.slice_type {
623            ShapedTextSliceType::Word => false,
624            ShapedTextSliceType::WhiteSpace | ShapedTextSliceType::WordAndWhiteSpace => true,
625        }
626    }
627
628    /// An iterator over the glyphs represented by this [`ShapedTextSlice`].
629    pub fn glyphs(&self) -> impl DoubleEndedIterator<Item = GlyphInfo<'_>> + use<'_> {
630        self.shaped_text.glyph_slice(self.glyph_range.clone())
631    }
632}
633
634/// A data structure used to efficiently slice up a [`ShapedText`] into [`ShapedTextSlice`]s.
635pub struct ShapedTextSlicer {
636    current_glyph_offset: usize,
637    current_character_offset: usize,
638    shaped_text: Arc<ShapedText>,
639}
640
641impl ShapedTextSlicer {
642    pub fn new(shaped_text: Arc<ShapedText>) -> Self {
643        let current_glyph_offset = if shaped_text.is_rtl {
644            shaped_text.glyph_count()
645        } else {
646            0
647        };
648
649        Self {
650            current_glyph_offset,
651            current_character_offset: 0,
652            shaped_text,
653        }
654    }
655
656    /// Given a desired character offset, consume glyphs until that character offset
657    /// is reached (inclusive of glyphs that come from zero characters). Return those
658    /// glyphs as a [`ShapedTextSlice`] tagged with the given whitespace-related
659    /// properties. Returns `None` if the resulting slice would not hold any glyphs.
660    pub fn slice_until_character_offset(
661        &mut self,
662        desired_character_offset: usize,
663        slice_type: ShapedTextSliceType,
664    ) -> Option<Arc<ShapedTextSlice>> {
665        let mut glyph_count = 0;
666        let mut total_word_separators = 0;
667        let mut total_advance = Au::zero();
668        let original_character_offset = self.current_character_offset;
669
670        if self.current_character_offset >= desired_character_offset {
671            return None;
672        }
673
674        // In `ShapedText` glyphs are stored in physical left-to-right order, which means that the
675        // indices of the characters that they represent might decrease. Since we want to consume
676        // characters in memory order, we may need to walk the glyphs in the `ShapedText` from right
677        // to left.
678        let iterator = if self.shaped_text.is_rtl {
679            Either::Left(
680                self.shaped_text
681                    .glyph_slice(0..self.current_glyph_offset)
682                    .rev(),
683            )
684        } else {
685            Either::Right(
686                self.shaped_text
687                    .glyph_slice(self.current_glyph_offset..self.shaped_text.glyph_count()),
688            )
689        };
690
691        for glyph in iterator {
692            // When glyphs span two character slices, prioritize the first slice and also
693            // ensure that glyphs that span zero characters are also included there.
694            if self.current_character_offset >= desired_character_offset &&
695                glyph.character_count() > 0
696            {
697                break;
698            }
699
700            glyph_count += 1;
701            self.current_character_offset += glyph.character_count();
702            total_advance += glyph.advance();
703            if glyph.char_is_word_separator() {
704                total_word_separators += 1;
705            }
706        }
707
708        let (new_glyph_offset, glyph_range) = if self.shaped_text.is_rtl {
709            assert!(self.current_glyph_offset >= glyph_count);
710            let new_glyph_offset = self.current_glyph_offset - glyph_count;
711            (
712                new_glyph_offset,
713                new_glyph_offset..self.current_glyph_offset,
714            )
715        } else {
716            let new_glyph_offset = self.current_glyph_offset + glyph_count;
717            (
718                new_glyph_offset,
719                self.current_glyph_offset..new_glyph_offset,
720            )
721        };
722
723        if glyph_count == 0 {
724            return None;
725        }
726
727        self.current_glyph_offset = new_glyph_offset;
728        Some(Arc::new(ShapedTextSlice {
729            shaped_text: self.shaped_text.clone(),
730            glyph_range,
731            total_advance,
732            character_count: self.current_character_offset - original_character_offset,
733            total_word_separators,
734            slice_type,
735        }))
736    }
737}