1 /* 2 * Copyright (C) 2006 Nikolas Zimmermann <zimmermann (at) kde.org> 3 * Copyright (C) Research In Motion Limited 2010. All rights reserved. 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 #if ENABLE(SVG) 24 #include "RenderSVGResourcePattern.h" 25 26 #include "FrameView.h" 27 #include "GraphicsContext.h" 28 #include "PatternAttributes.h" 29 #include "RenderSVGRoot.h" 30 #include "SVGImageBufferTools.h" 31 #include "SVGRenderSupport.h" 32 33 namespace WebCore { 34 35 RenderSVGResourceType RenderSVGResourcePattern::s_resourceType = PatternResourceType; 36 37 RenderSVGResourcePattern::RenderSVGResourcePattern(SVGPatternElement* node) 38 : RenderSVGResourceContainer(node) 39 , m_shouldCollectPatternAttributes(true) 40 { 41 } 42 43 RenderSVGResourcePattern::~RenderSVGResourcePattern() 44 { 45 if (m_pattern.isEmpty()) 46 return; 47 48 deleteAllValues(m_pattern); 49 m_pattern.clear(); 50 } 51 52 void RenderSVGResourcePattern::removeAllClientsFromCache(bool markForInvalidation) 53 { 54 if (!m_pattern.isEmpty()) { 55 deleteAllValues(m_pattern); 56 m_pattern.clear(); 57 } 58 59 m_shouldCollectPatternAttributes = true; 60 markAllClientsForInvalidation(markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation); 61 } 62 63 void RenderSVGResourcePattern::removeClientFromCache(RenderObject* client, bool markForInvalidation) 64 { 65 ASSERT(client); 66 67 if (m_pattern.contains(client)) 68 delete m_pattern.take(client); 69 70 markClientForInvalidation(client, markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation); 71 } 72 73 bool RenderSVGResourcePattern::applyResource(RenderObject* object, RenderStyle* style, GraphicsContext*& context, unsigned short resourceMode) 74 { 75 ASSERT(object); 76 ASSERT(style); 77 ASSERT(context); 78 ASSERT(resourceMode != ApplyToDefaultMode); 79 80 // Be sure to synchronize all SVG properties on the patternElement _before_ processing any further. 81 // Otherwhise the call to collectPatternAttributes() below, may cause the SVG DOM property 82 // synchronization to kick in, which causes removeAllClientsFromCache() to be called, which in turn deletes our 83 // PatternData object! Leaving out the line below will cause svg/dynamic-updates/SVGPatternElement-svgdom* to crash. 84 SVGPatternElement* patternElement = static_cast<SVGPatternElement*>(node()); 85 if (!patternElement) 86 return false; 87 88 if (m_shouldCollectPatternAttributes) { 89 patternElement->updateAnimatedSVGAttribute(anyQName()); 90 91 m_attributes = PatternAttributes(); 92 patternElement->collectPatternAttributes(m_attributes); 93 m_shouldCollectPatternAttributes = false; 94 } 95 96 // Spec: When the geometry of the applicable element has no width or height and objectBoundingBox is specified, 97 // then the given effect (e.g. a gradient or a filter) will be ignored. 98 FloatRect objectBoundingBox = object->objectBoundingBox(); 99 if (m_attributes.boundingBoxMode() && objectBoundingBox.isEmpty()) 100 return false; 101 102 if (!m_pattern.contains(object)) 103 m_pattern.set(object, new PatternData); 104 105 PatternData* patternData = m_pattern.get(object); 106 if (!patternData->pattern) { 107 // If we couldn't determine the pattern content element root, stop here. 108 if (!m_attributes.patternContentElement()) 109 return false; 110 111 // Compute all necessary transformations to build the tile image & the pattern. 112 FloatRect tileBoundaries; 113 AffineTransform tileImageTransform; 114 if (!buildTileImageTransform(object, m_attributes, patternElement, tileBoundaries, tileImageTransform)) 115 return false; 116 117 AffineTransform absoluteTransform; 118 SVGImageBufferTools::calculateTransformationToOutermostSVGCoordinateSystem(object, absoluteTransform); 119 120 FloatRect absoluteTileBoundaries = absoluteTransform.mapRect(tileBoundaries); 121 122 // Build tile image. 123 OwnPtr<ImageBuffer> tileImage = createTileImage(object, m_attributes, tileBoundaries, absoluteTileBoundaries, tileImageTransform); 124 if (!tileImage) 125 return false; 126 127 RefPtr<Image> copiedImage = tileImage->copyImage(); 128 if (!copiedImage) 129 return false; 130 131 // Build pattern. 132 patternData->pattern = Pattern::create(copiedImage, true, true); 133 if (!patternData->pattern) 134 return false; 135 136 // Compute pattern space transformation. 137 patternData->transform.translate(tileBoundaries.x(), tileBoundaries.y()); 138 patternData->transform.scale(tileBoundaries.width() / absoluteTileBoundaries.width(), tileBoundaries.height() / absoluteTileBoundaries.height()); 139 140 AffineTransform patternTransform = m_attributes.patternTransform(); 141 if (!patternTransform.isIdentity()) 142 patternData->transform = patternTransform * patternData->transform; 143 144 patternData->pattern->setPatternSpaceTransform(patternData->transform); 145 } 146 147 // Draw pattern 148 context->save(); 149 150 const SVGRenderStyle* svgStyle = style->svgStyle(); 151 ASSERT(svgStyle); 152 153 if (resourceMode & ApplyToFillMode) { 154 context->setAlpha(svgStyle->fillOpacity()); 155 context->setFillPattern(patternData->pattern); 156 context->setFillRule(svgStyle->fillRule()); 157 } else if (resourceMode & ApplyToStrokeMode) { 158 if (svgStyle->vectorEffect() == VE_NON_SCALING_STROKE) 159 patternData->pattern->setPatternSpaceTransform(transformOnNonScalingStroke(object, patternData->transform)); 160 context->setAlpha(svgStyle->strokeOpacity()); 161 context->setStrokePattern(patternData->pattern); 162 SVGRenderSupport::applyStrokeStyleToContext(context, style, object); 163 } 164 165 if (resourceMode & ApplyToTextMode) { 166 if (resourceMode & ApplyToFillMode) { 167 context->setTextDrawingMode(TextModeFill); 168 169 #if USE(CG) 170 context->applyFillPattern(); 171 #endif 172 } else if (resourceMode & ApplyToStrokeMode) { 173 context->setTextDrawingMode(TextModeStroke); 174 175 #if USE(CG) 176 context->applyStrokePattern(); 177 #endif 178 } 179 } 180 181 return true; 182 } 183 184 void RenderSVGResourcePattern::postApplyResource(RenderObject*, GraphicsContext*& context, unsigned short resourceMode, const Path* path) 185 { 186 ASSERT(context); 187 ASSERT(resourceMode != ApplyToDefaultMode); 188 189 if (path && !(resourceMode & ApplyToTextMode)) { 190 if (resourceMode & ApplyToFillMode) 191 context->fillPath(*path); 192 else if (resourceMode & ApplyToStrokeMode) 193 context->strokePath(*path); 194 } 195 196 context->restore(); 197 } 198 199 static inline FloatRect calculatePatternBoundaries(const PatternAttributes& attributes, 200 const FloatRect& objectBoundingBox, 201 const SVGPatternElement* patternElement) 202 { 203 ASSERT(patternElement); 204 205 if (attributes.boundingBoxMode()) 206 return FloatRect(attributes.x().valueAsPercentage() * objectBoundingBox.width() + objectBoundingBox.x(), 207 attributes.y().valueAsPercentage() * objectBoundingBox.height() + objectBoundingBox.y(), 208 attributes.width().valueAsPercentage() * objectBoundingBox.width(), 209 attributes.height().valueAsPercentage() * objectBoundingBox.height()); 210 211 return FloatRect(attributes.x().value(patternElement), 212 attributes.y().value(patternElement), 213 attributes.width().value(patternElement), 214 attributes.height().value(patternElement)); 215 } 216 217 bool RenderSVGResourcePattern::buildTileImageTransform(RenderObject* renderer, 218 const PatternAttributes& attributes, 219 const SVGPatternElement* patternElement, 220 FloatRect& patternBoundaries, 221 AffineTransform& tileImageTransform) const 222 { 223 ASSERT(renderer); 224 ASSERT(patternElement); 225 226 FloatRect objectBoundingBox = renderer->objectBoundingBox(); 227 patternBoundaries = calculatePatternBoundaries(attributes, objectBoundingBox, patternElement); 228 if (patternBoundaries.width() <= 0 || patternBoundaries.height() <= 0) 229 return false; 230 231 AffineTransform viewBoxCTM = patternElement->viewBoxToViewTransform(attributes.viewBox(), attributes.preserveAspectRatio(), patternBoundaries.width(), patternBoundaries.height()); 232 233 // Apply viewBox/objectBoundingBox transformations. 234 if (!viewBoxCTM.isIdentity()) 235 tileImageTransform = viewBoxCTM; 236 else if (attributes.boundingBoxModeContent()) 237 tileImageTransform.scale(objectBoundingBox.width(), objectBoundingBox.height()); 238 239 return true; 240 } 241 242 PassOwnPtr<ImageBuffer> RenderSVGResourcePattern::createTileImage(RenderObject* object, 243 const PatternAttributes& attributes, 244 const FloatRect& tileBoundaries, 245 const FloatRect& absoluteTileBoundaries, 246 const AffineTransform& tileImageTransform) const 247 { 248 ASSERT(object); 249 250 // Clamp tile image size against SVG viewport size, as last resort, to avoid allocating huge image buffers. 251 FloatRect contentBoxRect = SVGRenderSupport::findTreeRootObject(object)->contentBoxRect(); 252 253 FloatRect clampedAbsoluteTileBoundaries = absoluteTileBoundaries; 254 if (clampedAbsoluteTileBoundaries.width() > contentBoxRect.width()) 255 clampedAbsoluteTileBoundaries.setWidth(contentBoxRect.width()); 256 257 if (clampedAbsoluteTileBoundaries.height() > contentBoxRect.height()) 258 clampedAbsoluteTileBoundaries.setHeight(contentBoxRect.height()); 259 260 OwnPtr<ImageBuffer> tileImage; 261 262 if (!SVGImageBufferTools::createImageBuffer(absoluteTileBoundaries, clampedAbsoluteTileBoundaries, tileImage, ColorSpaceDeviceRGB)) 263 return PassOwnPtr<ImageBuffer>(); 264 265 GraphicsContext* tileImageContext = tileImage->context(); 266 ASSERT(tileImageContext); 267 268 // The image buffer represents the final rendered size, so the content has to be scaled (to avoid pixelation). 269 tileImageContext->scale(FloatSize(absoluteTileBoundaries.width() / tileBoundaries.width(), 270 absoluteTileBoundaries.height() / tileBoundaries.height())); 271 272 // Apply tile image transformations. 273 if (!tileImageTransform.isIdentity()) 274 tileImageContext->concatCTM(tileImageTransform); 275 276 AffineTransform contentTransformation; 277 if (attributes.boundingBoxModeContent()) 278 contentTransformation = tileImageTransform; 279 280 // Draw the content into the ImageBuffer. 281 for (Node* node = attributes.patternContentElement()->firstChild(); node; node = node->nextSibling()) { 282 if (!node->isSVGElement() || !static_cast<SVGElement*>(node)->isStyled() || !node->renderer()) 283 continue; 284 SVGImageBufferTools::renderSubtreeToImageBuffer(tileImage.get(), node->renderer(), contentTransformation); 285 } 286 287 return tileImage.release(); 288 } 289 290 } 291 292 #endif 293