summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/src/border.rs
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wr/webrender/src/border.rs')
-rw-r--r--gfx/wr/webrender/src/border.rs1474
1 files changed, 1474 insertions, 0 deletions
diff --git a/gfx/wr/webrender/src/border.rs b/gfx/wr/webrender/src/border.rs
new file mode 100644
index 0000000000..9fd54563db
--- /dev/null
+++ b/gfx/wr/webrender/src/border.rs
@@ -0,0 +1,1474 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use api::{BorderRadius, BorderSide, BorderStyle, ColorF, ColorU};
+use api::{NormalBorder as ApiNormalBorder, RepeatMode};
+use api::units::*;
+use crate::clip::ClipNodeId;
+use crate::ellipse::Ellipse;
+use euclid::vec2;
+use crate::scene_building::SceneBuilder;
+use crate::spatial_tree::SpatialNodeIndex;
+use crate::gpu_types::{BorderInstance, BorderSegment, BrushFlags};
+use crate::prim_store::{BorderSegmentInfo, BrushSegment, NinePatchDescriptor};
+use crate::prim_store::borders::{NormalBorderPrim, NormalBorderData};
+use crate::util::{lerp, RectHelpers};
+use crate::internal_types::LayoutPrimitiveInfo;
+use crate::segment::EdgeAaSegmentMask;
+
+// Using 2048 as the maximum radius in device space before which we
+// start stretching is up for debate.
+// the value must be chosen so that the corners will not use an
+// unreasonable amount of memory but should allow crisp corners in the
+// common cases.
+
+/// Maximum resolution in device pixels at which borders are rasterized.
+pub const MAX_BORDER_RESOLUTION: u32 = 2048;
+/// Maximum number of dots or dashes per segment to avoid freezing and filling up
+/// memory with unreasonable inputs. It would be better to address this by not building
+/// a list of per-dot information in the first place.
+pub const MAX_DASH_COUNT: u32 = 2048;
+
+// TODO(gw): Perhaps there is a better way to store
+// the border cache key than duplicating
+// all the border structs with hashable
+// variants...
+
+#[derive(Copy, Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BorderRadiusAu {
+ pub top_left: LayoutSizeAu,
+ pub top_right: LayoutSizeAu,
+ pub bottom_left: LayoutSizeAu,
+ pub bottom_right: LayoutSizeAu,
+}
+
+impl From<BorderRadius> for BorderRadiusAu {
+ fn from(radius: BorderRadius) -> BorderRadiusAu {
+ BorderRadiusAu {
+ top_left: radius.top_left.to_au(),
+ top_right: radius.top_right.to_au(),
+ bottom_right: radius.bottom_right.to_au(),
+ bottom_left: radius.bottom_left.to_au(),
+ }
+ }
+}
+
+impl From<BorderRadiusAu> for BorderRadius {
+ fn from(radius: BorderRadiusAu) -> Self {
+ BorderRadius {
+ top_left: LayoutSize::from_au(radius.top_left),
+ top_right: LayoutSize::from_au(radius.top_right),
+ bottom_right: LayoutSize::from_au(radius.bottom_right),
+ bottom_left: LayoutSize::from_au(radius.bottom_left),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BorderSideAu {
+ pub color: ColorU,
+ pub style: BorderStyle,
+}
+
+impl From<BorderSide> for BorderSideAu {
+ fn from(side: BorderSide) -> Self {
+ BorderSideAu {
+ color: side.color.into(),
+ style: side.style,
+ }
+ }
+}
+
+impl From<BorderSideAu> for BorderSide {
+ fn from(side: BorderSideAu) -> Self {
+ BorderSide {
+ color: side.color.into(),
+ style: side.style,
+ }
+ }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, Hash, Eq, MallocSizeOf, PartialEq)]
+pub struct NormalBorderAu {
+ pub left: BorderSideAu,
+ pub right: BorderSideAu,
+ pub top: BorderSideAu,
+ pub bottom: BorderSideAu,
+ pub radius: BorderRadiusAu,
+ /// Whether to apply anti-aliasing on the border corners.
+ ///
+ /// Note that for this to be `false` and work, this requires the borders to
+ /// be solid, and no border-radius.
+ pub do_aa: bool,
+}
+
+impl NormalBorderAu {
+ // Construct a border based upon self with color
+ pub fn with_color(&self, color: ColorU) -> Self {
+ let mut b = self.clone();
+ b.left.color = color;
+ b.right.color = color;
+ b.top.color = color;
+ b.bottom.color = color;
+ b
+ }
+}
+
+impl From<ApiNormalBorder> for NormalBorderAu {
+ fn from(border: ApiNormalBorder) -> Self {
+ NormalBorderAu {
+ left: border.left.into(),
+ right: border.right.into(),
+ top: border.top.into(),
+ bottom: border.bottom.into(),
+ radius: border.radius.into(),
+ do_aa: border.do_aa,
+ }
+ }
+}
+
+impl From<NormalBorderAu> for ApiNormalBorder {
+ fn from(border: NormalBorderAu) -> Self {
+ ApiNormalBorder {
+ left: border.left.into(),
+ right: border.right.into(),
+ top: border.top.into(),
+ bottom: border.bottom.into(),
+ radius: border.radius.into(),
+ do_aa: border.do_aa,
+ }
+ }
+}
+
+/// Cache key that uniquely identifies a border
+/// segment in the render task cache.
+#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BorderSegmentCacheKey {
+ pub size: LayoutSizeAu,
+ pub radius: LayoutSizeAu,
+ pub side0: BorderSideAu,
+ pub side1: BorderSideAu,
+ pub segment: BorderSegment,
+ pub do_aa: bool,
+ pub h_adjacent_corner_outer: LayoutPointAu,
+ pub h_adjacent_corner_radius: LayoutSizeAu,
+ pub v_adjacent_corner_outer: LayoutPointAu,
+ pub v_adjacent_corner_radius: LayoutSizeAu,
+}
+
+pub fn ensure_no_corner_overlap(
+ radius: &mut BorderRadius,
+ size: LayoutSize,
+) {
+ let mut ratio = 1.0;
+ let top_left_radius = &mut radius.top_left;
+ let top_right_radius = &mut radius.top_right;
+ let bottom_right_radius = &mut radius.bottom_right;
+ let bottom_left_radius = &mut radius.bottom_left;
+
+ let sum = top_left_radius.width + top_right_radius.width;
+ if size.width < sum {
+ ratio = f32::min(ratio, size.width / sum);
+ }
+
+ let sum = bottom_left_radius.width + bottom_right_radius.width;
+ if size.width < sum {
+ ratio = f32::min(ratio, size.width / sum);
+ }
+
+ let sum = top_left_radius.height + bottom_left_radius.height;
+ if size.height < sum {
+ ratio = f32::min(ratio, size.height / sum);
+ }
+
+ let sum = top_right_radius.height + bottom_right_radius.height;
+ if size.height < sum {
+ ratio = f32::min(ratio, size.height / sum);
+ }
+
+ if ratio < 1. {
+ top_left_radius.width *= ratio;
+ top_left_radius.height *= ratio;
+
+ top_right_radius.width *= ratio;
+ top_right_radius.height *= ratio;
+
+ bottom_left_radius.width *= ratio;
+ bottom_left_radius.height *= ratio;
+
+ bottom_right_radius.width *= ratio;
+ bottom_right_radius.height *= ratio;
+ }
+}
+
+impl<'a> SceneBuilder<'a> {
+ pub fn add_normal_border(
+ &mut self,
+ info: &LayoutPrimitiveInfo,
+ border: &ApiNormalBorder,
+ widths: LayoutSideOffsets,
+ spatial_node_index: SpatialNodeIndex,
+ clip_node_id: ClipNodeId,
+ ) {
+ let mut border = *border;
+ ensure_no_corner_overlap(&mut border.radius, info.rect.size());
+
+ self.add_primitive(
+ spatial_node_index,
+ clip_node_id,
+ info,
+ Vec::new(),
+ NormalBorderPrim {
+ border: border.into(),
+ widths: widths.to_au(),
+ },
+ );
+ }
+}
+
+pub trait BorderSideHelpers {
+ fn border_color(&self, is_inner_border: bool) -> ColorF;
+}
+
+impl BorderSideHelpers for BorderSide {
+ fn border_color(&self, is_inner_border: bool) -> ColorF {
+ let lighter = match self.style {
+ BorderStyle::Inset => is_inner_border,
+ BorderStyle::Outset => !is_inner_border,
+ _ => return self.color,
+ };
+
+ // The modulate colors below are not part of the specification. They are
+ // derived from the Gecko source code and experimentation, and used to
+ // modulate the colors in order to generate colors for the inset/outset
+ // and groove/ridge border styles.
+ //
+ // NOTE(emilio): Gecko at least takes the background color into
+ // account, should we do the same? Looks a bit annoying for this.
+ //
+ // NOTE(emilio): If you change this algorithm, do the same change on
+ // get_colors_for_side in cs_border_segment.glsl.
+ if self.color.r != 0.0 || self.color.g != 0.0 || self.color.b != 0.0 {
+ let scale = if lighter { 1.0 } else { 2.0 / 3.0 };
+ return self.color.scale_rgb(scale)
+ }
+
+ let black = if lighter { 0.7 } else { 0.3 };
+ ColorF::new(black, black, black, self.color.a)
+ }
+}
+
+/// The kind of border corner clip.
+#[repr(C)]
+#[derive(Copy, Debug, Clone, PartialEq)]
+pub enum BorderClipKind {
+ DashCorner = 1,
+ DashEdge = 2,
+ Dot = 3,
+}
+
+fn compute_outer_and_clip_sign(
+ corner_segment: BorderSegment,
+ radius: DeviceSize,
+) -> (DevicePoint, DeviceVector2D) {
+ let outer_scale = match corner_segment {
+ BorderSegment::TopLeft => DeviceVector2D::new(0.0, 0.0),
+ BorderSegment::TopRight => DeviceVector2D::new(1.0, 0.0),
+ BorderSegment::BottomRight => DeviceVector2D::new(1.0, 1.0),
+ BorderSegment::BottomLeft => DeviceVector2D::new(0.0, 1.0),
+ _ => panic!("bug: expected a corner segment"),
+ };
+ let outer = DevicePoint::new(
+ outer_scale.x * radius.width,
+ outer_scale.y * radius.height,
+ );
+
+ let clip_sign = DeviceVector2D::new(
+ 1.0 - 2.0 * outer_scale.x,
+ 1.0 - 2.0 * outer_scale.y,
+ );
+
+ (outer, clip_sign)
+}
+
+fn write_dashed_corner_instances(
+ corner_radius: DeviceSize,
+ widths: DeviceSize,
+ segment: BorderSegment,
+ base_instance: &BorderInstance,
+ instances: &mut Vec<BorderInstance>,
+) -> Result<(), ()> {
+ let ellipse = Ellipse::new(corner_radius);
+
+ let average_border_width = 0.5 * (widths.width + widths.height);
+
+ let (_half_dash, num_half_dashes) =
+ compute_half_dash(average_border_width, ellipse.total_arc_length);
+
+ if num_half_dashes == 0 {
+ return Err(());
+ }
+
+ let num_half_dashes = num_half_dashes.min(MAX_DASH_COUNT);
+
+ let (outer, clip_sign) = compute_outer_and_clip_sign(segment, corner_radius);
+
+ let instance_count = num_half_dashes / 4 + 1;
+ instances.reserve(instance_count as usize);
+
+ let half_dash_arc_length =
+ ellipse.total_arc_length / num_half_dashes as f32;
+ let dash_length = 2. * half_dash_arc_length;
+
+ let mut current_length = 0.;
+ for i in 0..instance_count {
+ let arc_length0 = current_length;
+ current_length += if i == 0 {
+ half_dash_arc_length
+ } else {
+ dash_length
+ };
+
+ let arc_length1 = current_length;
+ current_length += dash_length;
+
+ let alpha = ellipse.find_angle_for_arc_length(arc_length0);
+ let beta = ellipse.find_angle_for_arc_length(arc_length1);
+
+ let (point0, tangent0) = ellipse.get_point_and_tangent(alpha);
+ let (point1, tangent1) = ellipse.get_point_and_tangent(beta);
+
+ let point0 = DevicePoint::new(
+ outer.x + clip_sign.x * (corner_radius.width - point0.x),
+ outer.y + clip_sign.y * (corner_radius.height - point0.y),
+ );
+
+ let tangent0 = DeviceVector2D::new(
+ -tangent0.x * clip_sign.x,
+ -tangent0.y * clip_sign.y,
+ );
+
+ let point1 = DevicePoint::new(
+ outer.x + clip_sign.x * (corner_radius.width - point1.x),
+ outer.y + clip_sign.y * (corner_radius.height - point1.y),
+ );
+
+ let tangent1 = DeviceVector2D::new(
+ -tangent1.x * clip_sign.x,
+ -tangent1.y * clip_sign.y,
+ );
+
+ instances.push(BorderInstance {
+ flags: base_instance.flags | ((BorderClipKind::DashCorner as i32) << 24),
+ clip_params: [
+ point0.x,
+ point0.y,
+ tangent0.x,
+ tangent0.y,
+ point1.x,
+ point1.y,
+ tangent1.x,
+ tangent1.y,
+ ],
+ .. *base_instance
+ });
+ }
+
+ Ok(())
+}
+
+fn write_dotted_corner_instances(
+ corner_radius: DeviceSize,
+ widths: DeviceSize,
+ segment: BorderSegment,
+ base_instance: &BorderInstance,
+ instances: &mut Vec<BorderInstance>,
+) -> Result<(), ()> {
+ let mut corner_radius = corner_radius;
+ if corner_radius.width < (widths.width / 2.0) {
+ corner_radius.width = 0.0;
+ }
+ if corner_radius.height < (widths.height / 2.0) {
+ corner_radius.height = 0.0;
+ }
+
+ let (ellipse, max_dot_count) =
+ if corner_radius.width == 0. && corner_radius.height == 0. {
+ (Ellipse::new(corner_radius), 1)
+ } else {
+ // The centers of dots follow an ellipse along the middle of the
+ // border radius.
+ let inner_radius = (corner_radius - widths * 0.5).abs();
+ let ellipse = Ellipse::new(inner_radius);
+
+ // Allocate a "worst case" number of dot clips. This can be
+ // calculated by taking the minimum edge radius, since that
+ // will result in the maximum number of dots along the path.
+ let min_diameter = widths.width.min(widths.height);
+
+ // Get the number of circles (assuming spacing of one diameter
+ // between dots).
+ let max_dot_count = 0.5 * ellipse.total_arc_length / min_diameter;
+
+ // Add space for one extra dot since they are centered at the
+ // start of the arc.
+ (ellipse, max_dot_count.ceil() as usize)
+ };
+
+ if max_dot_count == 0 {
+ return Err(());
+ }
+
+ if max_dot_count == 1 {
+ let dot_diameter = lerp(widths.width, widths.height, 0.5);
+ instances.push(BorderInstance {
+ flags: base_instance.flags | ((BorderClipKind::Dot as i32) << 24),
+ clip_params: [
+ widths.width / 2.0, widths.height / 2.0, 0.5 * dot_diameter, 0.,
+ 0., 0., 0., 0.,
+ ],
+ .. *base_instance
+ });
+ return Ok(());
+ }
+
+ let max_dot_count = max_dot_count.min(MAX_DASH_COUNT as usize);
+
+ // FIXME(emilio): Should probably use SmallVec.
+ let mut forward_dots = Vec::with_capacity(max_dot_count / 2 + 1);
+ let mut back_dots = Vec::with_capacity(max_dot_count / 2 + 1);
+ let mut leftover_arc_length = 0.0;
+
+ // Alternate between adding dots at the start and end of the
+ // ellipse arc. This ensures that we always end up with an exact
+ // half dot at each end of the arc, to match up with the edges.
+ forward_dots.push(DotInfo::new(widths.width, widths.width));
+ back_dots.push(DotInfo::new(
+ ellipse.total_arc_length - widths.height,
+ widths.height,
+ ));
+
+ let (outer, clip_sign) = compute_outer_and_clip_sign(segment, corner_radius);
+ for dot_index in 0 .. max_dot_count {
+ let prev_forward_pos = *forward_dots.last().unwrap();
+ let prev_back_pos = *back_dots.last().unwrap();
+
+ // Select which end of the arc to place a dot from.
+ // This just alternates between the start and end of
+ // the arc, which ensures that there is always an
+ // exact half-dot at each end of the ellipse.
+ let going_forward = dot_index & 1 == 0;
+
+ let (next_dot_pos, leftover) = if going_forward {
+ let next_dot_pos =
+ prev_forward_pos.arc_pos + 2.0 * prev_forward_pos.diameter;
+ (next_dot_pos, prev_back_pos.arc_pos - next_dot_pos)
+ } else {
+ let next_dot_pos = prev_back_pos.arc_pos - 2.0 * prev_back_pos.diameter;
+ (next_dot_pos, next_dot_pos - prev_forward_pos.arc_pos)
+ };
+
+ // Use a lerp between each edge's dot
+ // diameter, based on the linear distance
+ // along the arc to get the diameter of the
+ // dot at this arc position.
+ let t = next_dot_pos / ellipse.total_arc_length;
+ let dot_diameter = lerp(widths.width, widths.height, t);
+
+ // If we can't fit a dot, bail out.
+ if leftover < dot_diameter {
+ leftover_arc_length = leftover;
+ break;
+ }
+
+ // We can place a dot!
+ let dot = DotInfo::new(next_dot_pos, dot_diameter);
+ if going_forward {
+ forward_dots.push(dot);
+ } else {
+ back_dots.push(dot);
+ }
+ }
+
+ // Now step through the dots, and distribute any extra
+ // leftover space on the arc between them evenly. Once
+ // the final arc position is determined, generate the correct
+ // arc positions and angles that get passed to the clip shader.
+ let number_of_dots = forward_dots.len() + back_dots.len();
+ let extra_space_per_dot = leftover_arc_length / (number_of_dots - 1) as f32;
+
+ let create_dot_data = |arc_length: f32, dot_radius: f32| -> [f32; 8] {
+ // Represents the GPU data for drawing a single dot to a clip mask. The order
+ // these are specified must stay in sync with the way this data is read in the
+ // dot clip shader.
+ let theta = ellipse.find_angle_for_arc_length(arc_length);
+ let (center, _) = ellipse.get_point_and_tangent(theta);
+
+ let center = DevicePoint::new(
+ outer.x + clip_sign.x * (corner_radius.width - center.x),
+ outer.y + clip_sign.y * (corner_radius.height - center.y),
+ );
+
+ [center.x, center.y, dot_radius, 0.0, 0.0, 0.0, 0.0, 0.0]
+ };
+
+ instances.reserve(number_of_dots);
+ for (i, dot) in forward_dots.iter().enumerate() {
+ let extra_dist = i as f32 * extra_space_per_dot;
+ instances.push(BorderInstance {
+ flags: base_instance.flags | ((BorderClipKind::Dot as i32) << 24),
+ clip_params: create_dot_data(dot.arc_pos + extra_dist, 0.5 * dot.diameter),
+ .. *base_instance
+ });
+ }
+
+ for (i, dot) in back_dots.iter().enumerate() {
+ let extra_dist = i as f32 * extra_space_per_dot;
+ instances.push(BorderInstance {
+ flags: base_instance.flags | ((BorderClipKind::Dot as i32) << 24),
+ clip_params: create_dot_data(dot.arc_pos - extra_dist, 0.5 * dot.diameter),
+ .. *base_instance
+ });
+ }
+
+ Ok(())
+}
+
+#[derive(Copy, Clone, Debug)]
+struct DotInfo {
+ arc_pos: f32,
+ diameter: f32,
+}
+
+impl DotInfo {
+ fn new(arc_pos: f32, diameter: f32) -> DotInfo {
+ DotInfo { arc_pos, diameter }
+ }
+}
+
+/// Information needed to place and draw a border edge.
+#[derive(Debug)]
+struct EdgeInfo {
+ /// Offset in local space to place the edge from origin.
+ local_offset: f32,
+ /// Size of the edge in local space.
+ local_size: f32,
+ /// Local stretch size for this edge (repeat past this).
+ stretch_size: f32,
+}
+
+impl EdgeInfo {
+ fn new(
+ local_offset: f32,
+ local_size: f32,
+ stretch_size: f32,
+ ) -> Self {
+ Self {
+ local_offset,
+ local_size,
+ stretch_size,
+ }
+ }
+}
+
+// Given a side width and the available space, compute the half-dash (half of
+// the 'on' segment) and the count of them for a given segment.
+fn compute_half_dash(side_width: f32, total_size: f32) -> (f32, u32) {
+ let half_dash = side_width * 1.5;
+ // 16k dashes should be enough for anyone
+ let num_half_dashes = (total_size / half_dash).ceil().min(16.0 * 1024.0) as u32;
+
+ if num_half_dashes == 0 {
+ return (0., 0);
+ }
+
+ // TODO(emilio): Gecko has some other heuristics here to start with a full
+ // dash when the border side is zero, for example. We might consider those
+ // in the future.
+ let num_half_dashes = if num_half_dashes % 4 != 0 {
+ num_half_dashes + 4 - num_half_dashes % 4
+ } else {
+ num_half_dashes
+ };
+
+ let half_dash = total_size / num_half_dashes as f32;
+ (half_dash, num_half_dashes)
+}
+
+
+// Get the needed size in device pixels for an edge,
+// based on the border style of that edge. This is used
+// to determine how big the render task should be.
+fn get_edge_info(
+ style: BorderStyle,
+ side_width: f32,
+ avail_size: f32,
+) -> EdgeInfo {
+ // To avoid division by zero below.
+ if side_width <= 0.0 || avail_size <= 0.0 {
+ return EdgeInfo::new(0.0, 0.0, 0.0);
+ }
+
+ match style {
+ BorderStyle::Dashed => {
+ // Basically, two times the dash size.
+ let (half_dash, _num_half_dashes) =
+ compute_half_dash(side_width, avail_size);
+ let stretch_size = 2.0 * 2.0 * half_dash;
+ EdgeInfo::new(0., avail_size, stretch_size)
+ }
+ BorderStyle::Dotted => {
+ let dot_and_space_size = 2.0 * side_width;
+ if avail_size < dot_and_space_size * 0.75 {
+ return EdgeInfo::new(0.0, 0.0, 0.0);
+ }
+ let approx_dot_count = avail_size / dot_and_space_size;
+ let dot_count = approx_dot_count.floor().max(1.0);
+ let used_size = dot_count * dot_and_space_size;
+ let extra_space = avail_size - used_size;
+ let stretch_size = dot_and_space_size;
+ let offset = (extra_space * 0.5).round();
+ EdgeInfo::new(offset, used_size, stretch_size)
+ }
+ _ => {
+ EdgeInfo::new(0.0, avail_size, 8.0)
+ }
+ }
+}
+
+/// Create the set of border segments and render task
+/// cache keys for a given CSS border.
+pub fn create_border_segments(
+ size: LayoutSize,
+ border: &ApiNormalBorder,
+ widths: &LayoutSideOffsets,
+ border_segments: &mut Vec<BorderSegmentInfo>,
+ brush_segments: &mut Vec<BrushSegment>,
+) {
+ let rect = LayoutRect::from_size(size);
+
+ let overlap = LayoutSize::new(
+ (widths.left + widths.right - size.width).max(0.0),
+ (widths.top + widths.bottom - size.height).max(0.0),
+ );
+ let non_overlapping_widths = LayoutSideOffsets::new(
+ widths.top - overlap.height / 2.0,
+ widths.right - overlap.width / 2.0,
+ widths.bottom - overlap.height / 2.0,
+ widths.left - overlap.width / 2.0,
+ );
+
+ let local_size_tl = LayoutSize::new(
+ border.radius.top_left.width.max(widths.left),
+ border.radius.top_left.height.max(widths.top),
+ );
+ let local_size_tr = LayoutSize::new(
+ border.radius.top_right.width.max(widths.right),
+ border.radius.top_right.height.max(widths.top),
+ );
+ let local_size_br = LayoutSize::new(
+ border.radius.bottom_right.width.max(widths.right),
+ border.radius.bottom_right.height.max(widths.bottom),
+ );
+ let local_size_bl = LayoutSize::new(
+ border.radius.bottom_left.width.max(widths.left),
+ border.radius.bottom_left.height.max(widths.bottom),
+ );
+
+ let top_edge_info = get_edge_info(
+ border.top.style,
+ widths.top,
+ rect.width() - local_size_tl.width - local_size_tr.width,
+ );
+ let bottom_edge_info = get_edge_info(
+ border.bottom.style,
+ widths.bottom,
+ rect.width() - local_size_bl.width - local_size_br.width,
+ );
+
+ let left_edge_info = get_edge_info(
+ border.left.style,
+ widths.left,
+ rect.height() - local_size_tl.height - local_size_bl.height,
+ );
+ let right_edge_info = get_edge_info(
+ border.right.style,
+ widths.right,
+ rect.height() - local_size_tr.height - local_size_br.height,
+ );
+
+ add_edge_segment(
+ LayoutRect::from_floats(
+ rect.min.x,
+ rect.min.y + local_size_tl.height + left_edge_info.local_offset,
+ rect.min.x + non_overlapping_widths.left,
+ rect.min.y + local_size_tl.height + left_edge_info.local_offset + left_edge_info.local_size,
+ ),
+ &left_edge_info,
+ border.left,
+ non_overlapping_widths.left,
+ BorderSegment::Left,
+ EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT,
+ brush_segments,
+ border_segments,
+ border.do_aa,
+ );
+ add_edge_segment(
+ LayoutRect::from_floats(
+ rect.min.x + local_size_tl.width + top_edge_info.local_offset,
+ rect.min.y,
+ rect.min.x + local_size_tl.width + top_edge_info.local_offset + top_edge_info.local_size,
+ rect.min.y + non_overlapping_widths.top,
+ ),
+ &top_edge_info,
+ border.top,
+ non_overlapping_widths.top,
+ BorderSegment::Top,
+ EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM,
+ brush_segments,
+ border_segments,
+ border.do_aa,
+ );
+ add_edge_segment(
+ LayoutRect::from_floats(
+ rect.min.x + rect.width() - non_overlapping_widths.right,
+ rect.min.y + local_size_tr.height + right_edge_info.local_offset,
+ rect.min.x + rect.width(),
+ rect.min.y + local_size_tr.height + right_edge_info.local_offset + right_edge_info.local_size,
+ ),
+ &right_edge_info,
+ border.right,
+ non_overlapping_widths.right,
+ BorderSegment::Right,
+ EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT,
+ brush_segments,
+ border_segments,
+ border.do_aa,
+ );
+ add_edge_segment(
+ LayoutRect::from_floats(
+ rect.min.x + local_size_bl.width + bottom_edge_info.local_offset,
+ rect.min.y + rect.height() - non_overlapping_widths.bottom,
+ rect.min.x + local_size_bl.width + bottom_edge_info.local_offset + bottom_edge_info.local_size,
+ rect.min.y + rect.height(),
+ ),
+ &bottom_edge_info,
+ border.bottom,
+ non_overlapping_widths.bottom,
+ BorderSegment::Bottom,
+ EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP,
+ brush_segments,
+ border_segments,
+ border.do_aa,
+ );
+
+ add_corner_segment(
+ LayoutRect::from_floats(
+ rect.min.x,
+ rect.min.y,
+ rect.min.x + local_size_tl.width,
+ rect.min.y + local_size_tl.height,
+ ),
+ LayoutRect::from_floats(
+ rect.min.x,
+ rect.min.y,
+ rect.max.x - non_overlapping_widths.right,
+ rect.max.y - non_overlapping_widths.bottom
+ ),
+ border.left,
+ border.top,
+ LayoutSize::new(widths.left, widths.top),
+ border.radius.top_left,
+ BorderSegment::TopLeft,
+ EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT,
+ rect.top_right(),
+ border.radius.top_right,
+ rect.bottom_left(),
+ border.radius.bottom_left,
+ brush_segments,
+ border_segments,
+ border.do_aa,
+ );
+ add_corner_segment(
+ LayoutRect::from_floats(
+ rect.min.x + rect.width() - local_size_tr.width,
+ rect.min.y,
+ rect.min.x + rect.width(),
+ rect.min.y + local_size_tr.height,
+ ),
+ LayoutRect::from_floats(
+ rect.min.x + non_overlapping_widths.left,
+ rect.min.y,
+ rect.max.x,
+ rect.max.y - non_overlapping_widths.bottom,
+ ),
+ border.top,
+ border.right,
+ LayoutSize::new(widths.right, widths.top),
+ border.radius.top_right,
+ BorderSegment::TopRight,
+ EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT,
+ rect.min,
+ border.radius.top_left,
+ rect.max,
+ border.radius.bottom_right,
+ brush_segments,
+ border_segments,
+ border.do_aa,
+ );
+ add_corner_segment(
+ LayoutRect::from_floats(
+ rect.min.x + rect.width() - local_size_br.width,
+ rect.min.y + rect.height() - local_size_br.height,
+ rect.min.x + rect.width(),
+ rect.min.y + rect.height(),
+ ),
+ LayoutRect::from_floats(
+ rect.min.x + non_overlapping_widths.left,
+ rect.min.y + non_overlapping_widths.top,
+ rect.max.x,
+ rect.max.y,
+ ),
+ border.right,
+ border.bottom,
+ LayoutSize::new(widths.right, widths.bottom),
+ border.radius.bottom_right,
+ BorderSegment::BottomRight,
+ EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT,
+ rect.bottom_left(),
+ border.radius.bottom_left,
+ rect.top_right(),
+ border.radius.top_right,
+ brush_segments,
+ border_segments,
+ border.do_aa,
+ );
+ add_corner_segment(
+ LayoutRect::from_floats(
+ rect.min.x,
+ rect.min.y + rect.height() - local_size_bl.height,
+ rect.min.x + local_size_bl.width,
+ rect.min.y + rect.height(),
+ ),
+ LayoutRect::from_floats(
+ rect.min.x,
+ rect.min.y + non_overlapping_widths.top,
+ rect.max.x - non_overlapping_widths.right,
+ rect.max.y,
+ ),
+ border.bottom,
+ border.left,
+ LayoutSize::new(widths.left, widths.bottom),
+ border.radius.bottom_left,
+ BorderSegment::BottomLeft,
+ EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::LEFT,
+ rect.max,
+ border.radius.bottom_right,
+ rect.min,
+ border.radius.top_left,
+ brush_segments,
+ border_segments,
+ border.do_aa,
+ );
+}
+
+/// Computes the maximum scale that we allow for this set of border parameters.
+/// capping the scale will result in rendering very large corners at a lower
+/// resolution and stretching them, so they will have the right shape, but
+/// blurrier.
+pub fn get_max_scale_for_border(
+ border_data: &NormalBorderData,
+) -> LayoutToDeviceScale {
+ let mut r = 1.0;
+ for segment in &border_data.border_segments {
+ let size = segment.local_task_size;
+ r = size.width.max(size.height.max(r));
+ }
+
+ LayoutToDeviceScale::new(MAX_BORDER_RESOLUTION as f32 / r)
+}
+
+fn add_segment(
+ task_rect: DeviceRect,
+ style0: BorderStyle,
+ style1: BorderStyle,
+ color0: ColorF,
+ color1: ColorF,
+ segment: BorderSegment,
+ instances: &mut Vec<BorderInstance>,
+ widths: DeviceSize,
+ radius: DeviceSize,
+ do_aa: bool,
+ h_adjacent_corner_outer: DevicePoint,
+ h_adjacent_corner_radius: DeviceSize,
+ v_adjacent_corner_outer: DevicePoint,
+ v_adjacent_corner_radius: DeviceSize,
+) {
+ let base_flags = (segment as i32) |
+ ((style0 as i32) << 8) |
+ ((style1 as i32) << 16) |
+ ((do_aa as i32) << 28);
+
+ let base_instance = BorderInstance {
+ task_origin: DevicePoint::zero(),
+ local_rect: task_rect,
+ flags: base_flags,
+ color0: color0.premultiplied(),
+ color1: color1.premultiplied(),
+ widths,
+ radius,
+ clip_params: [0.0; 8],
+ };
+
+ match segment {
+ BorderSegment::TopLeft |
+ BorderSegment::TopRight |
+ BorderSegment::BottomLeft |
+ BorderSegment::BottomRight => {
+ // TODO(gw): Similarly to the old border code, we don't correctly handle a a corner
+ // that is dashed on one edge, and dotted on another. We can handle this
+ // in the future by submitting two instances, each one with one side
+ // color set to have an alpha of 0.
+ if (style0 == BorderStyle::Dotted && style1 == BorderStyle::Dashed) ||
+ (style0 == BorderStyle::Dashed && style0 == BorderStyle::Dotted) {
+ warn!("TODO: Handle a corner with dotted / dashed transition.");
+ }
+
+ let dashed_or_dotted_corner = match style0 {
+ BorderStyle::Dashed => {
+ write_dashed_corner_instances(
+ radius,
+ widths,
+ segment,
+ &base_instance,
+ instances,
+ )
+ }
+ BorderStyle::Dotted => {
+ write_dotted_corner_instances(
+ radius,
+ widths,
+ segment,
+ &base_instance,
+ instances,
+ )
+ }
+ _ => Err(()),
+ };
+
+ if dashed_or_dotted_corner.is_err() {
+ let clip_params = [
+ h_adjacent_corner_outer.x,
+ h_adjacent_corner_outer.y,
+ h_adjacent_corner_radius.width,
+ h_adjacent_corner_radius.height,
+ v_adjacent_corner_outer.x,
+ v_adjacent_corner_outer.y,
+ v_adjacent_corner_radius.width,
+ v_adjacent_corner_radius.height,
+ ];
+
+ instances.push(BorderInstance {
+ clip_params,
+ ..base_instance
+ });
+ }
+ }
+ BorderSegment::Top |
+ BorderSegment::Bottom |
+ BorderSegment::Right |
+ BorderSegment::Left => {
+ let is_vertical = segment == BorderSegment::Left ||
+ segment == BorderSegment::Right;
+
+ match style0 {
+ BorderStyle::Dashed => {
+ let (x, y) = if is_vertical {
+ let half_dash_size = task_rect.height() * 0.25;
+ (0., half_dash_size)
+ } else {
+ let half_dash_size = task_rect.width() * 0.25;
+ (half_dash_size, 0.)
+ };
+
+ instances.push(BorderInstance {
+ flags: base_flags | ((BorderClipKind::DashEdge as i32) << 24),
+ clip_params: [
+ x, y, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+ ],
+ ..base_instance
+ });
+ }
+ BorderStyle::Dotted => {
+ let (x, y, r) = if is_vertical {
+ (widths.width * 0.5,
+ widths.width,
+ widths.width * 0.5)
+ } else {
+ (widths.height,
+ widths.height * 0.5,
+ widths.height * 0.5)
+ };
+
+ instances.push(BorderInstance {
+ flags: base_flags | ((BorderClipKind::Dot as i32) << 24),
+ clip_params: [
+ x, y, r, 0.0, 0.0, 0.0, 0.0, 0.0,
+ ],
+ ..base_instance
+ });
+ }
+ _ => {
+ instances.push(base_instance);
+ }
+ }
+ }
+ }
+}
+
+/// Add a corner segment (if valid) to the list of
+/// border segments for this primitive.
+fn add_corner_segment(
+ image_rect: LayoutRect,
+ non_overlapping_rect: LayoutRect,
+ side0: BorderSide,
+ side1: BorderSide,
+ widths: LayoutSize,
+ radius: LayoutSize,
+ segment: BorderSegment,
+ edge_flags: EdgeAaSegmentMask,
+ h_adjacent_corner_outer: LayoutPoint,
+ h_adjacent_corner_radius: LayoutSize,
+ v_adjacent_corner_outer: LayoutPoint,
+ v_adjacent_corner_radius: LayoutSize,
+ brush_segments: &mut Vec<BrushSegment>,
+ border_segments: &mut Vec<BorderSegmentInfo>,
+ do_aa: bool,
+) {
+ if side0.color.a <= 0.0 && side1.color.a <= 0.0 {
+ return;
+ }
+
+ if widths.width <= 0.0 && widths.height <= 0.0 {
+ return;
+ }
+
+ if side0.style.is_hidden() && side1.style.is_hidden() {
+ return;
+ }
+
+ let segment_rect = match image_rect.intersection(&non_overlapping_rect) {
+ Some(rect) => rect,
+ None => {
+ return;
+ }
+ };
+
+ let texture_rect = segment_rect
+ .translate(-image_rect.min.to_vector())
+ .scale(1.0 / image_rect.width(), 1.0 / image_rect.height());
+
+ brush_segments.push(
+ BrushSegment::new(
+ segment_rect,
+ /* may_need_clip_mask = */ true,
+ edge_flags,
+ [texture_rect.min.x, texture_rect.min.y, texture_rect.max.x, texture_rect.max.y],
+ BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_TEXEL_RECT,
+ )
+ );
+
+ // If the radii of the adjacent corners do not overlap with this segment,
+ // then set the outer position to this segment's corner and the radii to zero.
+ // That way the cache key is unaffected by non-overlapping corners, resulting
+ // in fewer misses.
+ let (h_corner_outer, h_corner_radius) = match segment {
+ BorderSegment::TopLeft => {
+ if h_adjacent_corner_outer.x - h_adjacent_corner_radius.width < image_rect.max.x {
+ (h_adjacent_corner_outer, h_adjacent_corner_radius)
+ } else {
+ (LayoutPoint::new(image_rect.max.x, image_rect.min.y), LayoutSize::zero())
+ }
+ }
+ BorderSegment::TopRight => {
+ if h_adjacent_corner_outer.x + h_adjacent_corner_radius.width > image_rect.min.x {
+ (h_adjacent_corner_outer, h_adjacent_corner_radius)
+ } else {
+ (LayoutPoint::new(image_rect.min.x, image_rect.min.y), LayoutSize::zero())
+ }
+ }
+ BorderSegment::BottomRight => {
+ if h_adjacent_corner_outer.x + h_adjacent_corner_radius.width > image_rect.min.x {
+ (h_adjacent_corner_outer, h_adjacent_corner_radius)
+ } else {
+ (LayoutPoint::new(image_rect.min.x, image_rect.max.y), LayoutSize::zero())
+ }
+ }
+ BorderSegment::BottomLeft => {
+ if h_adjacent_corner_outer.x - h_adjacent_corner_radius.width < image_rect.max.x {
+ (h_adjacent_corner_outer, h_adjacent_corner_radius)
+ } else {
+ (image_rect.max, LayoutSize::zero())
+ }
+ }
+ _ => unreachable!()
+ };
+
+ let (v_corner_outer, v_corner_radius) = match segment {
+ BorderSegment::TopLeft => {
+ if v_adjacent_corner_outer.y - v_adjacent_corner_radius.height < image_rect.max.y {
+ (v_adjacent_corner_outer, v_adjacent_corner_radius)
+ } else {
+ (LayoutPoint::new(image_rect.min.x, image_rect.max.y), LayoutSize::zero())
+ }
+ }
+ BorderSegment::TopRight => {
+ if v_adjacent_corner_outer.y - v_adjacent_corner_radius.height < image_rect.max.y {
+ (v_adjacent_corner_outer, v_adjacent_corner_radius)
+ } else {
+ (image_rect.max, LayoutSize::zero())
+ }
+ }
+ BorderSegment::BottomRight => {
+ if v_adjacent_corner_outer.y + v_adjacent_corner_radius.height > image_rect.min.y {
+ (v_adjacent_corner_outer, v_adjacent_corner_radius)
+ } else {
+ (LayoutPoint::new(image_rect.max.x, image_rect.min.y), LayoutSize::zero())
+ }
+ }
+ BorderSegment::BottomLeft => {
+ if v_adjacent_corner_outer.y + v_adjacent_corner_radius.height > image_rect.min.y {
+ (v_adjacent_corner_outer, v_adjacent_corner_radius)
+ } else {
+ (LayoutPoint::new(image_rect.min.x, image_rect.min.y), LayoutSize::zero())
+ }
+ }
+ _ => unreachable!()
+ };
+
+ border_segments.push(BorderSegmentInfo {
+ local_task_size: image_rect.size(),
+ cache_key: BorderSegmentCacheKey {
+ do_aa,
+ side0: side0.into(),
+ side1: side1.into(),
+ segment,
+ radius: radius.to_au(),
+ size: widths.to_au(),
+ h_adjacent_corner_outer: (h_corner_outer - image_rect.min).to_point().to_au(),
+ h_adjacent_corner_radius: h_corner_radius.to_au(),
+ v_adjacent_corner_outer: (v_corner_outer - image_rect.min).to_point().to_au(),
+ v_adjacent_corner_radius: v_corner_radius.to_au(),
+ },
+ });
+}
+
+/// Add an edge segment (if valid) to the list of
+/// border segments for this primitive.
+fn add_edge_segment(
+ image_rect: LayoutRect,
+ edge_info: &EdgeInfo,
+ side: BorderSide,
+ width: f32,
+ segment: BorderSegment,
+ edge_flags: EdgeAaSegmentMask,
+ brush_segments: &mut Vec<BrushSegment>,
+ border_segments: &mut Vec<BorderSegmentInfo>,
+ do_aa: bool,
+) {
+ if side.color.a <= 0.0 {
+ return;
+ }
+
+ if side.style.is_hidden() {
+ return;
+ }
+
+ let (size, brush_flags) = match segment {
+ BorderSegment::Left | BorderSegment::Right => {
+ (LayoutSize::new(width, edge_info.stretch_size), BrushFlags::SEGMENT_REPEAT_Y)
+ }
+ BorderSegment::Top | BorderSegment::Bottom => {
+ (LayoutSize::new(edge_info.stretch_size, width), BrushFlags::SEGMENT_REPEAT_X)
+ }
+ _ => {
+ unreachable!();
+ }
+ };
+
+ if image_rect.width() <= 0. || image_rect.height() <= 0. {
+ return;
+ }
+
+ brush_segments.push(
+ BrushSegment::new(
+ image_rect,
+ /* may_need_clip_mask = */ true,
+ edge_flags,
+ [0.0, 0.0, size.width, size.height],
+ BrushFlags::SEGMENT_RELATIVE | brush_flags,
+ )
+ );
+
+ border_segments.push(BorderSegmentInfo {
+ local_task_size: size,
+ cache_key: BorderSegmentCacheKey {
+ do_aa,
+ side0: side.into(),
+ side1: side.into(),
+ radius: LayoutSizeAu::zero(),
+ size: size.to_au(),
+ segment,
+ h_adjacent_corner_outer: LayoutPointAu::zero(),
+ h_adjacent_corner_radius: LayoutSizeAu::zero(),
+ v_adjacent_corner_outer: LayoutPointAu::zero(),
+ v_adjacent_corner_radius: LayoutSizeAu::zero(),
+ },
+ });
+}
+
+/// Build the set of border instances needed to draw a border
+/// segment into the render task cache.
+pub fn build_border_instances(
+ cache_key: &BorderSegmentCacheKey,
+ cache_size: DeviceIntSize,
+ border: &ApiNormalBorder,
+ scale: LayoutToDeviceScale,
+) -> Vec<BorderInstance> {
+ let mut instances = Vec::new();
+
+ let (side0, side1, flip0, flip1) = match cache_key.segment {
+ BorderSegment::Left => (&border.left, &border.left, false, false),
+ BorderSegment::Top => (&border.top, &border.top, false, false),
+ BorderSegment::Right => (&border.right, &border.right, true, true),
+ BorderSegment::Bottom => (&border.bottom, &border.bottom, true, true),
+ BorderSegment::TopLeft => (&border.left, &border.top, false, false),
+ BorderSegment::TopRight => (&border.top, &border.right, false, true),
+ BorderSegment::BottomRight => (&border.right, &border.bottom, true, true),
+ BorderSegment::BottomLeft => (&border.bottom, &border.left, true, false),
+ };
+
+ let style0 = if side0.style.is_hidden() {
+ side1.style
+ } else {
+ side0.style
+ };
+ let style1 = if side1.style.is_hidden() {
+ side0.style
+ } else {
+ side1.style
+ };
+
+ let color0 = side0.border_color(flip0);
+ let color1 = side1.border_color(flip1);
+
+ let widths = (LayoutSize::from_au(cache_key.size) * scale).ceil();
+ let radius = (LayoutSize::from_au(cache_key.radius) * scale).ceil();
+
+ let h_corner_outer = (LayoutPoint::from_au(cache_key.h_adjacent_corner_outer) * scale).round();
+ let h_corner_radius = (LayoutSize::from_au(cache_key.h_adjacent_corner_radius) * scale).ceil();
+ let v_corner_outer = (LayoutPoint::from_au(cache_key.v_adjacent_corner_outer) * scale).round();
+ let v_corner_radius = (LayoutSize::from_au(cache_key.v_adjacent_corner_radius) * scale).ceil();
+
+ add_segment(
+ DeviceRect::from_size(cache_size.to_f32()),
+ style0,
+ style1,
+ color0,
+ color1,
+ cache_key.segment,
+ &mut instances,
+ widths,
+ radius,
+ border.do_aa,
+ h_corner_outer,
+ h_corner_radius,
+ v_corner_outer,
+ v_corner_radius,
+ );
+
+ instances
+}
+
+impl NinePatchDescriptor {
+ pub fn create_segments(
+ &self,
+ size: LayoutSize,
+ ) -> Vec<BrushSegment> {
+ let rect = LayoutRect::from_size(size);
+
+ // Calculate the local texel coords of the slices.
+ let px0 = 0.0;
+ let px1 = self.slice.left as f32 / self.width as f32;
+ let px2 = (self.width as f32 - self.slice.right as f32) / self.width as f32;
+ let px3 = 1.0;
+
+ let py0 = 0.0;
+ let py1 = self.slice.top as f32 / self.height as f32;
+ let py2 = (self.height as f32 - self.slice.bottom as f32) / self.height as f32;
+ let py3 = 1.0;
+
+ let tl_outer = LayoutPoint::new(rect.min.x, rect.min.y);
+ let tl_inner = tl_outer + vec2(self.widths.left, self.widths.top);
+
+ let tr_outer = LayoutPoint::new(rect.min.x + rect.width(), rect.min.y);
+ let tr_inner = tr_outer + vec2(-self.widths.right, self.widths.top);
+
+ let bl_outer = LayoutPoint::new(rect.min.x, rect.min.y + rect.height());
+ let bl_inner = bl_outer + vec2(self.widths.left, -self.widths.bottom);
+
+ let br_outer = rect.max;
+
+ let br_inner = br_outer - vec2(self.widths.right, self.widths.bottom);
+
+ fn add_segment(
+ segments: &mut Vec<BrushSegment>,
+ rect: LayoutRect,
+ uv_rect: TexelRect,
+ repeat_horizontal: RepeatMode,
+ repeat_vertical: RepeatMode,
+ extra_flags: BrushFlags,
+ ) {
+ if uv_rect.uv1.x <= uv_rect.uv0.x || uv_rect.uv1.y <= uv_rect.uv0.y {
+ return;
+ }
+
+ // Use segment relative interpolation for all
+ // instances in this primitive.
+ let mut brush_flags =
+ BrushFlags::SEGMENT_RELATIVE |
+ BrushFlags::SEGMENT_TEXEL_RECT |
+ extra_flags;
+
+ // Enable repeat modes on the segment.
+ if repeat_horizontal == RepeatMode::Repeat {
+ brush_flags |= BrushFlags::SEGMENT_REPEAT_X | BrushFlags::SEGMENT_REPEAT_X_CENTERED;
+ } else if repeat_horizontal == RepeatMode::Round {
+ brush_flags |= BrushFlags::SEGMENT_REPEAT_X | BrushFlags::SEGMENT_REPEAT_X_ROUND;
+ }
+
+ if repeat_vertical == RepeatMode::Repeat {
+ brush_flags |= BrushFlags::SEGMENT_REPEAT_Y | BrushFlags::SEGMENT_REPEAT_Y_CENTERED;
+ } else if repeat_vertical == RepeatMode::Round {
+ brush_flags |= BrushFlags::SEGMENT_REPEAT_Y | BrushFlags::SEGMENT_REPEAT_Y_ROUND;
+ }
+
+ let segment = BrushSegment::new(
+ rect,
+ true,
+ EdgeAaSegmentMask::empty(),
+ [
+ uv_rect.uv0.x,
+ uv_rect.uv0.y,
+ uv_rect.uv1.x,
+ uv_rect.uv1.y,
+ ],
+ brush_flags,
+ );
+
+ segments.push(segment);
+ }
+
+ // Build the list of image segments
+ let mut segments = Vec::new();
+
+ // Top left
+ add_segment(
+ &mut segments,
+ LayoutRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
+ TexelRect::new(px0, py0, px1, py1),
+ RepeatMode::Stretch,
+ RepeatMode::Stretch,
+ BrushFlags::empty(),
+ );
+ // Top right
+ add_segment(
+ &mut segments,
+ LayoutRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y),
+ TexelRect::new(px2, py0, px3, py1),
+ RepeatMode::Stretch,
+ RepeatMode::Stretch,
+ BrushFlags::empty(),
+ );
+ // Bottom right
+ add_segment(
+ &mut segments,
+ LayoutRect::from_floats(br_inner.x, br_inner.y, br_outer.x, br_outer.y),
+ TexelRect::new(px2, py2, px3, py3),
+ RepeatMode::Stretch,
+ RepeatMode::Stretch,
+ BrushFlags::empty(),
+ );
+ // Bottom left
+ add_segment(
+ &mut segments,
+ LayoutRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y),
+ TexelRect::new(px0, py2, px1, py3),
+ RepeatMode::Stretch,
+ RepeatMode::Stretch,
+ BrushFlags::empty(),
+ );
+
+ // Center
+ if self.fill {
+ add_segment(
+ &mut segments,
+ LayoutRect::from_floats(tl_inner.x, tl_inner.y, tr_inner.x, bl_inner.y),
+ TexelRect::new(px1, py1, px2, py2),
+ self.repeat_horizontal,
+ self.repeat_vertical,
+ BrushFlags::SEGMENT_NINEPATCH_MIDDLE,
+ );
+ }
+
+ // Add edge segments.
+
+ // Top
+ add_segment(
+ &mut segments,
+ LayoutRect::from_floats(tl_inner.x, tl_outer.y, tr_inner.x, tl_inner.y),
+ TexelRect::new(px1, py0, px2, py1),
+ self.repeat_horizontal,
+ RepeatMode::Stretch,
+ BrushFlags::empty(),
+ );
+ // Bottom
+ add_segment(
+ &mut segments,
+ LayoutRect::from_floats(bl_inner.x, bl_inner.y, br_inner.x, bl_outer.y),
+ TexelRect::new(px1, py2, px2, py3),
+ self.repeat_horizontal,
+ RepeatMode::Stretch,
+ BrushFlags::empty(),
+ );
+ // Left
+ add_segment(
+ &mut segments,
+ LayoutRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
+ TexelRect::new(px0, py1, px1, py2),
+ RepeatMode::Stretch,
+ self.repeat_vertical,
+ BrushFlags::empty(),
+ );
+ // Right
+ add_segment(
+ &mut segments,
+ LayoutRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
+ TexelRect::new(px2, py1, px3, py2),
+ RepeatMode::Stretch,
+ self.repeat_vertical,
+ BrushFlags::empty(),
+ );
+
+ segments
+ }
+}