1use std::mem;
6use std::ops::Range;
7use std::sync::{Arc, OnceLock};
8
9use app_units::Au;
10use atomic_refcell::AtomicRefCell;
11use fonts::font_feature_values::ResolvedFontVariantAlternates;
12use fonts::{FontContext, FontRef, ShapedText, ShapedTextSlice, ShapingFlags, ShapingOptions};
13use icu_locale_core::subtags::Language;
14use icu_properties::props::{EnumeratedProperty, LineBreak};
15use log::warn;
16use malloc_size_of_derive::MallocSizeOf;
17use servo_arc::Arc as ServoArc;
18use servo_base::text::{RangeAny, Utf8CodeUnits, Utf32CodeUnits, is_bidi_control};
19use smallvec::SmallVec;
20use style::Zero;
21use style::computed_values::font_kerning::T as FontKerning;
22use style::computed_values::font_variant_position::T as FontVariantPosition;
23use style::computed_values::text_rendering::T as TextRendering;
24use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
25use style::font_face::FontLanguageOverride;
26use style::properties::ComputedValues;
27use style::values::computed::{
28 FontFeatureSettings, FontVariantEastAsian, FontVariantLigatures, FontVariantNumeric,
29};
30use unicode_bidi::Level;
31use unicode_script::Script;
32
33use super::{InlineFormattingContextLayout, SharedInlineStyles};
34use crate::ArcRefCell;
35use crate::context::LayoutContext;
36use crate::dom::WeakLayoutBox;
37use crate::flow::inline::shaping_queue::{ShapingQueueEntry, breaks_for_break_spaces};
38use crate::flow::inline::text_transform::OffsetMap;
39use crate::flow::inline::{BidiLevels, LineBlockSizes, LineItem, SegmentContentFlags};
40use crate::fragment_tree::BaseFragmentInfo;
41use crate::style_ext::ComputedValuesExt;
42
43#[derive(PartialEq)]
52enum SegmentStartSoftWrapPolicy {
53 Force,
54 FollowLinebreaker,
55}
56
57#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
59pub(crate) struct FontAndScriptInfo {
60 pub script: Script,
62 #[conditional_malloc_size_of]
64 pub font_info: Arc<FontInfo>,
65}
66
67impl FontAndScriptInfo {
68 pub(crate) fn simple_for_font(font: FontRef) -> Self {
72 Self {
73 script: Script::Common,
74 font_info: Arc::new(FontInfo::simple_for_font(font)),
75 }
76 }
77}
78
79#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
81pub(crate) struct FontInfo {
82 pub font: FontRef,
84 pub bidi_level: Level,
86 pub language: Language,
88 pub letter_spacing: Option<Au>,
93 pub word_spacing: Option<Au>,
95 pub text_rendering: TextRendering,
97 pub kerning: FontKerning,
99 pub ligatures: FontVariantLigatures,
101 pub numeric: FontVariantNumeric,
103 pub east_asian: FontVariantEastAsian,
105 pub feature_settings: FontFeatureSettings,
107 pub position: FontVariantPosition,
109 pub alternates: ResolvedFontVariantAlternates,
113}
114
115impl FontInfo {
116 fn simple_for_font(font: FontRef) -> Self {
117 Self {
118 font,
119 bidi_level: Level::ltr(),
120 language: Language::UNKNOWN,
121 letter_spacing: None,
122 word_spacing: None,
123 text_rendering: TextRendering::Auto,
124 kerning: FontKerning::Auto,
125 ligatures: FontVariantLigatures::NORMAL,
126 numeric: FontVariantNumeric::NORMAL,
127 east_asian: FontVariantEastAsian::NORMAL,
128 feature_settings: FontFeatureSettings::normal(),
129 position: FontVariantPosition::Normal,
130 alternates: Default::default(),
131 }
132 }
133}
134
135impl From<&FontAndScriptInfo> for ShapingOptions {
136 fn from(info: &FontAndScriptInfo) -> Self {
137 let mut ligatures = info.font_info.ligatures;
138 let mut flags = ShapingFlags::empty();
139 if info.font_info.bidi_level.is_rtl() {
140 flags.insert(ShapingFlags::RTL_FLAG);
141 }
142
143 let letter_spacing = info
147 .font_info
148 .letter_spacing
149 .filter(|_| !is_cursive_script(info.script));
150 if letter_spacing.is_some() {
151 ligatures = FontVariantLigatures::NONE;
152 };
153 let letter_spacing = letter_spacing.unwrap_or(Au::zero());
154 let word_spacing = info.font_info.word_spacing.unwrap_or(Au::zero());
155 if info.font_info.text_rendering == TextRendering::Optimizespeed {
156 ligatures = FontVariantLigatures::NONE;
157 flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
158 }
159
160 if info.font_info.kerning == FontKerning::None {
162 flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG);
163 }
164
165 Self {
166 letter_spacing,
167 word_spacing,
168 script: info.script,
169 language: info.font_info.language,
170 ligatures,
171 numeric: info.font_info.numeric,
172 east_asian: info.font_info.east_asian,
173 feature_settings: info.font_info.feature_settings.clone(),
174 position: info.font_info.position,
175 flags,
176 alternates: info.font_info.alternates.clone(),
177 }
178 }
179}
180
181#[derive(Clone, Debug, MallocSizeOf)]
182pub(crate) struct TextRunSegment {
183 pub info: FontAndScriptInfo,
186
187 pub byte_range: Range<Utf8CodeUnits>,
189
190 pub character_range: Range<Utf32CodeUnits>,
192
193 pub break_at_start: bool,
196
197 #[conditional_malloc_size_of]
199 pub runs: Vec<Arc<ShapedTextSlice>>,
200
201 #[conditional_malloc_size_of]
204 pub shaped_text: Option<Arc<ShapedText>>,
205}
206
207impl TextRunSegment {
208 fn new(
209 info: FontAndScriptInfo,
210 byte_range: Range<Utf8CodeUnits>,
211 character_range: Range<Utf32CodeUnits>,
212 ) -> Self {
213 Self {
214 info,
215 byte_range,
216 character_range,
217 runs: Vec::new(),
218 break_at_start: false,
219 shaped_text: None,
220 }
221 }
222
223 fn is_compatible(
226 &self,
227 new_font: &Option<FontRef>,
228 new_script: Script,
229 new_bidi_level: Level,
230 ) -> bool {
231 if self.info.font_info.bidi_level != new_bidi_level {
232 return false;
233 }
234 if new_font
235 .as_ref()
236 .is_some_and(|new_font| !Arc::ptr_eq(&self.info.font_info.font, new_font))
237 {
238 return false;
239 }
240
241 !script_is_specific(self.info.script) ||
242 !script_is_specific(new_script) ||
243 self.info.script == new_script
244 }
245
246 fn update(
249 &mut self,
250 next_byte_index: Utf8CodeUnits,
251 next_character_index: Utf32CodeUnits,
252 new_script: Script,
253 ) {
254 if !script_is_specific(self.info.script) && script_is_specific(new_script) {
255 self.info = FontAndScriptInfo {
256 script: new_script,
257 font_info: self.info.font_info.clone(),
258 };
259 }
260 self.character_range.end = next_character_index;
261 self.byte_range.end = next_byte_index;
262 }
263
264 fn layout_into_line_items(
265 &self,
266 text_run: &TextRun,
267 mut soft_wrap_policy: SegmentStartSoftWrapPolicy,
268 ifc: &mut InlineFormattingContextLayout,
269 ) {
270 if self.break_at_start && soft_wrap_policy == SegmentStartSoftWrapPolicy::FollowLinebreaker
271 {
272 soft_wrap_policy = SegmentStartSoftWrapPolicy::Force;
273 }
274
275 let mut character_range_start = self.character_range.start;
276 for (run_index, run) in self.runs.iter().enumerate() {
277 let new_character_range_end = character_range_start + run.character_count();
278
279 if run_index != 0 || soft_wrap_policy == SegmentStartSoftWrapPolicy::Force {
282 ifc.process_soft_wrap_opportunity();
283 }
284
285 let run_start = text_run.run_data.character_range_in_ifc_text.start;
286 ifc.push_shaped_text_slice_to_unbreakable_segment(
287 run.clone(),
288 text_run,
289 &self.info,
290 character_range_start - run_start..new_character_range_end - run_start,
291 );
292
293 character_range_start = new_character_range_end;
294 }
295 }
296
297 pub(crate) fn is_compatible_with_old_shaping_result(&self, old_segment: &Self) -> bool {
298 old_segment.info == self.info && self.byte_range == old_segment.byte_range
299 }
300}
301
302#[derive(Clone, Debug, MallocSizeOf)]
303pub(crate) struct CaretPlaceholder {
304 #[conditional_malloc_size_of]
306 pub run_data: Arc<SharedTextRunData>,
307 pub base_fragment_info: BaseFragmentInfo,
309 pub character_index: Utf32CodeUnits,
312}
313
314#[derive(Debug, MallocSizeOf)]
316pub(crate) enum TextRunItem {
317 LineBreak(Option<CaretPlaceholder>),
319 Tab {
321 bidi_level: Level,
322 break_at_start: bool,
323 },
324 TextSegment(Box<TextRunSegment>),
327}
328
329#[derive(Debug, MallocSizeOf)]
332pub(crate) struct SharedTextRunData {
333 #[conditional_malloc_size_of]
335 pub text_content: Arc<OnceLock<String>>,
336 pub inline_styles: SharedInlineStyles,
340 pub character_range_in_ifc_text: Range<Utf32CodeUnits>,
344 pub original_offset: Utf32CodeUnits,
347 pub selection: AtomicRefCell<Option<RangeAny<Utf32CodeUnits>>>,
351 pub paints_caret: bool,
353 pub needs_caret_placeholder: bool,
356 pub offset_map: ArcRefCell<OffsetMap>,
359}
360
361impl SharedTextRunData {
362 pub(crate) fn map_dom_range_to_transformed_range(
366 &self,
367 dom_range: RangeAny<Utf32CodeUnits>,
368 ) -> Range<Utf32CodeUnits> {
369 let offset_map = self.offset_map.borrow();
370 let offset_in_ifc_text = self.character_range_in_ifc_text.start;
371 let start = if let Some(dom_start) = dom_range.start() {
372 offset_map.map(dom_start + self.original_offset) - offset_in_ifc_text
373 } else {
374 Utf32CodeUnits(0)
375 };
376 let end = if let Some(dom_end) = dom_range.end() {
377 offset_map.map(dom_end + self.original_offset) - offset_in_ifc_text
378 } else {
379 self.character_range_in_ifc_text.end - self.character_range_in_ifc_text.start
380 };
381 start..end
382 }
383
384 pub(crate) fn map_transformed_offset_to_dom_offset(
387 &self,
388 offset: Utf32CodeUnits,
389 ) -> Utf32CodeUnits {
390 self.map_transformed_ifc_offset_to_dom_offset(
391 offset + self.character_range_in_ifc_text.start,
392 )
393 }
394
395 pub(crate) fn map_transformed_ifc_offset_to_dom_offset(
399 &self,
400 offset: Utf32CodeUnits,
401 ) -> Utf32CodeUnits {
402 let run_range = &self.character_range_in_ifc_text;
403 let offset = offset.clamp(run_range.start, run_range.end);
404 self.offset_map.borrow().reverse_map(offset) - self.original_offset
405 }
406
407 pub(crate) fn map_dom_offset_to_transformed_ifc_offset(
410 &self,
411 offset: Utf32CodeUnits,
412 ) -> Utf32CodeUnits {
413 self.offset_map.borrow().map(offset + self.original_offset)
414 }
415}
416
417#[derive(Debug, MallocSizeOf)]
424pub(crate) struct TextRun {
425 pub base_fragment_info: BaseFragmentInfo,
428
429 #[conditional_malloc_size_of]
432 pub run_data: Arc<SharedTextRunData>,
433
434 pub parent_box: Option<WeakLayoutBox>,
437
438 pub text_range: Range<Utf8CodeUnits>,
441
442 pub items: Vec<TextRunItem>,
446}
447
448impl TextRun {
449 pub(crate) fn new(
450 base_fragment_info: BaseFragmentInfo,
451 run_data: Arc<SharedTextRunData>,
452 text_range: Range<Utf8CodeUnits>,
453 old_text_run: Option<ArcRefCell<TextRun>>,
454 ) -> Self {
455 let items = old_text_run
457 .map(|old_text_run| std::mem::take(&mut old_text_run.borrow_mut().items))
458 .unwrap_or_default();
459 Self {
460 base_fragment_info,
461 run_data,
462 parent_box: None,
463 text_range,
464 items,
465 }
466 }
467
468 pub(super) fn inline_styles(&self) -> &SharedInlineStyles {
469 &self.run_data.inline_styles
470 }
471
472 pub(super) fn segment(
473 &mut self,
474 self_arc_ref_cell: ArcRefCell<TextRun>,
475 formatting_context_text: &str,
476 layout_context: &LayoutContext,
477 bidi_levels: &BidiLevels,
478 ) -> SmallVec<[ShapingQueueEntry; 1]> {
479 let parent_style = self.inline_styles().style.borrow().clone();
480 let items = self.segment_text_by_font(
481 layout_context,
482 formatting_context_text,
483 bidi_levels,
484 &parent_style,
485 );
486
487 let mut old_text_run_items = std::mem::replace(&mut self.items, items).into_iter();
490
491 self.items
492 .iter()
493 .enumerate()
494 .map(move |(index, text_run_item)| {
495 let old_text_run_item = old_text_run_items.next();
496 ShapingQueueEntry::new(
497 self_arc_ref_cell.clone(),
498 text_run_item,
499 index,
500 old_text_run_item,
501 )
502 })
503 .collect()
504 }
505
506 fn segment_text_by_font(
510 &mut self,
511 layout_context: &LayoutContext,
512 formatting_context_text: &str,
513 bidi_levels: &BidiLevels,
514 parent_style: &ServoArc<ComputedValues>,
515 ) -> Vec<TextRunItem> {
516 let font_style = parent_style.clone_font();
517 let language = font_style._x_lang.0.parse().unwrap_or(Language::UNKNOWN);
518 let language_for_shaping = Some(font_style.font_language_override)
519 .filter(|language_override| *language_override != FontLanguageOverride::normal())
520 .and_then(|language_override| {
521 Language::try_from_utf8(&language_override.0.to_be_bytes()[..3]).ok()
530 })
531 .unwrap_or(language);
532 let font_size = font_style.font_size.computed_size().into();
533 let kerning = font_style.font_kerning;
534 let ligatures = font_style.font_variant_ligatures;
535 let numeric = font_style.font_variant_numeric;
536 let east_asian = font_style.font_variant_east_asian;
537 let feature_settings = font_style.font_feature_settings.clone();
538 let position = font_style.font_variant_position;
539 let alternates = font_style.font_variant_alternates.clone();
540
541 let font_group = layout_context.font_context.font_group(font_style);
542 let inherited_text_style = parent_style.get_inherited_text();
543 let word_spacing = Some(inherited_text_style.word_spacing.to_used_value(font_size));
544 let letter_spacing = inherited_text_style
545 .letter_spacing
546 .0
547 .to_used_value(font_size);
548 let letter_spacing = if !letter_spacing.is_zero() {
549 Some(letter_spacing)
550 } else {
551 None
552 };
553 let text_rendering = inherited_text_style.text_rendering;
554
555 let mut current: Option<TextRunSegment> = None;
556 let mut results = Vec::new();
557 let finish_current_segment =
558 |current: &mut Option<TextRunSegment>, results: &mut Vec<TextRunItem>| {
559 if let Some(current) = current.take() {
560 results.push(TextRunItem::TextSegment(Box::new(current)));
561 }
562 };
563
564 let text_run_text =
565 &formatting_context_text[Utf8CodeUnits::to_usize_range(&self.text_range)];
566 let char_iterator = TwoCharsAtATimeIterator::new(text_run_text.chars());
567
568 let mut next_byte_index = self.text_range.start;
570 for (relative_character_index, (character, next_character)) in char_iterator.enumerate() {
571 let current_character_index = self.run_data.character_range_in_ifc_text.start +
573 Utf32CodeUnits(relative_character_index as u32);
574
575 let current_byte_index = next_byte_index;
576 next_byte_index += Utf8CodeUnits::length_of_char(character);
577
578 if character == '\n' {
579 finish_current_segment(&mut current, &mut results);
580 let needs_caret_placeholder = self.run_data.needs_caret_placeholder;
581 results.push(TextRunItem::LineBreak(needs_caret_placeholder.then(|| {
582 CaretPlaceholder {
583 run_data: self.run_data.clone(),
584 base_fragment_info: self.base_fragment_info,
585 character_index: Utf32CodeUnits((relative_character_index + 1) as u32),
588 }
589 })));
590 continue;
591 }
592
593 if character == '\t' {
594 finish_current_segment(&mut current, &mut results);
595
596 let break_at_start = *parent_style.get_white_space_collapse() ==
599 WhiteSpaceCollapse::BreakSpaces &&
600 formatting_context_text[0..current_byte_index.0 as usize]
601 .chars()
602 .next_back()
603 .is_some_and(breaks_for_break_spaces);
604
605 results.push(TextRunItem::Tab {
606 bidi_level: bidi_levels.level(current_byte_index.into()),
607 break_at_start,
608 });
609 continue;
610 }
611
612 let (font, script, bidi_level) = if character_cannot_change_font(character) {
613 (
614 None,
615 Script::Common,
616 bidi_levels.level(current_byte_index.into()),
617 )
618 } else {
619 (
620 font_group.find_by_codepoint(
621 &layout_context.font_context,
622 character,
623 next_character,
624 language,
625 ),
626 Script::from(character),
627 bidi_levels.level(current_byte_index.into()),
628 )
629 };
630
631 if let Some(current) = current.as_mut() &&
633 current.is_compatible(&font, script, bidi_level)
634 {
635 current.update(
636 next_byte_index,
637 current_character_index + Utf32CodeUnits(1),
638 script,
639 );
640 continue;
641 }
642
643 let Some(font) = font.or_else(|| font_group.first(&layout_context.font_context)) else {
644 continue;
645 };
646
647 let alternates = layout_context
648 .font_context
649 .resolve_font_variant_alternate_identifiers_for(
650 &font,
651 &alternates,
652 layout_context.style_context.stylist,
653 );
654 let info = FontAndScriptInfo {
655 script,
656 font_info: Arc::new(FontInfo {
657 font,
658 bidi_level,
659 language: language_for_shaping,
660 word_spacing,
661 letter_spacing,
662 text_rendering,
663 kerning,
664 ligatures,
665 numeric,
666 east_asian,
667 feature_settings: feature_settings.clone(),
668 alternates,
669 position,
670 }),
671 };
672
673 finish_current_segment(&mut current, &mut results);
674 assert!(current.is_none());
675
676 current = Some(TextRunSegment::new(
677 info,
678 current_byte_index..next_byte_index,
679 current_character_index..current_character_index + Utf32CodeUnits(1),
680 ));
681 }
682
683 finish_current_segment(&mut current, &mut results);
684 results
685 }
686
687 pub(super) fn layout_into_line_items(&self, ifc: &mut InlineFormattingContextLayout) {
688 if self.text_range.is_empty() {
689 if self.run_data.needs_caret_placeholder {
690 ifc.current_line.caret_placeholder = Some(CaretPlaceholder {
691 run_data: self.run_data.clone(),
692 base_fragment_info: self.base_fragment_info,
693 character_index: Utf32CodeUnits(0),
694 });
695 }
696 return;
697 }
698
699 let have_deferred_soft_wrap_opportunity =
703 mem::replace(&mut ifc.have_deferred_soft_wrap_opportunity, false);
704 let mut soft_wrap_policy = match have_deferred_soft_wrap_opportunity {
705 true => SegmentStartSoftWrapPolicy::Force,
706 false => SegmentStartSoftWrapPolicy::FollowLinebreaker,
707 };
708
709 for item in self.items.iter() {
710 ifc.possibly_flush_deferred_forced_line_break();
711
712 match item {
713 TextRunItem::LineBreak(caret_placeholder) => {
717 ifc.defer_forced_line_break_at_character_offset(caret_placeholder);
718 },
719 TextRunItem::Tab {
720 bidi_level,
721 break_at_start,
722 } => self.process_preserved_tab(ifc, *bidi_level, *break_at_start),
723 TextRunItem::TextSegment(segment) => {
724 segment.layout_into_line_items(self, soft_wrap_policy, ifc)
725 },
726 }
727 soft_wrap_policy = SegmentStartSoftWrapPolicy::FollowLinebreaker;
728 }
729 }
730
731 fn process_preserved_tab(
732 &self,
733 ifc_layout: &mut InlineFormattingContextLayout,
734 bidi_level: Level,
735 break_at_start: bool,
736 ) {
737 if break_at_start {
738 ifc_layout.process_soft_wrap_opportunity();
739 }
740
741 let position_after_current_segment =
742 ifc_layout.current_line.inline_position + ifc_layout.current_line_segment.inline_size;
743 let style = self.inline_styles().style.borrow();
744 let advance = ifc_layout
745 .ifc
746 .next_tab_stop_after_inline_advance(&style, position_after_current_segment);
747 if advance.is_zero() {
748 return;
749 }
750
751 let conditionally_hangs = style.white_space_may_hang_conditionally();
752 let (flags, hanging_advance) = if conditionally_hangs {
753 (SegmentContentFlags::Contentful, advance)
754 } else {
755 (
756 SegmentContentFlags::Contentful |
757 SegmentContentFlags::IncorporateTrailingWhiteSpace,
758 Au::zero(),
759 )
760 };
761
762 ifc_layout.update_unbreakable_segment_for_new_content(
763 &LineBlockSizes::zero(),
764 advance,
765 hanging_advance,
766 Au::zero(),
767 flags,
768 );
769 ifc_layout.push_line_item_to_unbreakable_segment(LineItem::Tab {
770 inline_box_identifier: ifc_layout.current_inline_box_identifier(),
771 advance,
772 bidi_level,
773 conditionally_hangs,
774 });
775
776 if *ifc_layout
777 .current_inline_container_state()
778 .style
779 .get_white_space_collapse() ==
780 WhiteSpaceCollapse::BreakSpaces
781 {
782 ifc_layout.process_soft_wrap_opportunity();
783 }
784 }
785}
786
787fn is_cursive_script(script: Script) -> bool {
792 matches!(
793 script,
794 Script::Arabic |
795 Script::Hanifi_Rohingya |
796 Script::Mandaic |
797 Script::Mongolian |
798 Script::Nko |
799 Script::Phags_Pa |
800 Script::Syriac
801 )
802}
803
804fn character_cannot_change_font(character: char) -> bool {
808 if character.is_control() {
809 return true;
810 }
811 if character == '\u{00A0}' {
812 return true;
813 }
814 if is_bidi_control(character) {
815 return false;
816 }
817
818 matches!(
819 LineBreak::for_char(character),
820 LineBreak::CombiningMark |
821 LineBreak::Glue |
822 LineBreak::ZWSpace |
823 LineBreak::WordJoiner |
824 LineBreak::ZWJ
825 )
826}
827
828pub(super) fn get_font_for_first_font_for_style(
829 style: &ComputedValues,
830 font_context: &FontContext,
831) -> Option<FontRef> {
832 let font = font_context
833 .font_group(style.clone_font())
834 .first(font_context);
835 if font.is_none() {
836 warn!("Could not find font for style: {:?}", style.clone_font());
837 }
838 font
839}
840pub(crate) struct TwoCharsAtATimeIterator<InputIterator> {
841 iterator: InputIterator,
843 next_character: Option<char>,
845}
846
847impl<InputIterator> TwoCharsAtATimeIterator<InputIterator> {
848 fn new(iterator: InputIterator) -> Self {
849 Self {
850 iterator,
851 next_character: None,
852 }
853 }
854}
855
856impl<InputIterator> Iterator for TwoCharsAtATimeIterator<InputIterator>
857where
858 InputIterator: Iterator<Item = char>,
859{
860 type Item = (char, Option<char>);
861
862 fn next(&mut self) -> Option<Self::Item> {
863 if self.next_character.is_none() {
865 self.next_character = self.iterator.next();
866 }
867 let character = self.next_character?;
868 self.next_character = self.iterator.next();
869 Some((character, self.next_character))
870 }
871}
872
873pub(crate) fn script_is_specific(script: Script) -> bool {
874 script != Script::Common && script != Script::Inherited
875}