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