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