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