1use std::cell::{OnceCell, RefCell};
6use std::sync::Arc;
7
8use app_units::{AU_PER_PX, Au};
9use base::id::ScrollTreeNodeId;
10use clip::{Clip, ClipId};
11use euclid::{Point2D, Rect, Scale, SideOffsets2D, Size2D, UnknownUnit, Vector2D};
12use fonts::GlyphStore;
13use gradient::WebRenderGradient;
14use net_traits::image_cache::Image as CachedImage;
15use paint_api::display_list::{PaintDisplayListInfo, SpatialTreeNodeInfo};
16use paint_api::largest_contentful_paint_candidate::{LCPCandidateID, LargestContentfulPaintType};
17use servo_arc::Arc as ServoArc;
18use servo_config::opts::DiagnosticsLogging;
19use servo_geometry::MaxRect;
20use style::Zero;
21use style::color::{AbsoluteColor, ColorSpace};
22use style::computed_values::border_image_outset::T as BorderImageOutset;
23use style::computed_values::text_decoration_style::{
24 T as ComputedTextDecorationStyle, T as TextDecorationStyle,
25};
26use style::dom::OpaqueNode;
27use style::properties::ComputedValues;
28use style::properties::longhands::visibility::computed_value::T as Visibility;
29use style::properties::style_structs::Border;
30use style::values::computed::{
31 BorderImageSideWidth, BorderImageWidth, BorderStyle, LengthPercentage,
32 NonNegativeLengthOrNumber, NumberOrPercentage, OutlineStyle,
33};
34use style::values::generics::NonNegative;
35use style::values::generics::color::ColorOrAuto;
36use style::values::generics::rect::Rect as StyleRect;
37use style::values::specified::text::TextDecorationLine;
38use style_traits::{CSSPixel as StyloCSSPixel, DevicePixel as StyloDevicePixel};
39use webrender_api::units::{
40 DeviceIntSize, DevicePixel, LayoutPixel, LayoutRect, LayoutSideOffsets, LayoutSize,
41};
42use webrender_api::{
43 self as wr, BorderDetails, BorderRadius, BorderSide, BoxShadowClipMode, BuiltDisplayList,
44 ClipChainId, ClipMode, ColorF, CommonItemProperties, ComplexClipRegion, NinePatchBorder,
45 NinePatchBorderSource, NormalBorder, PrimitiveFlags, PropertyBinding, SpatialId,
46 SpatialTreeItemKey, units,
47};
48use wr::units::LayoutVector2D;
49
50use crate::cell::ArcRefCell;
51use crate::context::{ImageResolver, ResolvedImage};
52pub(crate) use crate::display_list::conversions::ToWebRender;
53use crate::display_list::stacking_context::StackingContextSection;
54use crate::fragment_tree::{
55 BackgroundMode, BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo, Tag,
56 TextFragment,
57};
58use crate::geom::{
59 LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize,
60};
61use crate::replaced::NaturalSizes;
62use crate::style_ext::{BorderStyleColor, ComputedValuesExt};
63
64mod background;
65mod clip;
66mod conversions;
67mod gradient;
68mod hit_test;
69mod largest_contenful_paint_candidate_collector;
70mod stacking_context;
71
72use background::BackgroundPainter;
73pub(crate) use hit_test::HitTest;
74pub(crate) use largest_contenful_paint_candidate_collector::LargestContentfulPaintCandidateCollector;
75pub(crate) use stacking_context::*;
76
77const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(AU_PER_PX);
78
79pub(crate) struct DisplayListBuilder<'a> {
80 current_scroll_node_id: ScrollTreeNodeId,
85
86 current_reference_frame_scroll_node_id: ScrollTreeNodeId,
90
91 current_clip_id: ClipId,
96
97 pub webrender_display_list_builder: &'a mut wr::DisplayListBuilder,
99
100 pub paint_info: &'a mut PaintDisplayListInfo,
102
103 inspector_highlight: Option<InspectorHighlight>,
108
109 paint_body_background: bool,
113
114 clip_map: Vec<ClipChainId>,
117
118 image_resolver: Arc<ImageResolver>,
120
121 device_pixel_ratio: Scale<f32, StyloCSSPixel, StyloDevicePixel>,
123
124 lcp_candidate_collector: Option<&'a mut LargestContentfulPaintCandidateCollector>,
126}
127
128struct InspectorHighlight {
129 tag: Tag,
131
132 state: Option<HighlightTraversalState>,
137}
138
139struct HighlightTraversalState {
140 content_box: Rect<Au, StyloCSSPixel>,
143
144 spatial_id: SpatialId,
145
146 clip_chain_id: ClipChainId,
147
148 maybe_box_fragment: Option<ArcRefCell<BoxFragment>>,
151}
152
153impl InspectorHighlight {
154 fn for_node(node: OpaqueNode) -> Self {
155 Self {
156 tag: Tag {
157 node,
158 pseudo_element_chain: Default::default(),
160 },
161 state: None,
162 }
163 }
164}
165
166impl DisplayListBuilder<'_> {
167 pub(crate) fn build(
168 stacking_context_tree: &mut StackingContextTree,
169 fragment_tree: &FragmentTree,
170 image_resolver: Arc<ImageResolver>,
171 device_pixel_ratio: Scale<f32, StyloCSSPixel, StyloDevicePixel>,
172 highlighted_dom_node: Option<OpaqueNode>,
173 debug: &DiagnosticsLogging,
174 lcp_candidate_collector: Option<&mut LargestContentfulPaintCandidateCollector>,
175 ) -> BuiltDisplayList {
176 let paint_info = &mut stacking_context_tree.paint_info;
178 let pipeline_id = paint_info.pipeline_id;
179 let mut webrender_display_list_builder =
180 webrender_api::DisplayListBuilder::new(pipeline_id);
181 webrender_display_list_builder.begin();
182
183 if debug.display_list {
188 webrender_display_list_builder.dump_serialized_display_list();
189 }
190
191 let _span = profile_traits::trace_span!("DisplayListBuilder::build").entered();
192 let mut builder = DisplayListBuilder {
193 current_scroll_node_id: paint_info.root_reference_frame_id,
194 current_reference_frame_scroll_node_id: paint_info.root_reference_frame_id,
195 current_clip_id: ClipId::INVALID,
196 webrender_display_list_builder: &mut webrender_display_list_builder,
197 paint_info,
198 inspector_highlight: highlighted_dom_node.map(InspectorHighlight::for_node),
199 paint_body_background: true,
200 clip_map: Default::default(),
201 image_resolver,
202 device_pixel_ratio,
203 lcp_candidate_collector,
204 };
205
206 builder.add_all_spatial_nodes();
207
208 for clip in stacking_context_tree.clip_store.0.iter() {
209 builder.add_clip_to_display_list(clip);
210 }
211
212 let pipeline_id = builder.paint_info.pipeline_id;
215 let viewport_size = builder.paint_info.viewport_details.size;
216 let viewport_rect = LayoutRect::from_size(viewport_size.cast_unit());
217 builder.wr().push_hit_test(
218 viewport_rect,
219 ClipChainId::INVALID,
220 SpatialId::root_reference_frame(pipeline_id),
221 PrimitiveFlags::default(),
222 (0, 0), );
224
225 stacking_context_tree
227 .root_stacking_context
228 .build_canvas_background_display_list(&mut builder, fragment_tree);
229 stacking_context_tree
230 .root_stacking_context
231 .build_display_list(&mut builder);
232 builder.paint_dom_inspector_highlight();
233
234 webrender_display_list_builder.end().1
235 }
236
237 fn wr(&mut self) -> &mut wr::DisplayListBuilder {
238 self.webrender_display_list_builder
239 }
240
241 fn pipeline_id(&mut self) -> wr::PipelineId {
242 self.paint_info.pipeline_id
243 }
244
245 fn mark_is_contentful(&mut self) {
246 self.paint_info.is_contentful = true;
247 }
248
249 fn spatial_id(&self, id: ScrollTreeNodeId) -> SpatialId {
250 self.paint_info.scroll_tree.webrender_id(id)
251 }
252
253 fn clip_chain_id(&self, id: ClipId) -> ClipChainId {
254 match id {
255 ClipId::INVALID => ClipChainId::INVALID,
256 _ => *self
257 .clip_map
258 .get(id.0)
259 .expect("Should never try to get clip before adding it to WebRender display list"),
260 }
261 }
262
263 pub(crate) fn add_all_spatial_nodes(&mut self) {
264 let mut spatial_tree_count = 0;
268 let mut scroll_tree = std::mem::take(&mut self.paint_info.scroll_tree);
269 let mut mapping = Vec::with_capacity(scroll_tree.nodes.len());
270
271 mapping.push(SpatialId::root_reference_frame(self.pipeline_id()));
272 mapping.push(SpatialId::root_scroll_node(self.pipeline_id()));
273
274 let pipeline_id = self.pipeline_id();
275 let pipeline_tag = ((pipeline_id.0 as u64) << 32) | pipeline_id.1 as u64;
276
277 for node in scroll_tree.nodes.iter().skip(2) {
278 let parent_scroll_node_id = node
279 .parent
280 .expect("Should have already added root reference frame");
281 let parent_spatial_node_id = mapping
282 .get(parent_scroll_node_id.index)
283 .expect("Should add spatial nodes to display list in order");
284
285 spatial_tree_count += 1;
288 let spatial_tree_item_key = SpatialTreeItemKey::new(pipeline_tag, spatial_tree_count);
289
290 mapping.push(match &node.info {
291 SpatialTreeNodeInfo::ReferenceFrame(info) => {
292 let spatial_id = self.wr().push_reference_frame(
293 info.origin,
294 *parent_spatial_node_id,
295 info.transform_style,
296 PropertyBinding::Value(*info.transform.to_transform()),
297 info.kind,
298 spatial_tree_item_key,
299 );
300 self.wr().pop_reference_frame();
301 spatial_id
302 },
303 SpatialTreeNodeInfo::Scroll(info) => {
304 self.wr().define_scroll_frame(
305 *parent_spatial_node_id,
306 info.external_id,
307 info.content_rect,
308 info.clip_rect,
309 LayoutVector2D::zero(), 0, wr::HasScrollLinkedEffect::No,
312 spatial_tree_item_key,
313 )
314 },
315 SpatialTreeNodeInfo::Sticky(info) => {
316 self.wr().define_sticky_frame(
317 *parent_spatial_node_id,
318 info.frame_rect,
319 info.margins,
320 info.vertical_offset_bounds,
321 info.horizontal_offset_bounds,
322 LayoutVector2D::zero(), spatial_tree_item_key,
324 None, )
326 },
327 });
328 }
329
330 scroll_tree.update_mapping(mapping);
331 self.paint_info.scroll_tree = scroll_tree;
332 }
333
334 pub(crate) fn add_clip_to_display_list(&mut self, clip: &Clip) -> ClipChainId {
342 assert_eq!(
343 clip.id.0,
344 self.clip_map.len(),
345 "Clips should be added in order"
346 );
347
348 let spatial_id = self.spatial_id(clip.parent_scroll_node_id);
349 let new_clip_id = if clip.radii.is_zero() {
350 self.wr().define_clip_rect(spatial_id, clip.rect)
351 } else {
352 self.wr().define_clip_rounded_rect(
353 spatial_id,
354 ComplexClipRegion {
355 rect: clip.rect,
356 radii: clip.radii,
357 mode: ClipMode::Clip,
358 },
359 )
360 };
361
362 let parent_clip_chain_id = match self.clip_chain_id(clip.parent_clip_id) {
367 ClipChainId::INVALID => None,
368 parent => Some(parent),
369 };
370 let clip_chain_id = self
371 .wr()
372 .define_clip_chain(parent_clip_chain_id, [new_clip_id]);
373 self.clip_map.push(clip_chain_id);
374 clip_chain_id
375 }
376
377 fn maybe_create_clip(
381 &mut self,
382 radii: wr::BorderRadius,
383 rect: units::LayoutRect,
384 force_clip_creation: bool,
385 ) -> Option<ClipChainId> {
386 if radii.is_zero() && !force_clip_creation {
387 return None;
388 }
389
390 Some(self.add_clip_to_display_list(&Clip {
391 id: ClipId(self.clip_map.len()),
392 radii,
393 rect,
394 parent_scroll_node_id: self.current_scroll_node_id,
395 parent_clip_id: self.current_clip_id,
396 }))
397 }
398
399 fn common_properties(
400 &self,
401 clip_rect: units::LayoutRect,
402 style: &ComputedValues,
403 ) -> wr::CommonItemProperties {
404 wr::CommonItemProperties {
408 clip_rect,
409 spatial_id: self.spatial_id(self.current_scroll_node_id),
410 clip_chain_id: self.clip_chain_id(self.current_clip_id),
411 flags: style.get_webrender_primitive_flags(),
412 }
413 }
414
415 fn paint_dom_inspector_highlight(&mut self) {
417 let Some(highlight) = self
418 .inspector_highlight
419 .take()
420 .and_then(|highlight| highlight.state)
421 else {
422 return;
423 };
424
425 const CONTENT_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
426 r: 0.23,
427 g: 0.7,
428 b: 0.87,
429 a: 0.5,
430 };
431
432 const PADDING_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
433 r: 0.49,
434 g: 0.3,
435 b: 0.7,
436 a: 0.5,
437 };
438
439 const BORDER_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
440 r: 0.2,
441 g: 0.2,
442 b: 0.2,
443 a: 0.5,
444 };
445
446 const MARGIN_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
447 r: 1.,
448 g: 0.93,
449 b: 0.,
450 a: 0.5,
451 };
452
453 let content_box = highlight.content_box.to_webrender();
455 let properties = wr::CommonItemProperties {
456 clip_rect: content_box,
457 spatial_id: highlight.spatial_id,
458 clip_chain_id: highlight.clip_chain_id,
459 flags: wr::PrimitiveFlags::default(),
460 };
461
462 self.wr()
463 .push_rect(&properties, content_box, CONTENT_BOX_HIGHLIGHT_COLOR);
464
465 if let Some(box_fragment) = highlight.maybe_box_fragment {
467 let mut paint_highlight =
468 |color: webrender_api::ColorF,
469 fragment_relative_bounds: PhysicalRect<Au>,
470 widths: webrender_api::units::LayoutSideOffsets| {
471 if widths.is_zero() {
472 return;
473 }
474
475 let bounds = box_fragment
476 .borrow()
477 .offset_by_containing_block(&fragment_relative_bounds)
478 .to_webrender();
479
480 let border_style = wr::BorderSide {
482 color,
483 style: wr::BorderStyle::Solid,
484 };
485
486 let details = wr::BorderDetails::Normal(wr::NormalBorder {
487 top: border_style,
488 right: border_style,
489 bottom: border_style,
490 left: border_style,
491 radius: webrender_api::BorderRadius::default(),
492 do_aa: true,
493 });
494
495 let common = wr::CommonItemProperties {
496 clip_rect: bounds,
497 spatial_id: highlight.spatial_id,
498 clip_chain_id: highlight.clip_chain_id,
499 flags: wr::PrimitiveFlags::default(),
500 };
501 self.wr().push_border(&common, bounds, widths, details)
502 };
503
504 let box_fragment = box_fragment.borrow();
505 paint_highlight(
506 PADDING_BOX_HIGHLIGHT_COLOR,
507 box_fragment.padding_rect(),
508 box_fragment.padding.to_webrender(),
509 );
510 paint_highlight(
511 BORDER_BOX_HIGHLIGHT_COLOR,
512 box_fragment.border_rect(),
513 box_fragment.border.to_webrender(),
514 );
515 paint_highlight(
516 MARGIN_BOX_HIGHLIGHT_COLOR,
517 box_fragment.margin_rect(),
518 box_fragment.margin.to_webrender(),
519 );
520 }
521 }
522
523 #[inline]
524 fn collect_lcp_candidate(
525 &mut self,
526 lcp_type: LargestContentfulPaintType,
527 lcp_candidate_id: LCPCandidateID,
528 clip_rect: LayoutRect,
529 bounds: LayoutRect,
530 ) {
531 if let Some(lcp_collector) = &mut self.lcp_candidate_collector {
532 let transform = self
533 .paint_info
534 .scroll_tree
535 .cumulative_node_to_root_transform(self.current_scroll_node_id);
536 lcp_collector.add_or_update_candidate(
537 lcp_type,
538 lcp_candidate_id,
539 clip_rect,
540 bounds,
541 transform,
542 );
543 }
544 }
545}
546
547impl InspectorHighlight {
548 fn register_fragment_of_highlighted_dom_node(
549 &mut self,
550 fragment: &Fragment,
551 spatial_id: SpatialId,
552 clip_chain_id: ClipChainId,
553 containing_block: &PhysicalRect<Au>,
554 ) {
555 let state = self.state.get_or_insert(HighlightTraversalState {
556 content_box: Rect::zero(),
557 spatial_id,
558 clip_chain_id,
559 maybe_box_fragment: None,
560 });
561
562 debug_assert_eq!(spatial_id, state.spatial_id);
564 if clip_chain_id != ClipChainId::INVALID && state.clip_chain_id != ClipChainId::INVALID {
565 debug_assert_eq!(
566 clip_chain_id, state.clip_chain_id,
567 "Fragments of the same node must either have no clip chain or the same one"
568 );
569 }
570
571 let Some(fragment_relative_rect) = fragment.base().map(|base| base.rect) else {
572 return;
573 };
574 state.maybe_box_fragment = match fragment {
575 Fragment::Box(fragment) | Fragment::Float(fragment) => Some(fragment.clone()),
576 _ => None,
577 };
578
579 state.content_box = state
580 .content_box
581 .union(&fragment_relative_rect.translate(containing_block.origin.to_vector()));
582 }
583}
584
585impl Fragment {
586 pub(crate) fn build_display_list(
587 &self,
588 builder: &mut DisplayListBuilder,
589 containing_block: &PhysicalRect<Au>,
590 section: StackingContextSection,
591 is_hit_test_for_scrollable_overflow: bool,
592 is_collapsed_table_borders: bool,
593 text_decorations: &Arc<Vec<FragmentTextDecoration>>,
594 ) {
595 let spatial_id = builder.spatial_id(builder.current_scroll_node_id);
596 let clip_chain_id = builder.clip_chain_id(builder.current_clip_id);
597 if let Some(inspector_highlight) = &mut builder.inspector_highlight {
598 if self.tag() == Some(inspector_highlight.tag) {
599 inspector_highlight.register_fragment_of_highlighted_dom_node(
600 self,
601 spatial_id,
602 clip_chain_id,
603 containing_block,
604 );
605 }
606 }
607
608 match self {
609 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
610 let box_fragment = &*box_fragment.borrow();
611 match box_fragment.style().get_inherited_box().visibility {
612 Visibility::Visible => BuilderForBoxFragment::new(
613 box_fragment,
614 containing_block,
615 is_hit_test_for_scrollable_overflow,
616 is_collapsed_table_borders,
617 )
618 .build(builder, section),
619 Visibility::Hidden => (),
620 Visibility::Collapse => (),
621 }
622 },
623 Fragment::AbsoluteOrFixedPositioned(_) | Fragment::Positioning(_) => {},
624 Fragment::Image(image) => {
625 let image = image.borrow();
626 let style = image.base.style();
627 match style.get_inherited_box().visibility {
628 Visibility::Visible => {
629 builder.mark_is_contentful();
630
631 let image_rendering =
632 style.get_inherited_box().image_rendering.to_webrender();
633 let rect = image
634 .base
635 .rect
636 .translate(containing_block.origin.to_vector())
637 .to_webrender();
638 let clip = image
639 .clip
640 .translate(containing_block.origin.to_vector())
641 .to_webrender();
642 let common = builder.common_properties(clip, &style);
643
644 if let Some(image_key) = image.image_key {
645 builder.wr().push_image(
646 &common,
647 rect,
648 image_rendering,
649 wr::AlphaType::PremultipliedAlpha,
650 image_key,
651 wr::ColorF::WHITE,
652 );
653 }
654
655 if image.showing_broken_image_icon {
656 self.build_display_list_for_broken_image_border(
657 builder,
658 containing_block,
659 &common,
660 );
661 }
662
663 let lcp_candidate_id = image
664 .base
665 .tag
666 .map(|tag| LCPCandidateID(tag.node.id()))
667 .unwrap_or(LCPCandidateID(0));
668 builder.collect_lcp_candidate(
669 LargestContentfulPaintType::Image,
670 lcp_candidate_id,
671 common.clip_rect,
672 rect,
673 );
674 },
675 Visibility::Hidden => (),
676 Visibility::Collapse => (),
677 }
678 },
679 Fragment::IFrame(iframe) => {
680 let iframe = iframe.borrow();
681 let style = iframe.base.style();
682 match style.get_inherited_box().visibility {
683 Visibility::Visible => {
684 builder.mark_is_contentful();
685 let rect = iframe
686 .base
687 .rect
688 .translate(containing_block.origin.to_vector());
689
690 let common = builder.common_properties(rect.to_webrender(), &style);
691 builder.wr().push_iframe(
692 rect.to_webrender(),
693 common.clip_rect,
694 &wr::SpaceAndClipInfo {
695 spatial_id: common.spatial_id,
696 clip_chain_id: common.clip_chain_id,
697 },
698 iframe.pipeline_id.into(),
699 true,
700 );
701 },
702 Visibility::Hidden => (),
703 Visibility::Collapse => (),
704 }
705 },
706 Fragment::Text(text) => {
707 let text = &*text.borrow();
708 match text.base.style().get_inherited_box().visibility {
709 Visibility::Visible => self.build_display_list_for_text_fragment(
710 text,
711 builder,
712 containing_block,
713 text_decorations,
714 ),
715 Visibility::Hidden => (),
716 Visibility::Collapse => (),
717 }
718 },
719 }
720 }
721
722 fn build_display_list_for_text_fragment(
723 &self,
724 fragment: &TextFragment,
725 builder: &mut DisplayListBuilder,
726 containing_block: &PhysicalRect<Au>,
727 text_decorations: &Arc<Vec<FragmentTextDecoration>>,
728 ) {
729 builder.mark_is_contentful();
733
734 let rect = fragment
735 .base
736 .rect
737 .translate(containing_block.origin.to_vector());
738 let mut baseline_origin = rect.origin;
739 baseline_origin.y += fragment.font_metrics.ascent;
740 let include_whitespace =
741 fragment.offsets.is_some() || text_decorations.iter().any(|item| !item.line.is_empty());
742
743 let glyphs = glyphs(
744 &fragment.glyphs,
745 baseline_origin,
746 fragment.justification_adjustment,
747 include_whitespace,
748 );
749
750 if glyphs.is_empty() {
751 return;
752 }
753
754 let parent_style = fragment.base.style();
755 let color = parent_style.clone_color();
756 let font_metrics = &fragment.font_metrics;
757 let dppx = builder.device_pixel_ratio.get();
758 let common = builder.common_properties(rect.to_webrender(), &parent_style);
759
760 let shadows = &parent_style.get_inherited_text().text_shadow;
763 for shadow in shadows.0.iter().rev() {
764 builder.wr().push_shadow(
765 &wr::SpaceAndClipInfo {
766 spatial_id: common.spatial_id,
767 clip_chain_id: common.clip_chain_id,
768 },
769 wr::Shadow {
770 offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()),
771 color: rgba(shadow.color.resolve_to_absolute(&color)),
772 blur_radius: shadow.blur.px(),
773 },
774 true, );
776 }
777
778 for text_decoration in text_decorations.iter() {
779 if text_decoration.line.contains(TextDecorationLine::UNDERLINE) {
780 let mut rect = rect;
781 rect.origin.y += font_metrics.ascent - font_metrics.underline_offset;
782 rect.size.height =
783 Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
784
785 self.build_display_list_for_text_decoration(
786 &parent_style,
787 builder,
788 &rect,
789 text_decoration,
790 TextDecorationLine::UNDERLINE,
791 );
792 }
793 }
794
795 for text_decoration in text_decorations.iter() {
796 if text_decoration.line.contains(TextDecorationLine::OVERLINE) {
797 let mut rect = rect;
798 rect.size.height =
799 Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
800 self.build_display_list_for_text_decoration(
801 &parent_style,
802 builder,
803 &rect,
804 text_decoration,
805 TextDecorationLine::OVERLINE,
806 );
807 }
808 }
809
810 self.build_display_list_for_text_selection(
811 fragment,
812 builder,
813 containing_block,
814 fragment.base.rect.min_x(),
815 fragment.justification_adjustment,
816 );
817
818 builder.wr().push_text(
819 &common,
820 rect.to_webrender(),
821 &glyphs,
822 fragment.font_key,
823 rgba(color),
824 None,
825 );
826
827 for text_decoration in text_decorations.iter() {
828 if text_decoration
829 .line
830 .contains(TextDecorationLine::LINE_THROUGH)
831 {
832 let mut rect = rect;
833 rect.origin.y += font_metrics.ascent - font_metrics.strikeout_offset;
834 rect.size.height =
835 Au::from_f32_px(font_metrics.strikeout_size.to_nearest_pixel(dppx));
836 self.build_display_list_for_text_decoration(
837 &parent_style,
838 builder,
839 &rect,
840 text_decoration,
841 TextDecorationLine::LINE_THROUGH,
842 );
843 }
844 }
845
846 if !shadows.0.is_empty() {
847 builder.wr().pop_all_shadows();
848 }
849 }
850
851 fn build_display_list_for_text_decoration(
852 &self,
853 parent_style: &ServoArc<ComputedValues>,
854 builder: &mut DisplayListBuilder,
855 rect: &PhysicalRect<Au>,
856 text_decoration: &FragmentTextDecoration,
857 line: TextDecorationLine,
858 ) {
859 if text_decoration.style == ComputedTextDecorationStyle::MozNone {
860 return;
861 }
862
863 let mut rect = rect.to_webrender();
864 let line_thickness = rect.height().ceil();
865
866 if text_decoration.style == ComputedTextDecorationStyle::Wavy {
867 rect = rect.inflate(0.0, line_thickness * 1.0);
868 }
869
870 let common_properties = builder.common_properties(rect, parent_style);
871 builder.wr().push_line(
872 &common_properties,
873 &rect,
874 line_thickness,
875 wr::LineOrientation::Horizontal,
876 &rgba(text_decoration.color),
877 text_decoration.style.to_webrender(),
878 );
879
880 if text_decoration.style == TextDecorationStyle::Double {
881 let half_height = (rect.height() / 2.0).floor().max(1.0);
882 let y_offset = match line {
883 TextDecorationLine::OVERLINE => -rect.height() - half_height,
884 _ => rect.height() + half_height,
885 };
886 let rect = rect.translate(Vector2D::new(0.0, y_offset));
887 let common_properties = builder.common_properties(rect, parent_style);
888 builder.wr().push_line(
889 &common_properties,
890 &rect,
891 line_thickness,
892 wr::LineOrientation::Horizontal,
893 &rgba(text_decoration.color),
894 text_decoration.style.to_webrender(),
895 );
896 }
897 }
898
899 fn build_display_list_for_broken_image_border(
900 &self,
901 builder: &mut DisplayListBuilder,
902 containing_block: &PhysicalRect<Au>,
903 common: &CommonItemProperties,
904 ) {
905 let border_side = BorderSide {
906 color: ColorF::BLACK,
907 style: wr::BorderStyle::Inset,
908 };
909 builder.wr().push_border(
910 common,
911 containing_block.to_webrender(),
912 LayoutSideOffsets::new_all_same(1.0),
913 BorderDetails::Normal(NormalBorder {
914 left: border_side,
915 right: border_side,
916 top: border_side,
917 bottom: border_side,
918 radius: BorderRadius::zero(),
919 do_aa: true,
920 }),
921 );
922 }
923
924 fn build_display_list_for_text_selection(
927 &self,
928 fragment: &TextFragment,
929 builder: &mut DisplayListBuilder<'_>,
930 containing_block_rect: &PhysicalRect<Au>,
931 fragment_x_offset: Au,
932 justification_adjustment: Au,
933 ) {
934 let Some(offsets) = fragment.offsets.as_ref() else {
935 return;
936 };
937
938 let shared_selection = offsets.shared_selection.borrow();
939 if !shared_selection.enabled {
940 return;
941 }
942
943 if offsets.character_range.start > shared_selection.character_range.end ||
944 offsets.character_range.end < shared_selection.character_range.start
945 {
946 return;
947 }
948
949 let mut current_character_index = offsets.character_range.start;
950 let mut current_advance = Au::zero();
951 let mut start_advance = None;
952 let mut end_advance = None;
953 for glyph_store in fragment.glyphs.iter() {
954 let glyph_store_character_count = glyph_store.total_characters();
955 if current_character_index + glyph_store_character_count <
956 shared_selection.character_range.start
957 {
958 current_advance += glyph_store.total_advance() +
959 (justification_adjustment * glyph_store.total_word_separators() as i32);
960 current_character_index += glyph_store_character_count;
961 continue;
962 }
963
964 if current_character_index >= shared_selection.character_range.end {
965 break;
966 }
967
968 for glyph in glyph_store.glyphs() {
969 if current_character_index >= shared_selection.character_range.start {
970 start_advance = start_advance.or(Some(current_advance));
971 }
972
973 current_character_index += glyph.character_count();
974 current_advance += glyph.advance();
975 if glyph.char_is_word_separator() {
976 current_advance += justification_adjustment;
977 }
978
979 if current_character_index <= shared_selection.character_range.end {
980 end_advance = Some(current_advance);
981 }
982 }
983 }
984
985 let start_x = start_advance.unwrap_or(current_advance);
986 let end_x = end_advance.unwrap_or(current_advance);
987
988 let parent_style = fragment.base.style();
989 if !shared_selection.range.is_empty() {
990 let selection_rect = Rect::new(
991 containing_block_rect.origin +
992 Vector2D::new(fragment_x_offset + start_x, Au::zero()),
993 Size2D::new(end_x - start_x, containing_block_rect.height()),
994 )
995 .to_webrender();
996
997 if let Some(selection_color) = fragment
998 .selected_style
999 .borrow()
1000 .clone_background_color()
1001 .as_absolute()
1002 {
1003 let selection_common = builder.common_properties(selection_rect, &parent_style);
1004 builder
1005 .wr()
1006 .push_rect(&selection_common, selection_rect, rgba(*selection_color));
1007 }
1008 return;
1009 }
1010
1011 let insertion_point_rect = Rect::new(
1012 containing_block_rect.origin + Vector2D::new(start_x + fragment_x_offset, Au::zero()),
1013 Size2D::new(
1014 INSERTION_POINT_LOGICAL_WIDTH,
1015 containing_block_rect.height(),
1016 ),
1017 )
1018 .to_webrender();
1019
1020 let color = parent_style.clone_color();
1021 let caret_color = match parent_style.clone_caret_color().0 {
1022 ColorOrAuto::Color(caret_color) => caret_color.resolve_to_absolute(&color),
1023 ColorOrAuto::Auto => color,
1024 };
1025 let insertion_point_common = builder.common_properties(insertion_point_rect, &parent_style);
1026 builder.wr().push_rect(
1027 &insertion_point_common,
1028 insertion_point_rect,
1029 rgba(caret_color),
1030 );
1031 }
1032}
1033
1034struct BuilderForBoxFragment<'a> {
1035 fragment: &'a BoxFragment,
1036 containing_block: &'a PhysicalRect<Au>,
1037 border_rect: units::LayoutRect,
1038 margin_rect: OnceCell<units::LayoutRect>,
1039 padding_rect: OnceCell<units::LayoutRect>,
1040 content_rect: OnceCell<units::LayoutRect>,
1041 border_radius: wr::BorderRadius,
1042 border_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1043 padding_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1044 content_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1045 is_hit_test_for_scrollable_overflow: bool,
1046 is_collapsed_table_borders: bool,
1047}
1048
1049impl<'a> BuilderForBoxFragment<'a> {
1050 fn new(
1051 fragment: &'a BoxFragment,
1052 containing_block: &'a PhysicalRect<Au>,
1053 is_hit_test_for_scrollable_overflow: bool,
1054 is_collapsed_table_borders: bool,
1055 ) -> Self {
1056 let border_rect = fragment
1057 .border_rect()
1058 .translate(containing_block.origin.to_vector());
1059 Self {
1060 fragment,
1061 containing_block,
1062 border_rect: border_rect.to_webrender(),
1063 border_radius: fragment.border_radius(),
1064 margin_rect: OnceCell::new(),
1065 padding_rect: OnceCell::new(),
1066 content_rect: OnceCell::new(),
1067 border_edge_clip_chain_id: RefCell::new(None),
1068 padding_edge_clip_chain_id: RefCell::new(None),
1069 content_edge_clip_chain_id: RefCell::new(None),
1070 is_hit_test_for_scrollable_overflow,
1071 is_collapsed_table_borders,
1072 }
1073 }
1074
1075 fn content_rect(&self) -> &units::LayoutRect {
1076 self.content_rect.get_or_init(|| {
1077 self.fragment
1078 .content_rect()
1079 .translate(self.containing_block.origin.to_vector())
1080 .to_webrender()
1081 })
1082 }
1083
1084 fn padding_rect(&self) -> &units::LayoutRect {
1085 self.padding_rect.get_or_init(|| {
1086 self.fragment
1087 .padding_rect()
1088 .translate(self.containing_block.origin.to_vector())
1089 .to_webrender()
1090 })
1091 }
1092
1093 fn margin_rect(&self) -> &units::LayoutRect {
1094 self.margin_rect.get_or_init(|| {
1095 self.fragment
1096 .margin_rect()
1097 .translate(self.containing_block.origin.to_vector())
1098 .to_webrender()
1099 })
1100 }
1101
1102 fn border_edge_clip(
1103 &self,
1104 builder: &mut DisplayListBuilder,
1105 force_clip_creation: bool,
1106 ) -> Option<ClipChainId> {
1107 if let Some(clip) = *self.border_edge_clip_chain_id.borrow() {
1108 return Some(clip);
1109 }
1110
1111 let maybe_clip =
1112 builder.maybe_create_clip(self.border_radius, self.border_rect, force_clip_creation);
1113 *self.border_edge_clip_chain_id.borrow_mut() = maybe_clip;
1114 maybe_clip
1115 }
1116
1117 fn padding_edge_clip(
1118 &self,
1119 builder: &mut DisplayListBuilder,
1120 force_clip_creation: bool,
1121 ) -> Option<ClipChainId> {
1122 if let Some(clip) = *self.padding_edge_clip_chain_id.borrow() {
1123 return Some(clip);
1124 }
1125
1126 let radii = offset_radii(self.border_radius, -self.fragment.border.to_webrender());
1127 let maybe_clip =
1128 builder.maybe_create_clip(radii, *self.padding_rect(), force_clip_creation);
1129 *self.padding_edge_clip_chain_id.borrow_mut() = maybe_clip;
1130 maybe_clip
1131 }
1132
1133 fn content_edge_clip(
1134 &self,
1135 builder: &mut DisplayListBuilder,
1136 force_clip_creation: bool,
1137 ) -> Option<ClipChainId> {
1138 if let Some(clip) = *self.content_edge_clip_chain_id.borrow() {
1139 return Some(clip);
1140 }
1141
1142 let radii = offset_radii(
1143 self.border_radius,
1144 -(self.fragment.border + self.fragment.padding).to_webrender(),
1145 );
1146 let maybe_clip =
1147 builder.maybe_create_clip(radii, *self.content_rect(), force_clip_creation);
1148 *self.content_edge_clip_chain_id.borrow_mut() = maybe_clip;
1149 maybe_clip
1150 }
1151
1152 fn build(&mut self, builder: &mut DisplayListBuilder, section: StackingContextSection) {
1153 if self.is_hit_test_for_scrollable_overflow &&
1154 self.fragment.style().get_inherited_ui().pointer_events !=
1155 style::computed_values::pointer_events::T::None
1156 {
1157 self.build_hit_test(
1158 builder,
1159 self.fragment
1160 .scrollable_overflow()
1161 .translate(self.containing_block.origin.to_vector())
1162 .to_webrender(),
1163 );
1164 return;
1165 }
1166 if self.is_collapsed_table_borders {
1167 self.build_collapsed_table_borders(builder);
1168 return;
1169 }
1170
1171 if section == StackingContextSection::Outline {
1172 self.build_outline(builder);
1173 return;
1174 }
1175
1176 if self
1177 .fragment
1178 .base
1179 .flags
1180 .contains(FragmentFlags::DO_NOT_PAINT)
1181 {
1182 return;
1183 }
1184
1185 self.build_background(builder);
1186 self.build_box_shadow(builder);
1187 self.build_border(builder);
1188 }
1189
1190 fn build_hit_test(&self, builder: &mut DisplayListBuilder, rect: LayoutRect) {
1191 let external_scroll_node_id = builder
1192 .paint_info
1193 .external_scroll_id_for_scroll_tree_node(builder.current_scroll_node_id);
1194
1195 let mut common = builder.common_properties(rect, &self.fragment.style());
1196 if let Some(clip_chain_id) = self.border_edge_clip(builder, false) {
1197 common.clip_chain_id = clip_chain_id;
1198 }
1199 builder.wr().push_hit_test(
1200 common.clip_rect,
1201 common.clip_chain_id,
1202 common.spatial_id,
1203 common.flags,
1204 (external_scroll_node_id.0, 0), );
1206 }
1207
1208 fn build_background_for_painter(
1209 &mut self,
1210 builder: &mut DisplayListBuilder,
1211 painter: &BackgroundPainter,
1212 ) {
1213 let b = painter.style.get_background();
1214 let background_color = painter.style.resolve_color(&b.background_color);
1215 if background_color.alpha > 0.0 {
1216 let layer_index = b.background_image.0.len() - 1;
1220 let bounds = painter.painting_area(self, builder, layer_index);
1221 let common = painter.common_properties(self, builder, layer_index, bounds);
1222 builder
1223 .wr()
1224 .push_rect(&common, bounds, rgba(background_color))
1225 }
1226
1227 self.build_background_image(builder, painter);
1228 }
1229
1230 fn build_background(&mut self, builder: &mut DisplayListBuilder) {
1231 let flags = self.fragment.base.flags;
1232
1233 if flags.intersects(FragmentFlags::IS_ROOT_ELEMENT) {
1236 return;
1237 }
1238 if !builder.paint_body_background &&
1240 flags.intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT)
1241 {
1242 return;
1243 }
1244
1245 if let BackgroundMode::None = self.fragment.background_mode {
1247 return;
1248 }
1249
1250 if let BackgroundMode::Extra(ref extra_backgrounds) = self.fragment.background_mode {
1254 for extra_background in extra_backgrounds {
1255 let positioning_area = extra_background.rect;
1256 let painter = BackgroundPainter {
1257 style: &extra_background.style.borrow_mut(),
1258 painting_area_override: None,
1259 positioning_area_override: Some(
1260 positioning_area
1261 .translate(self.containing_block.origin.to_vector())
1262 .to_webrender(),
1263 ),
1264 };
1265 self.build_background_for_painter(builder, &painter);
1266 }
1267 }
1268
1269 let painter = BackgroundPainter {
1270 style: &self.fragment.style(),
1271 painting_area_override: None,
1272 positioning_area_override: None,
1273 };
1274 self.build_background_for_painter(builder, &painter);
1275 }
1276
1277 fn build_background_image(
1278 &mut self,
1279 builder: &mut DisplayListBuilder,
1280 painter: &BackgroundPainter,
1281 ) {
1282 let style = painter.style;
1283 let b = style.get_background();
1284 let node = self.fragment.base.tag.map(|tag| tag.node);
1285 for (index, image) in b.background_image.0.iter().enumerate().rev() {
1287 match builder.image_resolver.resolve_image(node, image) {
1288 Err(_) => {},
1289 Ok(ResolvedImage::Gradient(gradient)) => {
1290 let intrinsic = NaturalSizes::empty();
1291 let Some(layer) =
1292 &background::layout_layer(self, painter, builder, index, intrinsic)
1293 else {
1294 continue;
1295 };
1296
1297 match gradient::build(style, gradient, layer.tile_size, builder) {
1298 WebRenderGradient::Linear(linear_gradient) => builder.wr().push_gradient(
1299 &layer.common,
1300 layer.bounds,
1301 linear_gradient,
1302 layer.tile_size,
1303 layer.tile_spacing,
1304 ),
1305 WebRenderGradient::Radial(radial_gradient) => {
1306 builder.wr().push_radial_gradient(
1307 &layer.common,
1308 layer.bounds,
1309 radial_gradient,
1310 layer.tile_size,
1311 layer.tile_spacing,
1312 )
1313 },
1314 WebRenderGradient::Conic(conic_gradient) => {
1315 builder.wr().push_conic_gradient(
1316 &layer.common,
1317 layer.bounds,
1318 conic_gradient,
1319 layer.tile_size,
1320 layer.tile_spacing,
1321 )
1322 },
1323 }
1324 },
1325 Ok(ResolvedImage::Image { image, size }) => {
1326 let dppx = 1.0;
1328 let intrinsic =
1329 NaturalSizes::from_width_and_height(size.width / dppx, size.height / dppx);
1330 let layer = background::layout_layer(self, painter, builder, index, intrinsic);
1331 let image_wr_key = match image {
1332 CachedImage::Raster(raster_image) => raster_image.id,
1333 CachedImage::Vector(vector_image) => {
1334 let scale = builder.device_pixel_ratio.get();
1335 let default_size: DeviceIntSize =
1336 Size2D::new(size.width * scale, size.height * scale).to_i32();
1337 let layer_size = layer.as_ref().map(|layer| {
1338 Size2D::new(
1339 layer.tile_size.width * scale,
1340 layer.tile_size.height * scale,
1341 )
1342 .to_i32()
1343 });
1344
1345 node.and_then(|node| {
1346 let size = layer_size.unwrap_or(default_size);
1347 builder.image_resolver.rasterize_vector_image(
1348 vector_image.id,
1349 size,
1350 node,
1351 vector_image.svg_id,
1352 )
1353 })
1354 .and_then(|rasterized_image| rasterized_image.id)
1355 },
1356 };
1357
1358 let Some(image_key) = image_wr_key else {
1359 continue;
1360 };
1361
1362 if let Some(layer) = layer {
1363 if layer.repeat {
1364 builder.wr().push_repeating_image(
1365 &layer.common,
1366 layer.bounds,
1367 layer.tile_size,
1368 layer.tile_spacing,
1369 style.clone_image_rendering().to_webrender(),
1370 wr::AlphaType::PremultipliedAlpha,
1371 image_key,
1372 wr::ColorF::WHITE,
1373 )
1374 } else {
1375 builder.wr().push_image(
1376 &layer.common,
1377 layer.bounds,
1378 style.clone_image_rendering().to_webrender(),
1379 wr::AlphaType::PremultipliedAlpha,
1380 image_key,
1381 wr::ColorF::WHITE,
1382 )
1383 }
1384
1385 let lcp_candidate_id = self
1386 .fragment
1387 .base
1388 .tag
1389 .map(|tag| LCPCandidateID(tag.node.id()))
1390 .unwrap_or(LCPCandidateID(0));
1391 builder.collect_lcp_candidate(
1392 LargestContentfulPaintType::BackgroundImage,
1393 lcp_candidate_id,
1394 layer.common.clip_rect,
1395 layer.bounds,
1396 );
1397 }
1398 },
1399 }
1400 }
1401 }
1402
1403 fn build_border_side(&mut self, style_color: BorderStyleColor) -> wr::BorderSide {
1404 wr::BorderSide {
1405 color: rgba(style_color.color),
1406 style: match style_color.style {
1407 BorderStyle::None => wr::BorderStyle::None,
1408 BorderStyle::Solid => wr::BorderStyle::Solid,
1409 BorderStyle::Double => wr::BorderStyle::Double,
1410 BorderStyle::Dotted => wr::BorderStyle::Dotted,
1411 BorderStyle::Dashed => wr::BorderStyle::Dashed,
1412 BorderStyle::Hidden => wr::BorderStyle::Hidden,
1413 BorderStyle::Groove => wr::BorderStyle::Groove,
1414 BorderStyle::Ridge => wr::BorderStyle::Ridge,
1415 BorderStyle::Inset => wr::BorderStyle::Inset,
1416 BorderStyle::Outset => wr::BorderStyle::Outset,
1417 },
1418 }
1419 }
1420
1421 fn build_collapsed_table_borders(&mut self, builder: &mut DisplayListBuilder) {
1422 let Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(table_info)) =
1423 self.fragment.specific_layout_info()
1424 else {
1425 return;
1426 };
1427 let mut common =
1428 builder.common_properties(units::LayoutRect::default(), &self.fragment.style());
1429 let radius = wr::BorderRadius::default();
1430 let mut column_sum = Au::zero();
1431 for (x, column_size) in table_info.track_sizes.x.iter().enumerate() {
1432 let mut row_sum = Au::zero();
1433 for (y, row_size) in table_info.track_sizes.y.iter().enumerate() {
1434 let left_border = &table_info.collapsed_borders.x[x][y];
1435 let right_border = &table_info.collapsed_borders.x[x + 1][y];
1436 let top_border = &table_info.collapsed_borders.y[y][x];
1437 let bottom_border = &table_info.collapsed_borders.y[y + 1][x];
1438 let details = wr::BorderDetails::Normal(wr::NormalBorder {
1439 left: self.build_border_side(left_border.style_color.clone()),
1440 right: self.build_border_side(right_border.style_color.clone()),
1441 top: self.build_border_side(top_border.style_color.clone()),
1442 bottom: self.build_border_side(bottom_border.style_color.clone()),
1443 radius,
1444 do_aa: true,
1445 });
1446 let mut border_widths = PhysicalSides::new(
1447 top_border.width,
1448 right_border.width,
1449 bottom_border.width,
1450 left_border.width,
1451 );
1452 let left_adjustment = if x == 0 {
1453 -border_widths.left / 2
1454 } else {
1455 std::mem::take(&mut border_widths.left) / 2
1456 };
1457 let top_adjustment = if y == 0 {
1458 -border_widths.top / 2
1459 } else {
1460 std::mem::take(&mut border_widths.top) / 2
1461 };
1462 let origin =
1463 PhysicalPoint::new(column_sum + left_adjustment, row_sum + top_adjustment);
1464 let size = PhysicalSize::new(
1465 *column_size - left_adjustment + border_widths.right / 2,
1466 *row_size - top_adjustment + border_widths.bottom / 2,
1467 );
1468 let border_rect = PhysicalRect::new(origin, size)
1469 .translate(self.fragment.content_rect().origin.to_vector())
1470 .translate(self.containing_block.origin.to_vector())
1471 .to_webrender();
1472 common.clip_rect = border_rect;
1473 builder.wr().push_border(
1474 &common,
1475 border_rect,
1476 border_widths.to_webrender(),
1477 details,
1478 );
1479 row_sum += *row_size;
1480 }
1481 column_sum += *column_size;
1482 }
1483 }
1484
1485 fn build_border(&mut self, builder: &mut DisplayListBuilder) {
1486 if self.fragment.has_collapsed_borders() {
1487 return;
1490 }
1491
1492 let style = self.fragment.style();
1493 let border = style.get_border();
1494 let border_widths = self.fragment.border.to_webrender();
1495
1496 if border_widths == SideOffsets2D::zero() {
1497 return;
1498 }
1499
1500 let common = builder.common_properties(self.border_rect, &style);
1502 if self.build_border_image(builder, &common, border, border_widths) {
1503 return;
1504 }
1505
1506 let current_color = style.get_inherited_text().clone_color();
1507 let style_color = BorderStyleColor::from_border(border, ¤t_color);
1508 let details = wr::BorderDetails::Normal(wr::NormalBorder {
1509 top: self.build_border_side(style_color.top),
1510 right: self.build_border_side(style_color.right),
1511 bottom: self.build_border_side(style_color.bottom),
1512 left: self.build_border_side(style_color.left),
1513 radius: self.border_radius,
1514 do_aa: true,
1515 });
1516 builder
1517 .wr()
1518 .push_border(&common, self.border_rect, border_widths, details)
1519 }
1520
1521 fn build_border_image(
1523 &self,
1524 builder: &mut DisplayListBuilder,
1525 common: &CommonItemProperties,
1526 border: &Border,
1527 border_widths: SideOffsets2D<f32, LayoutPixel>,
1528 ) -> bool {
1529 let style = self.fragment.style();
1530 let border_style_struct = style.get_border();
1531 let border_image_outset =
1532 resolve_border_image_outset(border_style_struct.border_image_outset, border_widths);
1533 let border_image_area = self.border_rect.to_rect().outer_rect(border_image_outset);
1534 let border_image_size = border_image_area.size;
1535 let border_image_widths = resolve_border_image_width(
1536 &border_style_struct.border_image_width,
1537 border_widths,
1538 border_image_size,
1539 );
1540 let border_image_repeat = &border_style_struct.border_image_repeat;
1541 let border_image_fill = border_style_struct.border_image_slice.fill;
1542 let border_image_slice = &border_style_struct.border_image_slice.offsets;
1543
1544 let stops = Vec::new();
1545 let mut width = border_image_size.width;
1546 let mut height = border_image_size.height;
1547 let node = self.fragment.base.tag.map(|tag| tag.node);
1548 let source = match builder
1549 .image_resolver
1550 .resolve_image(node, &border.border_image_source)
1551 {
1552 Err(_) => return false,
1553 Ok(ResolvedImage::Image { image, size }) => {
1554 let Some(image) = image.as_raster_image() else {
1555 return false;
1556 };
1557
1558 let Some(key) = image.id else {
1559 return false;
1560 };
1561
1562 width = size.width;
1563 height = size.height;
1564 let image_rendering = style.clone_image_rendering().to_webrender();
1565 NinePatchBorderSource::Image(key, image_rendering)
1566 },
1567 Ok(ResolvedImage::Gradient(gradient)) => {
1568 match gradient::build(&style, gradient, border_image_size, builder) {
1569 WebRenderGradient::Linear(gradient) => {
1570 NinePatchBorderSource::Gradient(gradient)
1571 },
1572 WebRenderGradient::Radial(gradient) => {
1573 NinePatchBorderSource::RadialGradient(gradient)
1574 },
1575 WebRenderGradient::Conic(gradient) => {
1576 NinePatchBorderSource::ConicGradient(gradient)
1577 },
1578 }
1579 },
1580 };
1581
1582 let size = Size2D::new(width as i32, height as i32);
1583
1584 if size.is_empty() || border_image_size.is_empty() {
1587 return true;
1588 }
1589
1590 let details = BorderDetails::NinePatch(NinePatchBorder {
1591 source,
1592 width: size.width,
1593 height: size.height,
1594 slice: resolve_border_image_slice(border_image_slice, size),
1595 fill: border_image_fill,
1596 repeat_horizontal: border_image_repeat.0.to_webrender(),
1597 repeat_vertical: border_image_repeat.1.to_webrender(),
1598 });
1599 builder.wr().push_border(
1600 common,
1601 border_image_area.to_box2d(),
1602 border_image_widths,
1603 details,
1604 );
1605 builder.wr().push_stops(&stops);
1606 true
1607 }
1608
1609 fn build_outline(&mut self, builder: &mut DisplayListBuilder) {
1610 let style = self.fragment.style();
1611 let outline = style.get_outline();
1612 if outline.outline_style.none_or_hidden() {
1613 return;
1614 }
1615 let width = outline.outline_width.0.to_f32_px();
1616 if width == 0.0 {
1617 return;
1618 }
1619 let offset = outline.outline_offset.to_f32_px() + width;
1628 let outline_rect = self.border_rect.inflate(
1629 offset.max(-self.border_rect.width() / 2.0 + width),
1630 offset.max(-self.border_rect.height() / 2.0 + width),
1631 );
1632 let common = builder.common_properties(outline_rect, &style);
1633 let widths = SideOffsets2D::new_all_same(width);
1634 let border_style = match outline.outline_style {
1635 OutlineStyle::Auto => BorderStyle::Solid,
1638 OutlineStyle::BorderStyle(s) => s,
1639 };
1640 let side = self.build_border_side(BorderStyleColor {
1641 style: border_style,
1642 color: style.resolve_color(&outline.outline_color),
1643 });
1644 let details = wr::BorderDetails::Normal(wr::NormalBorder {
1645 top: side,
1646 right: side,
1647 bottom: side,
1648 left: side,
1649 radius: offset_radii(self.border_radius, SideOffsets2D::new_all_same(offset)),
1650 do_aa: true,
1651 });
1652 builder
1653 .wr()
1654 .push_border(&common, outline_rect, widths, details)
1655 }
1656
1657 fn build_box_shadow(&self, builder: &mut DisplayListBuilder<'_>) {
1658 let style = self.fragment.style();
1659 let box_shadows = &style.get_effects().box_shadow.0;
1660 if box_shadows.is_empty() {
1661 return;
1662 }
1663
1664 let common = builder.common_properties(MaxRect::max_rect(), &style);
1666 for box_shadow in box_shadows.iter().rev() {
1667 let (rect, clip_mode) = if box_shadow.inset {
1668 (*self.padding_rect(), BoxShadowClipMode::Inset)
1669 } else {
1670 (self.border_rect, BoxShadowClipMode::Outset)
1671 };
1672
1673 builder.wr().push_box_shadow(
1674 &common,
1675 rect,
1676 LayoutVector2D::new(
1677 box_shadow.base.horizontal.px(),
1678 box_shadow.base.vertical.px(),
1679 ),
1680 rgba(style.resolve_color(&box_shadow.base.color)),
1681 box_shadow.base.blur.px(),
1682 box_shadow.spread.px(),
1683 self.border_radius,
1684 clip_mode,
1685 );
1686 }
1687 }
1688}
1689
1690fn rgba(color: AbsoluteColor) -> wr::ColorF {
1691 let rgba = color.to_color_space(ColorSpace::Srgb);
1692 wr::ColorF::new(
1693 rgba.components.0.clamp(0.0, 1.0),
1694 rgba.components.1.clamp(0.0, 1.0),
1695 rgba.components.2.clamp(0.0, 1.0),
1696 rgba.alpha,
1697 )
1698}
1699
1700fn glyphs(
1701 glyph_runs: &[Arc<GlyphStore>],
1702 mut baseline_origin: PhysicalPoint<Au>,
1703 justification_adjustment: Au,
1704 include_whitespace: bool,
1705) -> Vec<wr::GlyphInstance> {
1706 let mut glyphs = vec![];
1707 for run in glyph_runs {
1708 for glyph in run.glyphs() {
1709 if !run.is_whitespace() || include_whitespace {
1710 let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
1711 let point = units::LayoutPoint::new(
1712 baseline_origin.x.to_f32_px() + glyph_offset.x.to_f32_px(),
1713 baseline_origin.y.to_f32_px() + glyph_offset.y.to_f32_px(),
1714 );
1715 let glyph_instance = wr::GlyphInstance {
1716 index: glyph.id(),
1717 point,
1718 };
1719 glyphs.push(glyph_instance);
1720 }
1721
1722 if glyph.char_is_word_separator() {
1723 baseline_origin.x += justification_adjustment;
1724 }
1725 baseline_origin.x += glyph.advance();
1726 }
1727 }
1728 glyphs
1729}
1730
1731fn offset_radii(mut radii: BorderRadius, offsets: LayoutSideOffsets) -> BorderRadius {
1735 let expand = |radius: &mut f32, offset: f32| {
1736 if offset < 0.0 {
1738 *radius = (*radius + offset).max(0.0);
1739 return;
1740 }
1741
1742 if *radius > 0.0 {
1747 *radius += offset;
1748 }
1749 };
1750 if offsets.left != 0.0 {
1751 expand(&mut radii.top_left.width, offsets.left);
1752 expand(&mut radii.bottom_left.width, offsets.left);
1753 }
1754 if offsets.right != 0.0 {
1755 expand(&mut radii.top_right.width, offsets.right);
1756 expand(&mut radii.bottom_right.width, offsets.right);
1757 }
1758 if offsets.top != 0.0 {
1759 expand(&mut radii.top_left.height, offsets.top);
1760 expand(&mut radii.top_right.height, offsets.top);
1761 }
1762 if offsets.bottom != 0.0 {
1763 expand(&mut radii.bottom_right.height, offsets.bottom);
1764 expand(&mut radii.bottom_left.height, offsets.bottom);
1765 }
1766 radii
1767}
1768
1769fn resolve_border_image_outset(
1771 outset: BorderImageOutset,
1772 border: SideOffsets2D<f32, LayoutPixel>,
1773) -> SideOffsets2D<f32, LayoutPixel> {
1774 fn image_outset_for_side(outset: NonNegativeLengthOrNumber, border_width: f32) -> f32 {
1775 match outset {
1776 NonNegativeLengthOrNumber::Length(length) => length.px(),
1777 NonNegativeLengthOrNumber::Number(factor) => border_width * factor.0,
1778 }
1779 }
1780
1781 SideOffsets2D::new(
1782 image_outset_for_side(outset.0, border.top),
1783 image_outset_for_side(outset.1, border.right),
1784 image_outset_for_side(outset.2, border.bottom),
1785 image_outset_for_side(outset.3, border.left),
1786 )
1787}
1788
1789fn resolve_border_image_width(
1791 width: &BorderImageWidth,
1792 border: SideOffsets2D<f32, LayoutPixel>,
1793 border_area: Size2D<f32, LayoutPixel>,
1794) -> SideOffsets2D<f32, LayoutPixel> {
1795 fn image_width_for_side(
1796 border_image_width: &BorderImageSideWidth,
1797 border_width: f32,
1798 total_length: f32,
1799 ) -> f32 {
1800 match border_image_width {
1801 BorderImageSideWidth::LengthPercentage(v) => {
1802 v.to_used_value(Au::from_f32_px(total_length)).to_f32_px()
1803 },
1804 BorderImageSideWidth::Number(x) => border_width * x.0,
1805 BorderImageSideWidth::Auto => border_width,
1806 }
1807 }
1808
1809 SideOffsets2D::new(
1810 image_width_for_side(&width.0, border.top, border_area.height),
1811 image_width_for_side(&width.1, border.right, border_area.width),
1812 image_width_for_side(&width.2, border.bottom, border_area.height),
1813 image_width_for_side(&width.3, border.left, border_area.width),
1814 )
1815}
1816
1817fn resolve_border_image_slice(
1819 border_image_slice: &StyleRect<NonNegative<NumberOrPercentage>>,
1820 size: Size2D<i32, UnknownUnit>,
1821) -> SideOffsets2D<i32, DevicePixel> {
1822 fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 {
1823 match value.0 {
1824 NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32,
1825 NumberOrPercentage::Number(n) => n.round() as i32,
1826 }
1827 }
1828
1829 SideOffsets2D::new(
1830 resolve_percentage(border_image_slice.0, size.height),
1831 resolve_percentage(border_image_slice.1, size.width),
1832 resolve_percentage(border_image_slice.2, size.height),
1833 resolve_percentage(border_image_slice.3, size.width),
1834 )
1835}
1836
1837pub(super) fn normalize_radii(rect: &units::LayoutRect, radius: &mut wr::BorderRadius) {
1838 let f = (rect.width() / (radius.top_left.width + radius.top_right.width))
1846 .min(rect.width() / (radius.bottom_left.width + radius.bottom_right.width))
1847 .min(rect.height() / (radius.top_left.height + radius.bottom_left.height))
1848 .min(rect.height() / (radius.top_right.height + radius.bottom_right.height));
1849 if f < 1.0 {
1850 radius.top_left *= f;
1851 radius.top_right *= f;
1852 radius.bottom_right *= f;
1853 radius.bottom_left *= f;
1854 }
1855}
1856
1857pub(super) fn compute_margin_box_radius(
1865 radius: wr::BorderRadius,
1866 layout_rect: LayoutSize,
1867 fragment: &BoxFragment,
1868) -> wr::BorderRadius {
1869 let style = fragment.style();
1870 let margin = style.physical_margin();
1871 let adjust_radius = |radius: f32, margin: f32| -> f32 {
1872 if margin <= 0. || (radius / margin) >= 1. {
1873 (radius + margin).max(0.)
1874 } else {
1875 radius + (margin * (1. + (radius / margin - 1.).powf(3.)))
1876 }
1877 };
1878 let compute_margin_radius = |radius: LayoutSize,
1879 layout_rect: LayoutSize,
1880 margin: Size2D<LengthPercentageOrAuto, UnknownUnit>|
1881 -> LayoutSize {
1882 let zero = LengthPercentage::zero();
1883 let width = margin
1884 .width
1885 .auto_is(|| &zero)
1886 .to_used_value(Au::from_f32_px(layout_rect.width));
1887 let height = margin
1888 .height
1889 .auto_is(|| &zero)
1890 .to_used_value(Au::from_f32_px(layout_rect.height));
1891 LayoutSize::new(
1892 adjust_radius(radius.width, width.to_f32_px()),
1893 adjust_radius(radius.height, height.to_f32_px()),
1894 )
1895 };
1896 wr::BorderRadius {
1897 top_left: compute_margin_radius(
1898 radius.top_left,
1899 layout_rect,
1900 Size2D::new(margin.left, margin.top),
1901 ),
1902 top_right: compute_margin_radius(
1903 radius.top_right,
1904 layout_rect,
1905 Size2D::new(margin.right, margin.top),
1906 ),
1907 bottom_left: compute_margin_radius(
1908 radius.bottom_left,
1909 layout_rect,
1910 Size2D::new(margin.left, margin.bottom),
1911 ),
1912 bottom_right: compute_margin_radius(
1913 radius.bottom_right,
1914 layout_rect,
1915 Size2D::new(margin.right, margin.bottom),
1916 ),
1917 }
1918}
1919
1920impl BoxFragment {
1921 fn border_radius(&self) -> BorderRadius {
1922 let style = self.style();
1923 let border = style.get_border();
1924 if border.border_top_left_radius.0.is_zero() &&
1925 border.border_top_right_radius.0.is_zero() &&
1926 border.border_bottom_right_radius.0.is_zero() &&
1927 border.border_bottom_left_radius.0.is_zero()
1928 {
1929 return BorderRadius::zero();
1930 }
1931
1932 let border_rect = self.border_rect();
1933 let resolve =
1934 |radius: &LengthPercentage, box_size: Au| radius.to_used_value(box_size).to_f32_px();
1935 let corner = |corner: &style::values::computed::BorderCornerRadius| {
1936 Size2D::new(
1937 resolve(&corner.0.width.0, border_rect.size.width),
1938 resolve(&corner.0.height.0, border_rect.size.height),
1939 )
1940 };
1941
1942 let mut radius = wr::BorderRadius {
1943 top_left: corner(&border.border_top_left_radius),
1944 top_right: corner(&border.border_top_right_radius),
1945 bottom_right: corner(&border.border_bottom_right_radius),
1946 bottom_left: corner(&border.border_bottom_left_radius),
1947 };
1948
1949 normalize_radii(&border_rect.to_webrender(), &mut radius);
1950 radius
1951 }
1952}