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