auto merge of #5134 : pcwalton/servo/transforms, r=SimonSapin

r? @SimonSapin
This commit is contained in:
bors-servo 2015-03-17 13:39:51 -06:00
commit 203240c1d8
14 changed files with 964 additions and 35 deletions

View file

@ -214,18 +214,28 @@ impl SizeOf for DisplayList {
pub struct StackingContext {
/// The display items that make up this stacking context.
pub display_list: Box<DisplayList>,
/// The layer for this stacking context, if there is one.
pub layer: Option<Arc<PaintLayer>>,
/// The position and size of this stacking context.
pub bounds: Rect<Au>,
/// The overflow rect for this stacking context in its coordinate system.
pub overflow: Rect<Au>,
/// The `z-index` for this stacking context.
pub z_index: i32,
/// CSS filters to be applied to this stacking context (including opacity).
pub filters: filter::T,
/// The blend mode with which this stacking context blends with its backdrop.
pub blend_mode: mix_blend_mode::T,
/// A transform to be applied to this stacking context.
///
/// TODO(pcwalton): 3D transforms.
pub transform: Matrix2D<AzFloat>,
}
impl StackingContext {
@ -235,6 +245,7 @@ impl StackingContext {
bounds: &Rect<Au>,
overflow: &Rect<Au>,
z_index: i32,
transform: &Matrix2D<AzFloat>,
filters: filter::T,
blend_mode: mix_blend_mode::T,
layer: Option<Arc<PaintLayer>>)
@ -245,6 +256,7 @@ impl StackingContext {
bounds: *bounds,
overflow: *overflow,
z_index: z_index,
transform: *transform,
filters: filters,
blend_mode: blend_mode,
}
@ -256,6 +268,7 @@ impl StackingContext {
tile_bounds: &Rect<AzFloat>,
transform: &Matrix2D<AzFloat>,
clip_rect: Option<&Rect<Au>>) {
let transform = transform.mul(&self.transform);
let temporary_draw_target =
paint_context.get_or_create_temporary_draw_target(&self.filters, self.blend_mode);
{
@ -282,7 +295,7 @@ impl StackingContext {
// Set up our clip rect and transform.
let old_transform = paint_subcontext.draw_target.get_transform();
paint_subcontext.draw_target.set_transform(transform);
paint_subcontext.draw_target.set_transform(&transform);
paint_subcontext.push_clip_if_applicable();
// Steps 1 and 2: Borders and background for the root.
@ -408,7 +421,7 @@ impl StackingContext {
/// the `pointer-events` CSS property If `topmost_only` is true, stops after placing one node
/// into the list. `result` must be empty upon entry to this function.
pub fn hit_test(&self,
point: Point2D<Au>,
mut point: Point2D<Au>,
result: &mut Vec<DisplayItemMetadata>,
topmost_only: bool) {
fn hit_test_in_list<'a,I>(point: Point2D<Au>,
@ -459,6 +472,9 @@ impl StackingContext {
}
debug_assert!(!topmost_only || result.is_empty());
let frac_point = self.transform.transform_point(&Point2D(point.x.to_frac32_px(),
point.y.to_frac32_px()));
point = Point2D(Au::from_frac32_px(frac_point.x), Au::from_frac32_px(frac_point.y));
// Iterate through display items in reverse stacking order. Steps here refer to the
// painting steps in CSS 2.1 Appendix E.

View file

@ -19,10 +19,10 @@ use fragment::{CoordinateSystem, Fragment, IframeFragmentInfo, ImageFragmentInfo
use fragment::{ScannedTextFragmentInfo, SpecificFragmentInfo};
use inline::InlineFlow;
use list_item::ListItemFlow;
use model::{self, MaybeAuto};
use model::{self, MaybeAuto, ToGfxMatrix};
use opaque_node::OpaqueNodeMethods;
use geom::{Point2D, Rect, Size2D, SideOffsets2D};
use geom::{Matrix2D, Point2D, Rect, Size2D, SideOffsets2D};
use gfx::color;
use gfx::display_list::{BLUR_INFLATION_FACTOR, BaseDisplayItem, BorderDisplayItem};
use gfx::display_list::{BorderRadii, BoxShadowClipMode, BoxShadowDisplayItem, ClippingRegion};
@ -49,6 +49,7 @@ use style::values::specified::{AngleOrCorner, HorizontalDirection, VerticalDirec
use style::values::computed::{Image, LinearGradient, LengthOrPercentage, LengthOrPercentageOrAuto};
use style::values::RGBA;
use style::computed_values::filter::Filter;
use style::computed_values::transform::ComputedMatrix;
use style::computed_values::{background_attachment, background_repeat, background_size};
use style::computed_values::{border_style, image_rendering, overflow_x, position, visibility};
use style::properties::style_structs::Border;
@ -1369,6 +1370,22 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
.relative_containing_block_mode,
CoordinateSystem::Parent);
let transform_origin = self.fragment.style().get_effects().transform_origin;
let transform_origin =
Point2D(model::specified(transform_origin.horizontal,
border_box.size.width).to_frac32_px(),
model::specified(transform_origin.vertical,
border_box.size.height).to_frac32_px());
let transform = self.fragment
.style()
.get_effects()
.transform
.unwrap_or(ComputedMatrix::identity())
.to_gfx_matrix(&border_box.size);
let transform = Matrix2D::identity().translate(transform_origin.x, transform_origin.y)
.mul(&transform)
.translate(-transform_origin.x, -transform_origin.y);
// FIXME(pcwalton): Is this vertical-writing-direction-safe?
let margin = self.fragment.margin.to_physical(self.base.writing_mode);
let overflow = self.base.overflow.translate(&-Point2D(margin.left, Au(0)));
@ -1384,6 +1401,7 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
&border_box,
&overflow,
self.fragment.style().get_box().z_index.number_or_zero(),
&transform,
filters,
self.fragment.style().get_effects().mix_blend_mode,
layer))

View file

@ -2001,6 +2001,9 @@ impl Fragment {
if self.style().get_effects().mix_blend_mode != mix_blend_mode::T::normal {
return true
}
if self.style().get_effects().transform.is_some() {
return true
}
match self.style().get_box().position {
position::T::absolute | position::T::fixed => {
// FIXME(pcwalton): This should only establish a new stacking context when

View file

@ -24,35 +24,49 @@ use wrapper::{LayoutNode, TLayoutNode, ThreadSafeLayoutNode};
use encoding::EncodingRef;
use encoding::all::UTF_8;
use geom::matrix2d::Matrix2D;
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use geom::scale_factor::ScaleFactor;
use geom::size::Size2D;
use gfx::color;
use gfx::display_list::{ClippingRegion, DisplayItemMetadata, DisplayList, OpaqueNode};
use gfx::display_list::{StackingContext};
use gfx::font_cache_task::FontCacheTask;
use gfx::paint_task::{PaintChan, PaintLayer};
use gfx::paint_task::Msg as PaintMsg;
use gfx::paint_task::{PaintChan, PaintLayer};
use layout_traits::{LayoutControlMsg, LayoutTaskFactory};
use log;
use script::dom::bindings::js::LayoutJS;
use script::dom::node::{LayoutData, Node, NodeTypeId};
use script::dom::element::ElementTypeId;
use script::dom::htmlelement::HTMLElementTypeId;
use script::layout_interface::{ContentBoxResponse, ContentBoxesResponse};
use script::layout_interface::ReflowQueryType;
use script::layout_interface::{HitTestResponse, LayoutChan, LayoutRPC};
use script::layout_interface::{MouseOverResponse, Msg};
use script::layout_interface::{Reflow, ReflowGoal, ScriptLayoutChan, TrustedNodeAddress};
use script_traits::{ConstellationControlMsg, CompositorEvent, OpaqueScriptLayoutChannel};
use script_traits::{ScriptControlChan, UntrustedNodeAddress};
use msg::compositor_msg::ScrollPolicy;
use msg::constellation_msg::Msg as ConstellationMsg;
use msg::constellation_msg::{ConstellationChan, Failure, PipelineExitType, PipelineId};
use net::image_cache_task::{ImageCacheTask, ImageResponseMsg};
use net::local_image_cache::{ImageResponder, LocalImageCache};
use net::resource_task::{ResourceTask, load_bytes_iter};
use script::dom::bindings::js::LayoutJS;
use script::dom::element::ElementTypeId;
use script::dom::htmlelement::HTMLElementTypeId;
use script::dom::node::{LayoutData, Node, NodeTypeId};
use script::layout_interface::ReflowQueryType;
use script::layout_interface::{ContentBoxResponse, ContentBoxesResponse};
use script::layout_interface::{HitTestResponse, LayoutChan, LayoutRPC};
use script::layout_interface::{MouseOverResponse, Msg};
use script::layout_interface::{Reflow, ReflowGoal, ScriptLayoutChan, TrustedNodeAddress};
use script_traits::{ConstellationControlMsg, CompositorEvent, OpaqueScriptLayoutChannel};
use script_traits::{ScriptControlChan, UntrustedNodeAddress};
use std::borrow::ToOwned;
use std::cell::Cell;
use std::mem;
use std::ops::{Deref, DerefMut};
use std::ptr;
use std::sync::mpsc::{channel, Sender, Receiver, Select};
use std::sync::{Arc, Mutex, MutexGuard};
use style::computed_values::{filter, mix_blend_mode};
use style::media_queries::{MediaType, MediaQueryList, Device};
use style::node::TNode;
use style::selector_matching::Stylist;
use style::stylesheets::{Origin, Stylesheet, iter_font_face_rules};
use url::Url;
use util::cursor::Cursor;
use util::geometry::Au;
use util::logical_geometry::LogicalPoint;
@ -65,19 +79,6 @@ use util::task_state;
use util::time::{TimeProfilerCategory, ProfilerMetadata, TimeProfilerChan};
use util::time::{TimerMetadataFrameType, TimerMetadataReflowType, profile};
use util::workqueue::WorkQueue;
use std::borrow::ToOwned;
use std::cell::Cell;
use std::ops::{Deref, DerefMut};
use std::sync::mpsc::{channel, Sender, Receiver, Select};
use std::mem;
use std::ptr;
use style::selector_matching::Stylist;
use style::computed_values::{filter, mix_blend_mode};
use style::stylesheets::{Origin, Stylesheet, iter_font_face_rules};
use style::node::TNode;
use style::media_queries::{MediaType, MediaQueryList, Device};
use std::sync::{Arc, Mutex, MutexGuard};
use url::Url;
/// Mutable data belonging to the LayoutTask.
///
@ -785,6 +786,7 @@ impl LayoutTask {
&origin,
&origin,
0,
&Matrix2D::identity(),
filter::T::new(Vec::new()),
mix_blend_mode::T::normal,
Some(paint_layer)));

View file

@ -8,13 +8,15 @@
use fragment::Fragment;
use geom::SideOffsets2D;
use style::values::computed::{LengthOrPercentageOrAuto, LengthOrPercentageOrNone, LengthOrPercentage};
use style::properties::ComputedValues;
use util::geometry::Au;
use util::logical_geometry::LogicalMargin;
use geom::{Matrix2D, SideOffsets2D, Size2D};
use std::cmp::{max, min};
use std::fmt;
use style::computed_values::transform::ComputedMatrix;
use style::properties::ComputedValues;
use style::values::computed::{LengthAndPercentage, LengthOrPercentageOrAuto};
use style::values::computed::{LengthOrPercentageOrNone, LengthOrPercentage};
use util::geometry::Au;
use util::logical_geometry::LogicalMargin;
/// A collapsible margin. See CSS 2.1 § 8.3.1.
#[derive(Copy)]
@ -390,3 +392,30 @@ pub fn padding_from_style(style: &ComputedValues, containing_block_inline_size:
specified(padding_style.padding_bottom, containing_block_inline_size),
specified(padding_style.padding_left, containing_block_inline_size)))
}
pub trait ToGfxMatrix {
fn to_gfx_matrix(&self, containing_size: &Size2D<Au>) -> Matrix2D<f32>;
}
impl ToGfxMatrix for ComputedMatrix {
fn to_gfx_matrix(&self, containing_size: &Size2D<Au>) -> Matrix2D<f32> {
Matrix2D::new(self.m11 as f32,
self.m12 as f32,
self.m21 as f32,
self.m22 as f32,
self.m31.to_au(containing_size.width).to_subpx() as f32,
self.m32.to_au(containing_size.height).to_subpx() as f32)
}
}
trait ToAu {
fn to_au(&self, containing_size: Au) -> Au;
}
impl ToAu for LengthAndPercentage {
#[inline]
fn to_au(&self, containing_size: Au) -> Au {
self.length + Au::from_frac_px(self.percentage * containing_size.to_subpx())
}
}

View file

@ -90,6 +90,9 @@ partial interface CSSStyleDeclaration {
[TreatNullAs=EmptyString] attribute DOMString clip;
[TreatNullAs=EmptyString] attribute DOMString transform;
[TreatNullAs=EmptyString] attribute DOMString transformOrigin;
[TreatNullAs=EmptyString] attribute DOMString direction;
[TreatNullAs=EmptyString] attribute DOMString filter;

View file

@ -2687,6 +2687,7 @@ pub mod longhands {
}
}
</%self:longhand>
<%self:longhand name="filter">
use values::specified::Angle;
pub use self::computed_value::T as SpecifiedValue;
@ -2834,6 +2835,488 @@ pub mod longhands {
}
</%self:longhand>
<%self:longhand name="transform">
use values::CSSFloat;
use values::computed::{ToComputedValue, Context};
use cssparser::ToCss;
use std::f64;
use std::ops::Mul;
use text_writer::{self, TextWriter};
use util::geometry::Au;
pub mod computed_value {
use values::CSSFloat;
use values::computed;
use std::num::Float;
use std::ops::Mul;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ComputedMatrix {
pub m11: CSSFloat, pub m12: CSSFloat,
pub m21: CSSFloat, pub m22: CSSFloat,
pub m31: computed::LengthAndPercentage, pub m32: computed::LengthAndPercentage,
}
impl Mul<ComputedMatrix> for ComputedMatrix {
type Output = ComputedMatrix;
fn mul(self, other: ComputedMatrix) -> ComputedMatrix {
ComputedMatrix {
m11: self.m11*other.m11 + self.m12*other.m21,
m12: self.m11*other.m12 + self.m12*other.m22,
m21: self.m21*other.m11 + self.m22*other.m21,
m22: self.m21*other.m12 + self.m22*other.m22,
m31: self.m31.clone()*other.m11 + self.m32.clone()*other.m21 + other.m31,
m32: self.m31*other.m12 + self.m32*other.m22 + other.m32,
}
}
}
impl ComputedMatrix {
#[inline]
fn new(m11: CSSFloat,
m12: CSSFloat,
m21: CSSFloat,
m22: CSSFloat,
m31: computed::LengthAndPercentage,
m32: computed::LengthAndPercentage)
-> ComputedMatrix {
ComputedMatrix {
m11: m11, m12: m12,
m21: m21, m22: m22,
m31: m31, m32: m32,
}
}
#[inline]
pub fn identity() -> ComputedMatrix {
ComputedMatrix {
m11: 1.0, m12: 0.0,
m21: 0.0, m22: 1.0,
m31: computed::LengthAndPercentage::zero(),
m32: computed::LengthAndPercentage::zero(),
}
}
pub fn scale(&mut self, sx: CSSFloat, sy: CSSFloat) {
*self = ComputedMatrix::new(sx, 0.0,
0.0, sy,
computed::LengthAndPercentage::zero(),
computed::LengthAndPercentage::zero()) *
(*self).clone()
}
pub fn skew(&mut self, sx: CSSFloat, sy: CSSFloat) {
*self = ComputedMatrix::new(1.0, sx,
sy, 1.0,
computed::LengthAndPercentage::zero(),
computed::LengthAndPercentage::zero()) *
(*self).clone()
}
pub fn translate(&mut self,
tx: computed::LengthAndPercentage,
ty: computed::LengthAndPercentage) {
*self = ComputedMatrix::new(1.0, 0.0, 0.0, 1.0, tx, ty) * (*self).clone()
}
pub fn rotate(&mut self, theta: CSSFloat) {
*self = ComputedMatrix::new(theta.cos(), -theta.sin(),
theta.sin(), theta.cos(),
computed::LengthAndPercentage::zero(),
computed::LengthAndPercentage::zero()) *
(*self).clone()
}
}
pub type T = Option<ComputedMatrix>;
}
#[derive(Clone, Debug, PartialEq)]
pub struct SpecifiedMatrix {
m11: CSSFloat, m12: CSSFloat,
m21: CSSFloat, m22: CSSFloat,
m31: specified::LengthAndPercentage, m32: specified::LengthAndPercentage,
}
impl ToCss for Option<SpecifiedMatrix> {
fn to_css<W>(&self, _: &mut W) -> text_writer::Result where W: TextWriter {
// TODO(pcwalton)
Ok(())
}
}
impl Mul<SpecifiedMatrix> for SpecifiedMatrix {
type Output = SpecifiedMatrix;
fn mul(self, other: SpecifiedMatrix) -> SpecifiedMatrix {
SpecifiedMatrix {
m11: self.m11*other.m11 + self.m12*other.m21,
m12: self.m11*other.m12 + self.m12*other.m22,
m21: self.m21*other.m11 + self.m22*other.m21,
m22: self.m21*other.m12 + self.m22*other.m22,
m31: self.m31.clone()*other.m11 + self.m32.clone()*other.m21 + other.m31,
m32: self.m31*other.m12 + self.m32*other.m22 + other.m32,
}
}
}
impl SpecifiedMatrix {
#[inline]
fn new(m11: CSSFloat,
m12: CSSFloat,
m21: CSSFloat,
m22: CSSFloat,
m31: specified::LengthAndPercentage,
m32: specified::LengthAndPercentage)
-> SpecifiedMatrix {
SpecifiedMatrix {
m11: m11, m12: m12,
m21: m21, m22: m22,
m31: m31, m32: m32,
}
}
}
fn parse_two_lengths_or_percentages(input: &mut Parser)
-> Result<(specified::LengthAndPercentage,
specified::LengthAndPercentage),()> {
let first = try!(specified::LengthAndPercentage::parse(input));
let second = input.try(|input| {
try!(input.expect_comma());
specified::LengthAndPercentage::parse(input)
}).unwrap_or(specified::LengthAndPercentage::zero());
Ok((first, second))
}
fn parse_two_floats(input: &mut Parser) -> Result<(CSSFloat,CSSFloat),()> {
let first = try!(input.expect_number());
let second = input.try(|input| {
try!(input.expect_comma());
input.expect_number()
}).unwrap_or(first);
Ok((first, second))
}
#[derive(Clone, Debug, PartialEq)]
enum SpecifiedOperation {
Matrix(SpecifiedMatrix),
Translate(specified::LengthAndPercentage, specified::LengthAndPercentage),
Scale(CSSFloat, CSSFloat),
Rotate(specified::Angle),
Skew(CSSFloat, CSSFloat),
}
impl ToCss for SpecifiedOperation {
fn to_css<W>(&self, _: &mut W) -> text_writer::Result where W: TextWriter {
// TODO(pcwalton)
Ok(())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct SpecifiedValue(Vec<SpecifiedOperation>);
impl ToCss for SpecifiedValue {
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
let mut first = true;
for operation in self.0.iter() {
if !first {
try!(dest.write_str(" "));
}
first = false;
try!(operation.to_css(dest))
}
Ok(())
}
}
#[inline]
pub fn get_initial_value() -> computed_value::T {
None
}
pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
if input.try(|input| input.expect_ident_matching("none")).is_ok() {
return Ok(SpecifiedValue(Vec::new()))
}
let mut result = Vec::new();
loop {
let name = match input.expect_function() {
Ok(name) => name,
Err(_) => break,
};
match_ignore_ascii_case! {
name,
"matrix" => {
try!(input.parse_nested_block(|input| {
let values = try!(input.parse_comma_separated(|input| {
input.expect_number()
}));
if values.len() != 6 {
return Err(())
}
let (tx, ty) =
(specified::Length::Absolute(Au::from_frac_px(values[4])),
specified::Length::Absolute(Au::from_frac_px(values[5])));
let (tx, ty) =
(specified::LengthAndPercentage::from_length(tx),
specified::LengthAndPercentage::from_length(ty));
result.push(SpecifiedOperation::Matrix(
SpecifiedMatrix::new(values[0], values[1],
values[2], values[3],
tx, ty)));
Ok(())
}))
},
"translate" => {
try!(input.parse_nested_block(|input| {
let (tx, ty) = try!(parse_two_lengths_or_percentages(input));
result.push(SpecifiedOperation::Translate(tx, ty));
Ok(())
}))
},
"translatex" => {
try!(input.parse_nested_block(|input| {
let tx = try!(specified::LengthOrPercentage::parse(input));
result.push(SpecifiedOperation::Translate(
specified::LengthAndPercentage::from_length_or_percentage(
&tx),
specified::LengthAndPercentage::zero()));
Ok(())
}))
},
"translatey" => {
try!(input.parse_nested_block(|input| {
let ty = try!(specified::LengthOrPercentage::parse(input));
result.push(SpecifiedOperation::Translate(
specified::LengthAndPercentage::zero(),
specified::LengthAndPercentage::from_length_or_percentage(
&ty)));
Ok(())
}))
},
"scale" => {
try!(input.parse_nested_block(|input| {
let (sx, sy) = try!(parse_two_floats(input));
result.push(SpecifiedOperation::Scale(sx, sy));
Ok(())
}))
},
"scalex" => {
try!(input.parse_nested_block(|input| {
let sx = try!(input.expect_number());
result.push(SpecifiedOperation::Scale(sx, 1.0));
Ok(())
}))
},
"scaley" => {
try!(input.parse_nested_block(|input| {
let sy = try!(input.expect_number());
result.push(SpecifiedOperation::Scale(1.0, sy));
Ok(())
}))
},
"rotate" => {
try!(input.parse_nested_block(|input| {
let theta = try!(specified::Angle::parse(input));
result.push(SpecifiedOperation::Rotate(theta));
Ok(())
}))
},
"skew" => {
try!(input.parse_nested_block(|input| {
let (sx, sy) = try!(parse_two_floats(input));
result.push(SpecifiedOperation::Skew(sx, sy));
Ok(())
}))
},
"skewx" => {
try!(input.parse_nested_block(|input| {
let sx = try!(input.expect_number());
result.push(SpecifiedOperation::Skew(sx, 1.0));
Ok(())
}))
},
"skewy" => {
try!(input.parse_nested_block(|input| {
let sy = try!(input.expect_number());
result.push(SpecifiedOperation::Skew(1.0, sy));
Ok(())
}))
}
_ => return Err(())
}
}
if !result.is_empty() {
Ok(SpecifiedValue(result))
} else {
Err(())
}
}
impl ToComputedValue for SpecifiedValue {
type ComputedValue = computed_value::T;
#[inline]
fn to_computed_value(&self, context: &Context) -> computed_value::T {
if self.0.is_empty() {
return None
}
let mut result = computed_value::ComputedMatrix::identity();
for operation in self.0.iter() {
match *operation {
SpecifiedOperation::Matrix(ref matrix) => {
result = computed_value::ComputedMatrix {
m11: matrix.m11, m12: matrix.m12,
m21: matrix.m21, m22: matrix.m22,
m31: matrix.m31.to_computed_value(context),
m32: matrix.m32.to_computed_value(context),
} * result
}
SpecifiedOperation::Translate(ref tx, ref ty) => {
result.translate(tx.to_computed_value(context),
ty.to_computed_value(context))
}
SpecifiedOperation::Scale(sx, sy) => {
result.scale(sx, sy)
}
SpecifiedOperation::Rotate(ref theta) => {
result.rotate(f64::consts::PI_2 - theta.radians());
}
SpecifiedOperation::Skew(sx, sy) => {
result.skew(sx, sy)
}
}
}
Some(result)
}
}
</%self:longhand>
<%self:longhand name="transform-origin">
use values::computed::{ToComputedValue, Context};
use values::specified::LengthOrPercentage;
use cssparser::ToCss;
use text_writer::{self, TextWriter};
pub mod computed_value {
use values::computed::LengthOrPercentage;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct T {
pub horizontal: LengthOrPercentage,
pub vertical: LengthOrPercentage,
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SpecifiedValue {
horizontal: LengthOrPercentage,
vertical: LengthOrPercentage,
}
impl ToCss for SpecifiedValue {
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
try!(self.horizontal.to_css(dest));
try!(dest.write_str(" "));
self.vertical.to_css(dest)
}
}
#[inline]
pub fn get_initial_value() -> computed_value::T {
computed_value::T {
horizontal: computed::LengthOrPercentage::Percentage(0.5),
vertical: computed::LengthOrPercentage::Percentage(0.5),
}
}
pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
let (mut horizontal, mut vertical) = (None, None);
loop {
if let Err(_) = input.try(|input| {
let token = try!(input.expect_ident());
match_ignore_ascii_case! {
token,
"left" => {
if horizontal.is_none() {
horizontal = Some(LengthOrPercentage::Percentage(0.0))
} else {
return Err(())
}
},
"center" => {
if horizontal.is_none() {
horizontal = Some(LengthOrPercentage::Percentage(0.5))
} else if vertical.is_none() {
vertical = Some(LengthOrPercentage::Percentage(0.5))
} else {
return Err(())
}
},
"right" => {
if horizontal.is_none() {
horizontal = Some(LengthOrPercentage::Percentage(1.0))
} else {
return Err(())
}
},
"top" => {
if vertical.is_none() {
vertical = Some(LengthOrPercentage::Percentage(0.0))
} else {
return Err(())
}
},
"bottom" => {
if vertical.is_none() {
vertical = Some(LengthOrPercentage::Percentage(1.0))
} else {
return Err(())
}
}
_ => return Err(())
}
Ok(())
}) {
match LengthOrPercentage::parse(input) {
Ok(value) if horizontal.is_none() => horizontal = Some(value),
Ok(value) if vertical.is_none() => vertical = Some(value),
_ => break,
}
}
}
if horizontal.is_some() || vertical.is_some() {
Ok(SpecifiedValue {
horizontal: horizontal.unwrap_or(LengthOrPercentage::Percentage(0.5)),
vertical: vertical.unwrap_or(LengthOrPercentage::Percentage(0.5)),
})
} else {
Err(())
}
}
impl ToComputedValue for SpecifiedValue {
type ComputedValue = computed_value::T;
#[inline]
fn to_computed_value(&self, context: &Context) -> computed_value::T {
computed_value::T {
horizontal: self.horizontal.to_computed_value(context),
vertical: self.vertical.to_computed_value(context),
}
}
}
</%self:longhand>
${single_keyword("mix-blend-mode",
"""normal multiply screen overlay darken lighten color-dodge
color-burn hard-light soft-light difference exclusion hue

View file

@ -57,6 +57,7 @@ pub mod specified {
use std::fmt;
use std::fmt::{Formatter, Debug};
use std::num::{NumCast, ToPrimitive};
use std::ops::{Add, Mul};
use url::Url;
use cssparser::{self, Token, Parser, ToCss, CssStringWriter};
use geom::size::Size2D;
@ -243,6 +244,47 @@ pub mod specified {
}
}
impl Mul<CSSFloat> for Length {
type Output = Length;
#[inline]
fn mul(self, scalar: CSSFloat) -> Length {
match self {
Length::Absolute(Au(v)) => Length::Absolute(Au(((v as f64) * scalar) as i32)),
Length::FontRelative(v) => Length::FontRelative(v * scalar),
Length::ViewportPercentage(v) => Length::ViewportPercentage(v * scalar),
Length::ServoCharacterWidth(_) => panic!("Can't multiply ServoCharacterWidth!"),
}
}
}
impl Mul<CSSFloat> for FontRelativeLength {
type Output = FontRelativeLength;
#[inline]
fn mul(self, scalar: CSSFloat) -> FontRelativeLength {
match self {
FontRelativeLength::Em(v) => FontRelativeLength::Em(v * scalar),
FontRelativeLength::Ex(v) => FontRelativeLength::Ex(v * scalar),
FontRelativeLength::Rem(v) => FontRelativeLength::Rem(v * scalar),
}
}
}
impl Mul<CSSFloat> for ViewportPercentageLength {
type Output = ViewportPercentageLength;
#[inline]
fn mul(self, scalar: CSSFloat) -> ViewportPercentageLength {
match self {
ViewportPercentageLength::Vw(v) => ViewportPercentageLength::Vw(v * scalar),
ViewportPercentageLength::Vh(v) => ViewportPercentageLength::Vh(v * scalar),
ViewportPercentageLength::Vmin(v) => ViewportPercentageLength::Vmin(v * scalar),
ViewportPercentageLength::Vmax(v) => ViewportPercentageLength::Vmax(v * scalar),
}
}
}
const AU_PER_PX: CSSFloat = 60.;
const AU_PER_IN: CSSFloat = AU_PER_PX * 96.;
const AU_PER_CM: CSSFloat = AU_PER_IN / 2.54;
@ -444,6 +486,81 @@ pub mod specified {
}
}
/// The sum of a series of lengths and a percentage. This is used in `calc()` and other things
/// that effectively work like it (e.g. transforms).
#[derive(Clone, Debug, PartialEq)]
pub struct LengthAndPercentage {
/// The length components.
pub lengths: Vec<Length>,
/// The percentage component.
pub percentage: CSSFloat,
}
impl LengthAndPercentage {
pub fn zero() -> LengthAndPercentage {
LengthAndPercentage {
lengths: Vec::new(),
percentage: 0.0,
}
}
pub fn from_length_or_percentage(length_or_percentage: &LengthOrPercentage)
-> LengthAndPercentage {
match *length_or_percentage {
LengthOrPercentage::Length(ref length) => {
LengthAndPercentage::from_length(*length)
}
LengthOrPercentage::Percentage(percentage) => {
LengthAndPercentage::from_percentage(percentage)
}
}
}
pub fn parse(input: &mut Parser) -> Result<LengthAndPercentage, ()> {
LengthOrPercentage::parse(input).map(|value| {
LengthAndPercentage::from_length_or_percentage(&value)
})
}
pub fn from_length(length: Length) -> LengthAndPercentage {
LengthAndPercentage {
lengths: vec![length],
percentage: 0.0,
}
}
pub fn from_percentage(percentage: CSSFloat) -> LengthAndPercentage {
LengthAndPercentage {
lengths: Vec::new(),
percentage: percentage,
}
}
}
impl Add<LengthAndPercentage> for LengthAndPercentage {
type Output = LengthAndPercentage;
fn add(self, other: LengthAndPercentage) -> LengthAndPercentage {
let mut new_lengths = self.lengths.clone();
new_lengths.push_all(other.lengths.as_slice());
LengthAndPercentage {
lengths: new_lengths,
percentage: self.percentage + other.percentage,
}
}
}
impl Mul<CSSFloat> for LengthAndPercentage {
type Output = LengthAndPercentage;
fn mul(self, scalar: CSSFloat) -> LengthAndPercentage {
LengthAndPercentage {
lengths: self.lengths.iter().map(|length| *length * scalar).collect(),
percentage: self.percentage * scalar,
}
}
}
// http://dev.w3.org/csswg/css2/colors.html#propdef-background-position
#[derive(Clone, PartialEq, Copy)]
pub enum PositionComponent {
@ -756,6 +873,7 @@ pub mod computed {
use geom::size::Size2D;
use properties::longhands;
use std::fmt;
use std::ops::{Add, Mul};
use url::Url;
use util::geometry::Au;
@ -929,6 +1047,62 @@ pub mod computed {
}
}
/// The sum of a series of lengths and a percentage. This is used in `calc()` and other things
/// that effectively work like it (e.g. transforms).
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LengthAndPercentage {
/// The length component.
pub length: Au,
/// The percentage component.
pub percentage: CSSFloat,
}
impl LengthAndPercentage {
#[inline]
pub fn zero() -> LengthAndPercentage {
LengthAndPercentage {
length: Au(0),
percentage: 0.0,
}
}
}
impl ToComputedValue for specified::LengthAndPercentage {
type ComputedValue = LengthAndPercentage;
fn to_computed_value(&self, context: &Context) -> LengthAndPercentage {
let mut total_length = Au(0);
for length in self.lengths.iter() {
total_length = total_length + length.to_computed_value(context)
}
LengthAndPercentage {
length: total_length,
percentage: self.percentage,
}
}
}
impl Add<LengthAndPercentage> for LengthAndPercentage {
type Output = LengthAndPercentage;
fn add(self, other: LengthAndPercentage) -> LengthAndPercentage {
LengthAndPercentage {
length: self.length + other.length,
percentage: self.percentage + other.percentage,
}
}
}
impl Mul<CSSFloat> for LengthAndPercentage {
type Output = LengthAndPercentage;
fn mul(self, scalar: CSSFloat) -> LengthAndPercentage {
LengthAndPercentage {
length: Au::from_frac_px(self.length.to_subpx() * scalar),
percentage: self.percentage * scalar,
}
}
}
impl ToComputedValue for specified::Image {
type ComputedValue = Image;

View file

@ -251,6 +251,12 @@ impl Au {
((s as f64) / 60f64).round() as isize
}
#[inline]
pub fn to_frac32_px(&self) -> f32 {
let Au(s) = *self;
(s as f32) / 60f32
}
#[inline]
pub fn to_subpx(&self) -> f64 {
let Au(s) = *self;

View file

@ -267,6 +267,8 @@ experimental == rtl_simple.html rtl_simple_ref.html
== text_transform_lowercase_a.html text_transform_lowercase_ref.html
== text_transform_none_a.html text_transform_none_ref.html
== text_transform_uppercase_a.html text_transform_uppercase_ref.html
== transform_simple_a.html transform_simple_ref.html
== transform_stacking_context_a.html transform_stacking_context_ref.html
== upper_id_attr.html upper_id_attr_ref.html
flaky_cpu,experimental == vertical-lr-blocks.html vertical-lr-blocks_ref.html
== vertical_align_bottom_a.html vertical_align_bottom_ref.html

View file

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<!-- Tests that basic CSS transforms work. -->
<style>
section {
position: absolute;
width: 50px;
height: 50px;
top: 25px;
left: 50px;
transform: rotate(90deg) scale(1, 2);
}
div {
position: absolute;
width: 25px;
height: 25px;
}
#a {
background: cyan;
}
#b {
background: magenta;
}
#c {
background: yellow;
}
#d {
background: black;
}
#a, #b {
top: 0;
}
#c, #d {
top: 25px;
}
#a, #c {
left: 0;
}
#b, #d {
left: 25px;
}
</style>
</head>
<body>
<section><div id=a></div><div id=b></div><div id=c></div><div id=d></div></section>
</body>
</html>

View file

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<!-- Tests that basic CSS transforms work. -->
<style>
section {
position: absolute;
width: 100px;
height: 50px;
top: 25px;
left: 25px;
}
div {
position: absolute;
width: 50px;
height: 25px;
}
#a {
background: yellow;
}
#b {
background: cyan;
}
#c {
background: black;
}
#d {
background: magenta;
}
#a, #b {
top: 0;
}
#c, #d {
top: 25px;
}
#a, #c {
left: 0;
}
#b, #d {
left: 50px;
}
</style>
</head>
<body>
<section><div id=a></div><div id=b></div><div id=c></div><div id=d></div></section>
</body>
</html>

View file

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html>
<head>
<!-- Tests that `transform` causes a new stacking context to be formed. -->
<style>
body {
margin: 0;
}
section {
position: absolute;
width: 100px;
height: 100px;
}
#a {
background: red;
top: 0;
left: 0;
z-index: 1;
}
#b {
background: #00ff00;
top: 25px;
left: 25px;
z-index: 2;
}
#c {
background: blue;
top: 50px;
left: 50px;
z-index: 3;
}
#container {
transform: matrix(1, 0, 0, 1, 0, 0);
width: 200px;
height: 200px;
}
</style>
</head>
<body>
<section id=a></section>
<section id=c></section>
<div id=container>
<section id=b></section>
</div>
</body>
</html>

View file

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<!-- Tests that `transform` causes a new stacking context to be formed. -->
<style>
section {
position: absolute;
width: 100px;
height: 100px;
}
#a {
background: red;
top: 0;
left: 0;
z-index: 2;
}
#b {
background: #00ff00;
top: 25px;
left: 25px;
z-index: 1;
}
#c {
background: blue;
top: 50px;
left: 50px;
z-index: 3;
}
</style>
</head>
<body>
<section id=a></section>
<section id=c></section>
<section id=b></section>
</body>
</html>