Applying a filter on an SVG element, which is larger than 4096 pixels, causes this...
[WebKit-https.git] / Source / WebCore / rendering / svg / RenderSVGResourcePattern.cpp
index bc443e5..4bc0d95 100644 (file)
  */
 
 #include "config.h"
-
-#if ENABLE(SVG)
 #include "RenderSVGResourcePattern.h"
 
+#include "ElementIterator.h"
 #include "FrameView.h"
 #include "GraphicsContext.h"
-#include "PatternAttributes.h"
 #include "RenderSVGRoot.h"
-#include "SVGImageBufferTools.h"
-#include "SVGRenderSupport.h"
+#include "SVGFitToViewBox.h"
+#include "SVGRenderingContext.h"
 
 namespace WebCore {
 
-RenderSVGResourceType RenderSVGResourcePattern::s_resourceType = PatternResourceType;
-
-RenderSVGResourcePattern::RenderSVGResourcePattern(SVGPatternElement* node)
-    : RenderSVGResourceContainer(node)
+RenderSVGResourcePattern::RenderSVGResourcePattern(SVGPatternElement& element, Ref<RenderStyle>&& style)
+    : RenderSVGResourceContainer(element, WTF::move(style))
     , m_shouldCollectPatternAttributes(true)
 {
 }
 
-RenderSVGResourcePattern::~RenderSVGResourcePattern()
+SVGPatternElement& RenderSVGResourcePattern::patternElement() const
 {
-    if (m_pattern.isEmpty())
-        return;
-
-    deleteAllValues(m_pattern);
-    m_pattern.clear();
+    return downcast<SVGPatternElement>(RenderSVGResourceContainer::element());
 }
 
 void RenderSVGResourcePattern::removeAllClientsFromCache(bool markForInvalidation)
 {
-    if (!m_pattern.isEmpty()) {
-        deleteAllValues(m_pattern);
-        m_pattern.clear();
-    }
-
+    m_patternMap.clear();
     m_shouldCollectPatternAttributes = true;
     markAllClientsForInvalidation(markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
 }
 
-void RenderSVGResourcePattern::removeClientFromCache(RenderObject* client, bool markForInvalidation)
+void RenderSVGResourcePattern::removeClientFromCache(RenderElement& client, bool markForInvalidation)
 {
-    ASSERT(client);
-
-    if (m_pattern.contains(client))
-        delete m_pattern.take(client);
-
+    m_patternMap.remove(&client);
     markClientForInvalidation(client, markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
 }
 
-bool RenderSVGResourcePattern::applyResource(RenderObject* object, RenderStyle* style, GraphicsContext*& context, unsigned short resourceMode)
+PatternData* RenderSVGResourcePattern::buildPattern(RenderElement& renderer, 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;
+    PatternData* currentData = m_patternMap.get(&renderer);
+    if (currentData && currentData->pattern)
+        return currentData;
 
     if (m_shouldCollectPatternAttributes) {
-        patternElement->updateAnimatedSVGAttribute(anyQName());
+        patternElement().synchronizeAnimatedSVGAttribute(anyQName());
 
         m_attributes = PatternAttributes();
-        patternElement->collectPatternAttributes(m_attributes);
+        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);
+    // If we couldn't determine the pattern content element root, stop here.
+    if (!m_attributes.patternContentElement())
+        return nullptr;
 
-    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;
+    // An empty viewBox disables rendering.
+    if (m_attributes.hasViewBox() && m_attributes.viewBox().isEmpty())
+        return nullptr;
 
-        // 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;
+    // Compute all necessary transformations to build the tile image & the pattern.
+    FloatRect tileBoundaries;
+    AffineTransform tileImageTransform;
+    if (!buildTileImageTransform(renderer, m_attributes, patternElement(), tileBoundaries, tileImageTransform))
+        return nullptr;
 
-        AffineTransform absoluteTransformIgnoringRotation;
-        SVGImageBufferTools::calculateTransformationToOutermostSVGCoordinateSystem(object, absoluteTransformIgnoringRotation);
+    AffineTransform absoluteTransformIgnoringRotation = SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(renderer);
 
-        // Ignore 2D rotation, as it doesn't affect the size of the tile.
-        SVGImageBufferTools::clear2DRotation(absoluteTransformIgnoringRotation);
-        FloatRect absoluteTileBoundaries = absoluteTransformIgnoringRotation.mapRect(tileBoundaries);
-        FloatRect clampedAbsoluteTileBoundaries;
+    // Ignore 2D rotation, as it doesn't affect the size of the tile.
+    SVGRenderingContext::clear2DRotation(absoluteTransformIgnoringRotation);
+    FloatRect absoluteTileBoundaries = absoluteTransformIgnoringRotation.mapRect(tileBoundaries);
+    FloatRect clampedAbsoluteTileBoundaries;
 
-        // Scale the tile size to match the scale level of the patternTransform.
-        absoluteTileBoundaries.scale(static_cast<float>(m_attributes.patternTransform().xScale()),
-            static_cast<float>(m_attributes.patternTransform().yScale()));
+    // Scale the tile size to match the scale level of the patternTransform.
+    absoluteTileBoundaries.scale(static_cast<float>(m_attributes.patternTransform().xScale()),
+        static_cast<float>(m_attributes.patternTransform().yScale()));
 
-        // Build tile image.
-        OwnPtr<ImageBuffer> tileImage = createTileImage(m_attributes, tileBoundaries, absoluteTileBoundaries, tileImageTransform, clampedAbsoluteTileBoundaries);
-        if (!tileImage)
-            return false;
+    // Build tile image.
+    auto tileImage = createTileImage(m_attributes, tileBoundaries, absoluteTileBoundaries, tileImageTransform, clampedAbsoluteTileBoundaries);
+    if (!tileImage)
+        return nullptr;
 
-        RefPtr<Image> copiedImage = tileImage->copyImage(CopyBackingStore);
-        if (!copiedImage)
-            return false;
+    RefPtr<Image> copiedImage = tileImage->copyImage(CopyBackingStore);
+    if (!copiedImage)
+        return nullptr;
 
-        // Build pattern.
-        patternData->pattern = Pattern::create(copiedImage, true, true);
-        if (!patternData->pattern)
-            return false;
+    // Build pattern.
+    auto patternData = std::make_unique<PatternData>();
+    patternData->pattern = Pattern::create(copiedImage, true, true);
 
-        // Compute pattern space transformation.
-        patternData->transform.translate(tileBoundaries.x(), tileBoundaries.y());
-        patternData->transform.scale(tileBoundaries.width() / clampedAbsoluteTileBoundaries.width(), tileBoundaries.height() / clampedAbsoluteTileBoundaries.height());
+    // Compute pattern space transformation.
+    const IntSize tileImageSize = tileImage->logicalSize();
+    patternData->transform.translate(tileBoundaries.x(), tileBoundaries.y());
+    patternData->transform.scale(tileBoundaries.width() / tileImageSize.width(), tileBoundaries.height() / tileImageSize.height());
 
-        AffineTransform patternTransform = m_attributes.patternTransform();
-        if (!patternTransform.isIdentity())
-            patternData->transform = patternTransform * patternData->transform;
+    AffineTransform patternTransform = m_attributes.patternTransform();
+    if (!patternTransform.isIdentity())
+        patternData->transform = patternTransform * patternData->transform;
 
-        patternData->pattern->setPatternSpaceTransform(patternData->transform);
+    // Account for text drawing resetting the context to non-scaled, see SVGInlineTextBox::paintTextWithShadows.
+    if (resourceMode & ApplyToTextMode) {
+        AffineTransform additionalTextTransformation;
+        if (shouldTransformOnTextPainting(renderer, additionalTextTransformation))
+            patternData->transform *= additionalTextTransformation;
     }
+    patternData->pattern->setPatternSpaceTransform(patternData->transform);
+
+    // Various calls above may trigger invalidations in some fringe cases (ImageBuffer allocation
+    // failures in the SVG image cache for example). To avoid having our PatternData deleted by
+    // removeAllClientsFromCache(), we only make it visible in the cache at the very end.
+    return m_patternMap.set(&renderer, WTF::move(patternData)).iterator->value.get();
+}
+
+bool RenderSVGResourcePattern::applyResource(RenderElement& renderer, const RenderStyle& style, GraphicsContext*& context, unsigned short resourceMode)
+{
+    ASSERT(context);
+    ASSERT(resourceMode != ApplyToDefaultMode);
+
+    // 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 = renderer.objectBoundingBox();
+    if (m_attributes.patternUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX && objectBoundingBox.isEmpty())
+        return false;
+
+    PatternData* patternData = buildPattern(renderer, resourceMode);
+    if (!patternData)
+        return false;
 
     // Draw pattern
     context->save();
 
-    const SVGRenderStyle* svgStyle = style->svgStyle();
-    ASSERT(svgStyle);
+    const SVGRenderStyle& svgStyle = style.svgStyle();
 
     if (resourceMode & ApplyToFillMode) {
-        context->setAlpha(svgStyle->fillOpacity());
-        context->setFillPattern(patternData->pattern);
-        context->setFillRule(svgStyle->fillRule());
+        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 (svgStyle.vectorEffect() == VE_NON_SCALING_STROKE)
+            patternData->pattern->setPatternSpaceTransform(transformOnNonScalingStroke(&renderer, patternData->transform));
+        context->setAlpha(svgStyle.strokeOpacity());
+        context->setStrokePattern(*patternData->pattern);
+        SVGRenderSupport::applyStrokeStyleToContext(context, style, renderer);
     }
 
     if (resourceMode & ApplyToTextMode) {
@@ -188,7 +180,7 @@ bool RenderSVGResourcePattern::applyResource(RenderObject* object, RenderStyle*
     return true;
 }
 
-void RenderSVGResourcePattern::postApplyResource(RenderObject*, GraphicsContext*& context, unsigned short resourceMode, const Path* path, const RenderSVGShape* shape)
+void RenderSVGResourcePattern::postApplyResource(RenderElement&, GraphicsContext*& context, unsigned short resourceMode, const Path* path, const RenderSVGShape* shape)
 {
     ASSERT(context);
     ASSERT(resourceMode != ApplyToDefaultMode);
@@ -211,58 +203,38 @@ void RenderSVGResourcePattern::postApplyResource(RenderObject*, GraphicsContext*
 
 static inline FloatRect calculatePatternBoundaries(const PatternAttributes& attributes,
                                                    const FloatRect& objectBoundingBox,
-                                                   const SVGPatternElement* patternElement)
+                                                   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));
+    return SVGLengthContext::resolveRectangle(&patternElement, attributes.patternUnits(), objectBoundingBox, attributes.x(), attributes.y(), attributes.width(), attributes.height());
 }
 
-bool RenderSVGResourcePattern::buildTileImageTransform(RenderObject* renderer,
+bool RenderSVGResourcePattern::buildTileImageTransform(RenderElement& renderer,
                                                        const PatternAttributes& attributes,
-                                                       const SVGPatternElement* patternElement,
+                                                       const SVGPatternElement& patternElement,
                                                        FloatRect& patternBoundaries,
                                                        AffineTransform& tileImageTransform) const
 {
-    ASSERT(renderer);
-    ASSERT(patternElement);
-
-    FloatRect objectBoundingBox = renderer->objectBoundingBox();
+    FloatRect objectBoundingBox = renderer.objectBoundingBox();
     patternBoundaries = calculatePatternBoundaries(attributes, objectBoundingBox, patternElement); 
     if (patternBoundaries.width() <= 0 || patternBoundaries.height() <= 0)
         return false;
 
-    AffineTransform viewBoxCTM = patternElement->viewBoxToViewTransform(attributes.viewBox(), attributes.preserveAspectRatio(), patternBoundaries.width(), patternBoundaries.height());
+    AffineTransform viewBoxCTM = SVGFitToViewBox::viewBoxToViewTransform(attributes.viewBox(), attributes.preserveAspectRatio(), patternBoundaries.width(), patternBoundaries.height());
 
     // Apply viewBox/objectBoundingBox transformations.
     if (!viewBoxCTM.isIdentity())
         tileImageTransform = viewBoxCTM;
-    else if (attributes.boundingBoxModeContent())
+    else if (attributes.patternContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX)
         tileImageTransform.scale(objectBoundingBox.width(), objectBoundingBox.height());
 
     return true;
 }
 
-PassOwnPtr<ImageBuffer> RenderSVGResourcePattern::createTileImage(const PatternAttributes& attributes,
-                                                                  const FloatRect& tileBoundaries,
-                                                                  const FloatRect& absoluteTileBoundaries,
-                                                                  const AffineTransform& tileImageTransform,
-                                                                  FloatRect& clampedAbsoluteTileBoundaries) const
+std::unique_ptr<ImageBuffer> RenderSVGResourcePattern::createTileImage(const PatternAttributes& attributes, const FloatRect& tileBoundaries, const FloatRect& absoluteTileBoundaries, const AffineTransform& tileImageTransform, FloatRect& clampedAbsoluteTileBoundaries) const
 {
-    clampedAbsoluteTileBoundaries = SVGImageBufferTools::clampedAbsoluteTargetRect(absoluteTileBoundaries);
-
-    OwnPtr<ImageBuffer> tileImage;
-
-    if (!SVGImageBufferTools::createImageBuffer(absoluteTileBoundaries, clampedAbsoluteTileBoundaries, tileImage, ColorSpaceDeviceRGB))
+    clampedAbsoluteTileBoundaries = ImageBuffer::clampedRect(absoluteTileBoundaries);
+    auto tileImage = SVGRenderingContext::createImageBuffer(absoluteTileBoundaries, clampedAbsoluteTileBoundaries, ColorSpaceDeviceRGB, Unaccelerated);
+    if (!tileImage)
         return nullptr;
 
     GraphicsContext* tileImageContext = tileImage->context();
@@ -277,19 +249,19 @@ PassOwnPtr<ImageBuffer> RenderSVGResourcePattern::createTileImage(const PatternA
         tileImageContext->concatCTM(tileImageTransform);
 
     AffineTransform contentTransformation;
-    if (attributes.boundingBoxModeContent())
+    if (attributes.patternContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX)
         contentTransformation = tileImageTransform;
 
     // 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())
+    for (auto& child : childrenOfType<SVGElement>(*attributes.patternContentElement())) {
+        if (!child.renderer())
             continue;
-        SVGImageBufferTools::renderSubtreeToImageBuffer(tileImage.get(), node->renderer(), contentTransformation);
+        if (child.renderer()->needsLayout())
+            return nullptr;
+        SVGRenderingContext::renderSubtreeToImageBuffer(tileImage.get(), *child.renderer(), contentTransformation);
     }
 
-    return tileImage.release();
+    return tileImage;
 }
 
 }
-
-#endif