1use std::sync::Arc;
6
7use app_units::Au;
8use euclid::{Point2D, Rect, Size2D};
9use fonts::{FontMetrics, ShapedTextSlice};
10use layout_api::BoxAreaType;
11use malloc_size_of_derive::MallocSizeOf;
12use servo_base::id::PipelineId;
13use servo_base::print_tree::PrintTree;
14use servo_url::ServoUrl;
15use style::Zero;
16use style_traits::CSSPixel;
17use webrender_api::{FontInstanceKey, ImageKey};
18
19use super::{
20 BaseFragment, BoxFragment, ContainingBlockManager, HoistedSharedFragment, PositioningFragment,
21 Tag,
22};
23use crate::SharedStyle;
24use crate::cell::ArcRefCell;
25use crate::flow::inline::line::TextRunOffsets;
26use crate::geom::{LogicalSides, PhysicalPoint, PhysicalRect};
27use crate::layout_impl::LayoutThread;
28use crate::style_ext::ComputedValuesExt;
29
30#[derive(Clone, MallocSizeOf)]
31pub(crate) enum Fragment {
32 Box(#[conditional_malloc_size_of] Arc<BoxFragment>),
33 Float(#[conditional_malloc_size_of] Arc<BoxFragment>),
39 Positioning(#[conditional_malloc_size_of] Arc<PositioningFragment>),
40 AbsoluteOrFixedPositioned(ArcRefCell<HoistedSharedFragment>),
48 Text(#[conditional_malloc_size_of] Arc<TextFragment>),
49 Image(#[conditional_malloc_size_of] Arc<ImageFragment>),
50 IFrame(#[conditional_malloc_size_of] Arc<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<ShapedTextSlice>>,
75 pub justification_adjustment: Au,
77 pub offsets: Option<Box<TextRunOffsets>>,
80 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(&self) -> Option<&BaseFragment> {
102 Some(match self {
103 Fragment::Box(fragment) => &fragment.base,
104 Fragment::Text(fragment) => &fragment.base,
105 Fragment::AbsoluteOrFixedPositioned(_) => return None,
106 Fragment::Positioning(fragment) => &fragment.base,
107 Fragment::Image(fragment) => &fragment.base,
108 Fragment::IFrame(fragment) => &fragment.base,
109 Fragment::Float(fragment) => &fragment.base,
110 })
111 }
112
113 pub(crate) fn set_containing_block(&self, containing_block: &PhysicalRect<Au>) {
114 match self {
115 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
116 box_fragment.set_containing_block(containing_block)
117 },
118 Fragment::Positioning(positioning_fragment) => {
119 positioning_fragment.set_containing_block(containing_block)
120 },
121 Fragment::AbsoluteOrFixedPositioned(..) |
122 Fragment::Text(..) |
123 Fragment::Image(..) |
124 Fragment::IFrame(..) => {},
125 }
126 }
127
128 pub fn tag(&self) -> Option<Tag> {
129 self.base().and_then(|base| base.tag)
130 }
131
132 pub fn print(&self, tree: &mut PrintTree) {
133 match self {
134 Fragment::Box(fragment) => fragment.print(tree),
135 Fragment::Float(fragment) => {
136 tree.new_level("Float".to_string());
137 fragment.print(tree);
138 tree.end_level();
139 },
140 Fragment::AbsoluteOrFixedPositioned(_) => {
141 tree.add_item("AbsoluteOrFixedPositioned".to_string());
142 },
143 Fragment::Positioning(fragment) => fragment.print(tree),
144 Fragment::Text(fragment) => fragment.print(tree),
145 Fragment::Image(fragment) => fragment.print(tree),
146 Fragment::IFrame(fragment) => fragment.print(tree),
147 }
148 }
149
150 pub(crate) fn scrolling_area(&self, layout_thread: &LayoutThread) -> PhysicalRect<Au> {
151 match self {
152 Fragment::Box(fragment) | Fragment::Float(fragment) => fragment
153 .offset_by_containing_block(&fragment.scrollable_overflow(), layout_thread.into()),
154 _ => self.scrollable_overflow_for_parent(),
155 }
156 }
157
158 pub(crate) fn clear_scrollable_overflow(&self) {
162 match self {
163 Fragment::Box(fragment) | Fragment::Float(fragment) => {
164 fragment.clear_scrollable_overflow()
165 },
166 Fragment::Positioning(fragment) => fragment.clear_scrollable_overflow(),
167 _ => {},
168 }
169 }
170
171 pub(crate) fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
172 match self {
173 Fragment::Box(fragment) | Fragment::Float(fragment) => {
174 fragment.scrollable_overflow_for_parent()
175 },
176 Fragment::Positioning(fragment) => fragment.scrollable_overflow_for_parent(),
177 Fragment::AbsoluteOrFixedPositioned(_) |
178 Fragment::Text(..) |
179 Fragment::Image(..) |
180 Fragment::IFrame(..) => self.base().map(|base| base.rect()).unwrap_or_default(),
181 }
182 }
183
184 pub(crate) fn scrollable_overflow_padding_contribution_for_parent(
193 &self,
194 ) -> Option<PhysicalRect<Au>> {
195 match self {
196 Fragment::Box(fragment) | Fragment::Float(fragment) => {
198 if !fragment.style().clone_position().is_absolutely_positioned() {
199 Some(fragment.margin_rect())
200 } else {
201 None
202 }
203 },
204 Fragment::Positioning(fragment) => Some(fragment.base.rect()),
208 Fragment::AbsoluteOrFixedPositioned(_) => None,
209 Fragment::Text(..) | Fragment::Image(..) | Fragment::IFrame(..) => {
210 Some(self.base()?.rect())
211 },
212 }
213 }
214
215 pub(crate) fn cumulative_box_area_rect(
216 &self,
217 area: BoxAreaType,
218 containing_block_computation: ContainingBlockCalculation<'_>,
219 ) -> Option<PhysicalRect<Au>> {
220 match self {
221 Fragment::Box(fragment) | Fragment::Float(fragment) => Some(match area {
222 BoxAreaType::Content => {
223 fragment.cumulative_content_box_rect(containing_block_computation)
224 },
225 BoxAreaType::Padding => {
226 fragment.cumulative_padding_box_rect(containing_block_computation)
227 },
228 BoxAreaType::Border => {
229 fragment.cumulative_border_box_rect(containing_block_computation)
230 },
231 }),
232 Fragment::Positioning(fragment) => {
233 Some(fragment.offset_by_containing_block(
234 &fragment.base.rect(),
235 containing_block_computation,
236 ))
237 },
238 Fragment::Text(_) |
239 Fragment::AbsoluteOrFixedPositioned(_) |
240 Fragment::Image(_) |
241 Fragment::IFrame(_) => None,
242 }
243 }
244
245 pub(crate) fn client_rect(&self) -> Rect<i32, CSSPixel> {
246 let rect = match self {
247 Fragment::Box(fragment) | Fragment::Float(fragment) => {
248 if fragment.is_inline_box() {
254 return Rect::zero();
255 }
256
257 if fragment.is_table_wrapper() {
258 let mut rect = fragment.border_rect();
261 rect.origin = PhysicalPoint::zero();
262 rect
263 } else {
264 let mut rect = fragment.padding_rect();
265 rect.origin = PhysicalPoint::new(fragment.border.left, fragment.border.top);
266 rect
267 }
268 },
269 _ => return Rect::zero(),
270 };
271
272 let rect = Rect::new(
273 Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()),
274 Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()),
275 );
276 rect.round().to_i32()
277 }
278
279 pub(crate) fn children(&self) -> Option<&[Fragment]> {
280 match self {
281 Fragment::Box(fragment) | Fragment::Float(fragment) => Some(&fragment.children),
282 Fragment::Positioning(fragment) => Some(&fragment.children),
283 _ => None,
284 }
285 }
286
287 pub(crate) fn find<T>(
288 &self,
289 manager: &ContainingBlockManager<PhysicalRect<Au>>,
290 level: usize,
291 process_func: &mut impl FnMut(&Fragment, usize, &PhysicalRect<Au>) -> Option<T>,
292 ) -> Option<T> {
293 let containing_block = manager.get_containing_block_for_fragment(self);
294 if let Some(result) = process_func(self, level, containing_block) {
295 return Some(result);
296 }
297
298 match self {
299 Fragment::Box(fragment) | Fragment::Float(fragment) => {
300 let style = fragment.style();
301 let content_rect = fragment
302 .content_rect()
303 .translate(containing_block.origin.to_vector());
304 let padding_rect = fragment
305 .padding_rect()
306 .translate(containing_block.origin.to_vector());
307 let new_manager = if style
308 .establishes_containing_block_for_all_descendants(fragment.base.flags)
309 {
310 manager.new_for_absolute_and_fixed_descendants(&content_rect, &padding_rect)
311 } else if style
312 .establishes_containing_block_for_absolute_descendants(fragment.base.flags)
313 {
314 manager.new_for_absolute_descendants(&content_rect, &padding_rect)
315 } else {
316 manager.new_for_non_absolute_descendants(&content_rect)
317 };
318
319 fragment
320 .children
321 .iter()
322 .find_map(|child| child.find(&new_manager, level + 1, process_func))
323 },
324 Fragment::Positioning(fragment) => {
325 let content_rect = fragment
326 .base
327 .rect()
328 .translate(containing_block.origin.to_vector());
329 let new_manager = manager.new_for_non_absolute_descendants(&content_rect);
330 fragment
331 .children
332 .iter()
333 .find_map(|child| child.find(&new_manager, level + 1, process_func))
334 },
335 _ => None,
336 }
337 }
338
339 pub(crate) fn retrieve_box_fragment(&self) -> Option<&Arc<BoxFragment>> {
340 match self {
341 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => Some(box_fragment),
342 _ => None,
343 }
344 }
345}
346
347impl TextFragment {
348 pub fn print(&self, tree: &mut PrintTree) {
349 tree.add_item(format!(
350 "Text num_glyphs={} box={:?}",
351 self.glyphs
352 .iter()
353 .map(|shaped_text_slice| shaped_text_slice.glyph_count())
354 .sum::<usize>(),
355 self.base.rect()
356 ));
357 }
358
359 pub(crate) fn point_is_within_vertical_boundaries(
362 &self,
363 point_in_fragment: Point2D<Au, CSSPixel>,
364 ) -> bool {
365 let rect = &self.base.rect();
366 rect.min_y() <= point_in_fragment.y && rect.max_y() >= point_in_fragment.y
367 }
368
369 pub(crate) fn distance_to_point_for_glyph_offset(
373 &self,
374 point_in_fragment: Point2D<Au, CSSPixel>,
375 ) -> Au {
376 let rect = &self.base.rect();
379 let dx = (rect.min_x() - point_in_fragment.x)
380 .max(Au::zero())
381 .max(point_in_fragment.x - rect.max_x());
382 let dy = (rect.min_y() - point_in_fragment.y)
383 .max(Au::zero())
384 .max(point_in_fragment.y - rect.max_y());
385 Au::from_f64_px((dx.to_f64_px().powi(2) + dy.to_f64_px().powi(2)).sqrt())
386 }
387
388 pub(crate) fn character_offset(
396 &self,
397 point_in_fragment: Point2D<Au, CSSPixel>,
398 ) -> Option<usize> {
399 let offsets = self.offsets.as_ref()?;
401 let max_vertical_offset = self.base.rect().height().scale_by(0.25);
402 if point_in_fragment.y < -max_vertical_offset {
403 return Some(offsets.character_range.start);
404 }
405
406 if point_in_fragment.y > self.base.rect().max_y() + max_vertical_offset {
412 return None;
413 }
414
415 let mut current_character = offsets.character_range.start;
416 let mut current_offset = Au::zero();
417 for glyph_store in &self.glyphs {
418 for glyph in glyph_store.glyphs() {
419 let mut advance = glyph.advance();
420 if glyph.char_is_word_separator() {
421 advance += self.justification_adjustment;
422 }
423 if current_offset + advance.scale_by(0.5) >= point_in_fragment.x {
424 return Some(current_character);
425 }
426 current_offset += advance;
427 current_character += glyph.character_count();
428 }
429 }
430
431 Some(current_character)
432 }
433}
434
435impl ImageFragment {
436 pub fn print(&self, tree: &mut PrintTree) {
437 tree.add_item(format!(
438 "Image\
439 \nrect={:?}",
440 self.base.rect()
441 ));
442 }
443}
444
445impl IFrameFragment {
446 pub fn print(&self, tree: &mut PrintTree) {
447 tree.add_item(format!(
448 "IFrame\
449 \npipeline={:?} rect={:?}",
450 self.pipeline_id,
451 self.base.rect()
452 ));
453 }
454}
455
456impl CollapsedBlockMargins {
457 pub fn from_margin(margin: &LogicalSides<Au>) -> Self {
458 Self {
459 collapsed_through: false,
460 start: CollapsedMargin::new(margin.block_start),
461 end: CollapsedMargin::new(margin.block_end),
462 }
463 }
464
465 pub fn zero() -> Self {
466 Self {
467 collapsed_through: false,
468 start: CollapsedMargin::zero(),
469 end: CollapsedMargin::zero(),
470 }
471 }
472}
473
474impl CollapsedMargin {
475 pub fn zero() -> Self {
476 Self {
477 max_positive: Au::zero(),
478 min_negative: Au::zero(),
479 }
480 }
481
482 pub fn new(margin: Au) -> Self {
483 Self {
484 max_positive: margin.max(Au::zero()),
485 min_negative: margin.min(Au::zero()),
486 }
487 }
488
489 pub fn adjoin(&self, other: &Self) -> Self {
490 Self {
491 max_positive: self.max_positive.max(other.max_positive),
492 min_negative: self.min_negative.min(other.min_negative),
493 }
494 }
495
496 pub fn adjoin_assign(&mut self, other: &Self) {
497 *self = self.adjoin(other);
498 }
499
500 pub fn solve(&self) -> Au {
501 self.max_positive + self.min_negative
502 }
503}
504
505pub(crate) enum ContainingBlockCalculation<'a> {
512 Lazy { layout_thread: &'a LayoutThread },
516 AlreadyDoneWithStackingContextTree,
522}
523
524impl ContainingBlockCalculation<'_> {
525 pub(crate) fn ensure(&self) {
526 match self {
527 Self::Lazy { layout_thread } => layout_thread.ensure_containing_block_calculation(),
528 Self::AlreadyDoneWithStackingContextTree => {},
529 }
530 }
531}
532
533impl<'a> From<&'a LayoutThread> for ContainingBlockCalculation<'a> {
534 fn from(layout_thread: &'a LayoutThread) -> Self {
535 Self::Lazy { layout_thread }
536 }
537}