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