1use std::borrow::Cow;
6
7use rayon::iter::{IntoParallelIterator, ParallelIterator};
8use servo_arc::Arc;
9use style::properties::ComputedValues;
10use style::properties::longhands::list_style_position::computed_value::T as ListStylePosition;
11use style::selector_parser::PseudoElement;
12use style::str::char_is_whitespace;
13use style::values::specified::box_::DisplayOutside as StyloDisplayOutside;
14use unicode_categories::UnicodeCategories;
15
16use super::OutsideMarker;
17use super::inline::construct::InlineFormattingContextBuilder;
18use super::inline::inline_box::InlineBox;
19use super::inline::{InlineFormattingContext, SharedInlineStyles};
20use crate::PropagatedBoxTreeData;
21use crate::cell::ArcRefCell;
22use crate::context::LayoutContext;
23use crate::dom::{BoxSlot, LayoutBox, NodeExt};
24use crate::dom_traversal::{
25 Contents, NodeAndStyleInfo, NonReplacedContents, PseudoElementContentItem, TraversalHandler,
26};
27use crate::flow::float::FloatBox;
28use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox};
29use crate::formatting_contexts::{
30 IndependentFormattingContext, IndependentFormattingContextContents,
31};
32use crate::fragment_tree::FragmentFlags;
33use crate::layout_box_base::LayoutBoxBase;
34use crate::positioned::AbsolutelyPositionedBox;
35use crate::style_ext::{ComputedValuesExt, DisplayGeneratingBox, DisplayInside, DisplayOutside};
36use crate::table::{AnonymousTableContent, Table};
37
38impl BlockFormattingContext {
39 pub(crate) fn construct(
40 context: &LayoutContext,
41 info: &NodeAndStyleInfo<'_>,
42 contents: NonReplacedContents,
43 propagated_data: PropagatedBoxTreeData,
44 is_list_item: bool,
45 ) -> Self {
46 Self::from_block_container(BlockContainer::construct(
47 context,
48 info,
49 contents,
50 propagated_data,
51 is_list_item,
52 ))
53 }
54
55 pub(crate) fn from_block_container(contents: BlockContainer) -> Self {
56 let contains_floats = contents.contains_floats();
57 Self {
58 contents,
59 contains_floats,
60 }
61 }
62}
63
64struct BlockLevelJob<'dom> {
65 info: NodeAndStyleInfo<'dom>,
66 box_slot: BoxSlot<'dom>,
67 propagated_data: PropagatedBoxTreeData,
68 kind: BlockLevelCreator,
69}
70
71pub(crate) enum BlockLevelCreator {
72 SameFormattingContextBlock(IntermediateBlockContainer),
73 Independent {
74 display_inside: DisplayInside,
75 contents: Contents,
76 },
77 OutOfFlowAbsolutelyPositionedBox {
78 display_inside: DisplayInside,
79 contents: Contents,
80 },
81 OutOfFlowFloatBox {
82 display_inside: DisplayInside,
83 contents: Contents,
84 },
85 OutsideMarker {
86 list_item_style: Arc<ComputedValues>,
87 contents: Vec<PseudoElementContentItem>,
88 },
89 AnonymousTable {
90 table_block: ArcRefCell<BlockLevelBox>,
91 },
92}
93
94impl BlockLevelCreator {
95 pub(crate) fn new_for_inflow_block_level_element<'dom>(
96 info: &NodeAndStyleInfo<'dom>,
97 display_inside: DisplayInside,
98 contents: Contents,
99 propagated_data: PropagatedBoxTreeData,
100 ) -> Self {
101 match contents {
102 Contents::NonReplaced(contents) => match display_inside {
103 DisplayInside::Flow { is_list_item }
104 if !info.style.establishes_block_formatting_context(
109 FragmentFlags::empty()
110 ) =>
111 {
112 Self::SameFormattingContextBlock(
113 IntermediateBlockContainer::Deferred {
114 contents,
115 propagated_data,
116 is_list_item,
117 },
118 )
119 },
120 _ => Self::Independent {
121 display_inside,
122 contents: Contents::NonReplaced(contents),
123 },
124 },
125 Contents::Replaced(_) | Contents::Widget(_) => Self::Independent {
126 display_inside,
127 contents,
128 },
129 }
130 }
131}
132
133pub(crate) enum IntermediateBlockContainer {
141 InlineFormattingContext(BlockContainer),
142 Deferred {
143 contents: NonReplacedContents,
144 propagated_data: PropagatedBoxTreeData,
145 is_list_item: bool,
146 },
147}
148
149pub(crate) struct BlockContainerBuilder<'dom, 'style> {
154 context: &'style LayoutContext<'style>,
155
156 info: &'style NodeAndStyleInfo<'dom>,
159
160 block_level_boxes: Vec<BlockLevelJob<'dom>>,
174
175 have_already_seen_first_line_for_text_indent: bool,
178
179 propagated_data: PropagatedBoxTreeData,
181
182 inline_formatting_context_builder: Option<InlineFormattingContextBuilder>,
187
188 anonymous_box_info: Option<NodeAndStyleInfo<'dom>>,
191
192 anonymous_table_content: Vec<AnonymousTableContent<'dom>>,
196
197 display_contents_shared_styles: Vec<SharedInlineStyles>,
201}
202
203impl BlockContainer {
204 pub fn construct(
205 context: &LayoutContext,
206 info: &NodeAndStyleInfo<'_>,
207 contents: NonReplacedContents,
208 propagated_data: PropagatedBoxTreeData,
209 is_list_item: bool,
210 ) -> BlockContainer {
211 let mut builder = BlockContainerBuilder::new(context, info, propagated_data);
212
213 if is_list_item {
214 if let Some((marker_info, marker_contents)) = crate::lists::make_marker(context, info) {
215 match marker_info.style.clone_list_style_position() {
216 ListStylePosition::Inside => {
217 builder.handle_list_item_marker_inside(&marker_info, marker_contents)
218 },
219 ListStylePosition::Outside => builder.handle_list_item_marker_outside(
220 &marker_info,
221 marker_contents,
222 info.style.clone(),
223 ),
224 }
225 }
226 }
227
228 contents.traverse(context, info, &mut builder);
229 builder.finish()
230 }
231}
232
233impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> {
234 pub(crate) fn new(
235 context: &'style LayoutContext,
236 info: &'style NodeAndStyleInfo<'dom>,
237 propagated_data: PropagatedBoxTreeData,
238 ) -> Self {
239 BlockContainerBuilder {
240 context,
241 info,
242 block_level_boxes: Vec::new(),
243 propagated_data,
244 have_already_seen_first_line_for_text_indent: false,
245 anonymous_box_info: None,
246 anonymous_table_content: Vec::new(),
247 inline_formatting_context_builder: None,
248 display_contents_shared_styles: Vec::new(),
249 }
250 }
251
252 fn currently_processing_inline_box(&self) -> bool {
253 self.inline_formatting_context_builder
254 .as_ref()
255 .is_some_and(InlineFormattingContextBuilder::currently_processing_inline_box)
256 }
257
258 fn ensure_inline_formatting_context_builder(&mut self) -> &mut InlineFormattingContextBuilder {
259 self.inline_formatting_context_builder
260 .get_or_insert_with(|| {
261 let mut builder = InlineFormattingContextBuilder::new(self.info, self.context);
262 for shared_inline_styles in self.display_contents_shared_styles.iter() {
263 builder.enter_display_contents(shared_inline_styles.clone());
264 }
265 builder
266 })
267 }
268
269 fn finish_ongoing_inline_formatting_context(&mut self) -> Option<InlineFormattingContext> {
270 self.inline_formatting_context_builder.take()?.finish(
271 self.context,
272 !self.have_already_seen_first_line_for_text_indent,
273 self.info.node.is_single_line_text_input(),
274 self.info.style.to_bidi_level(),
275 )
276 }
277
278 pub(crate) fn finish(mut self) -> BlockContainer {
279 debug_assert!(!self.currently_processing_inline_box());
280
281 self.finish_anonymous_table_if_needed();
282
283 if let Some(inline_formatting_context) = self.finish_ongoing_inline_formatting_context() {
284 if self.block_level_boxes.is_empty() {
288 return BlockContainer::InlineFormattingContext(inline_formatting_context);
289 }
290 self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
291 }
292
293 let context = self.context;
294 let block_level_boxes = if self.context.use_rayon {
295 self.block_level_boxes
296 .into_par_iter()
297 .map(|block_level_job| block_level_job.finish(context))
298 .collect()
299 } else {
300 self.block_level_boxes
301 .into_iter()
302 .map(|block_level_job| block_level_job.finish(context))
303 .collect()
304 };
305
306 BlockContainer::BlockLevelBoxes(block_level_boxes)
307 }
308
309 fn finish_anonymous_table_if_needed(&mut self) {
310 if self.anonymous_table_content.is_empty() {
311 return;
312 }
313
314 let inline_table = self.currently_processing_inline_box();
322
323 let contents: Vec<AnonymousTableContent<'dom>> =
324 self.anonymous_table_content.drain(..).collect();
325 let last_text = match contents.last() {
326 Some(AnonymousTableContent::Text(info, text)) => Some((info.clone(), text.clone())),
327 _ => None,
328 };
329
330 let (table_info, ifc) =
331 Table::construct_anonymous(self.context, self.info, contents, self.propagated_data);
332
333 if inline_table {
334 self.ensure_inline_formatting_context_builder()
335 .push_atomic(|| ArcRefCell::new(ifc), None);
336 } else {
337 let table_block = ArcRefCell::new(BlockLevelBox::Independent(ifc));
338
339 if let Some(inline_formatting_context) = self.finish_ongoing_inline_formatting_context()
340 {
341 self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
342 }
343
344 let box_slot = table_info.node.box_slot();
345 self.block_level_boxes.push(BlockLevelJob {
346 info: table_info,
347 box_slot,
348 kind: BlockLevelCreator::AnonymousTable { table_block },
349 propagated_data: self.propagated_data,
350 });
351 }
352
353 if let Some((info, text)) = last_text {
363 self.handle_text(&info, text);
364 }
365 }
366}
367
368fn first_letter_range(text: &str) -> std::ops::Range<usize> {
379 use unicode_categories::UnicodeCategories;
380
381 enum State {
382 Start,
383 PrecedingPunc,
384 Lns,
386 SucceedingPunc,
387 }
388
389 let mut start = 0;
390 let mut end = None;
391 let mut state = State::Start;
392
393 for (i, c) in text.char_indices() {
394 match &mut state {
395 State::Start => {
396 if c.is_punctuation() {
397 start = i;
398 state = State::PrecedingPunc;
399 } else if c.is_letter() || c.is_number() || c.is_symbol() {
400 start = i;
401 state = State::Lns;
402 } else if c.is_separator_space() {
403 continue;
404 } else {
405 return 0..0;
407 }
408 },
409 State::PrecedingPunc => {
410 if c.is_letter() || c.is_number() || c.is_symbol() {
411 state = State::Lns;
412 } else if c.is_punctuation() || (c.is_separator_space() && c != '\u{3000}') {
413 continue;
414 } else {
415 return 0..0;
417 }
418 },
419 State::Lns => {
420 if c.is_punctuation() && !c.is_punctuation_open() && !c.is_punctuation_dash() {
423 state = State::SucceedingPunc;
424 } else {
425 end = Some(i);
426 break;
427 }
428 },
429 State::SucceedingPunc => {
430 if c.is_punctuation() && !c.is_punctuation_open() && !c.is_punctuation_dash() {
433 continue;
434 } else {
435 end = Some(i);
436 break;
437 }
438 },
439 }
440 }
441
442 match state {
443 State::Start | State::PrecedingPunc => 0..0,
444 State::Lns | State::SucceedingPunc => {
445 let end = end.unwrap_or(text.len());
446 start..end
447 },
448 }
449}
450
451impl<'dom> TraversalHandler<'dom> for BlockContainerBuilder<'dom, '_> {
452 fn handle_element(
453 &mut self,
454 info: &NodeAndStyleInfo<'dom>,
455 display: DisplayGeneratingBox,
456 contents: Contents,
457 box_slot: BoxSlot<'dom>,
458 ) {
459 match display {
460 DisplayGeneratingBox::OutsideInside { outside, inside } => {
461 self.finish_anonymous_table_if_needed();
462
463 match outside {
464 DisplayOutside::Inline => {
465 self.handle_inline_level_element(info, inside, contents, box_slot)
466 },
467 DisplayOutside::Block => {
468 let box_style = info.style.get_box();
469 if box_style.position.is_absolutely_positioned() {
472 self.handle_absolutely_positioned_element(
473 info, inside, contents, box_slot,
474 )
475 } else if box_style.float.is_floating() {
476 self.handle_float_element(info, inside, contents, box_slot)
477 } else {
478 self.handle_block_level_element(info, inside, contents, box_slot)
479 }
480 },
481 };
482 },
483 DisplayGeneratingBox::LayoutInternal(_) => {
484 self.anonymous_table_content
485 .push(AnonymousTableContent::Element {
486 info: info.clone(),
487 display,
488 contents,
489 box_slot,
490 });
491 },
492 }
493 }
494
495 fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) {
496 if text.is_empty() {
497 return;
498 }
499
500 if !self.anonymous_table_content.is_empty() && text.chars().all(char_is_whitespace) {
504 self.anonymous_table_content
505 .push(AnonymousTableContent::Text(info.clone(), text));
506 return;
507 } else {
508 self.finish_anonymous_table_if_needed();
509 }
510
511 let container_info = self.info;
512 let context = self.context;
513 let builder = self.ensure_inline_formatting_context_builder();
514
515 if let Some(pseudo_info) = (container_info.pseudo_element_chain().is_empty())
517 .then(|| container_info.with_pseudo_element(context, PseudoElement::FirstLetter))
518 .flatten()
519 .filter(|_| {
520 builder
521 .text_segments
522 .iter()
523 .flat_map(|seg| seg.chars())
524 .all(|c| c.is_separator_space())
525 })
526 {
527 let first_letter_range = first_letter_range(&text[..]);
528
529 if first_letter_range.start != 0 {
532 builder.push_text(Cow::Borrowed(&text[0..first_letter_range.start]), info);
533 }
534
535 builder.start_inline_box(
536 || ArcRefCell::new(InlineBox::new(&pseudo_info, context)),
537 None,
538 );
539 let first_letter_text = Cow::Borrowed(&text[first_letter_range.clone()]);
540 builder.push_text(first_letter_text, &pseudo_info);
541 builder.end_inline_box();
542
543 builder.push_text(Cow::Borrowed(&text[first_letter_range.end..]), info);
544 } else {
545 builder.push_text(text, info);
546 }
547 }
548
549 fn enter_display_contents(&mut self, styles: SharedInlineStyles) {
550 self.display_contents_shared_styles.push(styles.clone());
551 if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
552 builder.enter_display_contents(styles);
553 }
554 }
555
556 fn leave_display_contents(&mut self) {
557 self.display_contents_shared_styles.pop();
558 if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
559 builder.leave_display_contents();
560 }
561 }
562}
563
564impl<'dom> BlockContainerBuilder<'dom, '_> {
565 fn handle_list_item_marker_inside(
566 &mut self,
567 marker_info: &NodeAndStyleInfo<'dom>,
568 contents: Vec<crate::dom_traversal::PseudoElementContentItem>,
569 ) {
570 let box_slot = marker_info.node.box_slot();
571 self.handle_inline_level_element(
572 marker_info,
573 DisplayInside::Flow {
574 is_list_item: false,
575 },
576 Contents::for_pseudo_element(contents),
577 box_slot,
578 );
579 }
580
581 fn handle_list_item_marker_outside(
582 &mut self,
583 marker_info: &NodeAndStyleInfo<'dom>,
584 contents: Vec<crate::dom_traversal::PseudoElementContentItem>,
585 list_item_style: Arc<ComputedValues>,
586 ) {
587 let box_slot = marker_info.node.box_slot();
588 self.block_level_boxes.push(BlockLevelJob {
589 info: marker_info.clone(),
590 box_slot,
591 kind: BlockLevelCreator::OutsideMarker {
592 contents,
593 list_item_style,
594 },
595 propagated_data: self.propagated_data,
596 });
597 }
598
599 fn handle_inline_level_element(
600 &mut self,
601 info: &NodeAndStyleInfo<'dom>,
602 display_inside: DisplayInside,
603 contents: Contents,
604 box_slot: BoxSlot<'dom>,
605 ) {
606 let context = self.context;
607 let old_layout_box = box_slot.take_layout_box();
608 let (is_list_item, non_replaced_contents) = match (display_inside, contents) {
609 (
610 DisplayInside::Flow { is_list_item },
611 Contents::NonReplaced(non_replaced_contents),
612 ) => (is_list_item, non_replaced_contents),
613 (_, contents) => {
614 let propagated_data = self.propagated_data;
616
617 let construction_callback = || {
618 ArcRefCell::new(IndependentFormattingContext::construct(
619 context,
620 info,
621 display_inside,
622 contents,
623 propagated_data,
624 ))
625 };
626
627 let atomic = self
628 .ensure_inline_formatting_context_builder()
629 .push_atomic(construction_callback, old_layout_box);
630 box_slot.set(LayoutBox::InlineLevel(atomic));
631 return;
632 },
633 };
634
635 let inline_builder = self.ensure_inline_formatting_context_builder();
638 inline_builder.start_inline_box(
639 || ArcRefCell::new(InlineBox::new(info, context)),
640 old_layout_box,
641 );
642 box_slot.set(LayoutBox::InlineLevel(
643 inline_builder.inline_items.last().unwrap().clone(),
644 ));
645
646 if is_list_item {
647 if let Some((marker_info, marker_contents)) =
648 crate::lists::make_marker(self.context, info)
649 {
650 self.handle_list_item_marker_inside(&marker_info, marker_contents)
654 }
655 }
656
657 non_replaced_contents.traverse(self.context, info, self);
659
660 self.finish_anonymous_table_if_needed();
661
662 self.inline_formatting_context_builder
663 .as_mut()
664 .expect("Should be building an InlineFormattingContext")
665 .end_inline_box();
666 }
667
668 fn handle_block_level_element(
669 &mut self,
670 info: &NodeAndStyleInfo<'dom>,
671 display_inside: DisplayInside,
672 contents: Contents,
673 box_slot: BoxSlot<'dom>,
674 ) {
675 let propagated_data = self.propagated_data;
676 let kind = BlockLevelCreator::new_for_inflow_block_level_element(
677 info,
678 display_inside,
679 contents,
680 propagated_data,
681 );
682 let job = BlockLevelJob {
683 info: info.clone(),
684 box_slot,
685 kind,
686 propagated_data,
687 };
688 if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
689 if builder.currently_processing_inline_box() {
690 builder.push_block_level_box(job.finish(self.context));
691 return;
692 }
693 if let Some(context) = self.finish_ongoing_inline_formatting_context() {
694 self.push_block_level_job_for_inline_formatting_context(context);
695 }
696 }
697 self.block_level_boxes.push(job);
698
699 self.have_already_seen_first_line_for_text_indent = true;
702 }
703
704 fn handle_absolutely_positioned_element(
705 &mut self,
706 info: &NodeAndStyleInfo<'dom>,
707 display_inside: DisplayInside,
708 contents: Contents,
709 box_slot: BoxSlot<'dom>,
710 ) {
711 let needs_inline_builder =
717 info.style.get_box().original_display.outside() == StyloDisplayOutside::Inline;
718 if needs_inline_builder {
719 self.ensure_inline_formatting_context_builder();
720 }
721 let inline_builder = self
722 .inline_formatting_context_builder
723 .as_mut()
724 .filter(|builder| needs_inline_builder || !builder.is_empty);
725 if let Some(inline_builder) = inline_builder {
726 let constructor = || {
727 ArcRefCell::new(AbsolutelyPositionedBox::construct(
728 self.context,
729 info,
730 display_inside,
731 contents,
732 ))
733 };
734 let old_layout_box = box_slot.take_layout_box();
735 let inline_level_box =
736 inline_builder.push_absolutely_positioned_box(constructor, old_layout_box);
737 box_slot.set(LayoutBox::InlineLevel(inline_level_box));
738 return;
739 }
740
741 let kind = BlockLevelCreator::OutOfFlowAbsolutelyPositionedBox {
742 contents,
743 display_inside,
744 };
745 self.block_level_boxes.push(BlockLevelJob {
746 info: info.clone(),
747 box_slot,
748 kind,
749 propagated_data: self.propagated_data,
750 });
751 }
752
753 fn handle_float_element(
754 &mut self,
755 info: &NodeAndStyleInfo<'dom>,
756 display_inside: DisplayInside,
757 contents: Contents,
758 box_slot: BoxSlot<'dom>,
759 ) {
760 if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
761 if !builder.is_empty {
762 let constructor = || {
763 ArcRefCell::new(FloatBox::construct(
764 self.context,
765 info,
766 display_inside,
767 contents,
768 self.propagated_data,
769 ))
770 };
771 let old_layout_box = box_slot.take_layout_box();
772 let inline_level_box = builder.push_float_box(constructor, old_layout_box);
773 box_slot.set(LayoutBox::InlineLevel(inline_level_box));
774 return;
775 }
776 }
777
778 let kind = BlockLevelCreator::OutOfFlowFloatBox {
779 contents,
780 display_inside,
781 };
782 self.block_level_boxes.push(BlockLevelJob {
783 info: info.clone(),
784 box_slot,
785 kind,
786 propagated_data: self.propagated_data,
787 });
788 }
789
790 fn push_block_level_job_for_inline_formatting_context(
791 &mut self,
792 inline_formatting_context: InlineFormattingContext,
793 ) {
794 let layout_context = self.context;
795 let anonymous_info = self
796 .anonymous_box_info
797 .get_or_insert_with(|| {
798 self.info
799 .with_pseudo_element(layout_context, PseudoElement::ServoAnonymousBox)
800 .expect("Should never fail to create anonymous box")
801 })
802 .clone();
803
804 let box_slot = anonymous_info.node.box_slot();
805 self.block_level_boxes.push(BlockLevelJob {
806 info: anonymous_info,
807 box_slot,
808 kind: BlockLevelCreator::SameFormattingContextBlock(
809 IntermediateBlockContainer::InlineFormattingContext(
810 BlockContainer::InlineFormattingContext(inline_formatting_context),
811 ),
812 ),
813 propagated_data: self.propagated_data,
814 });
815
816 self.have_already_seen_first_line_for_text_indent = true;
817 }
818}
819
820impl BlockLevelJob<'_> {
821 fn finish(self, context: &LayoutContext) -> ArcRefCell<BlockLevelBox> {
822 let info = &self.info;
823
824 if let Some(block_level_box) = match &*self.box_slot.slot.borrow() {
827 Some(LayoutBox::BlockLevel(block_level_box)) => Some(block_level_box.clone()),
828 _ => None,
829 } {
830 return block_level_box;
831 }
832
833 let block_level_box = match self.kind {
834 BlockLevelCreator::SameFormattingContextBlock(intermediate_block_container) => {
835 let contents = intermediate_block_container.finish(context, info);
836 let contains_floats = contents.contains_floats();
837 ArcRefCell::new(BlockLevelBox::SameFormattingContextBlock {
838 base: LayoutBoxBase::new(info.into(), info.style.clone()),
839 contents,
840 contains_floats,
841 })
842 },
843 BlockLevelCreator::Independent {
844 display_inside,
845 contents,
846 } => {
847 let context = IndependentFormattingContext::construct(
848 context,
849 info,
850 display_inside,
851 contents,
852 self.propagated_data,
853 );
854 ArcRefCell::new(BlockLevelBox::Independent(context))
855 },
856 BlockLevelCreator::OutOfFlowAbsolutelyPositionedBox {
857 display_inside,
858 contents,
859 } => ArcRefCell::new(BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(
860 ArcRefCell::new(AbsolutelyPositionedBox::construct(
861 context,
862 info,
863 display_inside,
864 contents,
865 )),
866 )),
867 BlockLevelCreator::OutOfFlowFloatBox {
868 display_inside,
869 contents,
870 } => ArcRefCell::new(BlockLevelBox::OutOfFlowFloatBox(FloatBox::construct(
871 context,
872 info,
873 display_inside,
874 contents,
875 self.propagated_data,
876 ))),
877 BlockLevelCreator::OutsideMarker {
878 contents,
879 list_item_style,
880 } => {
881 let contents = NonReplacedContents::OfPseudoElement(contents);
882 let block_container = BlockContainer::construct(
883 context,
884 info,
885 contents,
886 self.propagated_data,
887 false, );
889 let block_formatting_context = BlockFormattingContext {
891 contents: block_container,
892 contains_floats: false,
893 };
894 ArcRefCell::new(BlockLevelBox::OutsideMarker(OutsideMarker {
895 context: IndependentFormattingContext::new(
896 LayoutBoxBase::new(info.into(), info.style.clone()),
897 IndependentFormattingContextContents::Flow(block_formatting_context),
898 self.propagated_data,
899 ),
900 list_item_style,
901 }))
902 },
903 BlockLevelCreator::AnonymousTable { table_block } => table_block,
904 };
905 self.box_slot
906 .set(LayoutBox::BlockLevel(block_level_box.clone()));
907 block_level_box
908 }
909}
910
911impl IntermediateBlockContainer {
912 fn finish(self, context: &LayoutContext, info: &NodeAndStyleInfo<'_>) -> BlockContainer {
913 match self {
914 IntermediateBlockContainer::Deferred {
915 contents,
916 propagated_data,
917 is_list_item,
918 } => BlockContainer::construct(context, info, contents, propagated_data, is_list_item),
919 IntermediateBlockContainer::InlineFormattingContext(block_container) => block_container,
920 }
921 }
922}
923
924#[cfg(test)]
925mod tests {
926 use super::*;
927
928 fn assert_first_letter_eq(text: &str, expected: &str) {
929 let range = first_letter_range(text);
930 assert_eq!(&text[range], expected);
931 }
932
933 #[test]
934 fn test_first_letter_range() {
935 assert_first_letter_eq("", "");
937 assert_first_letter_eq(" ", "");
938
939 assert_first_letter_eq("(", "");
941 assert_first_letter_eq(" (", "");
942 assert_first_letter_eq("( ", "");
943 assert_first_letter_eq("()", "");
944
945 assert_first_letter_eq("\u{0903}", "");
947
948 assert_first_letter_eq("A", "A");
950 assert_first_letter_eq(" A", "A");
951 assert_first_letter_eq("A ", "A");
952 assert_first_letter_eq(" A ", "A");
953
954 assert_first_letter_eq("App", "A");
956 assert_first_letter_eq(" App", "A");
957 assert_first_letter_eq("App ", "A");
958
959 assert_first_letter_eq(r#""A"#, r#""A"#);
961 assert_first_letter_eq(r#" "A"#, r#""A"#);
962 assert_first_letter_eq(r#""A "#, r#""A"#);
963 assert_first_letter_eq(r#"" A"#, r#"" A"#);
964 assert_first_letter_eq(r#" "A "#, r#""A"#);
965 assert_first_letter_eq(r#"("A"#, r#"("A"#);
966 assert_first_letter_eq(r#" ("A"#, r#"("A"#);
967 assert_first_letter_eq(r#"( "A"#, r#"( "A"#);
968 assert_first_letter_eq(r#"[ ( "A"#, r#"[ ( "A"#);
969
970 assert_first_letter_eq(r#"A""#, r#"A""#);
973 assert_first_letter_eq(r#"A" "#, r#"A""#);
974 assert_first_letter_eq(r#"A)]"#, r#"A)]"#);
975 assert_first_letter_eq(r#"A" )]"#, r#"A""#);
976 assert_first_letter_eq(r#"A)] >"#, r#"A)]"#);
977
978 assert_first_letter_eq(r#" ("A" )]"#, r#"("A""#);
980 assert_first_letter_eq(r#" ("A")] >"#, r#"("A")]"#);
981
982 assert_first_letter_eq("一", "一");
984 assert_first_letter_eq(" 一 ", "一");
985 assert_first_letter_eq("一二三", "一");
986 assert_first_letter_eq(" 一二三 ", "一");
987 assert_first_letter_eq("(一二三)", "(一");
988 assert_first_letter_eq(" (一二三) ", "(一");
989 assert_first_letter_eq("((一", "((一");
990 assert_first_letter_eq(" ( (一", "( (一");
991 assert_first_letter_eq("一)", "一)");
992 assert_first_letter_eq("一))", "一))");
993 assert_first_letter_eq("一) )", "一)");
994 }
995}