Skip to main content

layout/flow/inline/
inline_box.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::vec::IntoIter;
6
7use app_units::Au;
8use fonts::FontRef;
9use layout_api::LayoutNode;
10use malloc_size_of_derive::MallocSizeOf;
11use script::layout_dom::ServoLayoutNode;
12use servo_arc::Arc as ServoArc;
13use style::Zero;
14use style::computed_values::alignment_baseline::T as AlignmentBaseline;
15use style::computed_values::baseline_source::T as BaselineSource;
16use style::computed_values::box_decoration_break::T as BoxDecorationBreak;
17use style::context::SharedStyleContext;
18use style::properties::ComputedValues;
19use style::values::computed::{BaselineShift, LengthPercentage};
20
21use super::{
22    InlineContainerState, InlineContainerStateFlags, SharedInlineStyles,
23    inline_container_needs_strut,
24};
25use crate::ContainingBlock;
26use crate::cell::ArcRefCell;
27use crate::context::LayoutContext;
28use crate::dom_traversal::NodeAndStyleInfo;
29use crate::fragment_tree::BaseFragmentInfo;
30use crate::layout_box_base::LayoutBoxBase;
31use crate::style_ext::{ComputedValuesExt, LayoutStyle, PaddingBorderMargin};
32
33#[derive(Debug, MallocSizeOf)]
34pub(crate) struct InlineBox {
35    pub base: LayoutBoxBase,
36    /// The [`SharedInlineStyles`] for this [`InlineBox`] that are used to share styles
37    /// with all [`super::TextRun`] children.
38    pub(super) shared_inline_styles: SharedInlineStyles,
39    /// The identifier of this inline box in the containing [`super::InlineFormattingContext`].
40    pub(super) identifier: InlineBoxIdentifier,
41    /// The index of the default font in the [`super::InlineFormattingContext`]'s font metrics store.
42    /// This is initialized during IFC shaping.
43    pub default_font: Option<FontRef>,
44    /// Whether or not this [`InlineBox`] breaks shaping at the start.
45    pub breaks_shaping_at_start: bool,
46    /// Whether or not this [`InlineBox`] breaks shaping at the end.
47    pub breaks_shaping_at_end: bool,
48}
49
50impl InlineBox {
51    pub(crate) fn new(info: &NodeAndStyleInfo, context: &LayoutContext) -> Self {
52        let (breaks_shaping_at_start, breaks_shaping_at_end) =
53            inline_box_style_breaks_shaping(&info.style);
54        Self {
55            base: LayoutBoxBase::new(info.into(), info.style.clone()),
56            shared_inline_styles: SharedInlineStyles::from_info_and_context(info, context),
57            // This will be assigned later, when the box is actually added to the IFC.
58            identifier: InlineBoxIdentifier::default(),
59            default_font: None,
60            breaks_shaping_at_start,
61            breaks_shaping_at_end,
62        }
63    }
64
65    #[inline]
66    pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
67        LayoutStyle::Default(&self.base.style)
68    }
69
70    pub(crate) fn repair_style(
71        &mut self,
72        context: &SharedStyleContext,
73        node: &ServoLayoutNode,
74        new_style: &ServoArc<ComputedValues>,
75    ) {
76        (self.breaks_shaping_at_start, self.breaks_shaping_at_end) =
77            inline_box_style_breaks_shaping(new_style);
78
79        self.base.repair_style(new_style);
80        *self.shared_inline_styles.style.borrow_mut() = new_style.clone();
81        *self.shared_inline_styles.selected.borrow_mut() = node.selected_style(context);
82    }
83}
84
85#[derive(Debug, Default, MallocSizeOf)]
86pub(crate) struct InlineBoxes {
87    /// A collection of all inline boxes in a particular [`super::InlineFormattingContext`].
88    inline_boxes: Vec<ArcRefCell<InlineBox>>,
89
90    /// A list of tokens that represent the actual tree of inline boxes, while allowing
91    /// easy traversal forward and backwards through the tree. This structure is also
92    /// stored in the [`super::InlineFormattingContext::inline_items`], but this version is
93    /// faster to iterate.
94    inline_box_tree: Vec<InlineBoxTreePathToken>,
95}
96
97impl InlineBoxes {
98    pub(super) fn len(&self) -> usize {
99        self.inline_boxes.len()
100    }
101
102    pub(super) fn iter(&self) -> impl Iterator<Item = &ArcRefCell<InlineBox>> {
103        self.inline_boxes.iter()
104    }
105
106    pub(super) fn get(&self, identifier: &InlineBoxIdentifier) -> ArcRefCell<InlineBox> {
107        self.inline_boxes[identifier.index_in_inline_boxes as usize].clone()
108    }
109
110    pub(super) fn end_inline_box(&mut self, identifier: InlineBoxIdentifier) {
111        self.inline_box_tree
112            .push(InlineBoxTreePathToken::End(identifier));
113    }
114
115    pub(super) fn start_inline_box(
116        &mut self,
117        inline_box: ArcRefCell<InlineBox>,
118    ) -> InlineBoxIdentifier {
119        assert!(self.inline_boxes.len() <= u32::MAX as usize);
120        assert!(self.inline_box_tree.len() <= u32::MAX as usize);
121
122        let index_in_inline_boxes = self.inline_boxes.len() as u32;
123        let index_of_start_in_tree = self.inline_box_tree.len() as u32;
124
125        let identifier = InlineBoxIdentifier {
126            index_of_start_in_tree,
127            index_in_inline_boxes,
128        };
129        inline_box.borrow_mut().identifier = identifier;
130
131        self.inline_boxes.push(inline_box);
132        self.inline_box_tree
133            .push(InlineBoxTreePathToken::Start(identifier));
134
135        identifier
136    }
137
138    pub(super) fn get_path(
139        &self,
140        from: Option<InlineBoxIdentifier>,
141        to: InlineBoxIdentifier,
142    ) -> IntoIter<InlineBoxTreePathToken> {
143        if from == Some(to) {
144            return Vec::new().into_iter();
145        }
146
147        let mut from_index = match from {
148            Some(InlineBoxIdentifier {
149                index_of_start_in_tree,
150                ..
151            }) => index_of_start_in_tree as usize,
152            None => 0,
153        };
154        let mut to_index = to.index_of_start_in_tree as usize;
155        let is_reversed = to_index < from_index;
156
157        // Do not include the first or final token, depending on direction. These can be equal
158        // if we are starting or going to the root of the inline formatting context, in which
159        // case we don't want to adjust.
160        if to_index > from_index && from.is_some() {
161            from_index += 1;
162        } else if to_index < from_index {
163            to_index += 1;
164        }
165
166        let mut path = Vec::with_capacity(from_index.abs_diff(to_index));
167        let min = from_index.min(to_index);
168        let max = from_index.max(to_index);
169
170        for token in &self.inline_box_tree[min..=max] {
171            // Skip useless recursion into inline boxes; we are looking for a direct path.
172            if Some(&token.reverse()) == path.last() {
173                path.pop();
174            } else {
175                path.push(*token);
176            }
177        }
178
179        if is_reversed {
180            path.reverse();
181            for token in path.iter_mut() {
182                *token = token.reverse();
183            }
184        }
185
186        path.into_iter()
187    }
188}
189
190#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
191pub(super) enum InlineBoxTreePathToken {
192    Start(InlineBoxIdentifier),
193    End(InlineBoxIdentifier),
194}
195
196impl InlineBoxTreePathToken {
197    fn reverse(&self) -> Self {
198        match self {
199            Self::Start(index) => Self::End(*index),
200            Self::End(index) => Self::Start(*index),
201        }
202    }
203}
204
205/// An identifier for a particular [`InlineBox`] to be used to fetch it from an [`InlineBoxes`]
206/// store of inline boxes.
207///
208/// [`u32`] is used for the index, in order to save space. The value refers to the token
209/// in the start tree data structure which can be fetched to find the actual index of
210/// of the [`InlineBox`] in [`InlineBoxes::inline_boxes`].
211#[derive(Clone, Copy, Debug, Default, Eq, Hash, MallocSizeOf, PartialEq)]
212pub(crate) struct InlineBoxIdentifier {
213    pub index_of_start_in_tree: u32,
214    pub index_in_inline_boxes: u32,
215}
216
217pub(super) struct InlineBoxContainerState {
218    /// The container state common to both [`InlineBox`] and the root of the
219    /// [`super::InlineFormattingContext`].
220    pub base: InlineContainerState,
221
222    /// The [`InlineBoxIdentifier`] of this inline container state. If this is the root
223    /// the identifier is [`None`].
224    pub identifier: InlineBoxIdentifier,
225
226    /// The [`BaseFragmentInfo`] of the [`InlineBox`] that this state tracks.
227    pub base_fragment_info: BaseFragmentInfo,
228
229    /// The [`PaddingBorderMargin`] of the [`InlineBox`] that this state tracks.
230    pub pbm: PaddingBorderMargin,
231}
232
233impl InlineBoxContainerState {
234    pub(super) fn new(
235        inline_box: &InlineBox,
236        containing_block: &ContainingBlock,
237        layout_context: &LayoutContext,
238        parent_container: &InlineContainerState,
239        default_font: Option<FontRef>,
240    ) -> Self {
241        let style = inline_box.base.style.clone();
242        let pbm = inline_box
243            .layout_style()
244            .padding_border_margin(containing_block);
245
246        let mut flags = InlineContainerStateFlags::empty();
247        if inline_container_needs_strut(&style, layout_context, Some(&pbm)) {
248            flags.insert(InlineContainerStateFlags::CREATE_STRUT);
249        }
250
251        Self {
252            base: InlineContainerState::new(style, flags, Some(parent_container), default_font),
253            identifier: inline_box.identifier,
254            base_fragment_info: inline_box.base.base_fragment_info,
255            pbm,
256        }
257    }
258
259    pub(super) fn calculate_space_above_baseline(&self) -> Au {
260        let font_metrics = &self.base.font_metrics;
261        let (ascent, descent, line_gap) = (
262            font_metrics.ascent,
263            font_metrics.descent,
264            font_metrics.line_gap,
265        );
266        let leading = line_gap - (ascent + descent);
267        leading.scale_by(0.5) + ascent
268    }
269
270    pub(super) fn should_clone_pbm(&self) -> bool {
271        self.base.style.get_border().box_decoration_break == BoxDecorationBreak::Clone
272    }
273}
274
275/// Whether or not an inline box style breaks shaping at the start and end.
276/// The return value is `(breaks_shaping_at_start, breaks_shaping_at_end)`.
277///
278/// From <https://drafts.csswg.org/css-text/#boundary-shaping>
279/// > Text shaping must be broken at inline box boundaries when any of the
280/// > following are true for any box whose boundary separates the two typographic
281/// > character units:
282/// > - Any of margin/border/padding separating the two typographic character units
283/// >   in the inline axis is non-zero.
284/// > - `vertical-align` is not `baseline`.
285/// > - The boundary is a bidi isolation boundary.
286fn inline_box_style_breaks_shaping(style: &ComputedValues) -> (bool, bool) {
287    // Note: These steps are reordered for performance reasons and bidi isolation is handled
288    // intrinsically by the fact that bidi isolation inserts bidi characters into the inline
289    // formatting context text content. This leads to non-contiguous character offsets between
290    // shaping queue entries.
291    if style.clone_baseline_shift() != BaselineShift::zero() ||
292        style.clone_baseline_source() != BaselineSource::Auto ||
293        style.clone_alignment_baseline() != AlignmentBaseline::Baseline
294    {
295        return (true, true);
296    }
297
298    let layout_style = LayoutStyle::Default(style);
299    let border_widths = layout_style.border_width(style.writing_mode);
300    let padding = layout_style.padding(style.writing_mode);
301    let margin = style.margin(style.writing_mode);
302
303    (
304        !border_widths.inline_start.is_zero() ||
305            !padding.inline_start.is_zero() ||
306            !margin
307                .inline_start
308                .non_auto()
309                .is_none_or(LengthPercentage::is_zero),
310        !border_widths.inline_end.is_zero() ||
311            !padding.inline_end.is_zero() ||
312            !margin
313                .inline_end
314                .non_auto()
315                .is_none_or(LengthPercentage::is_zero),
316    )
317}