ShapeUtils.cpp (9941B)
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/. */ 6 7 #include "mozilla/ShapeUtils.h" 8 9 #include <cstdlib> 10 11 #include "mozilla/SVGContentUtils.h" 12 #include "mozilla/gfx/2D.h" 13 #include "mozilla/gfx/PathHelpers.h" 14 #include "nsCSSRendering.h" 15 #include "nsLayoutUtils.h" 16 #include "nsMargin.h" 17 #include "nsStyleStruct.h" 18 19 namespace mozilla { 20 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; 33 } 34 return length; 35 } 36 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(); 44 } 45 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!"); 50 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); 57 } 58 59 MOZ_ASSERT(position.IsPosition()); 60 return ComputePosition(position.AsPosition(), aRefBox); 61 } 62 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 }); 76 } 77 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); 84 } 85 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()); 97 } 98 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()); 104 } 105 106 return radii; 107 } 108 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())); 116 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); 129 } 130 131 /* static */ 132 bool ShapeUtils::ComputeRectRadii(const StyleBorderRadius& aBorderRadius, 133 const nsRect& aRefBox, const nsRect& aRect, 134 nsRectCornerRadii& aRadii) { 135 return nsIFrame::ComputeBorderRadii(aBorderRadius, aRefBox.Size(), 136 aRect.Size(), Sides(), aRadii); 137 } 138 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()!"); 143 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()); 150 } 151 return vertices; 152 } 153 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)}; 161 } 162 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(); 175 } 176 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)}; 183 } 184 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(); 195 } 196 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)); 209 } 210 } 211 aPathBuilder->Close(); 212 return aPathBuilder->Finish(); 213 } 214 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 nsRectCornerRadii appUnitsRadii; 221 const bool hasRadii = ComputeRectRadii(aShape.AsRect().round, aRefBox, 222 insetRect, appUnitsRadii); 223 return BuildRectPath(insetRect, hasRadii ? &appUnitsRadii : nullptr, aRefBox, 224 aAppUnitsPerPixel, aPathBuilder); 225 } 226 227 /* static */ 228 already_AddRefed<gfx::Path> ShapeUtils::BuildRectPath( 229 const nsRect& aRect, const nsRectCornerRadii* aRadii, 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); 235 AppendRoundedRectToPath(aPathBuilder, insetRectPixels, corners, true); 236 } else { 237 AppendRectToPath(aPathBuilder, insetRectPixels, true); 238 } 239 return aPathBuilder->Finish(); 240 } 241 242 } // namespace mozilla