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/v8/ExceptionMessages.h"
27 #include "bindings/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
38 namespace WebCore {
39
SVGLengthContext(const SVGElement * context)40 SVGLengthContext::SVGLengthContext(const SVGElement* context)
41 : m_context(context)
42 {
43 }
44
SVGLengthContext(const SVGElement * context,const FloatRect & viewport)45 SVGLengthContext::SVGLengthContext(const SVGElement* context, const FloatRect& viewport)
46 : m_context(context)
47 , m_overridenViewport(viewport)
48 {
49 }
50
resolveRectangle(const SVGElement * context,SVGUnitTypes::SVGUnitType type,const FloatRect & viewport,PassRefPtr<SVGLength> passX,PassRefPtr<SVGLength> passY,PassRefPtr<SVGLength> passWidth,PassRefPtr<SVGLength> passHeight)51 FloatRect 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
resolvePoint(const SVGElement * context,SVGUnitTypes::SVGUnitType type,PassRefPtr<SVGLength> passX,PassRefPtr<SVGLength> passY)72 FloatPoint 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
resolveLength(const SVGElement * context,SVGUnitTypes::SVGUnitType type,PassRefPtr<SVGLength> passX)87 float 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
convertValueToUserUnits(float value,SVGLengthMode mode,SVGLengthType fromUnit,ExceptionState & exceptionState) const101 float 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
convertValueFromUserUnits(float value,SVGLengthMode mode,SVGLengthType toUnit,ExceptionState & exceptionState) const141 float 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
convertValueFromUserUnitsToPercentage(float value,SVGLengthMode mode,ExceptionState & exceptionState) const173 float 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
convertValueFromPercentageToUserUnits(float value,SVGLengthMode mode,ExceptionState & exceptionState) const194 float 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
renderStyleForLengthResolving(const SVGElement * context)215 static 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
convertValueFromUserUnitsToEMS(float value,ExceptionState & exceptionState) const232 float 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
convertValueFromEMSToUserUnits(float value,ExceptionState & exceptionState) const249 float 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
convertValueFromUserUnitsToEXS(float value,ExceptionState & exceptionState) const260 float 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
convertValueFromEXSToUserUnits(float value,ExceptionState & exceptionState) const279 float 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
determineViewport(FloatSize & viewportSize) const292 bool 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