Skip to main content

layout/flow/inline/
text_run.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::mem;
6use std::ops::Range;
7use std::sync::{Arc, OnceLock};
8
9use app_units::Au;
10use atomic_refcell::AtomicRefCell;
11use fonts::font_feature_values::ResolvedFontVariantAlternates;
12use fonts::{FontContext, FontRef, ShapedText, ShapedTextSlice, ShapingFlags, ShapingOptions};
13use icu_locale_core::subtags::Language;
14use icu_properties::props::{EnumeratedProperty, LineBreak};
15use log::warn;
16use malloc_size_of_derive::MallocSizeOf;
17use servo_arc::Arc as ServoArc;
18use servo_base::text::{RangeAny, Utf8CodeUnits, Utf32CodeUnits, is_bidi_control};
19use smallvec::SmallVec;
20use style::Zero;
21use style::computed_values::font_kerning::T as FontKerning;
22use style::computed_values::font_variant_position::T as FontVariantPosition;
23use style::computed_values::text_rendering::T as TextRendering;
24use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
25use style::font_face::FontLanguageOverride;
26use style::properties::ComputedValues;
27use style::values::computed::{
28    FontFeatureSettings, FontVariantEastAsian, FontVariantLigatures, FontVariantNumeric,
29};
30use unicode_bidi::Level;
31use unicode_script::Script;
32
33use super::{InlineFormattingContextLayout, SharedInlineStyles};
34use crate::ArcRefCell;
35use crate::context::LayoutContext;
36use crate::dom::WeakLayoutBox;
37use crate::flow::inline::shaping_queue::ShapingQueueEntry;
38use crate::flow::inline::text_transform::OffsetMap;
39use crate::flow::inline::{BidiLevels, LineBlockSizes, LineItem, SegmentContentFlags};
40use crate::fragment_tree::BaseFragmentInfo;
41
42// There are two reasons why we might want to break at the start:
43//
44//  1. The line breaker told us that a break was necessary between two separate
45//     instances of sending text to it.
46//  2. We are following replaced content ie `have_deferred_soft_wrap_opportunity`.
47//
48// In both cases, we don't want to do this if the first character prevents a
49// soft wrap opportunity.
50#[derive(PartialEq)]
51enum SegmentStartSoftWrapPolicy {
52    Force,
53    FollowLinebreaker,
54}
55
56/// A data structure which contains information used when shaping a [`TextRunSegment`].
57#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
58pub(crate) struct FontAndScriptInfo {
59    /// The script used when shaping a [`TextRunSegment`].
60    pub script: Script,
61    /// The rest of the font information which is never modified.
62    #[conditional_malloc_size_of]
63    pub font_info: Arc<FontInfo>,
64}
65
66impl FontAndScriptInfo {
67    /// Creates a minimal [`FontAndScriptInfo`] for a single font, with generic language settings
68    /// and the default shaping configuration. This is only used to generate placeholders for
69    /// text carets on otherwise empty lines.
70    pub(crate) fn simple_for_font(font: FontRef) -> Self {
71        Self {
72            script: Script::Common,
73            font_info: Arc::new(FontInfo::simple_for_font(font)),
74        }
75    }
76}
77
78/// A data structure which contains information used when shaping a [`TextRunSegment`].
79#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
80pub(crate) struct FontInfo {
81    /// The font used when shaping a [`TextRunSegment`].
82    pub font: FontRef,
83    /// The BiDi [`Level`] used when shaping a [`TextRunSegment`].
84    pub bidi_level: Level,
85    /// The [`Language`] used when shaping a [`TextRunSegment`].
86    pub language: Language,
87    /// Spacing to add between each letter. Corresponds to the CSS 2.1 `letter-spacing` property.
88    ///
89    /// Letter spacing is not applied to all characters. Use [Self::letter_spacing_for_character] to
90    /// determine the amount of spacing to apply.
91    pub letter_spacing: Option<Au>,
92    /// Spacing to add between each word. Corresponds to the CSS 2.1 `word-spacing` property.
93    pub word_spacing: Option<Au>,
94    /// The [`TextRendering`] value from the original style.
95    pub text_rendering: TextRendering,
96    /// The value of the `font-kerning` property from the original style.
97    pub kerning: FontKerning,
98    /// The value of the `font-variant-ligatures` property from the original style.
99    pub ligatures: FontVariantLigatures,
100    /// The value of the `font-variant-numeric` property from the original style.
101    pub numeric: FontVariantNumeric,
102    /// The value of the `font-variant-east-asian` property from the original style.
103    pub east_asian: FontVariantEastAsian,
104    /// The value of the `font-feature-settings` property from the original style.
105    pub feature_settings: FontFeatureSettings,
106    /// The value of the `font-variant-position` property from the original style.
107    pub position: FontVariantPosition,
108    /// The value of the `font-variant-alternates` property from the original style.
109    ///
110    /// Any alternate names are already resolved at this point.
111    pub alternates: ResolvedFontVariantAlternates,
112}
113
114impl FontInfo {
115    fn simple_for_font(font: FontRef) -> Self {
116        Self {
117            font,
118            bidi_level: Level::ltr(),
119            language: Language::UNKNOWN,
120            letter_spacing: None,
121            word_spacing: None,
122            text_rendering: TextRendering::Auto,
123            kerning: FontKerning::Auto,
124            ligatures: FontVariantLigatures::NORMAL,
125            numeric: FontVariantNumeric::NORMAL,
126            east_asian: FontVariantEastAsian::NORMAL,
127            feature_settings: FontFeatureSettings::normal(),
128            position: FontVariantPosition::Normal,
129            alternates: Default::default(),
130        }
131    }
132}
133
134impl From<&FontAndScriptInfo> for ShapingOptions {
135    fn from(info: &FontAndScriptInfo) -> Self {
136        let mut ligatures = info.font_info.ligatures;
137        let mut flags = ShapingFlags::empty();
138        if info.font_info.bidi_level.is_rtl() {
139            flags.insert(ShapingFlags::RTL_FLAG);
140        }
141
142        // From https://www.w3.org/TR/css-text-3/#cursive-script:
143        // Cursive scripts do not admit gaps between their letters for either
144        // justification or letter-spacing.
145        let letter_spacing = info
146            .font_info
147            .letter_spacing
148            .filter(|_| !is_cursive_script(info.script));
149        if letter_spacing.is_some() {
150            ligatures = FontVariantLigatures::NONE;
151        };
152        let letter_spacing = letter_spacing.unwrap_or(Au::zero());
153        let word_spacing = info.font_info.word_spacing.unwrap_or(Au::zero());
154        if info.font_info.text_rendering == TextRendering::Optimizespeed {
155            ligatures = FontVariantLigatures::NONE;
156            flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
157        }
158
159        // We currently always leave kerning enabled for "font-kerning: auto".
160        if info.font_info.kerning == FontKerning::None {
161            flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG);
162        }
163
164        Self {
165            letter_spacing,
166            word_spacing,
167            script: info.script,
168            language: info.font_info.language,
169            ligatures,
170            numeric: info.font_info.numeric,
171            east_asian: info.font_info.east_asian,
172            feature_settings: info.font_info.feature_settings.clone(),
173            position: info.font_info.position,
174            flags,
175            alternates: info.font_info.alternates.clone(),
176        }
177    }
178}
179
180#[derive(Clone, Debug, MallocSizeOf)]
181pub(crate) struct TextRunSegment {
182    /// Information about the font and language used in this text run. This is produced by
183    /// segmenting the inline formatting context's text content by font, script, and bidi level.
184    pub info: FontAndScriptInfo,
185
186    /// The range of bytes in the parent [`super::InlineFormattingContext`]'s text content.
187    pub byte_range: Range<Utf8CodeUnits>,
188
189    /// The range of characters in the parent [`super::InlineFormattingContext`]'s text content.
190    pub character_range: Range<Utf32CodeUnits>,
191
192    /// Whether or not the linebreaker said that we should allow a line break at the start of this
193    /// segment.
194    pub break_at_start: bool,
195
196    /// The shaped runs within this segment.
197    #[conditional_malloc_size_of]
198    pub runs: Vec<Arc<ShapedTextSlice>>,
199
200    /// The shaped text that was used to produce this segment. [`Self::runs`] are slices
201    /// of this shaped text.
202    #[conditional_malloc_size_of]
203    pub shaped_text: Option<Arc<ShapedText>>,
204}
205
206impl TextRunSegment {
207    fn new(
208        info: FontAndScriptInfo,
209        byte_range: Range<Utf8CodeUnits>,
210        character_range: Range<Utf32CodeUnits>,
211    ) -> Self {
212        Self {
213            info,
214            byte_range,
215            character_range,
216            runs: Vec::new(),
217            break_at_start: false,
218            shaped_text: None,
219        }
220    }
221
222    /// Returns true if the new `Font`, `Script` and BiDi `Level` are compatible with this segment
223    /// or false otherwise.
224    fn is_compatible(
225        &self,
226        new_font: &Option<FontRef>,
227        new_script: Script,
228        new_bidi_level: Level,
229    ) -> bool {
230        if self.info.font_info.bidi_level != new_bidi_level {
231            return false;
232        }
233        if new_font
234            .as_ref()
235            .is_some_and(|new_font| !Arc::ptr_eq(&self.info.font_info.font, new_font))
236        {
237            return false;
238        }
239
240        !script_is_specific(self.info.script) ||
241            !script_is_specific(new_script) ||
242            self.info.script == new_script
243    }
244
245    /// Update this segment to end at the given byte and character index. The update will only ever
246    /// make the Script specific and will not change it otherwise.
247    fn update(
248        &mut self,
249        next_byte_index: Utf8CodeUnits,
250        next_character_index: Utf32CodeUnits,
251        new_script: Script,
252    ) {
253        if !script_is_specific(self.info.script) && script_is_specific(new_script) {
254            self.info = FontAndScriptInfo {
255                script: new_script,
256                font_info: self.info.font_info.clone(),
257            };
258        }
259        self.character_range.end = next_character_index;
260        self.byte_range.end = next_byte_index;
261    }
262
263    fn layout_into_line_items(
264        &self,
265        text_run: &TextRun,
266        mut soft_wrap_policy: SegmentStartSoftWrapPolicy,
267        ifc: &mut InlineFormattingContextLayout,
268    ) {
269        if self.break_at_start && soft_wrap_policy == SegmentStartSoftWrapPolicy::FollowLinebreaker
270        {
271            soft_wrap_policy = SegmentStartSoftWrapPolicy::Force;
272        }
273
274        let mut character_range_start = self.character_range.start;
275        for (run_index, run) in self.runs.iter().enumerate() {
276            let new_character_range_end = character_range_start + run.character_count();
277
278            // Break before each unbreakable run in this TextRun, except the first unless the
279            // linebreaker was set to break before the first run.
280            if run_index != 0 || soft_wrap_policy == SegmentStartSoftWrapPolicy::Force {
281                ifc.process_soft_wrap_opportunity();
282            }
283
284            let run_start = text_run.run_data.character_range_in_ifc_text.start;
285            ifc.push_shaped_text_slice_to_unbreakable_segment(
286                run.clone(),
287                text_run,
288                &self.info,
289                character_range_start - run_start..new_character_range_end - run_start,
290            );
291
292            character_range_start = new_character_range_end;
293        }
294    }
295
296    pub(crate) fn is_compatible_with_old_shaping_result(&self, old_segment: &Self) -> bool {
297        old_segment.info == self.info && self.byte_range == old_segment.byte_range
298    }
299}
300
301#[derive(Clone, Debug, MallocSizeOf)]
302pub(crate) struct CaretPlaceholder {
303    /// The [`TextFragmentRunData`] of the [`TextRun`] that contains this caret placeholder.
304    #[conditional_malloc_size_of]
305    pub run_data: Arc<SharedTextRunData>,
306    /// The `BaseFragmentInfo` of the originating text node that this caret placeholder is in.
307    pub base_fragment_info: BaseFragmentInfo,
308    /// Character index of the preserved newline in the IFC's transformed text, relative
309    /// to the start of the DOM node.
310    pub character_index: Utf32CodeUnits,
311}
312
313/// A single item in a [`TextRun`].
314#[derive(Debug, MallocSizeOf)]
315pub(crate) enum TextRunItem {
316    /// A hard line break i.e. a "\n" as other types line breaks are normalized to "\n".
317    LineBreak(Option<CaretPlaceholder>),
318    /// A preserved tab character that should advance the line to a tab stop.
319    Tab { bidi_level: Level },
320    /// Any other text for which a font can be matched. We store a `Box` here as [`TextRunSegment`]
321    /// is quite a bit larger than the other enum variants.
322    TextSegment(Box<TextRunSegment>),
323}
324
325/// A data structure that holds per-`TextRun` data used on `TextFragment`s.
326/// This ensures that the data is not duplicated between fragments.
327#[derive(Debug, MallocSizeOf)]
328pub(crate) struct SharedTextRunData {
329    /// The text content of the [`InlineFormattingContext`] that contains this `TextRun`.
330    #[conditional_malloc_size_of]
331    pub text_content: Arc<OnceLock<String>>,
332    /// The [`crate::SharedStyle`] from this `TextRun`'s parent element. This is
333    /// shared so that incremental layout can simply update the parent element and
334    /// this [`TextRun`] will be updated automatically.
335    pub inline_styles: SharedInlineStyles,
336    /// The range of characters in this text in `InlineFormattingContext::text_content`
337    /// of the `InlineFormattingContext` that owns this `TextRun`. These are counting
338    /// `char`s, *not* UTF-8 offsets.
339    pub character_range_in_ifc_text: Range<Utf32CodeUnits>,
340    /// The original offset of this `TextRun` in the `InlineFormattingContext`'s input
341    /// text (untransformed by white space collapse and `text-transform`).
342    pub original_offset: Utf32CodeUnits,
343    /// The selected text in this `TextRun`. This may either be document selection or form control
344    /// selection.
345    // TODO: make this more compact with a pair of `AtomicUsize`?
346    pub selection: AtomicRefCell<Option<RangeAny<Utf32CodeUnits>>>,
347    /// Whether a caret should be painted when the selection is an empty range (start == end)
348    pub paints_caret: bool,
349    /// Whether a caret placeholder should be added for empty text runs or for empty
350    /// lines after a forced line break.
351    pub needs_caret_placeholder: bool,
352    /// The [`OffsetMap`] used when creating this `TextRun`'s `InlineFormattingContext`. This
353    /// is used for mapping between DOM text offsets and layout text offsets (and vice-versa).
354    pub offset_map: ArcRefCell<OffsetMap>,
355}
356
357impl SharedTextRunData {
358    /// Map a range in the originating `TextRun`'s DOM node text into the range in the
359    /// `TextRun`'s layout transformed (by white space collapse and `text-transform`)
360    /// text.
361    pub(crate) fn map_dom_range_to_transformed_range(
362        &self,
363        dom_range: RangeAny<Utf32CodeUnits>,
364    ) -> Range<Utf32CodeUnits> {
365        let offset_map = self.offset_map.borrow();
366        let offset_in_ifc_text = self.character_range_in_ifc_text.start;
367        let start = if let Some(dom_start) = dom_range.start() {
368            offset_map.map(dom_start + self.original_offset) - offset_in_ifc_text
369        } else {
370            Utf32CodeUnits(0)
371        };
372        let end = if let Some(dom_end) = dom_range.end() {
373            offset_map.map(dom_end + self.original_offset) - offset_in_ifc_text
374        } else {
375            self.character_range_in_ifc_text.end - self.character_range_in_ifc_text.start
376        };
377        start..end
378    }
379
380    /// Map an offset in the originating `TextRun`s DOM node's transformed text (by white
381    /// space collapse and `text-transform`) to untransformed text for use by the DOM.
382    pub(crate) fn map_transformed_offset_to_dom_offset(
383        &self,
384        offset: Utf32CodeUnits,
385    ) -> Utf32CodeUnits {
386        let offset_map = self.offset_map.borrow();
387        let offset_in_ifc_text = self.character_range_in_ifc_text.start;
388        offset_map.reverse_map(offset + offset_in_ifc_text) - self.original_offset
389    }
390}
391
392/// A single [`TextRun`] for the box tree. These are all descendants of
393/// [`super::InlineBox`] or the root of the [`super::InlineFormattingContext`].  During
394/// box tree construction, text is split into [`TextRun`]s based on their font, script,
395/// etc. When these are created text is already shaped.
396///
397/// <https://www.w3.org/TR/css-display-3/#css-text-run>
398#[derive(Debug, MallocSizeOf)]
399pub(crate) struct TextRun {
400    /// The [`BaseFragmentInfo`] for this [`TextRun`]. Usually this comes from the
401    /// original text node in the DOM for the text.
402    pub base_fragment_info: BaseFragmentInfo,
403
404    /// Data to be used by all [`TextFragment`]s spawned by this [`TextRun`] to avoid
405    /// having to clone the data into each fragment.
406    #[conditional_malloc_size_of]
407    pub run_data: Arc<SharedTextRunData>,
408
409    /// A weak reference to the parent of this layout box. This becomes valid as soon
410    /// as the *parent* of this box is added to the tree.
411    pub parent_box: Option<WeakLayoutBox>,
412
413    /// The range of text in [`super::InlineFormattingContext::text_content`] of the
414    /// [`super::InlineFormattingContext`] that owns this [`TextRun`]. These are UTF-8 offsets.
415    pub text_range: Range<Utf8CodeUnits>,
416
417    /// The [`TextRunItem`]s of this text run. This is produced by segmenting the incoming text
418    /// by things such as font and script as well as separating out hard line breaks.
419    /// segments, and shaped.
420    pub items: Vec<TextRunItem>,
421}
422
423impl TextRun {
424    pub(crate) fn new(
425        base_fragment_info: BaseFragmentInfo,
426        run_data: Arc<SharedTextRunData>,
427        text_range: Range<Utf8CodeUnits>,
428        old_text_run: Option<ArcRefCell<TextRun>>,
429    ) -> Self {
430        // If there was a previous box tree layout of this text run, try to preserve the old shaped text.
431        let items = old_text_run
432            .map(|old_text_run| std::mem::take(&mut old_text_run.borrow_mut().items))
433            .unwrap_or_default();
434        Self {
435            base_fragment_info,
436            run_data,
437            parent_box: None,
438            text_range,
439            items,
440        }
441    }
442
443    pub(super) fn inline_styles(&self) -> &SharedInlineStyles {
444        &self.run_data.inline_styles
445    }
446
447    pub(super) fn segment(
448        &mut self,
449        self_arc_ref_cell: ArcRefCell<TextRun>,
450        formatting_context_text: &str,
451        layout_context: &LayoutContext,
452        bidi_levels: &BidiLevels,
453    ) -> SmallVec<[ShapingQueueEntry; 1]> {
454        let parent_style = self.inline_styles().style.borrow().clone();
455        let items = self.segment_text_by_font(
456            layout_context,
457            formatting_context_text,
458            bidi_levels,
459            &parent_style,
460        );
461
462        // If a previous box tree layout seeded this [`TextRun`] with old shaping results, use those
463        // to try to prevent re-shaping.
464        let mut old_text_run_items = std::mem::replace(&mut self.items, items).into_iter();
465
466        self.items
467            .iter()
468            .enumerate()
469            .map(move |(index, text_run_item)| {
470                let old_text_run_item = old_text_run_items.next();
471                ShapingQueueEntry::new(
472                    self_arc_ref_cell.clone(),
473                    text_run_item,
474                    index,
475                    old_text_run_item,
476                )
477            })
478            .collect()
479    }
480
481    /// Take the [`TextRun`]'s text and turn it into [`TextRunSegment`]s. Each segment has a matched
482    /// font and script. Fonts may differ when glyphs are found in fallback fonts.
483    /// [`super::InlineFormattingContext`].
484    fn segment_text_by_font(
485        &mut self,
486        layout_context: &LayoutContext,
487        formatting_context_text: &str,
488        bidi_levels: &BidiLevels,
489        parent_style: &ServoArc<ComputedValues>,
490    ) -> Vec<TextRunItem> {
491        let font_style = parent_style.clone_font();
492        let language = font_style._x_lang.0.parse().unwrap_or(Language::UNKNOWN);
493        let language_for_shaping = Some(font_style.font_language_override)
494            .filter(|language_override| *language_override != FontLanguageOverride::normal())
495            .and_then(|language_override| {
496                // FIXME: ICU4x limits language tags to three bytes as that is limit
497                // defined by BCP 47. But OpenType defines a couple four-letter
498                // languages, and stylo correctly stores a four-byte value for the computed
499                // value of the property.
500                //
501                // https://www.w3.org/TR/css-fonts-4/#font-language-override-string-value
502                //
503                // For now we need to truncate the language tag ):
504                Language::try_from_utf8(&language_override.0.to_be_bytes()[..3]).ok()
505            })
506            .unwrap_or(language);
507        let font_size = font_style.font_size.computed_size().into();
508        let kerning = font_style.font_kerning;
509        let ligatures = font_style.font_variant_ligatures;
510        let numeric = font_style.font_variant_numeric;
511        let east_asian = font_style.font_variant_east_asian;
512        let feature_settings = font_style.font_feature_settings.clone();
513        let position = font_style.font_variant_position;
514        let alternates = font_style.font_variant_alternates.clone();
515
516        let font_group = layout_context.font_context.font_group(font_style);
517        let inherited_text_style = parent_style.get_inherited_text();
518        let word_spacing = Some(inherited_text_style.word_spacing.to_used_value(font_size));
519        let letter_spacing = inherited_text_style
520            .letter_spacing
521            .0
522            .to_used_value(font_size);
523        let letter_spacing = if !letter_spacing.is_zero() {
524            Some(letter_spacing)
525        } else {
526            None
527        };
528        let text_rendering = inherited_text_style.text_rendering;
529
530        let mut current: Option<TextRunSegment> = None;
531        let mut results = Vec::new();
532        let finish_current_segment =
533            |current: &mut Option<TextRunSegment>, results: &mut Vec<TextRunItem>| {
534                if let Some(current) = current.take() {
535                    results.push(TextRunItem::TextSegment(Box::new(current)));
536                }
537            };
538
539        let text_run_text =
540            &formatting_context_text[Utf8CodeUnits::to_usize_range(&self.text_range)];
541        let char_iterator = TwoCharsAtATimeIterator::new(text_run_text.chars());
542        // The next bytes index of the character within the entire inline formatting context's text.
543        let mut next_byte_index = self.text_range.start;
544        for (relative_character_index, (character, next_character)) in char_iterator.enumerate() {
545            // The current character index within the entire inline formatting context's text.
546            let current_character_index = self.run_data.character_range_in_ifc_text.start +
547                Utf32CodeUnits(relative_character_index as u32);
548
549            let current_byte_index = next_byte_index;
550            next_byte_index += Utf8CodeUnits::length_of_char(character);
551
552            if character == '\n' {
553                finish_current_segment(&mut current, &mut results);
554                let needs_caret_placeholder = self.run_data.needs_caret_placeholder;
555                results.push(TextRunItem::LineBreak(needs_caret_placeholder.then(|| {
556                    CaretPlaceholder {
557                        run_data: self.run_data.clone(),
558                        base_fragment_info: self.base_fragment_info,
559                        // The placeholder that is placed after a newline is for the index after that newline.
560                        // The newline itself is at the end of the previous line.
561                        character_index: Utf32CodeUnits((relative_character_index + 1) as u32),
562                    }
563                })));
564                continue;
565            }
566
567            if character == '\t' {
568                finish_current_segment(&mut current, &mut results);
569                results.push(TextRunItem::Tab {
570                    bidi_level: bidi_levels.level(current_byte_index.into()),
571                });
572                continue;
573            }
574
575            let (font, script, bidi_level) = if character_cannot_change_font(character) {
576                (
577                    None,
578                    Script::Common,
579                    bidi_levels.level(current_byte_index.into()),
580                )
581            } else {
582                (
583                    font_group.find_by_codepoint(
584                        &layout_context.font_context,
585                        character,
586                        next_character,
587                        language,
588                    ),
589                    Script::from(character),
590                    bidi_levels.level(current_byte_index.into()),
591                )
592            };
593
594            // If the existing segment is compatible with the character, just merge the character into it.
595            if let Some(current) = current.as_mut() &&
596                current.is_compatible(&font, script, bidi_level)
597            {
598                current.update(
599                    next_byte_index,
600                    current_character_index + Utf32CodeUnits(1),
601                    script,
602                );
603                continue;
604            }
605
606            let Some(font) = font.or_else(|| font_group.first(&layout_context.font_context)) else {
607                continue;
608            };
609
610            let alternates = layout_context
611                .font_context
612                .resolve_font_variant_alternate_identifiers_for(
613                    &font,
614                    &alternates,
615                    layout_context.style_context.stylist,
616                );
617            let info = FontAndScriptInfo {
618                script,
619                font_info: Arc::new(FontInfo {
620                    font,
621                    bidi_level,
622                    language: language_for_shaping,
623                    word_spacing,
624                    letter_spacing,
625                    text_rendering,
626                    kerning,
627                    ligatures,
628                    numeric,
629                    east_asian,
630                    feature_settings: feature_settings.clone(),
631                    alternates,
632                    position,
633                }),
634            };
635
636            finish_current_segment(&mut current, &mut results);
637            assert!(current.is_none());
638
639            current = Some(TextRunSegment::new(
640                info,
641                current_byte_index..next_byte_index,
642                current_character_index..current_character_index + Utf32CodeUnits(1),
643            ));
644        }
645
646        finish_current_segment(&mut current, &mut results);
647        results
648    }
649
650    pub(super) fn layout_into_line_items(&self, ifc: &mut InlineFormattingContextLayout) {
651        if self.text_range.is_empty() {
652            if self.run_data.needs_caret_placeholder {
653                ifc.current_line.caret_placeholder = Some(CaretPlaceholder {
654                    run_data: self.run_data.clone(),
655                    base_fragment_info: self.base_fragment_info,
656                    character_index: Utf32CodeUnits(0),
657                });
658            }
659            return;
660        }
661
662        // If we are following replaced content, we should have a soft wrap opportunity, unless the
663        // first character of this `TextRun` prevents that soft wrap opportunity. If we see such a
664        // character it should also override the LineBreaker's indication to break at the start.
665        let have_deferred_soft_wrap_opportunity =
666            mem::replace(&mut ifc.have_deferred_soft_wrap_opportunity, false);
667        let mut soft_wrap_policy = match have_deferred_soft_wrap_opportunity {
668            true => SegmentStartSoftWrapPolicy::Force,
669            false => SegmentStartSoftWrapPolicy::FollowLinebreaker,
670        };
671
672        for item in self.items.iter() {
673            ifc.possibly_flush_deferred_forced_line_break();
674
675            match item {
676                // If this whitespace forces a line break, queue up a hard line break the next time we
677                // see any content. We don't line break immediately, because we'd like to finish processing
678                // any ongoing inline boxes before ending the line.
679                TextRunItem::LineBreak(caret_placeholder) => {
680                    ifc.defer_forced_line_break_at_character_offset(caret_placeholder);
681                },
682                TextRunItem::Tab { bidi_level } => self.process_preserved_tab(ifc, *bidi_level),
683                TextRunItem::TextSegment(segment) => {
684                    segment.layout_into_line_items(self, soft_wrap_policy, ifc)
685                },
686            }
687            soft_wrap_policy = SegmentStartSoftWrapPolicy::FollowLinebreaker;
688        }
689    }
690
691    fn process_preserved_tab(
692        &self,
693        ifc_layout: &mut InlineFormattingContextLayout,
694        bidi_level: Level,
695    ) {
696        let position_after_current_segment =
697            ifc_layout.current_line.inline_position + ifc_layout.current_line_segment.inline_size;
698        let advance = ifc_layout.ifc.next_tab_stop_after_inline_advance(
699            &self.inline_styles().style.borrow(),
700            position_after_current_segment,
701        );
702        if advance.is_zero() {
703            return;
704        }
705
706        // TODO: Tabs should hang when `pre-wrap` is active.
707        ifc_layout.update_unbreakable_segment_for_new_content(
708            &LineBlockSizes::zero(),
709            advance,
710            Au::zero(),
711            Au::zero(),
712            SegmentContentFlags::Contentful | SegmentContentFlags::IncorporateTrailingWhiteSpace,
713        );
714        ifc_layout.push_line_item_to_unbreakable_segment(LineItem::Tab {
715            inline_box_identifier: ifc_layout.current_inline_box_identifier(),
716            advance,
717            bidi_level,
718        });
719
720        if ifc_layout
721            .current_inline_container_state()
722            .style
723            .get_inherited_text()
724            .white_space_collapse ==
725            WhiteSpaceCollapse::BreakSpaces
726        {
727            ifc_layout.process_soft_wrap_opportunity();
728        }
729    }
730}
731
732/// From <https://www.w3.org/TR/css-text-3/#cursive-script>:
733/// Cursive scripts do not admit gaps between their letters for either justification
734/// or letter-spacing. The following Unicode scripts are included: Arabic, Hanifi
735/// Rohingya, Mandaic, Mongolian, N’Ko, Phags Pa, Syriac
736fn is_cursive_script(script: Script) -> bool {
737    matches!(
738        script,
739        Script::Arabic |
740            Script::Hanifi_Rohingya |
741            Script::Mandaic |
742            Script::Mongolian |
743            Script::Nko |
744            Script::Phags_Pa |
745            Script::Syriac
746    )
747}
748
749/// Whether or not this character should be able to change the font during segmentation.  Certain
750/// character are not rendered at all, so it doesn't matter what font we use to render them. They
751/// should just be added to the current segment.
752fn character_cannot_change_font(character: char) -> bool {
753    if character.is_control() {
754        return true;
755    }
756    if character == '\u{00A0}' {
757        return true;
758    }
759    if is_bidi_control(character) {
760        return false;
761    }
762
763    matches!(
764        LineBreak::for_char(character),
765        LineBreak::CombiningMark |
766            LineBreak::Glue |
767            LineBreak::ZWSpace |
768            LineBreak::WordJoiner |
769            LineBreak::ZWJ
770    )
771}
772
773pub(super) fn get_font_for_first_font_for_style(
774    style: &ComputedValues,
775    font_context: &FontContext,
776) -> Option<FontRef> {
777    let font = font_context
778        .font_group(style.clone_font())
779        .first(font_context);
780    if font.is_none() {
781        warn!("Could not find font for style: {:?}", style.clone_font());
782    }
783    font
784}
785pub(crate) struct TwoCharsAtATimeIterator<InputIterator> {
786    /// The input character iterator.
787    iterator: InputIterator,
788    /// The first character to produce in the next run of the iterator.
789    next_character: Option<char>,
790}
791
792impl<InputIterator> TwoCharsAtATimeIterator<InputIterator> {
793    fn new(iterator: InputIterator) -> Self {
794        Self {
795            iterator,
796            next_character: None,
797        }
798    }
799}
800
801impl<InputIterator> Iterator for TwoCharsAtATimeIterator<InputIterator>
802where
803    InputIterator: Iterator<Item = char>,
804{
805    type Item = (char, Option<char>);
806
807    fn next(&mut self) -> Option<Self::Item> {
808        // If the iterator isn't initialized do that now.
809        if self.next_character.is_none() {
810            self.next_character = self.iterator.next();
811        }
812        let character = self.next_character?;
813        self.next_character = self.iterator.next();
814        Some((character, self.next_character))
815    }
816}
817
818pub(crate) fn script_is_specific(script: Script) -> bool {
819    script != Script::Common && script != Script::Inherited
820}