Commit | Line | Data |
---|---|---|
6a1aa64f DV |
1 | // Copyright 2006 Google Inc. |
2 | // | |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | // you may not use this file except in compliance with the License. | |
5 | // You may obtain a copy of the License at | |
6 | // | |
7 | // http://www.apache.org/licenses/LICENSE-2.0 | |
8 | // | |
9 | // Unless required by applicable law or agreed to in writing, software | |
10 | // distributed under the License is distributed on an "AS IS" BASIS, | |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | // See the License for the specific language governing permissions and | |
13 | // limitations under the License. | |
14 | ||
15 | // TODO: Patterns | |
16 | // TODO: Radial gradient | |
17 | // TODO: Clipping paths | |
18 | // TODO: Coordsize (still need to support stretching) | |
19 | // TODO: Painting mode | |
20 | // TODO: Optimize | |
21 | // TODO: canvas width/height sets content size in moz, border size in ie | |
22 | ||
23 | // only add this code if we do not already have a canvas implementation | |
24 | if (!window.CanvasRenderingContext2D) { | |
25 | ||
26 | (function () { | |
27 | ||
28 | // alias some functions to make (compiled) code shorter | |
29 | var m = Math; | |
30 | var mr = m.round; | |
31 | var ms = m.sin; | |
32 | var mc = m.cos; | |
33 | ||
34 | var G_vmlCanvasManager_ = { | |
35 | init: function (opt_doc) { | |
36 | var doc = opt_doc || document; | |
37 | if (/MSIE/.test(navigator.userAgent) && !window.opera) { | |
38 | var self = this; | |
39 | doc.attachEvent("onreadystatechange", function () { | |
40 | self.init_(doc); | |
41 | }); | |
42 | } | |
43 | }, | |
44 | ||
45 | init_: function (doc, e) { | |
46 | if (doc.readyState == "complete") { | |
47 | // create xmlns | |
48 | if (!doc.namespaces["g_vml_"]) { | |
49 | doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml"); | |
50 | } | |
51 | ||
52 | // setup default css | |
53 | var ss = doc.createStyleSheet(); | |
54 | ss.cssText = "canvas{display:inline-block;overflow:hidden;" + | |
55 | "text-align:left;}" + | |
56 | "g_vml_\\:*{behavior:url(#default#VML)}"; | |
57 | ||
58 | // find all canvas elements | |
59 | var els = doc.getElementsByTagName("canvas"); | |
60 | for (var i = 0; i < els.length; i++) { | |
61 | if (!els[i].getContext) { | |
62 | this.initElement(els[i]); | |
63 | } | |
64 | } | |
65 | } | |
66 | }, | |
67 | ||
68 | fixElement_: function (el) { | |
69 | // in IE before version 5.5 we would need to add HTML: to the tag name | |
70 | // but we do not care about IE before version 6 | |
71 | var outerHTML = el.outerHTML; | |
72 | var newEl = document.createElement(outerHTML); | |
73 | // if the tag is still open IE has created the children as siblings and | |
74 | // it has also created a tag with the name "/FOO" | |
75 | if (outerHTML.slice(-2) != "/>") { | |
76 | var tagName = "/" + el.tagName; | |
77 | var ns; | |
78 | // remove content | |
79 | while ((ns = el.nextSibling) && ns.tagName != tagName) { | |
80 | ns.removeNode(); | |
81 | } | |
82 | // remove the incorrect closing tag | |
83 | if (ns) { | |
84 | ns.removeNode(); | |
85 | } | |
86 | } | |
87 | el.parentNode.replaceChild(newEl, el); | |
88 | return newEl; | |
89 | }, | |
90 | ||
91 | /** | |
92 | * Public initializes a canvas element so that it can be used as canvas | |
93 | * element from now on. This is called automatically before the page is | |
94 | * loaded but if you are creating elements using createElement you need to | |
95 | * make sure this is called on the element. | |
96 | * @param {HTMLElement} el The canvas element to initialize. | |
97 | * @return {HTMLElement} the element that was created. | |
98 | */ | |
99 | initElement: function (el) { | |
100 | el = this.fixElement_(el); | |
101 | el.getContext = function () { | |
102 | if (this.context_) { | |
103 | return this.context_; | |
104 | } | |
105 | return this.context_ = new CanvasRenderingContext2D_(this); | |
106 | }; | |
107 | ||
108 | // do not use inline function because that will leak memory | |
109 | // el.attachEvent('onpropertychange', onPropertyChange) | |
110 | el.attachEvent('onresize', onResize); | |
111 | ||
112 | var attrs = el.attributes; | |
113 | if (attrs.width && attrs.width.specified) { | |
114 | // TODO: use runtimeStyle and coordsize | |
115 | // el.getContext().setWidth_(attrs.width.nodeValue); | |
116 | el.style.width = attrs.width.nodeValue + "px"; | |
117 | } | |
118 | if (attrs.height && attrs.height.specified) { | |
119 | // TODO: use runtimeStyle and coordsize | |
120 | // el.getContext().setHeight_(attrs.height.nodeValue); | |
121 | el.style.height = attrs.height.nodeValue + "px"; | |
122 | } | |
123 | //el.getContext().setCoordsize_() | |
124 | return el; | |
125 | } | |
126 | }; | |
127 | ||
128 | function onPropertyChange(e) { | |
129 | // we need to watch changes to width and height | |
130 | switch (e.propertyName) { | |
131 | case 'width': | |
132 | case 'height': | |
133 | // TODO: coordsize and size | |
134 | break; | |
135 | } | |
136 | } | |
137 | ||
138 | function onResize(e) { | |
139 | var el = e.srcElement; | |
140 | if (el.firstChild) { | |
141 | el.firstChild.style.width = el.clientWidth + 'px'; | |
142 | el.firstChild.style.height = el.clientHeight + 'px'; | |
143 | } | |
144 | } | |
145 | ||
146 | G_vmlCanvasManager_.init(); | |
147 | ||
148 | // precompute "00" to "FF" | |
149 | var dec2hex = []; | |
150 | for (var i = 0; i < 16; i++) { | |
151 | for (var j = 0; j < 16; j++) { | |
152 | dec2hex[i * 16 + j] = i.toString(16) + j.toString(16); | |
153 | } | |
154 | } | |
155 | ||
156 | function createMatrixIdentity() { | |
157 | return [ | |
158 | [1, 0, 0], | |
159 | [0, 1, 0], | |
160 | [0, 0, 1] | |
161 | ]; | |
162 | } | |
163 | ||
164 | function matrixMultiply(m1, m2) { | |
165 | var result = createMatrixIdentity(); | |
166 | ||
167 | for (var x = 0; x < 3; x++) { | |
168 | for (var y = 0; y < 3; y++) { | |
169 | var sum = 0; | |
170 | ||
171 | for (var z = 0; z < 3; z++) { | |
172 | sum += m1[x][z] * m2[z][y]; | |
173 | } | |
174 | ||
175 | result[x][y] = sum; | |
176 | } | |
177 | } | |
178 | return result; | |
179 | } | |
180 | ||
181 | function copyState(o1, o2) { | |
182 | o2.fillStyle = o1.fillStyle; | |
183 | o2.lineCap = o1.lineCap; | |
184 | o2.lineJoin = o1.lineJoin; | |
185 | o2.lineWidth = o1.lineWidth; | |
186 | o2.miterLimit = o1.miterLimit; | |
187 | o2.shadowBlur = o1.shadowBlur; | |
188 | o2.shadowColor = o1.shadowColor; | |
189 | o2.shadowOffsetX = o1.shadowOffsetX; | |
190 | o2.shadowOffsetY = o1.shadowOffsetY; | |
191 | o2.strokeStyle = o1.strokeStyle; | |
192 | } | |
193 | ||
194 | function processStyle(styleString) { | |
195 | var str, alpha = 1; | |
196 | ||
197 | styleString = String(styleString); | |
198 | if (styleString.substring(0, 3) == "rgb") { | |
199 | var start = styleString.indexOf("(", 3); | |
200 | var end = styleString.indexOf(")", start + 1); | |
201 | var guts = styleString.substring(start + 1, end).split(","); | |
202 | ||
203 | str = "#"; | |
204 | for (var i = 0; i < 3; i++) { | |
205 | str += dec2hex[parseInt(guts[i])]; | |
206 | } | |
207 | ||
208 | if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) { | |
209 | alpha = guts[3]; | |
210 | } | |
211 | } else { | |
212 | str = styleString; | |
213 | } | |
214 | ||
215 | return [str, alpha]; | |
216 | } | |
217 | ||
218 | function processLineCap(lineCap) { | |
219 | switch (lineCap) { | |
220 | case "butt": | |
221 | return "flat"; | |
222 | case "round": | |
223 | return "round"; | |
224 | case "square": | |
225 | default: | |
226 | return "square"; | |
227 | } | |
228 | } | |
229 | ||
230 | /** | |
231 | * This class implements CanvasRenderingContext2D interface as described by | |
232 | * the WHATWG. | |
233 | * @param {HTMLElement} surfaceElement The element that the 2D context should | |
234 | * be associated with | |
235 | */ | |
236 | function CanvasRenderingContext2D_(surfaceElement) { | |
237 | this.m_ = createMatrixIdentity(); | |
238 | ||
239 | this.mStack_ = []; | |
240 | this.aStack_ = []; | |
241 | this.currentPath_ = []; | |
242 | ||
243 | // Canvas context properties | |
244 | this.strokeStyle = "#000"; | |
245 | this.fillStyle = "#ccc"; | |
246 | ||
247 | this.lineWidth = 1; | |
248 | this.lineJoin = "miter"; | |
249 | this.lineCap = "butt"; | |
250 | this.miterLimit = 10; | |
251 | this.globalAlpha = 1; | |
252 | ||
253 | var el = document.createElement('div'); | |
254 | el.style.width = surfaceElement.clientWidth + 'px'; | |
255 | el.style.height = surfaceElement.clientHeight + 'px'; | |
256 | el.style.overflow = 'hidden'; | |
257 | el.style.position = 'absolute'; | |
258 | surfaceElement.appendChild(el); | |
259 | ||
260 | this.element_ = el; | |
261 | this.arcScaleX_ = 1; | |
262 | this.arcScaleY_ = 1; | |
263 | }; | |
264 | ||
265 | var contextPrototype = CanvasRenderingContext2D_.prototype; | |
266 | contextPrototype.clearRect = function() { | |
267 | this.element_.innerHTML = ""; | |
268 | this.currentPath_ = []; | |
269 | }; | |
270 | ||
271 | contextPrototype.beginPath = function() { | |
272 | // TODO: Branch current matrix so that save/restore has no effect | |
273 | // as per safari docs. | |
274 | ||
275 | this.currentPath_ = []; | |
276 | }; | |
277 | ||
278 | contextPrototype.moveTo = function(aX, aY) { | |
279 | this.currentPath_.push({type: "moveTo", x: aX, y: aY}); | |
280 | }; | |
281 | ||
282 | contextPrototype.lineTo = function(aX, aY) { | |
283 | this.currentPath_.push({type: "lineTo", x: aX, y: aY}); | |
284 | }; | |
285 | ||
286 | contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, | |
287 | aCP2x, aCP2y, | |
288 | aX, aY) { | |
289 | this.currentPath_.push({type: "bezierCurveTo", | |
290 | cp1x: aCP1x, | |
291 | cp1y: aCP1y, | |
292 | cp2x: aCP2x, | |
293 | cp2y: aCP2y, | |
294 | x: aX, | |
295 | y: aY}); | |
296 | }; | |
297 | ||
298 | contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { | |
299 | // VML's qb produces different output to Firefox's | |
300 | // FF's behaviour seems to have changed in 1.5.0.1, check this | |
301 | this.bezierCurveTo(aCPx, aCPy, aCPx, aCPy, aX, aY); | |
302 | }; | |
303 | ||
304 | contextPrototype.arc = function(aX, aY, aRadius, | |
305 | aStartAngle, aEndAngle, aClockwise) { | |
306 | aRadius *= 10; | |
307 | var arcType = aClockwise ? "at" : "wa"; | |
308 | ||
309 | var xStart = aX + (mc(aStartAngle) * aRadius) - 5; | |
310 | var yStart = aY + (ms(aStartAngle) * aRadius) - 5; | |
311 | ||
312 | var xEnd = aX + (mc(aEndAngle) * aRadius) - 5; | |
313 | var yEnd = aY + (ms(aEndAngle) * aRadius) - 5; | |
314 | ||
315 | this.currentPath_.push({type: arcType, | |
316 | x: aX, | |
317 | y: aY, | |
318 | radius: aRadius, | |
319 | xStart: xStart, | |
320 | yStart: yStart, | |
321 | xEnd: xEnd, | |
322 | yEnd: yEnd}); | |
323 | ||
324 | }; | |
325 | ||
326 | contextPrototype.rect = function(aX, aY, aWidth, aHeight) { | |
327 | this.moveTo(aX, aY); | |
328 | this.lineTo(aX + aWidth, aY); | |
329 | this.lineTo(aX + aWidth, aY + aHeight); | |
330 | this.lineTo(aX, aY + aHeight); | |
331 | this.closePath(); | |
332 | }; | |
333 | ||
334 | contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { | |
335 | // Will destroy any existing path (same as FF behaviour) | |
336 | this.beginPath(); | |
337 | this.moveTo(aX, aY); | |
338 | this.lineTo(aX + aWidth, aY); | |
339 | this.lineTo(aX + aWidth, aY + aHeight); | |
340 | this.lineTo(aX, aY + aHeight); | |
341 | this.closePath(); | |
342 | this.stroke(); | |
343 | }; | |
344 | ||
345 | contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { | |
346 | // Will destroy any existing path (same as FF behaviour) | |
347 | this.beginPath(); | |
348 | this.moveTo(aX, aY); | |
349 | this.lineTo(aX + aWidth, aY); | |
350 | this.lineTo(aX + aWidth, aY + aHeight); | |
351 | this.lineTo(aX, aY + aHeight); | |
352 | this.closePath(); | |
353 | this.fill(); | |
354 | }; | |
355 | ||
356 | contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { | |
357 | var gradient = new CanvasGradient_("gradient"); | |
358 | return gradient; | |
359 | }; | |
360 | ||
361 | contextPrototype.createRadialGradient = function(aX0, aY0, | |
362 | aR0, aX1, | |
363 | aY1, aR1) { | |
364 | var gradient = new CanvasGradient_("gradientradial"); | |
365 | gradient.radius1_ = aR0; | |
366 | gradient.radius2_ = aR1; | |
367 | gradient.focus_.x = aX0; | |
368 | gradient.focus_.y = aY0; | |
369 | return gradient; | |
370 | }; | |
371 | ||
372 | contextPrototype.drawImage = function (image, var_args) { | |
373 | var dx, dy, dw, dh, sx, sy, sw, sh; | |
374 | var w = image.width; | |
375 | var h = image.height; | |
376 | ||
377 | if (arguments.length == 3) { | |
378 | dx = arguments[1]; | |
379 | dy = arguments[2]; | |
380 | sx = sy = 0; | |
381 | sw = dw = w; | |
382 | sh = dh = h; | |
383 | } else if (arguments.length == 5) { | |
384 | dx = arguments[1]; | |
385 | dy = arguments[2]; | |
386 | dw = arguments[3]; | |
387 | dh = arguments[4]; | |
388 | sx = sy = 0; | |
389 | sw = w; | |
390 | sh = h; | |
391 | } else if (arguments.length == 9) { | |
392 | sx = arguments[1]; | |
393 | sy = arguments[2]; | |
394 | sw = arguments[3]; | |
395 | sh = arguments[4]; | |
396 | dx = arguments[5]; | |
397 | dy = arguments[6]; | |
398 | dw = arguments[7]; | |
399 | dh = arguments[8]; | |
400 | } else { | |
401 | throw "Invalid number of arguments"; | |
402 | } | |
403 | ||
404 | var d = this.getCoords_(dx, dy); | |
405 | ||
406 | var w2 = (sw / 2); | |
407 | var h2 = (sh / 2); | |
408 | ||
409 | var vmlStr = []; | |
410 | ||
411 | // For some reason that I've now forgotten, using divs didn't work | |
412 | vmlStr.push(' <g_vml_:group', | |
413 | ' coordsize="1000,1000"', | |
414 | ' coordorigin="0, 0"' , | |
415 | ' style="width:100px;height:100px;position:absolute;'); | |
416 | ||
417 | // If filters are necessary (rotation exists), create them | |
418 | // filters are bog-slow, so only create them if abbsolutely necessary | |
419 | // The following check doesn't account for skews (which don't exist | |
420 | // in the canvas spec (yet) anyway. | |
421 | ||
422 | if (this.m_[0][0] != 1 || this.m_[0][1]) { | |
423 | var filter = []; | |
424 | ||
425 | // Note the 12/21 reversal | |
426 | filter.push("M11='", this.m_[0][0], "',", | |
427 | "M12='", this.m_[1][0], "',", | |
428 | "M21='", this.m_[0][1], "',", | |
429 | "M22='", this.m_[1][1], "',", | |
430 | "Dx='", d.x, "',", | |
431 | "Dy='", d.y, "'"); | |
432 | ||
433 | // Bounding box calculation (need to minimize displayed area so that | |
434 | // filters don't waste time on unused pixels. | |
435 | var max = d; | |
436 | var c2 = this.getCoords_(dx+dw, dy); | |
437 | var c3 = this.getCoords_(dx, dy+dh); | |
438 | var c4 = this.getCoords_(dx+dw, dy+dh); | |
439 | ||
440 | max.x = Math.max(max.x, c2.x, c3.x, c4.x); | |
441 | max.y = Math.max(max.y, c2.y, c3.y, c4.y); | |
442 | ||
443 | vmlStr.push(" padding:0 ", mr(max.x), "px ", mr(max.y), | |
444 | "px 0;filter:progid:DXImageTransform.Microsoft.Matrix(", | |
445 | filter.join(""), ", sizingmethod='clip');") | |
446 | } else { | |
447 | vmlStr.push(" top:", d.y, "px;left:", d.x, "px;") | |
448 | } | |
449 | ||
450 | vmlStr.push(' ">' , | |
451 | '<g_vml_:image src="', image.src, '"', | |
452 | ' style="width:', dw, ';', | |
453 | ' height:', dh, ';"', | |
454 | ' cropleft="', sx / w, '"', | |
455 | ' croptop="', sy / h, '"', | |
456 | ' cropright="', (w - sx - sw) / w, '"', | |
457 | ' cropbottom="', (h - sy - sh) / h, '"', | |
458 | ' />', | |
459 | '</g_vml_:group>'); | |
460 | ||
461 | this.element_.insertAdjacentHTML("BeforeEnd", | |
462 | vmlStr.join("")); | |
463 | }; | |
464 | ||
465 | contextPrototype.stroke = function(aFill) { | |
466 | var lineStr = []; | |
467 | var lineOpen = false; | |
468 | var a = processStyle(aFill ? this.fillStyle : this.strokeStyle); | |
469 | var color = a[0]; | |
470 | var opacity = a[1] * this.globalAlpha; | |
471 | ||
472 | lineStr.push('<g_vml_:shape', | |
473 | ' fillcolor="', color, '"', | |
474 | ' filled="', Boolean(aFill), '"', | |
475 | ' style="position:absolute;width:10;height:10;"', | |
476 | ' coordorigin="0 0" coordsize="100 100"', | |
477 | ' stroked="', !aFill, '"', | |
478 | ' strokeweight="', this.lineWidth, '"', | |
479 | ' strokecolor="', color, '"', | |
480 | ' path="'); | |
481 | ||
482 | var newSeq = false; | |
483 | var min = {x: null, y: null}; | |
484 | var max = {x: null, y: null}; | |
485 | ||
486 | for (var i = 0; i < this.currentPath_.length; i++) { | |
487 | var p = this.currentPath_[i]; | |
488 | ||
489 | if (p.type == "moveTo") { | |
490 | lineStr.push(" m "); | |
491 | var c = this.getCoords_(p.x, p.y); | |
492 | lineStr.push(mr(c.x), ",", mr(c.y)); | |
493 | } else if (p.type == "lineTo") { | |
494 | lineStr.push(" l "); | |
495 | var c = this.getCoords_(p.x, p.y); | |
496 | lineStr.push(mr(c.x), ",", mr(c.y)); | |
497 | } else if (p.type == "close") { | |
498 | lineStr.push(" x "); | |
499 | } else if (p.type == "bezierCurveTo") { | |
500 | lineStr.push(" c "); | |
501 | var c = this.getCoords_(p.x, p.y); | |
502 | var c1 = this.getCoords_(p.cp1x, p.cp1y); | |
503 | var c2 = this.getCoords_(p.cp2x, p.cp2y); | |
504 | lineStr.push(mr(c1.x), ",", mr(c1.y), ",", | |
505 | mr(c2.x), ",", mr(c2.y), ",", | |
506 | mr(c.x), ",", mr(c.y)); | |
507 | } else if (p.type == "at" || p.type == "wa") { | |
508 | lineStr.push(" ", p.type, " "); | |
509 | var c = this.getCoords_(p.x, p.y); | |
510 | var cStart = this.getCoords_(p.xStart, p.yStart); | |
511 | var cEnd = this.getCoords_(p.xEnd, p.yEnd); | |
512 | ||
513 | lineStr.push(mr(c.x - this.arcScaleX_ * p.radius), ",", | |
514 | mr(c.y - this.arcScaleY_ * p.radius), " ", | |
515 | mr(c.x + this.arcScaleX_ * p.radius), ",", | |
516 | mr(c.y + this.arcScaleY_ * p.radius), " ", | |
517 | mr(cStart.x), ",", mr(cStart.y), " ", | |
518 | mr(cEnd.x), ",", mr(cEnd.y)); | |
519 | } | |
520 | ||
521 | ||
522 | // TODO: Following is broken for curves due to | |
523 | // move to proper paths. | |
524 | ||
525 | // Figure out dimensions so we can do gradient fills | |
526 | // properly | |
527 | if(c) { | |
528 | if (min.x == null || c.x < min.x) { | |
529 | min.x = c.x; | |
530 | } | |
531 | if (max.x == null || c.x > max.x) { | |
532 | max.x = c.x; | |
533 | } | |
534 | if (min.y == null || c.y < min.y) { | |
535 | min.y = c.y; | |
536 | } | |
537 | if (max.y == null || c.y > max.y) { | |
538 | max.y = c.y; | |
539 | } | |
540 | } | |
541 | } | |
542 | lineStr.push(' ">'); | |
543 | ||
544 | if (typeof this.fillStyle == "object") { | |
545 | var focus = {x: "50%", y: "50%"}; | |
546 | var width = (max.x - min.x); | |
547 | var height = (max.y - min.y); | |
548 | var dimension = (width > height) ? width : height; | |
549 | ||
550 | focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%"; | |
551 | focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%"; | |
552 | ||
553 | var colors = []; | |
554 | ||
555 | // inside radius (%) | |
556 | if (this.fillStyle.type_ == "gradientradial") { | |
557 | var inside = (this.fillStyle.radius1_ / dimension * 100); | |
558 | ||
559 | // percentage that outside radius exceeds inside radius | |
560 | var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside; | |
561 | } else { | |
562 | var inside = 0; | |
563 | var expansion = 100; | |
564 | } | |
565 | ||
566 | var insidecolor = {offset: null, color: null}; | |
567 | var outsidecolor = {offset: null, color: null}; | |
568 | ||
569 | // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie | |
570 | // won't interpret it correctly | |
571 | this.fillStyle.colors_.sort(function (cs1, cs2) { | |
572 | return cs1.offset - cs2.offset; | |
573 | }); | |
574 | ||
575 | for (var i = 0; i < this.fillStyle.colors_.length; i++) { | |
576 | var fs = this.fillStyle.colors_[i]; | |
577 | ||
578 | colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ","); | |
579 | ||
580 | if (fs.offset > insidecolor.offset || insidecolor.offset == null) { | |
581 | insidecolor.offset = fs.offset; | |
582 | insidecolor.color = fs.color; | |
583 | } | |
584 | ||
585 | if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) { | |
586 | outsidecolor.offset = fs.offset; | |
587 | outsidecolor.color = fs.color; | |
588 | } | |
589 | } | |
590 | colors.pop(); | |
591 | ||
592 | lineStr.push('<g_vml_:fill', | |
593 | ' color="', outsidecolor.color, '"', | |
594 | ' color2="', insidecolor.color, '"', | |
595 | ' type="', this.fillStyle.type_, '"', | |
596 | ' focusposition="', focus.x, ', ', focus.y, '"', | |
597 | ' colors="', colors.join(""), '"', | |
598 | ' opacity="', opacity, '" />'); | |
599 | } else if (aFill) { | |
600 | lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />'); | |
601 | } else { | |
602 | lineStr.push( | |
603 | '<g_vml_:stroke', | |
604 | ' opacity="', opacity,'"', | |
605 | ' joinstyle="', this.lineJoin, '"', | |
606 | ' miterlimit="', this.miterLimit, '"', | |
607 | ' endcap="', processLineCap(this.lineCap) ,'"', | |
608 | ' weight="', this.lineWidth, 'px"', | |
609 | ' color="', color,'" />' | |
610 | ); | |
611 | } | |
612 | ||
613 | lineStr.push("</g_vml_:shape>"); | |
614 | ||
615 | this.element_.insertAdjacentHTML("beforeEnd", lineStr.join("")); | |
616 | ||
617 | this.currentPath_ = []; | |
618 | }; | |
619 | ||
620 | contextPrototype.fill = function() { | |
621 | this.stroke(true); | |
622 | } | |
623 | ||
624 | contextPrototype.closePath = function() { | |
625 | this.currentPath_.push({type: "close"}); | |
626 | }; | |
627 | ||
628 | /** | |
629 | * @private | |
630 | */ | |
631 | contextPrototype.getCoords_ = function(aX, aY) { | |
632 | return { | |
633 | x: 10 * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - 5, | |
634 | y: 10 * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - 5 | |
635 | } | |
636 | }; | |
637 | ||
638 | contextPrototype.save = function() { | |
639 | var o = {}; | |
640 | copyState(this, o); | |
641 | this.aStack_.push(o); | |
642 | this.mStack_.push(this.m_); | |
643 | this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); | |
644 | }; | |
645 | ||
646 | contextPrototype.restore = function() { | |
647 | copyState(this.aStack_.pop(), this); | |
648 | this.m_ = this.mStack_.pop(); | |
649 | }; | |
650 | ||
651 | contextPrototype.translate = function(aX, aY) { | |
652 | var m1 = [ | |
653 | [1, 0, 0], | |
654 | [0, 1, 0], | |
655 | [aX, aY, 1] | |
656 | ]; | |
657 | ||
658 | this.m_ = matrixMultiply(m1, this.m_); | |
659 | }; | |
660 | ||
661 | contextPrototype.rotate = function(aRot) { | |
662 | var c = mc(aRot); | |
663 | var s = ms(aRot); | |
664 | ||
665 | var m1 = [ | |
666 | [c, s, 0], | |
667 | [-s, c, 0], | |
668 | [0, 0, 1] | |
669 | ]; | |
670 | ||
671 | this.m_ = matrixMultiply(m1, this.m_); | |
672 | }; | |
673 | ||
674 | contextPrototype.scale = function(aX, aY) { | |
675 | this.arcScaleX_ *= aX; | |
676 | this.arcScaleY_ *= aY; | |
677 | var m1 = [ | |
678 | [aX, 0, 0], | |
679 | [0, aY, 0], | |
680 | [0, 0, 1] | |
681 | ]; | |
682 | ||
683 | this.m_ = matrixMultiply(m1, this.m_); | |
684 | }; | |
685 | ||
686 | /******** STUBS ********/ | |
687 | contextPrototype.clip = function() { | |
688 | // TODO: Implement | |
689 | }; | |
690 | ||
691 | contextPrototype.arcTo = function() { | |
692 | // TODO: Implement | |
693 | }; | |
694 | ||
695 | contextPrototype.createPattern = function() { | |
696 | return new CanvasPattern_; | |
697 | }; | |
698 | ||
699 | // Gradient / Pattern Stubs | |
700 | function CanvasGradient_(aType) { | |
701 | this.type_ = aType; | |
702 | this.radius1_ = 0; | |
703 | this.radius2_ = 0; | |
704 | this.colors_ = []; | |
705 | this.focus_ = {x: 0, y: 0}; | |
706 | } | |
707 | ||
708 | CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { | |
709 | aColor = processStyle(aColor); | |
710 | this.colors_.push({offset: 1-aOffset, color: aColor}); | |
711 | }; | |
712 | ||
713 | function CanvasPattern_() {} | |
714 | ||
715 | // set up externs | |
716 | G_vmlCanvasManager = G_vmlCanvasManager_; | |
717 | CanvasRenderingContext2D = CanvasRenderingContext2D_; | |
718 | CanvasGradient = CanvasGradient_; | |
719 | CanvasPattern = CanvasPattern_; | |
720 | ||
721 | })(); | |
722 | ||
723 | } // if |