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, ShapedText, ShapedTextSlice, ShapingFlags, ShapingOptions,
14 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 smallvec::SmallVec;
24use style::Zero;
25use style::computed_values::font_kerning::T as FontKerning;
26use style::computed_values::font_variant_position::T as FontVariantPosition;
27use style::computed_values::text_rendering::T as TextRendering;
28use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
29use style::font_face::FontLanguageOverride;
30use style::properties::ComputedValues;
31use style::values::computed::{
32 FontFeatureSettings, FontVariantEastAsian, FontVariantLigatures, FontVariantNumeric,
33};
34use unicode_bidi::Level;
35use unicode_script::Script;
36
37use super::{InlineFormattingContextLayout, SharedInlineStyles};
38use crate::ArcRefCell;
39use crate::context::LayoutContext;
40use crate::dom::WeakLayoutBox;
41use crate::flow::inline::line::TextRunOffsets;
42use crate::flow::inline::shaping_queue::ShapingQueueEntry;
43use crate::flow::inline::{BidiLevels, LineBlockSizes, LineItem, SegmentContentFlags};
44use crate::fragment_tree::BaseFragmentInfo;
45
46#[derive(PartialEq)]
55enum SegmentStartSoftWrapPolicy {
56 Force,
57 FollowLinebreaker,
58}
59
60#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
62pub(crate) struct FontAndScriptInfo {
63 pub script: Script,
65 #[conditional_malloc_size_of]
67 pub font_info: Arc<FontInfo>,
68}
69
70impl FontAndScriptInfo {
71 pub(crate) fn simple_for_font(font: FontRef) -> Self {
75 Self {
76 script: Script::Common,
77 font_info: Arc::new(FontInfo::simple_for_font(font)),
78 }
79 }
80}
81
82#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
84pub(crate) struct FontInfo {
85 pub font: FontRef,
87 pub bidi_level: Level,
89 pub language: Language,
91 pub letter_spacing: Option<Au>,
96 pub word_spacing: Option<Au>,
98 pub text_rendering: TextRendering,
100 pub kerning: FontKerning,
102 pub ligatures: FontVariantLigatures,
104 pub numeric: FontVariantNumeric,
106 pub east_asian: FontVariantEastAsian,
108 pub feature_settings: FontFeatureSettings,
110 pub position: FontVariantPosition,
112 pub alternates: ResolvedFontVariantAlternates,
116}
117
118impl FontInfo {
119 fn simple_for_font(font: FontRef) -> Self {
120 Self {
121 font,
122 bidi_level: Level::ltr(),
123 language: Language::UND,
124 letter_spacing: None,
125 word_spacing: None,
126 text_rendering: TextRendering::Auto,
127 kerning: FontKerning::Auto,
128 ligatures: FontVariantLigatures::NORMAL,
129 numeric: FontVariantNumeric::NORMAL,
130 east_asian: FontVariantEastAsian::NORMAL,
131 feature_settings: FontFeatureSettings::normal(),
132 position: FontVariantPosition::Normal,
133 alternates: Default::default(),
134 }
135 }
136}
137
138impl From<&FontAndScriptInfo> for ShapingOptions {
139 fn from(info: &FontAndScriptInfo) -> Self {
140 let mut ligatures = info.font_info.ligatures;
141 let mut flags = ShapingFlags::empty();
142 if info.font_info.bidi_level.is_rtl() {
143 flags.insert(ShapingFlags::RTL_FLAG);
144 }
145
146 let letter_spacing = info
150 .font_info
151 .letter_spacing
152 .filter(|_| !is_cursive_script(info.script));
153 if letter_spacing.is_some() {
154 ligatures = FontVariantLigatures::NONE;
155 };
156 if info.font_info.text_rendering == TextRendering::Optimizespeed {
157 ligatures = FontVariantLigatures::NONE;
158 flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
159 }
160
161 if info.font_info.kerning == FontKerning::None {
163 flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG);
164 }
165
166 Self {
167 letter_spacing,
168 word_spacing: info.font_info.word_spacing,
169 script: info.script,
170 language: info.font_info.language,
171 ligatures,
172 numeric: info.font_info.numeric,
173 east_asian: info.font_info.east_asian,
174 feature_settings: info.font_info.feature_settings.clone(),
175 position: info.font_info.position,
176 flags,
177 alternates: info.font_info.alternates.clone(),
178 }
179 }
180}
181
182#[derive(Clone, Debug, MallocSizeOf)]
183pub(crate) struct TextRunSegment {
184 pub info: FontAndScriptInfo,
187
188 pub byte_range: Range<usize>,
190
191 pub character_range: Range<usize>,
193
194 pub break_at_start: bool,
197
198 #[conditional_malloc_size_of]
200 pub runs: Vec<Arc<ShapedTextSlice>>,
201
202 #[conditional_malloc_size_of]
205 pub shaped_text: Option<Arc<ShapedText>>,
206}
207
208impl TextRunSegment {
209 fn new(
210 info: FontAndScriptInfo,
211 byte_range: Range<usize>,
212 character_range: Range<usize>,
213 ) -> Self {
214 Self {
215 info,
216 byte_range,
217 character_range,
218 runs: Vec::new(),
219 break_at_start: false,
220 shaped_text: None,
221 }
222 }
223
224 fn is_compatible(
227 &self,
228 new_font: &Option<FontRef>,
229 new_script: Script,
230 new_bidi_level: Level,
231 ) -> bool {
232 if self.info.font_info.bidi_level != new_bidi_level {
233 return false;
234 }
235 if new_font
236 .as_ref()
237 .is_some_and(|new_font| !Arc::ptr_eq(&self.info.font_info.font, new_font))
238 {
239 return false;
240 }
241
242 !script_is_specific(self.info.script) ||
243 !script_is_specific(new_script) ||
244 self.info.script == new_script
245 }
246
247 fn update(&mut self, next_byte_index: usize, next_character_index: usize, new_script: Script) {
250 if !script_is_specific(self.info.script) && script_is_specific(new_script) {
251 self.info = FontAndScriptInfo {
252 script: new_script,
253 font_info: self.info.font_info.clone(),
254 };
255 }
256 self.character_range.end = next_character_index;
257 self.byte_range.end = next_byte_index;
258 }
259
260 fn layout_into_line_items(
261 &self,
262 text_run: &TextRun,
263 mut soft_wrap_policy: SegmentStartSoftWrapPolicy,
264 ifc: &mut InlineFormattingContextLayout,
265 ) {
266 if self.break_at_start && soft_wrap_policy == SegmentStartSoftWrapPolicy::FollowLinebreaker
267 {
268 soft_wrap_policy = SegmentStartSoftWrapPolicy::Force;
269 }
270
271 let mut character_range_start = self.character_range.start;
272 for (run_index, run) in self.runs.iter().enumerate() {
273 let new_character_range_end = character_range_start + run.character_count();
274 let offsets = ifc
275 .ifc
276 .shared_selection
277 .clone()
278 .or_else(|| {
279 if text_run.document_selection.is_empty() {
280 None
281 } else {
282 Some(Arc::new(AtomicRefCell::new(ScriptSelection {
283 range: TextByteRange::new(ByteIndex::zero(), ByteIndex::zero()),
284 character_range: text_run.document_selection.start.0..
285 text_run.document_selection.end.0,
286 enabled: true,
287 })))
288 }
289 })
290 .map(|shared_selection| TextRunOffsets {
291 shared_selection,
292 character_range: character_range_start..new_character_range_end,
293 });
294
295 if run_index != 0 || soft_wrap_policy == SegmentStartSoftWrapPolicy::Force {
298 ifc.process_soft_wrap_opportunity();
299 }
300
301 ifc.push_glyph_store_to_unbreakable_segment(run.clone(), text_run, &self.info, offsets);
302 character_range_start = new_character_range_end;
303 }
304 }
305
306 pub(crate) fn is_compatible_with_old_shaping_result(&self, old_segment: &Self) -> bool {
307 old_segment.info == self.info && self.byte_range == old_segment.byte_range
308 }
309}
310
311#[derive(Debug, MallocSizeOf)]
313pub(crate) enum TextRunItem {
314 LineBreak { character_index: usize },
316 Tab { bidi_level: Level },
318 TextSegment(Box<TextRunSegment>),
321}
322
323#[derive(Debug, MallocSizeOf)]
330pub(crate) struct TextRun {
331 pub base_fragment_info: BaseFragmentInfo,
334
335 pub parent_box: Option<WeakLayoutBox>,
338
339 pub inline_styles: SharedInlineStyles,
343
344 pub text_range: Range<usize>,
347
348 pub character_range: Range<usize>,
352
353 pub document_selection: Range<Utf32CodeUnits>,
355
356 pub items: Vec<TextRunItem>,
360}
361
362impl TextRun {
363 pub(crate) fn new(
364 base_fragment_info: BaseFragmentInfo,
365 inline_styles: SharedInlineStyles,
366 text_range: Range<usize>,
367 character_range: Range<usize>,
368 document_selection: Range<Utf32CodeUnits>,
369 old_text_run: Option<ArcRefCell<TextRun>>,
370 ) -> Self {
371 let items = old_text_run
373 .map(|old_text_run| std::mem::take(&mut old_text_run.borrow_mut().items))
374 .unwrap_or_default();
375 Self {
376 base_fragment_info,
377 parent_box: None,
378 inline_styles,
379 text_range,
380 character_range,
381 document_selection,
382 items,
383 }
384 }
385
386 pub(super) fn segment(
387 &mut self,
388 self_arc_ref_cell: ArcRefCell<TextRun>,
389 formatting_context_text: &str,
390 layout_context: &LayoutContext,
391 bidi_levels: &BidiLevels,
392 ) -> SmallVec<[ShapingQueueEntry; 1]> {
393 let parent_style = self.inline_styles.style.borrow().clone();
394 let items = self.segment_text_by_font(
395 layout_context,
396 formatting_context_text,
397 bidi_levels,
398 &parent_style,
399 );
400
401 let mut old_text_run_items = std::mem::replace(&mut self.items, items).into_iter();
404
405 self.items
406 .iter()
407 .enumerate()
408 .map(move |(index, text_run_item)| {
409 let old_text_run_item = old_text_run_items.next();
410 ShapingQueueEntry::new(
411 self_arc_ref_cell.clone(),
412 text_run_item,
413 index,
414 old_text_run_item,
415 )
416 })
417 .collect()
418 }
419
420 fn segment_text_by_font(
424 &mut self,
425 layout_context: &LayoutContext,
426 formatting_context_text: &str,
427 bidi_levels: &BidiLevels,
428 parent_style: &ServoArc<ComputedValues>,
429 ) -> Vec<TextRunItem> {
430 let font_style = parent_style.clone_font();
431 let language = font_style._x_lang.0.parse().unwrap_or(Language::UND);
432 let language_for_shaping = Some(font_style.font_language_override)
433 .filter(|language_override| *language_override != FontLanguageOverride::normal())
434 .and_then(|language_override| {
435 Language::try_from_bytes(&language_override.0.to_be_bytes()[..3]).ok()
444 })
445 .unwrap_or(language);
446 let font_size = font_style.font_size.computed_size().into();
447 let kerning = font_style.font_kerning;
448 let ligatures = font_style.font_variant_ligatures;
449 let numeric = font_style.font_variant_numeric;
450 let east_asian = font_style.font_variant_east_asian;
451 let feature_settings = font_style.font_feature_settings.clone();
452 let position = font_style.font_variant_position;
453 let alternates = font_style.font_variant_alternates.clone();
454
455 let font_group = layout_context.font_context.font_group(font_style);
456 let inherited_text_style = parent_style.get_inherited_text();
457 let word_spacing = Some(inherited_text_style.word_spacing.to_used_value(font_size));
458 let letter_spacing = inherited_text_style
459 .letter_spacing
460 .0
461 .to_used_value(font_size);
462 let letter_spacing = if !letter_spacing.is_zero() {
463 Some(letter_spacing)
464 } else {
465 None
466 };
467 let text_rendering = inherited_text_style.text_rendering;
468
469 let mut current: Option<TextRunSegment> = None;
470 let mut results = Vec::new();
471 let finish_current_segment =
472 |current: &mut Option<TextRunSegment>, results: &mut Vec<TextRunItem>| {
473 if let Some(current) = current.take() {
474 results.push(TextRunItem::TextSegment(Box::new(current)));
475 }
476 };
477
478 let text_run_text = &formatting_context_text[self.text_range.clone()];
479 let char_iterator = TwoCharsAtATimeIterator::new(text_run_text.chars());
480 let mut next_byte_index = self.text_range.start;
482 for (relative_character_index, (character, next_character)) in char_iterator.enumerate() {
483 let current_character_index = self.character_range.start + relative_character_index;
485
486 let current_byte_index = next_byte_index;
487 next_byte_index += character.len_utf8();
488
489 if character == '\n' {
490 finish_current_segment(&mut current, &mut results);
491 results.push(TextRunItem::LineBreak {
492 character_index: current_character_index,
493 });
494 continue;
495 }
496
497 if character == '\t' {
498 finish_current_segment(&mut current, &mut results);
499 results.push(TextRunItem::Tab {
500 bidi_level: bidi_levels.level(current_byte_index),
501 });
502 continue;
503 }
504
505 let (font, script, bidi_level) = if character_cannot_change_font(character) {
506 (None, Script::Common, bidi_levels.level(current_byte_index))
507 } else {
508 (
509 font_group.find_by_codepoint(
510 &layout_context.font_context,
511 character,
512 next_character,
513 language,
514 ),
515 Script::from(character),
516 bidi_levels.level(current_byte_index),
517 )
518 };
519
520 if let Some(current) = current.as_mut() &&
522 current.is_compatible(&font, script, bidi_level)
523 {
524 current.update(next_byte_index, current_character_index + 1, script);
525 continue;
526 }
527
528 let Some(font) = font.or_else(|| font_group.first(&layout_context.font_context)) else {
529 continue;
530 };
531
532 let alternates = layout_context
533 .font_context
534 .resolve_font_variant_alternate_identifiers_for(
535 &font,
536 &alternates,
537 layout_context.style_context.stylist,
538 );
539 let info = FontAndScriptInfo {
540 script,
541 font_info: Arc::new(FontInfo {
542 font,
543 bidi_level,
544 language: language_for_shaping,
545 word_spacing,
546 letter_spacing,
547 text_rendering,
548 kerning,
549 ligatures,
550 numeric,
551 east_asian,
552 feature_settings: feature_settings.clone(),
553 alternates,
554 position,
555 }),
556 };
557
558 finish_current_segment(&mut current, &mut results);
559 assert!(current.is_none());
560
561 current = Some(TextRunSegment::new(
562 info,
563 current_byte_index..next_byte_index,
564 current_character_index..current_character_index + 1,
565 ));
566 }
567
568 finish_current_segment(&mut current, &mut results);
569 results
570 }
571
572 pub(super) fn layout_into_line_items(&self, ifc: &mut InlineFormattingContextLayout) {
573 if self.text_range.is_empty() {
574 return;
575 }
576
577 let have_deferred_soft_wrap_opportunity =
581 mem::replace(&mut ifc.have_deferred_soft_wrap_opportunity, false);
582 let mut soft_wrap_policy = match have_deferred_soft_wrap_opportunity {
583 true => SegmentStartSoftWrapPolicy::Force,
584 false => SegmentStartSoftWrapPolicy::FollowLinebreaker,
585 };
586
587 for item in self.items.iter() {
588 ifc.possibly_flush_deferred_forced_line_break();
589
590 match item {
591 TextRunItem::LineBreak { character_index } => {
595 ifc.defer_forced_line_break_at_character_offset(*character_index);
596 },
597 TextRunItem::Tab { bidi_level } => self.process_preserved_tab(ifc, *bidi_level),
598 TextRunItem::TextSegment(segment) => {
599 segment.layout_into_line_items(self, soft_wrap_policy, ifc)
600 },
601 }
602 soft_wrap_policy = SegmentStartSoftWrapPolicy::FollowLinebreaker;
603 }
604 }
605
606 fn process_preserved_tab(
607 &self,
608 ifc_layout: &mut InlineFormattingContextLayout,
609 bidi_level: Level,
610 ) {
611 let advance = ifc_layout.ifc.next_tab_stop_after_inline_advance(
612 &self.inline_styles.style.borrow(),
613 ifc_layout.potential_line_size().inline,
614 );
615 if advance.is_zero() {
616 return;
617 }
618
619 ifc_layout.update_unbreakable_segment_for_new_content(
620 &LineBlockSizes::zero(),
621 advance,
622 SegmentContentFlags::empty(),
623 );
624 ifc_layout.push_line_item_to_unbreakable_segment(LineItem::Tab {
625 inline_box_identifier: ifc_layout.current_inline_box_identifier(),
626 advance,
627 bidi_level,
628 });
629
630 if ifc_layout
631 .current_inline_container_state()
632 .style
633 .get_inherited_text()
634 .white_space_collapse ==
635 WhiteSpaceCollapse::BreakSpaces
636 {
637 ifc_layout.process_soft_wrap_opportunity();
638 }
639 }
640}
641
642fn is_cursive_script(script: Script) -> bool {
647 matches!(
648 script,
649 Script::Arabic |
650 Script::Hanifi_Rohingya |
651 Script::Mandaic |
652 Script::Mongolian |
653 Script::Nko |
654 Script::Phags_Pa |
655 Script::Syriac
656 )
657}
658
659fn character_cannot_change_font(character: char) -> bool {
663 if character.is_control() {
664 return true;
665 }
666 if character == '\u{00A0}' {
667 return true;
668 }
669 if is_bidi_control(character) {
670 return false;
671 }
672
673 matches!(
674 icu_properties::maps::line_break().get(character),
675 LineBreak::CombiningMark |
676 LineBreak::Glue |
677 LineBreak::ZWSpace |
678 LineBreak::WordJoiner |
679 LineBreak::ZWJ
680 )
681}
682
683pub(super) fn get_font_for_first_font_for_style(
684 style: &ComputedValues,
685 font_context: &FontContext,
686) -> Option<FontRef> {
687 let font = font_context
688 .font_group(style.clone_font())
689 .first(font_context);
690 if font.is_none() {
691 warn!("Could not find font for style: {:?}", style.clone_font());
692 }
693 font
694}
695pub(crate) struct TwoCharsAtATimeIterator<InputIterator> {
696 iterator: InputIterator,
698 next_character: Option<char>,
700}
701
702impl<InputIterator> TwoCharsAtATimeIterator<InputIterator> {
703 fn new(iterator: InputIterator) -> Self {
704 Self {
705 iterator,
706 next_character: None,
707 }
708 }
709}
710
711impl<InputIterator> Iterator for TwoCharsAtATimeIterator<InputIterator>
712where
713 InputIterator: Iterator<Item = char>,
714{
715 type Item = (char, Option<char>);
716
717 fn next(&mut self) -> Option<Self::Item> {
718 if self.next_character.is_none() {
720 self.next_character = self.iterator.next();
721 }
722 let character = self.next_character?;
723 self.next_character = self.iterator.next();
724 Some((character, self.next_character))
725 }
726}
727
728pub(crate) fn script_is_specific(script: Script) -> bool {
729 script != Script::Common && script != Script::Inherited
730}