Home | History | Annotate | Download | only in svg
      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 "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 
     39 SVGLengthContext::SVGLengthContext(const SVGElement* context)
     40     : m_context(context)
     41 {
     42 }
     43 
     44 SVGLengthContext::SVGLengthContext(const SVGElement* context, const FloatRect& viewport)
     45     : m_context(context)
     46     , m_overridenViewport(viewport)
     47 {
     48 }
     49 
     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 
     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 
     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 
     89 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 
    129 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 
    161 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 
    182 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 
    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 
    220 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 
    237 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 
    248 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 
    267 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 
    280 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