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