1 /* 2 * Copyright (C) 2004, 2005, 2006 Nikolas Zimmermann <zimmermann (at) kde.org> 3 * Copyright (C) 2004, 2005, 2006, 2007 Rob Buis <buis (at) 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 38 namespace blink { 39 40 SVGLengthContext::SVGLengthContext(const SVGElement* context) 41 : m_context(context) 42 { 43 } 44 45 SVGLengthContext::SVGLengthContext(const SVGElement* context, const FloatRect& viewport) 46 : m_context(context) 47 , m_overridenViewport(viewport) 48 { 49 } 50 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 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 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 101 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 141 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 173 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 194 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 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 232 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 249 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 260 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 279 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 292 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