1/*
2 * Copyright (C) 2004, 2005, 2008 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2004, 2005, 2006 Rob Buis <buis@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
32namespace blink {
33
34SVGGraphicsElement::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
42SVGGraphicsElement::~SVGGraphicsElement()
43{
44}
45
46PassRefPtr<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
62static bool isViewportElement(const Element& element)
63{
64    return (isSVGSVGElement(element)
65        || isSVGSymbolElement(element)
66        || isSVGForeignObjectElement(element)
67        || isSVGImageElement(element));
68}
69
70AffineTransform 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
104AffineTransform SVGGraphicsElement::getCTM(StyleUpdateStrategy styleUpdateStrategy)
105{
106    return computeCTM(NearestViewportScope, styleUpdateStrategy);
107}
108
109AffineTransform SVGGraphicsElement::getScreenCTM(StyleUpdateStrategy styleUpdateStrategy)
110{
111    return computeCTM(ScreenScope, styleUpdateStrategy);
112}
113
114PassRefPtr<SVGMatrixTearOff> SVGGraphicsElement::getCTMFromJavascript()
115{
116    return SVGMatrixTearOff::create(getCTM());
117}
118
119PassRefPtr<SVGMatrixTearOff> SVGGraphicsElement::getScreenCTMFromJavascript()
120{
121    return SVGMatrixTearOff::create(getScreenCTM());
122}
123
124AffineTransform 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
163AffineTransform* SVGGraphicsElement::supplementalTransform()
164{
165    if (!m_supplementalTransform)
166        m_supplementalTransform = adoptPtr(new AffineTransform);
167    return m_supplementalTransform.get();
168}
169
170bool 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
180void SVGGraphicsElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
181{
182    parseAttributeNew(name, value);
183}
184
185void 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
213SVGElement* 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
223SVGElement* 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
233FloatRect 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
244PassRefPtr<SVGRectTearOff> SVGGraphicsElement::getBBoxFromJavascript()
245{
246    return SVGRectTearOff::create(SVGRect::create(getBBox()), 0, PropertyIsNotAnimVal);
247}
248
249RenderObject* SVGGraphicsElement::createRenderer(RenderStyle*)
250{
251    // By default, any subclass is expected to do path-based drawing
252    return new RenderSVGPath(this);
253}
254
255void 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