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