layout/fragment_tree/
fragment.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::sync::Arc;
6
7use app_units::Au;
8use atomic_refcell::{AtomicRef, AtomicRefMut};
9use base::id::PipelineId;
10use base::print_tree::PrintTree;
11use euclid::{Point2D, Rect, Size2D};
12use fonts::{FontMetrics, GlyphStore};
13use layout_api::BoxAreaType;
14use malloc_size_of_derive::MallocSizeOf;
15use servo_url::ServoUrl;
16use style::Zero;
17use style_traits::CSSPixel;
18use webrender_api::{FontInstanceKey, ImageKey};
19
20use super::{
21    BaseFragment, BoxFragment, ContainingBlockManager, HoistedSharedFragment, PositioningFragment,
22    Tag,
23};
24use crate::SharedStyle;
25use crate::cell::ArcRefCell;
26use crate::flow::inline::line::TextRunOffsets;
27use crate::geom::{LogicalSides, PhysicalPoint, PhysicalRect};
28use crate::style_ext::ComputedValuesExt;
29
30#[derive(Clone, MallocSizeOf)]
31pub(crate) enum Fragment {
32    Box(ArcRefCell<BoxFragment>),
33    /// Floating content. A floated fragment is very similar to a normal
34    /// [BoxFragment] but it isn't positioned using normal in block flow
35    /// positioning rules (margin collapse, etc). Instead, they are laid
36    /// out by the [crate::flow::float::SequentialLayoutState] of their
37    /// float containing block formatting context.
38    Float(ArcRefCell<BoxFragment>),
39    Positioning(ArcRefCell<PositioningFragment>),
40    /// Absolute and fixed position fragments are hoisted up so that they
41    /// are children of the BoxFragment that establishes their containing
42    /// blocks, so that they can be laid out properly. When this happens
43    /// an `AbsoluteOrFixedPositioned` fragment is left at the original tree
44    /// position. This allows these hoisted fragments to be painted with
45    /// regard to their original tree order during stacking context tree /
46    /// display list construction.
47    AbsoluteOrFixedPositioned(ArcRefCell<HoistedSharedFragment>),
48    Text(ArcRefCell<TextFragment>),
49    Image(ArcRefCell<ImageFragment>),
50    IFrame(ArcRefCell<IFrameFragment>),
51}
52
53#[derive(Clone, MallocSizeOf)]
54pub(crate) struct CollapsedBlockMargins {
55    pub collapsed_through: bool,
56    pub start: CollapsedMargin,
57    pub end: CollapsedMargin,
58}
59
60#[derive(Clone, Copy, Debug, MallocSizeOf)]
61pub(crate) struct CollapsedMargin {
62    max_positive: Au,
63    min_negative: Au,
64}
65
66#[derive(MallocSizeOf)]
67pub(crate) struct TextFragment {
68    pub base: BaseFragment,
69    pub selected_style: SharedStyle,
70    #[conditional_malloc_size_of]
71    pub font_metrics: Arc<FontMetrics>,
72    pub font_key: FontInstanceKey,
73    #[conditional_malloc_size_of]
74    pub glyphs: Vec<Arc<GlyphStore>>,
75    /// Extra space to add for each justification opportunity.
76    pub justification_adjustment: Au,
77    /// When necessary, this field store the [`TextRunOffsets`] for a particular
78    /// [`TextRunLineItem`]. This is currently only used inside of text inputs.
79    pub offsets: Option<Box<TextRunOffsets>>,
80    /// Whether or not this [`TextFragment`] is an empty fragment added for the
81    /// benefit of placing a text cursor on an otherwise empty editable line.
82    pub is_empty_for_text_cursor: bool,
83}
84
85#[derive(MallocSizeOf)]
86pub(crate) struct ImageFragment {
87    pub base: BaseFragment,
88    pub clip: PhysicalRect<Au>,
89    pub image_key: Option<ImageKey>,
90    pub showing_broken_image_icon: bool,
91    pub url: Option<ServoUrl>,
92}
93
94#[derive(MallocSizeOf)]
95pub(crate) struct IFrameFragment {
96    pub base: BaseFragment,
97    pub pipeline_id: PipelineId,
98}
99
100impl Fragment {
101    pub fn base<'a>(&'a self) -> Option<AtomicRef<'a, BaseFragment>> {
102        Some(match self {
103            Fragment::Box(fragment) => AtomicRef::map(fragment.borrow(), |fragment| &fragment.base),
104            Fragment::Text(fragment) => {
105                AtomicRef::map(fragment.borrow(), |fragment| &fragment.base)
106            },
107            Fragment::AbsoluteOrFixedPositioned(_) => return None,
108            Fragment::Positioning(fragment) => {
109                AtomicRef::map(fragment.borrow(), |fragment| &fragment.base)
110            },
111            Fragment::Image(fragment) => {
112                AtomicRef::map(fragment.borrow(), |fragment| &fragment.base)
113            },
114            Fragment::IFrame(fragment) => {
115                AtomicRef::map(fragment.borrow(), |fragment| &fragment.base)
116            },
117            Fragment::Float(fragment) => {
118                AtomicRef::map(fragment.borrow(), |fragment| &fragment.base)
119            },
120        })
121    }
122
123    pub fn base_mut<'a>(&'a self) -> Option<AtomicRefMut<'a, BaseFragment>> {
124        Some(match self {
125            Fragment::Box(fragment) => {
126                AtomicRefMut::map(fragment.borrow_mut(), |fragment| &mut fragment.base)
127            },
128            Fragment::Text(fragment) => {
129                AtomicRefMut::map(fragment.borrow_mut(), |fragment| &mut fragment.base)
130            },
131            Fragment::AbsoluteOrFixedPositioned(_) => return None,
132            Fragment::Positioning(fragment) => {
133                AtomicRefMut::map(fragment.borrow_mut(), |fragment| &mut fragment.base)
134            },
135            Fragment::Image(fragment) => {
136                AtomicRefMut::map(fragment.borrow_mut(), |fragment| &mut fragment.base)
137            },
138            Fragment::IFrame(fragment) => {
139                AtomicRefMut::map(fragment.borrow_mut(), |fragment| &mut fragment.base)
140            },
141            Fragment::Float(fragment) => {
142                AtomicRefMut::map(fragment.borrow_mut(), |fragment| &mut fragment.base)
143            },
144        })
145    }
146
147    pub(crate) fn set_containing_block(&self, containing_block: &PhysicalRect<Au>) {
148        match self {
149            Fragment::Box(box_fragment) => box_fragment
150                .borrow_mut()
151                .set_containing_block(containing_block),
152            Fragment::Float(float_fragment) => float_fragment
153                .borrow_mut()
154                .set_containing_block(containing_block),
155            Fragment::Positioning(positioning_fragment) => positioning_fragment
156                .borrow_mut()
157                .set_containing_block(containing_block),
158            Fragment::AbsoluteOrFixedPositioned(hoisted_shared_fragment) => {
159                if let Some(ref fragment) = hoisted_shared_fragment.borrow().fragment {
160                    fragment.set_containing_block(containing_block);
161                }
162            },
163            Fragment::Text(_) => {},
164            Fragment::Image(_) => {},
165            Fragment::IFrame(_) => {},
166        }
167    }
168
169    pub fn tag(&self) -> Option<Tag> {
170        self.base().and_then(|base| base.tag)
171    }
172
173    pub fn print(&self, tree: &mut PrintTree) {
174        match self {
175            Fragment::Box(fragment) => fragment.borrow().print(tree),
176            Fragment::Float(fragment) => {
177                tree.new_level("Float".to_string());
178                fragment.borrow().print(tree);
179                tree.end_level();
180            },
181            Fragment::AbsoluteOrFixedPositioned(_) => {
182                tree.add_item("AbsoluteOrFixedPositioned".to_string());
183            },
184            Fragment::Positioning(fragment) => fragment.borrow().print(tree),
185            Fragment::Text(fragment) => fragment.borrow().print(tree),
186            Fragment::Image(fragment) => fragment.borrow().print(tree),
187            Fragment::IFrame(fragment) => fragment.borrow().print(tree),
188        }
189    }
190
191    pub(crate) fn scrolling_area(&self) -> PhysicalRect<Au> {
192        match self {
193            Fragment::Box(fragment) | Fragment::Float(fragment) => {
194                let fragment = fragment.borrow();
195                fragment.offset_by_containing_block(&fragment.scrollable_overflow())
196            },
197            _ => self.scrollable_overflow_for_parent(),
198        }
199    }
200
201    pub(crate) fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
202        match self {
203            Fragment::Box(fragment) | Fragment::Float(fragment) => {
204                return fragment.borrow().scrollable_overflow_for_parent();
205            },
206            Fragment::Positioning(fragment) => fragment.borrow().scrollable_overflow_for_parent(),
207            Fragment::AbsoluteOrFixedPositioned(_) |
208            Fragment::Text(..) |
209            Fragment::Image(..) |
210            Fragment::IFrame(..) => self.base().map(|base| base.rect).unwrap_or_default(),
211        }
212    }
213
214    pub(crate) fn calculate_scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
215        self.calculate_scrollable_overflow();
216        self.scrollable_overflow_for_parent()
217    }
218
219    pub(crate) fn calculate_scrollable_overflow(&self) {
220        match self {
221            Fragment::Box(fragment) | Fragment::Float(fragment) => {
222                fragment.borrow_mut().calculate_scrollable_overflow()
223            },
224            Fragment::Positioning(fragment) => {
225                fragment.borrow_mut().calculate_scrollable_overflow()
226            },
227            _ => {},
228        }
229    }
230
231    pub(crate) fn cumulative_box_area_rect(&self, area: BoxAreaType) -> Option<PhysicalRect<Au>> {
232        match self {
233            Fragment::Box(fragment) | Fragment::Float(fragment) => Some(match area {
234                BoxAreaType::Content => fragment.borrow().cumulative_content_box_rect(),
235                BoxAreaType::Padding => fragment.borrow().cumulative_padding_box_rect(),
236                BoxAreaType::Border => fragment.borrow().cumulative_border_box_rect(),
237            }),
238            Fragment::Positioning(fragment) => {
239                let fragment = fragment.borrow();
240                Some(fragment.offset_by_containing_block(&fragment.base.rect))
241            },
242            Fragment::Text(_) |
243            Fragment::AbsoluteOrFixedPositioned(_) |
244            Fragment::Image(_) |
245            Fragment::IFrame(_) => None,
246        }
247    }
248
249    pub(crate) fn client_rect(&self) -> Rect<i32, CSSPixel> {
250        let rect = match self {
251            Fragment::Box(fragment) | Fragment::Float(fragment) => {
252                // https://drafts.csswg.org/cssom-view/#dom-element-clienttop
253                // " If the element has no associated CSS layout box or if the
254                //   CSS layout box is inline, return zero." For this check we
255                // also explicitly ignore the list item portion of the display
256                // style.
257                let fragment = fragment.borrow();
258                if fragment.is_inline_box() {
259                    return Rect::zero();
260                }
261
262                if fragment.is_table_wrapper() {
263                    // For tables the border actually belongs to the table grid box,
264                    // so we need to include it in the dimension of the table wrapper box.
265                    let mut rect = fragment.border_rect();
266                    rect.origin = PhysicalPoint::zero();
267                    rect
268                } else {
269                    let mut rect = fragment.padding_rect();
270                    rect.origin = PhysicalPoint::new(fragment.border.left, fragment.border.top);
271                    rect
272                }
273            },
274            _ => return Rect::zero(),
275        };
276
277        let rect = Rect::new(
278            Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()),
279            Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()),
280        );
281        rect.round().to_i32()
282    }
283
284    pub(crate) fn children<'a>(&'a self) -> Option<AtomicRef<'a, Vec<Fragment>>> {
285        match self {
286            Fragment::Box(fragment) | Fragment::Float(fragment) => {
287                let fragment = fragment.borrow();
288                Some(AtomicRef::map(fragment, |fragment| &fragment.children))
289            },
290            Fragment::Positioning(fragment) => {
291                let fragment = fragment.borrow();
292                Some(AtomicRef::map(fragment, |fragment| &fragment.children))
293            },
294            _ => None,
295        }
296    }
297
298    pub(crate) fn find<T>(
299        &self,
300        manager: &ContainingBlockManager<PhysicalRect<Au>>,
301        level: usize,
302        process_func: &mut impl FnMut(&Fragment, usize, &PhysicalRect<Au>) -> Option<T>,
303    ) -> Option<T> {
304        let containing_block = manager.get_containing_block_for_fragment(self);
305        if let Some(result) = process_func(self, level, containing_block) {
306            return Some(result);
307        }
308
309        match self {
310            Fragment::Box(fragment) | Fragment::Float(fragment) => {
311                let fragment = fragment.borrow();
312                let style = fragment.style();
313                let content_rect = fragment
314                    .content_rect()
315                    .translate(containing_block.origin.to_vector());
316                let padding_rect = fragment
317                    .padding_rect()
318                    .translate(containing_block.origin.to_vector());
319                let new_manager = if style
320                    .establishes_containing_block_for_all_descendants(fragment.base.flags)
321                {
322                    manager.new_for_absolute_and_fixed_descendants(&content_rect, &padding_rect)
323                } else if style
324                    .establishes_containing_block_for_absolute_descendants(fragment.base.flags)
325                {
326                    manager.new_for_absolute_descendants(&content_rect, &padding_rect)
327                } else {
328                    manager.new_for_non_absolute_descendants(&content_rect)
329                };
330
331                fragment
332                    .children
333                    .iter()
334                    .find_map(|child| child.find(&new_manager, level + 1, process_func))
335            },
336            Fragment::Positioning(fragment) => {
337                let fragment = fragment.borrow();
338                let content_rect = fragment
339                    .base
340                    .rect
341                    .translate(containing_block.origin.to_vector());
342                let new_manager = manager.new_for_non_absolute_descendants(&content_rect);
343                fragment
344                    .children
345                    .iter()
346                    .find_map(|child| child.find(&new_manager, level + 1, process_func))
347            },
348            _ => None,
349        }
350    }
351
352    pub(crate) fn retrieve_box_fragment(&self) -> Option<&ArcRefCell<BoxFragment>> {
353        match self {
354            Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => Some(box_fragment),
355            _ => None,
356        }
357    }
358}
359
360impl TextFragment {
361    pub fn print(&self, tree: &mut PrintTree) {
362        tree.add_item(format!(
363            "Text num_glyphs={} box={:?}",
364            self.glyphs
365                .iter()
366                .map(|glyph_store| glyph_store.len())
367                .sum::<usize>(),
368            self.base.rect
369        ));
370    }
371
372    /// Find the distance between for point relative to a [`TextFragment`] for the
373    /// purposes of finding a glyph offset. This is used to identify the most relevant
374    /// fragment for glyph offset queries during click handling.
375    pub(crate) fn distance_to_point_for_glyph_offset(
376        &self,
377        point_in_fragment: Point2D<Au, CSSPixel>,
378    ) -> Option<Au> {
379        // Accept any `TextFragment` that is within the vertical range of the point, as one
380        // can click past the end of a line to move the cursor to its end.
381        let rect = &self.base.rect;
382        if point_in_fragment.y < Au::zero() || point_in_fragment.y > rect.height() {
383            return None;
384        }
385        // Only consider clicks that are to the right of the fragment's origin.
386        if point_in_fragment.x < Au::zero() {
387            return None;
388        }
389        Some(point_in_fragment.x - rect.width().max(Au::zero()))
390    }
391
392    /// Given a point relative to this [`TextFragment`], find the most appropriate character
393    /// offset. Note that the given point may be outside the [`TextFragment`]'s content rect.
394    pub(crate) fn character_offset(&self, point_in_fragment: Point2D<Au, CSSPixel>) -> usize {
395        let Some(offsets) = self.offsets.as_ref() else {
396            return 0;
397        };
398
399        let mut current_character = offsets.character_range.start;
400        let mut current_offset = Au::zero();
401        for glyph_store in &self.glyphs {
402            for glyph in glyph_store.glyphs() {
403                let mut advance = glyph.advance();
404                if glyph.char_is_word_separator() {
405                    advance += self.justification_adjustment;
406                }
407                if current_offset + advance.scale_by(0.5) >= point_in_fragment.x {
408                    return current_character;
409                }
410                current_offset += advance;
411                current_character += glyph.character_count();
412            }
413        }
414
415        current_character
416    }
417}
418
419impl ImageFragment {
420    pub fn print(&self, tree: &mut PrintTree) {
421        tree.add_item(format!(
422            "Image\
423                \nrect={:?}",
424            self.base.rect
425        ));
426    }
427}
428
429impl IFrameFragment {
430    pub fn print(&self, tree: &mut PrintTree) {
431        tree.add_item(format!(
432            "IFrame\
433                \npipeline={:?} rect={:?}",
434            self.pipeline_id, self.base.rect
435        ));
436    }
437}
438
439impl CollapsedBlockMargins {
440    pub fn from_margin(margin: &LogicalSides<Au>) -> Self {
441        Self {
442            collapsed_through: false,
443            start: CollapsedMargin::new(margin.block_start),
444            end: CollapsedMargin::new(margin.block_end),
445        }
446    }
447
448    pub fn zero() -> Self {
449        Self {
450            collapsed_through: false,
451            start: CollapsedMargin::zero(),
452            end: CollapsedMargin::zero(),
453        }
454    }
455}
456
457impl CollapsedMargin {
458    pub fn zero() -> Self {
459        Self {
460            max_positive: Au::zero(),
461            min_negative: Au::zero(),
462        }
463    }
464
465    pub fn new(margin: Au) -> Self {
466        Self {
467            max_positive: margin.max(Au::zero()),
468            min_negative: margin.min(Au::zero()),
469        }
470    }
471
472    pub fn adjoin(&self, other: &Self) -> Self {
473        Self {
474            max_positive: self.max_positive.max(other.max_positive),
475            min_negative: self.min_negative.min(other.min_negative),
476        }
477    }
478
479    pub fn adjoin_assign(&mut self, other: &Self) {
480        *self = self.adjoin(other);
481    }
482
483    pub fn solve(&self) -> Au {
484        self.max_positive + self.min_negative
485    }
486}