1use std::mem;
6use std::ops::Range;
7use std::sync::Arc;
8
9use app_units::Au;
10use fonts::{
11 FontContext, FontRef, ShapedTextSlice, ShapedTextSlicer, ShapingFlags, ShapingOptions,
12};
13use icu_locid::subtags::Language;
14use icu_properties::{self, LineBreak};
15use log::warn;
16use malloc_size_of_derive::MallocSizeOf;
17use servo_arc::Arc as ServoArc;
18use servo_base::text::is_bidi_control;
19use style::Zero;
20use style::computed_values::font_kerning::T as FontKerning;
21use style::computed_values::text_rendering::T as TextRendering;
22use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
23use style::computed_values::word_break::T as WordBreak;
24use style::properties::ComputedValues;
25use style::str::char_is_whitespace;
26use style::values::computed::OverflowWrap;
27use unicode_bidi::{BidiInfo, Level};
28use unicode_script::Script;
29
30use super::line_breaker::LineBreaker;
31use super::{InlineFormattingContextLayout, SharedInlineStyles};
32use crate::ArcRefCell;
33use crate::context::LayoutContext;
34use crate::dom::WeakLayoutBox;
35use crate::flow::inline::line::TextRunOffsets;
36use crate::flow::inline::{LineBlockSizes, LineItem, SegmentContentFlags};
37use crate::fragment_tree::BaseFragmentInfo;
38
39#[derive(PartialEq)]
48enum SegmentStartSoftWrapPolicy {
49 Force,
50 FollowLinebreaker,
51}
52
53#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
55pub(crate) struct FontAndScriptInfo {
56 pub font: FontRef,
58 pub script: Script,
60 pub bidi_level: Level,
62 pub language: Language,
64 pub letter_spacing: Option<Au>,
70 pub word_spacing: Option<Au>,
72 pub text_rendering: TextRendering,
74 pub kerning: FontKerning,
76}
77
78impl FontAndScriptInfo {
79 pub(crate) fn simple_for_font(font: FontRef) -> Self {
83 Self {
84 font,
85 script: Script::Common,
86 bidi_level: Level::ltr(),
87 language: Language::UND,
88 letter_spacing: None,
89 word_spacing: None,
90 text_rendering: TextRendering::Auto,
91 kerning: FontKerning::Auto,
92 }
93 }
94}
95
96impl From<&FontAndScriptInfo> for ShapingOptions {
97 fn from(info: &FontAndScriptInfo) -> Self {
98 let mut flags = ShapingFlags::empty();
99 if info.bidi_level.is_rtl() {
100 flags.insert(ShapingFlags::RTL_FLAG);
101 }
102
103 let letter_spacing = info
107 .letter_spacing
108 .filter(|_| !is_cursive_script(info.script));
109 if letter_spacing.is_some() {
110 flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG);
111 };
112 if info.text_rendering == TextRendering::Optimizespeed {
113 flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG);
114 flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
115 }
116
117 if info.kerning == FontKerning::None {
119 flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG);
120 }
121
122 Self {
123 letter_spacing,
124 word_spacing: info.word_spacing,
125 script: info.script,
126 language: info.language,
127 flags,
128 }
129 }
130}
131
132#[derive(Clone, Debug, MallocSizeOf)]
133pub(crate) struct TextRunSegment {
134 #[conditional_malloc_size_of]
137 pub info: Arc<FontAndScriptInfo>,
138
139 pub byte_range: Range<usize>,
141
142 pub character_range: Range<usize>,
144
145 pub break_at_start: bool,
148
149 #[conditional_malloc_size_of]
151 pub runs: Vec<Arc<ShapedTextSlice>>,
152}
153
154impl TextRunSegment {
155 fn new(
156 info: Arc<FontAndScriptInfo>,
157 byte_range: Range<usize>,
158 character_range: Range<usize>,
159 ) -> Self {
160 Self {
161 info,
162 byte_range,
163 character_range,
164 runs: Vec::new(),
165 break_at_start: false,
166 }
167 }
168
169 fn is_compatible(
172 &self,
173 new_font: &Option<FontRef>,
174 new_script: Script,
175 new_bidi_level: Level,
176 ) -> bool {
177 if self.info.bidi_level != new_bidi_level {
178 return false;
179 }
180 if new_font
181 .as_ref()
182 .is_some_and(|new_font| !Arc::ptr_eq(&self.info.font, new_font))
183 {
184 return false;
185 }
186
187 !script_is_specific(self.info.script) ||
188 !script_is_specific(new_script) ||
189 self.info.script == new_script
190 }
191
192 fn update(&mut self, next_byte_index: usize, next_character_index: usize, new_script: Script) {
195 if !script_is_specific(self.info.script) && script_is_specific(new_script) {
196 self.info = Arc::new(FontAndScriptInfo {
197 script: new_script,
198 ..(*self.info).clone()
199 });
200 }
201 self.character_range.end = next_character_index;
202 self.byte_range.end = next_byte_index;
203 }
204
205 fn layout_into_line_items(
206 &self,
207 text_run: &TextRun,
208 mut soft_wrap_policy: SegmentStartSoftWrapPolicy,
209 ifc: &mut InlineFormattingContextLayout,
210 ) {
211 if self.break_at_start && soft_wrap_policy == SegmentStartSoftWrapPolicy::FollowLinebreaker
212 {
213 soft_wrap_policy = SegmentStartSoftWrapPolicy::Force;
214 }
215
216 let mut character_range_start = self.character_range.start;
217 for (run_index, run) in self.runs.iter().enumerate() {
218 let new_character_range_end = character_range_start + run.character_count();
219 let offsets = ifc
220 .ifc
221 .shared_selection
222 .clone()
223 .map(|shared_selection| TextRunOffsets {
224 shared_selection,
225 character_range: character_range_start..new_character_range_end,
226 });
227
228 if run_index != 0 || soft_wrap_policy == SegmentStartSoftWrapPolicy::Force {
231 ifc.process_soft_wrap_opportunity();
232 }
233
234 ifc.push_glyph_store_to_unbreakable_segment(run.clone(), text_run, &self.info, offsets);
235 character_range_start = new_character_range_end;
236 }
237 }
238
239 fn shape_text(
243 &mut self,
244 parent_style: &ComputedValues,
245 formatting_context_text: &str,
246 linebreaker: &mut LineBreaker,
247 old_text_run_item: Option<TextRunItem>,
248 ) {
249 let range = self.byte_range.clone();
253 let linebreaks = linebreaker.advance_to_linebreaks_in_range(self.byte_range.clone());
254 let linebreak_iter = linebreaks.iter().chain(std::iter::once(&range.end));
255
256 let options: ShapingOptions = (&*self.info).into();
257 let shaped_text = old_text_run_item
258 .and_then(|old_text_run_item| {
259 let TextRunItem::TextSegment(old_text_segment) = old_text_run_item else {
260 return None;
261 };
262 if !self.is_compatible_with_old_shaping_result(&old_text_segment) {
263 return None;
264 }
265 Some(old_text_segment.runs.first()?.shaped_text())
266 })
267 .unwrap_or_else(|| {
268 self.info
269 .font
270 .shape_text(&formatting_context_text[range.clone()], &options)
271 });
272
273 let mut shaped_text_slicer = ShapedTextSlicer::new(shaped_text);
274
275 self.runs.clear();
276 self.runs.reserve(linebreaks.len());
277 self.break_at_start = false;
278
279 let text_style = parent_style.get_inherited_text().clone();
280 let can_break_anywhere = text_style.word_break == WordBreak::BreakAll ||
281 text_style.overflow_wrap == OverflowWrap::Anywhere ||
282 text_style.overflow_wrap == OverflowWrap::BreakWord;
283
284 let mut last_slice = self.byte_range.start..self.byte_range.start;
285 for break_index in linebreak_iter {
286 if *break_index == self.byte_range.start {
287 self.break_at_start = true;
288 continue;
289 }
290
291 let mut slice = last_slice.end..*break_index;
293 let word = &formatting_context_text[slice.clone()];
294
295 let mut whitespace = slice.end..slice.end;
297 let rev_char_indices = word.char_indices().rev().peekable();
298
299 let mut non_whitespace_slice_ends_with_whitespace = false;
300 let mut ends_with_whitespace = false;
301 if let Some((first_white_space_index, first_white_space_character)) = rev_char_indices
302 .take_while(|&(_, character)| char_is_whitespace(character))
303 .last()
304 {
305 ends_with_whitespace = true;
306 whitespace.start = slice.start + first_white_space_index;
307
308 if text_style.white_space_collapse == WhiteSpaceCollapse::BreakSpaces &&
314 !can_break_anywhere
315 {
316 whitespace.start += first_white_space_character.len_utf8();
317 non_whitespace_slice_ends_with_whitespace = true;
318 }
319
320 slice.end = whitespace.start;
321 }
322
323 if !ends_with_whitespace &&
326 *break_index != self.byte_range.end &&
327 text_style.word_break == WordBreak::KeepAll &&
328 !can_break_anywhere
329 {
330 continue;
331 }
332
333 last_slice = slice.start..*break_index;
335
336 if !slice.is_empty() {
338 let character_count = formatting_context_text[slice].chars().count();
339 self.runs.push(shaped_text_slicer.slice_for_character_count(
340 character_count,
341 false, non_whitespace_slice_ends_with_whitespace,
343 ));
344 }
345
346 if whitespace.is_empty() {
347 continue;
348 }
349
350 if text_style.white_space_collapse == WhiteSpaceCollapse::BreakSpaces {
353 for _ in formatting_context_text[whitespace].chars() {
354 self.runs.push(shaped_text_slicer.slice_for_character_count(
355 1, true, true, ));
358 }
359 continue;
360 }
361
362 let character_count = formatting_context_text[whitespace].chars().count();
363 self.runs.push(shaped_text_slicer.slice_for_character_count(
364 character_count,
365 true, true, ));
368 }
369 }
370
371 fn is_compatible_with_old_shaping_result(&self, old_segment: &Self) -> bool {
372 *old_segment.info == *self.info && self.byte_range == old_segment.byte_range
373 }
374}
375
376#[derive(Debug, MallocSizeOf)]
378pub(crate) enum TextRunItem {
379 LineBreak { character_index: usize },
381 Tab { bidi_level: Level },
383 TextSegment(Box<TextRunSegment>),
386}
387
388#[derive(Debug, MallocSizeOf)]
395pub(crate) struct TextRun {
396 pub base_fragment_info: BaseFragmentInfo,
399
400 pub parent_box: Option<WeakLayoutBox>,
403
404 pub inline_styles: SharedInlineStyles,
408
409 pub text_range: Range<usize>,
412
413 pub character_range: Range<usize>,
417
418 pub items: Vec<TextRunItem>,
422}
423
424impl TextRun {
425 pub(crate) fn new(
426 base_fragment_info: BaseFragmentInfo,
427 inline_styles: SharedInlineStyles,
428 text_range: Range<usize>,
429 character_range: Range<usize>,
430 old_text_run: Option<ArcRefCell<TextRun>>,
431 ) -> Self {
432 let items = old_text_run
434 .map(|old_text_run| std::mem::take(&mut old_text_run.borrow_mut().items))
435 .unwrap_or_default();
436 Self {
437 base_fragment_info,
438 parent_box: None,
439 inline_styles,
440 text_range,
441 character_range,
442 items,
443 }
444 }
445
446 pub(super) fn segment_and_shape(
447 &mut self,
448 formatting_context_text: &str,
449 layout_context: &LayoutContext,
450 linebreaker: &mut LineBreaker,
451 bidi_info: &BidiInfo,
452 ) {
453 let parent_style = self.inline_styles.style.borrow().clone();
454 let items = self.segment_text_by_font(
455 layout_context,
456 formatting_context_text,
457 bidi_info,
458 &parent_style,
459 );
460
461 let mut old_text_run_items = std::mem::replace(&mut self.items, items).into_iter();
464 for item in self.items.iter_mut() {
465 let old_text_run_item = old_text_run_items.next();
466 if let TextRunItem::TextSegment(text_segment) = item {
467 text_segment.shape_text(
468 &parent_style,
469 formatting_context_text,
470 linebreaker,
471 old_text_run_item,
472 );
473 }
474 }
475 }
476
477 fn segment_text_by_font(
481 &mut self,
482 layout_context: &LayoutContext,
483 formatting_context_text: &str,
484 bidi_info: &BidiInfo,
485 parent_style: &ServoArc<ComputedValues>,
486 ) -> Vec<TextRunItem> {
487 let font_style = parent_style.clone_font();
488 let language = font_style._x_lang.0.parse().unwrap_or(Language::UND);
489 let font_size = font_style.font_size.computed_size().into();
490 let kerning = font_style.font_kerning;
491 let font_group = layout_context.font_context.font_group(font_style);
492 let inherited_text_style = parent_style.get_inherited_text();
493 let word_spacing = Some(inherited_text_style.word_spacing.to_used_value(font_size));
494 let letter_spacing = inherited_text_style
495 .letter_spacing
496 .0
497 .to_used_value(font_size);
498 let letter_spacing = if !letter_spacing.is_zero() {
499 Some(letter_spacing)
500 } else {
501 None
502 };
503 let text_rendering = inherited_text_style.text_rendering;
504
505 let mut current: Option<TextRunSegment> = None;
506 let mut results = Vec::new();
507 let finish_current_segment =
508 |current: &mut Option<TextRunSegment>, results: &mut Vec<TextRunItem>| {
509 if let Some(current) = current.take() {
510 results.push(TextRunItem::TextSegment(Box::new(current)));
511 }
512 };
513
514 let text_run_text = &formatting_context_text[self.text_range.clone()];
515 let char_iterator = TwoCharsAtATimeIterator::new(text_run_text.chars());
516 let mut next_byte_index = self.text_range.start;
518 for (relative_character_index, (character, next_character)) in char_iterator.enumerate() {
519 let current_character_index = self.character_range.start + relative_character_index;
521
522 let current_byte_index = next_byte_index;
523 next_byte_index += character.len_utf8();
524
525 if character == '\n' {
526 finish_current_segment(&mut current, &mut results);
527 results.push(TextRunItem::LineBreak {
528 character_index: current_character_index,
529 });
530 continue;
531 }
532
533 if character == '\t' {
534 finish_current_segment(&mut current, &mut results);
535 results.push(TextRunItem::Tab {
536 bidi_level: bidi_info.levels[current_byte_index],
537 });
538 continue;
539 }
540
541 let (font, script, bidi_level) = if character_cannot_change_font(character) {
542 (None, Script::Common, bidi_info.levels[current_byte_index])
543 } else {
544 (
545 font_group.find_by_codepoint(
546 &layout_context.font_context,
547 character,
548 next_character,
549 language,
550 ),
551 Script::from(character),
552 bidi_info.levels[current_byte_index],
553 )
554 };
555
556 if let Some(current) = current.as_mut() {
558 if current.is_compatible(&font, script, bidi_level) {
559 current.update(next_byte_index, current_character_index + 1, script);
560 continue;
561 }
562 }
563
564 let Some(font) = font.or_else(|| font_group.first(&layout_context.font_context)) else {
565 continue;
566 };
567 let info = FontAndScriptInfo {
568 font,
569 script,
570 bidi_level,
571 language,
572 word_spacing,
573 letter_spacing,
574 text_rendering,
575 kerning,
576 };
577
578 finish_current_segment(&mut current, &mut results);
579 assert!(current.is_none());
580
581 current = Some(TextRunSegment::new(
582 Arc::new(info),
583 current_byte_index..next_byte_index,
584 current_character_index..current_character_index + 1,
585 ));
586 }
587
588 finish_current_segment(&mut current, &mut results);
589 results
590 }
591
592 pub(super) fn layout_into_line_items(&self, ifc: &mut InlineFormattingContextLayout) {
593 if self.text_range.is_empty() {
594 return;
595 }
596
597 let have_deferred_soft_wrap_opportunity =
601 mem::replace(&mut ifc.have_deferred_soft_wrap_opportunity, false);
602 let mut soft_wrap_policy = match have_deferred_soft_wrap_opportunity {
603 true => SegmentStartSoftWrapPolicy::Force,
604 false => SegmentStartSoftWrapPolicy::FollowLinebreaker,
605 };
606
607 for item in self.items.iter() {
608 ifc.possibly_flush_deferred_forced_line_break();
609
610 match item {
611 TextRunItem::LineBreak { character_index } => {
615 ifc.defer_forced_line_break_at_character_offset(*character_index);
616 },
617 TextRunItem::Tab { bidi_level } => self.process_preserved_tab(ifc, *bidi_level),
618 TextRunItem::TextSegment(segment) => {
619 segment.layout_into_line_items(self, soft_wrap_policy, ifc)
620 },
621 }
622 soft_wrap_policy = SegmentStartSoftWrapPolicy::FollowLinebreaker;
623 }
624 }
625
626 fn process_preserved_tab(
627 &self,
628 ifc_layout: &mut InlineFormattingContextLayout,
629 bidi_level: Level,
630 ) {
631 let advance = ifc_layout
632 .ifc
633 .next_tab_stop_after_inline_advance(ifc_layout.potential_line_size().inline);
634 if advance.is_zero() {
635 return;
636 }
637
638 ifc_layout.update_unbreakable_segment_for_new_content(
639 &LineBlockSizes::zero(),
640 advance,
641 SegmentContentFlags::empty(),
642 );
643 ifc_layout.push_line_item_to_unbreakable_segment(LineItem::Tab {
644 inline_box_identifier: ifc_layout.current_inline_box_identifier(),
645 advance,
646 bidi_level,
647 });
648
649 if ifc_layout
650 .current_inline_container_state()
651 .style
652 .get_inherited_text()
653 .white_space_collapse ==
654 WhiteSpaceCollapse::BreakSpaces
655 {
656 ifc_layout.process_soft_wrap_opportunity();
657 }
658 }
659}
660
661fn is_cursive_script(script: Script) -> bool {
666 matches!(
667 script,
668 Script::Arabic |
669 Script::Hanifi_Rohingya |
670 Script::Mandaic |
671 Script::Mongolian |
672 Script::Nko |
673 Script::Phags_Pa |
674 Script::Syriac
675 )
676}
677
678fn character_cannot_change_font(character: char) -> bool {
682 if character.is_control() {
683 return true;
684 }
685 if character == '\u{00A0}' {
686 return true;
687 }
688 if is_bidi_control(character) {
689 return false;
690 }
691
692 matches!(
693 icu_properties::maps::line_break().get(character),
694 LineBreak::CombiningMark |
695 LineBreak::Glue |
696 LineBreak::ZWSpace |
697 LineBreak::WordJoiner |
698 LineBreak::ZWJ
699 )
700}
701
702pub(super) fn get_font_for_first_font_for_style(
703 style: &ComputedValues,
704 font_context: &FontContext,
705) -> Option<FontRef> {
706 let font = font_context
707 .font_group(style.clone_font())
708 .first(font_context);
709 if font.is_none() {
710 warn!("Could not find font for style: {:?}", style.clone_font());
711 }
712 font
713}
714pub(crate) struct TwoCharsAtATimeIterator<InputIterator> {
715 iterator: InputIterator,
717 next_character: Option<char>,
719}
720
721impl<InputIterator> TwoCharsAtATimeIterator<InputIterator> {
722 fn new(iterator: InputIterator) -> Self {
723 Self {
724 iterator,
725 next_character: None,
726 }
727 }
728}
729
730impl<InputIterator> Iterator for TwoCharsAtATimeIterator<InputIterator>
731where
732 InputIterator: Iterator<Item = char>,
733{
734 type Item = (char, Option<char>);
735
736 fn next(&mut self) -> Option<Self::Item> {
737 if self.next_character.is_none() {
739 self.next_character = self.iterator.next();
740 }
741 let character = self.next_character?;
742 self.next_character = self.iterator.next();
743 Some((character, self.next_character))
744 }
745}
746
747fn script_is_specific(script: Script) -> bool {
748 script != Script::Common && script != Script::Inherited
749}