blob: 316e23dbf1f9eb30766f23b311ce59067c17f15c [file] [log] [blame]
/**
* Copyright 2014 Google Inc. All rights reserved.
*
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*
* @fileoverview Description of this file.
*
* A polyfill for HTML Canvas features, including
* Path2D support.
*/
if (CanvasRenderingContext2D.prototype.ellipse == undefined) {
CanvasRenderingContext2D.prototype.ellipse = function(x, y, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise) {
this.save();
this.translate(x, y);
this.rotate(rotation);
this.scale(radiusX, radiusY);
this.arc(0, 0, 1, startAngle, endAngle, antiClockwise);
this.restore();
}
}
if (typeof Path2D !== 'function' || typeof Path2D.prototype.addPath !== 'function' || typeof Path2D.prototype.ellipse !== 'function') {
(function() {
// Include the SVG path parser.
//= svgpath.js
function Path_(arg) {
this.ops_ = [];
if (arg == undefined) {
return;
}
if (typeof arg == 'string') {
try {
this.ops_ = parser.parse(arg);
} catch(e) {
// Treat an invalid SVG path as an empty path.
}
} else if (arg.hasOwnProperty('ops_')) {
this.ops_ = arg.ops_.slice(0);
} else {
throw 'Error: ' + typeof arg + 'is not a valid argument to Path';
}
};
// TODO(jcgregorio) test for arcTo and implement via something.
// Path methods that map simply to the CanvasRenderingContext2D.
var simple_mapping = [
'closePath',
'moveTo',
'lineTo',
'quadraticCurveTo',
'bezierCurveTo',
'rect',
'arc',
'arcTo',
'ellipse'
];
function createFunction(name) {
return function() {
this.ops_.push({type: name, args: Array.prototype.slice.call(arguments, 0)});
};
}
// Add simple_mapping methods to Path2D.
for (var i=0; i<simple_mapping.length; i++) {
var name = simple_mapping[i];
Path_.prototype[name] = createFunction(name);
}
Path_.prototype['addPath'] = function(path, tr) {
var hasTx = false;
if (tr) {
hasTx = true;
this.ops_.push({type: 'save', args: []});
this.ops_.push({type: 'transform', args: [tr.a, tr.b, tr.c, tr.d, tr.e, tr.f]});
}
this.ops_ = this.ops_.concat(path.ops_);
if (hasTx) {
this.ops_.push({type: 'restore', args: []});
}
}
var original_fill = CanvasRenderingContext2D.prototype.fill;
var original_stroke = CanvasRenderingContext2D.prototype.stroke;
var original_clip = CanvasRenderingContext2D.prototype.clip;
// Replace methods on CanvasRenderingContext2D with ones that understand Path2D.
CanvasRenderingContext2D.prototype.fill = function(arg) {
if (arg instanceof Path_) {
this.beginPath();
for (var i = 0, len = arg.ops_.length; i < len; i++) {
var op = arg.ops_[i];
CanvasRenderingContext2D.prototype[op.type].apply(this, op.args);
}
original_fill.apply(this, Array.prototype.slice.call(arguments, 1));
} else {
original_fill.apply(this, arguments);
}
}
CanvasRenderingContext2D.prototype.stroke = function(arg) {
if (arg instanceof Path_) {
this.beginPath();
for (var i = 0, len = arg.ops_.length; i < len; i++) {
var op = arg.ops_[i];
CanvasRenderingContext2D.prototype[op.type].apply(this, op.args);
}
original_stroke.call(this);
} else {
original_stroke.call(this);
}
}
CanvasRenderingContext2D.prototype.clip = function(arg) {
if (arg instanceof Path_) {
// Note that we don't save and restore the context state, since the
// clip region is part of the state. Not really a problem since the
// HTML 5 spec doesn't say that clip(path) doesn't affect the current
// path.
this.beginPath();
for (var i = 0, len = arg.ops_.length; i < len; i++) {
var op = arg.ops_[i];
CanvasRenderingContext2D.prototype[op.type].apply(this, op.args);
}
original_clip.apply(this, Array.prototype.slice.call(arguments, 1));
} else {
original_clip.apply(this, arguments);
}
}
// Set up externs.
Path2D = Path_;
})();
}