зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1480665 - Implement the contain flag for ray() function. r=birtles
Basically, the math calculation logic is from the github repo of the spec
author, Eric:
20b7403d85/ray.py (L311-L363)
Differential Revision: https://phabricator.services.mozilla.com/D42722
--HG--
extra : moz-landing-system : lando
This commit is contained in:
Родитель
88285c24ba
Коммит
62b0e230d6
|
@ -10117,6 +10117,38 @@ static CSSCoord ComputeRayPathLength(const nsIFrame* aFrame,
|
|||
return 0.0;
|
||||
}
|
||||
|
||||
static void ApplyRotationAndMoveRayToXAxis(
|
||||
const StyleOffsetRotate& aOffsetRotate, const StyleAngle& aRayAngle,
|
||||
AutoTArray<gfx::Point, 4>& aVertices) {
|
||||
const StyleAngle directionAngle = aRayAngle - StyleAngle{90.0f};
|
||||
// Get the final rotation which includes the direction angle and
|
||||
// offset-rotate.
|
||||
const StyleAngle rotateAngle =
|
||||
(aOffsetRotate.auto_ ? directionAngle : StyleAngle{0.0f}) +
|
||||
aOffsetRotate.angle;
|
||||
// This is the rotation to rotate ray to positive x-axis (i.e. 90deg).
|
||||
const StyleAngle rayToXAxis = StyleAngle{90.0} - aRayAngle;
|
||||
|
||||
gfx::Matrix m;
|
||||
m.PreRotate((rotateAngle + rayToXAxis).ToRadians());
|
||||
for (gfx::Point& p : aVertices) {
|
||||
p = m.TransformPoint(p);
|
||||
}
|
||||
}
|
||||
|
||||
class RayPointComparator {
|
||||
public:
|
||||
bool Equals(const gfx::Point& a, const gfx::Point& b) const {
|
||||
return std::fabs(a.y) == std::fabs(b.y);
|
||||
}
|
||||
|
||||
bool LessThan(const gfx::Point& a, const gfx::Point& b) const {
|
||||
return std::fabs(a.y) > std::fabs(b.y);
|
||||
}
|
||||
};
|
||||
// Note: the calculation of contain doesn't take other transform-like properties
|
||||
// into account. The spec doesn't mention the co-operation for this, so for now,
|
||||
// we assume we only need to take motion-path into account.
|
||||
static CSSCoord ComputeRayUsedDistance(const nsStyleDisplay* aDisplay,
|
||||
const nsSize& aSize,
|
||||
const CSSCoord& aPathLength) {
|
||||
|
@ -10124,9 +10156,141 @@ static CSSCoord ComputeRayUsedDistance(const nsStyleDisplay* aDisplay,
|
|||
|
||||
CSSCoord usedDistance =
|
||||
aDisplay->mOffsetDistance.ResolveToCSSPixels(aPathLength);
|
||||
if (!aDisplay->mOffsetPath.AsRay().contain) {
|
||||
return usedDistance;
|
||||
}
|
||||
|
||||
if (aDisplay->mOffsetPath.AsRay().contain) {
|
||||
// TODO: In the next patch.
|
||||
// We have to simulate the 4 vertices to check if any of them is outside the
|
||||
// path circle. Here, we create a 2D Cartesian coordinate system and its
|
||||
// origin is at the anchor point of the box. And then apply the rotation on
|
||||
// these 4 vertices, calculate the range of |usedDistance| which makes the box
|
||||
// entirely contained within the path.
|
||||
// Note:
|
||||
// "Contained within the path" means the rectangle is inside a circle whose
|
||||
// radius is |aPathLength|.
|
||||
const StylePositionOrAuto& anchor = aDisplay->mOffsetAnchor;
|
||||
const StyleTransformOrigin& origin = aDisplay->mTransformOrigin;
|
||||
const StyleLengthPercentage& anchorX =
|
||||
anchor.IsAuto() ? origin.horizontal : anchor.AsPosition().horizontal;
|
||||
const StyleLengthPercentage& anchorY =
|
||||
anchor.IsAuto() ? origin.vertical : anchor.AsPosition().vertical;
|
||||
|
||||
const CSSCoord width = CSSPixel::FromAppUnits(aSize.width);
|
||||
const CSSCoord height = CSSPixel::FromAppUnits(aSize.height);
|
||||
const CSSPoint usedAnchor = {anchorX.ResolveToCSSPixels(width),
|
||||
anchorY.ResolveToCSSPixels(height)};
|
||||
AutoTArray<gfx::Point, 4> vertices = {
|
||||
{-usedAnchor.x, -usedAnchor.y},
|
||||
{width - usedAnchor.x, -usedAnchor.y},
|
||||
{width - usedAnchor.x, height - usedAnchor.y},
|
||||
{-usedAnchor.x, height - usedAnchor.y}};
|
||||
|
||||
ApplyRotationAndMoveRayToXAxis(aDisplay->mOffsetRotate,
|
||||
aDisplay->mOffsetPath.AsRay().angle, vertices);
|
||||
|
||||
// We have to check if all 4 vertices are inside the circle with radius |r|.
|
||||
// Assume the position of the vertex is (x, y), and the box is moved by
|
||||
// |usedDistance| along the path:
|
||||
//
|
||||
// (usedDistance + x)^2 + y^2 <= r^2
|
||||
// ==> (usedDistance + x)^2 <= r^2 - y^2 = d
|
||||
// ==> -x - sqrt(d) <= used distance <= -x + sqrt(d)
|
||||
//
|
||||
// Note: |usedDistance| is added into |x| because we convert the ray function
|
||||
// to 90deg, x-axis):
|
||||
float upperMin = std::numeric_limits<float>::max();
|
||||
float lowerMax = std::numeric_limits<float>::min();
|
||||
bool shouldIncreasePathLength = false;
|
||||
for (const gfx::Point& p : vertices) {
|
||||
float d = aPathLength.value * aPathLength.value - p.y * p.y;
|
||||
if (d < 0) {
|
||||
// Impossible to make the box inside the path circle. Need to increase
|
||||
// the path length.
|
||||
shouldIncreasePathLength = true;
|
||||
break;
|
||||
}
|
||||
float sqrtD = sqrt(d);
|
||||
upperMin = std::min(upperMin, -p.x + sqrtD);
|
||||
lowerMax = std::max(lowerMax, -p.x - sqrtD);
|
||||
}
|
||||
|
||||
if (!shouldIncreasePathLength) {
|
||||
return std::max(lowerMax, std::min(upperMin, (float)usedDistance));
|
||||
}
|
||||
|
||||
// Sort by the absolute value of y, so the first vertex of the each pair of
|
||||
// vertices we check has a larger y value. (i.e. |yi| is always larger than or
|
||||
// equal to |yj|.)
|
||||
vertices.Sort(RayPointComparator());
|
||||
|
||||
// Assume we set |usedDistance| to |-vertices[0].x|, so the current radius is
|
||||
// fabs(vertices[0].y). This is a possible solution.
|
||||
double radius = std::fabs(vertices[0].y);
|
||||
usedDistance = -vertices[0].x;
|
||||
const double epsilon = 1e-5;
|
||||
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
for (size_t j = i + 1; j < 4; ++j) {
|
||||
double xi = vertices[i].x;
|
||||
double yi = vertices[i].y;
|
||||
double xj = vertices[j].x;
|
||||
double yj = vertices[j].y;
|
||||
double dx = xi - xj;
|
||||
|
||||
// Check if any path that enclosed vertices[i] would also enclose
|
||||
// vertices[j].
|
||||
//
|
||||
// For example, the initial setup:
|
||||
// * (0, yi)
|
||||
// |
|
||||
// r
|
||||
// | * (xj - xi, yj)
|
||||
// xi | dx
|
||||
// ----*-----------*----------*---
|
||||
// (anchor point) | (0, 0)
|
||||
//
|
||||
// Assuming (0, yi) is on the path and (xj - xi, yj) is inside the path
|
||||
// circle, we should use the inequality to check this:
|
||||
// (xj - xi)^2 + yj^2 <= yi^2
|
||||
//
|
||||
// After the first iterations, the updated inequality is:
|
||||
// (dx + d)^2 + yj^2 <= yi^2 + d^2
|
||||
// ==> dx^2 + 2dx*d + yj^2 <= yi^2
|
||||
// ==> dx^2 + yj^2 <= yi^2 - 2dx*d <= yi^2
|
||||
// , |d| is the difference (or offset) between the old |usedDistance| and
|
||||
// new |usedDistance|.
|
||||
//
|
||||
// Note: `2dx * d` must be positive because
|
||||
// 1. if |xj| is larger than |xi|, only negative |d| could be used to get
|
||||
// a new path length which encloses both vertices.
|
||||
// 2. if |xj| is smaller than |xi|, only positive |d| could be used to get
|
||||
// a new path length which encloses both vertices.
|
||||
if (dx * dx + yj * yj <= yi * yi + epsilon) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have to find a new usedDistance which let both vertices[i] and
|
||||
// vertices[j] be on the path.
|
||||
// (usedDistance + xi)^2 + yi^2 = (usedDistance + xj)^2 + yj^2
|
||||
// = radius^2
|
||||
// ==> usedDistance = (xj^2 + yj^2 - xi^2 - yi^2) / 2(xi-xj)
|
||||
//
|
||||
// Note: it's impossible to have a "divide by zero" problem here.
|
||||
// If |dx| is zero, the if-condition above should always be true and so
|
||||
// we skip the calculation.
|
||||
double newUsedDistance =
|
||||
(xj * xj + yj * yj - xi * xi - yi * yi) / dx / 2.0;
|
||||
// Then, move vertices[i] and vertices[j] by |newUsedDistance|.
|
||||
xi += newUsedDistance; // or xj += newUsedDistance; if we use |xj| to get
|
||||
// |newRadius|.
|
||||
double newRadius = sqrt(xi * xi + yi * yi);
|
||||
if (newRadius > radius) {
|
||||
// We have to increase the path length to make sure both vertices[i] and
|
||||
// vertices[j] are contained by this new path length.
|
||||
radius = newRadius;
|
||||
usedDistance = (float)newUsedDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return usedDistance;
|
||||
|
|
|
@ -519,6 +519,12 @@ renaming_overrides_prefixing = true
|
|||
inline static StyleAngle Zero();
|
||||
inline float ToDegrees() const;
|
||||
inline double ToRadians() const;
|
||||
StyleAngle operator+(const StyleAngle& aAngle) const {
|
||||
return StyleAngle{_0 + aAngle._0};
|
||||
}
|
||||
StyleAngle operator-(const StyleAngle& aAngle) const {
|
||||
return StyleAngle{_0 - aAngle._0};
|
||||
}
|
||||
"""
|
||||
|
||||
"TextOverflow" = """
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Motion Path: ray paths with contain</title>
|
||||
<style>
|
||||
#container {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
#target {
|
||||
position: relative;
|
||||
left: 150px;
|
||||
top: 150px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: lime;
|
||||
transform: rotate(0deg) translate(50px, -150px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="target"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Motion Path: ray paths with contain</title>
|
||||
<link rel="help" href="https://drafts.fxtf.org/motion-1/#offset-path-property">
|
||||
<link rel="match" href="offset-path-ray-contain-001-ref.html">
|
||||
<meta name="assert" content="This tests that the element should be contained in ray() path circle.">
|
||||
<style>
|
||||
#container {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
#target {
|
||||
position: relative;
|
||||
left: 150px;
|
||||
top: 150px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: lime;
|
||||
offset-path: ray(45deg closest-corner contain);
|
||||
offset-rotate: 0deg;
|
||||
offset-distance: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="target"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,27 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Motion Path: ray paths with contain</title>
|
||||
<style>
|
||||
#container {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
#target {
|
||||
position: relative;
|
||||
left: 100px;
|
||||
top: 100px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: lime;
|
||||
/* The vertical movement is about sqrt(150^2 - 50^2) - 50 */
|
||||
transform: translateY(91.42px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="target"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Motion Path: ray paths with contain</title>
|
||||
<link rel="help" href="https://drafts.fxtf.org/motion-1/#offset-path-property">
|
||||
<link rel="match" href="offset-path-ray-contain-002-ref.html">
|
||||
<meta name="assert" content="This tests that the element should be contained in ray() path circle.">
|
||||
<style>
|
||||
#container {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
#target {
|
||||
position: relative;
|
||||
left: 150px;
|
||||
top: 150px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: lime;
|
||||
offset-path: ray(180deg closest-side contain);
|
||||
offset-rotate: 0deg;
|
||||
offset-distance: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="target"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,27 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Motion Path: ray paths with contain</title>
|
||||
<style>
|
||||
#container {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
#target {
|
||||
position: relative;
|
||||
left: 100px;
|
||||
top: 100px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: lime;
|
||||
/* The movement is about 150 - 50 * sqrt(2) */
|
||||
transform: rotate(-45deg) translate(79.29px) rotate(45deg);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="target"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Motion Path: ray paths with contain</title>
|
||||
<link rel="help" href="https://drafts.fxtf.org/motion-1/#offset-path-property">
|
||||
<link rel="match" href="offset-path-ray-contain-003-ref.html">
|
||||
<meta name="assert" content="This tests that the element should be contained in ray() path circle.">
|
||||
<style>
|
||||
#container {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
#target {
|
||||
position: relative;
|
||||
left: 150px;
|
||||
top: 150px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: lime;
|
||||
offset-path: ray(45deg closest-side contain);
|
||||
offset-rotate: 0deg;
|
||||
offset-distance: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="target"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,27 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Motion Path: ray paths with contain</title>
|
||||
<style>
|
||||
#container {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
#target {
|
||||
position: relative;
|
||||
left: 100px;
|
||||
top: 100px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: lime;
|
||||
/* The movement is about sqrt(150^2 - 50^2) - 50 */
|
||||
transform: rotate(-45deg) translate(91.42px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="target"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Motion Path: ray paths with contain</title>
|
||||
<link rel="help" href="https://drafts.fxtf.org/motion-1/#offset-path-property">
|
||||
<link rel="match" href="offset-path-ray-contain-004-ref.html">
|
||||
<meta name="assert" content="This tests that the element should be contained in ray() path circle.">
|
||||
<style>
|
||||
#container {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
#target {
|
||||
position: relative;
|
||||
left: 150px;
|
||||
top: 150px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: lime;
|
||||
offset-path: ray(45deg closest-side contain);
|
||||
offset-rotate: auto;
|
||||
offset-distance: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="target"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Motion Path: ray paths with contain</title>
|
||||
<style>
|
||||
#container {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
transform: translate(100px);
|
||||
}
|
||||
#target {
|
||||
position: relative;
|
||||
left: 50px;
|
||||
top: 50px;
|
||||
width: 150px;
|
||||
height: 25px;
|
||||
background-color: lime;
|
||||
/*
|
||||
* The original path length is 50px, which is not enough to contain
|
||||
* the element entirely, so it should be increased.
|
||||
* "75px" is just the center of the element, which makes the path
|
||||
* length increase minimally.
|
||||
* Besides, -75px = (-150px * 2) + 225px, the used offset distance is
|
||||
* -225px in this case.
|
||||
* Note: offset-anchor is "200% -300%", and ray angle is -90deg.
|
||||
*/
|
||||
transform: translate(calc(-75px), calc(25px * 3));
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="target"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Motion Path: ray paths with contain</title>
|
||||
<link rel="help" href="https://drafts.fxtf.org/motion-1/#offset-path-property">
|
||||
<link rel="match" href="offset-path-ray-contain-005-ref.html">
|
||||
<meta name="assert" content="This tests that ray() with contain and the path length increases.">
|
||||
<style>
|
||||
#container {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
/* move container to right 100px to make sure we render the element
|
||||
properly */
|
||||
transform: translate(100px);
|
||||
}
|
||||
#target {
|
||||
position: relative;
|
||||
left: 50px;
|
||||
top: 50px;
|
||||
width: 150px;
|
||||
height: 25px;
|
||||
background-color: lime;
|
||||
offset-path: ray(-90deg closest-side contain);
|
||||
offset-anchor: 200% -300%;
|
||||
offset-rotate: 0deg;
|
||||
offset-distance: 0%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="target"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Загрузка…
Ссылка в новой задаче