1use std::borrow::Cow;
6use std::cell::LazyCell;
7use std::ops::Range;
8
9use atomic_refcell::AtomicRefCell;
10use icu_properties::CodePointMapData;
11use icu_properties::props::BidiClass;
12use layout_api::LayoutNode;
13use servo_base::text::{RangeAny, Utf32CodeUnits};
14use style::computed_values::direction::T as Direction;
15use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
16use style::dom::NodeInfo;
17use style::selector_parser::PseudoElement;
18use unicode_bidi::Level;
19use unicode_categories::UnicodeCategories;
20
21use super::text_run::TextRun;
22use super::{
23 InlineBox, InlineBoxIdentifier, InlineBoxes, InlineFormattingContext, InlineItem,
24 SharedInlineStyles,
25};
26use crate::cell::ArcRefCell;
27use crate::context::LayoutContext;
28use crate::dom::{LayoutBox, NodeExt};
29use crate::dom_traversal::{BoxTreeString, NodeAndStyleInfo};
30use crate::flow::BlockLevelBox;
31use crate::flow::float::FloatBox;
32use crate::flow::inline::text_run::SharedTextRunData;
33use crate::flow::inline::text_transform::{OffsetMap, TextTransformationIterator};
34use crate::formatting_contexts::IndependentFormattingContext;
35use crate::positioned::AbsolutelyPositionedBox;
36use crate::style_ext::ComputedValuesExt;
37
38#[derive(Default)]
39pub(crate) struct InlineFormattingContextBuilder {
40 pub shared_inline_styles_stack: Vec<SharedInlineStyles>,
45
46 pub text_segments: Vec<String>,
49
50 current_text_offset: usize,
53
54 current_character_offset: usize,
58
59 last_inline_box_ended_with_collapsible_white_space: bool,
67
68 on_word_boundary: bool,
71
72 pub contains_floats: bool,
74
75 pub inline_items: Vec<InlineItem>,
79
80 pub inline_boxes: InlineBoxes,
82
83 inline_box_stack: Vec<InlineBoxIdentifier>,
92
93 pub is_empty: bool,
97
98 has_processed_first_letter: bool,
101
102 pub has_right_to_left_content: bool,
106
107 pub offset_map: ArcRefCell<OffsetMap>,
110}
111
112impl InlineFormattingContextBuilder {
113 pub(crate) fn is_document_white_space(character: char) -> bool {
126 character.is_ascii_whitespace()
127 }
128
129 pub(crate) fn new(info: &NodeAndStyleInfo, context: &LayoutContext) -> Self {
130 let has_right_to_left_content = info.style.get_inherited_box().direction == Direction::Rtl;
131 Self {
132 on_word_boundary: true,
134 is_empty: true,
135 shared_inline_styles_stack: vec![SharedInlineStyles::from_info_and_context(
136 info, context,
137 )],
138 has_right_to_left_content,
139 ..Default::default()
140 }
141 }
142
143 pub(crate) fn currently_processing_inline_box(&self) -> bool {
144 !self.inline_box_stack.is_empty()
145 }
146
147 fn push_control_character_string(&mut self, string_to_push: &str) {
148 self.text_segments.push(string_to_push.to_owned());
149 self.current_text_offset += string_to_push.len();
150
151 let new_characters = Utf32CodeUnits::length_of(string_to_push);
152 self.current_character_offset += new_characters.0;
153 self.offset_map
154 .borrow_mut()
155 .push_range(new_characters, new_characters);
156 }
157
158 fn shared_inline_styles(&self) -> SharedInlineStyles {
159 self.shared_inline_styles_stack
160 .last()
161 .expect("Should always have at least one SharedInlineStyles")
162 .clone()
163 }
164
165 pub(crate) fn push_atomic(
166 &mut self,
167 independent_formatting_context_creator: impl FnOnce()
168 -> ArcRefCell<IndependentFormattingContext>,
169 old_layout_box: Option<LayoutBox>,
170 ) -> InlineItem {
171 let independent_formatting_context = old_layout_box
173 .and_then(|layout_box| match layout_box {
174 LayoutBox::InlineLevel(InlineItem::Atomic(atomic, ..)) => Some(atomic),
175 _ => None,
176 })
177 .unwrap_or_else(independent_formatting_context_creator);
178
179 let inline_level_box = InlineItem::Atomic(
180 independent_formatting_context,
181 self.current_text_offset,
182 Level::ltr(), );
184 self.inline_items.push(inline_level_box.clone());
185 self.is_empty = false;
186
187 self.push_control_character_string("\u{fffc}");
190
191 self.last_inline_box_ended_with_collapsible_white_space = false;
192 self.on_word_boundary = true;
193
194 self.has_processed_first_letter = true;
196
197 inline_level_box
198 }
199
200 pub(crate) fn push_absolutely_positioned_box(
201 &mut self,
202 absolutely_positioned_box_creator: impl FnOnce() -> ArcRefCell<AbsolutelyPositionedBox>,
203 old_layout_box: Option<LayoutBox>,
204 ) -> InlineItem {
205 let absolutely_positioned_box = old_layout_box
206 .and_then(|layout_box| match layout_box {
207 LayoutBox::InlineLevel(InlineItem::OutOfFlowAbsolutelyPositionedBox(
208 positioned_box,
209 ..,
210 )) => Some(positioned_box),
211 _ => None,
212 })
213 .unwrap_or_else(absolutely_positioned_box_creator);
214
215 let inline_level_box = InlineItem::OutOfFlowAbsolutelyPositionedBox(
217 absolutely_positioned_box,
218 self.current_text_offset,
219 );
220
221 self.inline_items.push(inline_level_box.clone());
222 self.is_empty = false;
223 inline_level_box
224 }
225
226 pub(crate) fn push_float_box(
227 &mut self,
228 float_box_creator: impl FnOnce() -> ArcRefCell<FloatBox>,
229 old_layout_box: Option<LayoutBox>,
230 ) -> InlineItem {
231 let inline_level_box = old_layout_box
232 .and_then(|layout_box| match layout_box {
233 LayoutBox::InlineLevel(inline_item) => Some(inline_item),
234 _ => None,
235 })
236 .unwrap_or_else(|| InlineItem::OutOfFlowFloatBox(float_box_creator()));
237
238 debug_assert!(
239 matches!(inline_level_box, InlineItem::OutOfFlowFloatBox(..),),
240 "Created float box with incompatible `old_layout_box`"
241 );
242
243 self.inline_items.push(inline_level_box.clone());
244 self.is_empty = false;
245 self.contains_floats = true;
246 inline_level_box
247 }
248
249 pub(crate) fn push_block_level_box(&mut self, block_level: ArcRefCell<BlockLevelBox>) {
250 assert!(self.currently_processing_inline_box());
251 self.contains_floats = self.contains_floats || block_level.borrow().contains_floats();
252 self.inline_items.push(InlineItem::BlockLevel(block_level));
253 }
254
255 pub(crate) fn start_inline_box(
256 &mut self,
257 inline_box_creator: impl FnOnce() -> ArcRefCell<InlineBox>,
258 old_layout_box: Option<LayoutBox>,
259 ) -> InlineItem {
260 let inline_box = old_layout_box
262 .and_then(|layout_box| match layout_box {
263 LayoutBox::InlineLevel(InlineItem::StartInlineBox(inline_box)) => Some(inline_box),
264 _ => None,
265 })
266 .unwrap_or_else(inline_box_creator);
267
268 let borrowed_inline_box = inline_box.borrow();
269
270 let style = &borrowed_inline_box.base.style;
271 self.push_control_character_string(style.bidi_control_chars().0);
272 self.has_right_to_left_content =
273 self.has_right_to_left_content || style.get_inherited_box().direction == Direction::Rtl;
274
275 self.shared_inline_styles_stack
276 .push(borrowed_inline_box.shared_inline_styles.clone());
277 std::mem::drop(borrowed_inline_box);
278
279 let identifier = self.inline_boxes.start_inline_box(inline_box.clone());
280 let inline_item = InlineItem::StartInlineBox(inline_box);
281 self.inline_items.push(inline_item.clone());
282 self.inline_box_stack.push(identifier);
283 self.is_empty = false;
284 inline_item
285 }
286
287 pub(crate) fn end_inline_box(&mut self) {
292 let identifier = self
293 .inline_box_stack
294 .pop()
295 .expect("Ended non-existent inline box");
296 let inline_level_box = self.inline_boxes.get(&identifier);
297
298 self.shared_inline_styles_stack.pop();
299 self.inline_items
300 .push(InlineItem::EndInlineBox(inline_level_box.clone()));
301 self.inline_boxes.end_inline_box(identifier);
302 let bidi_control_chars = inline_level_box.borrow().base.style.bidi_control_chars();
303 self.push_control_character_string(bidi_control_chars.1);
304 }
305
306 pub(crate) fn push_text_with_possible_first_letter<'dom>(
314 &mut self,
315 text: BoxTreeString<'dom>,
316 info: &NodeAndStyleInfo<'dom>,
317 container_info: &NodeAndStyleInfo<'dom>,
318 layout_context: &LayoutContext,
319 ) -> bool {
320 let selection = info.node.text_node_selection();
321 if self.has_processed_first_letter || !container_info.pseudo_element_chain().is_empty() {
322 self.push_text(text, info, selection);
323 return false;
324 }
325
326 let Some(first_letter_info) =
327 container_info.with_pseudo_element(layout_context, PseudoElement::FirstLetter)
328 else {
329 self.push_text(text, info, selection);
330 return false;
331 };
332
333 let first_letter_range = first_letter_range(&text[..]);
334 if first_letter_range.is_empty() {
335 return false;
336 }
337
338 let first_letter_range_u32 = LazyCell::new(|| {
340 Utf32CodeUnits::length_of(&text[..first_letter_range.start])..
341 Utf32CodeUnits::length_of(&text[..first_letter_range.end])
342 });
343 if first_letter_range.start != 0 {
344 let leading_whitespace_range = 0..first_letter_range.start;
345 let leading_whitespace_selection_range = selection.and_then(|range| {
346 let leading_whitespace_range_u32 = RangeAny {
347 start: None,
348 end: Some(first_letter_range_u32.start),
349 };
350 range.intersect(leading_whitespace_range_u32)
351 });
352
353 self.push_text(
354 Cow::Borrowed(&text[leading_whitespace_range]).into(),
355 info,
356 leading_whitespace_selection_range,
357 );
358 }
359
360 let box_slot = first_letter_info.node.box_slot();
362 let inline_item = self.start_inline_box(
363 || ArcRefCell::new(InlineBox::new(&first_letter_info, layout_context)),
364 None,
365 );
366 box_slot.set(LayoutBox::InlineLevel(inline_item));
367
368 let first_letter_text = Cow::Borrowed(&text[first_letter_range.clone()]);
369 let first_letter_selection_range = selection.and_then(|range| {
370 range
371 .intersect((*first_letter_range_u32).clone().into())
372 .map(|range| range.map(|offset| offset - first_letter_range_u32.start))
373 });
374 self.push_text(
375 first_letter_text.into(),
376 &first_letter_info,
377 first_letter_selection_range,
378 );
379 self.end_inline_box();
380 self.has_processed_first_letter = true;
381
382 let remaining_selection_range = selection.and_then(|range| {
384 let remaining_text_range_u32 = RangeAny {
385 start: Some(first_letter_range_u32.end),
386 end: range.end,
387 };
388 range
389 .intersect(remaining_text_range_u32)
390 .map(|range| range.map(|offset| offset - first_letter_range_u32.end))
391 });
392 self.push_text(
393 Cow::Borrowed(&text[first_letter_range.end..]).into(),
394 info,
395 remaining_selection_range,
396 );
397
398 true
399 }
400
401 pub(crate) fn push_text<'dom>(
402 &mut self,
403 text: BoxTreeString<'dom>,
404 info: &NodeAndStyleInfo<'dom>,
405 selection: Option<RangeAny<Utf32CodeUnits>>,
406 ) {
407 let mut offset_map = self.offset_map.borrow_mut();
408 let original_size_before = offset_map.total_original_size();
409
410 let bidi_class_map = CodePointMapData::<BidiClass>::new();
411 let white_space_collapse = info.style.clone_white_space_collapse();
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 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 if let Some(last_character) = new_text.chars().next_back() {
455 self.on_word_boundary = last_character.is_whitespace();
456 self.last_inline_box_ended_with_collapsible_white_space =
457 self.on_word_boundary && white_space_collapse != WhiteSpaceCollapse::Preserve;
458 }
459
460 let new_utf8_range = self.current_text_offset..self.current_text_offset + new_text.len();
461 self.current_text_offset = new_utf8_range.end;
462
463 let new_character_range =
464 self.current_character_offset..self.current_character_offset + character_count;
465 self.current_character_offset = new_character_range.end;
466
467 self.text_segments.push(new_text);
468
469 let current_inline_styles = self.shared_inline_styles();
470 let box_slot = info.node.is_text_node().then(|| info.node.box_slot());
471 let text_run = ArcRefCell::new(TextRun::new(
472 info.into(),
473 SharedTextRunData {
474 inline_styles: current_inline_styles,
475 character_range_in_ifc_text: new_character_range,
476 original_offset: original_size_before,
477 selection: AtomicRefCell::new(selection),
478 paint_caret: info.node.text_node_paints_caret(),
479 offset_map: self.offset_map.clone(),
480 }
481 .into(),
482 new_utf8_range,
483 box_slot
484 .as_ref()
485 .and_then(|box_slot| box_slot.take_layout_box_as_text_run()),
486 ));
487 self.inline_items
488 .push(InlineItem::TextRun(text_run.clone()));
489
490 if let Some(box_slot) = box_slot {
491 box_slot.set(LayoutBox::Text(text_run));
492 }
493 }
494
495 pub(crate) fn enter_display_contents(&mut self, shared_inline_styles: SharedInlineStyles) {
496 self.shared_inline_styles_stack.push(shared_inline_styles);
497 }
498
499 pub(crate) fn leave_display_contents(&mut self) {
500 self.shared_inline_styles_stack.pop();
501 }
502
503 pub(crate) fn finish(
505 self,
506 layout_context: &LayoutContext,
507 has_first_formatted_line: bool,
508 is_single_line_text_input: bool,
509 default_bidi_level: Level,
510 ) -> Option<InlineFormattingContext> {
511 if self.is_empty {
512 return None;
513 }
514
515 assert!(self.inline_box_stack.is_empty());
516 debug_assert_eq!(
517 self.offset_map.borrow().total_final_size().0,
518 self.current_character_offset
519 );
520
521 Some(InlineFormattingContext::new_with_builder(
522 self,
523 layout_context,
524 has_first_formatted_line,
525 is_single_line_text_input,
526 default_bidi_level,
527 ))
528 }
529}
530
531fn first_letter_range(text: &str) -> Range<usize> {
541 enum State {
542 Start,
544 PrecedingPunctuation,
546 Lns,
548 TrailingPunctuation,
551 }
552
553 let mut start = 0;
554 let mut state = State::Start;
555 for (index, character) in text.char_indices() {
556 match &mut state {
557 State::Start => {
558 if character.is_letter() || character.is_number() || character.is_symbol() {
559 start = index;
560 state = State::Lns;
561 } else if character.is_punctuation() {
562 start = index;
563 state = State::PrecedingPunctuation
564 }
565 },
566 State::PrecedingPunctuation => {
567 if character.is_letter() || character.is_number() || character.is_symbol() {
568 state = State::Lns;
569 } else if !character.is_separator_space() && !character.is_punctuation() {
570 return 0..0;
571 }
572 },
573 State::Lns => {
574 if character.is_punctuation() &&
577 !character.is_punctuation_open() &&
578 !character.is_punctuation_dash()
579 {
580 state = State::TrailingPunctuation;
581 } else {
582 return start..index;
583 }
584 },
585 State::TrailingPunctuation => {
586 if character.is_punctuation() &&
589 !character.is_punctuation_open() &&
590 !character.is_punctuation_dash()
591 {
592 continue;
593 } else {
594 return start..index;
595 }
596 },
597 }
598 }
599
600 match state {
601 State::Start | State::PrecedingPunctuation => 0..0,
602 State::Lns | State::TrailingPunctuation => start..text.len(),
603 }
604}
605
606#[cfg(test)]
607mod tests {
608 use super::*;
609
610 fn assert_first_letter_eq(text: &str, expected: &str) {
611 let range = first_letter_range(text);
612 assert_eq!(&text[range], expected);
613 }
614
615 #[test]
616 fn test_first_letter_range() {
617 assert_first_letter_eq("", "");
619 assert_first_letter_eq(" ", "");
620
621 assert_first_letter_eq("(", "");
623 assert_first_letter_eq(" (", "");
624 assert_first_letter_eq("( ", "");
625 assert_first_letter_eq("()", "");
626
627 assert_first_letter_eq("\u{0903}", "");
629
630 assert_first_letter_eq("A", "A");
632 assert_first_letter_eq(" A", "A");
633 assert_first_letter_eq("A ", "A");
634 assert_first_letter_eq(" A ", "A");
635
636 assert_first_letter_eq("App", "A");
638 assert_first_letter_eq(" App", "A");
639 assert_first_letter_eq("App ", "A");
640
641 assert_first_letter_eq(r#""A"#, r#""A"#);
643 assert_first_letter_eq(r#" "A"#, r#""A"#);
644 assert_first_letter_eq(r#""A "#, r#""A"#);
645 assert_first_letter_eq(r#"" A"#, r#"" A"#);
646 assert_first_letter_eq(r#" "A "#, r#""A"#);
647 assert_first_letter_eq(r#"("A"#, r#"("A"#);
648 assert_first_letter_eq(r#" ("A"#, r#"("A"#);
649 assert_first_letter_eq(r#"( "A"#, r#"( "A"#);
650 assert_first_letter_eq(r#"[ ( "A"#, r#"[ ( "A"#);
651
652 assert_first_letter_eq(r#"A""#, r#"A""#);
655 assert_first_letter_eq(r#"A" "#, r#"A""#);
656 assert_first_letter_eq(r#"A)]"#, r#"A)]"#);
657 assert_first_letter_eq(r#"A" )]"#, r#"A""#);
658 assert_first_letter_eq(r#"A)] >"#, r#"A)]"#);
659
660 assert_first_letter_eq(r#" ("A" )]"#, r#"("A""#);
662 assert_first_letter_eq(r#" ("A")] >"#, r#"("A")]"#);
663
664 assert_first_letter_eq("一", "一");
666 assert_first_letter_eq(" 一 ", "一");
667 assert_first_letter_eq("一二三", "一");
668 assert_first_letter_eq(" 一二三 ", "一");
669 assert_first_letter_eq("(一二三)", "(一");
670 assert_first_letter_eq(" (一二三) ", "(一");
671 assert_first_letter_eq("((一", "((一");
672 assert_first_letter_eq(" ( (一", "( (一");
673 assert_first_letter_eq("一)", "一)");
674 assert_first_letter_eq("一))", "一))");
675 assert_first_letter_eq("一) )", "一)");
676 }
677}