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