1use std::mem;
6use std::ops::Range;
7use std::sync::Arc;
8
9use app_units::Au;
10use fonts::font_feature_values::ResolvedFontVariantAlternates;
11use fonts::{FontContext, FontRef, ShapedText, ShapedTextSlice, ShapingFlags, ShapingOptions};
12use icu_locid::subtags::Language;
13use icu_properties::{self, LineBreak};
14use layout_api::SharedSelection;
15use log::warn;
16use malloc_size_of_derive::MallocSizeOf;
17use servo_arc::Arc as ServoArc;
18use servo_base::text::{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::UND,
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 #[conditional_malloc_size_of]
337 pub selection: Option<SharedSelection>,
338 pub offset_map: ArcRefCell<OffsetMap>,
341}
342
343impl SharedTextRunData {
344 pub(crate) fn map_dom_range_to_transformed_range(
348 &self,
349 range: Range<Utf32CodeUnits>,
350 ) -> Range<Utf32CodeUnits> {
351 let offset_map = self.offset_map.borrow();
352 let offset_in_ifc_text = Utf32CodeUnits(self.character_range_in_ifc_text.start);
353 offset_map.map(range.start + self.original_offset) - offset_in_ifc_text..
354 offset_map.map(range.end + self.original_offset) - offset_in_ifc_text
355 }
356
357 pub(crate) fn map_transformed_offset_to_dom_offset(
360 &self,
361 offset: Utf32CodeUnits,
362 ) -> Utf32CodeUnits {
363 let offset_map = self.offset_map.borrow();
364 let offset_in_ifc_text = Utf32CodeUnits(self.character_range_in_ifc_text.start);
365 offset_map.reverse_map(offset + offset_in_ifc_text) - self.original_offset
366 }
367}
368
369#[derive(Debug, MallocSizeOf)]
376pub(crate) struct TextRun {
377 pub base_fragment_info: BaseFragmentInfo,
380
381 #[conditional_malloc_size_of]
384 pub run_data: Arc<SharedTextRunData>,
385
386 pub parent_box: Option<WeakLayoutBox>,
389
390 pub text_range: Range<usize>,
393
394 pub items: Vec<TextRunItem>,
398}
399
400impl TextRun {
401 pub(crate) fn new(
402 base_fragment_info: BaseFragmentInfo,
403 run_data: Arc<SharedTextRunData>,
404 text_range: Range<usize>,
405 old_text_run: Option<ArcRefCell<TextRun>>,
406 ) -> Self {
407 let items = old_text_run
409 .map(|old_text_run| std::mem::take(&mut old_text_run.borrow_mut().items))
410 .unwrap_or_default();
411 Self {
412 base_fragment_info,
413 run_data,
414 parent_box: None,
415 text_range,
416 items,
417 }
418 }
419
420 pub(super) fn inline_styles(&self) -> &SharedInlineStyles {
421 &self.run_data.inline_styles
422 }
423
424 pub(super) fn segment(
425 &mut self,
426 self_arc_ref_cell: ArcRefCell<TextRun>,
427 formatting_context_text: &str,
428 layout_context: &LayoutContext,
429 bidi_levels: &BidiLevels,
430 ) -> SmallVec<[ShapingQueueEntry; 1]> {
431 let parent_style = self.inline_styles().style.borrow().clone();
432 let items = self.segment_text_by_font(
433 layout_context,
434 formatting_context_text,
435 bidi_levels,
436 &parent_style,
437 );
438
439 let mut old_text_run_items = std::mem::replace(&mut self.items, items).into_iter();
442
443 self.items
444 .iter()
445 .enumerate()
446 .map(move |(index, text_run_item)| {
447 let old_text_run_item = old_text_run_items.next();
448 ShapingQueueEntry::new(
449 self_arc_ref_cell.clone(),
450 text_run_item,
451 index,
452 old_text_run_item,
453 )
454 })
455 .collect()
456 }
457
458 fn segment_text_by_font(
462 &mut self,
463 layout_context: &LayoutContext,
464 formatting_context_text: &str,
465 bidi_levels: &BidiLevels,
466 parent_style: &ServoArc<ComputedValues>,
467 ) -> Vec<TextRunItem> {
468 let font_style = parent_style.clone_font();
469 let language = font_style._x_lang.0.parse().unwrap_or(Language::UND);
470 let language_for_shaping = Some(font_style.font_language_override)
471 .filter(|language_override| *language_override != FontLanguageOverride::normal())
472 .and_then(|language_override| {
473 Language::try_from_bytes(&language_override.0.to_be_bytes()[..3]).ok()
482 })
483 .unwrap_or(language);
484 let font_size = font_style.font_size.computed_size().into();
485 let kerning = font_style.font_kerning;
486 let ligatures = font_style.font_variant_ligatures;
487 let numeric = font_style.font_variant_numeric;
488 let east_asian = font_style.font_variant_east_asian;
489 let feature_settings = font_style.font_feature_settings.clone();
490 let position = font_style.font_variant_position;
491 let alternates = font_style.font_variant_alternates.clone();
492
493 let font_group = layout_context.font_context.font_group(font_style);
494 let inherited_text_style = parent_style.get_inherited_text();
495 let word_spacing = Some(inherited_text_style.word_spacing.to_used_value(font_size));
496 let letter_spacing = inherited_text_style
497 .letter_spacing
498 .0
499 .to_used_value(font_size);
500 let letter_spacing = if !letter_spacing.is_zero() {
501 Some(letter_spacing)
502 } else {
503 None
504 };
505 let text_rendering = inherited_text_style.text_rendering;
506
507 let mut current: Option<TextRunSegment> = None;
508 let mut results = Vec::new();
509 let finish_current_segment =
510 |current: &mut Option<TextRunSegment>, results: &mut Vec<TextRunItem>| {
511 if let Some(current) = current.take() {
512 results.push(TextRunItem::TextSegment(Box::new(current)));
513 }
514 };
515
516 let text_run_text = &formatting_context_text[self.text_range.clone()];
517 let char_iterator = TwoCharsAtATimeIterator::new(text_run_text.chars());
518 let mut next_byte_index = self.text_range.start;
520 for (relative_character_index, (character, next_character)) in char_iterator.enumerate() {
521 let current_character_index =
523 self.run_data.character_range_in_ifc_text.start + relative_character_index;
524
525 let current_byte_index = next_byte_index;
526 next_byte_index += character.len_utf8();
527
528 if character == '\n' {
529 finish_current_segment(&mut current, &mut results);
530 results.push(TextRunItem::LineBreak(
531 self.run_data.selection.is_some().then(|| CaretPlaceholder {
532 run_data: self.run_data.clone(),
533 base_fragment_info: self.base_fragment_info,
534 character_index: relative_character_index + 1,
537 }),
538 ));
539 continue;
540 }
541
542 if character == '\t' {
543 finish_current_segment(&mut current, &mut results);
544 results.push(TextRunItem::Tab {
545 bidi_level: bidi_levels.level(current_byte_index),
546 });
547 continue;
548 }
549
550 let (font, script, bidi_level) = if character_cannot_change_font(character) {
551 (None, Script::Common, bidi_levels.level(current_byte_index))
552 } else {
553 (
554 font_group.find_by_codepoint(
555 &layout_context.font_context,
556 character,
557 next_character,
558 language,
559 ),
560 Script::from(character),
561 bidi_levels.level(current_byte_index),
562 )
563 };
564
565 if let Some(current) = current.as_mut() &&
567 current.is_compatible(&font, script, bidi_level)
568 {
569 current.update(next_byte_index, current_character_index + 1, script);
570 continue;
571 }
572
573 let Some(font) = font.or_else(|| font_group.first(&layout_context.font_context)) else {
574 continue;
575 };
576
577 let alternates = layout_context
578 .font_context
579 .resolve_font_variant_alternate_identifiers_for(
580 &font,
581 &alternates,
582 layout_context.style_context.stylist,
583 );
584 let info = FontAndScriptInfo {
585 script,
586 font_info: Arc::new(FontInfo {
587 font,
588 bidi_level,
589 language: language_for_shaping,
590 word_spacing,
591 letter_spacing,
592 text_rendering,
593 kerning,
594 ligatures,
595 numeric,
596 east_asian,
597 feature_settings: feature_settings.clone(),
598 alternates,
599 position,
600 }),
601 };
602
603 finish_current_segment(&mut current, &mut results);
604 assert!(current.is_none());
605
606 current = Some(TextRunSegment::new(
607 info,
608 current_byte_index..next_byte_index,
609 current_character_index..current_character_index + 1,
610 ));
611 }
612
613 finish_current_segment(&mut current, &mut results);
614 results
615 }
616
617 pub(super) fn layout_into_line_items(&self, ifc: &mut InlineFormattingContextLayout) {
618 if self.text_range.is_empty() {
619 return;
620 }
621
622 let have_deferred_soft_wrap_opportunity =
626 mem::replace(&mut ifc.have_deferred_soft_wrap_opportunity, false);
627 let mut soft_wrap_policy = match have_deferred_soft_wrap_opportunity {
628 true => SegmentStartSoftWrapPolicy::Force,
629 false => SegmentStartSoftWrapPolicy::FollowLinebreaker,
630 };
631
632 for item in self.items.iter() {
633 ifc.possibly_flush_deferred_forced_line_break();
634
635 match item {
636 TextRunItem::LineBreak(caret_placeholder) => {
640 ifc.defer_forced_line_break_at_character_offset(caret_placeholder);
641 },
642 TextRunItem::Tab { bidi_level } => self.process_preserved_tab(ifc, *bidi_level),
643 TextRunItem::TextSegment(segment) => {
644 segment.layout_into_line_items(self, soft_wrap_policy, ifc)
645 },
646 }
647 soft_wrap_policy = SegmentStartSoftWrapPolicy::FollowLinebreaker;
648 }
649 }
650
651 fn process_preserved_tab(
652 &self,
653 ifc_layout: &mut InlineFormattingContextLayout,
654 bidi_level: Level,
655 ) {
656 let advance = ifc_layout.ifc.next_tab_stop_after_inline_advance(
657 &self.inline_styles().style.borrow(),
658 ifc_layout.potential_line_size().inline,
659 );
660 if advance.is_zero() {
661 return;
662 }
663
664 ifc_layout.update_unbreakable_segment_for_new_content(
665 &LineBlockSizes::zero(),
666 advance,
667 SegmentContentFlags::empty(),
668 );
669 ifc_layout.push_line_item_to_unbreakable_segment(LineItem::Tab {
670 inline_box_identifier: ifc_layout.current_inline_box_identifier(),
671 advance,
672 bidi_level,
673 });
674
675 if ifc_layout
676 .current_inline_container_state()
677 .style
678 .get_inherited_text()
679 .white_space_collapse ==
680 WhiteSpaceCollapse::BreakSpaces
681 {
682 ifc_layout.process_soft_wrap_opportunity();
683 }
684 }
685}
686
687fn is_cursive_script(script: Script) -> bool {
692 matches!(
693 script,
694 Script::Arabic |
695 Script::Hanifi_Rohingya |
696 Script::Mandaic |
697 Script::Mongolian |
698 Script::Nko |
699 Script::Phags_Pa |
700 Script::Syriac
701 )
702}
703
704fn character_cannot_change_font(character: char) -> bool {
708 if character.is_control() {
709 return true;
710 }
711 if character == '\u{00A0}' {
712 return true;
713 }
714 if is_bidi_control(character) {
715 return false;
716 }
717
718 matches!(
719 icu_properties::maps::line_break().get(character),
720 LineBreak::CombiningMark |
721 LineBreak::Glue |
722 LineBreak::ZWSpace |
723 LineBreak::WordJoiner |
724 LineBreak::ZWJ
725 )
726}
727
728pub(super) fn get_font_for_first_font_for_style(
729 style: &ComputedValues,
730 font_context: &FontContext,
731) -> Option<FontRef> {
732 let font = font_context
733 .font_group(style.clone_font())
734 .first(font_context);
735 if font.is_none() {
736 warn!("Could not find font for style: {:?}", style.clone_font());
737 }
738 font
739}
740pub(crate) struct TwoCharsAtATimeIterator<InputIterator> {
741 iterator: InputIterator,
743 next_character: Option<char>,
745}
746
747impl<InputIterator> TwoCharsAtATimeIterator<InputIterator> {
748 fn new(iterator: InputIterator) -> Self {
749 Self {
750 iterator,
751 next_character: None,
752 }
753 }
754}
755
756impl<InputIterator> Iterator for TwoCharsAtATimeIterator<InputIterator>
757where
758 InputIterator: Iterator<Item = char>,
759{
760 type Item = (char, Option<char>);
761
762 fn next(&mut self) -> Option<Self::Item> {
763 if self.next_character.is_none() {
765 self.next_character = self.iterator.next();
766 }
767 let character = self.next_character?;
768 self.next_character = self.iterator.next();
769 Some((character, self.next_character))
770 }
771}
772
773pub(crate) fn script_is_specific(script: Script) -> bool {
774 script != Script::Common && script != Script::Inherited
775}