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