webrender/
border.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use api::{BorderRadius, BorderSide, BorderStyle, ColorF, ColorU};
6use api::{NormalBorder as ApiNormalBorder, RepeatMode};
7use api::units::*;
8use crate::clip::ClipNodeId;
9use crate::ellipse::Ellipse;
10use euclid::vec2;
11use crate::scene_building::SceneBuilder;
12use crate::spatial_tree::SpatialNodeIndex;
13use crate::gpu_types::{BorderInstance, BorderSegment, BrushFlags};
14use crate::prim_store::{BorderSegmentInfo, BrushSegment, NinePatchDescriptor};
15use crate::prim_store::borders::{NormalBorderPrim, NormalBorderData};
16use crate::util::{lerp, RectHelpers};
17use crate::internal_types::LayoutPrimitiveInfo;
18use crate::segment::EdgeAaSegmentMask;
19
20// Using 2048 as the maximum radius in device space before which we
21// start stretching is up for debate.
22// the value must be chosen so that the corners will not use an
23// unreasonable amount of memory but should allow crisp corners in the
24// common cases.
25
26/// Maximum resolution in device pixels at which borders are rasterized.
27pub const MAX_BORDER_RESOLUTION: u32 = 2048;
28/// Maximum number of dots or dashes per segment to avoid freezing and filling up
29/// memory with unreasonable inputs. It would be better to address this by not building
30/// a list of per-dot information in the first place.
31pub const MAX_DASH_COUNT: u32 = 2048;
32
33// TODO(gw): Perhaps there is a better way to store
34//           the border cache key than duplicating
35//           all the border structs with hashable
36//           variants...
37
38#[derive(Copy, Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
39#[cfg_attr(feature = "capture", derive(Serialize))]
40#[cfg_attr(feature = "replay", derive(Deserialize))]
41pub struct BorderRadiusAu {
42    pub top_left: LayoutSizeAu,
43    pub top_right: LayoutSizeAu,
44    pub bottom_left: LayoutSizeAu,
45    pub bottom_right: LayoutSizeAu,
46}
47
48impl From<BorderRadius> for BorderRadiusAu {
49    fn from(radius: BorderRadius) -> BorderRadiusAu {
50        BorderRadiusAu {
51            top_left: radius.top_left.to_au(),
52            top_right: radius.top_right.to_au(),
53            bottom_right: radius.bottom_right.to_au(),
54            bottom_left: radius.bottom_left.to_au(),
55        }
56    }
57}
58
59impl From<BorderRadiusAu> for BorderRadius {
60    fn from(radius: BorderRadiusAu) -> Self {
61        BorderRadius {
62            top_left: LayoutSize::from_au(radius.top_left),
63            top_right: LayoutSize::from_au(radius.top_right),
64            bottom_right: LayoutSize::from_au(radius.bottom_right),
65            bottom_left: LayoutSize::from_au(radius.bottom_left),
66        }
67    }
68}
69
70#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
71#[cfg_attr(feature = "capture", derive(Serialize))]
72#[cfg_attr(feature = "replay", derive(Deserialize))]
73pub struct BorderSideAu {
74    pub color: ColorU,
75    pub style: BorderStyle,
76}
77
78impl From<BorderSide> for BorderSideAu {
79    fn from(side: BorderSide) -> Self {
80        BorderSideAu {
81            color: side.color.into(),
82            style: side.style,
83        }
84    }
85}
86
87impl From<BorderSideAu> for BorderSide {
88    fn from(side: BorderSideAu) -> Self {
89        BorderSide {
90            color: side.color.into(),
91            style: side.style,
92        }
93    }
94}
95
96#[cfg_attr(feature = "capture", derive(Serialize))]
97#[cfg_attr(feature = "replay", derive(Deserialize))]
98#[derive(Debug, Clone, Hash, Eq, MallocSizeOf, PartialEq)]
99pub struct NormalBorderAu {
100    pub left: BorderSideAu,
101    pub right: BorderSideAu,
102    pub top: BorderSideAu,
103    pub bottom: BorderSideAu,
104    pub radius: BorderRadiusAu,
105    /// Whether to apply anti-aliasing on the border corners.
106    ///
107    /// Note that for this to be `false` and work, this requires the borders to
108    /// be solid, and no border-radius.
109    pub do_aa: bool,
110}
111
112impl NormalBorderAu {
113    // Construct a border based upon self with color
114    pub fn with_color(&self, color: ColorU) -> Self {
115        let mut b = self.clone();
116        b.left.color = color;
117        b.right.color = color;
118        b.top.color = color;
119        b.bottom.color = color;
120        b
121    }
122}
123
124impl From<ApiNormalBorder> for NormalBorderAu {
125    fn from(border: ApiNormalBorder) -> Self {
126        NormalBorderAu {
127            left: border.left.into(),
128            right: border.right.into(),
129            top: border.top.into(),
130            bottom: border.bottom.into(),
131            radius: border.radius.into(),
132            do_aa: border.do_aa,
133        }
134    }
135}
136
137impl From<NormalBorderAu> for ApiNormalBorder {
138    fn from(border: NormalBorderAu) -> Self {
139        ApiNormalBorder {
140            left: border.left.into(),
141            right: border.right.into(),
142            top: border.top.into(),
143            bottom: border.bottom.into(),
144            radius: border.radius.into(),
145            do_aa: border.do_aa,
146        }
147    }
148}
149
150/// Cache key that uniquely identifies a border
151/// segment in the render task cache.
152#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
153#[cfg_attr(feature = "capture", derive(Serialize))]
154#[cfg_attr(feature = "replay", derive(Deserialize))]
155pub struct BorderSegmentCacheKey {
156    pub size: LayoutSizeAu,
157    pub radius: LayoutSizeAu,
158    pub side0: BorderSideAu,
159    pub side1: BorderSideAu,
160    pub segment: BorderSegment,
161    pub do_aa: bool,
162    pub h_adjacent_corner_outer: LayoutPointAu,
163    pub h_adjacent_corner_radius: LayoutSizeAu,
164    pub v_adjacent_corner_outer: LayoutPointAu,
165    pub v_adjacent_corner_radius: LayoutSizeAu,
166}
167
168pub fn ensure_no_corner_overlap(
169    radius: &mut BorderRadius,
170    size: LayoutSize,
171) {
172    let mut ratio = 1.0;
173    let top_left_radius = &mut radius.top_left;
174    let top_right_radius = &mut radius.top_right;
175    let bottom_right_radius = &mut radius.bottom_right;
176    let bottom_left_radius = &mut radius.bottom_left;
177
178    if size.width > 0.0 {
179        let sum = top_left_radius.width + top_right_radius.width;
180        if size.width < sum {
181            ratio = f32::min(ratio, size.width / sum);
182        }
183
184        let sum = bottom_left_radius.width + bottom_right_radius.width;
185        if size.width < sum {
186            ratio = f32::min(ratio, size.width / sum);
187        }
188    }
189
190    if size.height > 0.0 {
191        let sum = top_left_radius.height + bottom_left_radius.height;
192        if size.height < sum {
193            ratio = f32::min(ratio, size.height / sum);
194        }
195
196        let sum = top_right_radius.height + bottom_right_radius.height;
197        if size.height < sum {
198            ratio = f32::min(ratio, size.height / sum);
199        }
200    }
201
202    if ratio < 1. {
203        top_left_radius.width *= ratio;
204        top_left_radius.height *= ratio;
205
206        top_right_radius.width *= ratio;
207        top_right_radius.height *= ratio;
208
209        bottom_left_radius.width *= ratio;
210        bottom_left_radius.height *= ratio;
211
212        bottom_right_radius.width *= ratio;
213        bottom_right_radius.height *= ratio;
214    }
215}
216
217impl<'a> SceneBuilder<'a> {
218    pub fn add_normal_border(
219        &mut self,
220        info: &LayoutPrimitiveInfo,
221        border: &ApiNormalBorder,
222        widths: LayoutSideOffsets,
223        spatial_node_index: SpatialNodeIndex,
224        clip_node_id: ClipNodeId,
225    ) {
226        let mut border = *border;
227        ensure_no_corner_overlap(&mut border.radius, info.rect.size());
228
229        self.add_primitive(
230            spatial_node_index,
231            clip_node_id,
232            info,
233            Vec::new(),
234            NormalBorderPrim {
235                border: border.into(),
236                widths: widths.to_au(),
237            },
238        );
239    }
240}
241
242pub trait BorderSideHelpers {
243    fn border_color(&self, is_inner_border: bool) -> ColorF;
244}
245
246impl BorderSideHelpers for BorderSide {
247    fn border_color(&self, is_inner_border: bool) -> ColorF {
248        let lighter = match self.style {
249            BorderStyle::Inset => is_inner_border,
250            BorderStyle::Outset => !is_inner_border,
251            _ => return self.color,
252        };
253
254        // The modulate colors below are not part of the specification. They are
255        // derived from the Gecko source code and experimentation, and used to
256        // modulate the colors in order to generate colors for the inset/outset
257        // and groove/ridge border styles.
258        //
259        // NOTE(emilio): Gecko at least takes the background color into
260        // account, should we do the same? Looks a bit annoying for this.
261        //
262        // NOTE(emilio): If you change this algorithm, do the same change on
263        // get_colors_for_side in cs_border_segment.glsl.
264        if self.color.r != 0.0 || self.color.g != 0.0 || self.color.b != 0.0 {
265            let scale = if lighter { 1.0 } else { 2.0 / 3.0 };
266            return self.color.scale_rgb(scale)
267        }
268
269        let black = if lighter { 0.7 } else { 0.3 };
270        ColorF::new(black, black, black, self.color.a)
271    }
272}
273
274/// The kind of border corner clip.
275#[repr(C)]
276#[derive(Copy, Debug, Clone, PartialEq)]
277pub enum BorderClipKind {
278    DashCorner = 1,
279    DashEdge = 2,
280    Dot = 3,
281}
282
283fn compute_outer_and_clip_sign(
284    corner_segment: BorderSegment,
285    radius: DeviceSize,
286) -> (DevicePoint, DeviceVector2D) {
287    let outer_scale = match corner_segment {
288        BorderSegment::TopLeft => DeviceVector2D::new(0.0, 0.0),
289        BorderSegment::TopRight => DeviceVector2D::new(1.0, 0.0),
290        BorderSegment::BottomRight => DeviceVector2D::new(1.0, 1.0),
291        BorderSegment::BottomLeft => DeviceVector2D::new(0.0, 1.0),
292        _ => panic!("bug: expected a corner segment"),
293    };
294    let outer = DevicePoint::new(
295        outer_scale.x * radius.width,
296        outer_scale.y * radius.height,
297    );
298
299    let clip_sign = DeviceVector2D::new(
300        1.0 - 2.0 * outer_scale.x,
301        1.0 - 2.0 * outer_scale.y,
302    );
303
304    (outer, clip_sign)
305}
306
307fn write_dashed_corner_instances(
308    corner_radius: DeviceSize,
309    widths: DeviceSize,
310    segment: BorderSegment,
311    base_instance: &BorderInstance,
312    instances: &mut Vec<BorderInstance>,
313) -> Result<(), ()> {
314    let ellipse = Ellipse::new(corner_radius);
315
316    let average_border_width = 0.5 * (widths.width + widths.height);
317
318    let (_half_dash, num_half_dashes) =
319        compute_half_dash(average_border_width, ellipse.total_arc_length);
320
321    if num_half_dashes == 0 {
322        return Err(());
323    }
324
325    let num_half_dashes = num_half_dashes.min(MAX_DASH_COUNT);
326
327    let (outer, clip_sign) = compute_outer_and_clip_sign(segment, corner_radius);
328
329    let instance_count = num_half_dashes / 4 + 1;
330    instances.reserve(instance_count as usize);
331
332    let half_dash_arc_length =
333        ellipse.total_arc_length / num_half_dashes as f32;
334    let dash_length = 2. * half_dash_arc_length;
335
336    let mut current_length = 0.;
337    for i in 0..instance_count {
338        let arc_length0 = current_length;
339        current_length += if i == 0 {
340            half_dash_arc_length
341        } else {
342            dash_length
343        };
344
345        let arc_length1 = current_length;
346        current_length += dash_length;
347
348        let alpha = ellipse.find_angle_for_arc_length(arc_length0);
349        let beta = ellipse.find_angle_for_arc_length(arc_length1);
350
351        let (point0, tangent0) = ellipse.get_point_and_tangent(alpha);
352        let (point1, tangent1) = ellipse.get_point_and_tangent(beta);
353
354        let point0 = DevicePoint::new(
355            outer.x + clip_sign.x * (corner_radius.width - point0.x),
356            outer.y + clip_sign.y * (corner_radius.height - point0.y),
357        );
358
359        let tangent0 = DeviceVector2D::new(
360            -tangent0.x * clip_sign.x,
361            -tangent0.y * clip_sign.y,
362        );
363
364        let point1 = DevicePoint::new(
365            outer.x + clip_sign.x * (corner_radius.width - point1.x),
366            outer.y + clip_sign.y * (corner_radius.height - point1.y),
367        );
368
369        let tangent1 = DeviceVector2D::new(
370            -tangent1.x * clip_sign.x,
371            -tangent1.y * clip_sign.y,
372        );
373
374        instances.push(BorderInstance {
375            flags: base_instance.flags | ((BorderClipKind::DashCorner as i32) << 24),
376            clip_params: [
377                point0.x,
378                point0.y,
379                tangent0.x,
380                tangent0.y,
381                point1.x,
382                point1.y,
383                tangent1.x,
384                tangent1.y,
385            ],
386            .. *base_instance
387        });
388    }
389
390    Ok(())
391}
392
393fn write_dotted_corner_instances(
394    corner_radius: DeviceSize,
395    widths: DeviceSize,
396    segment: BorderSegment,
397    base_instance: &BorderInstance,
398    instances: &mut Vec<BorderInstance>,
399) -> Result<(), ()> {
400    let mut corner_radius = corner_radius;
401    if corner_radius.width < (widths.width / 2.0) {
402        corner_radius.width = 0.0;
403    }
404    if corner_radius.height < (widths.height / 2.0) {
405        corner_radius.height = 0.0;
406    }
407
408    let (ellipse, max_dot_count) =
409        if corner_radius.width == 0. && corner_radius.height == 0. {
410            (Ellipse::new(corner_radius), 1)
411        } else {
412            // The centers of dots follow an ellipse along the middle of the
413            // border radius.
414            let inner_radius = (corner_radius - widths * 0.5).abs();
415            let ellipse = Ellipse::new(inner_radius);
416
417            // Allocate a "worst case" number of dot clips. This can be
418            // calculated by taking the minimum edge radius, since that
419            // will result in the maximum number of dots along the path.
420            let min_diameter = widths.width.min(widths.height);
421
422            // Get the number of circles (assuming spacing of one diameter
423            // between dots).
424            let max_dot_count = 0.5 * ellipse.total_arc_length / min_diameter;
425
426            // Add space for one extra dot since they are centered at the
427            // start of the arc.
428            (ellipse, max_dot_count.ceil() as usize)
429        };
430
431    if max_dot_count == 0 {
432        return Err(());
433    }
434
435    if max_dot_count == 1 {
436        let dot_diameter = lerp(widths.width, widths.height, 0.5);
437        instances.push(BorderInstance {
438            flags: base_instance.flags | ((BorderClipKind::Dot as i32) << 24),
439            clip_params: [
440                widths.width / 2.0, widths.height / 2.0, 0.5 * dot_diameter, 0.,
441                0., 0., 0., 0.,
442            ],
443            .. *base_instance
444        });
445        return Ok(());
446    }
447
448    let max_dot_count = max_dot_count.min(MAX_DASH_COUNT as usize);
449
450    // FIXME(emilio): Should probably use SmallVec.
451    let mut forward_dots = Vec::with_capacity(max_dot_count / 2 + 1);
452    let mut back_dots = Vec::with_capacity(max_dot_count / 2 + 1);
453    let mut leftover_arc_length = 0.0;
454
455    // Alternate between adding dots at the start and end of the
456    // ellipse arc. This ensures that we always end up with an exact
457    // half dot at each end of the arc, to match up with the edges.
458    forward_dots.push(DotInfo::new(widths.width, widths.width));
459    back_dots.push(DotInfo::new(
460        ellipse.total_arc_length - widths.height,
461        widths.height,
462    ));
463
464    let (outer, clip_sign) = compute_outer_and_clip_sign(segment, corner_radius);
465    for dot_index in 0 .. max_dot_count {
466        let prev_forward_pos = *forward_dots.last().unwrap();
467        let prev_back_pos = *back_dots.last().unwrap();
468
469        // Select which end of the arc to place a dot from.
470        // This just alternates between the start and end of
471        // the arc, which ensures that there is always an
472        // exact half-dot at each end of the ellipse.
473        let going_forward = dot_index & 1 == 0;
474
475        let (next_dot_pos, leftover) = if going_forward {
476            let next_dot_pos =
477                prev_forward_pos.arc_pos + 2.0 * prev_forward_pos.diameter;
478            (next_dot_pos, prev_back_pos.arc_pos - next_dot_pos)
479        } else {
480            let next_dot_pos = prev_back_pos.arc_pos - 2.0 * prev_back_pos.diameter;
481            (next_dot_pos, next_dot_pos - prev_forward_pos.arc_pos)
482        };
483
484        // Use a lerp between each edge's dot
485        // diameter, based on the linear distance
486        // along the arc to get the diameter of the
487        // dot at this arc position.
488        let t = next_dot_pos / ellipse.total_arc_length;
489        let dot_diameter = lerp(widths.width, widths.height, t);
490
491        // If we can't fit a dot, bail out.
492        if leftover < dot_diameter {
493            leftover_arc_length = leftover;
494            break;
495        }
496
497        // We can place a dot!
498        let dot = DotInfo::new(next_dot_pos, dot_diameter);
499        if going_forward {
500            forward_dots.push(dot);
501        } else {
502            back_dots.push(dot);
503        }
504    }
505
506    // Now step through the dots, and distribute any extra
507    // leftover space on the arc between them evenly. Once
508    // the final arc position is determined, generate the correct
509    // arc positions and angles that get passed to the clip shader.
510    let number_of_dots = forward_dots.len() + back_dots.len();
511    let extra_space_per_dot = leftover_arc_length / (number_of_dots - 1) as f32;
512
513    let create_dot_data = |arc_length: f32, dot_radius: f32| -> [f32; 8] {
514        // Represents the GPU data for drawing a single dot to a clip mask. The order
515        // these are specified must stay in sync with the way this data is read in the
516        // dot clip shader.
517        let theta = ellipse.find_angle_for_arc_length(arc_length);
518        let (center, _) = ellipse.get_point_and_tangent(theta);
519
520        let center = DevicePoint::new(
521            outer.x + clip_sign.x * (corner_radius.width - center.x),
522            outer.y + clip_sign.y * (corner_radius.height - center.y),
523        );
524
525        [center.x, center.y, dot_radius, 0.0, 0.0, 0.0, 0.0, 0.0]
526    };
527
528    instances.reserve(number_of_dots);
529    for (i, dot) in forward_dots.iter().enumerate() {
530        let extra_dist = i as f32 * extra_space_per_dot;
531        instances.push(BorderInstance {
532            flags: base_instance.flags | ((BorderClipKind::Dot as i32) << 24),
533            clip_params: create_dot_data(dot.arc_pos + extra_dist, 0.5 * dot.diameter),
534            .. *base_instance
535        });
536    }
537
538    for (i, dot) in back_dots.iter().enumerate() {
539        let extra_dist = i as f32 * extra_space_per_dot;
540        instances.push(BorderInstance {
541            flags: base_instance.flags | ((BorderClipKind::Dot as i32) << 24),
542            clip_params: create_dot_data(dot.arc_pos - extra_dist, 0.5 * dot.diameter),
543            .. *base_instance
544        });
545    }
546
547    Ok(())
548}
549
550#[derive(Copy, Clone, Debug)]
551struct DotInfo {
552    arc_pos: f32,
553    diameter: f32,
554}
555
556impl DotInfo {
557    fn new(arc_pos: f32, diameter: f32) -> DotInfo {
558        DotInfo { arc_pos, diameter }
559    }
560}
561
562/// Information needed to place and draw a border edge.
563#[derive(Debug)]
564struct EdgeInfo {
565    /// Offset in local space to place the edge from origin.
566    local_offset: f32,
567    /// Size of the edge in local space.
568    local_size: f32,
569    /// Local stretch size for this edge (repeat past this).
570    stretch_size: f32,
571}
572
573impl EdgeInfo {
574    fn new(
575        local_offset: f32,
576        local_size: f32,
577        stretch_size: f32,
578    ) -> Self {
579        Self {
580            local_offset,
581            local_size,
582            stretch_size,
583        }
584    }
585}
586
587// Given a side width and the available space, compute the half-dash (half of
588// the 'on' segment) and the count of them for a given segment.
589fn compute_half_dash(side_width: f32, total_size: f32) -> (f32, u32) {
590    let half_dash = side_width * 1.5;
591    // 16k dashes should be enough for anyone
592    let num_half_dashes = (total_size / half_dash).ceil().min(16.0 * 1024.0) as u32;
593
594    if num_half_dashes == 0 {
595        return (0., 0);
596    }
597
598    // TODO(emilio): Gecko has some other heuristics here to start with a full
599    // dash when the border side is zero, for example. We might consider those
600    // in the future.
601    let num_half_dashes = if num_half_dashes % 4 != 0 {
602        num_half_dashes + 4 - num_half_dashes % 4
603    } else {
604        num_half_dashes
605    };
606
607    let half_dash = total_size / num_half_dashes as f32;
608    (half_dash, num_half_dashes)
609}
610
611
612// Get the needed size in device pixels for an edge,
613// based on the border style of that edge. This is used
614// to determine how big the render task should be.
615fn get_edge_info(
616    style: BorderStyle,
617    side_width: f32,
618    avail_size: f32,
619) -> EdgeInfo {
620    // To avoid division by zero below.
621    if side_width <= 0.0 || avail_size <= 0.0 {
622        return EdgeInfo::new(0.0, 0.0, 0.0);
623    }
624
625    match style {
626        BorderStyle::Dashed => {
627            // Basically, two times the dash size.
628            let (half_dash, _num_half_dashes) =
629                compute_half_dash(side_width, avail_size);
630            let stretch_size = 2.0 * 2.0 * half_dash;
631            EdgeInfo::new(0., avail_size, stretch_size)
632        }
633        BorderStyle::Dotted => {
634            let dot_and_space_size = 2.0 * side_width;
635            if avail_size < dot_and_space_size * 0.75 {
636                return EdgeInfo::new(0.0, 0.0, 0.0);
637            }
638            let approx_dot_count = avail_size / dot_and_space_size;
639            let dot_count = approx_dot_count.floor().max(1.0);
640            let used_size = dot_count * dot_and_space_size;
641            let extra_space = avail_size - used_size;
642            let stretch_size = dot_and_space_size;
643            let offset = (extra_space * 0.5).round();
644            EdgeInfo::new(offset, used_size, stretch_size)
645        }
646        _ => {
647            EdgeInfo::new(0.0, avail_size, 8.0)
648        }
649    }
650}
651
652/// Create the set of border segments and render task
653/// cache keys for a given CSS border.
654pub fn create_border_segments(
655    size: LayoutSize,
656    border: &ApiNormalBorder,
657    widths: &LayoutSideOffsets,
658    border_segments: &mut Vec<BorderSegmentInfo>,
659    brush_segments: &mut Vec<BrushSegment>,
660) {
661    let rect = LayoutRect::from_size(size);
662
663    let overlap = LayoutSize::new(
664        (widths.left + widths.right - size.width).max(0.0),
665        (widths.top + widths.bottom - size.height).max(0.0),
666    );
667    let non_overlapping_widths = LayoutSideOffsets::new(
668        widths.top - overlap.height / 2.0,
669        widths.right - overlap.width / 2.0,
670        widths.bottom - overlap.height / 2.0,
671        widths.left - overlap.width / 2.0,
672    );
673
674    let local_size_tl = LayoutSize::new(
675        border.radius.top_left.width.max(widths.left),
676        border.radius.top_left.height.max(widths.top),
677    );
678    let local_size_tr = LayoutSize::new(
679        border.radius.top_right.width.max(widths.right),
680        border.radius.top_right.height.max(widths.top),
681    );
682    let local_size_br = LayoutSize::new(
683        border.radius.bottom_right.width.max(widths.right),
684        border.radius.bottom_right.height.max(widths.bottom),
685    );
686    let local_size_bl = LayoutSize::new(
687        border.radius.bottom_left.width.max(widths.left),
688        border.radius.bottom_left.height.max(widths.bottom),
689    );
690
691    let top_edge_info = get_edge_info(
692        border.top.style,
693        widths.top,
694        rect.width() - local_size_tl.width - local_size_tr.width,
695    );
696    let bottom_edge_info = get_edge_info(
697        border.bottom.style,
698        widths.bottom,
699        rect.width() - local_size_bl.width - local_size_br.width,
700    );
701
702    let left_edge_info = get_edge_info(
703        border.left.style,
704        widths.left,
705        rect.height() - local_size_tl.height - local_size_bl.height,
706    );
707    let right_edge_info = get_edge_info(
708        border.right.style,
709        widths.right,
710        rect.height() - local_size_tr.height - local_size_br.height,
711    );
712
713    add_edge_segment(
714        LayoutRect::from_floats(
715            rect.min.x,
716            rect.min.y + local_size_tl.height + left_edge_info.local_offset,
717            rect.min.x + non_overlapping_widths.left,
718            rect.min.y + local_size_tl.height + left_edge_info.local_offset + left_edge_info.local_size,
719        ),
720        &left_edge_info,
721        border.left,
722        non_overlapping_widths.left,
723        BorderSegment::Left,
724        EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT,
725        brush_segments,
726        border_segments,
727        border.do_aa,
728    );
729    add_edge_segment(
730        LayoutRect::from_floats(
731            rect.min.x + local_size_tl.width + top_edge_info.local_offset,
732            rect.min.y,
733            rect.min.x + local_size_tl.width + top_edge_info.local_offset + top_edge_info.local_size,
734            rect.min.y + non_overlapping_widths.top,
735        ),
736        &top_edge_info,
737        border.top,
738        non_overlapping_widths.top,
739        BorderSegment::Top,
740        EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM,
741        brush_segments,
742        border_segments,
743        border.do_aa,
744    );
745    add_edge_segment(
746        LayoutRect::from_floats(
747            rect.min.x + rect.width() - non_overlapping_widths.right,
748            rect.min.y + local_size_tr.height + right_edge_info.local_offset,
749            rect.min.x + rect.width(),
750            rect.min.y + local_size_tr.height + right_edge_info.local_offset + right_edge_info.local_size,
751        ),
752        &right_edge_info,
753        border.right,
754        non_overlapping_widths.right,
755        BorderSegment::Right,
756        EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT,
757        brush_segments,
758        border_segments,
759        border.do_aa,
760    );
761    add_edge_segment(
762        LayoutRect::from_floats(
763            rect.min.x + local_size_bl.width + bottom_edge_info.local_offset,
764            rect.min.y + rect.height() - non_overlapping_widths.bottom,
765            rect.min.x + local_size_bl.width + bottom_edge_info.local_offset + bottom_edge_info.local_size,
766            rect.min.y + rect.height(),
767        ),
768        &bottom_edge_info,
769        border.bottom,
770        non_overlapping_widths.bottom,
771        BorderSegment::Bottom,
772        EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP,
773        brush_segments,
774        border_segments,
775        border.do_aa,
776    );
777
778    add_corner_segment(
779        LayoutRect::from_floats(
780            rect.min.x,
781            rect.min.y,
782            rect.min.x + local_size_tl.width,
783            rect.min.y + local_size_tl.height,
784        ),
785        LayoutRect::from_floats(
786            rect.min.x,
787            rect.min.y,
788            rect.max.x - non_overlapping_widths.right,
789            rect.max.y - non_overlapping_widths.bottom
790        ),
791        border.left,
792        border.top,
793        LayoutSize::new(widths.left, widths.top),
794        border.radius.top_left,
795        BorderSegment::TopLeft,
796        EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT,
797        rect.top_right(),
798        border.radius.top_right,
799        rect.bottom_left(),
800        border.radius.bottom_left,
801        brush_segments,
802        border_segments,
803        border.do_aa,
804    );
805    add_corner_segment(
806        LayoutRect::from_floats(
807            rect.min.x + rect.width() - local_size_tr.width,
808            rect.min.y,
809            rect.min.x + rect.width(),
810            rect.min.y + local_size_tr.height,
811        ),
812        LayoutRect::from_floats(
813            rect.min.x + non_overlapping_widths.left,
814            rect.min.y,
815            rect.max.x,
816            rect.max.y - non_overlapping_widths.bottom,
817        ),
818        border.top,
819        border.right,
820        LayoutSize::new(widths.right, widths.top),
821        border.radius.top_right,
822        BorderSegment::TopRight,
823        EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT,
824        rect.min,
825        border.radius.top_left,
826        rect.max,
827        border.radius.bottom_right,
828        brush_segments,
829        border_segments,
830        border.do_aa,
831    );
832    add_corner_segment(
833        LayoutRect::from_floats(
834            rect.min.x + rect.width() - local_size_br.width,
835            rect.min.y + rect.height() - local_size_br.height,
836            rect.min.x + rect.width(),
837            rect.min.y + rect.height(),
838        ),
839        LayoutRect::from_floats(
840            rect.min.x + non_overlapping_widths.left,
841            rect.min.y + non_overlapping_widths.top,
842            rect.max.x,
843            rect.max.y,
844        ),
845        border.right,
846        border.bottom,
847        LayoutSize::new(widths.right, widths.bottom),
848        border.radius.bottom_right,
849        BorderSegment::BottomRight,
850        EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT,
851        rect.bottom_left(),
852        border.radius.bottom_left,
853        rect.top_right(),
854        border.radius.top_right,
855        brush_segments,
856        border_segments,
857        border.do_aa,
858    );
859    add_corner_segment(
860        LayoutRect::from_floats(
861            rect.min.x,
862            rect.min.y + rect.height() - local_size_bl.height,
863            rect.min.x + local_size_bl.width,
864            rect.min.y + rect.height(),
865        ),
866        LayoutRect::from_floats(
867            rect.min.x,
868            rect.min.y + non_overlapping_widths.top,
869            rect.max.x - non_overlapping_widths.right,
870            rect.max.y,
871        ),
872        border.bottom,
873        border.left,
874        LayoutSize::new(widths.left, widths.bottom),
875        border.radius.bottom_left,
876        BorderSegment::BottomLeft,
877        EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::LEFT,
878        rect.max,
879        border.radius.bottom_right,
880        rect.min,
881        border.radius.top_left,
882        brush_segments,
883        border_segments,
884        border.do_aa,
885    );
886}
887
888/// Computes the maximum scale that we allow for this set of border parameters.
889/// capping the scale will result in rendering very large corners at a lower
890/// resolution and stretching them, so they will have the right shape, but
891/// blurrier.
892pub fn get_max_scale_for_border(
893    border_data: &NormalBorderData,
894) -> LayoutToDeviceScale {
895    let mut r = 1.0;
896    for segment in &border_data.border_segments {
897        let size = segment.local_task_size;
898        r = size.width.max(size.height.max(r));
899    }
900
901    LayoutToDeviceScale::new(MAX_BORDER_RESOLUTION as f32 / r)
902}
903
904fn add_segment(
905    task_rect: DeviceRect,
906    style0: BorderStyle,
907    style1: BorderStyle,
908    color0: ColorF,
909    color1: ColorF,
910    segment: BorderSegment,
911    instances: &mut Vec<BorderInstance>,
912    widths: DeviceSize,
913    radius: DeviceSize,
914    do_aa: bool,
915    h_adjacent_corner_outer: DevicePoint,
916    h_adjacent_corner_radius: DeviceSize,
917    v_adjacent_corner_outer: DevicePoint,
918    v_adjacent_corner_radius: DeviceSize,
919) {
920    let base_flags = (segment as i32) |
921                     ((style0 as i32) << 8) |
922                     ((style1 as i32) << 16) |
923                     ((do_aa as i32) << 28);
924
925    let base_instance = BorderInstance {
926        task_origin: DevicePoint::zero(),
927        local_rect: task_rect,
928        flags: base_flags,
929        color0: color0.premultiplied(),
930        color1: color1.premultiplied(),
931        widths,
932        radius,
933        clip_params: [0.0; 8],
934    };
935
936    match segment {
937        BorderSegment::TopLeft |
938        BorderSegment::TopRight |
939        BorderSegment::BottomLeft |
940        BorderSegment::BottomRight => {
941            // TODO(gw): Similarly to the old border code, we don't correctly handle a a corner
942            //           that is dashed on one edge, and dotted on another. We can handle this
943            //           in the future by submitting two instances, each one with one side
944            //           color set to have an alpha of 0.
945            if (style0 == BorderStyle::Dotted && style1 == BorderStyle::Dashed) ||
946               (style0 == BorderStyle::Dashed && style0 == BorderStyle::Dotted) {
947                warn!("TODO: Handle a corner with dotted / dashed transition.");
948            }
949
950            let dashed_or_dotted_corner = match style0 {
951                BorderStyle::Dashed => {
952                    write_dashed_corner_instances(
953                        radius,
954                        widths,
955                        segment,
956                        &base_instance,
957                        instances,
958                    )
959                }
960                BorderStyle::Dotted => {
961                    write_dotted_corner_instances(
962                        radius,
963                        widths,
964                        segment,
965                        &base_instance,
966                        instances,
967                    )
968                }
969                _ => Err(()),
970            };
971
972            if dashed_or_dotted_corner.is_err() {
973                let clip_params = [
974                    h_adjacent_corner_outer.x,
975                    h_adjacent_corner_outer.y,
976                    h_adjacent_corner_radius.width,
977                    h_adjacent_corner_radius.height,
978                    v_adjacent_corner_outer.x,
979                    v_adjacent_corner_outer.y,
980                    v_adjacent_corner_radius.width,
981                    v_adjacent_corner_radius.height,
982                ];
983
984                instances.push(BorderInstance {
985                    clip_params,
986                    ..base_instance
987                });
988            }
989        }
990        BorderSegment::Top |
991        BorderSegment::Bottom |
992        BorderSegment::Right |
993        BorderSegment::Left => {
994            let is_vertical = segment == BorderSegment::Left ||
995                              segment == BorderSegment::Right;
996
997            match style0 {
998                BorderStyle::Dashed => {
999                    let (x, y) = if is_vertical {
1000                        let half_dash_size = task_rect.height() * 0.25;
1001                        (0., half_dash_size)
1002                    } else {
1003                        let half_dash_size = task_rect.width() * 0.25;
1004                        (half_dash_size, 0.)
1005                    };
1006
1007                    instances.push(BorderInstance {
1008                        flags: base_flags | ((BorderClipKind::DashEdge as i32) << 24),
1009                        clip_params: [
1010                            x, y, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1011                        ],
1012                        ..base_instance
1013                    });
1014                }
1015                BorderStyle::Dotted => {
1016                    let (x, y, r) = if is_vertical {
1017                        (widths.width * 0.5,
1018                         widths.width,
1019                         widths.width * 0.5)
1020                    } else {
1021                        (widths.height,
1022                         widths.height * 0.5,
1023                         widths.height * 0.5)
1024                    };
1025
1026                    instances.push(BorderInstance {
1027                        flags: base_flags | ((BorderClipKind::Dot as i32) << 24),
1028                        clip_params: [
1029                            x, y, r, 0.0, 0.0, 0.0, 0.0, 0.0,
1030                        ],
1031                        ..base_instance
1032                    });
1033                }
1034                _ => {
1035                    instances.push(base_instance);
1036                }
1037            }
1038        }
1039    }
1040}
1041
1042/// Add a corner segment (if valid) to the list of
1043/// border segments for this primitive.
1044fn add_corner_segment(
1045    image_rect: LayoutRect,
1046    non_overlapping_rect: LayoutRect,
1047    side0: BorderSide,
1048    side1: BorderSide,
1049    widths: LayoutSize,
1050    radius: LayoutSize,
1051    segment: BorderSegment,
1052    edge_flags: EdgeAaSegmentMask,
1053    h_adjacent_corner_outer: LayoutPoint,
1054    h_adjacent_corner_radius: LayoutSize,
1055    v_adjacent_corner_outer: LayoutPoint,
1056    v_adjacent_corner_radius: LayoutSize,
1057    brush_segments: &mut Vec<BrushSegment>,
1058    border_segments: &mut Vec<BorderSegmentInfo>,
1059    do_aa: bool,
1060) {
1061    if side0.color.a <= 0.0 && side1.color.a <= 0.0 {
1062        return;
1063    }
1064
1065    if widths.width <= 0.0 && widths.height <= 0.0 {
1066        return;
1067    }
1068
1069    if side0.style.is_hidden() && side1.style.is_hidden() {
1070        return;
1071    }
1072
1073    let segment_rect = match image_rect.intersection(&non_overlapping_rect) {
1074        Some(rect) => rect,
1075        None => {
1076            return;
1077        }
1078    };
1079
1080    let texture_rect = segment_rect
1081        .translate(-image_rect.min.to_vector())
1082        .scale(1.0 / image_rect.width(), 1.0 / image_rect.height());
1083
1084    brush_segments.push(
1085        BrushSegment::new(
1086            segment_rect,
1087            /* may_need_clip_mask = */ true,
1088            edge_flags,
1089            [texture_rect.min.x, texture_rect.min.y, texture_rect.max.x, texture_rect.max.y],
1090            BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_TEXEL_RECT,
1091        )
1092    );
1093
1094    // If the radii of the adjacent corners do not overlap with this segment,
1095    // then set the outer position to this segment's corner and the radii to zero.
1096    // That way the cache key is unaffected by non-overlapping corners, resulting
1097    // in fewer misses.
1098    let (h_corner_outer, h_corner_radius) = match segment {
1099        BorderSegment::TopLeft => {
1100            if h_adjacent_corner_outer.x - h_adjacent_corner_radius.width < image_rect.max.x {
1101                (h_adjacent_corner_outer, h_adjacent_corner_radius)
1102            } else {
1103                (LayoutPoint::new(image_rect.max.x, image_rect.min.y), LayoutSize::zero())
1104            }
1105        }
1106        BorderSegment::TopRight => {
1107            if h_adjacent_corner_outer.x + h_adjacent_corner_radius.width > image_rect.min.x {
1108                (h_adjacent_corner_outer, h_adjacent_corner_radius)
1109            } else {
1110                (LayoutPoint::new(image_rect.min.x, image_rect.min.y), LayoutSize::zero())
1111            }
1112        }
1113        BorderSegment::BottomRight => {
1114            if h_adjacent_corner_outer.x + h_adjacent_corner_radius.width > image_rect.min.x {
1115                (h_adjacent_corner_outer, h_adjacent_corner_radius)
1116            } else {
1117                (LayoutPoint::new(image_rect.min.x, image_rect.max.y), LayoutSize::zero())
1118            }
1119        }
1120        BorderSegment::BottomLeft => {
1121            if h_adjacent_corner_outer.x - h_adjacent_corner_radius.width < image_rect.max.x {
1122                (h_adjacent_corner_outer, h_adjacent_corner_radius)
1123            } else {
1124                (image_rect.max, LayoutSize::zero())
1125            }
1126        }
1127        _ => unreachable!()
1128    };
1129
1130    let (v_corner_outer, v_corner_radius) = match segment {
1131        BorderSegment::TopLeft => {
1132            if v_adjacent_corner_outer.y - v_adjacent_corner_radius.height < image_rect.max.y {
1133                (v_adjacent_corner_outer, v_adjacent_corner_radius)
1134            } else {
1135                (LayoutPoint::new(image_rect.min.x, image_rect.max.y), LayoutSize::zero())
1136            }
1137        }
1138        BorderSegment::TopRight => {
1139            if v_adjacent_corner_outer.y - v_adjacent_corner_radius.height < image_rect.max.y {
1140                (v_adjacent_corner_outer, v_adjacent_corner_radius)
1141            } else {
1142                (image_rect.max, LayoutSize::zero())
1143            }
1144        }
1145        BorderSegment::BottomRight => {
1146            if v_adjacent_corner_outer.y + v_adjacent_corner_radius.height > image_rect.min.y {
1147                (v_adjacent_corner_outer, v_adjacent_corner_radius)
1148            } else {
1149                (LayoutPoint::new(image_rect.max.x, image_rect.min.y), LayoutSize::zero())
1150            }
1151        }
1152        BorderSegment::BottomLeft => {
1153            if v_adjacent_corner_outer.y + v_adjacent_corner_radius.height > image_rect.min.y {
1154                (v_adjacent_corner_outer, v_adjacent_corner_radius)
1155            } else {
1156                (LayoutPoint::new(image_rect.min.x, image_rect.min.y), LayoutSize::zero())
1157            }
1158        }
1159        _ => unreachable!()
1160    };
1161
1162    border_segments.push(BorderSegmentInfo {
1163        local_task_size: image_rect.size(),
1164        cache_key: BorderSegmentCacheKey {
1165            do_aa,
1166            side0: side0.into(),
1167            side1: side1.into(),
1168            segment,
1169            radius: radius.to_au(),
1170            size: widths.to_au(),
1171            h_adjacent_corner_outer: (h_corner_outer - image_rect.min).to_point().to_au(),
1172            h_adjacent_corner_radius: h_corner_radius.to_au(),
1173            v_adjacent_corner_outer: (v_corner_outer - image_rect.min).to_point().to_au(),
1174            v_adjacent_corner_radius: v_corner_radius.to_au(),
1175        },
1176    });
1177}
1178
1179/// Add an edge segment (if valid) to the list of
1180/// border segments for this primitive.
1181fn add_edge_segment(
1182    image_rect: LayoutRect,
1183    edge_info: &EdgeInfo,
1184    side: BorderSide,
1185    width: f32,
1186    segment: BorderSegment,
1187    edge_flags: EdgeAaSegmentMask,
1188    brush_segments: &mut Vec<BrushSegment>,
1189    border_segments: &mut Vec<BorderSegmentInfo>,
1190    do_aa: bool,
1191) {
1192    if side.color.a <= 0.0 {
1193        return;
1194    }
1195
1196    if side.style.is_hidden() {
1197        return;
1198    }
1199
1200    let (size, brush_flags) = match segment {
1201        BorderSegment::Left | BorderSegment::Right => {
1202            (LayoutSize::new(width, edge_info.stretch_size), BrushFlags::SEGMENT_REPEAT_Y)
1203        }
1204        BorderSegment::Top | BorderSegment::Bottom => {
1205            (LayoutSize::new(edge_info.stretch_size, width), BrushFlags::SEGMENT_REPEAT_X)
1206        }
1207        _ => {
1208            unreachable!();
1209        }
1210    };
1211
1212    if image_rect.width() <= 0. || image_rect.height() <= 0. {
1213        return;
1214    }
1215
1216    brush_segments.push(
1217        BrushSegment::new(
1218            image_rect,
1219            /* may_need_clip_mask = */ true,
1220            edge_flags,
1221            [0.0, 0.0, size.width, size.height],
1222            BrushFlags::SEGMENT_RELATIVE | brush_flags,
1223        )
1224    );
1225
1226    border_segments.push(BorderSegmentInfo {
1227        local_task_size: size,
1228        cache_key: BorderSegmentCacheKey {
1229            do_aa,
1230            side0: side.into(),
1231            side1: side.into(),
1232            radius: LayoutSizeAu::zero(),
1233            size: size.to_au(),
1234            segment,
1235            h_adjacent_corner_outer: LayoutPointAu::zero(),
1236            h_adjacent_corner_radius: LayoutSizeAu::zero(),
1237            v_adjacent_corner_outer: LayoutPointAu::zero(),
1238            v_adjacent_corner_radius: LayoutSizeAu::zero(),
1239        },
1240    });
1241}
1242
1243/// Build the set of border instances needed to draw a border
1244/// segment into the render task cache.
1245pub fn build_border_instances(
1246    cache_key: &BorderSegmentCacheKey,
1247    cache_size: DeviceIntSize,
1248    border: &ApiNormalBorder,
1249    scale: LayoutToDeviceScale,
1250) -> Vec<BorderInstance> {
1251    let mut instances = Vec::new();
1252
1253    let (side0, side1, flip0, flip1) = match cache_key.segment {
1254        BorderSegment::Left => (&border.left, &border.left, false, false),
1255        BorderSegment::Top => (&border.top, &border.top, false, false),
1256        BorderSegment::Right => (&border.right, &border.right, true, true),
1257        BorderSegment::Bottom => (&border.bottom, &border.bottom, true, true),
1258        BorderSegment::TopLeft => (&border.left, &border.top, false, false),
1259        BorderSegment::TopRight => (&border.top, &border.right, false, true),
1260        BorderSegment::BottomRight => (&border.right, &border.bottom, true, true),
1261        BorderSegment::BottomLeft => (&border.bottom, &border.left, true, false),
1262    };
1263
1264    let style0 = if side0.style.is_hidden() {
1265        side1.style
1266    } else {
1267        side0.style
1268    };
1269    let style1 = if side1.style.is_hidden() {
1270        side0.style
1271    } else {
1272        side1.style
1273    };
1274
1275    let color0 = side0.border_color(flip0);
1276    let color1 = side1.border_color(flip1);
1277
1278    let widths = (LayoutSize::from_au(cache_key.size) * scale).ceil();
1279    let radius = (LayoutSize::from_au(cache_key.radius) * scale).ceil();
1280
1281    let h_corner_outer = (LayoutPoint::from_au(cache_key.h_adjacent_corner_outer) * scale).round();
1282    let h_corner_radius = (LayoutSize::from_au(cache_key.h_adjacent_corner_radius) * scale).ceil();
1283    let v_corner_outer = (LayoutPoint::from_au(cache_key.v_adjacent_corner_outer) * scale).round();
1284    let v_corner_radius = (LayoutSize::from_au(cache_key.v_adjacent_corner_radius) * scale).ceil();
1285
1286    add_segment(
1287        DeviceRect::from_size(cache_size.to_f32()),
1288        style0,
1289        style1,
1290        color0,
1291        color1,
1292        cache_key.segment,
1293        &mut instances,
1294        widths,
1295        radius,
1296        border.do_aa,
1297        h_corner_outer,
1298        h_corner_radius,
1299        v_corner_outer,
1300        v_corner_radius,
1301    );
1302
1303    instances
1304}
1305
1306impl NinePatchDescriptor {
1307    pub fn create_segments(
1308        &self,
1309        size: LayoutSize,
1310    ) -> Vec<BrushSegment> {
1311        let rect = LayoutRect::from_size(size);
1312
1313        // Calculate the local texel coords of the slices.
1314        let px0 = 0.0;
1315        let px1 = self.slice.left as f32 / self.width as f32;
1316        let px2 = (self.width as f32 - self.slice.right as f32) / self.width as f32;
1317        let px3 = 1.0;
1318
1319        let py0 = 0.0;
1320        let py1 = self.slice.top as f32 / self.height as f32;
1321        let py2 = (self.height as f32 - self.slice.bottom as f32) / self.height as f32;
1322        let py3 = 1.0;
1323
1324        let tl_outer = LayoutPoint::new(rect.min.x, rect.min.y);
1325        let tl_inner = tl_outer + vec2(self.widths.left, self.widths.top);
1326
1327        let tr_outer = LayoutPoint::new(rect.min.x + rect.width(), rect.min.y);
1328        let tr_inner = tr_outer + vec2(-self.widths.right, self.widths.top);
1329
1330        let bl_outer = LayoutPoint::new(rect.min.x, rect.min.y + rect.height());
1331        let bl_inner = bl_outer + vec2(self.widths.left, -self.widths.bottom);
1332
1333        let br_outer = rect.max;
1334
1335        let br_inner = br_outer - vec2(self.widths.right, self.widths.bottom);
1336
1337        fn add_segment(
1338            segments: &mut Vec<BrushSegment>,
1339            rect: LayoutRect,
1340            uv_rect: TexelRect,
1341            repeat_horizontal: RepeatMode,
1342            repeat_vertical: RepeatMode,
1343            extra_flags: BrushFlags,
1344        ) {
1345            if uv_rect.uv1.x <= uv_rect.uv0.x || uv_rect.uv1.y <= uv_rect.uv0.y {
1346                return;
1347            }
1348
1349            // Use segment relative interpolation for all
1350            // instances in this primitive.
1351            let mut brush_flags =
1352                BrushFlags::SEGMENT_RELATIVE |
1353                BrushFlags::SEGMENT_TEXEL_RECT |
1354                extra_flags;
1355
1356            // Enable repeat modes on the segment.
1357            if repeat_horizontal == RepeatMode::Repeat {
1358                brush_flags |= BrushFlags::SEGMENT_REPEAT_X | BrushFlags::SEGMENT_REPEAT_X_CENTERED;
1359            } else if repeat_horizontal == RepeatMode::Round {
1360                brush_flags |= BrushFlags::SEGMENT_REPEAT_X | BrushFlags::SEGMENT_REPEAT_X_ROUND;
1361            }
1362
1363            if repeat_vertical == RepeatMode::Repeat {
1364                brush_flags |= BrushFlags::SEGMENT_REPEAT_Y | BrushFlags::SEGMENT_REPEAT_Y_CENTERED;
1365            } else if repeat_vertical == RepeatMode::Round {
1366                brush_flags |= BrushFlags::SEGMENT_REPEAT_Y | BrushFlags::SEGMENT_REPEAT_Y_ROUND;
1367            }
1368
1369            let segment = BrushSegment::new(
1370                rect,
1371                true,
1372                EdgeAaSegmentMask::empty(),
1373                [
1374                    uv_rect.uv0.x,
1375                    uv_rect.uv0.y,
1376                    uv_rect.uv1.x,
1377                    uv_rect.uv1.y,
1378                ],
1379                brush_flags,
1380            );
1381
1382            segments.push(segment);
1383        }
1384
1385        // Build the list of image segments
1386        let mut segments = Vec::new();
1387
1388        // Top left
1389        add_segment(
1390            &mut segments,
1391            LayoutRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
1392            TexelRect::new(px0, py0, px1, py1),
1393            RepeatMode::Stretch,
1394            RepeatMode::Stretch,
1395            BrushFlags::empty(),
1396        );
1397        // Top right
1398        add_segment(
1399            &mut segments,
1400            LayoutRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y),
1401            TexelRect::new(px2, py0, px3, py1),
1402            RepeatMode::Stretch,
1403            RepeatMode::Stretch,
1404            BrushFlags::empty(),
1405        );
1406        // Bottom right
1407        add_segment(
1408            &mut segments,
1409            LayoutRect::from_floats(br_inner.x, br_inner.y, br_outer.x, br_outer.y),
1410            TexelRect::new(px2, py2, px3, py3),
1411            RepeatMode::Stretch,
1412            RepeatMode::Stretch,
1413            BrushFlags::empty(),
1414        );
1415        // Bottom left
1416        add_segment(
1417            &mut segments,
1418            LayoutRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y),
1419            TexelRect::new(px0, py2, px1, py3),
1420            RepeatMode::Stretch,
1421            RepeatMode::Stretch,
1422            BrushFlags::empty(),
1423        );
1424
1425        // Center
1426        if self.fill {
1427            add_segment(
1428                &mut segments,
1429                LayoutRect::from_floats(tl_inner.x, tl_inner.y, tr_inner.x, bl_inner.y),
1430                TexelRect::new(px1, py1, px2, py2),
1431                self.repeat_horizontal,
1432                self.repeat_vertical,
1433                BrushFlags::SEGMENT_NINEPATCH_MIDDLE,
1434            );
1435        }
1436
1437        // Add edge segments.
1438
1439        // Top
1440        add_segment(
1441            &mut segments,
1442            LayoutRect::from_floats(tl_inner.x, tl_outer.y, tr_inner.x, tl_inner.y),
1443            TexelRect::new(px1, py0, px2, py1),
1444            self.repeat_horizontal,
1445            RepeatMode::Stretch,
1446            BrushFlags::empty(),
1447        );
1448        // Bottom
1449        add_segment(
1450            &mut segments,
1451            LayoutRect::from_floats(bl_inner.x, bl_inner.y, br_inner.x, bl_outer.y),
1452            TexelRect::new(px1, py2, px2, py3),
1453            self.repeat_horizontal,
1454            RepeatMode::Stretch,
1455            BrushFlags::empty(),
1456        );
1457        // Left
1458        add_segment(
1459            &mut segments,
1460            LayoutRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
1461            TexelRect::new(px0, py1, px1, py2),
1462            RepeatMode::Stretch,
1463            self.repeat_vertical,
1464            BrushFlags::empty(),
1465        );
1466        // Right
1467        add_segment(
1468            &mut segments,
1469            LayoutRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
1470            TexelRect::new(px2, py1, px3, py2),
1471            RepeatMode::Stretch,
1472            self.repeat_vertical,
1473            BrushFlags::empty(),
1474        );
1475
1476        segments
1477    }
1478}