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 "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