1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/MotionPathUtils.h"
9 #include "gfxPlatform.h"
10 #include "mozilla/dom/SVGGeometryElement.h"
11 #include "mozilla/dom/SVGPathData.h"
12 #include "mozilla/dom/SVGViewportElement.h"
13 #include "mozilla/gfx/2D.h"
14 #include "mozilla/gfx/Matrix.h"
15 #include "mozilla/layers/LayersMessages.h"
16 #include "mozilla/RefPtr.h"
17 #include "mozilla/SVGObserverUtils.h"
18 #include "mozilla/ShapeUtils.h"
20 #include "nsLayoutUtils.h"
21 #include "nsStyleTransformMatrix.h"
27 using nsStyleTransformMatrix::TransformReferenceBox
;
30 CSSPoint
MotionPathUtils::ComputeAnchorPointAdjustment(const nsIFrame
& aFrame
) {
31 if (!aFrame
.HasAnyStateBits(NS_FRAME_SVG_LAYOUT
)) {
35 auto transformBox
= aFrame
.StyleDisplay()->mTransformBox
;
36 if (transformBox
== StyleTransformBox::ViewBox
||
37 transformBox
== StyleTransformBox::BorderBox
) {
41 if (aFrame
.IsSVGContainerFrame()) {
42 nsRect boxRect
= nsLayoutUtils::ComputeSVGReferenceRect(
43 const_cast<nsIFrame
*>(&aFrame
), StyleGeometryBox::FillBox
);
44 return CSSPoint::FromAppUnits(boxRect
.TopLeft());
46 return CSSPoint::FromAppUnits(aFrame
.GetPosition());
49 // Convert the StyleCoordBox into the StyleGeometryBox in CSS layout.
50 // https://drafts.csswg.org/css-box-4/#keywords
51 static StyleGeometryBox
CoordBoxToGeometryBoxInCSSLayout(
52 StyleCoordBox aCoordBox
) {
54 case StyleCoordBox::ContentBox
:
55 return StyleGeometryBox::ContentBox
;
56 case StyleCoordBox::PaddingBox
:
57 return StyleGeometryBox::PaddingBox
;
58 case StyleCoordBox::BorderBox
:
59 return StyleGeometryBox::BorderBox
;
60 case StyleCoordBox::FillBox
:
61 return StyleGeometryBox::ContentBox
;
62 case StyleCoordBox::StrokeBox
:
63 case StyleCoordBox::ViewBox
:
64 return StyleGeometryBox::BorderBox
;
66 MOZ_ASSERT_UNREACHABLE("Unknown coord-box type");
67 return StyleGeometryBox::BorderBox
;
71 const nsIFrame
* MotionPathUtils::GetOffsetPathReferenceBox(
72 const nsIFrame
* aFrame
, nsRect
& aOutputRect
) {
73 const StyleOffsetPath
& offsetPath
= aFrame
->StyleDisplay()->mOffsetPath
;
74 if (offsetPath
.IsNone()) {
78 if (aFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
)) {
79 MOZ_ASSERT(aFrame
->GetContent()->IsSVGElement());
80 auto* viewportElement
=
81 dom::SVGElement::FromNode(aFrame
->GetContent())->GetCtx();
82 aOutputRect
= nsLayoutUtils::ComputeSVGOriginBox(viewportElement
);
83 return viewportElement
? viewportElement
->GetPrimaryFrame() : nullptr;
86 const nsIFrame
* containingBlock
= aFrame
->GetContainingBlock();
87 const StyleCoordBox coordBox
= offsetPath
.IsCoordBox()
88 ? offsetPath
.AsCoordBox()
89 : offsetPath
.AsOffsetPath().coord_box
;
90 aOutputRect
= nsLayoutUtils::ComputeHTMLReferenceRect(
91 containingBlock
, CoordBoxToGeometryBoxInCSSLayout(coordBox
));
92 return containingBlock
;
96 CSSCoord
MotionPathUtils::GetRayContainReferenceSize(nsIFrame
* aFrame
) {
97 // We use the border-box size to calculate the reduced path length when using
99 // https://drafts.fxtf.org/motion-1/#valdef-ray-contain
101 // Note: Per the spec, border-box is treated as stroke-box in the SVG context,
102 // https://drafts.csswg.org/css-box-4/#valdef-box-border-box
104 // To calculate stroke bounds for an element with `non-scaling-stroke` we
105 // need to resolve its transform to its outer-svg, but to resolve that
106 // transform when it has `transform-box:stroke-box` (or `border-box`)
107 // may require its stroke bounds. There's no ideal way to break this
108 // cyclical dependency, but we break it by using the FillBox.
109 // https://github.com/w3c/csswg-drafts/issues/9640
111 const auto size
= CSSSize::FromAppUnits(
112 (aFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
)
113 ? nsLayoutUtils::ComputeSVGReferenceRect(
114 aFrame
, aFrame
->StyleSVGReset()->HasNonScalingStroke()
115 ? StyleGeometryBox::FillBox
116 : StyleGeometryBox::StrokeBox
)
117 : nsLayoutUtils::ComputeHTMLReferenceRect(
118 aFrame
, StyleGeometryBox::BorderBox
))
120 return std::max(size
.width
, size
.height
);
124 nsTArray
<nscoord
> MotionPathUtils::ComputeBorderRadii(
125 const StyleBorderRadius
& aBorderRadius
, const nsRect
& aCoordBox
) {
126 const nsRect insetRect
= ShapeUtils::ComputeInsetRect(
127 StyleRect
<LengthPercentage
>::WithAllSides(LengthPercentage::Zero()),
129 nsTArray
<nscoord
> result(8);
131 if (!ShapeUtils::ComputeRectRadii(aBorderRadius
, aCoordBox
, insetRect
,
132 result
.Elements())) {
138 // The distance is measured between the origin and the intersection of the ray
139 // with the reference box of the containing block.
140 // Note: |aOrigin| and |aContaingBlock| should be in the same coordinate system
141 // (i.e. the nsIFrame::mRect of the containing block).
142 // https://drafts.fxtf.org/motion-1/#size-sides
143 static CSSCoord
ComputeSides(const CSSPoint
& aOrigin
,
144 const CSSRect
& aContainingBlock
,
145 const StyleAngle
& aAngle
) {
146 const CSSPoint
& topLeft
= aContainingBlock
.TopLeft();
147 // Given an acute angle |theta| (i.e. |t|) of a right-angled triangle, the
148 // hypotenuse |h| is the side that connects the two acute angles. The side
149 // |b| adjacent to |theta| is the side of the triangle that connects |theta|
150 // to the right angle.
152 // e.g. if the angle |t| is 0 ~ 90 degrees, and b * tan(theta) <= b',
155 // (topLeft) #--------*-----*--# (aContainingBlock.XMost(), topLeft.y)
161 // (aOrigin) *---b'---* (aContainingBlock.XMost(), aOrigin.y)
167 // #-----------------# (aContainingBlock.XMost(),
168 // (topLeft.x, aContainingBlock.YMost())
169 // aContainingBlock.YMost())
170 const double theta
= aAngle
.ToRadians();
171 double sint
= std::sin(theta
);
172 double cost
= std::cos(theta
);
174 const double b
= cost
>= 0 ? aOrigin
.y
.value
- topLeft
.y
175 : aContainingBlock
.YMost() - aOrigin
.y
.value
;
176 const double bPrime
= sint
>= 0 ? aContainingBlock
.XMost() - aOrigin
.x
.value
177 : aOrigin
.x
.value
- topLeft
.x
;
178 sint
= std::fabs(sint
);
179 cost
= std::fabs(cost
);
181 // The trigonometric formula here doesn't work well if |theta| is 0deg or
182 // 90deg, so we handle these edge cases first.
183 if (sint
< std::numeric_limits
<double>::epsilon()) {
184 // For 0deg (or 180deg), we use |b| directly.
185 return static_cast<float>(b
);
188 if (cost
< std::numeric_limits
<double>::epsilon()) {
189 // For 90deg (or 270deg), we use |bPrime| directly. This can also avoid 0/0
190 // if both |b| and |cost| are 0.0. (i.e. b / cost).
191 return static_cast<float>(bPrime
);
194 // Note: The following formula works well only when 0 < theta < 90deg. So we
195 // handle 0deg and 90deg above first.
197 // If |b * tan(theta)| is larger than |bPrime|, the intersection is
198 // on the other side, and |b'| is the opposite side of angle |theta| in this
201 // e.g. If b * tan(theta) > b', h = b' / sin(theta):
209 if (b
* sint
> bPrime
* cost
) {
210 return bPrime
/ sint
;
215 // Compute the position of "at <position>" together with offset starting
216 // position (i.e. offset-position).
217 static nsPoint
ComputePosition(const StylePositionOrAuto
& aAtPosition
,
218 const StyleOffsetPosition
& aOffsetPosition
,
219 const nsRect
& aCoordBox
,
220 const nsPoint
& aCurrentCoord
) {
221 if (aAtPosition
.IsPosition()) {
222 // Resolve this by using the <position> to position a 0x0 object area within
223 // the box’s containing block.
224 return ShapeUtils::ComputePosition(aAtPosition
.AsPosition(), aCoordBox
);
227 MOZ_ASSERT(aAtPosition
.IsAuto(), "\"at <position>\" should be omitted");
229 // Use the offset starting position of the element, given by offset-position.
230 // https://drafts.fxtf.org/motion-1/#valdef-ray-at-position
231 if (aOffsetPosition
.IsPosition()) {
232 return ShapeUtils::ComputePosition(aOffsetPosition
.AsPosition(), aCoordBox
);
235 if (aOffsetPosition
.IsNormal()) {
236 // If the element doesn’t have an offset starting position either, it
237 // behaves as at center.
238 const StylePosition
& center
= StylePosition::FromPercentage(0.5);
239 return ShapeUtils::ComputePosition(center
, aCoordBox
);
242 MOZ_ASSERT(aOffsetPosition
.IsAuto());
243 return aCurrentCoord
;
246 static CSSCoord
ComputeRayPathLength(const StyleRaySize aRaySizeType
,
247 const StyleAngle
& aAngle
,
248 const CSSPoint
& aOrigin
,
249 const CSSRect
& aContainingBlock
) {
250 if (aRaySizeType
== StyleRaySize::Sides
) {
251 // If the initial position is not within the box, the distance is 0.
253 // Note: If the origin is at XMost() (and/or YMost()), we should consider it
254 // to be inside containing block (because we expect 100% x (or y) coordinate
255 // is still to be considered inside the containing block.
256 if (!aContainingBlock
.ContainsInclusively(aOrigin
)) {
260 return ComputeSides(aOrigin
, aContainingBlock
, aAngle
);
263 // left: the length between the origin and the left side.
264 // right: the length between the origin and the right side.
265 // top: the length between the origin and the top side.
266 // bottom: the lenght between the origin and the bottom side.
267 const CSSPoint
& topLeft
= aContainingBlock
.TopLeft();
268 const CSSCoord left
= std::abs(aOrigin
.x
- topLeft
.x
);
269 const CSSCoord right
= std::abs(aContainingBlock
.XMost() - aOrigin
.x
);
270 const CSSCoord top
= std::abs(aOrigin
.y
- topLeft
.y
);
271 const CSSCoord bottom
= std::abs(aContainingBlock
.YMost() - aOrigin
.y
);
273 switch (aRaySizeType
) {
274 case StyleRaySize::ClosestSide
:
275 return std::min({left
, right
, top
, bottom
});
277 case StyleRaySize::FarthestSide
:
278 return std::max({left
, right
, top
, bottom
});
280 case StyleRaySize::ClosestCorner
:
281 case StyleRaySize::FarthestCorner
: {
284 if (aRaySizeType
== StyleRaySize::ClosestCorner
) {
285 h
= std::min(left
, right
);
286 v
= std::min(top
, bottom
);
288 h
= std::max(left
, right
);
289 v
= std::max(top
, bottom
);
291 return sqrt(h
.value
* h
.value
+ v
.value
* v
.value
);
293 case StyleRaySize::Sides
:
294 MOZ_ASSERT_UNREACHABLE("Unsupported ray size");
300 static CSSCoord
ComputeRayUsedDistance(
301 const StyleRayFunction
& aRay
, const LengthPercentage
& aDistance
,
302 const CSSCoord
& aPathLength
, const CSSCoord
& aRayContainReferenceLength
) {
303 CSSCoord usedDistance
= aDistance
.ResolveToCSSPixels(aPathLength
);
308 // The length of the offset path is reduced so that the element stays within
309 // the containing block even at offset-distance: 100%. Specifically, the
310 // path’s length is reduced by half the width or half the height of the
311 // element’s border box, whichever is larger, and floored at zero.
312 // https://drafts.fxtf.org/motion-1/#valdef-ray-contain
313 return std::max((usedDistance
- aRayContainReferenceLength
/ 2.0f
).value
,
318 Maybe
<ResolvedMotionPathData
> MotionPathUtils::ResolveMotionPath(
319 const OffsetPathData
& aPath
, const LengthPercentage
& aDistance
,
320 const StyleOffsetRotate
& aRotate
, const StylePositionOrAuto
& aAnchor
,
321 const StyleOffsetPosition
& aPosition
, const CSSPoint
& aTransformOrigin
,
322 TransformReferenceBox
& aRefBox
, const CSSPoint
& aAnchorPointAdjustment
) {
323 if (aPath
.IsNone()) {
327 // Compute the point and angle for creating the equivalent translate and
329 double directionAngle
= 0.0;
331 if (aPath
.IsShape()) {
332 const auto& data
= aPath
.AsShape();
333 RefPtr
<gfx::Path
> path
= data
.mGfxPath
;
334 MOZ_ASSERT(path
, "The empty path is not allowed");
336 // Per the spec, we have to convert offset distance to pixels, with 100%
337 // being converted to total length. So here |gfxPath| is built with CSS
338 // pixel, and we calculate |pathLength| and |computedDistance| with CSS
340 gfx::Float pathLength
= path
->ComputeLength();
341 gfx::Float usedDistance
=
342 aDistance
.ResolveToCSSPixels(CSSCoord(pathLength
));
343 if (data
.mIsClosedLoop
) {
344 // Per the spec, let used offset distance be equal to offset distance
345 // modulus the total length of the path. If the total length of the path
346 // is 0, used offset distance is also 0.
347 usedDistance
= pathLength
> 0.0 ? fmod(usedDistance
, pathLength
) : 0.0;
348 // We make sure |usedDistance| is 0.0 or a positive value.
349 if (usedDistance
< 0.0) {
350 usedDistance
+= pathLength
;
353 // Per the spec, for unclosed interval, let used offset distance be equal
354 // to offset distance clamped by 0 and the total length of the path.
355 usedDistance
= clamped(usedDistance
, 0.0f
, pathLength
);
358 point
= path
->ComputePointAtLength(usedDistance
, &tangent
);
359 // Basically, |point| should be a relative distance between the current
360 // position and the target position. The built |path| is in the coordinate
361 // system of its containing block. Therefore, we have to take the current
362 // position of this box into account to offset the translation so it's final
363 // position is not affected by other boxes in the same containing block.
364 point
-= NSPointToPoint(data
.mCurrentPosition
, AppUnitsPerCSSPixel());
365 directionAngle
= atan2((double)tangent
.y
, (double)tangent
.x
); // in Radian.
366 } else if (aPath
.IsRay()) {
367 const auto& ray
= aPath
.AsRay();
368 MOZ_ASSERT(ray
.mRay
);
370 // Compute the origin, where the ray’s line begins (the 0% position).
371 // https://drafts.fxtf.org/motion-1/#ray-origin
372 const CSSPoint origin
= CSSPoint::FromAppUnits(ComputePosition(
373 ray
.mRay
->position
, aPosition
, ray
.mCoordBox
, ray
.mCurrentPosition
));
374 const CSSCoord pathLength
=
375 ComputeRayPathLength(ray
.mRay
->size
, ray
.mRay
->angle
, origin
,
376 CSSRect::FromAppUnits(ray
.mCoordBox
));
377 const CSSCoord usedDistance
= ComputeRayUsedDistance(
378 *ray
.mRay
, aDistance
, pathLength
, ray
.mContainReferenceLength
);
380 // 0deg pointing up and positive angles representing clockwise rotation.
382 StyleAngle
{ray
.mRay
->angle
.ToDegrees() - 90.0f
}.ToRadians();
384 // The vector from the current position of this box to the origin of this
385 // polar coordinate system.
386 const gfx::Point vectorToOrigin
=
387 (origin
- CSSPoint::FromAppUnits(ray
.mCurrentPosition
))
389 // |vectorToOrigin| + The vector from the origin to this polar coordinate,
390 // (|usedDistance|, |directionAngle|), i.e. the vector from the current
391 // position to this polar coordinate.
394 gfx::Point(usedDistance
* static_cast<gfx::Float
>(cos(directionAngle
)),
395 usedDistance
* static_cast<gfx::Float
>(sin(directionAngle
)));
397 MOZ_ASSERT_UNREACHABLE("Unsupported offset-path value");
401 // If |rotate.auto_| is true, the element should be rotated by the angle of
402 // the direction (i.e. directional tangent vector) of the offset-path, and the
403 // computed value of <angle> is added to this.
404 // Otherwise, the element has a constant clockwise rotation transformation
405 // applied to it by the specified rotation angle. (i.e. Don't need to
406 // consider the direction of the path.)
407 gfx::Float angle
= static_cast<gfx::Float
>(
408 (aRotate
.auto_
? directionAngle
: 0.0) + aRotate
.angle
.ToRadians());
410 // Compute the offset for motion path translate.
411 // Bug 1559232: the translate parameters will be adjusted more after we
412 // support offset-position.
413 // Per the spec, the default offset-anchor is `auto`, so initialize the anchor
414 // point to transform-origin.
415 CSSPoint
anchorPoint(aTransformOrigin
);
417 if (!aAnchor
.IsAuto()) {
418 const auto& pos
= aAnchor
.AsPosition();
419 anchorPoint
= nsStyleTransformMatrix::Convert2DPosition(
420 pos
.horizontal
, pos
.vertical
, aRefBox
);
421 // We need this value to shift the origin from transform-origin to
422 // offset-anchor (and vice versa).
423 // See nsStyleTransformMatrix::ReadTransform for more details.
424 shift
= (anchorPoint
- aTransformOrigin
).ToUnknownPoint();
427 anchorPoint
+= aAnchorPointAdjustment
;
429 return Some(ResolvedMotionPathData
{point
- anchorPoint
.ToUnknownPoint(),
433 static inline bool IsClosedLoop(const StyleSVGPathData
& aPathData
) {
434 return !aPathData
._0
.AsSpan().empty() &&
435 aPathData
._0
.AsSpan().rbegin()->IsClosePath();
438 // Create a path for "inset(0 round X)", where X is the value of border-radius
439 // on the element that establishes the containing block for this element.
440 static already_AddRefed
<gfx::Path
> BuildSimpleInsetPath(
441 const StyleBorderRadius
& aBorderRadius
, const nsRect
& aCoordBox
,
442 gfx::PathBuilder
* aPathBuilder
) {
447 const nsRect insetRect
= ShapeUtils::ComputeInsetRect(
448 StyleRect
<LengthPercentage
>::WithAllSides(LengthPercentage::Zero()),
451 const bool hasRadii
=
452 ShapeUtils::ComputeRectRadii(aBorderRadius
, aCoordBox
, insetRect
, radii
);
453 return ShapeUtils::BuildRectPath(insetRect
, hasRadii
? radii
: nullptr,
454 aCoordBox
, AppUnitsPerCSSPixel(),
458 // Create a path for `path("m 0 0")`, which is the default URL path if we cannot
459 // resolve a SVG shape element.
460 // https://drafts.fxtf.org/motion-1/#valdef-offset-path-url
461 static already_AddRefed
<gfx::Path
> BuildDefaultPathForURL(
462 gfx::PathBuilder
* aBuilder
) {
467 Array
<const StylePathCommand
, 1> array(StylePathCommand::MoveTo(
468 StyleCoordPair(gfx::Point
{0.0, 0.0}), StyleIsAbsolute::No
));
469 return SVGPathData::BuildPath(array
, aBuilder
, StyleStrokeLinecap::Butt
, 0.0);
472 // Generate data for motion path on the main thread.
473 static OffsetPathData
GenerateOffsetPathData(const nsIFrame
* aFrame
) {
474 const StyleOffsetPath
& offsetPath
= aFrame
->StyleDisplay()->mOffsetPath
;
475 if (offsetPath
.IsNone()) {
476 return OffsetPathData::None();
480 if (offsetPath
.IsRay()) {
482 const nsIFrame
* containingBlockFrame
=
483 MotionPathUtils::GetOffsetPathReferenceBox(aFrame
, coordBox
);
484 return !containingBlockFrame
485 ? OffsetPathData::None()
486 : OffsetPathData::Ray(
487 offsetPath
.AsRay(), std::move(coordBox
),
488 aFrame
->GetOffsetTo(containingBlockFrame
),
489 MotionPathUtils::GetRayContainReferenceSize(
490 const_cast<nsIFrame
*>(aFrame
)));
493 // Handle path(). We cache it so we handle it separately.
494 // FIXME: Bug 1837042, cache gfx::Path for shapes other than path(). Once we
495 // cache all basic shapes, we can merge this branch into other basic shapes.
496 if (offsetPath
.IsPath()) {
497 const StyleSVGPathData
& pathData
= offsetPath
.AsSVGPathData();
498 RefPtr
<gfx::Path
> gfxPath
=
499 aFrame
->GetProperty(nsIFrame::OffsetPathCache());
500 MOZ_ASSERT(gfxPath
|| pathData
._0
.IsEmpty(),
501 "Should have a valid cached gfx::Path or an empty path string");
502 // FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
503 // to give it the current box position.
504 return OffsetPathData::Shape(gfxPath
.forget(), {}, IsClosedLoop(pathData
));
508 const nsIFrame
* containingFrame
=
509 MotionPathUtils::GetOffsetPathReferenceBox(aFrame
, coordBox
);
510 if (!containingFrame
|| coordBox
.IsEmpty()) {
511 return OffsetPathData::None();
513 nsPoint currentPosition
= aFrame
->GetOffsetTo(containingFrame
);
514 RefPtr
<gfx::PathBuilder
> builder
= MotionPathUtils::GetPathBuilder();
516 if (offsetPath
.IsUrl()) {
517 dom::SVGGeometryElement
* element
=
518 SVGObserverUtils::GetAndObserveGeometry(const_cast<nsIFrame
*>(aFrame
));
520 // Note: This behaves as path("m 0 0") (a <basic-shape>).
521 RefPtr
<gfx::Path
> path
= BuildDefaultPathForURL(builder
);
522 // FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
523 // to give it the current box position.
524 return path
? OffsetPathData::Shape(path
.forget(), {}, false)
525 : OffsetPathData::None();
528 // We just need this path to calculate the specific point and direction
529 // angle, so use measuring function and get the benefit of caching the path
530 // in the SVG shape element.
531 RefPtr
<gfx::Path
> path
= element
->GetOrBuildPathForMeasuring();
533 // The built |path| from SVG shape element doesn't take |coordBox| into
534 // account. It uses the SVG viewport as its coordinate system. So after
535 // mapping it into the CSS layout, we should use |coordBox| as its viewport
536 // and user coordinate system. |currentPosition| is based on the border-box
537 // of the containing block. Therefore, we have to apply an extra translation
538 // to put it at the correct position based on |coordBox|.
540 // Note: we reuse |OffsetPathData::ShapeData::mCurrentPosition| to include
541 // this extra translation, so we don't have to add an extra field.
542 nsPoint positionInCoordBox
= currentPosition
- coordBox
.TopLeft();
543 return path
? OffsetPathData::Shape(path
.forget(),
544 std::move(positionInCoordBox
),
545 element
->IsClosedLoop())
546 : OffsetPathData::None();
549 // The rest part is to handle "<basic-shape> || <coord-box>".
550 MOZ_ASSERT(offsetPath
.IsBasicShapeOrCoordBox());
552 const nsStyleDisplay
* disp
= aFrame
->StyleDisplay();
553 RefPtr
<gfx::Path
> path
=
554 disp
->mOffsetPath
.IsCoordBox()
555 ? BuildSimpleInsetPath(containingFrame
->StyleBorder()->mBorderRadius
,
557 : MotionPathUtils::BuildPath(
558 disp
->mOffsetPath
.AsOffsetPath().path
->AsShape(),
559 disp
->mOffsetPosition
, coordBox
, currentPosition
, builder
);
560 return path
? OffsetPathData::Shape(path
.forget(), std::move(currentPosition
),
562 : OffsetPathData::None();
566 Maybe
<ResolvedMotionPathData
> MotionPathUtils::ResolveMotionPath(
567 const nsIFrame
* aFrame
, TransformReferenceBox
& aRefBox
) {
570 const nsStyleDisplay
* display
= aFrame
->StyleDisplay();
572 // FIXME: It's possible to refactor the calculation of transform-origin, so we
573 // could calculate from the caller, and reuse the value in nsDisplayList.cpp.
574 CSSPoint transformOrigin
= nsStyleTransformMatrix::Convert2DPosition(
575 display
->mTransformOrigin
.horizontal
, display
->mTransformOrigin
.vertical
,
578 return ResolveMotionPath(
579 GenerateOffsetPathData(aFrame
), display
->mOffsetDistance
,
580 display
->mOffsetRotate
, display
->mOffsetAnchor
, display
->mOffsetPosition
,
581 transformOrigin
, aRefBox
, ComputeAnchorPointAdjustment(*aFrame
));
584 // Generate data for motion path on the compositor thread.
585 static OffsetPathData
GenerateOffsetPathData(
586 const StyleOffsetPath
& aOffsetPath
,
587 const StyleOffsetPosition
& aOffsetPosition
,
588 const layers::MotionPathData
& aMotionPathData
,
589 gfx::Path
* aCachedMotionPath
) {
590 if (aOffsetPath
.IsNone()) {
591 return OffsetPathData::None();
595 if (aOffsetPath
.IsRay()) {
596 return aMotionPathData
.coordBox().IsEmpty()
597 ? OffsetPathData::None()
598 : OffsetPathData::Ray(
599 aOffsetPath
.AsRay(), aMotionPathData
.coordBox(),
600 aMotionPathData
.currentPosition(),
601 aMotionPathData
.rayContainReferenceLength());
605 // FIXME: Bug 1837042, cache gfx::Path for shapes other than path().
606 if (aOffsetPath
.IsPath()) {
607 const StyleSVGPathData
& pathData
= aOffsetPath
.AsSVGPathData();
608 // If aCachedMotionPath is valid, we have a fixed path.
609 // This means we have pre-built it already and no need to update.
610 RefPtr
<gfx::Path
> path
= aCachedMotionPath
;
612 RefPtr
<gfx::PathBuilder
> builder
=
613 MotionPathUtils::GetCompositorPathBuilder();
614 path
= MotionPathUtils::BuildSVGPath(pathData
, builder
);
616 // FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
617 // to give it the current box position.
618 return OffsetPathData::Shape(path
.forget(), {}, IsClosedLoop(pathData
));
621 // The rest part is to handle "<basic-shape> || <coord-box>".
622 MOZ_ASSERT(aOffsetPath
.IsBasicShapeOrCoordBox());
624 const nsRect
& coordBox
= aMotionPathData
.coordBox();
625 if (coordBox
.IsEmpty()) {
626 return OffsetPathData::None();
629 RefPtr
<gfx::PathBuilder
> builder
=
630 MotionPathUtils::GetCompositorPathBuilder();
632 return OffsetPathData::None();
635 RefPtr
<gfx::Path
> path
;
636 if (aOffsetPath
.IsCoordBox()) {
637 const nsRect insetRect
= ShapeUtils::ComputeInsetRect(
638 StyleRect
<LengthPercentage
>::WithAllSides(LengthPercentage::Zero()),
640 const nsTArray
<nscoord
>& radii
= aMotionPathData
.coordBoxInsetRadii();
641 path
= ShapeUtils::BuildRectPath(
642 insetRect
, radii
.IsEmpty() ? nullptr : radii
.Elements(), coordBox
,
643 AppUnitsPerCSSPixel(), builder
);
645 path
= MotionPathUtils::BuildPath(
646 aOffsetPath
.AsOffsetPath().path
->AsShape(), aOffsetPosition
, coordBox
,
647 aMotionPathData
.currentPosition(), builder
);
650 return path
? OffsetPathData::Shape(
651 path
.forget(), nsPoint(aMotionPathData
.currentPosition()),
653 : OffsetPathData::None();
657 Maybe
<ResolvedMotionPathData
> MotionPathUtils::ResolveMotionPath(
658 const StyleOffsetPath
* aPath
, const StyleLengthPercentage
* aDistance
,
659 const StyleOffsetRotate
* aRotate
, const StylePositionOrAuto
* aAnchor
,
660 const StyleOffsetPosition
* aPosition
,
661 const Maybe
<layers::MotionPathData
>& aMotionPathData
,
662 TransformReferenceBox
& aRefBox
, gfx::Path
* aCachedMotionPath
) {
667 MOZ_ASSERT(aMotionPathData
);
669 auto zeroOffsetDistance
= LengthPercentage::Zero();
670 auto autoOffsetRotate
= StyleOffsetRotate
{true, StyleAngle::Zero()};
671 auto autoOffsetAnchor
= StylePositionOrAuto::Auto();
672 auto autoOffsetPosition
= StyleOffsetPosition::Auto();
673 return ResolveMotionPath(
674 GenerateOffsetPathData(*aPath
,
675 aPosition
? *aPosition
: autoOffsetPosition
,
676 *aMotionPathData
, aCachedMotionPath
),
677 aDistance
? *aDistance
: zeroOffsetDistance
,
678 aRotate
? *aRotate
: autoOffsetRotate
,
679 aAnchor
? *aAnchor
: autoOffsetAnchor
,
680 aPosition
? *aPosition
: autoOffsetPosition
, aMotionPathData
->origin(),
681 aRefBox
, aMotionPathData
->anchorAdjustment());
685 already_AddRefed
<gfx::Path
> MotionPathUtils::BuildSVGPath(
686 const StyleSVGPathData
& aPath
, gfx::PathBuilder
* aPathBuilder
) {
691 const Span
<const StylePathCommand
>& path
= aPath
._0
.AsSpan();
692 return SVGPathData::BuildPath(path
, aPathBuilder
, StyleStrokeLinecap::Butt
,
697 already_AddRefed
<gfx::Path
> MotionPathUtils::BuildPath(
698 const StyleBasicShape
& aBasicShape
,
699 const StyleOffsetPosition
& aOffsetPosition
, const nsRect
& aCoordBox
,
700 const nsPoint
& aCurrentPosition
, gfx::PathBuilder
* aPathBuilder
) {
705 switch (aBasicShape
.tag
) {
706 case StyleBasicShape::Tag::Circle
: {
707 const nsPoint center
=
708 ComputePosition(aBasicShape
.AsCircle().position
, aOffsetPosition
,
709 aCoordBox
, aCurrentPosition
);
710 return ShapeUtils::BuildCirclePath(aBasicShape
, aCoordBox
, center
,
711 AppUnitsPerCSSPixel(), aPathBuilder
);
713 case StyleBasicShape::Tag::Ellipse
: {
714 const nsPoint center
=
715 ComputePosition(aBasicShape
.AsEllipse().position
, aOffsetPosition
,
716 aCoordBox
, aCurrentPosition
);
717 return ShapeUtils::BuildEllipsePath(aBasicShape
, aCoordBox
, center
,
718 AppUnitsPerCSSPixel(), aPathBuilder
);
720 case StyleBasicShape::Tag::Rect
:
721 return ShapeUtils::BuildInsetPath(aBasicShape
, aCoordBox
,
722 AppUnitsPerCSSPixel(), aPathBuilder
);
723 case StyleBasicShape::Tag::Polygon
:
724 return ShapeUtils::BuildPolygonPath(aBasicShape
, aCoordBox
,
725 AppUnitsPerCSSPixel(), aPathBuilder
);
726 case StyleBasicShape::Tag::Path
:
727 // FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
728 // to also check its containing block as well. For now, we are still
729 // building its gfx::Path directly by its SVGPathData without other
730 // reference. https://github.com/w3c/fxtf-drafts/issues/504
731 return BuildSVGPath(aBasicShape
.AsPath().path
, aPathBuilder
);
738 already_AddRefed
<gfx::PathBuilder
> MotionPathUtils::GetPathBuilder() {
739 // Here we only need to build a valid path for motion path, so
740 // using the default values of stroke-width, stoke-linecap, and fill-rule
741 // is fine for now because what we want is to get the point and its normal
742 // vector along the path, instead of rendering it.
743 RefPtr
<gfx::PathBuilder
> builder
=
744 gfxPlatform::GetPlatform()
745 ->ScreenReferenceDrawTarget()
746 ->CreatePathBuilder(gfx::FillRule::FILL_WINDING
);
747 return builder
.forget();
751 already_AddRefed
<gfx::PathBuilder
> MotionPathUtils::GetCompositorPathBuilder() {
752 // FIXME: Perhaps we need a PathBuilder which is independent on the backend.
753 RefPtr
<gfx::PathBuilder
> builder
=
754 gfxPlatform::Initialized()
755 ? gfxPlatform::GetPlatform()
756 ->ScreenReferenceDrawTarget()
757 ->CreatePathBuilder(gfx::FillRule::FILL_WINDING
)
758 : gfx::Factory::CreateSimplePathBuilder();
759 return builder
.forget();
762 } // namespace mozilla