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    /// Adds glyph that corresponds to a single character (as far we know) in the originating string.
352    #[inline]
353    pub(crate) fn add_glyph(&mut self, character: char, glyph: &ShapedGlyph) {
354        if !glyph.can_be_simple_glyph() {
355            self.add_detailed_glyph(glyph, Some(character), 1);
356            return;
357        }
358
359        let mut simple_glyph_entry = GlyphEntry::simple(glyph.glyph_id, glyph.advance);
360        if character_is_word_separator(character) {
361            self.total_word_separators += 1;
362            simple_glyph_entry.set_char_is_word_separator();
363        }
364
365        self.character_count += 1;
366        self.total_advance += glyph.advance;
367        self.glyphs.push(simple_glyph_entry)
368    }
369
370    fn add_detailed_glyph(
371        &mut self,
372        shaped_glyph: &ShapedGlyph,
373        character: Option<char>,
374        character_count: usize,
375    ) {
376        let is_word_separator = character.is_some_and(character_is_word_separator);
377        if is_word_separator {
378            self.total_word_separators += 1;
379        }
380
381        self.character_count += character_count;
382        self.total_advance += shaped_glyph.advance;
383        self.detailed_glyphs.push(DetailedGlyphEntry {
384            id: shaped_glyph.glyph_id,
385            advance: shaped_glyph.advance,
386            offset: shaped_glyph.offset,
387            character_count,
388            is_word_separator,
389        });
390        self.glyphs
391            .push(GlyphEntry::complex(self.detailed_glyphs.len() - 1));
392    }
393
394    fn extend_previous_glyph_by_character(&mut self) {
395        let detailed_glyph_index = self.ensure_last_glyph_is_detailed();
396        let detailed_glyph = self
397            .detailed_glyphs
398            .get_mut(detailed_glyph_index)
399            .expect("GlyphEntry should have valid index to detailed glyph");
400        detailed_glyph.character_count += 1;
401        self.character_count += 1;
402    }
403
404    fn add_glyph_for_current_character(
405        &mut self,
406        shaped_glyph: &ShapedGlyph,
407        options: &ShapingOptions,
408    ) {
409        // If this glyph cluster is extending to include another glyph and we applied
410        // letter spacing to the previous glyph, ensure that the letter spacing is only
411        // applied to the last glyph in the cluster. Note that this is unconditionally
412        // converting the previous glyph to a detailed one because it's quite likely that
413        // the advance will not fit into the simple bitmask due to being negative.
414        if let Some(letter_spacing) = options.letter_spacing &&
415            letter_spacing != Au::zero()
416        {
417            let last_glyph_index = self.ensure_last_glyph_is_detailed();
418            self.detailed_glyphs[last_glyph_index].advance -= letter_spacing;
419        }
420
421        // Add a detailed glyph entry for this new glyph, but it corresponds to a character
422        // we have already started processing. It should not contribute any character count.
423        self.add_detailed_glyph(shaped_glyph, None, 0);
424    }
425
426    /// If the last glyph added to this [`ShapedText`] was a simple glyph, convert it to a
427    /// detailed one. In either case, return the index into [`Self::detailed_glyphs`] for
428    /// the most recently added glyph.
429    fn ensure_last_glyph_is_detailed(&mut self) -> usize {
430        let last_glyph = self
431            .glyphs
432            .last_mut()
433            .expect("Should never call this before any glyphs have been added.");
434        if !last_glyph.is_simple() {
435            return last_glyph.detailed_glyph_index();
436        }
437
438        self.detailed_glyphs.push(DetailedGlyphEntry {
439            id: last_glyph.id(),
440            advance: last_glyph.advance(),
441            offset: Default::default(),
442            character_count: 1,
443            is_word_separator: last_glyph.char_is_word_separator(),
444        });
445
446        let detailed_glyph_index = self.detailed_glyphs.len() - 1;
447        *last_glyph = GlyphEntry::complex(detailed_glyph_index);
448        detailed_glyph_index
449    }
450
451    pub fn glyphs(&self) -> impl DoubleEndedIterator<Item = GlyphInfo<'_>> + use<'_> {
452        self.glyph_slice(0..self.glyphs.len())
453    }
454
455    fn glyph_slice(
456        &self,
457        glyph_range: Range<usize>,
458    ) -> impl DoubleEndedIterator<Item = GlyphInfo<'_>> + use<'_> {
459        self.glyphs[glyph_range].iter().map(|entry| {
460            if entry.is_simple() {
461                GlyphInfo::Simple(entry)
462            } else {
463                GlyphInfo::Detail(&self.detailed_glyphs[entry.detailed_glyph_index()])
464            }
465        })
466    }
467}
468
469impl ShapedGlyph {
470    fn can_be_simple_glyph(&self) -> bool {
471        is_simple_glyph_id(self.glyph_id) &&
472            is_simple_advance(self.advance) &&
473            self.offset
474                .is_none_or(|offset| offset == Default::default())
475    }
476
477    /// After shaping is complete, some glyphs need their spacing adjusted to take into
478    /// account `letter-spacing` and `word-spacing`.
479    pub(crate) fn adjust_for_character(
480        &mut self,
481        character: char,
482        shaping_options: &ShapingOptions,
483    ) {
484        if let Some(letter_spacing) = shaping_options.letter_spacing_for_character(character) {
485            self.advance += letter_spacing;
486        };
487
488        // CSS 2.1 ยง 16.4 states that "word spacing affects each space (U+0020) and non-breaking
489        // space (U+00A0) left in the text after the white space processing rules have been
490        // applied. The effect of the property on other word-separator characters is undefined."
491        // We elect to only space the two required code points.
492        if let Some(word_spacing) = shaping_options.word_spacing &&
493            (character == ' ' || character == '\u{a0}')
494        {
495            // https://drafts.csswg.org/css-text-3/#word-spacing-property
496            self.advance += word_spacing;
497        }
498    }
499}
500
501fn character_is_word_separator(character: char) -> bool {
502    // This list is taken from the non-exhaustive list of word separator characters in
503    // the CSS Text Module Level 3 Spec:
504    // See https://drafts.csswg.org/css-text/#word-separator
505    let is_word_separator = matches!(
506        character,
507        ' ' |
508                '\u{00A0}' | // non-breaking space
509                '\u{1361}' | // Ethiopic word space
510                '\u{10100}' | // Aegean word separator
511                '\u{10101}' | // Aegean word separator
512                '\u{1039F}' | // Ugartic word divider
513                '\u{1091F}' // Phoenician word separator
514    );
515    is_word_separator
516}
517
518impl fmt::Debug for ShapedText {
519    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
520        writeln!(formatter, "ShapedText:")?;
521        for entry in self.glyphs.iter() {
522            if entry.is_simple() {
523                writeln!(
524                    formatter,
525                    "  simple id={:?} advance={:?}",
526                    entry.id(),
527                    entry.advance()
528                )?;
529                continue;
530            } else {
531                let detailed_glyph = &self.detailed_glyphs[entry.detailed_glyph_index()];
532                writeln!(
533                    formatter,
534                    "  detailed id={:?} advance={:?} characters={:?}",
535                    detailed_glyph.id, detailed_glyph.advance, detailed_glyph.character_count,
536                )?;
537            }
538        }
539        Ok(())
540    }
541}
542
543/// A slice of a [`ShapedText`] which allows having different views into a shaped
544/// text run. This is used for splitting up shaped text during layout, without
545/// duplicating the entire run.
546#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
547pub struct ShapedTextSlice {
548    /// The [`ShapedText`] that this [`ShapedTextSlice`] refers to.
549    #[conditional_malloc_size_of]
550    shaped_text: Arc<ShapedText>,
551
552    /// The range of glyphs within the [`ShapedText`] that this [`ShapedTextSlice`] represents.
553    glyph_range: Range<usize>,
554
555    /// A cache of the advance of the entire [`ShapedTextSlice`].
556    total_advance: Au,
557
558    /// The number of characters that correspond to the glyphs in this [`ShapedTextSlice`]
559    character_count: usize,
560
561    /// A precomputed count of the number of word separators in the entire [`ShapedTextSlice`]. See
562    /// <https://drafts.csswg.org/css-text/#word-separator>.
563    total_word_separators: usize,
564
565    /// Whether or not this [`ShapedTextSlice`] is entirely whitespace.
566    is_whitespace: bool,
567
568    /// Whether or not this [`ShapedTextSlice`] ends with whitespace glyphs.
569    /// Typically whitespace glyphs are placed in a separate slice,
570    /// but that may not be the case with `white-space: break-spaces`.
571    ends_with_whitespace: bool,
572}
573
574impl ShapedTextSlice {
575    /// Return the [`ShapedText`] that backs this [`ShapedTextSlice`].
576    #[inline]
577    pub fn shaped_text(&self) -> Arc<ShapedText> {
578        self.shaped_text.clone()
579    }
580
581    /// Return the number of glyphs represented by this [`ShapedTextSlice`].
582    #[inline]
583    pub fn glyph_count(&self) -> usize {
584        self.glyph_range.len()
585    }
586
587    /// The number of characters that were consumed to produce this [`ShapedTextSlice`]. Some
588    /// characters correspond to more than one glyph and some glyphs correspond to more than
589    /// one character.
590    #[inline]
591    pub fn character_count(&self) -> usize {
592        self.character_count
593    }
594
595    /// The total advance of the characters represented by this [`ShapedTextSlice`].
596    #[inline]
597    pub fn total_advance(&self) -> Au {
598        self.total_advance
599    }
600
601    /// The number of word separators in this [`ShapedTextSlice`].
602    #[inline]
603    pub fn total_word_separators(&self) -> usize {
604        self.total_word_separators
605    }
606
607    /// Whether or not this [`ShapedTextSlice`] is entirely whitespace.
608    #[inline]
609    pub fn is_whitespace(&self) -> bool {
610        self.is_whitespace
611    }
612
613    /// Whether or not this [`ShapedTextSlice`] ends with whitespace.
614    #[inline]
615    pub fn ends_with_whitespace(&self) -> bool {
616        self.ends_with_whitespace
617    }
618
619    /// An iterator over the glyphs represented by this [`ShapedTextSlice`].
620    pub fn glyphs(&self) -> impl DoubleEndedIterator<Item = GlyphInfo<'_>> + use<'_> {
621        self.shaped_text.glyph_slice(self.glyph_range.clone())
622    }
623}
624
625/// A data structure used to efficiently slice up a [`ShapedText`] into [`ShapedTextSlice`]s.
626pub struct ShapedTextSlicer {
627    current_glyph_offset: usize,
628    shaped_text: Arc<ShapedText>,
629}
630
631impl ShapedTextSlicer {
632    pub fn new(shaped_text: Arc<ShapedText>) -> Self {
633        let current_glyph_offset = if shaped_text.is_rtl {
634            shaped_text.glyph_count()
635        } else {
636            0
637        };
638
639        Self {
640            current_glyph_offset,
641            shaped_text,
642        }
643    }
644
645    /// Given a desired character count, consume that number of characters worth
646    /// of glyphs from the [`ShapedText`] of this [`ShapedTextSlicer`]. In addition,
647    /// tag the resulting [`ShapedTextSlice`] with the given whitespace-related
648    /// properties.
649    pub fn slice_for_character_count(
650        &mut self,
651        desired_character_count: usize,
652        is_whitespace: bool,
653        ends_with_whitespace: bool,
654    ) -> Arc<ShapedTextSlice> {
655        let mut glyph_count = 0;
656        let mut character_count = 0;
657        let mut total_word_separators = 0;
658        let mut total_advance = Au::zero();
659
660        // In `ShapedText` glyphs are stored in physical left-to-right order, which means that the
661        // indices of the characters that they represent might decrease. Since we want to consume
662        // characters in memory order, we may need to walk the glyphs in the `ShapedText` from right
663        // to left.
664        let iterator = if self.shaped_text.is_rtl {
665            Either::Left(
666                self.shaped_text
667                    .glyph_slice(0..self.current_glyph_offset)
668                    .rev(),
669            )
670        } else {
671            Either::Right(
672                self.shaped_text
673                    .glyph_slice(self.current_glyph_offset..self.shaped_text.glyph_count()),
674            )
675        };
676
677        for glyph in iterator {
678            // When one character produces multiple glyphs, only the first glyph has a character
679            // count of one and the rest have a character count of 0. This checks the potential
680            // total before accumulating a glyph, which allows merging glyphs with a 0 character
681            // count into the slice with the first glyph.
682            let glyph_character_count = glyph.character_count();
683            if character_count + glyph_character_count > desired_character_count {
684                break;
685            }
686
687            glyph_count += 1;
688            character_count += glyph_character_count;
689            total_advance += glyph.advance();
690            if glyph.char_is_word_separator() {
691                total_word_separators += 1;
692            }
693        }
694
695        let (new_glyph_offset, glyph_range) = if self.shaped_text.is_rtl {
696            assert!(self.current_glyph_offset >= glyph_count);
697            let new_glyph_offset = self.current_glyph_offset - glyph_count;
698            (
699                new_glyph_offset,
700                new_glyph_offset..self.current_glyph_offset,
701            )
702        } else {
703            let new_glyph_offset = self.current_glyph_offset + glyph_count;
704            (
705                new_glyph_offset,
706                self.current_glyph_offset..new_glyph_offset,
707            )
708        };
709
710        self.current_glyph_offset = new_glyph_offset;
711        Arc::new(ShapedTextSlice {
712            shaped_text: self.shaped_text.clone(),
713            glyph_range,
714            total_advance,
715            character_count,
716            is_whitespace,
717            ends_with_whitespace,
718            total_word_separators,
719        })
720    }
721}