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 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, 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        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    // TODO: make this more compact with a pair of `AtomicUsize`?
337    pub selection: AtomicRefCell<Option<RangeAny<Utf32CodeUnits>>>,
338    /// Whether a caret should be painted when the selection is an empty range (start == end)
339    pub paint_caret: bool,
340    /// The [`OffsetMap`] used when creating this `TextRun`'s `InlineFormattingContext`. This
341    /// is used for mapping between DOM text offsets and layout text offsets (and vice-versa).
342    pub offset_map: ArcRefCell<OffsetMap>,
343}
344
345impl SharedTextRunData {
346    /// Map a range in the originating `TextRun`'s DOM node text into the range in the
347    /// `TextRun`'s layout transformed (by white space collapse and `text-transform`)
348    /// text.
349    pub(crate) fn map_dom_range_to_transformed_range(
350        &self,
351        dom_range: RangeAny<Utf32CodeUnits>,
352    ) -> Range<Utf32CodeUnits> {
353        let offset_map = self.offset_map.borrow();
354        let offset_in_ifc_text = Utf32CodeUnits(self.character_range_in_ifc_text.start);
355        let start = if let Some(dom_start) = dom_range.start {
356            offset_map.map(dom_start + self.original_offset) - offset_in_ifc_text
357        } else {
358            Utf32CodeUnits(0)
359        };
360        let end = if let Some(dom_end) = dom_range.end {
361            offset_map.map(dom_end + self.original_offset) - offset_in_ifc_text
362        } else {
363            Utf32CodeUnits(self.character_range_in_ifc_text.len())
364        };
365        start..end
366    }
367
368    /// Map an offset in the originating `TextRun`s DOM node's transformed text (by white
369    /// space collapse and `text-transform`) to untransformed text for use by the DOM.
370    pub(crate) fn map_transformed_offset_to_dom_offset(
371        &self,
372        offset: Utf32CodeUnits,
373    ) -> Utf32CodeUnits {
374        let offset_map = self.offset_map.borrow();
375        let offset_in_ifc_text = Utf32CodeUnits(self.character_range_in_ifc_text.start);
376        offset_map.reverse_map(offset + offset_in_ifc_text) - self.original_offset
377    }
378}
379
380/// A single [`TextRun`] for the box tree. These are all descendants of
381/// [`super::InlineBox`] or the root of the [`super::InlineFormattingContext`].  During
382/// box tree construction, text is split into [`TextRun`]s based on their font, script,
383/// etc. When these are created text is already shaped.
384///
385/// <https://www.w3.org/TR/css-display-3/#css-text-run>
386#[derive(Debug, MallocSizeOf)]
387pub(crate) struct TextRun {
388    /// The [`BaseFragmentInfo`] for this [`TextRun`]. Usually this comes from the
389    /// original text node in the DOM for the text.
390    pub base_fragment_info: BaseFragmentInfo,
391
392    /// Data to be used by all [`TextFragment`]s spawned by this [`TextRun`] to avoid
393    /// having to clone the data into each fragment.
394    #[conditional_malloc_size_of]
395    pub run_data: Arc<SharedTextRunData>,
396
397    /// A weak reference to the parent of this layout box. This becomes valid as soon
398    /// as the *parent* of this box is added to the tree.
399    pub parent_box: Option<WeakLayoutBox>,
400
401    /// The range of text in [`super::InlineFormattingContext::text_content`] of the
402    /// [`super::InlineFormattingContext`] that owns this [`TextRun`]. These are UTF-8 offsets.
403    pub text_range: Range<usize>,
404
405    /// The [`TextRunItem`]s of this text run. This is produced by segmenting the incoming text
406    /// by things such as font and script as well as separating out hard line breaks.
407    /// segments, and shaped.
408    pub items: Vec<TextRunItem>,
409}
410
411impl TextRun {
412    pub(crate) fn new(
413        base_fragment_info: BaseFragmentInfo,
414        run_data: Arc<SharedTextRunData>,
415        text_range: Range<usize>,
416        old_text_run: Option<ArcRefCell<TextRun>>,
417    ) -> Self {
418        // If there was a previous box tree layout of this text run, try to preserve the old shaped text.
419        let items = old_text_run
420            .map(|old_text_run| std::mem::take(&mut old_text_run.borrow_mut().items))
421            .unwrap_or_default();
422        Self {
423            base_fragment_info,
424            run_data,
425            parent_box: None,
426            text_range,
427            items,
428        }
429    }
430
431    pub(super) fn inline_styles(&self) -> &SharedInlineStyles {
432        &self.run_data.inline_styles
433    }
434
435    pub(super) fn segment(
436        &mut self,
437        self_arc_ref_cell: ArcRefCell<TextRun>,
438        formatting_context_text: &str,
439        layout_context: &LayoutContext,
440        bidi_levels: &BidiLevels,
441    ) -> SmallVec<[ShapingQueueEntry; 1]> {
442        let parent_style = self.inline_styles().style.borrow().clone();
443        let items = self.segment_text_by_font(
444            layout_context,
445            formatting_context_text,
446            bidi_levels,
447            &parent_style,
448        );
449
450        // If a previous box tree layout seeded this [`TextRun`] with old shaping results, use those
451        // to try to prevent re-shaping.
452        let mut old_text_run_items = std::mem::replace(&mut self.items, items).into_iter();
453
454        self.items
455            .iter()
456            .enumerate()
457            .map(move |(index, text_run_item)| {
458                let old_text_run_item = old_text_run_items.next();
459                ShapingQueueEntry::new(
460                    self_arc_ref_cell.clone(),
461                    text_run_item,
462                    index,
463                    old_text_run_item,
464                )
465            })
466            .collect()
467    }
468
469    /// Take the [`TextRun`]'s text and turn it into [`TextRunSegment`]s. Each segment has a matched
470    /// font and script. Fonts may differ when glyphs are found in fallback fonts.
471    /// [`super::InlineFormattingContext`].
472    fn segment_text_by_font(
473        &mut self,
474        layout_context: &LayoutContext,
475        formatting_context_text: &str,
476        bidi_levels: &BidiLevels,
477        parent_style: &ServoArc<ComputedValues>,
478    ) -> Vec<TextRunItem> {
479        let font_style = parent_style.clone_font();
480        let language = font_style._x_lang.0.parse().unwrap_or(Language::UNKNOWN);
481        let language_for_shaping = Some(font_style.font_language_override)
482            .filter(|language_override| *language_override != FontLanguageOverride::normal())
483            .and_then(|language_override| {
484                // FIXME: ICU4x limits language tags to three bytes as that is limit
485                // defined by BCP 47. But OpenType defines a couple four-letter
486                // languages, and stylo correctly stores a four-byte value for the computed
487                // value of the property.
488                //
489                // https://www.w3.org/TR/css-fonts-4/#font-language-override-string-value
490                //
491                // For now we need to truncate the language tag ):
492                Language::try_from_utf8(&language_override.0.to_be_bytes()[..3]).ok()
493            })
494            .unwrap_or(language);
495        let font_size = font_style.font_size.computed_size().into();
496        let kerning = font_style.font_kerning;
497        let ligatures = font_style.font_variant_ligatures;
498        let numeric = font_style.font_variant_numeric;
499        let east_asian = font_style.font_variant_east_asian;
500        let feature_settings = font_style.font_feature_settings.clone();
501        let position = font_style.font_variant_position;
502        let alternates = font_style.font_variant_alternates.clone();
503
504        let font_group = layout_context.font_context.font_group(font_style);
505        let inherited_text_style = parent_style.get_inherited_text();
506        let word_spacing = Some(inherited_text_style.word_spacing.to_used_value(font_size));
507        let letter_spacing = inherited_text_style
508            .letter_spacing
509            .0
510            .to_used_value(font_size);
511        let letter_spacing = if !letter_spacing.is_zero() {
512            Some(letter_spacing)
513        } else {
514            None
515        };
516        let text_rendering = inherited_text_style.text_rendering;
517
518        let mut current: Option<TextRunSegment> = None;
519        let mut results = Vec::new();
520        let finish_current_segment =
521            |current: &mut Option<TextRunSegment>, results: &mut Vec<TextRunItem>| {
522                if let Some(current) = current.take() {
523                    results.push(TextRunItem::TextSegment(Box::new(current)));
524                }
525            };
526
527        let text_run_text = &formatting_context_text[self.text_range.clone()];
528        let char_iterator = TwoCharsAtATimeIterator::new(text_run_text.chars());
529        // The next bytes index of the character within the entire inline formatting context's text.
530        let mut next_byte_index = self.text_range.start;
531        for (relative_character_index, (character, next_character)) in char_iterator.enumerate() {
532            // The current character index within the entire inline formatting context's text.
533            let current_character_index =
534                self.run_data.character_range_in_ifc_text.start + relative_character_index;
535
536            let current_byte_index = next_byte_index;
537            next_byte_index += character.len_utf8();
538
539            if character == '\n' {
540                finish_current_segment(&mut current, &mut results);
541                let paint_caret = self.run_data.paint_caret;
542                results.push(TextRunItem::LineBreak(paint_caret.then(|| {
543                    CaretPlaceholder {
544                        run_data: self.run_data.clone(),
545                        base_fragment_info: self.base_fragment_info,
546                        // The placeholder that is placed after a newline is for the index after that newline.
547                        // The newline itself is at the end of the previous line.
548                        character_index: relative_character_index + 1,
549                    }
550                })));
551                continue;
552            }
553
554            if character == '\t' {
555                finish_current_segment(&mut current, &mut results);
556                results.push(TextRunItem::Tab {
557                    bidi_level: bidi_levels.level(current_byte_index),
558                });
559                continue;
560            }
561
562            let (font, script, bidi_level) = if character_cannot_change_font(character) {
563                (None, Script::Common, bidi_levels.level(current_byte_index))
564            } else {
565                (
566                    font_group.find_by_codepoint(
567                        &layout_context.font_context,
568                        character,
569                        next_character,
570                        language,
571                    ),
572                    Script::from(character),
573                    bidi_levels.level(current_byte_index),
574                )
575            };
576
577            // If the existing segment is compatible with the character, just merge the character into it.
578            if let Some(current) = current.as_mut() &&
579                current.is_compatible(&font, script, bidi_level)
580            {
581                current.update(next_byte_index, current_character_index + 1, script);
582                continue;
583            }
584
585            let Some(font) = font.or_else(|| font_group.first(&layout_context.font_context)) else {
586                continue;
587            };
588
589            let alternates = layout_context
590                .font_context
591                .resolve_font_variant_alternate_identifiers_for(
592                    &font,
593                    &alternates,
594                    layout_context.style_context.stylist,
595                );
596            let info = FontAndScriptInfo {
597                script,
598                font_info: Arc::new(FontInfo {
599                    font,
600                    bidi_level,
601                    language: language_for_shaping,
602                    word_spacing,
603                    letter_spacing,
604                    text_rendering,
605                    kerning,
606                    ligatures,
607                    numeric,
608                    east_asian,
609                    feature_settings: feature_settings.clone(),
610                    alternates,
611                    position,
612                }),
613            };
614
615            finish_current_segment(&mut current, &mut results);
616            assert!(current.is_none());
617
618            current = Some(TextRunSegment::new(
619                info,
620                current_byte_index..next_byte_index,
621                current_character_index..current_character_index + 1,
622            ));
623        }
624
625        finish_current_segment(&mut current, &mut results);
626        results
627    }
628
629    pub(super) fn layout_into_line_items(&self, ifc: &mut InlineFormattingContextLayout) {
630        if self.text_range.is_empty() {
631            return;
632        }
633
634        // If we are following replaced content, we should have a soft wrap opportunity, unless the
635        // first character of this `TextRun` prevents that soft wrap opportunity. If we see such a
636        // character it should also override the LineBreaker's indication to break at the start.
637        let have_deferred_soft_wrap_opportunity =
638            mem::replace(&mut ifc.have_deferred_soft_wrap_opportunity, false);
639        let mut soft_wrap_policy = match have_deferred_soft_wrap_opportunity {
640            true => SegmentStartSoftWrapPolicy::Force,
641            false => SegmentStartSoftWrapPolicy::FollowLinebreaker,
642        };
643
644        for item in self.items.iter() {
645            ifc.possibly_flush_deferred_forced_line_break();
646
647            match item {
648                // If this whitespace forces a line break, queue up a hard line break the next time we
649                // see any content. We don't line break immediately, because we'd like to finish processing
650                // any ongoing inline boxes before ending the line.
651                TextRunItem::LineBreak(caret_placeholder) => {
652                    ifc.defer_forced_line_break_at_character_offset(caret_placeholder);
653                },
654                TextRunItem::Tab { bidi_level } => self.process_preserved_tab(ifc, *bidi_level),
655                TextRunItem::TextSegment(segment) => {
656                    segment.layout_into_line_items(self, soft_wrap_policy, ifc)
657                },
658            }
659            soft_wrap_policy = SegmentStartSoftWrapPolicy::FollowLinebreaker;
660        }
661    }
662
663    fn process_preserved_tab(
664        &self,
665        ifc_layout: &mut InlineFormattingContextLayout,
666        bidi_level: Level,
667    ) {
668        let advance = ifc_layout.ifc.next_tab_stop_after_inline_advance(
669            &self.inline_styles().style.borrow(),
670            ifc_layout.potential_line_size().inline,
671        );
672        if advance.is_zero() {
673            return;
674        }
675
676        ifc_layout.update_unbreakable_segment_for_new_content(
677            &LineBlockSizes::zero(),
678            advance,
679            SegmentContentFlags::empty(),
680        );
681        ifc_layout.push_line_item_to_unbreakable_segment(LineItem::Tab {
682            inline_box_identifier: ifc_layout.current_inline_box_identifier(),
683            advance,
684            bidi_level,
685        });
686
687        if ifc_layout
688            .current_inline_container_state()
689            .style
690            .get_inherited_text()
691            .white_space_collapse ==
692            WhiteSpaceCollapse::BreakSpaces
693        {
694            ifc_layout.process_soft_wrap_opportunity();
695        }
696    }
697}
698
699/// From <https://www.w3.org/TR/css-text-3/#cursive-script>:
700/// Cursive scripts do not admit gaps between their letters for either justification
701/// or letter-spacing. The following Unicode scripts are included: Arabic, Hanifi
702/// Rohingya, Mandaic, Mongolian, N’Ko, Phags Pa, Syriac
703fn is_cursive_script(script: Script) -> bool {
704    matches!(
705        script,
706        Script::Arabic |
707            Script::Hanifi_Rohingya |
708            Script::Mandaic |
709            Script::Mongolian |
710            Script::Nko |
711            Script::Phags_Pa |
712            Script::Syriac
713    )
714}
715
716/// Whether or not this character should be able to change the font during segmentation.  Certain
717/// character are not rendered at all, so it doesn't matter what font we use to render them. They
718/// should just be added to the current segment.
719fn character_cannot_change_font(character: char) -> bool {
720    if character.is_control() {
721        return true;
722    }
723    if character == '\u{00A0}' {
724        return true;
725    }
726    if is_bidi_control(character) {
727        return false;
728    }
729
730    matches!(
731        LineBreak::for_char(character),
732        LineBreak::CombiningMark |
733            LineBreak::Glue |
734            LineBreak::ZWSpace |
735            LineBreak::WordJoiner |
736            LineBreak::ZWJ
737    )
738}
739
740pub(super) fn get_font_for_first_font_for_style(
741    style: &ComputedValues,
742    font_context: &FontContext,
743) -> Option<FontRef> {
744    let font = font_context
745        .font_group(style.clone_font())
746        .first(font_context);
747    if font.is_none() {
748        warn!("Could not find font for style: {:?}", style.clone_font());
749    }
750    font
751}
752pub(crate) struct TwoCharsAtATimeIterator<InputIterator> {
753    /// The input character iterator.
754    iterator: InputIterator,
755    /// The first character to produce in the next run of the iterator.
756    next_character: Option<char>,
757}
758
759impl<InputIterator> TwoCharsAtATimeIterator<InputIterator> {
760    fn new(iterator: InputIterator) -> Self {
761        Self {
762            iterator,
763            next_character: None,
764        }
765    }
766}
767
768impl<InputIterator> Iterator for TwoCharsAtATimeIterator<InputIterator>
769where
770    InputIterator: Iterator<Item = char>,
771{
772    type Item = (char, Option<char>);
773
774    fn next(&mut self) -> Option<Self::Item> {
775        // If the iterator isn't initialized do that now.
776        if self.next_character.is_none() {
777            self.next_character = self.iterator.next();
778        }
779        let character = self.next_character?;
780        self.next_character = self.iterator.next();
781        Some((character, self.next_character))
782    }
783}
784
785pub(crate) fn script_is_specific(script: Script) -> bool {
786    script != Script::Common && script != Script::Inherited
787}