/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* 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::*; usecrate::clip::ClipNodeId; usecrate::ellipse::Ellipse; use euclid::vec2; usecrate::scene_building::SceneBuilder; usecrate::spatial_tree::SpatialNodeIndex; usecrate::gpu_types::{BorderInstance, BorderSegment, BrushFlags}; usecrate::prim_store::{BorderSegmentInfo, BrushSegment, NinePatchDescriptor}; usecrate::prim_store::borders::NormalBorderPrim; usecrate::util::{lerp, RectHelpers}; usecrate::internal_types::LayoutPrimitiveInfo; usecrate::segment::EdgeMask;
// 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. pubconst 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. pubconst 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...
#[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug, Clone, Hash, Eq, MallocSizeOf, PartialEq)] pubstruct 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 pubfn with_color(&self, color: ColorU) -> Self { letmut b = self.clone();
b.left.color = color;
b.right.color = color;
b.top.color = color;
b.bottom.color = color;
b
}
}
pubfn ensure_no_corner_overlap(
radius: &mut BorderRadius,
size: LayoutSize,
) { letmut 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;
if size.width > 0.0 { 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);
}
}
if size.height > 0.0 { 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;
// 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. ifself.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 }; returnself.color.scale_rgb(scale)
}
let black = if lighter { 0.7 } else { 0.3 };
ColorF::new(black, black, black, self.color.a)
}
}
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;
letmut current_length = 0.; for i in0..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 (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)
};
// 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 in0 .. 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),
);
/// 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,
}
// 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. pubfn 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);
/// 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. pubfn get_max_scale_for_border(
border_segments: &[BorderSegmentInfo],
) -> LayoutToDeviceScale { letmut r = 1.0; for segment in border_segments { let size = segment.local_task_size;
r = size.width.max(size.height.max(r));
}
LayoutToDeviceScale::new(MAX_BORDER_RESOLUTION as f32 / r)
}
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.");
}
// 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!()
};
impl NinePatchDescriptor { pubfn for_each_segment(
&self,
rect: &LayoutRect,
add_segment: &mutdyn FnMut(
&LayoutRect, // dst rect
&TexelRect, // src rect
EdgeMask, // segment side
RepeatMode, // horizontal
RepeatMode, // vertical
),
) { // 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);
// Top left let top_left_src = TexelRect::new(px0, py0, px1, py1); if !top_left_src.is_empty() {
add_segment(
&LayoutRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
&top_left_src,
EdgeMask::TOP | EdgeMask::LEFT,
RepeatMode::Stretch,
RepeatMode::Stretch,
);
}
// Top right let top_right_src = TexelRect::new(px2, py0, px3, py1); if !top_right_src.is_empty() {
add_segment(
&LayoutRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y),
&top_right_src,
EdgeMask::TOP | EdgeMask::RIGHT,
RepeatMode::Stretch,
RepeatMode::Stretch,
);
}
// Bottom right let bottom_right_src = TexelRect::new(px2, py2, px3, py3); if !bottom_right_src.is_empty() {
add_segment(
&LayoutRect::from_floats(br_inner.x, br_inner.y, br_outer.x, br_outer.y),
&bottom_right_src,
EdgeMask::BOTTOM | EdgeMask::RIGHT,
RepeatMode::Stretch,
RepeatMode::Stretch,
);
}
// Bottom left let bottom_left_src = TexelRect::new(px0, py2, px1, py3); if !bottom_left_src.is_empty() {
add_segment(
&LayoutRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y),
&bottom_left_src,
EdgeMask::BOTTOM | EdgeMask::LEFT,
RepeatMode::Stretch,
RepeatMode::Stretch,
);
}
// Left let left_src = TexelRect::new(px0, py1, px1, py2); if !left_src.is_empty() {
add_segment(
&LayoutRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
&left_src,
EdgeMask::LEFT,
RepeatMode::Stretch, self.repeat_vertical,
);
}
// Right let right_src = TexelRect::new(px2, py1, px3, py2); if !right_src.is_empty() {
add_segment(
&LayoutRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
&right_src,
EdgeMask::RIGHT,
RepeatMode::Stretch, self.repeat_vertical,
);
}
}
pubfn create_brush_segments(&self, size: LayoutSize) -> Vec<BrushSegment> { // Build the list of image segments letmut segments = Vec::new();
let r = LayoutRect::from_size(size); self.for_each_segment(&r, &mut |rect, uv_rect, side, repeat_horizontal, repeat_vertical| { // Use segment relative interpolation for all // instances in this primitive. letmut brush_flags =
BrushFlags::SEGMENT_RELATIVE |
BrushFlags::SEGMENT_TEXEL_RECT;
if side == EdgeMask::empty() {
brush_flags |= BrushFlags::SEGMENT_NINEPATCH_MIDDLE;
}
// Enable repeat modes on the segment. if repeat_horizontal == RepeatMode::Repeat {
brush_flags |= BrushFlags::SEGMENT_REPEAT_X | BrushFlags::SEGMENT_REPEAT_X_CENTERED;
} elseif repeat_horizontal == RepeatMode::Round {
brush_flags |= BrushFlags::SEGMENT_REPEAT_X | BrushFlags::SEGMENT_REPEAT_X_ROUND;
}
let segment = BrushSegment::new(
*rect, true,
EdgeMask::empty(),
uv_rect.to_array(),
brush_flags,
);
segments.push(segment);
});
segments
}
}
// Computes the stretch-size of a repeated pattern along a border segment, // given the segment size and the size of the source pattern. pubfn compute_border_repetition(
segment_size: LayoutSize,
src_size: DeviceSize,
repeat_x: RepeatMode,
repeat_y: RepeatMode,
stretch_size: &mut LayoutSize,
spacing: &mut LayoutSize,
offset: &mut LayoutVector2D,
) { use euclid::size2;
letmut stretch_size; if repeat_mode == RepeatMode::Stretch {
stretch_size = segment_size.width;
} else { let xy_ratio = src_size.width / src_size.height; // Maintain the aspect ratio of the source pattern.
stretch_size = segment_size.height * xy_ratio;
let repetitions = (segment_size.width / stretch_size).floor().max(1.0); let remaining_space = (segment_size.width - stretch_size * repetitions).max(0.0);
if repeat_mode == RepeatMode::Round { // Stretch the pattern so that an integer number of repetitions // fill the segment exactly.
stretch_size = segment_size.width / repetitions
}
if repeat_mode == RepeatMode::Space { // Maintain an integer number of repetitions using some space // between them.
*out_spacing = remaining_space / (repetitions - 1.0).max(1.0);
}
if repeat_mode == RepeatMode::Repeat { // Offset the pattern to distribute the overflowing repetitions // equally on both sides. To partially include a repetition on the // left side we have to enlarge the local rect to include a full // repetition and let the local clip rect remove the part we don't // want.
*out_offset = (remaining_space - stretch_size) * 0.5;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.