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/ShapeUtils.h"
11 #include "nsCSSRendering.h"
12 #include "nsLayoutUtils.h"
14 #include "nsStyleStruct.h"
15 #include "mozilla/SVGContentUtils.h"
16 #include "mozilla/gfx/2D.h"
17 #include "mozilla/gfx/PathHelpers.h"
21 nscoord
ShapeUtils::ComputeShapeRadius(const StyleShapeRadius
& aType
,
22 const nscoord aCenter
,
23 const nscoord aPosMin
,
24 const nscoord aPosMax
) {
25 MOZ_ASSERT(aType
.IsFarthestSide() || aType
.IsClosestSide());
26 nscoord dist1
= std::abs(aPosMin
- aCenter
);
27 nscoord dist2
= std::abs(aPosMax
- aCenter
);
29 if (aType
.IsFarthestSide()) {
30 length
= dist1
> dist2
? dist1
: dist2
;
32 length
= dist1
> dist2
? dist2
: dist1
;
37 nsPoint
ShapeUtils::ComputePosition(const StylePosition
& aPosition
,
38 const nsRect
& aRefBox
) {
39 nsPoint topLeft
, anchor
;
40 nsSize
size(aRefBox
.Size());
41 nsImageRenderer::ComputeObjectAnchorPoint(aPosition
, size
, size
, &topLeft
,
43 return anchor
+ aRefBox
.TopLeft();
46 nsPoint
ShapeUtils::ComputeCircleOrEllipseCenter(
47 const StyleBasicShape
& aBasicShape
, const nsRect
& aRefBox
) {
48 MOZ_ASSERT(aBasicShape
.IsCircle() || aBasicShape
.IsEllipse(),
49 "The basic shape must be circle() or ellipse!");
51 const auto& position
= aBasicShape
.IsCircle()
52 ? aBasicShape
.AsCircle().position
53 : aBasicShape
.AsEllipse().position
;
54 MOZ_ASSERT(position
.IsPosition(), "A default position should be given");
55 return ComputePosition(position
.AsPosition(), aRefBox
);
58 nscoord
ShapeUtils::ComputeCircleRadius(const StyleBasicShape
& aBasicShape
,
59 const nsPoint
& aCenter
,
60 const nsRect
& aRefBox
) {
61 MOZ_ASSERT(aBasicShape
.IsCircle(), "The basic shape must be circle()!");
62 const auto& radius
= aBasicShape
.AsCircle().radius
;
63 if (radius
.IsLength()) {
64 return radius
.AsLength().Resolve([&] {
65 // We resolve percent <shape-radius> value for circle() as defined here:
66 // https://drafts.csswg.org/css-shapes/#funcdef-circle
67 double referenceLength
= SVGContentUtils::ComputeNormalizedHypotenuse(
68 aRefBox
.width
, aRefBox
.height
);
69 return NSToCoordRound(referenceLength
);
74 ComputeShapeRadius(radius
, aCenter
.x
, aRefBox
.x
, aRefBox
.XMost());
76 ComputeShapeRadius(radius
, aCenter
.y
, aRefBox
.y
, aRefBox
.YMost());
77 return radius
.IsFarthestSide() ? std::max(horizontal
, vertical
)
78 : std::min(horizontal
, vertical
);
81 nsSize
ShapeUtils::ComputeEllipseRadii(const StyleBasicShape
& aBasicShape
,
82 const nsPoint
& aCenter
,
83 const nsRect
& aRefBox
) {
84 MOZ_ASSERT(aBasicShape
.IsEllipse(), "The basic shape must be ellipse()!");
85 const auto& ellipse
= aBasicShape
.AsEllipse();
87 if (ellipse
.semiaxis_x
.IsLength()) {
88 radii
.width
= ellipse
.semiaxis_x
.AsLength().Resolve(aRefBox
.width
);
90 radii
.width
= ComputeShapeRadius(ellipse
.semiaxis_x
, aCenter
.x
, aRefBox
.x
,
94 if (ellipse
.semiaxis_y
.IsLength()) {
95 radii
.height
= ellipse
.semiaxis_y
.AsLength().Resolve(aRefBox
.height
);
97 radii
.height
= ComputeShapeRadius(ellipse
.semiaxis_y
, aCenter
.y
, aRefBox
.y
,
105 nsRect
ShapeUtils::ComputeInsetRect(const StyleBasicShape
& aBasicShape
,
106 const nsRect
& aRefBox
) {
107 MOZ_ASSERT(aBasicShape
.IsInset(), "The basic shape must be inset()!");
108 return ComputeInsetRect(aBasicShape
.AsInset().rect
, aRefBox
);
112 nsRect
ShapeUtils::ComputeInsetRect(
113 const StyleRect
<LengthPercentage
>& aStyleRect
, const nsRect
& aRefBox
) {
114 nsMargin
inset(aStyleRect
._0
.Resolve(aRefBox
.Height()),
115 aStyleRect
._1
.Resolve(aRefBox
.Width()),
116 aStyleRect
._2
.Resolve(aRefBox
.Height()),
117 aStyleRect
._3
.Resolve(aRefBox
.Width()));
119 nscoord x
= aRefBox
.X() + inset
.left
;
120 nscoord width
= aRefBox
.Width() - inset
.LeftRight();
121 nscoord y
= aRefBox
.Y() + inset
.top
;
122 nscoord height
= aRefBox
.Height() - inset
.TopBottom();
124 // Invert left and right, if necessary.
130 // Invert top and bottom, if necessary.
136 return nsRect(x
, y
, width
, height
);
140 bool ShapeUtils::ComputeInsetRadii(const StyleBasicShape
& aBasicShape
,
141 const nsRect
& aRefBox
,
142 const nsRect
& aInsetRect
,
144 const auto& radius
= aBasicShape
.AsInset().round
;
145 return nsIFrame::ComputeBorderRadii(radius
, aRefBox
.Size(), aInsetRect
.Size(),
150 nsTArray
<nsPoint
> ShapeUtils::ComputePolygonVertices(
151 const StyleBasicShape
& aBasicShape
, const nsRect
& aRefBox
) {
152 MOZ_ASSERT(aBasicShape
.IsPolygon(), "The basic shape must be polygon()!");
154 auto coords
= aBasicShape
.AsPolygon().coordinates
.AsSpan();
155 nsTArray
<nsPoint
> vertices(coords
.Length());
156 for (const StylePolygonCoord
<LengthPercentage
>& point
: coords
) {
157 vertices
.AppendElement(nsPoint(point
._0
.Resolve(aRefBox
.width
),
158 point
._1
.Resolve(aRefBox
.height
)) +
165 static inline gfx::Point
ConvertToGfxPoint(const nsPoint
& aPoint
,
166 nscoord aAppUnitsPerPixel
) {
167 return {static_cast<gfx::Float
>(aPoint
.x
) /
168 static_cast<gfx::Float
>(aAppUnitsPerPixel
),
169 static_cast<gfx::Float
>(aPoint
.y
) /
170 static_cast<gfx::Float
>(aAppUnitsPerPixel
)};
174 already_AddRefed
<gfx::Path
> ShapeUtils::BuildCirclePath(
175 const StyleBasicShape
& aShape
, const nsRect
& aRefBox
,
176 const nsPoint
& aCenter
, nscoord aAppUnitsPerPixel
,
177 gfx::PathBuilder
* aPathBuilder
) {
178 const nscoord r
= ComputeCircleRadius(aShape
, aCenter
, aRefBox
);
180 ConvertToGfxPoint(aCenter
, aAppUnitsPerPixel
),
181 static_cast<float>(r
) / static_cast<float>(aAppUnitsPerPixel
), 0.0,
182 gfx::Float(2.0 * M_PI
));
183 aPathBuilder
->Close();
184 return aPathBuilder
->Finish();
187 static inline gfx::Size
ConvertToGfxSize(const nsSize
& aSize
,
188 nscoord aAppUnitsPerPixel
) {
189 return {static_cast<gfx::Float
>(aSize
.width
) /
190 static_cast<gfx::Float
>(aAppUnitsPerPixel
),
191 static_cast<gfx::Float
>(aSize
.height
) /
192 static_cast<gfx::Float
>(aAppUnitsPerPixel
)};
196 already_AddRefed
<gfx::Path
> ShapeUtils::BuildEllipsePath(
197 const StyleBasicShape
& aShape
, const nsRect
& aRefBox
,
198 const nsPoint
& aCenter
, nscoord aAppUnitsPerPixel
,
199 gfx::PathBuilder
* aPathBuilder
) {
200 const nsSize radii
= ComputeEllipseRadii(aShape
, aCenter
, aRefBox
);
201 EllipseToBezier(aPathBuilder
, ConvertToGfxPoint(aCenter
, aAppUnitsPerPixel
),
202 ConvertToGfxSize(radii
, aAppUnitsPerPixel
));
203 aPathBuilder
->Close();
204 return aPathBuilder
->Finish();
208 already_AddRefed
<gfx::Path
> ShapeUtils::BuildPolygonPath(
209 const StyleBasicShape
& aShape
, const nsRect
& aRefBox
,
210 nscoord aAppUnitsPerPixel
, gfx::PathBuilder
* aPathBuilder
) {
211 nsTArray
<nsPoint
> vertices
= ComputePolygonVertices(aShape
, aRefBox
);
212 if (vertices
.IsEmpty()) {
213 MOZ_ASSERT_UNREACHABLE(
214 "ComputePolygonVertices() should've given us some vertices!");
216 aPathBuilder
->MoveTo(NSPointToPoint(vertices
[0], aAppUnitsPerPixel
));
217 for (size_t i
= 1; i
< vertices
.Length(); ++i
) {
218 aPathBuilder
->LineTo(NSPointToPoint(vertices
[i
], aAppUnitsPerPixel
));
221 aPathBuilder
->Close();
222 return aPathBuilder
->Finish();
226 already_AddRefed
<gfx::Path
> ShapeUtils::BuildInsetPath(
227 const StyleBasicShape
& aShape
, const nsRect
& aRefBox
,
228 nscoord aAppUnitsPerPixel
, gfx::PathBuilder
* aPathBuilder
) {
229 const nsRect insetRect
= ComputeInsetRect(aShape
, aRefBox
);
230 nscoord appUnitsRadii
[8];
231 const bool hasRadii
=
232 ComputeInsetRadii(aShape
, aRefBox
, insetRect
, appUnitsRadii
);
233 return BuildInsetPath(insetRect
, hasRadii
? appUnitsRadii
: nullptr, aRefBox
,
234 aAppUnitsPerPixel
, aPathBuilder
);
238 already_AddRefed
<gfx::Path
> ShapeUtils::BuildInsetPath(
239 const nsRect
& aInsetRect
, const nscoord aRadii
[8], const nsRect
& aRefBox
,
240 nscoord aAppUnitsPerPixel
, gfx::PathBuilder
* aPathBuilder
) {
241 const gfx::Rect insetRectPixels
= NSRectToRect(aInsetRect
, aAppUnitsPerPixel
);
243 gfx::RectCornerRadii corners
;
244 nsCSSRendering::ComputePixelRadii(aRadii
, aAppUnitsPerPixel
, &corners
);
246 AppendRoundedRectToPath(aPathBuilder
, insetRectPixels
, corners
, true);
248 AppendRectToPath(aPathBuilder
, insetRectPixels
, true);
250 return aPathBuilder
->Finish();
253 } // namespace mozilla