WebCore/rendering/RenderSVGResourcePattern.cpp
changeset 0 4f2f89ce4247
--- /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 <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 "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<RenderObject*, PatternData*>::const_iterator end = m_pattern.end();
+    for (HashMap<RenderObject*, PatternData*>::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<SVGPatternElement*>(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<ImageBuffer> 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<SVGElement*>(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<ImageBuffer> 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<ImageBuffer> 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<SVGElement*>(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<ImageBuffer> 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<int>(patternData->boundaries.width() + 0.5f);
+    int tileHeight = static_cast<int>(patternData->boundaries.height() + 0.5f);
+
+    // Don't create ImageBuffers with image size of 0
+    if (!tileWidth || !tileHeight) {
+        patternData->pattern = 0;
+        return;
+    }
+
+    OwnPtr<ImageBuffer> newTileImage = ImageBuffer::create(IntSize(tileWidth, tileHeight));
+    GraphicsContext* newTileImageContext = newTileImage->context();
+
+    int numY = static_cast<int>(ceilf(tileRect.height() / tileHeight)) + 1;
+    int numX = static_cast<int>(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