layout/flow/inline/
mod.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
5//! # Inline Formatting Context Layout
6//!
7//! Inline layout is divided into three phases:
8//!
9//! 1. Box Tree Construction
10//! 2. Box to Line Layout
11//! 3. Line to Fragment Layout
12//!
13//! The first phase happens during normal box tree constrution, while the second two phases happen
14//! during fragment tree construction (sometimes called just "layout").
15//!
16//! ## Box Tree Construction
17//!
18//! During box tree construction, DOM elements are transformed into a box tree. This phase collects
19//! all of the inline boxes, text, atomic inline elements (boxes with `display: inline-block` or
20//! `display: inline-table` as well as things like images and canvas), absolutely positioned blocks,
21//! and floated blocks.
22//!
23//! During the last part of this phase, whitespace is collapsed and text is segmented into
24//! [`TextRun`]s based on script, chosen font, and line breaking opportunities. In addition, default
25//! fonts are selected for every inline box. Each segment of text is shaped using HarfBuzz and
26//! turned into a series of glyphs, which all have a size and a position relative to the origin of
27//! the [`TextRun`] (calculated in later phases).
28//!
29//! The code for this phase is mainly in `construct.rs`, but text handling can also be found in
30//! `text_runs.rs.`
31//!
32//! ## Box to Line Layout
33//!
34//! During the first phase of fragment tree construction, box tree items are laid out into
35//! [`LineItem`]s and fragmented based on line boundaries. This is where line breaking happens. This
36//! part of layout fragments boxes and their contents across multiple lines while positioning floats
37//! and making sure non-floated contents flow around them. In addition, all atomic elements are laid
38//! out, which may descend into their respective trees and create fragments. Finally, absolutely
39//! positioned content is collected in order to later hoist it to the containing block for
40//! absolutes.
41//!
42//! Note that during this phase, layout does not know the final block position of content. Only
43//! during line to fragment layout, are the final block positions calculated based on the line's
44//! final content and its vertical alignment. Instead, positions and line heights are calculated
45//! relative to the line's final baseline which will be determined in the final phase.
46//!
47//! [`LineItem`]s represent a particular set of content on a line. Currently this is represented by
48//! a linear series of items that describe the line's hierarchy of inline boxes and content. The
49//! item types are:
50//!
51//!  - [`LineItem::InlineStartBoxPaddingBorderMargin`]
52//!  - [`LineItem::InlineEndBoxPaddingBorderMargin`]
53//!  - [`LineItem::TextRun`]
54//!  - [`LineItem::Atomic`]
55//!  - [`LineItem::AbsolutelyPositioned`]
56//!  - [`LineItem::Float`]
57//!
58//! The code for this can be found by looking for methods of the form `layout_into_line_item()`.
59//!
60//! ## Line to Fragment Layout
61//!
62//! During the second phase of fragment tree construction, the final block position of [`LineItem`]s
63//! is calculated and they are converted into [`Fragment`]s. After layout, the [`LineItem`]s are
64//! discarded and the new fragments are incorporated into the fragment tree. The final static
65//! position of absolutely positioned content is calculated and it is hoisted to its containing
66//! block via [`PositioningContext`].
67//!
68//! The code for this phase, can mainly be found in `line.rs`.
69//!
70
71pub mod construct;
72pub mod inline_box;
73pub mod line;
74mod line_breaker;
75pub mod text_run;
76
77use std::cell::{OnceCell, RefCell};
78use std::mem;
79use std::rc::Rc;
80
81use app_units::{Au, MAX_AU};
82use base::id::RenderingGroupId;
83use bitflags::bitflags;
84use construct::InlineFormattingContextBuilder;
85use fonts::{ByteIndex, FontMetrics, GlyphStore};
86use inline_box::{InlineBox, InlineBoxContainerState, InlineBoxIdentifier, InlineBoxes};
87use line::{
88    AbsolutelyPositionedLineItem, AtomicLineItem, FloatLineItem, LineItem, LineItemLayout,
89    TextRunLineItem,
90};
91use line_breaker::LineBreaker;
92use malloc_size_of_derive::MallocSizeOf;
93use range::Range;
94use script::layout_dom::ServoThreadSafeLayoutNode;
95use servo_arc::Arc;
96use style::Zero;
97use style::computed_values::text_wrap_mode::T as TextWrapMode;
98use style::computed_values::vertical_align::T as VerticalAlign;
99use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
100use style::context::{QuirksMode, SharedStyleContext};
101use style::properties::ComputedValues;
102use style::properties::style_structs::InheritedText;
103use style::values::generics::box_::VerticalAlignKeyword;
104use style::values::generics::font::LineHeight;
105use style::values::specified::box_::BaselineSource;
106use style::values::specified::text::TextAlignKeyword;
107use style::values::specified::{TextAlignLast, TextJustify};
108use text_run::{
109    TextRun, XI_LINE_BREAKING_CLASS_GL, XI_LINE_BREAKING_CLASS_WJ, XI_LINE_BREAKING_CLASS_ZWJ,
110    add_or_get_font, get_font_for_first_font_for_style,
111};
112use unicode_bidi::{BidiInfo, Level};
113use webrender_api::FontInstanceKey;
114use xi_unicode::linebreak_property;
115
116use super::float::{Clear, PlacementAmongFloats};
117use super::{CacheableLayoutResult, IndependentFloatOrAtomicLayoutResult};
118use crate::cell::ArcRefCell;
119use crate::context::LayoutContext;
120use crate::dom_traversal::NodeAndStyleInfo;
121use crate::flow::CollapsibleWithParentStartMargin;
122use crate::flow::float::{FloatBox, SequentialLayoutState};
123use crate::formatting_contexts::{Baselines, IndependentFormattingContext};
124use crate::fragment_tree::{
125    BoxFragment, CollapsedBlockMargins, CollapsedMargin, Fragment, FragmentFlags,
126    PositioningFragment,
127};
128use crate::geom::{LogicalRect, LogicalVec2, ToLogical};
129use crate::layout_box_base::LayoutBoxBase;
130use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
131use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult};
132use crate::style_ext::{ComputedValuesExt, PaddingBorderMargin};
133use crate::{ConstraintSpace, ContainingBlock, IndefiniteContainingBlock, SharedStyle};
134
135// From gfxFontConstants.h in Firefox.
136static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20;
137static FONT_SUPERSCRIPT_OFFSET_RATIO: f32 = 0.34;
138
139#[derive(Debug, MallocSizeOf)]
140pub(crate) struct InlineFormattingContext {
141    /// All [`InlineItem`]s in this [`InlineFormattingContext`] stored in a flat array.
142    /// [`InlineItem::StartInlineBox`] and [`InlineItem::EndInlineBox`] allow representing
143    /// the tree of inline boxes within the formatting context, but a flat array allows
144    /// easy iteration through all inline items.
145    pub(super) inline_items: Vec<ArcRefCell<InlineItem>>,
146
147    /// The tree of inline boxes in this [`InlineFormattingContext`]. These are stored in
148    /// a flat array with each being given a [`InlineBoxIdentifier`].
149    pub(super) inline_boxes: InlineBoxes,
150
151    /// The text content of this inline formatting context.
152    pub(super) text_content: String,
153
154    /// A store of font information for all the shaped segments in this formatting
155    /// context in order to avoid duplicating this information.
156    pub font_metrics: Vec<FontKeyAndMetrics>,
157
158    /// The [`SharedInlineStyles`] for the root of this [`InlineFormattingContext`] that are used to
159    /// share styles with all [`TextRun`] children.
160    pub(super) shared_inline_styles: SharedInlineStyles,
161
162    /// Whether this IFC contains the 1st formatted line of an element:
163    /// <https://www.w3.org/TR/css-pseudo-4/#first-formatted-line>.
164    pub(super) has_first_formatted_line: bool,
165
166    /// Whether or not this [`InlineFormattingContext`] contains floats.
167    pub(super) contains_floats: bool,
168
169    /// Whether or not this is an [`InlineFormattingContext`] for a single line text input's inner
170    /// text container.
171    pub(super) is_single_line_text_input: bool,
172
173    /// Whether or not this is an [`InlineFormattingContext`] has right-to-left content, which
174    /// will require reordering during layout.
175    pub(super) has_right_to_left_content: bool,
176}
177
178/// [`TextRun`] and `TextFragment`s need a handle on their parent inline box (or inline
179/// formatting context root)'s style. In order to implement incremental layout, these are
180/// wrapped in [`SharedStyle`]. This allows updating the parent box tree element without
181/// updating every single descendant box tree node and fragment.
182#[derive(Clone, Debug, MallocSizeOf)]
183pub(crate) struct SharedInlineStyles {
184    pub style: SharedStyle,
185    pub selected: SharedStyle,
186}
187
188impl From<&NodeAndStyleInfo<'_>> for SharedInlineStyles {
189    fn from(info: &NodeAndStyleInfo) -> Self {
190        Self {
191            style: SharedStyle::new(info.style.clone()),
192            selected: SharedStyle::new(info.node.selected_style()),
193        }
194    }
195}
196
197/// A collection of data used to cache [`FontMetrics`] in the [`InlineFormattingContext`]
198#[derive(Debug, MallocSizeOf)]
199pub(crate) struct FontKeyAndMetrics {
200    pub key: FontInstanceKey,
201    pub pt_size: Au,
202    pub metrics: FontMetrics,
203}
204
205#[derive(Debug, MallocSizeOf)]
206pub(crate) enum InlineItem {
207    StartInlineBox(ArcRefCell<InlineBox>),
208    EndInlineBox,
209    TextRun(ArcRefCell<TextRun>),
210    OutOfFlowAbsolutelyPositionedBox(
211        ArcRefCell<AbsolutelyPositionedBox>,
212        usize, /* offset_in_text */
213    ),
214    OutOfFlowFloatBox(ArcRefCell<FloatBox>),
215    Atomic(
216        ArcRefCell<IndependentFormattingContext>,
217        usize, /* offset_in_text */
218        Level, /* bidi_level */
219    ),
220}
221
222impl InlineItem {
223    pub(crate) fn repair_style(
224        &self,
225        context: &SharedStyleContext,
226        node: &ServoThreadSafeLayoutNode,
227        new_style: &Arc<ComputedValues>,
228    ) {
229        match self {
230            InlineItem::StartInlineBox(inline_box) => {
231                inline_box.borrow_mut().repair_style(node, new_style);
232            },
233            InlineItem::EndInlineBox => {},
234            // TextRun holds a handle the `InlineSharedStyles` which is updated when repairing inline box
235            // and `display: contents` styles.
236            InlineItem::TextRun(..) => {},
237            InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => positioned_box
238                .borrow_mut()
239                .context
240                .repair_style(context, node, new_style),
241            InlineItem::OutOfFlowFloatBox(float_box) => float_box
242                .borrow_mut()
243                .contents
244                .repair_style(context, node, new_style),
245            InlineItem::Atomic(atomic, ..) => {
246                atomic.borrow_mut().repair_style(context, node, new_style)
247            },
248        }
249    }
250
251    pub(crate) fn clear_fragment_layout_cache(&self) {
252        match self {
253            InlineItem::StartInlineBox(inline_box) => {
254                inline_box.borrow().base.clear_fragment_layout_cache()
255            },
256            InlineItem::EndInlineBox | InlineItem::TextRun(..) => {},
257            InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => {
258                positioned_box
259                    .borrow()
260                    .context
261                    .base
262                    .clear_fragment_layout_cache();
263            },
264            InlineItem::OutOfFlowFloatBox(float_box) => float_box
265                .borrow()
266                .contents
267                .base
268                .clear_fragment_layout_cache(),
269            InlineItem::Atomic(independent_formatting_context, ..) => {
270                independent_formatting_context
271                    .borrow()
272                    .base
273                    .clear_fragment_layout_cache()
274            },
275        }
276    }
277
278    pub(crate) fn with_base<T>(&self, callback: impl FnOnce(&LayoutBoxBase) -> T) -> T {
279        match self {
280            InlineItem::StartInlineBox(inline_box) => callback(&inline_box.borrow().base),
281            InlineItem::EndInlineBox | InlineItem::TextRun(..) => {
282                unreachable!("Should never have these kind of fragments attached to a DOM node")
283            },
284            InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => {
285                callback(&positioned_box.borrow().context.base)
286            },
287            InlineItem::OutOfFlowFloatBox(float_box) => callback(&float_box.borrow().contents.base),
288            InlineItem::Atomic(independent_formatting_context, ..) => {
289                callback(&independent_formatting_context.borrow().base)
290            },
291        }
292    }
293
294    pub(crate) fn with_base_mut<T>(&mut self, callback: impl FnOnce(&mut LayoutBoxBase) -> T) -> T {
295        match self {
296            InlineItem::StartInlineBox(inline_box) => callback(&mut inline_box.borrow_mut().base),
297            InlineItem::EndInlineBox | InlineItem::TextRun(..) => {
298                unreachable!("Should never have these kind of fragments attached to a DOM node")
299            },
300            InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => {
301                callback(&mut positioned_box.borrow_mut().context.base)
302            },
303            InlineItem::OutOfFlowFloatBox(float_box) => {
304                callback(&mut float_box.borrow_mut().contents.base)
305            },
306            InlineItem::Atomic(independent_formatting_context, ..) => {
307                callback(&mut independent_formatting_context.borrow_mut().base)
308            },
309        }
310    }
311}
312
313/// Information about the current line under construction for a particular
314/// [`InlineFormattingContextLayout`]. This tracks position and size information while
315/// [`LineItem`]s are collected and is used as input when those [`LineItem`]s are
316/// converted into [`Fragment`]s during the final phase of line layout. Note that this
317/// does not store the [`LineItem`]s themselves, as they are stored as part of the
318/// nesting state in the [`InlineFormattingContextLayout`].
319struct LineUnderConstruction {
320    /// The position where this line will start once it is laid out. This includes any
321    /// offset from `text-indent`.
322    start_position: LogicalVec2<Au>,
323
324    /// The current inline position in the line being laid out into [`LineItem`]s in this
325    /// [`InlineFormattingContext`] independent of the depth in the nesting level.
326    inline_position: Au,
327
328    /// The maximum block size of all boxes that ended and are in progress in this line.
329    /// This uses [`LineBlockSizes`] instead of a simple value, because the final block size
330    /// depends on vertical alignment.
331    max_block_size: LineBlockSizes,
332
333    /// Whether any active linebox has added a glyph or atomic element to this line, which
334    /// indicates that the next run that exceeds the line length can cause a line break.
335    has_content: bool,
336
337    /// Whether any active linebox has added some inline-axis padding, border or margin
338    /// to this line.
339    has_inline_pbm: bool,
340
341    /// Whether or not there are floats that did not fit on the current line. Before
342    /// the [`LineItem`]s of this line are laid out, these floats will need to be
343    /// placed directly below this line, but still as children of this line's Fragments.
344    has_floats_waiting_to_be_placed: bool,
345
346    /// A rectangular area (relative to the containing block / inline formatting
347    /// context boundaries) where we can fit the line box without overlapping floats.
348    /// Note that when this is not empty, its start corner takes precedence over
349    /// [`LineUnderConstruction::start_position`].
350    placement_among_floats: OnceCell<LogicalRect<Au>>,
351
352    /// The LineItems for the current line under construction that have already
353    /// been committed to this line.
354    line_items: Vec<LineItem>,
355}
356
357impl LineUnderConstruction {
358    fn new(start_position: LogicalVec2<Au>) -> Self {
359        Self {
360            inline_position: start_position.inline,
361            start_position,
362            max_block_size: LineBlockSizes::zero(),
363            has_content: false,
364            has_inline_pbm: false,
365            has_floats_waiting_to_be_placed: false,
366            placement_among_floats: OnceCell::new(),
367            line_items: Vec::new(),
368        }
369    }
370
371    fn line_block_start_considering_placement_among_floats(&self) -> Au {
372        match self.placement_among_floats.get() {
373            Some(placement_among_floats) => placement_among_floats.start_corner.block,
374            None => self.start_position.block,
375        }
376    }
377
378    fn replace_placement_among_floats(&mut self, new_placement: LogicalRect<Au>) {
379        self.placement_among_floats.take();
380        let _ = self.placement_among_floats.set(new_placement);
381    }
382
383    /// Trim the trailing whitespace in this line and return the width of the whitespace trimmed.
384    fn trim_trailing_whitespace(&mut self) -> Au {
385        // From <https://www.w3.org/TR/css-text-3/#white-space-phase-2>:
386        // > 3. A sequence of collapsible spaces at the end of a line is removed,
387        // >    as well as any trailing U+1680   OGHAM SPACE MARK whose white-space
388        // >    property is normal, nowrap, or pre-line.
389        let mut whitespace_trimmed = Au::zero();
390        for item in self.line_items.iter_mut().rev() {
391            if !item.trim_whitespace_at_end(&mut whitespace_trimmed) {
392                break;
393            }
394        }
395
396        whitespace_trimmed
397    }
398
399    /// Count the number of justification opportunities in this line.
400    fn count_justification_opportunities(&self) -> usize {
401        self.line_items
402            .iter()
403            .filter_map(|item| match item {
404                LineItem::TextRun(_, text_run) => Some(
405                    text_run
406                        .text
407                        .iter()
408                        .map(|glyph_store| glyph_store.total_word_separators())
409                        .sum::<usize>(),
410                ),
411                _ => None,
412            })
413            .sum()
414    }
415}
416
417/// A block size relative to a line's final baseline. This is to track the size
418/// contribution of a particular element of a line above and below the baseline.
419/// These sizes can be combined with other baseline relative sizes before the
420/// final baseline position is known. The values here are relative to the
421/// overall line's baseline and *not* the nested baseline of an inline box.
422#[derive(Clone, Debug)]
423struct BaselineRelativeSize {
424    /// The ascent above the baseline, where a positive value means a larger
425    /// ascent. Thus, the top of this size contribution is `baseline_offset -
426    /// ascent`.
427    ascent: Au,
428
429    /// The descent below the baseline, where a positive value means a larger
430    /// descent. Thus, the bottom of this size contribution is `baseline_offset +
431    /// descent`.
432    descent: Au,
433}
434
435impl BaselineRelativeSize {
436    fn zero() -> Self {
437        Self {
438            ascent: Au::zero(),
439            descent: Au::zero(),
440        }
441    }
442
443    fn max(&self, other: &Self) -> Self {
444        BaselineRelativeSize {
445            ascent: self.ascent.max(other.ascent),
446            descent: self.descent.max(other.descent),
447        }
448    }
449
450    /// Given an offset from the line's root baseline, adjust this [`BaselineRelativeSize`]
451    /// by that offset. This is used to adjust a [`BaselineRelativeSize`] for different kinds
452    /// of baseline-relative `vertical-align`. This will "move" measured size of a particular
453    /// inline box's block size. For example, in the following HTML:
454    ///
455    /// ```html
456    ///     <div>
457    ///         <span style="vertical-align: 5px">child content</span>
458    ///     </div>
459    /// ````
460    ///
461    /// If this [`BaselineRelativeSize`] is for the `<span>` then the adjustment
462    /// passed here would be equivalent to -5px.
463    fn adjust_for_nested_baseline_offset(&mut self, baseline_offset: Au) {
464        self.ascent -= baseline_offset;
465        self.descent += baseline_offset;
466    }
467}
468
469#[derive(Clone, Debug)]
470struct LineBlockSizes {
471    line_height: Au,
472    baseline_relative_size_for_line_height: Option<BaselineRelativeSize>,
473    size_for_baseline_positioning: BaselineRelativeSize,
474}
475
476impl LineBlockSizes {
477    fn zero() -> Self {
478        LineBlockSizes {
479            line_height: Au::zero(),
480            baseline_relative_size_for_line_height: None,
481            size_for_baseline_positioning: BaselineRelativeSize::zero(),
482        }
483    }
484
485    fn resolve(&self) -> Au {
486        let height_from_ascent_and_descent = self
487            .baseline_relative_size_for_line_height
488            .as_ref()
489            .map(|size| (size.ascent + size.descent).abs())
490            .unwrap_or_else(Au::zero);
491        self.line_height.max(height_from_ascent_and_descent)
492    }
493
494    fn max(&self, other: &LineBlockSizes) -> LineBlockSizes {
495        let baseline_relative_size = match (
496            self.baseline_relative_size_for_line_height.as_ref(),
497            other.baseline_relative_size_for_line_height.as_ref(),
498        ) {
499            (Some(our_size), Some(other_size)) => Some(our_size.max(other_size)),
500            (our_size, other_size) => our_size.or(other_size).cloned(),
501        };
502        Self {
503            line_height: self.line_height.max(other.line_height),
504            baseline_relative_size_for_line_height: baseline_relative_size,
505            size_for_baseline_positioning: self
506                .size_for_baseline_positioning
507                .max(&other.size_for_baseline_positioning),
508        }
509    }
510
511    fn max_assign(&mut self, other: &LineBlockSizes) {
512        *self = self.max(other);
513    }
514
515    fn adjust_for_baseline_offset(&mut self, baseline_offset: Au) {
516        if let Some(size) = self.baseline_relative_size_for_line_height.as_mut() {
517            size.adjust_for_nested_baseline_offset(baseline_offset)
518        }
519        self.size_for_baseline_positioning
520            .adjust_for_nested_baseline_offset(baseline_offset);
521    }
522
523    /// From <https://drafts.csswg.org/css2/visudet.html#line-height>:
524    ///  > The inline-level boxes are aligned vertically according to their 'vertical-align'
525    ///  > property. In case they are aligned 'top' or 'bottom', they must be aligned so as
526    ///  > to minimize the line box height. If such boxes are tall enough, there are multiple
527    ///  > solutions and CSS 2 does not define the position of the line box's baseline (i.e.,
528    ///  > the position of the strut, see below).
529    fn find_baseline_offset(&self) -> Au {
530        match self.baseline_relative_size_for_line_height.as_ref() {
531            Some(size) => size.ascent,
532            None => {
533                // This is the case mentinoned above where there are multiple solutions.
534                // This code is putting the baseline roughly in the middle of the line.
535                let leading = self.resolve() -
536                    (self.size_for_baseline_positioning.ascent +
537                        self.size_for_baseline_positioning.descent);
538                leading.scale_by(0.5) + self.size_for_baseline_positioning.ascent
539            },
540        }
541    }
542}
543
544/// The current unbreakable segment under construction for an inline formatting context.
545/// Items accumulate here until we reach a soft line break opportunity during processing
546/// of inline content or we reach the end of the formatting context.
547struct UnbreakableSegmentUnderConstruction {
548    /// The size of this unbreakable segment in both dimension.
549    inline_size: Au,
550
551    /// The maximum block size that this segment has. This uses [`LineBlockSizes`] instead of a
552    /// simple value, because the final block size depends on vertical alignment.
553    max_block_size: LineBlockSizes,
554
555    /// The LineItems for the segment under construction
556    line_items: Vec<LineItem>,
557
558    /// The depth in the inline box hierarchy at the start of this segment. This is used
559    /// to prefix this segment when it is pushed to a new line.
560    inline_box_hierarchy_depth: Option<usize>,
561
562    /// Whether any active linebox has added a glyph or atomic element to this line
563    /// segment, which indicates that the next run that exceeds the line length can cause
564    /// a line break.
565    has_content: bool,
566
567    /// Whether any active linebox has added some inline-axis padding, border or margin
568    /// to this line segment.
569    has_inline_pbm: bool,
570
571    /// The inline size of any trailing whitespace in this segment.
572    trailing_whitespace_size: Au,
573}
574
575impl UnbreakableSegmentUnderConstruction {
576    fn new() -> Self {
577        Self {
578            inline_size: Au::zero(),
579            max_block_size: LineBlockSizes {
580                line_height: Au::zero(),
581                baseline_relative_size_for_line_height: None,
582                size_for_baseline_positioning: BaselineRelativeSize::zero(),
583            },
584            line_items: Vec::new(),
585            inline_box_hierarchy_depth: None,
586            has_content: false,
587            has_inline_pbm: false,
588            trailing_whitespace_size: Au::zero(),
589        }
590    }
591
592    /// Reset this segment after its contents have been committed to a line.
593    fn reset(&mut self) {
594        assert!(self.line_items.is_empty()); // Preserve allocated memory.
595        self.inline_size = Au::zero();
596        self.max_block_size = LineBlockSizes::zero();
597        self.inline_box_hierarchy_depth = None;
598        self.has_content = false;
599        self.trailing_whitespace_size = Au::zero();
600    }
601
602    /// Push a single line item to this segment. In addition, record the inline box
603    /// hierarchy depth if this is the first segment. The hierarchy depth is used to
604    /// duplicate the necessary `StartInlineBox` tokens if this segment is ultimately
605    /// placed on a new empty line.
606    fn push_line_item(&mut self, line_item: LineItem, inline_box_hierarchy_depth: usize) {
607        if self.line_items.is_empty() {
608            self.inline_box_hierarchy_depth = Some(inline_box_hierarchy_depth);
609        }
610        self.line_items.push(line_item);
611    }
612
613    /// Trim whitespace from the beginning of this UnbreakbleSegmentUnderConstruction.
614    ///
615    /// From <https://www.w3.org/TR/css-text-3/#white-space-phase-2>:
616    ///
617    /// > Then, the entire block is rendered. Inlines are laid out, taking bidi
618    /// > reordering into account, and wrapping as specified by the text-wrap
619    /// > property. As each line is laid out,
620    /// >  1. A sequence of collapsible spaces at the beginning of a line is removed.
621    ///
622    /// This prevents whitespace from being added to the beginning of a line.
623    fn trim_leading_whitespace(&mut self) {
624        let mut whitespace_trimmed = Au::zero();
625        for item in self.line_items.iter_mut() {
626            if !item.trim_whitespace_at_start(&mut whitespace_trimmed) {
627                break;
628            }
629        }
630        self.inline_size -= whitespace_trimmed;
631    }
632}
633
634bitflags! {
635    pub struct InlineContainerStateFlags: u8 {
636        const CREATE_STRUT = 0b0001;
637        const IS_SINGLE_LINE_TEXT_INPUT = 0b0010;
638    }
639}
640
641pub(super) struct InlineContainerState {
642    /// The style of this inline container.
643    style: Arc<ComputedValues>,
644
645    /// Flags which describe details of this [`InlineContainerState`].
646    flags: InlineContainerStateFlags,
647
648    /// Whether or not we have processed any content (an atomic element or text) for
649    /// this inline box on the current line OR any previous line.
650    has_content: RefCell<bool>,
651
652    /// The block size contribution of this container's default font ie the size of the
653    /// "strut." Whether this is integrated into the [`Self::nested_strut_block_sizes`]
654    /// depends on the line-height quirk described in
655    /// <https://quirks.spec.whatwg.org/#the-line-height-calculation-quirk>.
656    strut_block_sizes: LineBlockSizes,
657
658    /// The strut block size of this inline container maxed with the strut block
659    /// sizes of all inline container ancestors. In quirks mode, this will be
660    /// zero, until we know that an element has inline content.
661    nested_strut_block_sizes: LineBlockSizes,
662
663    /// The baseline offset of this container from the baseline of the line. The is the
664    /// cumulative offset of this container and all of its parents. In contrast to the
665    /// `vertical-align` property a positive value indicates an offset "below" the
666    /// baseline while a negative value indicates one "above" it (when the block direction
667    /// is vertical).
668    pub baseline_offset: Au,
669
670    /// The font metrics of the non-fallback font for this container.
671    font_metrics: FontMetrics,
672}
673
674pub(super) struct InlineFormattingContextLayout<'layout_data> {
675    positioning_context: &'layout_data mut PositioningContext,
676    containing_block: &'layout_data ContainingBlock<'layout_data>,
677    sequential_layout_state: Option<&'layout_data mut SequentialLayoutState>,
678    layout_context: &'layout_data LayoutContext<'layout_data>,
679
680    /// The [`InlineFormattingContext`] that we are laying out.
681    ifc: &'layout_data InlineFormattingContext,
682
683    /// The [`InlineContainerState`] for the container formed by the root of the
684    /// [`InlineFormattingContext`]. This is effectively the "root inline box" described
685    /// by <https://drafts.csswg.org/css-inline/#model>:
686    ///
687    /// > The block container also generates a root inline box, which is an anonymous
688    /// > inline box that holds all of its inline-level contents. (Thus, all text in an
689    /// > inline formatting context is directly contained by an inline box, whether the root
690    /// > inline box or one of its descendants.) The root inline box inherits from its
691    /// > parent block container, but is otherwise unstyleable.
692    root_nesting_level: InlineContainerState,
693
694    /// A stack of [`InlineBoxContainerState`] that is used to produce [`LineItem`]s either when we
695    /// reach the end of an inline box or when we reach the end of a line. Only at the end
696    /// of the inline box is the state popped from the stack.
697    inline_box_state_stack: Vec<Rc<InlineBoxContainerState>>,
698
699    /// A collection of [`InlineBoxContainerState`] of all the inlines that are present
700    /// in this inline formatting context. We keep this as well as the stack, so that we
701    /// can access them during line layout, which may happen after relevant [`InlineBoxContainerState`]s
702    /// have been popped of the the stack.
703    inline_box_states: Vec<Rc<InlineBoxContainerState>>,
704
705    /// A vector of fragment that are laid out. This includes one [`Fragment::Positioning`]
706    /// per line that is currently laid out plus fragments for all floats, which
707    /// are currently laid out at the top-level of each [`InlineFormattingContext`].
708    fragments: Vec<Fragment>,
709
710    /// Information about the line currently being laid out into [`LineItem`]s.
711    current_line: LineUnderConstruction,
712
713    /// Information about the unbreakable line segment currently being laid out into [`LineItem`]s.
714    current_line_segment: UnbreakableSegmentUnderConstruction,
715
716    /// After a forced line break (for instance from a `<br>` element) we wait to actually
717    /// break the line until seeing more content. This allows ongoing inline boxes to finish,
718    /// since in the case where they have no more content they should not be on the next
719    /// line.
720    ///
721    /// For instance:
722    ///
723    /// ``` html
724    ///    <span style="border-right: 30px solid blue;">
725    ///         first line<br>
726    ///    </span>
727    ///    second line
728    /// ```
729    ///
730    /// In this case, the `<span>` should not extend to the second line. If we linebreak
731    /// as soon as we encounter the `<br>` the `<span>`'s ending inline borders would be
732    /// placed on the second line, because we add those borders in
733    /// [`InlineFormattingContextLayout::finish_inline_box()`].
734    linebreak_before_new_content: bool,
735
736    /// When a `<br>` element has `clear`, this needs to be applied after the linebreak,
737    /// which will be processed *after* the `<br>` element is processed. This member
738    /// stores any deferred `clear` to apply after a linebreak.
739    deferred_br_clear: Clear,
740
741    /// Whether or not a soft wrap opportunity is queued. Soft wrap opportunities are
742    /// queued after replaced content and they are processed when the next text content
743    /// is encountered.
744    pub have_deferred_soft_wrap_opportunity: bool,
745
746    /// Whether or not this InlineFormattingContext contains line boxes, excluding
747    /// [phantom line boxes](https://drafts.csswg.org/css-inline-3/#phantom-line-box).
748    has_line_boxes: bool,
749
750    /// Whether or not the layout of this InlineFormattingContext depends on the block size
751    /// of its container for the purposes of flexbox layout.
752    depends_on_block_constraints: bool,
753
754    /// The currently white-space-collapse setting of this line. This is stored on the
755    /// [`InlineFormattingContextLayout`] because when a soft wrap opportunity is defined
756    /// by the boundary between two characters, the white-space-collapse property of their
757    /// nearest common ancestor is used.
758    white_space_collapse: WhiteSpaceCollapse,
759
760    /// The currently text-wrap-mode setting of this line. This is stored on the
761    /// [`InlineFormattingContextLayout`] because when a soft wrap opportunity is defined
762    /// by the boundary between two characters, the text-wrap-mode property of their nearest
763    /// common ancestor is used.
764    text_wrap_mode: TextWrapMode,
765
766    /// The offset of the first and last baselines in the inline formatting context that we
767    /// are laying out. This is used to propagate baselines to the ancestors of
768    /// `display: inline-block` elements and table content.
769    baselines: Baselines,
770}
771
772impl InlineFormattingContextLayout<'_> {
773    fn current_inline_container_state(&self) -> &InlineContainerState {
774        match self.inline_box_state_stack.last() {
775            Some(inline_box_state) => &inline_box_state.base,
776            None => &self.root_nesting_level,
777        }
778    }
779
780    fn current_inline_box_identifier(&self) -> Option<InlineBoxIdentifier> {
781        self.inline_box_state_stack
782            .last()
783            .map(|state| state.identifier)
784    }
785
786    fn current_line_max_block_size_including_nested_containers(&self) -> LineBlockSizes {
787        self.current_inline_container_state()
788            .nested_strut_block_sizes
789            .max(&self.current_line.max_block_size)
790    }
791
792    fn propagate_current_nesting_level_white_space_style(&mut self) {
793        let style = match self.inline_box_state_stack.last() {
794            Some(inline_box_state) => &inline_box_state.base.style,
795            None => self.containing_block.style,
796        };
797        let style_text = style.get_inherited_text();
798        self.white_space_collapse = style_text.white_space_collapse;
799        self.text_wrap_mode = style_text.text_wrap_mode;
800    }
801
802    fn processing_br_element(&self) -> bool {
803        self.inline_box_state_stack
804            .last()
805            .map(|state| {
806                state
807                    .base_fragment_info
808                    .flags
809                    .contains(FragmentFlags::IS_BR_ELEMENT)
810            })
811            .unwrap_or(false)
812    }
813
814    /// Start laying out a particular [`InlineBox`] into line items. This will push
815    /// a new [`InlineBoxContainerState`] onto [`Self::inline_box_state_stack`].
816    fn start_inline_box(&mut self, inline_box: &InlineBox) {
817        let inline_box_state = InlineBoxContainerState::new(
818            inline_box,
819            self.containing_block,
820            self.layout_context,
821            self.current_inline_container_state(),
822            inline_box.is_last_split,
823            inline_box
824                .default_font_index
825                .map(|index| &self.ifc.font_metrics[index].metrics),
826        );
827
828        self.depends_on_block_constraints |= inline_box
829            .base
830            .style
831            .depends_on_block_constraints_due_to_relative_positioning(
832                self.containing_block.style.writing_mode,
833            );
834
835        // If we are starting a `<br>` element prepare to clear after its deferred linebreak has been
836        // processed. Note that a `<br>` is composed of the element itself and the inner pseudo-element
837        // with the actual linebreak. Both will have this `FragmentFlag`; that's why this code only
838        // sets `deferred_br_clear` if it isn't set yet.
839        if inline_box_state
840            .base_fragment_info
841            .flags
842            .contains(FragmentFlags::IS_BR_ELEMENT) &&
843            self.deferred_br_clear == Clear::None
844        {
845            self.deferred_br_clear = Clear::from_style_and_container_writing_mode(
846                &inline_box_state.base.style,
847                self.containing_block.style.writing_mode,
848            );
849        }
850
851        if inline_box.is_first_split {
852            let padding = inline_box_state.pbm.padding.inline_start;
853            let border = inline_box_state.pbm.border.inline_start;
854            let margin = inline_box_state.pbm.margin.inline_start.auto_is(Au::zero);
855            // We can't just check if the sum is zero because the margin can be negative,
856            // we need to check the values separately.
857            if !padding.is_zero() || !border.is_zero() || !margin.is_zero() {
858                self.current_line_segment.has_inline_pbm = true;
859            }
860            self.current_line_segment.inline_size += padding + border + margin;
861            self.current_line_segment
862                .line_items
863                .push(LineItem::InlineStartBoxPaddingBorderMargin(
864                    inline_box.identifier,
865                ));
866        }
867
868        let inline_box_state = Rc::new(inline_box_state);
869
870        // Push the state onto the IFC-wide collection of states. Inline boxes are numbered in
871        // the order that they are encountered, so this should correspond to the order they
872        // are pushed onto `self.inline_box_states`.
873        assert_eq!(
874            self.inline_box_states.len(),
875            inline_box.identifier.index_in_inline_boxes as usize
876        );
877        self.inline_box_states.push(inline_box_state.clone());
878        self.inline_box_state_stack.push(inline_box_state);
879    }
880
881    /// Finish laying out a particular [`InlineBox`] into line items. This will
882    /// pop its state off of [`Self::inline_box_state_stack`].
883    fn finish_inline_box(&mut self) {
884        let inline_box_state = match self.inline_box_state_stack.pop() {
885            Some(inline_box_state) => inline_box_state,
886            None => return, // We are at the root.
887        };
888
889        self.current_line_segment
890            .max_block_size
891            .max_assign(&inline_box_state.base.nested_strut_block_sizes);
892
893        // If the inline box that we just finished had any content at all, we want to propagate
894        // the `white-space` property of its parent to future inline children. This is because
895        // when a soft wrap opportunity is defined by the boundary between two elements, the
896        // `white-space` used is that of their nearest common ancestor.
897        if *inline_box_state.base.has_content.borrow() {
898            self.propagate_current_nesting_level_white_space_style();
899        }
900
901        if inline_box_state.is_last_fragment {
902            let padding = inline_box_state.pbm.padding.inline_end;
903            let border = inline_box_state.pbm.border.inline_end;
904            let margin = inline_box_state.pbm.margin.inline_end.auto_is(Au::zero);
905            // We can't just check if the sum is zero because the margin can be negative,
906            // we need to check the values separately.
907            if !padding.is_zero() || !border.is_zero() || !margin.is_zero() {
908                self.current_line_segment.has_inline_pbm = true;
909            }
910            self.current_line_segment.inline_size += padding + border + margin;
911            self.current_line_segment
912                .line_items
913                .push(LineItem::InlineEndBoxPaddingBorderMargin(
914                    inline_box_state.identifier,
915                ))
916        }
917    }
918
919    fn finish_last_line(&mut self) {
920        // We are at the end of the IFC, and we need to do a few things to make sure that
921        // the current segment is committed and that the final line is finished.
922        //
923        // A soft wrap opportunity makes it so the current segment is placed on a new line
924        // if it doesn't fit on the current line under construction.
925        self.process_soft_wrap_opportunity();
926
927        // `process_soft_line_wrap_opportunity` does not commit the segment to a line if
928        // there is no line wrapping, so this forces the segment into the current line.
929        self.commit_current_segment_to_line();
930
931        // Finally we finish the line itself and convert all of the LineItems into
932        // fragments.
933        self.finish_current_line_and_reset(true /* last_line_or_forced_line_break */);
934    }
935
936    /// Finish layout of all inline boxes for the current line. This will gather all
937    /// [`LineItem`]s and turn them into [`Fragment`]s, then reset the
938    /// [`InlineFormattingContextLayout`] preparing it for laying out a new line.
939    fn finish_current_line_and_reset(&mut self, last_line_or_forced_line_break: bool) {
940        let whitespace_trimmed = self.current_line.trim_trailing_whitespace();
941        let (inline_start_position, justification_adjustment) = self
942            .calculate_current_line_inline_start_and_justification_adjustment(
943                whitespace_trimmed,
944                last_line_or_forced_line_break,
945            );
946
947        let block_start_position = self
948            .current_line
949            .line_block_start_considering_placement_among_floats();
950        let had_inline_advance =
951            self.current_line.inline_position != self.current_line.start_position.inline;
952
953        let effective_block_advance = if self.current_line.has_content ||
954            had_inline_advance ||
955            self.linebreak_before_new_content
956        {
957            self.current_line_max_block_size_including_nested_containers()
958        } else {
959            LineBlockSizes::zero()
960        };
961
962        let resolved_block_advance = effective_block_advance.resolve();
963        let mut block_end_position = block_start_position + resolved_block_advance;
964        if let Some(sequential_layout_state) = self.sequential_layout_state.as_mut() {
965            // This amount includes both the block size of the line and any extra space
966            // added to move the line down in order to avoid overlapping floats.
967            let increment = block_end_position - self.current_line.start_position.block;
968            sequential_layout_state.advance_block_position(increment);
969
970            // This newline may have been triggered by a `<br>` with clearance, in which case we
971            // want to make sure that we make space not only for the current line, but any clearance
972            // from floats.
973            if let Some(clearance) = sequential_layout_state
974                .calculate_clearance(self.deferred_br_clear, &CollapsedMargin::zero())
975            {
976                sequential_layout_state.advance_block_position(clearance);
977                block_end_position += clearance;
978            };
979            self.deferred_br_clear = Clear::None;
980        }
981
982        // Set up the new line now that we no longer need the old one.
983        let mut line_to_layout = std::mem::replace(
984            &mut self.current_line,
985            LineUnderConstruction::new(LogicalVec2 {
986                inline: Au::zero(),
987                block: block_end_position,
988            }),
989        );
990
991        if line_to_layout.has_floats_waiting_to_be_placed {
992            place_pending_floats(self, &mut line_to_layout.line_items);
993        }
994
995        let start_position = LogicalVec2 {
996            block: block_start_position,
997            inline: inline_start_position,
998        };
999
1000        let baseline_offset = effective_block_advance.find_baseline_offset();
1001        let start_positioning_context_length = self.positioning_context.len();
1002        let fragments = LineItemLayout::layout_line_items(
1003            self,
1004            line_to_layout.line_items,
1005            start_position,
1006            &effective_block_advance,
1007            justification_adjustment,
1008        );
1009
1010        // https://drafts.csswg.org/css-inline-3/#invisible-line-boxes
1011        // > Line boxes that contain no text, no preserved white space, no inline boxes with non-zero
1012        // > inline-axis margins, padding, or borders, and no other in-flow content (such as atomic
1013        // > inlines or ruby annotations), and do not end with a forced line break are phantom line boxes.
1014        // > Such boxes [...] must be treated as not existing for any other layout or rendering purpose.
1015        if line_to_layout.has_content || line_to_layout.has_inline_pbm {
1016            let baseline = baseline_offset + block_start_position;
1017            self.baselines.first.get_or_insert(baseline);
1018            self.baselines.last = Some(baseline);
1019            self.has_line_boxes = true;
1020        }
1021
1022        // If the line doesn't have any fragments, we don't need to add a containing fragment for it.
1023        if fragments.is_empty() &&
1024            self.positioning_context.len() == start_positioning_context_length
1025        {
1026            return;
1027        }
1028
1029        // The inline part of this start offset was taken into account when determining
1030        // the inline start of the line in `calculate_inline_start_for_current_line` so
1031        // we do not need to include it in the `start_corner` of the line's main Fragment.
1032        let start_corner = LogicalVec2 {
1033            inline: Au::zero(),
1034            block: block_start_position,
1035        };
1036
1037        let logical_origin_in_physical_coordinates =
1038            start_corner.to_physical_vector(self.containing_block.style.writing_mode);
1039        self.positioning_context
1040            .adjust_static_position_of_hoisted_fragments_with_offset(
1041                &logical_origin_in_physical_coordinates,
1042                start_positioning_context_length,
1043            );
1044
1045        let physical_line_rect = LogicalRect {
1046            start_corner,
1047            size: LogicalVec2 {
1048                inline: self.containing_block.size.inline,
1049                block: effective_block_advance.resolve(),
1050            },
1051        }
1052        .as_physical(Some(self.containing_block));
1053        self.fragments
1054            .push(Fragment::Positioning(PositioningFragment::new_anonymous(
1055                self.root_nesting_level.style.clone(),
1056                physical_line_rect,
1057                fragments,
1058            )));
1059    }
1060
1061    /// Given the amount of whitespace trimmed from the line and taking into consideration
1062    /// the `text-align` property, calculate where the line under construction starts in
1063    /// the inline axis as well as the adjustment needed for every justification opportunity
1064    /// to account for `text-align: justify`.
1065    fn calculate_current_line_inline_start_and_justification_adjustment(
1066        &self,
1067        whitespace_trimmed: Au,
1068        last_line_or_forced_line_break: bool,
1069    ) -> (Au, Au) {
1070        enum TextAlign {
1071            Start,
1072            Center,
1073            End,
1074        }
1075        let style = self.containing_block.style;
1076        let mut text_align_keyword = style.clone_text_align();
1077
1078        if last_line_or_forced_line_break {
1079            text_align_keyword = match style.clone_text_align_last() {
1080                TextAlignLast::Auto if text_align_keyword == TextAlignKeyword::Justify => {
1081                    TextAlignKeyword::Start
1082                },
1083                TextAlignLast::Auto => text_align_keyword,
1084                TextAlignLast::Start => TextAlignKeyword::Start,
1085                TextAlignLast::End => TextAlignKeyword::End,
1086                TextAlignLast::Left => TextAlignKeyword::Left,
1087                TextAlignLast::Right => TextAlignKeyword::Right,
1088                TextAlignLast::Center => TextAlignKeyword::Center,
1089                TextAlignLast::Justify => TextAlignKeyword::Justify,
1090            };
1091        }
1092
1093        let text_align = match text_align_keyword {
1094            TextAlignKeyword::Start => TextAlign::Start,
1095            TextAlignKeyword::Center | TextAlignKeyword::MozCenter => TextAlign::Center,
1096            TextAlignKeyword::End => TextAlign::End,
1097            TextAlignKeyword::Left | TextAlignKeyword::MozLeft => {
1098                if style.writing_mode.line_left_is_inline_start() {
1099                    TextAlign::Start
1100                } else {
1101                    TextAlign::End
1102                }
1103            },
1104            TextAlignKeyword::Right | TextAlignKeyword::MozRight => {
1105                if style.writing_mode.line_left_is_inline_start() {
1106                    TextAlign::End
1107                } else {
1108                    TextAlign::Start
1109                }
1110            },
1111            TextAlignKeyword::Justify => TextAlign::Start,
1112        };
1113
1114        let (line_start, available_space) = match self.current_line.placement_among_floats.get() {
1115            Some(placement_among_floats) => (
1116                placement_among_floats.start_corner.inline,
1117                placement_among_floats.size.inline,
1118            ),
1119            None => (Au::zero(), self.containing_block.size.inline),
1120        };
1121
1122        // Properly handling text-indent requires that we do not align the text
1123        // into the text-indent.
1124        // See <https://drafts.csswg.org/css-text/#text-indent-property>
1125        // "This property specifies the indentation applied to lines of inline content in
1126        // a block. The indent is treated as a margin applied to the start edge of the
1127        // line box."
1128        let text_indent = self.current_line.start_position.inline;
1129        let line_length = self.current_line.inline_position - whitespace_trimmed - text_indent;
1130        let adjusted_line_start = line_start +
1131            match text_align {
1132                TextAlign::Start => text_indent,
1133                TextAlign::End => (available_space - line_length).max(text_indent),
1134                TextAlign::Center => (available_space - line_length + text_indent)
1135                    .scale_by(0.5)
1136                    .max(text_indent),
1137            };
1138
1139        // Calculate the justification adjustment. This is simply the remaining space on the line,
1140        // dividided by the number of justficiation opportunities that we recorded when building
1141        // the line.
1142        let text_justify = self.containing_block.style.clone_text_justify();
1143        let justification_adjustment = match (text_align_keyword, text_justify) {
1144            // `text-justify: none` should disable text justification.
1145            // TODO: Handle more `text-justify` values.
1146            (TextAlignKeyword::Justify, TextJustify::None) => Au::zero(),
1147            (TextAlignKeyword::Justify, _) => {
1148                match self.current_line.count_justification_opportunities() {
1149                    0 => Au::zero(),
1150                    num_justification_opportunities => {
1151                        (available_space - text_indent - line_length)
1152                            .scale_by(1. / num_justification_opportunities as f32)
1153                    },
1154                }
1155            },
1156            _ => Au::zero(),
1157        };
1158
1159        // If the content overflows the line, then justification adjustment will become negative. In
1160        // that case, do not make any adjustment for justification.
1161        let justification_adjustment = justification_adjustment.max(Au::zero());
1162
1163        (adjusted_line_start, justification_adjustment)
1164    }
1165
1166    fn place_float_fragment(&mut self, fragment: &mut BoxFragment) {
1167        let state = self
1168            .sequential_layout_state
1169            .as_mut()
1170            .expect("Tried to lay out a float with no sequential placement state!");
1171
1172        let block_offset_from_containining_block_top = state
1173            .current_block_position_including_margins() -
1174            state.current_containing_block_offset();
1175        state.place_float_fragment(
1176            fragment,
1177            self.containing_block,
1178            CollapsedMargin::zero(),
1179            block_offset_from_containining_block_top,
1180        );
1181    }
1182
1183    /// Place a FloatLineItem. This is done when an unbreakable segment is committed to
1184    /// the current line. Placement of FloatLineItems might need to be deferred until the
1185    /// line is complete in the case that floats stop fitting on the current line.
1186    ///
1187    /// When placing floats we do not want to take into account any trailing whitespace on
1188    /// the line, because that whitespace will be trimmed in the case that the line is
1189    /// broken. Thus this function takes as an argument the new size (without whitespace) of
1190    /// the line that these floats are joining.
1191    fn place_float_line_item_for_commit_to_line(
1192        &mut self,
1193        float_item: &mut FloatLineItem,
1194        line_inline_size_without_trailing_whitespace: Au,
1195    ) {
1196        let mut float_fragment = float_item.fragment.borrow_mut();
1197        let logical_margin_rect_size = float_fragment
1198            .margin_rect()
1199            .size
1200            .to_logical(self.containing_block.style.writing_mode);
1201        let inline_size = logical_margin_rect_size.inline.max(Au::zero());
1202
1203        let available_inline_size = match self.current_line.placement_among_floats.get() {
1204            Some(placement_among_floats) => placement_among_floats.size.inline,
1205            None => self.containing_block.size.inline,
1206        } - line_inline_size_without_trailing_whitespace;
1207
1208        // If this float doesn't fit on the current line or a previous float didn't fit on
1209        // the current line, we need to place it starting at the next line BUT still as
1210        // children of this line's hierarchy of inline boxes (for the purposes of properly
1211        // parenting in their stacking contexts). Once all the line content is gathered we
1212        // will place them later.
1213        let has_content = self.current_line.has_content || self.current_line_segment.has_content;
1214        let fits_on_line = !has_content || inline_size <= available_inline_size;
1215        let needs_placement_later =
1216            self.current_line.has_floats_waiting_to_be_placed || !fits_on_line;
1217
1218        if needs_placement_later {
1219            self.current_line.has_floats_waiting_to_be_placed = true;
1220        } else {
1221            self.place_float_fragment(&mut float_fragment);
1222            float_item.needs_placement = false;
1223        }
1224
1225        // We've added a new float to the IFC, but this may have actually changed the
1226        // position of the current line. In order to determine that we regenerate the
1227        // placement among floats for the current line, which may adjust its inline
1228        // start position.
1229        let new_placement = self.place_line_among_floats(&LogicalVec2 {
1230            inline: line_inline_size_without_trailing_whitespace,
1231            block: self.current_line.max_block_size.resolve(),
1232        });
1233        self.current_line
1234            .replace_placement_among_floats(new_placement);
1235    }
1236
1237    /// Given a new potential line size for the current line, create a "placement" for that line.
1238    /// This tells us whether or not the new potential line will fit in the current block position
1239    /// or need to be moved. In addition, the placement rect determines the inline start and end
1240    /// of the line if it's used as the final placement among floats.
1241    fn place_line_among_floats(&self, potential_line_size: &LogicalVec2<Au>) -> LogicalRect<Au> {
1242        let sequential_layout_state = self
1243            .sequential_layout_state
1244            .as_ref()
1245            .expect("Should not have called this function without having floats.");
1246
1247        let ifc_offset_in_float_container = LogicalVec2 {
1248            inline: sequential_layout_state
1249                .floats
1250                .containing_block_info
1251                .inline_start,
1252            block: sequential_layout_state.current_containing_block_offset(),
1253        };
1254
1255        let ceiling = self
1256            .current_line
1257            .line_block_start_considering_placement_among_floats();
1258        let mut placement = PlacementAmongFloats::new(
1259            &sequential_layout_state.floats,
1260            ceiling + ifc_offset_in_float_container.block,
1261            LogicalVec2 {
1262                inline: potential_line_size.inline,
1263                block: potential_line_size.block,
1264            },
1265            &PaddingBorderMargin::zero(),
1266        );
1267
1268        let mut placement_rect = placement.place();
1269        placement_rect.start_corner -= ifc_offset_in_float_container;
1270        placement_rect
1271    }
1272
1273    /// Returns true if a new potential line size for the current line would require a line
1274    /// break. This takes into account floats and will also update the "placement among
1275    /// floats" for this line if the potential line size would not cause a line break.
1276    /// Thus, calling this method has side effects and should only be done while in the
1277    /// process of laying out line content that is always going to be committed to this
1278    /// line or the next.
1279    fn new_potential_line_size_causes_line_break(
1280        &mut self,
1281        potential_line_size: &LogicalVec2<Au>,
1282    ) -> bool {
1283        let available_line_space = if self.sequential_layout_state.is_some() {
1284            self.current_line
1285                .placement_among_floats
1286                .get_or_init(|| self.place_line_among_floats(potential_line_size))
1287                .size
1288        } else {
1289            LogicalVec2 {
1290                inline: self.containing_block.size.inline,
1291                block: MAX_AU,
1292            }
1293        };
1294
1295        let inline_would_overflow = potential_line_size.inline > available_line_space.inline;
1296        let block_would_overflow = potential_line_size.block > available_line_space.block;
1297
1298        // The first content that is added to a line cannot trigger a line break and
1299        // the `white-space` propertly can also prevent all line breaking.
1300        let can_break = self.current_line.has_content;
1301
1302        // If this is the first content on the line and we already have a float placement,
1303        // that means that the placement was initialized by a leading float in the IFC.
1304        // This placement needs to be updated, because the first line content might push
1305        // the block start of the line downward. If there is no float placement, we want
1306        // to make one to properly set the block position of the line.
1307        if !can_break {
1308            // Even if we cannot break, adding content to this line might change its position.
1309            // In that case we need to redo our placement among floats.
1310            if self.sequential_layout_state.is_some() &&
1311                (inline_would_overflow || block_would_overflow)
1312            {
1313                let new_placement = self.place_line_among_floats(potential_line_size);
1314                self.current_line
1315                    .replace_placement_among_floats(new_placement);
1316            }
1317
1318            return false;
1319        }
1320
1321        // If the potential line is larger than the containing block we do not even need to consider
1322        // floats. We definitely have to do a linebreak.
1323        if potential_line_size.inline > self.containing_block.size.inline {
1324            return true;
1325        }
1326
1327        // Not fitting in the block space means that our block size has changed and we had a
1328        // placement among floats that is no longer valid. This same placement might just
1329        // need to be expanded or perhaps we need to line break.
1330        if block_would_overflow {
1331            // If we have a limited block size then we are wedging this line between floats.
1332            assert!(self.sequential_layout_state.is_some());
1333            let new_placement = self.place_line_among_floats(potential_line_size);
1334            if new_placement.start_corner.block !=
1335                self.current_line
1336                    .line_block_start_considering_placement_among_floats()
1337            {
1338                return true;
1339            } else {
1340                self.current_line
1341                    .replace_placement_among_floats(new_placement);
1342                return false;
1343            }
1344        }
1345
1346        // Otherwise the new potential line size will require a newline if it fits in the
1347        // inline space available for this line. This space may be smaller than the
1348        // containing block if floats shrink the available inline space.
1349        inline_would_overflow
1350    }
1351
1352    pub(super) fn defer_forced_line_break(&mut self) {
1353        // If the current portion of the unbreakable segment does not fit on the current line
1354        // we need to put it on a new line *before* actually triggering the hard line break.
1355        if !self.unbreakable_segment_fits_on_line() {
1356            self.process_line_break(false /* forced_line_break */);
1357        }
1358
1359        // Defer the actual line break until we've cleared all ending inline boxes.
1360        self.linebreak_before_new_content = true;
1361
1362        // In quirks mode, the line-height isn't automatically added to the line. If we consider a
1363        // forced line break a kind of preserved white space, quirks mode requires that we add the
1364        // line-height of the current element to the line box height.
1365        //
1366        // The exception here is `<br>` elements. They are implemented with `pre-line` in Servo, but
1367        // this is an implementation detail. The "magic" behavior of `<br>` elements is that they
1368        // add line-height to the line conditionally: only when they are on an otherwise empty line.
1369        let line_is_empty =
1370            !self.current_line_segment.has_content && !self.current_line.has_content;
1371        if !self.processing_br_element() || line_is_empty {
1372            let strut_size = self
1373                .current_inline_container_state()
1374                .strut_block_sizes
1375                .clone();
1376            self.update_unbreakable_segment_for_new_content(
1377                &strut_size,
1378                Au::zero(),
1379                SegmentContentFlags::empty(),
1380            );
1381        }
1382    }
1383
1384    pub(super) fn possibly_flush_deferred_forced_line_break(&mut self) {
1385        if !self.linebreak_before_new_content {
1386            return;
1387        }
1388
1389        self.commit_current_segment_to_line();
1390        self.process_line_break(true /* forced_line_break */);
1391        self.linebreak_before_new_content = false;
1392    }
1393
1394    fn push_line_item_to_unbreakable_segment(&mut self, line_item: LineItem) {
1395        self.current_line_segment
1396            .push_line_item(line_item, self.inline_box_state_stack.len());
1397    }
1398
1399    pub(super) fn push_glyph_store_to_unbreakable_segment(
1400        &mut self,
1401        glyph_store: std::sync::Arc<GlyphStore>,
1402        text_run: &TextRun,
1403        font_index: usize,
1404        bidi_level: Level,
1405        range: range::Range<ByteIndex>,
1406    ) {
1407        let inline_advance = glyph_store.total_advance();
1408        let flags = if glyph_store.is_whitespace() {
1409            SegmentContentFlags::from(text_run.inline_styles.style.borrow().get_inherited_text())
1410        } else {
1411            SegmentContentFlags::empty()
1412        };
1413
1414        // If the metrics of this font don't match the default font, we are likely using a fallback
1415        // font and need to adjust the line size to account for a potentially different font.
1416        // If somehow the metrics match, the line size won't change.
1417        let ifc_font_info = &self.ifc.font_metrics[font_index];
1418        let font_metrics = ifc_font_info.metrics.clone();
1419        let using_fallback_font =
1420            self.current_inline_container_state().font_metrics != font_metrics;
1421
1422        let quirks_mode = self.layout_context.style_context.quirks_mode() != QuirksMode::NoQuirks;
1423        let strut_size = if using_fallback_font {
1424            // TODO(mrobinson): This value should probably be cached somewhere.
1425            let container_state = self.current_inline_container_state();
1426            let vertical_align = effective_vertical_align(
1427                &container_state.style,
1428                self.inline_box_state_stack.last().map(|c| &c.base),
1429            );
1430            let mut block_size = container_state.get_block_size_contribution(
1431                vertical_align,
1432                &font_metrics,
1433                &container_state.font_metrics,
1434            );
1435            block_size.adjust_for_baseline_offset(container_state.baseline_offset);
1436            block_size
1437        } else if quirks_mode && !flags.is_collapsible_whitespace() {
1438            // Normally, the strut is incorporated into the nested block size. In quirks mode though
1439            // if we find any text that isn't collapsed whitespace, we need to incorporate the strut.
1440            // TODO(mrobinson): This isn't quite right for situations where collapsible white space
1441            // ultimately does not collapse because it is between two other pieces of content.
1442            self.current_inline_container_state()
1443                .strut_block_sizes
1444                .clone()
1445        } else {
1446            LineBlockSizes::zero()
1447        };
1448        self.update_unbreakable_segment_for_new_content(&strut_size, inline_advance, flags);
1449
1450        let current_inline_box_identifier = self.current_inline_box_identifier();
1451        match self.current_line_segment.line_items.last_mut() {
1452            Some(LineItem::TextRun(inline_box_identifier, line_item))
1453                if *inline_box_identifier == current_inline_box_identifier &&
1454                    line_item.can_merge(ifc_font_info.key, bidi_level) =>
1455            {
1456                line_item.text.push(glyph_store);
1457                return;
1458            },
1459            _ => {},
1460        }
1461
1462        let selection_range = if let Some(selection) = &text_run.selection_range {
1463            let intersection = selection.intersect(&range);
1464            if intersection.is_empty() {
1465                let insertion_point_index = selection.begin();
1466                // We only allow the caret to be shown in the start of the fragment if it is the first fragment.
1467                // Otherwise this will cause duplicate caret, especially apparent when encountered line break.
1468                if insertion_point_index >= range.begin() &&
1469                    insertion_point_index <= range.end() &&
1470                    (range.begin() != insertion_point_index || range.begin().0 == 0)
1471                {
1472                    Some(Range::new(
1473                        insertion_point_index - range.begin(),
1474                        ByteIndex(0),
1475                    ))
1476                } else {
1477                    None
1478                }
1479            } else {
1480                Some(Range::new(
1481                    intersection.begin() - range.begin(),
1482                    intersection.length(),
1483                ))
1484            }
1485        } else {
1486            None
1487        };
1488
1489        self.push_line_item_to_unbreakable_segment(LineItem::TextRun(
1490            current_inline_box_identifier,
1491            TextRunLineItem {
1492                text: vec![glyph_store],
1493                base_fragment_info: text_run.base_fragment_info,
1494                inline_styles: text_run.inline_styles.clone(),
1495                font_metrics,
1496                font_key: ifc_font_info.key,
1497                bidi_level,
1498                selection_range,
1499            },
1500        ));
1501    }
1502
1503    fn update_unbreakable_segment_for_new_content(
1504        &mut self,
1505        block_sizes_of_content: &LineBlockSizes,
1506        inline_size: Au,
1507        flags: SegmentContentFlags,
1508    ) {
1509        if flags.is_collapsible_whitespace() || flags.is_wrappable_and_hangable() {
1510            self.current_line_segment.trailing_whitespace_size = inline_size;
1511        } else {
1512            self.current_line_segment.trailing_whitespace_size = Au::zero();
1513        }
1514        if !flags.is_collapsible_whitespace() {
1515            self.current_line_segment.has_content = true;
1516        }
1517
1518        // This may or may not include the size of the strut depending on the quirks mode setting.
1519        let container_max_block_size = &self
1520            .current_inline_container_state()
1521            .nested_strut_block_sizes
1522            .clone();
1523        self.current_line_segment
1524            .max_block_size
1525            .max_assign(container_max_block_size);
1526        self.current_line_segment
1527            .max_block_size
1528            .max_assign(block_sizes_of_content);
1529
1530        self.current_line_segment.inline_size += inline_size;
1531
1532        // Propagate the whitespace setting to the current nesting level.
1533        *self
1534            .current_inline_container_state()
1535            .has_content
1536            .borrow_mut() = true;
1537        self.propagate_current_nesting_level_white_space_style();
1538    }
1539
1540    fn process_line_break(&mut self, forced_line_break: bool) {
1541        self.current_line_segment.trim_leading_whitespace();
1542        self.finish_current_line_and_reset(forced_line_break);
1543    }
1544
1545    pub(super) fn unbreakable_segment_fits_on_line(&mut self) -> bool {
1546        let potential_line_size = LogicalVec2 {
1547            inline: self.current_line.inline_position + self.current_line_segment.inline_size -
1548                self.current_line_segment.trailing_whitespace_size,
1549            block: self
1550                .current_line_max_block_size_including_nested_containers()
1551                .max(&self.current_line_segment.max_block_size)
1552                .resolve(),
1553        };
1554
1555        !self.new_potential_line_size_causes_line_break(&potential_line_size)
1556    }
1557
1558    /// Process a soft wrap opportunity. This will either commit the current unbreakble
1559    /// segment to the current line, if it fits within the containing block and float
1560    /// placement boundaries, or do a line break and then commit the segment.
1561    pub(super) fn process_soft_wrap_opportunity(&mut self) {
1562        if self.current_line_segment.line_items.is_empty() {
1563            return;
1564        }
1565        if self.text_wrap_mode == TextWrapMode::Nowrap {
1566            return;
1567        }
1568
1569        let potential_line_size = LogicalVec2 {
1570            inline: self.current_line.inline_position + self.current_line_segment.inline_size -
1571                self.current_line_segment.trailing_whitespace_size,
1572            block: self
1573                .current_line_max_block_size_including_nested_containers()
1574                .max(&self.current_line_segment.max_block_size)
1575                .resolve(),
1576        };
1577
1578        if self.new_potential_line_size_causes_line_break(&potential_line_size) {
1579            self.process_line_break(false /* forced_line_break */);
1580        }
1581        self.commit_current_segment_to_line();
1582    }
1583
1584    /// Commit the current unbrekable segment to the current line. In addition, this will
1585    /// place all floats in the unbreakable segment and expand the line dimensions.
1586    fn commit_current_segment_to_line(&mut self) {
1587        // The line segments might have no items and have content after processing a forced
1588        // linebreak on an empty line.
1589        if self.current_line_segment.line_items.is_empty() && !self.current_line_segment.has_content
1590        {
1591            return;
1592        }
1593
1594        if !self.current_line.has_content {
1595            self.current_line_segment.trim_leading_whitespace();
1596        }
1597
1598        self.current_line.inline_position += self.current_line_segment.inline_size;
1599        self.current_line.max_block_size = self
1600            .current_line_max_block_size_including_nested_containers()
1601            .max(&self.current_line_segment.max_block_size);
1602        let line_inline_size_without_trailing_whitespace =
1603            self.current_line.inline_position - self.current_line_segment.trailing_whitespace_size;
1604
1605        // Place all floats in this unbreakable segment.
1606        let mut segment_items = mem::take(&mut self.current_line_segment.line_items);
1607        for item in segment_items.iter_mut() {
1608            if let LineItem::Float(_, float_item) = item {
1609                self.place_float_line_item_for_commit_to_line(
1610                    float_item,
1611                    line_inline_size_without_trailing_whitespace,
1612                );
1613            }
1614        }
1615
1616        // If the current line was never placed among floats, we need to do that now based on the
1617        // new size. Calling `new_potential_line_size_causes_line_break()` here triggers the
1618        // new line to be positioned among floats. This should never ask for a line
1619        // break because it is the first content on the line.
1620        if self.current_line.line_items.is_empty() {
1621            let will_break = self.new_potential_line_size_causes_line_break(&LogicalVec2 {
1622                inline: line_inline_size_without_trailing_whitespace,
1623                block: self.current_line_segment.max_block_size.resolve(),
1624            });
1625            assert!(!will_break);
1626        }
1627
1628        self.current_line.line_items.extend(segment_items);
1629        self.current_line.has_content |= self.current_line_segment.has_content;
1630        self.current_line.has_inline_pbm |= self.current_line_segment.has_inline_pbm;
1631
1632        self.current_line_segment.reset();
1633    }
1634}
1635
1636bitflags! {
1637    pub struct SegmentContentFlags: u8 {
1638        const COLLAPSIBLE_WHITESPACE = 0b00000001;
1639        const WRAPPABLE_AND_HANGABLE_WHITESPACE = 0b00000010;
1640    }
1641}
1642
1643impl SegmentContentFlags {
1644    fn is_collapsible_whitespace(&self) -> bool {
1645        self.contains(Self::COLLAPSIBLE_WHITESPACE)
1646    }
1647
1648    fn is_wrappable_and_hangable(&self) -> bool {
1649        self.contains(Self::WRAPPABLE_AND_HANGABLE_WHITESPACE)
1650    }
1651}
1652
1653impl From<&InheritedText> for SegmentContentFlags {
1654    fn from(style_text: &InheritedText) -> Self {
1655        let mut flags = Self::empty();
1656
1657        // White-space with `white-space-collapse: break-spaces` or `white-space-collapse: preserve`
1658        // never collapses.
1659        if !matches!(
1660            style_text.white_space_collapse,
1661            WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
1662        ) {
1663            flags.insert(Self::COLLAPSIBLE_WHITESPACE);
1664        }
1665
1666        // White-space with `white-space-collapse: break-spaces` never hangs and always takes up
1667        // space.
1668        if style_text.text_wrap_mode == TextWrapMode::Wrap &&
1669            style_text.white_space_collapse != WhiteSpaceCollapse::BreakSpaces
1670        {
1671            flags.insert(Self::WRAPPABLE_AND_HANGABLE_WHITESPACE);
1672        }
1673        flags
1674    }
1675}
1676
1677impl InlineFormattingContext {
1678    #[servo_tracing::instrument(name = "InlineFormattingContext::new_with_builder", skip_all)]
1679    pub(super) fn new_with_builder(
1680        builder: InlineFormattingContextBuilder,
1681        layout_context: &LayoutContext,
1682        has_first_formatted_line: bool,
1683        is_single_line_text_input: bool,
1684        starting_bidi_level: Level,
1685        rendering_group_id: RenderingGroupId,
1686    ) -> Self {
1687        // This is to prevent a double borrow.
1688        let text_content: String = builder.text_segments.into_iter().collect();
1689        let mut font_metrics = Vec::new();
1690
1691        let bidi_info = BidiInfo::new(&text_content, Some(starting_bidi_level));
1692        let has_right_to_left_content = bidi_info.has_rtl();
1693
1694        let mut new_linebreaker = LineBreaker::new(text_content.as_str());
1695        for item in builder.inline_items.iter() {
1696            match &mut *item.borrow_mut() {
1697                InlineItem::TextRun(text_run) => {
1698                    text_run.borrow_mut().segment_and_shape(
1699                        &text_content,
1700                        &layout_context.font_context,
1701                        &mut new_linebreaker,
1702                        &mut font_metrics,
1703                        &bidi_info,
1704                        rendering_group_id,
1705                    );
1706                },
1707                InlineItem::StartInlineBox(inline_box) => {
1708                    let inline_box = &mut *inline_box.borrow_mut();
1709                    if let Some(font) = get_font_for_first_font_for_style(
1710                        &inline_box.base.style,
1711                        &layout_context.font_context,
1712                    ) {
1713                        inline_box.default_font_index = Some(add_or_get_font(
1714                            &font,
1715                            &mut font_metrics,
1716                            &layout_context.font_context,
1717                            rendering_group_id,
1718                        ));
1719                    }
1720                },
1721                InlineItem::Atomic(_, index_in_text, bidi_level) => {
1722                    *bidi_level = bidi_info.levels[*index_in_text];
1723                },
1724                InlineItem::OutOfFlowAbsolutelyPositionedBox(..) |
1725                InlineItem::OutOfFlowFloatBox(_) |
1726                InlineItem::EndInlineBox => {},
1727            }
1728        }
1729
1730        InlineFormattingContext {
1731            text_content,
1732            inline_items: builder.inline_items,
1733            inline_boxes: builder.inline_boxes,
1734            font_metrics,
1735            shared_inline_styles: builder
1736                .shared_inline_styles_stack
1737                .last()
1738                .expect("Should have at least one SharedInlineStyle for the root of an IFC")
1739                .clone(),
1740            has_first_formatted_line,
1741            contains_floats: builder.contains_floats,
1742            is_single_line_text_input,
1743            has_right_to_left_content,
1744        }
1745    }
1746
1747    pub(crate) fn repair_style(
1748        &self,
1749        node: &ServoThreadSafeLayoutNode,
1750        new_style: &Arc<ComputedValues>,
1751    ) {
1752        *self.shared_inline_styles.style.borrow_mut() = new_style.clone();
1753        *self.shared_inline_styles.selected.borrow_mut() = node.selected_style();
1754    }
1755
1756    pub(crate) fn inline_start_for_first_line(
1757        &self,
1758        containing_block: IndefiniteContainingBlock,
1759    ) -> Au {
1760        if !self.has_first_formatted_line {
1761            return Au::zero();
1762        }
1763        containing_block
1764            .style
1765            .get_inherited_text()
1766            .text_indent
1767            .length
1768            .to_used_value(containing_block.size.inline.unwrap_or_default())
1769    }
1770
1771    pub(super) fn layout(
1772        &self,
1773        layout_context: &LayoutContext,
1774        positioning_context: &mut PositioningContext,
1775        containing_block: &ContainingBlock,
1776        sequential_layout_state: Option<&mut SequentialLayoutState>,
1777        collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
1778    ) -> CacheableLayoutResult {
1779        // Clear any cached inline fragments from previous layouts.
1780        for inline_box in self.inline_boxes.iter() {
1781            inline_box.borrow().base.clear_fragments();
1782        }
1783
1784        let style = containing_block.style;
1785
1786        // It's unfortunate that it isn't possible to get this during IFC text processing, but in
1787        // that situation the style of the containing block is unknown.
1788        let default_font_metrics =
1789            get_font_for_first_font_for_style(style, &layout_context.font_context)
1790                .map(|font| font.metrics.clone());
1791
1792        let style_text = containing_block.style.get_inherited_text();
1793        let mut inline_container_state_flags = InlineContainerStateFlags::empty();
1794        if inline_container_needs_strut(style, layout_context, None) {
1795            inline_container_state_flags.insert(InlineContainerStateFlags::CREATE_STRUT);
1796        }
1797        if self.is_single_line_text_input {
1798            inline_container_state_flags
1799                .insert(InlineContainerStateFlags::IS_SINGLE_LINE_TEXT_INPUT);
1800        }
1801
1802        let mut layout = InlineFormattingContextLayout {
1803            positioning_context,
1804            containing_block,
1805            sequential_layout_state,
1806            layout_context,
1807            ifc: self,
1808            fragments: Vec::new(),
1809            current_line: LineUnderConstruction::new(LogicalVec2 {
1810                inline: self.inline_start_for_first_line(containing_block.into()),
1811                block: Au::zero(),
1812            }),
1813            root_nesting_level: InlineContainerState::new(
1814                style.to_arc(),
1815                inline_container_state_flags,
1816                None, /* parent_container */
1817                default_font_metrics.as_ref(),
1818            ),
1819            inline_box_state_stack: Vec::new(),
1820            inline_box_states: Vec::with_capacity(self.inline_boxes.len()),
1821            current_line_segment: UnbreakableSegmentUnderConstruction::new(),
1822            linebreak_before_new_content: false,
1823            deferred_br_clear: Clear::None,
1824            have_deferred_soft_wrap_opportunity: false,
1825            has_line_boxes: false,
1826            depends_on_block_constraints: false,
1827            white_space_collapse: style_text.white_space_collapse,
1828            text_wrap_mode: style_text.text_wrap_mode,
1829            baselines: Baselines::default(),
1830        };
1831
1832        // FIXME(pcwalton): This assumes that margins never collapse through inline formatting
1833        // contexts (i.e. that inline formatting contexts are never empty). Is that right?
1834        // FIXME(mrobinson): This should not happen if the IFC collapses through.
1835        if let Some(ref mut sequential_layout_state) = layout.sequential_layout_state {
1836            sequential_layout_state.collapse_margins();
1837            // FIXME(mrobinson): Collapse margins in the containing block offsets as well??
1838        }
1839
1840        for item in self.inline_items.iter() {
1841            let item = &*item.borrow();
1842
1843            // Any new box should flush a pending hard line break.
1844            if !matches!(item, InlineItem::EndInlineBox) {
1845                layout.possibly_flush_deferred_forced_line_break();
1846            }
1847
1848            match item {
1849                InlineItem::StartInlineBox(inline_box) => {
1850                    layout.start_inline_box(&inline_box.borrow());
1851                },
1852                InlineItem::EndInlineBox => layout.finish_inline_box(),
1853                InlineItem::TextRun(run) => run.borrow().layout_into_line_items(&mut layout),
1854                InlineItem::Atomic(atomic_formatting_context, offset_in_text, bidi_level) => {
1855                    atomic_formatting_context.borrow().layout_into_line_items(
1856                        &mut layout,
1857                        *offset_in_text,
1858                        *bidi_level,
1859                    );
1860                },
1861                InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, _) => {
1862                    layout.push_line_item_to_unbreakable_segment(LineItem::AbsolutelyPositioned(
1863                        layout.current_inline_box_identifier(),
1864                        AbsolutelyPositionedLineItem {
1865                            absolutely_positioned_box: positioned_box.clone(),
1866                        },
1867                    ));
1868                },
1869                InlineItem::OutOfFlowFloatBox(float_box) => {
1870                    float_box.borrow().layout_into_line_items(&mut layout);
1871                },
1872            }
1873        }
1874
1875        layout.finish_last_line();
1876
1877        let mut collapsible_margins_in_children = CollapsedBlockMargins::zero();
1878        let content_block_size = layout.current_line.start_position.block;
1879        collapsible_margins_in_children.collapsed_through = !layout.has_line_boxes &&
1880            content_block_size.is_zero() &&
1881            collapsible_with_parent_start_margin.0;
1882
1883        CacheableLayoutResult {
1884            fragments: layout.fragments,
1885            content_block_size,
1886            collapsible_margins_in_children,
1887            baselines: layout.baselines,
1888            depends_on_block_constraints: layout.depends_on_block_constraints,
1889            content_inline_size_for_table: None,
1890            specific_layout_info: None,
1891        }
1892    }
1893
1894    fn next_character_prevents_soft_wrap_opportunity(&self, index: usize) -> bool {
1895        let Some(character) = self.text_content[index..].chars().nth(1) else {
1896            return false;
1897        };
1898        char_prevents_soft_wrap_opportunity_when_before_or_after_atomic(character)
1899    }
1900
1901    fn previous_character_prevents_soft_wrap_opportunity(&self, index: usize) -> bool {
1902        let Some(character) = self.text_content[0..index].chars().next_back() else {
1903            return false;
1904        };
1905        char_prevents_soft_wrap_opportunity_when_before_or_after_atomic(character)
1906    }
1907}
1908
1909impl InlineContainerState {
1910    fn new(
1911        style: Arc<ComputedValues>,
1912        flags: InlineContainerStateFlags,
1913        parent_container: Option<&InlineContainerState>,
1914        font_metrics: Option<&FontMetrics>,
1915    ) -> Self {
1916        let font_metrics = font_metrics.cloned().unwrap_or_else(FontMetrics::empty);
1917        let line_height = line_height(
1918            &style,
1919            &font_metrics,
1920            flags.contains(InlineContainerStateFlags::IS_SINGLE_LINE_TEXT_INPUT),
1921        );
1922
1923        let mut baseline_offset = Au::zero();
1924        let mut strut_block_sizes = Self::get_block_sizes_with_style(
1925            effective_vertical_align(&style, parent_container),
1926            &style,
1927            &font_metrics,
1928            &font_metrics,
1929            line_height,
1930        );
1931        if let Some(parent_container) = parent_container {
1932            // The baseline offset from `vertical-align` might adjust where our block size contribution is
1933            // within the line.
1934            baseline_offset = parent_container.get_cumulative_baseline_offset_for_child(
1935                style.clone_vertical_align(),
1936                &strut_block_sizes,
1937            );
1938            strut_block_sizes.adjust_for_baseline_offset(baseline_offset);
1939        }
1940
1941        let mut nested_block_sizes = parent_container
1942            .map(|container| container.nested_strut_block_sizes.clone())
1943            .unwrap_or_else(LineBlockSizes::zero);
1944        if flags.contains(InlineContainerStateFlags::CREATE_STRUT) {
1945            nested_block_sizes.max_assign(&strut_block_sizes);
1946        }
1947
1948        Self {
1949            style,
1950            flags,
1951            has_content: RefCell::new(false),
1952            nested_strut_block_sizes: nested_block_sizes,
1953            strut_block_sizes,
1954            baseline_offset,
1955            font_metrics,
1956        }
1957    }
1958
1959    fn get_block_sizes_with_style(
1960        vertical_align: VerticalAlign,
1961        style: &ComputedValues,
1962        font_metrics: &FontMetrics,
1963        font_metrics_of_first_font: &FontMetrics,
1964        line_height: Au,
1965    ) -> LineBlockSizes {
1966        if !is_baseline_relative(vertical_align) {
1967            return LineBlockSizes {
1968                line_height,
1969                baseline_relative_size_for_line_height: None,
1970                size_for_baseline_positioning: BaselineRelativeSize::zero(),
1971            };
1972        }
1973
1974        // From https://drafts.csswg.org/css-inline/#inline-height
1975        // > If line-height computes to `normal` and either `text-box-edge` is `leading` or this
1976        // > is the root inline box, the font’s line gap metric may also be incorporated
1977        // > into A and D by adding half to each side as half-leading.
1978        //
1979        // `text-box-edge` isn't implemented (and this is a draft specification), so it's
1980        // always effectively `leading`, which means we always take into account the line gap
1981        // when `line-height` is normal.
1982        let mut ascent = font_metrics.ascent;
1983        let mut descent = font_metrics.descent;
1984        if style.get_font().line_height == LineHeight::Normal {
1985            let half_leading_from_line_gap =
1986                (font_metrics.line_gap - descent - ascent).scale_by(0.5);
1987            ascent += half_leading_from_line_gap;
1988            descent += half_leading_from_line_gap;
1989        }
1990
1991        // The ascent and descent we use for computing the line's final line height isn't
1992        // the same the ascent and descent we use for finding the baseline. For finding
1993        // the baseline we want the content rect.
1994        let size_for_baseline_positioning = BaselineRelativeSize { ascent, descent };
1995
1996        // From https://drafts.csswg.org/css-inline/#inline-height
1997        // > When its computed line-height is not normal, its layout bounds are derived solely
1998        // > from metrics of its first available font (ignoring glyphs from other fonts), and
1999        // > leading is used to adjust the effective A and D to add up to the used line-height.
2000        // > Calculate the leading L as L = line-height - (A + D). Half the leading (its
2001        // > half-leading) is added above A of the first available font, and the other half
2002        // > below D of the first available font, giving an effective ascent above the baseline
2003        // > of A′ = A + L/2, and an effective descent of D′ = D + L/2.
2004        //
2005        // Note that leading might be negative here and the line-height might be zero. In
2006        // the case where the height is zero, ascent and descent will move to the same
2007        // point in the block axis.  Even though the contribution to the line height is
2008        // zero in this case, the line may get some height when taking them into
2009        // considering with other zero line height boxes that converge on other block axis
2010        // locations when using the above formula.
2011        if style.get_font().line_height != LineHeight::Normal {
2012            ascent = font_metrics_of_first_font.ascent;
2013            descent = font_metrics_of_first_font.descent;
2014            let half_leading = (line_height - (ascent + descent)).scale_by(0.5);
2015            // We want the sum of `ascent` and `descent` to equal `line_height`.
2016            // If we just add `half_leading` to both, then we may not get `line_height`
2017            // due to precision limitations of `Au`. Instead, we set `descent` to
2018            // the value that will guarantee the correct sum.
2019            ascent += half_leading;
2020            descent = line_height - ascent;
2021        }
2022
2023        LineBlockSizes {
2024            line_height,
2025            baseline_relative_size_for_line_height: Some(BaselineRelativeSize { ascent, descent }),
2026            size_for_baseline_positioning,
2027        }
2028    }
2029
2030    fn get_block_size_contribution(
2031        &self,
2032        vertical_align: VerticalAlign,
2033        font_metrics: &FontMetrics,
2034        font_metrics_of_first_font: &FontMetrics,
2035    ) -> LineBlockSizes {
2036        Self::get_block_sizes_with_style(
2037            vertical_align,
2038            &self.style,
2039            font_metrics,
2040            font_metrics_of_first_font,
2041            line_height(
2042                &self.style,
2043                font_metrics,
2044                self.flags
2045                    .contains(InlineContainerStateFlags::IS_SINGLE_LINE_TEXT_INPUT),
2046            ),
2047        )
2048    }
2049
2050    fn get_cumulative_baseline_offset_for_child(
2051        &self,
2052        child_vertical_align: VerticalAlign,
2053        child_block_size: &LineBlockSizes,
2054    ) -> Au {
2055        let block_size = self.get_block_size_contribution(
2056            child_vertical_align.clone(),
2057            &self.font_metrics,
2058            &self.font_metrics,
2059        );
2060        self.baseline_offset +
2061            match child_vertical_align {
2062                // `top` and `bottom are not actually relative to the baseline, but this value is unused
2063                // in those cases.
2064                // TODO: We should distinguish these from `baseline` in order to implement "aligned subtrees" properly.
2065                // See https://drafts.csswg.org/css2/#aligned-subtree.
2066                VerticalAlign::Keyword(VerticalAlignKeyword::Baseline) |
2067                VerticalAlign::Keyword(VerticalAlignKeyword::Top) |
2068                VerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => Au::zero(),
2069                VerticalAlign::Keyword(VerticalAlignKeyword::Sub) => {
2070                    block_size.resolve().scale_by(FONT_SUBSCRIPT_OFFSET_RATIO)
2071                },
2072                VerticalAlign::Keyword(VerticalAlignKeyword::Super) => {
2073                    -block_size.resolve().scale_by(FONT_SUPERSCRIPT_OFFSET_RATIO)
2074                },
2075                VerticalAlign::Keyword(VerticalAlignKeyword::TextTop) => {
2076                    child_block_size.size_for_baseline_positioning.ascent - self.font_metrics.ascent
2077                },
2078                VerticalAlign::Keyword(VerticalAlignKeyword::Middle) => {
2079                    // "Align the vertical midpoint of the box with the baseline of the parent
2080                    // box plus half the x-height of the parent."
2081                    (child_block_size.size_for_baseline_positioning.ascent -
2082                        child_block_size.size_for_baseline_positioning.descent -
2083                        self.font_metrics.x_height)
2084                        .scale_by(0.5)
2085                },
2086                VerticalAlign::Keyword(VerticalAlignKeyword::TextBottom) => {
2087                    self.font_metrics.descent -
2088                        child_block_size.size_for_baseline_positioning.descent
2089                },
2090                VerticalAlign::Length(length_percentage) => {
2091                    -length_percentage.to_used_value(child_block_size.line_height)
2092                },
2093            }
2094    }
2095}
2096
2097impl IndependentFormattingContext {
2098    fn layout_into_line_items(
2099        &self,
2100        layout: &mut InlineFormattingContextLayout,
2101        offset_in_text: usize,
2102        bidi_level: Level,
2103    ) {
2104        // We need to know the inline size of the atomic before deciding whether to do the line break.
2105        let mut child_positioning_context = PositioningContext::default();
2106        let IndependentFloatOrAtomicLayoutResult {
2107            mut fragment,
2108            baselines,
2109            pbm_sums,
2110        } = self.layout_float_or_atomic_inline(
2111            layout.layout_context,
2112            &mut child_positioning_context,
2113            layout.containing_block,
2114        );
2115
2116        // If this Fragment's layout depends on the block size of the containing block,
2117        // then the entire layout of the inline formatting context does as well.
2118        layout.depends_on_block_constraints |= fragment.base.flags.contains(
2119            FragmentFlags::SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM,
2120        );
2121
2122        // Offset the content rectangle by the physical offset of the padding, border, and margin.
2123        let container_writing_mode = layout.containing_block.style.writing_mode;
2124        let pbm_physical_offset = pbm_sums
2125            .start_offset()
2126            .to_physical_size(container_writing_mode);
2127        fragment.content_rect = fragment
2128            .content_rect
2129            .translate(pbm_physical_offset.to_vector());
2130
2131        // Apply baselines.
2132        fragment = fragment.with_baselines(baselines);
2133
2134        // Lay out absolutely positioned children if this new atomic establishes a containing block
2135        // for absolutes.
2136        let positioning_context = if self.is_replaced() {
2137            None
2138        } else {
2139            if fragment
2140                .style
2141                .establishes_containing_block_for_absolute_descendants(fragment.base.flags)
2142            {
2143                child_positioning_context
2144                    .layout_collected_children(layout.layout_context, &mut fragment);
2145            }
2146            Some(child_positioning_context)
2147        };
2148
2149        if layout.text_wrap_mode == TextWrapMode::Wrap &&
2150            !layout
2151                .ifc
2152                .previous_character_prevents_soft_wrap_opportunity(offset_in_text)
2153        {
2154            layout.process_soft_wrap_opportunity();
2155        }
2156
2157        let size = pbm_sums.sum() +
2158            fragment
2159                .content_rect
2160                .size
2161                .to_logical(container_writing_mode);
2162        let baseline_offset = self
2163            .pick_baseline(&fragment.baselines(container_writing_mode))
2164            .map(|baseline| pbm_sums.block_start + baseline)
2165            .unwrap_or(size.block);
2166
2167        let (block_sizes, baseline_offset_in_parent) =
2168            self.get_block_sizes_and_baseline_offset(layout, size.block, baseline_offset);
2169        layout.update_unbreakable_segment_for_new_content(
2170            &block_sizes,
2171            size.inline,
2172            SegmentContentFlags::empty(),
2173        );
2174
2175        let fragment = ArcRefCell::new(fragment);
2176        self.base.set_fragment(Fragment::Box(fragment.clone()));
2177
2178        layout.push_line_item_to_unbreakable_segment(LineItem::Atomic(
2179            layout.current_inline_box_identifier(),
2180            AtomicLineItem {
2181                fragment,
2182                size,
2183                positioning_context,
2184                baseline_offset_in_parent,
2185                baseline_offset_in_item: baseline_offset,
2186                bidi_level,
2187            },
2188        ));
2189
2190        // If there's a soft wrap opportunity following this atomic, defer a soft wrap opportunity
2191        // for when we next process text content.
2192        if !layout
2193            .ifc
2194            .next_character_prevents_soft_wrap_opportunity(offset_in_text)
2195        {
2196            layout.have_deferred_soft_wrap_opportunity = true;
2197        }
2198    }
2199
2200    /// Picks either the first or the last baseline, depending on `baseline-source`.
2201    /// TODO: clarify that this is not to be used for box alignment in flex/grid
2202    /// <https://drafts.csswg.org/css-inline/#baseline-source>
2203    fn pick_baseline(&self, baselines: &Baselines) -> Option<Au> {
2204        match self.style().clone_baseline_source() {
2205            BaselineSource::First => baselines.first,
2206            BaselineSource::Last => baselines.last,
2207            BaselineSource::Auto if self.is_block_container() => baselines.last,
2208            BaselineSource::Auto => baselines.first,
2209        }
2210    }
2211
2212    fn get_block_sizes_and_baseline_offset(
2213        &self,
2214        ifc: &InlineFormattingContextLayout,
2215        block_size: Au,
2216        baseline_offset_in_content_area: Au,
2217    ) -> (LineBlockSizes, Au) {
2218        let mut contribution = if !is_baseline_relative(self.style().clone_vertical_align()) {
2219            LineBlockSizes {
2220                line_height: block_size,
2221                baseline_relative_size_for_line_height: None,
2222                size_for_baseline_positioning: BaselineRelativeSize::zero(),
2223            }
2224        } else {
2225            let baseline_relative_size = BaselineRelativeSize {
2226                ascent: baseline_offset_in_content_area,
2227                descent: block_size - baseline_offset_in_content_area,
2228            };
2229            LineBlockSizes {
2230                line_height: block_size,
2231                baseline_relative_size_for_line_height: Some(baseline_relative_size.clone()),
2232                size_for_baseline_positioning: baseline_relative_size,
2233            }
2234        };
2235
2236        let baseline_offset = ifc
2237            .current_inline_container_state()
2238            .get_cumulative_baseline_offset_for_child(
2239                self.style().clone_vertical_align(),
2240                &contribution,
2241            );
2242        contribution.adjust_for_baseline_offset(baseline_offset);
2243
2244        (contribution, baseline_offset)
2245    }
2246}
2247
2248impl FloatBox {
2249    fn layout_into_line_items(&self, layout: &mut InlineFormattingContextLayout) {
2250        let fragment = ArcRefCell::new(self.layout(
2251            layout.layout_context,
2252            layout.positioning_context,
2253            layout.containing_block,
2254        ));
2255
2256        self.contents
2257            .base
2258            .set_fragment(Fragment::Box(fragment.clone()));
2259        layout.push_line_item_to_unbreakable_segment(LineItem::Float(
2260            layout.current_inline_box_identifier(),
2261            FloatLineItem {
2262                fragment,
2263                needs_placement: true,
2264            },
2265        ));
2266    }
2267}
2268
2269fn place_pending_floats(ifc: &mut InlineFormattingContextLayout, line_items: &mut [LineItem]) {
2270    for item in line_items.iter_mut() {
2271        if let LineItem::Float(_, float_line_item) = item {
2272            if float_line_item.needs_placement {
2273                ifc.place_float_fragment(&mut float_line_item.fragment.borrow_mut());
2274            }
2275        }
2276    }
2277}
2278
2279fn line_height(
2280    parent_style: &ComputedValues,
2281    font_metrics: &FontMetrics,
2282    is_single_line_text_input: bool,
2283) -> Au {
2284    let font = parent_style.get_font();
2285    let font_size = font.font_size.computed_size();
2286    let mut line_height = match font.line_height {
2287        LineHeight::Normal => font_metrics.line_gap,
2288        LineHeight::Number(number) => (font_size * number.0).into(),
2289        LineHeight::Length(length) => length.0.into(),
2290    };
2291
2292    // The line height of a single-line text input's inner text container is clamped to
2293    // the size of `normal`.
2294    // <https://html.spec.whatwg.org/multipage/#the-input-element-as-a-text-entry-widget>
2295    if is_single_line_text_input {
2296        line_height.max_assign(font_metrics.line_gap);
2297    }
2298
2299    line_height
2300}
2301
2302fn effective_vertical_align(
2303    style: &ComputedValues,
2304    container: Option<&InlineContainerState>,
2305) -> VerticalAlign {
2306    if container.is_none() {
2307        // If we are at the root of the inline formatting context, we shouldn't use the
2308        // computed `vertical-align`, since it has no effect on the contents of this IFC
2309        // (it can just affect how the block container is aligned within the parent IFC).
2310        VerticalAlign::Keyword(VerticalAlignKeyword::Baseline)
2311    } else {
2312        style.clone_vertical_align()
2313    }
2314}
2315
2316fn is_baseline_relative(vertical_align: VerticalAlign) -> bool {
2317    !matches!(
2318        vertical_align,
2319        VerticalAlign::Keyword(VerticalAlignKeyword::Top) |
2320            VerticalAlign::Keyword(VerticalAlignKeyword::Bottom)
2321    )
2322}
2323
2324/// Whether or not a strut should be created for an inline container. Normally
2325/// all inline containers get struts. In quirks mode this isn't always the case
2326/// though.
2327///
2328/// From <https://quirks.spec.whatwg.org/#the-line-height-calculation-quirk>
2329///
2330/// > ### § 3.3. The line height calculation quirk
2331/// > In quirks mode and limited-quirks mode, an inline box that matches the following
2332/// > conditions, must, for the purpose of line height calculation, act as if the box had a
2333/// > line-height of zero.
2334/// >
2335/// >  - The border-top-width, border-bottom-width, padding-top and padding-bottom
2336/// >    properties have a used value of zero and the box has a vertical writing mode, or the
2337/// >    border-right-width, border-left-width, padding-right and padding-left properties have
2338/// >    a used value of zero and the box has a horizontal writing mode.
2339/// >  - It either contains no text or it contains only collapsed whitespace.
2340/// >
2341/// > ### § 3.4. The blocks ignore line-height quirk
2342/// > In quirks mode and limited-quirks mode, for a block container element whose content is
2343/// > composed of inline-level elements, the element’s line-height must be ignored for the
2344/// > purpose of calculating the minimal height of line boxes within the element.
2345///
2346/// Since we incorporate the size of the strut into the line-height calculation when
2347/// adding text, we can simply not incorporate the strut at the start of inline box
2348/// processing. This also works the same for the root of the IFC.
2349fn inline_container_needs_strut(
2350    style: &ComputedValues,
2351    layout_context: &LayoutContext,
2352    pbm: Option<&PaddingBorderMargin>,
2353) -> bool {
2354    if layout_context.style_context.quirks_mode() == QuirksMode::NoQuirks {
2355        return true;
2356    }
2357
2358    // This is not in a standard yet, but all browsers disable this quirk for list items.
2359    // See https://github.com/whatwg/quirks/issues/38.
2360    if style.get_box().display.is_list_item() {
2361        return true;
2362    }
2363
2364    pbm.map(|pbm| !pbm.padding_border_sums.inline.is_zero())
2365        .unwrap_or(false)
2366}
2367
2368impl ComputeInlineContentSizes for InlineFormattingContext {
2369    // This works on an already-constructed `InlineFormattingContext`,
2370    // Which would have to change if/when
2371    // `BlockContainer::construct` parallelize their construction.
2372    fn compute_inline_content_sizes(
2373        &self,
2374        layout_context: &LayoutContext,
2375        constraint_space: &ConstraintSpace,
2376    ) -> InlineContentSizesResult {
2377        ContentSizesComputation::compute(self, layout_context, constraint_space)
2378    }
2379}
2380
2381/// A struct which takes care of computing [`ContentSizes`] for an [`InlineFormattingContext`].
2382struct ContentSizesComputation<'layout_data> {
2383    layout_context: &'layout_data LayoutContext<'layout_data>,
2384    constraint_space: &'layout_data ConstraintSpace<'layout_data>,
2385    paragraph: ContentSizes,
2386    current_line: ContentSizes,
2387    /// Size for whitespace pending to be added to this line.
2388    pending_whitespace: ContentSizes,
2389    /// Whether or not the current line has seen any content (excluding collapsed whitespace),
2390    /// when sizing under a min-content constraint.
2391    had_content_yet_for_min_content: bool,
2392    /// Whether or not the current line has seen any content (excluding collapsed whitespace),
2393    /// when sizing under a max-content constraint.
2394    had_content_yet_for_max_content: bool,
2395    /// Stack of ending padding, margin, and border to add to the length
2396    /// when an inline box finishes.
2397    ending_inline_pbm_stack: Vec<Au>,
2398    depends_on_block_constraints: bool,
2399}
2400
2401impl<'layout_data> ContentSizesComputation<'layout_data> {
2402    fn traverse(
2403        mut self,
2404        inline_formatting_context: &InlineFormattingContext,
2405    ) -> InlineContentSizesResult {
2406        self.add_inline_size(
2407            inline_formatting_context.inline_start_for_first_line(self.constraint_space.into()),
2408        );
2409        for inline_item in inline_formatting_context.inline_items.iter() {
2410            self.process_item(&inline_item.borrow(), inline_formatting_context);
2411        }
2412        self.forced_line_break();
2413
2414        InlineContentSizesResult {
2415            sizes: self.paragraph,
2416            depends_on_block_constraints: self.depends_on_block_constraints,
2417        }
2418    }
2419
2420    fn process_item(
2421        &mut self,
2422        inline_item: &InlineItem,
2423        inline_formatting_context: &InlineFormattingContext,
2424    ) {
2425        match inline_item {
2426            InlineItem::StartInlineBox(inline_box) => {
2427                // For margins and paddings, a cyclic percentage is resolved against zero
2428                // for determining intrinsic size contributions.
2429                // https://drafts.csswg.org/css-sizing-3/#min-percentage-contribution
2430                let inline_box = inline_box.borrow();
2431                let zero = Au::zero();
2432                let writing_mode = self.constraint_space.style.writing_mode;
2433                let layout_style = inline_box.layout_style();
2434                let padding = layout_style
2435                    .padding(writing_mode)
2436                    .percentages_relative_to(zero);
2437                let border = layout_style.border_width(writing_mode);
2438                let margin = inline_box
2439                    .base
2440                    .style
2441                    .margin(writing_mode)
2442                    .percentages_relative_to(zero)
2443                    .auto_is(Au::zero);
2444
2445                let pbm = margin + padding + border;
2446                if inline_box.is_first_split {
2447                    self.add_inline_size(pbm.inline_start);
2448                }
2449                if inline_box.is_last_split {
2450                    self.ending_inline_pbm_stack.push(pbm.inline_end);
2451                } else {
2452                    self.ending_inline_pbm_stack.push(Au::zero());
2453                }
2454            },
2455            InlineItem::EndInlineBox => {
2456                let length = self.ending_inline_pbm_stack.pop().unwrap_or_else(Au::zero);
2457                self.add_inline_size(length);
2458            },
2459            InlineItem::TextRun(text_run) => {
2460                let text_run = &*text_run.borrow();
2461                let parent_style = text_run.inline_styles.style.borrow();
2462                for segment in text_run.shaped_text.iter() {
2463                    let style_text = parent_style.get_inherited_text();
2464                    let can_wrap = style_text.text_wrap_mode == TextWrapMode::Wrap;
2465
2466                    // TODO: This should take account whether or not the first and last character prevent
2467                    // linebreaks after atomics as in layout.
2468                    if can_wrap && segment.break_at_start && self.had_content_yet_for_min_content {
2469                        self.line_break_opportunity()
2470                    }
2471
2472                    for run in segment.runs.iter() {
2473                        let advance = run.glyph_store.total_advance();
2474                        if run.glyph_store.is_whitespace() {
2475                            // If this run is a forced line break, we *must* break the line
2476                            // and start measuring from the inline origin once more.
2477                            if run.is_single_preserved_newline() {
2478                                self.forced_line_break();
2479                                continue;
2480                            }
2481                            if !matches!(
2482                                style_text.white_space_collapse,
2483                                WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
2484                            ) {
2485                                if self.had_content_yet_for_min_content {
2486                                    if can_wrap {
2487                                        self.line_break_opportunity();
2488                                    } else {
2489                                        self.pending_whitespace.min_content += advance;
2490                                    }
2491                                }
2492                                if self.had_content_yet_for_max_content {
2493                                    self.pending_whitespace.max_content += advance;
2494                                }
2495                                continue;
2496                            }
2497                            if can_wrap {
2498                                self.pending_whitespace.max_content += advance;
2499                                self.commit_pending_whitespace();
2500                                self.line_break_opportunity();
2501                                continue;
2502                            }
2503                        }
2504
2505                        self.commit_pending_whitespace();
2506                        self.add_inline_size(advance);
2507
2508                        // Typically whitespace glyphs are placed in a separate store,
2509                        // but for `white-space: break-spaces` we place the first whitespace
2510                        // with the preceding text. That prevents a line break before that
2511                        // first space, but we still need to allow a line break after it.
2512                        if can_wrap && run.glyph_store.ends_with_whitespace() {
2513                            self.line_break_opportunity();
2514                        }
2515                    }
2516                }
2517            },
2518            InlineItem::Atomic(atomic, offset_in_text, _level) => {
2519                let InlineContentSizesResult {
2520                    sizes: outer,
2521                    depends_on_block_constraints,
2522                } = atomic.borrow().outer_inline_content_sizes(
2523                    self.layout_context,
2524                    &self.constraint_space.into(),
2525                    &LogicalVec2::zero(),
2526                    false, /* auto_block_size_stretches_to_containing_block */
2527                );
2528                self.depends_on_block_constraints |= depends_on_block_constraints;
2529
2530                // TODO: need to handle TextWrapMode::Nowrap.
2531                if self.had_content_yet_for_min_content &&
2532                    !inline_formatting_context
2533                        .previous_character_prevents_soft_wrap_opportunity(*offset_in_text)
2534                {
2535                    self.line_break_opportunity();
2536                }
2537
2538                self.commit_pending_whitespace();
2539                self.current_line += outer;
2540
2541                // TODO: need to handle TextWrapMode::Nowrap.
2542                if !inline_formatting_context
2543                    .next_character_prevents_soft_wrap_opportunity(*offset_in_text)
2544                {
2545                    self.line_break_opportunity();
2546                }
2547            },
2548            _ => {},
2549        }
2550    }
2551
2552    fn add_inline_size(&mut self, l: Au) {
2553        self.current_line.min_content += l;
2554        self.current_line.max_content += l;
2555    }
2556
2557    fn line_break_opportunity(&mut self) {
2558        // Clear the pending whitespace, assuming that at the end of the line
2559        // it needs to either hang or be removed. If that isn't the case,
2560        // `commit_pending_whitespace()` should be called first.
2561        self.pending_whitespace.min_content = Au::zero();
2562        let current_min_content = mem::take(&mut self.current_line.min_content);
2563        self.paragraph.min_content.max_assign(current_min_content);
2564        self.had_content_yet_for_min_content = false;
2565    }
2566
2567    fn forced_line_break(&mut self) {
2568        // Handle the line break for min-content sizes.
2569        self.line_break_opportunity();
2570
2571        // Repeat the same logic, but now for max-content sizes.
2572        self.pending_whitespace.max_content = Au::zero();
2573        let current_max_content = mem::take(&mut self.current_line.max_content);
2574        self.paragraph.max_content.max_assign(current_max_content);
2575        self.had_content_yet_for_max_content = false;
2576    }
2577
2578    fn commit_pending_whitespace(&mut self) {
2579        self.current_line += mem::take(&mut self.pending_whitespace);
2580        self.had_content_yet_for_min_content = true;
2581        self.had_content_yet_for_max_content = true;
2582    }
2583
2584    /// Compute the [`ContentSizes`] of the given [`InlineFormattingContext`].
2585    fn compute(
2586        inline_formatting_context: &InlineFormattingContext,
2587        layout_context: &'layout_data LayoutContext,
2588        constraint_space: &'layout_data ConstraintSpace,
2589    ) -> InlineContentSizesResult {
2590        Self {
2591            layout_context,
2592            constraint_space,
2593            paragraph: ContentSizes::zero(),
2594            current_line: ContentSizes::zero(),
2595            pending_whitespace: ContentSizes::zero(),
2596            had_content_yet_for_min_content: false,
2597            had_content_yet_for_max_content: false,
2598            ending_inline_pbm_stack: Vec::new(),
2599            depends_on_block_constraints: false,
2600        }
2601        .traverse(inline_formatting_context)
2602    }
2603}
2604
2605/// Whether or not this character will rpevent a soft wrap opportunity when it
2606/// comes before or after an atomic inline element.
2607///
2608/// From <https://www.w3.org/TR/css-text-3/#line-break-details>:
2609///
2610/// > For Web-compatibility there is a soft wrap opportunity before and after each
2611/// > replaced element or other atomic inline, even when adjacent to a character that
2612/// > would normally suppress them, including U+00A0 NO-BREAK SPACE. However, with
2613/// > the exception of U+00A0 NO-BREAK SPACE, there must be no soft wrap opportunity
2614/// > between atomic inlines and adjacent characters belonging to the Unicode GL, WJ,
2615/// > or ZWJ line breaking classes.
2616fn char_prevents_soft_wrap_opportunity_when_before_or_after_atomic(character: char) -> bool {
2617    if character == '\u{00A0}' {
2618        return false;
2619    }
2620    let class = linebreak_property(character);
2621    class == XI_LINE_BREAKING_CLASS_GL ||
2622        class == XI_LINE_BREAKING_CLASS_WJ ||
2623        class == XI_LINE_BREAKING_CLASS_ZWJ
2624}