Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / layout / base / ShapeUtils.cpp
blob166051d64b1aa3b91505a3004dee7a201c7d1970
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"
9 #include <cstdlib>
11 #include "nsCSSRendering.h"
12 #include "nsLayoutUtils.h"
13 #include "nsMargin.h"
14 #include "nsStyleStruct.h"
15 #include "mozilla/SVGContentUtils.h"
16 #include "mozilla/gfx/2D.h"
17 #include "mozilla/gfx/PathHelpers.h"
19 namespace mozilla {
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);
28 nscoord length = 0;
29 if (aType.IsFarthestSide()) {
30 length = dist1 > dist2 ? dist1 : dist2;
31 } else {
32 length = dist1 > dist2 ? dist2 : dist1;
34 return length;
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,
42 &anchor);
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);
75 });
78 nscoord horizontal =
79 ComputeShapeRadius(radius, aCenter.x, aRefBox.x, aRefBox.XMost());
80 nscoord vertical =
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();
91 nsSize radii;
92 if (ellipse.semiaxis_x.IsLength()) {
93 radii.width = ellipse.semiaxis_x.AsLength().Resolve(aRefBox.width);
94 } else {
95 radii.width = ComputeShapeRadius(ellipse.semiaxis_x, aCenter.x, aRefBox.x,
96 aRefBox.XMost());
99 if (ellipse.semiaxis_y.IsLength()) {
100 radii.height = ellipse.semiaxis_y.AsLength().Resolve(aRefBox.height);
101 } else {
102 radii.height = ComputeShapeRadius(ellipse.semiaxis_y, aCenter.y, aRefBox.y,
103 aRefBox.YMost());
106 return radii;
109 /* static */
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);
131 /* static */
132 bool ShapeUtils::ComputeRectRadii(const StyleBorderRadius& aBorderRadius,
133 const nsRect& aRefBox, const nsRect& aRect,
134 nscoord aRadii[8]) {
135 return nsIFrame::ComputeBorderRadii(aBorderRadius, aRefBox.Size(),
136 aRect.Size(), Sides(), aRadii);
139 /* static */
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)) +
149 aRefBox.TopLeft());
151 return vertices;
154 /* static */
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)};
163 /* static */
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);
169 aPathBuilder->Arc(
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)};
185 /* static */
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();
197 /* static */
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!");
205 } else {
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();
215 /* static */
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);
227 /* static */
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);
232 if (aRadii) {
233 gfx::RectCornerRadii corners;
234 nsCSSRendering::ComputePixelRadii(aRadii, aAppUnitsPerPixel, &corners);
236 AppendRoundedRectToPath(aPathBuilder, insetRectPixels, corners, true);
237 } else {
238 AppendRectToPath(aPathBuilder, insetRectPixels, true);
240 return aPathBuilder->Finish();
243 } // namespace mozilla