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(
115 aFrame
->StyleSVGReset()->HasNonScalingStroke()
116 ? StyleGeometryBox::FillBox
117 : StyleGeometryBox::StrokeBox
,
118 nsLayoutUtils::MayHaveNonScalingStrokeCyclicDependency::Yes
)
119 : nsLayoutUtils::ComputeHTMLReferenceRect(
120 aFrame
, StyleGeometryBox::BorderBox
))
122 return std::max(size
.width
, size
.height
);
126 nsTArray
<nscoord
> MotionPathUtils::ComputeBorderRadii(
127 const StyleBorderRadius
& aBorderRadius
, const nsRect
& aCoordBox
) {
128 const nsRect insetRect
= ShapeUtils::ComputeInsetRect(
129 StyleRect
<LengthPercentage
>::WithAllSides(LengthPercentage::Zero()),
131 nsTArray
<nscoord
> result(8);
133 if (!ShapeUtils::ComputeRectRadii(aBorderRadius
, aCoordBox
, insetRect
,
134 result
.Elements())) {
140 // The distance is measured between the origin and the intersection of the ray
141 // with the reference box of the containing block.
142 // Note: |aOrigin| and |aContaingBlock| should be in the same coordinate system
143 // (i.e. the nsIFrame::mRect of the containing block).
144 // https://drafts.fxtf.org/motion-1/#size-sides
145 static CSSCoord
ComputeSides(const CSSPoint
& aOrigin
,
146 const CSSRect
& aContainingBlock
,
147 const StyleAngle
& aAngle
) {
148 const CSSPoint
& topLeft
= aContainingBlock
.TopLeft();
149 // Given an acute angle |theta| (i.e. |t|) of a right-angled triangle, the
150 // hypotenuse |h| is the side that connects the two acute angles. The side
151 // |b| adjacent to |theta| is the side of the triangle that connects |theta|
152 // to the right angle.
154 // e.g. if the angle |t| is 0 ~ 90 degrees, and b * tan(theta) <= b',
157 // (topLeft) #--------*-----*--# (aContainingBlock.XMost(), topLeft.y)
163 // (aOrigin) *---b'---* (aContainingBlock.XMost(), aOrigin.y)
169 // #-----------------# (aContainingBlock.XMost(),
170 // (topLeft.x, aContainingBlock.YMost())
171 // aContainingBlock.YMost())
172 const double theta
= aAngle
.ToRadians();
173 double sint
= std::sin(theta
);
174 double cost
= std::cos(theta
);
176 const double b
= cost
>= 0 ? aOrigin
.y
.value
- topLeft
.y
177 : aContainingBlock
.YMost() - aOrigin
.y
.value
;
178 const double bPrime
= sint
>= 0 ? aContainingBlock
.XMost() - aOrigin
.x
.value
179 : aOrigin
.x
.value
- topLeft
.x
;
180 sint
= std::fabs(sint
);
181 cost
= std::fabs(cost
);
183 // The trigonometric formula here doesn't work well if |theta| is 0deg or
184 // 90deg, so we handle these edge cases first.
185 if (sint
< std::numeric_limits
<double>::epsilon()) {
186 // For 0deg (or 180deg), we use |b| directly.
187 return static_cast<float>(b
);
190 if (cost
< std::numeric_limits
<double>::epsilon()) {
191 // For 90deg (or 270deg), we use |bPrime| directly. This can also avoid 0/0
192 // if both |b| and |cost| are 0.0. (i.e. b / cost).
193 return static_cast<float>(bPrime
);
196 // Note: The following formula works well only when 0 < theta < 90deg. So we
197 // handle 0deg and 90deg above first.
199 // If |b * tan(theta)| is larger than |bPrime|, the intersection is
200 // on the other side, and |b'| is the opposite side of angle |theta| in this
203 // e.g. If b * tan(theta) > b', h = b' / sin(theta):
211 if (b
* sint
> bPrime
* cost
) {
212 return bPrime
/ sint
;
217 // Compute the position of "at <position>" together with offset starting
218 // position (i.e. offset-position).
219 static nsPoint
ComputePosition(const StylePositionOrAuto
& aAtPosition
,
220 const StyleOffsetPosition
& aOffsetPosition
,
221 const nsRect
& aCoordBox
,
222 const nsPoint
& aCurrentCoord
) {
223 if (aAtPosition
.IsPosition()) {
224 // Resolve this by using the <position> to position a 0x0 object area within
225 // the box’s containing block.
226 return ShapeUtils::ComputePosition(aAtPosition
.AsPosition(), aCoordBox
);
229 MOZ_ASSERT(aAtPosition
.IsAuto(), "\"at <position>\" should be omitted");
231 // Use the offset starting position of the element, given by offset-position.
232 // https://drafts.fxtf.org/motion-1/#valdef-ray-at-position
233 if (aOffsetPosition
.IsPosition()) {
234 return ShapeUtils::ComputePosition(aOffsetPosition
.AsPosition(), aCoordBox
);
237 if (aOffsetPosition
.IsNormal()) {
238 // If the element doesn’t have an offset starting position either, it
239 // behaves as at center.
240 const StylePosition
& center
= StylePosition::FromPercentage(0.5);
241 return ShapeUtils::ComputePosition(center
, aCoordBox
);
244 MOZ_ASSERT(aOffsetPosition
.IsAuto());
245 return aCurrentCoord
;
248 static CSSCoord
ComputeRayPathLength(const StyleRaySize aRaySizeType
,
249 const StyleAngle
& aAngle
,
250 const CSSPoint
& aOrigin
,
251 const CSSRect
& aContainingBlock
) {
252 if (aRaySizeType
== StyleRaySize::Sides
) {
253 // If the initial position is not within the box, the distance is 0.
255 // Note: If the origin is at XMost() (and/or YMost()), we should consider it
256 // to be inside containing block (because we expect 100% x (or y) coordinate
257 // is still to be considered inside the containing block.
258 if (!aContainingBlock
.ContainsInclusively(aOrigin
)) {
262 return ComputeSides(aOrigin
, aContainingBlock
, aAngle
);
265 // left: the length between the origin and the left side.
266 // right: the length between the origin and the right side.
267 // top: the length between the origin and the top side.
268 // bottom: the lenght between the origin and the bottom side.
269 const CSSPoint
& topLeft
= aContainingBlock
.TopLeft();
270 const CSSCoord left
= std::abs(aOrigin
.x
- topLeft
.x
);
271 const CSSCoord right
= std::abs(aContainingBlock
.XMost() - aOrigin
.x
);
272 const CSSCoord top
= std::abs(aOrigin
.y
- topLeft
.y
);
273 const CSSCoord bottom
= std::abs(aContainingBlock
.YMost() - aOrigin
.y
);
275 switch (aRaySizeType
) {
276 case StyleRaySize::ClosestSide
:
277 return std::min({left
, right
, top
, bottom
});
279 case StyleRaySize::FarthestSide
:
280 return std::max({left
, right
, top
, bottom
});
282 case StyleRaySize::ClosestCorner
:
283 case StyleRaySize::FarthestCorner
: {
286 if (aRaySizeType
== StyleRaySize::ClosestCorner
) {
287 h
= std::min(left
, right
);
288 v
= std::min(top
, bottom
);
290 h
= std::max(left
, right
);
291 v
= std::max(top
, bottom
);
293 return sqrt(h
.value
* h
.value
+ v
.value
* v
.value
);
295 case StyleRaySize::Sides
:
296 MOZ_ASSERT_UNREACHABLE("Unsupported ray size");
302 static CSSCoord
ComputeRayUsedDistance(
303 const StyleRayFunction
& aRay
, const LengthPercentage
& aDistance
,
304 const CSSCoord
& aPathLength
, const CSSCoord
& aRayContainReferenceLength
) {
305 CSSCoord usedDistance
= aDistance
.ResolveToCSSPixels(aPathLength
);
310 // The length of the offset path is reduced so that the element stays within
311 // the containing block even at offset-distance: 100%. Specifically, the
312 // path’s length is reduced by half the width or half the height of the
313 // element’s border box, whichever is larger, and floored at zero.
314 // https://drafts.fxtf.org/motion-1/#valdef-ray-contain
315 return std::max((usedDistance
- aRayContainReferenceLength
/ 2.0f
).value
,
320 Maybe
<ResolvedMotionPathData
> MotionPathUtils::ResolveMotionPath(
321 const OffsetPathData
& aPath
, const LengthPercentage
& aDistance
,
322 const StyleOffsetRotate
& aRotate
, const StylePositionOrAuto
& aAnchor
,
323 const StyleOffsetPosition
& aPosition
, const CSSPoint
& aTransformOrigin
,
324 TransformReferenceBox
& aRefBox
, const CSSPoint
& aAnchorPointAdjustment
) {
325 if (aPath
.IsNone()) {
329 // Compute the point and angle for creating the equivalent translate and
331 double directionAngle
= 0.0;
333 if (aPath
.IsShape()) {
334 const auto& data
= aPath
.AsShape();
335 RefPtr
<gfx::Path
> path
= data
.mGfxPath
;
336 MOZ_ASSERT(path
, "The empty path is not allowed");
338 // Per the spec, we have to convert offset distance to pixels, with 100%
339 // being converted to total length. So here |gfxPath| is built with CSS
340 // pixel, and we calculate |pathLength| and |computedDistance| with CSS
342 gfx::Float pathLength
= path
->ComputeLength();
343 gfx::Float usedDistance
=
344 aDistance
.ResolveToCSSPixels(CSSCoord(pathLength
));
345 if (data
.mIsClosedLoop
) {
346 // Per the spec, let used offset distance be equal to offset distance
347 // modulus the total length of the path. If the total length of the path
348 // is 0, used offset distance is also 0.
349 usedDistance
= pathLength
> 0.0 ? fmod(usedDistance
, pathLength
) : 0.0;
350 // We make sure |usedDistance| is 0.0 or a positive value.
351 if (usedDistance
< 0.0) {
352 usedDistance
+= pathLength
;
355 // Per the spec, for unclosed interval, let used offset distance be equal
356 // to offset distance clamped by 0 and the total length of the path.
357 usedDistance
= clamped(usedDistance
, 0.0f
, pathLength
);
360 point
= path
->ComputePointAtLength(usedDistance
, &tangent
);
361 // Basically, |point| should be a relative distance between the current
362 // position and the target position. The built |path| is in the coordinate
363 // system of its containing block. Therefore, we have to take the current
364 // position of this box into account to offset the translation so it's final
365 // position is not affected by other boxes in the same containing block.
366 point
-= NSPointToPoint(data
.mCurrentPosition
, AppUnitsPerCSSPixel());
367 directionAngle
= atan2((double)tangent
.y
, (double)tangent
.x
); // in Radian.
368 } else if (aPath
.IsRay()) {
369 const auto& ray
= aPath
.AsRay();
370 MOZ_ASSERT(ray
.mRay
);
372 // Compute the origin, where the ray’s line begins (the 0% position).
373 // https://drafts.fxtf.org/motion-1/#ray-origin
374 const CSSPoint origin
= CSSPoint::FromAppUnits(ComputePosition(
375 ray
.mRay
->position
, aPosition
, ray
.mCoordBox
, ray
.mCurrentPosition
));
376 const CSSCoord pathLength
=
377 ComputeRayPathLength(ray
.mRay
->size
, ray
.mRay
->angle
, origin
,
378 CSSRect::FromAppUnits(ray
.mCoordBox
));
379 const CSSCoord usedDistance
= ComputeRayUsedDistance(
380 *ray
.mRay
, aDistance
, pathLength
, ray
.mContainReferenceLength
);
382 // 0deg pointing up and positive angles representing clockwise rotation.
384 StyleAngle
{ray
.mRay
->angle
.ToDegrees() - 90.0f
}.ToRadians();
386 // The vector from the current position of this box to the origin of this
387 // polar coordinate system.
388 const gfx::Point vectorToOrigin
=
389 (origin
- CSSPoint::FromAppUnits(ray
.mCurrentPosition
))
391 // |vectorToOrigin| + The vector from the origin to this polar coordinate,
392 // (|usedDistance|, |directionAngle|), i.e. the vector from the current
393 // position to this polar coordinate.
396 gfx::Point(usedDistance
* static_cast<gfx::Float
>(cos(directionAngle
)),
397 usedDistance
* static_cast<gfx::Float
>(sin(directionAngle
)));
399 MOZ_ASSERT_UNREACHABLE("Unsupported offset-path value");
403 // If |rotate.auto_| is true, the element should be rotated by the angle of
404 // the direction (i.e. directional tangent vector) of the offset-path, and the
405 // computed value of <angle> is added to this.
406 // Otherwise, the element has a constant clockwise rotation transformation
407 // applied to it by the specified rotation angle. (i.e. Don't need to
408 // consider the direction of the path.)
409 gfx::Float angle
= static_cast<gfx::Float
>(
410 (aRotate
.auto_
? directionAngle
: 0.0) + aRotate
.angle
.ToRadians());
412 // Compute the offset for motion path translate.
413 // Bug 1559232: the translate parameters will be adjusted more after we
414 // support offset-position.
415 // Per the spec, the default offset-anchor is `auto`, so initialize the anchor
416 // point to transform-origin.
417 CSSPoint
anchorPoint(aTransformOrigin
);
419 if (!aAnchor
.IsAuto()) {
420 const auto& pos
= aAnchor
.AsPosition();
421 anchorPoint
= nsStyleTransformMatrix::Convert2DPosition(
422 pos
.horizontal
, pos
.vertical
, aRefBox
);
423 // We need this value to shift the origin from transform-origin to
424 // offset-anchor (and vice versa).
425 // See nsStyleTransformMatrix::ReadTransform for more details.
426 shift
= (anchorPoint
- aTransformOrigin
).ToUnknownPoint();
429 anchorPoint
+= aAnchorPointAdjustment
;
431 return Some(ResolvedMotionPathData
{point
- anchorPoint
.ToUnknownPoint(),
435 static inline bool IsClosedLoop(const StyleSVGPathData
& aPathData
) {
436 return !aPathData
._0
.AsSpan().empty() &&
437 aPathData
._0
.AsSpan().rbegin()->IsClose();
440 // Create a path for "inset(0 round X)", where X is the value of border-radius
441 // on the element that establishes the containing block for this element.
442 static already_AddRefed
<gfx::Path
> BuildSimpleInsetPath(
443 const StyleBorderRadius
& aBorderRadius
, const nsRect
& aCoordBox
,
444 gfx::PathBuilder
* aPathBuilder
) {
449 const nsRect insetRect
= ShapeUtils::ComputeInsetRect(
450 StyleRect
<LengthPercentage
>::WithAllSides(LengthPercentage::Zero()),
453 const bool hasRadii
=
454 ShapeUtils::ComputeRectRadii(aBorderRadius
, aCoordBox
, insetRect
, radii
);
455 return ShapeUtils::BuildRectPath(insetRect
, hasRadii
? radii
: nullptr,
456 aCoordBox
, AppUnitsPerCSSPixel(),
460 // Create a path for `path("m 0 0")`, which is the default URL path if we cannot
461 // resolve a SVG shape element.
462 // https://drafts.fxtf.org/motion-1/#valdef-offset-path-url
463 static already_AddRefed
<gfx::Path
> BuildDefaultPathForURL(
464 gfx::PathBuilder
* aBuilder
) {
469 Array
<const StylePathCommand
, 1> array(StylePathCommand::Move(
470 StyleByTo::By
, StyleCoordinatePair
<StyleCSSFloat
>{0.0, 0.0}));
471 return SVGPathData::BuildPath(array
, aBuilder
, StyleStrokeLinecap::Butt
, 0.0);
474 // Generate data for motion path on the main thread.
475 static OffsetPathData
GenerateOffsetPathData(const nsIFrame
* aFrame
) {
476 const StyleOffsetPath
& offsetPath
= aFrame
->StyleDisplay()->mOffsetPath
;
477 if (offsetPath
.IsNone()) {
478 return OffsetPathData::None();
482 if (offsetPath
.IsRay()) {
484 const nsIFrame
* containingBlockFrame
=
485 MotionPathUtils::GetOffsetPathReferenceBox(aFrame
, coordBox
);
486 return !containingBlockFrame
487 ? OffsetPathData::None()
488 : OffsetPathData::Ray(
489 offsetPath
.AsRay(), std::move(coordBox
),
490 aFrame
->GetOffsetTo(containingBlockFrame
),
491 MotionPathUtils::GetRayContainReferenceSize(
492 const_cast<nsIFrame
*>(aFrame
)));
495 // Handle path(). We cache it so we handle it separately.
496 // FIXME: Bug 1837042, cache gfx::Path for shapes other than path(). Once we
497 // cache all basic shapes, we can merge this branch into other basic shapes.
498 if (offsetPath
.IsPath()) {
499 const StyleSVGPathData
& pathData
= offsetPath
.AsSVGPathData();
500 RefPtr
<gfx::Path
> gfxPath
=
501 aFrame
->GetProperty(nsIFrame::OffsetPathCache());
502 MOZ_ASSERT(gfxPath
|| pathData
._0
.IsEmpty(),
503 "Should have a valid cached gfx::Path or an empty path string");
504 // FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
505 // to give it the current box position.
506 return OffsetPathData::Shape(gfxPath
.forget(), {}, IsClosedLoop(pathData
));
510 const nsIFrame
* containingFrame
=
511 MotionPathUtils::GetOffsetPathReferenceBox(aFrame
, coordBox
);
512 if (!containingFrame
|| coordBox
.IsEmpty()) {
513 return OffsetPathData::None();
515 nsPoint currentPosition
= aFrame
->GetOffsetTo(containingFrame
);
516 RefPtr
<gfx::PathBuilder
> builder
= MotionPathUtils::GetPathBuilder();
518 if (offsetPath
.IsUrl()) {
519 dom::SVGGeometryElement
* element
=
520 SVGObserverUtils::GetAndObserveGeometry(const_cast<nsIFrame
*>(aFrame
));
522 // Note: This behaves as path("m 0 0") (a <basic-shape>).
523 RefPtr
<gfx::Path
> path
= BuildDefaultPathForURL(builder
);
524 // FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
525 // to give it the current box position.
526 return path
? OffsetPathData::Shape(path
.forget(), {}, false)
527 : OffsetPathData::None();
530 // We just need this path to calculate the specific point and direction
531 // angle, so use measuring function and get the benefit of caching the path
532 // in the SVG shape element.
533 RefPtr
<gfx::Path
> path
= element
->GetOrBuildPathForMeasuring();
535 // The built |path| from SVG shape element doesn't take |coordBox| into
536 // account. It uses the SVG viewport as its coordinate system. So after
537 // mapping it into the CSS layout, we should use |coordBox| as its viewport
538 // and user coordinate system. |currentPosition| is based on the border-box
539 // of the containing block. Therefore, we have to apply an extra translation
540 // to put it at the correct position based on |coordBox|.
542 // Note: we reuse |OffsetPathData::ShapeData::mCurrentPosition| to include
543 // this extra translation, so we don't have to add an extra field.
544 nsPoint positionInCoordBox
= currentPosition
- coordBox
.TopLeft();
545 return path
? OffsetPathData::Shape(path
.forget(),
546 std::move(positionInCoordBox
),
547 element
->IsClosedLoop())
548 : OffsetPathData::None();
551 // The rest part is to handle "<basic-shape> || <coord-box>".
552 MOZ_ASSERT(offsetPath
.IsBasicShapeOrCoordBox());
554 const nsStyleDisplay
* disp
= aFrame
->StyleDisplay();
555 RefPtr
<gfx::Path
> path
=
556 disp
->mOffsetPath
.IsCoordBox()
557 ? BuildSimpleInsetPath(containingFrame
->StyleBorder()->mBorderRadius
,
559 : MotionPathUtils::BuildPath(
560 disp
->mOffsetPath
.AsOffsetPath().path
->AsShape(),
561 disp
->mOffsetPosition
, coordBox
, currentPosition
, builder
);
562 return path
? OffsetPathData::Shape(path
.forget(), std::move(currentPosition
),
564 : OffsetPathData::None();
568 Maybe
<ResolvedMotionPathData
> MotionPathUtils::ResolveMotionPath(
569 const nsIFrame
* aFrame
, TransformReferenceBox
& aRefBox
) {
572 const nsStyleDisplay
* display
= aFrame
->StyleDisplay();
574 // FIXME: It's possible to refactor the calculation of transform-origin, so we
575 // could calculate from the caller, and reuse the value in nsDisplayList.cpp.
576 CSSPoint transformOrigin
= nsStyleTransformMatrix::Convert2DPosition(
577 display
->mTransformOrigin
.horizontal
, display
->mTransformOrigin
.vertical
,
580 return ResolveMotionPath(
581 GenerateOffsetPathData(aFrame
), display
->mOffsetDistance
,
582 display
->mOffsetRotate
, display
->mOffsetAnchor
, display
->mOffsetPosition
,
583 transformOrigin
, aRefBox
, ComputeAnchorPointAdjustment(*aFrame
));
586 // Generate data for motion path on the compositor thread.
587 static OffsetPathData
GenerateOffsetPathData(
588 const StyleOffsetPath
& aOffsetPath
,
589 const StyleOffsetPosition
& aOffsetPosition
,
590 const layers::MotionPathData
& aMotionPathData
,
591 gfx::Path
* aCachedMotionPath
) {
592 if (aOffsetPath
.IsNone()) {
593 return OffsetPathData::None();
597 if (aOffsetPath
.IsRay()) {
598 return aMotionPathData
.coordBox().IsEmpty()
599 ? OffsetPathData::None()
600 : OffsetPathData::Ray(
601 aOffsetPath
.AsRay(), aMotionPathData
.coordBox(),
602 aMotionPathData
.currentPosition(),
603 aMotionPathData
.rayContainReferenceLength());
607 // FIXME: Bug 1837042, cache gfx::Path for shapes other than path().
608 if (aOffsetPath
.IsPath()) {
609 const StyleSVGPathData
& pathData
= aOffsetPath
.AsSVGPathData();
610 // If aCachedMotionPath is valid, we have a fixed path.
611 // This means we have pre-built it already and no need to update.
612 RefPtr
<gfx::Path
> path
= aCachedMotionPath
;
614 RefPtr
<gfx::PathBuilder
> builder
=
615 MotionPathUtils::GetCompositorPathBuilder();
616 path
= MotionPathUtils::BuildSVGPath(pathData
, builder
);
618 // FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
619 // to give it the current box position.
620 return OffsetPathData::Shape(path
.forget(), {}, IsClosedLoop(pathData
));
623 // The rest part is to handle "<basic-shape> || <coord-box>".
624 MOZ_ASSERT(aOffsetPath
.IsBasicShapeOrCoordBox());
626 const nsRect
& coordBox
= aMotionPathData
.coordBox();
627 if (coordBox
.IsEmpty()) {
628 return OffsetPathData::None();
631 RefPtr
<gfx::PathBuilder
> builder
=
632 MotionPathUtils::GetCompositorPathBuilder();
634 return OffsetPathData::None();
637 RefPtr
<gfx::Path
> path
;
638 if (aOffsetPath
.IsCoordBox()) {
639 const nsRect insetRect
= ShapeUtils::ComputeInsetRect(
640 StyleRect
<LengthPercentage
>::WithAllSides(LengthPercentage::Zero()),
642 const nsTArray
<nscoord
>& radii
= aMotionPathData
.coordBoxInsetRadii();
643 path
= ShapeUtils::BuildRectPath(
644 insetRect
, radii
.IsEmpty() ? nullptr : radii
.Elements(), coordBox
,
645 AppUnitsPerCSSPixel(), builder
);
647 path
= MotionPathUtils::BuildPath(
648 aOffsetPath
.AsOffsetPath().path
->AsShape(), aOffsetPosition
, coordBox
,
649 aMotionPathData
.currentPosition(), builder
);
652 return path
? OffsetPathData::Shape(
653 path
.forget(), nsPoint(aMotionPathData
.currentPosition()),
655 : OffsetPathData::None();
659 Maybe
<ResolvedMotionPathData
> MotionPathUtils::ResolveMotionPath(
660 const StyleOffsetPath
* aPath
, const StyleLengthPercentage
* aDistance
,
661 const StyleOffsetRotate
* aRotate
, const StylePositionOrAuto
* aAnchor
,
662 const StyleOffsetPosition
* aPosition
,
663 const Maybe
<layers::MotionPathData
>& aMotionPathData
,
664 TransformReferenceBox
& aRefBox
, gfx::Path
* aCachedMotionPath
) {
669 MOZ_ASSERT(aMotionPathData
);
671 auto zeroOffsetDistance
= LengthPercentage::Zero();
672 auto autoOffsetRotate
= StyleOffsetRotate
{true, StyleAngle::Zero()};
673 auto autoOffsetAnchor
= StylePositionOrAuto::Auto();
674 auto autoOffsetPosition
= StyleOffsetPosition::Auto();
675 return ResolveMotionPath(
676 GenerateOffsetPathData(*aPath
,
677 aPosition
? *aPosition
: autoOffsetPosition
,
678 *aMotionPathData
, aCachedMotionPath
),
679 aDistance
? *aDistance
: zeroOffsetDistance
,
680 aRotate
? *aRotate
: autoOffsetRotate
,
681 aAnchor
? *aAnchor
: autoOffsetAnchor
,
682 aPosition
? *aPosition
: autoOffsetPosition
, aMotionPathData
->origin(),
683 aRefBox
, aMotionPathData
->anchorAdjustment());
687 already_AddRefed
<gfx::Path
> MotionPathUtils::BuildSVGPath(
688 const StyleSVGPathData
& aPath
, gfx::PathBuilder
* aPathBuilder
) {
693 const Span
<const StylePathCommand
>& path
= aPath
._0
.AsSpan();
694 return SVGPathData::BuildPath(path
, aPathBuilder
, StyleStrokeLinecap::Butt
,
698 static already_AddRefed
<gfx::Path
> BuildShape(
699 const Span
<const StyleShapeCommand
>& aShape
, gfx::PathBuilder
* aPathBuilder
,
700 const nsRect
& aCoordBox
) {
705 // For motion path, we always use CSSPixel unit to compute the offset
706 // transform (i.e. motion path transform).
707 const auto rect
= CSSRect::FromAppUnits(aCoordBox
);
708 return SVGPathData::BuildPath(aShape
, aPathBuilder
, StyleStrokeLinecap::Butt
,
710 rect
.TopLeft().ToUnknownPoint());
714 already_AddRefed
<gfx::Path
> MotionPathUtils::BuildPath(
715 const StyleBasicShape
& aBasicShape
,
716 const StyleOffsetPosition
& aOffsetPosition
, const nsRect
& aCoordBox
,
717 const nsPoint
& aCurrentPosition
, gfx::PathBuilder
* aPathBuilder
) {
722 switch (aBasicShape
.tag
) {
723 case StyleBasicShape::Tag::Circle
: {
724 const nsPoint center
=
725 ComputePosition(aBasicShape
.AsCircle().position
, aOffsetPosition
,
726 aCoordBox
, aCurrentPosition
);
727 return ShapeUtils::BuildCirclePath(aBasicShape
, aCoordBox
, center
,
728 AppUnitsPerCSSPixel(), aPathBuilder
);
730 case StyleBasicShape::Tag::Ellipse
: {
731 const nsPoint center
=
732 ComputePosition(aBasicShape
.AsEllipse().position
, aOffsetPosition
,
733 aCoordBox
, aCurrentPosition
);
734 return ShapeUtils::BuildEllipsePath(aBasicShape
, aCoordBox
, center
,
735 AppUnitsPerCSSPixel(), aPathBuilder
);
737 case StyleBasicShape::Tag::Rect
:
738 return ShapeUtils::BuildInsetPath(aBasicShape
, aCoordBox
,
739 AppUnitsPerCSSPixel(), aPathBuilder
);
740 case StyleBasicShape::Tag::Polygon
:
741 return ShapeUtils::BuildPolygonPath(aBasicShape
, aCoordBox
,
742 AppUnitsPerCSSPixel(), aPathBuilder
);
743 case StyleBasicShape::Tag::PathOrShape
: {
744 // FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
745 // to also check its containing block as well. For now, we are still
746 // building its gfx::Path directly by its SVGPathData without other
747 // reference. https://github.com/w3c/fxtf-drafts/issues/504
748 const auto& pathOrShape
= aBasicShape
.AsPathOrShape();
749 if (pathOrShape
.IsPath()) {
750 return BuildSVGPath(pathOrShape
.AsPath().path
, aPathBuilder
);
753 // Note that shape() always defines the initial position, i.e. "from x y",
754 // by its first move command, so |aOffsetPosition|, i.e. offset-position
755 // property, is ignored.
756 return BuildShape(pathOrShape
.AsShape().commands
.AsSpan(), aPathBuilder
,
765 already_AddRefed
<gfx::PathBuilder
> MotionPathUtils::GetPathBuilder() {
766 // Here we only need to build a valid path for motion path, so
767 // using the default values of stroke-width, stoke-linecap, and fill-rule
768 // is fine for now because what we want is to get the point and its normal
769 // vector along the path, instead of rendering it.
770 RefPtr
<gfx::PathBuilder
> builder
=
771 gfxPlatform::GetPlatform()
772 ->ScreenReferenceDrawTarget()
773 ->CreatePathBuilder(gfx::FillRule::FILL_WINDING
);
774 return builder
.forget();
778 already_AddRefed
<gfx::PathBuilder
> MotionPathUtils::GetCompositorPathBuilder() {
779 // FIXME: Perhaps we need a PathBuilder which is independent on the backend.
780 RefPtr
<gfx::PathBuilder
> builder
=
781 gfxPlatform::Initialized()
782 ? gfxPlatform::GetPlatform()
783 ->ScreenReferenceDrawTarget()
784 ->CreatePathBuilder(gfx::FillRule::FILL_WINDING
)
785 : gfx::Factory::CreateSimplePathBuilder();
786 return builder
.forget();
789 } // namespace mozilla