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"
|
2023-08-08 01:04:02 +03:00
|
|
|
|
#include "mozilla/dom/SVGGeometryElement.h"
|
2019-10-31 23:07:28 +03:00
|
|
|
|
#include "mozilla/dom/SVGPathData.h"
|
2023-06-07 00:40:52 +03:00
|
|
|
|
#include "mozilla/dom/SVGViewportElement.h"
|
2019-10-31 23:07:28 +03:00
|
|
|
|
#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"
|
2023-08-08 01:04:02 +03:00
|
|
|
|
#include "mozilla/SVGObserverUtils.h"
|
2023-06-07 00:40:52 +03:00
|
|
|
|
#include "mozilla/ShapeUtils.h"
|
2019-10-31 23:07:28 +03:00
|
|
|
|
#include "nsIFrame.h"
|
2021-09-15 21:48:17 +03:00
|
|
|
|
#include "nsLayoutUtils.h"
|
2019-10-31 23:07:28 +03:00
|
|
|
|
#include "nsStyleTransformMatrix.h"
|
|
|
|
|
|
|
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
|
|
2020-02-07 16:34:42 +03:00
|
|
|
|
using nsStyleTransformMatrix::TransformReferenceBox;
|
|
|
|
|
|
2023-06-27 02:23:54 +03:00
|
|
|
|
/* static */
|
|
|
|
|
CSSPoint MotionPathUtils::ComputeAnchorPointAdjustment(const nsIFrame& aFrame) {
|
|
|
|
|
if (!aFrame.HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto transformBox = aFrame.StyleDisplay()->mTransformBox;
|
2023-08-22 22:49:03 +03:00
|
|
|
|
if (transformBox == StyleTransformBox::ViewBox ||
|
|
|
|
|
transformBox == StyleTransformBox::BorderBox) {
|
2023-06-27 02:23:54 +03:00
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-27 01:17:28 +03:00
|
|
|
|
if (aFrame.IsSVGContainerFrame()) {
|
2023-10-11 01:00:32 +03:00
|
|
|
|
nsRect boxRect = nsLayoutUtils::ComputeSVGReferenceRect(
|
2023-06-27 02:23:54 +03:00
|
|
|
|
const_cast<nsIFrame*>(&aFrame), StyleGeometryBox::FillBox);
|
|
|
|
|
return CSSPoint::FromAppUnits(boxRect.TopLeft());
|
|
|
|
|
}
|
|
|
|
|
return CSSPoint::FromAppUnits(aFrame.GetPosition());
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-11 01:00:32 +03:00
|
|
|
|
// Convert the StyleCoordBox into the StyleGeometryBox in CSS layout.
|
|
|
|
|
// https://drafts.csswg.org/css-box-4/#keywords
|
|
|
|
|
static StyleGeometryBox CoordBoxToGeometryBoxInCSSLayout(
|
|
|
|
|
StyleCoordBox aCoordBox) {
|
|
|
|
|
switch (aCoordBox) {
|
|
|
|
|
case StyleCoordBox::ContentBox:
|
|
|
|
|
return StyleGeometryBox::ContentBox;
|
|
|
|
|
case StyleCoordBox::PaddingBox:
|
|
|
|
|
return StyleGeometryBox::PaddingBox;
|
|
|
|
|
case StyleCoordBox::BorderBox:
|
|
|
|
|
return StyleGeometryBox::BorderBox;
|
|
|
|
|
case StyleCoordBox::FillBox:
|
|
|
|
|
return StyleGeometryBox::ContentBox;
|
|
|
|
|
case StyleCoordBox::StrokeBox:
|
|
|
|
|
case StyleCoordBox::ViewBox:
|
|
|
|
|
return StyleGeometryBox::BorderBox;
|
|
|
|
|
}
|
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Unknown coord-box type");
|
|
|
|
|
return StyleGeometryBox::BorderBox;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-27 02:23:54 +03:00
|
|
|
|
/* static */
|
|
|
|
|
const nsIFrame* MotionPathUtils::GetOffsetPathReferenceBox(
|
|
|
|
|
const nsIFrame* aFrame, nsRect& aOutputRect) {
|
2023-06-17 01:43:34 +03:00
|
|
|
|
const StyleOffsetPath& offsetPath = aFrame->StyleDisplay()->mOffsetPath;
|
|
|
|
|
if (offsetPath.IsNone()) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-07 00:40:52 +03:00
|
|
|
|
if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
|
|
|
|
|
MOZ_ASSERT(aFrame->GetContent()->IsSVGElement());
|
|
|
|
|
auto* viewportElement =
|
|
|
|
|
dom::SVGElement::FromNode(aFrame->GetContent())->GetCtx();
|
2023-10-10 09:26:51 +03:00
|
|
|
|
aOutputRect = nsLayoutUtils::ComputeSVGOriginBox(viewportElement);
|
2023-06-07 00:40:52 +03:00
|
|
|
|
return viewportElement ? viewportElement->GetPrimaryFrame() : nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const nsIFrame* containingBlock = aFrame->GetContainingBlock();
|
2023-06-17 01:43:34 +03:00
|
|
|
|
const StyleCoordBox coordBox = offsetPath.IsCoordBox()
|
|
|
|
|
? offsetPath.AsCoordBox()
|
|
|
|
|
: offsetPath.AsOffsetPath().coord_box;
|
|
|
|
|
aOutputRect = nsLayoutUtils::ComputeHTMLReferenceRect(
|
2023-10-11 01:00:32 +03:00
|
|
|
|
containingBlock, CoordBoxToGeometryBoxInCSSLayout(coordBox));
|
2023-06-07 00:40:52 +03:00
|
|
|
|
return containingBlock;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-27 02:23:54 +03:00
|
|
|
|
/* static */
|
|
|
|
|
CSSCoord MotionPathUtils::GetRayContainReferenceSize(nsIFrame* aFrame) {
|
2023-05-26 03:14:36 +03:00
|
|
|
|
// We use the border-box size to calculate the reduced path length when using
|
|
|
|
|
// "contain" keyword.
|
|
|
|
|
// https://drafts.fxtf.org/motion-1/#valdef-ray-contain
|
|
|
|
|
//
|
|
|
|
|
// Note: Per the spec, border-box is treated as stroke-box in the SVG context,
|
|
|
|
|
// https://drafts.csswg.org/css-box-4/#valdef-box-border-box
|
2023-12-06 12:19:01 +03:00
|
|
|
|
|
|
|
|
|
// To calculate stroke bounds for an element with `non-scaling-stroke` we
|
|
|
|
|
// need to resolve its transform to its outer-svg, but to resolve that
|
|
|
|
|
// transform when it has `transform-box:stroke-box` (or `border-box`)
|
|
|
|
|
// may require its stroke bounds. There's no ideal way to break this
|
|
|
|
|
// cyclical dependency, but we break it by using the FillBox.
|
|
|
|
|
// https://github.com/w3c/csswg-drafts/issues/9640
|
|
|
|
|
|
|
|
|
|
const auto size = CSSSize::FromAppUnits(
|
|
|
|
|
(aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)
|
|
|
|
|
? nsLayoutUtils::ComputeSVGReferenceRect(
|
|
|
|
|
aFrame, aFrame->StyleSVGReset()->HasNonScalingStroke()
|
|
|
|
|
? StyleGeometryBox::FillBox
|
|
|
|
|
: StyleGeometryBox::StrokeBox)
|
|
|
|
|
: nsLayoutUtils::ComputeHTMLReferenceRect(
|
|
|
|
|
aFrame, StyleGeometryBox::BorderBox))
|
|
|
|
|
.Size());
|
2023-06-27 02:23:54 +03:00
|
|
|
|
return std::max(size.width, size.height);
|
2019-10-31 23:07:30 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-27 02:23:55 +03:00
|
|
|
|
/* static */
|
|
|
|
|
nsTArray<nscoord> MotionPathUtils::ComputeBorderRadii(
|
|
|
|
|
const StyleBorderRadius& aBorderRadius, const nsRect& aCoordBox) {
|
|
|
|
|
const nsRect insetRect = ShapeUtils::ComputeInsetRect(
|
|
|
|
|
StyleRect<LengthPercentage>::WithAllSides(LengthPercentage::Zero()),
|
|
|
|
|
aCoordBox);
|
|
|
|
|
nsTArray<nscoord> result(8);
|
|
|
|
|
result.SetLength(8);
|
2023-07-07 21:12:41 +03:00
|
|
|
|
if (!ShapeUtils::ComputeRectRadii(aBorderRadius, aCoordBox, insetRect,
|
2023-06-27 02:23:55 +03:00
|
|
|
|
result.Elements())) {
|
|
|
|
|
result.Clear();
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-17 01:43:34 +03:00
|
|
|
|
// The distance is measured between the origin and the intersection of the ray
|
|
|
|
|
// with the reference box of the containing block.
|
|
|
|
|
// Note: |aOrigin| and |aContaingBlock| should be in the same coordinate system
|
|
|
|
|
// (i.e. the nsIFrame::mRect of the containing block).
|
2019-10-31 23:07:28 +03:00
|
|
|
|
// https://drafts.fxtf.org/motion-1/#size-sides
|
2023-06-17 01:43:34 +03:00
|
|
|
|
static CSSCoord ComputeSides(const CSSPoint& aOrigin,
|
|
|
|
|
const CSSRect& aContainingBlock,
|
2019-10-31 23:07:28 +03:00
|
|
|
|
const StyleAngle& aAngle) {
|
2023-06-17 01:43:34 +03:00
|
|
|
|
const CSSPoint& topLeft = aContainingBlock.TopLeft();
|
2019-10-31 23:07:28 +03:00
|
|
|
|
// 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)
|
2023-06-17 01:43:34 +03:00
|
|
|
|
// (topLeft) #--------*-----*--# (aContainingBlock.XMost(), topLeft.y)
|
2019-10-31 23:07:28 +03:00
|
|
|
|
// | | / |
|
|
|
|
|
// | | / |
|
|
|
|
|
// | b h |
|
|
|
|
|
// | |t/ |
|
|
|
|
|
// | |/ |
|
2023-06-17 01:43:34 +03:00
|
|
|
|
// (aOrigin) *---b'---* (aContainingBlock.XMost(), aOrigin.y)
|
2019-10-31 23:07:28 +03:00
|
|
|
|
// | | |
|
|
|
|
|
// | | |
|
|
|
|
|
// | | |
|
|
|
|
|
// | | |
|
|
|
|
|
// | | |
|
2023-06-17 01:43:34 +03:00
|
|
|
|
// #-----------------# (aContainingBlock.XMost(),
|
|
|
|
|
// (topLeft.x, aContainingBlock.YMost())
|
|
|
|
|
// aContainingBlock.YMost())
|
|
|
|
|
const double theta = aAngle.ToRadians();
|
2019-10-31 23:07:28 +03:00
|
|
|
|
double sint = std::sin(theta);
|
|
|
|
|
double cost = std::cos(theta);
|
|
|
|
|
|
2023-06-17 01:43:34 +03:00
|
|
|
|
const double b = cost >= 0 ? aOrigin.y.value - topLeft.y
|
|
|
|
|
: aContainingBlock.YMost() - aOrigin.y.value;
|
|
|
|
|
const double bPrime = sint >= 0 ? aContainingBlock.XMost() - aOrigin.x.value
|
|
|
|
|
: aOrigin.x.value - topLeft.x;
|
2019-10-31 23:07:28 +03:00
|
|
|
|
sint = std::fabs(sint);
|
|
|
|
|
cost = std::fabs(cost);
|
|
|
|
|
|
2023-10-20 06:41:10 +03:00
|
|
|
|
// The trigonometric formula here doesn't work well if |theta| is 0deg or
|
|
|
|
|
// 90deg, so we handle these edge cases first.
|
|
|
|
|
if (sint < std::numeric_limits<double>::epsilon()) {
|
|
|
|
|
// For 0deg (or 180deg), we use |b| directly.
|
|
|
|
|
return static_cast<float>(b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cost < std::numeric_limits<double>::epsilon()) {
|
|
|
|
|
// For 90deg (or 270deg), we use |bPrime| directly. This can also avoid 0/0
|
|
|
|
|
// if both |b| and |cost| are 0.0. (i.e. b / cost).
|
|
|
|
|
return static_cast<float>(bPrime);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Note: The following formula works well only when 0 < theta < 90deg. So we
|
|
|
|
|
// handle 0deg and 90deg above first.
|
|
|
|
|
//
|
2019-10-31 23:07:28 +03:00
|
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-27 02:23:54 +03:00
|
|
|
|
// Compute the position of "at <position>" together with offset starting
|
|
|
|
|
// position (i.e. offset-position).
|
|
|
|
|
static nsPoint ComputePosition(const StylePositionOrAuto& aAtPosition,
|
|
|
|
|
const StyleOffsetPosition& aOffsetPosition,
|
|
|
|
|
const nsRect& aCoordBox,
|
|
|
|
|
const nsPoint& aCurrentCoord) {
|
2023-06-07 00:40:52 +03:00
|
|
|
|
if (aAtPosition.IsPosition()) {
|
|
|
|
|
// Resolve this by using the <position> to position a 0x0 object area within
|
|
|
|
|
// the box’s containing block.
|
2023-06-27 02:23:54 +03:00
|
|
|
|
return ShapeUtils::ComputePosition(aAtPosition.AsPosition(), aCoordBox);
|
2023-06-07 00:40:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MOZ_ASSERT(aAtPosition.IsAuto(), "\"at <position>\" should be omitted");
|
|
|
|
|
|
|
|
|
|
// Use the offset starting position of the element, given by offset-position.
|
|
|
|
|
// https://drafts.fxtf.org/motion-1/#valdef-ray-at-position
|
|
|
|
|
if (aOffsetPosition.IsPosition()) {
|
2023-06-27 02:23:54 +03:00
|
|
|
|
return ShapeUtils::ComputePosition(aOffsetPosition.AsPosition(), aCoordBox);
|
2023-06-07 00:40:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (aOffsetPosition.IsNormal()) {
|
|
|
|
|
// If the element doesn’t have an offset starting position either, it
|
|
|
|
|
// behaves as at center.
|
2023-06-27 02:23:54 +03:00
|
|
|
|
const StylePosition& center = StylePosition::FromPercentage(0.5);
|
|
|
|
|
return ShapeUtils::ComputePosition(center, aCoordBox);
|
2023-06-07 00:40:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MOZ_ASSERT(aOffsetPosition.IsAuto());
|
2023-06-27 02:23:54 +03:00
|
|
|
|
return aCurrentCoord;
|
2023-06-07 00:40:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-31 23:07:30 +03:00
|
|
|
|
static CSSCoord ComputeRayPathLength(const StyleRaySize aRaySizeType,
|
|
|
|
|
const StyleAngle& aAngle,
|
2023-06-07 00:40:52 +03:00
|
|
|
|
const CSSPoint& aOrigin,
|
|
|
|
|
const CSSRect& aContainingBlock) {
|
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.
|
2023-10-20 06:41:10 +03:00
|
|
|
|
//
|
|
|
|
|
// Note: If the origin is at XMost() (and/or YMost()), we should consider it
|
|
|
|
|
// to be inside containing block (because we expect 100% x (or y) coordinate
|
|
|
|
|
// is still to be considered inside the containing block.
|
|
|
|
|
if (!aContainingBlock.ContainsInclusively(aOrigin)) {
|
2019-10-31 23:07:28 +03:00
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-17 01:43:34 +03:00
|
|
|
|
return ComputeSides(aOrigin, aContainingBlock, aAngle);
|
2019-10-31 23:07:28 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-17 01:43:34 +03:00
|
|
|
|
// left: the length between the origin and the left side.
|
|
|
|
|
// right: the length between the origin and the right side.
|
|
|
|
|
// top: the length between the origin and the top side.
|
|
|
|
|
// bottom: the lenght between the origin and the bottom side.
|
|
|
|
|
const CSSPoint& topLeft = aContainingBlock.TopLeft();
|
|
|
|
|
const CSSCoord left = std::abs(aOrigin.x - topLeft.x);
|
|
|
|
|
const CSSCoord right = std::abs(aContainingBlock.XMost() - aOrigin.x);
|
|
|
|
|
const CSSCoord top = std::abs(aOrigin.y - topLeft.y);
|
|
|
|
|
const CSSCoord bottom = std::abs(aContainingBlock.YMost() - aOrigin.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);
|
|
|
|
|
}
|
2023-06-17 01:43:34 +03:00
|
|
|
|
case StyleRaySize::Sides:
|
2019-10-31 23:07:28 +03:00
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Unsupported ray size");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-27 02:23:54 +03:00
|
|
|
|
static CSSCoord ComputeRayUsedDistance(
|
|
|
|
|
const StyleRayFunction& aRay, const LengthPercentage& aDistance,
|
|
|
|
|
const CSSCoord& aPathLength, const CSSCoord& aRayContainReferenceLength) {
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-26 03:14:36 +03:00
|
|
|
|
// The length of the offset path is reduced so that the element stays within
|
|
|
|
|
// the containing block even at offset-distance: 100%. Specifically, the
|
|
|
|
|
// path’s length is reduced by half the width or half the height of the
|
|
|
|
|
// element’s border box, whichever is larger, and floored at zero.
|
|
|
|
|
// https://drafts.fxtf.org/motion-1/#valdef-ray-contain
|
2023-06-27 02:23:54 +03:00
|
|
|
|
return std::max((usedDistance - aRayContainReferenceLength / 2.0f).value,
|
|
|
|
|
0.0f);
|
2020-02-07 16:34:42 +03:00
|
|
|
|
}
|
|
|
|
|
|
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,
|
2023-06-07 00:40:52 +03:00
|
|
|
|
const StyleOffsetPosition& aPosition, 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;
|
2023-06-27 02:23:54 +03:00
|
|
|
|
if (aPath.IsShape()) {
|
2023-06-27 02:23:55 +03:00
|
|
|
|
const auto& data = aPath.AsShape();
|
|
|
|
|
RefPtr<gfx::Path> path = data.mGfxPath;
|
|
|
|
|
MOZ_ASSERT(path, "The empty path is not allowed");
|
2019-10-31 23:07:28 +03:00
|
|
|
|
|
|
|
|
|
// 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.
|
2023-06-27 02:23:55 +03:00
|
|
|
|
gfx::Float pathLength = path->ComputeLength();
|
2019-10-31 23:07:28 +03:00
|
|
|
|
gfx::Float usedDistance =
|
2019-10-31 23:07:30 +03:00
|
|
|
|
aDistance.ResolveToCSSPixels(CSSCoord(pathLength));
|
2023-08-08 01:04:02 +03:00
|
|
|
|
if (data.mIsClosedLoop) {
|
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.
|
|
|
|
|
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;
|
2023-06-27 02:23:55 +03:00
|
|
|
|
point = path->ComputePointAtLength(usedDistance, &tangent);
|
|
|
|
|
// Basically, |point| should be a relative distance between the current
|
|
|
|
|
// position and the target position. The built |path| is in the coordinate
|
|
|
|
|
// system of its containing block. Therefore, we have to take the current
|
|
|
|
|
// position of this box into account to offset the translation so it's final
|
|
|
|
|
// position is not affected by other boxes in the same containing block.
|
|
|
|
|
point -= NSPointToPoint(data.mCurrentPosition, AppUnitsPerCSSPixel());
|
|
|
|
|
directionAngle = atan2((double)tangent.y, (double)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);
|
|
|
|
|
|
2023-06-27 02:23:54 +03:00
|
|
|
|
// Compute the origin, where the ray’s line begins (the 0% position).
|
|
|
|
|
// https://drafts.fxtf.org/motion-1/#ray-origin
|
|
|
|
|
const CSSPoint origin = CSSPoint::FromAppUnits(ComputePosition(
|
|
|
|
|
ray.mRay->position, aPosition, ray.mCoordBox, ray.mCurrentPosition));
|
2023-06-07 00:40:52 +03:00
|
|
|
|
const CSSCoord pathLength =
|
|
|
|
|
ComputeRayPathLength(ray.mRay->size, ray.mRay->angle, origin,
|
2023-06-27 02:23:54 +03:00
|
|
|
|
CSSRect::FromAppUnits(ray.mCoordBox));
|
2023-06-07 00:40:52 +03:00
|
|
|
|
const CSSCoord usedDistance = ComputeRayUsedDistance(
|
2023-06-27 02:23:54 +03:00
|
|
|
|
*ray.mRay, aDistance, pathLength, ray.mContainReferenceLength);
|
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
|
|
|
|
|
2023-06-07 00:40:52 +03:00
|
|
|
|
// The vector from the current position of this box to the origin of this
|
|
|
|
|
// polar coordinate system.
|
|
|
|
|
const gfx::Point vectorToOrigin =
|
2023-06-27 02:23:54 +03:00
|
|
|
|
(origin - CSSPoint::FromAppUnits(ray.mCurrentPosition))
|
|
|
|
|
.ToUnknownPoint();
|
2023-06-07 00:40:52 +03:00
|
|
|
|
// |vectorToOrigin| + The vector from the origin to this polar coordinate,
|
|
|
|
|
// (|usedDistance|, |directionAngle|), i.e. the vector from the current
|
|
|
|
|
// position to this polar coordinate.
|
|
|
|
|
point =
|
|
|
|
|
vectorToOrigin +
|
|
|
|
|
gfx::Point(usedDistance * static_cast<gfx::Float>(cos(directionAngle)),
|
|
|
|
|
usedDistance * static_cast<gfx::Float>(sin(directionAngle)));
|
2019-10-31 23:07:28 +03:00
|
|
|
|
} 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
|
|
|
|
}
|
|
|
|
|
|
2023-08-08 01:04:02 +03:00
|
|
|
|
static inline bool IsClosedLoop(const StyleSVGPathData& aPathData) {
|
2023-06-27 02:23:55 +03:00
|
|
|
|
return !aPathData._0.AsSpan().empty() &&
|
|
|
|
|
aPathData._0.AsSpan().rbegin()->IsClosePath();
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-27 02:23:55 +03:00
|
|
|
|
// Create a path for "inset(0 round X)", where X is the value of border-radius
|
|
|
|
|
// on the element that establishes the containing block for this element.
|
|
|
|
|
static already_AddRefed<gfx::Path> BuildSimpleInsetPath(
|
|
|
|
|
const StyleBorderRadius& aBorderRadius, const nsRect& aCoordBox,
|
|
|
|
|
gfx::PathBuilder* aPathBuilder) {
|
|
|
|
|
if (!aPathBuilder) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const nsRect insetRect = ShapeUtils::ComputeInsetRect(
|
|
|
|
|
StyleRect<LengthPercentage>::WithAllSides(LengthPercentage::Zero()),
|
|
|
|
|
aCoordBox);
|
|
|
|
|
nscoord radii[8];
|
2023-07-07 21:12:41 +03:00
|
|
|
|
const bool hasRadii =
|
|
|
|
|
ShapeUtils::ComputeRectRadii(aBorderRadius, aCoordBox, insetRect, radii);
|
|
|
|
|
return ShapeUtils::BuildRectPath(insetRect, hasRadii ? radii : nullptr,
|
|
|
|
|
aCoordBox, AppUnitsPerCSSPixel(),
|
|
|
|
|
aPathBuilder);
|
2023-06-27 02:23:55 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-08 01:04:02 +03:00
|
|
|
|
// Create a path for `path("m 0 0")`, which is the default URL path if we cannot
|
|
|
|
|
// resolve a SVG shape element.
|
2023-08-08 01:04:01 +03:00
|
|
|
|
// https://drafts.fxtf.org/motion-1/#valdef-offset-path-url
|
|
|
|
|
static already_AddRefed<gfx::Path> BuildDefaultPathForURL(
|
|
|
|
|
gfx::PathBuilder* aBuilder) {
|
|
|
|
|
if (!aBuilder) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Array<const StylePathCommand, 1> array(StylePathCommand::MoveTo(
|
|
|
|
|
StyleCoordPair(gfx::Point{0.0, 0.0}), StyleIsAbsolute::No));
|
|
|
|
|
return SVGPathData::BuildPath(array, aBuilder, StyleStrokeLinecap::Butt, 0.0);
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-27 02:23:55 +03:00
|
|
|
|
// Generate data for motion path on the main thread.
|
2019-10-31 23:07:30 +03:00
|
|
|
|
static OffsetPathData GenerateOffsetPathData(const nsIFrame* aFrame) {
|
2023-06-15 03:03:59 +03:00
|
|
|
|
const StyleOffsetPath& offsetPath = aFrame->StyleDisplay()->mOffsetPath;
|
|
|
|
|
if (offsetPath.IsNone()) {
|
|
|
|
|
return OffsetPathData::None();
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-27 02:23:55 +03:00
|
|
|
|
// Handle ray().
|
|
|
|
|
if (offsetPath.IsRay()) {
|
2023-06-27 02:23:54 +03:00
|
|
|
|
nsRect coordBox;
|
|
|
|
|
const nsIFrame* containingBlockFrame =
|
|
|
|
|
MotionPathUtils::GetOffsetPathReferenceBox(aFrame, coordBox);
|
|
|
|
|
return !containingBlockFrame
|
|
|
|
|
? OffsetPathData::None()
|
|
|
|
|
: OffsetPathData::Ray(
|
2023-06-27 02:23:55 +03:00
|
|
|
|
offsetPath.AsRay(), std::move(coordBox),
|
2023-06-27 02:23:54 +03:00
|
|
|
|
aFrame->GetOffsetTo(containingBlockFrame),
|
|
|
|
|
MotionPathUtils::GetRayContainReferenceSize(
|
|
|
|
|
const_cast<nsIFrame*>(aFrame)));
|
2019-10-31 23:07:30 +03:00
|
|
|
|
}
|
2023-06-15 03:03:59 +03:00
|
|
|
|
|
2023-06-27 02:23:55 +03:00
|
|
|
|
// Handle path(). We cache it so we handle it separately.
|
|
|
|
|
// FIXME: Bug 1837042, cache gfx::Path for shapes other than path(). Once we
|
|
|
|
|
// cache all basic shapes, we can merge this branch into other basic shapes.
|
|
|
|
|
if (offsetPath.IsPath()) {
|
|
|
|
|
const StyleSVGPathData& pathData = offsetPath.AsSVGPathData();
|
2023-06-15 03:03:59 +03:00
|
|
|
|
RefPtr<gfx::Path> gfxPath =
|
|
|
|
|
aFrame->GetProperty(nsIFrame::OffsetPathCache());
|
|
|
|
|
MOZ_ASSERT(gfxPath || pathData._0.IsEmpty(),
|
|
|
|
|
"Should have a valid cached gfx::Path or an empty path string");
|
2023-06-27 02:23:55 +03:00
|
|
|
|
// FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
|
|
|
|
|
// to give it the current box position.
|
2023-08-08 01:04:02 +03:00
|
|
|
|
return OffsetPathData::Shape(gfxPath.forget(), {}, IsClosedLoop(pathData));
|
2023-06-15 03:03:59 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-08 01:04:02 +03:00
|
|
|
|
nsRect coordBox;
|
|
|
|
|
const nsIFrame* containingFrame =
|
|
|
|
|
MotionPathUtils::GetOffsetPathReferenceBox(aFrame, coordBox);
|
|
|
|
|
if (!containingFrame || coordBox.IsEmpty()) {
|
|
|
|
|
return OffsetPathData::None();
|
|
|
|
|
}
|
|
|
|
|
nsPoint currentPosition = aFrame->GetOffsetTo(containingFrame);
|
2023-08-08 01:04:01 +03:00
|
|
|
|
RefPtr<gfx::PathBuilder> builder = MotionPathUtils::GetPathBuilder();
|
|
|
|
|
|
|
|
|
|
if (offsetPath.IsUrl()) {
|
2023-08-08 01:04:02 +03:00
|
|
|
|
dom::SVGGeometryElement* element =
|
|
|
|
|
SVGObserverUtils::GetAndObserveGeometry(const_cast<nsIFrame*>(aFrame));
|
|
|
|
|
if (!element) {
|
|
|
|
|
// Note: This behaves as path("m 0 0") (a <basic-shape>).
|
|
|
|
|
RefPtr<gfx::Path> path = BuildDefaultPathForURL(builder);
|
|
|
|
|
// FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
|
|
|
|
|
// to give it the current box position.
|
|
|
|
|
return path ? OffsetPathData::Shape(path.forget(), {}, false)
|
|
|
|
|
: OffsetPathData::None();
|
|
|
|
|
}
|
2023-08-08 01:04:01 +03:00
|
|
|
|
|
2023-08-08 01:04:02 +03:00
|
|
|
|
// We just need this path to calculate the specific point and direction
|
|
|
|
|
// angle, so use measuring function and get the benefit of caching the path
|
|
|
|
|
// in the SVG shape element.
|
|
|
|
|
RefPtr<gfx::Path> path = element->GetOrBuildPathForMeasuring();
|
|
|
|
|
|
|
|
|
|
// The built |path| from SVG shape element doesn't take |coordBox| into
|
|
|
|
|
// account. It uses the SVG viewport as its coordinate system. So after
|
|
|
|
|
// mapping it into the CSS layout, we should use |coordBox| as its viewport
|
|
|
|
|
// and user coordinate system. |currentPosition| is based on the border-box
|
|
|
|
|
// of the containing block. Therefore, we have to apply an extra translation
|
|
|
|
|
// to put it at the correct position based on |coordBox|.
|
|
|
|
|
//
|
|
|
|
|
// Note: we reuse |OffsetPathData::ShapeData::mCurrentPosition| to include
|
|
|
|
|
// this extra translation, so we don't have to add an extra field.
|
|
|
|
|
nsPoint positionInCoordBox = currentPosition - coordBox.TopLeft();
|
|
|
|
|
return path ? OffsetPathData::Shape(path.forget(),
|
|
|
|
|
std::move(positionInCoordBox),
|
|
|
|
|
element->IsClosedLoop())
|
2023-08-08 01:04:01 +03:00
|
|
|
|
: OffsetPathData::None();
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-27 02:23:55 +03:00
|
|
|
|
// The rest part is to handle "<basic-shape> || <coord-box>".
|
|
|
|
|
MOZ_ASSERT(offsetPath.IsBasicShapeOrCoordBox());
|
2023-06-27 02:23:55 +03:00
|
|
|
|
|
2023-08-08 01:04:01 +03:00
|
|
|
|
const nsStyleDisplay* disp = aFrame->StyleDisplay();
|
2023-06-27 02:23:55 +03:00
|
|
|
|
RefPtr<gfx::Path> path =
|
|
|
|
|
disp->mOffsetPath.IsCoordBox()
|
|
|
|
|
? BuildSimpleInsetPath(containingFrame->StyleBorder()->mBorderRadius,
|
|
|
|
|
coordBox, builder)
|
|
|
|
|
: MotionPathUtils::BuildPath(
|
|
|
|
|
disp->mOffsetPath.AsOffsetPath().path->AsShape(),
|
|
|
|
|
disp->mOffsetPosition, coordBox, currentPosition, builder);
|
|
|
|
|
return path ? OffsetPathData::Shape(path.forget(), std::move(currentPosition),
|
|
|
|
|
true)
|
|
|
|
|
: OffsetPathData::None();
|
2019-10-31 23:07:30 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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
|
|
|
|
|
2023-06-07 00:40:52 +03:00
|
|
|
|
return ResolveMotionPath(
|
|
|
|
|
GenerateOffsetPathData(aFrame), display->mOffsetDistance,
|
|
|
|
|
display->mOffsetRotate, display->mOffsetAnchor, display->mOffsetPosition,
|
|
|
|
|
transformOrigin, aRefBox, ComputeAnchorPointAdjustment(*aFrame));
|
2019-10-31 23:07:30 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-27 02:23:55 +03:00
|
|
|
|
// Generate data for motion path on the compositor thread.
|
2019-11-01 00:36:39 +03:00
|
|
|
|
static OffsetPathData GenerateOffsetPathData(
|
2023-06-15 03:03:59 +03:00
|
|
|
|
const StyleOffsetPath& aOffsetPath,
|
2023-06-27 02:23:55 +03:00
|
|
|
|
const StyleOffsetPosition& aOffsetPosition,
|
2023-06-27 02:23:54 +03:00
|
|
|
|
const layers::MotionPathData& aMotionPathData,
|
|
|
|
|
gfx::Path* aCachedMotionPath) {
|
2023-06-15 03:03:59 +03:00
|
|
|
|
if (aOffsetPath.IsNone()) {
|
2023-06-15 03:03:59 +03:00
|
|
|
|
return OffsetPathData::None();
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-27 02:23:55 +03:00
|
|
|
|
// Handle ray().
|
|
|
|
|
if (aOffsetPath.IsRay()) {
|
2023-06-27 02:23:54 +03:00
|
|
|
|
return aMotionPathData.coordBox().IsEmpty()
|
|
|
|
|
? OffsetPathData::None()
|
|
|
|
|
: OffsetPathData::Ray(
|
2023-06-27 02:23:55 +03:00
|
|
|
|
aOffsetPath.AsRay(), aMotionPathData.coordBox(),
|
2023-06-27 02:23:54 +03:00
|
|
|
|
aMotionPathData.currentPosition(),
|
|
|
|
|
aMotionPathData.rayContainReferenceLength());
|
2023-06-15 03:03:59 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-27 02:23:55 +03:00
|
|
|
|
// Handle path().
|
2023-06-27 02:23:55 +03:00
|
|
|
|
// FIXME: Bug 1837042, cache gfx::Path for shapes other than path().
|
2023-06-27 02:23:55 +03:00
|
|
|
|
if (aOffsetPath.IsPath()) {
|
|
|
|
|
const StyleSVGPathData& pathData = aOffsetPath.AsSVGPathData();
|
2023-06-15 03:03:59 +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();
|
2023-06-27 02:23:55 +03:00
|
|
|
|
path = MotionPathUtils::BuildSVGPath(pathData, builder);
|
2019-11-01 00:36:39 +03:00
|
|
|
|
}
|
2023-06-27 02:23:55 +03:00
|
|
|
|
// FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
|
|
|
|
|
// to give it the current box position.
|
2023-08-08 01:04:02 +03:00
|
|
|
|
return OffsetPathData::Shape(path.forget(), {}, IsClosedLoop(pathData));
|
2019-11-01 00:36:39 +03:00
|
|
|
|
}
|
2023-06-15 03:03:59 +03:00
|
|
|
|
|
2023-06-27 02:23:55 +03:00
|
|
|
|
// The rest part is to handle "<basic-shape> || <coord-box>".
|
|
|
|
|
MOZ_ASSERT(aOffsetPath.IsBasicShapeOrCoordBox());
|
2023-06-27 02:23:55 +03:00
|
|
|
|
|
|
|
|
|
const nsRect& coordBox = aMotionPathData.coordBox();
|
|
|
|
|
if (coordBox.IsEmpty()) {
|
|
|
|
|
return OffsetPathData::None();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RefPtr<gfx::PathBuilder> builder =
|
|
|
|
|
MotionPathUtils::GetCompositorPathBuilder();
|
|
|
|
|
if (!builder) {
|
|
|
|
|
return OffsetPathData::None();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RefPtr<gfx::Path> path;
|
|
|
|
|
if (aOffsetPath.IsCoordBox()) {
|
|
|
|
|
const nsRect insetRect = ShapeUtils::ComputeInsetRect(
|
|
|
|
|
StyleRect<LengthPercentage>::WithAllSides(LengthPercentage::Zero()),
|
|
|
|
|
coordBox);
|
|
|
|
|
const nsTArray<nscoord>& radii = aMotionPathData.coordBoxInsetRadii();
|
2023-07-07 21:12:41 +03:00
|
|
|
|
path = ShapeUtils::BuildRectPath(
|
2023-06-27 02:23:55 +03:00
|
|
|
|
insetRect, radii.IsEmpty() ? nullptr : radii.Elements(), coordBox,
|
|
|
|
|
AppUnitsPerCSSPixel(), builder);
|
|
|
|
|
} else {
|
|
|
|
|
path = MotionPathUtils::BuildPath(
|
|
|
|
|
aOffsetPath.AsOffsetPath().path->AsShape(), aOffsetPosition, coordBox,
|
|
|
|
|
aMotionPathData.currentPosition(), builder);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return path ? OffsetPathData::Shape(
|
|
|
|
|
path.forget(), nsPoint(aMotionPathData.currentPosition()),
|
|
|
|
|
true)
|
|
|
|
|
: OffsetPathData::None();
|
2019-11-01 00:36:39 +03:00
|
|
|
|
}
|
|
|
|
|
|
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,
|
2023-06-07 00:40:52 +03:00
|
|
|
|
const StyleOffsetPosition* aPosition,
|
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();
|
2023-06-07 00:40:52 +03:00
|
|
|
|
auto autoOffsetPosition = StyleOffsetPosition::Auto();
|
2020-02-07 16:34:42 +03:00
|
|
|
|
return ResolveMotionPath(
|
2023-06-27 02:23:55 +03:00
|
|
|
|
GenerateOffsetPathData(*aPath,
|
|
|
|
|
aPosition ? *aPosition : autoOffsetPosition,
|
|
|
|
|
*aMotionPathData, aCachedMotionPath),
|
2019-10-31 23:07:41 +03:00
|
|
|
|
aDistance ? *aDistance : zeroOffsetDistance,
|
|
|
|
|
aRotate ? *aRotate : autoOffsetRotate,
|
2023-06-07 00:40:52 +03:00
|
|
|
|
aAnchor ? *aAnchor : autoOffsetAnchor,
|
2023-06-07 00:40:52 +03:00
|
|
|
|
aPosition ? *aPosition : autoOffsetPosition, aMotionPathData->origin(),
|
|
|
|
|
aRefBox, aMotionPathData->anchorAdjustment());
|
2019-10-31 23:07:41 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-11-01 00:36:39 +03:00
|
|
|
|
/* static */
|
2023-06-27 02:23:55 +03:00
|
|
|
|
already_AddRefed<gfx::Path> MotionPathUtils::BuildSVGPath(
|
2019-11-01 00:36:39 +03:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
2023-06-27 02:23:55 +03:00
|
|
|
|
/* static */
|
|
|
|
|
already_AddRefed<gfx::Path> MotionPathUtils::BuildPath(
|
|
|
|
|
const StyleBasicShape& aBasicShape,
|
|
|
|
|
const StyleOffsetPosition& aOffsetPosition, const nsRect& aCoordBox,
|
|
|
|
|
const nsPoint& aCurrentPosition, gfx::PathBuilder* aPathBuilder) {
|
|
|
|
|
if (!aPathBuilder) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (aBasicShape.tag) {
|
|
|
|
|
case StyleBasicShape::Tag::Circle: {
|
|
|
|
|
const nsPoint center =
|
|
|
|
|
ComputePosition(aBasicShape.AsCircle().position, aOffsetPosition,
|
|
|
|
|
aCoordBox, aCurrentPosition);
|
|
|
|
|
return ShapeUtils::BuildCirclePath(aBasicShape, aCoordBox, center,
|
|
|
|
|
AppUnitsPerCSSPixel(), aPathBuilder);
|
|
|
|
|
}
|
|
|
|
|
case StyleBasicShape::Tag::Ellipse: {
|
|
|
|
|
const nsPoint center =
|
|
|
|
|
ComputePosition(aBasicShape.AsEllipse().position, aOffsetPosition,
|
|
|
|
|
aCoordBox, aCurrentPosition);
|
|
|
|
|
return ShapeUtils::BuildEllipsePath(aBasicShape, aCoordBox, center,
|
|
|
|
|
AppUnitsPerCSSPixel(), aPathBuilder);
|
|
|
|
|
}
|
Bug 1842277 - Compute <basic-shape-rect> to the equivalent inset() function. r=devtools-reviewers,emilio
Per spec
https://drafts.csswg.org/css-shapes-1/#basic-shape-computed-values, and
the spec issue, https://github.com/w3c/csswg-drafts/issues/9053,
all <basic-shape-rect> functions compute to the equivalent inset() function.
i.e.
`xywh(x y w h)` computes to
`inset(y calc(100% - x - w) calc(100% - y - h) x)`
The basic concept is to use `BasicShapeRect` as the specified value of
`<basic-shape-rect>`, i.e. inset()/xywh()/rect(), and its computed value is
the equivalent `InsetRect`, and so it becomes possible to interpolate among
these three functions.
Also, we can drop BuildXywhPath() and ComputeRect() because now there is
only inset() function when building gfx::Path.
Besides, tweak the test, offset-path-shape-xywh-003.html, because the original
test, `xywh(10% 10% 80% 80%)`, has to calculate `calc(100% - 10% - 80%)`.
Its result is `Percentage(0.099999964)` in Rust code, and then it makes us
compute an imprecision inset rect when building the gfx::path, which results in
a transform with the sub-pixel translation. So change it to
`xywh(10% 10% 90% 90%)` to avoid adding fuzzy tolerance.
Differential Revision: https://phabricator.services.mozilla.com/D183221
2023-07-12 23:16:47 +03:00
|
|
|
|
case StyleBasicShape::Tag::Rect:
|
2023-06-27 02:23:55 +03:00
|
|
|
|
return ShapeUtils::BuildInsetPath(aBasicShape, aCoordBox,
|
|
|
|
|
AppUnitsPerCSSPixel(), aPathBuilder);
|
|
|
|
|
case StyleBasicShape::Tag::Polygon:
|
|
|
|
|
return ShapeUtils::BuildPolygonPath(aBasicShape, aCoordBox,
|
|
|
|
|
AppUnitsPerCSSPixel(), aPathBuilder);
|
|
|
|
|
case StyleBasicShape::Tag::Path:
|
|
|
|
|
// FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
|
|
|
|
|
// to also check its containing block as well. For now, we are still
|
|
|
|
|
// building its gfx::Path directly by its SVGPathData without other
|
|
|
|
|
// reference. https://github.com/w3c/fxtf-drafts/issues/504
|
|
|
|
|
return BuildSVGPath(aBasicShape.AsPath().path, aPathBuilder);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* static */
|
|
|
|
|
already_AddRefed<gfx::PathBuilder> MotionPathUtils::GetPathBuilder() {
|
|
|
|
|
// Here we only need to build a valid path for motion path, so
|
|
|
|
|
// using the default values of stroke-width, stoke-linecap, and fill-rule
|
|
|
|
|
// is fine for now because what we want is to get the point and its normal
|
|
|
|
|
// vector along the path, instead of rendering it.
|
|
|
|
|
RefPtr<gfx::PathBuilder> builder =
|
|
|
|
|
gfxPlatform::GetPlatform()
|
|
|
|
|
->ScreenReferenceDrawTarget()
|
|
|
|
|
->CreatePathBuilder(gfx::FillRule::FILL_WINDING);
|
|
|
|
|
return builder.forget();
|
|
|
|
|
}
|
|
|
|
|
|
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
|