1use 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#[derive(PartialEq)]
57enum SegmentStartSoftWrapPolicy {
58 Force,
59 FollowLinebreaker,
60}
61
62#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
64pub(crate) struct FontAndScriptInfo {
65 pub script: Script,
67 #[conditional_malloc_size_of]
69 pub font_info: Arc<FontInfo>,
70}
71
72impl FontAndScriptInfo {
73 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#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
86pub(crate) struct FontInfo {
87 pub font: FontRef,
89 pub bidi_level: Level,
91 pub language: Language,
93 pub letter_spacing: Option<Au>,
98 pub word_spacing: Option<Au>,
100 pub text_rendering: TextRendering,
102 pub kerning: FontKerning,
104 pub ligatures: FontVariantLigatures,
106 pub numeric: FontVariantNumeric,
108 pub east_asian: FontVariantEastAsian,
110 pub feature_settings: FontFeatureSettings,
112 pub position: FontVariantPosition,
114 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 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 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 pub info: FontAndScriptInfo,
189
190 pub byte_range: Range<usize>,
192
193 pub character_range: Range<usize>,
195
196 pub break_at_start: bool,
199
200 #[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 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 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 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 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 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 let mut slice = last_slice.end..*break_index;
358 let word = &formatting_context_text[slice.clone()];
359
360 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 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 !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 last_slice = slice.start..*break_index;
400
401 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, non_whitespace_slice_ends_with_whitespace,
408 ));
409 }
410
411 if whitespace.is_empty() {
412 continue;
413 }
414
415 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, true, ));
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, true, ));
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#[derive(Debug, MallocSizeOf)]
443pub(crate) enum TextRunItem {
444 LineBreak { character_index: usize },
446 Tab { bidi_level: Level },
448 TextSegment(Box<TextRunSegment>),
451}
452
453#[derive(Debug, MallocSizeOf)]
460pub(crate) struct TextRun {
461 pub base_fragment_info: BaseFragmentInfo,
464
465 pub parent_box: Option<WeakLayoutBox>,
468
469 pub inline_styles: SharedInlineStyles,
473
474 pub text_range: Range<usize>,
477
478 pub character_range: Range<usize>,
482
483 pub document_selection: Range<Utf32CodeUnits>,
485
486 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 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 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 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 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 let mut next_byte_index = self.text_range.start;
609 for (relative_character_index, (character, next_character)) in char_iterator.enumerate() {
610 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 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 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 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
769fn 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
786fn 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 iterator: InputIterator,
825 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 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}