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 #ifndef DOM_SVG_SVGGEOMETRYPROPERTY_H_
8 #define DOM_SVG_SVGGEOMETRYPROPERTY_H_
9 
10 #include "mozilla/SVGImageFrame.h"
11 #include "mozilla/dom/SVGElement.h"
12 #include "ComputedStyle.h"
13 #include "SVGAnimatedLength.h"
14 #include "nsComputedDOMStyle.h"
15 #include "nsGkAtoms.h"
16 #include "nsIFrame.h"
17 #include <type_traits>
18 
19 namespace mozilla {
20 namespace dom {
21 
22 namespace SVGGeometryProperty {
23 namespace ResolverTypes {
24 struct LengthPercentNoAuto {};
25 struct LengthPercentRXY {};
26 struct LengthPercentWidthHeight {};
27 }  // namespace ResolverTypes
28 
29 namespace Tags {
30 
31 #define SVGGEOMETRYPROPERTY_GENERATETAG(tagName, resolver, direction, \
32                                         styleStruct)                  \
33   struct tagName {                                                    \
34     using ResolverType = ResolverTypes::resolver;                     \
35     constexpr static auto CtxDirection = SVGContentUtils::direction;  \
36     constexpr static auto Getter = &styleStruct::m##tagName;          \
37   }
38 
39 SVGGEOMETRYPROPERTY_GENERATETAG(X, LengthPercentNoAuto, X, nsStyleSVGReset);
40 SVGGEOMETRYPROPERTY_GENERATETAG(Y, LengthPercentNoAuto, Y, nsStyleSVGReset);
41 SVGGEOMETRYPROPERTY_GENERATETAG(Cx, LengthPercentNoAuto, X, nsStyleSVGReset);
42 SVGGEOMETRYPROPERTY_GENERATETAG(Cy, LengthPercentNoAuto, Y, nsStyleSVGReset);
43 SVGGEOMETRYPROPERTY_GENERATETAG(R, LengthPercentNoAuto, XY, nsStyleSVGReset);
44 
45 #undef SVGGEOMETRYPROPERTY_GENERATETAG
46 
47 struct Height;
48 struct Width {
49   using ResolverType = ResolverTypes::LengthPercentWidthHeight;
50   constexpr static auto CtxDirection = SVGContentUtils::X;
51   constexpr static auto Getter = &nsStylePosition::mWidth;
52   constexpr static auto SizeGetter = &gfx::Size::width;
AspectRatioRelativeWidth53   static AspectRatio AspectRatioRelative(AspectRatio aAspectRatio) {
54     return aAspectRatio.Inverted();
55   }
56   constexpr static uint32_t DefaultObjectSize = 300;
57   using CounterPart = Height;
58 };
59 struct Height {
60   using ResolverType = ResolverTypes::LengthPercentWidthHeight;
61   constexpr static auto CtxDirection = SVGContentUtils::Y;
62   constexpr static auto Getter = &nsStylePosition::mHeight;
63   constexpr static auto SizeGetter = &gfx::Size::height;
AspectRatioRelativeHeight64   static AspectRatio AspectRatioRelative(AspectRatio aAspectRatio) {
65     return aAspectRatio;
66   }
67   constexpr static uint32_t DefaultObjectSize = 150;
68   using CounterPart = Width;
69 };
70 
71 struct Ry;
72 struct Rx {
73   using ResolverType = ResolverTypes::LengthPercentRXY;
74   constexpr static auto CtxDirection = SVGContentUtils::X;
75   constexpr static auto Getter = &nsStyleSVGReset::mRx;
76   using CounterPart = Ry;
77 };
78 struct Ry {
79   using ResolverType = ResolverTypes::LengthPercentRXY;
80   constexpr static auto CtxDirection = SVGContentUtils::Y;
81   constexpr static auto Getter = &nsStyleSVGReset::mRy;
82   using CounterPart = Rx;
83 };
84 
85 }  // namespace Tags
86 
87 namespace details {
88 template <class T>
89 using AlwaysFloat = float;
90 using dummy = int[];
91 
92 using CtxDirectionType = decltype(SVGContentUtils::X);
93 
94 template <CtxDirectionType CTD>
ResolvePureLengthPercentage(SVGElement * aElement,const LengthPercentage & aLP)95 float ResolvePureLengthPercentage(SVGElement* aElement,
96                                   const LengthPercentage& aLP) {
97   return aLP.ResolveToCSSPixelsWith(
98       [&] { return CSSCoord{SVGElementMetrics(aElement).GetAxisLength(CTD)}; });
99 }
100 
101 template <class Tag>
ResolveImpl(ComputedStyle const & aStyle,SVGElement * aElement,ResolverTypes::LengthPercentNoAuto)102 float ResolveImpl(ComputedStyle const& aStyle, SVGElement* aElement,
103                   ResolverTypes::LengthPercentNoAuto) {
104   auto const& value = aStyle.StyleSVGReset()->*Tag::Getter;
105   return ResolvePureLengthPercentage<Tag::CtxDirection>(aElement, value);
106 }
107 
108 template <class Tag>
ResolveImpl(ComputedStyle const & aStyle,SVGElement * aElement,ResolverTypes::LengthPercentWidthHeight)109 float ResolveImpl(ComputedStyle const& aStyle, SVGElement* aElement,
110                   ResolverTypes::LengthPercentWidthHeight) {
111   static_assert(
112       std::is_same<Tag, Tags::Width>{} || std::is_same<Tag, Tags::Height>{},
113       "Wrong tag");
114 
115   auto const& value = aStyle.StylePosition()->*Tag::Getter;
116   if (value.IsLengthPercentage()) {
117     return ResolvePureLengthPercentage<Tag::CtxDirection>(
118         aElement, value.AsLengthPercentage());
119   }
120 
121   if (aElement->IsSVGElement(nsGkAtoms::image)) {
122     // It's not clear per SVG2 spec what should be done for values other
123     // than |auto| (e.g. |max-content|). We treat them as nonsense, thus
124     // using the initial value behavior, i.e. |auto|.
125     // The following procedure follows the Default Sizing Algorithm as
126     // specified in:
127     // https://svgwg.org/svg2-draft/embedded.html#ImageElement
128 
129     SVGImageFrame* imgf = do_QueryFrame(aElement->GetPrimaryFrame());
130     MOZ_ASSERT(imgf);
131 
132     using Other = typename Tag::CounterPart;
133     auto const& valueOther = aStyle.StylePosition()->*Other::Getter;
134 
135     gfx::Size intrinsicImageSize;
136     AspectRatio aspectRatio;
137     if (!imgf->GetIntrinsicImageDimensions(intrinsicImageSize, aspectRatio)) {
138       // No image container, just return 0.
139       return 0.f;
140     }
141 
142     if (valueOther.IsLengthPercentage()) {
143       // We are |auto|, but the other side has specifed length.
144       float lengthOther = ResolvePureLengthPercentage<Other::CtxDirection>(
145           aElement, valueOther.AsLengthPercentage());
146 
147       if (aspectRatio) {
148         // Preserve aspect ratio if it's present.
149         return Other::AspectRatioRelative(aspectRatio)
150             .ApplyToFloat(lengthOther);
151       }
152 
153       float intrinsicLength = intrinsicImageSize.*Tag::SizeGetter;
154       if (intrinsicLength >= 0) {
155         // Use the intrinsic length if it's present.
156         return intrinsicLength;
157       }
158 
159       // No specified size, no aspect ratio, no intrinsic length,
160       // then use default size.
161       return Tag::DefaultObjectSize;
162     }
163 
164     // |width| and |height| are both |auto|
165     if (intrinsicImageSize.*Tag::SizeGetter >= 0) {
166       return intrinsicImageSize.*Tag::SizeGetter;
167     }
168 
169     if (intrinsicImageSize.*Other::SizeGetter >= 0 && aspectRatio) {
170       return Other::AspectRatioRelative(aspectRatio)
171           .ApplyTo(intrinsicImageSize.*Other::SizeGetter);
172     }
173 
174     if (aspectRatio) {
175       // Resolve as a contain constraint against the default object size.
176       auto defaultAspectRatioRelative =
177           AspectRatio{float(Other::DefaultObjectSize) / Tag::DefaultObjectSize};
178       auto aspectRatioRelative = Tag::AspectRatioRelative(aspectRatio);
179 
180       if (defaultAspectRatioRelative < aspectRatioRelative) {
181         // Using default length in our side and the intrinsic aspect ratio,
182         // the other side cannot be contained.
183         return aspectRatioRelative.Inverted().ApplyTo(Other::DefaultObjectSize);
184       }
185 
186       return Tag::DefaultObjectSize;
187     }
188 
189     return Tag::DefaultObjectSize;
190   }
191 
192   // For other elements, |auto| and |max-content| etc. are treated as 0.
193   return 0.f;
194 }
195 
196 template <class Tag>
ResolveImpl(ComputedStyle const & aStyle,SVGElement * aElement,ResolverTypes::LengthPercentRXY)197 float ResolveImpl(ComputedStyle const& aStyle, SVGElement* aElement,
198                   ResolverTypes::LengthPercentRXY) {
199   static_assert(std::is_same<Tag, Tags::Rx>{} || std::is_same<Tag, Tags::Ry>{},
200                 "Wrong tag");
201 
202   auto const& value = aStyle.StyleSVGReset()->*Tag::Getter;
203   if (value.IsLengthPercentage()) {
204     return ResolvePureLengthPercentage<Tag::CtxDirection>(
205         aElement, value.AsLengthPercentage());
206   }
207 
208   MOZ_ASSERT(value.IsAuto());
209   using Rother = typename Tag::CounterPart;
210   auto const& valueOther = aStyle.StyleSVGReset()->*Rother::Getter;
211 
212   if (valueOther.IsAuto()) {
213     // Per SVG2, |Rx|, |Ry| resolve to 0 if both are |auto|
214     return 0.f;
215   }
216 
217   // If |Rx| is auto while |Ry| not, |Rx| gets the value of |Ry|.
218   return ResolvePureLengthPercentage<Rother::CtxDirection>(
219       aElement, valueOther.AsLengthPercentage());
220 }
221 
222 }  // namespace details
223 
224 template <class Tag>
ResolveWith(const ComputedStyle & aStyle,const SVGElement * aElement)225 float ResolveWith(const ComputedStyle& aStyle, const SVGElement* aElement) {
226   // TODO: There are a lot of utilities lacking const-ness in dom/svg.
227   // We should fix that problem and remove this `const_cast`.
228   return details::ResolveImpl<Tag>(aStyle, const_cast<SVGElement*>(aElement),
229                                    typename Tag::ResolverType{});
230 }
231 
232 template <class Func>
DoForComputedStyle(const SVGElement * aElement,Func aFunc)233 bool DoForComputedStyle(const SVGElement* aElement, Func aFunc) {
234   if (const nsIFrame* f = aElement->GetPrimaryFrame()) {
235     aFunc(f->Style());
236     return true;
237   }
238 
239   if (RefPtr<ComputedStyle> computedStyle =
240           nsComputedDOMStyle::GetComputedStyleNoFlush(aElement, nullptr)) {
241     aFunc(computedStyle.get());
242     return true;
243   }
244 
245   return false;
246 }
247 
248 #define SVGGEOMETRYPROPERTY_EVAL_ALL(expr) \
249   (void)details::dummy { 0, (static_cast<void>(expr), 0)... }
250 
251 // To add support for new properties, or to handle special cases for
252 // existing properties, you can add a new tag in |Tags| and |ResolverTypes|
253 // namespace, then implement the behavior in |details::ResolveImpl|.
254 template <class... Tags>
ResolveAll(const SVGElement * aElement,details::AlwaysFloat<Tags> * ...aRes)255 bool ResolveAll(const SVGElement* aElement,
256                 details::AlwaysFloat<Tags>*... aRes) {
257   bool res = DoForComputedStyle(aElement, [&](auto const* style) {
258     SVGGEOMETRYPROPERTY_EVAL_ALL(*aRes = ResolveWith<Tags>(*style, aElement));
259   });
260 
261   if (res) {
262     return true;
263   }
264 
265   SVGGEOMETRYPROPERTY_EVAL_ALL(*aRes = 0);
266   return false;
267 }
268 
269 #undef SVGGEOMETRYPROPERTY_EVAL_ALL
270 
271 nsCSSUnit SpecifiedUnitTypeToCSSUnit(uint8_t aSpecifiedUnit);
272 nsCSSPropertyID AttrEnumToCSSPropId(const SVGElement* aElement,
273                                     uint8_t aAttrEnum);
274 
275 bool IsNonNegativeGeometryProperty(nsCSSPropertyID aProp);
276 bool ElementMapsLengthsToStyle(SVGElement const* aElement);
277 
278 }  // namespace SVGGeometryProperty
279 }  // namespace dom
280 }  // namespace mozilla
281 
282 #endif  // DOM_SVG_SVGGEOMETRYPROPERTY_H_
283