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;
38use crate::flow::inline::text_transform::OffsetMap;
39use crate::flow::inline::{BidiLevels, LineBlockSizes, LineItem, SegmentContentFlags};
40use crate::fragment_tree::BaseFragmentInfo;
41
42#[derive(PartialEq)]
51enum SegmentStartSoftWrapPolicy {
52 Force,
53 FollowLinebreaker,
54}
55
56#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
58pub(crate) struct FontAndScriptInfo {
59 pub script: Script,
61 #[conditional_malloc_size_of]
63 pub font_info: Arc<FontInfo>,
64}
65
66impl FontAndScriptInfo {
67 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#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
80pub(crate) struct FontInfo {
81 pub font: FontRef,
83 pub bidi_level: Level,
85 pub language: Language,
87 pub letter_spacing: Option<Au>,
92 pub word_spacing: Option<Au>,
94 pub text_rendering: TextRendering,
96 pub kerning: FontKerning,
98 pub ligatures: FontVariantLigatures,
100 pub numeric: FontVariantNumeric,
102 pub east_asian: FontVariantEastAsian,
104 pub feature_settings: FontFeatureSettings,
106 pub position: FontVariantPosition,
108 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 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 let letter_spacing = letter_spacing.unwrap_or(Au::zero());
153 let word_spacing = info.font_info.word_spacing.unwrap_or(Au::zero());
154 if info.font_info.text_rendering == TextRendering::Optimizespeed {
155 ligatures = FontVariantLigatures::NONE;
156 flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
157 }
158
159 if info.font_info.kerning == FontKerning::None {
161 flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG);
162 }
163
164 Self {
165 letter_spacing,
166 word_spacing,
167 script: info.script,
168 language: info.font_info.language,
169 ligatures,
170 numeric: info.font_info.numeric,
171 east_asian: info.font_info.east_asian,
172 feature_settings: info.font_info.feature_settings.clone(),
173 position: info.font_info.position,
174 flags,
175 alternates: info.font_info.alternates.clone(),
176 }
177 }
178}
179
180#[derive(Clone, Debug, MallocSizeOf)]
181pub(crate) struct TextRunSegment {
182 pub info: FontAndScriptInfo,
185
186 pub byte_range: Range<Utf8CodeUnits>,
188
189 pub character_range: Range<Utf32CodeUnits>,
191
192 pub break_at_start: bool,
195
196 #[conditional_malloc_size_of]
198 pub runs: Vec<Arc<ShapedTextSlice>>,
199
200 #[conditional_malloc_size_of]
203 pub shaped_text: Option<Arc<ShapedText>>,
204}
205
206impl TextRunSegment {
207 fn new(
208 info: FontAndScriptInfo,
209 byte_range: Range<Utf8CodeUnits>,
210 character_range: Range<Utf32CodeUnits>,
211 ) -> Self {
212 Self {
213 info,
214 byte_range,
215 character_range,
216 runs: Vec::new(),
217 break_at_start: false,
218 shaped_text: None,
219 }
220 }
221
222 fn is_compatible(
225 &self,
226 new_font: &Option<FontRef>,
227 new_script: Script,
228 new_bidi_level: Level,
229 ) -> bool {
230 if self.info.font_info.bidi_level != new_bidi_level {
231 return false;
232 }
233 if new_font
234 .as_ref()
235 .is_some_and(|new_font| !Arc::ptr_eq(&self.info.font_info.font, new_font))
236 {
237 return false;
238 }
239
240 !script_is_specific(self.info.script) ||
241 !script_is_specific(new_script) ||
242 self.info.script == new_script
243 }
244
245 fn update(
248 &mut self,
249 next_byte_index: Utf8CodeUnits,
250 next_character_index: Utf32CodeUnits,
251 new_script: Script,
252 ) {
253 if !script_is_specific(self.info.script) && script_is_specific(new_script) {
254 self.info = FontAndScriptInfo {
255 script: new_script,
256 font_info: self.info.font_info.clone(),
257 };
258 }
259 self.character_range.end = next_character_index;
260 self.byte_range.end = next_byte_index;
261 }
262
263 fn layout_into_line_items(
264 &self,
265 text_run: &TextRun,
266 mut soft_wrap_policy: SegmentStartSoftWrapPolicy,
267 ifc: &mut InlineFormattingContextLayout,
268 ) {
269 if self.break_at_start && soft_wrap_policy == SegmentStartSoftWrapPolicy::FollowLinebreaker
270 {
271 soft_wrap_policy = SegmentStartSoftWrapPolicy::Force;
272 }
273
274 let mut character_range_start = self.character_range.start;
275 for (run_index, run) in self.runs.iter().enumerate() {
276 let new_character_range_end = character_range_start + run.character_count();
277
278 if run_index != 0 || soft_wrap_policy == SegmentStartSoftWrapPolicy::Force {
281 ifc.process_soft_wrap_opportunity();
282 }
283
284 let run_start = text_run.run_data.character_range_in_ifc_text.start;
285 ifc.push_shaped_text_slice_to_unbreakable_segment(
286 run.clone(),
287 text_run,
288 &self.info,
289 character_range_start - run_start..new_character_range_end - run_start,
290 );
291
292 character_range_start = new_character_range_end;
293 }
294 }
295
296 pub(crate) fn is_compatible_with_old_shaping_result(&self, old_segment: &Self) -> bool {
297 old_segment.info == self.info && self.byte_range == old_segment.byte_range
298 }
299}
300
301#[derive(Clone, Debug, MallocSizeOf)]
302pub(crate) struct CaretPlaceholder {
303 #[conditional_malloc_size_of]
305 pub run_data: Arc<SharedTextRunData>,
306 pub base_fragment_info: BaseFragmentInfo,
308 pub character_index: Utf32CodeUnits,
311}
312
313#[derive(Debug, MallocSizeOf)]
315pub(crate) enum TextRunItem {
316 LineBreak(Option<CaretPlaceholder>),
318 Tab { bidi_level: Level },
320 TextSegment(Box<TextRunSegment>),
323}
324
325#[derive(Debug, MallocSizeOf)]
328pub(crate) struct SharedTextRunData {
329 #[conditional_malloc_size_of]
331 pub text_content: Arc<OnceLock<String>>,
332 pub inline_styles: SharedInlineStyles,
336 pub character_range_in_ifc_text: Range<Utf32CodeUnits>,
340 pub original_offset: Utf32CodeUnits,
343 pub selection: AtomicRefCell<Option<RangeAny<Utf32CodeUnits>>>,
347 pub paints_caret: bool,
349 pub needs_caret_placeholder: bool,
352 pub offset_map: ArcRefCell<OffsetMap>,
355}
356
357impl SharedTextRunData {
358 pub(crate) fn map_dom_range_to_transformed_range(
362 &self,
363 dom_range: RangeAny<Utf32CodeUnits>,
364 ) -> Range<Utf32CodeUnits> {
365 let offset_map = self.offset_map.borrow();
366 let offset_in_ifc_text = self.character_range_in_ifc_text.start;
367 let start = if let Some(dom_start) = dom_range.start() {
368 offset_map.map(dom_start + self.original_offset) - offset_in_ifc_text
369 } else {
370 Utf32CodeUnits(0)
371 };
372 let end = if let Some(dom_end) = dom_range.end() {
373 offset_map.map(dom_end + self.original_offset) - offset_in_ifc_text
374 } else {
375 self.character_range_in_ifc_text.end - self.character_range_in_ifc_text.start
376 };
377 start..end
378 }
379
380 pub(crate) fn map_transformed_offset_to_dom_offset(
383 &self,
384 offset: Utf32CodeUnits,
385 ) -> Utf32CodeUnits {
386 let offset_map = self.offset_map.borrow();
387 let offset_in_ifc_text = self.character_range_in_ifc_text.start;
388 offset_map.reverse_map(offset + offset_in_ifc_text) - self.original_offset
389 }
390}
391
392#[derive(Debug, MallocSizeOf)]
399pub(crate) struct TextRun {
400 pub base_fragment_info: BaseFragmentInfo,
403
404 #[conditional_malloc_size_of]
407 pub run_data: Arc<SharedTextRunData>,
408
409 pub parent_box: Option<WeakLayoutBox>,
412
413 pub text_range: Range<Utf8CodeUnits>,
416
417 pub items: Vec<TextRunItem>,
421}
422
423impl TextRun {
424 pub(crate) fn new(
425 base_fragment_info: BaseFragmentInfo,
426 run_data: Arc<SharedTextRunData>,
427 text_range: Range<Utf8CodeUnits>,
428 old_text_run: Option<ArcRefCell<TextRun>>,
429 ) -> Self {
430 let items = old_text_run
432 .map(|old_text_run| std::mem::take(&mut old_text_run.borrow_mut().items))
433 .unwrap_or_default();
434 Self {
435 base_fragment_info,
436 run_data,
437 parent_box: None,
438 text_range,
439 items,
440 }
441 }
442
443 pub(super) fn inline_styles(&self) -> &SharedInlineStyles {
444 &self.run_data.inline_styles
445 }
446
447 pub(super) fn segment(
448 &mut self,
449 self_arc_ref_cell: ArcRefCell<TextRun>,
450 formatting_context_text: &str,
451 layout_context: &LayoutContext,
452 bidi_levels: &BidiLevels,
453 ) -> SmallVec<[ShapingQueueEntry; 1]> {
454 let parent_style = self.inline_styles().style.borrow().clone();
455 let items = self.segment_text_by_font(
456 layout_context,
457 formatting_context_text,
458 bidi_levels,
459 &parent_style,
460 );
461
462 let mut old_text_run_items = std::mem::replace(&mut self.items, items).into_iter();
465
466 self.items
467 .iter()
468 .enumerate()
469 .map(move |(index, text_run_item)| {
470 let old_text_run_item = old_text_run_items.next();
471 ShapingQueueEntry::new(
472 self_arc_ref_cell.clone(),
473 text_run_item,
474 index,
475 old_text_run_item,
476 )
477 })
478 .collect()
479 }
480
481 fn segment_text_by_font(
485 &mut self,
486 layout_context: &LayoutContext,
487 formatting_context_text: &str,
488 bidi_levels: &BidiLevels,
489 parent_style: &ServoArc<ComputedValues>,
490 ) -> Vec<TextRunItem> {
491 let font_style = parent_style.clone_font();
492 let language = font_style._x_lang.0.parse().unwrap_or(Language::UNKNOWN);
493 let language_for_shaping = Some(font_style.font_language_override)
494 .filter(|language_override| *language_override != FontLanguageOverride::normal())
495 .and_then(|language_override| {
496 Language::try_from_utf8(&language_override.0.to_be_bytes()[..3]).ok()
505 })
506 .unwrap_or(language);
507 let font_size = font_style.font_size.computed_size().into();
508 let kerning = font_style.font_kerning;
509 let ligatures = font_style.font_variant_ligatures;
510 let numeric = font_style.font_variant_numeric;
511 let east_asian = font_style.font_variant_east_asian;
512 let feature_settings = font_style.font_feature_settings.clone();
513 let position = font_style.font_variant_position;
514 let alternates = font_style.font_variant_alternates.clone();
515
516 let font_group = layout_context.font_context.font_group(font_style);
517 let inherited_text_style = parent_style.get_inherited_text();
518 let word_spacing = Some(inherited_text_style.word_spacing.to_used_value(font_size));
519 let letter_spacing = inherited_text_style
520 .letter_spacing
521 .0
522 .to_used_value(font_size);
523 let letter_spacing = if !letter_spacing.is_zero() {
524 Some(letter_spacing)
525 } else {
526 None
527 };
528 let text_rendering = inherited_text_style.text_rendering;
529
530 let mut current: Option<TextRunSegment> = None;
531 let mut results = Vec::new();
532 let finish_current_segment =
533 |current: &mut Option<TextRunSegment>, results: &mut Vec<TextRunItem>| {
534 if let Some(current) = current.take() {
535 results.push(TextRunItem::TextSegment(Box::new(current)));
536 }
537 };
538
539 let text_run_text =
540 &formatting_context_text[Utf8CodeUnits::to_usize_range(&self.text_range)];
541 let char_iterator = TwoCharsAtATimeIterator::new(text_run_text.chars());
542 let mut next_byte_index = self.text_range.start;
544 for (relative_character_index, (character, next_character)) in char_iterator.enumerate() {
545 let current_character_index = self.run_data.character_range_in_ifc_text.start +
547 Utf32CodeUnits(relative_character_index as u32);
548
549 let current_byte_index = next_byte_index;
550 next_byte_index += Utf8CodeUnits::length_of_char(character);
551
552 if character == '\n' {
553 finish_current_segment(&mut current, &mut results);
554 let needs_caret_placeholder = self.run_data.needs_caret_placeholder;
555 results.push(TextRunItem::LineBreak(needs_caret_placeholder.then(|| {
556 CaretPlaceholder {
557 run_data: self.run_data.clone(),
558 base_fragment_info: self.base_fragment_info,
559 character_index: Utf32CodeUnits((relative_character_index + 1) as u32),
562 }
563 })));
564 continue;
565 }
566
567 if character == '\t' {
568 finish_current_segment(&mut current, &mut results);
569 results.push(TextRunItem::Tab {
570 bidi_level: bidi_levels.level(current_byte_index.into()),
571 });
572 continue;
573 }
574
575 let (font, script, bidi_level) = if character_cannot_change_font(character) {
576 (
577 None,
578 Script::Common,
579 bidi_levels.level(current_byte_index.into()),
580 )
581 } else {
582 (
583 font_group.find_by_codepoint(
584 &layout_context.font_context,
585 character,
586 next_character,
587 language,
588 ),
589 Script::from(character),
590 bidi_levels.level(current_byte_index.into()),
591 )
592 };
593
594 if let Some(current) = current.as_mut() &&
596 current.is_compatible(&font, script, bidi_level)
597 {
598 current.update(
599 next_byte_index,
600 current_character_index + Utf32CodeUnits(1),
601 script,
602 );
603 continue;
604 }
605
606 let Some(font) = font.or_else(|| font_group.first(&layout_context.font_context)) else {
607 continue;
608 };
609
610 let alternates = layout_context
611 .font_context
612 .resolve_font_variant_alternate_identifiers_for(
613 &font,
614 &alternates,
615 layout_context.style_context.stylist,
616 );
617 let info = FontAndScriptInfo {
618 script,
619 font_info: Arc::new(FontInfo {
620 font,
621 bidi_level,
622 language: language_for_shaping,
623 word_spacing,
624 letter_spacing,
625 text_rendering,
626 kerning,
627 ligatures,
628 numeric,
629 east_asian,
630 feature_settings: feature_settings.clone(),
631 alternates,
632 position,
633 }),
634 };
635
636 finish_current_segment(&mut current, &mut results);
637 assert!(current.is_none());
638
639 current = Some(TextRunSegment::new(
640 info,
641 current_byte_index..next_byte_index,
642 current_character_index..current_character_index + Utf32CodeUnits(1),
643 ));
644 }
645
646 finish_current_segment(&mut current, &mut results);
647 results
648 }
649
650 pub(super) fn layout_into_line_items(&self, ifc: &mut InlineFormattingContextLayout) {
651 if self.text_range.is_empty() {
652 if self.run_data.needs_caret_placeholder {
653 ifc.current_line.caret_placeholder = Some(CaretPlaceholder {
654 run_data: self.run_data.clone(),
655 base_fragment_info: self.base_fragment_info,
656 character_index: Utf32CodeUnits(0),
657 });
658 }
659 return;
660 }
661
662 let have_deferred_soft_wrap_opportunity =
666 mem::replace(&mut ifc.have_deferred_soft_wrap_opportunity, false);
667 let mut soft_wrap_policy = match have_deferred_soft_wrap_opportunity {
668 true => SegmentStartSoftWrapPolicy::Force,
669 false => SegmentStartSoftWrapPolicy::FollowLinebreaker,
670 };
671
672 for item in self.items.iter() {
673 ifc.possibly_flush_deferred_forced_line_break();
674
675 match item {
676 TextRunItem::LineBreak(caret_placeholder) => {
680 ifc.defer_forced_line_break_at_character_offset(caret_placeholder);
681 },
682 TextRunItem::Tab { bidi_level } => self.process_preserved_tab(ifc, *bidi_level),
683 TextRunItem::TextSegment(segment) => {
684 segment.layout_into_line_items(self, soft_wrap_policy, ifc)
685 },
686 }
687 soft_wrap_policy = SegmentStartSoftWrapPolicy::FollowLinebreaker;
688 }
689 }
690
691 fn process_preserved_tab(
692 &self,
693 ifc_layout: &mut InlineFormattingContextLayout,
694 bidi_level: Level,
695 ) {
696 let position_after_current_segment =
697 ifc_layout.current_line.inline_position + ifc_layout.current_line_segment.inline_size;
698 let advance = ifc_layout.ifc.next_tab_stop_after_inline_advance(
699 &self.inline_styles().style.borrow(),
700 position_after_current_segment,
701 );
702 if advance.is_zero() {
703 return;
704 }
705
706 ifc_layout.update_unbreakable_segment_for_new_content(
708 &LineBlockSizes::zero(),
709 advance,
710 Au::zero(),
711 Au::zero(),
712 SegmentContentFlags::Contentful | SegmentContentFlags::IncorporateTrailingWhiteSpace,
713 );
714 ifc_layout.push_line_item_to_unbreakable_segment(LineItem::Tab {
715 inline_box_identifier: ifc_layout.current_inline_box_identifier(),
716 advance,
717 bidi_level,
718 });
719
720 if ifc_layout
721 .current_inline_container_state()
722 .style
723 .get_inherited_text()
724 .white_space_collapse ==
725 WhiteSpaceCollapse::BreakSpaces
726 {
727 ifc_layout.process_soft_wrap_opportunity();
728 }
729 }
730}
731
732fn is_cursive_script(script: Script) -> bool {
737 matches!(
738 script,
739 Script::Arabic |
740 Script::Hanifi_Rohingya |
741 Script::Mandaic |
742 Script::Mongolian |
743 Script::Nko |
744 Script::Phags_Pa |
745 Script::Syriac
746 )
747}
748
749fn character_cannot_change_font(character: char) -> bool {
753 if character.is_control() {
754 return true;
755 }
756 if character == '\u{00A0}' {
757 return true;
758 }
759 if is_bidi_control(character) {
760 return false;
761 }
762
763 matches!(
764 LineBreak::for_char(character),
765 LineBreak::CombiningMark |
766 LineBreak::Glue |
767 LineBreak::ZWSpace |
768 LineBreak::WordJoiner |
769 LineBreak::ZWJ
770 )
771}
772
773pub(super) fn get_font_for_first_font_for_style(
774 style: &ComputedValues,
775 font_context: &FontContext,
776) -> Option<FontRef> {
777 let font = font_context
778 .font_group(style.clone_font())
779 .first(font_context);
780 if font.is_none() {
781 warn!("Could not find font for style: {:?}", style.clone_font());
782 }
783 font
784}
785pub(crate) struct TwoCharsAtATimeIterator<InputIterator> {
786 iterator: InputIterator,
788 next_character: Option<char>,
790}
791
792impl<InputIterator> TwoCharsAtATimeIterator<InputIterator> {
793 fn new(iterator: InputIterator) -> Self {
794 Self {
795 iterator,
796 next_character: None,
797 }
798 }
799}
800
801impl<InputIterator> Iterator for TwoCharsAtATimeIterator<InputIterator>
802where
803 InputIterator: Iterator<Item = char>,
804{
805 type Item = (char, Option<char>);
806
807 fn next(&mut self) -> Option<Self::Item> {
808 if self.next_character.is_none() {
810 self.next_character = self.iterator.next();
811 }
812 let character = self.next_character?;
813 self.next_character = self.iterator.next();
814 Some((character, self.next_character))
815 }
816}
817
818pub(crate) fn script_is_specific(script: Script) -> bool {
819 script != Script::Common && script != Script::Inherited
820}