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