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::properties::ComputedValues;
14use style::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword};
15use style::values::generics::font::LineHeight;
16use style::values::specified::align::AlignFlags;
17use style::values::specified::box_::DisplayOutside;
18use unicode_bidi::{BidiInfo, Level};
19use webrender_api::FontInstanceKey;
20
21use super::inline_box::{InlineBoxContainerState, InlineBoxIdentifier, InlineBoxTreePathToken};
22use super::{InlineFormattingContextLayout, LineBlockSizes, SharedInlineStyles};
23use crate::cell::ArcRefCell;
24use crate::fragment_tree::{BaseFragmentInfo, BoxFragment, Fragment, TextFragment};
25use crate::geom::{LogicalRect, LogicalVec2, PhysicalRect, ToLogical};
26use crate::positioned::{
27 AbsolutelyPositionedBox, PositioningContext, PositioningContextLength, relative_adjustement,
28};
29use crate::{ContainingBlock, ContainingBlockSize};
30
31pub(super) struct LineMetrics {
32 pub block_offset: Au,
35
36 pub block_size: Au,
38
39 pub baseline_block_offset: Au,
41}
42
43bitflags! {
44 struct LineLayoutInlineContainerFlags: u8 {
45 const HAD_ANY_LINE_ITEMS = 1 << 0;
48 const HAD_INLINE_START_PBM = 1 << 2;
51 const HAD_INLINE_END_PBM = 1 << 3;
54 const HAD_ANY_FLOATS = 1 << 4;
56 }
57}
58
59pub(super) struct LineItemLayoutInlineContainerState {
63 pub identifier: Option<InlineBoxIdentifier>,
66
67 pub fragments: Vec<(Fragment, LogicalRect<Au>)>,
72
73 pub inline_advance: Au,
75
76 flags: LineLayoutInlineContainerFlags,
78
79 pub parent_offset: LogicalVec2<Au>,
83
84 pub baseline_offset: Au,
88
89 pub positioning_context_or_start_offset_in_parent:
94 Either<PositioningContext, PositioningContextLength>,
95}
96
97impl LineItemLayoutInlineContainerState {
98 fn new(
99 identifier: Option<InlineBoxIdentifier>,
100 parent_offset: LogicalVec2<Au>,
101 baseline_offset: Au,
102 positioning_context_or_start_offset_in_parent: Either<
103 PositioningContext,
104 PositioningContextLength,
105 >,
106 ) -> Self {
107 Self {
108 identifier,
109 fragments: Vec::new(),
110 inline_advance: Au::zero(),
111 flags: LineLayoutInlineContainerFlags::empty(),
112 parent_offset,
113 baseline_offset,
114 positioning_context_or_start_offset_in_parent,
115 }
116 }
117
118 fn root(starting_inline_advance: Au, baseline_offset: Au) -> Self {
119 let mut state = Self::new(
120 None,
121 LogicalVec2::zero(),
122 baseline_offset,
123 Either::Right(PositioningContextLength::zero()),
124 );
125 state.inline_advance = starting_inline_advance;
126 state
127 }
128}
129
130pub(super) struct LineItemLayout<'layout_data, 'layout> {
134 layout: &'layout mut InlineFormattingContextLayout<'layout_data>,
136
137 pub state_stack: Vec<LineItemLayoutInlineContainerState>,
140
141 pub current_state: LineItemLayoutInlineContainerState,
143
144 pub line_metrics: LineMetrics,
147
148 pub justification_adjustment: Au,
151}
152
153impl LineItemLayout<'_, '_> {
154 pub(super) fn layout_line_items(
155 layout: &mut InlineFormattingContextLayout,
156 line_items: Vec<LineItem>,
157 start_position: LogicalVec2<Au>,
158 effective_block_advance: &LineBlockSizes,
159 justification_adjustment: Au,
160 ) -> Vec<Fragment> {
161 let baseline_offset = effective_block_advance.find_baseline_offset();
162 LineItemLayout {
163 layout,
164 state_stack: Vec::new(),
165 current_state: LineItemLayoutInlineContainerState::root(
166 start_position.inline,
167 baseline_offset,
168 ),
169 line_metrics: LineMetrics {
170 block_offset: start_position.block,
171 block_size: effective_block_advance.resolve(),
172 baseline_block_offset: baseline_offset,
173 },
174 justification_adjustment,
175 }
176 .layout(line_items)
177 }
178
179 fn prepare_layout_for_inline_box(&mut self, new_inline_box: Option<InlineBoxIdentifier>) {
181 let Some(new_inline_box) = new_inline_box else {
183 while !self.state_stack.is_empty() {
184 self.end_inline_box();
185 }
186 return;
187 };
188
189 let path = self
192 .layout
193 .ifc
194 .inline_boxes
195 .get_path(self.current_state.identifier, new_inline_box);
196 for token in path {
197 match token {
198 InlineBoxTreePathToken::Start(ref identifier) => self.start_inline_box(identifier),
199 InlineBoxTreePathToken::End(_) => self.end_inline_box(),
200 }
201 }
202 }
203
204 pub(super) fn layout(&mut self, mut line_items: Vec<LineItem>) -> Vec<Fragment> {
205 let mut last_level = Level::ltr();
206 let levels: Vec<_> = line_items
207 .iter()
208 .map(|item| {
209 let level = match item {
210 LineItem::TextRun(_, text_run) => text_run.bidi_level,
211 LineItem::InlineStartBoxPaddingBorderMargin(_) => last_level,
215 LineItem::InlineEndBoxPaddingBorderMargin(_) => last_level,
216 LineItem::Atomic(_, atomic) => atomic.bidi_level,
217 LineItem::AbsolutelyPositioned(..) => last_level,
218 LineItem::Float(..) => {
219 last_level
222 },
223 };
224 last_level = level;
225 level
226 })
227 .collect();
228
229 if self.layout.ifc.has_right_to_left_content {
230 sort_by_indices_in_place(&mut line_items, BidiInfo::reorder_visual(&levels));
231 }
232
233 let line_item_iterator = if self
240 .layout
241 .containing_block
242 .style
243 .writing_mode
244 .is_bidi_ltr()
245 {
246 Either::Left(line_items.into_iter())
247 } else {
248 Either::Right(line_items.into_iter().rev())
249 };
250
251 for item in line_item_iterator.into_iter().by_ref() {
252 self.prepare_layout_for_inline_box(item.inline_box_identifier());
257
258 self.current_state
259 .flags
260 .insert(LineLayoutInlineContainerFlags::HAD_ANY_LINE_ITEMS);
261 match item {
262 LineItem::InlineStartBoxPaddingBorderMargin(_) => {
263 self.current_state
264 .flags
265 .insert(LineLayoutInlineContainerFlags::HAD_INLINE_START_PBM);
266 },
267 LineItem::InlineEndBoxPaddingBorderMargin(_) => {
268 self.current_state
269 .flags
270 .insert(LineLayoutInlineContainerFlags::HAD_INLINE_END_PBM);
271 },
272 LineItem::TextRun(_, text_run) => self.layout_text_run(text_run),
273 LineItem::Atomic(_, atomic) => self.layout_atomic(atomic),
274 LineItem::AbsolutelyPositioned(_, absolute) => self.layout_absolute(absolute),
275 LineItem::Float(_, float) => self.layout_float(float),
276 }
277 }
278
279 self.prepare_layout_for_inline_box(None);
281
282 let fragments_and_rectangles = std::mem::take(&mut self.current_state.fragments);
283 fragments_and_rectangles
284 .into_iter()
285 .map(|(mut fragment, logical_rect)| {
286 if matches!(fragment, Fragment::Float(_)) {
287 return fragment;
288 }
289
290 fragment.mutate_content_rect(|content_rect| {
294 *content_rect = logical_rect.as_physical(Some(self.layout.containing_block))
295 });
296
297 fragment
298 })
299 .collect()
300 }
301
302 fn current_positioning_context_mut(&mut self) -> &mut PositioningContext {
303 if let Either::Left(ref mut positioning_context) = self
304 .current_state
305 .positioning_context_or_start_offset_in_parent
306 {
307 return positioning_context;
308 }
309 self.state_stack
310 .iter_mut()
311 .rev()
312 .find_map(
313 |state| match state.positioning_context_or_start_offset_in_parent {
314 Either::Left(ref mut positioning_context) => Some(positioning_context),
315 Either::Right(_) => None,
316 },
317 )
318 .unwrap_or(self.layout.positioning_context)
319 }
320
321 fn start_inline_box(&mut self, identifier: &InlineBoxIdentifier) {
322 let inline_box_state =
323 &*self.layout.inline_box_states[identifier.index_in_inline_boxes as usize];
324 let inline_box = self.layout.ifc.inline_boxes.get(identifier);
325 let inline_box = &*(inline_box.borrow());
326
327 let space_above_baseline = inline_box_state.calculate_space_above_baseline();
328 let block_start_offset =
329 self.calculate_inline_box_block_start(inline_box_state, space_above_baseline);
330
331 let positioning_context_or_start_offset_in_parent =
332 match PositioningContext::new_for_layout_box_base(&inline_box.base) {
333 Some(positioning_context) => Either::Left(positioning_context),
334 None => Either::Right(self.current_positioning_context_mut().len()),
335 };
336
337 let parent_offset = LogicalVec2 {
338 inline: self.current_state.inline_advance + self.current_state.parent_offset.inline,
339 block: block_start_offset,
340 };
341
342 let outer_state = std::mem::replace(
343 &mut self.current_state,
344 LineItemLayoutInlineContainerState::new(
345 Some(*identifier),
346 parent_offset,
347 block_start_offset + space_above_baseline,
348 positioning_context_or_start_offset_in_parent,
349 ),
350 );
351
352 self.state_stack.push(outer_state);
353 }
354
355 fn end_inline_box(&mut self) {
356 let outer_state = self.state_stack.pop().expect("Ended unknown inline box");
357 let inner_state = std::mem::replace(&mut self.current_state, outer_state);
358
359 let identifier = inner_state.identifier.expect("Ended unknown inline box");
360 let inline_box_state =
361 &*self.layout.inline_box_states[identifier.index_in_inline_boxes as usize];
362 let inline_box = self.layout.ifc.inline_boxes.get(&identifier);
363 let inline_box = &*(inline_box.borrow());
364
365 let mut padding = inline_box_state.pbm.padding;
366 let mut border = inline_box_state.pbm.border;
367 let mut margin = inline_box_state.pbm.margin.auto_is(Au::zero);
368
369 let mut had_start = inner_state
370 .flags
371 .contains(LineLayoutInlineContainerFlags::HAD_INLINE_START_PBM);
372 let mut had_end = inner_state
373 .flags
374 .contains(LineLayoutInlineContainerFlags::HAD_INLINE_END_PBM);
375
376 let containing_block_writing_mode = self.layout.containing_block.style.writing_mode;
377 if containing_block_writing_mode.is_bidi_ltr() !=
378 inline_box.base.style.writing_mode.is_bidi_ltr()
379 {
380 std::mem::swap(&mut had_start, &mut had_end)
381 }
382
383 if !had_start {
384 padding.inline_start = Au::zero();
385 border.inline_start = Au::zero();
386 margin.inline_start = Au::zero();
387 }
388 if !had_end {
389 padding.inline_end = Au::zero();
390 border.inline_end = Au::zero();
391 margin.inline_end = Au::zero();
392 }
393 let pbm_sums = padding + border + margin;
400 if inner_state.fragments.is_empty() && !had_start && pbm_sums.inline_sum().is_zero() {
401 return;
402 }
403
404 let mut content_rect = LogicalRect {
406 start_corner: LogicalVec2 {
407 inline: self.current_state.inline_advance + pbm_sums.inline_start,
408 block: inner_state.parent_offset.block - self.current_state.parent_offset.block,
409 },
410 size: LogicalVec2 {
411 inline: inner_state.inline_advance,
412 block: inline_box_state.base.font_metrics.line_gap,
413 },
414 };
415
416 let style = &inline_box.base.style;
419 if style.get_box().position == Position::Relative {
420 content_rect.start_corner += relative_adjustement(style, self.layout.containing_block);
421 }
422
423 let ifc_writing_mode = self.layout.containing_block.style.writing_mode;
424 let inline_box_containing_block = ContainingBlock {
425 size: ContainingBlockSize {
426 inline: content_rect.size.inline,
427 block: Default::default(),
428 },
429 style: self.layout.containing_block.style,
430 };
431 let fragments = inner_state
432 .fragments
433 .into_iter()
434 .map(|(mut fragment, logical_rect)| {
435 let is_float = matches!(fragment, Fragment::Float(_));
436 fragment.mutate_content_rect(|content_rect| {
437 if is_float {
438 content_rect.origin -=
439 pbm_sums.start_offset().to_physical_size(ifc_writing_mode);
440 } else {
441 *content_rect = logical_rect.as_physical(Some(&inline_box_containing_block))
445 }
446 });
447 fragment
448 })
449 .collect();
450
451 let physical_content_rect = content_rect.as_physical(Some(self.layout.containing_block));
454 let mut fragment = BoxFragment::new(
455 inline_box.base.base_fragment_info,
456 style.clone(),
457 fragments,
458 physical_content_rect,
459 padding.to_physical(ifc_writing_mode),
460 border.to_physical(ifc_writing_mode),
461 margin.to_physical(ifc_writing_mode),
462 None, );
464
465 let offset_from_parent_ifc = LogicalVec2 {
466 inline: pbm_sums.inline_start + self.current_state.inline_advance,
467 block: content_rect.start_corner.block,
468 }
469 .to_physical_vector(self.layout.containing_block.style.writing_mode);
470
471 match inner_state.positioning_context_or_start_offset_in_parent {
472 Either::Left(mut positioning_context) => {
473 positioning_context
474 .layout_collected_children(self.layout.layout_context, &mut fragment);
475 positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
476 &offset_from_parent_ifc,
477 PositioningContextLength::zero(),
478 );
479 self.current_positioning_context_mut()
480 .append(positioning_context);
481 },
482 Either::Right(start_offset) => {
483 self.current_positioning_context_mut()
484 .adjust_static_position_of_hoisted_fragments_with_offset(
485 &offset_from_parent_ifc,
486 start_offset,
487 );
488 },
489 }
490
491 self.current_state.inline_advance += inner_state.inline_advance + pbm_sums.inline_sum();
492
493 let fragment = Fragment::Box(ArcRefCell::new(fragment));
494 inline_box.base.add_fragment(fragment.clone());
495
496 self.current_state.fragments.push((fragment, content_rect));
497 }
498
499 fn calculate_inline_box_block_start(
500 &self,
501 inline_box_state: &InlineBoxContainerState,
502 space_above_baseline: Au,
503 ) -> Au {
504 let font_metrics = &inline_box_state.base.font_metrics;
505 let style = &inline_box_state.base.style;
506 let line_gap = font_metrics.line_gap;
507
508 match inline_box_state.base.style.clone_vertical_align() {
511 GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) => {
512 let line_height: Au = line_height(style, font_metrics);
513 (line_height - line_gap).scale_by(0.5)
514 },
515 GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => {
516 let line_height: Au = line_height(style, font_metrics);
517 let half_leading = (line_height - line_gap).scale_by(0.5);
518 self.line_metrics.block_size - line_height + half_leading
519 },
520 _ => {
521 self.line_metrics.baseline_block_offset + inline_box_state.base.baseline_offset -
522 space_above_baseline
523 },
524 }
525 }
526
527 fn layout_text_run(&mut self, text_item: TextRunLineItem) {
528 if text_item.text.is_empty() {
529 return;
530 }
531
532 let mut number_of_justification_opportunities = 0;
533 let mut inline_advance = text_item
534 .text
535 .iter()
536 .map(|glyph_store| {
537 number_of_justification_opportunities += glyph_store.total_word_separators();
538 glyph_store.total_advance()
539 })
540 .sum();
541
542 if !self.justification_adjustment.is_zero() {
543 inline_advance += self
544 .justification_adjustment
545 .scale_by(number_of_justification_opportunities as f32);
546 }
547
548 let start_corner = LogicalVec2 {
552 inline: self.current_state.inline_advance,
553 block: self.current_state.baseline_offset -
554 text_item.font_metrics.ascent -
555 self.current_state.parent_offset.block,
556 };
557 let content_rect = LogicalRect {
558 start_corner,
559 size: LogicalVec2 {
560 block: text_item.font_metrics.line_gap,
561 inline: inline_advance,
562 },
563 };
564
565 self.current_state.inline_advance += inline_advance;
566 self.current_state.fragments.push((
567 Fragment::Text(ArcRefCell::new(TextFragment {
568 base: text_item.base_fragment_info.into(),
569 inline_styles: text_item.inline_styles.clone(),
570 rect: PhysicalRect::zero(),
571 font_metrics: text_item.font_metrics,
572 font_key: text_item.font_key,
573 glyphs: text_item.text,
574 justification_adjustment: self.justification_adjustment,
575 selection_range: text_item.selection_range,
576 })),
577 content_rect,
578 ));
579 }
580
581 fn layout_atomic(&mut self, atomic: AtomicLineItem) {
582 let ifc_writing_mode = self.layout.containing_block.style.writing_mode;
587 let content_rect = {
588 let block_start = atomic.calculate_block_start(&self.line_metrics);
589 let atomic_fragment = atomic.fragment.borrow_mut();
590 let padding_border_margin_sides = atomic_fragment
591 .padding_border_margin()
592 .to_logical(ifc_writing_mode);
593
594 let mut atomic_offset = LogicalVec2 {
595 inline: self.current_state.inline_advance +
596 padding_border_margin_sides.inline_start,
597 block: block_start - self.current_state.parent_offset.block +
598 padding_border_margin_sides.block_start,
599 };
600
601 if atomic_fragment.style.get_box().position == Position::Relative {
602 atomic_offset +=
603 relative_adjustement(&atomic_fragment.style, self.layout.containing_block);
604 }
605
606 LogicalRect {
609 start_corner: atomic_offset,
610 size: atomic_fragment
611 .content_rect
612 .size
613 .to_logical(ifc_writing_mode),
614 }
615 };
616
617 if let Some(mut positioning_context) = atomic.positioning_context {
618 let physical_rect_as_if_in_root =
619 content_rect.as_physical(Some(self.layout.containing_block));
620 positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
621 &physical_rect_as_if_in_root.origin.to_vector(),
622 PositioningContextLength::zero(),
623 );
624
625 self.current_positioning_context_mut()
626 .append(positioning_context);
627 }
628
629 self.current_state.inline_advance += atomic.size.inline;
630
631 self.current_state
632 .fragments
633 .push((Fragment::Box(atomic.fragment), content_rect));
634 }
635
636 fn layout_absolute(&mut self, absolute: AbsolutelyPositionedLineItem) {
637 let absolutely_positioned_box = (*absolute.absolutely_positioned_box).borrow();
638 let style = absolutely_positioned_box.context.style();
639
640 let initial_start_corner =
653 if style.get_box().original_display.outside() == DisplayOutside::Inline {
654 LogicalVec2 {
656 inline: self.current_state.inline_advance,
657 block: -self.current_state.parent_offset.block,
658 }
659 } else {
660 LogicalVec2 {
662 inline: -self.current_state.parent_offset.inline,
663 block: self.line_metrics.block_size - self.current_state.parent_offset.block,
664 }
665 };
666
667 let static_position_rect = LogicalRect {
670 start_corner: initial_start_corner,
671 size: LogicalVec2::zero(),
672 }
673 .as_physical(Some(self.layout.containing_block));
674
675 let hoisted_box = AbsolutelyPositionedBox::to_hoisted(
676 absolute.absolutely_positioned_box.clone(),
677 static_position_rect,
678 LogicalVec2 {
679 inline: AlignFlags::START,
680 block: AlignFlags::START,
681 },
682 self.layout.containing_block.style.writing_mode,
683 );
684
685 let hoisted_fragment = hoisted_box.fragment.clone();
686 self.current_positioning_context_mut().push(hoisted_box);
687 self.current_state.fragments.push((
688 Fragment::AbsoluteOrFixedPositioned(hoisted_fragment),
689 LogicalRect::zero(),
690 ));
691 }
692
693 fn layout_float(&mut self, float: FloatLineItem) {
694 self.current_state
695 .flags
696 .insert(LineLayoutInlineContainerFlags::HAD_ANY_FLOATS);
697
698 let distance_from_parent_to_ifc = LogicalVec2 {
704 inline: self.current_state.parent_offset.inline,
705 block: self.line_metrics.block_offset + self.current_state.parent_offset.block,
706 };
707 float.fragment.borrow_mut().content_rect.origin -= distance_from_parent_to_ifc
708 .to_physical_size(self.layout.containing_block.style.writing_mode);
709
710 self.current_state
711 .fragments
712 .push((Fragment::Float(float.fragment), LogicalRect::zero()));
713 }
714}
715
716pub(super) enum LineItem {
717 InlineStartBoxPaddingBorderMargin(InlineBoxIdentifier),
718 InlineEndBoxPaddingBorderMargin(InlineBoxIdentifier),
719 TextRun(Option<InlineBoxIdentifier>, TextRunLineItem),
720 Atomic(Option<InlineBoxIdentifier>, AtomicLineItem),
721 AbsolutelyPositioned(Option<InlineBoxIdentifier>, AbsolutelyPositionedLineItem),
722 Float(Option<InlineBoxIdentifier>, FloatLineItem),
723}
724
725impl LineItem {
726 fn inline_box_identifier(&self) -> Option<InlineBoxIdentifier> {
727 match self {
728 LineItem::InlineStartBoxPaddingBorderMargin(identifier) => Some(*identifier),
729 LineItem::InlineEndBoxPaddingBorderMargin(identifier) => Some(*identifier),
730 LineItem::TextRun(identifier, _) => *identifier,
731 LineItem::Atomic(identifier, _) => *identifier,
732 LineItem::AbsolutelyPositioned(identifier, _) => *identifier,
733 LineItem::Float(identifier, _) => *identifier,
734 }
735 }
736
737 pub(super) fn trim_whitespace_at_end(&mut self, whitespace_trimmed: &mut Au) -> bool {
738 match self {
739 LineItem::InlineStartBoxPaddingBorderMargin(_) => true,
740 LineItem::InlineEndBoxPaddingBorderMargin(_) => true,
741 LineItem::TextRun(_, item) => item.trim_whitespace_at_end(whitespace_trimmed),
742 LineItem::Atomic(..) => false,
743 LineItem::AbsolutelyPositioned(..) => true,
744 LineItem::Float(..) => true,
745 }
746 }
747
748 pub(super) fn trim_whitespace_at_start(&mut self, whitespace_trimmed: &mut Au) -> bool {
749 match self {
750 LineItem::InlineStartBoxPaddingBorderMargin(_) => true,
751 LineItem::InlineEndBoxPaddingBorderMargin(_) => true,
752 LineItem::TextRun(_, item) => item.trim_whitespace_at_start(whitespace_trimmed),
753 LineItem::Atomic(..) => false,
754 LineItem::AbsolutelyPositioned(..) => true,
755 LineItem::Float(..) => true,
756 }
757 }
758}
759
760pub(super) struct TextRunLineItem {
761 pub base_fragment_info: BaseFragmentInfo,
762 pub inline_styles: SharedInlineStyles,
763 pub text: Vec<std::sync::Arc<GlyphStore>>,
764 pub font_metrics: FontMetrics,
765 pub font_key: FontInstanceKey,
766 pub bidi_level: Level,
768 pub selection_range: Option<Range<ByteIndex>>,
769}
770
771impl TextRunLineItem {
772 fn trim_whitespace_at_end(&mut self, whitespace_trimmed: &mut Au) -> bool {
773 if matches!(
774 self.inline_styles
775 .style
776 .borrow()
777 .get_inherited_text()
778 .white_space_collapse,
779 WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
780 ) {
781 return false;
782 }
783
784 let index_of_last_non_whitespace = self
785 .text
786 .iter()
787 .rev()
788 .position(|glyph| !glyph.is_whitespace())
789 .map(|offset_from_end| self.text.len() - offset_from_end);
790
791 let first_whitespace_index = index_of_last_non_whitespace.unwrap_or(0);
792 *whitespace_trimmed += self
793 .text
794 .drain(first_whitespace_index..)
795 .map(|glyph| glyph.total_advance())
796 .sum();
797
798 index_of_last_non_whitespace.is_none()
800 }
801
802 fn trim_whitespace_at_start(&mut self, whitespace_trimmed: &mut Au) -> bool {
803 if matches!(
804 self.inline_styles
805 .style
806 .borrow()
807 .get_inherited_text()
808 .white_space_collapse,
809 WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
810 ) {
811 return false;
812 }
813
814 let index_of_first_non_whitespace = self
815 .text
816 .iter()
817 .position(|glyph| !glyph.is_whitespace())
818 .unwrap_or(self.text.len());
819
820 *whitespace_trimmed += self
821 .text
822 .drain(0..index_of_first_non_whitespace)
823 .map(|glyph| glyph.total_advance())
824 .sum();
825
826 self.text.is_empty()
828 }
829
830 pub(crate) fn can_merge(&self, font_key: FontInstanceKey, bidi_level: Level) -> bool {
831 self.font_key == font_key && self.bidi_level == bidi_level
832 }
833}
834
835pub(super) struct AtomicLineItem {
836 pub fragment: ArcRefCell<BoxFragment>,
837 pub size: LogicalVec2<Au>,
838 pub positioning_context: Option<PositioningContext>,
839
840 pub baseline_offset_in_parent: Au,
844
845 pub baseline_offset_in_item: Au,
847
848 pub bidi_level: Level,
850}
851
852impl AtomicLineItem {
853 fn calculate_block_start(&self, line_metrics: &LineMetrics) -> Au {
856 match self.fragment.borrow().style.clone_vertical_align() {
857 GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) => Au::zero(),
858 GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => {
859 line_metrics.block_size - self.size.block
860 },
861
862 _ => {
864 let baseline = line_metrics.baseline_block_offset + self.baseline_offset_in_parent;
865 baseline - self.baseline_offset_in_item
866 },
867 }
868 }
869}
870
871pub(super) struct AbsolutelyPositionedLineItem {
872 pub absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>,
873}
874
875pub(super) struct FloatLineItem {
876 pub fragment: ArcRefCell<BoxFragment>,
877 pub needs_placement: bool,
881}
882
883fn line_height(parent_style: &ComputedValues, font_metrics: &FontMetrics) -> Au {
884 let font = parent_style.get_font();
885 let font_size = font.font_size.computed_size();
886 match font.line_height {
887 LineHeight::Normal => font_metrics.line_gap,
888 LineHeight::Number(number) => (font_size * number.0).into(),
889 LineHeight::Length(length) => length.0.into(),
890 }
891}
892
893fn sort_by_indices_in_place<T>(data: &mut [T], mut indices: Vec<usize>) {
896 for idx in 0..data.len() {
897 if indices[idx] == idx {
898 continue;
899 }
900
901 let mut current_idx = idx;
902 loop {
903 let target_idx = indices[current_idx];
904 indices[current_idx] = current_idx;
905 if indices[target_idx] == target_idx {
906 break;
907 }
908 data.swap(current_idx, target_idx);
909 current_idx = target_idx;
910 }
911 }
912}