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