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/dom/SVGRectElement.h"
8 #include "mozilla/dom/SVGLengthBinding.h"
9 #include "mozilla/dom/SVGRectElementBinding.h"
10 #include "mozilla/gfx/2D.h"
11 #include "mozilla/gfx/Matrix.h"
12 #include "mozilla/gfx/Rect.h"
13 #include "mozilla/gfx/PathHelpers.h"
14 #include "nsGkAtoms.h"
15 #include "SVGGeometryProperty.h"
16 #include <algorithm>
17 
18 NS_IMPL_NS_NEW_SVG_ELEMENT(Rect)
19 
20 using namespace mozilla::gfx;
21 
22 namespace mozilla {
23 namespace dom {
24 
25 class DOMSVGAnimatedLength;
26 
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)27 JSObject* SVGRectElement::WrapNode(JSContext* aCx,
28                                    JS::Handle<JSObject*> aGivenProto) {
29   return SVGRectElement_Binding::Wrap(aCx, this, aGivenProto);
30 }
31 
32 SVGElement::LengthInfo SVGRectElement::sLengthInfo[6] = {
33     {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER,
34      SVGContentUtils::X},
35     {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER,
36      SVGContentUtils::Y},
37     {nsGkAtoms::width, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER,
38      SVGContentUtils::X},
39     {nsGkAtoms::height, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER,
40      SVGContentUtils::Y},
41     {nsGkAtoms::rx, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER,
42      SVGContentUtils::X},
43     {nsGkAtoms::ry, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER,
44      SVGContentUtils::Y}};
45 
46 //----------------------------------------------------------------------
47 // Implementation
48 
SVGRectElement(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo)49 SVGRectElement::SVGRectElement(
50     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
51     : SVGRectElementBase(std::move(aNodeInfo)) {}
52 
IsAttributeMapped(const nsAtom * aAttribute) const53 bool SVGRectElement::IsAttributeMapped(const nsAtom* aAttribute) const {
54   return IsInLengthInfo(aAttribute, sLengthInfo) ||
55          SVGRectElementBase::IsAttributeMapped(aAttribute);
56 }
57 
58 namespace SVGT = SVGGeometryProperty::Tags;
59 
60 //----------------------------------------------------------------------
61 // nsINode methods
62 
NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGRectElement)63 NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGRectElement)
64 
65 //----------------------------------------------------------------------
66 
67 already_AddRefed<DOMSVGAnimatedLength> SVGRectElement::X() {
68   return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this);
69 }
70 
Y()71 already_AddRefed<DOMSVGAnimatedLength> SVGRectElement::Y() {
72   return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this);
73 }
74 
Width()75 already_AddRefed<DOMSVGAnimatedLength> SVGRectElement::Width() {
76   return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this);
77 }
78 
Height()79 already_AddRefed<DOMSVGAnimatedLength> SVGRectElement::Height() {
80   return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this);
81 }
82 
Rx()83 already_AddRefed<DOMSVGAnimatedLength> SVGRectElement::Rx() {
84   return mLengthAttributes[ATTR_RX].ToDOMAnimatedLength(this);
85 }
86 
Ry()87 already_AddRefed<DOMSVGAnimatedLength> SVGRectElement::Ry() {
88   return mLengthAttributes[ATTR_RY].ToDOMAnimatedLength(this);
89 }
90 
91 //----------------------------------------------------------------------
92 // SVGElement methods
93 
94 /* virtual */
HasValidDimensions() const95 bool SVGRectElement::HasValidDimensions() const {
96   float width, height;
97 
98   if (SVGGeometryProperty::ResolveAll<SVGT::Width, SVGT::Height>(this, &width,
99                                                                  &height)) {
100     return width > 0 && height > 0;
101   }
102   // This function might be called for an element in display:none subtree
103   // (e.g. SMIL animateMotion), we fall back to use SVG attributes.
104   return mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() &&
105          mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0 &&
106          mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() &&
107          mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0;
108 }
109 
GetLengthInfo()110 SVGElement::LengthAttributesInfo SVGRectElement::GetLengthInfo() {
111   return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
112                               ArrayLength(sLengthInfo));
113 }
114 
115 //----------------------------------------------------------------------
116 // SVGGeometryElement methods
117 
GetGeometryBounds(Rect * aBounds,const StrokeOptions & aStrokeOptions,const Matrix & aToBoundsSpace,const Matrix * aToNonScalingStrokeSpace)118 bool SVGRectElement::GetGeometryBounds(Rect* aBounds,
119                                        const StrokeOptions& aStrokeOptions,
120                                        const Matrix& aToBoundsSpace,
121                                        const Matrix* aToNonScalingStrokeSpace) {
122   Rect rect;
123   Float rx, ry;
124 
125   DebugOnly<bool> ok =
126       SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width,
127                                       SVGT::Height, SVGT::Rx, SVGT::Ry>(
128           this, &rect.x, &rect.y, &rect.width, &rect.height, &rx, &ry);
129   MOZ_ASSERT(ok, "SVGGeometryProperty::ResolveAll failed");
130 
131   if (rect.IsEmpty()) {
132     // Rendering of the element disabled
133     rect.SetEmpty();  // Make sure width/height are zero and not negative
134     // We still want the x/y position from 'rect'
135     *aBounds = aToBoundsSpace.TransformBounds(rect);
136     return true;
137   }
138 
139   if (!aToBoundsSpace.IsRectilinear()) {
140     // We can't ignore the radii in this case if we want tight bounds
141     rx = std::max(rx, 0.0f);
142     ry = std::max(ry, 0.0f);
143 
144     if (rx != 0 || ry != 0) {
145       return false;
146     }
147   }
148 
149   if (aStrokeOptions.mLineWidth > 0.f) {
150     if (aToNonScalingStrokeSpace) {
151       if (aToNonScalingStrokeSpace->IsRectilinear()) {
152         MOZ_ASSERT(!aToNonScalingStrokeSpace->IsSingular());
153         rect = aToNonScalingStrokeSpace->TransformBounds(rect);
154         // Note that, in principle, an author could cause the corners of the
155         // rect to be beveled by specifying stroke-linejoin or setting
156         // stroke-miterlimit to be less than sqrt(2). In that very unlikely
157         // event the bounds that we calculate here may be too big if
158         // aToBoundsSpace is non-rectilinear. This is likely to be so rare it's
159         // not worth handling though.
160         rect.Inflate(aStrokeOptions.mLineWidth / 2.f);
161         Matrix nonScalingToBounds =
162             aToNonScalingStrokeSpace->Inverse() * aToBoundsSpace;
163         *aBounds = nonScalingToBounds.TransformBounds(rect);
164         return true;
165       }
166       return false;
167     }
168     // The "beveled" comment above applies here too
169     rect.Inflate(aStrokeOptions.mLineWidth / 2.f);
170   }
171 
172   *aBounds = aToBoundsSpace.TransformBounds(rect);
173   return true;
174 }
175 
GetAsSimplePath(SimplePath * aSimplePath)176 void SVGRectElement::GetAsSimplePath(SimplePath* aSimplePath) {
177   float x, y, width, height, rx, ry;
178 
179   DebugOnly<bool> ok =
180       SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width,
181                                       SVGT::Height, SVGT::Rx, SVGT::Ry>(
182           this, &x, &y, &width, &height, &rx, &ry);
183   MOZ_ASSERT(ok, "SVGGeometryProperty::ResolveAll failed");
184 
185   if (width <= 0 || height <= 0) {
186     aSimplePath->Reset();
187     return;
188   }
189 
190   rx = std::max(rx, 0.0f);
191   ry = std::max(ry, 0.0f);
192 
193   if (rx != 0 || ry != 0) {
194     aSimplePath->Reset();
195     return;
196   }
197 
198   aSimplePath->SetRect(x, y, width, height);
199 }
200 
BuildPath(PathBuilder * aBuilder)201 already_AddRefed<Path> SVGRectElement::BuildPath(PathBuilder* aBuilder) {
202   float x, y, width, height, rx, ry;
203 
204   if (!SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width,
205                                        SVGT::Height, SVGT::Rx, SVGT::Ry>(
206           this, &x, &y, &width, &height, &rx, &ry)) {
207     // This function might be called for element in display:none subtree
208     // (e.g. getTotalLength), we fall back to use SVG attributes.
209     GetAnimatedLengthValues(&x, &y, &width, &height, &rx, &ry, nullptr);
210     // If either the 'rx' or the 'ry' attribute isn't set, then we have to
211     // set it to the value of the other:
212     bool hasRx = mLengthAttributes[ATTR_RX].IsExplicitlySet();
213     bool hasRy = mLengthAttributes[ATTR_RY].IsExplicitlySet();
214     if (hasRx && !hasRy) {
215       ry = rx;
216     } else if (hasRy && !hasRx) {
217       rx = ry;
218     }
219   }
220 
221   if (width <= 0 || height <= 0) {
222     return nullptr;
223   }
224 
225   rx = std::max(rx, 0.0f);
226   ry = std::max(ry, 0.0f);
227 
228   if (rx == 0 && ry == 0) {
229     // Optimization for the no rounded corners case.
230     Rect r(x, y, width, height);
231     aBuilder->MoveTo(r.TopLeft());
232     aBuilder->LineTo(r.TopRight());
233     aBuilder->LineTo(r.BottomRight());
234     aBuilder->LineTo(r.BottomLeft());
235     aBuilder->Close();
236   } else {
237     // Clamp rx and ry to half the rect's width and height respectively:
238     rx = std::min(rx, width / 2);
239     ry = std::min(ry, height / 2);
240 
241     RectCornerRadii radii(rx, ry);
242     AppendRoundedRectToPath(aBuilder, Rect(x, y, width, height), radii);
243   }
244 
245   return aBuilder->Finish();
246 }
247 
IsLengthChangedViaCSS(const ComputedStyle & aNewStyle,const ComputedStyle & aOldStyle)248 bool SVGRectElement::IsLengthChangedViaCSS(const ComputedStyle& aNewStyle,
249                                            const ComputedStyle& aOldStyle) {
250   const auto& newSVGReset = *aNewStyle.StyleSVGReset();
251   const auto& oldSVGReset = *aOldStyle.StyleSVGReset();
252   const auto& newPosition = *aNewStyle.StylePosition();
253   const auto& oldPosition = *aOldStyle.StylePosition();
254   return newSVGReset.mX != oldSVGReset.mX || newSVGReset.mY != oldSVGReset.mY ||
255          newPosition.mWidth != oldPosition.mWidth ||
256          newPosition.mHeight != oldPosition.mHeight ||
257          newSVGReset.mRx != oldSVGReset.mRx ||
258          newSVGReset.mRy != oldSVGReset.mRy;
259 }
260 
GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum)261 nsCSSPropertyID SVGRectElement::GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum) {
262   switch (aAttrEnum) {
263     case ATTR_X:
264       return eCSSProperty_x;
265     case ATTR_Y:
266       return eCSSProperty_y;
267     case ATTR_WIDTH:
268       return eCSSProperty_width;
269     case ATTR_HEIGHT:
270       return eCSSProperty_height;
271     case ATTR_RX:
272       return eCSSProperty_rx;
273     case ATTR_RY:
274       return eCSSProperty_ry;
275     default:
276       MOZ_ASSERT_UNREACHABLE("Unknown attr enum");
277       return eCSSProperty_UNKNOWN;
278   }
279 }
280 
281 }  // namespace dom
282 }  // namespace mozilla
283