https://bugs.webkit.org/show_bug.cgi?id=21586
authordino@apple.com <dino@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 25 Aug 2011 19:17:51 +0000 (19:17 +0000)
committerdino@apple.com <dino@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 25 Aug 2011 19:17:51 +0000 (19:17 +0000)
Apply CSS animations and transitions to SVG properties

Reviewed by Simon Fraser.

Expose the applicable SVG properties from SVGRenderStyle to
RenderStyle and add them to the list of animatable
properties. There aren't many SVG properties that are
animatable directly. SVGPaint (used in "fill" and "stroke")
is supported only when both ends of the animation are
a Color. Also updated SVGLength to have some methods
for converting to and from user space units.

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

LayoutTests/ChangeLog
LayoutTests/transitions/resources/transition-test-helpers.js
LayoutTests/transitions/svg-transitions-expected.txt [new file with mode: 0644]
LayoutTests/transitions/svg-transitions.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/page/animation/AnimationBase.cpp
Source/WebCore/rendering/style/RenderStyle.h
Source/WebCore/rendering/style/SVGRenderStyle.h
Source/WebCore/svg/SVGLength.cpp
Source/WebCore/svg/SVGLength.h

index 3070f1c..678afb9 100644 (file)
@@ -1,3 +1,23 @@
+2011-08-25  Dean Jackson  <dino@apple.com>
+
+        https://bugs.webkit.org/show_bug.cgi?id=21586
+        Apply CSS animations and transitions to SVG properties
+
+        Reviewed by Simon Fraser.
+
+        New test for the SVG properties that can now animate. Also
+        updated the test helper script to handle colors and
+        percentages.
+
+        This test has one entry that intentionally fails for a type
+        of animation that we don't yet support: with a paint
+        server of the form "url(#invalidref) green".
+
+        * transitions/resources/transition-test-helpers.js:
+        (expected):
+        * transitions/svg-transitions-expected.txt: Added.
+        * transitions/svg-transitions.html: Added.
+
 2011-08-25  Igor Oliveira  <igor.oliveira@openbossa.org>
 
         [Qt] Unreviewed, rebaseline for bug 53438
index f494da4..d67a41e 100644 (file)
@@ -52,6 +52,13 @@ function getShadowXY(cssValue)
     return [parseInt(result[1]), parseInt(result[2])];
 }
 
+function compareRGB(rgb, expected, tolerance)
+{
+    return (isCloseEnough(parseInt(rgb[0]), expected[0], tolerance) &&
+            isCloseEnough(parseInt(rgb[1]), expected[1], tolerance) &&
+            isCloseEnough(parseInt(rgb[2]), expected[2], tolerance));
+}
+
 function checkExpectedValue(expected, index)
 {
     var time = expected[index][0];
@@ -81,6 +88,24 @@ function checkExpectedValue(expected, index)
                     break;
             }
         }
+    } else if (property == "fill" || property == "stroke") {
+        computedValue = window.getComputedStyle(document.getElementById(elementId)).getPropertyCSSValue(property).rgbColor;
+        if (compareRGB([computedValue.red.cssText, computedValue.green.cssText, computedValue.blue.cssText], expectedValue, tolerance))
+            pass = true;
+        else {
+            // We failed. Make sure computed value is something we can read in the error message
+            computedValue = window.getComputedStyle(document.getElementById(elementId)).getPropertyCSSValue(property).cssText;
+        }
+    } else if (property == "stop-color" || property == "flood-color" || property == "lighting-color") {
+        computedValue = window.getComputedStyle(document.getElementById(elementId)).getPropertyCSSValue(property);
+        // The computedValue cssText is rgb(num, num, num)
+        var components = computedValue.cssText.split("(")[1].split(")")[0].split(",");
+        if (compareRGB(components, expectedValue, tolerance))
+            pass = true;
+        else {
+            // We failed. Make sure computed value is something we can read in the error message
+            computedValue = computedValue.cssText;
+        }
     } else if (property == "lineHeight") {
         computedValue = parseInt(window.getComputedStyle(document.getElementById(elementId)).lineHeight);
         pass = isCloseEnough(computedValue, expectedValue, tolerance);
@@ -133,6 +158,10 @@ function checkExpectedValue(expected, index)
                      for (var i = 0; i < 4; ++i)
                          pass &= isCloseEnough(computedValue[i], expectedValue[i], tolerance);
                     break;
+                case CSSPrimitiveValue.CSS_PERCENTAGE:
+                    computedValue = parseFloat(computedStyle.cssText);
+                    pass = isCloseEnough(computedValue, expectedValue, tolerance);
+                    break;
                 default:
                     computedValue = computedStyle.getFloatValue(CSSPrimitiveValue.CSS_NUMBER);
                     pass = isCloseEnough(computedValue, expectedValue, tolerance);
diff --git a/LayoutTests/transitions/svg-transitions-expected.txt b/LayoutTests/transitions/svg-transitions-expected.txt
new file mode 100644 (file)
index 0000000..5a53092
--- /dev/null
@@ -0,0 +1,21 @@
+CONSOLE MESSAGE: line 212: Failed to pause 'fill' transition on element 'rect7'
+Example
+PASS - "fill-opacity" property for "rect1" element at 1s saw something close to: 0.6
+PASS - "stroke-width" property for "rect1" element at 1s saw something close to: 3
+PASS - "stroke-opacity" property for "rect1" element at 1s saw something close to: 0.6
+PASS - "stroke-dashoffset" property for "rect1" element at 1s saw something close to: 5
+PASS - "fill" property for "rect2" element at 1s saw something close to: 0,127,127
+PASS - "stroke" property for "rect2" element at 1s saw something close to: 127,0,0
+PASS - "stroke-width" property for "rect4" element at 1s saw something close to: 8
+PASS - "stroke-width" property for "rect5" element at 1s saw something close to: 5
+PASS - "stroke-width" property for "rect6" element at 1s saw something close to: 15
+FAIL - "fill" property for "rect7" element at 1s expected: 0,0,127 but saw: #invalid #0000ff
+PASS - "stop-color" property for "stop1" element at 1s saw something close to: 127,127,0
+PASS - "stop-opacity" property for "stop1" element at 1s saw something close to: 0.75
+PASS - "stroke-miterlimit" property for "polyline1" element at 1s saw something close to: 11
+PASS - "kerning" property for "text1" element at 1s saw something close to: 5
+PASS - "baseline-shift" property for "text1" element at 1s saw something close to: 5
+PASS - "flood-opacity" property for "flood1" element at 1s saw something close to: 0.5
+PASS - "flood-color" property for "flood1" element at 1s saw something close to: 127,127,0
+PASS - "lighting-color" property for "light1" element at 1s saw something close to: 127,127,0
+
diff --git a/LayoutTests/transitions/svg-transitions.html b/LayoutTests/transitions/svg-transitions.html
new file mode 100644 (file)
index 0000000..f298a5d
--- /dev/null
@@ -0,0 +1,185 @@
+<html>
+<head>
+  <style>
+
+  svg * {
+      -webkit-transition-property: fill, stroke, fill-opacity, stroke-opacity, stroke-width, stroke-dashoffset, stroke-miterlimit, kerning, baseline-shift, flood-color, flood-opacity, stop-color, stop-opacity, lighting-color;
+      -webkit-transition-duration: 2s;
+      -webkit-transition-timing-function: linear;
+  }
+
+  #rect1 {
+      fill: rgb(0, 0, 255);
+      stroke: red;
+      fill-opacity: 1;
+      stroke-opacity: 1;
+      stroke-width: 2;
+      stroke-dashoffset: 0;
+      stroke-dasharray: 10;
+  }
+  .animating #rect1 {
+      fill: rgb(0, 255, 0);
+      stroke: black;
+      fill-opacity: 0.2;
+      stroke-opacity: 0.2;
+      stroke-width: 4;
+      stroke-dashoffset: 10;
+  }
+
+  #rect2 {
+      fill: rgb(0, 0, 255);
+      stroke: rgb(255, 0, 0);
+  }
+  .animating #rect2 {
+      fill: rgb(0, 255, 0);
+      stroke: rgb(0, 0, 0);
+  }
+
+  #rect3 {
+      fill: url(#gradient1);
+  }
+  .animating #rect3 {
+      fill: url(#gradient2);
+  }
+
+  #stop1 {
+      stop-color: rgb(255,0,0);
+      stop-opacity: 1;
+  }
+  .animating #stop1 {
+      stop-color: rgb(0,255,0);
+      stop-opacity: 0.5;
+  }
+
+  #rect4 {
+      stroke: red;
+      stroke-width: 1px;
+  }
+  .animating #rect4 {
+      stroke-width: 4mm;
+  }
+
+  #rect5 {
+      stroke: black;
+      stroke-width: 0;
+  }
+  .animating #rect5 {
+      stroke-width: 10px;
+  }
+
+  #rect6 {
+      stroke: black;
+      stroke-width: 10%;
+  }
+  .animating #rect6 {
+      stroke-width: 20%;
+  }
+
+  #rect7 {
+      fill: url(#invalid) black;
+  }
+  .animating #rect7 {
+      fill: url(#invalid) blue;
+  }
+
+  #polyline1 {
+      fill: none;
+      stroke: black;
+      stroke-width: 4;
+      stroke-miterlimit: 12;
+  }
+  .animating #polyline1 {
+      stroke-miterlimit: 10;
+  }
+
+  #text1 {
+      kerning: 0;
+      baseline-shift: 0;
+  }
+  .animating #text1 {
+      kerning: 10px;
+      baseline-shift: 10px;
+  }
+
+  #flood1 {
+      flood-opacity: 1;
+      flood-color: rgb(255, 0, 0);
+  }
+  .animating #flood1 {
+      flood-opacity: 0;
+      flood-color: rgb(0, 255, 0);
+  }
+
+  #light1 {
+      lighting-color: rgb(255, 0, 0);
+  }
+  .animating #light1 {
+      lighting-color: rgb(0, 255, 0);
+  }
+  </style>
+  <script src="resources/transition-test-helpers.js"></script>
+  <script>
+  const expectedValues = [
+  // [time, element-id, property, expected-value, tolerance]
+  [1, "rect1", "fill-opacity", 0.6, 0.1], // 1 -> 0.2
+  [1, "rect1", "stroke-width", 3, 0.5],   // 2 -> 4
+  [1, "rect1", "stroke-opacity", 0.6, 0.1], // 1 -> 0.2
+  [1, "rect1", "stroke-dashoffset", 5, 1], // 0 -> 10
+  [1, "rect2", "fill", [0, 127, 127], 20], // rgb(0, 0, 255) -> rgb(0, 255, 0)
+  [1, "rect2", "stroke", [127, 0, 0], 20],  // rgb(255, 0, 0) -> rgb(0, 0, 0)
+  [1, "rect4", "stroke-width", 8, 1],  // 1px to 4mm
+  [1, "rect5", "stroke-width", 5, 1],  // 0 to 10px
+  [1, "rect6", "stroke-width", 15, 2],  // 10% to 20%
+  [1, "rect7", "fill", [0, 0, 127], 20],  // url(#invalid) black -> url(#invalid) blue
+  [1, "stop1", "stop-color", [127, 127, 0], 20], // rgb(255,0,0) -> rgb(0, 255, 0)
+  [1, "stop1", "stop-opacity", 0.75, 0.1], // 1 -> 0.5
+  [1, "polyline1", "stroke-miterlimit", 11, 0.5], // 12 -> 10 (this is an abrupt change in rendering even though the property animation is smooth)
+  [1, "text1", "kerning", 5, 1], // 0 -> 10px
+  [1, "text1", "baseline-shift", 5, 1], // 0 -> 10px
+  [1, "flood1", "flood-opacity", 0.5, 0.1], // 1 -> 0
+  [1, "flood1", "flood-color", [127, 127, 0], 20], // rgb(0, 255, 0) -> rgb(255, 0, 0)
+  [1, "light1", "lighting-color", [127, 127, 0], 20], // rgb(255, 0, 0) -> rgb(0, 255, 0)
+  ];
+    
+  function setupTest()
+  {
+      document.getElementById('container').className = 'animating';
+  }
+
+  runTransitionTest(expectedValues, setupTest, usePauseAPI);
+  </script>
+</head>
+<body>
+
+<div id="container">
+  <svg viewBox="0 0 160 120" width="400px" height="300px">
+    <defs>
+      <linearGradient id="gradient1">
+        <stop id="stop1" offset="5%"/>
+        <stop offset="95%" stop-color="rgb(0,0,255)" />
+      </linearGradient>
+      <linearGradient id="gradient2">
+        <stop offset="10%" stop-color="rgb(0,255,255)" />
+        <stop offset="95%" stop-color="rgb(255, 0,255)" />
+      </linearGradient>
+      <filter id="filter1">
+        <feFlood id="flood1"/>
+        <feDiffuseLighting id="light1"/>
+      </filter>
+    </defs>
+    <rect id="rect1" x="10" y="10" width="30" height="30"/>
+    <rect id="rect2" x="90" y="10" width="30" height="30"/>
+    <rect id="rect3" x="90" y="60" width="30" height="20"/>
+    <rect id="rect4" x="10" y="100" width="10" height="10"/>
+    <rect id="rect5" x="50" y="100" width="10" height="10"/>
+    <rect id="rect6" x="90" y="100" width="10" height="10"/>
+    <rect id="rect7" x="130" y="100" width="10" height="10"/>
+    <polyline id="polyline1" points="10,70 60,75 10,80"/>
+    <text id="text1" x="100" y="60">Example</text>
+  </svg>
+</div>
+
+<div id="result"></div>
+
+</body>
+</html>
\ No newline at end of file
index 34927de..a23edc1 100644 (file)
@@ -1,3 +1,71 @@
+2011-08-25  Dean Jackson  <dino@apple.com>
+
+        https://bugs.webkit.org/show_bug.cgi?id=21586
+        Apply CSS animations and transitions to SVG properties
+
+        Reviewed by Simon Fraser.
+
+        Expose the applicable SVG properties from SVGRenderStyle to
+        RenderStyle and add them to the list of animatable
+        properties. There aren't many SVG properties that are
+        animatable directly. SVGPaint (used in "fill" and "stroke")
+        is supported only when both ends of the animation are
+        a Color. Also updated SVGLength to have some methods
+        for converting to and from user space units.
+
+        Test: transitions/svg-transitions.html
+
+        * page/animation/AnimationBase.cpp:
+        (WebCore::blendFunc):
+            Calls the blend method on SVGLength
+        (WebCore::PropertyWrapperSVGPaint::PropertyWrapperSVGPaint):
+            New property wrapper type for SVGPaint
+        (WebCore::PropertyWrapperSVGPaint::equals):
+        (WebCore::PropertyWrapperSVGPaint::blend):
+        (WebCore::AnimationBase::ensurePropertyMap):
+        * rendering/style/RenderStyle.h:
+        (WebCore::InheritedFlags::fillPaintType):
+        (WebCore::InheritedFlags::fillPaintColor):
+        (WebCore::InheritedFlags::setFillPaintColor):
+        (WebCore::InheritedFlags::strokePaintType):
+        (WebCore::InheritedFlags::strokePaintColor):
+        (WebCore::InheritedFlags::setStrokePaintColor):
+        (WebCore::InheritedFlags::strokeWidth):
+        (WebCore::InheritedFlags::setStrokeWidth):
+        (WebCore::InheritedFlags::strokeDashOffset):
+        (WebCore::InheritedFlags::setStrokeDashOffset):
+        (WebCore::InheritedFlags::strokeMiterLimit):
+        (WebCore::InheritedFlags::setStrokeMiterLimit):
+        (WebCore::InheritedFlags::stopOpacity):
+        (WebCore::InheritedFlags::setStopOpacity):
+        (WebCore::InheritedFlags::setStopColor):
+        (WebCore::InheritedFlags::setFloodColor):
+        (WebCore::InheritedFlags::setLightingColor):
+        (WebCore::InheritedFlags::baselineShiftValue):
+        (WebCore::InheritedFlags::setBaselineShiftValue):
+        (WebCore::InheritedFlags::kerning):
+        (WebCore::InheritedFlags::setKerning):
+        (WebCore::InheritedFlags::stopColor):
+        (WebCore::InheritedFlags::floodColor):
+        (WebCore::InheritedFlags::lightingColor):
+        * rendering/style/SVGRenderStyle.h:
+        (WebCore::SVGRenderStyle::stopColor):
+        (WebCore::SVGRenderStyle::floodColor):
+        (WebCore::SVGRenderStyle::lightingColor):
+        * svg/SVGLength.cpp:
+        (WebCore::SVGLength::value):
+        (WebCore::SVGLength::setValue):
+        (WebCore::SVGLength::convertValueFromUserUnits):
+        (WebCore::SVGLength::convertValueToUserUnits):
+            New conversion functions used when setting and getting values
+        * svg/SVGLength.h:
+        (WebCore::SVGLength::isZero):
+        (WebCore::SVGLength::blend):
+            Custom blend function that takes into account
+            whether the units of a length can be converted
+            in an animation, where you don't necessarily have
+            an SVGElement for context.
+
 2011-08-25  Kent Tamura  <tkent@chromium.org>
 
         REGRESSION(r90971): Fix an assertion failure with textarea placeholder.
index ee17833..ad3c592 100644 (file)
@@ -217,6 +217,13 @@ static inline LengthBox blendFunc(const AnimationBase* anim, const LengthBox& fr
     return result;
 }
 
+#if ENABLE(SVG)
+static inline SVGLength blendFunc(const AnimationBase*, const SVGLength& from, const SVGLength& to, double progress)
+{  
+    return to.blend(from, narrowPrecisionToFloat(progress));
+}
+#endif
+
 class PropertyWrapperBase;
 
 static void addShorthandProperties();
@@ -663,6 +670,67 @@ private:
     Vector<PropertyWrapperBase*> m_propertyWrappers;
 };
 
+#if ENABLE(SVG)
+class PropertyWrapperSVGPaint : public PropertyWrapperBase {
+public:
+    PropertyWrapperSVGPaint(int prop, const SVGPaint::SVGPaintType& (RenderStyle::*paintTypeGetter)() const, const Color& (RenderStyle::*getter)() const, void (RenderStyle::*setter)(const Color&))
+        : PropertyWrapperBase(prop)
+        , m_paintTypeGetter(paintTypeGetter)
+        , m_getter(getter)
+        , m_setter(setter)
+    {
+    }
+
+    virtual bool equals(const RenderStyle* a, const RenderStyle* b) const
+    {
+        if ((a->*m_paintTypeGetter)() != (b->*m_paintTypeGetter)())
+            return false;
+        
+        // We only support animations between SVGPaints that are pure Color values.
+        // For everything else we must return true for this method, otherwise
+        // we will try to animate between values forever.
+        if ((a->*m_paintTypeGetter)() == SVGPaint::SVG_PAINTTYPE_RGBCOLOR) {
+            Color fromColor = (a->*m_getter)();
+            Color toColor = (b->*m_getter)();
+
+            if (!fromColor.isValid() && !toColor.isValid())
+                return true;
+
+            if (!fromColor.isValid())
+                fromColor = Color();
+            if (!toColor.isValid())
+                toColor = Color();
+
+            return fromColor == toColor;
+        }
+        return true;
+    }
+
+    virtual void blend(const AnimationBase* anim, RenderStyle* dst, const RenderStyle* a, const RenderStyle* b, double progress) const
+    {
+        if ((a->*m_paintTypeGetter)() != SVGPaint::SVG_PAINTTYPE_RGBCOLOR
+            || (b->*m_paintTypeGetter)() != SVGPaint::SVG_PAINTTYPE_RGBCOLOR)
+            return;
+
+        Color fromColor = (a->*m_getter)();
+        Color toColor = (b->*m_getter)();
+
+        if (!fromColor.isValid() && !toColor.isValid())
+            return;
+
+        if (!fromColor.isValid())
+            fromColor = Color();
+        if (!toColor.isValid())
+            toColor = Color();
+        (dst->*m_setter)(blendFunc(anim, fromColor, toColor, progress));
+    }
+
+private:
+    const SVGPaint::SVGPaintType& (RenderStyle::*m_paintTypeGetter)() const;
+    const Color& (RenderStyle::*m_getter)() const;
+    void (RenderStyle::*m_setter)(const Color&);
+};
+#endif
 
 static Vector<PropertyWrapperBase*>* gPropertyWrappers = 0;
 static int gPropertyWrapperMap[numCSSProperties];
@@ -767,9 +835,25 @@ void AnimationBase::ensurePropertyMap()
         gPropertyWrappers->append(new PropertyWrapperShadow(CSSPropertyTextShadow, &RenderStyle::textShadow, &RenderStyle::setTextShadow));
 
 #if ENABLE(SVG)
+        gPropertyWrappers->append(new PropertyWrapperSVGPaint(CSSPropertyFill, &RenderStyle::fillPaintType, &RenderStyle::fillPaintColor, &RenderStyle::setFillPaintColor));
         gPropertyWrappers->append(new PropertyWrapper<float>(CSSPropertyFillOpacity, &RenderStyle::fillOpacity, &RenderStyle::setFillOpacity));
-        gPropertyWrappers->append(new PropertyWrapper<float>(CSSPropertyFloodOpacity, &RenderStyle::floodOpacity, &RenderStyle::setFloodOpacity));
+
+        gPropertyWrappers->append(new PropertyWrapperSVGPaint(CSSPropertyStroke, &RenderStyle::strokePaintType, &RenderStyle::strokePaintColor, &RenderStyle::setStrokePaintColor));
         gPropertyWrappers->append(new PropertyWrapper<float>(CSSPropertyStrokeOpacity, &RenderStyle::strokeOpacity, &RenderStyle::setStrokeOpacity));
+        gPropertyWrappers->append(new PropertyWrapper<SVGLength>(CSSPropertyStrokeWidth, &RenderStyle::strokeWidth, &RenderStyle::setStrokeWidth));
+        gPropertyWrappers->append(new PropertyWrapper<SVGLength>(CSSPropertyStrokeDashoffset, &RenderStyle::strokeDashOffset, &RenderStyle::setStrokeDashOffset));
+        gPropertyWrappers->append(new PropertyWrapper<float>(CSSPropertyStrokeMiterlimit, &RenderStyle::strokeMiterLimit, &RenderStyle::setStrokeMiterLimit));
+
+        gPropertyWrappers->append(new PropertyWrapper<float>(CSSPropertyFloodOpacity, &RenderStyle::floodOpacity, &RenderStyle::setFloodOpacity));
+        gPropertyWrappers->append(new PropertyWrapperMaybeInvalidColor(CSSPropertyFloodColor, &RenderStyle::floodColor, &RenderStyle::setFloodColor));
+
+        gPropertyWrappers->append(new PropertyWrapper<float>(CSSPropertyStopOpacity, &RenderStyle::stopOpacity, &RenderStyle::setStopOpacity));
+        gPropertyWrappers->append(new PropertyWrapperMaybeInvalidColor(CSSPropertyStopColor, &RenderStyle::stopColor, &RenderStyle::setStopColor));
+
+        gPropertyWrappers->append(new PropertyWrapperMaybeInvalidColor(CSSPropertyLightingColor, &RenderStyle::lightingColor, &RenderStyle::setLightingColor));
+
+        gPropertyWrappers->append(new PropertyWrapper<SVGLength>(CSSPropertyBaselineShift, &RenderStyle::baselineShiftValue, &RenderStyle::setBaselineShiftValue));
+        gPropertyWrappers->append(new PropertyWrapper<SVGLength>(CSSPropertyKerning, &RenderStyle::kerning, &RenderStyle::setKerning));
 #endif
 
         // TODO:
index 4266cf8..6bae685 100644 (file)
@@ -75,6 +75,7 @@
 #endif
 
 #if ENABLE(SVG)
+#include "SVGPaint.h"
 #include "SVGRenderStyle.h"
 #endif
 
@@ -1165,15 +1166,39 @@ public:
 #if ENABLE(SVG)
     const SVGRenderStyle* svgStyle() const { return m_svgStyle.get(); }
     SVGRenderStyle* accessSVGStyle() { return m_svgStyle.access(); }
-    
+
+    const SVGPaint::SVGPaintType& fillPaintType() const { return svgStyle()->fillPaintType(); }
+    const Color& fillPaintColor() const { return svgStyle()->fillPaintColor(); }
+    void setFillPaintColor(const Color& c) { accessSVGStyle()->setFillPaint(SVGPaint::SVG_PAINTTYPE_RGBCOLOR, c, ""); }
     float fillOpacity() const { return svgStyle()->fillOpacity(); }
     void setFillOpacity(float f) { accessSVGStyle()->setFillOpacity(f); }
-    
+
+    const SVGPaint::SVGPaintType& strokePaintType() const { return svgStyle()->strokePaintType(); }
+    const Color& strokePaintColor() const { return svgStyle()->strokePaintColor(); }
+    void setStrokePaintColor(const Color& c) { accessSVGStyle()->setStrokePaint(SVGPaint::SVG_PAINTTYPE_RGBCOLOR, c, ""); }
     float strokeOpacity() const { return svgStyle()->strokeOpacity(); }
     void setStrokeOpacity(float f) { accessSVGStyle()->setStrokeOpacity(f); }
-    
+    SVGLength strokeWidth() const { return svgStyle()->strokeWidth(); }
+    void setStrokeWidth(SVGLength w) { accessSVGStyle()->setStrokeWidth(w); }
+    SVGLength strokeDashOffset() const { return svgStyle()->strokeDashOffset(); }
+    void setStrokeDashOffset(SVGLength d) { accessSVGStyle()->setStrokeDashOffset(d); }
+    float strokeMiterLimit() const { return svgStyle()->strokeMiterLimit(); }
+    void setStrokeMiterLimit(float f) { accessSVGStyle()->setStrokeMiterLimit(f); }
+
     float floodOpacity() const { return svgStyle()->floodOpacity(); }
     void setFloodOpacity(float f) { accessSVGStyle()->setFloodOpacity(f); }
+
+    float stopOpacity() const { return svgStyle()->stopOpacity(); }
+    void setStopOpacity(float f) { accessSVGStyle()->setStopOpacity(f); }
+
+    void setStopColor(const Color& c) { accessSVGStyle()->setStopColor(c); }
+    void setFloodColor(const Color& c) { accessSVGStyle()->setFloodColor(c); }
+    void setLightingColor(const Color& c) { accessSVGStyle()->setLightingColor(c); }
+
+    SVGLength baselineShiftValue() const { return svgStyle()->baselineShiftValue(); }
+    void setBaselineShiftValue(SVGLength s) { accessSVGStyle()->setBaselineShiftValue(s); }
+    SVGLength kerning() const { return svgStyle()->kerning(); }
+    void setKerning(SVGLength k) { accessSVGStyle()->setKerning(k); }
 #endif
 
     void setWrapShape(PassRefPtr<CSSWrapShape> shape)
@@ -1412,6 +1437,12 @@ private:
     
     const Color colorIncludingFallback(int colorProperty) const;
 
+#if ENABLE(SVG)
+    const Color& stopColor() const { return svgStyle()->stopColor(); }
+    const Color& floodColor() const { return svgStyle()->floodColor(); }
+    const Color& lightingColor() const { return svgStyle()->lightingColor(); }
+#endif
+
     void appendContent(PassOwnPtr<ContentData>);
 };
 
index 0fe4a80..4aadeea 100644 (file)
@@ -315,10 +315,10 @@ public:
     SVGLength strokeDashOffset() const { return stroke->dashOffset; }
     SVGLength kerning() const { return text->kerning; }
     float stopOpacity() const { return stops->opacity; }
-    Color stopColor() const { return stops->color; }
+    const Color& stopColor() const { return stops->color; }
     float floodOpacity() const { return misc->floodOpacity; }
-    Color floodColor() const { return misc->floodColor; }
-    Color lightingColor() const { return misc->lightingColor; }
+    const Color& floodColor() const { return misc->floodColor; }
+    const Color& lightingColor() const { return misc->lightingColor; }
     SVGLength baselineShiftValue() const { return misc->baselineShiftValue; }
     ShadowData* shadow() const { return shadowSVG->shadow.get(); }
     String clipperResource() const { return resources->clipper; }
index 6b21a82..ad42f5e 100644 (file)
@@ -194,88 +194,86 @@ float SVGLength::value(const SVGElement* context) const
 
 float SVGLength::value(const SVGElement* context, ExceptionCode& ec) const
 {
-    switch (extractType(m_unit)) {
+    return convertValueToUserUnits(m_valueInSpecifiedUnits, extractType(m_unit), context, ec);
+}
+
+void SVGLength::setValue(const SVGElement* context, float value, SVGLengthMode mode, SVGLengthType unitType, ExceptionCode& ec)
+{
+    m_unit = storeUnit(mode, unitType);
+    setValue(value, context, ec);
+}
+
+void SVGLength::setValue(float value, const SVGElement* context, ExceptionCode& ec)
+{
+    // 100% = 100.0 instead of 1.0 for historical reasons, this could eventually be changed
+    if (extractType(m_unit) == LengthTypePercentage)
+        value = value / 100;
+
+    float convertedValue = convertValueFromUserUnits(value, extractType(m_unit), context, ec);
+    if (!ec)
+        m_valueInSpecifiedUnits = convertedValue;
+}
+
+float SVGLength::convertValueToUserUnits(float value, SVGLengthType fromUnit, const SVGElement* context, ExceptionCode& ec) const
+{
+    switch (fromUnit) {
     case LengthTypeUnknown:
         ec = NOT_SUPPORTED_ERR;
         return 0;
     case LengthTypeNumber:
-        return m_valueInSpecifiedUnits;
+        return value;
+    case LengthTypePX:
+        return value;
     case LengthTypePercentage:
-        return convertValueFromPercentageToUserUnits(m_valueInSpecifiedUnits / 100, context, ec);
+        return convertValueFromPercentageToUserUnits(value / 100, context, ec);
     case LengthTypeEMS:
-        return convertValueFromEMSToUserUnits(m_valueInSpecifiedUnits, context, ec);
+        return convertValueFromEMSToUserUnits(value, context, ec);
     case LengthTypeEXS:
-        return convertValueFromEXSToUserUnits(m_valueInSpecifiedUnits, context, ec);
-    case LengthTypePX:
-        return m_valueInSpecifiedUnits;
+        return convertValueFromEXSToUserUnits(value, context, ec);
     case LengthTypeCM:
-        return m_valueInSpecifiedUnits / 2.54f * cssPixelsPerInch;
+        return value * cssPixelsPerInch / 2.54f;
     case LengthTypeMM:
-        return m_valueInSpecifiedUnits / 25.4f * cssPixelsPerInch;
+        return value * cssPixelsPerInch / 25.4f;
     case LengthTypeIN:
-        return m_valueInSpecifiedUnits * cssPixelsPerInch;
+        return value * cssPixelsPerInch;
     case LengthTypePT:
-        return m_valueInSpecifiedUnits / 72 * cssPixelsPerInch;
+        return value * cssPixelsPerInch / 72;
     case LengthTypePC:
-        return m_valueInSpecifiedUnits / 6 * cssPixelsPerInch;
+        return value * cssPixelsPerInch / 6;
     }
-
     ASSERT_NOT_REACHED();
     return 0;
 }
 
-void SVGLength::setValue(const SVGElement* context, float value, SVGLengthMode mode, SVGLengthType unitType, ExceptionCode& ec)
+float SVGLength::convertValueFromUserUnits(float value, SVGLengthType toUnit, const SVGElement* context, ExceptionCode& ec) const
 {
-    m_unit = storeUnit(mode, unitType);
-    setValue(value, context, ec);
-}
-
-void SVGLength::setValue(float value, const SVGElement* context, ExceptionCode& ec)
-{
-    switch (extractType(m_unit)) {
+    switch (toUnit) {
     case LengthTypeUnknown:
         ec = NOT_SUPPORTED_ERR;
-        break;
+        return 0;
     case LengthTypeNumber:
-        m_valueInSpecifiedUnits = value;
-        break;
-    case LengthTypePercentage: {
-        float result = convertValueFromUserUnitsToPercentage(value, context, ec);
-        if (!ec)
-            m_valueInSpecifiedUnits = result; 
-        break;
-    }
-    case LengthTypeEMS: {
-        float result = convertValueFromUserUnitsToEMS(value, context, ec);
-        if (!ec)
-            m_valueInSpecifiedUnits = result;
-        break;
-    }
-    case LengthTypeEXS: {
-        float result = convertValueFromUserUnitsToEXS(value, context, ec);
-        if (!ec)
-            m_valueInSpecifiedUnits = result; 
-        break;
-    }
+        return value;
+    case LengthTypePercentage:
+        return convertValueFromUserUnitsToPercentage(value * 100, context, ec);
+    case LengthTypeEMS:
+        return convertValueFromUserUnitsToEMS(value, context, ec);
+    case LengthTypeEXS:
+        return convertValueFromUserUnitsToEXS(value, context, ec);
     case LengthTypePX:
-        m_valueInSpecifiedUnits = value;
-        break;
+        return value;
     case LengthTypeCM:
-        m_valueInSpecifiedUnits = value * 2.54f / cssPixelsPerInch;
-        break;
+        return value * 2.54f / cssPixelsPerInch;
     case LengthTypeMM:
-        m_valueInSpecifiedUnits = value * 25.4f / cssPixelsPerInch;
-        break;
+        return value * 25.4f / cssPixelsPerInch;
     case LengthTypeIN:
-        m_valueInSpecifiedUnits = value / cssPixelsPerInch;
-        break;
+        return value / cssPixelsPerInch;
     case LengthTypePT:
-        m_valueInSpecifiedUnits = value * 72 / cssPixelsPerInch;
-        break;
+        return value * 72 / cssPixelsPerInch;
     case LengthTypePC:
-        m_valueInSpecifiedUnits = value * 6 / cssPixelsPerInch;
-        break;
+        return value * 6 / cssPixelsPerInch;
     }
+    ASSERT_NOT_REACHED();
+    return 0;
 }
 
 float SVGLength::valueAsPercentage() const
index 2632a50..f36cc15 100644 (file)
@@ -110,13 +110,76 @@ public:
         return type == LengthTypePercentage || type == LengthTypeEMS || type == LengthTypeEXS;
     }
 
+    bool isZero() const 
+    { 
+        return !m_valueInSpecifiedUnits;
+    }
+
     static SVGLength fromCSSPrimitiveValue(CSSPrimitiveValue*);
     static PassRefPtr<CSSPrimitiveValue> toCSSPrimitiveValue(const SVGLength&);
     static SVGLengthMode lengthModeForAnimatedLengthAttribute(const QualifiedName&);
 
+    SVGLength blend(const SVGLength& from, float progress) const
+    {
+        SVGLengthType toType = unitType();
+        SVGLengthType fromType = from.unitType();
+
+        if ((from.isZero() && isZero())
+            || fromType == LengthTypeUnknown
+            || toType == LengthTypeUnknown
+            || (!from.isZero() && fromType != LengthTypePercentage && toType == LengthTypePercentage)
+            || (!isZero() && fromType == LengthTypePercentage && toType != LengthTypePercentage)
+            || (!from.isZero() && !isZero() && (fromType == LengthTypeEMS || fromType == LengthTypeEXS) && fromType != toType))
+            return *this;
+
+        SVGLength length;
+        ExceptionCode ec = 0;
+
+        if (fromType == LengthTypePercentage || toType == LengthTypePercentage) {
+            float fromPercent = from.valueAsPercentage() * 100;
+            float toPercent = valueAsPercentage() * 100;
+            length.newValueSpecifiedUnits(LengthTypePercentage, fromPercent + (toPercent - fromPercent) * progress, ec);
+            if (ec)
+                return SVGLength();
+            return length;
+        }
+
+        if (fromType == toType || from.isZero() || isZero() || fromType == LengthTypeEMS || fromType == LengthTypeEXS) {
+            float fromValue = from.valueInSpecifiedUnits();
+            float toValue = valueInSpecifiedUnits();
+            if (isZero())
+                length.newValueSpecifiedUnits(fromType, fromValue + (toValue - fromValue) * progress, ec);
+            else
+                length.newValueSpecifiedUnits(toType, fromValue + (toValue - fromValue) * progress, ec);
+            if (ec)
+                return SVGLength();
+            return length;
+        }
+
+        ASSERT(!isRelative());
+        ASSERT(!from.isRelative());
+        float fromValueInUserUnits = convertValueToUserUnits(from.valueInSpecifiedUnits(), fromType, 0, ec);
+        if (ec)
+            return SVGLength();
+
+        float fromValue = convertValueFromUserUnits(fromValueInUserUnits, toType, 0, ec);
+        if (ec)
+            return SVGLength();
+
+        float toValue = valueInSpecifiedUnits();
+        length.newValueSpecifiedUnits(toType, fromValue + (toValue - fromValue) * progress, ec);
+
+        if (ec)
+            return SVGLength();
+        return length;
+    }
+
 private:
     bool determineViewport(const SVGElement* context, float& width, float& height) const;
 
+    float convertValueToUserUnits(float value, SVGLengthType fromUnit, const SVGElement* context, ExceptionCode&) const;
+    float convertValueFromUserUnits(float value, SVGLengthType toUnit, const SVGElement* context, ExceptionCode&) const;
+
     float convertValueFromPercentageToUserUnits(float value, const SVGElement* context, ExceptionCode&) const;
     float convertValueFromUserUnitsToPercentage(float value, const SVGElement* context, ExceptionCode&) const;