Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge remote-tracking branch 'schanzer/master'

  • Loading branch information...
commit f7f80e708128c1845d825b486923c43add835263 2 parents 3ba4f14 + 299d8df
@dyoo dyoo authored
View
22 js-runtime/lib/compat/canvas-text/LICENSE
@@ -1,22 +0,0 @@
-Copyright (c) 2008 Fabien M�nager
-
-Permission is hereby granted, free of charge, to any person
-obtaining a copy of this software and associated documentation
-files (the "Software"), to deal in the Software without
-restriction, including without limitation the rights to use,
-copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the
-Software is furnished to do so, subject to the following
-conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-OTHER DEALINGS IN THE SOFTWARE.
View
473 js-runtime/lib/compat/canvas-text/canvas.text.js
@@ -1,473 +0,0 @@
-/* $Id: canvas.text.js 43 2009-10-18 20:02:39Z fabien.menager $ */
-
-/**
- * @projectDescription An cross-browser implementation of the HTML5 <canvas> text methods
- * @author Fabien Ménager
- * @version $Revision: 43 $
- * @license MIT License <http://www.opensource.org/licenses/mit-license.php>
- */
-
-/**
- * Known issues:
- * - The 'light' font weight is not supported, neither is the 'oblique' font style.
- * - Optimize the different hacks (for Opera9)
- */
-
-window.Canvas = window.Canvas || {};
-window.Canvas.Text = {
- // http://mondaybynoon.com/2007/04/02/linux-font-equivalents-to-popular-web-typefaces/
- equivalentFaces: {
- // Web popular fonts
- 'arial': ['liberation sans', 'nimbus sans l', 'freesans'],
- 'times new roman': ['liberation serif', 'linux libertine', 'freeserif'],
- 'courier new': ['dejavu sans mono', 'liberation mono', 'nimbus mono l', 'freemono'],
- 'georgia': ['nimbus roman no9 l'],
- 'helvetica': ['nimbus sans l', 'freesans'],
- 'tahoma': ['dejavu sans', 'bitstream vera sans'],
- 'verdana': ['dejavu sans', 'bitstream vera sans']
- },
- genericFaces: {
- 'serif': ['times new roman', 'georgia', 'garamond', 'bodoni', 'minion web', 'itc stone serif', 'bitstream cyberbit'],
- 'sans-serif': ['arial', 'verdana', 'trebuchet', 'tahoma', 'helvetica', 'itc avant garde gothic', 'univers', 'futura',
- 'gill sans', 'akzidenz grotesk', 'attika', 'typiko new era', 'itc stone sans', 'monotype gill sans 571'],
- 'monospace': ['courier', 'courier new', 'prestige', 'everson mono'],
- 'cursive': ['caflisch script', 'adobe poetica', 'sanvito', 'ex ponto', 'snell roundhand', 'zapf-chancery'],
- 'fantasy': ['alpha geometrique', 'critter', 'cottonwood', 'fb reactor', 'studz']
- },
- faces: {},
- scaling: 0.962,
- _styleCache: {}
-};
-
-/** The implementation of the text functions */
-(function(){
- var isOpera9 = (window.opera && navigator.userAgent.match(/Opera\/9/)), // It seems to be faster when the hacked methods are used. But there are artifacts with Opera 10.
- proto = window.CanvasRenderingContext2D ? window.CanvasRenderingContext2D.prototype : document.createElement('canvas').getContext('2d').__proto__,
- ctxt = window.Canvas.Text;
-
- // Global options
- ctxt.options = {
- fallbackCharacter: ' ', // The character that will be drawn when not present in the font face file
- dontUseMoz: false, // Don't use the builtin Firefox 3.0 functions (mozDrawText, mozPathText and mozMeasureText)
- reimplement: false, // Don't use the builtin official functions present in Chrome 2, Safari 4, and Firefox 3.1+
- debug: false, // Debug mode, not used yet
- autoload: false // Specify the directory containing the face files or false
- };
-
- function initialize(){
- var libFileName = 'canvas.text.js',
- scripts = document.getElementsByTagName("script"), i, j;
-
- for (i = 0; i < scripts.length; i++) {
- var src = scripts[i].src;
- if (src.indexOf(libFileName) != -1) {
- var parts = src.split('?');
- ctxt.basePath = parts[0].replace(libFileName, '');
- if (parts[1]) {
- var options = parts[1].split('&');
- for (j = options.length-1; j >= 0; --j) {
- var pair = options[j].split('=');
- ctxt.options[pair[0]] = pair[1];
- }
- }
- break;
- }
- }
- }
- initialize();
-
- // What is the browser's implementation ?
- var moz = !ctxt.options.dontUseMoz && proto.mozDrawText && !proto.strokeText;
-
- // If the text functions are already here : nothing to do !
- if (proto.strokeText && !ctxt.options.reimplement) {
- // This property is needed, when including the font face files
- return window._typeface_js = {loadFace: function(){}};
- }
-
- function getCSSWeightEquivalent(weight){
- switch(String(weight)) {
- case 'bolder':
- case 'bold':
- case '900':
- case '800':
- case '700': return 'bold';
- case '600':
- case '500':
- case '400':
- default:
- case 'normal': return 'normal';
- //default: return 'light';
- }
- }
-
- function getElementStyle(e){
- if (document.defaultView && document.defaultView.getComputedStyle) {
- return document.defaultView.getComputedStyle(e, null);
- }
- return e.currentStyle || e.style;
- }
-
- function getXHR(){
- if (!ctxt.xhr) {
- var methods = [
- function(){return new XMLHttpRequest()},
- function(){return new ActiveXObject('Msxml2.XMLHTTP')},
- function(){return new ActiveXObject('Microsoft.XMLHTTP')}
- ];
- for (i = 0; i < methods.length; i++) {
- try {
- ctxt.xhr = methods[i]();
- break;
- }
- catch (e) {}
- }
- }
- return ctxt.xhr;
- }
-
- function arrayContains(a, v){
- var i, l = a.length;
- for (i = l-1; i >= 0; --i) if (a[i] === v) return true;
- return false;
- }
-
- ctxt.lookupFamily = function(family){
- var faces = this.faces, face, i, f, list,
- equiv = this.equivalentFaces,
- generic = this.genericFaces;
-
- if (faces[family]) return faces[family];
-
- if (generic[family]) {
- for (i = 0; i < generic[family].length; i++) {
- if (f = this.lookupFamily(generic[family][i])) return f;
- }
- }
-
- if (!(list = equiv[family])) return false;
-
- for (i = 0; i < list.length; i++)
- if (face = faces[list[i]]) return face;
- return false;
- }
-
- ctxt.getFace = function(family, weight, style){
- var face = this.lookupFamily(family);
- if (!face) return false;
-
- if (face &&
- face[weight] &&
- face[weight][style]) return face[weight][style];
-
- if (!this.options.autoload) return false;
-
- var faceName = (family.replace(/[ -]/g, '_')+'-'+weight+'-'+style),
- xhr = this.xhr,
- url = this.basePath+this.options.autoload+'/'+faceName+'.js';
-
- xhr = getXHR();
- xhr.open("get", url, false);
- xhr.send(null);
- if(xhr.status == 200) {
- eval(xhr.responseText);
- return this.faces[family][weight][style];
- }
- else throw 'Unable to load the font ['+family+' '+weight+' '+style+']';
- return false;
- };
-
- ctxt.loadFace = function(data){
- var family = data.familyName.toLowerCase();
- this.faces[family] = this.faces[family] || {};
- this.faces[family][data.cssFontWeight] = this.faces[family][data.cssFontWeight] || {};
- this.faces[family][data.cssFontWeight][data.cssFontStyle] = data;
- return data;
- };
-
- // To use the typeface.js face files
- window._typeface_js = {faces: ctxt.faces, loadFace: ctxt.loadFace};
-
- ctxt.getFaceFromStyle = function(style){
- var weight = getCSSWeightEquivalent(style.weight),
- families = style.family, i, face;
-
- for (i = 0; i < families.length; i++) {
- if (face = this.getFace(families[i].toLowerCase(), weight, style.style)) {
- return face;
- }
- }
- return false;
- };
-
- // Default values
- // Firefox 3.5 throws an error when redefining these properties
- try {
- proto.font = "10px sans-serif";
- proto.textAlign = "start";
- proto.textBaseline = "alphabetic";
- }
- catch(e){}
-
- proto.parseStyle = function(styleText){
- if (ctxt._styleCache[styleText]) return this.getComputedStyle(ctxt._styleCache[styleText]);
-
- var style = {}, computedStyle, families;
-
- if (!this._elt) {
- this._elt = document.createElement('span');
- this.canvas.appendChild(this._elt);
- }
-
- // Default style
- this.canvas.font = '10px sans-serif';
- this._elt.style.font = styleText;
-
- computedStyle = getElementStyle(this._elt);
- style.size = computedStyle.fontSize;
- style.weight = getCSSWeightEquivalent(computedStyle.fontWeight);
- style.style = computedStyle.fontStyle;
-
- families = computedStyle.fontFamily.split(',');
- for(i = 0; i < families.length; i++) {
- families[i] = families[i].replace(/^["'\s]*/, '').replace(/["'\s]*$/, '');
- }
- style.family = families;
- return this.getComputedStyle(ctxt._styleCache[styleText] = style);
- };
-
- proto.buildStyle = function (style){
- return style.style+' '+style.weight+' '+style.size+'px "'+style.family+'"';
- };
-
- proto.renderText = function(text, style){
- var face = ctxt.getFaceFromStyle(style),
- scale = (style.size / face.resolution) * 0.75,
- offset = 0, i,
- chars = text.split(''),
- length = chars.length;
-
- if (!isOpera9) {
- this.scale(scale, -scale);
- this.lineWidth /= scale;
- }
-
- for (i = 0; i < length; i++) {
- offset += this.renderGlyph(chars[i], face, scale, offset);
- }
- };
-
- if (isOpera9) {
- proto.renderGlyph = function(c, face, scale, offset){
- var i, cpx, cpy, outline, action, glyph = face.glyphs[c], length;
-
- if (!glyph) return;
-
- if (glyph.o) {
- outline = glyph._cachedOutline || (glyph._cachedOutline = glyph.o.split(' '));
- length = outline.length;
- for (i = 0; i < length; ) {
- action = outline[i++];
-
- switch(action) {
- case 'm':
- this.moveTo(outline[i++]*scale+offset, outline[i++]*-scale);
- break;
- case 'l':
- this.lineTo(outline[i++]*scale+offset, outline[i++]*-scale);
- break;
- case 'q':
- cpx = outline[i++]*scale+offset;
- cpy = outline[i++]*-scale;
- this.quadraticCurveTo(outline[i++]*scale+offset, outline[i++]*-scale, cpx, cpy);
- break;
- }
- }
- }
- return glyph.ha*scale;
- };
- }
- else {
- proto.renderGlyph = function(c, face){
- var i, cpx, cpy, outline, action, glyph = face.glyphs[c], length;
-
- if (!glyph) return;
-
- if (glyph.o) {
- outline = glyph._cachedOutline || (glyph._cachedOutline = glyph.o.split(' '));
- length = outline.length;
- for (i = 0; i < length; ) {
- action = outline[i++];
-
- switch(action) {
- case 'm':
- this.moveTo(outline[i++], outline[i++]);
- break;
- case 'l':
- this.lineTo(outline[i++], outline[i++]);
- break;
- case 'q':
- cpx = outline[i++];
- cpy = outline[i++];
- this.quadraticCurveTo(outline[i++], outline[i++], cpx, cpy);
- break;
- }
- }
- }
- if (glyph.ha) this.translate(glyph.ha, 0);
- };
- }
-
- proto.getTextExtents = function(text, style){
- var width = 0, height = 0, ha = 0,
- face = ctxt.getFaceFromStyle(style),
- i, length = text.length, glyph;
-
- for (i = 0; i < length; i++) {
- glyph = face.glyphs[text.charAt(i)] || face.glyphs[ctxt.options.fallbackCharacter];
- width += Math.max(glyph.ha, glyph.x_max);
- ha += glyph.ha;
- }
-
- return {
- width: width,
- height: face.lineHeight,
- ha: ha
- };
- };
-
- proto.getComputedStyle = function(style){
- var p, canvasStyle = getElementStyle(this.canvas),
- computedStyle = {},
- s = style.size,
- canvasFontSize = parseFloat(canvasStyle.fontSize),
- fontSize = parseFloat(s);
-
- for (p in style) {
- computedStyle[p] = style[p];
- }
-
- // Compute the size
- if (typeof s === 'number' || s.indexOf('px') != -1)
- computedStyle.size = fontSize;
- else if (s.indexOf('em') != -1)
- computedStyle.size = canvasFontSize * fontSize;
- else if (s.indexOf('%') != -1)
- computedStyle.size = (canvasFontSize / 100) * fontSize;
- else if (s.indexOf('pt') != -1)
- computedStyle.size = fontSize / 0.75;
- else
- computedStyle.size = canvasFontSize;
-
- return computedStyle;
- };
-
- proto.getTextOffset = function(text, style, face){
- var canvasStyle = getElementStyle(this.canvas),
- metrics = this.measureText(text),
- scale = (style.size / face.resolution) * 0.75,
- offset = {x: 0, y: 0, metrics: metrics, scale: scale};
-
- switch (this.textAlign) {
- default:
- case null:
- case 'left': break;
- case 'center': offset.x = -metrics.width/2; break;
- case 'right': offset.x = -metrics.width; break;
- case 'start': offset.x = (canvasStyle.direction == 'rtl') ? -metrics.width : 0; break;
- case 'end': offset.x = (canvasStyle.direction == 'ltr') ? -metrics.width : 0; break;
- }
-
- switch (this.textBaseline) {
- case 'alphabetic': break;
- default:
- case null:
- case 'ideographic':
- case 'bottom': offset.y = face.descender; break;
- case 'hanging':
- case 'top': offset.y = face.ascender; break;
- case 'middle': offset.y = (face.ascender + face.descender) / 2; break;
- }
- offset.y *= scale;
- return offset;
- };
-
- proto.drawText = function(text, x, y, maxWidth, stroke){
- var style = this.parseStyle(this.font),
- face = ctxt.getFaceFromStyle(style),
- offset = this.getTextOffset(text, style, face);
-
- this.save();
- this.translate(x + offset.x, y + offset.y);
- if (face.strokeFont && !stroke) {
- this.strokeStyle = this.fillStyle;
- }
- this.beginPath();
- if (moz) {
- this.mozTextStyle = this.buildStyle(style);
- this[stroke ? 'mozPathText' : 'mozDrawText'](text);
- }
- else {
- this.scale(ctxt.scaling, ctxt.scaling);
- this.renderText(text, style);
- if (face.strokeFont) {
- this.lineWidth = style.size * (style.weight == 'bold' ? 0.5 : 0.3);
- }
- }
- // dyoo: for some reason, the drawing isn't taking unless
- // we call this twice. I don't know why yet.
- this[(stroke || (face.strokeFont && !moz)) ? 'stroke' : 'fill']();
- this[(stroke || (face.strokeFont && !moz)) ? 'stroke' : 'fill']();
- this.closePath();
- this.restore();
-
-
-
-
- if (ctxt.options.debug) {
- var left = Math.floor(offset.x + x) + 0.5,
- top = Math.floor(y)+0.5;
- this.save();
- this.strokeStyle = '#F00';
- this.lineWidth = 0.5;
- this.beginPath();
- // Text baseline
- this.moveTo(left + offset.metrics.width, top);
- this.lineTo(left, top);
- // Text align
- this.moveTo(left - offset.x, top + offset.y);
- this.lineTo(left - offset.x, top + offset.y - style.size);
-
- this.stroke();
- this.closePath();
- this.restore();
-
- }
- };
-
- proto.fillText = function(text, x, y, maxWidth){
- this.drawText(text, x, y, maxWidth, false);
- };
-
- proto.strokeText = function(text, x, y, maxWidth){
- this.drawText(text, x, y, maxWidth, true);
- };
-
- proto.measureText = function(text){
- var style = this.parseStyle(this.font),
- dim = {width: 0};
-
- if (moz) {
- this.mozTextStyle = this.buildStyle(style);
- dim.width = this.mozMeasureText(text);
- }
- else {
- var face = ctxt.getFaceFromStyle(style),
- scale = (style.size / face.resolution) * 0.75;
-
- dim.width = this.getTextExtents(text, style).ha * scale * ctxt.scaling;
- }
-
- return dim;
- };
-})();
View
1  js-runtime/lib/compat/canvas-text/faces/optimer-normal-normal.js
0 additions, 1 deletion not shown
View
945 js-runtime/lib/compat/excanvas.js
@@ -15,7 +15,7 @@
// Known Issues:
//
-// * Patterns are not implemented.
+// * Patterns only support repeat.
// * Radial gradient are not implemented. The VML version of these look very
// different from the canvas one.
// * Clipping paths are not implemented.
@@ -47,6 +47,8 @@ if (!document.createElement('canvas').getContext) {
// this is used for sub pixel precision
var Z = 10;
var Z2 = Z / 2;
+
+ var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];
/**
* This funtion is assigned to the <canvas> elements as element.getContext().
@@ -83,41 +85,43 @@ if (!document.createElement('canvas').getContext) {
};
}
+ function encodeHtmlAttribute(s) {
+ return String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
+ }
+
+ function addNamespace(doc, prefix, urn) {
+ if (!doc.namespaces[prefix]) {
+ doc.namespaces.add(prefix, urn, '#default#VML');
+ }
+ }
+
+ function addNamespacesAndStylesheet(doc) {
+ addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml');
+ addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office');
+
+ // Setup default CSS. Only add one style sheet per document
+ if (!doc.styleSheets['ex_canvas_']) {
+ var ss = doc.createStyleSheet();
+ ss.owningElement.id = 'ex_canvas_';
+ ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
+ // default size is 300x150 in Gecko and Opera
+ 'text-align:left;width:300px;height:150px}';
+ }
+ }
+
+ // Add namespaces and stylesheet at startup.
+ addNamespacesAndStylesheet(document);
+
var G_vmlCanvasManager_ = {
init: function(opt_doc) {
- if (/MSIE/.test(navigator.userAgent) && !window.opera) {
- var doc = opt_doc || document;
- // Create a dummy element so that IE will allow canvas elements to be
- // recognized.
- doc.createElement('canvas');
- doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
- }
+ var doc = opt_doc || document;
+ // Create a dummy element so that IE will allow canvas elements to be
+ // recognized.
+ doc.createElement('canvas');
+ doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
},
init_: function(doc) {
- // create xmlns
- if (!doc.namespaces['g_vml_']) {
- doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml',
- '#default#VML');
-
- }
- if (!doc.namespaces['g_o_']) {
- doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office',
- '#default#VML');
- }
-
- // Setup default CSS. Only add one style sheet per document
- if (!doc.styleSheets['ex_canvas_']) {
- var ss = doc.createStyleSheet();
- ss.owningElement.id = 'ex_canvas_';
- ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
- // default size is 300x150 in Gecko and Opera
- 'text-align:left;width:300px;height:150px}' +
- 'g_vml_\\:*{behavior:url(#default#VML)}' +
- 'g_o_\\:*{behavior:url(#default#VML)}';
-
- }
-
// find all canvas elements
var els = doc.getElementsByTagName('canvas');
for (var i = 0; i < els.length; i++) {
@@ -135,9 +139,11 @@ if (!document.createElement('canvas').getContext) {
*/
initElement: function(el) {
if (!el.getContext) {
-
el.getContext = getContext;
+ // Add namespaces and stylesheet to document of the element.
+ addNamespacesAndStylesheet(el.ownerDocument);
+
// Remove fallback content. There is no way to hide text nodes so we
// just remove all childNodes. We could hide all elements and remove
// text nodes but who really cares about the fallback content.
@@ -168,17 +174,35 @@ if (!document.createElement('canvas').getContext) {
}
};
+ function createClip(el){
+ var clip = document.createElement('div');
+ clip.className = 'ExCanvasClippingDiv';
+ clip.style.width = el.style.width;
+ clip.style.height = el.style.height;
+ clip.style.left = '0px';
+ clip.style.right = '0px';
+ clip.style.position = 'absolute';
+ clip.style.padding = '0px';
+ clip.style.overflow = 'hidden';
+ el.appendChild(clip);
+ return clip;
+ }
+
+
function onPropertyChange(e) {
var el = e.srcElement;
switch (e.propertyName) {
case 'width':
- el.style.width = el.attributes.width.nodeValue + 'px';
el.getContext().clearRect();
+ el.style.width = el.attributes.width.nodeValue + 'px';
+ // In IE8 this does not trigger onresize.
+ el.firstChild.style.width = el.clientWidth + 'px';
break;
case 'height':
- el.style.height = el.attributes.height.nodeValue + 'px';
el.getContext().clearRect();
+ el.style.height = el.attributes.height.nodeValue + 'px';
+ el.firstChild.style.height = el.clientHeight + 'px';
break;
}
}
@@ -194,10 +218,10 @@ if (!document.createElement('canvas').getContext) {
G_vmlCanvasManager_.init();
// precompute "00" to "FF"
- var dec2hex = [];
+ var decToHex = [];
for (var i = 0; i < 16; i++) {
for (var j = 0; j < 16; j++) {
- dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
+ decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
}
}
@@ -238,58 +262,331 @@ if (!document.createElement('canvas').getContext) {
o2.shadowOffsetY = o1.shadowOffsetY;
o2.strokeStyle = o1.strokeStyle;
o2.globalAlpha = o1.globalAlpha;
+ o2.font = o1.font;
+ o2.textAlign = o1.textAlign;
+ o2.textBaseline = o1.textBaseline;
o2.arcScaleX_ = o1.arcScaleX_;
o2.arcScaleY_ = o1.arcScaleY_;
o2.lineScale_ = o1.lineScale_;
+ o2.rotation_ = o1.rotation_; // used for images
+ }
+
+ var colorData = {
+ aliceblue: '#F0F8FF',
+ antiquewhite: '#FAEBD7',
+ aquamarine: '#7FFFD4',
+ azure: '#F0FFFF',
+ beige: '#F5F5DC',
+ bisque: '#FFE4C4',
+ black: '#000000',
+ blanchedalmond: '#FFEBCD',
+ blueviolet: '#8A2BE2',
+ brown: '#A52A2A',
+ burlywood: '#DEB887',
+ cadetblue: '#5F9EA0',
+ chartreuse: '#7FFF00',
+ chocolate: '#D2691E',
+ coral: '#FF7F50',
+ cornflowerblue: '#6495ED',
+ cornsilk: '#FFF8DC',
+ crimson: '#DC143C',
+ cyan: '#00FFFF',
+ darkblue: '#00008B',
+ darkcyan: '#008B8B',
+ darkgoldenrod: '#B8860B',
+ darkgray: '#A9A9A9',
+ darkgreen: '#006400',
+ darkgrey: '#A9A9A9',
+ darkkhaki: '#BDB76B',
+ darkmagenta: '#8B008B',
+ darkolivegreen: '#556B2F',
+ darkorange: '#FF8C00',
+ darkorchid: '#9932CC',
+ darkred: '#8B0000',
+ darksalmon: '#E9967A',
+ darkseagreen: '#8FBC8F',
+ darkslateblue: '#483D8B',
+ darkslategray: '#2F4F4F',
+ darkslategrey: '#2F4F4F',
+ darkturquoise: '#00CED1',
+ darkviolet: '#9400D3',
+ deeppink: '#FF1493',
+ deepskyblue: '#00BFFF',
+ dimgray: '#696969',
+ dimgrey: '#696969',
+ dodgerblue: '#1E90FF',
+ firebrick: '#B22222',
+ floralwhite: '#FFFAF0',
+ forestgreen: '#228B22',
+ gainsboro: '#DCDCDC',
+ ghostwhite: '#F8F8FF',
+ gold: '#FFD700',
+ goldenrod: '#DAA520',
+ grey: '#808080',
+ greenyellow: '#ADFF2F',
+ honeydew: '#F0FFF0',
+ hotpink: '#FF69B4',
+ indianred: '#CD5C5C',
+ indigo: '#4B0082',
+ ivory: '#FFFFF0',
+ khaki: '#F0E68C',
+ lavender: '#E6E6FA',
+ lavenderblush: '#FFF0F5',
+ lawngreen: '#7CFC00',
+ lemonchiffon: '#FFFACD',
+ lightblue: '#ADD8E6',
+ lightcoral: '#F08080',
+ lightcyan: '#E0FFFF',
+ lightgoldenrodyellow: '#FAFAD2',
+ lightgreen: '#90EE90',
+ lightgrey: '#D3D3D3',
+ lightpink: '#FFB6C1',
+ lightsalmon: '#FFA07A',
+ lightseagreen: '#20B2AA',
+ lightskyblue: '#87CEFA',
+ lightslategray: '#778899',
+ lightslategrey: '#778899',
+ lightsteelblue: '#B0C4DE',
+ lightyellow: '#FFFFE0',
+ limegreen: '#32CD32',
+ linen: '#FAF0E6',
+ magenta: '#FF00FF',
+ mediumaquamarine: '#66CDAA',
+ mediumblue: '#0000CD',
+ mediumorchid: '#BA55D3',
+ mediumpurple: '#9370DB',
+ mediumseagreen: '#3CB371',
+ mediumslateblue: '#7B68EE',
+ mediumspringgreen: '#00FA9A',
+ mediumturquoise: '#48D1CC',
+ mediumvioletred: '#C71585',
+ midnightblue: '#191970',
+ mintcream: '#F5FFFA',
+ mistyrose: '#FFE4E1',
+ moccasin: '#FFE4B5',
+ navajowhite: '#FFDEAD',
+ oldlace: '#FDF5E6',
+ olivedrab: '#6B8E23',
+ orange: '#FFA500',
+ orangered: '#FF4500',
+ orchid: '#DA70D6',
+ palegoldenrod: '#EEE8AA',
+ palegreen: '#98FB98',
+ paleturquoise: '#AFEEEE',
+ palevioletred: '#DB7093',
+ papayawhip: '#FFEFD5',
+ peachpuff: '#FFDAB9',
+ peru: '#CD853F',
+ pink: '#FFC0CB',
+ plum: '#DDA0DD',
+ powderblue: '#B0E0E6',
+ rosybrown: '#BC8F8F',
+ royalblue: '#4169E1',
+ saddlebrown: '#8B4513',
+ salmon: '#FA8072',
+ sandybrown: '#F4A460',
+ seagreen: '#2E8B57',
+ seashell: '#FFF5EE',
+ sienna: '#A0522D',
+ skyblue: '#87CEEB',
+ slateblue: '#6A5ACD',
+ slategray: '#708090',
+ slategrey: '#708090',
+ snow: '#FFFAFA',
+ springgreen: '#00FF7F',
+ steelblue: '#4682B4',
+ tan: '#D2B48C',
+ thistle: '#D8BFD8',
+ tomato: '#FF6347',
+ turquoise: '#40E0D0',
+ violet: '#EE82EE',
+ wheat: '#F5DEB3',
+ whitesmoke: '#F5F5F5',
+ yellowgreen: '#9ACD32'
+ };
+
+
+ function getRgbHslContent(styleString) {
+ var start = styleString.indexOf('(', 3);
+ var end = styleString.indexOf(')', start + 1);
+ var parts = styleString.substring(start + 1, end).split(',');
+ // add alpha if needed
+ if (parts.length != 4 || styleString.charAt(3) != 'a') {
+ parts[3] = 1;
+ }
+ return parts;
+ }
+
+ function percent(s) {
+ return parseFloat(s) / 100;
}
+ function clamp(v, min, max) {
+ return Math.min(max, Math.max(min, v));
+ }
+
+ function hslToRgb(parts){
+ var r, g, b, h, s, l;
+ h = parseFloat(parts[0]) / 360 % 360;
+ if (h < 0)
+ h++;
+ s = clamp(percent(parts[1]), 0, 1);
+ l = clamp(percent(parts[2]), 0, 1);
+ if (s == 0) {
+ r = g = b = l; // achromatic
+ } else {
+ var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ var p = 2 * l - q;
+ r = hueToRgb(p, q, h + 1 / 3);
+ g = hueToRgb(p, q, h);
+ b = hueToRgb(p, q, h - 1 / 3);
+ }
+
+ return '#' + decToHex[Math.floor(r * 255)] +
+ decToHex[Math.floor(g * 255)] +
+ decToHex[Math.floor(b * 255)];
+ }
+
+ function hueToRgb(m1, m2, h) {
+ if (h < 0)
+ h++;
+ if (h > 1)
+ h--;
+
+ if (6 * h < 1)
+ return m1 + (m2 - m1) * 6 * h;
+ else if (2 * h < 1)
+ return m2;
+ else if (3 * h < 2)
+ return m1 + (m2 - m1) * (2 / 3 - h) * 6;
+ else
+ return m1;
+ }
+
+ var processStyleCache = {};
+
function processStyle(styleString) {
+ if (styleString in processStyleCache) {
+ return processStyleCache[styleString];
+ }
+
var str, alpha = 1;
styleString = String(styleString);
- if (styleString.substring(0, 3) == 'rgb') {
- var start = styleString.indexOf('(', 3);
- var end = styleString.indexOf(')', start + 1);
- var guts = styleString.substring(start + 1, end).split(',');
-
- str = '#';
+ if (styleString.charAt(0) == '#') {
+ str = styleString;
+ } else if (/^rgb/.test(styleString)) {
+ var parts = getRgbHslContent(styleString);
+ var str = '#', n;
for (var i = 0; i < 3; i++) {
- str += dec2hex[Number(guts[i])];
+ if (parts[i].indexOf('%') != -1) {
+ n = Math.floor(percent(parts[i]) * 255);
+ } else {
+ n = +parts[i];
+ }
+ str += decToHex[clamp(n, 0, 255)];
}
+ alpha = +parts[3];
+ } else if (/^hsl/.test(styleString)) {
+ var parts = getRgbHslContent(styleString);
+ str = hslToRgb(parts);
+ alpha = parts[3];
+ } else {
+ str = colorData[styleString] || styleString;
+ }
+ return processStyleCache[styleString] = {color: str, alpha: alpha};
+ }
- if (guts.length == 4 && styleString.substr(3, 1) == 'a') {
- alpha = guts[3];
- }
+ var DEFAULT_STYLE = {
+ style: 'normal',
+ variant: 'normal',
+ weight: 'normal',
+ size: 10,
+ family: 'sans-serif'
+ };
+
+ // Internal text style cache
+ var fontStyleCache = {};
+
+ function processFontStyle(styleString) {
+ if (fontStyleCache[styleString]) {
+ return fontStyleCache[styleString];
+ }
+
+ var el = document.createElement('div');
+ var style = el.style;
+ try {
+ style.font = styleString;
+ } catch (ex) {
+ // Ignore failures to set to invalid font.
+ }
+
+ return fontStyleCache[styleString] = {
+ style: style.fontStyle || DEFAULT_STYLE.style,
+ variant: style.fontVariant || DEFAULT_STYLE.variant,
+ weight: style.fontWeight || DEFAULT_STYLE.weight,
+ size: style.fontSize || DEFAULT_STYLE.size,
+ family: style.fontFamily || DEFAULT_STYLE.family
+ };
+ }
+
+ function getComputedStyle(style, element) {
+ var computedStyle = {};
+
+ for (var p in style) {
+ computedStyle[p] = style[p];
+ }
+
+ if(computedStyle.family && computedStyle.family.indexOf(' ')>-1){
+ computedStyle.family = computedStyle.family.split(' ')[1];
+ }
+
+ // Compute the size
+ var canvasFontSize = parseFloat(element.currentStyle.fontSize),
+ fontSize = parseFloat(style.size);
+
+ if (typeof style.size == 'number') {
+ computedStyle.size = style.size;
+ } else if (style.size.indexOf('px') != -1) {
+ computedStyle.size = fontSize;
+ } else if (style.size.indexOf('em') != -1) {
+ computedStyle.size = canvasFontSize * fontSize;
+ } else if(style.size.indexOf('%') != -1) {
+ computedStyle.size = (canvasFontSize / 100) * fontSize;
+ } else if (style.size.indexOf('pt') != -1) {
+ computedStyle.size = fontSize / .75;
} else {
- str = styleString;
+ computedStyle.size = canvasFontSize;
}
- return {color: str, alpha: alpha};
+ // Different scaling between normal text and VML text. This was found using
+ // trial and error to get the same size as non VML text.
+ computedStyle.size *= 0.981;
+
+ return computedStyle;
}
+ var lineCapMap = {
+ 'butt': 'flat',
+ 'round': 'round'
+ };
+
function processLineCap(lineCap) {
- switch (lineCap) {
- case 'butt':
- return 'flat';
- case 'round':
- return 'round';
- case 'square':
- default:
- return 'square';
- }
+ return lineCapMap[lineCap] || 'square';
}
/**
* This class implements CanvasRenderingContext2D interface as described by
* the WHATWG.
- * @param {HTMLElement} surfaceElement The element that the 2D context should
+ * @param {HTMLElement} canvasElement The element that the 2D context should
* be associated with
*/
- function CanvasRenderingContext2D_(surfaceElement) {
+ function CanvasRenderingContext2D_(canvasElement) {
this.m_ = createMatrixIdentity();
this.mStack_ = [];
this.aStack_ = [];
+ this.cStack_ = [];
this.currentPath_ = [];
// Canvas context properties
@@ -301,24 +598,41 @@ if (!document.createElement('canvas').getContext) {
this.lineCap = 'butt';
this.miterLimit = Z * 1;
this.globalAlpha = 1;
- this.canvas = surfaceElement;
-
- var el = surfaceElement.ownerDocument.createElement('div');
- el.style.width = surfaceElement.clientWidth + 'px';
- el.style.height = surfaceElement.clientHeight + 'px';
- el.style.overflow = 'hidden';
- el.style.position = 'absolute';
- surfaceElement.appendChild(el);
+ this.font = '10px sans-serif';
+ this.textAlign = 'left';
+ this.textBaseline = 'alphabetic';
+ this.canvas = canvasElement;
+
+ var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' +
+ canvasElement.clientHeight + 'px;overflow:hidden;position:absolute';
+ var el = canvasElement.ownerDocument.createElement('div');
+ el.style.cssText = cssText;
+ canvasElement.appendChild(el);
+
+ var overlayEl = el.cloneNode(false);
+ // Use a non transparent background.
+ overlayEl.style.backgroundColor = 'red';
+ overlayEl.style.filter = 'alpha(opacity=0)';
+ canvasElement.appendChild(overlayEl);
this.element_ = el;
this.arcScaleX_ = 1;
this.arcScaleY_ = 1;
this.lineScale_ = 1;
+ this.rotation_ = 0;
+
+ this.clip_ = createClip(canvasElement);
+ this.cStack_.push(this.clip_);
+
}
var contextPrototype = CanvasRenderingContext2D_.prototype;
contextPrototype.clearRect = function() {
- this.element_.innerHTML = '';
+ if (this.textMeasureEl_) {
+ this.textMeasureEl_.removeNode(true);
+ this.textMeasureEl_ = null;
+ }
+ this.clip_.innerHTML = '';
};
contextPrototype.beginPath = function() {
@@ -328,14 +642,14 @@ if (!document.createElement('canvas').getContext) {
};
contextPrototype.moveTo = function(aX, aY) {
- var p = this.getCoords_(aX, aY);
+ var p = getCoords(this, aX, aY);
this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
this.currentX_ = p.x;
this.currentY_ = p.y;
};
contextPrototype.lineTo = function(aX, aY) {
- var p = this.getCoords_(aX, aY);
+ var p = getCoords(this, aX, aY);
this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
this.currentX_ = p.x;
@@ -345,9 +659,9 @@ if (!document.createElement('canvas').getContext) {
contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
aCP2x, aCP2y,
aX, aY) {
- var p = this.getCoords_(aX, aY);
- var cp1 = this.getCoords_(aCP1x, aCP1y);
- var cp2 = this.getCoords_(aCP2x, aCP2y);
+ var p = getCoords(this, aX, aY);
+ var cp1 = getCoords(this, aCP1x, aCP1y);
+ var cp2 = getCoords(this, aCP2x, aCP2y);
bezierCurveTo(this, cp1, cp2, p);
};
@@ -370,8 +684,8 @@ if (!document.createElement('canvas').getContext) {
// the following is lifted almost directly from
// http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
- var cp = this.getCoords_(aCPx, aCPy);
- var p = this.getCoords_(aX, aY);
+ var cp = getCoords(this, aCPx, aCPy);
+ var p = getCoords(this, aX, aY);
var cp1 = {
x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
@@ -402,9 +716,9 @@ if (!document.createElement('canvas').getContext) {
// that can be represented in binary
}
- var p = this.getCoords_(aX, aY);
- var pStart = this.getCoords_(xStart, yStart);
- var pEnd = this.getCoords_(xEnd, yEnd);
+ var p = getCoords(this, aX, aY);
+ var pStart = getCoords(this, xStart, yStart);
+ var pEnd = getCoords(this, xEnd, yEnd);
this.currentPath_.push({type: arcType,
x: p.x,
@@ -474,23 +788,30 @@ if (!document.createElement('canvas').getContext) {
return gradient;
};
- contextPrototype.drawImage = function(image, var_args) {
+ contextPrototype.drawImage = function(image) {
var dx, dy, dw, dh, sx, sy, sw, sh;
-
+
+ // to fix new Image() we check the existance of runtimeStyle
+ var rts = image.runtimeStyle.width;
+
// to find the original width we overide the width and height
- var oldRuntimeWidth = image.runtimeStyle.width;
- var oldRuntimeHeight = image.runtimeStyle.height;
- image.runtimeStyle.width = 'auto';
- image.runtimeStyle.height = 'auto';
+ if(rts) {
+ var oldRuntimeWidth = image.runtimeStyle.width;
+ var oldRuntimeHeight = image.runtimeStyle.height;
+ image.runtimeStyle.width = 'auto';
+ image.runtimeStyle.height = 'auto';
+ }
// get the original size
var w = image.width;
var h = image.height;
-
+
// and remove overides
- image.runtimeStyle.width = oldRuntimeWidth;
- image.runtimeStyle.height = oldRuntimeHeight;
-
+ if(rts) {
+ image.runtimeStyle.width = oldRuntimeWidth;
+ image.runtimeStyle.height = oldRuntimeHeight;
+ }
+
if (arguments.length == 3) {
dx = arguments[1];
dy = arguments[2];
@@ -518,7 +839,7 @@ if (!document.createElement('canvas').getContext) {
throw Error('Invalid number of arguments');
}
- var d = this.getCoords_(dx, dy);
+ var d = getCoords(this, dx, dy);
var w2 = sw / 2;
var h2 = sh / 2;
@@ -527,67 +848,98 @@ if (!document.createElement('canvas').getContext) {
var W = 10;
var H = 10;
-
- // For some reason that I've now forgotten, using divs didn't work
- vmlStr.push(' <g_vml_:group',
- ' coordsize="', Z * W, ',', Z * H, '"',
- ' coordorigin="0,0"' ,
- ' style="width:', W, 'px;height:', H, 'px;position:absolute;');
+
+ var scaleX = scaleY = 1;
+
+ // FIX: divs give better quality then vml image and also fixes transparent PNG's
+ vmlStr.push(' <div style="position:absolute;');
// If filters are necessary (rotation exists), create them
// filters are bog-slow, so only create them if abbsolutely necessary
// The following check doesn't account for skews (which don't exist
// in the canvas spec (yet) anyway.
-
- if (this.m_[0][0] != 1 || this.m_[0][1]) {
+
+ if (this.m_[0][0] != 1 || this.m_[0][1] ||
+ this.m_[1][1] != 1 || this.m_[1][0]) {
var filter = [];
- // Note the 12/21 reversal
- filter.push('M11=', this.m_[0][0], ',',
- 'M12=', this.m_[1][0], ',',
- 'M21=', this.m_[0][1], ',',
- 'M22=', this.m_[1][1], ',',
- 'Dx=', mr(d.x / Z), ',',
- 'Dy=', mr(d.y / Z), '');
+ // Scaling images using width & height instead of Transform Matrix
+ // because of quality loss
+ var c = mc(this.rotation_);
+ var s = ms(this.rotation_);
+
+ // Inverse rotation matrix
+ var irm = [
+ [c, -s, 0],
+ [s, c, 0],
+ [0, 0, 1]
+ ];
+
+ // Get unrotated matrix to get only scaling values
+ var urm = matrixMultiply(irm, this.m_);
+ scaleX = urm[0][0];
+ scaleY = urm[1][1];
+
+ // Apply only rotation and translation to Matrix
+ filter.push('M11=', c, ',',
+ 'M12=', -s, ',',
+ 'M21=', s, ',',
+ 'M22=', c, ',',
+ 'Dx=', d.x / Z, ',',
+ 'Dy=', d.y / Z);
// Bounding box calculation (need to minimize displayed area so that
// filters don't waste time on unused pixels.
var max = d;
- var c2 = this.getCoords_(dx + dw, dy);
- var c3 = this.getCoords_(dx, dy + dh);
- var c4 = this.getCoords_(dx + dw, dy + dh);
+ var c2 = getCoords(this, dx + dw, dy);
+ var c3 = getCoords(this, dx, dy + dh);
+ var c4 = getCoords(this, dx + dw, dy + dh);
max.x = m.max(max.x, c2.x, c3.x, c4.x);
max.y = m.max(max.y, c2.y, c3.y, c4.y);
vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z),
'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
- filter.join(''), ", sizingmethod='clip');")
+ filter.join(''), ", sizingmethod='clip');");
+
} else {
vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
}
-
- vmlStr.push(' ">' ,
- '<g_vml_:image src="', image.src, '"',
- ' style="width:', Z * dw, 'px;',
- ' height:', Z * dh, 'px;"',
- ' cropleft="', sx / w, '"',
- ' croptop="', sy / h, '"',
- ' cropright="', (w - sx - sw) / w, '"',
- ' cropbottom="', (h - sy - sh) / h, '"',
- ' />',
- '</g_vml_:group>');
-
- this.element_.insertAdjacentHTML('BeforeEnd',
- vmlStr.join(''));
+
+ vmlStr.push(' ">');
+
+ // Draw a special cropping div if needed
+ if (sx || sy) {
+ // Apply scales to width and height
+ vmlStr.push('<div style="overflow: hidden; width:', Math.ceil((dw + sx * dw / sw) * scaleX), 'px;',
+ ' height:', Math.ceil((dh + sy * dh / sh) * scaleY), 'px;',
+ ' filter:progid:DxImageTransform.Microsoft.Matrix(Dx=',
+ -sx * dw / sw * scaleX, ',Dy=', -sy * dh / sh * scaleY, ');">');
+ }
+
+ // Apply scales to width and height
+ vmlStr.push('<div style="width:', Math.round(scaleX * w * dw / sw), 'px;',
+ ' height:', Math.round(scaleY * h * dh / sh), 'px;',
+ ' filter:');
+
+ // If there is a globalAlpha, apply it to image
+ if(this.globalAlpha < 1) {
+ vmlStr.push(' progid:DXImageTransform.Microsoft.Alpha(opacity=' + (this.globalAlpha * 100) + ')');
+ }
+
+ vmlStr.push(' progid:DXImageTransform.Microsoft.AlphaImageLoader(src=', image.src, ',sizingMethod=scale)">');
+
+ // Close the crop div if necessary
+ if (sx || sy) vmlStr.push('</div>');
+
+ vmlStr.push('</div></div>');
+
+ this.clip_.insertAdjacentHTML('beforeEnd', vmlStr.join(''));
};
contextPrototype.stroke = function(aFill) {
var lineStr = [];
var lineOpen = false;
- var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
- var color = a.color;
- var opacity = a.alpha * this.globalAlpha;
var W = 10;
var H = 10;
@@ -595,7 +947,8 @@ if (!document.createElement('canvas').getContext) {
lineStr.push('<g_vml_:shape',
' filled="', !!aFill, '"',
' style="position:absolute;width:', W, 'px;height:', H, 'px;"',
- ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"',
+ ' coordorigin="0,0"',
+ ' coordsize="', Z * W, ',', Z * H, '"',
' stroked="', !aFill, '"',
' path="');
@@ -661,25 +1014,47 @@ if (!document.createElement('canvas').getContext) {
lineStr.push(' ">');
if (!aFill) {
- var lineWidth = this.lineScale_ * this.lineWidth;
+ appendStroke(this, lineStr);
+ } else {
+ appendFill(this, lineStr, min, max);
+ }
- // VML cannot correctly render a line if the width is less than 1px.
- // In that case, we dilute the color to make the line look thinner.
- if (lineWidth < 1) {
- opacity *= lineWidth;
- }
+ lineStr.push('</g_vml_:shape>');
+
+ this.clip_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+ };
+
+ function appendStroke(ctx, lineStr) {
+ var a = processStyle(ctx.strokeStyle);
+ var color = a.color;
+ var opacity = a.alpha * ctx.globalAlpha;
+ var lineWidth = ctx.lineScale_ * ctx.lineWidth;
- lineStr.push(
- '<g_vml_:stroke',
- ' opacity="', opacity, '"',
- ' joinstyle="', this.lineJoin, '"',
- ' miterlimit="', this.miterLimit, '"',
- ' endcap="', processLineCap(this.lineCap), '"',
- ' weight="', lineWidth, 'px"',
- ' color="', color, '" />'
- );
- } else if (typeof this.fillStyle == 'object') {
- var fillStyle = this.fillStyle;
+ // VML cannot correctly render a line if the width is less than 1px.
+ // In that case, we dilute the color to make the line look thinner.
+ if (lineWidth < 1) {
+ opacity *= lineWidth;
+ }
+
+ lineStr.push(
+ '<g_vml_:stroke',
+ ' opacity="', opacity, '"',
+ ' joinstyle="', ctx.lineJoin, '"',
+ ' miterlimit="', ctx.miterLimit, '"',
+ ' endcap="', processLineCap(ctx.lineCap), '"',
+ ' weight="', lineWidth, 'px"',
+ ' color="', color, '" />'
+ );
+ }
+
+ function appendFill(ctx, lineStr, min, max) {
+ var fillStyle = ctx.fillStyle;
+ var arcScaleX = ctx.arcScaleX_;
+ var arcScaleY = ctx.arcScaleY_;
+ var width = max.x - min.x;
+ var height = max.y - min.y;
+ if (fillStyle instanceof CanvasGradient_) {
+ // TODO: Gradients transformed with the transformation matrix.
var angle = 0;
var focus = {x: 0, y: 0};
@@ -689,12 +1064,12 @@ if (!document.createElement('canvas').getContext) {
var expansion = 1;
if (fillStyle.type_ == 'gradient') {
- var x0 = fillStyle.x0_ / this.arcScaleX_;
- var y0 = fillStyle.y0_ / this.arcScaleY_;
- var x1 = fillStyle.x1_ / this.arcScaleX_;
- var y1 = fillStyle.y1_ / this.arcScaleY_;
- var p0 = this.getCoords_(x0, y0);
- var p1 = this.getCoords_(x1, y1);
+ var x0 = fillStyle.x0_ / arcScaleX;
+ var y0 = fillStyle.y0_ / arcScaleY;
+ var x1 = fillStyle.x1_ / arcScaleX;
+ var y1 = fillStyle.y1_ / arcScaleY;
+ var p0 = getCoords(ctx, x0, y0);
+ var p1 = getCoords(ctx, x1, y1);
var dx = p1.x - p0.x;
var dy = p1.y - p0.y;
angle = Math.atan2(dx, dy) * 180 / Math.PI;
@@ -710,16 +1085,14 @@ if (!document.createElement('canvas').getContext) {
angle = 0;
}
} else {
- var p0 = this.getCoords_(fillStyle.x0_, fillStyle.y0_);
- var width = max.x - min.x;
- var height = max.y - min.y;
+ var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
focus = {
x: (p0.x - min.x) / width,
y: (p0.y - min.y) / height
};
- width /= this.arcScaleX_ * Z;
- height /= this.arcScaleY_ * Z;
+ width /= arcScaleX * Z;
+ height /= arcScaleY * Z;
var dimension = m.max(width, height);
shift = 2 * fillStyle.r0_ / dimension;
expansion = 2 * fillStyle.r1_ / dimension - shift;
@@ -735,8 +1108,8 @@ if (!document.createElement('canvas').getContext) {
var length = stops.length;
var color1 = stops[0].color;
var color2 = stops[length - 1].color;
- var opacity1 = stops[0].alpha * this.globalAlpha;
- var opacity2 = stops[length - 1].alpha * this.globalAlpha;
+ var opacity1 = stops[0].alpha * ctx.globalAlpha;
+ var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
var colors = [];
for (var i = 0; i < length; i++) {
@@ -755,33 +1128,42 @@ if (!document.createElement('canvas').getContext) {
' g_o_:opacity2="', opacity1, '"',
' angle="', angle, '"',
' focusposition="', focus.x, ',', focus.y, '" />');
+ } else if (fillStyle instanceof CanvasPattern_) {
+ if (width && height) {
+ var deltaLeft = -min.x;
+ var deltaTop = -min.y;
+ lineStr.push('<g_vml_:fill',
+ ' position="',
+ deltaLeft / width * arcScaleX * arcScaleX, ',',
+ deltaTop / height * arcScaleY * arcScaleY, '"',
+ ' type="tile"',
+ // TODO: Figure out the correct size to fit the scale.
+ //' size="', w, 'px ', h, 'px"',
+ ' src="', fillStyle.src_, '" />');
+ }
} else {
+ var a = processStyle(ctx.fillStyle);
+ var color = a.color;
+ var opacity = a.alpha * ctx.globalAlpha;
lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity,
'" />');
}
-
- lineStr.push('</g_vml_:shape>');
-
- this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
- };
+ }
contextPrototype.fill = function() {
this.stroke(true);
- }
+ };
contextPrototype.closePath = function() {
this.currentPath_.push({type: 'close'});
};
- /**
- * @private
- */
- contextPrototype.getCoords_ = function(aX, aY) {
- var m = this.m_;
+ function getCoords(ctx, aX, aY) {
+ var m = ctx.m_;
return {
x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
- }
+ };
};
contextPrototype.save = function() {
@@ -789,23 +1171,23 @@ if (!document.createElement('canvas').getContext) {
copyState(this, o);
this.aStack_.push(o);
this.mStack_.push(this.m_);
+ this.cStack_.push(this.clip_);
+ this.clip_ = createClip(this.clip_);
this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
};
contextPrototype.restore = function() {
- copyState(this.aStack_.pop(), this);
- this.m_ = this.mStack_.pop();
+ if (this.aStack_.length) {
+ copyState(this.aStack_.pop(), this);
+ this.m_ = this.mStack_.pop();
+ this.clip_ = this.cStack_.pop();
+ }
};
function matrixIsFinite(m) {
- for (var j = 0; j < 3; j++) {
- for (var k = 0; k < 2; k++) {
- if (!isFinite(m[j][k]) || isNaN(m[j][k])) {
- return false;
- }
- }
- }
- return true;
+ return isFinite(m[0][0]) && isFinite(m[0][1]) &&
+ isFinite(m[1][0]) && isFinite(m[1][1]) &&
+ isFinite(m[2][0]) && isFinite(m[2][1]);
}
function setM(ctx, m, updateLineScale) {
@@ -838,6 +1220,8 @@ if (!document.createElement('canvas').getContext) {
var c = mc(aRot);
var s = ms(aRot);
+ this.rotation_ += aRot;
+
var m1 = [
[c, s, 0],
[-s, c, 0],
@@ -879,17 +1263,156 @@ if (!document.createElement('canvas').getContext) {
setM(this, m, true);
};
+ /**
+ * The text drawing function.
+ * The maxWidth argument isn't taken in account, since no browser supports
+ * it yet.
+ */
+ contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
+ var m = this.m_,
+ delta = 1000,
+ left = 0,
+ right = delta,
+ offset = {x: 0, y: 0},
+ lineStr = [];
+
+ var fontStyle = getComputedStyle(processFontStyle(this.font),
+ this.clip_);
+
+ var vmlStyleString = fontStyle.style + ' ' + fontStyle.variant + ' ' + fontStyle.weight + ' ' +
+ fontStyle.size + 'px ' + fontStyle.family;
+
+ var elementStyle = this.clip_.currentStyle;
+ var textAlign = this.textAlign.toLowerCase();
+ switch (textAlign) {
+ case 'left':
+ case 'center':
+ case 'right':
+ break;
+ case 'end':
+ textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
+ break;
+ case 'start':
+ textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
+ break;
+ default:
+ textAlign = 'left';
+ }
+
+ // 1.75 is an arbitrary number, as there is no info about the text baseline
+ switch (this.textBaseline) {
+ case 'hanging':
+ case 'top':
+ offset.y = fontStyle.size / 1.75;
+ break;
+ case 'middle':
+ break;
+ default:
+ case null:
+ case 'alphabetic':
+ case 'ideographic':
+ case 'bottom':
+ offset.y = -fontStyle.size / 2.25;
+ break;
+ }
+
+ switch(textAlign) {
+ case 'right':
+ left = delta;
+ right = 0.05;
+ break;
+ case 'center':
+ left = right = delta / 2;
+ break;
+ }
+
+ var d = getCoords(this, x + offset.x, y + offset.y);
+
+ lineStr.push('<g_vml_:line from="', -left ,' 0" to="', right ,' 0.05" ',
+ ' coordsize="100 100" coordorigin="0 0"',
+ ' filled="', !stroke, '" stroked="', !!stroke,
+ '" style="position:absolute;width:1px;height:1px;">');
+
+ if (stroke) {
+ appendStroke(this, lineStr);
+ } else {
+ // TODO: Fix the min and max params.
+ appendFill(this, lineStr, {x: -left, y: 0},
+ {x: right, y: fontStyle.size});
+ }
+
+ var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' +
+ m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
+
+ var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
+
+ lineStr.push('<g_vml_:skew on="t" matrix="', skewM ,'" ',
+ ' offset="', skewOffset, '" origin="', left ,' 0" />',
+ '<g_vml_:path textpathok="true" />',
+ '<g_vml_:textpath on="true" string="',
+ encodeHtmlAttribute(text),
+ '" style="v-text-align:', textAlign,
+ ';font:', encodeHtmlAttribute(vmlStyleString),
+ '" /></g_vml_:line>');
+
+ this.clip_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+ };
+
+ contextPrototype.fillText = function(text, x, y, maxWidth) {
+ this.drawText_(text, x, y, maxWidth, false);
+ };
+
+ contextPrototype.strokeText = function(text, x, y, maxWidth) {
+ this.drawText_(text, x, y, maxWidth, true);
+ };
+
+ contextPrototype.measureText = function(text) {
+ if (!this.textMeasureEl_) {
+ var s = '<span style="position:absolute;' +
+ 'top:-20000px;left:0;padding:0;margin:0;border:none;' +
+ 'white-space:pre;"></span>';
+ document.body.insertAdjacentHTML('beforeEnd', s);
+ this.textMeasureEl_ = document.body.lastChild;
+ }
+ this.textMeasureEl_.innerHTML = '';
+ // FIX: Apply current font style to textMeasureEl to get correct size
+ var fontStyle = getComputedStyle(processFontStyle(this.font), this.clip_);
+ this.textMeasureEl_.style.font = fontStyle.style+' '+fontStyle.variant+' '+fontStyle.weight+' '+fontStyle.size+' '+fontStyle.family;
+
+ // Don't use innerHTML or innerText because they allow markup/whitespace.
+ this.textMeasureEl_.appendChild(document.createTextNode(text));
+ var width = this.textMeasureEl_.offsetWidth;
+ this.textMeasureEl_.removeNode(true);
+ this.textMeasureEl_ = null;
+ return {width: width};
+ };
+
/******** STUBS ********/
contextPrototype.clip = function() {
- // TODO: Implement
+ // skip if there's nothing to clip
+ if(!this.currentPath_ || this.currentPath_.length == 0) return;
+ // otherwise, clip using the path's bounding box
+ var path = this.currentPath_;
+ var xs = [], ys = [];
+ for(var i=1; i<path.length; i++){
+ if(path[i].type == 'close') continue;
+ xs.push(path[i].x/Z + Z2); // inexplicably make up for use of Z's in getCoords
+ ys.push(path[i].y/Z + Z2); // inexplicably make up for use of Z's in getCoords
+ }
+ var left = Math.min.apply(Math, xs), right = Math.max.apply(Math, xs),
+ top = Math.min.apply(Math, ys), bottom = Math.max.apply(Math, ys);
+ this.clip_ = createClip(this.clip_);
+ this.clip_.style.clip = 'rect('+top+'px,'+right+'px,'+bottom+'px,'+left+'px)';
+ // replace the stack item with this new one, so all future drawing goes to the new clipping region
+ this.cStack_[0] = this.clip_;
};
contextPrototype.arcTo = function() {
// TODO: Implement
};
- contextPrototype.createPattern = function() {
- return new CanvasPattern_;
+ contextPrototype.createPattern = function(image, repetition) {
+ return new CanvasPattern_(image, repetition);
};
// Gradient / Pattern Stubs
@@ -911,14 +1434,70 @@ if (!document.createElement('canvas').getContext) {
alpha: aColor.alpha});
};
- function CanvasPattern_() {}
+ function CanvasPattern_(image, repetition) {
+ assertImageIsValid(image);
+ switch (repetition) {
+ case 'repeat':
+ case null:
+ case '':
+ this.repetition_ = 'repeat';
+ break
+ case 'repeat-x':
+ case 'repeat-y':
+ case 'no-repeat':
+ this.repetition_ = repetition;
+ break;
+ default:
+ throwException('SYNTAX_ERR');
+ }
+
+ this.src_ = image.src;
+ this.width_ = image.width;
+ this.height_ = image.height;
+ }
+
+ function throwException(s) {
+ throw new DOMException_(s);
+ }
+
+ function assertImageIsValid(img) {
+ if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
+ throwException('TYPE_MISMATCH_ERR');
+ }
+ if (img.readyState != 'complete') {
+ throwException('INVALID_STATE_ERR');
+ }
+ }
+
+ function DOMException_(s) {
+ this.code = this[s];
+ this.message = s +': DOM Exception ' + this.code;
+ }
+ var p = DOMException_.prototype = new Error;
+ p.INDEX_SIZE_ERR = 1;
+ p.DOMSTRING_SIZE_ERR = 2;
+ p.HIERARCHY_REQUEST_ERR = 3;
+ p.WRONG_DOCUMENT_ERR = 4;
+ p.INVALID_CHARACTER_ERR = 5;
+ p.NO_DATA_ALLOWED_ERR = 6;
+ p.NO_MODIFICATION_ALLOWED_ERR = 7;
+ p.NOT_FOUND_ERR = 8;
+ p.NOT_SUPPORTED_ERR = 9;
+ p.INUSE_ATTRIBUTE_ERR = 10;
+ p.INVALID_STATE_ERR = 11;
+ p.SYNTAX_ERR = 12;
+ p.INVALID_MODIFICATION_ERR = 13;
+ p.NAMESPACE_ERR = 14;
+ p.INVALID_ACCESS_ERR = 15;
+ p.VALIDATION_ERR = 16;
+ p.TYPE_MISMATCH_ERR = 17;
// set up externs
G_vmlCanvasManager = G_vmlCanvasManager_;
CanvasRenderingContext2D = CanvasRenderingContext2D_;
CanvasGradient = CanvasGradient_;
CanvasPattern = CanvasPattern_;
-
+ DOMException = DOMException_;
})();
} // if
View
43 js-runtime/lib/primitive.js
@@ -4695,9 +4695,9 @@ PRIMITIVES['put-pinhole'] =
3,
false, false,
function(aState, x, y, img) {
- check(aState, x, isReal, "put-pinhole", "real", 1, arguments);
- check(aState, y, isReal, "put-pinhole", "real", 2, arguments);
- check(aState, img, isImage, "put-pinhole", "image", 3, arguments);
+ check(aState, img, isImage, "put-pinhole", "image", 1, arguments);
+ check(aState, x, isReal, "put-pinhole", "real", 2, arguments);
+ check(aState, y, isReal, "put-pinhole", "real", 3, arguments);
return img.updatePinhole(jsnums.toFixnum(x), jsnums.toFixnum(y));
});
@@ -5301,7 +5301,7 @@ PRIMITIVES['text'] =
aColor = colorDb.get(aColor);
}
return world.Kernel.textImage(aString.toString(), jsnums.toFixnum(aSize), aColor,
- "normal", "Optimer","","",false);
+ "normal", "Arial","","",false);
});
@@ -5331,42 +5331,9 @@ new PrimProc('text/font',
} catch(e) {
// Under IE 8, something breaks. I don't know yet what it is.
return world.Kernel.textImage(aString.toString(), jsnums.toFixnum(aSize), aColor,
- "normal", "Optimer","","",false);
+ "normal", "Arial","","",false);
}
});
-/*
-PRIMITIVES['bitmap/url'] =
-PRIMITIVES['image-url'] =
- new PrimProc('image-url',
- 1,
- false, true,
- function(aState, path) {
- check(aState, path, isString, "image-url", "string", 1);
- var originalPath = path.toString();
- if (aState.getImageProxyHook()) {
- path = (aState.getImageProxyHook() +
- "?url=" + encodeURIComponent(path.toString()));
- } else {
- path = path.toString();
- }
-
- aState.v = PAUSE(function(restarter, caller) {
- var rawImage = new Image();
- rawImage.onload = function() {
- world.Kernel.fileImage(
- path,
- rawImage,
- restarter);
- };
- rawImage.onerror = function(e) {
- restarter(types.schemeError(types.incompleteExn(
- types.exnFail,
- " (unable to load: " + originalPath + ")",
- [])));
- };
- rawImage.src = path;
- });
- });*/
PRIMITIVES['bitmap/url'] =
PRIMITIVES['image-url'] =
new PrimProc('image-url',
View
38 js-runtime/lib/world/world.js
@@ -353,8 +353,9 @@ if (typeof(world) === 'undefined') {
ctx.fillStyle = "rgba(0,0,0,0)";
ctx.fillRect(x, y, this.width, this.height);
ctx.restore();
-
+ // save the context, reset the path, and clip to the path around the scene edge
ctx.save();
+ ctx.beginPath();
ctx.rect(x, y, this.width, this.height);
ctx.clip();
// Ask every object to render itself inside the region
@@ -451,7 +452,7 @@ if (typeof(world) === 'undefined') {
FileImage.installBrokenImage = function(path) {
imageCache[path] = new TextImage("Unable to load " + path, 10, colorDb.get("red"),
- "normal", "Optimer","","",false);
+ "normal", "Arial","","",false);
};
FileImage.prototype.render = function(ctx, x, y) {
@@ -459,13 +460,13 @@ if (typeof(world) === 'undefined') {
};
// The following is a hack that we use to allow animated gifs to show
- // as animating on the canvas.
+ // as animating on the canvas. They have to be added to the DOM as *images*
+ // in order to have their frames fed to the canvas, so we add them someplace hidden
FileImage.prototype.installHackToSupportAnimatedGifs = function(afterInit) {
var that = this;
this.animationHackImg = this.img.cloneNode(true);
document.body.appendChild(this.animationHackImg);
- this.animationHackImg.width = 0;
- this.animationHackImg.height = 0;
+ this.animationHackImg.style.top = '-2000px';
if (this.animationHackImg.complete) {
afterInit(that);
} else {
@@ -997,23 +998,26 @@ if (typeof(world) === 'undefined') {
BaseImage.call(this);
var metrics;
this.msg = msg;
- this.size = size;
- this.color = color;
- this.face = face;
- this.family = family;
+ this.size = size; // 18
+ this.color = color; // red
+ this.face = face; // Gill Sans
+ this.family = family; // 'swiss
this.style = (style === "slant")? "oblique" : style; // Racket's "slant" -> CSS's "oblique"
this.weight = (weight=== "light")? "lighter" : weight; // Racket's "light" -> CSS's "lighter"
this.underline = underline;
// example: "bold italic 20px 'Times', sans-serif".
- // Default weight is "normal", face is "Optimer"
- var canvas = world.Kernel.makeCanvas(0, 0);
- var ctx = canvas.getContext("2d");
-
- this.font = (this.weight + " " +
- this.style + " " +
+ // Default weight is "normal", face is "Arial"
+
+ // NOTE: we *ignore* font-family, as it causes a number of font bugs due the browser inconsistencies
+ var canvas = world.Kernel.makeCanvas(0, 0),
+ ctx = canvas.getContext("2d");
+
+ this.font = (this.style + " " +
+ this.weight + " " +
this.size + "px " +
- maybeQuote(this.face) + " " +
- maybeQuote(this.family));
+ '"'+this.face+'", '+
+ this.family);
+
try {
ctx.font = this.font;
} catch (e) {
View
57 tests/test-htdocs/image-library-test.scm
@@ -1,61 +1,4 @@
(printf "images.rkt\n")
-
-(define flag
- (place-image
- (rotate 90
- (underlay/align
- "center" "center"
- (rectangle 50 450 "solid" "white")
- (rotate 90
- (rectangle 50 450 "solid" "white"))
- (rotate 90
- (rectangle 30 450 "solid" "red"))
- (rotate 180
- (rectangle 30 450 "solid" "red"))))
-
- 200 100
- (place-image
- (rotate 65
- (underlay/align
- "center" "center"
- (rectangle 15 450 "solid" "red")
- (rotate 50
- (rectangle 15 450 "solid" "red"))))
- 200 100
- (place-image
- (rotate 65
- (underlay/align
- "center" "center"
- (rectangle 40 450 "solid" "white")
- (rotate 50
- (rectangle 40 450 "solid" "white"))))
- 200 100
- (rectangle 400 200 "solid" "navy")))))
-
-
-(define Australia
- (place-image
- flag
- 200 100
- (place-image
- (star-polygon 30 7 3 "solid" "white")
- 650 60
- (place-image
- (star-polygon 50 7 3 "solid" "white")
- 200 300
- (place-image
- (star-polygon 40 7 3 "solid" "white")
- 60 20
- (place-image
- (star-polygon 40 7 3 "solid" "white")
- 68 124
- (rectangle 900 400 "solid" "navy")))))))
- flag
-Australia
-
-
-
-
"These three circles (red, green, blue) should be left aligned"
Please sign in to comment.
Something went wrong with that request. Please try again.