From 7694b11a04e1e6a8200111ee52f1503fa9704d18 Mon Sep 17 00:00:00 2001
From: MoyuScript <i@moyu.moe>
Date: Mon, 31 Dec 2012 20:10:18 +0200
Subject: [PATCH 1/6] added border radius test

---
 tests/cases/border/radius.html | 57 ++++++++++++++++++++++++++++++++++
 tests/rangetest.html           | 37 ++++++++++++++++++++++
 2 files changed, 94 insertions(+)
 create mode 100644 tests/cases/border/radius.html
 create mode 100644 tests/rangetest.html

diff --git a/tests/cases/border/radius.html b/tests/cases/border/radius.html
new file mode 100644
index 0000000..fe100b1
--- /dev/null
+++ b/tests/cases/border/radius.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Borders tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style type="text/css">
+      div {
+        width: 200px;
+        height: 200px;
+        display: inline-block;
+        margin: 10px;
+        background:#6F428C;
+        border-style: solid;
+        border-radius: 50px;
+      }
+
+      .box1 {
+        border-width: 1px;
+        border-left-color: #00b5e2;
+        border-top-color: red;
+        border-right-color: green;
+      }
+
+      .box2 {
+        border-width: 3px;
+        border-left-color: #00b5e2;
+        border-top-color: red;
+        border-right-color: green;
+      }
+
+      .box3 {
+        border-width: 10px;
+        border-left-color: #00b5e2;
+        border-top-color: red;
+        border-right-color: green;
+      }
+
+      .box4 {
+        border-width: 50px;
+        border-left-color: #00b5e2;
+        border-top-color: red;
+        border-right-color: green;
+      }
+
+      html {
+        background: #3a84c3;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="box1">&nbsp;</div>
+    <div class="box2">&nbsp;</div>
+    <div class="box3">&nbsp;</div>
+    <div class="box4">&nbsp;</div>
+  </body>
+</html>
diff --git a/tests/rangetest.html b/tests/rangetest.html
new file mode 100644
index 0000000..bbd3308
--- /dev/null
+++ b/tests/rangetest.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Range tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <style>
+      #tests {
+        font-family:Arial;
+      }
+      </style>
+  </head>
+  <body>
+    <div id="tests"></div>
+    <script>
+      var div = document.getElementById("tests"), item, range, textNode, bounds;
+
+      for (var i = 6; i < 72; i++) {
+        item = document.createElement("div");
+        item.style.fontSize = i + "px";
+        item.style.lineHeight = (10 + i * 2) + "px";
+        textNode = document.createTextNode(i);
+        item.appendChild(textNode);
+        div.appendChild(item);
+
+        range = document.createRange();
+        range.setStart(textNode, 0);
+        range.setEnd(textNode, i.toString().length);
+        bounds = range.getBoundingClientRect();
+
+        textNode.nodeValue += " " + bounds.height + " " + bounds.bottom + (i % 3 === 0);
+
+      }
+
+    </script>
+    <script src="test.js"></script>
+  </body>
+</html>

From ca32ce6254fe9b88efaf9d1a50b2424328d7b461 Mon Sep 17 00:00:00 2001
From: MoyuScript <i@moyu.moe>
Date: Wed, 2 Jan 2013 21:26:24 +0200
Subject: [PATCH 2/6] initial border-radius rendering

---
 src/Parse.js                   | 345 +++++++++++++++++++++++++--------
 src/Queue.js                   |   6 +
 tests/cases/border/radius.html |   4 +-
 3 files changed, 273 insertions(+), 82 deletions(-)

diff --git a/src/Parse.js b/src/Parse.js
index eb5ba85..9850ba9 100644
--- a/src/Parse.js
+++ b/src/Parse.js
@@ -330,105 +330,293 @@ _html2canvas.Parse = function (images, options) {
       );
   }
 
-  function renderBorders(el, ctx, bounds, clip){
+  function getBorderData(element) {
+    return ["Top", "Right", "Bottom", "Left"].map(function(side) {
+      return {
+        width: getCSSInt(element, 'border' + side + 'Width'),
+        color: getCSS(element, 'border' + side + 'Color')
+      };
+    });
+  }
+
+  function getBorderRadiusData(element) {
+    return ["TopLeft", "TopRight", "BottomRight", "BottomLeft"].map(function(side) {
+      return getCSS(element, 'border' + side + 'Radius');
+    });
+  }
+
+  function pathArc(x, y, cornerX, cornerY, radius) {
+    return ["arc", cornerX, cornerY, x, y, radius];
+  }
+
+  var getCurvePoints = (function(kappa) {
+
+    return function(x, y, r1, r2) {
+      var ox = (r1) * kappa, // control point offset horizontal
+      oy = (r2) * kappa, // control point offset vertical
+      xm = x + r1, // x-middle
+      ym = y + r2; // y-middle
+      return {
+        topLeft: bezierCurve({
+          x:x,
+          y:ym
+        }, {
+          x:x,
+          y:ym - oy
+        }, {
+          x:xm - ox,
+          y:y
+        }, {
+          x:xm,
+          y:y
+        }),
+        topRight: bezierCurve({
+          x:x,
+          y:y
+        }, {
+          x:x + ox,
+          y:y
+        }, {
+          x:xm,
+          y:ym - oy
+        }, {
+          x:xm,
+          y:ym
+        }),
+        bottomRight: bezierCurve({
+          x:xm,
+          y:y
+        }, {
+          x:xm,
+          y:y + oy
+        }, {
+          x:x + ox,
+          y:ym
+        }, {
+          x:x,
+          y:ym
+        }),
+        bottomLeft: bezierCurve({
+          x:xm,
+          y:ym
+        }, {
+          x:xm - ox,
+          y:ym
+        }, {
+          x:x,
+          y:y + oy
+        }, {
+          x:x,
+          y:y
+        })
+      };
+    };
+  })(4 * ((Math.sqrt(2) - 1) / 3));
+
+  function bezierCurve(start, startControl, endControl, end) {
+
+    var lerp = function (a, b, t) {
+      return {
+        x:a.x + (b.x - a.x) * t,
+        y:a.y + (b.y - a.y) * t
+      };
+    };
+
+    return {
+      start: start,
+      startControl: startControl,
+      endControl: endControl,
+      end: end,
+      subdivide: function(t) {
+        var ab = lerp(start, startControl, t),
+        bc = lerp(startControl, endControl, t),
+        cd = lerp(endControl, end, t),
+        abbc = lerp(ab, bc, t),
+        bccd = lerp(bc, cd, t),
+        dest = lerp(abbc, bccd, t);
+        return [bezierCurve(start, ab, abbc, dest), bezierCurve(dest, bccd, cd, end)];
+      },
+      curveTo: function(borderArgs) {
+        borderArgs.push(["bezierCurve", startControl.x, startControl.y, endControl.x, endControl.y, end.x, end.y]);
+      },
+      curveToReversed: function(borderArgs) {
+        borderArgs.push(["bezierCurve", endControl.x, endControl.y, startControl.x, startControl.y, start.x, start.y]);
+      }
+    };
+  }
+
+  function drawSide(borderData, radius1, radius2, outer1, inner1, outer2, inner2) {
+    var borderArgs = [];
+
+    if (radius1[0] > 0 || radius1[1] > 0) {
+      borderArgs.push(["line", outer1[1].start.x, outer1[1].start.y]);
+      outer1[1].curveTo(borderArgs);
+    } else {
+      borderArgs.push([ "line", borderData.c1[0], borderData.c1[1]]);
+    }
+
+    if (radius2[0] > 0 || radius2[1] > 0) {
+      borderArgs.push(["line", outer2[0].start.x, outer2[0].start.y]);
+      outer2[0].curveTo(borderArgs);
+      borderArgs.push(["line", inner2[0].end.x, inner2[0].end.y]);
+      inner2[0].curveToReversed(borderArgs);
+    } else {
+      borderArgs.push([ "line", borderData.c2[0], borderData.c2[1]]);
+      borderArgs.push([ "line", borderData.c3[0], borderData.c3[1]]);
+    }
+
+    if (radius1[0] > 0 || radius1[1] > 0) {
+      borderArgs.push(["line", inner1[1].end.x, inner1[1].end.y]);
+      inner1[1].curveToReversed(borderArgs);
+    } else {
+      borderArgs.push([ "line", borderData.c4[0], borderData.c4[1]]);
+    }
+
+    return borderArgs;
+  }
+
+  function parseBorders(element, ctx, bounds, clip, borders){
     var x = bounds.left,
     y = bounds.top,
-    w = bounds.width,
-    h = bounds.height,
+    width = bounds.width,
+    height = bounds.height,
     borderSide,
     borderData,
     bx,
     by,
     bw,
     bh,
-    i,
     borderArgs,
     borderBounds,
-    borders = (function(el){
-      var borders = [],
-      sides = ["Top","Right","Bottom","Left"],
-      s;
-
-      for (s = 0; s < 4; s+=1){
-        borders.push({
-          width: getCSSInt(el, 'border' + sides[s] + 'Width'),
-          color: getCSS(el, 'border' + sides[s] + 'Color')
-        });
-      }
-
-      return borders;
-
-    }(el)),
     // http://www.w3.org/TR/css3-background/#the-border-radius
-    borderRadius = (function( el ) {
-      var borders = [],
-      sides = ["TopLeft","TopRight","BottomRight","BottomLeft"],
-      s;
-
-      for (s = 0; s < 4; s+=1){
-        borders.push( getCSS(el, 'border' + sides[s] + 'Radius') );
-      }
-
-      return borders;
-    })( el );
+    borderRadius = getBorderRadiusData(element);
 
 
+    var tlh = borderRadius[0][0];
+    var tlv = borderRadius[0][1];
+    var trh = borderRadius[1][0];
+    var trv = borderRadius[1][1];
+    var brv = borderRadius[2][0];
+    var brh = borderRadius[2][1];
+    var blh = borderRadius[3][0];
+    var blv = borderRadius[3][1];
 
-    for ( borderSide = 0; borderSide < 4; borderSide+=1 ) {
-      borderData = borders[ borderSide ];
+
+    var topWidth = width - trh;
+    var rightHeight = height - brv;
+    var bottomWidth = width - brh;
+    var leftHeight = height - blv;
+
+    var topLeftOuter = getCurvePoints(
+      x,
+      y,
+      tlh,
+      tlv
+      ).topLeft.subdivide(0.5);
+
+    var topLeftInner = getCurvePoints(
+      x + borders[3].width,
+      y + borders[0].width,
+      Math.max(0, tlh - borders[3].width),
+      Math.max(0, tlv - borders[0].width)
+      ).topLeft.subdivide(0.5);
+
+    var topRightOuter = getCurvePoints(
+      x + topWidth,
+      y,
+      trh,
+      trv
+      ).topRight.subdivide(0.5);
+
+    var topRightInner = getCurvePoints(
+      x + Math.min(topWidth, width + borders[3].width),
+      y + borders[0].width,
+      (topWidth > width + borders[3].width) ? 0 :trh - borders[3].width,
+      trv - borders[0].width
+      ).topRight.subdivide(0.5);
+
+    var bottomRightOuter = getCurvePoints(
+      x + bottomWidth,
+      y + rightHeight,
+      brh,
+      brv
+      ).bottomRight.subdivide(0.5);
+    var bottomRightInner = getCurvePoints(
+      x + Math.min(bottomWidth, width + borders[3].width),
+      y + Math.min(rightHeight, height + borders[0].width),
+      Math.max(0, brh - borders[1].width),
+      Math.max(0, brv - borders[2].width)
+      ).bottomRight.subdivide(0.5);
+
+    var bottomLeftOuter = getCurvePoints(
+      x,
+      y + leftHeight,
+      blh,
+      blv
+      ).bottomLeft.subdivide(0.5);
+
+    var bottomLeftInner = getCurvePoints(
+      x + borders[3].width,
+      y + leftHeight,
+      Math.max(0, blh - borders[3].width),
+      Math.max(0, blv - borders[2].width)
+      ).bottomLeft.subdivide(0.5);
+
+    for (borderSide = 0; borderSide < 4; borderSide++) {
+      borderData = borders[borderSide];
       borderArgs = [];
       if (borderData.width>0){
         bx = x;
         by = y;
-        bw = w;
-        bh = h - (borders[2].width);
+        bw = width;
+        bh = height - (borders[2].width);
 
         switch(borderSide){
           case 0:
             // top border
             bh = borders[0].width;
 
-            i = 0;
-            borderArgs[ i++ ] = [ "line", bx, by ];  // top left
-            borderArgs[ i++ ] = [ "line", bx + bw, by ]; // top right
-            borderArgs[ i++ ] = [ "line", bx + bw - borders[ 1 ].width, by + bh  ]; // bottom right
-            borderArgs[ i++ ] = [ "line", bx + borders[ 3 ].width, by + bh ]; // bottom left
-
+            borderArgs = drawSide({
+              c1: [bx, by],
+              c2: [bx + bw, by],
+              c3: [bx + bw - borders[1].width, by + bh],
+              c4: [bx + borders[3].width, by + bh]
+            }, borderRadius[0], borderRadius[1], topLeftOuter, topLeftInner, topRightOuter, topRightInner);
             break;
           case 1:
             // right border
-            bx = x + w - (borders[1].width);
+            bx = x + width - (borders[1].width);
             bw = borders[1].width;
 
-            i = 0;
-            borderArgs[ i++ ] = [ "line", bx, by + borders[ 0 ].width];  // top left
-            borderArgs[ i++ ] = [ "line", bx + bw, by ]; // top right
-            borderArgs[ i++ ] = [ "line", bx + bw, by + bh + borders[ 2 ].width ]; // bottom right
-            borderArgs[ i++ ] = [ "line", bx, by + bh ]; // bottom left
-
+            borderArgs = drawSide({
+              c1: [bx + bw, by],
+              c2: [bx + bw, by + bh + borders[2].width],
+              c3: [bx, by + bh],
+              c4: [bx, by + borders[0].width]
+            }, borderRadius[1], borderRadius[2], topRightOuter, topRightInner, bottomRightOuter, bottomRightInner);
             break;
           case 2:
             // bottom border
-            by = (by + h) - (borders[2].width);
+            by = (by + height) - (borders[2].width);
             bh = borders[2].width;
 
-            i = 0;
-            borderArgs[ i++ ] = [ "line", bx + borders[ 3 ].width, by ];  // top left
-            borderArgs[ i++ ] = [ "line", bx + bw - borders[ 2 ].width, by ]; // top right
-            borderArgs[ i++ ] = [ "line", bx + bw, by + bh ]; // bottom right
-            borderArgs[ i++ ] = [ "line", bx, by + bh ]; // bottom left
-
+            borderArgs = drawSide({
+              c1: [bx + bw, by + bh],
+              c2: [bx, by + bh],
+              c3: [bx + borders[3].width, by],
+              c4: [bx + bw - borders[2].width, by]
+            }, borderRadius[2], borderRadius[3], bottomRightOuter, bottomRightInner, bottomLeftOuter, bottomLeftInner);
             break;
           case 3:
             // left border
             bw = borders[3].width;
 
-            i = 0;
-            borderArgs[ i++ ] = [ "line", bx, by ];  // top left
-            borderArgs[ i++ ] = [ "line", bx + bw, by + borders[ 0 ].width ]; // top right
-            borderArgs[ i++ ] = [ "line", bx + bw, by + bh ]; // bottom right
-            borderArgs[ i++ ] = [ "line", bx, by + bh + borders[ 2 ].width ]; // bottom left
-
+            borderArgs = drawSide({
+              c1: [bx, by + bh + borders[2].width],
+              c2: [bx, by],
+              c3: [bx + bw, by + borders[0].width],
+              c4: [bx + bw, by + bh]
+            }, borderRadius[3], borderRadius[0], bottomLeftOuter, bottomLeftInner, topLeftOuter, topLeftInner);
             break;
         }
 
@@ -442,32 +630,25 @@ _html2canvas.Parse = function (images, options) {
         if (clip){
           borderBounds = clipBounds(borderBounds, clip);
         }
-
-
-        if ( borderBounds.width > 0 && borderBounds.height > 0 ) {
-
-          if ( borderData.color !== "transparent" ){
-            ctx.setVariable( "fillStyle", borderData.color );
-
-            var shape = ctx.drawShape(),
-            numBorderArgs = borderArgs.length;
-
-            for ( i = 0; i < numBorderArgs; i++ ) {
-              shape[( i === 0) ? "moveTo" : borderArgs[ i ][ 0 ] + "To" ].apply( null, borderArgs[ i ].slice(1) );
-            }
-
-            numDraws+=1;
-          }
-
-        }
-
-
+        renderBorders(ctx, borderArgs, borderBounds, borderData.color);
       }
     }
 
     return borders;
   }
 
+  function renderBorders(ctx, borderArgs, borderBounds, color) {
+    if (borderBounds.width > 0 && borderBounds.height > 0) {
+      if (color !== "transparent") {
+        ctx.setVariable( "fillStyle", color);
+        var shape = ctx.drawShape();
+        borderArgs.forEach(function(border, index) {
+          shape[(index === 0) ? "moveTo" : border[0] + "To" ].apply(null, border.slice(1));
+        });
+        numDraws+=1;
+      }
+    }
+  }
 
   function renderFormValue (el, bounds, stack){
 
@@ -695,7 +876,7 @@ _html2canvas.Parse = function (images, options) {
       zIndex: setZ(getCSS(element, "zIndex"), (parentStack) ? parentStack.zIndex : null),
       opacity: setOpacity(ctx, element, parentStack),
       cssPosition: getCSS(element, "position"),
-      borders: renderBorders(element, ctx, bounds, false),
+      borders: getBorderData(element),
       clip: (parentStack && parentStack.clip) ? _html2canvas.Util.Extend( {}, parentStack.clip ) : null
     };
 
@@ -738,6 +919,8 @@ _html2canvas.Parse = function (images, options) {
       renderBackgroundImage(element, backgroundBounds, ctx);
     }
 
+    parseBorders(element, ctx, bounds, stack.clip, borders);
+
     switch(element.nodeName){
       case "IMG":
         if ((image = loadImage(element.getAttribute('src')))) {
diff --git a/src/Queue.js b/src/Queue.js
index bf8dcdf..74ec318 100644
--- a/src/Queue.js
+++ b/src/Queue.js
@@ -34,6 +34,12 @@ function h2cRenderContext(width, height) {
             'arguments': arguments
           });
         },
+        arcTo: function() {
+          shape.push({
+            name: "arcTo",
+            'arguments': arguments
+          });
+        },
         bezierCurveTo: function() {
           shape.push({
             name: "bezierCurveTo",
diff --git a/tests/cases/border/radius.html b/tests/cases/border/radius.html
index fe100b1..84b0095 100644
--- a/tests/cases/border/radius.html
+++ b/tests/cases/border/radius.html
@@ -31,7 +31,7 @@
 
       .box3 {
         border-width: 10px;
-        border-left-color: #00b5e2;
+        border-left-color: transparent;
         border-top-color: red;
         border-right-color: green;
       }
@@ -40,7 +40,9 @@
         border-width: 50px;
         border-left-color: #00b5e2;
         border-top-color: red;
+        border-top-width: 10px;
         border-right-color: green;
+        border-bottom-right-radius: 190px;
       }
 
       html {

From d29d90b13471405544045d651341fba208b9bd1d Mon Sep 17 00:00:00 2001
From: MoyuScript <i@moyu.moe>
Date: Thu, 3 Jan 2013 18:37:27 +0200
Subject: [PATCH 3/6] refactored border radius calculation

---
 src/Parse.js                   | 170 ++++++++++++++++++---------------
 tests/cases/border/radius.html |   3 +-
 2 files changed, 96 insertions(+), 77 deletions(-)

diff --git a/src/Parse.js b/src/Parse.js
index 9850ba9..6c70e35 100644
--- a/src/Parse.js
+++ b/src/Parse.js
@@ -475,6 +475,90 @@ _html2canvas.Parse = function (images, options) {
     return borderArgs;
   }
 
+  function getBorderBounds(element) {
+    var backgroundClip = getCSS(element, 'backgroundClip');
+  }
+
+  function calculateCurvePoints(bounds, borderRadius, borders) {
+
+    var x = bounds.left,
+    y = bounds.top,
+    width = bounds.width,
+    height = bounds.height,
+
+    tlh = borderRadius[0][0],
+    tlv = borderRadius[0][1],
+    trh = borderRadius[1][0],
+    trv = borderRadius[1][1],
+    brv = borderRadius[2][0],
+    brh = borderRadius[2][1],
+    blh = borderRadius[3][0],
+    blv = borderRadius[3][1],
+
+    topWidth = width - trh,
+    rightHeight = height - brv,
+    bottomWidth = width - brh,
+    leftHeight = height - blv;
+
+    return {
+      topLeftOuter: getCurvePoints(
+        x,
+        y,
+        tlh,
+        tlv
+        ).topLeft.subdivide(0.5),
+
+      topLeftInner: getCurvePoints(
+        x + borders[3].width,
+        y + borders[0].width,
+        Math.max(0, tlh - borders[3].width),
+        Math.max(0, tlv - borders[0].width)
+        ).topLeft.subdivide(0.5),
+
+      topRightOuter: getCurvePoints(
+        x + topWidth,
+        y,
+        trh,
+        trv
+        ).topRight.subdivide(0.5),
+
+      topRightInner: getCurvePoints(
+        x + Math.min(topWidth, width + borders[3].width),
+        y + borders[0].width,
+        (topWidth > width + borders[3].width) ? 0 :trh - borders[3].width,
+        trv - borders[0].width
+        ).topRight.subdivide(0.5),
+
+      bottomRightOuter: getCurvePoints(
+        x + bottomWidth,
+        y + rightHeight,
+        brh,
+        brv
+        ).bottomRight.subdivide(0.5),
+
+      bottomRightInner: getCurvePoints(
+        x + Math.min(bottomWidth, width + borders[3].width),
+        y + Math.min(rightHeight, height + borders[0].width),
+        Math.max(0, brh - borders[1].width),
+        Math.max(0, brv - borders[2].width)
+        ).bottomRight.subdivide(0.5),
+
+      bottomLeftOuter: getCurvePoints(
+        x,
+        y + leftHeight,
+        blh,
+        blv
+        ).bottomLeft.subdivide(0.5),
+
+      bottomLeftInner: getCurvePoints(
+        x + borders[3].width,
+        y + leftHeight,
+        Math.max(0, blh - borders[3].width),
+        Math.max(0, blv - borders[2].width)
+        ).bottomLeft.subdivide(0.5)
+    }
+  }
+
   function parseBorders(element, ctx, bounds, clip, borders){
     var x = bounds.left,
     y = bounds.top,
@@ -489,78 +573,8 @@ _html2canvas.Parse = function (images, options) {
     borderArgs,
     borderBounds,
     // http://www.w3.org/TR/css3-background/#the-border-radius
-    borderRadius = getBorderRadiusData(element);
-
-
-    var tlh = borderRadius[0][0];
-    var tlv = borderRadius[0][1];
-    var trh = borderRadius[1][0];
-    var trv = borderRadius[1][1];
-    var brv = borderRadius[2][0];
-    var brh = borderRadius[2][1];
-    var blh = borderRadius[3][0];
-    var blv = borderRadius[3][1];
-
-
-    var topWidth = width - trh;
-    var rightHeight = height - brv;
-    var bottomWidth = width - brh;
-    var leftHeight = height - blv;
-
-    var topLeftOuter = getCurvePoints(
-      x,
-      y,
-      tlh,
-      tlv
-      ).topLeft.subdivide(0.5);
-
-    var topLeftInner = getCurvePoints(
-      x + borders[3].width,
-      y + borders[0].width,
-      Math.max(0, tlh - borders[3].width),
-      Math.max(0, tlv - borders[0].width)
-      ).topLeft.subdivide(0.5);
-
-    var topRightOuter = getCurvePoints(
-      x + topWidth,
-      y,
-      trh,
-      trv
-      ).topRight.subdivide(0.5);
-
-    var topRightInner = getCurvePoints(
-      x + Math.min(topWidth, width + borders[3].width),
-      y + borders[0].width,
-      (topWidth > width + borders[3].width) ? 0 :trh - borders[3].width,
-      trv - borders[0].width
-      ).topRight.subdivide(0.5);
-
-    var bottomRightOuter = getCurvePoints(
-      x + bottomWidth,
-      y + rightHeight,
-      brh,
-      brv
-      ).bottomRight.subdivide(0.5);
-    var bottomRightInner = getCurvePoints(
-      x + Math.min(bottomWidth, width + borders[3].width),
-      y + Math.min(rightHeight, height + borders[0].width),
-      Math.max(0, brh - borders[1].width),
-      Math.max(0, brv - borders[2].width)
-      ).bottomRight.subdivide(0.5);
-
-    var bottomLeftOuter = getCurvePoints(
-      x,
-      y + leftHeight,
-      blh,
-      blv
-      ).bottomLeft.subdivide(0.5);
-
-    var bottomLeftInner = getCurvePoints(
-      x + borders[3].width,
-      y + leftHeight,
-      Math.max(0, blh - borders[3].width),
-      Math.max(0, blv - borders[2].width)
-      ).bottomLeft.subdivide(0.5);
+    borderRadius = getBorderRadiusData(element),
+    borderPoints = calculateCurvePoints(bounds, borderRadius, borders);
 
     for (borderSide = 0; borderSide < 4; borderSide++) {
       borderData = borders[borderSide];
@@ -581,7 +595,8 @@ _html2canvas.Parse = function (images, options) {
               c2: [bx + bw, by],
               c3: [bx + bw - borders[1].width, by + bh],
               c4: [bx + borders[3].width, by + bh]
-            }, borderRadius[0], borderRadius[1], topLeftOuter, topLeftInner, topRightOuter, topRightInner);
+            }, borderRadius[0], borderRadius[1],
+            borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner);
             break;
           case 1:
             // right border
@@ -593,7 +608,8 @@ _html2canvas.Parse = function (images, options) {
               c2: [bx + bw, by + bh + borders[2].width],
               c3: [bx, by + bh],
               c4: [bx, by + borders[0].width]
-            }, borderRadius[1], borderRadius[2], topRightOuter, topRightInner, bottomRightOuter, bottomRightInner);
+            }, borderRadius[1], borderRadius[2],
+            borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner);
             break;
           case 2:
             // bottom border
@@ -605,7 +621,8 @@ _html2canvas.Parse = function (images, options) {
               c2: [bx, by + bh],
               c3: [bx + borders[3].width, by],
               c4: [bx + bw - borders[2].width, by]
-            }, borderRadius[2], borderRadius[3], bottomRightOuter, bottomRightInner, bottomLeftOuter, bottomLeftInner);
+            }, borderRadius[2], borderRadius[3],
+            borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
             break;
           case 3:
             // left border
@@ -616,7 +633,8 @@ _html2canvas.Parse = function (images, options) {
               c2: [bx, by],
               c3: [bx + bw, by + borders[0].width],
               c4: [bx + bw, by + bh]
-            }, borderRadius[3], borderRadius[0], bottomLeftOuter, bottomLeftInner, topLeftOuter, topLeftInner);
+            }, borderRadius[3], borderRadius[0],
+            borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner);
             break;
         }
 
diff --git a/tests/cases/border/radius.html b/tests/cases/border/radius.html
index 84b0095..6b883fe 100644
--- a/tests/cases/border/radius.html
+++ b/tests/cases/border/radius.html
@@ -38,11 +38,12 @@
 
       .box4 {
         border-width: 50px;
-        border-left-color: #00b5e2;
+        border-left-color: transparent;
         border-top-color: red;
         border-top-width: 10px;
         border-right-color: green;
         border-bottom-right-radius: 190px;
+        background-clip: padding-box;
       }
 
       html {

From 1b29b8bc20b2af935ccd1143f9b2d8c351b3d258 Mon Sep 17 00:00:00 2001
From: MoyuScript <i@moyu.moe>
Date: Thu, 3 Jan 2013 20:34:47 +0200
Subject: [PATCH 4/6] background clipping support

---
 src/Parse.js                     | 120 ++++++++++++++++++++-----------
 src/Queue.js                     |  28 ++++++++
 src/renderers/Canvas.js          |  16 ++---
 tests/cases/background/clip.html |  60 ++++++++++++++++
 4 files changed, 174 insertions(+), 50 deletions(-)
 create mode 100644 tests/cases/background/clip.html

diff --git a/src/Parse.js b/src/Parse.js
index 6c70e35..98aeace 100644
--- a/src/Parse.js
+++ b/src/Parse.js
@@ -345,10 +345,6 @@ _html2canvas.Parse = function (images, options) {
     });
   }
 
-  function pathArc(x, y, cornerX, cornerY, radius) {
-    return ["arc", cornerX, cornerY, x, y, radius];
-  }
-
   var getCurvePoints = (function(kappa) {
 
     return function(x, y, r1, r2) {
@@ -445,6 +441,20 @@ _html2canvas.Parse = function (images, options) {
     };
   }
 
+  function parseCorner(borderArgs, radius1, radius2, corner1, corner2, x, y) {
+    if (radius1[0] > 0 || radius1[1] > 0) {
+      borderArgs.push(["line", corner1[0].start.x, corner1[0].start.y]);
+      corner1[0].curveTo(borderArgs);
+      corner1[1].curveTo(borderArgs);
+    } else {
+      borderArgs.push(["line", x, y]);
+    }
+
+    if (radius2[0] > 0 || radius2[1] > 0) {
+      borderArgs.push(["line", corner2[0].start.x, corner2[0].start.y]);
+    }
+  }
+
   function drawSide(borderData, radius1, radius2, outer1, inner1, outer2, inner2) {
     var borderArgs = [];
 
@@ -475,10 +485,6 @@ _html2canvas.Parse = function (images, options) {
     return borderArgs;
   }
 
-  function getBorderBounds(element) {
-    var backgroundClip = getCSS(element, 'backgroundClip');
-  }
-
   function calculateCurvePoints(bounds, borderRadius, borders) {
 
     var x = bounds.left,
@@ -556,36 +562,61 @@ _html2canvas.Parse = function (images, options) {
         Math.max(0, blh - borders[3].width),
         Math.max(0, blv - borders[2].width)
         ).bottomLeft.subdivide(0.5)
-    }
+    };
   }
 
-  function parseBorders(element, ctx, bounds, clip, borders){
+  function getBorderClip(element, borderPoints, borders, radius, bounds) {
+    var backgroundClip = getCSS(element, 'backgroundClip'),
+    borderArgs = [];
+
+    switch(backgroundClip) {
+      case "content-box":
+      case "padding-box":
+        parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftInner, borderPoints.topRightInner, bounds.left + borders[3].width, bounds.top + borders[0].width);
+        parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightInner, borderPoints.bottomRightInner, bounds.left + bounds.width - borders[1].width, bounds.top + borders[0].width);
+        parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightInner, borderPoints.bottomLeftInner, bounds.left + bounds.width - borders[1].width, bounds.top + bounds.height - borders[2].width);
+        parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width);
+        break;
+
+      default:
+        parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftOuter, borderPoints.topRightOuter, bounds.left, bounds.top);
+        parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightOuter, borderPoints.bottomRightOuter, bounds.left + bounds.width, bounds.top);
+        parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightOuter, borderPoints.bottomLeftOuter, bounds.left + bounds.width, bounds.top + bounds.height);
+        parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftOuter, borderPoints.topLeftOuter, bounds.left, bounds.top + bounds.height);
+        break;
+    }
+
+    return borderArgs;
+  }
+
+  function parseBorders(element, bounds, borders){
     var x = bounds.left,
     y = bounds.top,
     width = bounds.width,
     height = bounds.height,
     borderSide,
-    borderData,
     bx,
     by,
     bw,
     bh,
     borderArgs,
-    borderBounds,
     // http://www.w3.org/TR/css3-background/#the-border-radius
     borderRadius = getBorderRadiusData(element),
-    borderPoints = calculateCurvePoints(bounds, borderRadius, borders);
+    borderPoints = calculateCurvePoints(bounds, borderRadius, borders),
+    borderData = {
+      clip: getBorderClip(element, borderPoints, borders, borderRadius, bounds),
+      borders: []
+    };
 
     for (borderSide = 0; borderSide < 4; borderSide++) {
-      borderData = borders[borderSide];
-      borderArgs = [];
-      if (borderData.width>0){
+
+      if (borders[borderSide].width > 0) {
         bx = x;
         by = y;
         bw = width;
         bh = height - (borders[2].width);
 
-        switch(borderSide){
+        switch(borderSide) {
           case 0:
             // top border
             bh = borders[0].width;
@@ -638,33 +669,31 @@ _html2canvas.Parse = function (images, options) {
             break;
         }
 
-        borderBounds = {
-          left:bx,
-          top:by,
-          width: bw,
-          height:bh
-        };
+        borderData.borders.push({
+          args: borderArgs,
+          color: borders[borderSide].color
+        });
 
-        if (clip){
-          borderBounds = clipBounds(borderBounds, clip);
-        }
-        renderBorders(ctx, borderArgs, borderBounds, borderData.color);
       }
     }
 
-    return borders;
+    return borderData;
   }
 
-  function renderBorders(ctx, borderArgs, borderBounds, color) {
-    if (borderBounds.width > 0 && borderBounds.height > 0) {
-      if (color !== "transparent") {
-        ctx.setVariable( "fillStyle", color);
-        var shape = ctx.drawShape();
-        borderArgs.forEach(function(border, index) {
-          shape[(index === 0) ? "moveTo" : border[0] + "To" ].apply(null, border.slice(1));
-        });
-        numDraws+=1;
-      }
+  function createShape(ctx, args) {
+    var shape = ctx.drawShape();
+    args.forEach(function(border, index) {
+      shape[(index === 0) ? "moveTo" : border[0] + "To" ].apply(null, border.slice(1));
+    });
+    return shape;
+  }
+
+  function renderBorders(ctx, borderArgs, color) {
+    if (color !== "transparent") {
+      ctx.setVariable( "fillStyle", color);
+      createShape(ctx, borderArgs);
+      ctx.fill();
+      numDraws+=1;
     }
   }
 
@@ -930,14 +959,23 @@ _html2canvas.Parse = function (images, options) {
     stack = createStack(element, parentStack, bounds),
     borders = stack.borders,
     ctx = stack.ctx,
-    backgroundBounds = getBackgroundBounds(borders, bounds, stack.clip);
+    backgroundBounds = getBackgroundBounds(borders, bounds, stack.clip),
+    borderData = parseBorders(element, bounds, borders),
+    clipPath = createShape(ctx, borderData.clip);
+
+    ctx.save();
+    ctx.clip();
 
     if (backgroundBounds.height > 0 && backgroundBounds.width > 0){
-      renderBackgroundColor(ctx, backgroundBounds, bgcolor);
+      renderBackgroundColor(ctx, bounds, bgcolor);
       renderBackgroundImage(element, backgroundBounds, ctx);
     }
 
-    parseBorders(element, ctx, bounds, stack.clip, borders);
+    ctx.restore();
+
+    borderData.borders.forEach(function(border) {
+      renderBorders(ctx, border.args, border.color);
+    });
 
     switch(element.nodeName){
       case "IMG":
diff --git a/src/Queue.js b/src/Queue.js
index 74ec318..8d33477 100644
--- a/src/Queue.js
+++ b/src/Queue.js
@@ -4,6 +4,34 @@ function h2cRenderContext(width, height) {
     storage: storage,
     width: width,
     height: height,
+    clip: function() {
+      storage.push({
+        type: "function",
+        name: "clip",
+        'arguments': arguments
+      });
+    },
+    fill: function() {
+      storage.push({
+        type: "function",
+        name: "fill",
+        'arguments': arguments
+      });
+    },
+    save: function() {
+      storage.push({
+        type: "function",
+        name: "save",
+        'arguments': arguments
+      });
+    },
+    restore: function() {
+      storage.push({
+        type: "function",
+        name: "restore",
+        'arguments': arguments
+      });
+    },
     fillRect: function () {
       storage.push({
         type: "function",
diff --git a/src/renderers/Canvas.js b/src/renderers/Canvas.js
index a8fe43c..6d1be34 100644
--- a/src/renderers/Canvas.js
+++ b/src/renderers/Canvas.js
@@ -98,16 +98,12 @@ _html2canvas.Renderer.Canvas = function( options ) {
       ctx.fillRect(0, 0, canvas.width, canvas.height);
       ctx.fillStyle = fstyle;
 
-      var drawShape = function(args) {
-
-        var i, len = args.length;
+      var createShape = function(args) {
         ctx.beginPath();
-        for ( i = 0; i < len; i++ ) {
-          ctx[ args[ i ].name ].apply( ctx, args[ i ]['arguments'] );
-        }
+        args.forEach(function(arg) {
+          ctx[arg.name].apply(ctx, arg['arguments']);
+        });
         ctx.closePath();
-        ctx.fill();
-
       };
 
       if ( options.svgRendering && zStack.svgRender !== undefined ) {
@@ -150,7 +146,7 @@ _html2canvas.Renderer.Canvas = function( options ) {
                       ctx.fillRect.apply( ctx, renderItem['arguments'] );
                     }
                   } else if (renderItem.name === "drawShape") {
-                    drawShape(renderItem['arguments']);
+                    createShape(renderItem['arguments']);
                   } else if (renderItem.name === "fillText") {
                     if (!usingFlashcanvas || renderItem['arguments'][1] < flashMaxSize  && renderItem['arguments'][2] < flashMaxSize) {
                       ctx.fillText.apply( ctx, renderItem['arguments'] );
@@ -175,6 +171,8 @@ _html2canvas.Renderer.Canvas = function( options ) {
                       }
                       ctx.drawImage.apply( ctx, renderItem['arguments'] );
                     }
+                  } else {
+                    ctx[renderItem.name].apply(ctx, renderItem['arguments']);
                   }
 
 
diff --git a/tests/cases/background/clip.html b/tests/cases/background/clip.html
new file mode 100644
index 0000000..00bbb22
--- /dev/null
+++ b/tests/cases/background/clip.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Background attribute tests</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <script type="text/javascript" src="../../test.js"></script>
+        <style>
+            html {
+                background-color: red;
+            }
+            body {
+                background-color: lime;
+            }
+            .small div{
+                width:100px;
+                height:100px;
+                float:left;
+                margin:10px;
+                border:1px solid #000;
+            }
+
+            .medium div{
+                width:200px;
+                height:200px;
+                float:left;
+                margin:10px;
+                border:20px solid transparent;
+                border-width: 10px 20px 30px 40px;
+                background: green;
+            }
+
+            .small, .medium{
+                clear:both;
+            }
+
+            div{
+                display:block;
+            }
+
+        </style>
+
+    </head>
+    <body>
+
+        <div class="medium">
+            <div style="background:url(../../assets/image.jpg);background-clip: border-box;"></div>
+            <div style="background:url(../../assets/image.jpg);background-clip: padding-box;"></div>
+            <div style="background:url(../../assets/image.jpg);background-clip: content-box;"></div>
+            <div style="background:url(../../assets/image.jpg);"></div>
+        </div>
+
+        <div class="medium">
+            <div style="background-clip: border-box;"></div>
+            <div style="background-clip: padding-box;"></div>
+            <div style="background-clip: content-box;"></div>
+            <div style=""></div>
+        </div>
+
+    </body>
+</html>

From 1e81b220270f300112afcaf7b928e37429df1224 Mon Sep 17 00:00:00 2001
From: MoyuScript <i@moyu.moe>
Date: Thu, 3 Jan 2013 22:25:35 +0200
Subject: [PATCH 5/6] switched background rendering to use patterns

---
 src/Generate.js         |  31 +++------
 src/Parse.js            | 139 ++++++++--------------------------------
 src/Preload.js          |   8 +--
 src/Queue.js            |  14 ++++
 src/renderers/Canvas.js |   2 +
 tests/test.js           |   2 +-
 6 files changed, 55 insertions(+), 141 deletions(-)

diff --git a/src/Generate.js b/src/Generate.js
index f07d8f7..a9a41ba 100644
--- a/src/Generate.js
+++ b/src/Generate.js
@@ -323,18 +323,16 @@
   _html2canvas.Generate.Gradient = function(src, bounds) {
     var canvas = document.createElement('canvas'),
     ctx = canvas.getContext('2d'),
-    gradient, grad, i, len, img;
+    gradient, grad, i, len;
 
     canvas.width = bounds.width;
     canvas.height = bounds.height;
 
-    // TODO: add support for multi defined background gradients (like radial gradient example in background.html)
+    // TODO: add support for multi defined background gradients
     gradient = _html2canvas.Generate.parseGradient(src, bounds);
 
-    img = new Image();
-
-    if(gradient){
-      if(gradient.type === 'linear'){
+    if(gradient) {
+      if(gradient.type === 'linear') {
         grad = ctx.createLinearGradient(gradient.x0, gradient.y0, gradient.x1, gradient.y1);
 
         for (i = 0, len = gradient.colorStops.length; i < len; i+=1) {
@@ -349,8 +347,7 @@
         ctx.fillStyle = grad;
         ctx.fillRect(0, 0, bounds.width, bounds.height);
 
-        img.src = canvas.toDataURL();
-      } else if(gradient.type === 'circle'){
+      } else if(gradient.type === 'circle') {
 
         grad = ctx.createRadialGradient(gradient.cx, gradient.cy, 0, gradient.cx, gradient.cy, gradient.rx);
 
@@ -366,8 +363,7 @@
         ctx.fillStyle = grad;
         ctx.fillRect(0, 0, bounds.width, bounds.height);
 
-        img.src = canvas.toDataURL();
-      } else if(gradient.type === 'ellipse'){
+      } else if(gradient.type === 'ellipse') {
 
         // draw circle
         var canvasRadial = document.createElement('canvas'),
@@ -378,7 +374,7 @@
         canvasRadial.width = canvasRadial.height = di;
 
         grad = ctxRadial.createRadialGradient(gradient.rx, gradient.ry, 0, gradient.rx, gradient.ry, ri);
-
+        
         for (i = 0, len = gradient.colorStops.length; i < len; i+=1) {
           try {
             grad.addColorStop(gradient.colorStops[i].stop, gradient.colorStops[i].color);
@@ -393,21 +389,12 @@
 
         ctx.fillStyle = gradient.colorStops[i - 1].color;
         ctx.fillRect(0, 0, canvas.width, canvas.height);
+        ctx.drawImage(canvasRadial, gradient.cx - gradient.rx, gradient.cy - gradient.ry, 2 * gradient.rx, 2 * gradient.ry);
 
-        imgRadial = new Image();
-        imgRadial.onload = function() { // wait until the image is filled
-
-          // transform circle to ellipse
-          ctx.drawImage(imgRadial, gradient.cx - gradient.rx, gradient.cy - gradient.ry, 2 * gradient.rx, 2 * gradient.ry);
-
-          img.src = canvas.toDataURL();
-
-        };
-        imgRadial.src = canvasRadial.toDataURL();
       }
     }
 
-    return img;
+    return canvas;
   };
 
   _html2canvas.Generate.ListAlpha = function(number) {
diff --git a/src/Parse.js b/src/Parse.js
index 98aeace..103ade9 100644
--- a/src/Parse.js
+++ b/src/Parse.js
@@ -740,118 +740,27 @@ _html2canvas.Parse = function (images, options) {
     numDraws+=1;
   }
 
-  function renderBackgroundSlice (ctx, image, x, y, width, height, elx, ely){
-    var sourceX = (elx - x > 0) ? elx-x :0,
-    sourceY= (ely - y > 0) ? ely-y : 0;
-
-    drawImage(
-      ctx,
-      image,
-      Math.floor(sourceX), // source X
-      Math.floor(sourceY), // source Y
-      Math.ceil(width-sourceX), // source Width
-      Math.ceil(height-sourceY), // source Height
-      Math.ceil(x+sourceX), // destination X
-      Math.ceil(y+sourceY), // destination Y
-      Math.ceil(width-sourceX), // destination width
-      Math.ceil(height-sourceY) // destination height
-      );
-  }
-
   function renderBackgroundRepeat(ctx, image, backgroundPosition, bounds) {
-    var bgy,
-    height,
-    add,
-    h;
+    var offsetX = Math.round(bounds.left + backgroundPosition.left),
+    offsetY = Math.round(bounds.top + backgroundPosition.top);
 
-    backgroundPosition.top -= Math.ceil(backgroundPosition.top / image.height) * image.height;
-
-    for(bgy = (bounds.top + backgroundPosition.top); bgy < (bounds.height + bounds.top); bgy = Math.floor(bgy+image.height) - add) {
-      h = Math.min(image.height,(bounds.height + bounds.top) - bgy);
-
-      height = (Math.floor(bgy + image.height) > h + bgy) ? (h + bgy) - bgy : image.height;
-
-      if (bgy < bounds.top){
-        add = bounds.top - bgy;
-        bgy = bounds.top;
-      } else {
-        add = 0;
-      }
-
-      renderBackgroundRepeatX(ctx, image, backgroundPosition, bounds.left, bgy, bounds.width, height);
-
-      if (add > 0){
-        backgroundPosition.top += add;
-      }
-    }
+    ctx.createPattern(image);
+    ctx.translate(offsetX, offsetY);
+    ctx.fill();
+    ctx.translate(-offsetX, -offsetY);
   }
 
-  function renderBackgroundNoRepeat(ctx, image, backgroundPosition, x, y, w, h) {
-    var bgdw = w - backgroundPosition.left,
-    bgdh = h - backgroundPosition.top,
-    bgsx = backgroundPosition.left,
-    bgsy = backgroundPosition.top,
-    bgdx = backgroundPosition.left + x,
-    bgdy = backgroundPosition.top + y;
-
-    if (bgsx<0){
-      bgsx = Math.abs(bgsx);
-      bgdx += bgsx;
-      bgdw = Math.min(w,image.width-bgsx);
-    } else {
-      bgdw = Math.min(bgdw,image.width);
-      bgsx = 0;
-    }
-
-    if (bgsy < 0){
-      bgsy = Math.abs(bgsy);
-      bgdy += bgsy;
-      bgdh = Math.min(h, image.height - bgsy);
-    } else {
-      bgdh = Math.min(bgdh, image.height);
-      bgsy = 0;
-    }
-
-    if (bgdh > 0 && bgdw > 0){
-      drawImage(
-        ctx,
-        image,
-        bgsx,
-        bgsy,
-        bgdw,
-        bgdh,
-        bgdx,
-        bgdy,
-        bgdw,
-        bgdh
-        );
-    }
-  }
-
-  function renderBackgroundRepeatY (ctx, image, backgroundPosition, x, y, w, h){
-    var height,
-    width = Math.min(image.width, w),
-    bgy;
-
-    backgroundPosition.top -= Math.ceil(backgroundPosition.top / image.height) * image.height;
-
-    for (bgy = y + backgroundPosition.top; bgy < h + y; bgy = Math.round(bgy + image.height)){
-      height = (Math.floor(bgy + image.height) > h + y) ? (h+y) - bgy : image.height;
-      renderBackgroundSlice(ctx, image, x + backgroundPosition.left, bgy,width, height, x, y);
-    }
-  }
-
-  function renderBackgroundRepeatX(ctx, image, backgroundPosition, x, y, w, h){
-    var height = Math.min(image.height, h),
-    width,
-    bgx;
-
-    backgroundPosition.left -= Math.ceil(backgroundPosition.left / image.width) * image.width;
-
-    for (bgx = x + backgroundPosition.left; bgx < w + x; bgx = Math.round(bgx + image.width)) {
-      width = (Math.floor(bgx + image.width) > w + x) ? (w + x) - bgx : image.width;
-      renderBackgroundSlice(ctx, image, bgx,(y + backgroundPosition.top), width, height, x, y);
-    }
+  function backgroundRepeatShape(ctx, image, backgroundPosition, bounds, left, top, width, height) {
+    var args = [];
+    args.push(["line", Math.round(left), Math.round(top)]);
+    args.push(["line", Math.round(left + width), Math.round(top)]);
+    args.push(["line", Math.round(left + width), Math.round(height + top)]);
+    args.push(["line", Math.round(left), Math.round(height + top)]);
+    createShape(ctx, args);
+    ctx.save();
+    ctx.clip();
+    renderBackgroundRepeat(ctx, image, backgroundPosition, bounds);
+    ctx.restore();
   }
 
   function renderBackgroundColor(ctx, backgroundBounds, bgcolor) {
@@ -870,15 +779,18 @@ _html2canvas.Parse = function (images, options) {
     backgroundRepeat = getCSS(el, "backgroundRepeat").split(",")[0];
     switch (backgroundRepeat) {
       case "repeat-x":
-        renderBackgroundRepeatX(ctx, image, backgroundPosition, bounds.left, bounds.top, bounds.width, bounds.height);
+        backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
+          bounds.left, bounds.top + backgroundPosition.top, 99999, image.height);
         break;
 
       case "repeat-y":
-        renderBackgroundRepeatY(ctx, image, backgroundPosition, bounds.left, bounds.top, bounds.width, bounds.height);
+        backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
+          bounds.left + backgroundPosition.left, bounds.top, image.width, 99999);
         break;
 
       case "no-repeat":
-        renderBackgroundNoRepeat(ctx, image, backgroundPosition, bounds.left, bounds.top, bounds.width, bounds.height);
+        backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
+          bounds.left + backgroundPosition.left, bounds.top + backgroundPosition.top, image.width, image.height);
         break;
 
       default:
@@ -960,8 +872,9 @@ _html2canvas.Parse = function (images, options) {
     borders = stack.borders,
     ctx = stack.ctx,
     backgroundBounds = getBackgroundBounds(borders, bounds, stack.clip),
-    borderData = parseBorders(element, bounds, borders),
-    clipPath = createShape(ctx, borderData.clip);
+    borderData = parseBorders(element, bounds, borders);
+
+    createShape(ctx, borderData.clip);
 
     ctx.save();
     ctx.clip();
diff --git a/src/Preload.js b/src/Preload.js
index 4a6f168..cab1e7d 100644
--- a/src/Preload.js
+++ b/src/Preload.js
@@ -119,16 +119,14 @@ _html2canvas.Preload = function( options ) {
       // opera throws exception on external-content.html
       try {
         background_image = _html2canvas.Util.getCSS(el, 'backgroundImage');
-      }catch(e) {
+      } catch(e) {
         h2clog("html2canvas: failed to get background-image - Exception: " + e.message);
       }
-      if ( background_image && background_image !== "1" && background_image !== "none" ) {
-
+      if (background_image && background_image !== "1" && background_image !== "none") {
         // TODO add multi image background support
 
         if (/^(-webkit|-o|-moz|-ms|linear)-/.test( background_image )) {
-
-          img = _html2canvas.Generate.Gradient( background_image, _html2canvas.Util.Bounds( el ) );
+          img = _html2canvas.Generate.Gradient(background_image, _html2canvas.Util.Bounds( el ) );
 
           if ( img !== undefined ){
             images[background_image] = {
diff --git a/src/Queue.js b/src/Queue.js
index 8d33477..3a8d22c 100644
--- a/src/Queue.js
+++ b/src/Queue.js
@@ -11,6 +11,13 @@ function h2cRenderContext(width, height) {
         'arguments': arguments
       });
     },
+    translate: function() {
+      storage.push({
+        type: "function",
+        name: "translate",
+        'arguments': arguments
+      });
+    },
     fill: function() {
       storage.push({
         type: "function",
@@ -39,6 +46,13 @@ function h2cRenderContext(width, height) {
         'arguments': arguments
       });
     },
+    createPattern: function() {
+      storage.push({
+        type: "function",
+        name: "createPattern",
+        'arguments': arguments
+      });
+    },
     drawShape: function() {
 
       var shape = [];
diff --git a/src/renderers/Canvas.js b/src/renderers/Canvas.js
index 6d1be34..78e8d7e 100644
--- a/src/renderers/Canvas.js
+++ b/src/renderers/Canvas.js
@@ -145,6 +145,8 @@ _html2canvas.Renderer.Canvas = function( options ) {
                     if (!usingFlashcanvas || renderItem['arguments'][0] + renderItem['arguments'][2] < flashMaxSize  && renderItem['arguments'][1] + renderItem['arguments'][3] < flashMaxSize) {
                       ctx.fillRect.apply( ctx, renderItem['arguments'] );
                     }
+                  } else if (renderItem.name === "createPattern") {
+                    ctx.fillStyle = ctx.createPattern(renderItem['arguments'][0], "repeat");
                   } else if (renderItem.name === "drawShape") {
                     createShape(renderItem['arguments']);
                   } else if (renderItem.name === "fillText") {
diff --git a/tests/test.js b/tests/test.js
index 77079c7..cc720a3 100644
--- a/tests/test.js
+++ b/tests/test.js
@@ -13,7 +13,7 @@ var h2cSelector, h2cOptions;
     document.write(srcStart + '/tests/assets/jquery.plugin.html2canvas.js' + scrEnd);
     var html2canvas = ['Core', 'Generate', 'Parse', 'Preload', 'Queue', 'Renderer', 'Util', 'Support', 'Font', 'renderers/Canvas'], i;
     for (i = 0; i < html2canvas.length; ++i) {
-        document.write(srcStart + '/src/' + html2canvas[i] + '.js' + scrEnd);
+        document.write(srcStart + '/src/' + html2canvas[i] + '.js?' + Math.random() + scrEnd);
     }
     window.onload = function() {
         h2cSelector = [document.body];

From 687d77174fe8ad6c12d92033cc5b21b9cdbf8899 Mon Sep 17 00:00:00 2001
From: MoyuScript <i@moyu.moe>
Date: Thu, 3 Jan 2013 22:30:52 +0200
Subject: [PATCH 6/6] updated results

---
 tests/readme.md | 40 +++++++++++++++++++++-------------------
 1 file changed, 21 insertions(+), 19 deletions(-)

diff --git a/tests/readme.md b/tests/readme.md
index bf29ecd..51db39c 100644
--- a/tests/readme.md
+++ b/tests/readme.md
@@ -1,31 +1,33 @@
 <table><thead><tr><td></td><th>chrome<br />23.0.1271.97</th><th>firefox<br />12.0</th><th>iexplorer<br />9</th></tr></thead><tbody>
+<tr><td>background/clip.html</td><td>100%</td><td>100%</td><td>99.89%</td></tr>
 <tr><td>background/encoded.html</td><td>100%</td><td>100%</td><td>100%</td></tr>
-<tr><td>background/linear-gradient.html</td><td>82.27%</td><td>85.64%</td><td>100%</td></tr>
-<tr><td>background/multi.html</td><td>96.6%</td><td>96.45%</td><td>96.89%</td></tr>
-<tr><td>background/position.html</td><td>97.03%</td><td>96.97%</td><td>97.03%</td></tr>
-<tr><td>background/radial-gradient.html</td><td>57.9%</td><td>54.87%</td><td>94.02%</td></tr>
-<tr><td>background/repeat.html</td><td>100%</td><td>100%</td><td>100%</td></tr>
+<tr><td>background/linear-gradient.html</td><td>89.87%</td><td>90.73%</td><td>100%</td></tr>
+<tr><td>background/multi.html</td><td>96.6%</td><td>96.45%</td><td>96.85%</td></tr>
+<tr><td>background/position.html</td><td>100%</td><td>100%</td><td>99.87%</td></tr>
+<tr><td>background/radial-gradient.html</td><td>73.23%</td><td>70.32%</td><td>94.02%</td></tr>
+<tr><td>background/repeat.html</td><td>100%</td><td>100%</td><td>99.92%</td></tr>
 <tr><td>border/dashed.html</td><td>96.45%</td><td>98.38%</td><td>97.7%</td></tr>
 <tr><td>border/dotted.html</td><td>97.41%</td><td>96.46%</td><td>95.93%</td></tr>
 <tr><td>border/double.html</td><td>97.96%</td><td>97.87%</td><td>97.95%</td></tr>
+<tr><td>border/radius.html</td><td>99.74%</td><td>99.77%</td><td>99.75%</td></tr>
 <tr><td>border/solid.html</td><td>99.97%</td><td>99.97%</td><td>99.98%</td></tr>
-<tr><td>forms.html</td><td>95.96%</td><td>94.55%</td><td>95.02%</td></tr>
+<tr><td>forms.html</td><td>95.96%</td><td>94.55%</td><td>95.01%</td></tr>
 <tr><td>images/canvas.html</td><td>99.86%</td><td>100%</td><td>100%</td></tr>
 <tr><td>images/cross-origin.html</td><td>97.99%</td><td>97.58%</td><td>99.35%</td></tr>
 <tr><td>images/empty.html</td><td>99.86%</td><td>99.87%</td><td>99.85%</td></tr>
 <tr><td>images/images.html</td><td>83.72%</td><td>96.93%</td><td>55.09%</td></tr>
 <tr><td>images/svg.html</td><td>99.92%</td><td>96.79%</td><td>99.93%</td></tr>
-<tr><td>list/decimal-leading-zero.html</td><td>99.63%</td><td>99.72%</td><td>35.04%</td></tr>
-<tr><td>list/decimal.html</td><td>99.64%</td><td>99.73%</td><td>35.06%</td></tr>
-<tr><td>list/lower-alpha.html</td><td>99.65%</td><td>99.73%</td><td>35.05%</td></tr>
-<tr><td>list/upper-roman.html</td><td>99.45%</td><td>99.61%</td><td>35.11%</td></tr>
-<tr><td>overflow.html</td><td>96.85%</td><td>97.49%</td><td>96.51%</td></tr>
-<tr><td>text/linethrough.html</td><td>97.14%</td><td>94.12%</td><td>45.74%</td></tr>
-<tr><td>text/text.html</td><td>95.71%</td><td>94.67%</td><td>79.85%</td></tr>
-<tr><td>text/underline-lineheight.html</td><td>97.06%</td><td>92.35%</td><td>51.38%</td></tr>
-<tr><td>text/underline.html</td><td>97.65%</td><td>93.5%</td><td>45.69%</td></tr>
-<tr><td>visibility.html</td><td>99.19%</td><td>98.92%</td><td>99.39%</td></tr>
-<tr><td>zindex/z-index1.html</td><td>97.09%</td><td>99.38%</td><td>99.54%</td></tr>
-<tr><td>zindex/z-index2.html</td><td>95.94%</td><td>98.16%</td><td>97.81%</td></tr>
-<tr><td>zindex/z-index3.html</td><td>98.98%</td><td>98.55%</td><td>98.68%</td></tr>
+<tr><td>list/decimal-leading-zero.html</td><td>99.63%</td><td>99.72%</td><td>35.88%</td></tr>
+<tr><td>list/decimal.html</td><td>99.64%</td><td>99.73%</td><td>35.89%</td></tr>
+<tr><td>list/lower-alpha.html</td><td>99.65%</td><td>99.73%</td><td>35.89%</td></tr>
+<tr><td>list/upper-roman.html</td><td>99.45%</td><td>99.61%</td><td>35.94%</td></tr>
+<tr><td>overflow.html</td><td>96.85%</td><td>97.49%</td><td>96.5%</td></tr>
+<tr><td>text/linethrough.html</td><td>97.14%</td><td>94.12%</td><td>47.08%</td></tr>
+<tr><td>text/text.html</td><td>95.71%</td><td>94.67%</td><td>85.01%</td></tr>
+<tr><td>text/underline-lineheight.html</td><td>97.06%</td><td>92.35%</td><td>53%</td></tr>
+<tr><td>text/underline.html</td><td>97.65%</td><td>93.5%</td><td>47.02%</td></tr>
+<tr><td>visibility.html</td><td>99.19%</td><td>98.81%</td><td>99.39%</td></tr>
+<tr><td>zindex/z-index1.html</td><td>96.99%</td><td>99.27%</td><td>99.44%</td></tr>
+<tr><td>zindex/z-index2.html</td><td>95.85%</td><td>98.06%</td><td>97.72%</td></tr>
+<tr><td>zindex/z-index3.html</td><td>98.6%</td><td>98.29%</td><td>98.56%</td></tr>
 </tbody></table>
\ No newline at end of file