Skip to content

Instantly share code, notes, and snippets.

@tobitailor
Created October 25, 2012 12:16
Show Gist options
  • Save tobitailor/3952263 to your computer and use it in GitHub Desktop.
Save tobitailor/3952263 to your computer and use it in GitHub Desktop.
// 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