2019-10-31 23:07:28 +03:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
#include "mozilla/MotionPathUtils.h"
|
|
|
|
|
|
|
|
#include "gfxPlatform.h"
|
|
|
|
#include "mozilla/dom/SVGPathData.h"
|
|
|
|
#include "mozilla/gfx/2D.h"
|
|
|
|
#include "mozilla/gfx/Matrix.h"
|
2019-10-31 23:07:41 +03:00
|
|
|
#include "mozilla/layers/LayersMessages.h"
|
2019-10-31 23:07:28 +03:00
|
|
|
#include "mozilla/RefPtr.h"
|
|
|
|
#include "nsIFrame.h"
|
|
|
|
#include "nsStyleTransformMatrix.h"
|
|
|
|
|
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
|
2020-02-07 16:34:42 +03:00
|
|
|
using nsStyleTransformMatrix::TransformReferenceBox;
|
|
|
|
|
2019-10-31 23:07:30 +03:00
|
|
|
RayReferenceData::RayReferenceData(const nsIFrame* aFrame) {
|
|
|
|
// We use GetContainingBlock() for now. TYLin said this function is buggy in
|
|
|
|
// modern CSS layout, but is ok for most cases.
|
|
|
|
// FIXME: Bug 1581237: This is still not clear that which box we should use
|
|
|
|
// for calculating the path length. We may need to update this.
|
|
|
|
// https://github.com/w3c/fxtf-drafts/issues/369
|
|
|
|
// FIXME: Bug 1579294: SVG layout may get a |container| with empty mRect
|
2020-07-11 05:20:20 +03:00
|
|
|
// (e.g. SVGOuterSVGAnonChildFrame), which makes the path length zero.
|
2019-10-31 23:07:30 +03:00
|
|
|
const nsIFrame* container = aFrame->GetContainingBlock();
|
|
|
|
if (!container) {
|
|
|
|
// If there is no parent frame, it's impossible to calculate the path
|
|
|
|
// length, so does the path.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The initial position is (0, 0) in |aFrame|, and we have to transform it
|
|
|
|
// into the space of |container|, so use GetOffsetsTo() to get the delta
|
|
|
|
// value.
|
|
|
|
// FIXME: Bug 1559232: The initial position will be adjusted after
|
|
|
|
// supporting `offset-position`.
|
|
|
|
mInitialPosition = CSSPoint::FromAppUnits(aFrame->GetOffsetTo(container));
|
|
|
|
// FIXME: We need a better definition for containing box in the spec. For now,
|
|
|
|
// we use border box for calculation.
|
|
|
|
// https://github.com/w3c/fxtf-drafts/issues/369
|
|
|
|
mContainingBlockRect =
|
|
|
|
CSSRect::FromAppUnits(container->GetRectRelativeToSelf());
|
|
|
|
}
|
|
|
|
|
2019-10-31 23:07:28 +03:00
|
|
|
// The distance is measured between the initial position and the intersection of
|
|
|
|
// the ray with the box
|
|
|
|
// https://drafts.fxtf.org/motion-1/#size-sides
|
|
|
|
static CSSCoord ComputeSides(const CSSPoint& aInitialPosition,
|
|
|
|
const CSSSize& aContainerSize,
|
|
|
|
const StyleAngle& aAngle) {
|
|
|
|
// Given an acute angle |theta| (i.e. |t|) of a right-angled triangle, the
|
|
|
|
// hypotenuse |h| is the side that connects the two acute angles. The side
|
|
|
|
// |b| adjacent to |theta| is the side of the triangle that connects |theta|
|
|
|
|
// to the right angle.
|
|
|
|
//
|
|
|
|
// e.g. if the angle |t| is 0 ~ 90 degrees, and b * tan(theta) <= b',
|
|
|
|
// h = b / cos(t):
|
|
|
|
// b*tan(t)
|
|
|
|
// (0, 0) #--------*-----*--# (aContainerSize.width, 0)
|
|
|
|
// | | / |
|
|
|
|
// | | / |
|
|
|
|
// | b h |
|
|
|
|
// | |t/ |
|
|
|
|
// | |/ |
|
|
|
|
// (aInitialPosition) *---b'---* (aContainerSize.width, aInitialPosition.y)
|
|
|
|
// | | |
|
|
|
|
// | | |
|
|
|
|
// | | |
|
|
|
|
// | | |
|
|
|
|
// | | |
|
|
|
|
// #-----------------# (aContainerSize.width,
|
|
|
|
// (0, aContainerSize.height) aContainerSize.height)
|
|
|
|
double theta = aAngle.ToRadians();
|
|
|
|
double sint = std::sin(theta);
|
|
|
|
double cost = std::cos(theta);
|
|
|
|
|
|
|
|
double b = cost >= 0 ? aInitialPosition.y
|
|
|
|
: aContainerSize.height - aInitialPosition.y;
|
|
|
|
double bPrime = sint >= 0 ? aContainerSize.width - aInitialPosition.x
|
|
|
|
: aInitialPosition.x;
|
|
|
|
sint = std::fabs(sint);
|
|
|
|
cost = std::fabs(cost);
|
|
|
|
|
|
|
|
// If |b * tan(theta)| is larger than |bPrime|, the intersection is
|
|
|
|
// on the other side, and |b'| is the opposite side of angle |theta| in this
|
|
|
|
// case.
|
|
|
|
//
|
|
|
|
// e.g. If b * tan(theta) > b', h = b' / sin(theta):
|
|
|
|
// *----*
|
|
|
|
// | |
|
|
|
|
// | /|
|
|
|
|
// b /t|
|
|
|
|
// |t/ |
|
|
|
|
// |/ |
|
|
|
|
// *-b'-*
|
|
|
|
if (b * sint > bPrime * cost) {
|
|
|
|
return bPrime / sint;
|
|
|
|
}
|
|
|
|
return b / cost;
|
|
|
|
}
|
|
|
|
|
2019-10-31 23:07:30 +03:00
|
|
|
static CSSCoord ComputeRayPathLength(const StyleRaySize aRaySizeType,
|
|
|
|
const StyleAngle& aAngle,
|
|
|
|
const RayReferenceData& aRayData) {
|
2019-10-31 23:07:28 +03:00
|
|
|
if (aRaySizeType == StyleRaySize::Sides) {
|
|
|
|
// If the initial position is not within the box, the distance is 0.
|
2019-10-31 23:07:30 +03:00
|
|
|
if (!aRayData.mContainingBlockRect.Contains(aRayData.mInitialPosition)) {
|
2019-10-31 23:07:28 +03:00
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
|
2019-10-31 23:07:30 +03:00
|
|
|
return ComputeSides(aRayData.mInitialPosition,
|
|
|
|
aRayData.mContainingBlockRect.Size(), aAngle);
|
2019-10-31 23:07:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// left: the length between the initial point and the left side.
|
|
|
|
// right: the length between the initial point and the right side.
|
|
|
|
// top: the length between the initial point and the top side.
|
|
|
|
// bottom: the lenght between the initial point and the bottom side.
|
2019-10-31 23:07:30 +03:00
|
|
|
CSSCoord left = std::abs(aRayData.mInitialPosition.x);
|
|
|
|
CSSCoord right = std::abs(aRayData.mContainingBlockRect.width -
|
|
|
|
aRayData.mInitialPosition.x);
|
|
|
|
CSSCoord top = std::abs(aRayData.mInitialPosition.y);
|
|
|
|
CSSCoord bottom = std::abs(aRayData.mContainingBlockRect.height -
|
|
|
|
aRayData.mInitialPosition.y);
|
2019-10-31 23:07:28 +03:00
|
|
|
|
|
|
|
switch (aRaySizeType) {
|
|
|
|
case StyleRaySize::ClosestSide:
|
|
|
|
return std::min({left, right, top, bottom});
|
|
|
|
|
|
|
|
case StyleRaySize::FarthestSide:
|
|
|
|
return std::max({left, right, top, bottom});
|
|
|
|
|
|
|
|
case StyleRaySize::ClosestCorner:
|
|
|
|
case StyleRaySize::FarthestCorner: {
|
|
|
|
CSSCoord h = 0;
|
|
|
|
CSSCoord v = 0;
|
|
|
|
if (aRaySizeType == StyleRaySize::ClosestCorner) {
|
|
|
|
h = std::min(left, right);
|
|
|
|
v = std::min(top, bottom);
|
|
|
|
} else {
|
|
|
|
h = std::max(left, right);
|
|
|
|
v = std::max(top, bottom);
|
|
|
|
}
|
|
|
|
return sqrt(h.value * h.value + v.value * v.value);
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Unsupported ray size");
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
2019-10-31 23:07:30 +03:00
|
|
|
static CSSCoord ComputeRayUsedDistance(const RayFunction& aRay,
|
|
|
|
const LengthPercentage& aDistance,
|
|
|
|
const StyleOffsetRotate& aRotate,
|
|
|
|
const StylePositionOrAuto& aAnchor,
|
|
|
|
const CSSPoint& aTransformOrigin,
|
2020-02-07 16:34:42 +03:00
|
|
|
TransformReferenceBox& aRefBox,
|
2019-10-31 23:07:28 +03:00
|
|
|
const CSSCoord& aPathLength) {
|
2019-10-31 23:07:30 +03:00
|
|
|
CSSCoord usedDistance = aDistance.ResolveToCSSPixels(aPathLength);
|
|
|
|
if (!aRay.contain) {
|
2019-10-31 23:07:28 +03:00
|
|
|
return usedDistance;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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|.
|
2019-10-31 23:07:30 +03:00
|
|
|
CSSPoint usedAnchor = aTransformOrigin;
|
2020-02-07 16:34:42 +03:00
|
|
|
CSSSize size =
|
|
|
|
CSSPixel::FromAppUnits(nsSize(aRefBox.Width(), aRefBox.Height()));
|
2019-10-31 23:07:30 +03:00
|
|
|
if (!aAnchor.IsAuto()) {
|
|
|
|
const StylePosition& anchor = aAnchor.AsPosition();
|
2020-02-07 16:34:42 +03:00
|
|
|
usedAnchor.x = anchor.horizontal.ResolveToCSSPixels(size.width);
|
|
|
|
usedAnchor.y = anchor.vertical.ResolveToCSSPixels(size.height);
|
2019-10-31 23:07:30 +03:00
|
|
|
}
|
2019-10-31 23:07:28 +03:00
|
|
|
AutoTArray<gfx::Point, 4> vertices = {
|
|
|
|
{-usedAnchor.x, -usedAnchor.y},
|
2020-02-07 16:34:42 +03:00
|
|
|
{size.width - usedAnchor.x, -usedAnchor.y},
|
|
|
|
{size.width - usedAnchor.x, size.height - usedAnchor.y},
|
|
|
|
{-usedAnchor.x, size.height - usedAnchor.y}};
|
2019-10-31 23:07:28 +03:00
|
|
|
|
2019-10-31 23:07:30 +03:00
|
|
|
ApplyRotationAndMoveRayToXAxis(aRotate, aRay.angle, vertices);
|
2019-10-31 23:07:28 +03:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2020-02-07 16:34:42 +03:00
|
|
|
/* static */
|
|
|
|
CSSPoint MotionPathUtils::ComputeAnchorPointAdjustment(const nsIFrame& aFrame) {
|
|
|
|
if (!aFrame.HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
auto transformBox = aFrame.StyleDisplay()->mTransformBox;
|
|
|
|
if (transformBox == StyleGeometryBox::ViewBox ||
|
|
|
|
transformBox == StyleGeometryBox::BorderBox) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aFrame.IsFrameOfType(nsIFrame::eSVGContainer)) {
|
|
|
|
nsRect boxRect = nsLayoutUtils::ComputeGeometryBox(
|
|
|
|
const_cast<nsIFrame*>(&aFrame), StyleGeometryBox::FillBox);
|
|
|
|
return CSSPoint::FromAppUnits(boxRect.TopLeft());
|
|
|
|
}
|
|
|
|
return CSSPoint::FromAppUnits(aFrame.GetPosition());
|
|
|
|
}
|
|
|
|
|
2019-10-31 23:07:28 +03:00
|
|
|
/* static */
|
2020-01-08 12:02:38 +03:00
|
|
|
Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
|
2019-10-31 23:07:30 +03:00
|
|
|
const OffsetPathData& aPath, const LengthPercentage& aDistance,
|
|
|
|
const StyleOffsetRotate& aRotate, const StylePositionOrAuto& aAnchor,
|
2020-02-07 16:34:42 +03:00
|
|
|
const CSSPoint& aTransformOrigin, TransformReferenceBox& aRefBox,
|
|
|
|
const CSSPoint& aAnchorPointAdjustment) {
|
2019-10-31 23:07:30 +03:00
|
|
|
if (aPath.IsNone()) {
|
2019-10-31 23:07:28 +03:00
|
|
|
return Nothing();
|
|
|
|
}
|
|
|
|
|
2019-10-31 23:07:30 +03:00
|
|
|
// Compute the point and angle for creating the equivalent translate and
|
|
|
|
// rotate.
|
2019-10-31 23:07:28 +03:00
|
|
|
double directionAngle = 0.0;
|
|
|
|
gfx::Point point;
|
2019-10-31 23:07:30 +03:00
|
|
|
if (aPath.IsPath()) {
|
|
|
|
const auto& path = aPath.AsPath();
|
|
|
|
if (!path.mGfxPath) {
|
2020-01-24 21:59:03 +03:00
|
|
|
// Empty gfx::Path means it is path('') (i.e. empty path string).
|
2019-10-31 23:07:28 +03:00
|
|
|
return Nothing();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Per the spec, we have to convert offset distance to pixels, with 100%
|
|
|
|
// being converted to total length. So here |gfxPath| is built with CSS
|
|
|
|
// pixel, and we calculate |pathLength| and |computedDistance| with CSS
|
|
|
|
// pixel as well.
|
2019-10-31 23:07:30 +03:00
|
|
|
gfx::Float pathLength = path.mGfxPath->ComputeLength();
|
2019-10-31 23:07:28 +03:00
|
|
|
gfx::Float usedDistance =
|
2019-10-31 23:07:30 +03:00
|
|
|
aDistance.ResolveToCSSPixels(CSSCoord(pathLength));
|
|
|
|
if (path.mIsClosedIntervals) {
|
2019-10-31 23:07:28 +03:00
|
|
|
// Per the spec, let used offset distance be equal to offset distance
|
|
|
|
// modulus the total length of the path. If the total length of the path
|
|
|
|
// is 0, used offset distance is also 0.
|
|
|
|
usedDistance = pathLength > 0.0 ? fmod(usedDistance, pathLength) : 0.0;
|
|
|
|
// We make sure |usedDistance| is 0.0 or a positive value.
|
|
|
|
// https://github.com/w3c/fxtf-drafts/issues/339
|
|
|
|
if (usedDistance < 0.0) {
|
|
|
|
usedDistance += pathLength;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Per the spec, for unclosed interval, let used offset distance be equal
|
|
|
|
// to offset distance clamped by 0 and the total length of the path.
|
|
|
|
usedDistance = clamped(usedDistance, 0.0f, pathLength);
|
|
|
|
}
|
|
|
|
gfx::Point tangent;
|
2019-10-31 23:07:30 +03:00
|
|
|
point = path.mGfxPath->ComputePointAtLength(usedDistance, &tangent);
|
2019-10-31 23:07:28 +03:00
|
|
|
directionAngle = (double)atan2(tangent.y, tangent.x); // In Radian.
|
2019-10-31 23:07:30 +03:00
|
|
|
} else if (aPath.IsRay()) {
|
|
|
|
const auto& ray = aPath.AsRay();
|
|
|
|
MOZ_ASSERT(ray.mRay);
|
|
|
|
|
|
|
|
CSSCoord pathLength =
|
|
|
|
ComputeRayPathLength(ray.mRay->size, ray.mRay->angle, ray.mData);
|
2019-10-31 23:07:28 +03:00
|
|
|
CSSCoord usedDistance =
|
2019-10-31 23:07:30 +03:00
|
|
|
ComputeRayUsedDistance(*ray.mRay, aDistance, aRotate, aAnchor,
|
2020-02-07 16:34:42 +03:00
|
|
|
aTransformOrigin, aRefBox, pathLength);
|
2019-10-31 23:07:28 +03:00
|
|
|
|
|
|
|
// 0deg pointing up and positive angles representing clockwise rotation.
|
2019-10-31 23:07:30 +03:00
|
|
|
directionAngle =
|
|
|
|
StyleAngle{ray.mRay->angle.ToDegrees() - 90.0f}.ToRadians();
|
2019-10-31 23:07:28 +03:00
|
|
|
|
|
|
|
point.x = usedDistance * cos(directionAngle);
|
|
|
|
point.y = usedDistance * sin(directionAngle);
|
|
|
|
} else {
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Unsupported offset-path value");
|
|
|
|
return Nothing();
|
|
|
|
}
|
|
|
|
|
|
|
|
// If |rotate.auto_| is true, the element should be rotated by the angle of
|
|
|
|
// the direction (i.e. directional tangent vector) of the offset-path, and the
|
|
|
|
// computed value of <angle> is added to this.
|
|
|
|
// Otherwise, the element has a constant clockwise rotation transformation
|
|
|
|
// applied to it by the specified rotation angle. (i.e. Don't need to
|
|
|
|
// consider the direction of the path.)
|
|
|
|
gfx::Float angle = static_cast<gfx::Float>(
|
2019-10-31 23:07:30 +03:00
|
|
|
(aRotate.auto_ ? directionAngle : 0.0) + aRotate.angle.ToRadians());
|
2019-10-31 23:07:28 +03:00
|
|
|
|
|
|
|
// Compute the offset for motion path translate.
|
|
|
|
// Bug 1559232: the translate parameters will be adjusted more after we
|
|
|
|
// support offset-position.
|
|
|
|
// Per the spec, the default offset-anchor is `auto`, so initialize the anchor
|
|
|
|
// point to transform-origin.
|
2019-10-31 23:07:30 +03:00
|
|
|
CSSPoint anchorPoint(aTransformOrigin);
|
2019-10-31 23:07:28 +03:00
|
|
|
gfx::Point shift;
|
2019-10-31 23:07:30 +03:00
|
|
|
if (!aAnchor.IsAuto()) {
|
|
|
|
const auto& pos = aAnchor.AsPosition();
|
2019-10-31 23:07:28 +03:00
|
|
|
anchorPoint = nsStyleTransformMatrix::Convert2DPosition(
|
2020-02-07 16:34:42 +03:00
|
|
|
pos.horizontal, pos.vertical, aRefBox);
|
2019-10-31 23:07:28 +03:00
|
|
|
// We need this value to shift the origin from transform-origin to
|
|
|
|
// offset-anchor (and vice versa).
|
|
|
|
// See nsStyleTransformMatrix::ReadTransform for more details.
|
2019-10-31 23:07:30 +03:00
|
|
|
shift = (anchorPoint - aTransformOrigin).ToUnknownPoint();
|
2019-10-31 23:07:28 +03:00
|
|
|
}
|
|
|
|
|
2020-02-07 16:34:42 +03:00
|
|
|
anchorPoint += aAnchorPointAdjustment;
|
2019-10-31 23:07:28 +03:00
|
|
|
|
2020-01-08 12:02:38 +03:00
|
|
|
return Some(ResolvedMotionPathData{point - anchorPoint.ToUnknownPoint(),
|
|
|
|
angle, shift});
|
2019-10-31 23:07:28 +03:00
|
|
|
}
|
|
|
|
|
2019-10-31 23:07:30 +03:00
|
|
|
static OffsetPathData GenerateOffsetPathData(const nsIFrame* aFrame) {
|
|
|
|
const StyleOffsetPath& path = aFrame->StyleDisplay()->mOffsetPath;
|
|
|
|
switch (path.tag) {
|
2019-10-31 23:07:31 +03:00
|
|
|
case StyleOffsetPath::Tag::Path: {
|
2020-01-24 21:59:03 +03:00
|
|
|
const StyleSVGPathData& pathData = path.AsPath();
|
2019-11-01 00:36:39 +03:00
|
|
|
RefPtr<gfx::Path> gfxPath =
|
2019-11-07 06:17:58 +03:00
|
|
|
aFrame->GetProperty(nsIFrame::OffsetPathCache());
|
2020-01-24 21:59:03 +03:00
|
|
|
MOZ_ASSERT(
|
|
|
|
gfxPath || pathData._0.IsEmpty(),
|
|
|
|
"Should have a valid cached gfx::Path or an empty path string");
|
|
|
|
return OffsetPathData::Path(pathData, gfxPath.forget());
|
2019-10-31 23:07:31 +03:00
|
|
|
}
|
2019-10-31 23:07:30 +03:00
|
|
|
case StyleOffsetPath::Tag::Ray:
|
|
|
|
return OffsetPathData::Ray(path.AsRay(), RayReferenceData(aFrame));
|
|
|
|
case StyleOffsetPath::Tag::None:
|
2020-02-07 16:34:42 +03:00
|
|
|
return OffsetPathData::None();
|
2019-10-31 23:07:30 +03:00
|
|
|
default:
|
2020-02-07 16:34:42 +03:00
|
|
|
MOZ_ASSERT_UNREACHABLE("Unknown offset-path");
|
2019-10-31 23:07:30 +03:00
|
|
|
return OffsetPathData::None();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static*/
|
2020-01-08 12:02:38 +03:00
|
|
|
Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
|
2020-02-07 16:34:42 +03:00
|
|
|
const nsIFrame* aFrame, TransformReferenceBox& aRefBox) {
|
2019-10-31 23:07:30 +03:00
|
|
|
MOZ_ASSERT(aFrame);
|
|
|
|
|
|
|
|
const nsStyleDisplay* display = aFrame->StyleDisplay();
|
|
|
|
|
|
|
|
// FIXME: It's possible to refactor the calculation of transform-origin, so we
|
|
|
|
// could calculate from the caller, and reuse the value in nsDisplayList.cpp.
|
|
|
|
CSSPoint transformOrigin = nsStyleTransformMatrix::Convert2DPosition(
|
|
|
|
display->mTransformOrigin.horizontal, display->mTransformOrigin.vertical,
|
2020-02-07 16:34:42 +03:00
|
|
|
aRefBox);
|
2019-10-31 23:07:30 +03:00
|
|
|
|
2020-02-07 16:34:42 +03:00
|
|
|
return ResolveMotionPath(GenerateOffsetPathData(aFrame),
|
|
|
|
display->mOffsetDistance, display->mOffsetRotate,
|
|
|
|
display->mOffsetAnchor, transformOrigin, aRefBox,
|
|
|
|
ComputeAnchorPointAdjustment(*aFrame));
|
2019-10-31 23:07:30 +03:00
|
|
|
}
|
|
|
|
|
2019-11-01 00:36:39 +03:00
|
|
|
static OffsetPathData GenerateOffsetPathData(
|
2020-01-08 12:02:36 +03:00
|
|
|
const StyleOffsetPath& aPath, const RayReferenceData& aRayReferenceData,
|
2019-11-01 00:36:39 +03:00
|
|
|
gfx::Path* aCachedMotionPath) {
|
|
|
|
switch (aPath.tag) {
|
|
|
|
case StyleOffsetPath::Tag::Path: {
|
2020-01-24 21:59:03 +03:00
|
|
|
const StyleSVGPathData& pathData = aPath.AsPath();
|
2019-11-01 00:36:39 +03:00
|
|
|
// If aCachedMotionPath is valid, we have a fixed path.
|
|
|
|
// This means we have pre-built it already and no need to update.
|
|
|
|
RefPtr<gfx::Path> path = aCachedMotionPath;
|
|
|
|
if (!path) {
|
|
|
|
RefPtr<gfx::PathBuilder> builder =
|
|
|
|
MotionPathUtils::GetCompositorPathBuilder();
|
2020-01-24 21:59:03 +03:00
|
|
|
path = MotionPathUtils::BuildPath(pathData, builder);
|
2019-11-01 00:36:39 +03:00
|
|
|
}
|
2020-01-24 21:59:03 +03:00
|
|
|
return OffsetPathData::Path(pathData, path.forget());
|
2019-11-01 00:36:39 +03:00
|
|
|
}
|
|
|
|
case StyleOffsetPath::Tag::Ray:
|
2020-01-08 12:02:36 +03:00
|
|
|
return OffsetPathData::Ray(aPath.AsRay(), aRayReferenceData);
|
2019-11-01 00:36:39 +03:00
|
|
|
case StyleOffsetPath::Tag::None:
|
|
|
|
default:
|
|
|
|
return OffsetPathData::None();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-31 23:07:41 +03:00
|
|
|
/* static */
|
2020-01-08 12:02:38 +03:00
|
|
|
Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
|
2019-10-31 23:07:41 +03:00
|
|
|
const StyleOffsetPath* aPath, const StyleLengthPercentage* aDistance,
|
|
|
|
const StyleOffsetRotate* aRotate, const StylePositionOrAuto* aAnchor,
|
2020-01-08 12:02:36 +03:00
|
|
|
const Maybe<layers::MotionPathData>& aMotionPathData,
|
2020-02-07 16:34:42 +03:00
|
|
|
TransformReferenceBox& aRefBox, gfx::Path* aCachedMotionPath) {
|
2019-10-31 23:07:41 +03:00
|
|
|
if (!aPath) {
|
|
|
|
return Nothing();
|
|
|
|
}
|
|
|
|
|
2020-01-08 12:02:36 +03:00
|
|
|
MOZ_ASSERT(aMotionPathData);
|
|
|
|
|
2019-10-31 23:07:41 +03:00
|
|
|
auto zeroOffsetDistance = LengthPercentage::Zero();
|
|
|
|
auto autoOffsetRotate = StyleOffsetRotate{true, StyleAngle::Zero()};
|
|
|
|
auto autoOffsetAnchor = StylePositionOrAuto::Auto();
|
2020-02-07 16:34:42 +03:00
|
|
|
return ResolveMotionPath(
|
2020-01-08 12:02:36 +03:00
|
|
|
GenerateOffsetPathData(*aPath, aMotionPathData->rayReferenceData(),
|
|
|
|
aCachedMotionPath),
|
2019-10-31 23:07:41 +03:00
|
|
|
aDistance ? *aDistance : zeroOffsetDistance,
|
|
|
|
aRotate ? *aRotate : autoOffsetRotate,
|
2020-02-07 16:34:42 +03:00
|
|
|
aAnchor ? *aAnchor : autoOffsetAnchor, aMotionPathData->origin(), aRefBox,
|
|
|
|
aMotionPathData->anchorAdjustment());
|
2019-10-31 23:07:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
2020-01-22 23:18:35 +03:00
|
|
|
StyleSVGPathData MotionPathUtils::NormalizeSVGPathData(
|
2019-10-31 23:07:41 +03:00
|
|
|
const StyleSVGPathData& aPath) {
|
|
|
|
StyleSVGPathData n;
|
|
|
|
Servo_SVGPathData_Normalize(&aPath, &n);
|
2020-01-22 23:18:35 +03:00
|
|
|
return n;
|
2019-10-31 23:07:41 +03:00
|
|
|
}
|
|
|
|
|
2019-11-01 00:36:39 +03:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<gfx::Path> MotionPathUtils::BuildPath(
|
|
|
|
const StyleSVGPathData& aPath, gfx::PathBuilder* aPathBuilder) {
|
|
|
|
if (!aPathBuilder) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
const Span<const StylePathCommand>& path = aPath._0.AsSpan();
|
2019-11-21 09:07:30 +03:00
|
|
|
return SVGPathData::BuildPath(path, aPathBuilder, StyleStrokeLinecap::Butt,
|
|
|
|
0.0);
|
2019-11-01 00:36:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
|
|
already_AddRefed<gfx::PathBuilder> MotionPathUtils::GetCompositorPathBuilder() {
|
|
|
|
// FIXME: Perhaps we need a PathBuilder which is independent on the backend.
|
|
|
|
RefPtr<gfx::PathBuilder> builder =
|
|
|
|
gfxPlatform::Initialized()
|
|
|
|
? gfxPlatform::GetPlatform()
|
|
|
|
->ScreenReferenceDrawTarget()
|
|
|
|
->CreatePathBuilder(gfx::FillRule::FILL_WINDING)
|
|
|
|
: gfx::Factory::CreateSimplePathBuilder();
|
|
|
|
return builder.forget();
|
|
|
|
}
|
|
|
|
|
2019-10-31 23:07:28 +03:00
|
|
|
} // namespace mozilla
|