Bug 1786161 - Support rect() function. r=devtools-reviewers,emilio

Note that rect() computes to the equivalent inset() function as well.
i.e. Given `rect(t r b l)`, the equivalent function is
`inset(t calc(100% - r) calc(100% - b) l)`.

The implementation is straightforward, and we don't have to change
anything in cpp because it is always `inset()` when building the gfx::Path.

The tests for clip-path will be added in the following patch.

Differential Revision: https://phabricator.services.mozilla.com/D183528
This commit is contained in:
Boris Chiou 2023-07-25 02:32:43 +00:00
parent 2161f57ee1
commit a0d6bab413
24 changed files with 342 additions and 137 deletions

View File

@ -1915,6 +1915,7 @@ exports.CSS_PROPERTIES = {
"padding-box",
"path",
"polygon",
"rect",
"revert",
"revert-layer",
"stroke-box",
@ -5869,6 +5870,7 @@ exports.CSS_PROPERTIES = {
"padding-box",
"path",
"polygon",
"rect",
"revert",
"revert-layer",
"stroke-box",
@ -9360,6 +9362,7 @@ exports.CSS_PROPERTIES = {
"path",
"polygon",
"ray",
"rect",
"reverse",
"revert",
"revert-layer",
@ -9425,6 +9428,7 @@ exports.CSS_PROPERTIES = {
"path",
"polygon",
"ray",
"rect",
"revert",
"revert-layer",
"stroke-box",
@ -11015,6 +11019,7 @@ exports.CSS_PROPERTIES = {
"path",
"polygon",
"radial-gradient",
"rect",
"repeating-conic-gradient",
"repeating-linear-gradient",
"repeating-radial-gradient",

View File

@ -4,6 +4,7 @@ prefs =
dom.animations-api.compositing.enabled=true
dom.animations.mainthread-synchronization-with-geometric-animations=true
gfx.omta.background-color=true
layout.css.basic-shape-rect.enabled=true
layout.css.basic-shape-xywh.enabled=true
layout.css.individual-transform.enabled=true
layout.css.motion-path.enabled=true

View File

@ -1260,6 +1260,19 @@ promise_test(async t => {
'offset-path:xywh() animation should be running on the compositor');
}, 'offset-path:xywh() animation runs on the compositor');
promise_test(async t => {
const div = addDiv(t);
const animation = div.animate({ offsetPath: ['rect(0% 0% 10px 10px)',
'rect(10% 10% 20px 20px)'] },
100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation);
await waitForPaints();
assert_animation_is_running_on_compositor(animation,
'offset-path:rect() animation should be running on the compositor');
}, 'offset-path:rect() animation runs on the compositor');
promise_test(async t => {
const div = addDiv(t);
const animation = div.animate({ offsetDistance: ['0%', '100%'] },

View File

@ -1,6 +1,7 @@
[DEFAULT]
prefs =
layout.css.nesting.enabled=true
layout.css.basic-shape-rect.enabled=true
layout.css.basic-shape-xywh.enabled=true
support-files =
bug1202095.css

View File

@ -207,10 +207,10 @@ function do_test() {
ok(testValues(values, expected), "property box-shadow's values");
// Regression test for bug 1255379.
var expected = [ "inherit", "initial", "unset", "revert", "revert-layer", "none", "url",
"polygon", "circle", "ellipse", "inset", "path", "xywh",
"fill-box", "stroke-box", "view-box", "margin-box",
"border-box", "padding-box", "content-box" ];
var expected = [ "inherit", "initial", "unset", "revert", "revert-layer",
"none", "url", "polygon", "circle", "ellipse", "inset",
"path", "rect", "xywh", "fill-box", "stroke-box", "view-box",
"margin-box", "border-box", "padding-box", "content-box" ];
var values = InspectorUtils.getCSSValuesForProperty("clip-path");
ok(testValues(values, expected), "property clip-path's values");

View File

@ -22,6 +22,7 @@ prefs =
layout.css.scrollbar-gutter.enabled=true
layout.css.linear-easing-function.enabled=true
layout.css.animation-composition.enabled=true
layout.css.basic-shape-rect.enabled=true
layout.css.basic-shape-xywh.enabled=true
support-files =
animation_utils.js

View File

@ -1017,15 +1017,26 @@ var basicShapeUnbalancedValues = [
"inset(1px 2px 3px 4px round 5px / 6px",
];
var basicShapeXywhValues = [];
var basicShapeXywhRectValues = [];
if (IsCSSPropertyPrefEnabled("layout.css.basic-shape-xywh.enabled")) {
basicShapeXywhValues = [
basicShapeXywhRectValues.push(
"xywh(1px 2% 3px 4em)",
"xywh(1px 2% 3px 4em round 0px)",
"xywh(1px 2% 3px 4em round 0px 1%)",
"xywh(1px 2% 3px 4em round 0px 1% 2px)",
"xywh(1px 2% 3px 4em round 0px 1% 2px 3em)",
];
"xywh(1px 2% 3px 4em round 0px 1% 2px 3em)"
);
}
if (IsCSSPropertyPrefEnabled("layout.css.basic-shape-rect.enabled")) {
basicShapeXywhRectValues.push(
"rect(auto auto auto auto)",
"rect(1px 2% auto 4em)",
"rect(1px 2% auto 4em round 0px)",
"rect(1px 2% auto 4em round 0px 1%)",
"rect(1px 2% auto 4em round 0px 1% 2px)",
"rect(1px 2% auto 4em round 0px 1% 2px 3em)"
);
}
if (/* mozGradientsEnabled */ true) {
@ -8727,7 +8738,7 @@ var gCSSProperties = {
.concat(basicShapeSVGBoxValues)
.concat(basicShapeOtherValues)
.concat(basicShapeOtherValuesWithFillRule)
.concat(basicShapeXywhValues),
.concat(basicShapeXywhRectValues),
invalid_values: [
"path(nonzero)",
"path(abs, 'M 10 10 L 10 10 z')",
@ -13520,7 +13531,7 @@ if (IsCSSPropertyPrefEnabled("layout.css.motion-path.enabled")) {
if (IsCSSPropertyPrefEnabled("layout.css.motion-path-basic-shapes.enabled")) {
gCSSProperties["offset-path"]["other_values"].push(
...basicShapeOtherValues,
...basicShapeXywhValues
...basicShapeXywhRectValues
);
}

View File

@ -57,9 +57,6 @@ class MOZ_STACK_CLASS CSSClipPathInstance {
already_AddRefed<Path> CreateClipPathPath(DrawTarget* aDrawTarget);
already_AddRefed<Path> CreateClipPathXywh(DrawTarget* aDrawTarget,
const nsRect& aRefBox);
/**
* The frame for the element that is currently being clipped.
*/

View File

@ -8315,6 +8315,13 @@
value: false
mirror: always
# Is support for rect() enabled?
- name: layout.css.basic-shape-rect.enabled
type: RelaxedAtomicBool
value: @IS_NIGHTLY_BUILD@
mirror: always
rust: true
# Is support for xywh() enabled?
- name: layout.css.basic-shape-xywh.enabled
type: RelaxedAtomicBool

View File

@ -83,6 +83,24 @@ where
// <first> <second> <third> <fourth>
Ok(Self::new(first, second, third, fourth))
}
/// Parses a new `Rect<T>` value which all components must be specified, with the given parse
/// function.
pub fn parse_all_components_with<'i, 't, Parse>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
parse: Parse,
) -> Result<Self, ParseError<'i>>
where
Parse: Fn(&ParserContext, &mut Parser<'i, 't>) -> Result<T, ParseError<'i>>,
{
let first = parse(context, input)?;
let second = parse(context, input)?;
let third = parse(context, input)?;
let fourth = parse(context, input)?;
// <first> <second> <third> <fourth>
Ok(Self::new(first, second, third, fourth))
}
}
impl<T> Parse for Rect<T>

View File

@ -15,6 +15,7 @@ use crate::values::generics::basic_shape::{Path, PolygonCoord};
use crate::values::generics::rect::Rect;
use crate::values::specified::border::BorderRadius;
use crate::values::specified::image::Image;
use crate::values::specified::length::LengthPercentageOrAuto;
use crate::values::specified::position::{Position, PositionOrAuto};
use crate::values::specified::url::SpecifiedUrl;
use crate::values::specified::{LengthPercentage, NonNegativeLengthPercentage, SVGPathData};
@ -79,6 +80,26 @@ pub struct Xywh {
pub round: BorderRadius,
}
/// Defines a rectangle via insets from the top and left edges of the reference box.
///
/// https://drafts.csswg.org/css-shapes-1/#funcdef-basic-shape-rect
#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem)]
#[repr(C)]
pub struct ShapeRectFunction {
/// The four <length-percentage>s define the position of the top, right, bottom, and left edges
/// of a rectangle, respectively, as insets from the top edge of the reference box (for the
/// first and third values) or the left edge of the reference box (for the second and fourth
/// values).
///
/// An auto value makes the edge of the box coincide with the corresponding edge of the
/// reference box: its equivalent to 0% as the first (top) or fourth (left) value, and
/// equivalent to 100% as the second (right) or third (bottom) value.
pub rect: Rect<LengthPercentageOrAuto>,
/// The optional <border-radius> argument(s) define rounded corners for the inset rectangle
/// using the border-radius shorthand syntax.
pub round: BorderRadius,
}
/// The specified value of <basic-shape-rect>.
/// <basic-shape-rect> = <inset()> | <rect()> | <xywh()>
///
@ -90,7 +111,9 @@ pub enum BasicShapeRect {
/// Defines a xywh function.
#[css(function)]
Xywh(Xywh),
// TODO: Bug 1786161. Add rect().
/// Defines a rect function.
#[css(function)]
Rect(ShapeRectFunction),
}
/// For filled shapes, we use fill-rule, and store it for path() and polygon().
@ -149,8 +172,8 @@ bitflags! {
const INSET = 1 << 0;
/// xywh().
const XYWH = 1 << 1;
// TODO: Bug 1786161. Add rect().
// const RECT = 1 << 2;
/// rect().
const RECT = 1 << 2;
/// circle().
const CIRCLE = 1 << 3;
/// ellipse().
@ -166,6 +189,7 @@ bitflags! {
const ALL =
Self::INSET.bits |
Self::XYWH.bits |
Self::RECT.bits |
Self::CIRCLE.bits |
Self::ELLIPSE.bits |
Self::POLYGON.bits |
@ -299,6 +323,22 @@ impl BasicShape {
.map(BasicShapeRect::Inset)
.map(BasicShape::Rect)
},
"xywh"
if flags.contains(AllowedBasicShapes::XYWH)
&& static_prefs::pref!("layout.css.basic-shape-xywh.enabled") =>
{
Xywh::parse_function_arguments(context, i)
.map(BasicShapeRect::Xywh)
.map(BasicShape::Rect)
},
"rect"
if flags.contains(AllowedBasicShapes::RECT)
&& static_prefs::pref!("layout.css.basic-shape-rect.enabled") =>
{
ShapeRectFunction::parse_function_arguments(context, i)
.map(BasicShapeRect::Rect)
.map(BasicShape::Rect)
},
"circle" if flags.contains(AllowedBasicShapes::CIRCLE) => {
Circle::parse_function_arguments(context, i, default_position)
.map(BasicShape::Circle)
@ -314,14 +354,6 @@ impl BasicShape {
"path" if flags.contains(AllowedBasicShapes::PATH) => {
Path::parse_function_arguments(i, shape_type).map(BasicShape::Path)
},
"xywh"
if flags.contains(AllowedBasicShapes::XYWH)
&& static_prefs::pref!("layout.css.basic-shape-xywh.enabled") =>
{
Xywh::parse_function_arguments(context, i)
.map(BasicShapeRect::Xywh)
.map(BasicShape::Rect)
},
_ => Err(location
.new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone()))),
}
@ -339,6 +371,20 @@ impl Parse for InsetRect {
}
}
fn parse_round<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<BorderRadius, ParseError<'i>> {
if input
.try_parse(|i| i.expect_ident_matching("round"))
.is_ok()
{
return BorderRadius::parse(context, input);
}
Ok(BorderRadius::zero())
}
impl InsetRect {
/// Parse the inner function arguments of `inset()`
fn parse_function_arguments<'i, 't>(
@ -346,14 +392,7 @@ impl InsetRect {
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let rect = Rect::parse_with(context, input, LengthPercentage::parse)?;
let round = if input
.try_parse(|i| i.expect_ident_matching("round"))
.is_ok()
{
BorderRadius::parse(context, input)?
} else {
BorderRadius::zero()
};
let round = parse_round(context, input)?;
Ok(generic::InsetRect { rect, round })
}
}
@ -509,6 +548,17 @@ impl Path {
}
}
fn round_to_css<W>(round: &BorderRadius, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if !round.is_zero() {
dest.write_str(" round ")?;
round.to_css(dest)?;
}
Ok(())
}
impl ToCss for Xywh {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
@ -521,16 +571,12 @@ impl ToCss for Xywh {
self.width.to_css(dest)?;
dest.write_char(' ')?;
self.height.to_css(dest)?;
if !self.round.is_zero() {
dest.write_str(" round ")?;
self.round.to_css(dest)?;
}
Ok(())
round_to_css(&self.round, dest)
}
}
impl Xywh {
/// Parse the inner function arguments of `xywh()`
/// Parse the inner function arguments of `xywh()`.
fn parse_function_arguments<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
@ -539,15 +585,7 @@ impl Xywh {
let y = LengthPercentage::parse(context, input)?;
let width = NonNegativeLengthPercentage::parse(context, input)?;
let height = NonNegativeLengthPercentage::parse(context, input)?;
let round = if input
.try_parse(|i| i.expect_ident_matching("round"))
.is_ok()
{
BorderRadius::parse(context, input)?
} else {
BorderRadius::zero()
};
let round = parse_round(context, input)?;
Ok(Xywh {
x,
y,
@ -558,12 +596,41 @@ impl Xywh {
}
}
impl ToCss for ShapeRectFunction {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
self.rect.0.to_css(dest)?;
dest.write_char(' ')?;
self.rect.1.to_css(dest)?;
dest.write_char(' ')?;
self.rect.2.to_css(dest)?;
dest.write_char(' ')?;
self.rect.3.to_css(dest)?;
round_to_css(&self.round, dest)
}
}
impl ShapeRectFunction {
/// Parse the inner function arguments of `rect()`.
fn parse_function_arguments<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let rect = Rect::parse_all_components_with(context, input, LengthPercentageOrAuto::parse)?;
let round = parse_round(context, input)?;
Ok(ShapeRectFunction { rect, round })
}
}
impl ToComputedValue for BasicShapeRect {
type ComputedValue = ComputedInsetRect;
#[inline]
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
use crate::values::computed::LengthPercentage;
use crate::values::computed::LengthPercentageOrAuto;
use style_traits::values::specified::AllowedNumericType;
match self {
@ -594,6 +661,42 @@ impl ToComputedValue for BasicShapeRect {
round: xywh.round.to_computed_value(context),
}
},
Self::Rect(ref rect) => {
// Given `rect(t r b l)`, the equivalent function is
// `inset(t calc(100% - r) calc(100% - b) l)`.
//
// https://drafts.csswg.org/css-shapes-1/#basic-shape-computed-values
fn compute_top_or_left(v: LengthPercentageOrAuto) -> LengthPercentage {
match v {
// its equivalent to 0% as the first (top) or fourth (left) value.
// https://drafts.csswg.org/css-shapes-1/#funcdef-basic-shape-rect
LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
LengthPercentageOrAuto::LengthPercentage(lp) => lp,
}
}
fn compute_bottom_or_right(v: LengthPercentageOrAuto) -> LengthPercentage {
match v {
// It's equivalent to 100% as the second (right) or third (bottom) value.
// So calc(100% - 100%) = 0%.
// https://drafts.csswg.org/css-shapes-1/#funcdef-basic-shape-rect
LengthPercentageOrAuto::Auto => LengthPercentage::zero_percent(),
LengthPercentageOrAuto::LengthPercentage(lp) => {
LengthPercentage::hundred_percent_minus(lp, AllowedNumericType::All)
},
}
}
let round = rect.round.to_computed_value(context);
let rect = rect.rect.to_computed_value(context);
let rect = Rect::new(
compute_top_or_left(rect.0),
compute_bottom_or_right(rect.1),
compute_bottom_or_right(rect.2),
compute_top_or_left(rect.3),
);
ComputedInsetRect { rect, round }
},
}
}

View File

@ -1 +1 @@
prefs: [layout.css.basic-shape-xywh.enabled:true]
prefs: [layout.css.basic-shape-rect.enabled:false, layout.css.basic-shape-xywh.enabled:true]

View File

@ -1 +1 @@
prefs: [layout.css.motion-path.enabled:true, layout.css.individual-transform.enabled:true, dom.animations-api.core.enabled:true, layout.css.motion-path-ray.enabled:true, layout.css.motion-path-offset-position.enabled:true, layout.css.motion-path-basic-shapes.enabled:true, layout.css.motion-path-coord-box.enabled:true, layout.css.basic-shape-xywh.enabled:true]
prefs: [layout.css.motion-path.enabled:true, layout.css.individual-transform.enabled:true, dom.animations-api.core.enabled:true, layout.css.motion-path-ray.enabled:true, layout.css.motion-path-offset-position.enabled:true, layout.css.motion-path-basic-shapes.enabled:true, layout.css.motion-path-coord-box.enabled:true, layout.css.basic-shape-rect.enabled:true, layout.css.basic-shape-xywh.enabled:true]

View File

@ -1,84 +0,0 @@
[offset-path-interpolation-006.html]
[CSS Transitions: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (0.5) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[CSS Transitions: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (0.6) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[CSS Transitions: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (1) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[CSS Transitions: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (1.5) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[CSS Transitions with transition: all: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (-0.3) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[CSS Transitions with transition: all: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (0) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[CSS Transitions with transition: all: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (0.3) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[CSS Transitions with transition: all: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (0.5) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[CSS Transitions with transition: all: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (0.6) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[CSS Transitions with transition: all: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (1) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[CSS Transitions with transition: all: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (1.5) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[CSS Animations: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (-0.3) should be [none\]]
expected: FAIL
[CSS Animations: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (0) should be [none\]]
expected: FAIL
[CSS Animations: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (0.3) should be [none\]]
expected: FAIL
[CSS Animations: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (0.5) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[CSS Animations: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (0.6) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[CSS Animations: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (1) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[CSS Animations: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (1.5) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[Web Animations: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (-0.3) should be [none\]]
expected: FAIL
[Web Animations: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (0) should be [none\]]
expected: FAIL
[Web Animations: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (0.3) should be [none\]]
expected: FAIL
[Web Animations: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (0.5) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[Web Animations: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (0.6) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[Web Animations: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (1) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[Web Animations: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (1.5) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[CSS Transitions: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (-0.3) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[CSS Transitions: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (0) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL
[CSS Transitions: property <offset-path> from [none\] to [rect(10px 10px 10px 10px)\] at (0.3) should be [rect(10px 10px 10px 10px)\]]
expected: FAIL

View File

@ -1,2 +0,0 @@
[offset-path-shape-rect-001.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[offset-path-shape-rect-002.html]
expected: FAIL

View File

@ -110,6 +110,32 @@
{at: 2, expect: 'xywh(15px 15px 50% 50%)'},
]);
test_interpolation({
property: 'offset-path',
from: 'rect(10px 100px 50% 10%)',
to: 'rect(50px 200px 90% 50%)'
}, [
{at: -1, expect: 'rect(-30px 0px 10% -30%)'},
{at: 0, expect: 'rect(10px 100px 50% 10%)'},
{at: 0.125, expect: 'rect(15px 112.5px 55% 15%)'},
{at: 0.875, expect: 'rect(45px 187.5px 85% 45%)'},
{at: 1, expect: 'rect(50px 200px 90% 50%)'},
{at: 2, expect: 'rect(90px 300px 130% 90%)'},
]);
test_interpolation({
property: 'offset-path',
from: 'rect(auto auto auto auto)',
to: 'rect(80% 20% 20% 80%)'
}, [
{at: -1, expect: 'inset(-80%)'},
{at: 0, expect: 'inset(0%)'},
{at: 0.125, expect: 'inset(10%)'},
{at: 0.875, expect: 'inset(70%)'},
{at: 1, expect: 'inset(80%)'},
{at: 2, expect: 'inset(160%)'},
]);
// All <basic-shape-rect>s compute to the equivalent inset() function, so
// they are interpolatable.
// https://drafts.csswg.org/css-shapes-1/#basic-shape-computed-values
@ -129,6 +155,35 @@
{at: 2, expect: 'inset(90px calc(-100% + 310px) calc(-100% + 210px) 90px)'},
]);
test_interpolation({
property: 'offset-path',
from: 'inset(10px)',
// inset(50px calc(100% - 70px) 20% 20%).
to: 'rect(50px 70px 80% 20%)',
}, [
{at: -1, expect: 'inset(-30px calc(-100% + 90px) calc(-20% + 20px) calc(-20% + 20px)'},
{at: 0, expect: 'inset(10px calc(0% + 10px) calc(0% + 10px))'},
{at: 0.125, expect: 'inset(15px 12.5% calc(2.5% + 8.75px) calc(2.5% + 8.75px))'},
{at: 0.875, expect: 'inset(45px calc(87.5% - 60px) calc(17.5% + 1.25px) calc(17.5% + 1.25px))'},
{at: 1, expect: 'inset(50px calc(100% - 70px) 20% 20%)'},
{at: 2, expect: 'inset(90px calc(200% - 150px) calc(40% - 10px) calc(40% - 10px))'},
]);
test_interpolation({
property: 'clip-path',
// inset(10% calc(100% - 100px) 50% 0px round 20px).
from: 'xywh(0px 10% 100px 40% round 20px)',
// inset(20% 50% calc(100% - 200px) 20px).
to: 'rect(20% 50% 200px 20px)',
}, [
{at: -1, expect: 'inset(0% calc(150% - 200px) calc(0% + 200px) -20px round 40px'},
{at: 0, expect: 'inset(10% calc(100% - 100px) 50% 0px round 20px)'},
{at: 0.125, expect: 'inset(11.25% calc(93.75% - 87.5px) calc(56.25% - 25px) 2.5px round 17.5px)'},
{at: 0.875, expect: 'inset(18.75% calc(56.25% - 12.5px) calc(93.75% - 175px) 17.5px round 2.5px)'},
{at: 1, expect: 'inset(20% 50% calc(100% - 200px) 20px)'},
{at: 2, expect: 'inset(30% calc(0% + 100px) calc(150% - 400px) 40px)'},
]);
// No interpolation between different radius keywords.
test_no_interpolation({
property: 'offset-path',

View File

@ -14,6 +14,7 @@
#box {
background-color: green;
transform: translate(420.272px, 317.407px) rotate(161.625deg) translate(40px, 40px);
border-radius: 50% 50% 0 0;
width: 100px;
height: 100px;
}

View File

@ -1,6 +1,7 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS Motion Path test: &lt;basic-shape&gt; rect() path with explicit arguments and radius</title>
<meta name=fuzzy content="0-96;0-440">
<link rel="author" title="Daniil Sakhapov" href="sakhapov@google.com">
<link rel="match" href="offset-path-shape-rect-002-ref.html">
<link rel="help" href="https://drafts.fxtf.org/motion/#valdef-offset-path-basic-shape">
@ -19,6 +20,7 @@
offset-path: rect(5px 95% 95% 5px round 30%);
offset-distance: 45%;
offset-anchor: 10% 10%;
border-radius: 50% 50% 0 0;
width: 100px;
height: 100px;
}

View File

@ -0,0 +1,26 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS Motion Path test reference: offset-path:rect() path with explicit arguments and padding-box</title>
<style>
#outer {
top: 100px;
left: 100px;
position: relative;
width: 200px;
height: 100px;
padding: 50px;
border: 50px solid black;
}
#box {
background-color: green;
transform: translate(200px, -78px) rotate(90deg);
border-radius: 50% 50% 0 0;
width: 100px;
height: 100px;
}
</style>
<div id="outer">
<div id="box"></div>
</div>

View File

@ -0,0 +1,31 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS Motion Path test: offset-path:rect() path with explicit arguments and padding-box</title>
<link rel="match" href="offset-path-shape-rect-003-ref.html">
<link rel="help" href="https://drafts.fxtf.org/motion/#valdef-offset-path-basic-shape">
<link rel="help" href="https://drafts.csswg.org/css-shapes-1/#funcdef-basic-shape-rect">
<style>
#outer {
top: 100px;
left: 100px;
position: relative;
width: 200px;
height: 100px;
padding: 50px;
border: 50px solid black;
}
#box {
background-color: green;
position: relative;
offset-path: rect(auto auto 50% 10px) padding-box;
offset-distance: 40%;
border-radius: 50% 50% 0 0;
width: 100px;
height: 100px;
}
</style>
<div id="outer">
<div id="box"></div>
</div>

View File

@ -84,6 +84,8 @@ test_computed_value("offset-path", "polygon(1% 2%)");
test_computed_value("offset-path", "polygon(1px 2px, 3% 4%)");
// All <basic-shape-rect> functions compute to the equivalent inset() function.
// Given "xywh(x y w h)", the equivalent function is
// "inset(y calc(100% - x - w) calc(100% - y - h) x)".
// https://drafts.csswg.org/css-shapes-1/#basic-shape-computed-values
// https://github.com/w3c/csswg-drafts/issues/9053
test_computed_value("offset-path", "xywh(0 1% 2px 3em)", "inset(1% calc(100% - 2px) calc(99% - 48px) 0px)");
@ -91,6 +93,14 @@ test_computed_value("offset-path", "xywh(0px 1% 2px 3em round 0)", "inset(1% cal
test_computed_value("offset-path", "xywh(0px 1% 2px 3em round 0 1px)", "inset(1% calc(100% - 2px) calc(99% - 48px) 0px round 0px 1px)");
test_computed_value("offset-path", "xywh(0px 1% 2px 3% round 0px 1px 2em)", "inset(1% calc(100% - 2px) 96% 0px round 0px 1px 32px)");
test_computed_value("offset-path", "xywh(0px 1% 2px 3% round 0px 1px 2% 3px)", "inset(1% calc(100% - 2px) 96% 0px round 0px 1px 2% 3px)");
// Given "rect(t r b l)", the equivalent function is
// "inset(t calc(100% - r) calc(100% - b) l)".
test_computed_value("offset-path", "rect(auto auto auto auto)", "inset(0%)");
test_computed_value("offset-path", "rect(0 1% 2px 3em)", "inset(0px 99% calc(100% - 2px) 48px)");
test_computed_value("offset-path", "rect(0px 1% auto 3em round 0)", "inset(0px 99% 0% 48px)");
test_computed_value("offset-path", "rect(0px 1% auto 3% round 0 1px)", "inset(0px 99% 0% 3% round 0px 1px)");
test_computed_value("offset-path", "rect(0px 1% auto 3% round 0px 1px 2em)", "inset(0px 99% 0% 3% round 0px 1px 32px)");
test_computed_value("offset-path", "rect(0px 1% auto 3% round 0px 1px 2% 3px)", "inset(0px 99% 0% 3% round 0px 1px 2% 3px)");
test_computed_value("offset-path", "content-box");
test_computed_value("offset-path", "border-box");

View File

@ -34,6 +34,10 @@ test_invalid_value("offset-path", "xywh(0px)");
test_invalid_value("offset-path", "xywh(0px 1%)");
test_invalid_value("offset-path", "xywh(0px 1% 2em)");
test_invalid_value("offset-path", "rect(0px)");
test_invalid_value("offset-path", "rect(0px 1%)");
test_invalid_value("offset-path", "rect(0px 1% auto)");
</script>
</body>
</html>

View File

@ -82,6 +82,13 @@ test_valid_value("offset-path", "xywh(0px 1% 2px 3em round 0 1px)", "xywh(0px 1%
test_valid_value("offset-path", "xywh(0px 1% 2px 3em round 0px 1px 2%)");
test_valid_value("offset-path", "xywh(0px 1% 2px 3em round 0px 1px 2% 3em)");
test_valid_value("offset-path", "rect(0 100% 200px 4em)", "rect(0px 100% 200px 4em)");
test_valid_value("offset-path", "rect(auto auto auto auto)");
test_valid_value("offset-path", "rect(0px 100% auto 4em round 0)", "rect(0px 100% auto 4em)");
test_valid_value("offset-path", "rect(0px 100% auto 4em round 0 1px)", "rect(0px 100% auto 4em round 0px 1px)");
test_valid_value("offset-path", "rect(0px 100% auto 4em round 0px 1px 2%)");
test_valid_value("offset-path", "rect(0px 100% auto 4em round 0px 1px 2% 3em)");
test_valid_value("offset-path", "content-box");
test_valid_value("offset-path", "border-box");
test_valid_value("offset-path", "view-box");