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 blink { 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 parseAttributeNew(name, value); 183 } 184 185 void 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 213 SVGElement* 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 223 SVGElement* 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 233 FloatRect 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 244 PassRefPtr<SVGRectTearOff> SVGGraphicsElement::getBBoxFromJavascript() 245 { 246 return SVGRectTearOff::create(SVGRect::create(getBBox()), 0, PropertyIsNotAnimVal); 247 } 248 249 RenderObject* SVGGraphicsElement::createRenderer(RenderStyle*) 250 { 251 // By default, any subclass is expected to do path-based drawing 252 return new RenderSVGPath(this); 253 } 254 255 void 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