joon hee - Canvas.Explorer-1.01
NAME
Canvas.Silverlight - CanvasRenderingContext2D from Silverlight
SYNOPSIS
if ( typeof CanvasRenderingContext2D == 'undefined' ) {
JSAN.use('Canvas.Silverlight');
}
DESCRIPTION
This implements the HTML5 CanvasRenderingContext2D Class using Silverlight, if the silverlight plugin is available and the canvas class has not yet been defined then this module will allow your code to run, as long as you initialize each Canvas element.
DEPENDENCIES
Silverlight
METHODS
G_vmlCanvasManager.initElement(HTMLElement el) Returns HTMLElement
This initializes a Canvas into a silverlight element.
KNOWN ISSUES
Doing a transformation during a path (ie lineTo, transform, lineTo) will not work corerctly because the transform is done to the whole path (ie transform, lineTo, lineTo)
Patterns are not yet implemented.
AUTHORS
- Emil A Eklund <emil@eae.net>
- Erik Arvidsson <erik@eae.net>
- Glen Murphy <glen@glenmurphy.com>
COPYRIGHT
Copyright 2006 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
POD ERRORS
Hey! The above document had some coding errors, which are explained below:
- Around line 35:
-
'=item' outside of any '=over'
- Around line 41:
-
You forgot a '=back' before '=head1'
/*=pod
=head1 NAME
Canvas.Silverlight - CanvasRenderingContext2D from Silverlight
=head1 SYNOPSIS
if ( typeof CanvasRenderingContext2D == 'undefined' ) {
JSAN.use('Canvas.Silverlight');
}
=head1 DESCRIPTION
This implements the HTML5 CanvasRenderingContext2D Class using Silverlight, if the silverlight plugin is available and the canvas class has not yet been defined then this module will allow your code to run, as long as you initialize each Canvas element.
=head1 DEPENDENCIES
Silverlight
=head1 METHODS
=head2 G_vmlCanvasManager.initElement(HTMLElement el) Returns HTMLElement
This initializes a Canvas into a silverlight element.
=head1 KNOWN ISSUES
Doing a transformation during a path (ie lineTo, transform, lineTo) will not work corerctly because the transform is done to the whole path (ie transform, lineTo, lineTo)
Patterns are not yet implemented.
=head1 AUTHORS
=item * Emil A Eklund <emil@eae.net>
=item * Erik Arvidsson <erik@eae.net>
=item * Glen Murphy <glen@glenmurphy.com>
=head1 COPYRIGHT
Copyright 2006 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut*/
// only add this code if we do not already have a canvas implementation
if (!window.CanvasRenderingContext2D) {
(function () {
var xamlId;
var G_vmlCanvasManager_ = {
init: function (opt_doc) {
var doc = opt_doc || document;
// Create a dummy element so that IE will allow canvas elements to be
// recognized.
doc.createElement('canvas');
if (/MSIE/.test(navigator.userAgent) && !window.opera) {
var self = this;
createXamlScriptTag();
doc.attachEvent('onreadystatechange', function () {
self.init_(doc);
});
}
},
init_: function (doc) {
// setup default css
var ss = doc.createStyleSheet();
ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
// default size is 300x150 in Gecko and Opera
'text-align:left;width:300px;height:150px}' +
'canvas object{width:100%;height:100%;border:0;' +
'background:transparen;margin:0}';
// find all canvas elements
var els = doc.getElementsByTagName('canvas');
for (var i = 0; i < els.length; i++) {
if (!els[i].getContext) {
this.initElement(els[i]);
}
}
},
/**
* Public initializes a canvas element so that it can be used as canvas
* element from now on. This is called automatically before the page is
* loaded but if you are creating elements using createElement you need to
* make sure this is called on the element.
* @param {HTMLElement} el The canvas element to initialize.
* @return {HTMLElement} the element that was created.
*/
initElement: function (el) {
el.getContext = function () {
if (this.context_) {
return this.context_;
}
return this.context_ = new CanvasRenderingContext2D_(this);
};
var attrs = el.attributes;
if (attrs.width && attrs.width.specified) {
// TODO: use runtimeStyle and coordsize
// el.getContext().setWidth_(attrs.width.nodeValue);
el.style.width = attrs.width.nodeValue + 'px';
} else {
el.width = el.clientWidth;
}
if (attrs.height && attrs.height.specified) {
// TODO: use runtimeStyle and coordsize
// el.getContext().setHeight_(attrs.height.nodeValue);
el.style.height = attrs.height.nodeValue + 'px';
} else {
el.height = el.clientHeight;
}
// insert object tag
el.innerHTML = getObjectHtml();
// do not use inline function because that will leak memory
el.attachEvent('onpropertychange', onPropertyChange);
return el;
}
};
function onPropertyChange(e) {
var el = e.srcElement;
switch (e.propertyName) {
case 'width':
el.style.width = el.attributes.width.nodeValue + 'px';
el.getContext().clearRect();
break;
case 'height':
el.style.height = el.attributes.height.nodeValue + 'px';
el.getContext().clearRect();
break;
}
}
G_vmlCanvasManager_.init();
function createXamlScriptTag() {
// This script tag contains the boilerplate XAML.
document.write('<script type=text/xaml>' +
'<Canvas x:Name="root" ' +
'xmlns="http://schemas.microsoft.com/client/2007" ' +
'xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" ' +
'Width="300" ' +
'Height="150" ' +
'Background="Transparent"> ' +
'</Canvas>' +
'</script>');
// Find the id of the writtenscript file.
var scripts = document.scripts;
var script = scripts[scripts.length - 1];
xamlId = script.uniqueID;
script.id = xamlId;
}
function getObjectHtml(fn) {
return '<object type="application/x-silverlight" >' +
'<param name="windowless" value="true">' +
'<param name="background" value="transparent">' +
'<param name="source" value="#' + xamlId + '">' +
'</object>';
}
function hasSilverlight() {
try {
new ActiveXObject('AgControl.AgControl');
return true;
} catch(_) {
return false;
}
}
// precompute "00" to "FF"
var dec2hex = [];
for (var i = 0; i < 16; i++) {
for (var j = 0; j < 16; j++) {
dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
}
}
function createMatrixIdentity() {
return [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]
];
}
function matrixMultiply(m1, m2) {
var result = createMatrixIdentity();
for (var x = 0; x < 3; x++) {
for (var y = 0; y < 3; y++) {
var sum = 0;
for (var z = 0; z < 3; z++) {
sum += m1[x][z] * m2[z][y];
}
result[x][y] = sum;
}
}
return result;
}
function doTransform(ctx) {
transformObject(ctx, getRoot(ctx), ctx.m_);
}
function transformObject(ctx, obj, m) {
var transform = obj.renderTransform;
var matrix;
if (!transform) {
transform = create(ctx, '<MatrixTransform/>');
matrix = create(ctx, '<Matrix/>');
transform.matrix = matrix;
obj.renderTransform = transform;
} else {
matrix = transform.matrix;
}
matrix.m11 = m[0][0];
matrix.m12 = m[0][1];
matrix.m21 = m[1][0];
matrix.m22 = m[1][1];
matrix.offsetX = m[2][0];
matrix.offsetY = m[2][1];
}
function copyState(o1, o2) {
o2.fillStyle = o1.fillStyle;
o2.lineCap = o1.lineCap;
o2.lineJoin = o1.lineJoin;
o2.lineWidth = o1.lineWidth;
o2.miterLimit = o1.miterLimit;
o2.shadowBlur = o1.shadowBlur;
o2.shadowColor = o1.shadowColor;
o2.shadowOffsetX = o1.shadowOffsetX;
o2.shadowOffsetY = o1.shadowOffsetY;
o2.strokeStyle = o1.strokeStyle;
o2.globalAlpha = o1.globalAlpha;
o2.arcScaleX_ = o1.arcScaleX_;
o2.arcScaleY_ = o1.arcScaleY_;
}
function translateColor(s) {
var rgbaMatch = /rgba\(([^)]+)\)/gi.exec(s);
if (rgbaMatch) {
var parts = rgbaMatch[1].split(',');
return '#' + dec2hex[Math.floor(Number(parts[3]) * 255)] +
dec2hex[Number(parts[0])] +
dec2hex[Number(parts[1])] +
dec2hex[Number(parts[2])];
}
var rgbMatch = /rgb\(([^)]+)\)/gi.exec(s);
if (rgbMatch) {
var parts = rgbMatch[1].split(',');
return '#FF' + dec2hex[Number(parts[0])] +
dec2hex[Number(parts[1])] +
dec2hex[Number(parts[2])];
}
return s;
}
function processLineCap(lineCap) {
switch (lineCap) {
case 'butt':
return 'flat';
case 'round':
return 'round';
case 'square':
default:
return 'square';
}
}
function getRoot(ctx) {
return ctx.canvas.firstChild.content.findName('root');
}
function create(ctx, s, opt_args) {
if (opt_args) {
s = s.replace(/\%(\d+)/g, function(match, index) {
return opt_args[Number(index) - 1];
});
}
try {
return ctx.canvas.firstChild.content.createFromXaml(s);
} catch (ex) {
throw Error('Could not create XAML from: ' + s);
}
}
function drawShape(ctx, s, opt_args) {
var canvas = ctx.lastCanvas_ || create(ctx, '<Canvas/>');
var shape = create(ctx, s, opt_args);
canvas.children.add(shape);
transformObject(ctx, canvas, ctx.m_);
if (!ctx.lastCanvas_) {
getRoot(ctx).children.add(canvas);
ctx.lastCanvas_ = canvas;
}
return shape;
}
function createBrushObject(ctx, value) {
if (value instanceof CanvasGradient_) {
return value.createBrush_(ctx);
} else if (value instanceof CanvasPattern_) {
throw Error('Not implemented');
} else {
return create(ctx, '<SolidColorBrush Color="%1"/>',
[translateColor(value)]);
}
}
/**
* This class implements CanvasRenderingContext2D interface as described by
* the WHATWG.
* @param {HTMLElement} surfaceElement The element that the 2D context should
* be associated with
*/
function CanvasRenderingContext2D_(surfaceElement) {
this.m_ = createMatrixIdentity();
this.lastCanvas_ = null;
this.mStack_ = [];
this.aStack_ = [];
this.currentPath_ = [];
// Canvas context properties
this.strokeStyle = '#000';
this.fillStyle = '#000';
this.lineWidth = 1;
this.lineJoin = 'miter';
this.lineCap = 'butt';
this.miterLimit = 10;
this.globalAlpha = 1;
this.canvas = surfaceElement;
};
var contextPrototype = CanvasRenderingContext2D_.prototype;
contextPrototype.clearRect = function() {
var root = getRoot(this);
root.children.clear();
// TODO: Implement
this.currentPath_ = [];
this.lastCanvas_ = null;
};
contextPrototype.beginPath = function() {
// TODO: Branch current matrix so that save/restore has no effect
// as per safari docs.
this.currentPath_ = [];
};
contextPrototype.moveTo = function(aX, aY) {
this.currentPath_.push('M' + aX + ',' + aY);
};
contextPrototype.lineTo = function(aX, aY) {
if (this.currentPath_.length == 0) return;
this.currentPath_.push('L' + aX + ',' + aY);
};
contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
aCP2x, aCP2y,
aX, aY) {
if (this.currentPath_.length == 0) return;
this.currentPath_.push('C' + aCP1x + ',' + aCP1y + ' ' +
aCP2x + ',' + aCP2y + ' ' +
aX + ' ' + aY);
};
contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
if (this.currentPath_.length == 0) return;
this.currentPath_.push('Q' + aCPx + ',' + aCPy + ' ' +
aX + ',' + aY);
};
contextPrototype.arcTo = function(x1, y1, x2, y2, radius) {
if (this.currentPath_.length == 0) return;
// TODO: Implement
};
contextPrototype.arc = function(aX, aY, aRadius,
aStartAngle, aEndAngle, aClockwise) {
var deltaAngle = Math.abs(aStartAngle - aEndAngle);
// If start and stop are the same WebKit and Moz does nothing
if (aStartAngle == aEndAngle) {
// different browsers behave differently here so we do the easiest thing
return;
}
var endX = aX + aRadius * Math.cos(aEndAngle);
var endY = aY + aRadius * Math.sin(aEndAngle);
if (deltaAngle >= 2 * Math.PI) {
// if larger than 2PI
this.arc(aX, aY, aRadius, aStartAngle, aStartAngle + Math.PI, aClockwise);
this.arc(aX, aY, aRadius, aStartAngle + Math.PI,
aStartAngle + 2 * Math.PI, aClockwise);
// now move to end point
this.moveTo(endX, endY);
return;
}
var startX = aX + aRadius * Math.cos(aStartAngle);
var startY = aY + aRadius * Math.sin(aStartAngle);
var rotationAngle = deltaAngle * 180 / Math.PI; // sign, abs?
var sweepDirection = aClockwise ? 0 : 1;
var isLargeArc = rotationAngle >= 180 == Boolean(aClockwise) ? 0 : 1;
if (this.currentPath_.length != 0) {
// add line to start point
this.lineTo(startX, startY);
} else {
this.moveTo(startX, startY);
}
this.currentPath_.push('A' + aRadius + ',' + aRadius + ' ' +
rotationAngle + ' ' +
isLargeArc + ' ' +
sweepDirection + ' ' +
endX + ',' + endY);
};
contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
};
contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
// Will destroy any existing path (same as FF behaviour)
this.beginPath();
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
this.stroke();
this.currentPath_ = [];
};
contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
// Will destroy any existing path (same as FF behaviour)
this.beginPath();
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
this.fill();
this.currentPath_ = [];
};
contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
return new LinearCanvasGradient_(aX0, aY0, aX1, aY1);
};
contextPrototype.createRadialGradient = function(x0, y0,
r0, x1,
y1, r1) {
return new RadialCanvasGradient_(x0, y0, r0, x1, y1, r1);
};
contextPrototype.drawImage = function (image, var_args) {
var dx, dy, dw, dh, sx, sy, sw, sh;
// For Silverlight we don't need to get the size of the image since
// Silverlight uses the image original dimension if not provided.
if (arguments.length == 3) {
dx = arguments[1];
dy = arguments[2];
// Keep sx, sy, sw, dw, sh and dh undefined
} else if (arguments.length == 5) {
dx = arguments[1];
dy = arguments[2];
dw = arguments[3];
dh = arguments[4];
// Keep sx, sy, sw and sh undefined
} else if (arguments.length == 9) {
sx = arguments[1];
sy = arguments[2];
sw = arguments[3];
sh = arguments[4];
dx = arguments[5];
dy = arguments[6];
dw = arguments[7];
dh = arguments[8];
} else {
throw Error('Invalid number of arguments');
}
var slImage;
// If we have a source rect we need to clip the image.
if (arguments.length == 9) {
slImage = drawShape(this, '<Image Source="%1"/>', [image.src]);
var clipRect = create(this,
'<RectangleGeometry Rect="%1,%2,%3,%4"/>', [sx, sy, sw, sh]);
slImage.clip = clipRect;
var m = createMatrixIdentity();
// translate to 0,0
m[2][0] = -sx;
m[2][1] = -sy;
// scale
var m2 = createMatrixIdentity();
m2[0][0] = dw / sw;
m2[1][1] = dh / sh;
m = matrixMultiply(m, m2);
// translate to destination
m[2][0] += dx;
m[2][1] += dy;
transformObject(this, slImage, m);
} else {
slImage = drawShape(this,
'<Image Source="%1" Canvas.Left="%2" Canvas.Top="%3"/>',
[image.src, dx, dy]);
if (dw != undefined || dh != undefined) {
slImage.width = dw;
slImage.height = dh;
slImage.stretch = 'fill';
}
}
};
contextPrototype.stroke = function() {
if (this.currentPath_.length == 0) return;
var path = drawShape(this, '<Path Data="%1"/>',
[this.currentPath_.join(' ')]);
path.stroke = createBrushObject(this, this.strokeStyle);
path.opacity = this.globalAlpha;
path.strokeThickness = this.lineWidth;
path.strokeMiterLimit = this.miterLimit;
path.strokeLineJoin = this.lineJoin;
// Canvas does not differentiate start from end
path.strokeEndLineCap = path.strokeStartLineCap =
processLineCap(this.lineCap);
};
contextPrototype.fill = function() {
if (this.currentPath_.length == 0) return;
var path = drawShape(this, '<Path Data="%1"/>',
[this.currentPath_.join(' ')]);
// The spec says to use non zero but Silverlight uses EvenOdd by defaul
path.data.fillRule = 'NonZero';
path.fill = createBrushObject(this, this.fillStyle);
// TODO: What about even-odd etc?
};
contextPrototype.closePath = function() {
this.currentPath_.push('z');
};
/**
* Sets the transformation matrix and marks things as dirty
*/
function setM(self, m) {
self.m_ = m;
self.lastCanvas_ = null;
};
contextPrototype.save = function() {
var o = {};
copyState(this, o);
this.aStack_.push(o);
this.mStack_.push(this.m_);
setM(this, matrixMultiply(createMatrixIdentity(), this.m_));
};
contextPrototype.restore = function() {
copyState(this.aStack_.pop(), this);
setM(this, this.mStack_.pop());
};
contextPrototype.translate = function(aX, aY) {
var m1 = [
[1, 0, 0],
[0, 1, 0],
[aX, aY, 1]
];
setM(this, matrixMultiply(m1, this.m_));
};
contextPrototype.rotate = function(aRot) {
var c = Math.cos(aRot);
var s = Math.sin(aRot);
var m1 = [
[c, s, 0],
[-s, c, 0],
[0, 0, 1]
];
setM(this, matrixMultiply(m1, this.m_));
};
contextPrototype.scale = function(aX, aY) {
var m1 = [
[aX, 0, 0],
[0, aY, 0],
[0, 0, 1]
];
setM(this, matrixMultiply(m1, this.m_));
};
contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
var m1 = [
[m11, m12, 0],
[m21, m22, 0],
[ dx, dy, 1]
];
setM(this, matrixMultiply(m1, this.m_));
};
contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
setM(this, [
[m11, m12, 0],
[m21, m22, 0],
[ dx, dy, 1],
]);
};
/******** STUBS ********/
contextPrototype.clip = function() {
// TODO: Implement
};
contextPrototype.createPattern = function() {
return new CanvasPattern_;
};
// Gradient / Pattern Stubs
function CanvasGradient_() {
this.colors_ = [];
}
CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
aColor = translateColor(aColor);
this.colors_.push({offset: aOffset, color: aColor});
};
CanvasGradient_.prototype.createStops_ = function(ctx, brushObj, colors) {
var gradientStopCollection = brushObj.gradientStops;
for (var i = 0, c; c = colors[i]; i++) {
var color = translateColor(c.color);
gradientStopCollection.add(create(ctx,
'<GradientStop Color="%1" Offset="%2"/>', [color, c.offset]));
}
};
function LinearCanvasGradient_(x0, y0, x1, y1) {
CanvasGradient_.call(this);
this.x0_ = x0;
this.y0_ = y0;
this.x1_ = x1;
this.y1_ = y1;
}
LinearCanvasGradient_.prototype = new CanvasGradient_;
LinearCanvasGradient_.prototype.createBrush_ = function(ctx) {
var brushObj = create(ctx, '<LinearGradientBrush MappingMode="Absolute" ' +
'StartPoint="%1,%2" EndPoint="%3,%4"/>',
[this.x0_, this.y0_, this.x1_, this.y1_]);
this.createStops_(ctx, brushObj, this.colors_);
return brushObj;
};
function isNanOrInfinite(v) {
return isNaN(v) || !isFinite(v);
}
function RadialCanvasGradient_(x0, y0, r0, x1, y1, r1) {
if (r0 < 0 || r1 < 0 || isNanOrInfinite(x0) || isNanOrInfinite(y0) ||
isNanOrInfinite(x1) || isNanOrInfinite(y1)) {
// IE does not support DOMException so this is as close as we get.
var error = Error('DOMException.INDEX_SIZE_ERR');
error.code = 1;
throw error;
}
CanvasGradient_.call(this);
this.x0_ = x0;
this.y0_ = y0;
this.r0_ = r0;
this.x1_ = x1;
this.y1_ = y1;
this.r1_ = r1;
}
RadialCanvasGradient_.prototype = new CanvasGradient_;
CanvasGradient_.prototype.createBrush_ = function(ctx) {
if (this.x0_ == this.x1_ && this.y0_ == this.y1_ && this.r0_ == this.r1_) {
return null;
}
var radius = Math.max(this.r0_, this.r1_);
var minRadius = Math.min(this.r0_, this.r1_);
var brushObj = create(ctx, '<RadialGradientBrush MappingMode="Absolute" ' +
'GradientOrigin="%1,%2" Center="%3,%4" ' +
'RadiusX="%5" RadiusY="%5"/>',
[this.x0_, this.y0_, this.x1_, this.y1_, radius]);
var colors = this.colors_.concat();
if (this.r1_ < this.r0_) {
// reverse color stop array
colors.reverse();
for (var i = 0, c; c = colors[i]; i++) {
c.offset = 1 - c.offset;
}
}
// sort the color stops
colors.sort(function(c1, c2) {
return c1.offset - c2.offset;
});
if (minRadius > 0) {
// We need to adjust the color stops since SL always have the inner radius
// at (0, 0) so we change the stops in case the min radius is not 0.
for (var i = 0, c; c = colors[i]; i++) {
c.offset = minRadius / radius + (radius - minRadius) / radius * c.offset;
}
}
this.createStops_(ctx, brushObj, colors);
return brushObj;
};
function CanvasPattern_() {}
// set up externs
G_vmlCanvasManager = G_vmlCanvasManager_;
CanvasRenderingContext2D = CanvasRenderingContext2D_;
CanvasGradient = CanvasGradient_;
CanvasPattern = CanvasPattern_;
})();
} // if