1/*
2 * Copyright (C) 2004, 2005, 2006 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2004, 2005, 2006, 2007 Rob Buis <buis@kde.org>
4 * Copyright (C) 2007 Apple Inc. All rights reserved.
5 * Copyright (C) Research In Motion Limited 2011. All rights reserved.
6 * Copyright (C) 2014 Adobe Systems Incorporated. All rights reserved.
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23
24#include "config.h"
25#include "SVGLengthContext.h"
26
27#include "CSSHelper.h"
28#include "FontMetrics.h"
29#include "Frame.h"
30#include "LengthFunctions.h"
31#include "RenderSVGRoot.h"
32#include "RenderSVGViewportContainer.h"
33#include "RenderView.h"
34#include "SVGSVGElement.h"
35
36namespace WebCore {
37
38SVGLengthContext::SVGLengthContext(const SVGElement* context)
39 : m_context(context)
40{
41}
42
43SVGLengthContext::SVGLengthContext(const SVGElement* context, const FloatRect& viewport)
44 : m_context(context)
45 , m_overriddenViewport(viewport)
46{
47}
48
49FloatRect SVGLengthContext::resolveRectangle(const SVGElement* context, SVGUnitTypes::SVGUnitType type, const FloatRect& viewport, const SVGLengthValue& x, const SVGLengthValue& y, const SVGLengthValue& width, const SVGLengthValue& height)
50{
51 ASSERT(type != SVGUnitTypes::SVG_UNIT_TYPE_UNKNOWN);
52 if (type == SVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE) {
53 SVGLengthContext lengthContext(context);
54 return FloatRect(x.value(lengthContext), y.value(lengthContext), width.value(lengthContext), height.value(lengthContext));
55 }
56
57 SVGLengthContext lengthContext(context, viewport);
58 return FloatRect(x.value(lengthContext) + viewport.x(),
59 y.value(lengthContext) + viewport.y(),
60 width.value(lengthContext),
61 height.value(lengthContext));
62}
63
64FloatPoint SVGLengthContext::resolvePoint(const SVGElement* context, SVGUnitTypes::SVGUnitType type, const SVGLengthValue& x, const SVGLengthValue& y)
65{
66 ASSERT(type != SVGUnitTypes::SVG_UNIT_TYPE_UNKNOWN);
67 if (type == SVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE) {
68 SVGLengthContext lengthContext(context);
69 return FloatPoint(x.value(lengthContext), y.value(lengthContext));
70 }
71
72 // FIXME: valueAsPercentage() won't be correct for eg. cm units. They need to be resolved in user space and then be considered in objectBoundingBox space.
73 return FloatPoint(x.valueAsPercentage(), y.valueAsPercentage());
74}
75
76float SVGLengthContext::resolveLength(const SVGElement* context, SVGUnitTypes::SVGUnitType type, const SVGLengthValue& x)
77{
78 ASSERT(type != SVGUnitTypes::SVG_UNIT_TYPE_UNKNOWN);
79 if (type == SVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE) {
80 SVGLengthContext lengthContext(context);
81 return x.value(lengthContext);
82 }
83
84 // FIXME: valueAsPercentage() won't be correct for eg. cm units. They need to be resolved in user space and then be considered in objectBoundingBox space.
85 return x.valueAsPercentage();
86}
87
88float SVGLengthContext::valueForLength(const Length& length, SVGLengthMode mode)
89{
90 if (length.isPercent()) {
91 auto result = convertValueFromPercentageToUserUnits(length.value() / 100, mode);
92 if (result.hasException())
93 return 0;
94 return result.releaseReturnValue();
95 }
96 if (length.isAuto() || !length.isSpecified())
97 return 0;
98
99 FloatSize viewportSize;
100 determineViewport(viewportSize);
101
102 switch (mode) {
103 case LengthModeWidth:
104 return floatValueForLength(length, viewportSize.width());
105 case LengthModeHeight:
106 return floatValueForLength(length, viewportSize.height());
107 case LengthModeOther:
108 return floatValueForLength(length, std::sqrt(viewportSize.diagonalLengthSquared() / 2));
109 };
110 return 0;
111}
112
113ExceptionOr<float> SVGLengthContext::convertValueToUserUnits(float value, SVGLengthMode mode, SVGLengthType fromUnit) const
114{
115 // If the SVGLengthContext carries a custom viewport, force resolving against it.
116 if (!m_overriddenViewport.isEmpty()) {
117 // 100% = 100.0 instead of 1.0 for historical reasons, this could eventually be changed
118 if (fromUnit == LengthTypePercentage)
119 value /= 100;
120 return convertValueFromPercentageToUserUnits(value, mode);
121 }
122
123 switch (fromUnit) {
124 case LengthTypeUnknown:
125 return Exception { NotSupportedError };
126 case LengthTypeNumber:
127 return value;
128 case LengthTypePX:
129 return value;
130 case LengthTypePercentage:
131 return convertValueFromPercentageToUserUnits(value / 100, mode);
132 case LengthTypeEMS:
133 return convertValueFromEMSToUserUnits(value);
134 case LengthTypeEXS:
135 return convertValueFromEXSToUserUnits(value);
136 case LengthTypeCM:
137 return value * cssPixelsPerInch / 2.54f;
138 case LengthTypeMM:
139 return value * cssPixelsPerInch / 25.4f;
140 case LengthTypeIN:
141 return value * cssPixelsPerInch;
142 case LengthTypePT:
143 return value * cssPixelsPerInch / 72;
144 case LengthTypePC:
145 return value * cssPixelsPerInch / 6;
146 }
147
148 ASSERT_NOT_REACHED();
149 return 0;
150}
151
152ExceptionOr<float> SVGLengthContext::convertValueFromUserUnits(float value, SVGLengthMode mode, SVGLengthType toUnit) const
153{
154 switch (toUnit) {
155 case LengthTypeUnknown:
156 return Exception { NotSupportedError };
157 case LengthTypeNumber:
158 return value;
159 case LengthTypePercentage:
160 return convertValueFromUserUnitsToPercentage(value * 100, mode);
161 case LengthTypeEMS:
162 return convertValueFromUserUnitsToEMS(value);
163 case LengthTypeEXS:
164 return convertValueFromUserUnitsToEXS(value);
165 case LengthTypePX:
166 return value;
167 case LengthTypeCM:
168 return value * 2.54f / cssPixelsPerInch;
169 case LengthTypeMM:
170 return value * 25.4f / cssPixelsPerInch;
171 case LengthTypeIN:
172 return value / cssPixelsPerInch;
173 case LengthTypePT:
174 return value * 72 / cssPixelsPerInch;
175 case LengthTypePC:
176 return value * 6 / cssPixelsPerInch;
177 }
178
179 ASSERT_NOT_REACHED();
180 return 0;
181}
182
183ExceptionOr<float> SVGLengthContext::convertValueFromUserUnitsToPercentage(float value, SVGLengthMode mode) const
184{
185 FloatSize viewportSize;
186 if (!determineViewport(viewportSize))
187 return Exception { NotSupportedError };
188
189 switch (mode) {
190 case LengthModeWidth:
191 return value / viewportSize.width() * 100;
192 case LengthModeHeight:
193 return value / viewportSize.height() * 100;
194 case LengthModeOther:
195 return value / (std::sqrt(viewportSize.diagonalLengthSquared() / 2)) * 100;
196 };
197
198 ASSERT_NOT_REACHED();
199 return 0;
200}
201
202ExceptionOr<float> SVGLengthContext::convertValueFromPercentageToUserUnits(float value, SVGLengthMode mode) const
203{
204 FloatSize viewportSize;
205 if (!determineViewport(viewportSize))
206 return Exception { NotSupportedError };
207
208 switch (mode) {
209 case LengthModeWidth:
210 return value * viewportSize.width();
211 case LengthModeHeight:
212 return value * viewportSize.height();
213 case LengthModeOther:
214 return value * std::sqrt(viewportSize.diagonalLengthSquared() / 2);
215 };
216
217 ASSERT_NOT_REACHED();
218 return 0;
219}
220
221static inline const RenderStyle* renderStyleForLengthResolving(const SVGElement* context)
222{
223 if (!context)
224 return nullptr;
225
226 const ContainerNode* currentContext = context;
227 do {
228 if (currentContext->renderer())
229 return &currentContext->renderer()->style();
230 currentContext = currentContext->parentNode();
231 } while (currentContext);
232
233 // There must be at least a RenderSVGRoot renderer, carrying a style.
234 ASSERT_NOT_REACHED();
235 return nullptr;
236}
237
238ExceptionOr<float> SVGLengthContext::convertValueFromUserUnitsToEMS(float value) const
239{
240 auto* style = renderStyleForLengthResolving(m_context);
241 if (!style)
242 return Exception { NotSupportedError };
243
244 float fontSize = style->computedFontPixelSize();
245 if (!fontSize)
246 return Exception { NotSupportedError };
247
248 return value / fontSize;
249}
250
251ExceptionOr<float> SVGLengthContext::convertValueFromEMSToUserUnits(float value) const
252{
253 auto* style = renderStyleForLengthResolving(m_context);
254 if (!style)
255 return Exception { NotSupportedError };
256
257 return value * style->computedFontPixelSize();
258}
259
260ExceptionOr<float> SVGLengthContext::convertValueFromUserUnitsToEXS(float value) const
261{
262 auto* style = renderStyleForLengthResolving(m_context);
263 if (!style)
264 return Exception { NotSupportedError };
265
266 // Use of ceil allows a pixel match to the W3Cs expected output of coords-units-03-b.svg
267 // if this causes problems in real world cases maybe it would be best to remove this
268 float xHeight = std::ceil(style->fontMetrics().xHeight());
269 if (!xHeight)
270 return Exception { NotSupportedError };
271
272 return value / xHeight;
273}
274
275ExceptionOr<float> SVGLengthContext::convertValueFromEXSToUserUnits(float value) const
276{
277 auto* style = renderStyleForLengthResolving(m_context);
278 if (!style)
279 return Exception { NotSupportedError };
280
281 // Use of ceil allows a pixel match to the W3Cs expected output of coords-units-03-b.svg
282 // if this causes problems in real world cases maybe it would be best to remove this
283 return value * std::ceil(style->fontMetrics().xHeight());
284}
285
286bool SVGLengthContext::determineViewport(FloatSize& viewportSize) const
287{
288 if (!m_context)
289 return false;
290
291 // If an overridden viewport is given, it has precedence.
292 if (!m_overriddenViewport.isEmpty()) {
293 viewportSize = m_overriddenViewport.size();
294 return true;
295 }
296
297 // Root <svg> element lengths are resolved against the top level viewport.
298 if (m_context->isOutermostSVGSVGElement()) {
299 viewportSize = downcast<SVGSVGElement>(*m_context).currentViewportSize();
300 return true;
301 }
302
303 // Take size from nearest viewport element.
304 auto viewportElement = makeRefPtr(m_context->viewportElement());
305 if (!is<SVGSVGElement>(viewportElement))
306 return false;
307
308 const SVGSVGElement& svg = downcast<SVGSVGElement>(*viewportElement);
309 viewportSize = svg.currentViewBoxRect().size();
310 if (viewportSize.isEmpty())
311 viewportSize = svg.currentViewportSize();
312
313 return true;
314}
315
316}
317