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 std::ops::Range;
6use std::sync::Arc;
7
8use app_units::Au;
9use bitflags::bitflags;
10use fonts::{FontMetrics, GlyphStore};
11use itertools::Either;
12use layout_api::wrapper_traits::SharedSelection;
13use malloc_size_of_derive::MallocSizeOf;
14use style::Zero;
15use style::computed_values::position::T as Position;
16use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
17use style::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword};
18use style::values::specified::align::AlignFlags;
19use style::values::specified::box_::DisplayOutside;
20use unicode_bidi::{BidiInfo, Level};
21use webrender_api::FontInstanceKey;
22
23use super::inline_box::{InlineBoxContainerState, InlineBoxIdentifier, InlineBoxTreePathToken};
24use super::{InlineFormattingContextLayout, LineBlockSizes, SharedInlineStyles, line_height};
25use crate::cell::ArcRefCell;
26use crate::fragment_tree::{BaseFragment, BaseFragmentInfo, BoxFragment, Fragment, TextFragment};
27use crate::geom::{
28    LogicalRect, LogicalVec2, PhysicalRect, ToLogical, ToLogicalWithContainingBlock,
29};
30use crate::positioned::{
31    AbsolutelyPositionedBox, PositioningContext, PositioningContextLength, relative_adjustement,
32};
33use crate::{ContainingBlock, ContainingBlockSize};
34
35pub(super) struct LineMetrics {
36    /// The block offset of the line start in the containing
37    /// [`crate::flow::InlineFormattingContext`].
38    pub block_offset: Au,
39
40    /// The block size of this line.
41    pub block_size: Au,
42
43    /// The block offset of this line's baseline from [`Self::block_offset`].
44    pub baseline_block_offset: Au,
45}
46
47bitflags! {
48    struct LineLayoutInlineContainerFlags: u8 {
49        /// Whether or not any line items were processed for this inline box, this includes
50        /// any child inline boxes.
51        const HAD_ANY_LINE_ITEMS = 1 << 0;
52        /// Whether or not the starting inline border, padding, or margin of the inline box
53        /// was encountered.
54        const HAD_INLINE_START_PBM = 1 << 2;
55        /// Whether or not the ending inline border, padding, or margin of the inline box
56        /// was encountered.
57        const HAD_INLINE_END_PBM = 1 << 3;
58        /// Whether or not any floats were encountered while laying out this inline box.
59        const HAD_ANY_FLOATS = 1 << 4;
60    }
61}
62
63/// The state used when laying out a collection of [`LineItem`]s into a line. This state is stored
64/// per-inline container. For instance, when laying out the conents of a `<span>` a fresh
65/// [`LineItemLayoutInlineContainerState`] is pushed onto [`LineItemLayout`]'s stack of states.
66pub(super) struct LineItemLayoutInlineContainerState {
67    /// If this inline container is not the root inline container, the identifier of the [`super::InlineBox`]
68    /// that is currently being laid out.
69    pub identifier: Option<InlineBoxIdentifier>,
70
71    /// The fragments and their logical rectangle relative within the current inline box (or
72    /// line). These logical rectangles will be converted into physical ones and the Fragment's
73    /// `content_rect` will be updated once the inline box's final size is known in
74    /// [`LineItemLayout::end_inline_box`].
75    pub fragments: Vec<(Fragment, LogicalRect<Au>)>,
76
77    /// The current inline advance of the layout in the coordinates of this inline box.
78    pub inline_advance: Au,
79
80    /// Flags which track various features during layout.
81    flags: LineLayoutInlineContainerFlags,
82
83    /// The offset of the parent, relative to the start position of the line, not including
84    /// any inline start and end borders which are only processed when the inline box is
85    /// finished.
86    pub parent_offset: LogicalVec2<Au>,
87
88    /// The block offset of the parent's baseline relative to the block start of the line. This
89    /// is often the same as [`Self::parent_offset`], but can be different for the root
90    /// element.
91    pub baseline_offset: Au,
92
93    /// If this inline box establishes a containing block for positioned elements, this
94    /// is a fresh positioning context to contain them. Otherwise, this holds the starting
95    /// offset in the *parent* positioning context so that static positions can be updated
96    /// at the end of layout.
97    pub positioning_context_or_start_offset_in_parent:
98        Either<PositioningContext, PositioningContextLength>,
99}
100
101impl LineItemLayoutInlineContainerState {
102    fn new(
103        identifier: Option<InlineBoxIdentifier>,
104        parent_offset: LogicalVec2<Au>,
105        baseline_offset: Au,
106        positioning_context_or_start_offset_in_parent: Either<
107            PositioningContext,
108            PositioningContextLength,
109        >,
110    ) -> Self {
111        Self {
112            identifier,
113            fragments: Vec::new(),
114            inline_advance: Au::zero(),
115            flags: LineLayoutInlineContainerFlags::empty(),
116            parent_offset,
117            baseline_offset,
118            positioning_context_or_start_offset_in_parent,
119        }
120    }
121
122    fn root(starting_inline_advance: Au, baseline_offset: Au) -> Self {
123        let mut state = Self::new(
124            None,
125            LogicalVec2::zero(),
126            baseline_offset,
127            Either::Right(PositioningContextLength::zero()),
128        );
129        state.inline_advance = starting_inline_advance;
130        state
131    }
132}
133
134/// The second phase of [`super::InlineFormattingContext`] layout: once items are gathered
135/// for a line, we must lay them out and create fragments for them, properly positioning them
136/// according to their baselines and also handling absolutely positioned children.
137pub(super) struct LineItemLayout<'layout_data, 'layout> {
138    /// The state of the overall [`super::InlineFormattingContext`] layout.
139    layout: &'layout mut InlineFormattingContextLayout<'layout_data>,
140
141    /// The set of [`LineItemLayoutInlineContainerState`] created while laying out items
142    /// on this line. This does not include the current level of recursion.
143    pub state_stack: Vec<LineItemLayoutInlineContainerState>,
144
145    /// The current [`LineItemLayoutInlineContainerState`].
146    pub current_state: LineItemLayoutInlineContainerState,
147
148    /// The metrics of this line, which should remain constant throughout the
149    /// layout process.
150    pub line_metrics: LineMetrics,
151
152    /// The amount of space to add to each justification opportunity in order to implement
153    /// `text-align: justify`.
154    pub justification_adjustment: Au,
155
156    /// Whether this is a phantom line box.
157    /// <https://drafts.csswg.org/css-inline-3/#invisible-line-boxes>
158    is_phantom_line: bool,
159}
160
161impl LineItemLayout<'_, '_> {
162    pub(super) fn layout_line_items(
163        layout: &mut InlineFormattingContextLayout,
164        line_items: Vec<LineItem>,
165        start_position: LogicalVec2<Au>,
166        effective_block_advance: &LineBlockSizes,
167        justification_adjustment: Au,
168        is_phantom_line: bool,
169    ) -> Vec<Fragment> {
170        let baseline_offset = effective_block_advance.find_baseline_offset();
171        LineItemLayout {
172            layout,
173            state_stack: Vec::new(),
174            current_state: LineItemLayoutInlineContainerState::root(
175                start_position.inline,
176                baseline_offset,
177            ),
178            line_metrics: LineMetrics {
179                block_offset: start_position.block,
180                block_size: effective_block_advance.resolve(),
181                baseline_block_offset: baseline_offset,
182            },
183            justification_adjustment,
184            is_phantom_line,
185        }
186        .layout(line_items)
187    }
188
189    /// Start and end inline boxes in tree order, so that it reflects the given inline box.
190    fn prepare_layout_for_inline_box(&mut self, new_inline_box: Option<InlineBoxIdentifier>) {
191        // Optimize the case where we are moving to the root of the inline box stack.
192        let Some(new_inline_box) = new_inline_box else {
193            while !self.state_stack.is_empty() {
194                self.end_inline_box();
195            }
196            return;
197        };
198
199        // Otherwise, follow the path given to us by our collection of inline boxes, so we know which
200        // inline boxes to start and end.
201        let path = self
202            .layout
203            .ifc
204            .inline_boxes
205            .get_path(self.current_state.identifier, new_inline_box);
206        for token in path {
207            match token {
208                InlineBoxTreePathToken::Start(ref identifier) => self.start_inline_box(identifier),
209                InlineBoxTreePathToken::End(_) => self.end_inline_box(),
210            }
211        }
212    }
213
214    pub(super) fn layout(&mut self, mut line_items: Vec<LineItem>) -> Vec<Fragment> {
215        let mut last_level = Level::ltr();
216        let levels: Vec<_> = line_items
217            .iter()
218            .map(|item| {
219                let level = match item {
220                    LineItem::TextRun(_, text_run) => text_run.bidi_level,
221                    // TODO: This level needs either to be last_level, or if there were
222                    // unicode characters inserted for the inline box, we need to get the
223                    // level from them.
224                    LineItem::InlineStartBoxPaddingBorderMargin(_) => last_level,
225                    LineItem::InlineEndBoxPaddingBorderMargin(_) => last_level,
226                    LineItem::Atomic(_, atomic) => atomic.bidi_level,
227                    LineItem::AbsolutelyPositioned(..) => last_level,
228                    LineItem::Float(..) => {
229                        // At this point the float is already positioned, so it doesn't really matter what
230                        // position it's fragment has in the order of line items.
231                        last_level
232                    },
233                    LineItem::AnonymousBlockBox(..) => last_level,
234                };
235                last_level = level;
236                level
237            })
238            .collect();
239
240        if self.layout.ifc.has_right_to_left_content {
241            sort_by_indices_in_place(&mut line_items, BidiInfo::reorder_visual(&levels));
242        }
243
244        // `BidiInfo::reorder_visual` will reorder the contents of the line so that they
245        // are in the correct order as if one was looking at the line from left-to-right.
246        // During this layout we do not lay out from left to right. Instead we lay out
247        // from inline-start to inline-end. If the overall line contents have been flipped
248        // for BiDi, flip them again so that they are in line start-to-end order rather
249        // than left-to-right order.
250        let containing_block = self.containing_block();
251        let line_item_iterator = if containing_block.style.writing_mode.is_bidi_ltr() {
252            Either::Left(line_items.into_iter())
253        } else {
254            Either::Right(line_items.into_iter().rev())
255        };
256
257        for item in line_item_iterator.into_iter().by_ref() {
258            // When preparing to lay out a new line item, start and end inline boxes, so that the current
259            // inline box state reflects the item's parent. Items in the line are not necessarily in tree
260            // order due to BiDi and other reordering so the inline box of the item could potentially be
261            // any in the inline formatting context.
262            self.prepare_layout_for_inline_box(item.inline_box_identifier());
263
264            self.current_state
265                .flags
266                .insert(LineLayoutInlineContainerFlags::HAD_ANY_LINE_ITEMS);
267            match item {
268                LineItem::InlineStartBoxPaddingBorderMargin(_) => {
269                    self.current_state
270                        .flags
271                        .insert(LineLayoutInlineContainerFlags::HAD_INLINE_START_PBM);
272                },
273                LineItem::InlineEndBoxPaddingBorderMargin(_) => {
274                    self.current_state
275                        .flags
276                        .insert(LineLayoutInlineContainerFlags::HAD_INLINE_END_PBM);
277                },
278                LineItem::TextRun(_, text_run) => self.layout_text_run(text_run),
279                LineItem::Atomic(_, atomic) => self.layout_atomic(atomic),
280                LineItem::AbsolutelyPositioned(_, absolute) => self.layout_absolute(absolute),
281                LineItem::Float(_, float) => self.layout_float(float),
282                LineItem::AnonymousBlockBox(_, block_box) => self.layout_block(block_box),
283            }
284        }
285
286        // Move back to the root of the inline box tree, so that all boxes are ended.
287        self.prepare_layout_for_inline_box(None);
288
289        let fragments_and_rectangles = std::mem::take(&mut self.current_state.fragments);
290        let containing_block = self.containing_block();
291        fragments_and_rectangles
292            .into_iter()
293            .map(|(fragment, logical_rect)| {
294                if matches!(fragment, Fragment::Float(_)) {
295                    return fragment;
296                }
297
298                // We do not know the actual physical position of a logically laid out inline element, until
299                // we know the width of the containing inline block. This step converts the logical rectangle
300                // into a physical one based on the inline formatting context width.
301                if let Some(mut base) = fragment.base_mut() {
302                    base.rect = logical_rect.as_physical(Some(containing_block));
303                }
304
305                fragment
306            })
307            .collect()
308    }
309
310    fn current_positioning_context_mut(&mut self) -> &mut PositioningContext {
311        if let Either::Left(ref mut positioning_context) = self
312            .current_state
313            .positioning_context_or_start_offset_in_parent
314        {
315            return positioning_context;
316        }
317        self.state_stack
318            .iter_mut()
319            .rev()
320            .find_map(
321                |state| match state.positioning_context_or_start_offset_in_parent {
322                    Either::Left(ref mut positioning_context) => Some(positioning_context),
323                    Either::Right(_) => None,
324                },
325            )
326            .unwrap_or(self.layout.positioning_context)
327    }
328
329    fn start_inline_box(&mut self, identifier: &InlineBoxIdentifier) {
330        let inline_box_state =
331            &*self.layout.inline_box_states[identifier.index_in_inline_boxes as usize];
332        let inline_box = self.layout.ifc.inline_boxes.get(identifier);
333        let inline_box = &*(inline_box.borrow());
334
335        let space_above_baseline = inline_box_state.calculate_space_above_baseline();
336        let block_start_offset =
337            self.calculate_inline_box_block_start(inline_box_state, space_above_baseline);
338
339        let positioning_context_or_start_offset_in_parent =
340            match PositioningContext::new_for_layout_box_base(&inline_box.base) {
341                Some(positioning_context) => Either::Left(positioning_context),
342                None => Either::Right(self.current_positioning_context_mut().len()),
343            };
344
345        let parent_offset = LogicalVec2 {
346            inline: self.current_state.inline_advance + self.current_state.parent_offset.inline,
347            block: block_start_offset,
348        };
349
350        let outer_state = std::mem::replace(
351            &mut self.current_state,
352            LineItemLayoutInlineContainerState::new(
353                Some(*identifier),
354                parent_offset,
355                block_start_offset + space_above_baseline,
356                positioning_context_or_start_offset_in_parent,
357            ),
358        );
359
360        self.state_stack.push(outer_state);
361    }
362
363    fn end_inline_box(&mut self) {
364        let outer_state = self.state_stack.pop().expect("Ended unknown inline box");
365        let inner_state = std::mem::replace(&mut self.current_state, outer_state);
366
367        let identifier = inner_state.identifier.expect("Ended unknown inline box");
368        let inline_box_state =
369            &*self.layout.inline_box_states[identifier.index_in_inline_boxes as usize];
370        let inline_box = self.layout.ifc.inline_boxes.get(&identifier);
371        let inline_box = &*(inline_box.borrow());
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 = self.containing_block();
381        let containing_block_writing_mode = containing_block.style.writing_mode;
382        if containing_block_writing_mode.is_bidi_ltr() !=
383            inline_box.base.style.writing_mode.is_bidi_ltr()
384        {
385            std::mem::swap(&mut had_start, &mut had_end)
386        }
387
388        let mut padding = inline_box_state.pbm.padding;
389        let mut border = inline_box_state.pbm.border;
390        let mut margin = inline_box_state.pbm.margin.auto_is(Au::zero);
391        if !had_start {
392            padding.inline_start = Au::zero();
393            border.inline_start = Au::zero();
394            margin.inline_start = Au::zero();
395        }
396        if !had_end {
397            padding.inline_end = Au::zero();
398            border.inline_end = Au::zero();
399            margin.inline_end = Au::zero();
400        }
401        let pbm_sums = padding + border + margin;
402
403        // Make `content_rect` relative to the parent Fragment.
404        let mut content_rect = LogicalRect {
405            start_corner: LogicalVec2 {
406                inline: self.current_state.inline_advance + pbm_sums.inline_start,
407                block: inner_state.parent_offset.block - self.current_state.parent_offset.block,
408            },
409            size: LogicalVec2 {
410                inline: inner_state.inline_advance,
411                block: if self.is_phantom_line {
412                    Au::zero()
413                } else {
414                    inline_box_state.base.font_metrics.line_gap
415                },
416            },
417        };
418
419        // Relative adjustment should not affect the rest of line layout, so we can
420        // do it right before creating the Fragment.
421        let style = &inline_box.base.style;
422        if style.get_box().position == Position::Relative {
423            content_rect.start_corner += relative_adjustement(style, containing_block);
424        }
425
426        let inline_box_containing_block = ContainingBlock {
427            size: ContainingBlockSize {
428                inline: content_rect.size.inline,
429                block: Default::default(),
430            },
431            style: containing_block.style,
432        };
433        let fragments = inner_state
434            .fragments
435            .into_iter()
436            .map(|(fragment, logical_rect)| {
437                let is_float = matches!(fragment, Fragment::Float(_));
438                if let Some(mut base) = fragment.base_mut() {
439                    if is_float {
440                        base.rect.origin -= pbm_sums
441                            .start_offset()
442                            .to_physical_size(containing_block_writing_mode);
443                    } else {
444                        // We do not know the actual physical position of a logically laid out inline element, until
445                        // we know the width of the containing inline block. This step converts the logical rectangle
446                        // into a physical one now that we've computed inline size of the containing inline block above.
447                        base.rect = logical_rect.as_physical(Some(&inline_box_containing_block))
448                    }
449                }
450                fragment
451            })
452            .collect();
453
454        // Previously all the fragment's children were positioned relative to the linebox,
455        // but they need to be made relative to this fragment.
456        let physical_content_rect = content_rect.as_physical(Some(containing_block));
457        let mut fragment = BoxFragment::new(
458            inline_box.base.base_fragment_info,
459            style.clone(),
460            fragments,
461            physical_content_rect,
462            padding.to_physical(containing_block_writing_mode),
463            border.to_physical(containing_block_writing_mode),
464            margin.to_physical(containing_block_writing_mode),
465            None, /* specific_layout_info */
466        );
467
468        let offset_from_parent_ifc = LogicalVec2 {
469            inline: pbm_sums.inline_start + self.current_state.inline_advance,
470            block: content_rect.start_corner.block,
471        }
472        .to_physical_vector(containing_block_writing_mode);
473
474        match inner_state.positioning_context_or_start_offset_in_parent {
475            Either::Left(mut positioning_context) => {
476                positioning_context
477                    .layout_collected_children(self.layout.layout_context, &mut fragment);
478                positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
479                    &offset_from_parent_ifc,
480                    PositioningContextLength::zero(),
481                );
482                self.current_positioning_context_mut()
483                    .append(positioning_context);
484            },
485            Either::Right(start_offset) => {
486                self.current_positioning_context_mut()
487                    .adjust_static_position_of_hoisted_fragments_with_offset(
488                        &offset_from_parent_ifc,
489                        start_offset,
490                    );
491            },
492        }
493
494        self.current_state.inline_advance += inner_state.inline_advance + pbm_sums.inline_sum();
495
496        let fragment = Fragment::Box(ArcRefCell::new(fragment));
497        inline_box.base.add_fragment(fragment.clone());
498
499        self.current_state.fragments.push((fragment, content_rect));
500    }
501
502    fn calculate_inline_box_block_start(
503        &self,
504        inline_box_state: &InlineBoxContainerState,
505        space_above_baseline: Au,
506    ) -> Au {
507        if self.is_phantom_line {
508            return Au::zero();
509        };
510        let font_metrics = &inline_box_state.base.font_metrics;
511        let style = &inline_box_state.base.style;
512        let line_gap = font_metrics.line_gap;
513
514        // The baseline offset that we have in `Self::baseline_offset` is relative to the line
515        // baseline, so we need to make it relative to the line block start.
516        match inline_box_state.base.style.clone_vertical_align() {
517            GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) => {
518                let line_height = line_height(style, font_metrics, &inline_box_state.base.flags);
519                (line_height - line_gap).scale_by(0.5)
520            },
521            GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => {
522                let line_height = line_height(style, font_metrics, &inline_box_state.base.flags);
523                let half_leading = (line_height - line_gap).scale_by(0.5);
524                self.line_metrics.block_size - line_height + half_leading
525            },
526            _ => {
527                self.line_metrics.baseline_block_offset + inline_box_state.base.baseline_offset -
528                    space_above_baseline
529            },
530        }
531    }
532
533    fn layout_text_run(&mut self, text_item: TextRunLineItem) {
534        if text_item.text.is_empty() {
535            return;
536        }
537
538        let mut number_of_justification_opportunities = 0;
539        let mut inline_advance = text_item
540            .text
541            .iter()
542            .map(|glyph_store| {
543                number_of_justification_opportunities += glyph_store.total_word_separators();
544                glyph_store.total_advance()
545            })
546            .sum();
547
548        if !self.justification_adjustment.is_zero() {
549            inline_advance += self
550                .justification_adjustment
551                .scale_by(number_of_justification_opportunities as f32);
552        }
553
554        // The block start of the TextRun is often zero (meaning it has the same font metrics as the
555        // inline box's strut), but for children of the inline formatting context root or for
556        // fallback fonts that use baseline relative alignment, it might be different.
557        let start_corner = LogicalVec2 {
558            inline: self.current_state.inline_advance,
559            block: self.current_state.baseline_offset -
560                text_item.font_metrics.ascent -
561                self.current_state.parent_offset.block,
562        };
563        let content_rect = LogicalRect {
564            start_corner,
565            size: LogicalVec2 {
566                block: text_item.font_metrics.line_gap,
567                inline: inline_advance,
568            },
569        };
570
571        self.current_state.inline_advance += inline_advance;
572        self.current_state.fragments.push((
573            Fragment::Text(ArcRefCell::new(TextFragment {
574                base: BaseFragment::new(
575                    text_item.base_fragment_info,
576                    text_item.inline_styles.style.clone().into(),
577                    PhysicalRect::zero(),
578                ),
579                selected_style: text_item.inline_styles.selected.clone(),
580                font_metrics: text_item.font_metrics,
581                font_key: text_item.font_key,
582                glyphs: text_item.text,
583                justification_adjustment: self.justification_adjustment,
584                offsets: text_item.offsets,
585            })),
586            content_rect,
587        ));
588    }
589
590    fn layout_atomic(&mut self, atomic: AtomicLineItem) {
591        // The initial `start_corner` of the Fragment is only the PaddingBorderMargin sum start
592        // offset, which is the sum of the start component of the padding, border, and margin.
593        // This needs to be added to the calculated block and inline positions.
594        // Make the final result relative to the parent box.
595        let containing_block = self.containing_block();
596        let ifc_writing_mode = containing_block.style.writing_mode;
597        let content_rect = {
598            let block_start = atomic.calculate_block_start(&self.line_metrics);
599            let atomic_fragment = atomic.fragment.borrow_mut();
600            let padding_border_margin_sides = atomic_fragment
601                .padding_border_margin()
602                .to_logical(ifc_writing_mode);
603
604            let mut atomic_offset = LogicalVec2 {
605                inline: self.current_state.inline_advance +
606                    padding_border_margin_sides.inline_start,
607                block: block_start - self.current_state.parent_offset.block +
608                    padding_border_margin_sides.block_start,
609            };
610
611            let style = atomic_fragment.style();
612            if style.get_box().position == Position::Relative {
613                atomic_offset += relative_adjustement(&style, containing_block);
614            }
615
616            // Reconstruct a logical rectangle relative to the inline box container that will be used
617            // after the inline box is procesed to find a final physical rectangle.
618            LogicalRect {
619                start_corner: atomic_offset,
620                size: atomic_fragment
621                    .content_rect()
622                    .size
623                    .to_logical(ifc_writing_mode),
624            }
625        };
626
627        if let Some(mut positioning_context) = atomic.positioning_context {
628            let physical_rect_as_if_in_root = content_rect.as_physical(Some(containing_block));
629            positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
630                &physical_rect_as_if_in_root.origin.to_vector(),
631                PositioningContextLength::zero(),
632            );
633
634            self.current_positioning_context_mut()
635                .append(positioning_context);
636        }
637
638        self.current_state.inline_advance += atomic.size.inline;
639
640        self.current_state
641            .fragments
642            .push((Fragment::Box(atomic.fragment), content_rect));
643    }
644
645    fn layout_absolute(&mut self, absolute: AbsolutelyPositionedLineItem) {
646        let absolutely_positioned_box = (*absolute.absolutely_positioned_box).borrow();
647        let style = absolutely_positioned_box.context.style();
648
649        // From https://drafts.csswg.org/css2/#abs-non-replaced-width
650        // > The static-position containing block is the containing block of a
651        // > hypothetical box that would have been the first box of the element if its
652        // > specified position value had been static and its specified float had been
653        // > none. (Note that due to the rules in section 9.7 this hypothetical
654        // > calculation might require also assuming a different computed value for
655        // > display.)
656        //
657        // This box is different based on the original `display` value of the
658        // absolutely positioned element. If it's `inline` it would be placed inline
659        // at the top of the line, but if it's block it would be placed in a new
660        // block position after the linebox established by this line.
661        let block_position = self.layout.placement_state.current_margin.solve() -
662            self.current_state.parent_offset.block;
663        let initial_start_corner =
664            if style.get_box().original_display.outside() == DisplayOutside::Inline {
665                // Top of the line at the current inline position.
666                LogicalVec2 {
667                    inline: self.current_state.inline_advance,
668                    block: block_position,
669                }
670            } else {
671                // After the bottom of the line at the start of the inline formatting context.
672                LogicalVec2 {
673                    inline: -self.current_state.parent_offset.inline,
674                    block: block_position + self.line_metrics.block_size,
675                }
676            };
677
678        // Since alignment of absolutes in inlines is currently always `start`, the size of
679        // of the static position rectangle does not matter.
680        let containing_block = self.containing_block();
681        let static_position_rect = LogicalRect {
682            start_corner: initial_start_corner,
683            size: LogicalVec2::zero(),
684        }
685        .as_physical(Some(containing_block));
686
687        let hoisted_box = AbsolutelyPositionedBox::to_hoisted(
688            absolute.absolutely_positioned_box.clone(),
689            static_position_rect,
690            LogicalVec2 {
691                inline: AlignFlags::START,
692                block: AlignFlags::START,
693            },
694            containing_block.style.writing_mode,
695        );
696
697        let hoisted_fragment = hoisted_box.fragment.clone();
698        self.current_positioning_context_mut().push(hoisted_box);
699        self.current_state.fragments.push((
700            Fragment::AbsoluteOrFixedPositioned(hoisted_fragment),
701            LogicalRect::zero(),
702        ));
703    }
704
705    fn layout_float(&mut self, float: FloatLineItem) {
706        self.current_state
707            .flags
708            .insert(LineLayoutInlineContainerFlags::HAD_ANY_FLOATS);
709
710        // The `BoxFragment` for this float is positioned relative to the IFC, so we need
711        // to move it to be positioned relative to our parent InlineBox line item. Float
712        // fragments are children of these InlineBoxes and not children of the inline
713        // formatting context, so that they are parented properly for StackingContext
714        // properties such as opacity & filters.
715        let distance_from_parent_to_ifc = LogicalVec2 {
716            inline: self.current_state.parent_offset.inline,
717            block: self.line_metrics.block_offset + self.current_state.parent_offset.block,
718        };
719        float.fragment.borrow_mut().base.rect.origin -= distance_from_parent_to_ifc
720            .to_physical_size(self.containing_block().style.writing_mode);
721
722        self.current_state
723            .fragments
724            .push((Fragment::Float(float.fragment), LogicalRect::zero()));
725    }
726
727    fn layout_block(&mut self, block: ArcRefCell<BoxFragment>) {
728        let containing_block = self.containing_block();
729        let mut content_rect = block.borrow().content_rect().to_logical(containing_block);
730        // Anonymous blocks are always placed at the logical origin of the line.
731        content_rect.start_corner.inline -= self.current_state.parent_offset.inline;
732        content_rect.start_corner.block -= self.line_metrics.block_offset;
733        let fragment_and_rect = (Fragment::Box(block), content_rect);
734        self.current_state.fragments.push(fragment_and_rect);
735    }
736
737    #[inline]
738    fn containing_block(&self) -> &ContainingBlock<'_> {
739        self.layout.containing_block()
740    }
741}
742
743pub(super) enum LineItem {
744    InlineStartBoxPaddingBorderMargin(InlineBoxIdentifier),
745    InlineEndBoxPaddingBorderMargin(InlineBoxIdentifier),
746    TextRun(Option<InlineBoxIdentifier>, TextRunLineItem),
747    Atomic(Option<InlineBoxIdentifier>, AtomicLineItem),
748    AbsolutelyPositioned(Option<InlineBoxIdentifier>, AbsolutelyPositionedLineItem),
749    Float(Option<InlineBoxIdentifier>, FloatLineItem),
750    AnonymousBlockBox(Option<InlineBoxIdentifier>, ArcRefCell<BoxFragment>),
751}
752
753impl LineItem {
754    fn inline_box_identifier(&self) -> Option<InlineBoxIdentifier> {
755        match self {
756            LineItem::InlineStartBoxPaddingBorderMargin(identifier) => Some(*identifier),
757            LineItem::InlineEndBoxPaddingBorderMargin(identifier) => Some(*identifier),
758            LineItem::TextRun(identifier, _) => *identifier,
759            LineItem::Atomic(identifier, _) => *identifier,
760            LineItem::AbsolutelyPositioned(identifier, _) => *identifier,
761            LineItem::Float(identifier, _) => *identifier,
762            LineItem::AnonymousBlockBox(identifier, _) => *identifier,
763        }
764    }
765
766    pub(super) fn trim_whitespace_at_end(&mut self, whitespace_trimmed: &mut Au) -> bool {
767        match self {
768            LineItem::InlineStartBoxPaddingBorderMargin(_) => true,
769            LineItem::InlineEndBoxPaddingBorderMargin(_) => true,
770            LineItem::TextRun(_, item) => item.trim_whitespace_at_end(whitespace_trimmed),
771            LineItem::Atomic(..) => false,
772            LineItem::AbsolutelyPositioned(..) => true,
773            LineItem::Float(..) => true,
774            LineItem::AnonymousBlockBox(..) => true,
775        }
776    }
777
778    pub(super) fn trim_whitespace_at_start(&mut self, whitespace_trimmed: &mut Au) -> bool {
779        match self {
780            LineItem::InlineStartBoxPaddingBorderMargin(_) => true,
781            LineItem::InlineEndBoxPaddingBorderMargin(_) => true,
782            LineItem::TextRun(_, item) => item.trim_whitespace_at_start(whitespace_trimmed),
783            LineItem::Atomic(..) => false,
784            LineItem::AbsolutelyPositioned(..) => true,
785            LineItem::Float(..) => true,
786            LineItem::AnonymousBlockBox(..) => true,
787        }
788    }
789}
790
791#[derive(MallocSizeOf)]
792pub(crate) struct TextRunOffsets {
793    /// The selection range of the containing inline formatting context.
794    #[ignore_malloc_size_of = "This is stored primarily in the DOM"]
795    pub shared_selection: SharedSelection,
796    /// The range of characters this [`TextRun`] represents within the entire text of its
797    /// inline formatting context.
798    pub character_range: Range<usize>,
799}
800
801pub(super) struct TextRunLineItem {
802    pub base_fragment_info: BaseFragmentInfo,
803    pub inline_styles: SharedInlineStyles,
804    pub text: Vec<std::sync::Arc<GlyphStore>>,
805    pub font_metrics: Arc<FontMetrics>,
806    pub font_key: FontInstanceKey,
807    /// The BiDi level of this [`TextRunLineItem`] to enable reordering.
808    pub bidi_level: Level,
809    /// When necessary, this field store the [`TextRunOffsets`] for a particular
810    /// [`TextRunLineItem`]. This is currently only used inside of text inputs.
811    pub offsets: Option<Box<TextRunOffsets>>,
812}
813
814impl TextRunLineItem {
815    fn trim_whitespace_at_end(&mut self, whitespace_trimmed: &mut Au) -> bool {
816        if matches!(
817            self.inline_styles
818                .style
819                .borrow()
820                .get_inherited_text()
821                .white_space_collapse,
822            WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
823        ) {
824            return false;
825        }
826
827        let index_of_last_non_whitespace = self
828            .text
829            .iter()
830            .rev()
831            .position(|glyph| !glyph.is_whitespace())
832            .map(|offset_from_end| self.text.len() - offset_from_end);
833
834        let first_whitespace_index = index_of_last_non_whitespace.unwrap_or(0);
835        *whitespace_trimmed += self
836            .text
837            .drain(first_whitespace_index..)
838            .map(|glyph| glyph.total_advance())
839            .sum();
840
841        // Only keep going if we only encountered whitespace.
842        index_of_last_non_whitespace.is_none()
843    }
844
845    fn trim_whitespace_at_start(&mut self, whitespace_trimmed: &mut Au) -> bool {
846        if matches!(
847            self.inline_styles
848                .style
849                .borrow()
850                .get_inherited_text()
851                .white_space_collapse,
852            WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
853        ) {
854            return false;
855        }
856
857        let index_of_first_non_whitespace = self
858            .text
859            .iter()
860            .position(|glyph| !glyph.is_whitespace())
861            .unwrap_or(self.text.len());
862
863        *whitespace_trimmed += self
864            .text
865            .drain(0..index_of_first_non_whitespace)
866            .map(|glyph| glyph.total_advance())
867            .sum();
868
869        // Only keep going if we only encountered whitespace.
870        self.text.is_empty()
871    }
872
873    pub(crate) fn merge_if_possible(
874        &mut self,
875        new_font_key: FontInstanceKey,
876        new_bidi_level: Level,
877        new_glyph_store: &Arc<GlyphStore>,
878        new_offsets: &Option<TextRunOffsets>,
879    ) -> bool {
880        if self.font_key != new_font_key || self.bidi_level != new_bidi_level {
881            return false;
882        }
883        self.text.push(new_glyph_store.clone());
884
885        assert_eq!(self.offsets.is_some(), new_offsets.is_some());
886        if let (Some(new_offsets), Some(existing_offsets)) = (new_offsets, self.offsets.as_mut()) {
887            existing_offsets.character_range.end = new_offsets.character_range.end;
888        }
889
890        true
891    }
892}
893
894pub(super) struct AtomicLineItem {
895    pub fragment: ArcRefCell<BoxFragment>,
896    pub size: LogicalVec2<Au>,
897    pub positioning_context: Option<PositioningContext>,
898
899    /// The block offset of this items' baseline relative to the baseline of the line.
900    /// This will be zero for boxes with `vertical-align: top` and `vertical-align:
901    /// bottom` since their baselines are calculated late in layout.
902    pub baseline_offset_in_parent: Au,
903
904    /// The offset of the baseline inside this item.
905    pub baseline_offset_in_item: Au,
906
907    /// The BiDi level of this [`AtomicLineItem`] to enable reordering.
908    pub bidi_level: Level,
909}
910
911impl AtomicLineItem {
912    /// Given the metrics for a line, our vertical alignment, and our block size, find a block start
913    /// position relative to the top of the line.
914    fn calculate_block_start(&self, line_metrics: &LineMetrics) -> Au {
915        match self.fragment.borrow().style().clone_vertical_align() {
916            GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) => Au::zero(),
917            GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => {
918                line_metrics.block_size - self.size.block
919            },
920
921            // This covers all baseline-relative vertical alignment.
922            _ => {
923                let baseline = line_metrics.baseline_block_offset + self.baseline_offset_in_parent;
924                baseline - self.baseline_offset_in_item
925            },
926        }
927    }
928}
929
930pub(super) struct AbsolutelyPositionedLineItem {
931    pub absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>,
932}
933
934pub(super) struct FloatLineItem {
935    pub fragment: ArcRefCell<BoxFragment>,
936    /// Whether or not this float Fragment has been placed yet. Fragments that
937    /// do not fit on a line need to be placed after the hypothetical block start
938    /// of the next line.
939    pub needs_placement: bool,
940}
941
942/// Sort a mutable slice by the given indices array in place, reording the slice so that final
943/// value of `slice[x]` is `slice[indices[x]]`.
944fn sort_by_indices_in_place<T>(data: &mut [T], mut indices: Vec<usize>) {
945    for idx in 0..data.len() {
946        if indices[idx] == idx {
947            continue;
948        }
949
950        let mut current_idx = idx;
951        loop {
952            let target_idx = indices[current_idx];
953            indices[current_idx] = current_idx;
954            if indices[target_idx] == target_idx {
955                break;
956            }
957            data.swap(current_idx, target_idx);
958            current_idx = target_idx;
959        }
960    }
961}