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::{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, 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 if info.font_info.text_rendering == TextRendering::Optimizespeed {
153 ligatures = FontVariantLigatures::NONE;
154 flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
155 }
156
157 if info.font_info.kerning == FontKerning::None {
159 flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG);
160 }
161
162 Self {
163 letter_spacing,
164 word_spacing: info.font_info.word_spacing,
165 script: info.script,
166 language: info.font_info.language,
167 ligatures,
168 numeric: info.font_info.numeric,
169 east_asian: info.font_info.east_asian,
170 feature_settings: info.font_info.feature_settings.clone(),
171 position: info.font_info.position,
172 flags,
173 alternates: info.font_info.alternates.clone(),
174 }
175 }
176}
177
178#[derive(Clone, Debug, MallocSizeOf)]
179pub(crate) struct TextRunSegment {
180 pub info: FontAndScriptInfo,
183
184 pub byte_range: Range<usize>,
186
187 pub character_range: Range<usize>,
189
190 pub break_at_start: bool,
193
194 #[conditional_malloc_size_of]
196 pub runs: Vec<Arc<ShapedTextSlice>>,
197
198 #[conditional_malloc_size_of]
201 pub shaped_text: Option<Arc<ShapedText>>,
202}
203
204impl TextRunSegment {
205 fn new(
206 info: FontAndScriptInfo,
207 byte_range: Range<usize>,
208 character_range: Range<usize>,
209 ) -> Self {
210 Self {
211 info,
212 byte_range,
213 character_range,
214 runs: Vec::new(),
215 break_at_start: false,
216 shaped_text: None,
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
271 if run_index != 0 || soft_wrap_policy == SegmentStartSoftWrapPolicy::Force {
274 ifc.process_soft_wrap_opportunity();
275 }
276
277 let run_start = text_run.run_data.character_range_in_ifc_text.start;
278 ifc.push_glyph_store_to_unbreakable_segment(
279 run.clone(),
280 text_run,
281 &self.info,
282 Utf32CodeUnits(character_range_start - run_start)..
283 Utf32CodeUnits(new_character_range_end - run_start),
284 );
285
286 character_range_start = new_character_range_end;
287 }
288 }
289
290 pub(crate) fn is_compatible_with_old_shaping_result(&self, old_segment: &Self) -> bool {
291 old_segment.info == self.info && self.byte_range == old_segment.byte_range
292 }
293}
294
295#[derive(Clone, Debug, MallocSizeOf)]
296pub(crate) struct CaretPlaceholder {
297 #[conditional_malloc_size_of]
299 pub run_data: Arc<SharedTextRunData>,
300 pub base_fragment_info: BaseFragmentInfo,
302 pub character_index: usize,
305}
306
307#[derive(Debug, MallocSizeOf)]
309pub(crate) enum TextRunItem {
310 LineBreak(Option<CaretPlaceholder>),
312 Tab { bidi_level: Level },
314 TextSegment(Box<TextRunSegment>),
317}
318
319#[derive(Debug, MallocSizeOf)]
322pub(crate) struct SharedTextRunData {
323 pub inline_styles: SharedInlineStyles,
327 pub character_range_in_ifc_text: Range<usize>,
331 pub original_offset: Utf32CodeUnits,
334 pub selection: AtomicRefCell<Option<RangeAny<Utf32CodeUnits>>>,
338 pub paint_caret: bool,
340 pub offset_map: ArcRefCell<OffsetMap>,
343}
344
345impl SharedTextRunData {
346 pub(crate) fn map_dom_range_to_transformed_range(
350 &self,
351 dom_range: RangeAny<Utf32CodeUnits>,
352 ) -> Range<Utf32CodeUnits> {
353 let offset_map = self.offset_map.borrow();
354 let offset_in_ifc_text = Utf32CodeUnits(self.character_range_in_ifc_text.start);
355 let start = if let Some(dom_start) = dom_range.start {
356 offset_map.map(dom_start + self.original_offset) - offset_in_ifc_text
357 } else {
358 Utf32CodeUnits(0)
359 };
360 let end = if let Some(dom_end) = dom_range.end {
361 offset_map.map(dom_end + self.original_offset) - offset_in_ifc_text
362 } else {
363 Utf32CodeUnits(self.character_range_in_ifc_text.len())
364 };
365 start..end
366 }
367
368 pub(crate) fn map_transformed_offset_to_dom_offset(
371 &self,
372 offset: Utf32CodeUnits,
373 ) -> Utf32CodeUnits {
374 let offset_map = self.offset_map.borrow();
375 let offset_in_ifc_text = Utf32CodeUnits(self.character_range_in_ifc_text.start);
376 offset_map.reverse_map(offset + offset_in_ifc_text) - self.original_offset
377 }
378}
379
380#[derive(Debug, MallocSizeOf)]
387pub(crate) struct TextRun {
388 pub base_fragment_info: BaseFragmentInfo,
391
392 #[conditional_malloc_size_of]
395 pub run_data: Arc<SharedTextRunData>,
396
397 pub parent_box: Option<WeakLayoutBox>,
400
401 pub text_range: Range<usize>,
404
405 pub items: Vec<TextRunItem>,
409}
410
411impl TextRun {
412 pub(crate) fn new(
413 base_fragment_info: BaseFragmentInfo,
414 run_data: Arc<SharedTextRunData>,
415 text_range: Range<usize>,
416 old_text_run: Option<ArcRefCell<TextRun>>,
417 ) -> Self {
418 let items = old_text_run
420 .map(|old_text_run| std::mem::take(&mut old_text_run.borrow_mut().items))
421 .unwrap_or_default();
422 Self {
423 base_fragment_info,
424 run_data,
425 parent_box: None,
426 text_range,
427 items,
428 }
429 }
430
431 pub(super) fn inline_styles(&self) -> &SharedInlineStyles {
432 &self.run_data.inline_styles
433 }
434
435 pub(super) fn segment(
436 &mut self,
437 self_arc_ref_cell: ArcRefCell<TextRun>,
438 formatting_context_text: &str,
439 layout_context: &LayoutContext,
440 bidi_levels: &BidiLevels,
441 ) -> SmallVec<[ShapingQueueEntry; 1]> {
442 let parent_style = self.inline_styles().style.borrow().clone();
443 let items = self.segment_text_by_font(
444 layout_context,
445 formatting_context_text,
446 bidi_levels,
447 &parent_style,
448 );
449
450 let mut old_text_run_items = std::mem::replace(&mut self.items, items).into_iter();
453
454 self.items
455 .iter()
456 .enumerate()
457 .map(move |(index, text_run_item)| {
458 let old_text_run_item = old_text_run_items.next();
459 ShapingQueueEntry::new(
460 self_arc_ref_cell.clone(),
461 text_run_item,
462 index,
463 old_text_run_item,
464 )
465 })
466 .collect()
467 }
468
469 fn segment_text_by_font(
473 &mut self,
474 layout_context: &LayoutContext,
475 formatting_context_text: &str,
476 bidi_levels: &BidiLevels,
477 parent_style: &ServoArc<ComputedValues>,
478 ) -> Vec<TextRunItem> {
479 let font_style = parent_style.clone_font();
480 let language = font_style._x_lang.0.parse().unwrap_or(Language::UNKNOWN);
481 let language_for_shaping = Some(font_style.font_language_override)
482 .filter(|language_override| *language_override != FontLanguageOverride::normal())
483 .and_then(|language_override| {
484 Language::try_from_utf8(&language_override.0.to_be_bytes()[..3]).ok()
493 })
494 .unwrap_or(language);
495 let font_size = font_style.font_size.computed_size().into();
496 let kerning = font_style.font_kerning;
497 let ligatures = font_style.font_variant_ligatures;
498 let numeric = font_style.font_variant_numeric;
499 let east_asian = font_style.font_variant_east_asian;
500 let feature_settings = font_style.font_feature_settings.clone();
501 let position = font_style.font_variant_position;
502 let alternates = font_style.font_variant_alternates.clone();
503
504 let font_group = layout_context.font_context.font_group(font_style);
505 let inherited_text_style = parent_style.get_inherited_text();
506 let word_spacing = Some(inherited_text_style.word_spacing.to_used_value(font_size));
507 let letter_spacing = inherited_text_style
508 .letter_spacing
509 .0
510 .to_used_value(font_size);
511 let letter_spacing = if !letter_spacing.is_zero() {
512 Some(letter_spacing)
513 } else {
514 None
515 };
516 let text_rendering = inherited_text_style.text_rendering;
517
518 let mut current: Option<TextRunSegment> = None;
519 let mut results = Vec::new();
520 let finish_current_segment =
521 |current: &mut Option<TextRunSegment>, results: &mut Vec<TextRunItem>| {
522 if let Some(current) = current.take() {
523 results.push(TextRunItem::TextSegment(Box::new(current)));
524 }
525 };
526
527 let text_run_text = &formatting_context_text[self.text_range.clone()];
528 let char_iterator = TwoCharsAtATimeIterator::new(text_run_text.chars());
529 let mut next_byte_index = self.text_range.start;
531 for (relative_character_index, (character, next_character)) in char_iterator.enumerate() {
532 let current_character_index =
534 self.run_data.character_range_in_ifc_text.start + relative_character_index;
535
536 let current_byte_index = next_byte_index;
537 next_byte_index += character.len_utf8();
538
539 if character == '\n' {
540 finish_current_segment(&mut current, &mut results);
541 let paint_caret = self.run_data.paint_caret;
542 results.push(TextRunItem::LineBreak(paint_caret.then(|| {
543 CaretPlaceholder {
544 run_data: self.run_data.clone(),
545 base_fragment_info: self.base_fragment_info,
546 character_index: relative_character_index + 1,
549 }
550 })));
551 continue;
552 }
553
554 if character == '\t' {
555 finish_current_segment(&mut current, &mut results);
556 results.push(TextRunItem::Tab {
557 bidi_level: bidi_levels.level(current_byte_index),
558 });
559 continue;
560 }
561
562 let (font, script, bidi_level) = if character_cannot_change_font(character) {
563 (None, Script::Common, bidi_levels.level(current_byte_index))
564 } else {
565 (
566 font_group.find_by_codepoint(
567 &layout_context.font_context,
568 character,
569 next_character,
570 language,
571 ),
572 Script::from(character),
573 bidi_levels.level(current_byte_index),
574 )
575 };
576
577 if let Some(current) = current.as_mut() &&
579 current.is_compatible(&font, script, bidi_level)
580 {
581 current.update(next_byte_index, current_character_index + 1, script);
582 continue;
583 }
584
585 let Some(font) = font.or_else(|| font_group.first(&layout_context.font_context)) else {
586 continue;
587 };
588
589 let alternates = layout_context
590 .font_context
591 .resolve_font_variant_alternate_identifiers_for(
592 &font,
593 &alternates,
594 layout_context.style_context.stylist,
595 );
596 let info = FontAndScriptInfo {
597 script,
598 font_info: Arc::new(FontInfo {
599 font,
600 bidi_level,
601 language: language_for_shaping,
602 word_spacing,
603 letter_spacing,
604 text_rendering,
605 kerning,
606 ligatures,
607 numeric,
608 east_asian,
609 feature_settings: feature_settings.clone(),
610 alternates,
611 position,
612 }),
613 };
614
615 finish_current_segment(&mut current, &mut results);
616 assert!(current.is_none());
617
618 current = Some(TextRunSegment::new(
619 info,
620 current_byte_index..next_byte_index,
621 current_character_index..current_character_index + 1,
622 ));
623 }
624
625 finish_current_segment(&mut current, &mut results);
626 results
627 }
628
629 pub(super) fn layout_into_line_items(&self, ifc: &mut InlineFormattingContextLayout) {
630 if self.text_range.is_empty() {
631 return;
632 }
633
634 let have_deferred_soft_wrap_opportunity =
638 mem::replace(&mut ifc.have_deferred_soft_wrap_opportunity, false);
639 let mut soft_wrap_policy = match have_deferred_soft_wrap_opportunity {
640 true => SegmentStartSoftWrapPolicy::Force,
641 false => SegmentStartSoftWrapPolicy::FollowLinebreaker,
642 };
643
644 for item in self.items.iter() {
645 ifc.possibly_flush_deferred_forced_line_break();
646
647 match item {
648 TextRunItem::LineBreak(caret_placeholder) => {
652 ifc.defer_forced_line_break_at_character_offset(caret_placeholder);
653 },
654 TextRunItem::Tab { bidi_level } => self.process_preserved_tab(ifc, *bidi_level),
655 TextRunItem::TextSegment(segment) => {
656 segment.layout_into_line_items(self, soft_wrap_policy, ifc)
657 },
658 }
659 soft_wrap_policy = SegmentStartSoftWrapPolicy::FollowLinebreaker;
660 }
661 }
662
663 fn process_preserved_tab(
664 &self,
665 ifc_layout: &mut InlineFormattingContextLayout,
666 bidi_level: Level,
667 ) {
668 let advance = ifc_layout.ifc.next_tab_stop_after_inline_advance(
669 &self.inline_styles().style.borrow(),
670 ifc_layout.potential_line_size().inline,
671 );
672 if advance.is_zero() {
673 return;
674 }
675
676 ifc_layout.update_unbreakable_segment_for_new_content(
677 &LineBlockSizes::zero(),
678 advance,
679 SegmentContentFlags::empty(),
680 );
681 ifc_layout.push_line_item_to_unbreakable_segment(LineItem::Tab {
682 inline_box_identifier: ifc_layout.current_inline_box_identifier(),
683 advance,
684 bidi_level,
685 });
686
687 if ifc_layout
688 .current_inline_container_state()
689 .style
690 .get_inherited_text()
691 .white_space_collapse ==
692 WhiteSpaceCollapse::BreakSpaces
693 {
694 ifc_layout.process_soft_wrap_opportunity();
695 }
696 }
697}
698
699fn is_cursive_script(script: Script) -> bool {
704 matches!(
705 script,
706 Script::Arabic |
707 Script::Hanifi_Rohingya |
708 Script::Mandaic |
709 Script::Mongolian |
710 Script::Nko |
711 Script::Phags_Pa |
712 Script::Syriac
713 )
714}
715
716fn character_cannot_change_font(character: char) -> bool {
720 if character.is_control() {
721 return true;
722 }
723 if character == '\u{00A0}' {
724 return true;
725 }
726 if is_bidi_control(character) {
727 return false;
728 }
729
730 matches!(
731 LineBreak::for_char(character),
732 LineBreak::CombiningMark |
733 LineBreak::Glue |
734 LineBreak::ZWSpace |
735 LineBreak::WordJoiner |
736 LineBreak::ZWJ
737 )
738}
739
740pub(super) fn get_font_for_first_font_for_style(
741 style: &ComputedValues,
742 font_context: &FontContext,
743) -> Option<FontRef> {
744 let font = font_context
745 .font_group(style.clone_font())
746 .first(font_context);
747 if font.is_none() {
748 warn!("Could not find font for style: {:?}", style.clone_font());
749 }
750 font
751}
752pub(crate) struct TwoCharsAtATimeIterator<InputIterator> {
753 iterator: InputIterator,
755 next_character: Option<char>,
757}
758
759impl<InputIterator> TwoCharsAtATimeIterator<InputIterator> {
760 fn new(iterator: InputIterator) -> Self {
761 Self {
762 iterator,
763 next_character: None,
764 }
765 }
766}
767
768impl<InputIterator> Iterator for TwoCharsAtATimeIterator<InputIterator>
769where
770 InputIterator: Iterator<Item = char>,
771{
772 type Item = (char, Option<char>);
773
774 fn next(&mut self) -> Option<Self::Item> {
775 if self.next_character.is_none() {
777 self.next_character = self.iterator.next();
778 }
779 let character = self.next_character?;
780 self.next_character = self.iterator.next();
781 Some((character, self.next_character))
782 }
783}
784
785pub(crate) fn script_is_specific(script: Script) -> bool {
786 script != Script::Common && script != Script::Inherited
787}