1use app_units::Au;
6use bitflags::bitflags;
7use fonts::{ByteIndex, FontMetrics, GlyphStore};
8use itertools::Either;
9use range::Range;
10use style::Zero;
11use style::computed_values::position::T as Position;
12use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
13use style::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword};
14use style::values::specified::align::AlignFlags;
15use style::values::specified::box_::DisplayOutside;
16use unicode_bidi::{BidiInfo, Level};
17use webrender_api::FontInstanceKey;
18
19use super::inline_box::{InlineBoxContainerState, InlineBoxIdentifier, InlineBoxTreePathToken};
20use super::{InlineFormattingContextLayout, LineBlockSizes, SharedInlineStyles, line_height};
21use crate::cell::ArcRefCell;
22use crate::fragment_tree::{BaseFragmentInfo, BoxFragment, Fragment, TextFragment};
23use crate::geom::{LogicalRect, LogicalVec2, PhysicalRect, ToLogical};
24use crate::positioned::{
25 AbsolutelyPositionedBox, PositioningContext, PositioningContextLength, relative_adjustement,
26};
27use crate::{ContainingBlock, ContainingBlockSize};
28
29pub(super) struct LineMetrics {
30 pub block_offset: Au,
33
34 pub block_size: Au,
36
37 pub baseline_block_offset: Au,
39}
40
41bitflags! {
42 struct LineLayoutInlineContainerFlags: u8 {
43 const HAD_ANY_LINE_ITEMS = 1 << 0;
46 const HAD_INLINE_START_PBM = 1 << 2;
49 const HAD_INLINE_END_PBM = 1 << 3;
52 const HAD_ANY_FLOATS = 1 << 4;
54 }
55}
56
57pub(super) struct LineItemLayoutInlineContainerState {
61 pub identifier: Option<InlineBoxIdentifier>,
64
65 pub fragments: Vec<(Fragment, LogicalRect<Au>)>,
70
71 pub inline_advance: Au,
73
74 flags: LineLayoutInlineContainerFlags,
76
77 pub parent_offset: LogicalVec2<Au>,
81
82 pub baseline_offset: Au,
86
87 pub positioning_context_or_start_offset_in_parent:
92 Either<PositioningContext, PositioningContextLength>,
93}
94
95impl LineItemLayoutInlineContainerState {
96 fn new(
97 identifier: Option<InlineBoxIdentifier>,
98 parent_offset: LogicalVec2<Au>,
99 baseline_offset: Au,
100 positioning_context_or_start_offset_in_parent: Either<
101 PositioningContext,
102 PositioningContextLength,
103 >,
104 ) -> Self {
105 Self {
106 identifier,
107 fragments: Vec::new(),
108 inline_advance: Au::zero(),
109 flags: LineLayoutInlineContainerFlags::empty(),
110 parent_offset,
111 baseline_offset,
112 positioning_context_or_start_offset_in_parent,
113 }
114 }
115
116 fn root(starting_inline_advance: Au, baseline_offset: Au) -> Self {
117 let mut state = Self::new(
118 None,
119 LogicalVec2::zero(),
120 baseline_offset,
121 Either::Right(PositioningContextLength::zero()),
122 );
123 state.inline_advance = starting_inline_advance;
124 state
125 }
126}
127
128pub(super) struct LineItemLayout<'layout_data, 'layout> {
132 layout: &'layout mut InlineFormattingContextLayout<'layout_data>,
134
135 pub state_stack: Vec<LineItemLayoutInlineContainerState>,
138
139 pub current_state: LineItemLayoutInlineContainerState,
141
142 pub line_metrics: LineMetrics,
145
146 pub justification_adjustment: Au,
149
150 is_phantom_line: bool,
153}
154
155impl LineItemLayout<'_, '_> {
156 pub(super) fn layout_line_items(
157 layout: &mut InlineFormattingContextLayout,
158 line_items: Vec<LineItem>,
159 start_position: LogicalVec2<Au>,
160 effective_block_advance: &LineBlockSizes,
161 justification_adjustment: Au,
162 is_phantom_line: bool,
163 ) -> Vec<Fragment> {
164 let baseline_offset = effective_block_advance.find_baseline_offset();
165 LineItemLayout {
166 layout,
167 state_stack: Vec::new(),
168 current_state: LineItemLayoutInlineContainerState::root(
169 start_position.inline,
170 baseline_offset,
171 ),
172 line_metrics: LineMetrics {
173 block_offset: start_position.block,
174 block_size: effective_block_advance.resolve(),
175 baseline_block_offset: baseline_offset,
176 },
177 justification_adjustment,
178 is_phantom_line,
179 }
180 .layout(line_items)
181 }
182
183 fn prepare_layout_for_inline_box(&mut self, new_inline_box: Option<InlineBoxIdentifier>) {
185 let Some(new_inline_box) = new_inline_box else {
187 while !self.state_stack.is_empty() {
188 self.end_inline_box();
189 }
190 return;
191 };
192
193 let path = self
196 .layout
197 .ifc
198 .inline_boxes
199 .get_path(self.current_state.identifier, new_inline_box);
200 for token in path {
201 match token {
202 InlineBoxTreePathToken::Start(ref identifier) => self.start_inline_box(identifier),
203 InlineBoxTreePathToken::End(_) => self.end_inline_box(),
204 }
205 }
206 }
207
208 pub(super) fn layout(&mut self, mut line_items: Vec<LineItem>) -> Vec<Fragment> {
209 let mut last_level = Level::ltr();
210 let levels: Vec<_> = line_items
211 .iter()
212 .map(|item| {
213 let level = match item {
214 LineItem::TextRun(_, text_run) => text_run.bidi_level,
215 LineItem::InlineStartBoxPaddingBorderMargin(_) => last_level,
219 LineItem::InlineEndBoxPaddingBorderMargin(_) => last_level,
220 LineItem::Atomic(_, atomic) => atomic.bidi_level,
221 LineItem::AbsolutelyPositioned(..) => last_level,
222 LineItem::Float(..) => {
223 last_level
226 },
227 };
228 last_level = level;
229 level
230 })
231 .collect();
232
233 if self.layout.ifc.has_right_to_left_content {
234 sort_by_indices_in_place(&mut line_items, BidiInfo::reorder_visual(&levels));
235 }
236
237 let line_item_iterator = if self
244 .layout
245 .containing_block
246 .style
247 .writing_mode
248 .is_bidi_ltr()
249 {
250 Either::Left(line_items.into_iter())
251 } else {
252 Either::Right(line_items.into_iter().rev())
253 };
254
255 for item in line_item_iterator.into_iter().by_ref() {
256 self.prepare_layout_for_inline_box(item.inline_box_identifier());
261
262 self.current_state
263 .flags
264 .insert(LineLayoutInlineContainerFlags::HAD_ANY_LINE_ITEMS);
265 match item {
266 LineItem::InlineStartBoxPaddingBorderMargin(_) => {
267 self.current_state
268 .flags
269 .insert(LineLayoutInlineContainerFlags::HAD_INLINE_START_PBM);
270 },
271 LineItem::InlineEndBoxPaddingBorderMargin(_) => {
272 self.current_state
273 .flags
274 .insert(LineLayoutInlineContainerFlags::HAD_INLINE_END_PBM);
275 },
276 LineItem::TextRun(_, text_run) => self.layout_text_run(text_run),
277 LineItem::Atomic(_, atomic) => self.layout_atomic(atomic),
278 LineItem::AbsolutelyPositioned(_, absolute) => self.layout_absolute(absolute),
279 LineItem::Float(_, float) => self.layout_float(float),
280 }
281 }
282
283 self.prepare_layout_for_inline_box(None);
285
286 let fragments_and_rectangles = std::mem::take(&mut self.current_state.fragments);
287 fragments_and_rectangles
288 .into_iter()
289 .map(|(mut fragment, logical_rect)| {
290 if matches!(fragment, Fragment::Float(_)) {
291 return fragment;
292 }
293
294 fragment.mutate_content_rect(|content_rect| {
298 *content_rect = logical_rect.as_physical(Some(self.layout.containing_block))
299 });
300
301 fragment
302 })
303 .collect()
304 }
305
306 fn current_positioning_context_mut(&mut self) -> &mut PositioningContext {
307 if let Either::Left(ref mut positioning_context) = self
308 .current_state
309 .positioning_context_or_start_offset_in_parent
310 {
311 return positioning_context;
312 }
313 self.state_stack
314 .iter_mut()
315 .rev()
316 .find_map(
317 |state| match state.positioning_context_or_start_offset_in_parent {
318 Either::Left(ref mut positioning_context) => Some(positioning_context),
319 Either::Right(_) => None,
320 },
321 )
322 .unwrap_or(self.layout.positioning_context)
323 }
324
325 fn start_inline_box(&mut self, identifier: &InlineBoxIdentifier) {
326 let inline_box_state =
327 &*self.layout.inline_box_states[identifier.index_in_inline_boxes as usize];
328 let inline_box = self.layout.ifc.inline_boxes.get(identifier);
329 let inline_box = &*(inline_box.borrow());
330
331 let space_above_baseline = inline_box_state.calculate_space_above_baseline();
332 let block_start_offset =
333 self.calculate_inline_box_block_start(inline_box_state, space_above_baseline);
334
335 let positioning_context_or_start_offset_in_parent =
336 match PositioningContext::new_for_layout_box_base(&inline_box.base) {
337 Some(positioning_context) => Either::Left(positioning_context),
338 None => Either::Right(self.current_positioning_context_mut().len()),
339 };
340
341 let parent_offset = LogicalVec2 {
342 inline: self.current_state.inline_advance + self.current_state.parent_offset.inline,
343 block: block_start_offset,
344 };
345
346 let outer_state = std::mem::replace(
347 &mut self.current_state,
348 LineItemLayoutInlineContainerState::new(
349 Some(*identifier),
350 parent_offset,
351 block_start_offset + space_above_baseline,
352 positioning_context_or_start_offset_in_parent,
353 ),
354 );
355
356 self.state_stack.push(outer_state);
357 }
358
359 fn end_inline_box(&mut self) {
360 let outer_state = self.state_stack.pop().expect("Ended unknown inline box");
361 let inner_state = std::mem::replace(&mut self.current_state, outer_state);
362
363 let identifier = inner_state.identifier.expect("Ended unknown inline box");
364 let inline_box_state =
365 &*self.layout.inline_box_states[identifier.index_in_inline_boxes as usize];
366 let inline_box = self.layout.ifc.inline_boxes.get(&identifier);
367 let inline_box = &*(inline_box.borrow());
368
369 let mut padding = inline_box_state.pbm.padding;
370 let mut border = inline_box_state.pbm.border;
371 let mut margin = inline_box_state.pbm.margin.auto_is(Au::zero);
372
373 let mut had_start = inner_state
374 .flags
375 .contains(LineLayoutInlineContainerFlags::HAD_INLINE_START_PBM);
376 let mut had_end = inner_state
377 .flags
378 .contains(LineLayoutInlineContainerFlags::HAD_INLINE_END_PBM);
379
380 let containing_block_writing_mode = self.layout.containing_block.style.writing_mode;
381 if containing_block_writing_mode.is_bidi_ltr() !=
382 inline_box.base.style.writing_mode.is_bidi_ltr()
383 {
384 std::mem::swap(&mut had_start, &mut had_end)
385 }
386
387 if !had_start {
388 padding.inline_start = Au::zero();
389 border.inline_start = Au::zero();
390 margin.inline_start = Au::zero();
391 }
392 if !had_end {
393 padding.inline_end = Au::zero();
394 border.inline_end = Au::zero();
395 margin.inline_end = Au::zero();
396 }
397 let pbm_sums = padding + border + margin;
411 if inner_state.fragments.is_empty() &&
412 !had_start &&
413 pbm_sums.inline_end.is_zero() &&
414 margin.inline_end.is_zero()
415 {
416 return;
417 }
418
419 let mut content_rect = LogicalRect {
421 start_corner: LogicalVec2 {
422 inline: self.current_state.inline_advance + pbm_sums.inline_start,
423 block: inner_state.parent_offset.block - self.current_state.parent_offset.block,
424 },
425 size: LogicalVec2 {
426 inline: inner_state.inline_advance,
427 block: if self.is_phantom_line {
428 Au::zero()
429 } else {
430 inline_box_state.base.font_metrics.line_gap
431 },
432 },
433 };
434
435 let style = &inline_box.base.style;
438 if style.get_box().position == Position::Relative {
439 content_rect.start_corner += relative_adjustement(style, self.layout.containing_block);
440 }
441
442 let ifc_writing_mode = self.layout.containing_block.style.writing_mode;
443 let inline_box_containing_block = ContainingBlock {
444 size: ContainingBlockSize {
445 inline: content_rect.size.inline,
446 block: Default::default(),
447 },
448 style: self.layout.containing_block.style,
449 };
450 let fragments = inner_state
451 .fragments
452 .into_iter()
453 .map(|(mut fragment, logical_rect)| {
454 let is_float = matches!(fragment, Fragment::Float(_));
455 fragment.mutate_content_rect(|content_rect| {
456 if is_float {
457 content_rect.origin -=
458 pbm_sums.start_offset().to_physical_size(ifc_writing_mode);
459 } else {
460 *content_rect = logical_rect.as_physical(Some(&inline_box_containing_block))
464 }
465 });
466 fragment
467 })
468 .collect();
469
470 let physical_content_rect = content_rect.as_physical(Some(self.layout.containing_block));
473 let mut fragment = BoxFragment::new(
474 inline_box.base.base_fragment_info,
475 style.clone(),
476 fragments,
477 physical_content_rect,
478 padding.to_physical(ifc_writing_mode),
479 border.to_physical(ifc_writing_mode),
480 margin.to_physical(ifc_writing_mode),
481 None, );
483
484 let offset_from_parent_ifc = LogicalVec2 {
485 inline: pbm_sums.inline_start + self.current_state.inline_advance,
486 block: content_rect.start_corner.block,
487 }
488 .to_physical_vector(self.layout.containing_block.style.writing_mode);
489
490 match inner_state.positioning_context_or_start_offset_in_parent {
491 Either::Left(mut positioning_context) => {
492 positioning_context
493 .layout_collected_children(self.layout.layout_context, &mut fragment);
494 positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
495 &offset_from_parent_ifc,
496 PositioningContextLength::zero(),
497 );
498 self.current_positioning_context_mut()
499 .append(positioning_context);
500 },
501 Either::Right(start_offset) => {
502 self.current_positioning_context_mut()
503 .adjust_static_position_of_hoisted_fragments_with_offset(
504 &offset_from_parent_ifc,
505 start_offset,
506 );
507 },
508 }
509
510 self.current_state.inline_advance += inner_state.inline_advance + pbm_sums.inline_sum();
511
512 let fragment = Fragment::Box(ArcRefCell::new(fragment));
513 inline_box.base.add_fragment(fragment.clone());
514
515 self.current_state.fragments.push((fragment, content_rect));
516 }
517
518 fn calculate_inline_box_block_start(
519 &self,
520 inline_box_state: &InlineBoxContainerState,
521 space_above_baseline: Au,
522 ) -> Au {
523 if self.is_phantom_line {
524 return Au::zero();
525 };
526 let font_metrics = &inline_box_state.base.font_metrics;
527 let style = &inline_box_state.base.style;
528 let line_gap = font_metrics.line_gap;
529
530 match inline_box_state.base.style.clone_vertical_align() {
533 GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) => {
534 let line_height = line_height(style, font_metrics, &inline_box_state.base.flags);
535 (line_height - line_gap).scale_by(0.5)
536 },
537 GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => {
538 let line_height = line_height(style, font_metrics, &inline_box_state.base.flags);
539 let half_leading = (line_height - line_gap).scale_by(0.5);
540 self.line_metrics.block_size - line_height + half_leading
541 },
542 _ => {
543 self.line_metrics.baseline_block_offset + inline_box_state.base.baseline_offset -
544 space_above_baseline
545 },
546 }
547 }
548
549 fn layout_text_run(&mut self, text_item: TextRunLineItem) {
550 if text_item.text.is_empty() {
551 return;
552 }
553
554 let mut number_of_justification_opportunities = 0;
555 let mut inline_advance = text_item
556 .text
557 .iter()
558 .map(|glyph_store| {
559 number_of_justification_opportunities += glyph_store.total_word_separators();
560 glyph_store.total_advance()
561 })
562 .sum();
563
564 if !self.justification_adjustment.is_zero() {
565 inline_advance += self
566 .justification_adjustment
567 .scale_by(number_of_justification_opportunities as f32);
568 }
569
570 let start_corner = LogicalVec2 {
574 inline: self.current_state.inline_advance,
575 block: self.current_state.baseline_offset -
576 text_item.font_metrics.ascent -
577 self.current_state.parent_offset.block,
578 };
579 let content_rect = LogicalRect {
580 start_corner,
581 size: LogicalVec2 {
582 block: text_item.font_metrics.line_gap,
583 inline: inline_advance,
584 },
585 };
586
587 self.current_state.inline_advance += inline_advance;
588 self.current_state.fragments.push((
589 Fragment::Text(ArcRefCell::new(TextFragment {
590 base: text_item.base_fragment_info.into(),
591 inline_styles: text_item.inline_styles.clone(),
592 rect: PhysicalRect::zero(),
593 font_metrics: text_item.font_metrics,
594 font_key: text_item.font_key,
595 glyphs: text_item.text,
596 justification_adjustment: self.justification_adjustment,
597 selection_range: text_item.selection_range,
598 })),
599 content_rect,
600 ));
601 }
602
603 fn layout_atomic(&mut self, atomic: AtomicLineItem) {
604 let ifc_writing_mode = self.layout.containing_block.style.writing_mode;
609 let content_rect = {
610 let block_start = atomic.calculate_block_start(&self.line_metrics);
611 let atomic_fragment = atomic.fragment.borrow_mut();
612 let padding_border_margin_sides = atomic_fragment
613 .padding_border_margin()
614 .to_logical(ifc_writing_mode);
615
616 let mut atomic_offset = LogicalVec2 {
617 inline: self.current_state.inline_advance +
618 padding_border_margin_sides.inline_start,
619 block: block_start - self.current_state.parent_offset.block +
620 padding_border_margin_sides.block_start,
621 };
622
623 if atomic_fragment.style.get_box().position == Position::Relative {
624 atomic_offset +=
625 relative_adjustement(&atomic_fragment.style, self.layout.containing_block);
626 }
627
628 LogicalRect {
631 start_corner: atomic_offset,
632 size: atomic_fragment
633 .content_rect
634 .size
635 .to_logical(ifc_writing_mode),
636 }
637 };
638
639 if let Some(mut positioning_context) = atomic.positioning_context {
640 let physical_rect_as_if_in_root =
641 content_rect.as_physical(Some(self.layout.containing_block));
642 positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
643 &physical_rect_as_if_in_root.origin.to_vector(),
644 PositioningContextLength::zero(),
645 );
646
647 self.current_positioning_context_mut()
648 .append(positioning_context);
649 }
650
651 self.current_state.inline_advance += atomic.size.inline;
652
653 self.current_state
654 .fragments
655 .push((Fragment::Box(atomic.fragment), content_rect));
656 }
657
658 fn layout_absolute(&mut self, absolute: AbsolutelyPositionedLineItem) {
659 let absolutely_positioned_box = (*absolute.absolutely_positioned_box).borrow();
660 let style = absolutely_positioned_box.context.style();
661
662 let initial_start_corner =
675 if style.get_box().original_display.outside() == DisplayOutside::Inline {
676 LogicalVec2 {
678 inline: self.current_state.inline_advance,
679 block: -self.current_state.parent_offset.block,
680 }
681 } else {
682 LogicalVec2 {
684 inline: -self.current_state.parent_offset.inline,
685 block: self.line_metrics.block_size - self.current_state.parent_offset.block,
686 }
687 };
688
689 let static_position_rect = LogicalRect {
692 start_corner: initial_start_corner,
693 size: LogicalVec2::zero(),
694 }
695 .as_physical(Some(self.layout.containing_block));
696
697 let hoisted_box = AbsolutelyPositionedBox::to_hoisted(
698 absolute.absolutely_positioned_box.clone(),
699 static_position_rect,
700 LogicalVec2 {
701 inline: AlignFlags::START,
702 block: AlignFlags::START,
703 },
704 self.layout.containing_block.style.writing_mode,
705 );
706
707 let hoisted_fragment = hoisted_box.fragment.clone();
708 self.current_positioning_context_mut().push(hoisted_box);
709 self.current_state.fragments.push((
710 Fragment::AbsoluteOrFixedPositioned(hoisted_fragment),
711 LogicalRect::zero(),
712 ));
713 }
714
715 fn layout_float(&mut self, float: FloatLineItem) {
716 self.current_state
717 .flags
718 .insert(LineLayoutInlineContainerFlags::HAD_ANY_FLOATS);
719
720 let distance_from_parent_to_ifc = LogicalVec2 {
726 inline: self.current_state.parent_offset.inline,
727 block: self.line_metrics.block_offset + self.current_state.parent_offset.block,
728 };
729 float.fragment.borrow_mut().content_rect.origin -= distance_from_parent_to_ifc
730 .to_physical_size(self.layout.containing_block.style.writing_mode);
731
732 self.current_state
733 .fragments
734 .push((Fragment::Float(float.fragment), LogicalRect::zero()));
735 }
736}
737
738pub(super) enum LineItem {
739 InlineStartBoxPaddingBorderMargin(InlineBoxIdentifier),
740 InlineEndBoxPaddingBorderMargin(InlineBoxIdentifier),
741 TextRun(Option<InlineBoxIdentifier>, TextRunLineItem),
742 Atomic(Option<InlineBoxIdentifier>, AtomicLineItem),
743 AbsolutelyPositioned(Option<InlineBoxIdentifier>, AbsolutelyPositionedLineItem),
744 Float(Option<InlineBoxIdentifier>, FloatLineItem),
745}
746
747impl LineItem {
748 fn inline_box_identifier(&self) -> Option<InlineBoxIdentifier> {
749 match self {
750 LineItem::InlineStartBoxPaddingBorderMargin(identifier) => Some(*identifier),
751 LineItem::InlineEndBoxPaddingBorderMargin(identifier) => Some(*identifier),
752 LineItem::TextRun(identifier, _) => *identifier,
753 LineItem::Atomic(identifier, _) => *identifier,
754 LineItem::AbsolutelyPositioned(identifier, _) => *identifier,
755 LineItem::Float(identifier, _) => *identifier,
756 }
757 }
758
759 pub(super) fn trim_whitespace_at_end(&mut self, whitespace_trimmed: &mut Au) -> bool {
760 match self {
761 LineItem::InlineStartBoxPaddingBorderMargin(_) => true,
762 LineItem::InlineEndBoxPaddingBorderMargin(_) => true,
763 LineItem::TextRun(_, item) => item.trim_whitespace_at_end(whitespace_trimmed),
764 LineItem::Atomic(..) => false,
765 LineItem::AbsolutelyPositioned(..) => true,
766 LineItem::Float(..) => true,
767 }
768 }
769
770 pub(super) fn trim_whitespace_at_start(&mut self, whitespace_trimmed: &mut Au) -> bool {
771 match self {
772 LineItem::InlineStartBoxPaddingBorderMargin(_) => true,
773 LineItem::InlineEndBoxPaddingBorderMargin(_) => true,
774 LineItem::TextRun(_, item) => item.trim_whitespace_at_start(whitespace_trimmed),
775 LineItem::Atomic(..) => false,
776 LineItem::AbsolutelyPositioned(..) => true,
777 LineItem::Float(..) => true,
778 }
779 }
780}
781
782pub(super) struct TextRunLineItem {
783 pub base_fragment_info: BaseFragmentInfo,
784 pub inline_styles: SharedInlineStyles,
785 pub text: Vec<std::sync::Arc<GlyphStore>>,
786 pub font_metrics: FontMetrics,
787 pub font_key: FontInstanceKey,
788 pub bidi_level: Level,
790 pub selection_range: Option<Range<ByteIndex>>,
791}
792
793impl TextRunLineItem {
794 fn trim_whitespace_at_end(&mut self, whitespace_trimmed: &mut Au) -> bool {
795 if matches!(
796 self.inline_styles
797 .style
798 .borrow()
799 .get_inherited_text()
800 .white_space_collapse,
801 WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
802 ) {
803 return false;
804 }
805
806 let index_of_last_non_whitespace = self
807 .text
808 .iter()
809 .rev()
810 .position(|glyph| !glyph.is_whitespace())
811 .map(|offset_from_end| self.text.len() - offset_from_end);
812
813 let first_whitespace_index = index_of_last_non_whitespace.unwrap_or(0);
814 *whitespace_trimmed += self
815 .text
816 .drain(first_whitespace_index..)
817 .map(|glyph| glyph.total_advance())
818 .sum();
819
820 index_of_last_non_whitespace.is_none()
822 }
823
824 fn trim_whitespace_at_start(&mut self, whitespace_trimmed: &mut Au) -> bool {
825 if matches!(
826 self.inline_styles
827 .style
828 .borrow()
829 .get_inherited_text()
830 .white_space_collapse,
831 WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
832 ) {
833 return false;
834 }
835
836 let index_of_first_non_whitespace = self
837 .text
838 .iter()
839 .position(|glyph| !glyph.is_whitespace())
840 .unwrap_or(self.text.len());
841
842 *whitespace_trimmed += self
843 .text
844 .drain(0..index_of_first_non_whitespace)
845 .map(|glyph| glyph.total_advance())
846 .sum();
847
848 self.text.is_empty()
850 }
851
852 pub(crate) fn can_merge(&self, font_key: FontInstanceKey, bidi_level: Level) -> bool {
853 self.font_key == font_key && self.bidi_level == bidi_level
854 }
855}
856
857pub(super) struct AtomicLineItem {
858 pub fragment: ArcRefCell<BoxFragment>,
859 pub size: LogicalVec2<Au>,
860 pub positioning_context: Option<PositioningContext>,
861
862 pub baseline_offset_in_parent: Au,
866
867 pub baseline_offset_in_item: Au,
869
870 pub bidi_level: Level,
872}
873
874impl AtomicLineItem {
875 fn calculate_block_start(&self, line_metrics: &LineMetrics) -> Au {
878 match self.fragment.borrow().style.clone_vertical_align() {
879 GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) => Au::zero(),
880 GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => {
881 line_metrics.block_size - self.size.block
882 },
883
884 _ => {
886 let baseline = line_metrics.baseline_block_offset + self.baseline_offset_in_parent;
887 baseline - self.baseline_offset_in_item
888 },
889 }
890 }
891}
892
893pub(super) struct AbsolutelyPositionedLineItem {
894 pub absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>,
895}
896
897pub(super) struct FloatLineItem {
898 pub fragment: ArcRefCell<BoxFragment>,
899 pub needs_placement: bool,
903}
904
905fn sort_by_indices_in_place<T>(data: &mut [T], mut indices: Vec<usize>) {
908 for idx in 0..data.len() {
909 if indices[idx] == idx {
910 continue;
911 }
912
913 let mut current_idx = idx;
914 loop {
915 let target_idx = indices[current_idx];
916 indices[current_idx] = current_idx;
917 if indices[target_idx] == target_idx {
918 break;
919 }
920 data.swap(current_idx, target_idx);
921 current_idx = target_idx;
922 }
923 }
924}