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 WebCore { 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 if (!isSupportedAttribute(name)) { 183 SVGElement::parseAttribute(name, value); 184 return; 185 } 186 187 SVGParsingError parseError = NoError; 188 189 if (name == SVGNames::transformAttr) 190 m_transform->setBaseValueAsString(value, parseError); 191 else if (SVGTests::parseAttribute(name, value)) 192 return; 193 else 194 ASSERT_NOT_REACHED(); 195 196 reportAttributeParsingError(parseError, name, value); 197 } 198 199 void SVGGraphicsElement::svgAttributeChanged(const QualifiedName& attrName) 200 { 201 if (!isSupportedAttribute(attrName)) { 202 SVGElement::svgAttributeChanged(attrName); 203 return; 204 } 205 206 SVGElement::InvalidationGuard invalidationGuard(this); 207 208 // Reattach so the isValid() check will be run again during renderer creation. 209 if (SVGTests::isKnownAttribute(attrName)) { 210 lazyReattachIfAttached(); 211 return; 212 } 213 214 RenderObject* object = renderer(); 215 if (!object) 216 return; 217 218 if (attrName == SVGNames::transformAttr) { 219 object->setNeedsTransformUpdate(); 220 RenderSVGResource::markForLayoutAndParentResourceInvalidation(object); 221 return; 222 } 223 224 ASSERT_NOT_REACHED(); 225 } 226 227 SVGElement* SVGGraphicsElement::nearestViewportElement() const 228 { 229 for (Element* current = parentOrShadowHostElement(); current; current = current->parentOrShadowHostElement()) { 230 if (isViewportElement(*current)) 231 return toSVGElement(current); 232 } 233 234 return 0; 235 } 236 237 SVGElement* SVGGraphicsElement::farthestViewportElement() const 238 { 239 SVGElement* farthest = 0; 240 for (Element* current = parentOrShadowHostElement(); current; current = current->parentOrShadowHostElement()) { 241 if (isViewportElement(*current)) 242 farthest = toSVGElement(current); 243 } 244 return farthest; 245 } 246 247 FloatRect SVGGraphicsElement::getBBox() 248 { 249 document().updateLayoutIgnorePendingStylesheets(); 250 251 // FIXME: Eventually we should support getBBox for detached elements. 252 if (!renderer()) 253 return FloatRect(); 254 255 return renderer()->objectBoundingBox(); 256 } 257 258 PassRefPtr<SVGRectTearOff> SVGGraphicsElement::getBBoxFromJavascript() 259 { 260 return SVGRectTearOff::create(SVGRect::create(getBBox()), 0, PropertyIsNotAnimVal); 261 } 262 263 RenderObject* SVGGraphicsElement::createRenderer(RenderStyle*) 264 { 265 // By default, any subclass is expected to do path-based drawing 266 return new RenderSVGPath(this); 267 } 268 269 void SVGGraphicsElement::toClipPath(Path& path) 270 { 271 updatePathFromGraphicsElement(this, path); 272 // FIXME: How do we know the element has done a layout? 273 path.transform(animatedLocalTransform()); 274 } 275 276 } 277