Skip to main content

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