From bb73d3c15ea16434eacaf96b9cefd89330315ba1 Mon Sep 17 00:00:00 2001 From: Niklas von Hertzen Date: Wed, 2 Jan 2013 21:26:24 +0200 Subject: [PATCH] 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 {