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