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