layout/flow/inline/
line.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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    /// The block offset of the line start in the containing
33    /// [`crate::flow::InlineFormattingContext`].
34    pub block_offset: Au,
35
36    /// The block size of this line.
37    pub block_size: Au,
38
39    /// The block offset of this line's baseline from [`Self::block_offset`].
40    pub baseline_block_offset: Au,
41}
42
43bitflags! {
44    struct LineLayoutInlineContainerFlags: u8 {
45        /// Whether or not any line items were processed for this inline box, this includes
46        /// any child inline boxes.
47        const HAD_ANY_LINE_ITEMS = 1 << 0;
48        /// Whether or not the starting inline border, padding, or margin of the inline box
49        /// was encountered.
50        const HAD_INLINE_START_PBM = 1 << 2;
51        /// Whether or not the ending inline border, padding, or margin of the inline box
52        /// was encountered.
53        const HAD_INLINE_END_PBM = 1 << 3;
54        /// Whether or not any floats were encountered while laying out this inline box.
55        const HAD_ANY_FLOATS = 1 << 4;
56    }
57}
58
59/// The state used when laying out a collection of [`LineItem`]s into a line. This state is stored
60/// per-inline container. For instance, when laying out the conents of a `<span>` a fresh
61/// [`LineItemLayoutInlineContainerState`] is pushed onto [`LineItemLayout`]'s stack of states.
62pub(super) struct LineItemLayoutInlineContainerState {
63    /// If this inline container is not the root inline container, the identifier of the [`super::InlineBox`]
64    /// that is currently being laid out.
65    pub identifier: Option<InlineBoxIdentifier>,
66
67    /// The fragments and their logical rectangle relative within the current inline box (or
68    /// line). These logical rectangles will be converted into physical ones and the Fragment's
69    /// `content_rect` will be updated once the inline box's final size is known in
70    /// [`LineItemLayout::end_inline_box`].
71    pub fragments: Vec<(Fragment, LogicalRect<Au>)>,
72
73    /// The current inline advance of the layout in the coordinates of this inline box.
74    pub inline_advance: Au,
75
76    /// Flags which track various features during layout.
77    flags: LineLayoutInlineContainerFlags,
78
79    /// The offset of the parent, relative to the start position of the line, not including
80    /// any inline start and end borders which are only processed when the inline box is
81    /// finished.
82    pub parent_offset: LogicalVec2<Au>,
83
84    /// The block offset of the parent's baseline relative to the block start of the line. This
85    /// is often the same as [`Self::parent_offset`], but can be different for the root
86    /// element.
87    pub baseline_offset: Au,
88
89    /// If this inline box establishes a containing block for positioned elements, this
90    /// is a fresh positioning context to contain them. Otherwise, this holds the starting
91    /// offset in the *parent* positioning context so that static positions can be updated
92    /// at the end of layout.
93    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
130/// The second phase of [`super::InlineFormattingContext`] layout: once items are gathered
131/// for a line, we must lay them out and create fragments for them, properly positioning them
132/// according to their baselines and also handling absolutely positioned children.
133pub(super) struct LineItemLayout<'layout_data, 'layout> {
134    /// The state of the overall [`super::InlineFormattingContext`] layout.
135    layout: &'layout mut InlineFormattingContextLayout<'layout_data>,
136
137    /// The set of [`LineItemLayoutInlineContainerState`] created while laying out items
138    /// on this line. This does not include the current level of recursion.
139    pub state_stack: Vec<LineItemLayoutInlineContainerState>,
140
141    /// The current [`LineItemLayoutInlineContainerState`].
142    pub current_state: LineItemLayoutInlineContainerState,
143
144    /// The metrics of this line, which should remain constant throughout the
145    /// layout process.
146    pub line_metrics: LineMetrics,
147
148    /// The amount of space to add to each justification opportunity in order to implement
149    /// `text-align: justify`.
150    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    /// Start and end inline boxes in tree order, so that it reflects the given inline box.
180    fn prepare_layout_for_inline_box(&mut self, new_inline_box: Option<InlineBoxIdentifier>) {
181        // Optimize the case where we are moving to the root of the inline box stack.
182        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        // Otherwise, follow the path given to us by our collection of inline boxes, so we know which
190        // inline boxes to start and end.
191        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                    // TODO: This level needs either to be last_level, or if there were
212                    // unicode characters inserted for the inline box, we need to get the
213                    // level from them.
214                    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                        // At this point the float is already positioned, so it doesn't really matter what
220                        // position it's fragment has in the order of line items.
221                        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        // `BidiInfo::reorder_visual` will reorder the contents of the line so that they
234        // are in the correct order as if one was looking at the line from left-to-right.
235        // During this layout we do not lay out from left to right. Instead we lay out
236        // from inline-start to inline-end. If the overall line contents have been flipped
237        // for BiDi, flip them again so that they are in line start-to-end order rather
238        // than left-to-right order.
239        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            // When preparing to lay out a new line item, start and end inline boxes, so that the current
253            // inline box state reflects the item's parent. Items in the line are not necessarily in tree
254            // order due to BiDi and other reordering so the inline box of the item could potentially be
255            // any in the inline formatting context.
256            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        // Move back to the root of the inline box tree, so that all boxes are ended.
280        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                // We do not know the actual physical position of a logically laid out inline element, until
291                // we know the width of the containing inline block. This step converts the logical rectangle
292                // into a physical one based on the inline formatting context width.
293                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        // If the inline box didn't have any content at all and it isn't the first fragment for
394        // an element (needed for layout queries currently) and it didn't have any padding, border,
395        // or margin do not make a fragment for it.
396        //
397        // Note: This is an optimization, but also has side effects. Any fragments on a line will
398        // force the baseline to advance in the parent IFC.
399        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        // Make `content_rect` relative to the parent Fragment.
405        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        // Relative adjustment should not affect the rest of line layout, so we can
417        // do it right before creating the Fragment.
418        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                        // We do not know the actual physical position of a logically laid out inline element, until
442                        // we know the width of the containing inline block. This step converts the logical rectangle
443                        // into a physical one now that we've computed inline size of the containing inline block above.
444                        *content_rect = logical_rect.as_physical(Some(&inline_box_containing_block))
445                    }
446                });
447                fragment
448            })
449            .collect();
450
451        // Previously all the fragment's children were positioned relative to the linebox,
452        // but they need to be made relative to this fragment.
453        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, /* specific_layout_info */
463        );
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        // The baseline offset that we have in `Self::baseline_offset` is relative to the line
509        // baseline, so we need to make it relative to the line block start.
510        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        // The block start of the TextRun is often zero (meaning it has the same font metrics as the
549        // inline box's strut), but for children of the inline formatting context root or for
550        // fallback fonts that use baseline relative alignment, it might be different.
551        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        // The initial `start_corner` of the Fragment is only the PaddingBorderMargin sum start
583        // offset, which is the sum of the start component of the padding, border, and margin.
584        // This needs to be added to the calculated block and inline positions.
585        // Make the final result relative to the parent box.
586        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            // Reconstruct a logical rectangle relative to the inline box container that will be used
607            // after the inline box is procesed to find a final physical rectangle.
608            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        // From https://drafts.csswg.org/css2/#abs-non-replaced-width
641        // > The static-position containing block is the containing block of a
642        // > hypothetical box that would have been the first box of the element if its
643        // > specified position value had been static and its specified float had been
644        // > none. (Note that due to the rules in section 9.7 this hypothetical
645        // > calculation might require also assuming a different computed value for
646        // > display.)
647        //
648        // This box is different based on the original `display` value of the
649        // absolutely positioned element. If it's `inline` it would be placed inline
650        // at the top of the line, but if it's block it would be placed in a new
651        // block position after the linebox established by this line.
652        let initial_start_corner =
653            if style.get_box().original_display.outside() == DisplayOutside::Inline {
654                // Top of the line at the current inline position.
655                LogicalVec2 {
656                    inline: self.current_state.inline_advance,
657                    block: -self.current_state.parent_offset.block,
658                }
659            } else {
660                // After the bottom of the line at the start of the inline formatting context.
661                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        // Since alignment of absolutes in inlines is currently always `start`, the size of
668        // of the static position rectangle does not matter.
669        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        // The `BoxFragment` for this float is positioned relative to the IFC, so we need
699        // to move it to be positioned relative to our parent InlineBox line item. Float
700        // fragments are children of these InlineBoxes and not children of the inline
701        // formatting context, so that they are parented properly for StackingContext
702        // properties such as opacity & filters.
703        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    /// The BiDi level of this [`TextRunLineItem`] to enable reordering.
767    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        // Only keep going if we only encountered whitespace.
799        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        // Only keep going if we only encountered whitespace.
827        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    /// The block offset of this items' baseline relative to the baseline of the line.
841    /// This will be zero for boxes with `vertical-align: top` and `vertical-align:
842    /// bottom` since their baselines are calculated late in layout.
843    pub baseline_offset_in_parent: Au,
844
845    /// The offset of the baseline inside this item.
846    pub baseline_offset_in_item: Au,
847
848    /// The BiDi level of this [`AtomicLineItem`] to enable reordering.
849    pub bidi_level: Level,
850}
851
852impl AtomicLineItem {
853    /// Given the metrics for a line, our vertical alignment, and our block size, find a block start
854    /// position relative to the top of the line.
855    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            // This covers all baseline-relative vertical alignment.
863            _ => {
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    /// Whether or not this float Fragment has been placed yet. Fragments that
878    /// do not fit on a line need to be placed after the hypothetical block start
879    /// of the next line.
880    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
893/// Sort a mutable slice by the the given indices array in place, reording the slice so that final
894/// value of `slice[x]` is `slice[indices[x]]`.
895fn 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}