diff options
Diffstat (limited to 'Source/WebCore/rendering/RenderSVGResourcePattern.cpp')
-rw-r--r-- | Source/WebCore/rendering/RenderSVGResourcePattern.cpp | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/Source/WebCore/rendering/RenderSVGResourcePattern.cpp b/Source/WebCore/rendering/RenderSVGResourcePattern.cpp new file mode 100644 index 0000000..cd64183 --- /dev/null +++ b/Source/WebCore/rendering/RenderSVGResourcePattern.cpp @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#if ENABLE(SVG) +#include "RenderSVGResourcePattern.h" + +#include "FrameView.h" +#include "GraphicsContext.h" +#include "PatternAttributes.h" +#include "RenderSVGRoot.h" +#include "SVGImageBufferTools.h" +#include "SVGRenderSupport.h" + +namespace WebCore { + +RenderSVGResourceType RenderSVGResourcePattern::s_resourceType = PatternResourceType; + +RenderSVGResourcePattern::RenderSVGResourcePattern(SVGPatternElement* node) + : RenderSVGResourceContainer(node) + , m_shouldCollectPatternAttributes(true) +{ +} + +RenderSVGResourcePattern::~RenderSVGResourcePattern() +{ + if (m_pattern.isEmpty()) + return; + + deleteAllValues(m_pattern); + m_pattern.clear(); +} + +void RenderSVGResourcePattern::removeAllClientsFromCache(bool markForInvalidation) +{ + if (!m_pattern.isEmpty()) { + deleteAllValues(m_pattern); + m_pattern.clear(); + } + + m_shouldCollectPatternAttributes = true; + markAllClientsForInvalidation(markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation); +} + +void RenderSVGResourcePattern::removeClientFromCache(RenderObject* client, bool markForInvalidation) +{ + ASSERT(client); + + if (m_pattern.contains(client)) + delete m_pattern.take(client); + + markClientForInvalidation(client, markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation); +} + +bool RenderSVGResourcePattern::applyResource(RenderObject* object, RenderStyle* style, GraphicsContext*& context, unsigned short resourceMode) +{ + ASSERT(object); + ASSERT(style); + ASSERT(context); + ASSERT(resourceMode != ApplyToDefaultMode); + + // Be sure to synchronize all SVG properties on the patternElement _before_ processing any further. + // Otherwhise the call to collectPatternAttributes() below, may cause the SVG DOM property + // synchronization to kick in, which causes removeAllClientsFromCache() to be called, which in turn deletes our + // PatternData object! Leaving out the line below will cause svg/dynamic-updates/SVGPatternElement-svgdom* to crash. + SVGPatternElement* patternElement = static_cast<SVGPatternElement*>(node()); + if (!patternElement) + return false; + + if (m_shouldCollectPatternAttributes) { + patternElement->updateAnimatedSVGAttribute(anyQName()); + + m_attributes = PatternAttributes(); + patternElement->collectPatternAttributes(m_attributes); + m_shouldCollectPatternAttributes = false; + } + + // Spec: When the geometry of the applicable element has no width or height and objectBoundingBox is specified, + // then the given effect (e.g. a gradient or a filter) will be ignored. + FloatRect objectBoundingBox = object->objectBoundingBox(); + if (m_attributes.boundingBoxMode() && objectBoundingBox.isEmpty()) + return false; + + if (!m_pattern.contains(object)) + m_pattern.set(object, new PatternData); + + PatternData* patternData = m_pattern.get(object); + if (!patternData->pattern) { + // If we couldn't determine the pattern content element root, stop here. + if (!m_attributes.patternContentElement()) + return false; + + // Compute all necessary transformations to build the tile image & the pattern. + FloatRect tileBoundaries; + AffineTransform tileImageTransform; + if (!buildTileImageTransform(object, m_attributes, patternElement, tileBoundaries, tileImageTransform)) + return false; + + AffineTransform absoluteTransform; + SVGImageBufferTools::calculateTransformationToOutermostSVGCoordinateSystem(object, absoluteTransform); + + FloatRect absoluteTileBoundaries = absoluteTransform.mapRect(tileBoundaries); + + // Build tile image. + OwnPtr<ImageBuffer> tileImage = createTileImage(object, m_attributes, tileBoundaries, absoluteTileBoundaries, tileImageTransform); + if (!tileImage) + return false; + + RefPtr<Image> copiedImage = tileImage->copyImage(); + if (!copiedImage) + return false; + + // Build pattern. + patternData->pattern = Pattern::create(copiedImage, true, true); + if (!patternData->pattern) + return false; + + // Compute pattern space transformation. + patternData->transform.translate(tileBoundaries.x(), tileBoundaries.y()); + patternData->transform.scale(tileBoundaries.width() / absoluteTileBoundaries.width(), tileBoundaries.height() / absoluteTileBoundaries.height()); + + AffineTransform patternTransform = m_attributes.patternTransform(); + if (!patternTransform.isIdentity()) + patternData->transform.multiply(patternTransform); + + patternData->pattern->setPatternSpaceTransform(patternData->transform); + } + + // Draw pattern + context->save(); + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + + if (resourceMode & ApplyToFillMode) { + context->setAlpha(svgStyle->fillOpacity()); + context->setFillPattern(patternData->pattern); + context->setFillRule(svgStyle->fillRule()); + } else if (resourceMode & ApplyToStrokeMode) { + if (svgStyle->vectorEffect() == VE_NON_SCALING_STROKE) + patternData->pattern->setPatternSpaceTransform(transformOnNonScalingStroke(object, patternData->transform)); + context->setAlpha(svgStyle->strokeOpacity()); + context->setStrokePattern(patternData->pattern); + SVGRenderSupport::applyStrokeStyleToContext(context, style, object); + } + + if (resourceMode & ApplyToTextMode) { + if (resourceMode & ApplyToFillMode) { + context->setTextDrawingMode(TextModeFill); + +#if PLATFORM(CG) + context->applyFillPattern(); +#endif + } else if (resourceMode & ApplyToStrokeMode) { + context->setTextDrawingMode(TextModeStroke); + +#if PLATFORM(CG) + context->applyStrokePattern(); +#endif + } + } + + return true; +} + +void RenderSVGResourcePattern::postApplyResource(RenderObject*, GraphicsContext*& context, unsigned short resourceMode, const Path* path) +{ + ASSERT(context); + ASSERT(resourceMode != ApplyToDefaultMode); + + if (path && !(resourceMode & ApplyToTextMode)) { + if (resourceMode & ApplyToFillMode) + context->fillPath(*path); + else if (resourceMode & ApplyToStrokeMode) + context->strokePath(*path); + } + + context->restore(); +} + +static inline FloatRect calculatePatternBoundaries(const PatternAttributes& attributes, + const FloatRect& objectBoundingBox, + const SVGPatternElement* patternElement) +{ + ASSERT(patternElement); + + if (attributes.boundingBoxMode()) + return FloatRect(attributes.x().valueAsPercentage() * objectBoundingBox.width() + objectBoundingBox.x(), + attributes.y().valueAsPercentage() * objectBoundingBox.height() + objectBoundingBox.y(), + attributes.width().valueAsPercentage() * objectBoundingBox.width(), + attributes.height().valueAsPercentage() * objectBoundingBox.height()); + + return FloatRect(attributes.x().value(patternElement), + attributes.y().value(patternElement), + attributes.width().value(patternElement), + attributes.height().value(patternElement)); +} + +bool RenderSVGResourcePattern::buildTileImageTransform(RenderObject* renderer, + const PatternAttributes& attributes, + const SVGPatternElement* patternElement, + FloatRect& patternBoundaries, + AffineTransform& tileImageTransform) const +{ + ASSERT(renderer); + ASSERT(patternElement); + + FloatRect objectBoundingBox = renderer->objectBoundingBox(); + patternBoundaries = calculatePatternBoundaries(attributes, objectBoundingBox, patternElement); + if (patternBoundaries.width() <= 0 || patternBoundaries.height() <= 0) + return false; + + AffineTransform viewBoxCTM = patternElement->viewBoxToViewTransform(patternElement->viewBox(), patternElement->preserveAspectRatio(), patternBoundaries.width(), patternBoundaries.height()); + + // Apply viewBox/objectBoundingBox transformations. + if (!viewBoxCTM.isIdentity()) + tileImageTransform = viewBoxCTM; + else if (attributes.boundingBoxModeContent()) { + tileImageTransform.translate(objectBoundingBox.x(), objectBoundingBox.y()); + tileImageTransform.scale(objectBoundingBox.width(), objectBoundingBox.height()); + } + + return true; +} + +PassOwnPtr<ImageBuffer> RenderSVGResourcePattern::createTileImage(RenderObject* object, + const PatternAttributes& attributes, + const FloatRect& tileBoundaries, + const FloatRect& absoluteTileBoundaries, + const AffineTransform& tileImageTransform) const +{ + ASSERT(object); + + // Clamp tile image size against SVG viewport size, as last resort, to avoid allocating huge image buffers. + FloatRect contentBoxRect = SVGRenderSupport::findTreeRootObject(object)->contentBoxRect(); + + FloatRect clampedAbsoluteTileBoundaries = absoluteTileBoundaries; + if (clampedAbsoluteTileBoundaries.width() > contentBoxRect.width()) + clampedAbsoluteTileBoundaries.setWidth(contentBoxRect.width()); + + if (clampedAbsoluteTileBoundaries.height() > contentBoxRect.height()) + clampedAbsoluteTileBoundaries.setHeight(contentBoxRect.height()); + + OwnPtr<ImageBuffer> tileImage; + + if (!SVGImageBufferTools::createImageBuffer(absoluteTileBoundaries, clampedAbsoluteTileBoundaries, tileImage, ColorSpaceDeviceRGB)) + return PassOwnPtr<ImageBuffer>(); + + GraphicsContext* tileImageContext = tileImage->context(); + ASSERT(tileImageContext); + + // The image buffer represents the final rendered size, so the content has to be scaled (to avoid pixelation). + tileImageContext->scale(FloatSize(absoluteTileBoundaries.width() / tileBoundaries.width(), + absoluteTileBoundaries.height() / tileBoundaries.height())); + + // Apply tile image transformations. + if (!tileImageTransform.isIdentity()) + tileImageContext->concatCTM(tileImageTransform); + + AffineTransform contentTransformation; + + // Draw the content into the ImageBuffer. + for (Node* node = attributes.patternContentElement()->firstChild(); node; node = node->nextSibling()) { + if (!node->isSVGElement() || !static_cast<SVGElement*>(node)->isStyled() || !node->renderer()) + continue; + SVGImageBufferTools::renderSubtreeToImageBuffer(tileImage.get(), node->renderer(), contentTransformation); + } + + return tileImage.release(); +} + +} + +#endif |