diff -r 000000000000 -r 4f2f89ce4247 WebCore/rendering/RenderSVGResourcePattern.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebCore/rendering/RenderSVGResourcePattern.cpp Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2006 Nikolas Zimmermann + * 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 "SVGRenderSupport.h" + +namespace WebCore { + +RenderSVGResourceType RenderSVGResourcePattern::s_resourceType = PatternResourceType; + +RenderSVGResourcePattern::RenderSVGResourcePattern(SVGPatternElement* node) + : RenderSVGResourceContainer(node) +{ +} + +RenderSVGResourcePattern::~RenderSVGResourcePattern() +{ + deleteAllValues(m_pattern); + m_pattern.clear(); +} + +void RenderSVGResourcePattern::invalidateClients() +{ + const HashMap::const_iterator end = m_pattern.end(); + for (HashMap::const_iterator it = m_pattern.begin(); it != end; ++it) + markForLayoutAndResourceInvalidation(it->first, false); + + deleteAllValues(m_pattern); + m_pattern.clear(); +} + +void RenderSVGResourcePattern::invalidateClient(RenderObject* object) +{ + ASSERT(object); + if (!m_pattern.contains(object)) + return; + + delete m_pattern.take(object); + markForLayoutAndResourceInvalidation(object, false); +} + +bool RenderSVGResourcePattern::childElementReferencesResource(const SVGRenderStyle* style, const String& referenceId) const +{ + if (style->hasFill()) { + if (style->fillPaint()->matchesTargetURI(referenceId)) + return true; + } + + if (style->hasStroke()) { + if (style->strokePaint()->matchesTargetURI(referenceId)) + return true; + } + + return false; +} + +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() in createTileImage(), may cause the SVG DOM property + // synchronization to kick in, which causes invalidateClients() 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(node()); + if (!patternElement) + return false; + + patternElement->updateAnimatedSVGAttribute(anyQName()); + + if (!m_pattern.contains(object)) + m_pattern.set(object, new PatternData); + + PatternData* patternData = m_pattern.get(object); + if (!patternData->pattern) { + // Create tile image + OwnPtr tileImage = createTileImage(patternData, patternElement, object); + if (!tileImage) + return false; + + // Create pattern object + buildPattern(patternData, tileImage.release()); + + if (!patternData->pattern) + return false; + + 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(cTextFill); + +#if PLATFORM(CG) + context->applyFillPattern(); +#endif + } else if (resourceMode & ApplyToStrokeMode) { + context->setTextDrawingMode(cTextStroke); + +#if PLATFORM(CG) + context->applyStrokePattern(); +#endif + } + } + + return true; +} + +void RenderSVGResourcePattern::postApplyResource(RenderObject*, GraphicsContext*& context, unsigned short resourceMode) +{ + ASSERT(context); + ASSERT(resourceMode != ApplyToDefaultMode); + + if (!(resourceMode & ApplyToTextMode)) { + if (resourceMode & ApplyToFillMode) + context->fillPath(); + else if (resourceMode & ApplyToStrokeMode) + context->strokePath(); + } + + context->restore(); +} + +static inline FloatRect calculatePatternBoundaries(PatternAttributes& attributes, + const FloatRect& objectBoundingBox, + const SVGPatternElement* patternElement) +{ + if (attributes.boundingBoxMode()) + return FloatRect(attributes.x().valueAsPercentage() * objectBoundingBox.width(), + attributes.y().valueAsPercentage() * objectBoundingBox.height(), + 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)); +} + +FloatRect RenderSVGResourcePattern::calculatePatternBoundariesIncludingOverflow(PatternAttributes& attributes, + const FloatRect& objectBoundingBox, + const AffineTransform& viewBoxCTM, + const FloatRect& patternBoundaries) const +{ + // Eventually calculate the pattern content boundaries (only needed with overflow="visible"). + FloatRect patternContentBoundaries; + + const RenderStyle* style = this->style(); + if (style->overflowX() == OVISIBLE && style->overflowY() == OVISIBLE) { + for (Node* node = attributes.patternContentElement()->firstChild(); node; node = node->nextSibling()) { + if (!node->isSVGElement() || !static_cast(node)->isStyledTransformable() || !node->renderer()) + continue; + patternContentBoundaries.unite(node->renderer()->repaintRectInLocalCoordinates()); + } + } + + if (patternContentBoundaries.isEmpty()) + return patternBoundaries; + + FloatRect patternBoundariesIncludingOverflow = patternBoundaries; + + // Respect objectBoundingBoxMode for patternContentUnits, if viewBox is not set. + if (!viewBoxCTM.isIdentity()) + patternContentBoundaries = viewBoxCTM.mapRect(patternContentBoundaries); + else if (attributes.boundingBoxModeContent()) + patternContentBoundaries = FloatRect(patternContentBoundaries.x() * objectBoundingBox.width(), + patternContentBoundaries.y() * objectBoundingBox.height(), + patternContentBoundaries.width() * objectBoundingBox.width(), + patternContentBoundaries.height() * objectBoundingBox.height()); + + patternBoundariesIncludingOverflow.unite(patternContentBoundaries); + return patternBoundariesIncludingOverflow; +} + +// FIXME: This method should be removed. RenderSVGResourcePatterns usage of it is just wrong. +static inline void clampImageBufferSizeToViewport(FrameView* frameView, IntSize& size) +{ + if (!frameView) + return; + + int viewWidth = frameView->visibleWidth(); + int viewHeight = frameView->visibleHeight(); + + if (size.width() > viewWidth) + size.setWidth(viewWidth); + + if (size.height() > viewHeight) + size.setHeight(viewHeight); +} + +PassOwnPtr RenderSVGResourcePattern::createTileImage(PatternData* patternData, + const SVGPatternElement* patternElement, + RenderObject* object) const +{ + PatternAttributes attributes = patternElement->collectPatternProperties(); + + // If we couldn't determine the pattern content element root, stop here. + if (!attributes.patternContentElement()) + return 0; + + // Early exit, if this resource contains a child which references ourselves. + if (containsCyclicReference(attributes.patternContentElement())) + return 0; + + FloatRect objectBoundingBox = object->objectBoundingBox(); + FloatRect patternBoundaries = calculatePatternBoundaries(attributes, objectBoundingBox, patternElement); + AffineTransform patternTransform = attributes.patternTransform(); + + AffineTransform viewBoxCTM = patternElement->viewBoxToViewTransform(patternElement->viewBox(), + patternElement->preserveAspectRatio(), + patternBoundaries.width(), + patternBoundaries.height()); + + FloatRect patternBoundariesIncludingOverflow = calculatePatternBoundariesIncludingOverflow(attributes, + objectBoundingBox, + viewBoxCTM, + patternBoundaries); + + IntSize imageSize(lroundf(patternBoundariesIncludingOverflow.width()), lroundf(patternBoundariesIncludingOverflow.height())); + + // FIXME: We should be able to clip this more, needs investigation + clampImageBufferSizeToViewport(object->document()->view(), imageSize); + + // Don't create ImageBuffers with image size of 0 + if (imageSize.isEmpty()) + return 0; + + OwnPtr tileImage = ImageBuffer::create(imageSize); + + GraphicsContext* context = tileImage->context(); + ASSERT(context); + + context->save(); + + // Translate to pattern start origin + if (patternBoundariesIncludingOverflow.location() != patternBoundaries.location()) { + context->translate(patternBoundaries.x() - patternBoundariesIncludingOverflow.x(), + patternBoundaries.y() - patternBoundariesIncludingOverflow.y()); + + patternBoundaries.setLocation(patternBoundariesIncludingOverflow.location()); + } + + // Process viewBox or boundingBoxModeContent correction + if (!viewBoxCTM.isIdentity()) + context->concatCTM(viewBoxCTM); + else if (attributes.boundingBoxModeContent()) { + context->translate(objectBoundingBox.x(), objectBoundingBox.y()); + context->scale(FloatSize(objectBoundingBox.width(), objectBoundingBox.height())); + } + + // Render subtree into ImageBuffer + for (Node* node = attributes.patternContentElement()->firstChild(); node; node = node->nextSibling()) { + if (!node->isSVGElement() || !static_cast(node)->isStyled() || !node->renderer()) + continue; + SVGRenderSupport::renderSubtreeToImage(tileImage.get(), node->renderer()); + } + + patternData->boundaries = patternBoundaries; + + // Compute pattern transformation + patternData->transform.translate(patternBoundaries.x(), patternBoundaries.y()); + patternData->transform.multiply(patternTransform); + + context->restore(); + return tileImage.release(); +} + +void RenderSVGResourcePattern::buildPattern(PatternData* patternData, PassOwnPtr tileImage) const +{ + if (!tileImage->image()) { + patternData->pattern = 0; + return; + } + + IntRect tileRect = tileImage->image()->rect(); + if (tileRect.width() <= patternData->boundaries.width() && tileRect.height() <= patternData->boundaries.height()) { + patternData->pattern = Pattern::create(tileImage->image(), true, true); + return; + } + + // Draw the first cell of the pattern manually to support overflow="visible" on all platforms. + int tileWidth = static_cast(patternData->boundaries.width() + 0.5f); + int tileHeight = static_cast(patternData->boundaries.height() + 0.5f); + + // Don't create ImageBuffers with image size of 0 + if (!tileWidth || !tileHeight) { + patternData->pattern = 0; + return; + } + + OwnPtr newTileImage = ImageBuffer::create(IntSize(tileWidth, tileHeight)); + GraphicsContext* newTileImageContext = newTileImage->context(); + + int numY = static_cast(ceilf(tileRect.height() / tileHeight)) + 1; + int numX = static_cast(ceilf(tileRect.width() / tileWidth)) + 1; + + newTileImageContext->save(); + newTileImageContext->translate(-patternData->boundaries.width() * numX, -patternData->boundaries.height() * numY); + for (int i = numY; i > 0; --i) { + newTileImageContext->translate(0, patternData->boundaries.height()); + for (int j = numX; j > 0; --j) { + newTileImageContext->translate(patternData->boundaries.width(), 0); + newTileImageContext->drawImage(tileImage->image(), style()->colorSpace(), tileRect, tileRect); + } + newTileImageContext->translate(-patternData->boundaries.width() * numX, 0); + } + newTileImageContext->restore(); + + patternData->pattern = Pattern::create(newTileImage->image(), true, true); +} + +} + +#endif