From 268b957a090533096ed512c90ee3ee652504a66b Mon Sep 17 00:00:00 2001 From: Tobias Schneider Date: Sat, 13 Apr 2013 13:56:12 -0700 Subject: [PATCH] Merge gfx work. --- lib/Kanvas/kanvas.js | 2162 +++++++++++++++++++++++----- src/flash/display/DisplayObject.js | 53 +- src/flash/display/Graphics.js | 653 +-------- src/flash/display/Loader.js | 4 +- src/swf/renderer.js | 25 +- src/swf/shape.js | 231 ++- 6 files changed, 1965 insertions(+), 1163 deletions(-) diff --git a/lib/Kanvas/kanvas.js b/lib/Kanvas/kanvas.js index aa18612d7..e35c653fd 100644 --- a/lib/Kanvas/kanvas.js +++ b/lib/Kanvas/kanvas.js @@ -3,43 +3,83 @@ // Kanvas adds support for not (yet) implemented HTML Canvas APIs. -;var Kanvas = Kanvas || (function (document, undefined) { +;var Kanvas = Kanvas || (function (doc, undefined) { 'use strict'; - var Kanvas = { version: '0.0.0' }; + /** @const */ var PATH_OP_CLOSE = 0; + /** @const */ var PATH_OP_MOVE = 1; + /** @const */ var PATH_OP_LINE = 2; + /** @const */ var PATH_OP_CURVE = 3; + /** @const */ var PATH_OP_BEZIER = 4; + /** @const */ var PATH_OP_ARCTO = 5; + /** @const */ var PATH_OP_RECT = 6; + /** @const */ var PATH_OP_ARC = 7; + /** @const */ var PATH_OP_ELLIPSE = 8; + /** @const */ var PATH_OP_TRANSFORM = 9; - var nativeCanvas = document.createElement('canvas'); + /** @const */ var PI = Math.PI; + /** @const */ var PI_DOUBLE = PI * 2; + /** @const */ var PI_HALF = PI / 2; + + /** @const */ var SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; + + var Kanvas = { VERSION: '0.0.0' }; + + var nativeCanvas = doc.createElement('canvas'); var nativeCanvasClass = nativeCanvas.constructor; - var native2dContext = nativeCanvas.getContext('2d'); - var native2dContextClass = native2dContext.constructor; + var native2dCtx = nativeCanvas.getContext('2d'); + var native2dCtxClass = native2dCtx.constructor; var nativeCanvasProto = nativeCanvasClass.prototype; - var native2dContextProto = native2dContextClass.prototype; - var kanvas2dContextProto = Object.create(native2dContextProto); + var native2dCtxProto = native2dCtxClass.prototype; + var kanvas2dCtxProto = Object.create(null); - var nativeGetContext = nativeCanvas.getContext; + var nativePathClass = typeof Path === 'undefined' ? undefined : Path; + var nativePathProto = nativePathClass && nativePathClass.prototype; + var kanvasPathProto = Object.create(null); - var defineProperty = Object.defineProperty; - var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; - var getOwnPropertyNames = Object.getOwnPropertyNames; + var shimCurrentTransform = !('currentTransform' in native2dCtx); + var shimResetTransform = !('resetTransform' in native2dCtxProto); - var matrixClass; - var pathClass; + var shimEllipticalArcTo = false; + try { + native2dCtx.arcTo(0, 0, 1, 1, 1, -1); + } catch (e) { + shimEllipticalArcTo = true; + } - /** @const */ var PATH_CMD_CLOSE = 0; - /** @const */ var PATH_CMD_MOVE = 1; - /** @const */ var PATH_CMD_LINE = 2 - /** @const */ var PATH_CMD_CURVE = 3; + var shimEllipse = !('ellipse' in native2dCtxProto); - function defineLazyProperty(obj, prop, desc) { - defineProperty(obj, prop, { + var shimPath = !nativePathClass; + var shimBounds = shimPath; + var shimStrokePath = shimPath; + + if (!shimPath) { + shimBounds = !('getBounds' in nativePathProto); + shimStrokePath = !('StrokePath' in nativePathProto); + shimPath = shimBounds || shimStrokePath; + } + + var shimHitRegions = !('addHitRegions' in native2dCtxProto); + + var defineProp = Object.defineProperty; + var getOwnPropDesc = Object.getOwnPropertyDescriptor; + var getOwnPropNames = Object.getOwnPropertyNames; + + var transformClass, idTransform, pathClass; + + function defineLazyProp(obj, prop, desc) { + defineProp(obj, prop, { get: function () { + if (this === obj) + return; + var val; if (desc.get) { val = desc.get.call(this); - defineProperty(this, prop, { + defineProp(this, prop, { value: val, writable: desc.writable, configurable: desc.configurable, @@ -54,188 +94,841 @@ }); } - function arcToBezier(sink, cx, cy, radius, angle0, angle1, acw) { - var x = cx + Math.cos(angle0) * radius; - var y = cy + Math.sin(angle0) * radius; + function mixinProps(dest, source) { + var props = getOwnPropNames(source); + for (var i = 0; i < props.length; i++) { + var key = props[i]; + var desc = getOwnPropDesc(source, key); + safeReplaceProp(dest, key, desc); + } + } + function safeReplaceProp(obj, prop, desc) { + if (prop in obj) { + defineProp(obj, '_' + prop, { + value: obj[prop], + configurable: true + }); + } + defineProp(obj, prop, desc); + } - sink.__cmds__.push(PATH_CMD_LINE); - sink.__data__.push(x, y); + function tmplf(format) { + if (arguments.length === 1) + return tmplf.bind(null, format); - if (acw) { - if (angle0 < angle1) - angle0 += Math.ceil((angle1 - angle0) / (2 * Math.PI)) * 2 * Math.PI; - if (angle0 - angle1 > 2 * Math.PI) - angle1 = angle0 - 2 * Math.PI; - } else { - if (angle1 < angle0) - angle1 += Math.ceil((angle0 - angle1) / (2 * Math.PI)) * 2 * Math.PI; - if (angle1 - angle0 > 2 * Math.PI) - angle1 = angle0 + 2 * Math.PI; + var params = ['_']; + var args = [mapf]; + + for (var i = 1; i < arguments.length; i++) { + params[i] = '$' + i; + args[i] = arguments[i]; } - var sweep = Math.abs(angle1 - angle0); - var dir = acw ? -1 : 1; + return Function(params.join(','), + 'return "' + format.replace( + + /\$(\$|[1-9]\d*|\(((?:(['"]).*?\3|.)*?);\))|"/g, + + function sub(match, p1,p2) { + if (p1) { + if (p1 === '$') + return p1; + + if (p2) + return '"+(' + p2 + ')+"'; + + return '"+' + match + '+"'; + } + + return '\\"'; + } + ) + '"' + ).apply(null, args); + } + function mapf(array, format, separator) { + if (+array == array) + array = Array(array); + else if (typeof array === 'string') + array = array.split(','); + + if (format) + array = array.map(tmplf(format)); + + if (separator !== undefined) + return array.join(separator); + + return array; + } + function evalf(format) { + return (1, eval)('1,' + tmplf.apply(null, arguments)); + } + + function parseSvgDataStr(sink, d) { + var chunks = (d + '').match(/[a-z][^a-z]*/gi); + + var x0 = 0; + var y0 = 0; + var cpx = 0; + var cpy = 0; + + var x = 0; + var y = 0; + + for (var i = 0; i < chunks.length; i++) { + var seg = chunks[i]; + var cmd = seg[0].toUpperCase(); + + if (cmd === 'Z') { + sink.closePath(); + continue; + } + + var abs = cmd === seg[0]; + var args = seg.slice(1) + .trim() + .replace(/(\d)-/g, '$1 -') + .split(/,|\s/) + .map(parseFloat); + var narg = args.length; + + var j = 0; + while (j < narg) { + x0 = x; + y0 = y; + + if (abs) + x = y = 0; + + switch (cmd) { + case 'A': + var rx = args[j++]; + var ry = args[j++]; + var rotation = args[j++] * PI / 180; + var large = args[j++]; + var sweep = args[j++]; + + x += args[j++]; + y += args[j++]; + + var u = Math.cos(rotation); + var v = Math.sin(rotation); + + var h1x = (x0 - x) / 2; + var h1y = (y0 - y) / 2; + var x1 = u * h1x + v * h1y; + var y1 = -v * h1x + u * h1y; + + var prx = rx * rx; + var pry = ry * ry; + var plx = x1 * x1; + var ply = y1 * y1; + + var pl = plx / prx + ply / pry; + if (pl > 1) { + rx *= Math.sqrt(pl); + ry *= Math.sqrt(pl); + prx = rx * rx; + pry = ry * ry; + } + + var sq = (prx * pry - prx * ply - pry * plx) / (prx * ply + pry * plx); + var coef = (large === sweep ? -1 : 1) * (sq < 0 ? 0 : Math.sqrt(sq)); + var ox = coef * rx * y1 / ry; + var oy = coef * -ry * x1 / rx; + + var h2x = (x0 + x) / 2; + var h2y = (y0 + y) / 2; + var cx = u * ox - v * oy + h2x; + var cy = v * ox + u * oy + h2y; + + var ux = (x1 - ox) / rx; + var uy = (y1 - oy) / ry; + var vx = (-x1 - ox) / rx; + var vy = (-y1 - oy) / ry; + + var n0 = Math.sqrt(ux * ux + uy * uy); + var a0 = (uy < 0 ? -1 : 1) * Math.acos(ux / n0); + + var n1 = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)); + var p = ux * vx + uy * vy; + var aext = (ux * vy - uy * vx < 0 ? -1 : 1) * Math.acos(p / n1); + if (sweep) { + if (aext < 0) + aext += PI_DOUBLE; + } else if (aext > 0) { + aext += PI_DOUBLE; + } + var a1 = a0 + aext; + + sink.ellipse(cx,cy, rx,ry, rotation, a0,a1, !sweep); + break; + case 'C': + var x1 = x + args[j++]; + var y1 = y + args[j++]; + + cpx = x + args[j++]; + cpy = y + args[j++]; + + x += args[j++]; + y += args[j++]; + + sink.bezierCurveTo(x1,y1, cpx,cpy, x,y); + break; + case 'H': + x += args[j++]; + sink.lineTo(x, y); + break; + case 'L': + x += args[j++]; + y += args[j++]; + sink.lineTo(x, y); + break; + case 'M': + x += args[j++]; + y += args[j++]; + sink.moveTo(x, y); + break; + case 'Q': + cpx = x + args[j++]; + cpy = y + args[j++]; + + x += args[j++]; + y += args[j++]; + + sink.quadraticCurveTo(cpx,cpy, x,y); + break; + case 'S': + var x1 = x0 * 2 - cpx; + var y1 = y0 * 2 - cpy; + + cpx = x + args[j++]; + cpy = y + args[j++]; + + x += args[j++]; + y += args[j++]; + + sink.bezierCurveTo(x1,y1, cpx,cpy, x,y); + break; + case 'T': + cpx = x0 * 2 - cpx; + cpy = y0 * 2 - cpy; + + x += args[j++]; + y += args[j++]; + + sink.quadraticCurveTo(cpx,cpy, x,y); + break; + case 'V': + y += args[j++]; + sink.lineTo(x, y); + break; + default: + return; + } + } + } + } + + function arcToCurveSegs(sink, cx,cy, r, a0,a1, ccw, m11,m12, m21,m22, tx,ty) { + var x = cx + Math.cos(a0) * r; + var y = cy + Math.sin(a0) * r; + + var x1 = x * m11 + y * m12 + tx; + var y1 = x * m21 + y * m22 + ty; + + //sink._lineSeg(sink._state[4],sink._state[5], x1,y1); + + if (ccw) { + if (a0 < a1) + a0 += Math.ceil((a1 - a0) / PI_DOUBLE + 0.1) * PI_DOUBLE; + if (a0 - a1 > PI_DOUBLE) + a1 = a0 - PI_DOUBLE; + } else { + if (a1 < a0) + a1 += Math.ceil((a0 - a1) / PI_DOUBLE + 0.1) * PI_DOUBLE; + if (a1 - a0 > PI_DOUBLE) + a1 = a0 + PI_DOUBLE; + } + + var sweep = Math.abs(a1 - a0); + var dir = ccw ? -1 : 1; while (sweep > 0) { - if (sweep > Math.PI / 2) - angle1 = angle0 + Math.PI / 2 * dir; - else - angle1 = angle0 + sweep * dir; + a1 = a0 + (sweep > PI_HALF ? PI_HALF : sweep) * dir; - var kappa = (4 / 3) * Math.tan((angle1 - angle0) / 4) * radius; + var kappa = (4 / 3) * Math.tan((a1 - a0) / 4) * r; - x = cx + Math.cos(angle1) * radius; - y = cy + Math.sin(angle1) * radius; + var cos0 = Math.cos(a0); + var sin0 = Math.sin(a0); + var cp1x = cx + cos0 * r + -sin0 * kappa; + var cp1y = cy + sin0 * r + cos0 * kappa; - var cp1x = cx + Math.cos(angle0) * radius + -Math.sin(angle0) * kappa; - var cp1y = cy + Math.sin(angle0) * radius + Math.cos(angle0) * kappa; + var cos1 = Math.cos(a1); + var sin1 = Math.sin(a1); - var cp2x = x + Math.sin(angle1) * kappa; - var cp2y = y + -Math.cos(angle1) * kappa; + x = cx + cos1 * r; + y = cy + sin1 * r; - sink.__cmds__.push(PATH_CMD_CURVE); - sink.__data__.push(cp1x, cp1y, cp2x, cp2y, x, y); + var cp2x = x + sin1 * kappa; + var cp2y = y + -cos1 * kappa; - sweep -= Math.PI / 2; - angle0 = angle1; + var x2 = x * m11 + y * m12 + tx; + var y2 = x * m21 + y * m22 + ty; + + sink._curveSeg( + x1, + y1, + cp1x * m11 + cp1y * m12 + tx, + cp1x * m21 + cp1y * m22 + ty, + cp2x * m11 + cp2y * m12 + tx, + cp2x * m21 + cp2y * m22 + ty, + x2, + y2 + ); + + x1 = x2; + y1 = y2; + + a0 = a1; + sweep -= PI_HALF; + } + } + + function getDerivativeRoots(x0,y0, cp1x,cp1y, cp2x,cp2y, x,y) { + var res = []; + + var dn1x = -x0 + 3 * cp1x - 3 * cp2x + x; + if (dn1x) { + var txl = -x0 + 2 * cp1x - cp2x; + var txr = -Math.sqrt(-x0 * (cp2x + x) + cp1x * cp1x - + cp1x * (cp2x + x) + cp2x * cp2x); + + var r1 = (txl + txr) / dn1x; + if (r1 > 0 && r1 < 1) + res.push(r1); + + var r2 = (txl - txr) / dn1x; + if (r2 > 0 && r2 < 1) + res.push(r2); + } + + var dn2x = x0 - 3 * cp1x + 3 * cp2x - x; + if (dn2x) { + var r3 = (x0 - 2 * cp1x + cp2x) / dn2x; + if (r3 > 0 && r3 < 1) + res.push(r3); + } + + var dn1y = -y0 + 3 * cp1x - 3 * cp2x + y; + if (dn1y) { + var tyl = -y0 + 2 * cp1y - cp2y; + var tyr = -Math.sqrt(-y0 * (cp2y + y) + cp1y * cp1y - + cp1y * (cp2y + y) + cp2y * cp2y); + + var r4 = (tyl + tyr) / dn1y; + if (r4 > 0 && r4 < 1) + res.push(r4); + + var r5 = (tyl - tyr) / dn1y; + if (r5 > 0 && r5 < 1) + res.push(r5); + } + + var dn2y = y0 - 3 * cp1y + 3 * cp2y - y; + if (dn2y) { + var r6 = (y0 - 2 * cp1y + cp2y) / dn2y; + if (r6 > 0 && r6 < 1) + res.push(r6); + } + + return res; + } + + function computeCubicBaseValue(t, a,b,c,d) { + var mt = 1 - t; + return mt * mt * mt * a + 3 * mt * mt * t * b + + 3 * mt * t * t * c + t * t * t * d; + } + + function findCubicRoot(a,b,c,d, offset) { + var t = 1; + do { + var r = t; + var depth = 1; + + do { + var tt = r; + + var f = computeCubicBaseValue(tt, a,b,c,d) - offset; + + var t2 = tt * tt; + var t6 = 6 * tt; + var t12 = 2 * t6; + var t92 = t2 * 9; + var mt2 = (tt - 1) * (tt - 1); + var df = 3 * d * t2 + c * (t6 - t92) + + b * (t92 - t12 + 3) - 3 * a * mt2; + + r = tt - (f / df); + + if (!df) + return -1; + + if (depth > 12) { + if (Math.abs(tt - r) < 0.001) + break; + + return -1; + } + } while (Math.abs(tt - r) > 0.0001); + + if (r < 0 || r > 1) + continue; + + var y = computeCubicBaseValue(r, a,b,c,d); + + var dy = offset - y; + if (dy * dy > 1) + continue; + + return r; + } while (t--); + + return -1; + } + + function scaleCurve(x0,y0, cp1x,cp1y, cp2x,cp2y, x,y, distance) { + var pas = getAngularDir(cp1x - x0, cp1y - y0) + PI_HALF; + var pdxs = Math.cos(pas); + var pdys = Math.sin(pas); + + var pae = getAngularDir(x - cp2x, y - cp2y) + PI_HALF; + var pdxe = Math.cos(pae); + var pdye = Math.sin(pae); + + if (pdxs == pdxe && pdys == pdye) + return null; + + var sx, sy; + + if (pdxs == pdxe || pdys == pdye) { + sx = (x0 + x) / 2; + sy = (y0 + y) / 2; + } else { + var s = getLineIntersect( + x0, y0, x0 + pdxs, y0 + pdys, + x + pdxe, y + pdye, x, y + ); + sx = s[0]; + sy = s[1]; + } + + var a1 = getAngularDir(x0 - sx, y0 - sy); + var a2 = getAngularDir(cp1x - sx, cp1y - sy); + var a3 = getAngularDir(cp2x - sx, cp2y - sy); + var a4 = getAngularDir(x - sx, y - sy); + + if (a1 === a2 || a2 > a3) + distance = -distance; + + var nax = x0 - distance * Math.cos(a1); + var nay = y0 - distance * Math.sin(a1); + var ndx = x - distance * Math.cos(a4); + var ndy = y - distance * Math.sin(a4); + var nbx, nby, ncx, ncy; + + var nb = getLineIntersect( + nax, nay, + nax + (cp1x - x0), nay + (cp1y - y0), + sx, sy, + cp1x, cp1y + ); + if (nb) { + nbx = nb[0]; + nby = nb[1]; + } else { + nbx = nax; + nby = nay; + } + + var nc = getLineIntersect( + ndx, ndy, + ndx + (cp2x - x), ndy + (cp2y - y), + sx, sy, + cp2x, cp2y + ); + if (nc) { + ncx = nc[0]; + ncy = nc[1]; + } else { + ncx = ndx; + ncy = ndy; + } + + return [nax,nay, nbx,nby, ncx,ncy, ndx,ndy]; + } + function getAngularDir(x, y) { + var d1 = 0; + var d2 = PI_HALF; + var d3 = PI; + var d4 = 3 * PI_HALF; + + var angle = 0; + var ax = Math.abs(x); + var ay = Math.abs(y); + + if (!x) + angle = y >= 0 ? d2 : d4; + else if (!y) + angle = x >= 0 ? d1 : d3; + else if (x > 0 && y > 0) + angle = d1 + Math.atan(ay / ax); + else if (x < 0 && y < 0) + angle = d3 + Math.atan(ay / ax); + else if (x < 0 && y > 0) + angle = d2 + Math.atan(ax / ay); + else if (x > 0 && y < 0) + angle = d4 + Math.atan(ax / ay); + + return (angle + PI_DOUBLE) % PI_DOUBLE; + } + function getLineIntersect(ax1,ay1, ax2,ay2, bx1,by1, bx2,by2) { + var nx = (ax1 * ay2 - ay1 * ax2) * (bx1 - bx2) - + (ax1 - ax2) * (bx1 * by2 - by1 * bx2); + var ny = (ax1 * ay2 - ay1 * ax2) * (by1 - by2) - + (ay1 - ay2) * (bx1 * by2 - by1 * bx2); + var dn = (ax1 - ax2) * (by1 - by2) - (ay1 - ay2) * (bx1 - bx2); + + if (!dn) + return null; + + var px = nx / dn; + var py = ny / dn; + + return [px, py]; + } + + function buildOutline(sink, data, styles) { + sink.moveTo(data[1], data[2]); + + for (var i = 0; i < data.length;) { + var npoint = data[i]; + + var x0 = data[i + 1]; + var y0 = data[i + 2]; + + var a1x, a1y, a2x, a2y; + + if (npoint === 2) { + var x = data[i + 3]; + var y = data[i + 4]; + + sink.lineTo(x, y); + + a1x = x0; + a1y = y0; + a2x = x; + a2y = y; + + i += 5; + } else { + //for (var j = 1; j < npoint; j += 3, i += 6) { + var cp1x = data[i + 3]; + var cp1y = data[i + 4]; + + var cp2x = data[i + 5]; + var cp2y = data[i + 6]; + var x = data[i + 7]; + var y = data[i + 8]; + + sink.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); + + a1x = cp2x; + a1y = cp2y; + a2x = x; + a2y = y; + //} + + i += 9; + } + + var lineJoin = styles.lineJoin || 'bevel'; + + var p = i % data.length; + var b1x = data[p + 1]; + var b1y = data[p + 2]; + + if (~~(a2x + 0.5) === ~~(b1x + 0.5) && ~~(a2y + 0.5) === ~~(b1y + 0.5)) + continue; + + var b2x = data[p + 3]; + var b2y = data[p + 4]; + + var a1 = a2y - a1y; + var b1 = -(a2x - a1x); + var c1 = a1x * a2y - a2x * a1y; + var a2 = b2y - b1y; + var b2 = -(b2x - b1x); + var c2 = b1x * b2y - b2x * b1y; + var d = a1 * b2 - b1 * a2; + + if (!d) { + sink.lineTo(b1x, b1y); + } else { + var x = (c1 * b2 - b1 * c2) / d; + var y = (a1 * c2 - c1 * a2) / d; + + var ona = !((x < a1x && x < a2x) || (x > a1x && x > a2x) || + (y < a1y && y < a2y) || (y > a1y && y > a2y)); + var onb = !((x < b1x && x < b2x) || (x > b1x && x > b2x) || + (y < b1y && y < b2y) || (y > b1y && y > b2y)); + + if (!ona && !onb) { + switch (lineJoin) { + case 'bevel': + sink.lineTo(b1x, b1y); + break; + case 'round': + sink.quadraticCurveTo(x, y, b1x, b1y); + break; + case 'miter': + default: + var a = -a2y - b1y; + var b = a2x - b1x; + var d = Math.sqrt(a * a + b * b); + var miterLen = (a * (x - b1x) + b * (y - b1y)) / d; + var maxLen = styles.miterLimit * styles.lineWidth / 2; + if (miterLen > maxLen) { + var p2 = maxLen / miterLen; + var p1 = 1 - p2; + sink.lineTo( + a2x * p1 + x * p2, + a2y * p1 + y * p2 + ); + sink.lineTo( + b1x * p1 + x * p2, + b1y * p1 + y * p2 + ); + } else { + sink.lineTo(x, y); + } + + sink.lineTo(b1x, b1y); + break; + } + } else { + if (ona) + sink.lineTo(x, y); + + sink.lineTo(b1x, b1y); + } + } + } + + sink.closePath(); + } + function addLineCap(data, x,y, styles) { + var p = data.length; + + var x1 = data[p - 4]; + var y1 = data[p - 3]; + + var x2 = data[p - 2]; + var y2 = data[p - 1]; + + switch (styles.lineCap) { + case 'round': + var cx = (x2 + x) / 2; + var cy = (y2 + y) / 2; + + var a0 = Math.atan2(y2 - cy, x2 - cx); + var a1 = Math.atan2(y - cy, x - cx); + + var sweep = Math.abs(a1 - a0); + + var r = styles.lineWidth / 2; + + while (sweep > 0) { + a1 = a0 + (sweep > PI_HALF ? PI_HALF : sweep); + + var kappa = (4 / 3) * Math.tan((a1 - a0) / 4) * r; + + var cos0 = Math.cos(a0); + var sin0 = Math.sin(a0); + var cp1x = cx + cos0 * r + -sin0 * kappa; + var cp1y = cy + sin0 * r + cos0 * kappa; + + var cos1 = Math.cos(a1); + var sin1 = Math.sin(a1); + + var x = cx + cos1 * r; + var y = cy + sin1 * r; + + var cp2x = x + sin1 * kappa; + var cp2y = y + -cos1 * kappa; + + data[p] = 4; + data[p + 1] = x2; + data[p + 2] = y2; + data[p + 3] = cp1x; + data[p + 4] = cp1y; + data[p + 5] = cp2x; + data[p + 6] = cp2y; + data[p + 7] = x; + data[p + 8] = y; + p += 9; + + x2 = x; + y2 = y; + + a0 = a1; + sweep -= PI_HALF; + } + break; + case 'square': + var distance = styles.lineWidth / 2; + var dx = x2 - x1; + var dy = y2 - y1; + var d = Math.sqrt(dx * dx + dy * dy); + + var x3 = x2 + dx * distance / d; + var y3 = y2 + dy * distance / d; + + var x4 = x + dx * distance / d; + var y4 = y + dy * distance / d; + + data[p] = 2; + data[p + 1] = x2; + data[p + 2] = y2; + data[p + 3] = x3; + data[p + 4] = y3; + + data[p + 5] = 2; + data[p + 6] = x3; + data[p + 7] = y3; + data[p + 8] = x4; + data[p + 9] = y4; + + x2 = x4; + y2 = y4; + + p += 10; + case 'none': + default: + data[p] = 2; + data[p + 1] = x2; + data[p + 2] = y2; + data[p + 3] = x; + data[p + 4] = y; + break; } } try { - var matrix = new SVGMatrix; - matrixClass = matrix.constructor; + var idTransform = new SVGMatrix; + transformClass = idTransform.constructor; } catch (e) { - var svgNamespace = 'http://www.w3.org/2000/svg'; - var svgElement = document.createElementNS(svgNamespace, 'svg'); + var svgElement = doc.createElementNS(SVG_NAMESPACE, 'svg'); - matrixClass = function SVGMatrix() { + transformClass = function SVGMatrix() { return svgElement.createSVGMatrix(); } - matrixClass.prototype = SVGMatrix.prototype; + transformClass.prototype = SVGMatrix.prototype; + + idTransform = new transformClass; } - if (!('currentTransform' in native2dContext)) { - defineLazyProperty(kanvas2dContextProto, '__ct__', { + if (shimCurrentTransform) { + defineLazyProp(kanvas2dCtxProto, '_ct', { get: function () { - return new matrixClass; + return new transformClass; } }); - defineLazyProperty(kanvas2dContextProto, '__ctm__', { + defineLazyProp(kanvas2dCtxProto, '_ctm', { get: function () { - return [1, 0, 0, 1, 0, 0]; + return new Float32Array([1, 0, 0, 1, 0, 0]); } }); + defineLazyProp(kanvas2dCtxProto, '_stack', { get: Array }); - defineProperty(kanvas2dContextProto, 'currentTransform', { + defineProp(kanvas2dCtxProto, 'currentTransform', { get: function () { - return this.__ct__; + return this._ct; }, set: function(val) { - if (!(val instanceof matrixClass)) + if (!(val instanceof transformClass)) throw new TypeError; - defineProperty(this, '__ct__', { value: val }); + if (this._ct === val) + return; + + this.setTransform(val.a, val.b, val.c, val.d, val.e, val.f); + + defineProp(this, '_ct', { value: val }); } }); - defineLazyProperty(kanvas2dContextProto, '__stack__', { - get: function () { - return []; - } - }); + kanvas2dCtxProto.save = function () { + this._save(); - kanvas2dContextProto.save = function () { - this.__save__(); - this.__stack__.push(this.__ctm__.slice()); + var ctm = this._ctm; + this._stack.push([ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]]); }; - kanvas2dContextProto.restore = function () { - this.__restore__(); + kanvas2dCtxProto.restore = function () { + this._restore(); - if (this.__stack__.length) { - var ct = this.__ct__; - var ctm = this.__ctm__; - var m = this.__stack__.pop(); - - ct.a = ctm[0] = m[0]; - ct.b = ctm[1] = m[1]; - ct.c = ctm[2] = m[2]; - ct.d = ctm[3] = m[3]; - ct.e = ctm[4] = m[4]; - ct.f = ctm[5] = m[5]; + var stack = this._stack; + if (stack.length) { + var m = stack.pop(); + this.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]); } }; - kanvas2dContextProto.scale = function (x, y) { - if (+x != x || +y != y) - return; - - this.__scale__(x, y); - - var ct = this.__ct__; - var ctm = this.__ctm__; - - ct.a = ctm[0] *= x; - ct.b = ctm[1] *= x; - ct.c = ctm[2] *= y; - ct.d = ctm[3] *= y; + kanvas2dCtxProto.scale = function (x, y) { + var ctm = this._ctm; + this.setTransform( + ctm[0] * x, ctm[1] * x, + ctm[2] * y, ctm[3] * y, + ctm[4], ctm[5] + ); }; - kanvas2dContextProto.rotate = function (angle) { - if (+angle != angle) - return; - - this.__rotate__(angle); - - var ct = this.__ct__; - var ctm = this.__ctm__; + kanvas2dCtxProto.rotate = function (angle) { + var ctm = this._ctm; var u = Math.cos(angle); var v = Math.sin(angle); - - ct.a = ctm[0] = ctm[0] * u + ctm[2] * v; - ct.b = ctm[1] = ctm[1] * u + ctm[3] * v; - ct.c = ctm[2] = ctm[0] * -v + ctm[2] * u; - ct.d = ctm[3] = ctm[1] * -v + ctm[3] * u; + this.setTransform( + ctm[0] * u + ctm[2] * v, + ctm[1] * u + ctm[3] * v, + ctm[0] * -v + ctm[2] * u, + ctm[1] * -v + ctm[3] * u, + ctm[4], ctm[5] + ); }; - kanvas2dContextProto.translate = function (x, y) { - if (+x != x || +y != y) - return; - - this.__translate__(x, y); - - var ct = this.__ct__; - var ctm = this.__ctm__; - - ct.e = ctm[4] += ctm[0] * x + ctm[2] * y; - ct.f = ctm[5] += ctm[1] * x + ctm[3] * y; + kanvas2dCtxProto.translate = function (x, y) { + var ctm = this._ctm; + this.setTransform( + ctm[0], ctm[1], + ctm[2], ctm[3], + ctm[0] * x + ctm[2] * y + ctm[4], + ctm[1] * x + ctm[3] * y + ctm[5] + ); }; - kanvas2dContextProto.transform = function (a, b, c, d, e, f) { - if (+a != a || +b != b || +c != c || +d != d || +e != e || +f != f) - return; - - this.__transform__(a, b, c, d, e, f); - - var ct = this.__ct__; - var ctm = this.__ctm__; - var g = ctm[0]; - var h = ctm[1]; - var i = ctm[2]; - var j = ctm[3]; - - ct.a = ctm[0] = g * a + i * b; - ct.b = ctm[1] = h * a + j * b; - ct.c = ctm[2] = g * c + i * d; - ct.d = ctm[3] = h * c + j * d; - ct.e = ctm[4] += g * e + i * f; - ct.f = ctm[5] += h * e + j * f; + kanvas2dCtxProto.transform = function (a, b, c, d, e, f) { + var ctm = this._ctm; + this.setTransform( + ctm[0] * a + ctm[2] * b, + ctm[1] * a + ctm[3] * b, + ctm[0] * c + ctm[2] * d, + ctm[1] * c + ctm[3] * d, + ctm[0] * e + ctm[2] * f + ctm[4], + ctm[1] * e + ctm[3] * f + ctm[5] + ); }; - kanvas2dContextProto.setTransform = function (a, b, c, d, e, f) { - if (+a != a || +b != b || +c != c || +d != d || +e != e || +f != f) - return; - - this.__setTransform__(a, b, c, d, e, f); - - var ct = this.__ct__; - var ctm = this.__ctm__; + kanvas2dCtxProto.setTransform = function (a, b, c, d, e, f) { + this._setTransform(a, b, c, d, e, f); + var ct = this._ct; + var ctm = this._ctm; ct.a = ctm[0] = a; ct.b = ctm[1] = b; ct.c = ctm[2] = c; @@ -243,292 +936,1005 @@ ct.e = ctm[4] = e; ct.f = ctm[5] = f; }; - kanvas2dContextProto.resetTransform = function (a, b, c, d, e, f) { + + shimResetTransform = true; + } + + if (shimResetTransform) { + kanvas2dCtxProto.resetTransform = function () { this.setTransform(1, 0, 0, 1, 0, 0); }; } - if (!('ellipse' in native2dContext)) { - kanvas2dContextProto.ellipse = function ( - x, y, rx, ry, rotation, angle0, angle1, acw - ) { - if (+x != x || +y != y || - +rx != rx || +ry != ry || - +angle0 != angle0 || +angle1 != angle1) - return; - + if (shimEllipticalArcTo) { + kanvas2dCtxProto.arcTo = function (x1,y1, x2,y2, rx,ry, rotation) { if (rx < 0 || ry < 0) - throw new IndexSizeError; + throw new RangeError; - var u = Math.cos(rotation) - var v = Math.sin(rotation); + var x0 = x1; + var y0 = y1; + + var m11 = 1; + var m12 = 0; + var m21 = 0; + var m22 = 1; + var tx = 0; + var ty = 0; + + var ops = this._ops; + var p = ops.length; + while (p) { + switch (ops[p - 1]) { + case PATH_OP_CLOSE: + p = ops[p - 2]; + if (p) { + x0 = ops[p]; + y0 = ops[p + 1]; + } + break; + case PATH_OP_RECT: + x0 = ops[p - 5]; + y0 = ops[p - 4]; + break; + case PATH_OP_ARC: + var r = ops[p - 5]; + var a = ops[p - 3]; + x0 = ops[p - 7] + Math.cos(a) * r; + y0 = ops[p - 6] + Math.sin(a) * r; + break; + case PATH_OP_ELLIPSE: + var sx = ops[p - 7]; + var sy = ops[p - 6]; + var rot = ops[p - 5]; + var a = ops[p - 3]; + var u = Math.cos(rot) + var v = Math.sin(rot); + var x = Math.cos(a); + var y = Math.sin(a); + x0 = x * u * sx + y * v * sy + ops[p - 9]; + y0 = x * -v * sx + y * u * sy + ops[p - 8]; + break; + case PATH_OP_TRANSFORM: + var a = ops[p - 7]; + var b = ops[p - 6]; + var c = ops[p - 5]; + var d = ops[p - 4]; + var e = ops[p - 3]; + var f = ops[p - 2]; + p -= 8; + continue; + default: + x0 = ops[p - 3]; + y0 = ops[p - 2]; + } + break; + } + + if (x1 === x0 && y1 === y0) { + this.moveTo(x1, y1); + return; + } + + var dir = (x2 - x1) * (y0 - y1) + (y2 - y1) * (x1 - x0); + + if (x1 === x0 && y1 === y0 || + x1 === x2 && y1 === y2 || + !rx || !ry || + !dir) { + this.lineTo(x1, y1); + return; + } + + if (rx !== ry) { + var scale = ry / rx; + m22 = Math.cos(-rotation); + m12 = Math.sin(-rotation); + m11 = m22 / scale; + m21 = -m12 / scale; + + var ox1 = x0; + x0 = (ox1 * m22 - y0 * m12) * scale; + y0 = ox1 * m12 + y0 * m22; + + var ox2 = x1; + x1 = (ox2 * m22 - y1 * m12) * scale; + y1 = ox2 * m12 + y1 * m22; + + var ox3 = x2; + x2 = (ox3 * m22 - y2 * m12) * scale; + y2 = ox3 * m12 + y2 * m22; + } + + var pa = (x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1); + var pb = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); + var pc = (x0 - x2) * (x0 - x2) + (y0 - y2) * (y0 - y2); + + var cosx = (pa + pb - pc) / (2 * Math.sqrt(pa * pb)); + var sinx = Math.sqrt(1 - cosx * cosx); + var d = ry / ((1 - cosx) / sinx); + + var sqa = Math.sqrt(pa); + var anx = (x1 - x0) / sqa; + var any = (y1 - y0) / sqa; + + var sqb = Math.sqrt(pb); + var bnx = (x1 - x2) / sqb; + var bny = (y1 - y2) / sqb; + + var x3 = x1 - anx * d; + var y3 = y1 - any * d; + + var x4 = x1 - bnx * d; + var y4 = y1 - bny * d; + + var ccw = dir < 0; + + var cx = x3 + any * ry * (ccw ? 1 : -1); + var cy = y3 - anx * ry * (ccw ? 1 : -1); + + var a0 = Math.atan2(y3 - cy, x3 - cx); + var a1 = Math.atan2(y4 - cy, x4 - cx); this.save(); - this.transform(u * rx, v * ry, -v * rx, u * ry, x, y); - this.arc(0, 0, 1, angle0, angle1, acw); + this.transform(m11, m12, m21, m22, 0, 0); + this.lineTo(x3, y3); + this.arc(cx,cy, ry, a0,a1, ccw); this.restore(); }; } - if (typeof Path === 'undefined') { + if (shimEllipse) { + kanvas2dCtxProto.ellipse = function (cx,cy, rx,ry, rotation, a0,a1, ccw) { + if (rx < 0 || ry < 0) + throw new RangeError; + + if (rx === ry) { + this.arc(cx,cy, rx, a0,a1, ccw); + return; + } + + var u = Math.cos(rotation); + var v = Math.sin(rotation); + this.save(); + this.transform(u * rx, v * rx, -v * ry, u * ry, cx, cy); + this.arc(0,0, 1, a0,a1, ccw); + this.restore(); + }; + } + + if (shimPath) { pathClass = function Path(d) { if (!(this instanceof Path)) return new Path(d); + + var obj = this; + + if (nativePathClass) { + obj = new nativePathClass; + mixinProps(obj, kanvasPathProto); + } + + if (arguments.length) { + if (d instanceof Path) + obj.addPath(d); + else + parseSvgDataStr(obj, d); + } + + return obj; }; + pathClass.prototype = nativePathProto || kanvasPathProto; - var pathProto = Object.create(null); - - defineLazyProperty(pathProto, '__cmds__', { + defineLazyProp(kanvasPathProto, '_state', { get: function () { - return []; - } - }); - defineLazyProperty(pathProto, '__data__', { - get: function () { - return []; + return new Float32Array([0xffff, 0xffff, -0xffff, -0xffff, 0, 0]); } }); + defineLazyProp(kanvasPathProto, '_segs', { get: Array }); - pathProto.closePath = function () { - this.__cmds__.push(PATH_CMD_CLOSE); + kanvasPathProto._lineSeg = function (x0,y0, x,y, close) { + var state = this._state; + + if (x0 < state[0]) + state[0] = x0; + if (y0 < state[1]) + state[1] = y0; + if (x0 > state[2]) + state[2] = x0; + if (y0 > state[3]) + state[3] = y0; + + if (x < state[0]) + state[0] = x; + if (y < state[1]) + state[1] = y; + if (x > state[2]) + state[2] = x; + if (y > state[3]) + state[3] = y; + + var segs = this._segs; + var p = segs.length; + segs[p] = 2; + segs[p + 1] = x0; + segs[p + 2] = y0; + segs[p + 3] = x; + segs[p + 4] = y; + + if (close) + segs[p + 5] = 0; + + state[4] = x; + state[5] = y; }; - pathProto.moveTo = function (x, y) { - if (+x != x || +y != y) - return; + kanvasPathProto._curveSeg = function (x0,y0, cp1x,cp1y, cp2x,cp2y, x,y) { + var state = this._state; + var segs = this._segs; + var p = segs.length; - this.__cmds__.push(PATH_CMD_MOVE); - this.__data__.push(x, y); - }; - pathProto.lineTo = function (x, y) { - if (+x != x || +y != y) - return; + if (x0 < state[0]) + state[0] = x0; + if (y0 < state[1]) + state[1] = y0; + if (x0 > state[2]) + state[2] = x0; + if (y0 > state[3]) + state[3] = y0; - this.__cmds__.push(PATH_CMD_LINE); - this.__data__.push(x, y); - }; - pathProto.quadraticCurveTo = function (cpx, cpy, x, y) { - if (+cpx != cpx || +cpy != cpy || +x != x || +y != y) - return; + var roots = getDerivativeRoots(x0,y0, cp1x,cp1y, cp2x,cp2y, x,y); - var p = this.__data__.length - 2; - var ox = this.__data__[p]; - var oy = this.__data__[p + 1]; + for (var i = 0; i <= roots.length; i++) { + var t = roots[i] || 1; - var cp1x = ox + 2 / 3 * (cpx - ox); - var cp1y = oy + 2 / 3 * (cpy - oy); - var cp2x = cp1x + (x - ox) / 3; - var cp2y = cp1y + (y - oy) / 3; + if (t > 0 && t <= 1) { + segs[p] = 4; + segs[p + 1] = x0; + segs[p + 2] = y0; - this.__cmds__.push(PATH_CMD_CURVE); - this.__data__.push(cp1x, cp1y, cp2x, cp2y, x, y); - }; - pathProto.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { - if (+cp1x != cp1x || +cp1y != cp1y || - +cp2x != cp2x || +cp2y != cp2y || - +x != x || +y != y) - return; + var mt = 1 - t; - this.__cmds__.push(PATH_CMD_CURVE); - this.__data__.push(cp1x, cp1y, cp2x, cp2y, x, y); - }; - pathProto.arcTo = function (x1, y1, x2, y2, rx, ry, rotation) { - if (arguments.length > 5 && rx !== ry) - throw Error('Elliptical arcTo not supported'); + var mx1 = mt * x0 + t * cp1x; + var my1 = mt * y0 + t * cp1y; - if (+x1 != x1 || +y1 != y1 || +x2 != x2 || +y2 != y2 || +rx != rx) - return; + var mx2 = mt * cp1x + t * cp2x; + var my2 = mt * cp1y + t * cp2y; - if (rx < 0) { - throw new IndexSizeError; - return; - } + cp2x = mt * cp2x + t * x + cp2y = mt * cp2y + t * y; - var p = this.__data__.length - 2; - var ox = this.__data__[p]; - var oy = this.__data__[p + 1]; + var pcx = mt * mx1 + t * mx2; + var pcy = mt * my1 + t * my2; - var dir = (x2 - x1) * (oy - y1) + (y2 - y1) * (x1 - ox); + cp1x = mt * mx2 + t * cp2x; + cp1y = mt * my2 + t * cp2y; - if (!dir || - (ox === x1 && oy === y1) || - (x1 === x2 && y1 === y2) || - rx === 0) { - this.__cmds__.push(PATH_CMD_LINE); - this.__data__.push(x1, y1); - return; - } + x0 = mt * pcx + t * cp1x; + y0 = mt * pcy + t * cp1y; - var aa = (ox - x1) * (ox - x1) + (oy - y1) * (oy - y1); - var bb = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); - var cc = (ox - x2) * (ox - x2) + (oy - y2) * (oy - y2); - var cosx = (aa + bb - cc) / (2 * Math.sqrt(aa * bb)); + if (x0 < state[0]) + state[0] = x0; + if (y0 < state[1]) + state[1] = y0; + if (x0 > state[2]) + state[2] = x0; + if (y0 > state[3]) + state[3] = y0; - var sinx = Math.sqrt(1 - cosx * cosx); - var d = rx / ((1 - cosx) / sinx); + segs[p + 3] = mx1; + segs[p + 4] = my1; + segs[p + 5] = pcx; + segs[p + 6] = pcy; + segs[p + 7] = x0; + segs[p + 8] = y0; - var anx = (x1 - ox) / Math.sqrt(aa); - var any = (y1 - oy) / Math.sqrt(aa); - var bnx = (x1 - x2) / Math.sqrt(bb); - var bny = (y1 - y2) / Math.sqrt(bb); - var x3 = x1 - anx * d; - var y3 = y1 - any * d; - var x4 = x1 - bnx * d; - var y4 = y1 - bny * d; - var acw = dir < 0; - var cx = x3 + any * rx * (acw ? 1 : -1); - var cy = y3 - anx * rx * (acw ? 1 : -1); - var angle0 = Math.atan2(y3 - cy, x3 - cx); - var angle1 = Math.atan2(y4 - cy, x4 - cx); - - this.__cmds__.push(PATH_CMD_LINE); - this.__data__.push(x3, y3); - - arcToBezier(this, cx, cy, rx, angle0, angle1, acw); - }; - pathProto.rect = function (x, y, w, h) { - if (+x != x || +y != y || +w != w || +h != h) - return; - - this.__cmds__.push( - PATH_CMD_MOVE, - PATH_CMD_LINE, - PATH_CMD_LINE, - PATH_CMD_LINE, - PATH_CMD_CLOSE - ); - this.__data__.push(x, y, x + w, y, x + w, y + h, x, y + h); - }; - pathProto.arc = function (x, y, radius, angle0, angle1, acw) { - if (+x != x || +y != y || - +radius != radius || - +angle0 != angle0 || +angle1 != angle1) - return; - - if (radius < 0) { - throw new IndexSizeError; - return; - } - - arcToBezier(this, x, y, radius, angle0, angle1, acw); - }; - pathProto.ellipse = function (x, y, rx, ry, rotation, angle0, angle1, acw) { - if (+x != x || +y != y || - +rx != rx || +ry != ry || - +angle0 != angle0 || +angle1 != angle1) - return; - - if (rx < 0 || ry < 0) - throw new IndexSizeError; - - var u = Math.cos(rotation) - var v = Math.sin(rotation); - //var matrix = [u * rx, v * ry, -v * rx, u * ry, x, y]; - - arcToBezier(this, 0, 0, 1, angle0, angle1, acw); - }; - - pathClass.prototype = pathProto; - - defineProperty(kanvas2dContextProto, '__draw__', { - value: function (sink) { - var cmds = sink.__cmds__; - var data = sink.__data__; - for (var i = 0, j = 0; i < cmds.length; i++) { - var cmd = cmds[i]; - switch (cmd) { - case PATH_CMD_CLOSE: - this.closePath(); - break; - case PATH_CMD_MOVE: - this.moveTo(data[j++], data[j++]); - break; - case PATH_CMD_LINE: - this.lineTo(data[j++], data[j++]); - break; - case PATH_CMD_CURVE: - this.bezierCurveTo( - data[j++], data[j++], - data[j++], data[j++], - data[j++], data[j++] - ); - break; - } + p += 9; } } + + state[4] = x; + state[5] = y; + }; + + kanvasPathProto.closePath = function () { + if (this._sp) { + this._lineSeg( + this._state[4], this._state[5], + this._ops[this._sp], this._ops[this._sp + 1], + true + ); + } + }; + kanvasPathProto.moveTo = function (x, y) { + this._state[4] = x; + this._state[5] = y; + }; + kanvasPathProto.lineTo = function (x, y) { + if (this._sp) { + this._lineSeg(this._state[4],this._state[5], x,y); + } else { + this._state[4] = x; + this._state[5] = y; + } + }; + kanvasPathProto.quadraticCurveTo = function (cpx,cpy, x,y) { + var x0, y0; + if (this._sp) { + x0 = this._state[4]; + y0 = this._state[5]; + } else { + x0 = cpx; + y0 = cpy; + } + + var cp1x = x0 + 2 / 3 * (cpx - x0); + var cp1y = y0 + 2 / 3 * (cpy - y0); + + var cp2x = cp1x + (x - x0) / 3; + var cp2y = cp1y + (y - y0) / 3; + + this._curveSeg(x0,y0, cp1x,cp1y, cp2x,cp2y, x,y); + }; + kanvasPathProto.bezierCurveTo = function (cp1x,cp1y, cp2x,cp2y, x,y) { + var x0, y0; + if (this._sp) { + x0 = this._state[4]; + y0 = this._state[5]; + } else { + x0 = cp1x; + y0 = cp1y; + } + + this._curveSeg(x0,y0, cp1x,cp1y, cp2x,cp2y, x,y); + }; + kanvasPathProto.arcTo = function (x1,y1, x2,y2, rx,ry, rotation) { + if (arguments.length < 7) { + ry = rx; + rotation = 0; + } + + if (rx < 0 || ry < 0) + throw new RangeError; + + var x0, y0; + if (this._sp) { + x0 = this._state[4]; + y0 = this._state[5]; + } else { + x0 = x1; + y0 = y1; + } + + if (x1 === x0 && y1 === y0) + return; + + var dir = (x2 - x1) * (y0 - y1) + (y2 - y1) * (x1 - x0); + + if (x1 === x0 && y1 === y0 || x1 === x2 && y1 === y2 || !rx || !ry || !dir) { + this.lineTo(x1, y1); + return; + } + + if (rx !== ry) { + var scale = ry / rx; + var u = Math.cos(-rotation); + var v = Math.sin(-rotation); + + var ox1 = x0; + x0 = (ox1 * u - y0 * v) * scale; + y0 = ox1 * v + y0 * u; + + var ox2 = x1; + x1 = (ox2 * u - y1 * v) * scale; + y1 = ox2 * v + y1 * u; + + var ox3 = x2; + x2 = (ox3 * u - y2 * v) * scale; + y2 = ox3 * v + y2 * u; + } else { + var scale = 1; + var u = 1; + var v = 0; + } + + var pa = (x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1); + var pb = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); + var pc = (x0 - x2) * (x0 - x2) + (y0 - y2) * (y0 - y2); + + var cosx = (pa + pb - pc) / (2 * Math.sqrt(pa * pb)); + var sinx = Math.sqrt(1 - cosx * cosx); + var d = ry / ((1 - cosx) / sinx); + + var anx = (x1 - x0) / Math.sqrt(pa); + var any = (y1 - y0) / Math.sqrt(pa); + var bnx = (x1 - x2) / Math.sqrt(pb); + var bny = (y1 - y2) / Math.sqrt(pb); + + var x3 = x1 - anx * d; + var y3 = y1 - any * d; + + var x4 = x1 - bnx * d; + var y4 = y1 - bny * d; + + var ccw = dir < 0; + + var cx = x3 + any * ry * (ccw ? 1 : -1); + var cy = y3 - anx * ry * (ccw ? 1 : -1); + + var a0 = Math.atan2(y3 - cy, x3 - cx); + var a1 = Math.atan2(y4 - cy, x4 - cx); + + var m11 = u / scale; + var m21 = -v / scale; + + this._lineSeg( + x0 * m11 + y0 * v, + x0 * m21 + y0 * u, + x3 * m11 + y3 * v, + x3 * m21 + y3 * u + ); + + arcToCurveSegs(this, cx,cy, ry, a0,a1, ccw, m11,v, m21,u, 0,0); + }; + kanvasPathProto.rect = function (x,y, w,h) { + var tr = x + w; + var br = y + h; + this._lineSeg(x,y, tr,y); + this._lineSeg(tr,y, tr,br); + this._lineSeg(tr,br, x,br); + this._lineSeg(x,br, x,y, true); + }; + kanvasPathProto.arc = function (cx,cy, r, a0,a1, ccw) { + arcToCurveSegs(this, cx,cy, r, a0,a1, ccw, 1,0, 0,1, 0,0); + + if (a1 - a0 === PI_DOUBLE) + this._segs.push(0); + }; + kanvasPathProto.ellipse = function (x,y, rx,ry, rotation, a0,a1, ccw) { + var u = Math.cos(rotation) + var v = Math.sin(rotation); + var m11 = u * rx; + var m12 = v * ry; + var m21 = -v * rx; + var m22 = u * ry; + arcToCurveSegs(this, 0,0, 1, a0,a1, ccw, m11,m12, m21,m22, x,y); + }; + + kanvasPathProto.isPointInPath = function (x,y, winding) { + var state = this._state; + + if (x < state[0] || y < state[1] || x > state[2] || y > state[3]) + return false; + + var wn = 0; + + var segs = this._segs; + for (var i = 0; i < segs.length; i++) { + var npoint = segs[i]; + + if (!npoint) + continue; + + var x0 = segs[i + 1]; + var y0 = segs[i + 2]; + + if (npoint === 2) { + var x1 = segs[i + 3]; + var y1 = segs[i + 4]; + + if (y0 <= y) { + if (y1 > y) { + if ((x1 - x0) * (y - y0) - (x - x0) * (y1 - y0) > 0) + ++wn; + } + } else { + if (y1 <= y) { + if ((x1 - x0) * (y - y0) - (x - x0) * (y1 - y0) < 0) + --wn; + } + } + + i += 4; + } else { + //for (var j = 1; j < npoint; j += 3, i += 6) { + var cp1x = segs[i + 3]; + var cp1y = segs[i + 4]; + + var cp2x = segs[i + 5]; + var cp2y = segs[i + 6]; + + var x1 = segs[i + 7]; + var y1 = segs[i + 8]; + + if (y0 <= y) { + if (y1 > y) { + //if ((x1 - x0) * (y - y0) - (x - x0) * (y1 - y0) > 0) { + // ++wn; + //} else if ((cp2x - cp1x) * (y - cp1y) - (x - cp1x) * (cp2y - cp1y) > 0) { + var t = findCubicRoot(y0, cp1y, cp2y, y1, y); + if (t > -1 && computeCubicBaseValue(t, x0,cp1x,cp1x,x1) > x) + ++wn; + //} + } + } else { + if (y1 <= y) { + //if ((x1 - x0) * (y - y0) - (x - x0) * (y1 - y0) < 0) { + // --wn; + //} else if ((cp2x - cp1x) * (y - cp1y) - (x - cp1x) * (cp2y - cp1y) < 0) { + var t = findCubicRoot(y0, cp1y, cp2y, y1, y); + if (t > -1 && computeCubicBaseValue(t, x0,cp1x,cp1x,x1) > x) + --wn; + //} + } + } + + x0 = x1; + y0 = y1; + //} + + i += 8; + } + } + + return !!wn; + }; + + if (shimBounds) { + kanvasPathProto.getBounds = function () { + var state = this._state; + return { + x: state[0], + y: state[1], + width: state[2] - state[0], + height: state[3] - state[1] + }; + }; + } + + if (shimStrokePath) { + kanvasPathProto.strokePath = function (styles, transformation) { + if (!styles || !styles.lineWidth) + return; + + if (arguments.length > 2) { + if (!(transform instanceof transformClass)) + throw new TypeError; + } + + var path = new pathClass; + + var distance = styles.lineWidth / 2; + + var segs = this._segs; + + var outer = []; + var inner = []; + var p = 0; + + var x, y; + + for (var i = 0; i < segs.length;) { + var npoint = segs[i]; + + if (!npoint) { + if (outer.length) { + buildOutline(path, outer, styles); + + inner.reverse(); + + buildOutline(path, inner, styles); + + outer = []; + inner = []; + p = 0; + } + + i++; + continue; + } + + var x0 = segs[i + 1]; + var y0 = segs[i + 2]; + + if (x !== x0 || y !== y0) { + if (outer.length) { + inner.reverse(); + + addLineCap(inner, outer[1], outer[2], styles); + addLineCap(outer, inner[1], inner[2], styles); + + [].push.apply(outer, inner); + + buildOutline(path, outer, styles); + + outer = []; + inner = []; + p = 0; + } + } + + if (npoint === 2) { + x = segs[i + 3]; + y = segs[i + 4]; + + var dx = x - x0; + var dy = y - y0; + + if (dx || dy) { + var k = distance / Math.sqrt(dx * dx + dy * dy); + dx *= k; + dy *= k; + + outer[p] = 2; + outer[p + 1] = x0 + dy; + outer[p + 2] = y0 - dx; + outer[p + 3] = x + dy; + outer[p + 4] = y - dx; + + inner[p] = y0 + dx; + inner[p + 1] = x0 - dy; + inner[p + 2] = y + dx; + inner[p + 3] = x - dy; + inner[p + 4] = 2; + } + + p += 5; + i += 5; + } else { + //for (var j = 1; j < npoint; j += 3, i += 6, p += 9) { + var cp1x = segs[i + 3]; + var cp1y = segs[i + 4]; + + var cp2x = segs[i + 5]; + var cp2y = segs[i + 6]; + + x = segs[i + 7]; + y = segs[i + 8]; + + var sc1 = scaleCurve(x0,y0, cp1x,cp1y, cp2x,cp2y, x,y, distance); + var sc2 = scaleCurve(x0,y0, cp1x,cp1y, cp2x,cp2y, x,y, -distance); + + if (sc1 && sc2) { + outer[p] = 4; + outer[p + 1] = sc1[0]; + outer[p + 2] = sc1[1]; + + outer[p + 3] = sc1[2]; + outer[p + 4] = sc1[3]; + outer[p + 5] = sc1[4]; + outer[p + 6] = sc1[5]; + outer[p + 7] = sc1[6]; + outer[p + 8] = sc1[7]; + + inner[p] = sc2[1]; + inner[p + 1] = sc2[0]; + inner[p + 2] = sc2[3]; + inner[p + 3] = sc2[2]; + inner[p + 4] = sc2[5]; + inner[p + 5] = sc2[4]; + inner[p + 6] = sc2[7]; + inner[p + 7] = sc2[6]; + inner[p + 8] = 4; + + //x0 = x; + //y0 = y; + } + //} + + p += 9; + i += 9; + } + } + + if (outer.length) { + inner.reverse(); + + addLineCap(inner, outer[1], outer[2], styles); + addLineCap(outer, inner[1], inner[2], styles); + + [].push.apply(outer, inner); + + buildOutline(path, outer, styles); + } + + return path; + }; + } + + defineProp(kanvas2dCtxProto, 'currentPath', { + get: function () { + var path = new pathClass; + path._copyFrom(this); + return path; + }, + set: function (val) { + if (!(val instanceof pathClass)) + throw new TypeError; + + this.beginPath(); + this._copyFrom(val); + } }); - kanvas2dContextProto.fill = function (path) { - if (path instanceof pathClass) { - this.beginPath(); - this.__draw__(path); - } + kanvas2dCtxProto.beginPath = function () { + this._beginPath(); - this.__fill__(); + this._ops = this._ops.slice(this._tp - 1, this._tp + 1); + this._sp = this._tp = 0; }; - kanvas2dContextProto.stroke = function (path) { - if (path instanceof pathClass) { - this.beginPath(); - this.__draw__(path); - } + kanvas2dCtxProto.addPath = function (path) { + if(!(path instanceof pathClass)) + throw new TypeError; - this.__stroke__(); + this.closePath(); + this._addFrom(path); }; - kanvas2dContextProto.clip = function (path) { - if (path instanceof pathClass) { - this.beginPath(); - this.__draw__(path); - } - - this.__clip__(); - }; - kanvas2dContextProto.isPointInPath = function (x, y, path) { - if (path instanceof pathClass) { - this.beginPath(); - this.__draw__(path); - } - - this.__isPointInPath__(x, y); - }; - } else { - pathClass = Path; } - defineProperty(nativeCanvasProto, '__pctx__', { value: null }); + if (shimEllipticalArcTo || shimPath) { + var recorderProto = Object.create(null); + defineLazyProp(recorderProto, '_ops', { get: Array, writable: true }); + + defineProp(recorderProto, '_sp', { value: 0, writable: true }); + + var pathMethods = [ + [PATH_OP_CLOSE, 'closePath', '', true], + [PATH_OP_MOVE, 'moveTo', 'x,y', true], + [PATH_OP_LINE, 'lineTo', 'x,y'], + [PATH_OP_CURVE, 'quadraticCurveTo', 'cpx,cpy,x,y'], + [PATH_OP_BEZIER, 'bezierCurveTo', 'cp1x,cp1y,cp2x,cp2y,x,y'], + [PATH_OP_ARCTO, 'arcTo', 'x1,y1,x2,y2,rx,ry,rotation'], + [PATH_OP_RECT, 'rect', 'x,y,w,h'], + [PATH_OP_ARC, 'arc', 'cx,cy,r,a0,a1,ccw'], + [PATH_OP_ELLIPSE, 'ellipse', 'cx,cy,rx,ry,rotation,a0,a1,ccw'] + ]; + + var opArgPartial = 'o[p+$($2+1;)]'; + + pathMethods.forEach(function (tuple) { + var code = tuple[0]; + var name = tuple[1]; + var params = tuple[2]; + var move = tuple[3]; + + var tmpl = tmplf( + 'function($2){' + + 'this._$1($2);' + + + '$$1;' + + + 'var o=this._ops,' + + 'p=o.length;' + + + 'o[p]=$3,' + + '$(_($4,$6+"=+$1");),' + + 'o[p+$(_($4).length+1;)]=$3;' + + + '$($2&&!$5?"if(!this._sp)":"";)' + + 'this._sp=p+1' + + '}', + + name, params, + code, + params || 'this._sp', + move, + + opArgPartial + ); + + var fn = evalf(tmpl, ''); + + kanvas2dCtxProto[name] = fn; + + if (nativePathClass && /{([\s\S]*)}/.test(kanvasPathProto[name])) + kanvasPathProto[name] = evalf(tmpl, RegExp.$1); + else + safeReplaceProp(kanvasPathProto, name, { value: fn }); + }); + + defineProp(recorderProto, '_tp', { value: 0, writable: true }); + + var transformProps = 'a,b,c,d,e,f'; + + var transformTmpl = tmplf( + 'function($$2){'+ + 'this._$$1($$2);'+ + + 'var t=this.currentTransform,' + + 'o=this._ops,' + + 'p=o.length' + + '$3;' + + + 'if(o[p-1]===$1)' + + '$(_($2,$4+$6););' + + 'else ' + + 'o[p]=$1,' + + '$(_($2,$5+$6);),' + + 'o[p+7]=$1,' + + + 'this._tp=p+1' + + '}', + + PATH_OP_TRANSFORM, + transformProps, + + shimCurrentTransform ? ',m=this._ctm' : '', + 'o[p-$(7-$2;)]', + opArgPartial, + '=$1=t.$1' + (shimCurrentTransform ? '=m[$2]=$1' : '') + ); + + if (!shimCurrentTransform) { + [ + ['scale', 'x,y'], + ['rotate', 'angle'], + ['translate', 'x,y'], + ['transform', transformProps], + ['resetTransform', ''] + ].forEach(function (tuple) { + var name = tuple[0]; + var params = tuple[1]; + if (!(name in kanvas2dCtxProto)) + kanvas2dCtxProto[name] = evalf(transformTmpl, name, params); + }); + } + + kanvas2dCtxProto.setTransform = + evalf(transformTmpl, 'setTransform', transformProps); + + var concatPathTmpl = tmplf( + 'function(path){' + + 'this.beginPath();' + + + 'var o=path._ops,' + + 'p=0;' + + + 'while(p line1.x1 && x > line1.x2) || - (y < line1.y1 && y < line1.y2) || (y > line1.y1 && y > line1.y2)); - var onLine2 = !( - (x < line2.x1 && x < line2.x2) || (x > line2.x1 && x > line2.x2) || - (y < line2.y1 && y < line2.y2) || (y > line2.y1 && y > line2.y2)); - if (!onLine1 && !onLine2) { - switch (type) { - default: - case 'bevel': - cmds.push({type: 'lineTo', args: [line1.x2, line1.y2]}); - cmds.push({type: 'lineTo', args: [line2.x1, line2.y1]}); - break; - case 'round': - cmds.push({type: 'lineTo', args: [line1.x2, line1.y2]}); - cmds.push({type: 'quadraticCurveTo', args: [x, y, line2.x1, line2.y1]}); - break; - case 'miter': - cmds.push({type: 'lineTo', args: [line1.x2, line1.y2]}); - var a = -(line1.y2 - line2.y1), b = line1.x2 - line2.x1; - var d = Math.sqrt(a * a + b * b); - var miterLength = (a * (x - line2.x1) + b * (y - line2.y1)) / d; - var maxAllowedLength = options.miterLimit * options.strokeWidth / 2; - if (miterLength > maxAllowedLength) { - var p2 = maxAllowedLength / miterLength, p1 = 1 - p2; - cmds.push({type: 'lineTo', args: [line1.x2 * p1 + x * p2, line1.y2 * p1 + y * p2]}); - cmds.push({type: 'lineTo', args: [line2.x1 * p1 + x * p2, line2.y1 * p1 + y * p2]}); - } else { - cmds.push({type: 'lineTo', args: [x, y]}); - } - cmds.push({type: 'lineTo', args: [line2.x1, line2.y1]}); - break; - } - } else if (!onLine1 || !onLine2) { - cmds.push({type: 'lineTo', args: onLine1 ? [x, y] : [line1.x2, line1.y2]}); - cmds.push({type: 'lineTo', args: onLine2 ? [x, y] : [line2.x1, line2.y1]}); - } else { - cmds.push({type: 'lineTo', args: [x, y]}); - } - } - function buildPath(lines) { - var moveCmd = {type: 'moveTo', args: null}; - var cmds = [moveCmd]; - var joinType = options.join; - for (var j = 0; j < lines.length; j++) { - var type = lines[j].type; - switch (type) { - default: - joinLines(cmds, lines[j], lines[(j + 1) % lines.length], joinType); - break; - case 3: // simple line - cmds.push({type: 'lineTo', args: [lines[j].x2, lines[j].y2]}); - break; - case 4: // curve segment connector - joinLines(cmds, lines[j], lines[(j + 1) % lines.length], 'bevel'); - break; - } - } - moveCmd.args = cmds[cmds.length - 1].args.slice(-2); - cmds.push({type: 'closePath'}); - for (var i = 0; i < cmds.length; i++) { - output[cmds[i].type].apply(output, cmds[i].args); - } - } - var i = 0; - var segments = tracker.segments; - var points = tracker.points; - var start = segments[0]; - do { - ++i; - var end = i < segments.length ? segments[i] : points.length; - if (points[start].type !== 0) { - throw 'invalid points structure'; - } - if (start + 1 >= end) { - // only moveTo operation - end = start; - continue; - } - var pathClosed = points[end - 1].type === 2; - var lastX = points[start].x; - var lastY = points[start].y; - var lastType = 0; - start++; - // building paths - var forward = [], backward = []; - var strokeHalfWidth = options.strokeWidth / 2; - for (var j = start; j < end; j++) { - var x = points[j].x, y = points[j].y, type = points[j].type; - var dx = x - lastX; - var dy = y - lastY; - if (dx == 0 && dy == 0) continue; - var k = strokeHalfWidth / Math.sqrt(dx * dx + dy * dy); - dx *= k; dy *= k; - forward.push({ - x1: lastX + dy, y1: lastY - dx, - x2: x + dy, y2: y - dx, - type: type - }); - backward.push({ - x1: x - dy, y1: y + dx, - x2: lastX - dy, y2: lastY + dx, - type: lastType - }); - lastX = x; lastY = y; lastType = type; - } - if (forward.length === 0) { - // no segments are created, skipping the stroke - start = end; - continue; - } - - backward.reverse(); - if (!pathClosed) { - buildCap(forward, options.endCap, forward[forward.length - 1], backward[0]); - buildCap(backward, options.startCap, backward[backward.length - 1], forward[0]); - forward = forward.concat(backward); - buildPath(forward); - } else { - buildPath(forward); - buildPath(backward); - } - start = end; - } while (i < segments.length); - } - - function pushCurveApprox(points, x1, y1, x2, y2) { - var x0 = points[points.length - 1].x; - var y0 = points[points.length - 1].y; - for (var i = 0; i < CURVE_APPROX_POINTS; i++) { - var p2 = (i + 1) / CURVE_APPROX_POINTS, p1 = 1 - p2; - var x01 = x0 * p1 + x1 * p2, y01 = y0 * p1 + y1 * p2; - var x12 = x1 * p1 + x2 * p2, y12 = y1 * p1 + y2 * p2; - var x = x01 * p1 + x12 * p2, y = y01 * p1 + y12 * p2; - points.push({x: x, y: y, type: 4}); - } - points[points.length - 1].type = 1; - } - function pushBezierCurveApprox(points, x1, y1, x2, y2, x3, y3) { - var x0 = points[points.length - 1].x; - var y0 = points[points.length - 1].y; - for (var i = 0; i < CURVE_APPROX_POINTS; i++) { - var p2 = (i + 1) / CURVE_APPROX_POINTS, p1 = 1 - p2; - var x01 = x0 * p1 + x1 * p2, y01 = y0 * p1 + y1 * p2; - var x12 = x1 * p1 + x2 * p2, y12 = y1 * p1 + y2 * p2; - var x23 = x2 * p1 + x3 * p2, y23 = y2 * p1 + y3 * p2; - var x012 = x01 * p1 + x12 * p2, y012 = y01 * p1 + y12 * p2; - var x123 = x12 * p1 + x23 * p2, y123 = y12 * p1 + y23 * p2; - var x = x012 * p1 + x123 * p2, y = y012 * p1 + y123 * p2; - points.push({x: x, y: y, type: 4}); - } - points[points.length - 1].type = 1; - } - function normalizeAngle(angle) { - while (angle > Math.PI) - angle -= 2 * Math.PI; - while (angle <= -Math.PI) - angle += 2 * Math.PI; - return angle; - } - function pushArcApprox(points, x1, y1, x2, y2, radiusX, radiusY, rotation) { - var x0 = points[points.length - 1].x; - var y0 = points[points.length - 1].y; - var dx01 = x1 - x0, dy01 = y1 - y0, dx12 = x2 - x1, dy12 = y2 - y1; - var winding = dx01 * dy12 - dy01 * dx12; - // if radiusX or radiusY == 0, or points #0, #1, and #2 are on one line, - // just draw simple line to point #1 - if (radiusX <= 0 || radiusY <= 0 || winding == 0) { - points.push({x: x1, y: y1, type: 1}); - return; - } - var rotationCos = 1, rotationSin = 0; - if (rotation) { - rotationCos = Math.cos(rotation); - rotationSin = Math.sin(rotation); - } - // placing major axis to x - var dx01_ = dx01 * rotationCos + dy01 * rotationSin; - var dy01_ = -dx01 * rotationSin + dy01 * rotationCos; - var dx12_ = dx12 * rotationCos + dy12 * rotationSin; - var dy12_ = -dx12 * rotationSin + dy12 * rotationCos; - var alpha1 = Math.atan2(-dx01_ * radiusY, dy01_ * radiusX); - var alpha2 = Math.atan2(-dx12_ * radiusY, dy12_ * radiusX); - if (winding < 0) { - alpha1 = (alpha1 >= 0 ? -Math.PI : Math.PI) + alpha1; - alpha2 = (alpha2 >= 0 ? -Math.PI : Math.PI) + alpha2; - } - - // start and end offsets of the arc from center - var bx1_ = radiusX * Math.cos(alpha1), by1_ = radiusY * Math.sin(alpha1); - var bx2_ = radiusX * Math.cos(alpha2), by2_ = radiusY * Math.sin(alpha2); - var bx1 = bx1_ * rotationCos - by1_ * rotationSin; - var by1 = bx1_ * rotationSin + by1_ * rotationCos; - var bx2 = bx2_ * rotationCos - by2_ * rotationSin; - var by2 = bx2_ * rotationSin + by2_ * rotationCos; - - // finding center - // (x1 - bx1 - cx) * (y1 - y0) - (y1 - by1 - cy) * (x1 - x0) = 0 - var a1 = y1 - y0, b1 = -(x1 - x0), c1 = (x1 - bx1) * (y1 - y0) - (y1 - by1) * (x1 - x0); - var a2 = y2 - y1, b2 = -(x2 - x1), c2 = (x2 - bx2) * (y2 - y1) - (y2 - by2) * (x2 - x1); - var d = a1 * b2 - b1 * a2; - var cx = (c1 * b2 - b1 * c2) / d; - var cy = (a1 * c2 - c1 * a2) / d; - - points.push({x: bx1 + cx, y: by1 + cy, type: 1}); // line from point #0 - - // building arc segments - var angleDistance = normalizeAngle(alpha2 - alpha1); - var stepsCount = Math.ceil(Math.abs(angleDistance) / Math.PI * CIRCLE_APPROX_POINTS); - var step = angleDistance / stepsCount; - for (var i = 1; i <= stepsCount; i++) { - var alpha = alpha1 + (angleDistance * i / stepsCount); - var x_ = radiusX * Math.cos(alpha), y_ = radiusY * Math.sin(alpha); - var x = x_ * rotationCos - y_ * rotationSin + cx; - var y = x_ * rotationSin + y_ * rotationCos + cy; - points.push({x: x, y: y, type: 4}); - } - points[points.length - 1].type = 1; - } - - function PolygonTrackerNullOutput() {} - PolygonTrackerNullOutput.prototype = { - moveTo: function () {}, - lineTo: function () {}, - quadraticCurveTo: function () {}, - closePath: function () {} - }; - - function PolygonTracker(target, hitCtx) { - this.target = target || new PolygonTrackerNullOutput; - this.segments = [0]; - this.points = [{x: 0, y: 0, type: 0}]; - this.hitCtx = hitCtx; - } - PolygonTracker.prototype = { - get lineWidth() { - return this.target.lineWidth; - }, - set lineWidth(value) { - this.target.lineWidth = value; - }, - get lineCap() { - return this.target.lineCap; - }, - set lineCap(value) { - this.target.lineCap = value; - }, - get lineJoin() { - return this.target.lineJoin; - }, - set lineJoin(value) { - this.target.lineJoin = value; - }, - get miterLimit() { - return this.target.miterLimit; - }, - set miterLimit(value) { - this.target.miterLimit = value; - }, - moveTo: function (x, y) { - var segmentStartIndex = this.segments[this.segments.length - 1]; - if (segmentStartIndex === this.points.length - 1) { - this.points[segmentStartIndex].x = x; - this.points[segmentStartIndex].y = y; - } else { - this.segments.push(this.points.length); - this.points.push({x: x, y: y, type: 0}); - } - this.target.moveTo(x, y); - if (this.hitCtx) - this.hitCtx.moveTo(x, y); - }, - lineTo: function (x, y) { - this.points.push({x: x, y: y, type: 1}); - this.target.lineTo(x, y); - if (this.hitCtx) - this.hitCtx.lineTo(x, y); - }, - closePath: function () { - var segmentStartIndex = this.segments[this.segments.length - 1]; - this.points.push({x: this.points[segmentStartIndex].x, - y: this.points[segmentStartIndex].y, - type: 2}); - - this.target.closePath(); - if (this.hitCtx) - this.hitCtx.closePath(); - - this.segments.push(this.points.length); - this.points.push({x: this.points[segmentStartIndex].x, - y: this.points[segmentStartIndex].y, - type: 0}); - }, - quadraticCurveTo: function (cpx, cpy, x, y) { - pushCurveApprox(this.points, cpx, cpy, x, y); - this.target.quadraticCurveTo(cpx, cpy, x, y); - if (this.hitCtx) - this.hitCtx.quadraticCurveTo(cpx, cpy, x, y); - }, - bezierCurveTo: function (cpx1, cpy1, cpx2, cpy2, x, y) { - pushBezierCurveApprox(this.points, cpx1, cpy1, cpx2, cpy2, x, y); - this.target.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, x, y); - if (this.hitCtx) - this.hitCtx.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, x, y); - }, - arcTo: function (x1, y1, x2, y2, radiusX, radiusY, rotation) { - pushArcApprox(this.points, x1, y1, x2, y2, radiusX, - arguments.length < 6 ? radiusX : radiusY, - rotation); - this.target.arcTo.apply(this.target, arguments); - if (this.hitCtx) - this.hitCtx.arcTo.apply(this.hitCtx, arguments); - }, - rect: function (x, y, w, h) { - var segmentStartIndex = this.segments[this.segments.length - 1]; - if (segmentStartIndex === this.points.length - 1) { - this.points[segmentStartIndex].x = x; - this.points[segmentStartIndex].y = y; - } else { - this.segments.push(this.points.length); - this.points.push({x: x, y: y, type: 0}); - } - this.points.push({x: x + w, y: y, type: 1}); - this.points.push({x: x + w, y: y + h, type: 1}); - this.points.push({x: x, y: y + h, type: 1}); - this.points.push({x: x, y: y, type: 2}); - - this.target.rect(x, y, w, h); - if (this.hitCtx) - this.hitCtx.rect(x, y, w, h); - }, - strokeToPath: function (output, options) { - strokeToPath(this, options || { - strokeWidth: this.lineWidth, - startCap: this.lineCap, - endCap: this.lineCap, - join: this.lineJoin, - miterLimit: this.miterLimit - }, output); - }, - getBounds: function () { - var points = this.points; - if (points.length <= 1) { // HACK only moveTo - return null; - } - var minX, minY, maxX, maxY; - minX = maxX = points[0].x; - minY = maxY = points[0].y; - for (var i = 1; i < points.length; i++) { - var x = points[i].x, y = points[i].y; - if (x < minX) minX = x; - if (y < minY) minY = y; - if (x > maxX) maxX = x; - if (y > maxY) maxY = y; - } - return {minX: minX, minY: minY, maxX: maxX, maxY: maxY}; - } - }; - return PolygonTracker; - })(); - def.__glue__ = { native: { instance: { @@ -826,8 +286,9 @@ var GraphicsDefinition = (function () { copyFrom: def.copyFrom, cubicCurveTo: def.cubicCurveTo, curveTo: def.curveTo, + drawCircle: def.drawCircle, + drawEllipse: def.drawEllipse, drawPath: def.drawPath, - drawPathObject: def.drawPathObject, drawRect: def.drawRect, drawRoundRect: def.drawRoundRect, drawRoundRectComplex: def.drawRoundRectComplex, diff --git a/src/flash/display/Loader.js b/src/flash/display/Loader.js index f038b463e..9720357e9 100644 --- a/src/flash/display/Loader.js +++ b/src/flash/display/Loader.js @@ -745,7 +745,7 @@ var LoaderDefinition = (function () { props.variableName = symbol.variableName; break; case 'shape': - var createGraphicsData = new Function('d,r', 'return ' + symbol.data); + var createGraphicsSubPaths = new Function('c,d,r', 'return ' + symbol.data); className = symbol.morph ? 'flash.display.MorphShape' : 'flash.display.Shape'; props.bbox = symbol.bbox; props.graphicsFactory = function graphicsFactory(ratio) { @@ -754,7 +754,7 @@ var LoaderDefinition = (function () { var graphics = new flash.display.Graphics; graphics._scale = 0.05; - graphics.drawGraphicsData(createGraphicsData(dictionary, ratio)); + graphics._subpaths = createGraphicsSubPaths(graphics, dictionary, ratio); graphicsFactory[ratio] = graphics; diff --git a/src/swf/renderer.js b/src/swf/renderer.js index 5c7ce2d8f..915582226 100644 --- a/src/swf/renderer.js +++ b/src/swf/renderer.js @@ -30,32 +30,32 @@ function renderDisplayObject(child, ctx, transform, cxform, clip) { var subpaths = graphics._subpaths; for (var j = 0, o = subpaths.length; j < o; j++) { - var pathTracker = subpaths[j], path = pathTracker.target; + var path = subpaths[j]; + + ctx.currentPath = path; + if (clip) { - ctx.beginPath(); - ctx.__draw__(path); ctx.closePath(); } else { if (path.fillStyle) { ctx.fillStyle = path.fillStyle; - if (path.fillTransform) { - var m = path.fillTransform; - ctx.beginPath(); - ctx.__draw__(path); + + var m = path.fillStyle.currentTransform; + if (m) { ctx.save(); - ctx.transform(m.a, m.b, m.c, m.d, m.tx, m.ty); + ctx.transform(m.a, m.b, m.c, m.d, m.e, m.f); ctx.fill(); ctx.restore(); } else { - ctx.fill(path); + ctx.fill(); } } if (path.strokeStyle) { ctx.strokeStyle = path.strokeStyle; - var drawingStyles = pathTracker.drawingStyles; + var drawingStyles = path.drawingStyles; for (var prop in drawingStyles) ctx[prop] = drawingStyles[prop]; - ctx.stroke(path); + ctx.stroke(); } } } @@ -476,7 +476,6 @@ function renderStage(stage, ctx, onBeforeFrame, onAfterFrame) { if (renderDummyBalls) { renderDummyBalls(); } else { - visitContainer(stage, new MouseVisitor()); flushPendingScripts(); ctx.beginPath(); visitContainer(stage, new PreVisitor(ctx)); @@ -488,6 +487,8 @@ function renderStage(stage, ctx, onBeforeFrame, onAfterFrame) { onAfterFrame(); } } + } else { + visitContainer(stage, new MouseVisitor()); } requestAnimationFrame(draw); })(); diff --git a/src/swf/shape.js b/src/swf/shape.js index 856549819..0f9c7f0e7 100644 --- a/src/swf/shape.js +++ b/src/swf/shape.js @@ -7,13 +7,6 @@ var GRAPHICS_FILL_RADIAL_GRADIENT = 18; var GRAPHICS_FILL_REPEATING_BITMAP = 64; var GRAPHICS_FILL_SOLID = 0; -var GRAPHICS_PATH_COMMAND_CUBIC_CURVE_TO = 6; -var GRAPHICS_PATH_COMMAND_CURVE_TO = 3; -var GRAPHICS_PATH_COMMAND_LINE_TO = 2; -var GRAPHICS_PATH_COMMAND_MOVE_TO = 1; -var GRAPHICS_PATH_COMMAND_WIDE_LINE_TO = 5; -var GRAPHICS_PATH_COMMAND_WIDE_MOVE_TO = 4; - function morph(start, end) { if (!isNaN(end) && end !== start) return start + '+' + (end - start) + '*r'; @@ -21,44 +14,35 @@ function morph(start, end) { return start; } function morphColor(color, colorMorph) { - return '(' + morph(color.red, colorMorph.red) + ')<<16|' + - '(' + morph(color.green, colorMorph.green) + ')<<8|' + - '(' + morph(color.blue, colorMorph.blue) + ')'; + return '"rgba(" + (' + + morph(color.red, colorMorph.red) + ') + "," + (' + + morph(color.green, colorMorph.green) + ') + "," + (' + + morph(color.blue, colorMorph.blue) + ') + "," + (' + + morph(color.alpha / 255, colorMorph.alpha / 255) + + ') + ")"'; } -function toColorProperties(color, colorMorph) { - if (colorMorph) { - return 'color:' + morphColor(color, colorMorph) + ',' + - 'alpha:' + morph(color.alpha / 255, colorMorph.alpha / 255); - } +function toMatrixInstance(matrix, matrixMorph, scale) { + if (scale === undefined) + scale = 20; - if (color) { - return 'color:' + (color.red << 16 | color.green << 8 | color.blue) + ',' + - 'alpha:' + (color.alpha / 255); - } - - return 'color: 0, alpha: 1'; -} -function toMatrixInstance(matrix, matrixMorph) { if (matrixMorph) { return '{' + - '__class__:"flash.geom.Matrix",' + - 'a:' + morph(matrix.a * 20, matrixMorph.a * 20) + ',' + - 'b:' + morph(matrix.b * 20, matrixMorph.b * 20) + ',' + - 'c:' + morph(matrix.c * 20, matrixMorph.c * 20) + ',' + - 'd:' + morph(matrix.d * 20, matrixMorph.d * 20) + ',' + - 'tx:' + morph(matrix.tx, matrixMorph.tx) + ',' + - 'ty:' + morph(matrix.ty, matrixMorph.ty) + + 'a:' + morph(matrix.a * scale, matrixMorph.a * scale) + ',' + + 'b:' + morph(matrix.b * scale, matrixMorph.b * scale) + ',' + + 'c:' + morph(matrix.c * scale, matrixMorph.c * scale) + ',' + + 'd:' + morph(matrix.d * scale, matrixMorph.d * scale) + ',' + + 'e:' + morph(matrix.tx * 20, matrixMorph.ty * 20) + ',' + + 'f:' + morph(matrix.tx * 20, matrixMorph.ty * 20) + '}'; } return '{' + - '__class__:"flash.geom.Matrix",' + - 'a:' + (matrix.a * 20) + ',' + - 'b:' + (matrix.b * 20) + ',' + - 'c:' + (matrix.c * 20) + ',' + - 'd:' + (matrix.d * 20) + ',' + - 'tx:' + (matrix.tx * 20) + ',' + - 'ty:' + (matrix.ty * 20) + + 'a:' + (matrix.a * scale) + ',' + + 'b:' + (matrix.b * scale) + ',' + + 'c:' + (matrix.c * scale) + ',' + + 'd:' + (matrix.d * scale) + ',' + + 'e:' + (matrix.tx * 20) + ',' + + 'f:' + (matrix.ty * 20) + '}'; } @@ -272,117 +256,103 @@ function defineShape(tag, dictionary) { var commands = []; var fillStyle = fillStyles[i - 1]; + var fill; switch (fillStyle.type) { case GRAPHICS_FILL_SOLID: - commands.push('{' + - '__class__:"flash.display.GraphicsSolidFill",' + - '__isIGraphicsFill__:true,' + - toColorProperties(fillStyle.color, fillStyle.colorMorph) + - '}'); + if (fillStyle.colorMorph) { + fill = morphColor(fillStyle.color, fillStyle.colorMorph); + } else { + var color = fillStyle.color + fill = '"rgba(' + [color.red, color.green, color.blue, color.alpha / 255].join(',') + ')"'; + } break; case GRAPHICS_FILL_LINEAR_GRADIENT: case GRAPHICS_FILL_RADIAL_GRADIENT: case GRAPHICS_FILL_FOCAL_RADIAL_GRADIENT: var records = fillStyle.records; - var colors = []; - var alphas = []; - var ratios = []; + var stops = []; for (var j = 0, n = records.length; j < n; j++) { var record = records[j]; var color = record.color; if (record.colorMorph) { - var colorMorph = record.colorMorph; - colors.push(morphColor(color, colorMorph)); - alphas.push(morph(color.alpha, colorMorph.alpha)); - ratios.push(morph(record.ratio, record.ratioMorph)); + stops.push('f.addColorStop(' + + morph(record.ratio / 255, record.ratioMorph / 255) + ',' + + morphColor(color, record.colorMorph) + + ')'); } else { - colors.push(color.red << 16 | color.green << 8 | color.blue); - alphas.push(color.alpha); - ratios.push(record.ratio); + stops.push('f.addColorStop(' + + (record.ratio / 255) + ',' + + '"rgba(' + [color.red, color.green, color.blue, color.alpha / 255].join(',') + ')"' + + ')'); } } - commands.push('{' + - '__class__:"flash.display.GraphicsGradientFill",' + - '__isIGraphicsFill__:true,' + - 'type:' + (fillStyle.type == GRAPHICS_FILL_LINEAR_GRADIENT ? '"linear"' : '"radial"') + ',' + - 'colors:[' + colors.join(',') + '],' + - 'alphas:[' + alphas.join(',') + '],' + - 'ratios:[' + ratios.join(',') + '],' + - 'matrix:' + toMatrixInstance(fillStyle.matrix, fillStyle.matrixMorph), - 'spreadMode:"pad",' + - 'interpolationMode:"rgb",' + - 'focalPointRatio:' + morph(fillStyle.focalPoint, fillStyle.focalPointMorph) + - '}'); + fill = '(' + + 'f=c._create' + (GRAPHICS_FILL_LINEAR_GRADIENT ? 'Linear' : 'Radial') + 'Gradient(' + + (GRAPHICS_FILL_LINEAR_GRADIENT ? + '-1, 0, 1, 0' : + '(' + morph(fillStyle.focalPoint, fillStyle.focalPointMorph) + ' || 0), 0, 0, 0, 0, 1' + ) + + '),' + + stops.join(',') + ',' + + 'f.currentTransform=' + + toMatrixInstance(fillStyle.matrix, fillStyle.matrixMorph, 20 * 819.2) + ',' + + 'f)'; break; case GRAPHICS_FILL_REPEATING_BITMAP: case GRAPHICS_FILL_CLIPPED_BITMAP: case GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP: case GRAPHICS_FILL_NONSMOOTHED_CLIPPED_BITMAP: var bitmap = dictionary[fillStyle.bitmapId]; - commands.push('{' + - '__class__:"flash.display.GraphicsBitmapFill",' + - '__isIGraphicsFill__:true,' + - 'bitmapData: {' + - '__class__:"flash.display.BitmapData",' + - '_drawable:d[' + bitmap.id + '].value.props.img' + - '},' + - 'matrix:' + toMatrixInstance(fillStyle.matrix, fillStyle.matrixMorph), - 'repeat:' + !!fillStyle.repeat + - '}'); dependencies.push(bitmap.id); + fill = '(' + + 'f=c._createPattern(' + + 'd[' + bitmap.id + '].value.props.img,' + + (fillStyle.repeat ? '"repeat"' : '"no-repeat"') + + '),' + + 'f.currentTransform=' + + toMatrixInstance(fillStyle.matrix, fillStyle.matrixMorph, 1) + ',' + + 'f)'; break; default: fail('invalid fill style', 'shape'); } var cmds = []; - var data = []; var j = 0; var subpath; var prev = { }; while ((subpath = path[j++])) { - if (subpath.spt !== prev.dpt) { - cmds.push(GRAPHICS_PATH_COMMAND_MOVE_TO); - data.push(subpath.spt); - } + if (subpath.spt !== prev.dpt) + cmds.push('M' + subpath.spt); var edges = subpath.edges; if (subpath.flip) { var k = edges.length; var edge; while ((edge = edges[--k])) { - if (edge.cpt) { - cmds.push(GRAPHICS_PATH_COMMAND_CURVE_TO); - data.push(edge.cpt, edge.spt); - } else { - cmds.push(GRAPHICS_PATH_COMMAND_LINE_TO); - data.push(edge.spt); - } + if (edge.cpt) + cmds.push('Q' + edge.cpt + ',' + edge.spt); + else + cmds.push('L' + edge.spt); } } else { var k = 0; var edge; while ((edge = edges[k++])) { - if (edge.cpt) { - cmds.push(GRAPHICS_PATH_COMMAND_CURVE_TO); - data.push(edge.cpt, edge.dpt); - } else { - cmds.push(GRAPHICS_PATH_COMMAND_LINE_TO); - data.push(edge.dpt); - } + if (edge.cpt) + cmds.push('Q' + edge.cpt + ',' + edge.dpt); + else + cmds.push('L' + edge.dpt); } } prev = subpath; } - commands.push('{' + - '__class__:"flash.display.GraphicsPath",' + - '__isIGraphicsPath__:true,' + - 'commands:[' + cmds.join(',') + '],' + - 'data:[' + data.join(',') + ']' + - '},{' + - '__class__:"flash.display.GraphicsEndFill",' + - '__isIGraphicsFill__:true' + - '}'); + commands.push( + '(' + + 'p=Kanvas.Path("' + cmds.join('') + '"),' + + 'p.fillStyle=' + fill + ',' + + 'p)' + ); paths.push({ i: path[0].i, commands: commands}); } @@ -393,7 +363,11 @@ function defineShape(tag, dictionary) { while ((lineStyle = lineStyles[i++])) { var segments = lineSegments[i]; if (segments) { - var colorProps = toColorProperties(lineStyle.color, lineStyle.colorMorph); + var color = lineStyle.color; + var stroke = lineStyle.colorMorph ? + morphColor(color, lineStyle.colorMorph) : + '"rgba(' + [color.red, color.green, color.blue, color.alpha / 255].join(',') + ')"' + ; var lineWidth = morph(lineStyle.width || 20, isMorph ? lineStyle.widthMorph || 20 : undefined); // ignoring startCapStyle ? @@ -404,7 +378,6 @@ function defineShape(tag, dictionary) { var miterLimitFactor = lineStyle.miterLimitFactor; var cmds = []; - var data = []; var j = 0; var prev = { }; while ((segment = segments[j++])) { @@ -412,17 +385,12 @@ function defineShape(tag, dictionary) { var k = 0; var edge; while ((edge = edges[k++])) { - if (edge.spt !== prev.dpt) { - cmds.push(GRAPHICS_PATH_COMMAND_MOVE_TO); - data.push(edge.spt); - } - if (edge.cpt) { - cmds.push(GRAPHICS_PATH_COMMAND_CURVE_TO); - data.push(edge.cpt, edge.dpt); - } else { - cmds.push(GRAPHICS_PATH_COMMAND_LINE_TO); - data.push(edge.dpt); - } + if (edge.spt !== prev.dpt) + cmds.push('M' + edge.spt); + if (edge.cpt) + cmds.push('Q' + edge.cpt + ',' + edge.dpt); + else + cmds.push('L' + edge.dpt); prev = edge; } } @@ -430,29 +398,16 @@ function defineShape(tag, dictionary) { paths.push({ i: Number.MAX_VALUE, commands: [ - '{' + - '__class__:"flash.display.GraphicsStroke",' + - '__isIGraphicsStroke__:true,' + - 'thickness:' + lineWidth + ',' + - 'pixelHinting:false,' + - 'caps:"' + capsStyle + '",' + - 'joins:"' + joinStyle + '",' + - 'miterLimit:' + (miterLimitFactor * 2) + ',' + - 'scaleMode:"normal",' + - 'fill:{' + - '__class__:"flash.display.GraphicsSolidFill",' + - '__isIGraphicsFill__:true,' + - colorProps + - '}' + - '},{' + - '__class__:"flash.display.GraphicsPath",' + - '__isIGraphicsPath__:true,' + - 'commands:[' + cmds.join(',') + '],' + - 'data:[' + data.join(',') + ']' + - '},{' + - '__isIGraphicsStroke__:true,' + - 'fill:null' + - '}' + '(' + + 'p=Kanvas.Path("' + cmds.join('') + '"),' + + 'p.strokeStyle=' + stroke + ',' + + 'p.drawingStyles={' + + 'lineWidth:' + lineWidth + ',' + + 'lineCap:"' + capsStyle + '",' + + 'lineJoin:"' + joinStyle + '",' + + 'miterLimit:' + (miterLimitFactor * 2) + + '},' + + 'p)' ] }); }