1use std::mem;
6use std::ops::Range;
7use std::sync::Arc;
8
9use app_units::Au;
10use fonts::{
11 FontContext, FontRef, ShapedTextSlice, ShapedTextSlicer, ShapingFlags, ShapingOptions,
12};
13use icu_locid::subtags::Language;
14use icu_properties::{self, LineBreak};
15use log::warn;
16use malloc_size_of_derive::MallocSizeOf;
17use servo_arc::Arc as ServoArc;
18use servo_base::text::is_bidi_control;
19use style::Zero;
20use style::computed_values::font_kerning::T as FontKerning;
21use style::computed_values::font_variant_position::T as FontVariantPosition;
22use style::computed_values::text_rendering::T as TextRendering;
23use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
24use style::computed_values::word_break::T as WordBreak;
25use style::properties::ComputedValues;
26use style::str::char_is_whitespace;
27use style::values::computed::{
28 FontFeatureSettings, FontVariantEastAsian, FontVariantLigatures, FontVariantNumeric,
29 OverflowWrap,
30};
31use unicode_bidi::Level;
32use unicode_script::Script;
33
34use super::line_breaker::LineBreaker;
35use super::{InlineFormattingContextLayout, SharedInlineStyles};
36use crate::ArcRefCell;
37use crate::context::LayoutContext;
38use crate::dom::WeakLayoutBox;
39use crate::flow::inline::line::TextRunOffsets;
40use crate::flow::inline::{BidiLevels, LineBlockSizes, LineItem, SegmentContentFlags};
41use crate::fragment_tree::BaseFragmentInfo;
42
43#[derive(PartialEq)]
52enum SegmentStartSoftWrapPolicy {
53 Force,
54 FollowLinebreaker,
55}
56
57#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
59pub(crate) struct FontAndScriptInfo {
60 pub font: FontRef,
62 pub script: Script,
64 pub bidi_level: Level,
66 pub language: Language,
68 pub letter_spacing: Option<Au>,
73 pub word_spacing: Option<Au>,
75 pub text_rendering: TextRendering,
77 pub kerning: FontKerning,
79 pub ligatures: FontVariantLigatures,
81 pub numeric: FontVariantNumeric,
83 pub east_asian: FontVariantEastAsian,
85 pub feature_settings: FontFeatureSettings,
87 pub position: FontVariantPosition,
89}
90
91impl FontAndScriptInfo {
92 pub(crate) fn simple_for_font(font: FontRef) -> Self {
96 Self {
97 font,
98 script: Script::Common,
99 bidi_level: Level::ltr(),
100 language: Language::UND,
101 letter_spacing: None,
102 word_spacing: None,
103 text_rendering: TextRendering::Auto,
104 kerning: FontKerning::Auto,
105 ligatures: FontVariantLigatures::NORMAL,
106 numeric: FontVariantNumeric::NORMAL,
107 east_asian: FontVariantEastAsian::NORMAL,
108 feature_settings: FontFeatureSettings::normal(),
109 position: FontVariantPosition::Normal,
110 }
111 }
112}
113
114impl From<&FontAndScriptInfo> for ShapingOptions {
115 fn from(info: &FontAndScriptInfo) -> Self {
116 let mut ligatures = info.ligatures;
117 let mut flags = ShapingFlags::empty();
118 if info.bidi_level.is_rtl() {
119 flags.insert(ShapingFlags::RTL_FLAG);
120 }
121
122 let letter_spacing = info
126 .letter_spacing
127 .filter(|_| !is_cursive_script(info.script));
128 if letter_spacing.is_some() {
129 ligatures = FontVariantLigatures::NONE;
130 };
131 if info.text_rendering == TextRendering::Optimizespeed {
132 ligatures = FontVariantLigatures::NONE;
133 flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
134 }
135
136 if info.kerning == FontKerning::None {
138 flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG);
139 }
140
141 Self {
142 letter_spacing,
143 word_spacing: info.word_spacing,
144 script: info.script,
145 language: info.language,
146 ligatures,
147 numeric: info.numeric,
148 east_asian: info.east_asian,
149 feature_settings: info.feature_settings.clone(),
150 position: info.position,
151 flags,
152 }
153 }
154}
155
156#[derive(Clone, Debug, MallocSizeOf)]
157pub(crate) struct TextRunSegment {
158 #[conditional_malloc_size_of]
161 pub info: Arc<FontAndScriptInfo>,
162
163 pub byte_range: Range<usize>,
165
166 pub character_range: Range<usize>,
168
169 pub break_at_start: bool,
172
173 #[conditional_malloc_size_of]
175 pub runs: Vec<Arc<ShapedTextSlice>>,
176}
177
178impl TextRunSegment {
179 fn new(
180 info: Arc<FontAndScriptInfo>,
181 byte_range: Range<usize>,
182 character_range: Range<usize>,
183 ) -> Self {
184 Self {
185 info,
186 byte_range,
187 character_range,
188 runs: Vec::new(),
189 break_at_start: false,
190 }
191 }
192
193 fn is_compatible(
196 &self,
197 new_font: &Option<FontRef>,
198 new_script: Script,
199 new_bidi_level: Level,
200 ) -> bool {
201 if self.info.bidi_level != new_bidi_level {
202 return false;
203 }
204 if new_font
205 .as_ref()
206 .is_some_and(|new_font| !Arc::ptr_eq(&self.info.font, new_font))
207 {
208 return false;
209 }
210
211 !script_is_specific(self.info.script) ||
212 !script_is_specific(new_script) ||
213 self.info.script == new_script
214 }
215
216 fn update(&mut self, next_byte_index: usize, next_character_index: usize, new_script: Script) {
219 if !script_is_specific(self.info.script) && script_is_specific(new_script) {
220 self.info = Arc::new(FontAndScriptInfo {
221 script: new_script,
222 ..(*self.info).clone()
223 });
224 }
225 self.character_range.end = next_character_index;
226 self.byte_range.end = next_byte_index;
227 }
228
229 fn layout_into_line_items(
230 &self,
231 text_run: &TextRun,
232 mut soft_wrap_policy: SegmentStartSoftWrapPolicy,
233 ifc: &mut InlineFormattingContextLayout,
234 ) {
235 if self.break_at_start && soft_wrap_policy == SegmentStartSoftWrapPolicy::FollowLinebreaker
236 {
237 soft_wrap_policy = SegmentStartSoftWrapPolicy::Force;
238 }
239
240 let mut character_range_start = self.character_range.start;
241 for (run_index, run) in self.runs.iter().enumerate() {
242 let new_character_range_end = character_range_start + run.character_count();
243 let offsets = ifc
244 .ifc
245 .shared_selection
246 .clone()
247 .map(|shared_selection| TextRunOffsets {
248 shared_selection,
249 character_range: character_range_start..new_character_range_end,
250 });
251
252 if run_index != 0 || soft_wrap_policy == SegmentStartSoftWrapPolicy::Force {
255 ifc.process_soft_wrap_opportunity();
256 }
257
258 ifc.push_glyph_store_to_unbreakable_segment(run.clone(), text_run, &self.info, offsets);
259 character_range_start = new_character_range_end;
260 }
261 }
262
263 fn shape_text(
267 &mut self,
268 parent_style: &ComputedValues,
269 formatting_context_text: &str,
270 linebreaker: &mut LineBreaker,
271 old_text_run_item: Option<TextRunItem>,
272 ) {
273 let range = self.byte_range.clone();
277 let linebreaks = linebreaker.advance_to_linebreaks_in_range(self.byte_range.clone());
278 let linebreak_iter = linebreaks.iter().chain(std::iter::once(&range.end));
279
280 let options: ShapingOptions = (&*self.info).into();
281 let shaped_text = old_text_run_item
282 .and_then(|old_text_run_item| {
283 let TextRunItem::TextSegment(old_text_segment) = old_text_run_item else {
284 return None;
285 };
286 if !self.is_compatible_with_old_shaping_result(&old_text_segment) {
287 return None;
288 }
289 Some(old_text_segment.runs.first()?.shaped_text())
290 })
291 .unwrap_or_else(|| {
292 self.info
293 .font
294 .shape_text(&formatting_context_text[range.clone()], &options)
295 });
296
297 let mut shaped_text_slicer = ShapedTextSlicer::new(shaped_text);
298
299 self.runs.clear();
300 self.runs.reserve(linebreaks.len());
301 self.break_at_start = false;
302
303 let text_style = parent_style.get_inherited_text().clone();
304 let can_break_anywhere = text_style.word_break == WordBreak::BreakAll ||
305 text_style.overflow_wrap == OverflowWrap::Anywhere ||
306 text_style.overflow_wrap == OverflowWrap::BreakWord;
307
308 let mut last_slice = self.byte_range.start..self.byte_range.start;
309 for break_index in linebreak_iter {
310 if *break_index == self.byte_range.start {
311 self.break_at_start = true;
312 continue;
313 }
314
315 let mut slice = last_slice.end..*break_index;
317 let word = &formatting_context_text[slice.clone()];
318
319 let mut whitespace = slice.end..slice.end;
321 let rev_char_indices = word.char_indices().rev().peekable();
322
323 let mut non_whitespace_slice_ends_with_whitespace = false;
324 let mut ends_with_whitespace = false;
325 if let Some((first_white_space_index, first_white_space_character)) = rev_char_indices
326 .take_while(|&(_, character)| char_is_whitespace(character))
327 .last()
328 {
329 ends_with_whitespace = true;
330 whitespace.start = slice.start + first_white_space_index;
331
332 if text_style.white_space_collapse == WhiteSpaceCollapse::BreakSpaces &&
338 !can_break_anywhere
339 {
340 whitespace.start += first_white_space_character.len_utf8();
341 non_whitespace_slice_ends_with_whitespace = true;
342 }
343
344 slice.end = whitespace.start;
345 }
346
347 if !ends_with_whitespace &&
350 *break_index != self.byte_range.end &&
351 text_style.word_break == WordBreak::KeepAll &&
352 !can_break_anywhere
353 {
354 continue;
355 }
356
357 last_slice = slice.start..*break_index;
359
360 if !slice.is_empty() {
362 let character_count = formatting_context_text[slice].chars().count();
363 self.runs.push(shaped_text_slicer.slice_for_character_count(
364 character_count,
365 false, non_whitespace_slice_ends_with_whitespace,
367 ));
368 }
369
370 if whitespace.is_empty() {
371 continue;
372 }
373
374 if text_style.white_space_collapse == WhiteSpaceCollapse::BreakSpaces {
377 for _ in formatting_context_text[whitespace].chars() {
378 self.runs.push(shaped_text_slicer.slice_for_character_count(
379 1, true, true, ));
382 }
383 continue;
384 }
385
386 let character_count = formatting_context_text[whitespace].chars().count();
387 self.runs.push(shaped_text_slicer.slice_for_character_count(
388 character_count,
389 true, true, ));
392 }
393 }
394
395 fn is_compatible_with_old_shaping_result(&self, old_segment: &Self) -> bool {
396 *old_segment.info == *self.info && self.byte_range == old_segment.byte_range
397 }
398}
399
400#[derive(Debug, MallocSizeOf)]
402pub(crate) enum TextRunItem {
403 LineBreak { character_index: usize },
405 Tab { bidi_level: Level },
407 TextSegment(Box<TextRunSegment>),
410}
411
412#[derive(Debug, MallocSizeOf)]
419pub(crate) struct TextRun {
420 pub base_fragment_info: BaseFragmentInfo,
423
424 pub parent_box: Option<WeakLayoutBox>,
427
428 pub inline_styles: SharedInlineStyles,
432
433 pub text_range: Range<usize>,
436
437 pub character_range: Range<usize>,
441
442 pub items: Vec<TextRunItem>,
446}
447
448impl TextRun {
449 pub(crate) fn new(
450 base_fragment_info: BaseFragmentInfo,
451 inline_styles: SharedInlineStyles,
452 text_range: Range<usize>,
453 character_range: Range<usize>,
454 old_text_run: Option<ArcRefCell<TextRun>>,
455 ) -> Self {
456 let items = old_text_run
458 .map(|old_text_run| std::mem::take(&mut old_text_run.borrow_mut().items))
459 .unwrap_or_default();
460 Self {
461 base_fragment_info,
462 parent_box: None,
463 inline_styles,
464 text_range,
465 character_range,
466 items,
467 }
468 }
469
470 pub(super) fn segment_and_shape(
471 &mut self,
472 formatting_context_text: &str,
473 layout_context: &LayoutContext,
474 linebreaker: &mut LineBreaker,
475 bidi_levels: &BidiLevels,
476 ) {
477 let parent_style = self.inline_styles.style.borrow().clone();
478 let items = self.segment_text_by_font(
479 layout_context,
480 formatting_context_text,
481 bidi_levels,
482 &parent_style,
483 );
484
485 let mut old_text_run_items = std::mem::replace(&mut self.items, items).into_iter();
488 for item in self.items.iter_mut() {
489 let old_text_run_item = old_text_run_items.next();
490 if let TextRunItem::TextSegment(text_segment) = item {
491 text_segment.shape_text(
492 &parent_style,
493 formatting_context_text,
494 linebreaker,
495 old_text_run_item,
496 );
497 }
498 }
499 }
500
501 fn segment_text_by_font(
505 &mut self,
506 layout_context: &LayoutContext,
507 formatting_context_text: &str,
508 bidi_levels: &BidiLevels,
509 parent_style: &ServoArc<ComputedValues>,
510 ) -> Vec<TextRunItem> {
511 let font_style = parent_style.clone_font();
512 let language = font_style._x_lang.0.parse().unwrap_or(Language::UND);
513 let font_size = font_style.font_size.computed_size().into();
514 let kerning = font_style.font_kerning;
515 let ligatures = font_style.font_variant_ligatures;
516 let numeric = font_style.font_variant_numeric;
517 let east_asian = font_style.font_variant_east_asian;
518 let feature_settings = font_style.font_feature_settings.clone();
519 let position = font_style.font_variant_position;
520 let font_group = layout_context.font_context.font_group(font_style);
521 let inherited_text_style = parent_style.get_inherited_text();
522 let word_spacing = Some(inherited_text_style.word_spacing.to_used_value(font_size));
523 let letter_spacing = inherited_text_style
524 .letter_spacing
525 .0
526 .to_used_value(font_size);
527 let letter_spacing = if !letter_spacing.is_zero() {
528 Some(letter_spacing)
529 } else {
530 None
531 };
532 let text_rendering = inherited_text_style.text_rendering;
533
534 let mut current: Option<TextRunSegment> = None;
535 let mut results = Vec::new();
536 let finish_current_segment =
537 |current: &mut Option<TextRunSegment>, results: &mut Vec<TextRunItem>| {
538 if let Some(current) = current.take() {
539 results.push(TextRunItem::TextSegment(Box::new(current)));
540 }
541 };
542
543 let text_run_text = &formatting_context_text[self.text_range.clone()];
544 let char_iterator = TwoCharsAtATimeIterator::new(text_run_text.chars());
545 let mut next_byte_index = self.text_range.start;
547 for (relative_character_index, (character, next_character)) in char_iterator.enumerate() {
548 let current_character_index = self.character_range.start + relative_character_index;
550
551 let current_byte_index = next_byte_index;
552 next_byte_index += character.len_utf8();
553
554 if character == '\n' {
555 finish_current_segment(&mut current, &mut results);
556 results.push(TextRunItem::LineBreak {
557 character_index: current_character_index,
558 });
559 continue;
560 }
561
562 if character == '\t' {
563 finish_current_segment(&mut current, &mut results);
564 results.push(TextRunItem::Tab {
565 bidi_level: bidi_levels.level(current_byte_index),
566 });
567 continue;
568 }
569
570 let (font, script, bidi_level) = if character_cannot_change_font(character) {
571 (None, Script::Common, bidi_levels.level(current_byte_index))
572 } else {
573 (
574 font_group.find_by_codepoint(
575 &layout_context.font_context,
576 character,
577 next_character,
578 language,
579 ),
580 Script::from(character),
581 bidi_levels.level(current_byte_index),
582 )
583 };
584
585 if let Some(current) = current.as_mut() &&
587 current.is_compatible(&font, script, bidi_level)
588 {
589 current.update(next_byte_index, current_character_index + 1, script);
590 continue;
591 }
592
593 let Some(font) = font.or_else(|| font_group.first(&layout_context.font_context)) else {
594 continue;
595 };
596 let info = FontAndScriptInfo {
597 font,
598 script,
599 bidi_level,
600 language,
601 word_spacing,
602 letter_spacing,
603 text_rendering,
604 kerning,
605 ligatures,
606 numeric,
607 east_asian,
608 feature_settings: feature_settings.clone(),
609 position,
610 };
611
612 finish_current_segment(&mut current, &mut results);
613 assert!(current.is_none());
614
615 current = Some(TextRunSegment::new(
616 Arc::new(info),
617 current_byte_index..next_byte_index,
618 current_character_index..current_character_index + 1,
619 ));
620 }
621
622 finish_current_segment(&mut current, &mut results);
623 results
624 }
625
626 pub(super) fn layout_into_line_items(&self, ifc: &mut InlineFormattingContextLayout) {
627 if self.text_range.is_empty() {
628 return;
629 }
630
631 let have_deferred_soft_wrap_opportunity =
635 mem::replace(&mut ifc.have_deferred_soft_wrap_opportunity, false);
636 let mut soft_wrap_policy = match have_deferred_soft_wrap_opportunity {
637 true => SegmentStartSoftWrapPolicy::Force,
638 false => SegmentStartSoftWrapPolicy::FollowLinebreaker,
639 };
640
641 for item in self.items.iter() {
642 ifc.possibly_flush_deferred_forced_line_break();
643
644 match item {
645 TextRunItem::LineBreak { character_index } => {
649 ifc.defer_forced_line_break_at_character_offset(*character_index);
650 },
651 TextRunItem::Tab { bidi_level } => self.process_preserved_tab(ifc, *bidi_level),
652 TextRunItem::TextSegment(segment) => {
653 segment.layout_into_line_items(self, soft_wrap_policy, ifc)
654 },
655 }
656 soft_wrap_policy = SegmentStartSoftWrapPolicy::FollowLinebreaker;
657 }
658 }
659
660 fn process_preserved_tab(
661 &self,
662 ifc_layout: &mut InlineFormattingContextLayout,
663 bidi_level: Level,
664 ) {
665 let advance = ifc_layout.ifc.next_tab_stop_after_inline_advance(
666 &self.inline_styles.style.borrow(),
667 ifc_layout.potential_line_size().inline,
668 );
669 if advance.is_zero() {
670 return;
671 }
672
673 ifc_layout.update_unbreakable_segment_for_new_content(
674 &LineBlockSizes::zero(),
675 advance,
676 SegmentContentFlags::empty(),
677 );
678 ifc_layout.push_line_item_to_unbreakable_segment(LineItem::Tab {
679 inline_box_identifier: ifc_layout.current_inline_box_identifier(),
680 advance,
681 bidi_level,
682 });
683
684 if ifc_layout
685 .current_inline_container_state()
686 .style
687 .get_inherited_text()
688 .white_space_collapse ==
689 WhiteSpaceCollapse::BreakSpaces
690 {
691 ifc_layout.process_soft_wrap_opportunity();
692 }
693 }
694}
695
696fn is_cursive_script(script: Script) -> bool {
701 matches!(
702 script,
703 Script::Arabic |
704 Script::Hanifi_Rohingya |
705 Script::Mandaic |
706 Script::Mongolian |
707 Script::Nko |
708 Script::Phags_Pa |
709 Script::Syriac
710 )
711}
712
713fn character_cannot_change_font(character: char) -> bool {
717 if character.is_control() {
718 return true;
719 }
720 if character == '\u{00A0}' {
721 return true;
722 }
723 if is_bidi_control(character) {
724 return false;
725 }
726
727 matches!(
728 icu_properties::maps::line_break().get(character),
729 LineBreak::CombiningMark |
730 LineBreak::Glue |
731 LineBreak::ZWSpace |
732 LineBreak::WordJoiner |
733 LineBreak::ZWJ
734 )
735}
736
737pub(super) fn get_font_for_first_font_for_style(
738 style: &ComputedValues,
739 font_context: &FontContext,
740) -> Option<FontRef> {
741 let font = font_context
742 .font_group(style.clone_font())
743 .first(font_context);
744 if font.is_none() {
745 warn!("Could not find font for style: {:?}", style.clone_font());
746 }
747 font
748}
749pub(crate) struct TwoCharsAtATimeIterator<InputIterator> {
750 iterator: InputIterator,
752 next_character: Option<char>,
754}
755
756impl<InputIterator> TwoCharsAtATimeIterator<InputIterator> {
757 fn new(iterator: InputIterator) -> Self {
758 Self {
759 iterator,
760 next_character: None,
761 }
762 }
763}
764
765impl<InputIterator> Iterator for TwoCharsAtATimeIterator<InputIterator>
766where
767 InputIterator: Iterator<Item = char>,
768{
769 type Item = (char, Option<char>);
770
771 fn next(&mut self) -> Option<Self::Item> {
772 if self.next_character.is_none() {
774 self.next_character = self.iterator.next();
775 }
776 let character = self.next_character?;
777 self.next_character = self.iterator.next();
778 Some((character, self.next_character))
779 }
780}
781
782fn script_is_specific(script: Script) -> bool {
783 script != Script::Common && script != Script::Inherited
784}