1use std::borrow::Cow;
6use std::cell::LazyCell;
7use std::ops::{ControlFlow, Range};
8
9use icu_properties::BidiClass;
10use layout_api::{LayoutNode, SharedSelection};
11use servo_base::text::{RangeAny, Utf32CodeUnits};
12use style::computed_values::direction::T as Direction;
13use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
14use style::dom::NodeInfo;
15use style::selector_parser::PseudoElement;
16use unicode_bidi::Level;
17use unicode_categories::UnicodeCategories;
18
19use super::text_run::TextRun;
20use super::{
21 InlineBox, InlineBoxIdentifier, InlineBoxes, InlineFormattingContext, InlineItem,
22 SharedInlineStyles,
23};
24use crate::cell::ArcRefCell;
25use crate::context::LayoutContext;
26use crate::dom::{LayoutBox, NodeExt};
27use crate::dom_traversal::{BoxTreeString, NodeAndStyleInfo};
28use crate::flow::BlockLevelBox;
29use crate::flow::float::FloatBox;
30use crate::flow::inline::text_transform::{OffsetMap, TextTransformationIterator};
31use crate::formatting_contexts::IndependentFormattingContext;
32use crate::positioned::AbsolutelyPositionedBox;
33use crate::style_ext::ComputedValuesExt;
34
35#[derive(Default)]
36pub(crate) struct InlineFormattingContextBuilder {
37 pub shared_inline_styles_stack: Vec<SharedInlineStyles>,
42
43 pub text_segments: Vec<String>,
46
47 current_text_offset: usize,
50
51 current_character_offset: usize,
55
56 pub shared_selection: Option<SharedSelection>,
59
60 last_inline_box_ended_with_collapsible_white_space: bool,
68
69 on_word_boundary: bool,
72
73 pub contains_floats: bool,
75
76 pub inline_items: Vec<InlineItem>,
80
81 pub inline_boxes: InlineBoxes,
83
84 inline_box_stack: Vec<InlineBoxIdentifier>,
93
94 pub is_empty: bool,
98
99 has_processed_first_letter: bool,
102
103 pub has_right_to_left_content: bool,
107
108 pub offset_map: OffsetMap,
111}
112
113impl InlineFormattingContextBuilder {
114 pub(crate) fn is_document_white_space(character: char) -> bool {
127 character.is_ascii_whitespace()
128 }
129
130 pub(crate) fn new(info: &NodeAndStyleInfo, context: &LayoutContext) -> Self {
131 let has_right_to_left_content = info.style.get_inherited_box().direction == Direction::Rtl;
132 Self {
133 on_word_boundary: true,
135 is_empty: true,
136 shared_inline_styles_stack: vec![SharedInlineStyles::from_info_and_context(
137 info, context,
138 )],
139 shared_selection: info.node.selection(),
140 has_right_to_left_content,
141 ..Default::default()
142 }
143 }
144
145 pub(crate) fn currently_processing_inline_box(&self) -> bool {
146 !self.inline_box_stack.is_empty()
147 }
148
149 fn push_control_character_string(&mut self, string_to_push: &str) {
150 self.text_segments.push(string_to_push.to_owned());
151 self.current_text_offset += string_to_push.len();
152
153 let new_characters = Utf32CodeUnits::length_of(string_to_push);
154 self.current_character_offset += new_characters.0;
155 self.offset_map
156 .push_range(Utf32CodeUnits(0), new_characters);
157 }
158
159 fn shared_inline_styles(&self) -> SharedInlineStyles {
160 self.shared_inline_styles_stack
161 .last()
162 .expect("Should always have at least one SharedInlineStyles")
163 .clone()
164 }
165
166 pub(crate) fn push_atomic(
167 &mut self,
168 independent_formatting_context_creator: impl FnOnce()
169 -> ArcRefCell<IndependentFormattingContext>,
170 old_layout_box: Option<LayoutBox>,
171 ) -> InlineItem {
172 let independent_formatting_context = old_layout_box
174 .and_then(|layout_box| match layout_box {
175 LayoutBox::InlineLevel(InlineItem::Atomic(atomic, ..)) => Some(atomic),
176 _ => None,
177 })
178 .unwrap_or_else(independent_formatting_context_creator);
179
180 let inline_level_box = InlineItem::Atomic(
181 independent_formatting_context,
182 self.current_text_offset,
183 Level::ltr(), );
185 self.inline_items.push(inline_level_box.clone());
186 self.is_empty = false;
187
188 self.push_control_character_string("\u{fffc}");
191
192 self.last_inline_box_ended_with_collapsible_white_space = false;
193 self.on_word_boundary = true;
194
195 self.has_processed_first_letter = true;
197
198 inline_level_box
199 }
200
201 pub(crate) fn push_absolutely_positioned_box(
202 &mut self,
203 absolutely_positioned_box_creator: impl FnOnce() -> ArcRefCell<AbsolutelyPositionedBox>,
204 old_layout_box: Option<LayoutBox>,
205 ) -> InlineItem {
206 let absolutely_positioned_box = old_layout_box
207 .and_then(|layout_box| match layout_box {
208 LayoutBox::InlineLevel(InlineItem::OutOfFlowAbsolutelyPositionedBox(
209 positioned_box,
210 ..,
211 )) => Some(positioned_box),
212 _ => None,
213 })
214 .unwrap_or_else(absolutely_positioned_box_creator);
215
216 let inline_level_box = InlineItem::OutOfFlowAbsolutelyPositionedBox(
218 absolutely_positioned_box,
219 self.current_text_offset,
220 );
221
222 self.inline_items.push(inline_level_box.clone());
223 self.is_empty = false;
224 inline_level_box
225 }
226
227 pub(crate) fn push_float_box(
228 &mut self,
229 float_box_creator: impl FnOnce() -> ArcRefCell<FloatBox>,
230 old_layout_box: Option<LayoutBox>,
231 ) -> InlineItem {
232 let inline_level_box = old_layout_box
233 .and_then(|layout_box| match layout_box {
234 LayoutBox::InlineLevel(inline_item) => Some(inline_item),
235 _ => None,
236 })
237 .unwrap_or_else(|| InlineItem::OutOfFlowFloatBox(float_box_creator()));
238
239 debug_assert!(
240 matches!(inline_level_box, InlineItem::OutOfFlowFloatBox(..),),
241 "Created float box with incompatible `old_layout_box`"
242 );
243
244 self.inline_items.push(inline_level_box.clone());
245 self.is_empty = false;
246 self.contains_floats = true;
247 inline_level_box
248 }
249
250 pub(crate) fn push_block_level_box(&mut self, block_level: ArcRefCell<BlockLevelBox>) {
251 assert!(self.currently_processing_inline_box());
252 self.contains_floats = self.contains_floats || block_level.borrow().contains_floats();
253 self.inline_items.push(InlineItem::BlockLevel(block_level));
254 }
255
256 pub(crate) fn start_inline_box(
257 &mut self,
258 inline_box_creator: impl FnOnce() -> ArcRefCell<InlineBox>,
259 old_layout_box: Option<LayoutBox>,
260 ) -> InlineItem {
261 let inline_box = old_layout_box
263 .and_then(|layout_box| match layout_box {
264 LayoutBox::InlineLevel(InlineItem::StartInlineBox(inline_box)) => Some(inline_box),
265 _ => None,
266 })
267 .unwrap_or_else(inline_box_creator);
268
269 let borrowed_inline_box = inline_box.borrow();
270
271 let style = &borrowed_inline_box.base.style;
272 self.push_control_character_string(style.bidi_control_chars().0);
273 self.has_right_to_left_content =
274 self.has_right_to_left_content || style.get_inherited_box().direction == Direction::Rtl;
275
276 self.shared_inline_styles_stack
277 .push(borrowed_inline_box.shared_inline_styles.clone());
278 std::mem::drop(borrowed_inline_box);
279
280 let identifier = self.inline_boxes.start_inline_box(inline_box.clone());
281 let inline_item = InlineItem::StartInlineBox(inline_box);
282 self.inline_items.push(inline_item.clone());
283 self.inline_box_stack.push(identifier);
284 self.is_empty = false;
285 inline_item
286 }
287
288 pub(crate) fn end_inline_box(&mut self) {
293 let identifier = self
294 .inline_box_stack
295 .pop()
296 .expect("Ended non-existent inline box");
297 let inline_level_box = self.inline_boxes.get(&identifier);
298
299 self.shared_inline_styles_stack.pop();
300 self.inline_items
301 .push(InlineItem::EndInlineBox(inline_level_box.clone()));
302 self.inline_boxes.end_inline_box(identifier);
303 let bidi_control_chars = inline_level_box.borrow().base.style.bidi_control_chars();
304 self.push_control_character_string(bidi_control_chars.1);
305 }
306
307 pub(crate) fn push_text_with_possible_first_letter<'dom>(
315 &mut self,
316 text: BoxTreeString<'dom>,
317 info: &NodeAndStyleInfo<'dom>,
318 container_info: &NodeAndStyleInfo<'dom>,
319 layout_context: &LayoutContext,
320 ) -> bool {
321 let document_selection = info.node.document_selection_in_text_node();
322 if self.has_processed_first_letter || !container_info.pseudo_element_chain().is_empty() {
323 self.push_text(text, info, document_selection);
324 return false;
325 }
326
327 let Some(first_letter_info) =
328 container_info.with_pseudo_element(layout_context, PseudoElement::FirstLetter)
329 else {
330 self.push_text(text, info, document_selection);
331 return false;
332 };
333
334 let first_letter_range = first_letter_range(&text[..]);
335 if first_letter_range.is_empty() {
336 return false;
337 }
338
339 let first_letter_range_u32 = LazyCell::new(|| {
341 Utf32CodeUnits::length_of(&text[..first_letter_range.start])..
342 Utf32CodeUnits::length_of(&text[..first_letter_range.end])
343 });
344 if first_letter_range.start != 0 {
345 let leading_whitespace_range = 0..first_letter_range.start;
346 let leading_whitespace_selection_range =
347 document_selection.and_then(|document_selection| {
348 let leading_whitespace_range_u32 = RangeAny {
349 start: None,
350 end: Some(first_letter_range_u32.start),
351 };
352 document_selection.intersect(leading_whitespace_range_u32)
353 });
354
355 self.push_text(
356 Cow::Borrowed(&text[leading_whitespace_range]).into(),
357 info,
358 leading_whitespace_selection_range,
359 );
360 }
361
362 let box_slot = first_letter_info.node.box_slot();
364 let inline_item = self.start_inline_box(
365 || ArcRefCell::new(InlineBox::new(&first_letter_info, layout_context)),
366 None,
367 );
368 box_slot.set(LayoutBox::InlineLevel(inline_item));
369
370 let first_letter_text = Cow::Borrowed(&text[first_letter_range.clone()]);
371 let first_letter_selection_range = document_selection.and_then(|document_selection| {
372 document_selection
373 .intersect((*first_letter_range_u32).clone().into())
374 .map(|range| range.map(|offset| offset - first_letter_range_u32.start))
375 });
376 self.push_text(
377 first_letter_text.into(),
378 &first_letter_info,
379 first_letter_selection_range,
380 );
381 self.end_inline_box();
382 self.has_processed_first_letter = true;
383
384 let remaining_selection_range = document_selection.and_then(|document_selection| {
386 let remaining_text_range_u32 = RangeAny {
387 start: Some(first_letter_range_u32.end),
388 end: document_selection.end,
389 };
390 document_selection
391 .intersect(remaining_text_range_u32)
392 .map(|range| range.map(|offset| offset - first_letter_range_u32.end))
393 });
394 self.push_text(
395 Cow::Borrowed(&text[first_letter_range.end..]).into(),
396 info,
397 remaining_selection_range,
398 );
399
400 true
401 }
402
403 pub(crate) fn push_text<'dom>(
404 &mut self,
405 text: BoxTreeString<'dom>,
406 info: &NodeAndStyleInfo<'dom>,
407 document_selection: Option<RangeAny<Utf32CodeUnits>>,
408 ) {
409 let bidi_class_map = icu_properties::maps::bidi_class();
410 let white_space_collapse = info.style.clone_white_space_collapse();
411 let original_size_before = self.offset_map.total_original_size();
412 let mut character_count = 0;
413 let mut new_text = String::with_capacity(text.len());
414 for iteration in TextTransformationIterator::new(
415 &text,
416 &info.style,
417 self.last_inline_box_ended_with_collapsible_white_space,
418 self.on_word_boundary,
419 ) {
420 self.offset_map.push_iteration(&iteration);
421 for &character in iteration.characters() {
422 character_count += 1;
423
424 self.has_right_to_left_content = self.has_right_to_left_content ||
428 matches!(
429 bidi_class_map.get(character),
430 BidiClass::RightToLeft |
431 BidiClass::ArabicLetter |
432 BidiClass::RightToLeftEmbedding |
433 BidiClass::RightToLeftIsolate |
434 BidiClass::RightToLeftOverride
435 );
436
437 self.is_empty = self.is_empty &&
438 match white_space_collapse {
439 WhiteSpaceCollapse::Collapse => Self::is_document_white_space(character),
440 WhiteSpaceCollapse::PreserveBreaks => {
441 Self::is_document_white_space(character) && character != '\n'
442 },
443 WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces => false,
444 };
445
446 new_text.push(character)
447 }
448 }
449
450 if new_text.is_empty() {
451 return;
452 }
453
454 let document_selection = document_selection.map(|document_selection| {
455 let start = document_selection
456 .start
457 .map(|offset| self.offset_map.map(offset))
458 .unwrap_or(Utf32CodeUnits(0));
460 let end = document_selection
461 .end
462 .map(|offset| self.offset_map.map(offset))
463 .unwrap_or(Utf32CodeUnits(character_count));
465 original_size_before + start..original_size_before + end
466 });
467
468 if let Some(last_character) = new_text.chars().next_back() {
469 self.on_word_boundary = last_character.is_whitespace();
470 self.last_inline_box_ended_with_collapsible_white_space =
471 self.on_word_boundary && white_space_collapse != WhiteSpaceCollapse::Preserve;
472 }
473
474 let new_utf8_range = self.current_text_offset..self.current_text_offset + new_text.len();
475 self.current_text_offset = new_utf8_range.end;
476
477 let new_character_range =
478 self.current_character_offset..self.current_character_offset + character_count;
479 self.current_character_offset = new_character_range.end;
480
481 self.text_segments.push(new_text);
482
483 if self
484 .try_to_push_text_range_to_previous_text_run(
485 info,
486 &document_selection,
487 &new_utf8_range,
488 &new_character_range,
489 )
490 .is_break()
491 {
492 return;
493 }
494
495 let current_inline_styles = self.shared_inline_styles();
496 let box_slot = info.node.is_text_node().then(|| info.node.box_slot());
497 let text_run = ArcRefCell::new(TextRun::new(
498 info.into(),
499 current_inline_styles,
500 new_utf8_range,
501 new_character_range,
502 document_selection.unwrap_or_default(),
503 box_slot
504 .as_ref()
505 .and_then(|box_slot| box_slot.take_layout_box_as_text_run()),
506 ));
507 self.inline_items
508 .push(InlineItem::TextRun(text_run.clone()));
509
510 if let Some(box_slot) = box_slot {
511 box_slot.set(LayoutBox::Text(text_run));
512 }
513 }
514
515 fn try_to_push_text_range_to_previous_text_run(
516 &mut self,
517 info: &NodeAndStyleInfo,
518 new_text_selection: &Option<Range<Utf32CodeUnits>>,
519 new_range: &Range<usize>,
520 new_character_range: &Range<usize>,
521 ) -> ControlFlow<()> {
522 let Some(InlineItem::TextRun(text_run_arc)) = self.inline_items.last() else {
524 return ControlFlow::Continue(());
525 };
526
527 if !text_run_arc
529 .borrow()
530 .inline_styles
531 .ptr_eq(&self.shared_inline_styles())
532 {
533 return ControlFlow::Continue(());
534 }
535
536 let mut text_run = text_run_arc.borrow_mut();
537 if let Some(next_text_selection) = new_text_selection {
538 if !text_run.document_selection.is_empty() {
539 if text_run.document_selection.end.0 == next_text_selection.start.0 {
542 text_run.document_selection.end = next_text_selection.end;
543 } else {
544 return ControlFlow::Continue(());
545 }
546 } else {
547 text_run.document_selection = next_text_selection.start..next_text_selection.end;
549 }
550 }
551
552 text_run.text_range.end = new_range.end;
553 text_run.character_range.end = new_character_range.end;
554
555 let box_slot = info.node.box_slot();
560 let old_text_run = box_slot.take_layout_box_as_text_run();
561 if old_text_run.is_none() {
562 text_run.items.clear();
563 }
564
565 box_slot.set(LayoutBox::Text(text_run_arc.clone()));
566 ControlFlow::Break(())
567 }
568
569 pub(crate) fn enter_display_contents(&mut self, shared_inline_styles: SharedInlineStyles) {
570 self.shared_inline_styles_stack.push(shared_inline_styles);
571 }
572
573 pub(crate) fn leave_display_contents(&mut self) {
574 self.shared_inline_styles_stack.pop();
575 }
576
577 pub(crate) fn finish(
579 self,
580 layout_context: &LayoutContext,
581 has_first_formatted_line: bool,
582 is_single_line_text_input: bool,
583 default_bidi_level: Level,
584 ) -> Option<InlineFormattingContext> {
585 if self.is_empty {
586 return None;
587 }
588
589 assert!(self.inline_box_stack.is_empty());
590 assert_eq!(
591 self.offset_map.total_final_size().0,
592 self.current_character_offset
593 );
594
595 Some(InlineFormattingContext::new_with_builder(
596 self,
597 layout_context,
598 has_first_formatted_line,
599 is_single_line_text_input,
600 default_bidi_level,
601 ))
602 }
603}
604
605fn first_letter_range(text: &str) -> Range<usize> {
615 enum State {
616 Start,
618 PrecedingPunctuation,
620 Lns,
622 TrailingPunctuation,
625 }
626
627 let mut start = 0;
628 let mut state = State::Start;
629 for (index, character) in text.char_indices() {
630 match &mut state {
631 State::Start => {
632 if character.is_letter() || character.is_number() || character.is_symbol() {
633 start = index;
634 state = State::Lns;
635 } else if character.is_punctuation() {
636 start = index;
637 state = State::PrecedingPunctuation
638 }
639 },
640 State::PrecedingPunctuation => {
641 if character.is_letter() || character.is_number() || character.is_symbol() {
642 state = State::Lns;
643 } else if !character.is_separator_space() && !character.is_punctuation() {
644 return 0..0;
645 }
646 },
647 State::Lns => {
648 if character.is_punctuation() &&
651 !character.is_punctuation_open() &&
652 !character.is_punctuation_dash()
653 {
654 state = State::TrailingPunctuation;
655 } else {
656 return start..index;
657 }
658 },
659 State::TrailingPunctuation => {
660 if character.is_punctuation() &&
663 !character.is_punctuation_open() &&
664 !character.is_punctuation_dash()
665 {
666 continue;
667 } else {
668 return start..index;
669 }
670 },
671 }
672 }
673
674 match state {
675 State::Start | State::PrecedingPunctuation => 0..0,
676 State::Lns | State::TrailingPunctuation => start..text.len(),
677 }
678}
679
680#[cfg(test)]
681mod tests {
682 use super::*;
683
684 fn assert_first_letter_eq(text: &str, expected: &str) {
685 let range = first_letter_range(text);
686 assert_eq!(&text[range], expected);
687 }
688
689 #[test]
690 fn test_first_letter_range() {
691 assert_first_letter_eq("", "");
693 assert_first_letter_eq(" ", "");
694
695 assert_first_letter_eq("(", "");
697 assert_first_letter_eq(" (", "");
698 assert_first_letter_eq("( ", "");
699 assert_first_letter_eq("()", "");
700
701 assert_first_letter_eq("\u{0903}", "");
703
704 assert_first_letter_eq("A", "A");
706 assert_first_letter_eq(" A", "A");
707 assert_first_letter_eq("A ", "A");
708 assert_first_letter_eq(" A ", "A");
709
710 assert_first_letter_eq("App", "A");
712 assert_first_letter_eq(" App", "A");
713 assert_first_letter_eq("App ", "A");
714
715 assert_first_letter_eq(r#""A"#, r#""A"#);
717 assert_first_letter_eq(r#" "A"#, r#""A"#);
718 assert_first_letter_eq(r#""A "#, r#""A"#);
719 assert_first_letter_eq(r#"" A"#, r#"" A"#);
720 assert_first_letter_eq(r#" "A "#, r#""A"#);
721 assert_first_letter_eq(r#"("A"#, r#"("A"#);
722 assert_first_letter_eq(r#" ("A"#, r#"("A"#);
723 assert_first_letter_eq(r#"( "A"#, r#"( "A"#);
724 assert_first_letter_eq(r#"[ ( "A"#, r#"[ ( "A"#);
725
726 assert_first_letter_eq(r#"A""#, r#"A""#);
729 assert_first_letter_eq(r#"A" "#, r#"A""#);
730 assert_first_letter_eq(r#"A)]"#, r#"A)]"#);
731 assert_first_letter_eq(r#"A" )]"#, r#"A""#);
732 assert_first_letter_eq(r#"A)] >"#, r#"A)]"#);
733
734 assert_first_letter_eq(r#" ("A" )]"#, r#"("A""#);
736 assert_first_letter_eq(r#" ("A")] >"#, r#"("A")]"#);
737
738 assert_first_letter_eq("一", "一");
740 assert_first_letter_eq(" 一 ", "一");
741 assert_first_letter_eq("一二三", "一");
742 assert_first_letter_eq(" 一二三 ", "一");
743 assert_first_letter_eq("(一二三)", "(一");
744 assert_first_letter_eq(" (一二三) ", "(一");
745 assert_first_letter_eq("((一", "((一");
746 assert_first_letter_eq(" ( (一", "( (一");
747 assert_first_letter_eq("一)", "一)");
748 assert_first_letter_eq("一))", "一))");
749 assert_first_letter_eq("一) )", "一)");
750 }
751}