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