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