Reviewed by Darin.
authormjs <mjs@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 20 Jun 2006 02:43:10 +0000 (02:43 +0000)
committermjs <mjs@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 20 Jun 2006 02:43:10 +0000 (02:43 +0000)
        - fixed http://bugzilla.opendarwin.org/show_bug.cgi?id=9488
        "Animated GIFs do not respect transforms in SVG"

        http://bugzilla.opendarwin.org/show_bug.cgi?id=6946
        "SVG shows invalidation issues in WebKit"

        http://www.treebuilder.de/default.asp?file=441875.xml
        "Invalidation issues with "SVG 3d" demo"

        http://code.google.com/webstats/2005-12/pages.html
        "SVG text doesn't repaint correctly"

        * kcanvas/KCanvasContainer.cpp:
        (WebCore::KCanvasContainer::computeAbsoluteRepaintRect): Override base class, and apply
        appropriate transforms, so damage rects in transformed SVG content get propagated up properly.
        (WebCore::KCanvasContainer::getAbsoluteRepaintRect): note a FIXME; this method
        seems wrong.
        * kcanvas/KCanvasContainer.h: Prototype new method.

        * css/svg.css: Don't apply overflow:hidden to foreignObject, since that makes it a RenderLayer
        so it paints twice.
        * kcanvas/RenderForeignObject.cpp:
        (WebCore::RenderForeignObject::paint): Transform the damage rect before passing it down to HTML content,
        so everything paints that is supposed to. Also handle opacity here since we won't get layers.
        (WebCore::RenderForeignObject::computeAbsoluteRepaintRect): Override base class, and apply
        appropriate transforms, so damage rects in HTML embedded in SVG get propagated up properly.
        (WebCore::RenderForeignObject::requiresLayer): Never use a RenderLayer.
        (WebCore::RenderForeignObject::layout): Make sure to dirty our previous bounds when layout
        changes, as by transform.
        * kcanvas/RenderForeignObject.h:

        * kcanvas/RenderSVGImage.cpp:
        (WebCore::RenderSVGImage::paint): Transform the damage rect when painting. Also handle opacity
        here since we won't get layers.
        (WebCore::RenderForeignObject::computeAbsoluteRepaintRect): Override base class, and apply
        appropriate transforms, so damage rects in SVG images  get propagated up properly.
        (WebCore::RenderSVGImage::translationForAttributes): New helper method, factored out of below.
        (WebCore::RenderSVGImage::translateForAttributes): Use above.
        (WebCore::RenderSVGImage::requiresLayer): Never use a RenderLayer.
        (WebCore::RenderSVGImage::layout): Make sure to properly dirty the old bounds, accounting
        for transforms.
        (WebCore::RenderSVGImage::relativeBBox): Correct bbox computation.
        * kcanvas/RenderSVGImage.h:

        * kcanvas/RenderSVGText.cpp:
        (WebCore::RenderSVGText::paint): Transform incoming damage rect. Handle opacity here since we
        won't get a layer.
        (WebCore::RenderSVGText::computeAbsoluteRepaintRect): Apply transforms.
        (WebCore::RenderSVGText::requiresLayer): Never use a RenderLayer.
        (WebCore::RenderSVGText::layout): Make sure to dirty the old bounds.

        * platform/cg/GraphicsContextCG.cpp:
        (WebCore::GraphicsContext::roundToDevicePixels): Instead of transforming rect to device space
        and back to user space, do this for the origin and lower right corner of the rect. Otherwise the
        rect will get inflated if user space is rotated or skewed.

        * kcanvas/RenderPath.cpp:
        (WebCore::RenderPath::layout): Make sure to dirty the old bounds.
        (WebCore::RenderPath::paint): Hhandle opacity here.

        Bonus fix:

        * kcanvas/RenderSVGImage.cpp:
        (WebCore::RenderSVGImage::nodeAtPoint): Fix hit testing. Wasn't applying the x/y
        attribute transform.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@14918 268f45cc-cd09-0410-ab3c-d52691b4dbfc

18 files changed:
WebCore/ChangeLog
WebCore/css/svg.css
WebCore/kcanvas/KCanvasContainer.cpp
WebCore/kcanvas/KCanvasContainer.h
WebCore/kcanvas/RenderForeignObject.cpp
WebCore/kcanvas/RenderForeignObject.h
WebCore/kcanvas/RenderPath.cpp
WebCore/kcanvas/RenderSVGImage.cpp
WebCore/kcanvas/RenderSVGImage.h
WebCore/kcanvas/RenderSVGText.cpp
WebCore/kcanvas/RenderSVGText.h
WebCore/manual-tests/resources/3dolph.gif [new file with mode: 0644]
WebCore/manual-tests/svg-animated-gifs.svg [new file with mode: 0644]
WebCore/manual-tests/svg-repaint-foreignObject.svg [new file with mode: 0644]
WebCore/manual-tests/svg-repaint-group.svg [new file with mode: 0644]
WebCore/manual-tests/svg-repaint-image.svg [new file with mode: 0644]
WebCore/manual-tests/svg-repaint-path.svg [new file with mode: 0644]
WebCore/platform/cg/GraphicsContextCG.cpp

index 19240d44be0326a67df1feaac2bba25eaa4124d5..dc40a7887825100178a0aa3ddf4a278155697166 100644 (file)
@@ -1,3 +1,73 @@
+2006-06-18  Maciej Stachowiak  <mjs@apple.com>
+
+        Reviewed by Darin.
+
+        - fixed http://bugzilla.opendarwin.org/show_bug.cgi?id=9488
+        "Animated GIFs do not respect transforms in SVG"
+        
+        http://bugzilla.opendarwin.org/show_bug.cgi?id=6946
+        "SVG shows invalidation issues in WebKit"
+        
+        http://www.treebuilder.de/default.asp?file=441875.xml
+        "Invalidation issues with "SVG 3d" demo"
+        
+        http://code.google.com/webstats/2005-12/pages.html
+        "SVG text doesn't repaint correctly"
+
+        * kcanvas/KCanvasContainer.cpp:
+        (WebCore::KCanvasContainer::computeAbsoluteRepaintRect): Override base class, and apply
+        appropriate transforms, so damage rects in transformed SVG content get propagated up properly.
+        (WebCore::KCanvasContainer::getAbsoluteRepaintRect): note a FIXME; this method
+        seems wrong.
+        * kcanvas/KCanvasContainer.h: Prototype new method.
+
+        * css/svg.css: Don't apply overflow:hidden to foreignObject, since that makes it a RenderLayer
+        so it paints twice.
+        * kcanvas/RenderForeignObject.cpp:
+        (WebCore::RenderForeignObject::paint): Transform the damage rect before passing it down to HTML content,
+        so everything paints that is supposed to. Also handle opacity here since we won't get layers.
+        (WebCore::RenderForeignObject::computeAbsoluteRepaintRect): Override base class, and apply
+        appropriate transforms, so damage rects in HTML embedded in SVG get propagated up properly.
+        (WebCore::RenderForeignObject::requiresLayer): Never use a RenderLayer.
+        (WebCore::RenderForeignObject::layout): Make sure to dirty our previous bounds when layout
+        changes, as by transform.
+        * kcanvas/RenderForeignObject.h:
+
+        * kcanvas/RenderSVGImage.cpp:
+        (WebCore::RenderSVGImage::paint): Transform the damage rect when painting. Also handle opacity 
+        here since we won't get layers.
+        (WebCore::RenderForeignObject::computeAbsoluteRepaintRect): Override base class, and apply
+        appropriate transforms, so damage rects in SVG images  get propagated up properly.
+        (WebCore::RenderSVGImage::translationForAttributes): New helper method, factored out of below.
+        (WebCore::RenderSVGImage::translateForAttributes): Use above.
+        (WebCore::RenderSVGImage::requiresLayer): Never use a RenderLayer.
+        (WebCore::RenderSVGImage::layout): Make sure to properly dirty the old bounds, accounting
+        for transforms.
+        (WebCore::RenderSVGImage::relativeBBox): Correct bbox computation.
+        * kcanvas/RenderSVGImage.h:
+
+        * kcanvas/RenderSVGText.cpp:
+        (WebCore::RenderSVGText::paint): Transform incoming damage rect. Handle opacity here since we
+        won't get a layer.
+        (WebCore::RenderSVGText::computeAbsoluteRepaintRect): Apply transforms.
+        (WebCore::RenderSVGText::requiresLayer): Never use a RenderLayer.
+        (WebCore::RenderSVGText::layout): Make sure to dirty the old bounds.
+
+        * platform/cg/GraphicsContextCG.cpp:
+        (WebCore::GraphicsContext::roundToDevicePixels): Instead of transforming rect to device space
+        and back to user space, do this for the origin and lower right corner of the rect. Otherwise the
+        rect will get inflated if user space is rotated or skewed.
+
+        * kcanvas/RenderPath.cpp:
+        (WebCore::RenderPath::layout): Make sure to dirty the old bounds.
+        (WebCore::RenderPath::paint): Hhandle opacity here.
+        
+        Bonus fix:
+        
+        * kcanvas/RenderSVGImage.cpp:
+        (WebCore::RenderSVGImage::nodeAtPoint): Fix hit testing. Wasn't applying the x/y
+        attribute transform.
+
 2006-06-19  Sam Weinig  <sam.weinig@gmail.com>
 
         Reviewed by Darin.
index fc0a6af32c66b1c76f3c24a012055fb439bbe439..8ef43312519f0fd37151c1c41af94e079cf68cb5 100644 (file)
@@ -42,7 +42,7 @@ svg {
     height: 100%;
 }
 
-svg, symbol, marker, pattern, foreignObject {
+svg, symbol, marker, pattern {
     overflow: hidden
 }
 
index c902e3e19278d59ef7d240ffbce741a03419f87d..0d780c12f372ff1e4d856bafd5defed3c3c8d817 100644 (file)
@@ -27,6 +27,7 @@
 #include "KCanvasContainer.h"
 #include "SVGStyledElement.h"
 #include "GraphicsContext.h"
+#include "SVGStyledTransformableElement.h"
 
 namespace WebCore {
 
@@ -43,6 +44,7 @@ public:
     FloatRect viewport;
     FloatRect viewBox;
     KCAlign align;
+    IntRect absoluteBounds;
 };
 
 KCanvasContainer::KCanvasContainer(SVGStyledElement *node)
@@ -110,13 +112,15 @@ void KCanvasContainer::layout()
 
     IntRect oldBounds;
     bool checkForRepaint = checkForRepaintDuringLayout();
-    if (checkForRepaint)
-        oldBounds = getAbsoluteRepaintRect();
+    if (selfNeedsLayout() && checkForRepaint)
+        oldBounds = d->absoluteBounds;
 
     calcWidth();
     calcHeight();
 
-    if (checkForRepaint)
+    d->absoluteBounds = getAbsoluteRepaintRect();
+
+    if (selfNeedsLayout() && checkForRepaint)
         repaintAfterLayoutIfNeeded(oldBounds, oldBounds);
         
     RenderContainer::layout();
@@ -252,9 +256,21 @@ IntRect KCanvasContainer::getAbsoluteRepaintRect()
     if (filter)
         repaintRect.unite(enclosingIntRect(filter->filterBBoxForItemBBox(repaintRect)));
 
+    // FIXME: what about transform?
+
     return repaintRect;
 }
 
+void KCanvasContainer::computeAbsoluteRepaintRect(IntRect& r, bool f)
+{
+    QMatrix transform = localTransform();
+    r = transform.mapRect(r);
+    
+    // FIXME: consider filter
+
+    RenderContainer::computeAbsoluteRepaintRect(r, f);
+}
+
 QMatrix KCanvasContainer::absoluteTransform() const
 {
     return viewportTransform() * RenderContainer::absoluteTransform();
index 61d213fbf4a3a9c7644bd0c0b8878465429ab3f5..8d0fec384c3992529d11cd4b9e676c62744b392d 100644 (file)
@@ -70,6 +70,9 @@ public:
     virtual void paint(PaintInfo &paintInfo, int parentX, int parentY);
     
     virtual IntRect getAbsoluteRepaintRect();
+
+    virtual void computeAbsoluteRepaintRect(IntRect& r, bool f);
+
     virtual QMatrix absoluteTransform() const;
 
     bool fillContains(const FloatPoint&) const;
index c6008ff7612c7a78e3767c4d85f975e2214392a5..7e79f102d622e74055e7f41dcc82ed2e4933412c 100644 (file)
@@ -56,20 +56,66 @@ void RenderForeignObject::paint(PaintInfo& paintInfo, int parentX, int parentY)
         context = paintInfo.p->createRenderingDeviceContext();
         device->pushContext(context);
         shouldPopContext = true;
-    } else
-        paintInfo.p->save();
+    }
+
+    paintInfo.p->save();
 
     context->concatCTM(QMatrix().translate(parentX, parentY));
     context->concatCTM(localTransform());
     context->concatCTM(translationForAttributes());
-    
-    RenderBlock::paint(paintInfo, 0, 0);
 
+    paintInfo.p->addClip(getClipRect(parentX, parentY));
+
+    float opacity = style()->opacity();
+    if (opacity < 1.0f)
+        paintInfo.p->beginTransparencyLayer(opacity);
+
+    PaintInfo pi(paintInfo);
+    pi.r = absoluteTransform().invert().mapRect(paintInfo.r);
+    RenderBlock::paint(pi, 0, 0);
+
+    if (opacity < 1.0f)
+        paintInfo.p->endTransparencyLayer();
+    
     if (shouldPopContext) {
         device->popContext();
         delete context;
-    } else
-        paintInfo.p->restore();
+    }
+
+    paintInfo.p->restore();
+}
+
+void RenderForeignObject::computeAbsoluteRepaintRect(IntRect& r, bool f)
+{
+    QMatrix transform = translationForAttributes() * localTransform();
+    r = transform.mapRect(r);
+    
+    RenderBlock::computeAbsoluteRepaintRect(r, f);
+}
+
+bool RenderForeignObject::requiresLayer()
+{
+    return false;
+}
+
+void RenderForeignObject::layout()
+{
+    KHTMLAssert(needsLayout());
+    KHTMLAssert(minMaxKnown());
+
+    IntRect oldBounds;
+    bool checkForRepaint = checkForRepaintDuringLayout();
+    if (checkForRepaint)
+        oldBounds = m_absoluteBounds;
+
+    RenderBlock::layout();
+
+    m_absoluteBounds = getAbsoluteRepaintRect();
+
+    if (checkForRepaint)
+        repaintAfterLayoutIfNeeded(oldBounds, oldBounds);
+    
+    setNeedsLayout(false);
 }
 
 bool RenderForeignObject::nodeAtPoint(NodeInfo& info, int x, int y, int tx, int ty, HitTestAction hitTestAction)
index 33bfcace7a9a39c9fb3dcb20883fad39ba64764e..c9fe6cea63c5db5261a7afde79fc997f3ce410d2 100644 (file)
@@ -36,17 +36,22 @@ class RenderForeignObject : public RenderBlock
 public:
     RenderForeignObject(SVGForeignObjectElement *node);
     
-    const char *renderName() const { return "RenderForeignObject"; }
-    void paint(PaintInfo& paintInfo, int parentX, int parentY);
+    virtual const char *renderName() const { return "RenderForeignObject"; }
+    virtual void paint(PaintInfo& paintInfo, int parentX, int parentY);
     
     virtual QMatrix localTransform() const { return m_transform; }
     virtual void setLocalTransform(const QMatrix& transform) { m_transform = transform; }
     
-    bool nodeAtPoint(NodeInfo&, int x, int y, int tx, int ty, HitTestAction);
+    virtual void computeAbsoluteRepaintRect(IntRect& r, bool f);
+    virtual bool requiresLayer();
+    virtual void layout();
+
+    virtual bool nodeAtPoint(NodeInfo&, int x, int y, int tx, int ty, HitTestAction);
 
  private:
     QMatrix translationForAttributes();
     QMatrix m_transform;
+    IntRect m_absoluteBounds;
 };
 
 }
index 3e40f3ec696cf5844033d548e0af3554cc4f7fa7..8edcfbe5dd671af392be83ebc1fad145c5921cd6 100644 (file)
@@ -39,8 +39,10 @@ class RenderPath::Private {
 public:
     RefPtr<KCanvasPath> path;
 
-    FloatRect fillBBox, strokeBbox;
+    FloatRect fillBBox;
+    FloatRect strokeBbox;
     QMatrix matrix;
+    IntRect absoluteBounds;
 };        
 
 // RenderPath
@@ -143,10 +145,21 @@ void RenderPath::layout()
     // pretend that one of the attributes of the element has changed on the DOM
     // to force the DOM object to update this render object with new aboslute position values.
 
+    IntRect oldBounds;
+    bool checkForRepaint = checkForRepaintDuringLayout();
+    if (selfNeedsLayout() && checkForRepaint)
+        oldBounds = d->absoluteBounds;
+
     static_cast<SVGStyledElement*>(element())->notifyAttributeChange();
-    IntRect layoutRect = getAbsoluteRepaintRect();
-    setWidth(layoutRect.width());
-    setHeight(layoutRect.height());
+
+    d->absoluteBounds = getAbsoluteRepaintRect();
+
+    setWidth(d->absoluteBounds.width());
+    setHeight(d->absoluteBounds.height());
+
+    if (selfNeedsLayout() && checkForRepaint)
+        repaintAfterLayoutIfNeeded(oldBounds, oldBounds);
+        
     setNeedsLayout(false);
 }
 
@@ -230,8 +243,7 @@ void RenderPath::paint(PaintInfo &paintInfo, int parentX, int parentY)
         strokePaintServer->draw(context, this, APPLY_TO_STROKE);
     }
 
-    OwnPtr<GraphicsContext> c(context->createGraphicsContext());
-    drawMarkersIfNeeded(c.get(), paintInfo.r, path());
+    drawMarkersIfNeeded(paintInfo.p, paintInfo.r, path());
 
     // actually apply the filter
     if (filter)
index a65f0bc0bbe1f1f99a542966fccb83463254318f..8f5c79e8855e9f2a8755b4ac889975969455e538 100644 (file)
@@ -158,8 +158,14 @@ void RenderSVGImage::paint(PaintInfo& paintInfo, int parentX, int parentY)
         filter->prepareFilter(boundingBox);
     
     OwnPtr<GraphicsContext> c(device->currentContext()->createGraphicsContext());
-    PaintInfo pi = paintInfo;
+
+    float opacity = style()->opacity();
+    if (opacity < 1.0f)
+        c->beginTransparencyLayer(opacity);
+
+    PaintInfo pi(paintInfo);
     pi.p = c.get();
+    pi.r = absoluteTransform().invert().mapRect(paintInfo.r);
 
     int x = 0, y = 0;
     if (!shouldPaint(pi, x, y))
@@ -179,6 +185,9 @@ void RenderSVGImage::paint(PaintInfo& paintInfo, int parentX, int parentY)
     if (filter)
         filter->applyFilter(boundingBox);
     
+    if (opacity < 1.0f)
+        c->endTransparencyLayer();
+
     // restore drawing state
     if (!shouldPopContext)
         paintInfo.p->restore();
@@ -188,9 +197,58 @@ void RenderSVGImage::paint(PaintInfo& paintInfo, int parentX, int parentY)
     }
 }
 
+void RenderSVGImage::computeAbsoluteRepaintRect(IntRect& r, bool f)
+{
+    QMatrix transform = translationForAttributes() * localTransform();
+    r = transform.mapRect(r);
+    
+    RenderImage::computeAbsoluteRepaintRect(r, f);
+}
+
+bool RenderSVGImage::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction)
+{
+    QMatrix totalTransform = absoluteTransform();
+    totalTransform *= translationForAttributes();
+    double localX, localY;
+    totalTransform.invert().map(_x + _tx, _y + _ty, &localX, &localY);
+    return RenderImage::nodeAtPoint(info, (int)localX, (int)localY, 0, 0, hitTestAction);
+}
+
+bool RenderSVGImage::requiresLayer()
+{
+    return false;
+}
+
+void RenderSVGImage::layout()
+{
+    KHTMLAssert(needsLayout());
+    KHTMLAssert(minMaxKnown());
+
+    IntRect oldBounds;
+    bool checkForRepaint = checkForRepaintDuringLayout();
+    if (checkForRepaint)
+        oldBounds = m_absoluteBounds;
+
+    // minimum height
+    m_height = cachedImage() && cachedImage() ? intrinsicHeight() : 0;
+
+    calcWidth();
+    calcHeight();
+
+    m_absoluteBounds = getAbsoluteRepaintRect();
+
+    if (checkForRepaint)
+        repaintAfterLayoutIfNeeded(oldBounds, oldBounds);
+    
+    setNeedsLayout(false);
+}
+
 FloatRect RenderSVGImage::relativeBBox(bool includeStroke) const
 {
-    return FloatRect(0, 0, width(), height());
+    SVGImageElement *image = static_cast<SVGImageElement *>(node());
+    float xOffset = image->x()->baseVal() ? image->x()->baseVal()->value() : 0;
+    float yOffset = image->y()->baseVal() ? image->y()->baseVal()->value() : 0;
+    return FloatRect(xOffset, yOffset, width(), height());
 }
 
 void RenderSVGImage::imageChanged(CachedImage* image)
@@ -220,13 +278,19 @@ void RenderSVGImage::absoluteRects(DeprecatedValueList<IntRect>& rects, int _tx,
     rects.append(getAbsoluteRepaintRect());
 }
 
-void RenderSVGImage::translateForAttributes()
+
+QMatrix RenderSVGImage::translationForAttributes()
 {
-    KRenderingDeviceContext *context = renderingDevice()->currentContext();
     SVGImageElement *image = static_cast<SVGImageElement *>(node());
     float xOffset = image->x()->baseVal() ? image->x()->baseVal()->value() : 0;
     float yOffset = image->y()->baseVal() ? image->y()->baseVal()->value() : 0;
-    context->concatCTM(QMatrix().translate(xOffset, yOffset));
+    return QMatrix().translate(xOffset, yOffset);
+}
+
+void RenderSVGImage::translateForAttributes()
+{
+    KRenderingDeviceContext *context = renderingDevice()->currentContext();
+    context->concatCTM(translationForAttributes());
 }
 
 }
index 1ccf2e578485fe74c5e1d687340a4a8b9dc1c048..d153a23551964454bcaf1a0d2ae38a4e8fd23cb9 100644 (file)
@@ -47,9 +47,19 @@ namespace WebCore
         virtual void imageChanged(CachedImage*);
         void adjustRectsForAspectRatio(FloatRect& destRect, FloatRect& srcRect, SVGPreserveAspectRatio *aspectRatio);
         virtual void paint(PaintInfo& paintInfo, int parentX, int parentY);
+        virtual void layout();
+
+        bool requiresLayer();
+
+        virtual void computeAbsoluteRepaintRect(IntRect& r, bool f);
+
+        virtual bool RenderSVGImage::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction);
+
     private:
         void translateForAttributes();
+        QMatrix translationForAttributes();
         QMatrix m_transform;
+        IntRect m_absoluteBounds;
     };
 }
 
index 110d7ff3a0e8a25ff4a096ddf3dfb5520d2d10b2..369f21ea38d6be2f3ffbd0d328cbb4f356dc4abb 100644 (file)
@@ -91,9 +91,14 @@ void RenderSVGText::paint(PaintInfo& paintInfo, int parentX, int parentY)
     if (filter)
         filter->prepareFilter(boundingBox);
         
+    float opacity = style()->opacity();
+    if (opacity < 1.0f)
+        paintInfo.p->beginTransparencyLayer(opacity);
+
     OwnPtr<GraphicsContext> c(device->currentContext()->createGraphicsContext());
     PaintInfo pi = paintInfo;
     pi.p = c.get();
+    pi.r = (translationTopToBaseline() * translationForAttributes() * absoluteTransform()).invert().mapRect(paintInfo.r);
 
     KRenderingPaintServer *fillPaintServer = KSVGPainterFactory::fillPaintServer(style(), this);
     if (fillPaintServer) {
@@ -120,6 +125,9 @@ void RenderSVGText::paint(PaintInfo& paintInfo, int parentX, int parentY)
     if (filter)
         filter->applyFilter(boundingBox);
 
+    if (opacity < 1.0f)
+        paintInfo.p->endTransparencyLayer();
+
     // restore drawing state
     if (!shouldPopContext)
         paintInfo.p->restore();
@@ -129,6 +137,40 @@ void RenderSVGText::paint(PaintInfo& paintInfo, int parentX, int parentY)
     }
 }
 
+void RenderSVGText::computeAbsoluteRepaintRect(IntRect& r, bool f)
+{
+    QMatrix transform = translationTopToBaseline() * translationForAttributes() * absoluteTransform();
+    r = transform.mapRect(r);
+    
+    RenderContainer::computeAbsoluteRepaintRect(r, f);
+}
+
+bool RenderSVGText::requiresLayer()
+{
+    return false;
+}
+
+void RenderSVGText::layout()
+{
+    KHTMLAssert(needsLayout());
+    KHTMLAssert(minMaxKnown());
+
+    IntRect oldBounds;
+    bool checkForRepaint = checkForRepaintDuringLayout();
+    if (checkForRepaint)
+        oldBounds = m_absoluteBounds;
+
+    RenderBlock::layout();
+
+    m_absoluteBounds = getAbsoluteRepaintRect();
+
+    bool repainted = false;
+    if (checkForRepaint)
+        repainted = repaintAfterLayoutIfNeeded(oldBounds, oldBounds);
+    
+    setNeedsLayout(false);
+}
+
 bool RenderSVGText::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction)
 {
     QMatrix totalTransform = translationForAttributes();
index 7dc08ba1773006cf77aaeafeeb43a14a435cbfc1..944322ebc1c85cfb417c43a0704ea91cc90b741e 100644 (file)
@@ -36,20 +36,25 @@ class RenderSVGText : public RenderBlock
 public:
     RenderSVGText(SVGTextElement *node);
 
-    const char *renderName() const { return "RenderSVGText"; }
-    void paint(PaintInfo&, int parentX, int parentY);
+    virtual const char *renderName() const { return "RenderSVGText"; }
+    virtual void paint(PaintInfo&, int parentX, int parentY);
     
     virtual QMatrix localTransform() const { return m_transform; }
     virtual void setLocalTransform(const QMatrix& transform) { m_transform = transform; }
     
-    bool nodeAtPoint(NodeInfo&, int _x, int _y, int _tx, int _ty, WebCore::HitTestAction);
+    virtual bool nodeAtPoint(NodeInfo&, int _x, int _y, int _tx, int _ty, WebCore::HitTestAction);
 
     virtual void absoluteRects(DeprecatedValueList<IntRect>& rects, int tx, int ty);
 
+    virtual void computeAbsoluteRepaintRect(IntRect& r, bool f);
+    virtual bool requiresLayer();
+    virtual void layout();
+    
  private:
     QMatrix translationTopToBaseline();
     QMatrix translationForAttributes();
     QMatrix m_transform;
+    IntRect m_absoluteBounds;
 };
 
 }
diff --git a/WebCore/manual-tests/resources/3dolph.gif b/WebCore/manual-tests/resources/3dolph.gif
new file mode 100644 (file)
index 0000000..2a1801f
Binary files /dev/null and b/WebCore/manual-tests/resources/3dolph.gif differ
diff --git a/WebCore/manual-tests/svg-animated-gifs.svg b/WebCore/manual-tests/svg-animated-gifs.svg
new file mode 100644 (file)
index 0000000..c661d41
--- /dev/null
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+        xmlns:xlink="http://www.w3.org/1999/xlink"
+        xmlns:xhtml="http://www.w3.org/1999/xhtml">
+        
+        <text x="0" y="15">There should be only one animated image here, rotated, and not clipped:</text>
+        <g transform="rotate(45,100,250)" >
+            <rect fill="yellow" stroke="#000000" stroke-width="2" x="60" y="60" width="170" height="170" />
+                <foreignObject x="70" y="70" width="150" height="150" >
+                        <xhtml:img src="resources/3dolph.gif" width="150" height="150" />
+                </foreignObject>
+        </g>
+        <text x="0" y="400">The animated image below should look like the one above:</text>
+        <g transform="translate(0, 400) rotate(45,100,250)" >
+            <rect fill="yellow" stroke="#000000" stroke-width="2" x="60" y="60" width="170" height="170" />
+            <image xlink:href="resources/3dolph.gif" x="60" y="60" width="170" height="170" />
+        </g>
+</svg>
diff --git a/WebCore/manual-tests/svg-repaint-foreignObject.svg b/WebCore/manual-tests/svg-repaint-foreignObject.svg
new file mode 100644 (file)
index 0000000..5c5cad9
--- /dev/null
@@ -0,0 +1,81 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+        xmlns:xlink="http://www.w3.org/1999/xlink"
+        xmlns:xhtml="http://www.w3.org/1999/xhtml">
+        
+<script>
+<![CDATA[
+var stateIndex = 0;
+var currentTarget = 0;
+
+function stateA() 
+{
+    document.getElementById("A").textContent = '|A|';
+    document.getElementById("B").textContent = 'B';
+
+    currentTarget = 0;
+    startAnimation();
+}
+
+function stateB() 
+{
+    document.getElementById("A").textContent = 'A';
+    document.getElementById("B").textContent = '|B|';
+    currentTarget = 1;
+    startAnimation();
+}
+
+var intervalId = null;
+
+function startAnimation() {
+    if (intervalId == null) {
+       intervalId = setInterval(animationStep, 20);
+    }
+}
+
+function animationStep() {
+    if (Math.abs(stateIndex - currentTarget) < .001) {
+       clearInterval(intervalId);
+       intervalId = null;
+       return;
+    }
+
+    if (stateIndex < currentTarget) {
+       stateIndex += 1 / 128;
+    } else {
+       stateIndex -= 1 / 128;
+    }
+
+    var elt = document.getElementById("targetGroup");
+
+    var transform = "translate(" + (100 * stateIndex) + "," + (100 * stateIndex) + ") rotate(" + (405 * stateIndex) + ",100,250) scale(" + (1 +  stateIndex)  + ")" ;
+    var opacity = 1 - .75 * stateIndex;
+
+    elt.setAttribute("opacity", opacity);
+    elt.setAttribute("transform", transform);
+}
+
+
+]]>
+</script>
+
+        <text id="A" x="0" y="32" fill="red" font-size="32" onclick="stateA()">|A|</text>
+        <text id="B" x="60" y="32" fill="blue" font-size="32" onclick="stateB()">B</text>
+        <text x="0" y="642" fill="black" font-size="32">Click B and then A above.</text>
+        <text x="0" y="674" fill="black" font-size="32">The animation should have no trails or clipping.</text>
+
+       <circle fill="pink" cx="300" cy="300" stroke="lightblue" stroke-width="40" r="300" />
+
+        <g>
+            <rect fill="yellow" stroke="#000000" stroke-width="2" x="60" y="60" width="170" height="170" />
+
+               <foreignObject  id="targetGroup" x="60" y="60" width="170" height="170" >
+                   <xhtml:xhtml>
+                        <xhtml:img src="http://www.citilink.com/~grizzly/anigifs/3dolph.gif" width="170" height="170" /><br />
+                   </xhtml:xhtml>
+                </foreignObject>
+        </g>
+
+
+</svg>
+
+
diff --git a/WebCore/manual-tests/svg-repaint-group.svg b/WebCore/manual-tests/svg-repaint-group.svg
new file mode 100644 (file)
index 0000000..7f0972e
--- /dev/null
@@ -0,0 +1,81 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+        xmlns:xlink="http://www.w3.org/1999/xlink"
+        xmlns:xhtml="http://www.w3.org/1999/xhtml">
+        
+<script>
+<![CDATA[
+var stateIndex = 0;
+var currentTarget = 0;
+
+function stateA() 
+{
+    document.getElementById("A").textContent = '|A|';
+    document.getElementById("B").textContent = 'B';
+
+    currentTarget = 0;
+    startAnimation();
+}
+
+function stateB() 
+{
+    document.getElementById("A").textContent = 'A';
+    document.getElementById("B").textContent = '|B|';
+    currentTarget = 1;
+    startAnimation();
+}
+
+var intervalId = null;
+
+function startAnimation() {
+    if (intervalId == null) {
+       intervalId = setInterval(animationStep, 20);
+    }
+}
+
+function animationStep() {
+    if (Math.abs(stateIndex - currentTarget) < .001) {
+       clearInterval(intervalId);
+       intervalId = null;
+       return;
+    }
+
+    if (stateIndex < currentTarget) {
+       stateIndex += 1 / 128;
+    } else {
+       stateIndex -= 1 / 128;
+    }
+
+    var elt = document.getElementById("targetGroup");
+
+    var transform = "translate(" + (100 * stateIndex) + "," + (100 * stateIndex) + ") rotate(" + (405 * stateIndex) + ",100,250) scale(" + (1 +  stateIndex)  + ")" ;
+    var opacity = 1 - .75 * stateIndex;
+
+    elt.setAttribute("opacity", opacity);
+    elt.setAttribute("transform", transform);
+}
+
+
+]]>
+</script>
+
+        <text id="A" x="0" y="32" fill="red" font-size="32" onclick="stateA()">|A|</text>
+        <text id="B" x="60" y="32" fill="blue" font-size="32" onclick="stateB()">B</text>
+        <text x="0" y="642" fill="black" font-size="32">Click B and then A above.</text>
+        <text x="0" y="674" fill="black" font-size="32">The animation should have no trails or clipping.</text>
+
+       <circle fill="pink" cx="300" cy="300" stroke="lightblue" stroke-width="40" r="300" />
+
+        <g id="targetGroup">
+            <rect fill="yellow" stroke="#000000" stroke-width="2" x="60" y="60" width="170" height="170" />
+
+               <foreignObject x="60" y="60" width="170" height="170" >
+                   <xhtml:xhtml>
+                        <xhtml:img src="http://www.citilink.com/~grizzly/anigifs/3dolph.gif" width="170" height="170" /><br />
+                   </xhtml:xhtml>
+                </foreignObject>
+        </g>
+
+
+</svg>
+
+
diff --git a/WebCore/manual-tests/svg-repaint-image.svg b/WebCore/manual-tests/svg-repaint-image.svg
new file mode 100644 (file)
index 0000000..018a6a0
--- /dev/null
@@ -0,0 +1,77 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+        xmlns:xlink="http://www.w3.org/1999/xlink"
+        xmlns:xhtml="http://www.w3.org/1999/xhtml">
+        
+<script>
+<![CDATA[
+var stateIndex = 0;
+var currentTarget = 0;
+
+function stateA() 
+{
+    document.getElementById("A").textContent = '|A|';
+    document.getElementById("B").textContent = 'B';
+
+    currentTarget = 0;
+    startAnimation();
+}
+
+function stateB() 
+{
+    document.getElementById("A").textContent = 'A';
+    document.getElementById("B").textContent = '|B|';
+    currentTarget = 1;
+    startAnimation();
+}
+
+var intervalId = null;
+
+function startAnimation() {
+    if (intervalId == null) {
+       intervalId = setInterval(animationStep, 20);
+    }
+}
+
+function animationStep() {
+    if (Math.abs(stateIndex - currentTarget) < .001) {
+       clearInterval(intervalId);
+       intervalId = null;
+       return;
+    }
+
+    if (stateIndex < currentTarget) {
+       stateIndex += 1 / 128;
+    } else {
+       stateIndex -= 1 / 128;
+    }
+
+    var elt = document.getElementById("targetGroup");
+
+    var transform = "translate(" + (100 * stateIndex) + "," + (100 * stateIndex) + ") rotate(" + (405 * stateIndex) + ",100,250) scale(" + (1 +  stateIndex)  + ")" ;
+    var opacity = 1 - .75 * stateIndex;
+
+    elt.setAttribute("opacity", opacity);
+    elt.setAttribute("transform", transform);
+}
+
+
+]]>
+</script>
+
+        <text id="A" x="0" y="32" fill="red" font-size="32" onclick="stateA()">|A|</text>
+        <text id="B" x="60" y="32" fill="blue" font-size="32" onclick="stateB()">B</text>
+        <text x="0" y="642" fill="black" font-size="32">Click B and then A above.</text>
+        <text x="0" y="674" fill="black" font-size="32">The animation should have no trails or clipping.</text>
+
+       <circle fill="pink" cx="300" cy="300" stroke="lightblue" stroke-width="40" r="300" />
+
+        <g>
+            <rect fill="yellow" stroke="#000000" stroke-width="2" x="60" y="60" width="170" height="170" />
+
+               <image  id="targetGroup" x="60" y="60" width="170" height="170" xlink:href="resources/3dolph.gif"  />
+        </g>
+
+
+</svg>
+
+
diff --git a/WebCore/manual-tests/svg-repaint-path.svg b/WebCore/manual-tests/svg-repaint-path.svg
new file mode 100644 (file)
index 0000000..144e8ae
--- /dev/null
@@ -0,0 +1,77 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+        xmlns:xlink="http://www.w3.org/1999/xlink"
+        xmlns:xhtml="http://www.w3.org/1999/xhtml">
+        
+<script>
+<![CDATA[
+var stateIndex = 0;
+var currentTarget = 0;
+
+function stateA() 
+{
+    document.getElementById("A").textContent = '|A|';
+    document.getElementById("B").textContent = 'B';
+
+    currentTarget = 0;
+    startAnimation();
+}
+
+function stateB() 
+{
+    document.getElementById("A").textContent = 'A';
+    document.getElementById("B").textContent = '|B|';
+    currentTarget = 1;
+    startAnimation();
+}
+
+var intervalId = null;
+
+function startAnimation() {
+    if (intervalId == null) {
+       intervalId = setInterval(animationStep, 20);
+    }
+}
+
+function animationStep() {
+    if (Math.abs(stateIndex - currentTarget) < .001) {
+       clearInterval(intervalId);
+       intervalId = null;
+       return;
+    }
+
+    if (stateIndex < currentTarget) {
+       stateIndex += 1 / 128;
+    } else {
+       stateIndex -= 1 / 128;
+    }
+
+    var elt = document.getElementById("targetGroup");
+
+    var transform = "translate(" + (100 * stateIndex) + "," + (100 * stateIndex) + ") rotate(" + (405 * stateIndex) + ",100,250) scale(" + (1 +  stateIndex)  + ")" ;
+    var opacity = 1 - .75 * stateIndex;
+
+    elt.setAttribute("opacity", opacity);
+    elt.setAttribute("transform", transform);
+}
+
+
+]]>
+</script>
+
+        <text id="A" x="0" y="32" fill="red" font-size="32" onclick="stateA()">|A|</text>
+        <text id="B" x="60" y="32" fill="blue" font-size="32" onclick="stateB()">B</text>
+        <text x="0" y="642" fill="black" font-size="32">Click B and then A above.</text>
+        <text x="0" y="674" fill="black" font-size="32">The animation should have no trails or clipping.</text>
+
+       <circle fill="pink" cx="300" cy="300" stroke="lightblue" stroke-width="40" r="300" />
+
+        <g>
+            <rect id="targetGroup" fill="yellow" stroke="#000000" stroke-width="2" x="60" y="60" width="170" height="170" />
+
+               <image x="60" y="60" width="170" height="170" xlink:href="resources/3dolph.gif"  />
+        </g>
+
+
+</svg>
+
+
index cc4330ac901f9431b6df9b7d34aa77af6e04dbaf..5580046ea2f3923cc7988d8057b0b0101c38b813 100644 (file)
@@ -636,19 +636,27 @@ void GraphicsContext::translate(const FloatSize& size)
 
 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect)
 {
-    CGRect deviceRect = CGContextConvertRectToDeviceSpace(platformContext(), rect);
-    deviceRect.origin.x = roundf(deviceRect.origin.x);
-    deviceRect.origin.y = roundf(deviceRect.origin.y);
-    deviceRect.size.width = roundf(deviceRect.size.width);
-    deviceRect.size.height = roundf(deviceRect.size.height);
+    // It's important to separately transform the two corners, as transforming the whole rect
+    // will grab bounding boxes, and so will enlarge the rect when there is any rotation or skew
+    // in the current transform
+    CGPoint deviceOrigin = CGContextConvertPointToDeviceSpace(platformContext(), rect.location());
+    CGPoint deviceLowerRight = CGContextConvertPointToDeviceSpace(platformContext(), rect.location() + rect.size());
+
+    deviceOrigin.x = roundf(deviceOrigin.x);
+    deviceOrigin.y = roundf(deviceOrigin.y);
+    deviceLowerRight.x = roundf(deviceLowerRight.x);
+    deviceLowerRight.y = roundf(deviceLowerRight.y);
     
     // Don't let the height or width round to 0 unless either was originally 0
-    if (deviceRect.size.height == 0 && rect.height() != 0)
-        deviceRect.size.height = 1;
-    if (deviceRect.size.width == 0 && rect.width() != 0)
-        deviceRect.size.width = 1;
+    if (deviceOrigin.y == deviceLowerRight.y && rect.height() != 0)
+        deviceLowerRight.y += 1;
+    if (deviceOrigin.x == deviceLowerRight.x && rect.width() != 0)
+        deviceLowerRight.x += 1;
     
-    return CGContextConvertRectToUserSpace(platformContext(), deviceRect);
+    FloatPoint roundedOrigin = CGContextConvertPointToUserSpace(platformContext(), deviceOrigin);
+    FloatPoint roundedLowerRight = CGContextConvertPointToUserSpace(platformContext(), deviceLowerRight);
+
+    return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin);
 }
 
 void GraphicsContext::drawLineForText(const IntPoint& point, int yOffset, int width, bool printing)