1 | define(["./_base", "dojo/_base/lang", "dojo/_base/array", "dojo/_base/declare", "dojo/_base/window", "dojo/dom-geometry", |
---|
2 | "dojo/dom", "./_base", "./shape", "./path", "./arc", "./matrix", "./decompose"], |
---|
3 | function(g, lang, arr, declare, win, domGeom, dom, gfxBase, gs, pathLib, ga, m, decompose ){ |
---|
4 | /*===== |
---|
5 | dojox.gfx.canvas = { |
---|
6 | // module: |
---|
7 | // dojox/gfx/canvas |
---|
8 | // summary: |
---|
9 | // This the graphics rendering bridge for W3C Canvas compliant browsers. |
---|
10 | // Since Canvas is an immediate mode graphics api, with no object graph or |
---|
11 | // eventing capabilities, use of this module alone will only add in drawing support. |
---|
12 | // The additional module, canvasWithEvents extends this module with additional support |
---|
13 | // for handling events on Canvas. By default, the support for events is now included |
---|
14 | // however, if only drawing capabilities are needed, canvas event module can be disabled |
---|
15 | // using the dojoConfig option, canvasEvents:true|false. |
---|
16 | // The id of the Canvas renderer is 'canvas'. This id can be used when switch Dojo's |
---|
17 | // graphics context between renderer implementations. See dojox.gfx._base switchRenderer |
---|
18 | // API. |
---|
19 | }; |
---|
20 | g = dojox.gfx; |
---|
21 | gs = dojox.gfx.shape; |
---|
22 | pathLib.Path = dojox.gfx.path.Path; |
---|
23 | pathLib.TextPath = dojox.gfx.path.TextPath; |
---|
24 | canvas = dojox.gfx.canvas; |
---|
25 | canvas.Shape = dojox.gfx.canvas.Shape; |
---|
26 | gs.Shape = dojox.gfx.shape.Shape; |
---|
27 | gs.Rect = dojox.gfx.shape.Rect; |
---|
28 | gs.Ellipse = dojox.gfx.shape.Ellipse; |
---|
29 | gs.Circle = dojox.gfx.shape.Circle; |
---|
30 | gs.Line = dojox.gfx.shape.Line; |
---|
31 | gs.PolyLine = dojox.gfx.shape.PolyLine; |
---|
32 | gs.Image = dojox.gfx.shape.Image; |
---|
33 | gs.Text = dojox.gfx.shape.Text; |
---|
34 | gs.Surface = dojox.gfx.shape.Surface; |
---|
35 | =====*/ |
---|
36 | |
---|
37 | var canvas = g.canvas = {}; |
---|
38 | var pattrnbuffer = null, |
---|
39 | mp = m.multiplyPoint, |
---|
40 | pi = Math.PI, |
---|
41 | twoPI = 2 * pi, |
---|
42 | halfPI = pi /2, |
---|
43 | extend = lang.extend; |
---|
44 | |
---|
45 | declare("dojox.gfx.canvas.Shape", gs.Shape, { |
---|
46 | _render: function(/* Object */ ctx){ |
---|
47 | // summary: render the shape |
---|
48 | ctx.save(); |
---|
49 | this._renderTransform(ctx); |
---|
50 | this._renderShape(ctx); |
---|
51 | this._renderFill(ctx, true); |
---|
52 | this._renderStroke(ctx, true); |
---|
53 | ctx.restore(); |
---|
54 | }, |
---|
55 | _renderTransform: function(/* Object */ ctx){ |
---|
56 | if("canvasTransform" in this){ |
---|
57 | var t = this.canvasTransform; |
---|
58 | ctx.translate(t.dx, t.dy); |
---|
59 | ctx.rotate(t.angle2); |
---|
60 | ctx.scale(t.sx, t.sy); |
---|
61 | ctx.rotate(t.angle1); |
---|
62 | // The future implementation when vendors catch up with the spec: |
---|
63 | // var t = this.matrix; |
---|
64 | // ctx.transform(t.xx, t.yx, t.xy, t.yy, t.dx, t.dy); |
---|
65 | } |
---|
66 | }, |
---|
67 | _renderShape: function(/* Object */ ctx){ |
---|
68 | // nothing |
---|
69 | }, |
---|
70 | _renderFill: function(/* Object */ ctx, /* Boolean */ apply){ |
---|
71 | if("canvasFill" in this){ |
---|
72 | var fs = this.fillStyle; |
---|
73 | if("canvasFillImage" in this){ |
---|
74 | var w = fs.width, h = fs.height, |
---|
75 | iw = this.canvasFillImage.width, ih = this.canvasFillImage.height, |
---|
76 | // let's match the svg default behavior wrt. aspect ratio: xMidYMid meet |
---|
77 | sx = w == iw ? 1 : w / iw, |
---|
78 | sy = h == ih ? 1 : h / ih, |
---|
79 | s = Math.min(sx,sy), //meet->math.min , slice->math.max |
---|
80 | dx = (w - s * iw)/2, |
---|
81 | dy = (h - s * ih)/2; |
---|
82 | // the buffer used to scaled the image |
---|
83 | pattrnbuffer.width = w; pattrnbuffer.height = h; |
---|
84 | var copyctx = pattrnbuffer.getContext("2d"); |
---|
85 | copyctx.clearRect(0, 0, w, h); |
---|
86 | copyctx.drawImage(this.canvasFillImage, 0, 0, iw, ih, dx, dy, s*iw, s*ih); |
---|
87 | this.canvasFill = ctx.createPattern(pattrnbuffer, "repeat"); |
---|
88 | delete this.canvasFillImage; |
---|
89 | } |
---|
90 | ctx.fillStyle = this.canvasFill; |
---|
91 | if(apply){ |
---|
92 | // offset the pattern |
---|
93 | if (fs.type==="pattern" && (fs.x !== 0 || fs.y !== 0)) { |
---|
94 | ctx.translate(fs.x,fs.y); |
---|
95 | } |
---|
96 | ctx.fill(); |
---|
97 | } |
---|
98 | }else{ |
---|
99 | ctx.fillStyle = "rgba(0,0,0,0.0)"; |
---|
100 | } |
---|
101 | }, |
---|
102 | _renderStroke: function(/* Object */ ctx, /* Boolean */ apply){ |
---|
103 | var s = this.strokeStyle; |
---|
104 | if(s){ |
---|
105 | ctx.strokeStyle = s.color.toString(); |
---|
106 | ctx.lineWidth = s.width; |
---|
107 | ctx.lineCap = s.cap; |
---|
108 | if(typeof s.join == "number"){ |
---|
109 | ctx.lineJoin = "miter"; |
---|
110 | ctx.miterLimit = s.join; |
---|
111 | }else{ |
---|
112 | ctx.lineJoin = s.join; |
---|
113 | } |
---|
114 | if(apply){ ctx.stroke(); } |
---|
115 | }else if(!apply){ |
---|
116 | ctx.strokeStyle = "rgba(0,0,0,0.0)"; |
---|
117 | } |
---|
118 | }, |
---|
119 | |
---|
120 | // events are not implemented |
---|
121 | getEventSource: function(){ return null; }, |
---|
122 | connect: function(){}, |
---|
123 | disconnect: function(){} |
---|
124 | }); |
---|
125 | |
---|
126 | var modifyMethod = function(shape, method, extra){ |
---|
127 | var old = shape.prototype[method]; |
---|
128 | shape.prototype[method] = extra ? |
---|
129 | function(){ |
---|
130 | this.surface.makeDirty(); |
---|
131 | old.apply(this, arguments); |
---|
132 | extra.call(this); |
---|
133 | return this; |
---|
134 | } : |
---|
135 | function(){ |
---|
136 | this.surface.makeDirty(); |
---|
137 | return old.apply(this, arguments); |
---|
138 | }; |
---|
139 | }; |
---|
140 | |
---|
141 | modifyMethod(canvas.Shape, "setTransform", |
---|
142 | function(){ |
---|
143 | // prepare Canvas-specific structures |
---|
144 | if(this.matrix){ |
---|
145 | this.canvasTransform = g.decompose(this.matrix); |
---|
146 | }else{ |
---|
147 | delete this.canvasTransform; |
---|
148 | } |
---|
149 | }); |
---|
150 | |
---|
151 | modifyMethod(canvas.Shape, "setFill", |
---|
152 | function(){ |
---|
153 | // prepare Canvas-specific structures |
---|
154 | var fs = this.fillStyle, f; |
---|
155 | if(fs){ |
---|
156 | if(typeof(fs) == "object" && "type" in fs){ |
---|
157 | var ctx = this.surface.rawNode.getContext("2d"); |
---|
158 | switch(fs.type){ |
---|
159 | case "linear": |
---|
160 | case "radial": |
---|
161 | f = fs.type == "linear" ? |
---|
162 | ctx.createLinearGradient(fs.x1, fs.y1, fs.x2, fs.y2) : |
---|
163 | ctx.createRadialGradient(fs.cx, fs.cy, 0, fs.cx, fs.cy, fs.r); |
---|
164 | arr.forEach(fs.colors, function(step){ |
---|
165 | f.addColorStop(step.offset, g.normalizeColor(step.color).toString()); |
---|
166 | }); |
---|
167 | break; |
---|
168 | case "pattern": |
---|
169 | if (!pattrnbuffer) { |
---|
170 | pattrnbuffer = document.createElement("canvas"); |
---|
171 | } |
---|
172 | // no need to scale the image since the canvas.createPattern uses |
---|
173 | // the original image data and not the scaled ones (see spec.) |
---|
174 | // the scaling needs to be done at rendering time in a context buffer |
---|
175 | var img =new Image(); |
---|
176 | this.surface.downloadImage(img, fs.src); |
---|
177 | this.canvasFillImage = img; |
---|
178 | } |
---|
179 | }else{ |
---|
180 | // Set fill color using CSS RGBA func style |
---|
181 | f = fs.toString(); |
---|
182 | } |
---|
183 | this.canvasFill = f; |
---|
184 | }else{ |
---|
185 | delete this.canvasFill; |
---|
186 | } |
---|
187 | }); |
---|
188 | |
---|
189 | modifyMethod(canvas.Shape, "setStroke"); |
---|
190 | modifyMethod(canvas.Shape, "setShape"); |
---|
191 | |
---|
192 | declare("dojox.gfx.canvas.Group", canvas.Shape, { |
---|
193 | // summary: a group shape (Canvas), which can be used |
---|
194 | // to logically group shapes (e.g, to propagate matricies) |
---|
195 | constructor: function(){ |
---|
196 | gs.Container._init.call(this); |
---|
197 | }, |
---|
198 | _render: function(/* Object */ ctx){ |
---|
199 | // summary: render the group |
---|
200 | ctx.save(); |
---|
201 | this._renderTransform(ctx); |
---|
202 | for(var i = 0; i < this.children.length; ++i){ |
---|
203 | this.children[i]._render(ctx); |
---|
204 | } |
---|
205 | ctx.restore(); |
---|
206 | } |
---|
207 | }); |
---|
208 | |
---|
209 | declare("dojox.gfx.canvas.Rect", [canvas.Shape, gs.Rect], { |
---|
210 | // summary: a rectangle shape (Canvas) |
---|
211 | _renderShape: function(/* Object */ ctx){ |
---|
212 | var s = this.shape, r = Math.min(s.r, s.height / 2, s.width / 2), |
---|
213 | xl = s.x, xr = xl + s.width, yt = s.y, yb = yt + s.height, |
---|
214 | xl2 = xl + r, xr2 = xr - r, yt2 = yt + r, yb2 = yb - r; |
---|
215 | ctx.beginPath(); |
---|
216 | ctx.moveTo(xl2, yt); |
---|
217 | if(r){ |
---|
218 | ctx.arc(xr2, yt2, r, -halfPI, 0, false); |
---|
219 | ctx.arc(xr2, yb2, r, 0, halfPI, false); |
---|
220 | ctx.arc(xl2, yb2, r, halfPI, pi, false); |
---|
221 | ctx.arc(xl2, yt2, r, pi, pi + halfPI, false); |
---|
222 | }else{ |
---|
223 | ctx.lineTo(xr2, yt); |
---|
224 | ctx.lineTo(xr, yb2); |
---|
225 | ctx.lineTo(xl2, yb); |
---|
226 | ctx.lineTo(xl, yt2); |
---|
227 | } |
---|
228 | ctx.closePath(); |
---|
229 | } |
---|
230 | }); |
---|
231 | |
---|
232 | var bezierCircle = []; |
---|
233 | (function(){ |
---|
234 | var u = ga.curvePI4; |
---|
235 | bezierCircle.push(u.s, u.c1, u.c2, u.e); |
---|
236 | for(var a = 45; a < 360; a += 45){ |
---|
237 | var r = m.rotateg(a); |
---|
238 | bezierCircle.push(mp(r, u.c1), mp(r, u.c2), mp(r, u.e)); |
---|
239 | } |
---|
240 | })(); |
---|
241 | |
---|
242 | declare("dojox.gfx.canvas.Ellipse", [canvas.Shape, gs.Ellipse], { |
---|
243 | // summary: an ellipse shape (Canvas) |
---|
244 | setShape: function(){ |
---|
245 | this.inherited(arguments); |
---|
246 | // prepare Canvas-specific structures |
---|
247 | var s = this.shape, t, c1, c2, r = [], |
---|
248 | M = m.normalize([m.translate(s.cx, s.cy), m.scale(s.rx, s.ry)]); |
---|
249 | t = mp(M, bezierCircle[0]); |
---|
250 | r.push([t.x, t.y]); |
---|
251 | for(var i = 1; i < bezierCircle.length; i += 3){ |
---|
252 | c1 = mp(M, bezierCircle[i]); |
---|
253 | c2 = mp(M, bezierCircle[i + 1]); |
---|
254 | t = mp(M, bezierCircle[i + 2]); |
---|
255 | r.push([c1.x, c1.y, c2.x, c2.y, t.x, t.y]); |
---|
256 | } |
---|
257 | this.canvasEllipse = r; |
---|
258 | return this; |
---|
259 | }, |
---|
260 | _renderShape: function(/* Object */ ctx){ |
---|
261 | var r = this.canvasEllipse; |
---|
262 | ctx.beginPath(); |
---|
263 | ctx.moveTo.apply(ctx, r[0]); |
---|
264 | for(var i = 1; i < r.length; ++i){ |
---|
265 | ctx.bezierCurveTo.apply(ctx, r[i]); |
---|
266 | } |
---|
267 | ctx.closePath(); |
---|
268 | } |
---|
269 | }); |
---|
270 | |
---|
271 | declare("dojox.gfx.canvas.Circle", [canvas.Shape, gs.Circle], { |
---|
272 | // summary: a circle shape (Canvas) |
---|
273 | _renderShape: function(/* Object */ ctx){ |
---|
274 | var s = this.shape; |
---|
275 | ctx.beginPath(); |
---|
276 | ctx.arc(s.cx, s.cy, s.r, 0, twoPI, 1); |
---|
277 | } |
---|
278 | }); |
---|
279 | |
---|
280 | declare("dojox.gfx.canvas.Line", [canvas.Shape, gs.Line], { |
---|
281 | // summary: a line shape (Canvas) |
---|
282 | _renderShape: function(/* Object */ ctx){ |
---|
283 | var s = this.shape; |
---|
284 | ctx.beginPath(); |
---|
285 | ctx.moveTo(s.x1, s.y1); |
---|
286 | ctx.lineTo(s.x2, s.y2); |
---|
287 | } |
---|
288 | }); |
---|
289 | |
---|
290 | declare("dojox.gfx.canvas.Polyline", [canvas.Shape, gs.Polyline], { |
---|
291 | // summary: a polyline/polygon shape (Canvas) |
---|
292 | setShape: function(){ |
---|
293 | this.inherited(arguments); |
---|
294 | var p = this.shape.points, f = p[0], r, c, i; |
---|
295 | this.bbox = null; |
---|
296 | // normalize this.shape.points as array of points: [{x,y}, {x,y}, ...] |
---|
297 | this._normalizePoints(); |
---|
298 | // after _normalizePoints, if shape.points was [x1,y1,x2,y2,..], shape.points references a new array |
---|
299 | // and p references the original points array |
---|
300 | // prepare Canvas-specific structures, if needed |
---|
301 | if(p.length){ |
---|
302 | if(typeof f == "number"){ // already in the canvas format [x1,y1,x2,y2,...] |
---|
303 | r = p; |
---|
304 | }else{ // convert into canvas-specific format |
---|
305 | r = []; |
---|
306 | for(i=0; i < p.length; ++i){ |
---|
307 | c = p[i]; |
---|
308 | r.push(c.x, c.y); |
---|
309 | } |
---|
310 | } |
---|
311 | }else{ |
---|
312 | r = []; |
---|
313 | } |
---|
314 | this.canvasPolyline = r; |
---|
315 | return this; |
---|
316 | }, |
---|
317 | _renderShape: function(/* Object */ ctx){ |
---|
318 | var p = this.canvasPolyline; |
---|
319 | if(p.length){ |
---|
320 | ctx.beginPath(); |
---|
321 | ctx.moveTo(p[0], p[1]); |
---|
322 | for(var i = 2; i < p.length; i += 2){ |
---|
323 | ctx.lineTo(p[i], p[i + 1]); |
---|
324 | } |
---|
325 | } |
---|
326 | } |
---|
327 | }); |
---|
328 | |
---|
329 | declare("dojox.gfx.canvas.Image", [canvas.Shape, gs.Image], { |
---|
330 | // summary: an image shape (Canvas) |
---|
331 | setShape: function(){ |
---|
332 | this.inherited(arguments); |
---|
333 | // prepare Canvas-specific structures |
---|
334 | var img = new Image(); |
---|
335 | this.surface.downloadImage(img, this.shape.src); |
---|
336 | this.canvasImage = img; |
---|
337 | return this; |
---|
338 | }, |
---|
339 | _renderShape: function(/* Object */ ctx){ |
---|
340 | var s = this.shape; |
---|
341 | ctx.drawImage(this.canvasImage, s.x, s.y, s.width, s.height); |
---|
342 | } |
---|
343 | }); |
---|
344 | |
---|
345 | declare("dojox.gfx.canvas.Text", [canvas.Shape, gs.Text], { |
---|
346 | _setFont:function(){ |
---|
347 | if (this.fontStyle){ |
---|
348 | this.canvasFont = g.makeFontString(this.fontStyle); |
---|
349 | } else { |
---|
350 | delete this.canvasFont; |
---|
351 | } |
---|
352 | }, |
---|
353 | |
---|
354 | getTextWidth: function(){ |
---|
355 | // summary: get the text width in pixels |
---|
356 | var s = this.shape, w = 0, ctx; |
---|
357 | if(s.text && s.text.length > 0){ |
---|
358 | ctx = this.surface.rawNode.getContext("2d"); |
---|
359 | ctx.save(); |
---|
360 | this._renderTransform(ctx); |
---|
361 | this._renderFill(ctx, false); |
---|
362 | this._renderStroke(ctx, false); |
---|
363 | if (this.canvasFont) |
---|
364 | ctx.font = this.canvasFont; |
---|
365 | w = ctx.measureText(s.text).width; |
---|
366 | ctx.restore(); |
---|
367 | } |
---|
368 | return w; |
---|
369 | }, |
---|
370 | |
---|
371 | // override to apply first fill and stroke ( |
---|
372 | // the base implementation is for path-based shape that needs to first define the path then to fill/stroke it. |
---|
373 | // Here, we need the fillstyle or strokestyle to be set before calling fillText/strokeText. |
---|
374 | _render: function(/* Object */ctx){ |
---|
375 | // summary: render the shape |
---|
376 | // ctx : Object: the drawing context. |
---|
377 | ctx.save(); |
---|
378 | this._renderTransform(ctx); |
---|
379 | this._renderFill(ctx, false); |
---|
380 | this._renderStroke(ctx, false); |
---|
381 | this._renderShape(ctx); |
---|
382 | ctx.restore(); |
---|
383 | }, |
---|
384 | |
---|
385 | _renderShape: function(ctx){ |
---|
386 | // summary: a text shape (Canvas) |
---|
387 | // ctx : Object: the drawing context. |
---|
388 | var ta, s = this.shape; |
---|
389 | if(!s.text || s.text.length == 0){ |
---|
390 | return; |
---|
391 | } |
---|
392 | // text align |
---|
393 | ta = s.align === 'middle' ? 'center' : s.align; |
---|
394 | ctx.textAlign = ta; |
---|
395 | if(this.canvasFont){ |
---|
396 | ctx.font = this.canvasFont; |
---|
397 | } |
---|
398 | if(this.canvasFill){ |
---|
399 | ctx.fillText(s.text, s.x, s.y); |
---|
400 | } |
---|
401 | if(this.strokeStyle){ |
---|
402 | ctx.beginPath(); // fix bug in FF3.6. Fixed in FF4b8 |
---|
403 | ctx.strokeText(s.text, s.x, s.y); |
---|
404 | ctx.closePath(); |
---|
405 | } |
---|
406 | } |
---|
407 | }); |
---|
408 | modifyMethod(canvas.Text, "setFont"); |
---|
409 | |
---|
410 | // the next test is from https://github.com/phiggins42/has.js |
---|
411 | if(win.global.CanvasRenderingContext2D){ |
---|
412 | // need to doublecheck canvas is supported since module can be loaded if building layers (ticket 14288) |
---|
413 | var ctx2d = win.doc.createElement("canvas").getContext("2d"); |
---|
414 | if(ctx2d && typeof ctx2d.fillText != "function"){ |
---|
415 | canvas.Text.extend({ |
---|
416 | getTextWidth: function(){ |
---|
417 | return 0; |
---|
418 | }, |
---|
419 | _renderShape: function(){ |
---|
420 | } |
---|
421 | }); |
---|
422 | } |
---|
423 | } |
---|
424 | |
---|
425 | |
---|
426 | var pathRenderers = { |
---|
427 | M: "_moveToA", m: "_moveToR", |
---|
428 | L: "_lineToA", l: "_lineToR", |
---|
429 | H: "_hLineToA", h: "_hLineToR", |
---|
430 | V: "_vLineToA", v: "_vLineToR", |
---|
431 | C: "_curveToA", c: "_curveToR", |
---|
432 | S: "_smoothCurveToA", s: "_smoothCurveToR", |
---|
433 | Q: "_qCurveToA", q: "_qCurveToR", |
---|
434 | T: "_qSmoothCurveToA", t: "_qSmoothCurveToR", |
---|
435 | A: "_arcTo", a: "_arcTo", |
---|
436 | Z: "_closePath", z: "_closePath" |
---|
437 | }; |
---|
438 | |
---|
439 | declare("dojox.gfx.canvas.Path", [canvas.Shape, pathLib.Path], { |
---|
440 | // summary: a path shape (Canvas) |
---|
441 | constructor: function(){ |
---|
442 | this.lastControl = {}; |
---|
443 | }, |
---|
444 | setShape: function(){ |
---|
445 | this.canvasPath = []; |
---|
446 | return this.inherited(arguments); |
---|
447 | }, |
---|
448 | _updateWithSegment: function(segment){ |
---|
449 | var last = lang.clone(this.last); |
---|
450 | this[pathRenderers[segment.action]](this.canvasPath, segment.action, segment.args); |
---|
451 | this.last = last; |
---|
452 | this.inherited(arguments); |
---|
453 | }, |
---|
454 | _renderShape: function(/* Object */ ctx){ |
---|
455 | var r = this.canvasPath; |
---|
456 | ctx.beginPath(); |
---|
457 | for(var i = 0; i < r.length; i += 2){ |
---|
458 | ctx[r[i]].apply(ctx, r[i + 1]); |
---|
459 | } |
---|
460 | }, |
---|
461 | _moveToA: function(result, action, args){ |
---|
462 | result.push("moveTo", [args[0], args[1]]); |
---|
463 | for(var i = 2; i < args.length; i += 2){ |
---|
464 | result.push("lineTo", [args[i], args[i + 1]]); |
---|
465 | } |
---|
466 | this.last.x = args[args.length - 2]; |
---|
467 | this.last.y = args[args.length - 1]; |
---|
468 | this.lastControl = {}; |
---|
469 | }, |
---|
470 | _moveToR: function(result, action, args){ |
---|
471 | if("x" in this.last){ |
---|
472 | result.push("moveTo", [this.last.x += args[0], this.last.y += args[1]]); |
---|
473 | }else{ |
---|
474 | result.push("moveTo", [this.last.x = args[0], this.last.y = args[1]]); |
---|
475 | } |
---|
476 | for(var i = 2; i < args.length; i += 2){ |
---|
477 | result.push("lineTo", [this.last.x += args[i], this.last.y += args[i + 1]]); |
---|
478 | } |
---|
479 | this.lastControl = {}; |
---|
480 | }, |
---|
481 | _lineToA: function(result, action, args){ |
---|
482 | for(var i = 0; i < args.length; i += 2){ |
---|
483 | result.push("lineTo", [args[i], args[i + 1]]); |
---|
484 | } |
---|
485 | this.last.x = args[args.length - 2]; |
---|
486 | this.last.y = args[args.length - 1]; |
---|
487 | this.lastControl = {}; |
---|
488 | }, |
---|
489 | _lineToR: function(result, action, args){ |
---|
490 | for(var i = 0; i < args.length; i += 2){ |
---|
491 | result.push("lineTo", [this.last.x += args[i], this.last.y += args[i + 1]]); |
---|
492 | } |
---|
493 | this.lastControl = {}; |
---|
494 | }, |
---|
495 | _hLineToA: function(result, action, args){ |
---|
496 | for(var i = 0; i < args.length; ++i){ |
---|
497 | result.push("lineTo", [args[i], this.last.y]); |
---|
498 | } |
---|
499 | this.last.x = args[args.length - 1]; |
---|
500 | this.lastControl = {}; |
---|
501 | }, |
---|
502 | _hLineToR: function(result, action, args){ |
---|
503 | for(var i = 0; i < args.length; ++i){ |
---|
504 | result.push("lineTo", [this.last.x += args[i], this.last.y]); |
---|
505 | } |
---|
506 | this.lastControl = {}; |
---|
507 | }, |
---|
508 | _vLineToA: function(result, action, args){ |
---|
509 | for(var i = 0; i < args.length; ++i){ |
---|
510 | result.push("lineTo", [this.last.x, args[i]]); |
---|
511 | } |
---|
512 | this.last.y = args[args.length - 1]; |
---|
513 | this.lastControl = {}; |
---|
514 | }, |
---|
515 | _vLineToR: function(result, action, args){ |
---|
516 | for(var i = 0; i < args.length; ++i){ |
---|
517 | result.push("lineTo", [this.last.x, this.last.y += args[i]]); |
---|
518 | } |
---|
519 | this.lastControl = {}; |
---|
520 | }, |
---|
521 | _curveToA: function(result, action, args){ |
---|
522 | for(var i = 0; i < args.length; i += 6){ |
---|
523 | result.push("bezierCurveTo", args.slice(i, i + 6)); |
---|
524 | } |
---|
525 | this.last.x = args[args.length - 2]; |
---|
526 | this.last.y = args[args.length - 1]; |
---|
527 | this.lastControl.x = args[args.length - 4]; |
---|
528 | this.lastControl.y = args[args.length - 3]; |
---|
529 | this.lastControl.type = "C"; |
---|
530 | }, |
---|
531 | _curveToR: function(result, action, args){ |
---|
532 | for(var i = 0; i < args.length; i += 6){ |
---|
533 | result.push("bezierCurveTo", [ |
---|
534 | this.last.x + args[i], |
---|
535 | this.last.y + args[i + 1], |
---|
536 | this.lastControl.x = this.last.x + args[i + 2], |
---|
537 | this.lastControl.y = this.last.y + args[i + 3], |
---|
538 | this.last.x + args[i + 4], |
---|
539 | this.last.y + args[i + 5] |
---|
540 | ]); |
---|
541 | this.last.x += args[i + 4]; |
---|
542 | this.last.y += args[i + 5]; |
---|
543 | } |
---|
544 | this.lastControl.type = "C"; |
---|
545 | }, |
---|
546 | _smoothCurveToA: function(result, action, args){ |
---|
547 | for(var i = 0; i < args.length; i += 4){ |
---|
548 | var valid = this.lastControl.type == "C"; |
---|
549 | result.push("bezierCurveTo", [ |
---|
550 | valid ? 2 * this.last.x - this.lastControl.x : this.last.x, |
---|
551 | valid ? 2 * this.last.y - this.lastControl.y : this.last.y, |
---|
552 | args[i], |
---|
553 | args[i + 1], |
---|
554 | args[i + 2], |
---|
555 | args[i + 3] |
---|
556 | ]); |
---|
557 | this.lastControl.x = args[i]; |
---|
558 | this.lastControl.y = args[i + 1]; |
---|
559 | this.lastControl.type = "C"; |
---|
560 | } |
---|
561 | this.last.x = args[args.length - 2]; |
---|
562 | this.last.y = args[args.length - 1]; |
---|
563 | }, |
---|
564 | _smoothCurveToR: function(result, action, args){ |
---|
565 | for(var i = 0; i < args.length; i += 4){ |
---|
566 | var valid = this.lastControl.type == "C"; |
---|
567 | result.push("bezierCurveTo", [ |
---|
568 | valid ? 2 * this.last.x - this.lastControl.x : this.last.x, |
---|
569 | valid ? 2 * this.last.y - this.lastControl.y : this.last.y, |
---|
570 | this.last.x + args[i], |
---|
571 | this.last.y + args[i + 1], |
---|
572 | this.last.x + args[i + 2], |
---|
573 | this.last.y + args[i + 3] |
---|
574 | ]); |
---|
575 | this.lastControl.x = this.last.x + args[i]; |
---|
576 | this.lastControl.y = this.last.y + args[i + 1]; |
---|
577 | this.lastControl.type = "C"; |
---|
578 | this.last.x += args[i + 2]; |
---|
579 | this.last.y += args[i + 3]; |
---|
580 | } |
---|
581 | }, |
---|
582 | _qCurveToA: function(result, action, args){ |
---|
583 | for(var i = 0; i < args.length; i += 4){ |
---|
584 | result.push("quadraticCurveTo", args.slice(i, i + 4)); |
---|
585 | } |
---|
586 | this.last.x = args[args.length - 2]; |
---|
587 | this.last.y = args[args.length - 1]; |
---|
588 | this.lastControl.x = args[args.length - 4]; |
---|
589 | this.lastControl.y = args[args.length - 3]; |
---|
590 | this.lastControl.type = "Q"; |
---|
591 | }, |
---|
592 | _qCurveToR: function(result, action, args){ |
---|
593 | for(var i = 0; i < args.length; i += 4){ |
---|
594 | result.push("quadraticCurveTo", [ |
---|
595 | this.lastControl.x = this.last.x + args[i], |
---|
596 | this.lastControl.y = this.last.y + args[i + 1], |
---|
597 | this.last.x + args[i + 2], |
---|
598 | this.last.y + args[i + 3] |
---|
599 | ]); |
---|
600 | this.last.x += args[i + 2]; |
---|
601 | this.last.y += args[i + 3]; |
---|
602 | } |
---|
603 | this.lastControl.type = "Q"; |
---|
604 | }, |
---|
605 | _qSmoothCurveToA: function(result, action, args){ |
---|
606 | for(var i = 0; i < args.length; i += 2){ |
---|
607 | var valid = this.lastControl.type == "Q"; |
---|
608 | result.push("quadraticCurveTo", [ |
---|
609 | this.lastControl.x = valid ? 2 * this.last.x - this.lastControl.x : this.last.x, |
---|
610 | this.lastControl.y = valid ? 2 * this.last.y - this.lastControl.y : this.last.y, |
---|
611 | args[i], |
---|
612 | args[i + 1] |
---|
613 | ]); |
---|
614 | this.lastControl.type = "Q"; |
---|
615 | } |
---|
616 | this.last.x = args[args.length - 2]; |
---|
617 | this.last.y = args[args.length - 1]; |
---|
618 | }, |
---|
619 | _qSmoothCurveToR: function(result, action, args){ |
---|
620 | for(var i = 0; i < args.length; i += 2){ |
---|
621 | var valid = this.lastControl.type == "Q"; |
---|
622 | result.push("quadraticCurveTo", [ |
---|
623 | this.lastControl.x = valid ? 2 * this.last.x - this.lastControl.x : this.last.x, |
---|
624 | this.lastControl.y = valid ? 2 * this.last.y - this.lastControl.y : this.last.y, |
---|
625 | this.last.x + args[i], |
---|
626 | this.last.y + args[i + 1] |
---|
627 | ]); |
---|
628 | this.lastControl.type = "Q"; |
---|
629 | this.last.x += args[i]; |
---|
630 | this.last.y += args[i + 1]; |
---|
631 | } |
---|
632 | }, |
---|
633 | _arcTo: function(result, action, args){ |
---|
634 | var relative = action == "a"; |
---|
635 | for(var i = 0; i < args.length; i += 7){ |
---|
636 | var x1 = args[i + 5], y1 = args[i + 6]; |
---|
637 | if(relative){ |
---|
638 | x1 += this.last.x; |
---|
639 | y1 += this.last.y; |
---|
640 | } |
---|
641 | var arcs = ga.arcAsBezier( |
---|
642 | this.last, args[i], args[i + 1], args[i + 2], |
---|
643 | args[i + 3] ? 1 : 0, args[i + 4] ? 1 : 0, |
---|
644 | x1, y1 |
---|
645 | ); |
---|
646 | arr.forEach(arcs, function(p){ |
---|
647 | result.push("bezierCurveTo", p); |
---|
648 | }); |
---|
649 | this.last.x = x1; |
---|
650 | this.last.y = y1; |
---|
651 | } |
---|
652 | this.lastControl = {}; |
---|
653 | }, |
---|
654 | _closePath: function(result, action, args){ |
---|
655 | result.push("closePath", []); |
---|
656 | this.lastControl = {}; |
---|
657 | } |
---|
658 | }); |
---|
659 | arr.forEach(["moveTo", "lineTo", "hLineTo", "vLineTo", "curveTo", |
---|
660 | "smoothCurveTo", "qCurveTo", "qSmoothCurveTo", "arcTo", "closePath"], |
---|
661 | function(method){ modifyMethod(canvas.Path, method); } |
---|
662 | ); |
---|
663 | |
---|
664 | declare("dojox.gfx.canvas.TextPath", [canvas.Shape, pathLib.TextPath], { |
---|
665 | // summary: a text shape (Canvas) |
---|
666 | _renderShape: function(/* Object */ ctx){ |
---|
667 | var s = this.shape; |
---|
668 | // nothing for the moment |
---|
669 | }, |
---|
670 | _setText: function(){ |
---|
671 | // not implemented |
---|
672 | }, |
---|
673 | _setFont: function(){ |
---|
674 | // not implemented |
---|
675 | } |
---|
676 | }); |
---|
677 | |
---|
678 | declare("dojox.gfx.canvas.Surface", gs.Surface, { |
---|
679 | // summary: a surface object to be used for drawings (Canvas) |
---|
680 | constructor: function(){ |
---|
681 | gs.Container._init.call(this); |
---|
682 | this.pendingImageCount = 0; |
---|
683 | this.makeDirty(); |
---|
684 | }, |
---|
685 | setDimensions: function(width, height){ |
---|
686 | // summary: sets the width and height of the rawNode |
---|
687 | // width: String: width of surface, e.g., "100px" |
---|
688 | // height: String: height of surface, e.g., "100px" |
---|
689 | this.width = g.normalizedLength(width); // in pixels |
---|
690 | this.height = g.normalizedLength(height); // in pixels |
---|
691 | if(!this.rawNode) return this; |
---|
692 | var dirty = false; |
---|
693 | if (this.rawNode.width != this.width){ |
---|
694 | this.rawNode.width = this.width; |
---|
695 | dirty = true; |
---|
696 | } |
---|
697 | if (this.rawNode.height != this.height){ |
---|
698 | this.rawNode.height = this.height; |
---|
699 | dirty = true; |
---|
700 | } |
---|
701 | if (dirty) |
---|
702 | this.makeDirty(); |
---|
703 | return this; // self |
---|
704 | }, |
---|
705 | getDimensions: function(){ |
---|
706 | // summary: returns an object with properties "width" and "height" |
---|
707 | return this.rawNode ? {width: this.rawNode.width, height: this.rawNode.height} : null; // Object |
---|
708 | }, |
---|
709 | _render: function(){ |
---|
710 | // summary: render the all shapes |
---|
711 | if(this.pendingImageCount){ return; } |
---|
712 | var ctx = this.rawNode.getContext("2d"); |
---|
713 | ctx.save(); |
---|
714 | ctx.clearRect(0, 0, this.rawNode.width, this.rawNode.height); |
---|
715 | for(var i = 0; i < this.children.length; ++i){ |
---|
716 | this.children[i]._render(ctx); |
---|
717 | } |
---|
718 | ctx.restore(); |
---|
719 | if("pendingRender" in this){ |
---|
720 | clearTimeout(this.pendingRender); |
---|
721 | delete this.pendingRender; |
---|
722 | } |
---|
723 | }, |
---|
724 | makeDirty: function(){ |
---|
725 | // summary: internal method, which is called when we may need to redraw |
---|
726 | if(!this.pendingImagesCount && !("pendingRender" in this)){ |
---|
727 | this.pendingRender = setTimeout(lang.hitch(this, this._render), 0); |
---|
728 | } |
---|
729 | }, |
---|
730 | downloadImage: function(img, url){ |
---|
731 | // summary: |
---|
732 | // internal method, which starts an image download and renders, when it is ready |
---|
733 | // img: Image: |
---|
734 | // the image object |
---|
735 | // url: String: |
---|
736 | // the url of the image |
---|
737 | var handler = lang.hitch(this, this.onImageLoad); |
---|
738 | if(!this.pendingImageCount++ && "pendingRender" in this){ |
---|
739 | clearTimeout(this.pendingRender); |
---|
740 | delete this.pendingRender; |
---|
741 | } |
---|
742 | img.onload = handler; |
---|
743 | img.onerror = handler; |
---|
744 | img.onabort = handler; |
---|
745 | img.src = url; |
---|
746 | }, |
---|
747 | onImageLoad: function(){ |
---|
748 | if(!--this.pendingImageCount){ this._render(); } |
---|
749 | }, |
---|
750 | |
---|
751 | // events are not implemented |
---|
752 | getEventSource: function(){ return null; }, |
---|
753 | connect: function(){}, |
---|
754 | disconnect: function(){} |
---|
755 | }); |
---|
756 | |
---|
757 | canvas.createSurface = function(parentNode, width, height){ |
---|
758 | // summary: creates a surface (Canvas) |
---|
759 | // parentNode: Node: a parent node |
---|
760 | // width: String: width of surface, e.g., "100px" |
---|
761 | // height: String: height of surface, e.g., "100px" |
---|
762 | |
---|
763 | if(!width && !height){ |
---|
764 | var pos = domGeom.position(parentNode); |
---|
765 | width = width || pos.w; |
---|
766 | height = height || pos.h; |
---|
767 | } |
---|
768 | if(typeof width == "number"){ |
---|
769 | width = width + "px"; |
---|
770 | } |
---|
771 | if(typeof height == "number"){ |
---|
772 | height = height + "px"; |
---|
773 | } |
---|
774 | |
---|
775 | var s = new canvas.Surface(), |
---|
776 | p = dom.byId(parentNode), |
---|
777 | c = p.ownerDocument.createElement("canvas"); |
---|
778 | |
---|
779 | c.width = g.normalizedLength(width); // in pixels |
---|
780 | c.height = g.normalizedLength(height); // in pixels |
---|
781 | |
---|
782 | p.appendChild(c); |
---|
783 | s.rawNode = c; |
---|
784 | s._parent = p; |
---|
785 | s.surface = s; |
---|
786 | return s; // dojox.gfx.Surface |
---|
787 | }; |
---|
788 | |
---|
789 | // Extenders |
---|
790 | |
---|
791 | var C = gs.Container, Container = { |
---|
792 | add: function(shape){ |
---|
793 | this.surface.makeDirty(); |
---|
794 | return C.add.apply(this, arguments); |
---|
795 | }, |
---|
796 | remove: function(shape, silently){ |
---|
797 | this.surface.makeDirty(); |
---|
798 | return C.remove.apply(this, arguments); |
---|
799 | }, |
---|
800 | clear: function(){ |
---|
801 | this.surface.makeDirty(); |
---|
802 | return C.clear.apply(this, arguments); |
---|
803 | }, |
---|
804 | _moveChildToFront: function(shape){ |
---|
805 | this.surface.makeDirty(); |
---|
806 | return C._moveChildToFront.apply(this, arguments); |
---|
807 | }, |
---|
808 | _moveChildToBack: function(shape){ |
---|
809 | this.surface.makeDirty(); |
---|
810 | return C._moveChildToBack.apply(this, arguments); |
---|
811 | } |
---|
812 | }; |
---|
813 | |
---|
814 | var Creator = { |
---|
815 | // summary: Canvas shape creators |
---|
816 | createObject: function(shapeType, rawShape) { |
---|
817 | // summary: creates an instance of the passed shapeType class |
---|
818 | // shapeType: Function: a class constructor to create an instance of |
---|
819 | // rawShape: Object: properties to be passed in to the classes "setShape" method |
---|
820 | // overrideSize: Boolean: set the size explicitly, if true |
---|
821 | var shape = new shapeType(); |
---|
822 | shape.surface = this.surface; |
---|
823 | shape.setShape(rawShape); |
---|
824 | this.add(shape); |
---|
825 | return shape; // dojox.gfx.Shape |
---|
826 | } |
---|
827 | }; |
---|
828 | |
---|
829 | extend(canvas.Group, Container); |
---|
830 | extend(canvas.Group, gs.Creator); |
---|
831 | extend(canvas.Group, Creator); |
---|
832 | |
---|
833 | extend(canvas.Surface, Container); |
---|
834 | extend(canvas.Surface, gs.Creator); |
---|
835 | extend(canvas.Surface, Creator); |
---|
836 | |
---|
837 | // no event support -> nothing to fix. |
---|
838 | canvas.fixTarget = function(event, gfxElement){ |
---|
839 | return true; |
---|
840 | }; |
---|
841 | |
---|
842 | return canvas; |
---|
843 | }); |
---|