mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
Split layout/background.rs file
Have background, border and gradient modules for calculation functions. Use shorter names for functions that are qualified by the module name like `border::radii`. Use push_item and push_iter to add items to WebRender.
This commit is contained in:
parent
2304f02123
commit
2ff330a5c9
7 changed files with 577 additions and 658 deletions
|
@ -2,52 +2,23 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
//! Calculations for CSS images, backgrounds and borders.
|
|
||||||
//!
|
|
||||||
//! * [CSS Images Module Level 3](https://drafts.csswg.org/css-images-3/)
|
|
||||||
//! * [CSS Backgrounds and Borders Module Level 3](https://drafts.csswg.org/css-backgrounds-3/)
|
|
||||||
|
|
||||||
#![deny(unsafe_code)]
|
#![deny(unsafe_code)]
|
||||||
|
|
||||||
// FIXME(rust-lang/rust#26264): Remove GenericEndingShape, GenericGradientItem,
|
// FIXME(rust-lang/rust#26264): Remove GenericBackgroundSize.
|
||||||
// GenericBackgroundSize and GenericBorderImageSideWidth.
|
|
||||||
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use display_list::ToLayout;
|
use display_list::border;
|
||||||
use euclid::{Point2D, Rect, SideOffsets2D, Size2D, Vector2D};
|
use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
|
||||||
use model::{self, MaybeAuto};
|
use model::MaybeAuto;
|
||||||
use style::computed_values::background_attachment::single_value::T as BackgroundAttachment;
|
use style::computed_values::background_attachment::single_value::T as BackgroundAttachment;
|
||||||
use style::computed_values::background_clip::single_value::T as BackgroundClip;
|
use style::computed_values::background_clip::single_value::T as BackgroundClip;
|
||||||
use style::computed_values::background_origin::single_value::T as BackgroundOrigin;
|
use style::computed_values::background_origin::single_value::T as BackgroundOrigin;
|
||||||
use style::computed_values::border_image_outset::T as BorderImageOutset;
|
use style::properties::style_structs::Background;
|
||||||
use style::properties::ComputedValues;
|
use style::values::computed::{BackgroundSize, LengthOrPercentageOrAuto};
|
||||||
use style::properties::style_structs::{self, Background};
|
|
||||||
use style::values::Either;
|
|
||||||
use style::values::computed::{Angle, GradientItem, BackgroundSize};
|
|
||||||
use style::values::computed::{BorderImageWidth, BorderImageSideWidth};
|
|
||||||
use style::values::computed::{LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
|
|
||||||
use style::values::computed::{NumberOrPercentage, Percentage, Position};
|
|
||||||
use style::values::computed::image::{EndingShape, LineDirection};
|
|
||||||
use style::values::generics::NonNegative;
|
use style::values::generics::NonNegative;
|
||||||
use style::values::generics::background::BackgroundSize as GenericBackgroundSize;
|
use style::values::generics::background::BackgroundSize as GenericBackgroundSize;
|
||||||
use style::values::generics::border::{BorderImageSideWidth as GenericBorderImageSideWidth};
|
|
||||||
use style::values::generics::image::{Circle, Ellipse, ShapeExtent};
|
|
||||||
use style::values::generics::image::EndingShape as GenericEndingShape;
|
|
||||||
use style::values::generics::image::GradientItem as GenericGradientItem;
|
|
||||||
use style::values::specified::background::BackgroundRepeatKeyword;
|
use style::values::specified::background::BackgroundRepeatKeyword;
|
||||||
use style::values::specified::position::{X, Y};
|
use webrender_api::BorderRadius;
|
||||||
use webrender_api::{BorderRadius, BorderSide, BorderStyle, ColorF};
|
|
||||||
use webrender_api::{ExtendMode, Gradient, GradientStop, LayoutSize, LayoutSideOffsets};
|
|
||||||
use webrender_api::{NormalBorder, RadialGradient};
|
|
||||||
|
|
||||||
/// A helper data structure for gradients.
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
struct StopRun {
|
|
||||||
start_offset: f32,
|
|
||||||
end_offset: f32,
|
|
||||||
start_index: usize,
|
|
||||||
stop_count: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Placment information for both image and gradient backgrounds.
|
/// Placment information for both image and gradient backgrounds.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
@ -70,19 +41,6 @@ pub struct BackgroundPlacement {
|
||||||
pub fixed: bool,
|
pub fixed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ResolvePercentage {
|
|
||||||
fn resolve(&self, length: u32) -> u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResolvePercentage for NumberOrPercentage {
|
|
||||||
fn resolve(&self, length: u32) -> u32 {
|
|
||||||
match *self {
|
|
||||||
NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as u32,
|
|
||||||
NumberOrPercentage::Number(n) => n.round() as u32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Access element at index modulo the array length.
|
/// Access element at index modulo the array length.
|
||||||
///
|
///
|
||||||
/// Obviously it does not work with empty arrays.
|
/// Obviously it does not work with empty arrays.
|
||||||
|
@ -162,7 +120,7 @@ fn compute_background_image_size(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compute_background_clip(
|
pub fn clip(
|
||||||
bg_clip: BackgroundClip,
|
bg_clip: BackgroundClip,
|
||||||
absolute_bounds: Rect<Au>,
|
absolute_bounds: Rect<Au>,
|
||||||
border: SideOffsets2D<Au>,
|
border: SideOffsets2D<Au>,
|
||||||
|
@ -173,11 +131,11 @@ pub fn compute_background_clip(
|
||||||
BackgroundClip::BorderBox => (absolute_bounds, border_radii),
|
BackgroundClip::BorderBox => (absolute_bounds, border_radii),
|
||||||
BackgroundClip::PaddingBox => (
|
BackgroundClip::PaddingBox => (
|
||||||
absolute_bounds.inner_rect(border),
|
absolute_bounds.inner_rect(border),
|
||||||
calculate_inner_border_radii(border_radii, border),
|
border::inner_radii(border_radii, border),
|
||||||
),
|
),
|
||||||
BackgroundClip::ContentBox => (
|
BackgroundClip::ContentBox => (
|
||||||
absolute_bounds.inner_rect(border_padding),
|
absolute_bounds.inner_rect(border_padding),
|
||||||
calculate_inner_border_radii(border_radii, border_padding),
|
border::inner_radii(border_radii, border_padding),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,7 +144,7 @@ pub fn compute_background_clip(
|
||||||
///
|
///
|
||||||
/// Photos have their resolution as intrinsic size while gradients have
|
/// Photos have their resolution as intrinsic size while gradients have
|
||||||
/// no intrinsic size.
|
/// no intrinsic size.
|
||||||
pub fn compute_background_placement(
|
pub fn placement(
|
||||||
bg: &Background,
|
bg: &Background,
|
||||||
viewport_size: Size2D<Au>,
|
viewport_size: Size2D<Au>,
|
||||||
absolute_bounds: Rect<Au>,
|
absolute_bounds: Rect<Au>,
|
||||||
|
@ -204,7 +162,7 @@ pub fn compute_background_placement(
|
||||||
let bg_repeat = get_cyclic(&bg.background_repeat.0, index);
|
let bg_repeat = get_cyclic(&bg.background_repeat.0, index);
|
||||||
let bg_size = *get_cyclic(&bg.background_size.0, index);
|
let bg_size = *get_cyclic(&bg.background_size.0, index);
|
||||||
|
|
||||||
let (clip_rect, clip_radii) = compute_background_clip(
|
let (clip_rect, clip_radii) = clip(
|
||||||
bg_clip,
|
bg_clip,
|
||||||
absolute_bounds,
|
absolute_bounds,
|
||||||
border,
|
border,
|
||||||
|
@ -380,468 +338,3 @@ fn tile_image_axis(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines the radius of a circle if it was not explictly provided.
|
|
||||||
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
|
|
||||||
fn convert_circle_size_keyword(
|
|
||||||
keyword: ShapeExtent,
|
|
||||||
size: &Size2D<Au>,
|
|
||||||
center: &Point2D<Au>,
|
|
||||||
) -> Size2D<Au> {
|
|
||||||
let radius = match keyword {
|
|
||||||
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
|
|
||||||
let dist = get_distance_to_sides(size, center, ::std::cmp::min);
|
|
||||||
::std::cmp::min(dist.width, dist.height)
|
|
||||||
},
|
|
||||||
ShapeExtent::FarthestSide => {
|
|
||||||
let dist = get_distance_to_sides(size, center, ::std::cmp::max);
|
|
||||||
::std::cmp::max(dist.width, dist.height)
|
|
||||||
},
|
|
||||||
ShapeExtent::ClosestCorner => get_distance_to_corner(size, center, ::std::cmp::min),
|
|
||||||
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
|
|
||||||
get_distance_to_corner(size, center, ::std::cmp::max)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Size2D::new(radius, radius)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the radius for an ellipse with the same ratio as if it was matched to the sides.
|
|
||||||
fn get_ellipse_radius<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
|
|
||||||
where
|
|
||||||
F: Fn(Au, Au) -> Au,
|
|
||||||
{
|
|
||||||
let dist = get_distance_to_sides(size, center, cmp);
|
|
||||||
Size2D::new(
|
|
||||||
dist.width.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
|
|
||||||
dist.height
|
|
||||||
.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines the radius of an ellipse if it was not explictly provided.
|
|
||||||
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
|
|
||||||
fn convert_ellipse_size_keyword(
|
|
||||||
keyword: ShapeExtent,
|
|
||||||
size: &Size2D<Au>,
|
|
||||||
center: &Point2D<Au>,
|
|
||||||
) -> Size2D<Au> {
|
|
||||||
match keyword {
|
|
||||||
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
|
|
||||||
get_distance_to_sides(size, center, ::std::cmp::min)
|
|
||||||
},
|
|
||||||
ShapeExtent::FarthestSide => get_distance_to_sides(size, center, ::std::cmp::max),
|
|
||||||
ShapeExtent::ClosestCorner => get_ellipse_radius(size, center, ::std::cmp::min),
|
|
||||||
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
|
|
||||||
get_ellipse_radius(size, center, ::std::cmp::max)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_gradient_stops(
|
|
||||||
style: &ComputedValues,
|
|
||||||
gradient_items: &[GradientItem],
|
|
||||||
total_length: Au,
|
|
||||||
) -> Vec<GradientStop> {
|
|
||||||
// Determine the position of each stop per CSS-IMAGES § 3.4.
|
|
||||||
|
|
||||||
// Only keep the color stops, discard the color interpolation hints.
|
|
||||||
let mut stop_items = gradient_items
|
|
||||||
.iter()
|
|
||||||
.filter_map(|item| match *item {
|
|
||||||
GenericGradientItem::ColorStop(ref stop) => Some(*stop),
|
|
||||||
_ => None,
|
|
||||||
}).collect::<Vec<_>>();
|
|
||||||
|
|
||||||
assert!(stop_items.len() >= 2);
|
|
||||||
|
|
||||||
// Run the algorithm from
|
|
||||||
// https://drafts.csswg.org/css-images-3/#color-stop-syntax
|
|
||||||
|
|
||||||
// Step 1:
|
|
||||||
// If the first color stop does not have a position, set its position to 0%.
|
|
||||||
{
|
|
||||||
let first = stop_items.first_mut().unwrap();
|
|
||||||
if first.position.is_none() {
|
|
||||||
first.position = Some(LengthOrPercentage::Percentage(Percentage(0.0)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If the last color stop does not have a position, set its position to 100%.
|
|
||||||
{
|
|
||||||
let last = stop_items.last_mut().unwrap();
|
|
||||||
if last.position.is_none() {
|
|
||||||
last.position = Some(LengthOrPercentage::Percentage(Percentage(1.0)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Move any stops placed before earlier stops to the
|
|
||||||
// same position as the preceding stop.
|
|
||||||
let mut last_stop_position = stop_items.first().unwrap().position.unwrap();
|
|
||||||
for stop in stop_items.iter_mut().skip(1) {
|
|
||||||
if let Some(pos) = stop.position {
|
|
||||||
if position_to_offset(last_stop_position, total_length) >
|
|
||||||
position_to_offset(pos, total_length)
|
|
||||||
{
|
|
||||||
stop.position = Some(last_stop_position);
|
|
||||||
}
|
|
||||||
last_stop_position = stop.position.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3: Evenly space stops without position.
|
|
||||||
let mut stops = Vec::with_capacity(stop_items.len());
|
|
||||||
let mut stop_run = None;
|
|
||||||
for (i, stop) in stop_items.iter().enumerate() {
|
|
||||||
let offset = match stop.position {
|
|
||||||
None => {
|
|
||||||
if stop_run.is_none() {
|
|
||||||
// Initialize a new stop run.
|
|
||||||
// `unwrap()` here should never fail because this is the beginning of
|
|
||||||
// a stop run, which is always bounded by a length or percentage.
|
|
||||||
let start_offset =
|
|
||||||
position_to_offset(stop_items[i - 1].position.unwrap(), total_length);
|
|
||||||
// `unwrap()` here should never fail because this is the end of
|
|
||||||
// a stop run, which is always bounded by a length or percentage.
|
|
||||||
let (end_index, end_stop) = stop_items[(i + 1)..]
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|&(_, ref stop)| stop.position.is_some())
|
|
||||||
.unwrap();
|
|
||||||
let end_offset = position_to_offset(end_stop.position.unwrap(), total_length);
|
|
||||||
stop_run = Some(StopRun {
|
|
||||||
start_offset,
|
|
||||||
end_offset,
|
|
||||||
start_index: i - 1,
|
|
||||||
stop_count: end_index,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let stop_run = stop_run.unwrap();
|
|
||||||
let stop_run_length = stop_run.end_offset - stop_run.start_offset;
|
|
||||||
stop_run.start_offset +
|
|
||||||
stop_run_length * (i - stop_run.start_index) as f32 /
|
|
||||||
((2 + stop_run.stop_count) as f32)
|
|
||||||
},
|
|
||||||
Some(position) => {
|
|
||||||
stop_run = None;
|
|
||||||
position_to_offset(position, total_length)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
assert!(offset.is_finite());
|
|
||||||
stops.push(GradientStop {
|
|
||||||
offset: offset,
|
|
||||||
color: style.resolve_color(stop.color).to_layout(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
stops
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_gradient_extend_mode(repeating: bool) -> ExtendMode {
|
|
||||||
if repeating {
|
|
||||||
ExtendMode::Repeat
|
|
||||||
} else {
|
|
||||||
ExtendMode::Clamp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn convert_linear_gradient(
|
|
||||||
style: &ComputedValues,
|
|
||||||
size: Size2D<Au>,
|
|
||||||
stops: &[GradientItem],
|
|
||||||
direction: LineDirection,
|
|
||||||
repeating: bool,
|
|
||||||
) -> (Gradient, Vec<GradientStop>) {
|
|
||||||
let angle = match direction {
|
|
||||||
LineDirection::Angle(angle) => angle.radians(),
|
|
||||||
LineDirection::Horizontal(x) => match x {
|
|
||||||
X::Left => Angle::from_degrees(270.).radians(),
|
|
||||||
X::Right => Angle::from_degrees(90.).radians(),
|
|
||||||
},
|
|
||||||
LineDirection::Vertical(y) => match y {
|
|
||||||
Y::Top => Angle::from_degrees(0.).radians(),
|
|
||||||
Y::Bottom => Angle::from_degrees(180.).radians(),
|
|
||||||
},
|
|
||||||
LineDirection::Corner(horizontal, vertical) => {
|
|
||||||
// This the angle for one of the diagonals of the box. Our angle
|
|
||||||
// will either be this one, this one + PI, or one of the other
|
|
||||||
// two perpendicular angles.
|
|
||||||
let atan = (size.height.to_f32_px() / size.width.to_f32_px()).atan();
|
|
||||||
match (horizontal, vertical) {
|
|
||||||
(X::Right, Y::Bottom) => ::std::f32::consts::PI - atan,
|
|
||||||
(X::Left, Y::Bottom) => ::std::f32::consts::PI + atan,
|
|
||||||
(X::Right, Y::Top) => atan,
|
|
||||||
(X::Left, Y::Top) => -atan,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get correct gradient line length, based on:
|
|
||||||
// https://drafts.csswg.org/css-images-3/#linear-gradients
|
|
||||||
let dir = Point2D::new(angle.sin(), -angle.cos());
|
|
||||||
|
|
||||||
let line_length =
|
|
||||||
(dir.x * size.width.to_f32_px()).abs() + (dir.y * size.height.to_f32_px()).abs();
|
|
||||||
|
|
||||||
let inv_dir_length = 1.0 / (dir.x * dir.x + dir.y * dir.y).sqrt();
|
|
||||||
|
|
||||||
// This is the vector between the center and the ending point; i.e. half
|
|
||||||
// of the distance between the starting point and the ending point.
|
|
||||||
let delta = Vector2D::new(
|
|
||||||
Au::from_f32_px(dir.x * inv_dir_length * line_length / 2.0),
|
|
||||||
Au::from_f32_px(dir.y * inv_dir_length * line_length / 2.0),
|
|
||||||
);
|
|
||||||
|
|
||||||
// This is the length of the gradient line.
|
|
||||||
let length = Au::from_f32_px((delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0));
|
|
||||||
|
|
||||||
let stops = convert_gradient_stops(style, stops, length);
|
|
||||||
|
|
||||||
let center = Point2D::new(size.width / 2, size.height / 2);
|
|
||||||
|
|
||||||
(
|
|
||||||
Gradient {
|
|
||||||
start_point: (center - delta).to_layout(),
|
|
||||||
end_point: (center + delta).to_layout(),
|
|
||||||
extend_mode: as_gradient_extend_mode(repeating),
|
|
||||||
},
|
|
||||||
stops,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn convert_radial_gradient(
|
|
||||||
style: &ComputedValues,
|
|
||||||
size: Size2D<Au>,
|
|
||||||
stops: &[GradientItem],
|
|
||||||
shape: EndingShape,
|
|
||||||
center: Position,
|
|
||||||
repeating: bool,
|
|
||||||
) -> (RadialGradient, Vec<GradientStop>) {
|
|
||||||
let center = Point2D::new(
|
|
||||||
center.horizontal.to_used_value(size.width),
|
|
||||||
center.vertical.to_used_value(size.height),
|
|
||||||
);
|
|
||||||
let radius = match shape {
|
|
||||||
GenericEndingShape::Circle(Circle::Radius(length)) => {
|
|
||||||
let length = Au::from(length);
|
|
||||||
Size2D::new(length, length)
|
|
||||||
},
|
|
||||||
GenericEndingShape::Circle(Circle::Extent(extent)) => {
|
|
||||||
convert_circle_size_keyword(extent, &size, ¢er)
|
|
||||||
},
|
|
||||||
GenericEndingShape::Ellipse(Ellipse::Radii(x, y)) => {
|
|
||||||
Size2D::new(x.to_used_value(size.width), y.to_used_value(size.height))
|
|
||||||
},
|
|
||||||
GenericEndingShape::Ellipse(Ellipse::Extent(extent)) => {
|
|
||||||
convert_ellipse_size_keyword(extent, &size, ¢er)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let stops = convert_gradient_stops(style, stops, radius.width);
|
|
||||||
|
|
||||||
(
|
|
||||||
RadialGradient {
|
|
||||||
center: center.to_layout(),
|
|
||||||
radius: radius.to_layout(),
|
|
||||||
extend_mode: as_gradient_extend_mode(repeating),
|
|
||||||
// FIXME(pyfisch): These values are calculated by WR.
|
|
||||||
start_offset: 0.0,
|
|
||||||
end_offset: 0.0,
|
|
||||||
},
|
|
||||||
stops,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the the distance to the nearest or farthest corner depending on the comperator.
|
|
||||||
fn get_distance_to_corner<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Au
|
|
||||||
where
|
|
||||||
F: Fn(Au, Au) -> Au,
|
|
||||||
{
|
|
||||||
let dist = get_distance_to_sides(size, center, cmp);
|
|
||||||
Au::from_f32_px(dist.width.to_f32_px().hypot(dist.height.to_f32_px()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the distance to the nearest or farthest sides depending on the comparator.
|
|
||||||
///
|
|
||||||
/// The first return value is horizontal distance the second vertical distance.
|
|
||||||
fn get_distance_to_sides<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
|
|
||||||
where
|
|
||||||
F: Fn(Au, Au) -> Au,
|
|
||||||
{
|
|
||||||
let top_side = center.y;
|
|
||||||
let right_side = size.width - center.x;
|
|
||||||
let bottom_side = size.height - center.y;
|
|
||||||
let left_side = center.x;
|
|
||||||
Size2D::new(cmp(left_side, right_side), cmp(top_side, bottom_side))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn position_to_offset(position: LengthOrPercentage, total_length: Au) -> f32 {
|
|
||||||
if total_length == Au(0) {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
match position {
|
|
||||||
LengthOrPercentage::Length(l) => l.to_i32_au() as f32 / total_length.0 as f32,
|
|
||||||
LengthOrPercentage::Percentage(percentage) => percentage.0 as f32,
|
|
||||||
LengthOrPercentage::Calc(calc) => {
|
|
||||||
calc.to_used_value(Some(total_length)).unwrap().0 as f32 / total_length.0 as f32
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scale_border_radii(radii: BorderRadius, factor: f32) -> BorderRadius {
|
|
||||||
BorderRadius {
|
|
||||||
top_left: radii.top_left * factor,
|
|
||||||
top_right: radii.top_right * factor,
|
|
||||||
bottom_left: radii.bottom_left * factor,
|
|
||||||
bottom_right: radii.bottom_right * factor,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_overlapping_radii(size: LayoutSize, radii: BorderRadius) -> BorderRadius {
|
|
||||||
// No two corners' border radii may add up to more than the length of the edge
|
|
||||||
// between them. To prevent that, all radii are scaled down uniformly.
|
|
||||||
fn scale_factor(radius_a: f32, radius_b: f32, edge_length: f32) -> f32 {
|
|
||||||
let required = radius_a + radius_b;
|
|
||||||
|
|
||||||
if required <= edge_length {
|
|
||||||
1.0
|
|
||||||
} else {
|
|
||||||
edge_length / required
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let top_factor = scale_factor(radii.top_left.width, radii.top_right.width, size.width);
|
|
||||||
let bottom_factor = scale_factor(
|
|
||||||
radii.bottom_left.width,
|
|
||||||
radii.bottom_right.width,
|
|
||||||
size.width,
|
|
||||||
);
|
|
||||||
let left_factor = scale_factor(radii.top_left.height, radii.bottom_left.height, size.height);
|
|
||||||
let right_factor = scale_factor(
|
|
||||||
radii.top_right.height,
|
|
||||||
radii.bottom_right.height,
|
|
||||||
size.height,
|
|
||||||
);
|
|
||||||
let min_factor = top_factor
|
|
||||||
.min(bottom_factor)
|
|
||||||
.min(left_factor)
|
|
||||||
.min(right_factor);
|
|
||||||
if min_factor < 1.0 {
|
|
||||||
scale_border_radii(radii, min_factor)
|
|
||||||
} else {
|
|
||||||
radii
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_border_radius(
|
|
||||||
abs_bounds: Rect<Au>,
|
|
||||||
border_style: &style_structs::Border,
|
|
||||||
) -> BorderRadius {
|
|
||||||
// TODO(cgaebel): Support border radii even in the case of multiple border widths.
|
|
||||||
// This is an extension of supporting elliptical radii. For now, all percentage
|
|
||||||
// radii will be relative to the width.
|
|
||||||
|
|
||||||
handle_overlapping_radii(
|
|
||||||
abs_bounds.size.to_layout(),
|
|
||||||
BorderRadius {
|
|
||||||
top_left: model::specified_border_radius(
|
|
||||||
border_style.border_top_left_radius,
|
|
||||||
abs_bounds.size,
|
|
||||||
).to_layout(),
|
|
||||||
top_right: model::specified_border_radius(
|
|
||||||
border_style.border_top_right_radius,
|
|
||||||
abs_bounds.size,
|
|
||||||
).to_layout(),
|
|
||||||
bottom_right: model::specified_border_radius(
|
|
||||||
border_style.border_bottom_right_radius,
|
|
||||||
abs_bounds.size,
|
|
||||||
).to_layout(),
|
|
||||||
bottom_left: model::specified_border_radius(
|
|
||||||
border_style.border_bottom_left_radius,
|
|
||||||
abs_bounds.size,
|
|
||||||
).to_layout(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a four-sided border with uniform color, width and corner radius.
|
|
||||||
pub fn simple_normal_border(color: ColorF, style: BorderStyle) -> NormalBorder {
|
|
||||||
let side = BorderSide { color, style };
|
|
||||||
NormalBorder {
|
|
||||||
left: side,
|
|
||||||
right: side,
|
|
||||||
top: side,
|
|
||||||
bottom: side,
|
|
||||||
radius: BorderRadius::zero(),
|
|
||||||
do_aa: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculates radii for the inner side.
|
|
||||||
///
|
|
||||||
/// Radii usually describe the outer side of a border but for the lines to look nice
|
|
||||||
/// the inner radii need to be smaller depending on the line width.
|
|
||||||
///
|
|
||||||
/// This is used to determine clipping areas.
|
|
||||||
pub fn calculate_inner_border_radii(
|
|
||||||
mut radii: BorderRadius,
|
|
||||||
offsets: SideOffsets2D<Au>,
|
|
||||||
) -> BorderRadius {
|
|
||||||
fn inner_length(x: f32, offset: Au) -> f32 {
|
|
||||||
0.0_f32.max(x - offset.to_f32_px())
|
|
||||||
}
|
|
||||||
radii.top_left.width = inner_length(radii.top_left.width, offsets.left);
|
|
||||||
radii.bottom_left.width = inner_length(radii.bottom_left.width, offsets.left);
|
|
||||||
|
|
||||||
radii.top_right.width = inner_length(radii.top_right.width, offsets.right);
|
|
||||||
radii.bottom_right.width = inner_length(radii.bottom_right.width, offsets.right);
|
|
||||||
|
|
||||||
radii.top_left.height = inner_length(radii.top_left.height, offsets.top);
|
|
||||||
radii.top_right.height = inner_length(radii.top_right.height, offsets.top);
|
|
||||||
|
|
||||||
radii.bottom_left.height = inner_length(radii.bottom_left.height, offsets.bottom);
|
|
||||||
radii.bottom_right.height = inner_length(radii.bottom_right.height, offsets.bottom);
|
|
||||||
radii
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_border_image_outset_side(outset: LengthOrNumber, border_width: Au) -> Au {
|
|
||||||
match outset {
|
|
||||||
Either::First(length) => length.into(),
|
|
||||||
Either::Second(factor) => border_width.scale_by(factor),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn calculate_border_image_outset(
|
|
||||||
outset: BorderImageOutset,
|
|
||||||
border: SideOffsets2D<Au>,
|
|
||||||
) -> SideOffsets2D<Au> {
|
|
||||||
SideOffsets2D::new(
|
|
||||||
calculate_border_image_outset_side(outset.0, border.top),
|
|
||||||
calculate_border_image_outset_side(outset.1, border.right),
|
|
||||||
calculate_border_image_outset_side(outset.2, border.bottom),
|
|
||||||
calculate_border_image_outset_side(outset.3, border.left),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_border_image_width_side(
|
|
||||||
border_image_width: BorderImageSideWidth,
|
|
||||||
border_width: f32,
|
|
||||||
total_length: Au,
|
|
||||||
) -> f32 {
|
|
||||||
match border_image_width {
|
|
||||||
GenericBorderImageSideWidth::Length(v) => v.to_used_value(total_length).to_f32_px(),
|
|
||||||
GenericBorderImageSideWidth::Number(x) => border_width * x,
|
|
||||||
GenericBorderImageSideWidth::Auto => border_width,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn calculate_border_image_width(
|
|
||||||
width: &BorderImageWidth,
|
|
||||||
border: LayoutSideOffsets,
|
|
||||||
border_area: Size2D<Au>,
|
|
||||||
) -> LayoutSideOffsets {
|
|
||||||
LayoutSideOffsets::new(
|
|
||||||
calculate_border_image_width_side(width.0, border.top, border_area.height),
|
|
||||||
calculate_border_image_width_side(width.1, border.right, border_area.width),
|
|
||||||
calculate_border_image_width_side(width.2, border.bottom, border_area.height),
|
|
||||||
calculate_border_image_width_side(width.3, border.left, border_area.width),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
209
components/layout/display_list/border.rs
Normal file
209
components/layout/display_list/border.rs
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
// FIXME(rust-lang/rust#26264): Remove GenericBorderImageSideWidth.
|
||||||
|
|
||||||
|
use app_units::Au;
|
||||||
|
use display_list::ToLayout;
|
||||||
|
use euclid::{Rect, SideOffsets2D, Size2D};
|
||||||
|
use style::computed_values::border_image_outset::T as BorderImageOutset;
|
||||||
|
use style::properties::style_structs::Border;
|
||||||
|
use style::values::Either;
|
||||||
|
use style::values::computed::{BorderCornerRadius, BorderImageWidth};
|
||||||
|
use style::values::computed::{BorderImageSideWidth, LengthOrNumber};
|
||||||
|
use style::values::computed::NumberOrPercentage;
|
||||||
|
use style::values::generics::border::{BorderImageSideWidth as GenericBorderImageSideWidth};
|
||||||
|
use style::values::generics::rect::Rect as StyleRect;
|
||||||
|
use webrender_api::{BorderRadius, BorderSide, BorderStyle, ColorF};
|
||||||
|
use webrender_api::{LayoutSize, LayoutSideOffsets, NormalBorder};
|
||||||
|
|
||||||
|
/// Computes a border radius size against the containing size.
|
||||||
|
///
|
||||||
|
/// Note that percentages in `border-radius` are resolved against the relevant
|
||||||
|
/// box dimension instead of only against the width per [1]:
|
||||||
|
///
|
||||||
|
/// > Percentages: Refer to corresponding dimension of the border box.
|
||||||
|
///
|
||||||
|
/// [1]: https://drafts.csswg.org/css-backgrounds-3/#border-radius
|
||||||
|
fn corner_radius(
|
||||||
|
radius: BorderCornerRadius,
|
||||||
|
containing_size: Size2D<Au>,
|
||||||
|
) -> Size2D<Au> {
|
||||||
|
let w = radius.0.width().to_used_value(containing_size.width);
|
||||||
|
let h = radius.0.height().to_used_value(containing_size.height);
|
||||||
|
Size2D::new(w, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scaled_radii(radii: BorderRadius, factor: f32) -> BorderRadius {
|
||||||
|
BorderRadius {
|
||||||
|
top_left: radii.top_left * factor,
|
||||||
|
top_right: radii.top_right * factor,
|
||||||
|
bottom_left: radii.bottom_left * factor,
|
||||||
|
bottom_right: radii.bottom_right * factor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlapping_radii(size: LayoutSize, radii: BorderRadius) -> BorderRadius {
|
||||||
|
// No two corners' border radii may add up to more than the length of the edge
|
||||||
|
// between them. To prevent that, all radii are scaled down uniformly.
|
||||||
|
fn scale_factor(radius_a: f32, radius_b: f32, edge_length: f32) -> f32 {
|
||||||
|
let required = radius_a + radius_b;
|
||||||
|
|
||||||
|
if required <= edge_length {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
edge_length / required
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let top_factor = scale_factor(radii.top_left.width, radii.top_right.width, size.width);
|
||||||
|
let bottom_factor = scale_factor(
|
||||||
|
radii.bottom_left.width,
|
||||||
|
radii.bottom_right.width,
|
||||||
|
size.width,
|
||||||
|
);
|
||||||
|
let left_factor = scale_factor(radii.top_left.height, radii.bottom_left.height, size.height);
|
||||||
|
let right_factor = scale_factor(
|
||||||
|
radii.top_right.height,
|
||||||
|
radii.bottom_right.height,
|
||||||
|
size.height,
|
||||||
|
);
|
||||||
|
let min_factor = top_factor
|
||||||
|
.min(bottom_factor)
|
||||||
|
.min(left_factor)
|
||||||
|
.min(right_factor);
|
||||||
|
if min_factor < 1.0 {
|
||||||
|
scaled_radii(radii, min_factor)
|
||||||
|
} else {
|
||||||
|
radii
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn radii(
|
||||||
|
abs_bounds: Rect<Au>,
|
||||||
|
border_style: &Border,
|
||||||
|
) -> BorderRadius {
|
||||||
|
// TODO(cgaebel): Support border radii even in the case of multiple border widths.
|
||||||
|
// This is an extension of supporting elliptical radii. For now, all percentage
|
||||||
|
// radii will be relative to the width.
|
||||||
|
|
||||||
|
overlapping_radii(
|
||||||
|
abs_bounds.size.to_layout(),
|
||||||
|
BorderRadius {
|
||||||
|
top_left: corner_radius(
|
||||||
|
border_style.border_top_left_radius,
|
||||||
|
abs_bounds.size,
|
||||||
|
).to_layout(),
|
||||||
|
top_right: corner_radius(
|
||||||
|
border_style.border_top_right_radius,
|
||||||
|
abs_bounds.size,
|
||||||
|
).to_layout(),
|
||||||
|
bottom_right: corner_radius(
|
||||||
|
border_style.border_bottom_right_radius,
|
||||||
|
abs_bounds.size,
|
||||||
|
).to_layout(),
|
||||||
|
bottom_left: corner_radius(
|
||||||
|
border_style.border_bottom_left_radius,
|
||||||
|
abs_bounds.size,
|
||||||
|
).to_layout(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates radii for the inner side.
|
||||||
|
///
|
||||||
|
/// Radii usually describe the outer side of a border but for the lines to look nice
|
||||||
|
/// the inner radii need to be smaller depending on the line width.
|
||||||
|
///
|
||||||
|
/// This is used to determine clipping areas.
|
||||||
|
pub fn inner_radii(
|
||||||
|
mut radii: BorderRadius,
|
||||||
|
offsets: SideOffsets2D<Au>,
|
||||||
|
) -> BorderRadius {
|
||||||
|
fn inner_length(x: f32, offset: Au) -> f32 {
|
||||||
|
0.0_f32.max(x - offset.to_f32_px())
|
||||||
|
}
|
||||||
|
radii.top_left.width = inner_length(radii.top_left.width, offsets.left);
|
||||||
|
radii.bottom_left.width = inner_length(radii.bottom_left.width, offsets.left);
|
||||||
|
|
||||||
|
radii.top_right.width = inner_length(radii.top_right.width, offsets.right);
|
||||||
|
radii.bottom_right.width = inner_length(radii.bottom_right.width, offsets.right);
|
||||||
|
|
||||||
|
radii.top_left.height = inner_length(radii.top_left.height, offsets.top);
|
||||||
|
radii.top_right.height = inner_length(radii.top_right.height, offsets.top);
|
||||||
|
|
||||||
|
radii.bottom_left.height = inner_length(radii.bottom_left.height, offsets.bottom);
|
||||||
|
radii.bottom_right.height = inner_length(radii.bottom_right.height, offsets.bottom);
|
||||||
|
radii
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a four-sided border with uniform color, width and corner radius.
|
||||||
|
pub fn simple(color: ColorF, style: BorderStyle) -> NormalBorder {
|
||||||
|
let side = BorderSide { color, style };
|
||||||
|
NormalBorder {
|
||||||
|
left: side,
|
||||||
|
right: side,
|
||||||
|
top: side,
|
||||||
|
bottom: side,
|
||||||
|
radius: BorderRadius::zero(),
|
||||||
|
do_aa: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn side_image_outset(outset: LengthOrNumber, border_width: Au) -> Au {
|
||||||
|
match outset {
|
||||||
|
Either::First(length) => length.into(),
|
||||||
|
Either::Second(factor) => border_width.scale_by(factor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn image_outset(
|
||||||
|
outset: BorderImageOutset,
|
||||||
|
border: SideOffsets2D<Au>,
|
||||||
|
) -> SideOffsets2D<Au> {
|
||||||
|
SideOffsets2D::new(
|
||||||
|
side_image_outset(outset.0, border.top),
|
||||||
|
side_image_outset(outset.1, border.right),
|
||||||
|
side_image_outset(outset.2, border.bottom),
|
||||||
|
side_image_outset(outset.3, border.left),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn side_image_width(
|
||||||
|
border_image_width: BorderImageSideWidth,
|
||||||
|
border_width: f32,
|
||||||
|
total_length: Au,
|
||||||
|
) -> f32 {
|
||||||
|
match border_image_width {
|
||||||
|
GenericBorderImageSideWidth::Length(v) => v.to_used_value(total_length).to_f32_px(),
|
||||||
|
GenericBorderImageSideWidth::Number(x) => border_width * x,
|
||||||
|
GenericBorderImageSideWidth::Auto => border_width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn image_width(
|
||||||
|
width: &BorderImageWidth,
|
||||||
|
border: LayoutSideOffsets,
|
||||||
|
border_area: Size2D<Au>,
|
||||||
|
) -> LayoutSideOffsets {
|
||||||
|
LayoutSideOffsets::new(
|
||||||
|
side_image_width(width.0, border.top, border_area.height),
|
||||||
|
side_image_width(width.1, border.right, border_area.width),
|
||||||
|
side_image_width(width.2, border.bottom, border_area.height),
|
||||||
|
side_image_width(width.3, border.left, border_area.width),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_percentage(value: NumberOrPercentage, length: u32) -> u32 {
|
||||||
|
match value {
|
||||||
|
NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as u32,
|
||||||
|
NumberOrPercentage::Number(n) => n.round() as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn image_slice(border_image_slice: &StyleRect<NumberOrPercentage>, width: u32, height: u32) -> SideOffsets2D<u32> {
|
||||||
|
SideOffsets2D::new(
|
||||||
|
resolve_percentage(border_image_slice.0, height),
|
||||||
|
resolve_percentage(border_image_slice.1, width),
|
||||||
|
resolve_percentage(border_image_slice.2, height),
|
||||||
|
resolve_percentage(border_image_slice.3, width),
|
||||||
|
)
|
||||||
|
}
|
|
@ -15,11 +15,9 @@ use block::BlockFlow;
|
||||||
use canvas_traits::canvas::{CanvasMsg, FromLayoutMsg};
|
use canvas_traits::canvas::{CanvasMsg, FromLayoutMsg};
|
||||||
use context::LayoutContext;
|
use context::LayoutContext;
|
||||||
use display_list::ToLayout;
|
use display_list::ToLayout;
|
||||||
use display_list::background::{build_border_radius, calculate_inner_border_radii};
|
use display_list::background::{self, get_cyclic};
|
||||||
use display_list::background::{calculate_border_image_outset, calculate_border_image_width};
|
use display_list::border;
|
||||||
use display_list::background::{compute_background_clip, compute_background_placement};
|
use display_list::gradient;
|
||||||
use display_list::background::{convert_linear_gradient, convert_radial_gradient, get_cyclic};
|
|
||||||
use display_list::background::{simple_normal_border, ResolvePercentage};
|
|
||||||
use display_list::items::{BaseDisplayItem, BLUR_INFLATION_FACTOR, ClipScrollNode};
|
use display_list::items::{BaseDisplayItem, BLUR_INFLATION_FACTOR, ClipScrollNode};
|
||||||
use display_list::items::{ClipScrollNodeIndex, ClipScrollNodeType, ClippingAndScrolling};
|
use display_list::items::{ClipScrollNodeIndex, ClipScrollNodeType, ClippingAndScrolling};
|
||||||
use display_list::items::{ClippingRegion, DisplayItem, DisplayItemMetadata, DisplayList};
|
use display_list::items::{ClippingRegion, DisplayItem, DisplayItemMetadata, DisplayList};
|
||||||
|
@ -810,7 +808,7 @@ fn build_border_radius_for_inner_rect(
|
||||||
outer_rect: Rect<Au>,
|
outer_rect: Rect<Au>,
|
||||||
style: &ComputedValues,
|
style: &ComputedValues,
|
||||||
) -> BorderRadius {
|
) -> BorderRadius {
|
||||||
let radii = build_border_radius(outer_rect, style.get_border());
|
let radii = border::radii(outer_rect, style.get_border());
|
||||||
if radii.is_zero() {
|
if radii.is_zero() {
|
||||||
return radii;
|
return radii;
|
||||||
}
|
}
|
||||||
|
@ -819,7 +817,7 @@ fn build_border_radius_for_inner_rect(
|
||||||
// border width), we need to adjust to border radius so that we are smaller
|
// border width), we need to adjust to border radius so that we are smaller
|
||||||
// rectangle with the same border curve.
|
// rectangle with the same border curve.
|
||||||
let border_widths = style.logical_border_width().to_physical(style.writing_mode);
|
let border_widths = style.logical_border_width().to_physical(style.writing_mode);
|
||||||
calculate_inner_border_radii(radii, border_widths)
|
border::inner_radii(radii, border_widths)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FragmentDisplayListBuilding for Fragment {
|
impl FragmentDisplayListBuilding for Fragment {
|
||||||
|
@ -921,12 +919,12 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
// > with the bottom-most background image layer.
|
// > with the bottom-most background image layer.
|
||||||
let last_background_image_index = background.background_image.0.len() - 1;
|
let last_background_image_index = background.background_image.0.len() - 1;
|
||||||
let color_clip = *get_cyclic(&background.background_clip.0, last_background_image_index);
|
let color_clip = *get_cyclic(&background.background_clip.0, last_background_image_index);
|
||||||
let (bounds, border_radii) = compute_background_clip(
|
let (bounds, border_radii) = background::clip(
|
||||||
color_clip,
|
color_clip,
|
||||||
absolute_bounds,
|
absolute_bounds,
|
||||||
style.logical_border_width().to_physical(style.writing_mode),
|
style.logical_border_width().to_physical(style.writing_mode),
|
||||||
self.border_padding.to_physical(self.style.writing_mode),
|
self.border_padding.to_physical(self.style.writing_mode),
|
||||||
build_border_radius(absolute_bounds, style.get_border()),
|
border::radii(absolute_bounds, style.get_border()),
|
||||||
);
|
);
|
||||||
|
|
||||||
state.clipping_and_scrolling_scope(|state| {
|
state.clipping_and_scrolling_scope(|state| {
|
||||||
|
@ -1045,14 +1043,14 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
Au::from_px(webrender_image.width as i32),
|
Au::from_px(webrender_image.width as i32),
|
||||||
Au::from_px(webrender_image.height as i32),
|
Au::from_px(webrender_image.height as i32),
|
||||||
);
|
);
|
||||||
let placement = compute_background_placement(
|
let placement = background::placement(
|
||||||
style.get_background(),
|
style.get_background(),
|
||||||
state.layout_context.shared_context().viewport_size(),
|
state.layout_context.shared_context().viewport_size(),
|
||||||
absolute_bounds,
|
absolute_bounds,
|
||||||
Some(image),
|
Some(image),
|
||||||
style.logical_border_width().to_physical(style.writing_mode),
|
style.logical_border_width().to_physical(style.writing_mode),
|
||||||
self.border_padding.to_physical(self.style.writing_mode),
|
self.border_padding.to_physical(self.style.writing_mode),
|
||||||
build_border_radius(absolute_bounds, style.get_border()),
|
border::radii(absolute_bounds, style.get_border()),
|
||||||
index,
|
index,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1156,14 +1154,14 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
style: &ComputedValues,
|
style: &ComputedValues,
|
||||||
index: usize,
|
index: usize,
|
||||||
) {
|
) {
|
||||||
let placement = compute_background_placement(
|
let placement = background::placement(
|
||||||
style.get_background(),
|
style.get_background(),
|
||||||
state.layout_context.shared_context().viewport_size(),
|
state.layout_context.shared_context().viewport_size(),
|
||||||
absolute_bounds,
|
absolute_bounds,
|
||||||
None,
|
None,
|
||||||
style.logical_border_width().to_physical(style.writing_mode),
|
style.logical_border_width().to_physical(style.writing_mode),
|
||||||
self.border_padding.to_physical(self.style.writing_mode),
|
self.border_padding.to_physical(self.style.writing_mode),
|
||||||
build_border_radius(absolute_bounds, style.get_border()),
|
border::radii(absolute_bounds, style.get_border()),
|
||||||
index,
|
index,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1184,7 +1182,7 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
|
|
||||||
let display_item = match gradient.kind {
|
let display_item = match gradient.kind {
|
||||||
GradientKind::Linear(angle_or_corner) => {
|
GradientKind::Linear(angle_or_corner) => {
|
||||||
let (gradient, stops) = convert_linear_gradient(
|
let (gradient, stops) = gradient::linear(
|
||||||
style,
|
style,
|
||||||
placement.tile_size,
|
placement.tile_size,
|
||||||
&gradient.items[..],
|
&gradient.items[..],
|
||||||
|
@ -1199,7 +1197,7 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
DisplayItem::Gradient(CommonDisplayItem::with_data(base, item, stops))
|
DisplayItem::Gradient(CommonDisplayItem::with_data(base, item, stops))
|
||||||
},
|
},
|
||||||
GradientKind::Radial(shape, center, _angle) => {
|
GradientKind::Radial(shape, center, _angle) => {
|
||||||
let (gradient, stops) = convert_radial_gradient(
|
let (gradient, stops) = gradient::radial(
|
||||||
style,
|
style,
|
||||||
placement.tile_size,
|
placement.tile_size,
|
||||||
&gradient.items[..],
|
&gradient.items[..],
|
||||||
|
@ -1245,7 +1243,7 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
style.get_cursor(CursorKind::Default),
|
style.get_cursor(CursorKind::Default),
|
||||||
display_list_section,
|
display_list_section,
|
||||||
);
|
);
|
||||||
let border_radius = build_border_radius(absolute_bounds, style.get_border());
|
let border_radius = border::radii(absolute_bounds, style.get_border());
|
||||||
state.add_display_item(DisplayItem::BoxShadow(CommonDisplayItem::new(
|
state.add_display_item(DisplayItem::BoxShadow(CommonDisplayItem::new(
|
||||||
base,
|
base,
|
||||||
webrender_api::BoxShadowDisplayItem {
|
webrender_api::BoxShadowDisplayItem {
|
||||||
|
@ -1328,7 +1326,7 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
display_list_section,
|
display_list_section,
|
||||||
);
|
);
|
||||||
|
|
||||||
let border_radius = build_border_radius(bounds, border_style_struct);
|
let border_radius = border::radii(bounds, border_style_struct);
|
||||||
let border_widths = border.to_physical(style.writing_mode);
|
let border_widths = border.to_physical(style.writing_mode);
|
||||||
|
|
||||||
if let Either::Second(ref image) = border_style_struct.border_image_source {
|
if let Either::Second(ref image) = border_style_struct.border_image_source {
|
||||||
|
@ -1390,9 +1388,9 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let border_style_struct = style.get_border();
|
let border_style_struct = style.get_border();
|
||||||
let border_image_outset =
|
let border_image_outset =
|
||||||
calculate_border_image_outset(border_style_struct.border_image_outset, border_width);
|
border::image_outset(border_style_struct.border_image_outset, border_width);
|
||||||
let border_image_area = bounds.outer_rect(border_image_outset).size;
|
let border_image_area = bounds.outer_rect(border_image_outset).size;
|
||||||
let border_image_width = calculate_border_image_width(
|
let border_image_width = border::image_width(
|
||||||
&border_style_struct.border_image_width,
|
&border_style_struct.border_image_width,
|
||||||
border_width.to_layout(),
|
border_width.to_layout(),
|
||||||
border_image_area,
|
border_image_area,
|
||||||
|
@ -1429,7 +1427,7 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
},
|
},
|
||||||
Image::Gradient(ref gradient) => match gradient.kind {
|
Image::Gradient(ref gradient) => match gradient.kind {
|
||||||
GradientKind::Linear(angle_or_corner) => {
|
GradientKind::Linear(angle_or_corner) => {
|
||||||
let (wr_gradient, linear_stops) = convert_linear_gradient(
|
let (wr_gradient, linear_stops) = gradient::linear(
|
||||||
style,
|
style,
|
||||||
border_image_area,
|
border_image_area,
|
||||||
&gradient.items[..],
|
&gradient.items[..],
|
||||||
|
@ -1440,7 +1438,7 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
NinePatchBorderSource::Gradient(wr_gradient)
|
NinePatchBorderSource::Gradient(wr_gradient)
|
||||||
},
|
},
|
||||||
GradientKind::Radial(shape, center, _angle) => {
|
GradientKind::Radial(shape, center, _angle) => {
|
||||||
let (wr_gradient, radial_stops) = convert_radial_gradient(
|
let (wr_gradient, radial_stops) = gradient::radial(
|
||||||
style,
|
style,
|
||||||
border_image_area,
|
border_image_area,
|
||||||
&gradient.items[..],
|
&gradient.items[..],
|
||||||
|
@ -1459,12 +1457,7 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
source,
|
source,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
slice: SideOffsets2D::new(
|
slice: border::image_slice(border_image_slice, width, height),
|
||||||
border_image_slice.0.resolve(height),
|
|
||||||
border_image_slice.1.resolve(width),
|
|
||||||
border_image_slice.2.resolve(height),
|
|
||||||
border_image_slice.3.resolve(width),
|
|
||||||
),
|
|
||||||
fill: border_image_fill,
|
fill: border_image_fill,
|
||||||
repeat_horizontal: border_image_repeat.0.to_layout(),
|
repeat_horizontal: border_image_repeat.0.to_layout(),
|
||||||
repeat_vertical: border_image_repeat.1.to_layout(),
|
repeat_vertical: border_image_repeat.1.to_layout(),
|
||||||
|
@ -1526,7 +1519,7 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
base,
|
base,
|
||||||
webrender_api::BorderDisplayItem {
|
webrender_api::BorderDisplayItem {
|
||||||
widths: SideOffsets2D::new_all_same(width).to_layout(),
|
widths: SideOffsets2D::new_all_same(width).to_layout(),
|
||||||
details: BorderDetails::Normal(simple_normal_border(
|
details: BorderDetails::Normal(border::simple(
|
||||||
color,
|
color,
|
||||||
outline_style.to_layout(),
|
outline_style.to_layout(),
|
||||||
)),
|
)),
|
||||||
|
@ -1559,7 +1552,7 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
base,
|
base,
|
||||||
webrender_api::BorderDisplayItem {
|
webrender_api::BorderDisplayItem {
|
||||||
widths: SideOffsets2D::new_all_same(Au::from_px(1)).to_layout(),
|
widths: SideOffsets2D::new_all_same(Au::from_px(1)).to_layout(),
|
||||||
details: BorderDetails::Normal(simple_normal_border(
|
details: BorderDetails::Normal(border::simple(
|
||||||
ColorF::rgb(0, 0, 200),
|
ColorF::rgb(0, 0, 200),
|
||||||
webrender_api::BorderStyle::Solid,
|
webrender_api::BorderStyle::Solid,
|
||||||
)),
|
)),
|
||||||
|
@ -1609,7 +1602,7 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
base,
|
base,
|
||||||
webrender_api::BorderDisplayItem {
|
webrender_api::BorderDisplayItem {
|
||||||
widths: SideOffsets2D::new_all_same(Au::from_px(1)).to_layout(),
|
widths: SideOffsets2D::new_all_same(Au::from_px(1)).to_layout(),
|
||||||
details: BorderDetails::Normal(simple_normal_border(
|
details: BorderDetails::Normal(border::simple(
|
||||||
ColorF::rgb(0, 0, 200),
|
ColorF::rgb(0, 0, 200),
|
||||||
webrender_api::BorderStyle::Solid,
|
webrender_api::BorderStyle::Solid,
|
||||||
)),
|
)),
|
||||||
|
@ -3212,7 +3205,7 @@ impl BaseFlowDisplayListBuilding for BaseFlow {
|
||||||
base,
|
base,
|
||||||
webrender_api::BorderDisplayItem {
|
webrender_api::BorderDisplayItem {
|
||||||
widths: SideOffsets2D::new_all_same(Au::from_px(2)).to_layout(),
|
widths: SideOffsets2D::new_all_same(Au::from_px(2)).to_layout(),
|
||||||
details: BorderDetails::Normal(simple_normal_border(
|
details: BorderDetails::Normal(border::simple(
|
||||||
color,
|
color,
|
||||||
webrender_api::BorderStyle::Solid,
|
webrender_api::BorderStyle::Solid,
|
||||||
)),
|
)),
|
||||||
|
|
314
components/layout/display_list/gradient.rs
Normal file
314
components/layout/display_list/gradient.rs
Normal file
|
@ -0,0 +1,314 @@
|
||||||
|
// FIXME(rust-lang/rust#26264): Remove GenericEndingShape and GenericGradientItem.
|
||||||
|
|
||||||
|
use app_units::Au;
|
||||||
|
use display_list::ToLayout;
|
||||||
|
use euclid::{Point2D, Size2D, Vector2D};
|
||||||
|
use style::properties::ComputedValues;
|
||||||
|
use style::values::computed::{Angle, GradientItem, LengthOrPercentage, Percentage, Position};
|
||||||
|
use style::values::computed::image::{EndingShape, LineDirection};
|
||||||
|
use style::values::generics::image::{Circle, Ellipse, ShapeExtent};
|
||||||
|
use style::values::generics::image::EndingShape as GenericEndingShape;
|
||||||
|
use style::values::generics::image::GradientItem as GenericGradientItem;
|
||||||
|
use style::values::specified::position::{X, Y};
|
||||||
|
use webrender_api::{ExtendMode, Gradient, GradientBuilder, GradientStop, RadialGradient};
|
||||||
|
|
||||||
|
/// A helper data structure for gradients.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct StopRun {
|
||||||
|
start_offset: f32,
|
||||||
|
end_offset: f32,
|
||||||
|
start_index: usize,
|
||||||
|
stop_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines the radius of a circle if it was not explictly provided.
|
||||||
|
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
|
||||||
|
fn circle_size_keyword(
|
||||||
|
keyword: ShapeExtent,
|
||||||
|
size: &Size2D<Au>,
|
||||||
|
center: &Point2D<Au>,
|
||||||
|
) -> Size2D<Au> {
|
||||||
|
let radius = match keyword {
|
||||||
|
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
|
||||||
|
let dist = distance_to_sides(size, center, ::std::cmp::min);
|
||||||
|
::std::cmp::min(dist.width, dist.height)
|
||||||
|
},
|
||||||
|
ShapeExtent::FarthestSide => {
|
||||||
|
let dist = distance_to_sides(size, center, ::std::cmp::max);
|
||||||
|
::std::cmp::max(dist.width, dist.height)
|
||||||
|
},
|
||||||
|
ShapeExtent::ClosestCorner => distance_to_corner(size, center, ::std::cmp::min),
|
||||||
|
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
|
||||||
|
distance_to_corner(size, center, ::std::cmp::max)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Size2D::new(radius, radius)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the radius for an ellipse with the same ratio as if it was matched to the sides.
|
||||||
|
fn ellipse_radius<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
|
||||||
|
where
|
||||||
|
F: Fn(Au, Au) -> Au,
|
||||||
|
{
|
||||||
|
let dist = distance_to_sides(size, center, cmp);
|
||||||
|
Size2D::new(
|
||||||
|
dist.width.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
|
||||||
|
dist.height
|
||||||
|
.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines the radius of an ellipse if it was not explictly provided.
|
||||||
|
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
|
||||||
|
fn ellipse_size_keyword(
|
||||||
|
keyword: ShapeExtent,
|
||||||
|
size: &Size2D<Au>,
|
||||||
|
center: &Point2D<Au>,
|
||||||
|
) -> Size2D<Au> {
|
||||||
|
match keyword {
|
||||||
|
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
|
||||||
|
distance_to_sides(size, center, ::std::cmp::min)
|
||||||
|
},
|
||||||
|
ShapeExtent::FarthestSide => distance_to_sides(size, center, ::std::cmp::max),
|
||||||
|
ShapeExtent::ClosestCorner => ellipse_radius(size, center, ::std::cmp::min),
|
||||||
|
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
|
||||||
|
ellipse_radius(size, center, ::std::cmp::max)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_gradient_stops(
|
||||||
|
style: &ComputedValues,
|
||||||
|
gradient_items: &[GradientItem],
|
||||||
|
total_length: Au,
|
||||||
|
) -> GradientBuilder {
|
||||||
|
// Determine the position of each stop per CSS-IMAGES § 3.4.
|
||||||
|
|
||||||
|
// Only keep the color stops, discard the color interpolation hints.
|
||||||
|
let mut stop_items = gradient_items
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| match *item {
|
||||||
|
GenericGradientItem::ColorStop(ref stop) => Some(*stop),
|
||||||
|
_ => None,
|
||||||
|
}).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert!(stop_items.len() >= 2);
|
||||||
|
|
||||||
|
// Run the algorithm from
|
||||||
|
// https://drafts.csswg.org/css-images-3/#color-stop-syntax
|
||||||
|
|
||||||
|
// Step 1:
|
||||||
|
// If the first color stop does not have a position, set its position to 0%.
|
||||||
|
{
|
||||||
|
let first = stop_items.first_mut().unwrap();
|
||||||
|
if first.position.is_none() {
|
||||||
|
first.position = Some(LengthOrPercentage::Percentage(Percentage(0.0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the last color stop does not have a position, set its position to 100%.
|
||||||
|
{
|
||||||
|
let last = stop_items.last_mut().unwrap();
|
||||||
|
if last.position.is_none() {
|
||||||
|
last.position = Some(LengthOrPercentage::Percentage(Percentage(1.0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Move any stops placed before earlier stops to the
|
||||||
|
// same position as the preceding stop.
|
||||||
|
let mut last_stop_position = stop_items.first().unwrap().position.unwrap();
|
||||||
|
for stop in stop_items.iter_mut().skip(1) {
|
||||||
|
if let Some(pos) = stop.position {
|
||||||
|
if position_to_offset(last_stop_position, total_length) >
|
||||||
|
position_to_offset(pos, total_length)
|
||||||
|
{
|
||||||
|
stop.position = Some(last_stop_position);
|
||||||
|
}
|
||||||
|
last_stop_position = stop.position.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Evenly space stops without position.
|
||||||
|
let mut stops = GradientBuilder::new();
|
||||||
|
let mut stop_run = None;
|
||||||
|
for (i, stop) in stop_items.iter().enumerate() {
|
||||||
|
let offset = match stop.position {
|
||||||
|
None => {
|
||||||
|
if stop_run.is_none() {
|
||||||
|
// Initialize a new stop run.
|
||||||
|
// `unwrap()` here should never fail because this is the beginning of
|
||||||
|
// a stop run, which is always bounded by a length or percentage.
|
||||||
|
let start_offset =
|
||||||
|
position_to_offset(stop_items[i - 1].position.unwrap(), total_length);
|
||||||
|
// `unwrap()` here should never fail because this is the end of
|
||||||
|
// a stop run, which is always bounded by a length or percentage.
|
||||||
|
let (end_index, end_stop) = stop_items[(i + 1)..]
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|&(_, ref stop)| stop.position.is_some())
|
||||||
|
.unwrap();
|
||||||
|
let end_offset = position_to_offset(end_stop.position.unwrap(), total_length);
|
||||||
|
stop_run = Some(StopRun {
|
||||||
|
start_offset,
|
||||||
|
end_offset,
|
||||||
|
start_index: i - 1,
|
||||||
|
stop_count: end_index,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let stop_run = stop_run.unwrap();
|
||||||
|
let stop_run_length = stop_run.end_offset - stop_run.start_offset;
|
||||||
|
stop_run.start_offset +
|
||||||
|
stop_run_length * (i - stop_run.start_index) as f32 /
|
||||||
|
((2 + stop_run.stop_count) as f32)
|
||||||
|
},
|
||||||
|
Some(position) => {
|
||||||
|
stop_run = None;
|
||||||
|
position_to_offset(position, total_length)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert!(offset.is_finite());
|
||||||
|
stops.push(GradientStop {
|
||||||
|
offset: offset,
|
||||||
|
color: style.resolve_color(stop.color).to_layout(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
stops
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extend_mode(repeating: bool) -> ExtendMode {
|
||||||
|
if repeating {
|
||||||
|
ExtendMode::Repeat
|
||||||
|
} else {
|
||||||
|
ExtendMode::Clamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Returns the the distance to the nearest or farthest corner depending on the comperator.
|
||||||
|
fn distance_to_corner<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Au
|
||||||
|
where
|
||||||
|
F: Fn(Au, Au) -> Au,
|
||||||
|
{
|
||||||
|
let dist = distance_to_sides(size, center, cmp);
|
||||||
|
Au::from_f32_px(dist.width.to_f32_px().hypot(dist.height.to_f32_px()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the distance to the nearest or farthest sides depending on the comparator.
|
||||||
|
///
|
||||||
|
/// The first return value is horizontal distance the second vertical distance.
|
||||||
|
fn distance_to_sides<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
|
||||||
|
where
|
||||||
|
F: Fn(Au, Au) -> Au,
|
||||||
|
{
|
||||||
|
let top_side = center.y;
|
||||||
|
let right_side = size.width - center.x;
|
||||||
|
let bottom_side = size.height - center.y;
|
||||||
|
let left_side = center.x;
|
||||||
|
Size2D::new(cmp(left_side, right_side), cmp(top_side, bottom_side))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn position_to_offset(position: LengthOrPercentage, total_length: Au) -> f32 {
|
||||||
|
if total_length == Au(0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
match position {
|
||||||
|
LengthOrPercentage::Length(l) => l.to_i32_au() as f32 / total_length.0 as f32,
|
||||||
|
LengthOrPercentage::Percentage(percentage) => percentage.0 as f32,
|
||||||
|
LengthOrPercentage::Calc(calc) => {
|
||||||
|
calc.to_used_value(Some(total_length)).unwrap().0 as f32 / total_length.0 as f32
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn linear(
|
||||||
|
style: &ComputedValues,
|
||||||
|
size: Size2D<Au>,
|
||||||
|
stops: &[GradientItem],
|
||||||
|
direction: LineDirection,
|
||||||
|
repeating: bool,
|
||||||
|
) -> (Gradient, Vec<GradientStop>) {
|
||||||
|
let angle = match direction {
|
||||||
|
LineDirection::Angle(angle) => angle.radians(),
|
||||||
|
LineDirection::Horizontal(x) => match x {
|
||||||
|
X::Left => Angle::from_degrees(270.).radians(),
|
||||||
|
X::Right => Angle::from_degrees(90.).radians(),
|
||||||
|
},
|
||||||
|
LineDirection::Vertical(y) => match y {
|
||||||
|
Y::Top => Angle::from_degrees(0.).radians(),
|
||||||
|
Y::Bottom => Angle::from_degrees(180.).radians(),
|
||||||
|
},
|
||||||
|
LineDirection::Corner(horizontal, vertical) => {
|
||||||
|
// This the angle for one of the diagonals of the box. Our angle
|
||||||
|
// will either be this one, this one + PI, or one of the other
|
||||||
|
// two perpendicular angles.
|
||||||
|
let atan = (size.height.to_f32_px() / size.width.to_f32_px()).atan();
|
||||||
|
match (horizontal, vertical) {
|
||||||
|
(X::Right, Y::Bottom) => ::std::f32::consts::PI - atan,
|
||||||
|
(X::Left, Y::Bottom) => ::std::f32::consts::PI + atan,
|
||||||
|
(X::Right, Y::Top) => atan,
|
||||||
|
(X::Left, Y::Top) => -atan,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get correct gradient line length, based on:
|
||||||
|
// https://drafts.csswg.org/css-images-3/#linear-gradients
|
||||||
|
let dir = Point2D::new(angle.sin(), -angle.cos());
|
||||||
|
|
||||||
|
let line_length =
|
||||||
|
(dir.x * size.width.to_f32_px()).abs() + (dir.y * size.height.to_f32_px()).abs();
|
||||||
|
|
||||||
|
let inv_dir_length = 1.0 / (dir.x * dir.x + dir.y * dir.y).sqrt();
|
||||||
|
|
||||||
|
// This is the vector between the center and the ending point; i.e. half
|
||||||
|
// of the distance between the starting point and the ending point.
|
||||||
|
let delta = Vector2D::new(
|
||||||
|
Au::from_f32_px(dir.x * inv_dir_length * line_length / 2.0),
|
||||||
|
Au::from_f32_px(dir.y * inv_dir_length * line_length / 2.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
// This is the length of the gradient line.
|
||||||
|
let length = Au::from_f32_px((delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0));
|
||||||
|
|
||||||
|
let mut builder = convert_gradient_stops(style, stops, length);
|
||||||
|
|
||||||
|
let center = Point2D::new(size.width / 2, size.height / 2);
|
||||||
|
|
||||||
|
(
|
||||||
|
builder.gradient((center - delta).to_layout(), (center + delta).to_layout(), extend_mode(repeating)),
|
||||||
|
builder.stops().to_vec()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn radial(
|
||||||
|
style: &ComputedValues,
|
||||||
|
size: Size2D<Au>,
|
||||||
|
stops: &[GradientItem],
|
||||||
|
shape: EndingShape,
|
||||||
|
center: Position,
|
||||||
|
repeating: bool,
|
||||||
|
) -> (RadialGradient, Vec<GradientStop>) {
|
||||||
|
let center = Point2D::new(
|
||||||
|
center.horizontal.to_used_value(size.width),
|
||||||
|
center.vertical.to_used_value(size.height),
|
||||||
|
);
|
||||||
|
let radius = match shape {
|
||||||
|
GenericEndingShape::Circle(Circle::Radius(length)) => {
|
||||||
|
let length = Au::from(length);
|
||||||
|
Size2D::new(length, length)
|
||||||
|
},
|
||||||
|
GenericEndingShape::Circle(Circle::Extent(extent)) => {
|
||||||
|
circle_size_keyword(extent, &size, ¢er)
|
||||||
|
},
|
||||||
|
GenericEndingShape::Ellipse(Ellipse::Radii(x, y)) => {
|
||||||
|
Size2D::new(x.to_used_value(size.width), y.to_used_value(size.height))
|
||||||
|
},
|
||||||
|
GenericEndingShape::Ellipse(Ellipse::Extent(extent)) => {
|
||||||
|
ellipse_size_keyword(extent, &size, ¢er)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut builder = convert_gradient_stops(style, stops, radius.width);
|
||||||
|
(
|
||||||
|
builder.radial_gradient(center.to_layout(), radius.to_layout(), extend_mode(repeating)),
|
||||||
|
builder.stops().to_vec(),
|
||||||
|
)
|
||||||
|
}
|
|
@ -15,7 +15,9 @@ pub use self::conversions::ToLayout;
|
||||||
pub use self::webrender_helpers::WebRenderDisplayListConverter;
|
pub use self::webrender_helpers::WebRenderDisplayListConverter;
|
||||||
|
|
||||||
mod background;
|
mod background;
|
||||||
|
mod border;
|
||||||
mod builder;
|
mod builder;
|
||||||
mod conversions;
|
mod conversions;
|
||||||
|
mod gradient;
|
||||||
pub mod items;
|
pub mod items;
|
||||||
mod webrender_helpers;
|
mod webrender_helpers;
|
||||||
|
|
|
@ -11,7 +11,7 @@ use display_list::items::{ClipScrollNode, ClipScrollNodeIndex, ClipScrollNodeTyp
|
||||||
use display_list::items::{DisplayItem, DisplayList, StackingContextType};
|
use display_list::items::{DisplayItem, DisplayList, StackingContextType};
|
||||||
use msg::constellation_msg::PipelineId;
|
use msg::constellation_msg::PipelineId;
|
||||||
use webrender_api::{self, ClipAndScrollInfo, ClipId, DisplayListBuilder, RasterSpace};
|
use webrender_api::{self, ClipAndScrollInfo, ClipId, DisplayListBuilder, RasterSpace};
|
||||||
use webrender_api::LayoutPoint;
|
use webrender_api::{LayoutPoint, SpecificDisplayItem};
|
||||||
|
|
||||||
pub trait WebRenderDisplayListConverter {
|
pub trait WebRenderDisplayListConverter {
|
||||||
fn convert_to_webrender(&self, pipeline_id: PipelineId) -> DisplayListBuilder;
|
fn convert_to_webrender(&self, pipeline_id: PipelineId) -> DisplayListBuilder;
|
||||||
|
@ -104,94 +104,28 @@ impl WebRenderDisplayItemConverter for DisplayItem {
|
||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
DisplayItem::Rectangle(ref item) => {
|
DisplayItem::Rectangle(ref item) => {
|
||||||
builder.push_rect(&self.prim_info(), item.item.color);
|
builder.push_item(SpecificDisplayItem::Rectangle(item.item), &self.prim_info());
|
||||||
},
|
},
|
||||||
DisplayItem::Text(ref item) => {
|
DisplayItem::Text(ref item) => {
|
||||||
builder.push_text(
|
builder.push_item(SpecificDisplayItem::Text(item.item), &self.prim_info());
|
||||||
&self.prim_info(),
|
builder.push_iter(item.data.iter());
|
||||||
&item.data,
|
|
||||||
item.item.font_key,
|
|
||||||
item.item.color,
|
|
||||||
item.item.glyph_options,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
DisplayItem::Image(ref item) => {
|
DisplayItem::Image(ref item) => {
|
||||||
builder.push_image(
|
builder.push_item(SpecificDisplayItem::Image(item.item), &self.prim_info());
|
||||||
&self.prim_info(),
|
|
||||||
item.item.stretch_size,
|
|
||||||
item.item.tile_spacing,
|
|
||||||
item.item.image_rendering,
|
|
||||||
item.item.alpha_type,
|
|
||||||
item.item.image_key,
|
|
||||||
item.item.color,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
DisplayItem::Border(ref item) => {
|
DisplayItem::Border(ref item) => {
|
||||||
if item.data.is_empty() {
|
if !item.data.is_empty() {
|
||||||
builder.push_border(&self.prim_info(), item.item.widths, item.item.details);
|
builder.push_iter(item.data.iter());
|
||||||
} else {
|
|
||||||
let mut details = item.item.details.clone();
|
|
||||||
match &mut details {
|
|
||||||
webrender_api::BorderDetails::NinePatch(
|
|
||||||
webrender_api::NinePatchBorder {
|
|
||||||
source:
|
|
||||||
webrender_api::NinePatchBorderSource::Gradient(ref mut gradient),
|
|
||||||
..
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
*gradient = builder.create_gradient(
|
|
||||||
gradient.start_point,
|
|
||||||
gradient.end_point,
|
|
||||||
item.data.clone(),
|
|
||||||
gradient.extend_mode,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
webrender_api::BorderDetails::NinePatch(
|
|
||||||
webrender_api::NinePatchBorder {
|
|
||||||
source:
|
|
||||||
webrender_api::NinePatchBorderSource::RadialGradient(gradient),
|
|
||||||
..
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
*gradient = builder.create_radial_gradient(
|
|
||||||
gradient.center,
|
|
||||||
gradient.radius,
|
|
||||||
item.data.clone(),
|
|
||||||
gradient.extend_mode,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
builder.push_border(&self.prim_info(), item.item.widths, details);
|
|
||||||
}
|
}
|
||||||
|
builder.push_item(SpecificDisplayItem::Border(item.item), &self.prim_info());
|
||||||
},
|
},
|
||||||
DisplayItem::Gradient(ref item) => {
|
DisplayItem::Gradient(ref item) => {
|
||||||
let gradient = builder.create_gradient(
|
builder.push_iter(item.data.iter());
|
||||||
item.item.gradient.start_point,
|
builder.push_item(SpecificDisplayItem::Gradient(item.item), &self.prim_info());
|
||||||
item.item.gradient.end_point,
|
|
||||||
item.data.clone(),
|
|
||||||
item.item.gradient.extend_mode,
|
|
||||||
);
|
|
||||||
builder.push_gradient(
|
|
||||||
&self.prim_info(),
|
|
||||||
gradient,
|
|
||||||
item.item.tile_size,
|
|
||||||
item.item.tile_spacing,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
DisplayItem::RadialGradient(ref item) => {
|
DisplayItem::RadialGradient(ref item) => {
|
||||||
let gradient = builder.create_radial_gradient(
|
builder.push_iter(item.data.iter());
|
||||||
item.item.gradient.center,
|
builder.push_item(SpecificDisplayItem::RadialGradient(item.item), &self.prim_info());
|
||||||
item.item.gradient.radius,
|
|
||||||
item.data.clone(),
|
|
||||||
item.item.gradient.extend_mode,
|
|
||||||
);
|
|
||||||
builder.push_radial_gradient(
|
|
||||||
&self.prim_info(),
|
|
||||||
gradient,
|
|
||||||
item.item.tile_size,
|
|
||||||
item.item.tile_spacing,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
DisplayItem::Line(ref item) => {
|
DisplayItem::Line(ref item) => {
|
||||||
builder.push_line(
|
builder.push_line(
|
||||||
|
@ -204,16 +138,7 @@ impl WebRenderDisplayItemConverter for DisplayItem {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
DisplayItem::BoxShadow(ref item) => {
|
DisplayItem::BoxShadow(ref item) => {
|
||||||
builder.push_box_shadow(
|
builder.push_item(SpecificDisplayItem::BoxShadow(item.item), &self.prim_info());
|
||||||
&self.prim_info(),
|
|
||||||
item.item.box_bounds,
|
|
||||||
item.item.offset,
|
|
||||||
item.item.color,
|
|
||||||
item.item.blur_radius,
|
|
||||||
item.item.spread_radius,
|
|
||||||
item.item.border_radius,
|
|
||||||
item.item.clip_mode,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
DisplayItem::PushTextShadow(ref item) => {
|
DisplayItem::PushTextShadow(ref item) => {
|
||||||
builder.push_shadow(&self.prim_info(), item.shadow);
|
builder.push_shadow(&self.prim_info(), item.shadow);
|
||||||
|
|
|
@ -7,14 +7,14 @@
|
||||||
#![deny(unsafe_code)]
|
#![deny(unsafe_code)]
|
||||||
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use euclid::{SideOffsets2D, Size2D};
|
use euclid::SideOffsets2D;
|
||||||
use fragment::Fragment;
|
use fragment::Fragment;
|
||||||
use std::cmp::{max, min};
|
use std::cmp::{max, min};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use style::logical_geometry::{LogicalMargin, WritingMode};
|
use style::logical_geometry::{LogicalMargin, WritingMode};
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use style::values::computed::{BorderCornerRadius, LengthOrPercentageOrAuto};
|
use style::values::computed::{LengthOrPercentageOrAuto, LengthOrPercentage};
|
||||||
use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrNone};
|
use style::values::computed::LengthOrPercentageOrNone;
|
||||||
|
|
||||||
/// A collapsible margin. See CSS 2.1 § 8.3.1.
|
/// A collapsible margin. See CSS 2.1 § 8.3.1.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
@ -515,23 +515,6 @@ pub fn style_length(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes a border radius size against the containing size.
|
|
||||||
///
|
|
||||||
/// Note that percentages in `border-radius` are resolved against the relevant
|
|
||||||
/// box dimension instead of only against the width per [1]:
|
|
||||||
///
|
|
||||||
/// > Percentages: Refer to corresponding dimension of the border box.
|
|
||||||
///
|
|
||||||
/// [1]: https://drafts.csswg.org/css-backgrounds-3/#border-radius
|
|
||||||
pub fn specified_border_radius(
|
|
||||||
radius: BorderCornerRadius,
|
|
||||||
containing_size: Size2D<Au>,
|
|
||||||
) -> Size2D<Au> {
|
|
||||||
let w = radius.0.width().to_used_value(containing_size.width);
|
|
||||||
let h = radius.0.height().to_used_value(containing_size.height);
|
|
||||||
Size2D::new(w, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn padding_from_style(
|
pub fn padding_from_style(
|
||||||
style: &ComputedValues,
|
style: &ComputedValues,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue