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 * 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 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 49 SVGGraphicsElement::~SVGGraphicsElement() 50 { 51 } 52 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 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 92 AffineTransform SVGGraphicsElement::getCTM(StyleUpdateStrategy styleUpdateStrategy) 93 { 94 return computeCTM(this, NearestViewportScope, styleUpdateStrategy); 95 } 96 97 AffineTransform SVGGraphicsElement::getScreenCTM(StyleUpdateStrategy styleUpdateStrategy) 98 { 99 return computeCTM(this, ScreenScope, styleUpdateStrategy); 100 } 101 102 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 133 AffineTransform* SVGGraphicsElement::supplementalTransform() 134 { 135 if (!m_supplementalTransform) 136 m_supplementalTransform = adoptPtr(new AffineTransform); 137 return m_supplementalTransform.get(); 138 } 139 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 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 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 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 206 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 216 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 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 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 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 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