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