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
37 namespace WebCore {
38
SVGLengthContext(const SVGElement * context)39 SVGLengthContext::SVGLengthContext(const SVGElement* context)
40 : m_context(context)
41 {
42 }
43
SVGLengthContext(const SVGElement * context,const FloatRect & viewport)44 SVGLengthContext::SVGLengthContext(const SVGElement* context, const FloatRect& viewport)
45 : m_context(context)
46 , m_overridenViewport(viewport)
47 {
48 }
49
resolveRectangle(const SVGElement * context,SVGUnitTypes::SVGUnitType type,const FloatRect & viewport,const SVGLength & x,const SVGLength & y,const SVGLength & width,const SVGLength & height)50 FloatRect 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
resolvePoint(const SVGElement * context,SVGUnitTypes::SVGUnitType type,const SVGLength & x,const SVGLength & y)65 FloatPoint 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
resolveLength(const SVGElement * context,SVGUnitTypes::SVGUnitType type,const SVGLength & x)77 float 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
convertValueToUserUnits(float value,SVGLengthMode mode,SVGLengthType fromUnit,ExceptionState & exceptionState) const89 float 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
convertValueFromUserUnits(float value,SVGLengthMode mode,SVGLengthType toUnit,ExceptionState & exceptionState) const129 float 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
convertValueFromUserUnitsToPercentage(float value,SVGLengthMode mode,ExceptionState & exceptionState) const161 float 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
convertValueFromPercentageToUserUnits(float value,SVGLengthMode mode,ExceptionState & exceptionState) const182 float 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
renderStyleForLengthResolving(const SVGElement * context)203 static 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
convertValueFromUserUnitsToEMS(float value,ExceptionState & exceptionState) const220 float 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
convertValueFromEMSToUserUnits(float value,ExceptionState & exceptionState) const237 float 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
convertValueFromUserUnitsToEXS(float value,ExceptionState & exceptionState) const248 float 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
convertValueFromEXSToUserUnits(float value,ExceptionState & exceptionState) const267 float 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
determineViewport(FloatSize & viewportSize) const280 bool 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