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
32 namespace WebCore {
33
34 // Animated property definitions
DEFINE_ANIMATED_TRANSFORM_LIST(SVGGraphicsElement,SVGNames::transformAttr,Transform,transform)35 DEFINE_ANIMATED_TRANSFORM_LIST(SVGGraphicsElement, SVGNames::transformAttr, Transform, transform)
36
37 BEGIN_REGISTER_ANIMATED_PROPERTIES(SVGGraphicsElement)
38 REGISTER_LOCAL_ANIMATED_PROPERTY(transform)
39 REGISTER_PARENT_ANIMATED_PROPERTIES(SVGElement)
40 REGISTER_PARENT_ANIMATED_PROPERTIES(SVGTests)
41 END_REGISTER_ANIMATED_PROPERTIES
42
43 SVGGraphicsElement::SVGGraphicsElement(const QualifiedName& tagName, Document& document, ConstructionType constructionType)
44 : SVGElement(tagName, document, constructionType)
45 {
46 registerAnimatedPropertiesForSVGGraphicsElement();
47 }
48
~SVGGraphicsElement()49 SVGGraphicsElement::~SVGGraphicsElement()
50 {
51 }
52
getTransformToElement(SVGElement * target,ExceptionState & exceptionState)53 AffineTransform 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
computeCTM(SVGGraphicsElement * element,SVGElement::CTMScope mode,SVGGraphicsElement::StyleUpdateStrategy styleUpdateStrategy)69 static 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
getCTM(StyleUpdateStrategy styleUpdateStrategy)92 AffineTransform SVGGraphicsElement::getCTM(StyleUpdateStrategy styleUpdateStrategy)
93 {
94 return computeCTM(this, NearestViewportScope, styleUpdateStrategy);
95 }
96
getScreenCTM(StyleUpdateStrategy styleUpdateStrategy)97 AffineTransform SVGGraphicsElement::getScreenCTM(StyleUpdateStrategy styleUpdateStrategy)
98 {
99 return computeCTM(this, ScreenScope, styleUpdateStrategy);
100 }
101
animatedLocalTransform() const102 AffineTransform 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
supplementalTransform()133 AffineTransform* SVGGraphicsElement::supplementalTransform()
134 {
135 if (!m_supplementalTransform)
136 m_supplementalTransform = adoptPtr(new AffineTransform);
137 return m_supplementalTransform.get();
138 }
139
isSupportedAttribute(const QualifiedName & attrName)140 bool 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
parseAttribute(const QualifiedName & name,const AtomicString & value)150 void 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
svgAttributeChanged(const QualifiedName & attrName)170 void 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
isViewportElement(Node * node)198 static 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
nearestViewportElement() const206 SVGElement* 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
farthestViewportElement() const216 SVGElement* 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
getBBox()226 SVGRect 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
getStrokeBBox()237 SVGRect 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
createRenderer(RenderStyle *)248 RenderObject* SVGGraphicsElement::createRenderer(RenderStyle*)
249 {
250 // By default, any subclass is expected to do path-based drawing
251 return new RenderSVGPath(this);
252 }
253
toClipPath(Path & path)254 void 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