Home | History | Annotate | Download | only in svg
      1 /*
      2  * Copyright (C) 2004, 2005, 2008 Nikolas Zimmermann <zimmermann (at) kde.org>
      3  * Copyright (C) 2004, 2005, 2006 Rob Buis <buis (at) kde.org>
      4  * Copyright (C) 2014 Google, Inc.
      5  *
      6  * This library is free software; you can redistribute it and/or
      7  * modify it under the terms of the GNU Library General Public
      8  * License as published by the Free Software Foundation; either
      9  * version 2 of the License, or (at your option) any later version.
     10  *
     11  * This library is distributed in the hope that it will be useful,
     12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     14  * Library General Public License for more details.
     15  *
     16  * You should have received a copy of the GNU Library General Public License
     17  * along with this library; see the file COPYING.LIB.  If not, write to
     18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     19  * Boston, MA 02110-1301, USA.
     20  */
     21 
     22 #include "config.h"
     23 
     24 #include "core/svg/SVGGraphicsElement.h"
     25 
     26 #include "core/SVGNames.h"
     27 #include "core/rendering/svg/RenderSVGPath.h"
     28 #include "core/rendering/svg/RenderSVGResource.h"
     29 #include "core/rendering/svg/SVGPathData.h"
     30 #include "platform/transforms/AffineTransform.h"
     31 
     32 namespace blink {
     33 
     34 SVGGraphicsElement::SVGGraphicsElement(const QualifiedName& tagName, Document& document, ConstructionType constructionType)
     35     : SVGElement(tagName, document, constructionType)
     36     , SVGTests(this)
     37     , m_transform(SVGAnimatedTransformList::create(this, SVGNames::transformAttr, SVGTransformList::create()))
     38 {
     39     addToPropertyMap(m_transform);
     40 }
     41 
     42 SVGGraphicsElement::~SVGGraphicsElement()
     43 {
     44 }
     45 
     46 PassRefPtr<SVGMatrixTearOff> SVGGraphicsElement::getTransformToElement(SVGElement* target, ExceptionState& exceptionState)
     47 {
     48     AffineTransform ctm = getCTM(AllowStyleUpdate);
     49 
     50     if (target && target->isSVGGraphicsElement()) {
     51         AffineTransform targetCTM = toSVGGraphicsElement(target)->getCTM(AllowStyleUpdate);
     52         if (!targetCTM.isInvertible()) {
     53             exceptionState.throwDOMException(InvalidStateError, "The target transformation is not invertable.");
     54             return nullptr;
     55         }
     56         ctm = targetCTM.inverse() * ctm;
     57     }
     58 
     59     return SVGMatrixTearOff::create(ctm);
     60 }
     61 
     62 static bool isViewportElement(const Element& element)
     63 {
     64     return (isSVGSVGElement(element)
     65         || isSVGSymbolElement(element)
     66         || isSVGForeignObjectElement(element)
     67         || isSVGImageElement(element));
     68 }
     69 
     70 AffineTransform SVGGraphicsElement::computeCTM(SVGElement::CTMScope mode,
     71     SVGGraphicsElement::StyleUpdateStrategy styleUpdateStrategy, const SVGGraphicsElement* ancestor) const
     72 {
     73     if (styleUpdateStrategy == AllowStyleUpdate)
     74         document().updateLayoutIgnorePendingStylesheets();
     75 
     76     AffineTransform ctm;
     77     bool done = false;
     78 
     79     for (const Element* currentElement = this; currentElement && !done;
     80         currentElement = currentElement->parentOrShadowHostElement()) {
     81         if (!currentElement->isSVGElement())
     82             break;
     83 
     84         ctm = toSVGElement(currentElement)->localCoordinateSpaceTransform(mode).multiply(ctm);
     85 
     86         switch (mode) {
     87         case NearestViewportScope:
     88             // Stop at the nearest viewport ancestor.
     89             done = currentElement != this && isViewportElement(*currentElement);
     90             break;
     91         case AncestorScope:
     92             // Stop at the designated ancestor.
     93             done = currentElement == ancestor;
     94             break;
     95         default:
     96             ASSERT(mode == ScreenScope);
     97             break;
     98         }
     99     }
    100 
    101     return ctm;
    102 }
    103 
    104 AffineTransform SVGGraphicsElement::getCTM(StyleUpdateStrategy styleUpdateStrategy)
    105 {
    106     return computeCTM(NearestViewportScope, styleUpdateStrategy);
    107 }
    108 
    109 AffineTransform SVGGraphicsElement::getScreenCTM(StyleUpdateStrategy styleUpdateStrategy)
    110 {
    111     return computeCTM(ScreenScope, styleUpdateStrategy);
    112 }
    113 
    114 PassRefPtr<SVGMatrixTearOff> SVGGraphicsElement::getCTMFromJavascript()
    115 {
    116     return SVGMatrixTearOff::create(getCTM());
    117 }
    118 
    119 PassRefPtr<SVGMatrixTearOff> SVGGraphicsElement::getScreenCTMFromJavascript()
    120 {
    121     return SVGMatrixTearOff::create(getScreenCTM());
    122 }
    123 
    124 AffineTransform SVGGraphicsElement::animatedLocalTransform() const
    125 {
    126     AffineTransform matrix;
    127     RenderStyle* style = renderer() ? renderer()->style() : 0;
    128 
    129     // If CSS property was set, use that, otherwise fallback to attribute (if set).
    130     if (style && style->hasTransform()) {
    131         TransformationMatrix transform;
    132         float zoom = style->effectiveZoom();
    133 
    134         // CSS transforms operate with pre-scaled lengths. To make this work with SVG
    135         // (which applies the zoom factor globally, at the root level) we
    136         //
    137         //   * pre-scale the bounding box (to bring it into the same space as the other CSS values)
    138         //   * invert the zoom factor (to effectively compute the CSS transform under a 1.0 zoom)
    139         //
    140         // Note: objectBoundingBox is an emptyRect for elements like pattern or clipPath.
    141         // See the "Object bounding box units" section of http://dev.w3.org/csswg/css3-transforms/
    142         if (zoom != 1) {
    143             FloatRect scaledBBox = renderer()->objectBoundingBox();
    144             scaledBBox.scale(zoom);
    145             transform.scale(1 / zoom);
    146             style->applyTransform(transform, scaledBBox);
    147             transform.scale(zoom);
    148         } else {
    149             style->applyTransform(transform, renderer()->objectBoundingBox());
    150         }
    151 
    152         // Flatten any 3D transform.
    153         matrix = transform.toAffineTransform();
    154     } else {
    155         m_transform->currentValue()->concatenate(matrix);
    156     }
    157 
    158     if (m_supplementalTransform)
    159         return *m_supplementalTransform * matrix;
    160     return matrix;
    161 }
    162 
    163 AffineTransform* SVGGraphicsElement::supplementalTransform()
    164 {
    165     if (!m_supplementalTransform)
    166         m_supplementalTransform = adoptPtr(new AffineTransform);
    167     return m_supplementalTransform.get();
    168 }
    169 
    170 bool SVGGraphicsElement::isSupportedAttribute(const QualifiedName& attrName)
    171 {
    172     DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, supportedAttributes, ());
    173     if (supportedAttributes.isEmpty()) {
    174         SVGTests::addSupportedAttributes(supportedAttributes);
    175         supportedAttributes.add(SVGNames::transformAttr);
    176     }
    177     return supportedAttributes.contains<SVGAttributeHashTranslator>(attrName);
    178 }
    179 
    180 void SVGGraphicsElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
    181 {
    182     parseAttributeNew(name, value);
    183 }
    184 
    185 void SVGGraphicsElement::svgAttributeChanged(const QualifiedName& attrName)
    186 {
    187     if (!isSupportedAttribute(attrName)) {
    188         SVGElement::svgAttributeChanged(attrName);
    189         return;
    190     }
    191 
    192     SVGElement::InvalidationGuard invalidationGuard(this);
    193 
    194     // Reattach so the isValid() check will be run again during renderer creation.
    195     if (SVGTests::isKnownAttribute(attrName)) {
    196         lazyReattachIfAttached();
    197         return;
    198     }
    199 
    200     RenderObject* object = renderer();
    201     if (!object)
    202         return;
    203 
    204     if (attrName == SVGNames::transformAttr) {
    205         object->setNeedsTransformUpdate();
    206         RenderSVGResource::markForLayoutAndParentResourceInvalidation(object);
    207         return;
    208     }
    209 
    210     ASSERT_NOT_REACHED();
    211 }
    212 
    213 SVGElement* SVGGraphicsElement::nearestViewportElement() const
    214 {
    215     for (Element* current = parentOrShadowHostElement(); current; current = current->parentOrShadowHostElement()) {
    216         if (isViewportElement(*current))
    217             return toSVGElement(current);
    218     }
    219 
    220     return 0;
    221 }
    222 
    223 SVGElement* SVGGraphicsElement::farthestViewportElement() const
    224 {
    225     SVGElement* farthest = 0;
    226     for (Element* current = parentOrShadowHostElement(); current; current = current->parentOrShadowHostElement()) {
    227         if (isViewportElement(*current))
    228             farthest = toSVGElement(current);
    229     }
    230     return farthest;
    231 }
    232 
    233 FloatRect SVGGraphicsElement::getBBox()
    234 {
    235     document().updateLayoutIgnorePendingStylesheets();
    236 
    237     // FIXME: Eventually we should support getBBox for detached elements.
    238     if (!renderer())
    239         return FloatRect();
    240 
    241     return renderer()->objectBoundingBox();
    242 }
    243 
    244 PassRefPtr<SVGRectTearOff> SVGGraphicsElement::getBBoxFromJavascript()
    245 {
    246     return SVGRectTearOff::create(SVGRect::create(getBBox()), 0, PropertyIsNotAnimVal);
    247 }
    248 
    249 RenderObject* SVGGraphicsElement::createRenderer(RenderStyle*)
    250 {
    251     // By default, any subclass is expected to do path-based drawing
    252     return new RenderSVGPath(this);
    253 }
    254 
    255 void SVGGraphicsElement::toClipPath(Path& path)
    256 {
    257     updatePathFromGraphicsElement(this, path);
    258     // FIXME: How do we know the element has done a layout?
    259     path.transform(animatedLocalTransform());
    260 }
    261 
    262 }
    263