Created
October 25, 2012 12:16
-
-
Save tbtlr/3952263 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Kanvas v0.0.1 (c) 2012 Tobias Schneider | |
// Released under MIT license. | |
// Kanvas adds support for not (yet) implemented HTML Canvas APIs. | |
;var Kanvas = Kanvas || (function (document, undefined) { | |
'use strict'; | |
var Kanvas = { version: '0.0.1' }; | |
var nativeCanvas = document.createElement('canvas'); | |
var nativeCanvasClass = nativeCanvas.constructor; | |
if (nativeCanvasClass !== HTMLCanvasElement) | |
throw TypeError('Non-native HTMLCanvasElement'); | |
var native2dContext = nativeCanvas.getContext('2d'); | |
var native2dContextClass = native2dContext.constructor; | |
if (native2dContextClass !== CanvasRenderingContext2D) | |
throw TypeError('Non-native CanvasRenderingContext2D'); | |
var nativeCanvasProto = nativeCanvasClass.prototype; | |
var native2dContextProto = native2dContextClass.prototype; | |
var kanvas2dContextProto = Object.create(native2dContextProto); | |
var identityMatrix; | |
var matrixClass; | |
var pathClass; | |
var nativeGetContext = nativeCanvas.getContext; | |
var defineProperties = Object.defineProperties; | |
var defineProperty = Object.defineProperty; | |
function defineLazyProperty(obj, prop, desc) { | |
defineProperty(obj, prop, { | |
get: function () { | |
var val; | |
if (desc.get) { | |
val = desc.get.call(this); | |
defineProperty(this, prop, { | |
value: val, | |
writable: desc.writable, | |
configurable: desc.configurable, | |
enumerable: desc.enumerable | |
}); | |
} else { | |
val = desc.value; | |
} | |
return val; | |
}, | |
configurable: true | |
}); | |
} | |
function safeReplace(obj, prop, val) { | |
defineProperty(obj, '__' + prop + '__', { value: obj[prop] }); | |
obj[prop] = val; | |
} | |
nativeCanvasProto.getContext = function (contextId) { | |
var primaryContext = this.__primaryContext__; | |
if (primaryContext && primaryContext !== contextId) | |
return null; | |
var context; | |
if (contextId === 'kanvas-2d') { | |
context = nativeGetContext.call(this, '2d'); | |
context.__proto__ = kanvas2dContextProto; | |
} else { | |
context = nativeGetContext.apply(this, arguments); | |
} | |
if (!primaryContext && context !== null) | |
defineProperty(this, '__primaryContext__', { value: contextId }); | |
return context; | |
}; | |
try { | |
identityMatrix = new SVGMatrix; | |
matrixClass = SVGMatrix; | |
} catch (err) { | |
var svgNamespace = 'http://www.w3.org/2000/svg'; | |
var svgElement = document.createElementNS(svgNamespace, 'svg'); | |
matrixClass = function SVGMatrix() { | |
return svgElement.createSVGMatrix(); | |
} | |
matrixClass.prototype = SVGMatrix.prototype; | |
identityMatrix = new matrixClass; | |
} | |
defineLazyProperty(kanvas2dContextProto, '__currentTransform__', { | |
get: function () { | |
return new matrixClass; | |
}, | |
writable: true | |
}); | |
defineProperty(kanvas2dContextProto, 'currentTransform', { | |
get: function () { | |
return this.__currentTransform__; | |
}, | |
set: function (val) { | |
if (!(val instanceof matrixClass)) | |
throw TypeError(); | |
this.setTransform(val.a, val.b, val.c, val.d, val.e, val.f); | |
this.__currentTransform__ = val; | |
} | |
}); | |
} | |
defineLazyProperty(kanvas2dContextProto, '__transformStack__', { | |
get: function () { | |
return []; | |
} | |
}); | |
safeReplace(kanvas2dContextProto, 'save', function () { | |
this.__save__(); | |
this.__transformStack__.push(this.__currentMatrix__); | |
if (shimPath) | |
this.__displayList__.push({ cmd: 'save' }); | |
}); | |
safeReplace(kanvas2dContextProto, 'restore', function () { | |
this.__restore__(); | |
var stack = this.__transformStack__; | |
if (stack.length) { | |
var matrix = stack.pop(); | |
var transform = this.__currentTransform__; | |
transform.a = matrix[0]; | |
transform.b = matrix[1]; | |
transform.c = matrix[2]; | |
transform.d = matrix[3]; | |
transform.e = matrix[4]; | |
transform.f = matrix[5]; | |
this.__currentMatrix__ = matrix; | |
this.__displayList__.push({ cmd: 'restore' }); | |
} | |
}); | |
safeReplace(kanvas2dContextProto, 'scale', function (x, y) { | |
if (isNaN(+x + +y)) | |
return; | |
this.__scale__(x, y); | |
this.__displayList__.push({ cmd: 'scale', args: [x, y] }); | |
var transform = this.currentTransform; | |
var a = transform.a; | |
var b = transform.b; | |
var c = transform.c; | |
var d = transform.d; | |
var e = transform.e; | |
var f = transform.f; | |
transform.a = a = a * x; | |
transform.b = b = b * x; | |
transform.c = c = c * y; | |
transform.d = d = d * y; | |
this.__currentMatrix__ = [a, b, c, d, e, f]; | |
}); | |
safeReplace(kanvas2dContextProto, 'rotate', function (angle) { | |
if (isNaN(angle)) | |
return; | |
this.__rotate__(angle); | |
this.__displayList__.push({ cmd: 'rotate', args: [angle] }); | |
var transform = this.currentTransform; | |
var a = transform.a; | |
var b = transform.b; | |
var c = transform.c; | |
var d = transform.d; | |
var e = transform.e; | |
var f = transform.f; | |
var u = Math.cos(angle); | |
var v = Math.sin(angle); | |
var g = a * u + c * v; | |
var h = b * u + d * v; | |
var i = a * -v + c * u; | |
var j = b * -v + d * u; | |
transform.a = a = g; | |
transform.b = b = h; | |
transform.c = c = i; | |
transform.d = d = j; | |
this.__currentMatrix__ = [a, b, c, d, e, f]; | |
}); | |
safeReplace(kanvas2dContextProto, 'translate', function (x, y) { | |
if (isNaN(+x + +y)) | |
return; | |
this.__translate__(x, y); | |
this.__displayList__.push({ cmd: 'translate', args: [x, y] }); | |
var transform = this.currentTransform; | |
var a = transform.a; | |
var b = transform.b; | |
var c = transform.c; | |
var d = transform.d; | |
var e = transform.e; | |
var f = transform.f; | |
transform.e = e = e + a * x + c * y; | |
transform.f = f = f + b * x + d * y; | |
this.__currentMatrix__ = [a, b, c, d, e, f]; | |
}); | |
safeReplace(kanvas2dContextProto, 'transform', function (g, h, i, j, k, l) { | |
if (isNaN(+a + +b + +c + +d + +e + +f)) | |
return; | |
this.__transform__(g, h, i, j, k, l); | |
this.__displayList__.push({ cmd: 'transform', args: [g, h, i, j, k, l] }); | |
var transform = this.currentTransform; | |
var a = transform.a; | |
var b = transform.b; | |
var c = transform.c; | |
var d = transform.d; | |
var e = transform.e; | |
var f = transform.f; | |
var m = a * g + c * h; | |
var n = b * g + d * h; | |
var o = a * i + c * j; | |
var p = b * i + d * j; | |
transform.a = a = m; | |
transform.b = b = n; | |
transform.c = c = o; | |
transform.d = d = p; | |
transform.e = e = e + a * k + c * k; | |
transform.f = f = f + b * k + d * k; | |
this.__currentMatrix__ = [a, b, c, d, e, f]; | |
}); | |
safeReplace(kanvas2dContextProto, 'setTransform', function (a, b, c, d, e, f) { | |
if (isNaN(+a + +b + +c + +d + +e + +f)) | |
return; | |
this.__setTransform__(a, b, c, d, e, f); | |
this.__displayList__.push({ cmd: 'setTransform', args: [a, b, c, d, e, f] }); | |
var transform = this.__currentTransform__; | |
transform.a = a; | |
transform.b = b; | |
transform.c = c; | |
transform.d = d; | |
transform.e = e; | |
transform.f = f; | |
this.__currentMatrix__ = [a, b, c, d, e, f]; | |
}); | |
kanvas2dContextProto.resetTransform = function () { | |
this.__setTransform__( | |
identityMatrix.a, | |
identityMatrix.b, | |
identityMatrix.c, | |
identityMatrix.d, | |
identityMatrix.e, | |
identityMatrix.f | |
); | |
}; | |
defineLazyProperty(kanvas2dContextProto, '__currentMatrix__', { | |
get: function () { | |
return null; | |
}, | |
writable: true | |
}); | |
kanvas2dContextProto.ellipse = function (x, y, rx, ry, rotation, angle1, angle2, anticw) { | |
if (isNaN(+x + +y + +rx + +ry + +rotation + +angle1 + +angle2)) | |
return; | |
if (rx < 0 || ry < 0) | |
throw Error(); | |
var u = Math.cos(rotation) | |
var v = Math.sin(rotation); | |
this.save(); | |
this.transform(); | |
this.arc(0, 0, 1, angle1, angle2, anticw); | |
this.restore(); | |
}; | |
pathClass = function Path(d) { | |
if (!(this instanceof Path)) | |
return new Path(d); | |
}; | |
var pathMethods = Object.create(null); | |
pathMethods.closePath = function () { | |
}; | |
pathMethods.moveTo = function (x, y) { | |
if (isNaN(+x + +y)) | |
return; | |
this.__moveTo__(x, y); | |
this.__displayList__.push({ cmd: 'moveTo', args: [x, y] }); | |
var matrix = this.__currentMatrix__; | |
if (matrix) { | |
x = matrix[0] * x + matrix[2] * y + matrix[4]; | |
y = matrix[1] * x + matrix[3] * y + matrix[5]; | |
} | |
var bounds = this.__bounds__; | |
if (x < bounds[0]) | |
bounds[0] = x; | |
if (x > bounds[1]) | |
bounds[1] = x; | |
if (y < bounds[2]) | |
bounds[2] = y; | |
if (y > bounds[3]) | |
bounds[3] = y; | |
this.__x__ = x; | |
this.__y__ = y; | |
}; | |
pathMethods.lineTo = function (x, y) { | |
if (isNaN(+x + +y)) | |
return; | |
this.__lineTo__(x, y); | |
this.__displayList__.push({ cmd: 'lineTo', args: [x, y] }); | |
var matrix = this.__currentMatrix__; | |
if (matrix) { | |
x = matrix[0] * x + matrix[2] * y + matrix[4]; | |
y = matrix[1] * x + matrix[3] * y + matrix[5]; | |
} | |
var bounds = this.__bounds__; | |
if (x < bounds[0]) | |
bounds[0] = x; | |
if (x > bounds[1]) | |
bounds[1] = x; | |
if (y < bounds[2]) | |
bounds[2] = y; | |
if (y > bounds[3]) | |
bounds[3] = y; | |
this.__x__ = x; | |
this.__y__ = y; | |
}; | |
pathMethods.quadraticCurveTo = function (cpx, cpy, x, y) { | |
if (isNaN(+cpx + +cpy + +x + +y)) | |
return; | |
this.__quadraticCurveTo__(cpx, cpy, x, y); | |
this.__displayList__.push({ cmd: 'quadraticCurveTo', args: [cpx, cpy, x, y] }); | |
var matrix = this.__currentMatrix__; | |
if (matrix) { | |
var a = matrix[0]; | |
var b = matrix[1]; | |
var c = matrix[2]; | |
var d = matrix[3]; | |
var e = matrix[4]; | |
var f = matrix[5]; | |
cpx = a * cpx + c * cpy + e; | |
cpy = b * cpx + d * cpy + f; | |
x = a * x + c * y + e; | |
y = b * x + d * y + f; | |
} | |
var x0 = this.__x__; | |
var y0 = this.__y__; | |
var px; | |
var py; | |
var dx = x0 - 2 * cpx + x; | |
var tx = dx ? (x0 - cpx) / dx : -1; | |
if (tx >= 0 && tx <= 1) { | |
var mtx = 1 - tx; | |
px = mtx * mtx * x0 + 2 * mtx * tx * cpx + tx * tx * x; | |
} | |
var dy = y0 - 2 * cpy + y; | |
var ty = dy ? (y0 - cpy) / dy : -1; | |
if (ty >= 0 && ty <= 1) { | |
var mty = 1 - ty; | |
py = mty * mty * y0 + 2 * mty * ty * cpy + ty * ty * y; | |
} | |
var bounds = this.__bounds__; | |
bounds[0] = Math.min(bounds[0], px, x); | |
bounds[1] = Math.max(bounds[1], px, x); | |
bounds[2] = Math.min(bounds[2], py, y); | |
bounds[3] = Math.max(bounds[3], py, y); | |
this.__x__ = x; | |
this.__y__ = y; | |
}; | |
pathMethods.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { | |
if (isNaN(+cp1x + +cp1y + +cp2x + +cp2y + +x + +y)) | |
return; | |
this.__bezierCurveTo__(cp1x, cp1y, cp2x, cp2y, x, y); | |
this.__displayList__.push({ cmd: 'bezierCurveTo', args: [cp1x, cp1y, cp2x, cp2y, x, y] }); | |
var matrix = this.__currentMatrix__; | |
if (matrix) { | |
var a = matrix[0]; | |
var b = matrix[1]; | |
var c = matrix[2]; | |
var d = matrix[3]; | |
var e = matrix[4]; | |
var f = matrix[5]; | |
cp1x = a * cp1x + c * cp1y + e; | |
cp1y = b * cp1x + d * cp1y + f; | |
cp2x = a * cp2x + c * cp2y + e; | |
cp2y = b * cp2x + d * cp2y + f; | |
x = a * x + c * y + e; | |
y = b * x + d * y + f; | |
} | |
var x0 = this.__x__; | |
var y0 = this.__y__; | |
var p1x; | |
var p1y; | |
var p2x; | |
var p2y; | |
var txl = -x0 + 2 * cp1x - cp2x; | |
var txr = -Math.sqrt((-x0 * (cp2x - x) + cp1x * cp1x - cp1x * (cp2x + x) + cp2x * cp2x)); | |
var dx = -x0 + 3 * cp1x - 3 * cp2x + x; | |
var tx1 = dx ? (txl + txr) / dx : -1; | |
if (tx1 >= 0 && tx1 <= 1) { | |
var mtx1 = 1 - tx1; | |
p1x = mtx1 * mtx1 * mtx1 * x0 + 3 * mtx1 * mtx1 * tx1 * cp1x + | |
3 * mtx1 * tx1 * tx1 * cp2x + tx1 * tx1 * tx1 * x; | |
} | |
var tx2 = dx ? (txl - txr) / dx : -1; | |
if (tx2 >= 0 && tx2 <= 1) { | |
var mtx2 = 1 - tx2; | |
p2x = mtx2 * mtx2 * mtx2 * x0 + 3 * mtx2 * mtx2 * tx2 * cp1x + | |
3 * mtx2 * tx2 * tx2 * cp2x + tx2 * tx2 * tx2 * x; | |
} | |
var tyl = -y0 + 2 * cp1y - cp2y; | |
var tyr = -Math.sqrt((-y0 * (cp2y - y) + cp1y * cp1y - cp1y * (cp2y + y) + cp2y * cp2y)); | |
var dy = -y0 + 3 * cp1y - 3 * cp2y + y; | |
var ty1 = dy ? (tyl + tyr) / dy : -1; | |
if (ty1 >= 0 && ty1 <= 1) { | |
var mty1 = 1 - ty1; | |
p1y = mty1 * mty1 * mty1 * y0 + 3 * mty1 * mty1 * ty1 * cp1y + | |
3 * mty1 * ty1 * ty1 * cp2y + ty1 * ty1 * ty1 * y; | |
} | |
var ty2 = dy ? (tyl - tyr) / dy : -1; | |
if (ty2 >= 0 && ty2 <= 1) { | |
var mty2 = 1 - ty2; | |
p2y = mty2 * mty2 * mty2 * y0 + 3 * mty2 * mty2 * ty2 * cp1y + | |
3 * mty2 * ty2 * ty2 * cp2y + ty2 * ty2 * ty2 * y; | |
} | |
var bounds = this.__bounds__; | |
bounds[0] = Math.min(bounds[0], p1x, p2x, x); | |
bounds[1] = Math.max(bounds[1], p1x, p2x, x); | |
bounds[2] = Math.min(bounds[2], p1y, p2y, y); | |
bounds[3] = Math.max(bounds[3], p1y, p2y, y); | |
this.__x__ = x; | |
this.__y__ = y; | |
}; | |
pathMethods.arcTo = function (x1, y1, x2, y2, rx, ry, rotation) { | |
if (isNaN(+x1 + +y1 + +x2 + +y2 + +rx)) | |
return; | |
this.__displayList__.push({ cmd: 'arcTo', args: [x1, y1, x2, y2, rx, ry, rotation] }); | |
// TODO | |
}; | |
pathMethods.rect = function (x, y, w, h) { | |
if (isNaN(+x + +y + +w + +h)) | |
return; | |
this.__rect__(x, y, w, h); | |
this.__displayList__.push({ cmd: 'rect', args: [x, y, w, h] }); | |
var p1x = x; | |
var p1y = y; | |
var p2x = x + w; | |
var p2y = y; | |
var p3x = p2x; | |
var p3y = y + h; | |
var p4x = x; | |
var p4y = p3y; | |
var matrix = this.__currentMatrix__; | |
if (matrix) { | |
var a = matrix[0]; | |
var b = matrix[1]; | |
var c = matrix[2]; | |
var d = matrix[3]; | |
var e = matrix[4]; | |
var f = matrix[5]; | |
p1x = a * p1x + c * p1y + e; | |
p1y = b * p1x + d * p1y + f; | |
p2x = a * p2x + c * p2y + e; | |
p2y = b * p2x + d * p2y + f; | |
p3x = a * p3x + c * p3y + e; | |
p3y = b * p3x + d * p3y + f; | |
p4x = a * p4x + c * p4y + e; | |
p4y = b * p4x + d * p4y + f; | |
} | |
var bounds = this.__bounds__; | |
bounds[0] = Math.min(bounds[0], p1x, p2x, p3x, p4x); | |
bounds[1] = Math.max(bounds[1], p1x, p2x, p3x, p4x); | |
bounds[2] = Math.min(bounds[2], p1y, p2y, p3y, p4y); | |
bounds[3] = Math.max(bounds[3], p1y, p2y, p3y, p4y); | |
this.__x__ = p1x; | |
this.__y__ = p1y; | |
}; | |
pathMethods.arc = function (x, y, radius, angle1, angle2, anticw) { | |
this.ellipse(x, y, radius, radius, 0, angle1, angle2, anticw); | |
}; | |
pathMethods.ellipse = function (x, y, rx, ry, rotation, angle1, angle2, anticw) { | |
if (isNaN(+x + +y + +rx + +ry + +rotation + +angle1 + +angle2)) | |
return; | |
if (rx == ry) | |
this.arc(x, y, rx, angle1, angle2, anticw); | |
else | |
this.__ellipse__(x, y, rx, ry, rotation, angle1, angle2, anticw); | |
var matrix = this.__currentMatrix__; | |
if (matrix) { | |
var a = matrix.a; | |
var b = matrix.b; | |
var c = matrix.c; | |
var d = matrix.d; | |
var e = matrix.e; | |
var f = matrix.f; | |
x = a * x + c * y + e; | |
y = b * x + d * y + f; | |
rx *= (a > 0 ? 1 : -1) * Math.sqrt(d * d + c * c); | |
ry *= (d > 0 ? 1 : -1) * Math.sqrt(a * a + b * b); | |
rotation += Math.atan(a / b); | |
} | |
var u = Math.cos(rotation); | |
var v = Math.sin(rotation); | |
var px = x + rx * u; | |
var py = y + ry * v; | |
this.__displayList__.push( | |
rx == ry ? | |
{ cmd: 'arc', args: [x, y, rx, angle1, angle2, anticw] } : | |
{ cmd: 'ellipse', args: [x, y, rx, ry, rotation, angle1, angle2, anticw] } | |
); | |
var bounds = this.__bounds__; | |
bounds[0] = Math.min(bounds[0], px); | |
bounds[1] = Math.max(bounds[1], px); | |
bounds[2] = Math.min(bounds[2], py); | |
bounds[3] = Math.max(bounds[3], py); | |
this.__x__ = px; | |
this.__y__ = py; | |
}; | |
var pathProto = Object.create(pathMethods); | |
pathClass.prototype = pathProto; | |
defineLazyProperty(kanvas2dContextProto, '__displayList__', { | |
get: function () { | |
return []; | |
} | |
}); | |
defineLazyProperty(kanvas2dContextProto, '__bounds__', { | |
get: function () { | |
var canvas = this.canvas; | |
return [canvas.width, 0, canvas.height, 0]; | |
}, | |
writable: true | |
}); | |
safeReplace(kanvas2dContextProto, 'beginPath', function () { | |
this.__displayList__.length = 0; | |
this.__beginPath__(); | |
}); | |
for (var methodName in pathMethods) { | |
defineLazyProperty(pathProto, '__' + methodName + '__', { | |
get: function () { | |
return native2dContext[methodName].bind(this.__hitContext__); | |
} | |
}); | |
safeReplace(kanvas2dContextProto, methodName, pathMethods[methodName]); | |
} | |
//safeReplace(kanvas2dContextProto, 'fill', function (path) { | |
//}); | |
//safeReplace(kanvas2dContextProto, 'stroke', function (path) { | |
//}); | |
safeReplace(kanvas2dContextProto, 'clip', function (path) { | |
}); | |
safeReplace(kanvas2dContextProto, 'isPointInPath', function (x, y) { | |
var path; | |
if (x instanceof pathClass) { | |
path = x; | |
x = y; | |
y = arguments[2]; | |
var matrix = this.__currentTransform__.inverse(); | |
x = matrix.a * x + matrix.c * y + matrix.e; | |
y = matrix.b * x + matrix.d * y + matrix.f; | |
} else { | |
path = this; | |
} | |
return path.__isPointInPath__(x, y); | |
}); | |
Kanvas.SVGMatrix = matrixClass; | |
Kanvas.Path = pathClass; | |
return Kanvas; | |
}(document)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment