no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / layout / base / MotionPathUtils.cpp
blobd620064b73f324392e924ab27cfb108ae4dbd0f5
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"
19 #include "nsIFrame.h"
20 #include "nsLayoutUtils.h"
21 #include "nsStyleTransformMatrix.h"
23 #include <math.h>
25 namespace mozilla {
27 using nsStyleTransformMatrix::TransformReferenceBox;
29 /* static */
30 CSSPoint MotionPathUtils::ComputeAnchorPointAdjustment(const nsIFrame& aFrame) {
31 if (!aFrame.HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
32 return {};
35 auto transformBox = aFrame.StyleDisplay()->mTransformBox;
36 if (transformBox == StyleTransformBox::ViewBox ||
37 transformBox == StyleTransformBox::BorderBox) {
38 return {};
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) {
53 switch (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;
70 /* static */
71 const nsIFrame* MotionPathUtils::GetOffsetPathReferenceBox(
72 const nsIFrame* aFrame, nsRect& aOutputRect) {
73 const StyleOffsetPath& offsetPath = aFrame->StyleDisplay()->mOffsetPath;
74 if (offsetPath.IsNone()) {
75 return nullptr;
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;
95 /* static */
96 CSSCoord MotionPathUtils::GetRayContainReferenceSize(nsIFrame* aFrame) {
97 // We use the border-box size to calculate the reduced path length when using
98 // "contain" keyword.
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,
115 aFrame->StyleSVGReset()->HasNonScalingStroke()
116 ? StyleGeometryBox::FillBox
117 : StyleGeometryBox::StrokeBox,
118 nsLayoutUtils::MayHaveNonScalingStrokeCyclicDependency::Yes)
119 : nsLayoutUtils::ComputeHTMLReferenceRect(
120 aFrame, StyleGeometryBox::BorderBox))
121 .Size());
122 return std::max(size.width, size.height);
125 /* static */
126 nsTArray<nscoord> MotionPathUtils::ComputeBorderRadii(
127 const StyleBorderRadius& aBorderRadius, const nsRect& aCoordBox) {
128 const nsRect insetRect = ShapeUtils::ComputeInsetRect(
129 StyleRect<LengthPercentage>::WithAllSides(LengthPercentage::Zero()),
130 aCoordBox);
131 nsTArray<nscoord> result(8);
132 result.SetLength(8);
133 if (!ShapeUtils::ComputeRectRadii(aBorderRadius, aCoordBox, insetRect,
134 result.Elements())) {
135 result.Clear();
137 return result;
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',
155 // h = b / cos(t):
156 // b*tan(t)
157 // (topLeft) #--------*-----*--# (aContainingBlock.XMost(), topLeft.y)
158 // | | / |
159 // | | / |
160 // | b h |
161 // | |t/ |
162 // | |/ |
163 // (aOrigin) *---b'---* (aContainingBlock.XMost(), aOrigin.y)
164 // | | |
165 // | | |
166 // | | |
167 // | | |
168 // | | |
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
201 // case.
203 // e.g. If b * tan(theta) > b', h = b' / sin(theta):
204 // *----*
205 // | |
206 // | /|
207 // b /t|
208 // |t/ |
209 // |/ |
210 // *-b'-*
211 if (b * sint > bPrime * cost) {
212 return bPrime / sint;
214 return b / cost;
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)) {
259 return 0.0;
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: {
284 CSSCoord h = 0;
285 CSSCoord v = 0;
286 if (aRaySizeType == StyleRaySize::ClosestCorner) {
287 h = std::min(left, right);
288 v = std::min(top, bottom);
289 } else {
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");
299 return 0.0;
302 static CSSCoord ComputeRayUsedDistance(
303 const StyleRayFunction& aRay, const LengthPercentage& aDistance,
304 const CSSCoord& aPathLength, const CSSCoord& aRayContainReferenceLength) {
305 CSSCoord usedDistance = aDistance.ResolveToCSSPixels(aPathLength);
306 if (!aRay.contain) {
307 return usedDistance;
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,
316 0.0f);
319 /* static */
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()) {
326 return Nothing();
329 // Compute the point and angle for creating the equivalent translate and
330 // rotate.
331 double directionAngle = 0.0;
332 gfx::Point point;
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
341 // pixel as well.
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;
354 } else {
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);
359 gfx::Point tangent;
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.
383 directionAngle =
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))
390 .ToUnknownPoint();
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.
394 point =
395 vectorToOrigin +
396 gfx::Point(usedDistance * static_cast<gfx::Float>(cos(directionAngle)),
397 usedDistance * static_cast<gfx::Float>(sin(directionAngle)));
398 } else {
399 MOZ_ASSERT_UNREACHABLE("Unsupported offset-path value");
400 return Nothing();
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);
418 gfx::Point shift;
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(),
432 angle, shift});
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) {
445 if (!aPathBuilder) {
446 return nullptr;
449 const nsRect insetRect = ShapeUtils::ComputeInsetRect(
450 StyleRect<LengthPercentage>::WithAllSides(LengthPercentage::Zero()),
451 aCoordBox);
452 nscoord radii[8];
453 const bool hasRadii =
454 ShapeUtils::ComputeRectRadii(aBorderRadius, aCoordBox, insetRect, radii);
455 return ShapeUtils::BuildRectPath(insetRect, hasRadii ? radii : nullptr,
456 aCoordBox, AppUnitsPerCSSPixel(),
457 aPathBuilder);
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) {
465 if (!aBuilder) {
466 return nullptr;
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();
481 // Handle ray().
482 if (offsetPath.IsRay()) {
483 nsRect coordBox;
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));
509 nsRect coordBox;
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));
521 if (!element) {
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,
558 coordBox, builder)
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),
563 true)
564 : OffsetPathData::None();
567 /* static*/
568 Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
569 const nsIFrame* aFrame, TransformReferenceBox& aRefBox) {
570 MOZ_ASSERT(aFrame);
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,
578 aRefBox);
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();
596 // Handle ray().
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());
606 // Handle path().
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;
613 if (!path) {
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();
633 if (!builder) {
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()),
641 coordBox);
642 const nsTArray<nscoord>& radii = aMotionPathData.coordBoxInsetRadii();
643 path = ShapeUtils::BuildRectPath(
644 insetRect, radii.IsEmpty() ? nullptr : radii.Elements(), coordBox,
645 AppUnitsPerCSSPixel(), builder);
646 } else {
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()),
654 true)
655 : OffsetPathData::None();
658 /* static */
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) {
665 if (!aPath) {
666 return Nothing();
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());
686 /* static */
687 already_AddRefed<gfx::Path> MotionPathUtils::BuildSVGPath(
688 const StyleSVGPathData& aPath, gfx::PathBuilder* aPathBuilder) {
689 if (!aPathBuilder) {
690 return nullptr;
693 const Span<const StylePathCommand>& path = aPath._0.AsSpan();
694 return SVGPathData::BuildPath(path, aPathBuilder, StyleStrokeLinecap::Butt,
695 0.0);
698 static already_AddRefed<gfx::Path> BuildShape(
699 const Span<const StyleShapeCommand>& aShape, gfx::PathBuilder* aPathBuilder,
700 const nsRect& aCoordBox) {
701 if (!aPathBuilder) {
702 return nullptr;
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,
709 0.0, rect.Size(),
710 rect.TopLeft().ToUnknownPoint());
713 /* static */
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) {
718 if (!aPathBuilder) {
719 return nullptr;
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,
757 aCoordBox);
761 return nullptr;
764 /* static */
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();
777 /* static */
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