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 euclid::{Point2D, Rect, Size2D};
10use fonts::{FontMetrics, GlyphStore};
11use layout_api::BoxAreaType;
12use malloc_size_of_derive::MallocSizeOf;
13use servo_base::id::PipelineId;
14use servo_base::print_tree::PrintTree;
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    /// Whether or not the given point is within the vertical boundaries of this
373    /// [`TextFragment`].
374    pub(crate) fn point_is_within_vertical_boundaries(
375        &self,
376        point_in_fragment: Point2D<Au, CSSPixel>,
377    ) -> bool {
378        let rect = &self.base.rect;
379        rect.min_y() <= point_in_fragment.y && rect.max_y() >= point_in_fragment.y
380    }
381
382    /// Find the distance between for point relative to a [`TextFragment`] for the
383    /// purposes of finding a glyph offset. This is used to identify the most relevant
384    /// fragment for glyph offset queries during click handling.
385    pub(crate) fn distance_to_point_for_glyph_offset(
386        &self,
387        point_in_fragment: Point2D<Au, CSSPixel>,
388    ) -> Au {
389        // This is the distance between the closest point on the edge of the rectangle and
390        // the point. From <https://stackoverflow.com/a/18157551>.
391        let rect = &self.base.rect;
392        let dx = (rect.min_x() - point_in_fragment.x)
393            .max(Au::zero())
394            .max(point_in_fragment.x - rect.max_x());
395        let dy = (rect.min_y() - point_in_fragment.y)
396            .max(Au::zero())
397            .max(point_in_fragment.y - rect.max_y());
398        Au::from_f64_px((dx.to_f64_px().powi(2) + dy.to_f64_px().powi(2)).sqrt())
399    }
400
401    /// Given a point relative to this [`TextFragment`], find the most appropriate
402    /// character offset.
403    ///
404    /// Note that the given point may be outside the [`TextFragment`]'s content rect:
405    ///
406    ///  - If the point is vertically above the [`TextFragment`] the first offset will be returned.
407    ///  - If the point is vertically below the [`TextFragment`], `None` will be returned.
408    pub(crate) fn character_offset(
409        &self,
410        point_in_fragment: Point2D<Au, CSSPixel>,
411    ) -> Option<usize> {
412        // If the click was far enough above the top of the fragment, then pick the first index.
413        let offsets = self.offsets.as_ref()?;
414        let max_vertical_offset = self.base.rect.height().scale_by(0.25);
415        if point_in_fragment.y < -max_vertical_offset {
416            return Some(offsets.character_range.start);
417        }
418
419        // If the click was below the fragment, return `None`, which will cause the
420        // caller to move the cursor to the end.
421        //
422        // TODO: It would be nice to just return the last offset here, but <textarea>
423        // does not currently make a fragment for all selection indices.
424        if point_in_fragment.y > self.base.rect.max_y() + max_vertical_offset {
425            return None;
426        }
427
428        let mut current_character = offsets.character_range.start;
429        let mut current_offset = Au::zero();
430        for glyph_store in &self.glyphs {
431            for glyph in glyph_store.glyphs() {
432                let mut advance = glyph.advance();
433                if glyph.char_is_word_separator() {
434                    advance += self.justification_adjustment;
435                }
436                if current_offset + advance.scale_by(0.5) >= point_in_fragment.x {
437                    return Some(current_character);
438                }
439                current_offset += advance;
440                current_character += glyph.character_count();
441            }
442        }
443
444        Some(current_character)
445    }
446}
447
448impl ImageFragment {
449    pub fn print(&self, tree: &mut PrintTree) {
450        tree.add_item(format!(
451            "Image\
452                \nrect={:?}",
453            self.base.rect
454        ));
455    }
456}
457
458impl IFrameFragment {
459    pub fn print(&self, tree: &mut PrintTree) {
460        tree.add_item(format!(
461            "IFrame\
462                \npipeline={:?} rect={:?}",
463            self.pipeline_id, self.base.rect
464        ));
465    }
466}
467
468impl CollapsedBlockMargins {
469    pub fn from_margin(margin: &LogicalSides<Au>) -> Self {
470        Self {
471            collapsed_through: false,
472            start: CollapsedMargin::new(margin.block_start),
473            end: CollapsedMargin::new(margin.block_end),
474        }
475    }
476
477    pub fn zero() -> Self {
478        Self {
479            collapsed_through: false,
480            start: CollapsedMargin::zero(),
481            end: CollapsedMargin::zero(),
482        }
483    }
484}
485
486impl CollapsedMargin {
487    pub fn zero() -> Self {
488        Self {
489            max_positive: Au::zero(),
490            min_negative: Au::zero(),
491        }
492    }
493
494    pub fn new(margin: Au) -> Self {
495        Self {
496            max_positive: margin.max(Au::zero()),
497            min_negative: margin.min(Au::zero()),
498        }
499    }
500
501    pub fn adjoin(&self, other: &Self) -> Self {
502        Self {
503            max_positive: self.max_positive.max(other.max_positive),
504            min_negative: self.min_negative.min(other.min_negative),
505        }
506    }
507
508    pub fn adjoin_assign(&mut self, other: &Self) {
509        *self = self.adjoin(other);
510    }
511
512    pub fn solve(&self) -> Au {
513        self.max_positive + self.min_negative
514    }
515}