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