1use std::mem;
6use std::ops::Range;
7use std::sync::Arc;
8
9use app_units::Au;
10use fonts::{
11 FontContext, FontRef, GlyphStore, LAST_RESORT_GLYPH_ADVANCE, ShapingFlags, ShapingOptions,
12};
13use icu_locid::subtags::Language;
14use log::warn;
15use malloc_size_of_derive::MallocSizeOf;
16use servo_arc::Arc as ServoArc;
17use servo_base::text::is_bidi_control;
18use style::computed_values::text_rendering::T as TextRendering;
19use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
20use style::computed_values::word_break::T as WordBreak;
21use style::properties::ComputedValues;
22use style::str::char_is_whitespace;
23use style::values::computed::OverflowWrap;
24use unicode_bidi::{BidiInfo, Level};
25use unicode_script::Script;
26use xi_unicode::linebreak_property;
27
28use super::line_breaker::LineBreaker;
29use super::{InlineFormattingContextLayout, SharedInlineStyles};
30use crate::context::LayoutContext;
31use crate::dom::WeakLayoutBox;
32use crate::flow::inline::line::TextRunOffsets;
33use crate::fragment_tree::BaseFragmentInfo;
34
35pub(crate) const XI_LINE_BREAKING_CLASS_CM: u8 = 9;
38pub(crate) const XI_LINE_BREAKING_CLASS_GL: u8 = 12;
39pub(crate) const XI_LINE_BREAKING_CLASS_ZW: u8 = 28;
40pub(crate) const XI_LINE_BREAKING_CLASS_WJ: u8 = 30;
41pub(crate) const XI_LINE_BREAKING_CLASS_ZWJ: u8 = 42;
42
43#[derive(PartialEq)]
52enum SegmentStartSoftWrapPolicy {
53 Force,
54 FollowLinebreaker,
55}
56
57#[derive(Clone, Debug, MallocSizeOf)]
60pub(crate) struct FontAndScriptInfo {
61 pub font: FontRef,
62 pub script: Script,
63 pub bidi_level: Level,
64}
65
66#[derive(Debug, MallocSizeOf)]
67pub(crate) struct TextRunSegment {
68 #[conditional_malloc_size_of]
71 pub info: Arc<FontAndScriptInfo>,
72
73 pub range: Range<usize>,
75
76 pub character_range: Range<usize>,
78
79 pub break_at_start: bool,
82
83 #[conditional_malloc_size_of]
85 pub runs: Vec<Arc<GlyphStore>>,
86}
87
88impl TextRunSegment {
89 fn new(
90 info: Arc<FontAndScriptInfo>,
91 start_offset: usize,
92 start_character_offset: usize,
93 ) -> Self {
94 Self {
95 info,
96 range: start_offset..start_offset,
97 character_range: start_character_offset..start_character_offset,
98 runs: Vec::new(),
99 break_at_start: false,
100 }
101 }
102
103 fn update_if_compatible(&mut self, info: &FontAndScriptInfo) -> bool {
107 if self.info.bidi_level != info.bidi_level || !Arc::ptr_eq(&self.info.font, &info.font) {
108 return false;
109 }
110
111 fn is_specific(script: Script) -> bool {
112 script != Script::Common && script != Script::Inherited
113 }
114 if !is_specific(self.info.script) && is_specific(info.script) {
115 self.info = Arc::new(info.clone());
116 }
117 info.script == self.info.script || !is_specific(info.script)
118 }
119
120 fn layout_into_line_items(
121 &self,
122 text_run: &TextRun,
123 mut soft_wrap_policy: SegmentStartSoftWrapPolicy,
124 ifc: &mut InlineFormattingContextLayout,
125 ) {
126 if self.break_at_start && soft_wrap_policy == SegmentStartSoftWrapPolicy::FollowLinebreaker
127 {
128 soft_wrap_policy = SegmentStartSoftWrapPolicy::Force;
129 }
130
131 let mut character_range_start = self.character_range.start;
132 for (run_index, run) in self.runs.iter().enumerate() {
133 ifc.possibly_flush_deferred_forced_line_break();
134
135 let new_character_range_end = character_range_start + run.character_count();
136 let offsets = ifc
137 .ifc
138 .shared_selection
139 .clone()
140 .map(|shared_selection| TextRunOffsets {
141 shared_selection,
142 character_range: character_range_start..new_character_range_end,
143 });
144
145 if run.is_single_preserved_newline() {
149 ifc.possibly_push_empty_text_run_to_unbreakable_segment(
150 text_run, &self.info, offsets,
151 );
152 character_range_start = new_character_range_end;
153 ifc.defer_forced_line_break();
154 continue;
155 }
156
157 if run_index != 0 || soft_wrap_policy == SegmentStartSoftWrapPolicy::Force {
160 ifc.process_soft_wrap_opportunity();
161 }
162
163 ifc.push_glyph_store_to_unbreakable_segment(run.clone(), text_run, &self.info, offsets);
164 character_range_start = new_character_range_end;
165 }
166 }
167
168 fn shape_and_push_range(
169 &mut self,
170 range: &Range<usize>,
171 formatting_context_text: &str,
172 options: &ShapingOptions,
173 ) {
174 self.runs.push(
175 self.info
176 .font
177 .shape_text(&formatting_context_text[range.clone()], options),
178 );
179 }
180
181 fn shape_text(
185 &mut self,
186 parent_style: &ComputedValues,
187 formatting_context_text: &str,
188 linebreaker: &mut LineBreaker,
189 shaping_options: &ShapingOptions,
190 ) {
191 let range = self.range.clone();
195 let linebreaks = linebreaker.advance_to_linebreaks_in_range(self.range.clone());
196 let linebreak_iter = linebreaks.iter().chain(std::iter::once(&range.end));
197
198 self.runs.clear();
199 self.runs.reserve(linebreaks.len());
200 self.break_at_start = false;
201
202 let text_style = parent_style.get_inherited_text().clone();
203 let can_break_anywhere = text_style.word_break == WordBreak::BreakAll ||
204 text_style.overflow_wrap == OverflowWrap::Anywhere ||
205 text_style.overflow_wrap == OverflowWrap::BreakWord;
206
207 let mut last_slice = self.range.start..self.range.start;
208 for break_index in linebreak_iter {
209 if *break_index == self.range.start {
210 self.break_at_start = true;
211 continue;
212 }
213
214 let mut options = *shaping_options;
215
216 let mut slice = last_slice.end..*break_index;
218 let word = &formatting_context_text[slice.clone()];
219
220 let mut whitespace = slice.end..slice.end;
222 let mut rev_char_indices = word.char_indices().rev().peekable();
223
224 let mut ends_with_whitespace = false;
225 let ends_with_newline = rev_char_indices
226 .peek()
227 .is_some_and(|&(_, character)| character == '\n');
228 if let Some((first_white_space_index, first_white_space_character)) = rev_char_indices
229 .take_while(|&(_, character)| char_is_whitespace(character))
230 .last()
231 {
232 ends_with_whitespace = true;
233 whitespace.start = slice.start + first_white_space_index;
234
235 if text_style.white_space_collapse == WhiteSpaceCollapse::BreakSpaces &&
242 first_white_space_character != '\n' &&
243 !can_break_anywhere
244 {
245 whitespace.start += first_white_space_character.len_utf8();
246 options
247 .flags
248 .insert(ShapingFlags::ENDS_WITH_WHITESPACE_SHAPING_FLAG);
249 }
250
251 slice.end = whitespace.start;
252 }
253
254 if !ends_with_whitespace &&
257 *break_index != self.range.end &&
258 text_style.word_break == WordBreak::KeepAll &&
259 !can_break_anywhere
260 {
261 continue;
262 }
263
264 last_slice = slice.start..*break_index;
266
267 if !slice.is_empty() {
269 self.shape_and_push_range(&slice, formatting_context_text, &options);
270 }
271
272 if whitespace.is_empty() {
273 continue;
274 }
275
276 options.flags.insert(
277 ShapingFlags::IS_WHITESPACE_SHAPING_FLAG |
278 ShapingFlags::ENDS_WITH_WHITESPACE_SHAPING_FLAG,
279 );
280
281 if text_style.white_space_collapse == WhiteSpaceCollapse::BreakSpaces {
284 let start_index = whitespace.start;
285 for (index, character) in formatting_context_text[whitespace].char_indices() {
286 let index = start_index + index;
287 self.shape_and_push_range(
288 &(index..index + character.len_utf8()),
289 formatting_context_text,
290 &options,
291 );
292 }
293 continue;
294 }
295
296 if ends_with_newline && whitespace.len() > 1 {
302 self.shape_and_push_range(
303 &(whitespace.start..whitespace.end - 1),
304 formatting_context_text,
305 &options,
306 );
307 self.shape_and_push_range(
308 &(whitespace.end - 1..whitespace.end),
309 formatting_context_text,
310 &options,
311 );
312 } else {
313 self.shape_and_push_range(&whitespace, formatting_context_text, &options);
314 }
315 }
316 }
317}
318
319#[derive(Debug, MallocSizeOf)]
326pub(crate) struct TextRun {
327 pub base_fragment_info: BaseFragmentInfo,
330
331 pub parent_box: Option<WeakLayoutBox>,
334
335 pub inline_styles: SharedInlineStyles,
339
340 pub text_range: Range<usize>,
343
344 pub character_range: Range<usize>,
348
349 pub shaped_text: Vec<TextRunSegment>,
352}
353
354impl TextRun {
355 pub(crate) fn new(
356 base_fragment_info: BaseFragmentInfo,
357 inline_styles: SharedInlineStyles,
358 text_range: Range<usize>,
359 character_range: Range<usize>,
360 ) -> Self {
361 Self {
362 base_fragment_info,
363 parent_box: None,
364 inline_styles,
365 text_range,
366 character_range,
367 shaped_text: Vec::new(),
368 }
369 }
370
371 pub(super) fn segment_and_shape(
372 &mut self,
373 formatting_context_text: &str,
374 layout_context: &LayoutContext,
375 linebreaker: &mut LineBreaker,
376 bidi_info: &BidiInfo,
377 ) {
378 let parent_style = self.inline_styles.style.borrow().clone();
379 let inherited_text_style = parent_style.get_inherited_text().clone();
380 let letter_spacing = inherited_text_style
381 .letter_spacing
382 .0
383 .resolve(parent_style.clone_font().font_size.computed_size());
384 let letter_spacing = if letter_spacing.px() != 0. {
385 Some(app_units::Au::from(letter_spacing))
386 } else {
387 None
388 };
389 let language = parent_style
390 .get_font()
391 ._x_lang
392 .0
393 .parse()
394 .unwrap_or(Language::UND);
395
396 let mut flags = ShapingFlags::empty();
397 if inherited_text_style.text_rendering == TextRendering::Optimizespeed {
398 flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG);
399 flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
400 }
401
402 let specified_word_spacing = &inherited_text_style.word_spacing;
403 let style_word_spacing: Option<Au> = specified_word_spacing.to_length().map(|l| l.into());
404
405 let segments = self
406 .segment_text_by_font(
407 layout_context,
408 formatting_context_text,
409 bidi_info,
410 &parent_style,
411 )
412 .into_iter()
413 .map(|mut segment| {
414 let word_spacing = style_word_spacing.unwrap_or_else(|| {
415 let space_width = segment
416 .info
417 .font
418 .glyph_index(' ')
419 .map(|glyph_id| segment.info.font.glyph_h_advance(glyph_id))
420 .unwrap_or(LAST_RESORT_GLYPH_ADVANCE);
421 specified_word_spacing.to_used_value(Au::from_f64_px(space_width))
422 });
423
424 let mut flags = flags;
425 if segment.info.bidi_level.is_rtl() {
426 flags.insert(ShapingFlags::RTL_FLAG);
427 }
428
429 let letter_spacing = if is_cursive_script(segment.info.script) {
433 None
434 } else {
435 letter_spacing
436 };
437 if letter_spacing.is_some() {
438 flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG);
439 };
440
441 let shaping_options = ShapingOptions {
442 letter_spacing,
443 word_spacing,
444 script: segment.info.script,
445 language,
446 flags,
447 };
448
449 segment.shape_text(
450 &parent_style,
451 formatting_context_text,
452 linebreaker,
453 &shaping_options,
454 );
455
456 segment
457 })
458 .collect();
459
460 let _ = std::mem::replace(&mut self.shaped_text, segments);
461 }
462
463 fn segment_text_by_font(
467 &mut self,
468 layout_context: &LayoutContext,
469 formatting_context_text: &str,
470 bidi_info: &BidiInfo,
471 parent_style: &ServoArc<ComputedValues>,
472 ) -> Vec<TextRunSegment> {
473 let font_group = layout_context
474 .font_context
475 .font_group(parent_style.clone_font());
476 let mut current: Option<TextRunSegment> = None;
477 let mut results = Vec::new();
478
479 let lang = parent_style.get_font()._x_lang.clone();
480 let text_run_text = &formatting_context_text[self.text_range.clone()];
481 let char_iterator = TwoCharsAtATimeIterator::new(text_run_text.chars());
482
483 let mut next_character_index = self.character_range.start;
485 let mut next_byte_index = self.text_range.start;
487
488 for (character, next_character) in char_iterator {
489 let current_character_index = next_character_index;
490 next_character_index += 1;
491
492 let current_byte_index = next_byte_index;
493 next_byte_index += character.len_utf8();
494
495 if char_does_not_change_font(character) {
496 continue;
497 }
498
499 let Some(font) = font_group.find_by_codepoint(
500 &layout_context.font_context,
501 character,
502 next_character,
503 lang.clone(),
504 ) else {
505 continue;
506 };
507
508 let info = FontAndScriptInfo {
509 font,
510 script: Script::from(character),
511 bidi_level: bidi_info.levels[current_byte_index],
512 };
513
514 if let Some(current) = current.as_mut() {
516 if current.update_if_compatible(&info) {
517 continue;
518 }
519 }
520
521 let (start_byte_index, start_character_index) = match current {
526 Some(_) => (current_byte_index, current_character_index),
527 None => (self.text_range.start, self.character_range.start),
528 };
529 let new = TextRunSegment::new(Arc::new(info), start_byte_index, start_character_index);
530 if let Some(mut finished) = current.replace(new) {
531 finished.range.end = current_byte_index;
533 finished.character_range.end = current_character_index;
534 results.push(finished);
535 }
536 }
537
538 if current.is_none() {
541 current = font_group.first(&layout_context.font_context).map(|font| {
542 TextRunSegment::new(
543 Arc::new(FontAndScriptInfo {
544 font,
545 script: Script::Common,
546 bidi_level: Level::ltr(),
547 }),
548 self.text_range.start,
549 self.character_range.start,
550 )
551 })
552 }
553
554 if let Some(mut last_segment) = current.take() {
556 last_segment.range.end = self.text_range.end;
557 last_segment.character_range.end = self.character_range.end;
558 results.push(last_segment);
559 }
560
561 results
562 }
563
564 pub(super) fn layout_into_line_items(&self, ifc: &mut InlineFormattingContextLayout) {
565 if self.text_range.is_empty() {
566 return;
567 }
568
569 let have_deferred_soft_wrap_opportunity =
573 mem::replace(&mut ifc.have_deferred_soft_wrap_opportunity, false);
574 let mut soft_wrap_policy = match have_deferred_soft_wrap_opportunity {
575 true => SegmentStartSoftWrapPolicy::Force,
576 false => SegmentStartSoftWrapPolicy::FollowLinebreaker,
577 };
578
579 for segment in self.shaped_text.iter() {
580 segment.layout_into_line_items(self, soft_wrap_policy, ifc);
581 soft_wrap_policy = SegmentStartSoftWrapPolicy::FollowLinebreaker;
582 }
583 }
584}
585
586fn is_cursive_script(script: Script) -> bool {
591 matches!(
592 script,
593 Script::Arabic |
594 Script::Hanifi_Rohingya |
595 Script::Mandaic |
596 Script::Mongolian |
597 Script::Nko |
598 Script::Phags_Pa |
599 Script::Syriac
600 )
601}
602
603fn char_does_not_change_font(character: char) -> bool {
607 if character.is_control() {
608 return true;
609 }
610 if character == '\u{00A0}' {
611 return true;
612 }
613 if is_bidi_control(character) {
614 return false;
615 }
616
617 let class = linebreak_property(character);
618 class == XI_LINE_BREAKING_CLASS_CM ||
619 class == XI_LINE_BREAKING_CLASS_GL ||
620 class == XI_LINE_BREAKING_CLASS_ZW ||
621 class == XI_LINE_BREAKING_CLASS_WJ ||
622 class == XI_LINE_BREAKING_CLASS_ZWJ
623}
624
625pub(super) fn get_font_for_first_font_for_style(
626 style: &ComputedValues,
627 font_context: &FontContext,
628) -> Option<FontRef> {
629 let font = font_context
630 .font_group(style.clone_font())
631 .first(font_context);
632 if font.is_none() {
633 warn!("Could not find font for style: {:?}", style.clone_font());
634 }
635 font
636}
637pub(crate) struct TwoCharsAtATimeIterator<InputIterator> {
638 iterator: InputIterator,
640 next_character: Option<char>,
642}
643
644impl<InputIterator> TwoCharsAtATimeIterator<InputIterator> {
645 fn new(iterator: InputIterator) -> Self {
646 Self {
647 iterator,
648 next_character: None,
649 }
650 }
651}
652
653impl<InputIterator> Iterator for TwoCharsAtATimeIterator<InputIterator>
654where
655 InputIterator: Iterator<Item = char>,
656{
657 type Item = (char, Option<char>);
658
659 fn next(&mut self) -> Option<Self::Item> {
660 if self.next_character.is_none() {
662 self.next_character = self.iterator.next();
663 }
664 let character = self.next_character?;
665 self.next_character = self.iterator.next();
666 Some((character, self.next_character))
667 }
668}