/* * 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 "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); deleteAllValues(m_pattern); m_pattern.clear(); } void RenderSVGResourcePattern::invalidateClient(RenderObject* object) { ASSERT(object); // FIXME: The HashMap should always contain the object on calling invalidateClient. A race condition // during the parsing can causes a call of invalidateClient right before the call of applyResource. // We return earlier for the moment. This bug should be fixed in: // https://bugs.webkit.org/show_bug.cgi?id=35181 if (!m_pattern.contains(object)) return; delete m_pattern.take(object); markForLayoutAndResourceInvalidation(object); } 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); 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; } 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; 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; 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