mirror of
https://github.com/servo/servo.git
synced 2025-07-12 18:03:49 +01:00
In stylo, ComputedUrl and SpecifiedUrl happen to be the same. However, using ComputedUrl can make code clearer that conversion.rs is for converting computed values between gecko and servo types.
973 lines
44 KiB
Rust
973 lines
44 KiB
Rust
/* 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/. */
|
|
|
|
//! This module contains conversion helpers between Servo and Gecko types
|
|
//! Ideally, it would be in geckolib itself, but coherence
|
|
//! forces us to keep the traits and implementations here
|
|
|
|
#![allow(unsafe_code)]
|
|
|
|
use app_units::Au;
|
|
use gecko::values::{convert_rgba_to_nscolor, GeckoStyleCoordConvertible};
|
|
use gecko_bindings::bindings::{Gecko_CreateGradient, Gecko_SetGradientImageValue, Gecko_SetLayerImageImageValue};
|
|
use gecko_bindings::bindings::{Gecko_InitializeImageCropRect, Gecko_SetImageElement};
|
|
use gecko_bindings::structs::{self, nsCSSUnit, nsStyleCoord_CalcValue};
|
|
use gecko_bindings::structs::{nsStyleImage, nsresult, SheetType};
|
|
use gecko_bindings::sugar::ns_style_coord::{CoordDataValue, CoordData, CoordDataMut};
|
|
use std::f32::consts::PI;
|
|
use stylesheets::{Origin, RulesMutateError};
|
|
use values::computed::{Angle, CalcLengthOrPercentage, ComputedUrl, Gradient, Image};
|
|
use values::computed::{Integer, LengthOrPercentage, LengthOrPercentageOrAuto, Percentage};
|
|
use values::generics::box_::VerticalAlign;
|
|
use values::generics::grid::{TrackListValue, TrackSize};
|
|
use values::generics::image::{CompatMode, Image as GenericImage, GradientItem};
|
|
use values::generics::rect::Rect;
|
|
|
|
impl From<CalcLengthOrPercentage> for nsStyleCoord_CalcValue {
|
|
fn from(other: CalcLengthOrPercentage) -> nsStyleCoord_CalcValue {
|
|
let has_percentage = other.percentage.is_some();
|
|
nsStyleCoord_CalcValue {
|
|
mLength: other.unclamped_length().to_i32_au(),
|
|
mPercent: other.percentage.map_or(0., |p| p.0),
|
|
mHasPercent: has_percentage,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<nsStyleCoord_CalcValue> for CalcLengthOrPercentage {
|
|
fn from(other: nsStyleCoord_CalcValue) -> CalcLengthOrPercentage {
|
|
let percentage = if other.mHasPercent {
|
|
Some(Percentage(other.mPercent))
|
|
} else {
|
|
None
|
|
};
|
|
Self::new(Au(other.mLength).into(), percentage)
|
|
}
|
|
}
|
|
|
|
impl From<LengthOrPercentage> for nsStyleCoord_CalcValue {
|
|
fn from(other: LengthOrPercentage) -> nsStyleCoord_CalcValue {
|
|
match other {
|
|
LengthOrPercentage::Length(px) => {
|
|
nsStyleCoord_CalcValue {
|
|
mLength: px.to_i32_au(),
|
|
mPercent: 0.0,
|
|
mHasPercent: false,
|
|
}
|
|
},
|
|
LengthOrPercentage::Percentage(pc) => {
|
|
nsStyleCoord_CalcValue {
|
|
mLength: 0,
|
|
mPercent: pc.0,
|
|
mHasPercent: true,
|
|
}
|
|
},
|
|
LengthOrPercentage::Calc(calc) => calc.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl LengthOrPercentageOrAuto {
|
|
/// Convert this value in an appropriate `nsStyleCoord::CalcValue`.
|
|
pub fn to_calc_value(&self) -> Option<nsStyleCoord_CalcValue> {
|
|
match *self {
|
|
LengthOrPercentageOrAuto::Length(px) => {
|
|
Some(nsStyleCoord_CalcValue {
|
|
mLength: px.to_i32_au(),
|
|
mPercent: 0.0,
|
|
mHasPercent: false,
|
|
})
|
|
},
|
|
LengthOrPercentageOrAuto::Percentage(pc) => {
|
|
Some(nsStyleCoord_CalcValue {
|
|
mLength: 0,
|
|
mPercent: pc.0,
|
|
mHasPercent: true,
|
|
})
|
|
},
|
|
LengthOrPercentageOrAuto::Calc(calc) => Some(calc.into()),
|
|
LengthOrPercentageOrAuto::Auto => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<nsStyleCoord_CalcValue> for LengthOrPercentage {
|
|
fn from(other: nsStyleCoord_CalcValue) -> LengthOrPercentage {
|
|
match (other.mHasPercent, other.mLength) {
|
|
(false, _) => LengthOrPercentage::Length(Au(other.mLength).into()),
|
|
(true, 0) => LengthOrPercentage::Percentage(Percentage(other.mPercent)),
|
|
_ => LengthOrPercentage::Calc(other.into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<nsStyleCoord_CalcValue> for LengthOrPercentageOrAuto {
|
|
fn from(other: nsStyleCoord_CalcValue) -> LengthOrPercentageOrAuto {
|
|
match (other.mHasPercent, other.mLength) {
|
|
(false, _) => LengthOrPercentageOrAuto::Length(Au(other.mLength).into()),
|
|
(true, 0) => LengthOrPercentageOrAuto::Percentage(Percentage(other.mPercent)),
|
|
_ => LengthOrPercentageOrAuto::Calc(other.into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Angle> for CoordDataValue {
|
|
fn from(reference: Angle) -> Self {
|
|
match reference {
|
|
Angle::Deg(val) => CoordDataValue::Degree(val),
|
|
Angle::Grad(val) => CoordDataValue::Grad(val),
|
|
Angle::Rad(val) => CoordDataValue::Radian(val),
|
|
Angle::Turn(val) => CoordDataValue::Turn(val),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Angle {
|
|
/// Converts Angle struct into (value, unit) pair.
|
|
pub fn to_gecko_values(&self) -> (f32, nsCSSUnit) {
|
|
match *self {
|
|
Angle::Deg(val) => (val, nsCSSUnit::eCSSUnit_Degree),
|
|
Angle::Grad(val) => (val, nsCSSUnit::eCSSUnit_Grad),
|
|
Angle::Rad(val) => (val, nsCSSUnit::eCSSUnit_Radian),
|
|
Angle::Turn(val) => (val, nsCSSUnit::eCSSUnit_Turn),
|
|
}
|
|
}
|
|
|
|
/// Converts gecko (value, unit) pair into Angle struct
|
|
pub fn from_gecko_values(value: f32, unit: nsCSSUnit) -> Angle {
|
|
match unit {
|
|
nsCSSUnit::eCSSUnit_Degree => Angle::Deg(value),
|
|
nsCSSUnit::eCSSUnit_Grad => Angle::Grad(value),
|
|
nsCSSUnit::eCSSUnit_Radian => Angle::Rad(value),
|
|
nsCSSUnit::eCSSUnit_Turn => Angle::Turn(value),
|
|
_ => panic!("Unexpected unit {:?} for angle", unit),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl nsStyleImage {
|
|
/// Set a given Servo `Image` value into this `nsStyleImage`.
|
|
pub fn set(&mut self, image: Image) {
|
|
match image {
|
|
GenericImage::Gradient(boxed_gradient) => {
|
|
self.set_gradient(*boxed_gradient)
|
|
},
|
|
GenericImage::Url(ref url) => {
|
|
unsafe {
|
|
Gecko_SetLayerImageImageValue(self, url.image_value.clone().unwrap().get());
|
|
}
|
|
},
|
|
GenericImage::Rect(ref image_rect) => {
|
|
unsafe {
|
|
Gecko_SetLayerImageImageValue(self, image_rect.url.image_value.clone().unwrap().get());
|
|
Gecko_InitializeImageCropRect(self);
|
|
|
|
// Set CropRect
|
|
let ref mut rect = *self.mCropRect.mPtr;
|
|
image_rect.top.to_gecko_style_coord(&mut rect.data_at_mut(0));
|
|
image_rect.right.to_gecko_style_coord(&mut rect.data_at_mut(1));
|
|
image_rect.bottom.to_gecko_style_coord(&mut rect.data_at_mut(2));
|
|
image_rect.left.to_gecko_style_coord(&mut rect.data_at_mut(3));
|
|
}
|
|
}
|
|
GenericImage::Element(ref element) => {
|
|
unsafe {
|
|
Gecko_SetImageElement(self, element.as_ptr());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn set_gradient(&mut self, gradient: Gradient) {
|
|
use gecko_bindings::structs::{NS_STYLE_GRADIENT_SHAPE_CIRCULAR, NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL};
|
|
use gecko_bindings::structs::{NS_STYLE_GRADIENT_SHAPE_LINEAR, NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER};
|
|
use gecko_bindings::structs::{NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE, NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE};
|
|
use gecko_bindings::structs::{NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER, NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE};
|
|
use gecko_bindings::structs::nsStyleCoord;
|
|
use values::computed::image::LineDirection;
|
|
use values::generics::image::{Circle, Ellipse, EndingShape, GradientKind, ShapeExtent};
|
|
use values::specified::position::{X, Y};
|
|
|
|
let stop_count = gradient.items.len();
|
|
if stop_count >= ::std::u32::MAX as usize {
|
|
warn!("stylo: Prevented overflow due to too many gradient stops");
|
|
return;
|
|
}
|
|
|
|
let gecko_gradient = match gradient.kind {
|
|
GradientKind::Linear(direction) => {
|
|
let gecko_gradient = unsafe {
|
|
Gecko_CreateGradient(NS_STYLE_GRADIENT_SHAPE_LINEAR as u8,
|
|
NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER as u8,
|
|
gradient.repeating,
|
|
gradient.compat_mode != CompatMode::Modern,
|
|
gradient.compat_mode == CompatMode::Moz,
|
|
stop_count as u32)
|
|
};
|
|
|
|
match direction {
|
|
LineDirection::Angle(angle) => {
|
|
// PI radians (180deg) is ignored because it is the default value.
|
|
if angle.radians() != PI {
|
|
unsafe {
|
|
(*gecko_gradient).mAngle.set(angle);
|
|
}
|
|
}
|
|
},
|
|
LineDirection::Horizontal(x) => {
|
|
let x = match x {
|
|
X::Left => 0.0,
|
|
X::Right => 1.0,
|
|
};
|
|
|
|
unsafe {
|
|
(*gecko_gradient).mBgPosX
|
|
.set_value(CoordDataValue::Percent(x));
|
|
(*gecko_gradient).mBgPosY
|
|
.set_value(CoordDataValue::Percent(0.5));
|
|
}
|
|
},
|
|
LineDirection::Vertical(y) => {
|
|
// Although bottom is the default value, we can not ignore
|
|
// it here, because the rendering code of Gecko relies on
|
|
// this to behave correctly for legacy mode.
|
|
let y = match y {
|
|
Y::Top => 0.0,
|
|
Y::Bottom => 1.0,
|
|
};
|
|
unsafe {
|
|
(*gecko_gradient).mBgPosX
|
|
.set_value(CoordDataValue::Percent(0.5));
|
|
(*gecko_gradient).mBgPosY
|
|
.set_value(CoordDataValue::Percent(y));
|
|
}
|
|
},
|
|
LineDirection::Corner(horiz, vert) => {
|
|
let percent_x = match horiz {
|
|
X::Left => 0.0,
|
|
X::Right => 1.0,
|
|
};
|
|
let percent_y = match vert {
|
|
Y::Top => 0.0,
|
|
Y::Bottom => 1.0,
|
|
};
|
|
|
|
unsafe {
|
|
(*gecko_gradient).mBgPosX
|
|
.set_value(CoordDataValue::Percent(percent_x));
|
|
(*gecko_gradient).mBgPosY
|
|
.set_value(CoordDataValue::Percent(percent_y));
|
|
}
|
|
},
|
|
#[cfg(feature = "gecko")]
|
|
LineDirection::MozPosition(position, angle) => {
|
|
unsafe {
|
|
if let Some(position) = position {
|
|
(*gecko_gradient).mBgPosX.set(position.horizontal);
|
|
(*gecko_gradient).mBgPosY.set(position.vertical);
|
|
}
|
|
if let Some(angle) = angle {
|
|
(*gecko_gradient).mAngle.set(angle);
|
|
}
|
|
}
|
|
},
|
|
}
|
|
gecko_gradient
|
|
},
|
|
GradientKind::Radial(shape, position, angle) => {
|
|
let keyword_to_gecko_size = |keyword| {
|
|
match keyword {
|
|
ShapeExtent::ClosestSide => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE,
|
|
ShapeExtent::FarthestSide => NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE,
|
|
ShapeExtent::ClosestCorner => NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER,
|
|
ShapeExtent::FarthestCorner => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER,
|
|
ShapeExtent::Contain => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE,
|
|
ShapeExtent::Cover => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER,
|
|
}
|
|
};
|
|
let (gecko_shape, gecko_size) = match shape {
|
|
EndingShape::Circle(ref circle) => {
|
|
let size = match *circle {
|
|
Circle::Extent(extent) => {
|
|
keyword_to_gecko_size(extent)
|
|
},
|
|
_ => NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE,
|
|
};
|
|
(NS_STYLE_GRADIENT_SHAPE_CIRCULAR as u8, size as u8)
|
|
},
|
|
EndingShape::Ellipse(ref ellipse) => {
|
|
let size = match *ellipse {
|
|
Ellipse::Extent(extent) => {
|
|
keyword_to_gecko_size(extent)
|
|
},
|
|
_ => NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE,
|
|
};
|
|
(NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL as u8, size as u8)
|
|
}
|
|
};
|
|
|
|
let gecko_gradient = unsafe {
|
|
Gecko_CreateGradient(gecko_shape,
|
|
gecko_size,
|
|
gradient.repeating,
|
|
gradient.compat_mode == CompatMode::Moz,
|
|
gradient.compat_mode == CompatMode::Moz,
|
|
stop_count as u32)
|
|
};
|
|
|
|
// Clear mBgPos field and set mAngle if angle is set. Otherwise clear it.
|
|
unsafe {
|
|
if let Some(angle) = angle {
|
|
(*gecko_gradient).mAngle.set(angle);
|
|
}
|
|
}
|
|
|
|
// Setting radius values depending shape
|
|
match shape {
|
|
EndingShape::Circle(Circle::Radius(length)) => {
|
|
unsafe {
|
|
let au = length.to_i32_au();
|
|
(*gecko_gradient).mRadiusX.set_value(CoordDataValue::Coord(au));
|
|
(*gecko_gradient).mRadiusY.set_value(CoordDataValue::Coord(au));
|
|
}
|
|
},
|
|
EndingShape::Ellipse(Ellipse::Radii(x, y)) => {
|
|
unsafe {
|
|
(*gecko_gradient).mRadiusX.set(x);
|
|
(*gecko_gradient).mRadiusY.set(y);
|
|
}
|
|
},
|
|
_ => {},
|
|
}
|
|
unsafe {
|
|
(*gecko_gradient).mBgPosX.set(position.horizontal);
|
|
(*gecko_gradient).mBgPosY.set(position.vertical);
|
|
}
|
|
|
|
gecko_gradient
|
|
},
|
|
};
|
|
|
|
for (index, item) in gradient.items.iter().enumerate() {
|
|
// NB: stops are guaranteed to be none in the gecko side by
|
|
// default.
|
|
|
|
let gecko_stop = unsafe {
|
|
&mut (*gecko_gradient).mStops[index]
|
|
};
|
|
let mut coord = nsStyleCoord::null();
|
|
|
|
match *item {
|
|
GradientItem::ColorStop(ref stop) => {
|
|
gecko_stop.mColor = convert_rgba_to_nscolor(&stop.color);
|
|
gecko_stop.mIsInterpolationHint = false;
|
|
coord.set(stop.position);
|
|
},
|
|
GradientItem::InterpolationHint(hint) => {
|
|
gecko_stop.mIsInterpolationHint = true;
|
|
coord.set(Some(hint));
|
|
}
|
|
}
|
|
|
|
gecko_stop.mLocation.move_from(coord);
|
|
}
|
|
|
|
unsafe {
|
|
Gecko_SetGradientImageValue(self, gecko_gradient);
|
|
}
|
|
}
|
|
|
|
/// Converts into Image.
|
|
pub unsafe fn into_image(self: &nsStyleImage) -> Option<Image> {
|
|
use gecko_bindings::bindings::Gecko_GetImageElement;
|
|
use gecko_bindings::structs::nsStyleImageType;
|
|
use values::computed::{NumberOrPercentage, MozImageRect};
|
|
|
|
match self.mType {
|
|
nsStyleImageType::eStyleImageType_Null => {
|
|
None
|
|
},
|
|
nsStyleImageType::eStyleImageType_Image => {
|
|
let url = self.get_image_url();
|
|
if self.mCropRect.mPtr.is_null() {
|
|
Some(GenericImage::Url(url))
|
|
} else {
|
|
let ref rect = *self.mCropRect.mPtr;
|
|
match (NumberOrPercentage::from_gecko_style_coord(&rect.data_at(0)),
|
|
NumberOrPercentage::from_gecko_style_coord(&rect.data_at(1)),
|
|
NumberOrPercentage::from_gecko_style_coord(&rect.data_at(2)),
|
|
NumberOrPercentage::from_gecko_style_coord(&rect.data_at(3))) {
|
|
(Some(top), Some(right), Some(bottom), Some(left)) =>
|
|
Some(GenericImage::Rect(Box::new(MozImageRect { url, top, right, bottom, left } ))),
|
|
_ => {
|
|
debug_assert!(false, "mCropRect could not convert to NumberOrPercentage");
|
|
None
|
|
}
|
|
}
|
|
}
|
|
},
|
|
nsStyleImageType::eStyleImageType_Gradient => {
|
|
Some(GenericImage::Gradient(self.get_gradient()))
|
|
},
|
|
nsStyleImageType::eStyleImageType_Element => {
|
|
use gecko_string_cache::Atom;
|
|
let atom = Gecko_GetImageElement(self);
|
|
Some(GenericImage::Element(Atom::from(atom)))
|
|
},
|
|
x => panic!("Unexpected image type {:?}", x)
|
|
}
|
|
}
|
|
|
|
unsafe fn get_image_url(self: &nsStyleImage) -> ComputedUrl {
|
|
use gecko_bindings::bindings::Gecko_GetURLValue;
|
|
let url_value = Gecko_GetURLValue(self);
|
|
let mut url = ComputedUrl::from_url_value_data(url_value.as_ref().unwrap())
|
|
.expect("Could not convert to ComputedUrl");
|
|
url.build_image_value();
|
|
url
|
|
}
|
|
|
|
unsafe fn get_gradient(self: &nsStyleImage) -> Box<Gradient> {
|
|
use gecko::values::convert_nscolor_to_rgba;
|
|
use gecko_bindings::bindings::Gecko_GetGradientImageValue;
|
|
use gecko_bindings::structs::{NS_STYLE_GRADIENT_SHAPE_CIRCULAR, NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL};
|
|
use gecko_bindings::structs::{NS_STYLE_GRADIENT_SHAPE_LINEAR, NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER};
|
|
use gecko_bindings::structs::{NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE, NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE};
|
|
use gecko_bindings::structs::{NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER, NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE};
|
|
use values::computed::{Length, LengthOrPercentage};
|
|
use values::computed::image::LineDirection;
|
|
use values::computed::position::Position;
|
|
use values::generics::image::{ColorStop, CompatMode, Circle, Ellipse, EndingShape, GradientKind, ShapeExtent};
|
|
use values::specified::position::{X, Y};
|
|
|
|
let gecko_gradient = Gecko_GetGradientImageValue(self).as_ref().unwrap();
|
|
let angle = Angle::from_gecko_style_coord(&gecko_gradient.mAngle);
|
|
let horizontal_style = LengthOrPercentage::from_gecko_style_coord(&gecko_gradient.mBgPosX);
|
|
let vertical_style = LengthOrPercentage::from_gecko_style_coord(&gecko_gradient.mBgPosY);
|
|
|
|
let kind = match gecko_gradient.mShape as u32 {
|
|
NS_STYLE_GRADIENT_SHAPE_LINEAR => {
|
|
let line_direction = match (angle, horizontal_style, vertical_style) {
|
|
(Some(a), None, None) => LineDirection::Angle(a),
|
|
(None, Some(horizontal), Some(vertical)) => {
|
|
let horizontal_as_corner = match horizontal {
|
|
LengthOrPercentage::Percentage(percentage) => {
|
|
if percentage.0 == 0.0 {
|
|
Some(X::Left)
|
|
} else if percentage.0 == 1.0 {
|
|
Some(X::Right)
|
|
} else {
|
|
None
|
|
}
|
|
},
|
|
_ => None
|
|
};
|
|
let vertical_as_corner = match vertical {
|
|
LengthOrPercentage::Percentage(percentage) => {
|
|
if percentage.0 == 0.0 {
|
|
Some(Y::Top)
|
|
} else if percentage.0 == 1.0 {
|
|
Some(Y::Bottom)
|
|
} else {
|
|
None
|
|
}
|
|
},
|
|
_ => None
|
|
};
|
|
|
|
match (horizontal_as_corner, vertical_as_corner) {
|
|
(Some(hc), Some(vc)) => LineDirection::Corner(hc, vc),
|
|
_ => LineDirection::MozPosition(
|
|
Some(Position { horizontal, vertical }), None)
|
|
}
|
|
},
|
|
(Some(_), Some(horizontal), Some(vertical)) =>
|
|
LineDirection::MozPosition(
|
|
Some(Position { horizontal, vertical }), angle),
|
|
_ => {
|
|
debug_assert!(horizontal_style.is_none() && vertical_style.is_none(),
|
|
"Unexpected linear gradient direction");
|
|
LineDirection::MozPosition(None, None)
|
|
}
|
|
};
|
|
GradientKind::Linear(line_direction)
|
|
},
|
|
_ => {
|
|
let gecko_size_to_keyword = |gecko_size| {
|
|
match gecko_size {
|
|
NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE => ShapeExtent::ClosestSide,
|
|
NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE => ShapeExtent::FarthestSide,
|
|
NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER => ShapeExtent::ClosestCorner,
|
|
NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER => ShapeExtent::FarthestCorner,
|
|
// FIXME: We should support ShapeExtent::Contain and ShapeExtent::Cover.
|
|
// But we can't choose those yet since Gecko does not support both values.
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1217664
|
|
x => panic!("Found unexpected gecko_size: {:?}", x),
|
|
}
|
|
};
|
|
|
|
let shape = match gecko_gradient.mShape as u32 {
|
|
NS_STYLE_GRADIENT_SHAPE_CIRCULAR => {
|
|
let circle = match gecko_gradient.mSize as u32 {
|
|
NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE => {
|
|
let radius = Length::from_gecko_style_coord(&gecko_gradient.mRadiusX)
|
|
.expect("mRadiusX could not convert to Length");
|
|
debug_assert_eq!(radius,
|
|
Length::from_gecko_style_coord(&gecko_gradient.mRadiusY).unwrap());
|
|
Circle::Radius(radius)
|
|
},
|
|
size => Circle::Extent(gecko_size_to_keyword(size))
|
|
};
|
|
EndingShape::Circle(circle)
|
|
},
|
|
NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL => {
|
|
let length_percentage_keyword = match gecko_gradient.mSize as u32 {
|
|
NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE => {
|
|
match (LengthOrPercentage::from_gecko_style_coord(&gecko_gradient.mRadiusX),
|
|
LengthOrPercentage::from_gecko_style_coord(&gecko_gradient.mRadiusY)) {
|
|
(Some(x), Some(y)) => Ellipse::Radii(x, y),
|
|
_ => {
|
|
debug_assert!(false,
|
|
"mRadiusX, mRadiusY could not convert to LengthOrPercentage");
|
|
Ellipse::Radii(LengthOrPercentage::zero(),
|
|
LengthOrPercentage::zero())
|
|
}
|
|
}
|
|
},
|
|
size => Ellipse::Extent(gecko_size_to_keyword(size))
|
|
};
|
|
EndingShape::Ellipse(length_percentage_keyword)
|
|
},
|
|
x => panic!("Found unexpected mShape: {:?}", x),
|
|
};
|
|
|
|
let position = match (horizontal_style, vertical_style) {
|
|
(Some(horizontal), Some(vertical)) => Position { horizontal, vertical },
|
|
_ => {
|
|
debug_assert!(false,
|
|
"mRadiusX, mRadiusY could not convert to LengthOrPercentage");
|
|
Position {
|
|
horizontal: LengthOrPercentage::zero(),
|
|
vertical: LengthOrPercentage::zero()
|
|
}
|
|
}
|
|
};
|
|
|
|
GradientKind::Radial(shape, position, angle)
|
|
}
|
|
};
|
|
|
|
let items = gecko_gradient.mStops.iter().map(|ref stop| {
|
|
if stop.mIsInterpolationHint {
|
|
GradientItem::InterpolationHint(
|
|
LengthOrPercentage::from_gecko_style_coord(&stop.mLocation)
|
|
.expect("mLocation could not convert to LengthOrPercentage")
|
|
)
|
|
} else {
|
|
GradientItem::ColorStop(ColorStop {
|
|
color: convert_nscolor_to_rgba(stop.mColor),
|
|
position: LengthOrPercentage::from_gecko_style_coord(&stop.mLocation)
|
|
})
|
|
}
|
|
}).collect();
|
|
|
|
let compat_mode =
|
|
if gecko_gradient.mMozLegacySyntax {
|
|
CompatMode::Moz
|
|
} else if gecko_gradient.mLegacySyntax {
|
|
CompatMode::WebKit
|
|
} else {
|
|
CompatMode::Modern
|
|
};
|
|
|
|
Box::new(Gradient { items, repeating: gecko_gradient.mRepeating, kind, compat_mode })
|
|
}
|
|
}
|
|
|
|
pub mod basic_shape {
|
|
//! Conversions from and to CSS shape representations.
|
|
|
|
use gecko::values::GeckoStyleCoordConvertible;
|
|
use gecko_bindings::structs;
|
|
use gecko_bindings::structs::{StyleBasicShape, StyleBasicShapeType, StyleFillRule};
|
|
use gecko_bindings::structs::{StyleGeometryBox, StyleShapeSource, StyleShapeSourceType};
|
|
use gecko_bindings::structs::{nsStyleCoord, nsStyleCorners};
|
|
use gecko_bindings::sugar::ns_style_coord::{CoordDataMut, CoordDataValue};
|
|
use std::borrow::Borrow;
|
|
use values::computed::ComputedUrl;
|
|
use values::computed::basic_shape::{BasicShape, ShapeRadius};
|
|
use values::computed::border::{BorderCornerRadius, BorderRadius};
|
|
use values::computed::length::LengthOrPercentage;
|
|
use values::computed::position;
|
|
use values::generics::basic_shape::{BasicShape as GenericBasicShape, InsetRect, Polygon};
|
|
use values::generics::basic_shape::{Circle, Ellipse, FillRule};
|
|
use values::generics::basic_shape::{GeometryBox, ShapeBox, ShapeSource};
|
|
use values::generics::border::BorderRadius as GenericBorderRadius;
|
|
use values::generics::rect::Rect;
|
|
|
|
impl<'a, ReferenceBox> From<&'a StyleShapeSource> for ShapeSource<BasicShape, ReferenceBox, ComputedUrl>
|
|
where
|
|
ReferenceBox: From<StyleGeometryBox>,
|
|
{
|
|
fn from(other: &'a StyleShapeSource) -> Self {
|
|
match other.mType {
|
|
StyleShapeSourceType::None => ShapeSource::None,
|
|
StyleShapeSourceType::Box => ShapeSource::Box(other.mReferenceBox.into()),
|
|
StyleShapeSourceType::URL => {
|
|
unsafe {
|
|
let shape_image = &*other.mShapeImage.mPtr;
|
|
let other_url = &(**shape_image.__bindgen_anon_1.mURLValue.as_ref());
|
|
let url = ComputedUrl::from_url_value_data(&other_url._base).unwrap();
|
|
ShapeSource::Url(url)
|
|
}
|
|
},
|
|
StyleShapeSourceType::Shape => {
|
|
let other_shape = unsafe { &*other.mBasicShape.mPtr };
|
|
let shape = other_shape.into();
|
|
let reference_box = if other.mReferenceBox == StyleGeometryBox::NoBox {
|
|
None
|
|
} else {
|
|
Some(other.mReferenceBox.into())
|
|
};
|
|
ShapeSource::Shape(shape, reference_box)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a StyleBasicShape> for BasicShape {
|
|
fn from(other: &'a StyleBasicShape) -> Self {
|
|
match other.mType {
|
|
StyleBasicShapeType::Inset => {
|
|
let t = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[0]);
|
|
let r = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[1]);
|
|
let b = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[2]);
|
|
let l = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[3]);
|
|
let round = (&other.mRadius).into();
|
|
let rect = Rect::new(
|
|
t.expect("inset() offset should be a length, percentage, or calc value"),
|
|
r.expect("inset() offset should be a length, percentage, or calc value"),
|
|
b.expect("inset() offset should be a length, percentage, or calc value"),
|
|
l.expect("inset() offset should be a length, percentage, or calc value"),
|
|
);
|
|
GenericBasicShape::Inset(InsetRect {
|
|
rect: rect,
|
|
round: Some(round),
|
|
})
|
|
}
|
|
StyleBasicShapeType::Circle => {
|
|
GenericBasicShape::Circle(Circle {
|
|
radius: (&other.mCoordinates[0]).into(),
|
|
position: (&other.mPosition).into()
|
|
})
|
|
}
|
|
StyleBasicShapeType::Ellipse => {
|
|
GenericBasicShape::Ellipse(Ellipse {
|
|
semiaxis_x: (&other.mCoordinates[0]).into(),
|
|
semiaxis_y: (&other.mCoordinates[1]).into(),
|
|
position: (&other.mPosition).into()
|
|
})
|
|
}
|
|
StyleBasicShapeType::Polygon => {
|
|
let fill_rule = if other.mFillRule == StyleFillRule::Evenodd {
|
|
FillRule::EvenOdd
|
|
} else {
|
|
FillRule::NonZero
|
|
};
|
|
let mut coords = Vec::with_capacity(other.mCoordinates.len() / 2);
|
|
for i in 0..(other.mCoordinates.len() / 2) {
|
|
let x = 2 * i;
|
|
let y = x + 1;
|
|
coords.push((LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[x])
|
|
.expect("polygon() coordinate should be a length, percentage, or calc value"),
|
|
LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[y])
|
|
.expect("polygon() coordinate should be a length, percentage, or calc value")
|
|
))
|
|
}
|
|
GenericBasicShape::Polygon(Polygon {
|
|
fill: fill_rule,
|
|
coordinates: coords,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a nsStyleCorners> for BorderRadius {
|
|
fn from(other: &'a nsStyleCorners) -> Self {
|
|
let get_corner = |index| {
|
|
BorderCornerRadius::new(
|
|
LengthOrPercentage::from_gecko_style_coord(&other.data_at(index))
|
|
.expect("<border-radius> should be a length, percentage, or calc value"),
|
|
LengthOrPercentage::from_gecko_style_coord(&other.data_at(index + 1))
|
|
.expect("<border-radius> should be a length, percentage, or calc value"))
|
|
};
|
|
|
|
GenericBorderRadius {
|
|
top_left: get_corner(0),
|
|
top_right: get_corner(2),
|
|
bottom_right: get_corner(4),
|
|
bottom_left: get_corner(6),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Can't be a From impl since we need to set an existing
|
|
// nsStyleCorners, not create a new one
|
|
impl BorderRadius {
|
|
/// Set this `BorderRadius` into a given `nsStyleCoord`.
|
|
pub fn set_corners(&self, other: &mut nsStyleCorners) {
|
|
let mut set_corner = |field: &BorderCornerRadius, index| {
|
|
field.0.width().to_gecko_style_coord(&mut other.data_at_mut(index));
|
|
field.0.height().to_gecko_style_coord(&mut other.data_at_mut(index + 1));
|
|
};
|
|
set_corner(&self.top_left, 0);
|
|
set_corner(&self.top_right, 2);
|
|
set_corner(&self.bottom_right, 4);
|
|
set_corner(&self.bottom_left, 6);
|
|
}
|
|
}
|
|
|
|
/// We use None for a nonexistant radius, but Gecko uses (0 0 0 0 / 0 0 0 0)
|
|
pub fn set_corners_from_radius(radius: Option<BorderRadius>, other: &mut nsStyleCorners) {
|
|
if let Some(radius) = radius {
|
|
radius.set_corners(other);
|
|
} else {
|
|
for i in 0..8 {
|
|
other.data_at_mut(i).set_value(CoordDataValue::Coord(0));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Can't be a From impl since we need to set an existing
|
|
// Position, not create a new one
|
|
impl From<position::Position> for structs::Position {
|
|
fn from(other: position::Position) -> Self {
|
|
structs::Position {
|
|
mXPosition: other.horizontal.into(),
|
|
mYPosition: other.vertical.into()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a nsStyleCoord> for ShapeRadius {
|
|
fn from(other: &'a nsStyleCoord) -> Self {
|
|
let other = other.borrow();
|
|
ShapeRadius::from_gecko_style_coord(other)
|
|
.expect("<shape-radius> should be a length, percentage, calc, or keyword value")
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a structs::Position> for position::Position {
|
|
fn from(other: &'a structs::Position) -> Self {
|
|
position::Position {
|
|
horizontal: other.mXPosition.into(),
|
|
vertical: other.mYPosition.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<ShapeBox> for StyleGeometryBox {
|
|
fn from(reference: ShapeBox) -> Self {
|
|
use gecko_bindings::structs::StyleGeometryBox::*;
|
|
match reference {
|
|
ShapeBox::ContentBox => ContentBox,
|
|
ShapeBox::PaddingBox => PaddingBox,
|
|
ShapeBox::BorderBox => BorderBox,
|
|
ShapeBox::MarginBox => MarginBox,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<GeometryBox> for StyleGeometryBox {
|
|
fn from(reference: GeometryBox) -> Self {
|
|
use gecko_bindings::structs::StyleGeometryBox::*;
|
|
match reference {
|
|
GeometryBox::ShapeBox(shape_box) => From::from(shape_box),
|
|
GeometryBox::FillBox => FillBox,
|
|
GeometryBox::StrokeBox => StrokeBox,
|
|
GeometryBox::ViewBox => ViewBox,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Will panic on NoBox
|
|
// Ideally these would be implemented on Option<T>,
|
|
// but coherence doesn't like that and TryFrom isn't stable
|
|
impl From<StyleGeometryBox> for GeometryBox {
|
|
fn from(reference: StyleGeometryBox) -> Self {
|
|
use gecko_bindings::structs::StyleGeometryBox::*;
|
|
match reference {
|
|
ContentBox => GeometryBox::ShapeBox(ShapeBox::ContentBox),
|
|
PaddingBox => GeometryBox::ShapeBox(ShapeBox::PaddingBox),
|
|
BorderBox => GeometryBox::ShapeBox(ShapeBox::BorderBox),
|
|
MarginBox => GeometryBox::ShapeBox(ShapeBox::MarginBox),
|
|
FillBox => GeometryBox::FillBox,
|
|
StrokeBox => GeometryBox::StrokeBox,
|
|
ViewBox => GeometryBox::ViewBox,
|
|
other => panic!("Unexpected StyleGeometryBox::{:?} while converting to GeometryBox", other),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<StyleGeometryBox> for ShapeBox {
|
|
fn from(reference: StyleGeometryBox) -> Self {
|
|
use gecko_bindings::structs::StyleGeometryBox::*;
|
|
match reference {
|
|
ContentBox => ShapeBox::ContentBox,
|
|
PaddingBox => ShapeBox::PaddingBox,
|
|
BorderBox => ShapeBox::BorderBox,
|
|
MarginBox => ShapeBox::MarginBox,
|
|
other => panic!("Unexpected StyleGeometryBox::{:?} while converting to ShapeBox", other),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<RulesMutateError> for nsresult {
|
|
fn from(other: RulesMutateError) -> Self {
|
|
match other {
|
|
RulesMutateError::Syntax => nsresult::NS_ERROR_DOM_SYNTAX_ERR,
|
|
RulesMutateError::IndexSize => nsresult::NS_ERROR_DOM_INDEX_SIZE_ERR,
|
|
RulesMutateError::HierarchyRequest => nsresult::NS_ERROR_DOM_HIERARCHY_REQUEST_ERR,
|
|
RulesMutateError::InvalidState => nsresult::NS_ERROR_DOM_INVALID_STATE_ERR,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Origin> for SheetType {
|
|
fn from(other: Origin) -> Self {
|
|
match other {
|
|
Origin::UserAgent => SheetType::Agent,
|
|
Origin::Author => SheetType::Doc,
|
|
Origin::User => SheetType::User,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TrackSize<LengthOrPercentage> {
|
|
/// Return TrackSize from given two nsStyleCoord
|
|
pub fn from_gecko_style_coords<T: CoordData>(gecko_min: &T, gecko_max: &T) -> Self {
|
|
use gecko_bindings::structs::root::nsStyleUnit;
|
|
use values::computed::length::LengthOrPercentage;
|
|
use values::generics::grid::{TrackBreadth, TrackSize};
|
|
|
|
if gecko_min.unit() == nsStyleUnit::eStyleUnit_None {
|
|
debug_assert!(gecko_max.unit() == nsStyleUnit::eStyleUnit_Coord ||
|
|
gecko_max.unit() == nsStyleUnit::eStyleUnit_Percent ||
|
|
gecko_max.unit() == nsStyleUnit::eStyleUnit_Calc);
|
|
return TrackSize::FitContent(LengthOrPercentage::from_gecko_style_coord(gecko_max)
|
|
.expect("gecko_max could not convert to LengthOrPercentage"));
|
|
}
|
|
|
|
let min = TrackBreadth::from_gecko_style_coord(gecko_min)
|
|
.expect("gecko_min could not convert to TrackBreadth");
|
|
let max = TrackBreadth::from_gecko_style_coord(gecko_max)
|
|
.expect("gecko_max could not convert to TrackBreadth");
|
|
if min == max {
|
|
TrackSize::Breadth(max)
|
|
} else {
|
|
TrackSize::Minmax(min, max)
|
|
}
|
|
}
|
|
|
|
/// Save TrackSize to given gecko fields.
|
|
pub fn to_gecko_style_coords<T: CoordDataMut>(&self, gecko_min: &mut T, gecko_max: &mut T) {
|
|
use values::generics::grid::TrackSize;
|
|
|
|
match *self {
|
|
TrackSize::FitContent(ref lop) => {
|
|
// Gecko sets min value to None and max value to the actual value in fit-content
|
|
// https://dxr.mozilla.org/mozilla-central/rev/0eef1d5/layout/style/nsRuleNode.cpp#8221
|
|
gecko_min.set_value(CoordDataValue::None);
|
|
lop.to_gecko_style_coord(gecko_max);
|
|
},
|
|
TrackSize::Breadth(ref breadth) => {
|
|
// Set the value to both fields if there's one breadth value
|
|
// https://dxr.mozilla.org/mozilla-central/rev/0eef1d5/layout/style/nsRuleNode.cpp#8230
|
|
breadth.to_gecko_style_coord(gecko_min);
|
|
breadth.to_gecko_style_coord(gecko_max);
|
|
},
|
|
TrackSize::Minmax(ref min, ref max) => {
|
|
min.to_gecko_style_coord(gecko_min);
|
|
max.to_gecko_style_coord(gecko_max);
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TrackListValue<LengthOrPercentage, Integer> {
|
|
/// Return TrackSize from given two nsStyleCoord
|
|
pub fn from_gecko_style_coords<T: CoordData>(gecko_min: &T, gecko_max: &T) -> Self {
|
|
TrackListValue::TrackSize(TrackSize::from_gecko_style_coords(gecko_min, gecko_max))
|
|
}
|
|
|
|
/// Save TrackSize to given gecko fields.
|
|
pub fn to_gecko_style_coords<T: CoordDataMut>(&self, gecko_min: &mut T, gecko_max: &mut T) {
|
|
use values::generics::grid::TrackListValue;
|
|
|
|
match *self {
|
|
TrackListValue::TrackSize(ref size) => size.to_gecko_style_coords(gecko_min, gecko_max),
|
|
_ => unreachable!("Should only transform from track-size computed values"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> Rect<T> where T: GeckoStyleCoordConvertible {
|
|
/// Convert this generic Rect to given Gecko fields.
|
|
pub fn to_gecko_rect(&self, sides: &mut ::gecko_bindings::structs::nsStyleSides) {
|
|
self.0.to_gecko_style_coord(&mut sides.data_at_mut(0));
|
|
self.1.to_gecko_style_coord(&mut sides.data_at_mut(1));
|
|
self.2.to_gecko_style_coord(&mut sides.data_at_mut(2));
|
|
self.3.to_gecko_style_coord(&mut sides.data_at_mut(3));
|
|
}
|
|
|
|
/// Convert from given Gecko data to generic Rect.
|
|
pub fn from_gecko_rect(sides: &::gecko_bindings::structs::nsStyleSides)
|
|
-> Option<::values::generics::rect::Rect<T>> {
|
|
use values::generics::rect::Rect;
|
|
|
|
Some(
|
|
Rect::new(
|
|
T::from_gecko_style_coord(&sides.data_at(0)).expect("coord[0] cound not convert"),
|
|
T::from_gecko_style_coord(&sides.data_at(1)).expect("coord[1] cound not convert"),
|
|
T::from_gecko_style_coord(&sides.data_at(2)).expect("coord[2] cound not convert"),
|
|
T::from_gecko_style_coord(&sides.data_at(3)).expect("coord[3] cound not convert")
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<L> VerticalAlign<L> {
|
|
/// Converts an enumerated value coming from Gecko to a `VerticalAlign<L>`.
|
|
pub fn from_gecko_keyword(value: u32) -> Self {
|
|
match value {
|
|
structs::NS_STYLE_VERTICAL_ALIGN_BASELINE => VerticalAlign::Baseline,
|
|
structs::NS_STYLE_VERTICAL_ALIGN_SUB => VerticalAlign::Sub,
|
|
structs::NS_STYLE_VERTICAL_ALIGN_SUPER => VerticalAlign::Super,
|
|
structs::NS_STYLE_VERTICAL_ALIGN_TOP => VerticalAlign::Top,
|
|
structs::NS_STYLE_VERTICAL_ALIGN_TEXT_TOP => VerticalAlign::TextTop,
|
|
structs::NS_STYLE_VERTICAL_ALIGN_MIDDLE => VerticalAlign::Middle,
|
|
structs::NS_STYLE_VERTICAL_ALIGN_BOTTOM => VerticalAlign::Bottom,
|
|
structs::NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM => VerticalAlign::TextBottom,
|
|
structs::NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE => {
|
|
VerticalAlign::MozMiddleWithBaseline
|
|
},
|
|
_ => panic!("unexpected enumerated value for vertical-align"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Convert to String from given chars pointer.
|
|
pub unsafe fn string_from_chars_pointer(p: *const u16) -> String {
|
|
use std::slice;
|
|
let mut length = 0;
|
|
let mut iter = p;
|
|
while *iter != 0 {
|
|
length += 1;
|
|
iter = iter.offset(1);
|
|
}
|
|
let char_vec = slice::from_raw_parts(p, length as usize);
|
|
String::from_utf16_lossy(char_vec)
|
|
}
|