1use std::borrow::Cow;
6use std::cell::LazyCell;
7use std::ops::Range;
8use std::sync::{Arc, OnceLock};
9
10use atomic_refcell::AtomicRefCell;
11use icu_properties::CodePointMapData;
12use icu_properties::props::BidiClass;
13use layout_api::LayoutNode;
14use servo_base::text::{AssumeUnder4GB, RangeAny, Utf8CodeUnits, Utf32CodeUnits};
15use style::computed_values::direction::T as Direction;
16use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
17use style::dom::NodeInfo;
18use style::selector_parser::PseudoElement;
19use unicode_bidi::Level;
20use unicode_categories::UnicodeCategories;
21
22use super::text_run::TextRun;
23use super::{
24 InlineBox, InlineBoxIdentifier, InlineBoxes, InlineFormattingContext, InlineItem,
25 SharedInlineStyles,
26};
27use crate::cell::ArcRefCell;
28use crate::context::LayoutContext;
29use crate::dom::{LayoutBox, NodeExt};
30use crate::dom_traversal::{BoxTreeString, NodeAndStyleInfo};
31use crate::flow::BlockLevelBox;
32use crate::flow::float::FloatBox;
33use crate::flow::inline::text_run::SharedTextRunData;
34use crate::flow::inline::text_transform::{OffsetMap, TextTransformationIterator};
35use crate::formatting_contexts::IndependentFormattingContext;
36use crate::positioned::AbsolutelyPositionedBox;
37use crate::style_ext::ComputedValuesExt;
38
39#[derive(Default)]
40pub(crate) struct InlineFormattingContextBuilder {
41 pub shared_inline_styles_stack: Vec<SharedInlineStyles>,
46
47 pub text_segments: Vec<String>,
50
51 pub text_content_slot: Arc<OnceLock<String>>,
54
55 current_text_offset: Utf8CodeUnits,
58
59 current_character_offset: Utf32CodeUnits,
63
64 last_inline_box_ended_with_collapsible_white_space: bool,
72
73 on_word_boundary: bool,
76
77 pub contains_floats: bool,
79
80 pub inline_items: Vec<InlineItem>,
84
85 pub inline_boxes: InlineBoxes,
87
88 inline_box_stack: Vec<InlineBoxIdentifier>,
97
98 pub is_empty: bool,
102
103 should_process_first_letter: bool,
108
109 pub has_right_to_left_content: bool,
113
114 pub offset_map: ArcRefCell<OffsetMap>,
117}
118
119impl InlineFormattingContextBuilder {
120 pub(crate) fn is_document_white_space(character: char) -> bool {
133 character.is_ascii_whitespace()
134 }
135
136 pub(crate) fn new(
137 info: &NodeAndStyleInfo,
138 context: &LayoutContext,
139 should_process_first_letter: bool,
140 ) -> Self {
141 let has_right_to_left_content = info.style.get_inherited_box().direction == Direction::Rtl;
142 Self {
143 on_word_boundary: true,
145 is_empty: true,
146 shared_inline_styles_stack: vec![SharedInlineStyles::from_info_and_context(
147 info, context,
148 )],
149 has_right_to_left_content,
150 should_process_first_letter,
151 ..Default::default()
152 }
153 }
154
155 pub(crate) fn currently_processing_inline_box(&self) -> bool {
156 !self.inline_box_stack.is_empty()
157 }
158
159 fn push_control_character_string(&mut self, string_to_push: &str) {
160 self.text_segments.push(string_to_push.to_owned());
161 self.current_text_offset += Utf8CodeUnits::length_of(AssumeUnder4GB, string_to_push);
163
164 let new_characters = Utf32CodeUnits::length_of(AssumeUnder4GB, string_to_push);
165 self.current_character_offset += new_characters;
166 self.offset_map
167 .borrow_mut()
168 .push_range(new_characters, new_characters);
169 }
170
171 fn shared_inline_styles(&self) -> SharedInlineStyles {
172 self.shared_inline_styles_stack
173 .last()
174 .expect("Should always have at least one SharedInlineStyles")
175 .clone()
176 }
177
178 pub(crate) fn push_atomic(
179 &mut self,
180 independent_formatting_context_creator: impl FnOnce()
181 -> ArcRefCell<IndependentFormattingContext>,
182 old_layout_box: Option<LayoutBox>,
183 ) -> InlineItem {
184 let independent_formatting_context = old_layout_box
186 .and_then(|layout_box| match layout_box {
187 LayoutBox::InlineLevel(InlineItem::Atomic(atomic, ..)) => Some(atomic),
188 _ => None,
189 })
190 .unwrap_or_else(independent_formatting_context_creator);
191
192 let inline_level_box = InlineItem::Atomic(
193 independent_formatting_context,
194 self.current_text_offset,
195 Level::ltr(), );
197 self.inline_items.push(inline_level_box.clone());
198 self.is_empty = false;
199
200 self.push_control_character_string("\u{fffc}");
203
204 self.last_inline_box_ended_with_collapsible_white_space = false;
205 self.on_word_boundary = true;
206
207 self.should_process_first_letter = false;
209
210 inline_level_box
211 }
212
213 pub(crate) fn push_absolutely_positioned_box(
214 &mut self,
215 absolutely_positioned_box_creator: impl FnOnce() -> ArcRefCell<AbsolutelyPositionedBox>,
216 old_layout_box: Option<LayoutBox>,
217 ) -> InlineItem {
218 let absolutely_positioned_box = old_layout_box
219 .and_then(|layout_box| match layout_box {
220 LayoutBox::InlineLevel(InlineItem::OutOfFlowAbsolutelyPositionedBox(
221 positioned_box,
222 ..,
223 )) => Some(positioned_box),
224 _ => None,
225 })
226 .unwrap_or_else(absolutely_positioned_box_creator);
227
228 let inline_level_box = InlineItem::OutOfFlowAbsolutelyPositionedBox(
230 absolutely_positioned_box,
231 self.current_text_offset,
232 );
233
234 self.inline_items.push(inline_level_box.clone());
235 self.is_empty = false;
236 inline_level_box
237 }
238
239 pub(crate) fn push_float_box(
240 &mut self,
241 float_box_creator: impl FnOnce() -> ArcRefCell<FloatBox>,
242 old_layout_box: Option<LayoutBox>,
243 ) -> InlineItem {
244 let inline_level_box = old_layout_box
245 .and_then(|layout_box| match layout_box {
246 LayoutBox::InlineLevel(inline_item) => Some(inline_item),
247 _ => None,
248 })
249 .unwrap_or_else(|| InlineItem::OutOfFlowFloatBox(float_box_creator()));
250
251 debug_assert!(
252 matches!(inline_level_box, InlineItem::OutOfFlowFloatBox(..),),
253 "Created float box with incompatible `old_layout_box`"
254 );
255
256 self.inline_items.push(inline_level_box.clone());
257 self.is_empty = false;
258 self.contains_floats = true;
259 inline_level_box
260 }
261
262 pub(crate) fn push_block_level_box(&mut self, block_level: ArcRefCell<BlockLevelBox>) {
263 assert!(self.currently_processing_inline_box());
264 self.contains_floats = self.contains_floats || block_level.borrow().contains_floats();
265 self.inline_items.push(InlineItem::BlockLevel(block_level));
266 }
267
268 pub(crate) fn start_inline_box(
269 &mut self,
270 inline_box_creator: impl FnOnce() -> ArcRefCell<InlineBox>,
271 old_layout_box: Option<LayoutBox>,
272 ) -> InlineItem {
273 let inline_box = old_layout_box
275 .and_then(|layout_box| match layout_box {
276 LayoutBox::InlineLevel(InlineItem::StartInlineBox(inline_box)) => Some(inline_box),
277 _ => None,
278 })
279 .unwrap_or_else(inline_box_creator);
280
281 let borrowed_inline_box = inline_box.borrow();
282
283 let style = &borrowed_inline_box.base.style;
284 self.push_control_character_string(style.bidi_control_chars().0);
285 self.has_right_to_left_content =
286 self.has_right_to_left_content || style.get_inherited_box().direction == Direction::Rtl;
287
288 self.shared_inline_styles_stack
289 .push(borrowed_inline_box.shared_inline_styles.clone());
290 std::mem::drop(borrowed_inline_box);
291
292 let identifier = self.inline_boxes.start_inline_box(inline_box.clone());
293 let inline_item = InlineItem::StartInlineBox(inline_box);
294 self.inline_items.push(inline_item.clone());
295 self.inline_box_stack.push(identifier);
296 self.is_empty = false;
297 inline_item
298 }
299
300 pub(crate) fn end_inline_box(&mut self) {
305 let identifier = self
306 .inline_box_stack
307 .pop()
308 .expect("Ended non-existent inline box");
309 let inline_level_box = self.inline_boxes.get(&identifier);
310
311 self.shared_inline_styles_stack.pop();
312 self.inline_items
313 .push(InlineItem::EndInlineBox(inline_level_box.clone()));
314 self.inline_boxes.end_inline_box(identifier);
315 let bidi_control_chars = inline_level_box.borrow().base.style.bidi_control_chars();
316 self.push_control_character_string(bidi_control_chars.1);
317 }
318
319 pub(crate) fn push_text_with_possible_first_letter<'dom>(
327 &mut self,
328 text: BoxTreeString<'dom>,
329 info: &NodeAndStyleInfo<'dom>,
330 container_info: &NodeAndStyleInfo<'dom>,
331 layout_context: &LayoutContext,
332 ) -> bool {
333 let selection = info.node.text_node_selection();
334 if !self.should_process_first_letter || !container_info.pseudo_element_chain().is_empty() {
335 self.push_text(text, info, selection);
336 return false;
337 }
338
339 let Some(first_letter_info) =
340 container_info.with_pseudo_element(layout_context, PseudoElement::FirstLetter)
341 else {
342 self.push_text(text, info, selection);
343 return false;
344 };
345
346 let first_letter_range = first_letter_range(&text[..]);
347 if first_letter_range.is_empty() {
348 return false;
349 }
350
351 let first_letter_range_u32 = LazyCell::new(|| {
353 Utf32CodeUnits::length_of(AssumeUnder4GB, &text[..first_letter_range.start])..
355 Utf32CodeUnits::length_of(AssumeUnder4GB, &text[..first_letter_range.end])
356 });
357 if first_letter_range.start != 0 {
358 let leading_whitespace_range = 0..first_letter_range.start;
359 let leading_whitespace_selection_range = selection.and_then(|range| {
360 let leading_whitespace_range_u32 =
361 RangeAny::from_start_to(first_letter_range_u32.start);
362 range.intersect(leading_whitespace_range_u32)
363 });
364
365 self.push_text(
366 Cow::Borrowed(&text[leading_whitespace_range]).into(),
367 info,
368 leading_whitespace_selection_range,
369 );
370 }
371
372 let box_slot = first_letter_info.node.box_slot();
374 let inline_item = self.start_inline_box(
375 || ArcRefCell::new(InlineBox::new(&first_letter_info, layout_context)),
376 None,
377 );
378 box_slot.set(LayoutBox::InlineLevel(inline_item));
379
380 let first_letter_text = Cow::Borrowed(&text[first_letter_range.clone()]);
381 let first_letter_selection_range = selection.and_then(|range| {
382 range
383 .intersect((*first_letter_range_u32).clone().into())
384 .map(|range| range.map(|offset| offset - first_letter_range_u32.start))
385 });
386 self.push_text(
387 first_letter_text.into(),
388 &first_letter_info,
389 first_letter_selection_range,
390 );
391 self.end_inline_box();
392 self.should_process_first_letter = false;
393
394 let remaining_selection_range = selection.and_then(|range| {
396 let remaining_text_range_u32 =
397 RangeAny::new(Some(first_letter_range_u32.end), range.end());
398 range
399 .intersect(remaining_text_range_u32)
400 .map(|range| range.map(|offset| offset - first_letter_range_u32.end))
401 });
402 self.push_text(
403 Cow::Borrowed(&text[first_letter_range.end..]).into(),
404 info,
405 remaining_selection_range,
406 );
407
408 true
409 }
410
411 pub(crate) fn push_text<'dom>(
412 &mut self,
413 text: BoxTreeString<'dom>,
414 info: &NodeAndStyleInfo<'dom>,
415 selection: Option<RangeAny<Utf32CodeUnits>>,
416 ) {
417 let mut offset_map = self.offset_map.borrow_mut();
418 let original_size_before = offset_map.total_original_size();
419
420 let bidi_class_map = CodePointMapData::<BidiClass>::new();
421 let white_space_collapse = info.style.get_white_space_collapse();
422 let mut character_count = Utf32CodeUnits(0);
423 let mut new_text = String::with_capacity(text.len());
424 for iteration in TextTransformationIterator::new(
425 &text,
426 &info.style,
427 self.last_inline_box_ended_with_collapsible_white_space,
428 self.on_word_boundary,
429 ) {
430 offset_map.push_iteration(&iteration);
431 for &character in iteration.characters() {
432 character_count.0 += 1;
433
434 self.has_right_to_left_content = self.has_right_to_left_content ||
438 matches!(
439 bidi_class_map.get(character),
440 BidiClass::RightToLeft |
441 BidiClass::ArabicLetter |
442 BidiClass::RightToLeftEmbedding |
443 BidiClass::RightToLeftIsolate |
444 BidiClass::RightToLeftOverride
445 );
446
447 self.is_empty = self.is_empty &&
448 match white_space_collapse {
449 WhiteSpaceCollapse::Collapse => Self::is_document_white_space(character),
450 WhiteSpaceCollapse::PreserveBreaks => {
451 Self::is_document_white_space(character) && character != '\n'
452 },
453 WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces => false,
454 };
455
456 new_text.push(character)
457 }
458 }
459
460 let needs_caret_placeholder = info.node.text_node_needs_caret_placeholder();
461 self.is_empty &= !needs_caret_placeholder;
462 if new_text.is_empty() && !needs_caret_placeholder {
463 return;
464 }
465
466 if let Some(last_character) = new_text.chars().next_back() {
467 self.on_word_boundary = last_character.is_whitespace();
468 self.last_inline_box_ended_with_collapsible_white_space =
469 self.on_word_boundary && white_space_collapse != &WhiteSpaceCollapse::Preserve;
470 }
471
472 let new_text_len = Utf8CodeUnits::length_of(AssumeUnder4GB, &new_text);
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 let current_inline_styles = self.shared_inline_styles();
484 let box_slot = info.node.is_text_node().then(|| info.node.box_slot());
485 let text_run = ArcRefCell::new(TextRun::new(
486 info.into(),
487 SharedTextRunData {
488 text_content: self.text_content_slot.clone(),
489 inline_styles: current_inline_styles,
490 character_range_in_ifc_text: new_character_range,
491 original_offset: original_size_before,
492 selection: AtomicRefCell::new(selection),
493 paints_caret: info.node.text_node_paints_caret(),
494 needs_caret_placeholder,
495 offset_map: self.offset_map.clone(),
496 }
497 .into(),
498 new_utf8_range,
499 box_slot
500 .as_ref()
501 .and_then(|box_slot| box_slot.take_layout_box_as_text_run()),
502 ));
503 self.inline_items
504 .push(InlineItem::TextRun(text_run.clone()));
505
506 if let Some(box_slot) = box_slot {
507 box_slot.set(LayoutBox::Text(text_run));
508 }
509 }
510
511 pub(crate) fn enter_display_contents(&mut self, shared_inline_styles: SharedInlineStyles) {
512 self.shared_inline_styles_stack.push(shared_inline_styles);
513 }
514
515 pub(crate) fn leave_display_contents(&mut self) {
516 self.shared_inline_styles_stack.pop();
517 }
518
519 pub(crate) fn finish(
521 self,
522 layout_context: &LayoutContext,
523 has_first_formatted_line: bool,
524 is_single_line_text_input: bool,
525 default_bidi_level: Level,
526 ) -> Option<InlineFormattingContext> {
527 if self.is_empty {
528 return None;
529 }
530
531 assert!(self.inline_box_stack.is_empty());
532 debug_assert_eq!(
533 self.offset_map.borrow().total_final_size(),
534 self.current_character_offset
535 );
536
537 Some(InlineFormattingContext::new_with_builder(
538 self,
539 layout_context,
540 has_first_formatted_line,
541 is_single_line_text_input,
542 default_bidi_level,
543 ))
544 }
545
546 pub(crate) fn push_soft_wrap_opportunity(&mut self) {
549 self.push_control_character_string("\u{200B}");
550 }
551}
552
553fn first_letter_range(text: &str) -> Range<usize> {
563 enum State {
564 Start,
566 PrecedingPunctuation,
568 Lns,
570 TrailingPunctuation,
573 }
574
575 let mut start = 0;
576 let mut state = State::Start;
577 for (index, character) in text.char_indices() {
578 match &mut state {
579 State::Start => {
580 if character.is_letter() || character.is_number() || character.is_symbol() {
581 start = index;
582 state = State::Lns;
583 } else if character.is_punctuation() {
584 start = index;
585 state = State::PrecedingPunctuation
586 }
587 },
588 State::PrecedingPunctuation => {
589 if character.is_letter() || character.is_number() || character.is_symbol() {
590 state = State::Lns;
591 } else if !character.is_separator_space() && !character.is_punctuation() {
592 return 0..0;
593 }
594 },
595 State::Lns => {
596 if character.is_punctuation() &&
599 !character.is_punctuation_open() &&
600 !character.is_punctuation_dash()
601 {
602 state = State::TrailingPunctuation;
603 } else {
604 return start..index;
605 }
606 },
607 State::TrailingPunctuation => {
608 if character.is_punctuation() &&
611 !character.is_punctuation_open() &&
612 !character.is_punctuation_dash()
613 {
614 continue;
615 } else {
616 return start..index;
617 }
618 },
619 }
620 }
621
622 match state {
623 State::Start | State::PrecedingPunctuation => 0..0,
624 State::Lns | State::TrailingPunctuation => start..text.len(),
625 }
626}
627
628#[cfg(test)]
629mod tests {
630 use super::*;
631
632 fn assert_first_letter_eq(text: &str, expected: &str) {
633 let range = first_letter_range(text);
634 assert_eq!(&text[range], expected);
635 }
636
637 #[test]
638 fn test_first_letter_range() {
639 assert_first_letter_eq("", "");
641 assert_first_letter_eq(" ", "");
642
643 assert_first_letter_eq("(", "");
645 assert_first_letter_eq(" (", "");
646 assert_first_letter_eq("( ", "");
647 assert_first_letter_eq("()", "");
648
649 assert_first_letter_eq("\u{0903}", "");
651
652 assert_first_letter_eq("A", "A");
654 assert_first_letter_eq(" A", "A");
655 assert_first_letter_eq("A ", "A");
656 assert_first_letter_eq(" A ", "A");
657
658 assert_first_letter_eq("App", "A");
660 assert_first_letter_eq(" App", "A");
661 assert_first_letter_eq("App ", "A");
662
663 assert_first_letter_eq(r#""A"#, r#""A"#);
665 assert_first_letter_eq(r#" "A"#, r#""A"#);
666 assert_first_letter_eq(r#""A "#, r#""A"#);
667 assert_first_letter_eq(r#"" A"#, r#"" A"#);
668 assert_first_letter_eq(r#" "A "#, r#""A"#);
669 assert_first_letter_eq(r#"("A"#, r#"("A"#);
670 assert_first_letter_eq(r#" ("A"#, r#"("A"#);
671 assert_first_letter_eq(r#"( "A"#, r#"( "A"#);
672 assert_first_letter_eq(r#"[ ( "A"#, r#"[ ( "A"#);
673
674 assert_first_letter_eq(r#"A""#, r#"A""#);
677 assert_first_letter_eq(r#"A" "#, r#"A""#);
678 assert_first_letter_eq(r#"A)]"#, r#"A)]"#);
679 assert_first_letter_eq(r#"A" )]"#, r#"A""#);
680 assert_first_letter_eq(r#"A)] >"#, r#"A)]"#);
681
682 assert_first_letter_eq(r#" ("A" )]"#, r#"("A""#);
684 assert_first_letter_eq(r#" ("A")] >"#, r#"("A")]"#);
685
686 assert_first_letter_eq("一", "一");
688 assert_first_letter_eq(" 一 ", "一");
689 assert_first_letter_eq("一二三", "一");
690 assert_first_letter_eq(" 一二三 ", "一");
691 assert_first_letter_eq("(一二三)", "(一");
692 assert_first_letter_eq(" (一二三) ", "(一");
693 assert_first_letter_eq("((一", "((一");
694 assert_first_letter_eq(" ( (一", "( (一");
695 assert_first_letter_eq("一)", "一)");
696 assert_first_letter_eq("一))", "一))");
697 assert_first_letter_eq("一) )", "一)");
698 }
699}