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 // If position is not specified, we use 50% 50%.
55 if (position
.IsAuto()) {
56 return ComputePosition(StylePosition::FromPercentage(0.5), aRefBox
);
59 MOZ_ASSERT(position
.IsPosition());
60 return ComputePosition(position
.AsPosition(), aRefBox
);
63 nscoord
ShapeUtils::ComputeCircleRadius(const StyleBasicShape
& aBasicShape
,
64 const nsPoint
& aCenter
,
65 const nsRect
& aRefBox
) {
66 MOZ_ASSERT(aBasicShape
.IsCircle(), "The basic shape must be circle()!");
67 const auto& radius
= aBasicShape
.AsCircle().radius
;
68 if (radius
.IsLength()) {
69 return radius
.AsLength().Resolve([&] {
70 // We resolve percent <shape-radius> value for circle() as defined here:
71 // https://drafts.csswg.org/css-shapes/#funcdef-circle
72 double referenceLength
= SVGContentUtils::ComputeNormalizedHypotenuse(
73 aRefBox
.width
, aRefBox
.height
);
74 return NSToCoordRound(referenceLength
);
79 ComputeShapeRadius(radius
, aCenter
.x
, aRefBox
.x
, aRefBox
.XMost());
81 ComputeShapeRadius(radius
, aCenter
.y
, aRefBox
.y
, aRefBox
.YMost());
82 return radius
.IsFarthestSide() ? std::max(horizontal
, vertical
)
83 : std::min(horizontal
, vertical
);
86 nsSize
ShapeUtils::ComputeEllipseRadii(const StyleBasicShape
& aBasicShape
,
87 const nsPoint
& aCenter
,
88 const nsRect
& aRefBox
) {
89 MOZ_ASSERT(aBasicShape
.IsEllipse(), "The basic shape must be ellipse()!");
90 const auto& ellipse
= aBasicShape
.AsEllipse();
92 if (ellipse
.semiaxis_x
.IsLength()) {
93 radii
.width
= ellipse
.semiaxis_x
.AsLength().Resolve(aRefBox
.width
);
95 radii
.width
= ComputeShapeRadius(ellipse
.semiaxis_x
, aCenter
.x
, aRefBox
.x
,
99 if (ellipse
.semiaxis_y
.IsLength()) {
100 radii
.height
= ellipse
.semiaxis_y
.AsLength().Resolve(aRefBox
.height
);
102 radii
.height
= ComputeShapeRadius(ellipse
.semiaxis_y
, aCenter
.y
, aRefBox
.y
,
110 nsRect
ShapeUtils::ComputeInsetRect(
111 const StyleRect
<LengthPercentage
>& aStyleRect
, const nsRect
& aRefBox
) {
112 const nsMargin
inset(aStyleRect
._0
.Resolve(aRefBox
.Height()),
113 aStyleRect
._1
.Resolve(aRefBox
.Width()),
114 aStyleRect
._2
.Resolve(aRefBox
.Height()),
115 aStyleRect
._3
.Resolve(aRefBox
.Width()));
117 const nscoord x
= aRefBox
.X() + inset
.left
;
118 const nscoord y
= aRefBox
.Y() + inset
.top
;
119 // All <basic-shape-rect> functions are converted into inset() at the
120 // computing time, and it seems other browsers just clamp the width/height to
121 // 0 if the dimension (i.e. top+bottom or left+right) is larger than 100%.
122 // This is identical to flooring right/bottom values in rect(). Therefore,
123 // here we also floor right/bottom (i.e. make sure the width/height is not
124 // negative) to match the behavior of other browsers and the spec of rect().
125 // https://github.com/w3c/csswg-drafts/issues/10870
126 const nscoord width
= std::max(0, aRefBox
.Width() - inset
.LeftRight());
127 const nscoord height
= std::max(0, aRefBox
.Height() - inset
.TopBottom());
128 return nsRect(x
, y
, width
, height
);
132 bool ShapeUtils::ComputeRectRadii(const StyleBorderRadius
& aBorderRadius
,
133 const nsRect
& aRefBox
, const nsRect
& aRect
,
135 return nsIFrame::ComputeBorderRadii(aBorderRadius
, aRefBox
.Size(),
136 aRect
.Size(), Sides(), aRadii
);
140 nsTArray
<nsPoint
> ShapeUtils::ComputePolygonVertices(
141 const StyleBasicShape
& aBasicShape
, const nsRect
& aRefBox
) {
142 MOZ_ASSERT(aBasicShape
.IsPolygon(), "The basic shape must be polygon()!");
144 auto coords
= aBasicShape
.AsPolygon().coordinates
.AsSpan();
145 nsTArray
<nsPoint
> vertices(coords
.Length());
146 for (const StylePolygonCoord
<LengthPercentage
>& point
: coords
) {
147 vertices
.AppendElement(nsPoint(point
._0
.Resolve(aRefBox
.width
),
148 point
._1
.Resolve(aRefBox
.height
)) +
155 static inline gfx::Point
ConvertToGfxPoint(const nsPoint
& aPoint
,
156 nscoord aAppUnitsPerPixel
) {
157 return {static_cast<gfx::Float
>(aPoint
.x
) /
158 static_cast<gfx::Float
>(aAppUnitsPerPixel
),
159 static_cast<gfx::Float
>(aPoint
.y
) /
160 static_cast<gfx::Float
>(aAppUnitsPerPixel
)};
164 already_AddRefed
<gfx::Path
> ShapeUtils::BuildCirclePath(
165 const StyleBasicShape
& aShape
, const nsRect
& aRefBox
,
166 const nsPoint
& aCenter
, nscoord aAppUnitsPerPixel
,
167 gfx::PathBuilder
* aPathBuilder
) {
168 const nscoord r
= ComputeCircleRadius(aShape
, aCenter
, aRefBox
);
170 ConvertToGfxPoint(aCenter
, aAppUnitsPerPixel
),
171 static_cast<float>(r
) / static_cast<float>(aAppUnitsPerPixel
), 0.0,
172 gfx::Float(2.0 * M_PI
));
173 aPathBuilder
->Close();
174 return aPathBuilder
->Finish();
177 static inline gfx::Size
ConvertToGfxSize(const nsSize
& aSize
,
178 nscoord aAppUnitsPerPixel
) {
179 return {static_cast<gfx::Float
>(aSize
.width
) /
180 static_cast<gfx::Float
>(aAppUnitsPerPixel
),
181 static_cast<gfx::Float
>(aSize
.height
) /
182 static_cast<gfx::Float
>(aAppUnitsPerPixel
)};
186 already_AddRefed
<gfx::Path
> ShapeUtils::BuildEllipsePath(
187 const StyleBasicShape
& aShape
, const nsRect
& aRefBox
,
188 const nsPoint
& aCenter
, nscoord aAppUnitsPerPixel
,
189 gfx::PathBuilder
* aPathBuilder
) {
190 const nsSize radii
= ComputeEllipseRadii(aShape
, aCenter
, aRefBox
);
191 EllipseToBezier(aPathBuilder
, ConvertToGfxPoint(aCenter
, aAppUnitsPerPixel
),
192 ConvertToGfxSize(radii
, aAppUnitsPerPixel
));
193 aPathBuilder
->Close();
194 return aPathBuilder
->Finish();
198 already_AddRefed
<gfx::Path
> ShapeUtils::BuildPolygonPath(
199 const StyleBasicShape
& aShape
, const nsRect
& aRefBox
,
200 nscoord aAppUnitsPerPixel
, gfx::PathBuilder
* aPathBuilder
) {
201 nsTArray
<nsPoint
> vertices
= ComputePolygonVertices(aShape
, aRefBox
);
202 if (vertices
.IsEmpty()) {
203 MOZ_ASSERT_UNREACHABLE(
204 "ComputePolygonVertices() should've given us some vertices!");
206 aPathBuilder
->MoveTo(NSPointToPoint(vertices
[0], aAppUnitsPerPixel
));
207 for (size_t i
= 1; i
< vertices
.Length(); ++i
) {
208 aPathBuilder
->LineTo(NSPointToPoint(vertices
[i
], aAppUnitsPerPixel
));
211 aPathBuilder
->Close();
212 return aPathBuilder
->Finish();
216 already_AddRefed
<gfx::Path
> ShapeUtils::BuildInsetPath(
217 const StyleBasicShape
& aShape
, const nsRect
& aRefBox
,
218 nscoord aAppUnitsPerPixel
, gfx::PathBuilder
* aPathBuilder
) {
219 const nsRect insetRect
= ComputeInsetRect(aShape
.AsRect().rect
, aRefBox
);
220 nscoord appUnitsRadii
[8];
221 const bool hasRadii
= ComputeRectRadii(aShape
.AsRect().round
, aRefBox
,
222 insetRect
, appUnitsRadii
);
223 return BuildRectPath(insetRect
, hasRadii
? appUnitsRadii
: nullptr, aRefBox
,
224 aAppUnitsPerPixel
, aPathBuilder
);
228 already_AddRefed
<gfx::Path
> ShapeUtils::BuildRectPath(
229 const nsRect
& aRect
, const nscoord aRadii
[8], const nsRect
& aRefBox
,
230 nscoord aAppUnitsPerPixel
, gfx::PathBuilder
* aPathBuilder
) {
231 const gfx::Rect insetRectPixels
= NSRectToRect(aRect
, aAppUnitsPerPixel
);
233 gfx::RectCornerRadii corners
;
234 nsCSSRendering::ComputePixelRadii(aRadii
, aAppUnitsPerPixel
, &corners
);
236 AppendRoundedRectToPath(aPathBuilder
, insetRectPixels
, corners
, true);
238 AppendRectToPath(aPathBuilder
, insetRectPixels
, true);
240 return aPathBuilder
->Finish();
243 } // namespace mozilla