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