mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
auto merge of #5134 : pcwalton/servo/transforms, r=SimonSapin
r? @SimonSapin
This commit is contained in:
commit
203240c1d8
14 changed files with 964 additions and 35 deletions
|
@ -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.
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
49
tests/ref/transform_simple_a.html
Normal file
49
tests/ref/transform_simple_a.html
Normal 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>
|
||||
|
49
tests/ref/transform_simple_ref.html
Normal file
49
tests/ref/transform_simple_ref.html
Normal 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>
|
||||
|
||||
|
54
tests/ref/transform_stacking_context_a.html
Normal file
54
tests/ref/transform_stacking_context_a.html
Normal 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>
|
||||
|
||||
|
||||
|
41
tests/ref/transform_stacking_context_ref.html
Normal file
41
tests/ref/transform_stacking_context_ref.html
Normal 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>
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue