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#include "core/svg/SVGLengthContext.h"
25
26#include "SVGNames.h"
27#include "bindings/v8/ExceptionState.h"
28#include "core/css/CSSHelper.h"
29#include "core/dom/ExceptionCode.h"
30#include "core/rendering/RenderPart.h"
31#include "core/rendering/RenderView.h"
32#include "core/rendering/svg/RenderSVGRoot.h"
33#include "core/rendering/svg/RenderSVGViewportContainer.h"
34#include "core/svg/SVGSVGElement.h"
35#include "platform/fonts/FontMetrics.h"
36
37namespace WebCore {
38
39SVGLengthContext::SVGLengthContext(const SVGElement* context)
40    : m_context(context)
41{
42}
43
44SVGLengthContext::SVGLengthContext(const SVGElement* context, const FloatRect& viewport)
45    : m_context(context)
46    , m_overridenViewport(viewport)
47{
48}
49
50FloatRect SVGLengthContext::resolveRectangle(const SVGElement* context, SVGUnitTypes::SVGUnitType type, const FloatRect& viewport, const SVGLength& x, const SVGLength& y, const SVGLength& width, const SVGLength& height)
51{
52    ASSERT(type != SVGUnitTypes::SVG_UNIT_TYPE_UNKNOWN);
53    if (type == SVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE) {
54        SVGLengthContext lengthContext(context);
55        return FloatRect(x.value(lengthContext), y.value(lengthContext), width.value(lengthContext), height.value(lengthContext));
56    }
57
58    SVGLengthContext lengthContext(context, viewport);
59    return FloatRect(x.value(lengthContext) + viewport.x(),
60                     y.value(lengthContext) + viewport.y(),
61                     width.value(lengthContext),
62                     height.value(lengthContext));
63}
64
65FloatPoint SVGLengthContext::resolvePoint(const SVGElement* context, SVGUnitTypes::SVGUnitType type, const SVGLength& x, const SVGLength& y)
66{
67    ASSERT(type != SVGUnitTypes::SVG_UNIT_TYPE_UNKNOWN);
68    if (type == SVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE) {
69        SVGLengthContext lengthContext(context);
70        return FloatPoint(x.value(lengthContext), y.value(lengthContext));
71    }
72
73    // 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.
74    return FloatPoint(x.valueAsPercentage(), y.valueAsPercentage());
75}
76
77float SVGLengthContext::resolveLength(const SVGElement* context, SVGUnitTypes::SVGUnitType type, const SVGLength& x)
78{
79    ASSERT(type != SVGUnitTypes::SVG_UNIT_TYPE_UNKNOWN);
80    if (type == SVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE) {
81        SVGLengthContext lengthContext(context);
82        return x.value(lengthContext);
83    }
84
85    // 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.
86    return x.valueAsPercentage();
87}
88
89float SVGLengthContext::convertValueToUserUnits(float value, SVGLengthMode mode, SVGLengthType fromUnit, ExceptionState& exceptionState) const
90{
91    // If the SVGLengthContext carries a custom viewport, force resolving against it.
92    if (!m_overridenViewport.isEmpty()) {
93        // 100% = 100.0 instead of 1.0 for historical reasons, this could eventually be changed
94        if (fromUnit == LengthTypePercentage)
95            value /= 100;
96        return convertValueFromPercentageToUserUnits(value, mode, exceptionState);
97    }
98
99    switch (fromUnit) {
100    case LengthTypeUnknown:
101        exceptionState.throwUninformativeAndGenericDOMException(NotSupportedError);
102        return 0;
103    case LengthTypeNumber:
104        return value;
105    case LengthTypePX:
106        return value;
107    case LengthTypePercentage:
108        return convertValueFromPercentageToUserUnits(value / 100, mode, exceptionState);
109    case LengthTypeEMS:
110        return convertValueFromEMSToUserUnits(value, exceptionState);
111    case LengthTypeEXS:
112        return convertValueFromEXSToUserUnits(value, exceptionState);
113    case LengthTypeCM:
114        return value * cssPixelsPerCentimeter;
115    case LengthTypeMM:
116        return value * cssPixelsPerMillimeter;
117    case LengthTypeIN:
118        return value * cssPixelsPerInch;
119    case LengthTypePT:
120        return value * cssPixelsPerPoint;
121    case LengthTypePC:
122        return value * cssPixelsPerPica;
123    }
124
125    ASSERT_NOT_REACHED();
126    return 0;
127}
128
129float SVGLengthContext::convertValueFromUserUnits(float value, SVGLengthMode mode, SVGLengthType toUnit, ExceptionState& exceptionState) const
130{
131    switch (toUnit) {
132    case LengthTypeUnknown:
133        exceptionState.throwUninformativeAndGenericDOMException(NotSupportedError);
134        return 0;
135    case LengthTypeNumber:
136        return value;
137    case LengthTypePercentage:
138        return convertValueFromUserUnitsToPercentage(value * 100, mode, exceptionState);
139    case LengthTypeEMS:
140        return convertValueFromUserUnitsToEMS(value, exceptionState);
141    case LengthTypeEXS:
142        return convertValueFromUserUnitsToEXS(value, exceptionState);
143    case LengthTypePX:
144        return value;
145    case LengthTypeCM:
146        return value / cssPixelsPerCentimeter;
147    case LengthTypeMM:
148        return value / cssPixelsPerMillimeter;
149    case LengthTypeIN:
150        return value / cssPixelsPerInch;
151    case LengthTypePT:
152        return value / cssPixelsPerPoint;
153    case LengthTypePC:
154        return value / cssPixelsPerPica;
155    }
156
157    ASSERT_NOT_REACHED();
158    return 0;
159}
160
161float SVGLengthContext::convertValueFromUserUnitsToPercentage(float value, SVGLengthMode mode, ExceptionState& exceptionState) const
162{
163    FloatSize viewportSize;
164    if (!determineViewport(viewportSize)) {
165        exceptionState.throwUninformativeAndGenericDOMException(NotSupportedError);
166        return 0;
167    }
168
169    switch (mode) {
170    case LengthModeWidth:
171        return value / viewportSize.width() * 100;
172    case LengthModeHeight:
173        return value / viewportSize.height() * 100;
174    case LengthModeOther:
175        return value / sqrtf(viewportSize.diagonalLengthSquared() / 2) * 100;
176    };
177
178    ASSERT_NOT_REACHED();
179    return 0;
180}
181
182float SVGLengthContext::convertValueFromPercentageToUserUnits(float value, SVGLengthMode mode, ExceptionState& exceptionState) const
183{
184    FloatSize viewportSize;
185    if (!determineViewport(viewportSize)) {
186        exceptionState.throwUninformativeAndGenericDOMException(NotSupportedError);
187        return 0;
188    }
189
190    switch (mode) {
191    case LengthModeWidth:
192        return value * viewportSize.width();
193    case LengthModeHeight:
194        return value * viewportSize.height();
195    case LengthModeOther:
196        return value * sqrtf(viewportSize.diagonalLengthSquared() / 2);
197    };
198
199    ASSERT_NOT_REACHED();
200    return 0;
201}
202
203static inline RenderStyle* renderStyleForLengthResolving(const SVGElement* context)
204{
205    if (!context)
206        return 0;
207
208    const ContainerNode* currentContext = context;
209    while (currentContext) {
210        if (currentContext->renderer())
211            return currentContext->renderer()->style();
212        currentContext = currentContext->parentNode();
213    }
214
215    // There must be at least a RenderSVGRoot renderer, carrying a style.
216    ASSERT_NOT_REACHED();
217    return 0;
218}
219
220float SVGLengthContext::convertValueFromUserUnitsToEMS(float value, ExceptionState& exceptionState) const
221{
222    RenderStyle* style = renderStyleForLengthResolving(m_context);
223    if (!style) {
224        exceptionState.throwUninformativeAndGenericDOMException(NotSupportedError);
225        return 0;
226    }
227
228    float fontSize = style->specifiedFontSize();
229    if (!fontSize) {
230        exceptionState.throwUninformativeAndGenericDOMException(NotSupportedError);
231        return 0;
232    }
233
234    return value / fontSize;
235}
236
237float SVGLengthContext::convertValueFromEMSToUserUnits(float value, ExceptionState& exceptionState) const
238{
239    RenderStyle* style = renderStyleForLengthResolving(m_context);
240    if (!style) {
241        exceptionState.throwUninformativeAndGenericDOMException(NotSupportedError);
242        return 0;
243    }
244
245    return value * style->specifiedFontSize();
246}
247
248float SVGLengthContext::convertValueFromUserUnitsToEXS(float value, ExceptionState& exceptionState) const
249{
250    RenderStyle* style = renderStyleForLengthResolving(m_context);
251    if (!style) {
252        exceptionState.throwUninformativeAndGenericDOMException(NotSupportedError);
253        return 0;
254    }
255
256    // Use of ceil allows a pixel match to the W3Cs expected output of coords-units-03-b.svg
257    // if this causes problems in real world cases maybe it would be best to remove this
258    float xHeight = ceilf(style->fontMetrics().xHeight());
259    if (!xHeight) {
260        exceptionState.throwUninformativeAndGenericDOMException(NotSupportedError);
261        return 0;
262    }
263
264    return value / xHeight;
265}
266
267float SVGLengthContext::convertValueFromEXSToUserUnits(float value, ExceptionState& exceptionState) const
268{
269    RenderStyle* style = renderStyleForLengthResolving(m_context);
270    if (!style) {
271        exceptionState.throwUninformativeAndGenericDOMException(NotSupportedError);
272        return 0;
273    }
274
275    // Use of ceil allows a pixel match to the W3Cs expected output of coords-units-03-b.svg
276    // if this causes problems in real world cases maybe it would be best to remove this
277    return value * ceilf(style->fontMetrics().xHeight());
278}
279
280bool SVGLengthContext::determineViewport(FloatSize& viewportSize) const
281{
282    if (!m_context)
283        return false;
284
285    // If an overriden viewport is given, it has precedence.
286    if (!m_overridenViewport.isEmpty()) {
287        viewportSize = m_overridenViewport.size();
288        return true;
289    }
290
291    // Root <svg> element lengths are resolved against the top level viewport.
292    if (m_context->isOutermostSVGSVGElement()) {
293        viewportSize = toSVGSVGElement(m_context)->currentViewportSize();
294        return true;
295    }
296
297    // Take size from nearest viewport element.
298    SVGElement* viewportElement = m_context->viewportElement();
299    if (!viewportElement || !viewportElement->isSVGSVGElement())
300        return false;
301
302    const SVGSVGElement* svg = toSVGSVGElement(viewportElement);
303    viewportSize = svg->currentViewBoxRect().size();
304    if (viewportSize.isEmpty())
305        viewportSize = svg->currentViewportSize();
306
307    return true;
308}
309
310}
311