26 // Quirks mode will draw the canvas using border-box. Either change your |
26 // Quirks mode will draw the canvas using border-box. Either change your |
27 // doctype to HTML5 |
27 // doctype to HTML5 |
28 // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) |
28 // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) |
29 // or use Box Sizing Behavior from WebFX |
29 // or use Box Sizing Behavior from WebFX |
30 // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) |
30 // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) |
|
31 // * Non uniform scaling does not correctly scale strokes. |
31 // * Optimize. There is always room for speed improvements. |
32 // * Optimize. There is always room for speed improvements. |
32 |
33 |
33 // only add this code if we do not already have a canvas implementation |
34 // Only add this code if we do not already have a canvas implementation |
34 if (!window.CanvasRenderingContext2D) { |
35 if (!document.createElement('canvas').getContext) { |
35 |
36 |
36 (function () { |
37 (function() { |
37 |
38 |
38 // alias some functions to make (compiled) code shorter |
39 // alias some functions to make (compiled) code shorter |
39 var m = Math; |
40 var m = Math; |
40 var mr = m.round; |
41 var mr = m.round; |
41 var ms = m.sin; |
42 var ms = m.sin; |
42 var mc = m.cos; |
43 var mc = m.cos; |
|
44 var abs = m.abs; |
|
45 var sqrt = m.sqrt; |
43 |
46 |
44 // this is used for sub pixel precision |
47 // this is used for sub pixel precision |
45 var Z = 10; |
48 var Z = 10; |
46 var Z2 = Z / 2; |
49 var Z2 = Z / 2; |
47 |
50 |
|
51 /** |
|
52 * This funtion is assigned to the <canvas> elements as element.getContext(). |
|
53 * @this {HTMLElement} |
|
54 * @return {CanvasRenderingContext2D_} |
|
55 */ |
|
56 function getContext() { |
|
57 return this.context_ || |
|
58 (this.context_ = new CanvasRenderingContext2D_(this)); |
|
59 } |
|
60 |
|
61 var slice = Array.prototype.slice; |
|
62 |
|
63 /** |
|
64 * Binds a function to an object. The returned function will always use the |
|
65 * passed in {@code obj} as {@code this}. |
|
66 * |
|
67 * Example: |
|
68 * |
|
69 * g = bind(f, obj, a, b) |
|
70 * g(c, d) // will do f.call(obj, a, b, c, d) |
|
71 * |
|
72 * @param {Function} f The function to bind the object to |
|
73 * @param {Object} obj The object that should act as this when the function |
|
74 * is called |
|
75 * @param {*} var_args Rest arguments that will be used as the initial |
|
76 * arguments when the function is called |
|
77 * @return {Function} A new function that has bound this |
|
78 */ |
|
79 function bind(f, obj, var_args) { |
|
80 var a = slice.call(arguments, 2); |
|
81 return function() { |
|
82 return f.apply(obj, a.concat(slice.call(arguments))); |
|
83 }; |
|
84 } |
|
85 |
48 var G_vmlCanvasManager_ = { |
86 var G_vmlCanvasManager_ = { |
49 init: function (opt_doc) { |
87 init: function(opt_doc) { |
50 var doc = opt_doc || document; |
|
51 if (/MSIE/.test(navigator.userAgent) && !window.opera) { |
88 if (/MSIE/.test(navigator.userAgent) && !window.opera) { |
52 var self = this; |
89 var doc = opt_doc || document; |
53 doc.attachEvent("onreadystatechange", function () { |
90 // Create a dummy element so that IE will allow canvas elements to be |
54 self.init_(doc); |
91 // recognized. |
55 }); |
92 doc.createElement('canvas'); |
|
93 doc.attachEvent('onreadystatechange', bind(this.init_, this, doc)); |
56 } |
94 } |
57 }, |
95 }, |
58 |
96 |
59 init_: function (doc) { |
97 init_: function(doc) { |
60 if (doc.readyState == "complete") { |
98 // create xmlns |
61 // create xmlns |
99 if (!doc.namespaces['g_vml_']) { |
62 if (!doc.namespaces["g_vml_"]) { |
100 doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml', |
63 doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml"); |
101 '#default#VML'); |
64 } |
102 |
65 |
103 } |
66 // setup default css |
104 if (!doc.namespaces['g_o_']) { |
|
105 doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office', |
|
106 '#default#VML'); |
|
107 } |
|
108 |
|
109 // Setup default CSS. Only add one style sheet per document |
|
110 if (!doc.styleSheets['ex_canvas_']) { |
67 var ss = doc.createStyleSheet(); |
111 var ss = doc.createStyleSheet(); |
68 ss.cssText = "canvas{display:inline-block;overflow:hidden;" + |
112 ss.owningElement.id = 'ex_canvas_'; |
|
113 ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + |
69 // default size is 300x150 in Gecko and Opera |
114 // default size is 300x150 in Gecko and Opera |
70 "text-align:left;width:300px;height:150px}" + |
115 'text-align:left;width:300px;height:150px}' + |
71 "g_vml_\\:*{behavior:url(#default#VML)}"; |
116 'g_vml_\\:*{behavior:url(#default#VML)}' + |
72 |
117 'g_o_\\:*{behavior:url(#default#VML)}'; |
73 // find all canvas elements |
118 |
74 var els = doc.getElementsByTagName("canvas"); |
119 } |
75 for (var i = 0; i < els.length; i++) { |
120 |
76 if (!els[i].getContext) { |
121 // find all canvas elements |
77 this.initElement(els[i]); |
122 var els = doc.getElementsByTagName('canvas'); |
78 } |
123 for (var i = 0; i < els.length; i++) { |
79 } |
124 this.initElement(els[i]); |
80 } |
125 } |
81 }, |
|
82 |
|
83 fixElement_: function (el) { |
|
84 // in IE before version 5.5 we would need to add HTML: to the tag name |
|
85 // but we do not care about IE before version 6 |
|
86 var outerHTML = el.outerHTML; |
|
87 |
|
88 var newEl = el.ownerDocument.createElement(outerHTML); |
|
89 // if the tag is still open IE has created the children as siblings and |
|
90 // it has also created a tag with the name "/FOO" |
|
91 if (outerHTML.slice(-2) != "/>") { |
|
92 var tagName = "/" + el.tagName; |
|
93 var ns; |
|
94 // remove content |
|
95 while ((ns = el.nextSibling) && ns.tagName != tagName) { |
|
96 ns.removeNode(); |
|
97 } |
|
98 // remove the incorrect closing tag |
|
99 if (ns) { |
|
100 ns.removeNode(); |
|
101 } |
|
102 } |
|
103 el.parentNode.replaceChild(newEl, el); |
|
104 return newEl; |
|
105 }, |
126 }, |
106 |
127 |
107 /** |
128 /** |
108 * Public initializes a canvas element so that it can be used as canvas |
129 * Public initializes a canvas element so that it can be used as canvas |
109 * element from now on. This is called automatically before the page is |
130 * element from now on. This is called automatically before the page is |
110 * loaded but if you are creating elements using createElement you need to |
131 * loaded but if you are creating elements using createElement you need to |
111 * make sure this is called on the element. |
132 * make sure this is called on the element. |
112 * @param {HTMLElement} el The canvas element to initialize. |
133 * @param {HTMLElement} el The canvas element to initialize. |
113 * @return {HTMLElement} the element that was created. |
134 * @return {HTMLElement} the element that was created. |
114 */ |
135 */ |
115 initElement: function (el) { |
136 initElement: function(el) { |
116 el = this.fixElement_(el); |
137 if (!el.getContext) { |
117 el.getContext = function () { |
138 |
118 if (this.context_) { |
139 el.getContext = getContext; |
119 return this.context_; |
140 |
|
141 // Remove fallback content. There is no way to hide text nodes so we |
|
142 // just remove all childNodes. We could hide all elements and remove |
|
143 // text nodes but who really cares about the fallback content. |
|
144 el.innerHTML = ''; |
|
145 |
|
146 // do not use inline function because that will leak memory |
|
147 el.attachEvent('onpropertychange', onPropertyChange); |
|
148 el.attachEvent('onresize', onResize); |
|
149 |
|
150 var attrs = el.attributes; |
|
151 if (attrs.width && attrs.width.specified) { |
|
152 // TODO: use runtimeStyle and coordsize |
|
153 // el.getContext().setWidth_(attrs.width.nodeValue); |
|
154 el.style.width = attrs.width.nodeValue + 'px'; |
|
155 } else { |
|
156 el.width = el.clientWidth; |
120 } |
157 } |
121 return this.context_ = new CanvasRenderingContext2D_(this); |
158 if (attrs.height && attrs.height.specified) { |
122 }; |
159 // TODO: use runtimeStyle and coordsize |
123 |
160 // el.getContext().setHeight_(attrs.height.nodeValue); |
124 // do not use inline function because that will leak memory |
161 el.style.height = attrs.height.nodeValue + 'px'; |
125 el.attachEvent('onpropertychange', onPropertyChange); |
162 } else { |
126 el.attachEvent('onresize', onResize); |
163 el.height = el.clientHeight; |
127 |
164 } |
128 var attrs = el.attributes; |
165 //el.getContext().setCoordsize_() |
129 if (attrs.width && attrs.width.specified) { |
166 } |
130 // TODO: use runtimeStyle and coordsize |
|
131 // el.getContext().setWidth_(attrs.width.nodeValue); |
|
132 el.style.width = attrs.width.nodeValue + "px"; |
|
133 } else { |
|
134 el.width = el.clientWidth; |
|
135 } |
|
136 if (attrs.height && attrs.height.specified) { |
|
137 // TODO: use runtimeStyle and coordsize |
|
138 // el.getContext().setHeight_(attrs.height.nodeValue); |
|
139 el.style.height = attrs.height.nodeValue + "px"; |
|
140 } else { |
|
141 el.height = el.clientHeight; |
|
142 } |
|
143 //el.getContext().setCoordsize_() |
|
144 return el; |
167 return el; |
145 } |
168 } |
146 }; |
169 }; |
147 |
170 |
148 function onPropertyChange(e) { |
171 function onPropertyChange(e) { |
149 var el = e.srcElement; |
172 var el = e.srcElement; |
150 |
173 |
151 switch (e.propertyName) { |
174 switch (e.propertyName) { |
152 case 'width': |
175 case 'width': |
153 el.style.width = el.attributes.width.nodeValue + "px"; |
176 el.style.width = el.attributes.width.nodeValue + 'px'; |
154 el.getContext().clearRect(); |
177 el.getContext().clearRect(); |
155 break; |
178 break; |
156 case 'height': |
179 case 'height': |
157 el.style.height = el.attributes.height.nodeValue + "px"; |
180 el.style.height = el.attributes.height.nodeValue + 'px'; |
158 el.getContext().clearRect(); |
181 el.getContext().clearRect(); |
159 break; |
182 break; |
160 } |
183 } |
161 } |
184 } |
162 |
185 |
286 surfaceElement.appendChild(el); |
311 surfaceElement.appendChild(el); |
287 |
312 |
288 this.element_ = el; |
313 this.element_ = el; |
289 this.arcScaleX_ = 1; |
314 this.arcScaleX_ = 1; |
290 this.arcScaleY_ = 1; |
315 this.arcScaleY_ = 1; |
|
316 this.lineScale_ = 1; |
291 } |
317 } |
292 |
318 |
293 var contextPrototype = CanvasRenderingContext2D_.prototype; |
319 var contextPrototype = CanvasRenderingContext2D_.prototype; |
294 contextPrototype.clearRect = function() { |
320 contextPrototype.clearRect = function() { |
295 this.element_.innerHTML = ""; |
321 this.element_.innerHTML = ''; |
296 this.currentPath_ = []; |
|
297 }; |
322 }; |
298 |
323 |
299 contextPrototype.beginPath = function() { |
324 contextPrototype.beginPath = function() { |
300 // TODO: Branch current matrix so that save/restore has no effect |
325 // TODO: Branch current matrix so that save/restore has no effect |
301 // as per safari docs. |
326 // as per safari docs. |
302 |
|
303 this.currentPath_ = []; |
327 this.currentPath_ = []; |
304 }; |
328 }; |
305 |
329 |
306 contextPrototype.moveTo = function(aX, aY) { |
330 contextPrototype.moveTo = function(aX, aY) { |
307 this.currentPath_.push({type: "moveTo", x: aX, y: aY}); |
331 var p = this.getCoords_(aX, aY); |
308 this.currentX_ = aX; |
332 this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y}); |
309 this.currentY_ = aY; |
333 this.currentX_ = p.x; |
|
334 this.currentY_ = p.y; |
310 }; |
335 }; |
311 |
336 |
312 contextPrototype.lineTo = function(aX, aY) { |
337 contextPrototype.lineTo = function(aX, aY) { |
313 this.currentPath_.push({type: "lineTo", x: aX, y: aY}); |
338 var p = this.getCoords_(aX, aY); |
314 this.currentX_ = aX; |
339 this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y}); |
315 this.currentY_ = aY; |
340 |
|
341 this.currentX_ = p.x; |
|
342 this.currentY_ = p.y; |
316 }; |
343 }; |
317 |
344 |
318 contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, |
345 contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, |
319 aCP2x, aCP2y, |
346 aCP2x, aCP2y, |
320 aX, aY) { |
347 aX, aY) { |
321 this.currentPath_.push({type: "bezierCurveTo", |
348 var p = this.getCoords_(aX, aY); |
322 cp1x: aCP1x, |
349 var cp1 = this.getCoords_(aCP1x, aCP1y); |
323 cp1y: aCP1y, |
350 var cp2 = this.getCoords_(aCP2x, aCP2y); |
324 cp2x: aCP2x, |
351 bezierCurveTo(this, cp1, cp2, p); |
325 cp2y: aCP2y, |
352 }; |
326 x: aX, |
353 |
327 y: aY}); |
354 // Helper function that takes the already fixed cordinates. |
328 this.currentX_ = aX; |
355 function bezierCurveTo(self, cp1, cp2, p) { |
329 this.currentY_ = aY; |
356 self.currentPath_.push({ |
330 }; |
357 type: 'bezierCurveTo', |
|
358 cp1x: cp1.x, |
|
359 cp1y: cp1.y, |
|
360 cp2x: cp2.x, |
|
361 cp2y: cp2.y, |
|
362 x: p.x, |
|
363 y: p.y |
|
364 }); |
|
365 self.currentX_ = p.x; |
|
366 self.currentY_ = p.y; |
|
367 } |
331 |
368 |
332 contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { |
369 contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { |
333 // the following is lifted almost directly from |
370 // the following is lifted almost directly from |
334 // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes |
371 // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes |
335 var cp1x = this.currentX_ + 2.0 / 3.0 * (aCPx - this.currentX_); |
372 |
336 var cp1y = this.currentY_ + 2.0 / 3.0 * (aCPy - this.currentY_); |
373 var cp = this.getCoords_(aCPx, aCPy); |
337 var cp2x = cp1x + (aX - this.currentX_) / 3.0; |
374 var p = this.getCoords_(aX, aY); |
338 var cp2y = cp1y + (aY - this.currentY_) / 3.0; |
375 |
339 this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, aX, aY); |
376 var cp1 = { |
|
377 x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_), |
|
378 y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_) |
|
379 }; |
|
380 var cp2 = { |
|
381 x: cp1.x + (p.x - this.currentX_) / 3.0, |
|
382 y: cp1.y + (p.y - this.currentY_) / 3.0 |
|
383 }; |
|
384 |
|
385 bezierCurveTo(this, cp1, cp2, p); |
340 }; |
386 }; |
341 |
387 |
342 contextPrototype.arc = function(aX, aY, aRadius, |
388 contextPrototype.arc = function(aX, aY, aRadius, |
343 aStartAngle, aEndAngle, aClockwise) { |
389 aStartAngle, aEndAngle, aClockwise) { |
344 aRadius *= Z; |
390 aRadius *= Z; |
345 var arcType = aClockwise ? "at" : "wa"; |
391 var arcType = aClockwise ? 'at' : 'wa'; |
346 |
392 |
347 var xStart = aX + (mc(aStartAngle) * aRadius) - Z2; |
393 var xStart = aX + mc(aStartAngle) * aRadius - Z2; |
348 var yStart = aY + (ms(aStartAngle) * aRadius) - Z2; |
394 var yStart = aY + ms(aStartAngle) * aRadius - Z2; |
349 |
395 |
350 var xEnd = aX + (mc(aEndAngle) * aRadius) - Z2; |
396 var xEnd = aX + mc(aEndAngle) * aRadius - Z2; |
351 var yEnd = aY + (ms(aEndAngle) * aRadius) - Z2; |
397 var yEnd = aY + ms(aEndAngle) * aRadius - Z2; |
352 |
398 |
353 // IE won't render arches drawn counter clockwise if xStart == xEnd. |
399 // IE won't render arches drawn counter clockwise if xStart == xEnd. |
354 if (xStart == xEnd && !aClockwise) { |
400 if (xStart == xEnd && !aClockwise) { |
355 xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something |
401 xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something |
356 // that can be represented in binary |
402 // that can be represented in binary |
357 } |
403 } |
358 |
404 |
|
405 var p = this.getCoords_(aX, aY); |
|
406 var pStart = this.getCoords_(xStart, yStart); |
|
407 var pEnd = this.getCoords_(xEnd, yEnd); |
|
408 |
359 this.currentPath_.push({type: arcType, |
409 this.currentPath_.push({type: arcType, |
360 x: aX, |
410 x: p.x, |
361 y: aY, |
411 y: p.y, |
362 radius: aRadius, |
412 radius: aRadius, |
363 xStart: xStart, |
413 xStart: pStart.x, |
364 yStart: yStart, |
414 yStart: pStart.y, |
365 xEnd: xEnd, |
415 xEnd: pEnd.x, |
366 yEnd: yEnd}); |
416 yEnd: pEnd.y}); |
367 |
417 |
368 }; |
418 }; |
369 |
419 |
370 contextPrototype.rect = function(aX, aY, aWidth, aHeight) { |
420 contextPrototype.rect = function(aX, aY, aWidth, aHeight) { |
371 this.moveTo(aX, aY); |
421 this.moveTo(aX, aY); |
469 |
530 |
470 // For some reason that I've now forgotten, using divs didn't work |
531 // For some reason that I've now forgotten, using divs didn't work |
471 vmlStr.push(' <g_vml_:group', |
532 vmlStr.push(' <g_vml_:group', |
472 ' coordsize="', Z * W, ',', Z * H, '"', |
533 ' coordsize="', Z * W, ',', Z * H, '"', |
473 ' coordorigin="0,0"' , |
534 ' coordorigin="0,0"' , |
474 ' style="width:', W, ';height:', H, ';position:absolute;'); |
535 ' style="width:', W, 'px;height:', H, 'px;position:absolute;'); |
475 |
536 |
476 // If filters are necessary (rotation exists), create them |
537 // If filters are necessary (rotation exists), create them |
477 // filters are bog-slow, so only create them if abbsolutely necessary |
538 // filters are bog-slow, so only create them if abbsolutely necessary |
478 // The following check doesn't account for skews (which don't exist |
539 // The following check doesn't account for skews (which don't exist |
479 // in the canvas spec (yet) anyway. |
540 // in the canvas spec (yet) anyway. |
480 |
541 |
481 if (this.m_[0][0] != 1 || this.m_[0][1]) { |
542 if (this.m_[0][0] != 1 || this.m_[0][1]) { |
482 var filter = []; |
543 var filter = []; |
483 |
544 |
484 // Note the 12/21 reversal |
545 // Note the 12/21 reversal |
485 filter.push("M11='", this.m_[0][0], "',", |
546 filter.push('M11=', this.m_[0][0], ',', |
486 "M12='", this.m_[1][0], "',", |
547 'M12=', this.m_[1][0], ',', |
487 "M21='", this.m_[0][1], "',", |
548 'M21=', this.m_[0][1], ',', |
488 "M22='", this.m_[1][1], "',", |
549 'M22=', this.m_[1][1], ',', |
489 "Dx='", mr(d.x / Z), "',", |
550 'Dx=', mr(d.x / Z), ',', |
490 "Dy='", mr(d.y / Z), "'"); |
551 'Dy=', mr(d.y / Z), ''); |
491 |
552 |
492 // Bounding box calculation (need to minimize displayed area so that |
553 // Bounding box calculation (need to minimize displayed area so that |
493 // filters don't waste time on unused pixels. |
554 // filters don't waste time on unused pixels. |
494 var max = d; |
555 var max = d; |
495 var c2 = this.getCoords_(dx + dw, dy); |
556 var c2 = this.getCoords_(dx + dw, dy); |
496 var c3 = this.getCoords_(dx, dy + dh); |
557 var c3 = this.getCoords_(dx, dy + dh); |
497 var c4 = this.getCoords_(dx + dw, dy + dh); |
558 var c4 = this.getCoords_(dx + dw, dy + dh); |
498 |
559 |
499 max.x = Math.max(max.x, c2.x, c3.x, c4.x); |
560 max.x = m.max(max.x, c2.x, c3.x, c4.x); |
500 max.y = Math.max(max.y, c2.y, c3.y, c4.y); |
561 max.y = m.max(max.y, c2.y, c3.y, c4.y); |
501 |
562 |
502 vmlStr.push("padding:0 ", mr(max.x / Z), "px ", mr(max.y / Z), |
563 vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z), |
503 "px 0;filter:progid:DXImageTransform.Microsoft.Matrix(", |
564 'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(', |
504 filter.join(""), ", sizingmethod='clip');"); |
565 filter.join(''), ", sizingmethod='clip');") |
505 } else { |
566 } else { |
506 vmlStr.push("top:", mr(d.y / Z), "px;left:", mr(d.x / Z), "px;"); |
567 vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;'); |
507 } |
568 } |
508 |
569 |
509 vmlStr.push(' ">' , |
570 vmlStr.push(' ">' , |
510 '<g_vml_:image src="', image.src, '"', |
571 '<g_vml_:image src="', image.src, '"', |
511 ' style="width:', Z * dw, ';', |
572 ' style="width:', Z * dw, 'px;', |
512 ' height:', Z * dh, ';"', |
573 ' height:', Z * dh, 'px;"', |
513 ' cropleft="', sx / w, '"', |
574 ' cropleft="', sx / w, '"', |
514 ' croptop="', sy / h, '"', |
575 ' croptop="', sy / h, '"', |
515 ' cropright="', (w - sx - sw) / w, '"', |
576 ' cropright="', (w - sx - sw) / w, '"', |
516 ' cropbottom="', (h - sy - sh) / h, '"', |
577 ' cropbottom="', (h - sy - sh) / h, '"', |
517 ' />', |
578 ' />', |
518 '</g_vml_:group>'); |
579 '</g_vml_:group>'); |
519 |
580 |
520 this.element_.insertAdjacentHTML("BeforeEnd", |
581 this.element_.insertAdjacentHTML('BeforeEnd', |
521 vmlStr.join("")); |
582 vmlStr.join('')); |
522 }; |
583 }; |
523 |
584 |
524 contextPrototype.stroke = function(aFill) { |
585 contextPrototype.stroke = function(aFill) { |
525 var lineStr = []; |
586 var lineStr = []; |
526 var lineOpen = false; |
587 var lineOpen = false; |
527 var a = processStyle(aFill ? this.fillStyle : this.strokeStyle); |
588 var a = processStyle(aFill ? this.fillStyle : this.strokeStyle); |
528 var color = a[0]; |
589 var color = a.color; |
529 var opacity = a[1] * this.globalAlpha; |
590 var opacity = a.alpha * this.globalAlpha; |
530 |
591 |
531 var W = 10; |
592 var W = 10; |
532 var H = 10; |
593 var H = 10; |
533 |
594 |
534 lineStr.push('<g_vml_:shape', |
595 lineStr.push('<g_vml_:shape', |
535 ' fillcolor="', color, '"', |
596 ' filled="', !!aFill, '"', |
536 ' filled="', Boolean(aFill), '"', |
597 ' style="position:absolute;width:', W, 'px;height:', H, 'px;"', |
537 ' style="position:absolute;width:', W, ';height:', H, ';"', |
|
538 ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"', |
598 ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"', |
539 ' stroked="', !aFill, '"', |
599 ' stroked="', !aFill, '"', |
540 ' strokeweight="', this.lineWidth, '"', |
|
541 ' strokecolor="', color, '"', |
|
542 ' path="'); |
600 ' path="'); |
543 |
601 |
544 var newSeq = false; |
602 var newSeq = false; |
545 var min = {x: null, y: null}; |
603 var min = {x: null, y: null}; |
546 var max = {x: null, y: null}; |
604 var max = {x: null, y: null}; |
547 |
605 |
548 for (var i = 0; i < this.currentPath_.length; i++) { |
606 for (var i = 0; i < this.currentPath_.length; i++) { |
549 var p = this.currentPath_[i]; |
607 var p = this.currentPath_[i]; |
550 |
608 var c; |
551 if (p.type == "moveTo") { |
609 |
552 lineStr.push(" m "); |
610 switch (p.type) { |
553 var c = this.getCoords_(p.x, p.y); |
611 case 'moveTo': |
554 lineStr.push(mr(c.x), ",", mr(c.y)); |
612 c = p; |
555 } else if (p.type == "lineTo") { |
613 lineStr.push(' m ', mr(p.x), ',', mr(p.y)); |
556 lineStr.push(" l "); |
614 break; |
557 var c = this.getCoords_(p.x, p.y); |
615 case 'lineTo': |
558 lineStr.push(mr(c.x), ",", mr(c.y)); |
616 lineStr.push(' l ', mr(p.x), ',', mr(p.y)); |
559 } else if (p.type == "close") { |
617 break; |
560 lineStr.push(" x "); |
618 case 'close': |
561 } else if (p.type == "bezierCurveTo") { |
619 lineStr.push(' x '); |
562 lineStr.push(" c "); |
620 p = null; |
563 var c = this.getCoords_(p.x, p.y); |
621 break; |
564 var c1 = this.getCoords_(p.cp1x, p.cp1y); |
622 case 'bezierCurveTo': |
565 var c2 = this.getCoords_(p.cp2x, p.cp2y); |
623 lineStr.push(' c ', |
566 lineStr.push(mr(c1.x), ",", mr(c1.y), ",", |
624 mr(p.cp1x), ',', mr(p.cp1y), ',', |
567 mr(c2.x), ",", mr(c2.y), ",", |
625 mr(p.cp2x), ',', mr(p.cp2y), ',', |
568 mr(c.x), ",", mr(c.y)); |
626 mr(p.x), ',', mr(p.y)); |
569 } else if (p.type == "at" || p.type == "wa") { |
627 break; |
570 lineStr.push(" ", p.type, " "); |
628 case 'at': |
571 var c = this.getCoords_(p.x, p.y); |
629 case 'wa': |
572 var cStart = this.getCoords_(p.xStart, p.yStart); |
630 lineStr.push(' ', p.type, ' ', |
573 var cEnd = this.getCoords_(p.xEnd, p.yEnd); |
631 mr(p.x - this.arcScaleX_ * p.radius), ',', |
574 |
632 mr(p.y - this.arcScaleY_ * p.radius), ' ', |
575 lineStr.push(mr(c.x - this.arcScaleX_ * p.radius), ",", |
633 mr(p.x + this.arcScaleX_ * p.radius), ',', |
576 mr(c.y - this.arcScaleY_ * p.radius), " ", |
634 mr(p.y + this.arcScaleY_ * p.radius), ' ', |
577 mr(c.x + this.arcScaleX_ * p.radius), ",", |
635 mr(p.xStart), ',', mr(p.yStart), ' ', |
578 mr(c.y + this.arcScaleY_ * p.radius), " ", |
636 mr(p.xEnd), ',', mr(p.yEnd)); |
579 mr(cStart.x), ",", mr(cStart.y), " ", |
637 break; |
580 mr(cEnd.x), ",", mr(cEnd.y)); |
|
581 } |
638 } |
582 |
639 |
583 |
640 |
584 // TODO: Following is broken for curves due to |
641 // TODO: Following is broken for curves due to |
585 // move to proper paths. |
642 // move to proper paths. |
586 |
643 |
587 // Figure out dimensions so we can do gradient fills |
644 // Figure out dimensions so we can do gradient fills |
588 // properly |
645 // properly |
589 if(c) { |
646 if (p) { |
590 if (min.x == null || c.x < min.x) { |
647 if (min.x == null || p.x < min.x) { |
591 min.x = c.x; |
648 min.x = p.x; |
592 } |
649 } |
593 if (max.x == null || c.x > max.x) { |
650 if (max.x == null || p.x > max.x) { |
594 max.x = c.x; |
651 max.x = p.x; |
595 } |
652 } |
596 if (min.y == null || c.y < min.y) { |
653 if (min.y == null || p.y < min.y) { |
597 min.y = c.y; |
654 min.y = p.y; |
598 } |
655 } |
599 if (max.y == null || c.y > max.y) { |
656 if (max.y == null || p.y > max.y) { |
600 max.y = c.y; |
657 max.y = p.y; |
601 } |
658 } |
602 } |
659 } |
603 } |
660 } |
604 lineStr.push(' ">'); |
661 lineStr.push(' ">'); |
605 |
662 |
606 if (typeof this.fillStyle == "object") { |
663 if (!aFill) { |
607 var focus = {x: "50%", y: "50%"}; |
664 var lineWidth = this.lineScale_ * this.lineWidth; |
608 var width = (max.x - min.x); |
665 |
609 var height = (max.y - min.y); |
666 // VML cannot correctly render a line if the width is less than 1px. |
610 var dimension = (width > height) ? width : height; |
667 // In that case, we dilute the color to make the line look thinner. |
611 |
668 if (lineWidth < 1) { |
612 focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%"; |
669 opacity *= lineWidth; |
613 focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%"; |
670 } |
614 |
671 |
615 var colors = []; |
672 lineStr.push( |
616 |
673 '<g_vml_:stroke', |
617 // inside radius (%) |
674 ' opacity="', opacity, '"', |
618 if (this.fillStyle.type_ == "gradientradial") { |
675 ' joinstyle="', this.lineJoin, '"', |
619 var inside = (this.fillStyle.radius1_ / dimension * 100); |
676 ' miterlimit="', this.miterLimit, '"', |
620 |
677 ' endcap="', processLineCap(this.lineCap), '"', |
621 // percentage that outside radius exceeds inside radius |
678 ' weight="', lineWidth, 'px"', |
622 var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside; |
679 ' color="', color, '" />' |
|
680 ); |
|
681 } else if (typeof this.fillStyle == 'object') { |
|
682 var fillStyle = this.fillStyle; |
|
683 var angle = 0; |
|
684 var focus = {x: 0, y: 0}; |
|
685 |
|
686 // additional offset |
|
687 var shift = 0; |
|
688 // scale factor for offset |
|
689 var expansion = 1; |
|
690 |
|
691 if (fillStyle.type_ == 'gradient') { |
|
692 var x0 = fillStyle.x0_ / this.arcScaleX_; |
|
693 var y0 = fillStyle.y0_ / this.arcScaleY_; |
|
694 var x1 = fillStyle.x1_ / this.arcScaleX_; |
|
695 var y1 = fillStyle.y1_ / this.arcScaleY_; |
|
696 var p0 = this.getCoords_(x0, y0); |
|
697 var p1 = this.getCoords_(x1, y1); |
|
698 var dx = p1.x - p0.x; |
|
699 var dy = p1.y - p0.y; |
|
700 angle = Math.atan2(dx, dy) * 180 / Math.PI; |
|
701 |
|
702 // The angle should be a non-negative number. |
|
703 if (angle < 0) { |
|
704 angle += 360; |
|
705 } |
|
706 |
|
707 // Very small angles produce an unexpected result because they are |
|
708 // converted to a scientific notation string. |
|
709 if (angle < 1e-6) { |
|
710 angle = 0; |
|
711 } |
623 } else { |
712 } else { |
624 var inside = 0; |
713 var p0 = this.getCoords_(fillStyle.x0_, fillStyle.y0_); |
625 var expansion = 100; |
714 var width = max.x - min.x; |
626 } |
715 var height = max.y - min.y; |
627 |
716 focus = { |
628 var insidecolor = {offset: null, color: null}; |
717 x: (p0.x - min.x) / width, |
629 var outsidecolor = {offset: null, color: null}; |
718 y: (p0.y - min.y) / height |
630 |
719 }; |
631 // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie |
720 |
632 // won't interpret it correctly |
721 width /= this.arcScaleX_ * Z; |
633 this.fillStyle.colors_.sort(function (cs1, cs2) { |
722 height /= this.arcScaleY_ * Z; |
|
723 var dimension = m.max(width, height); |
|
724 shift = 2 * fillStyle.r0_ / dimension; |
|
725 expansion = 2 * fillStyle.r1_ / dimension - shift; |
|
726 } |
|
727 |
|
728 // We need to sort the color stops in ascending order by offset, |
|
729 // otherwise IE won't interpret it correctly. |
|
730 var stops = fillStyle.colors_; |
|
731 stops.sort(function(cs1, cs2) { |
634 return cs1.offset - cs2.offset; |
732 return cs1.offset - cs2.offset; |
635 }); |
733 }); |
636 |
734 |
637 for (var i = 0; i < this.fillStyle.colors_.length; i++) { |
735 var length = stops.length; |
638 var fs = this.fillStyle.colors_[i]; |
736 var color1 = stops[0].color; |
639 |
737 var color2 = stops[length - 1].color; |
640 colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ","); |
738 var opacity1 = stops[0].alpha * this.globalAlpha; |
641 |
739 var opacity2 = stops[length - 1].alpha * this.globalAlpha; |
642 if (fs.offset > insidecolor.offset || insidecolor.offset == null) { |
740 |
643 insidecolor.offset = fs.offset; |
741 var colors = []; |
644 insidecolor.color = fs.color; |
742 for (var i = 0; i < length; i++) { |
645 } |
743 var stop = stops[i]; |
646 |
744 colors.push(stop.offset * expansion + shift + ' ' + stop.color); |
647 if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) { |
745 } |
648 outsidecolor.offset = fs.offset; |
746 |
649 outsidecolor.color = fs.color; |
747 // When colors attribute is used, the meanings of opacity and o:opacity2 |
650 } |
748 // are reversed. |
651 } |
749 lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"', |
652 colors.pop(); |
750 ' method="none" focus="100%"', |
653 |
751 ' color="', color1, '"', |
654 lineStr.push('<g_vml_:fill', |
752 ' color2="', color2, '"', |
655 ' color="', outsidecolor.color, '"', |
753 ' colors="', colors.join(','), '"', |
656 ' color2="', insidecolor.color, '"', |
754 ' opacity="', opacity2, '"', |
657 ' type="', this.fillStyle.type_, '"', |
755 ' g_o_:opacity2="', opacity1, '"', |
658 ' focusposition="', focus.x, ', ', focus.y, '"', |
756 ' angle="', angle, '"', |
659 ' colors="', colors.join(""), '"', |
757 ' focusposition="', focus.x, ',', focus.y, '" />'); |
660 ' opacity="', opacity, '" />'); |
|
661 } else if (aFill) { |
|
662 lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />'); |
|
663 } else { |
758 } else { |
664 lineStr.push( |
759 lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, |
665 '<g_vml_:stroke', |
760 '" />'); |
666 ' opacity="', opacity,'"', |
761 } |
667 ' joinstyle="', this.lineJoin, '"', |
762 |
668 ' miterlimit="', this.miterLimit, '"', |
763 lineStr.push('</g_vml_:shape>'); |
669 ' endcap="', processLineCap(this.lineCap) ,'"', |
764 |
670 ' weight="', this.lineWidth, 'px"', |
765 this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); |
671 ' color="', color,'" />' |
|
672 ); |
|
673 } |
|
674 |
|
675 lineStr.push("</g_vml_:shape>"); |
|
676 |
|
677 this.element_.insertAdjacentHTML("beforeEnd", lineStr.join("")); |
|
678 |
|
679 //this.currentPath_ = []; |
|
680 }; |
766 }; |
681 |
767 |
682 contextPrototype.fill = function() { |
768 contextPrototype.fill = function() { |
683 this.stroke(true); |
769 this.stroke(true); |
684 }; |
770 } |
685 |
771 |
686 contextPrototype.closePath = function() { |
772 contextPrototype.closePath = function() { |
687 this.currentPath_.push({type: "close"}); |
773 this.currentPath_.push({type: 'close'}); |
688 }; |
774 }; |
689 |
775 |
690 /** |
776 /** |
691 * @private |
777 * @private |
692 */ |
778 */ |
693 contextPrototype.getCoords_ = function(aX, aY) { |
779 contextPrototype.getCoords_ = function(aX, aY) { |
|
780 var m = this.m_; |
694 return { |
781 return { |
695 x: Z * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - Z2, |
782 x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2, |
696 y: Z * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - Z2 |
783 y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2 |
697 } |
784 } |
698 }; |
785 }; |
699 |
786 |
700 contextPrototype.save = function() { |
787 contextPrototype.save = function() { |
701 var o = {}; |
788 var o = {}; |