1use std::borrow::Cow;
6use std::cell::LazyCell;
7use std::ops::Range;
8use std::sync::Arc;
9
10use atomic_refcell::AtomicRefCell;
11use fonts::TextByteRange;
12use icu_properties::BidiClass;
13use layout_api::{LayoutNode, ScriptSelection};
14use servo_base::text::{RangeAny, 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 current_text_offset: usize,
54
55 current_character_offset: usize,
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: ArcRefCell<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 has_right_to_left_content,
140 ..Default::default()
141 }
142 }
143
144 pub(crate) fn currently_processing_inline_box(&self) -> bool {
145 !self.inline_box_stack.is_empty()
146 }
147
148 fn push_control_character_string(&mut self, string_to_push: &str) {
149 self.text_segments.push(string_to_push.to_owned());
150 self.current_text_offset += string_to_push.len();
151
152 let new_characters = Utf32CodeUnits::length_of(string_to_push);
153 self.current_character_offset += new_characters.0;
154 self.offset_map
155 .borrow_mut()
156 .push_range(new_characters, 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 mut offset_map = self.offset_map.borrow_mut();
410 let original_size_before = offset_map.total_original_size();
411
412 let bidi_class_map = icu_properties::maps::bidi_class();
413 let white_space_collapse = info.style.clone_white_space_collapse();
414 let mut character_count = 0;
415 let mut new_text = String::with_capacity(text.len());
416 for iteration in TextTransformationIterator::new(
417 &text,
418 &info.style,
419 self.last_inline_box_ended_with_collapsible_white_space,
420 self.on_word_boundary,
421 ) {
422 offset_map.push_iteration(&iteration);
423 for &character in iteration.characters() {
424 character_count += 1;
425
426 self.has_right_to_left_content = self.has_right_to_left_content ||
430 matches!(
431 bidi_class_map.get(character),
432 BidiClass::RightToLeft |
433 BidiClass::ArabicLetter |
434 BidiClass::RightToLeftEmbedding |
435 BidiClass::RightToLeftIsolate |
436 BidiClass::RightToLeftOverride
437 );
438
439 self.is_empty = self.is_empty &&
440 match white_space_collapse {
441 WhiteSpaceCollapse::Collapse => Self::is_document_white_space(character),
442 WhiteSpaceCollapse::PreserveBreaks => {
443 Self::is_document_white_space(character) && character != '\n'
444 },
445 WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces => false,
446 };
447
448 new_text.push(character)
449 }
450 }
451
452 if new_text.is_empty() {
453 return;
454 }
455
456 let selection = info.node.form_control_selection_in_text_node().or_else(|| {
457 let document_selection = document_selection?;
458 let start = document_selection.start.unwrap_or(Utf32CodeUnits(0));
460 let end = document_selection
462 .end
463 .unwrap_or(offset_map.total_original_size() - original_size_before);
464
465 if start == end {
466 return None;
467 }
468 debug_assert!(end > start);
469
470 Some(Arc::new(AtomicRefCell::new(ScriptSelection {
471 range: TextByteRange::default(),
472 character_range: start.0..end.0,
473 enabled: true,
474 })))
475 });
476
477 if let Some(last_character) = new_text.chars().next_back() {
478 self.on_word_boundary = last_character.is_whitespace();
479 self.last_inline_box_ended_with_collapsible_white_space =
480 self.on_word_boundary && white_space_collapse != WhiteSpaceCollapse::Preserve;
481 }
482
483 let new_utf8_range = self.current_text_offset..self.current_text_offset + new_text.len();
484 self.current_text_offset = new_utf8_range.end;
485
486 let new_character_range =
487 self.current_character_offset..self.current_character_offset + character_count;
488 self.current_character_offset = new_character_range.end;
489
490 self.text_segments.push(new_text);
491
492 let current_inline_styles = self.shared_inline_styles();
493 let box_slot = info.node.is_text_node().then(|| info.node.box_slot());
494 let text_run = ArcRefCell::new(TextRun::new(
495 info.into(),
496 SharedTextRunData {
497 inline_styles: current_inline_styles,
498 character_range_in_ifc_text: new_character_range,
499 original_offset: original_size_before,
500 selection,
501 offset_map: self.offset_map.clone(),
502 }
503 .into(),
504 new_utf8_range,
505 box_slot
506 .as_ref()
507 .and_then(|box_slot| box_slot.take_layout_box_as_text_run()),
508 ));
509 self.inline_items
510 .push(InlineItem::TextRun(text_run.clone()));
511
512 if let Some(box_slot) = box_slot {
513 box_slot.set(LayoutBox::Text(text_run));
514 }
515 }
516
517 pub(crate) fn enter_display_contents(&mut self, shared_inline_styles: SharedInlineStyles) {
518 self.shared_inline_styles_stack.push(shared_inline_styles);
519 }
520
521 pub(crate) fn leave_display_contents(&mut self) {
522 self.shared_inline_styles_stack.pop();
523 }
524
525 pub(crate) fn finish(
527 self,
528 layout_context: &LayoutContext,
529 has_first_formatted_line: bool,
530 is_single_line_text_input: bool,
531 default_bidi_level: Level,
532 ) -> Option<InlineFormattingContext> {
533 if self.is_empty {
534 return None;
535 }
536
537 assert!(self.inline_box_stack.is_empty());
538 debug_assert_eq!(
539 self.offset_map.borrow().total_final_size().0,
540 self.current_character_offset
541 );
542
543 Some(InlineFormattingContext::new_with_builder(
544 self,
545 layout_context,
546 has_first_formatted_line,
547 is_single_line_text_input,
548 default_bidi_level,
549 ))
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}