diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs index 6bdba843f91..926d8538cb8 100644 --- a/components/gfx/display_list/mod.rs +++ b/components/gfx/display_list/mod.rs @@ -214,18 +214,28 @@ impl SizeOf for DisplayList { pub struct StackingContext { /// The display items that make up this stacking context. pub display_list: Box, + /// The layer for this stacking context, if there is one. pub layer: Option>, + /// The position and size of this stacking context. pub bounds: Rect, /// The overflow rect for this stacking context in its coordinate system. pub overflow: Rect, + /// 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, } impl StackingContext { @@ -235,6 +245,7 @@ impl StackingContext { bounds: &Rect, overflow: &Rect, z_index: i32, + transform: &Matrix2D, filters: filter::T, blend_mode: mix_blend_mode::T, layer: Option>) @@ -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, transform: &Matrix2D, clip_rect: Option<&Rect>) { + 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, + mut point: Point2D, result: &mut Vec, topmost_only: bool) { fn hit_test_in_list<'a,I>(point: Point2D, @@ -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. diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index fe361cd2575..fa1472f9b11 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -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)) diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 329d7849508..2e109af57cf 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -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 diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index d8c09a18218..2a1bd02e369 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -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))); diff --git a/components/layout/model.rs b/components/layout/model.rs index 4f617629f42..571a45a199e 100644 --- a/components/layout/model.rs +++ b/components/layout/model.rs @@ -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) -> Matrix2D; +} + +impl ToGfxMatrix for ComputedMatrix { + fn to_gfx_matrix(&self, containing_size: &Size2D) -> Matrix2D { + 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()) + } +} + diff --git a/components/script/dom/webidls/CSSStyleDeclaration.webidl b/components/script/dom/webidls/CSSStyleDeclaration.webidl index be5afa7244d..3be5abe1099 100644 --- a/components/script/dom/webidls/CSSStyleDeclaration.webidl +++ b/components/script/dom/webidls/CSSStyleDeclaration.webidl @@ -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; diff --git a/components/style/properties.mako.rs b/components/style/properties.mako.rs index 9592107cb81..5603c5ebf71 100644 --- a/components/style/properties.mako.rs +++ b/components/style/properties.mako.rs @@ -2687,6 +2687,7 @@ pub mod longhands { } } + <%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 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 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; + } + + #[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 { + fn to_css(&self, _: &mut W) -> text_writer::Result where W: TextWriter { + // TODO(pcwalton) + Ok(()) + } + } + + impl Mul 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(&self, _: &mut W) -> text_writer::Result where W: TextWriter { + // TODO(pcwalton) + Ok(()) + } + } + + #[derive(Clone, Debug, PartialEq)] + pub struct SpecifiedValue(Vec); + + impl ToCss for SpecifiedValue { + fn to_css(&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 { + 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 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(&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 { + 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), + } + } + } + + ${single_keyword("mix-blend-mode", """normal multiply screen overlay darken lighten color-dodge color-burn hard-light soft-light difference exclusion hue diff --git a/components/style/values.rs b/components/style/values.rs index 8c57af3884c..e152c9d954a 100644 --- a/components/style/values.rs +++ b/components/style/values.rs @@ -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 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 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 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, + /// 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 { + 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 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 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 for LengthAndPercentage { + type Output = LengthAndPercentage; + + fn add(self, other: LengthAndPercentage) -> LengthAndPercentage { + LengthAndPercentage { + length: self.length + other.length, + percentage: self.percentage + other.percentage, + } + } + } + + impl Mul 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; diff --git a/components/util/geometry.rs b/components/util/geometry.rs index 3a165facb5c..f67859b4503 100644 --- a/components/util/geometry.rs +++ b/components/util/geometry.rs @@ -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; diff --git a/tests/ref/basic.list b/tests/ref/basic.list index cdfed56affd..0212f59dea9 100644 --- a/tests/ref/basic.list +++ b/tests/ref/basic.list @@ -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 diff --git a/tests/ref/transform_simple_a.html b/tests/ref/transform_simple_a.html new file mode 100644 index 00000000000..f5753001c5a --- /dev/null +++ b/tests/ref/transform_simple_a.html @@ -0,0 +1,49 @@ + + + + + + + +
+ + + diff --git a/tests/ref/transform_simple_ref.html b/tests/ref/transform_simple_ref.html new file mode 100644 index 00000000000..ad55c46fa69 --- /dev/null +++ b/tests/ref/transform_simple_ref.html @@ -0,0 +1,49 @@ + + + + + + + +
+ + + + diff --git a/tests/ref/transform_stacking_context_a.html b/tests/ref/transform_stacking_context_a.html new file mode 100644 index 00000000000..d966f9676f3 --- /dev/null +++ b/tests/ref/transform_stacking_context_a.html @@ -0,0 +1,54 @@ + + + + + + + +
+
+
+
+
+ + + + + diff --git a/tests/ref/transform_stacking_context_ref.html b/tests/ref/transform_stacking_context_ref.html new file mode 100644 index 00000000000..dd71c1c9347 --- /dev/null +++ b/tests/ref/transform_stacking_context_ref.html @@ -0,0 +1,41 @@ + + + + + + + +
+
+
+ + + +