Skip to content
Permalink
Browse files
Implement contain flag for ray() in offset path
https://bugs.webkit.org/show_bug.cgi?id=240259
<rdar://93374029>

Reviewed by Simon Fraser.

Implement contain flag for ray(). Contains purpose is to have the entire box
being animated be contained within the path. "Contained within the path" is
defined as having the box remain within a circle with the radius of the path
length and positioned at the offset anchor. This solution is adapted from the
repository of the spec writer: https://github.com/ewilligers/petrogale-purpureicollis.

The way this solution works is that you construct a coordinate system with the origin
being the offset anchor. You then calculate the position of each vertex of the box.
Then, rotate the vertices based on the angles difference from the x-axis. Next, using
the circle equation, we want to find an offset such that (x + offset)^2 + y^2 = r^2.
This results in a lower and upper bound for offset: -x - sqrt(r^2 - y^2) <= offset <=
-x + sqrt(r^2 + y^2). Finally we choose the minimal value of these upper and lower
bounds to get the final clamped offset.

This patch currently doesn't take into account if it is not possible to fit the box
within the path, as this will be completed in a seperate patch. Currently, test 4 is
failing due to rounding error, and test 5 is failing due to the unimplemented part.

* Source/WebCore/platform/graphics/GeometryUtilities.cpp:
(WebCore::toRelatedAcuteAngle):
(WebCore::distanceOfPointToSidesOfRect):
(WebCore::verticesForBox):
* Source/WebCore/platform/graphics/GeometryUtilities.h:
* Source/WebCore/rendering/PathOperation.cpp:
(WebCore::RayPathOperation::lengthForContainPath const):
(WebCore::RayPathOperation::pathForReferenceRect const):
* Source/WebCore/rendering/PathOperation.h:
* Source/WebCore/rendering/style/RenderStyle.cpp:
(WebCore::getPathFromPathOperation):
(WebCore::RenderStyle::applyMotionPathTransform const):
* LayoutTests/TestExpectations:

Canonical link: https://commits.webkit.org/250776@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@294520 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
nmoucht committed May 20, 2022
1 parent db510b0 commit 44edfe0eb29422b468c50e99d344064fc97eafb3
Showing 6 changed files with 83 additions and 18 deletions.
@@ -5078,9 +5078,6 @@ webkit.org/b/233340 imported/w3c/web-platform-tests/css/motion/offset-anchor-tra
webkit.org/b/233344 imported/w3c/web-platform-tests/css/motion/offset-path-ray-007.html [ ImageOnlyFailure ]

# CSS motion path needs to implement contain for ray: https://bugs.webkit.org/show_bug.cgi?id=240259.
webkit.org/b/233344 imported/w3c/web-platform-tests/css/motion/offset-path-ray-contain-001.html [ ImageOnlyFailure ]
webkit.org/b/233344 imported/w3c/web-platform-tests/css/motion/offset-path-ray-contain-002.html [ ImageOnlyFailure ]
webkit.org/b/233344 imported/w3c/web-platform-tests/css/motion/offset-path-ray-contain-003.html [ ImageOnlyFailure ]
webkit.org/b/233344 imported/w3c/web-platform-tests/css/motion/offset-path-ray-contain-004.html [ ImageOnlyFailure ]
webkit.org/b/233344 imported/w3c/web-platform-tests/css/motion/offset-path-ray-contain-005.html [ ImageOnlyFailure ]

@@ -277,7 +277,7 @@ float toPositiveAngle(float angle)
}

// Compute acute angle from vertical axis
static float toRelatedAcuteAngle(float angle)
float toRelatedAcuteAngle(float angle)
{
angle = toPositiveAngle(angle);
if (angle < 90)
@@ -287,16 +287,24 @@ static float toRelatedAcuteAngle(float angle)
return std::abs(360 - angle);
}

RectEdges<double> distanceOfPointToSidesOfRect(const FloatRect& boundingRect, const FloatPoint& position)
RectEdges<double> distanceOfPointToSidesOfRect(const FloatRect& box, const FloatPoint& position)
{
// Compute distance to each side of the containing box
double top = std::abs(position.y());
double bottom = std::abs(position.y() - boundingRect.height());
double bottom = std::abs(position.y() - box.height());
double left = std::abs(position.x());
double right = std::abs(position.x() - boundingRect.width());
double right = std::abs(position.x() - box.width());
return RectEdges<double>(top, right, bottom, left);
}

std::array<FloatPoint, 4> verticesForBox(const FloatRect& box, const FloatPoint position)
{
return { FloatPoint(-position.x(), -position.y()),
FloatPoint(box.width() - position.x(), -position.y()),
FloatPoint(box.width() - position.x(), box.height() - position.y()),
FloatPoint(-position.x(), box.height() - position.y()) };
}

double lengthOfRayIntersectionWithBoundingBox(const FloatRect& boundingRect, const std::pair<const FloatPoint&, float> ray)
{
auto length = lengthOfPointToSideOfIntersection(boundingRect, ray);
@@ -29,6 +29,8 @@
#include "IntRect.h"
#include <wtf/Forward.h>

#include <wtf/Vector.h>

namespace WebCore {

class FloatQuad;
@@ -88,7 +90,13 @@ float angleOfPointToSideOfIntersection(const FloatRect& boundingRect, const std:

// Given a box and an offset from the top left corner, calculate the distance of the point from each side
RectEdges<double> distanceOfPointToSidesOfRect(const FloatRect&, const FloatPoint&);

// Given a box and an offset from the top left corner, construct a coordinate system with this offset as the origin,
// and return the vertices of the box in this coordinate system
std::array<FloatPoint, 4> verticesForBox(const FloatRect&, const FloatPoint);

float toPositiveAngle(float angle);
float toRelatedAcuteAngle(float angle);

struct RotatedRect {
FloatPoint center;
@@ -70,13 +70,63 @@ double RayPathOperation::lengthForPath() const
RELEASE_ASSERT_NOT_REACHED();
}

const Path RayPathOperation::pathForReferenceRect() const
double RayPathOperation::lengthForContainPath(const FloatRect& elementRect, double computedPathLength, const FloatPoint& anchor, const OffsetRotation rotation) const
{
// Construct vertices of element for determining if they are within the path length
// Anchor point as origin
auto vertices = verticesForBox(elementRect, anchor);

// Rotate vertices depending on offset rotate or angle
if (!rotation.hasAuto()) {
auto deg = toRelatedAcuteAngle(toPositiveAngle(m_angle - rotation.angle()));
auto angle = deg2rad(deg);
std::for_each(vertices.begin(), vertices.end(), [angle] (FloatPoint& p) {
p.rotate(angle);
});
}

Vector<std::pair<double, double>, 4> bounds;

for (const auto& p : vertices) {
// Use equation for circle (offset distance + x)^2 + y^2 <= r^2 to find offset distance that satisfies equation
// If no solution for above equation, must minimally increase it, otherwise clamp such that
// every point is within path
double discriminant = computedPathLength * computedPathLength - p.y() * p.y();
if (discriminant < 0) {
// Need to minimally increase path length
break;
}
bounds.append(std::make_pair(-p.x() - std::sqrt(discriminant), -p.x() + std::sqrt(discriminant)));
}

if (vertices.size() == bounds.size()) {
auto lowerBound = std::max_element(bounds.begin(), bounds.end(),
[] (std::pair<double, double> const lhs, std::pair<double, double> const rhs) {
return lhs.first < rhs.first;
})->first;

auto upperBound = std::min_element(bounds.begin(), bounds.end(),
[] (std::pair<double, double> const lhs, std::pair<double, double> const rhs) {
return lhs.second < rhs.second;
})->second;

if (lowerBound <= upperBound)
return std::max(lowerBound, std::min(upperBound, computedPathLength));
}

// TODO: Implement minimally increasing path length to allow all vertices to be within such a path length
return computedPathLength;
}

const Path RayPathOperation::pathForReferenceRect(const FloatRect& elementRect, const FloatPoint& anchor, const OffsetRotation rotation) const
{
Path path;
if (m_containingBlockBoundingRect.isZero())
return path;
auto length = lengthForPath();
auto radians = deg2rad(toPositiveAngle(m_angle) - 90);
double length = lengthForPath();
if (m_isContaining)
length = lengthForContainPath(elementRect, length, anchor, rotation);
auto radians = deg2rad(toPositiveAngle(m_angle) - 90.0);
auto point = FloatPoint(std::cos(radians) * length, std::sin(radians) * length);
path.addLineTo(point);
return path;
@@ -196,8 +196,9 @@ class RayPathOperation final : public PathOperation {
return RayPathOperation::create(WebCore::blend(m_angle, to.m_angle, context), m_size, m_isContaining);
}

const Path pathForReferenceRect() const;
const Path pathForReferenceRect(const FloatRect& elementRect, const FloatPoint& anchor, const OffsetRotation rotation) const;
double lengthForPath() const;
double lengthForContainPath(const FloatRect& elementRect, double computedPathLength, const FloatPoint& anchor, const OffsetRotation rotation) const;

void setContainingBlockReferenceRect(const FloatRect& boundingRect)
{
@@ -1563,7 +1563,7 @@ void RenderStyle::applyCSSTransform(TransformationMatrix& transform, const Float
// (implemented in unapplyTransformOrigin)
}

static std::optional<Path> getPathFromPathOperation(const FloatRect& box, const PathOperation& operation)
static std::optional<Path> getPathFromPathOperation(const FloatRect& box, const PathOperation& operation, const FloatPoint& anchor, OffsetRotation rotation)
{
switch (operation.type()) {
case PathOperation::Shape:
@@ -1575,7 +1575,7 @@ static std::optional<Path> getPathFromPathOperation(const FloatRect& box, const
case PathOperation::Box:
return downcast<BoxPathOperation>(operation).getPath();
case PathOperation::Ray:
return downcast<RayPathOperation>(operation).pathForReferenceRect();
return downcast<RayPathOperation>(operation).pathForReferenceRect(box, anchor, rotation);
}
RELEASE_ASSERT_NOT_REACHED();
}
@@ -1604,18 +1604,19 @@ void RenderStyle::applyMotionPathTransform(TransformationMatrix& transform, cons
if (!offsetPath())
return;

auto transformOrigin = floatPointForLengthPoint(transformOriginXY(), boundingBox.size()) + boundingBox.location();
auto anchor = transformOrigin;
if (!offsetAnchor().x().isAuto())
anchor = floatPointForLengthPoint(offsetAnchor(), boundingBox.size()) + boundingBox.location();

// Shift element to the point on path specified by offset-path and offset-distance.
auto path = getPathFromPathOperation(boundingBox, *offsetPath());
auto path = getPathFromPathOperation(boundingBox, *offsetPath(), anchor, offsetRotate());
if (!path)
return;
auto traversalState = getTraversalStateAtDistance(*path, offsetDistance());
transform.translate(traversalState.current().x(), traversalState.current().y());

// Shift element to the anchor specified by offset-anchor.
auto transformOrigin = floatPointForLengthPoint(transformOriginXY(), boundingBox.size()) + boundingBox.location();
auto anchor = transformOrigin;
if (!offsetAnchor().x().isAuto())
anchor = floatPointForLengthPoint(offsetAnchor(), boundingBox.size()) + boundingBox.location();
transform.translate(-anchor.x(), -anchor.y());

auto shiftToOrigin = anchor - transformOrigin;

0 comments on commit 44edfe0

Please sign in to comment.