New file |
0,0 → 1,682 |
if(!dojo._hasResource["dojox.gfx.canvas"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
dojo._hasResource["dojox.gfx.canvas"] = true; |
dojo.provide("dojox.gfx.canvas"); |
|
dojo.require("dojox.gfx._base"); |
dojo.require("dojox.gfx.shape"); |
dojo.require("dojox.gfx.path"); |
dojo.require("dojox.gfx.arc"); |
dojo.require("dojox.gfx.decompose"); |
|
dojo.experimental("dojox.gfx.canvas"); |
|
(function(){ |
var g = dojox.gfx, gs = g.shape, ga = g.arc, |
m = g.matrix, mp = m.multiplyPoint, twoPI = 2 * Math.PI; |
|
dojo.extend(g.Shape, { |
render: function(/* Object */ ctx){ |
// summary: render the shape |
ctx.save(); |
this._renderTransform(ctx); |
this._renderShape(ctx); |
this._renderFill(ctx, true); |
this._renderStroke(ctx, true); |
ctx.restore(); |
}, |
_renderTransform: function(/* Object */ ctx){ |
if("canvasTransform" in this){ |
var t = this.canvasTransform; |
ctx.translate(t.dx, t.dy); |
ctx.rotate(t.angle2); |
ctx.scale(t.sx, t.sy); |
ctx.rotate(t.angle1); |
// The future implementation when vendors catch up with the spec: |
// var t = this.matrix; |
// ctx.transform(t.xx, t.yx, t.xy, t.yy, t.dx, t.dy); |
} |
}, |
_renderShape: function(/* Object */ ctx){ |
// nothing |
}, |
_renderFill: function(/* Object */ ctx, /* Boolean */ apply){ |
if("canvasFill" in this){ |
if("canvasFillImage" in this){ |
this.canvasFill = ctx.createPattern(this.canvasFillImage, "repeat"); |
delete this.canvasFillImage; |
} |
ctx.fillStyle = this.canvasFill; |
if(apply){ ctx.fill(); } |
}else{ |
ctx.fillStyle = "rgba(0,0,0,0.0)"; |
} |
}, |
_renderStroke: function(/* Object */ ctx, /* Boolean */ apply){ |
var s = this.strokeStyle; |
if(s){ |
ctx.strokeStyle = s.color.toString(); |
ctx.lineWidth = s.width; |
ctx.lineCap = s.cap; |
if(typeof s.join == "number"){ |
ctx.lineJoin = "miter"; |
ctx.miterLimit = s.join; |
}else{ |
ctx.lineJoin = s.join; |
} |
if(apply){ ctx.stroke(); } |
}else if(!apply){ |
ctx.strokeStyle = "rgba(0,0,0,0.0)"; |
} |
}, |
|
// events are not implemented |
getEventSource: function(){ return null; }, |
connect: function(){}, |
disconnect: function(){} |
}); |
|
var modifyMethod = function(shape, method, extra){ |
var old = shape.prototype[method]; |
shape.prototype[method] = extra ? |
function(){ |
this.surface.makeDirty(); |
old.apply(this, arguments); |
extra.call(this); |
return this; |
} : |
function(){ |
this.surface.makeDirty(); |
return old.apply(this, arguments); |
}; |
}; |
|
modifyMethod(g.Shape, "setTransform", |
function(){ |
// prepare Canvas-specific structures |
if(this.matrix){ |
this.canvasTransform = g.decompose(this.matrix); |
}else{ |
delete this.canvasTransform; |
} |
}); |
|
modifyMethod(g.Shape, "setFill", |
function(){ |
// prepare Canvas-specific structures |
var fs = this.fillStyle, f; |
if(fs){ |
if(typeof(fs) == "object" && "type" in fs){ |
var ctx = this.surface.rawNode.getContext("2d"); |
switch(fs.type){ |
case "linear": |
case "radial": |
f = fs.type == "linear" ? |
ctx.createLinearGradient(fs.x1, fs.y1, fs.x2, fs.y2) : |
ctx.createRadialGradient(fs.cx, fs.cy, 0, fs.cx, fs.cy, fs.r); |
dojo.forEach(fs.colors, function(step){ |
f.addColorStop(step.offset, g.normalizeColor(step.color).toString()); |
}); |
break; |
case "pattern": |
var img = new Image(fs.width, fs.height); |
this.surface.downloadImage(img, fs.src); |
this.canvasFillImage = img; |
} |
}else{ |
// Set fill color using CSS RGBA func style |
f = fs.toString(); |
} |
this.canvasFill = f; |
}else{ |
delete this.canvasFill; |
} |
}); |
|
modifyMethod(g.Shape, "setStroke"); |
modifyMethod(g.Shape, "setShape"); |
|
dojo.declare("dojox.gfx.Group", g.Shape, { |
// summary: a group shape (Canvas), which can be used |
// to logically group shapes (e.g, to propagate matricies) |
constructor: function(){ |
gs.Container._init.call(this); |
}, |
render: function(/* Object */ ctx){ |
// summary: render the group |
ctx.save(); |
this._renderTransform(ctx); |
this._renderFill(ctx); |
this._renderStroke(ctx); |
for(var i = 0; i < this.children.length; ++i){ |
this.children[i].render(ctx); |
} |
ctx.restore(); |
} |
}); |
|
dojo.declare("dojox.gfx.Rect", gs.Rect, { |
// summary: a rectangle shape (Canvas) |
_renderShape: function(/* Object */ ctx){ |
var s = this.shape, r = Math.min(s.r, s.height / 2, s.width / 2), |
xl = s.x, xr = xl + s.width, yt = s.y, yb = yt + s.height, |
xl2 = xl + r, xr2 = xr - r, yt2 = yt + r, yb2 = yb - r; |
ctx.beginPath(); |
ctx.moveTo(xl2, yt); |
ctx.lineTo(xr2, yt); |
if(r){ ctx.arcTo(xr, yt, xr, yt2, r); } |
ctx.lineTo(xr, yb2); |
if(r){ ctx.arcTo(xr, yb, xr2, yb, r); } |
ctx.lineTo(xl2, yb); |
if(r){ ctx.arcTo(xl, yb, xl, yb2, r); } |
ctx.lineTo(xl, yt2); |
if(r){ ctx.arcTo(xl, yt, xl2, yt, r); } |
ctx.closePath(); |
} |
}); |
|
var bezierCircle = []; |
(function(){ |
var u = ga.curvePI4; |
bezierCircle.push(u.s, u.c1, u.c2, u.e); |
for(var a = 45; a < 360; a += 45){ |
var r = m.rotateg(a); |
bezierCircle.push(mp(r, u.c1), mp(r, u.c2), mp(r, u.e)); |
} |
})(); |
|
dojo.declare("dojox.gfx.Ellipse", gs.Ellipse, { |
// summary: an ellipse shape (Canvas) |
setShape: function(){ |
g.Ellipse.superclass.setShape.apply(this, arguments); |
// prepare Canvas-specific structures |
var s = this.shape, t, c1, c2, r = [], |
M = m.normalize([m.translate(s.cx, s.cy), m.scale(s.rx, s.ry)]); |
t = mp(M, bezierCircle[0]); |
r.push([t.x, t.y]); |
for(var i = 1; i < bezierCircle.length; i += 3){ |
c1 = mp(M, bezierCircle[i]); |
c2 = mp(M, bezierCircle[i + 1]); |
t = mp(M, bezierCircle[i + 2]); |
r.push([c1.x, c1.y, c2.x, c2.y, t.x, t.y]); |
} |
this.canvasEllipse = r; |
return this; |
}, |
_renderShape: function(/* Object */ ctx){ |
var r = this.canvasEllipse; |
ctx.beginPath(); |
ctx.moveTo.apply(ctx, r[0]); |
for(var i = 1; i < r.length; ++i){ |
ctx.bezierCurveTo.apply(ctx, r[i]); |
} |
ctx.closePath(); |
} |
}); |
|
dojo.declare("dojox.gfx.Circle", gs.Circle, { |
// summary: a circle shape (Canvas) |
_renderShape: function(/* Object */ ctx){ |
var s = this.shape; |
ctx.beginPath(); |
ctx.arc(s.cx, s.cy, s.r, 0, twoPI, 1); |
} |
}); |
|
dojo.declare("dojox.gfx.Line", gs.Line, { |
// summary: a line shape (Canvas) |
_renderShape: function(/* Object */ ctx){ |
var s = this.shape; |
ctx.beginPath(); |
ctx.moveTo(s.x1, s.y1); |
ctx.lineTo(s.x2, s.y2); |
} |
}); |
|
dojo.declare("dojox.gfx.Polyline", gs.Polyline, { |
// summary: a polyline/polygon shape (Canvas) |
setShape: function(){ |
g.Polyline.superclass.setShape.apply(this, arguments); |
// prepare Canvas-specific structures |
var p = this.shape.points, f = p[0], r = [], c, i; |
if(p.length){ |
if(typeof f == "number"){ |
r.push(f, p[1]); |
i = 2; |
}else{ |
r.push(f.x, f.y); |
i = 1; |
} |
for(; i < p.length; ++i){ |
c = p[i]; |
if(typeof c == "number"){ |
r.push(c, p[++i]); |
}else{ |
r.push(c.x, c.y); |
} |
} |
} |
this.canvasPolyline = r; |
return this; |
}, |
_renderShape: function(/* Object */ ctx){ |
var p = this.canvasPolyline; |
if(p.length){ |
ctx.beginPath(); |
ctx.moveTo(p[0], p[1]); |
for(var i = 2; i < p.length; i += 2){ |
ctx.lineTo(p[i], p[i + 1]); |
} |
} |
} |
}); |
|
dojo.declare("dojox.gfx.Image", gs.Image, { |
// summary: an image shape (Canvas) |
setShape: function(){ |
g.Image.superclass.setShape.apply(this, arguments); |
// prepare Canvas-specific structures |
var img = new Image(); |
this.surface.downloadImage(img, this.shape.src); |
this.canvasImage = img; |
return this; |
}, |
_renderShape: function(/* Object */ ctx){ |
var s = this.shape; |
ctx.drawImage(this.canvasImage, s.x, s.y, s.width, s.height); |
} |
}); |
|
dojo.declare("dojox.gfx.Text", gs.Text, { |
// summary: a text shape (Canvas) |
_renderShape: function(/* Object */ ctx){ |
var s = this.shape; |
// nothing for the moment |
} |
}); |
modifyMethod(g.Text, "setFont"); |
|
var pathRenderers = { |
M: "_moveToA", m: "_moveToR", |
L: "_lineToA", l: "_lineToR", |
H: "_hLineToA", h: "_hLineToR", |
V: "_vLineToA", v: "_vLineToR", |
C: "_curveToA", c: "_curveToR", |
S: "_smoothCurveToA", s: "_smoothCurveToR", |
Q: "_qCurveToA", q: "_qCurveToR", |
T: "_qSmoothCurveToA", t: "_qSmoothCurveToR", |
A: "_arcTo", a: "_arcTo", |
Z: "_closePath", z: "_closePath" |
}; |
|
dojo.declare("dojox.gfx.Path", g.path.Path, { |
// summary: a path shape (Canvas) |
constructor: function(){ |
this.last = {}; |
this.lastControl = {}; |
}, |
setShape: function(){ |
this.canvasPath = []; |
return g.Path.superclass.setShape.apply(this, arguments); |
}, |
_updateWithSegment: function(segment){ |
var last = dojo.clone(this.last); |
this[pathRenderers[segment.action]](this.canvasPath, segment.action, segment.args); |
this.last = last; |
g.Path.superclass._updateWithSegment.apply(this, arguments); |
}, |
_renderShape: function(/* Object */ ctx){ |
var r = this.canvasPath; |
ctx.beginPath(); |
for(var i = 0; i < r.length; i += 2){ |
ctx[r[i]].apply(ctx, r[i + 1]); |
} |
}, |
_moveToA: function(result, action, args){ |
result.push("moveTo", [args[0], args[1]]); |
for(var i = 2; i < args.length; i += 2){ |
result.push("lineTo", [args[i], args[i + 1]]); |
} |
this.last.x = args[args.length - 2]; |
this.last.y = args[args.length - 1]; |
this.lastControl = {}; |
}, |
_moveToR: function(result, action, args){ |
if("x" in this.last){ |
result.push("moveTo", [this.last.x += args[0], this.last.y += args[1]]); |
}else{ |
result.push("moveTo", [this.last.x = args[0], this.last.y = args[1]]); |
} |
for(var i = 2; i < args.length; i += 2){ |
result.push("lineTo", [this.last.x += args[i], this.last.y += args[i + 1]]); |
} |
this.lastControl = {}; |
}, |
_lineToA: function(result, action, args){ |
for(var i = 0; i < args.length; i += 2){ |
result.push("lineTo", [args[i], args[i + 1]]); |
} |
this.last.x = args[args.length - 2]; |
this.last.y = args[args.length - 1]; |
this.lastControl = {}; |
}, |
_lineToR: function(result, action, args){ |
for(var i = 0; i < args.length; i += 2){ |
result.push("lineTo", [this.last.x += args[i], this.last.y += args[i + 1]]); |
} |
this.lastControl = {}; |
}, |
_hLineToA: function(result, action, args){ |
for(var i = 0; i < args.length; ++i){ |
result.push("lineTo", [args[i], this.last.y]); |
} |
this.last.x = args[args.length - 1]; |
this.lastControl = {}; |
}, |
_hLineToR: function(result, action, args){ |
for(var i = 0; i < args.length; ++i){ |
result.push("lineTo", [this.last.x += args[i], this.last.y]); |
} |
this.lastControl = {}; |
}, |
_vLineToA: function(result, action, args){ |
for(var i = 0; i < args.length; ++i){ |
result.push("lineTo", [this.last.x, args[i]]); |
} |
this.last.y = args[args.length - 1]; |
this.lastControl = {}; |
}, |
_vLineToR: function(result, action, args){ |
for(var i = 0; i < args.length; ++i){ |
result.push("lineTo", [this.last.x, this.last.y += args[i]]); |
} |
this.lastControl = {}; |
}, |
_curveToA: function(result, action, args){ |
for(var i = 0; i < args.length; i += 6){ |
result.push("bezierCurveTo", args.slice(i, i + 6)); |
} |
this.last.x = args[args.length - 2]; |
this.last.y = args[args.length - 1]; |
this.lastControl.x = args[args.length - 4]; |
this.lastControl.y = args[args.length - 3]; |
this.lastControl.type = "C"; |
}, |
_curveToR: function(result, action, args){ |
for(var i = 0; i < args.length; i += 6){ |
result.push("bezierCurveTo", [ |
this.last.x + args[i], |
this.last.y + args[i + 1], |
this.lastControl.x = this.last.x + args[i + 2], |
this.lastControl.y = this.last.y + args[i + 3], |
this.last.x + args[i + 4], |
this.last.y + args[i + 5] |
]); |
this.last.x += args[i + 4]; |
this.last.y += args[i + 5]; |
} |
this.lastControl.type = "C"; |
}, |
_smoothCurveToA: function(result, action, args){ |
for(var i = 0; i < args.length; i += 4){ |
var valid = this.lastControl.type == "C"; |
result.push("bezierCurveTo", [ |
valid ? 2 * this.last.x - this.lastControl.x : this.last.x, |
valid ? 2 * this.last.y - this.lastControl.y : this.last.y, |
args[i], |
args[i + 1], |
args[i + 2], |
args[i + 3] |
]); |
this.lastControl.x = args[i]; |
this.lastControl.y = args[i + 1]; |
this.lastControl.type = "C"; |
} |
this.last.x = args[args.length - 2]; |
this.last.y = args[args.length - 1]; |
}, |
_smoothCurveToR: function(result, action, args){ |
for(var i = 0; i < args.length; i += 4){ |
var valid = this.lastControl.type == "C"; |
result.push("bezierCurveTo", [ |
valid ? 2 * this.last.x - this.lastControl.x : this.last.x, |
valid ? 2 * this.last.y - this.lastControl.y : this.last.y, |
this.last.x + args[i], |
this.last.y + args[i + 1], |
this.last.x + args[i + 2], |
this.last.y + args[i + 3] |
]); |
this.lastControl.x = this.last.x + args[i]; |
this.lastControl.y = this.last.y + args[i + 1]; |
this.lastControl.type = "C"; |
this.last.x += args[i + 2]; |
this.last.y += args[i + 3]; |
} |
}, |
_qCurveToA: function(result, action, args){ |
for(var i = 0; i < args.length; i += 4){ |
result.push("quadraticCurveTo", args.slice(i, i + 4)); |
} |
this.last.x = args[args.length - 2]; |
this.last.y = args[args.length - 1]; |
this.lastControl.x = args[args.length - 4]; |
this.lastControl.y = args[args.length - 3]; |
this.lastControl.type = "Q"; |
}, |
_qCurveToR: function(result, action, args){ |
for(var i = 0; i < args.length; i += 4){ |
result.push("quadraticCurveTo", [ |
this.lastControl.x = this.last.x + args[i], |
this.lastControl.y = this.last.y + args[i + 1], |
this.last.x + args[i + 2], |
this.last.y + args[i + 3] |
]); |
this.last.x += args[i + 2]; |
this.last.y += args[i + 3]; |
} |
this.lastControl.type = "Q"; |
}, |
_qSmoothCurveToA: function(result, action, args){ |
for(var i = 0; i < args.length; i += 2){ |
var valid = this.lastControl.type == "Q"; |
result.push("quadraticCurveTo", [ |
this.lastControl.x = valid ? 2 * this.last.x - this.lastControl.x : this.last.x, |
this.lastControl.y = valid ? 2 * this.last.y - this.lastControl.y : this.last.y, |
args[i], |
args[i + 1] |
]); |
this.lastControl.type = "Q"; |
} |
this.last.x = args[args.length - 2]; |
this.last.y = args[args.length - 1]; |
}, |
_qSmoothCurveToR: function(result, action, args){ |
for(var i = 0; i < args.length; i += 2){ |
var valid = this.lastControl.type == "Q"; |
result.push("quadraticCurveTo", [ |
this.lastControl.x = valid ? 2 * this.last.x - this.lastControl.x : this.last.x, |
this.lastControl.y = valid ? 2 * this.last.y - this.lastControl.y : this.last.y, |
this.last.x + args[i], |
this.last.y + args[i + 1] |
]); |
this.lastControl.type = "Q"; |
this.last.x += args[i]; |
this.last.y += args[i + 1]; |
} |
}, |
_arcTo: function(result, action, args){ |
var relative = action == "a"; |
for(var i = 0; i < args.length; i += 7){ |
var x1 = args[i + 5], y1 = args[i + 6]; |
if(relative){ |
x1 += this.last.x; |
y1 += this.last.y; |
} |
var arcs = ga.arcAsBezier( |
this.last, args[i], args[i + 1], args[i + 2], |
args[i + 3] ? 1 : 0, args[i + 4] ? 1 : 0, |
x1, y1 |
); |
dojo.forEach(arcs, function(p){ |
result.push("bezierCurveTo", p); |
}); |
this.last.x = x1; |
this.last.y = y1; |
} |
this.lastControl = {}; |
}, |
_closePath: function(result, action, args){ |
result.push("closePath", []); |
this.lastControl = {}; |
} |
}); |
dojo.forEach(["moveTo", "lineTo", "hLineTo", "vLineTo", "curveTo", |
"smoothCurveTo", "qCurveTo", "qSmoothCurveTo", "arcTo", "closePath"], |
function(method){ modifyMethod(g.Path, method); } |
); |
|
dojo.declare("dojox.gfx.TextPath", g.path.TextPath, { |
// summary: a text shape (Canvas) |
_renderShape: function(/* Object */ ctx){ |
var s = this.shape; |
// nothing for the moment |
} |
}); |
|
dojo.declare("dojox.gfx.Surface", gs.Surface, { |
// summary: a surface object to be used for drawings (Canvas) |
constructor: function(){ |
gs.Container._init.call(this); |
this.pendingImageCount = 0; |
this.makeDirty(); |
}, |
setDimensions: function(width, height){ |
// summary: sets the width and height of the rawNode |
// width: String: width of surface, e.g., "100px" |
// height: String: height of surface, e.g., "100px" |
this.width = g.normalizedLength(width); // in pixels |
this.height = g.normalizedLength(height); // in pixels |
if(!this.rawNode) return this; |
this.rawNode.width = width; |
this.rawNode.height = height; |
this.makeDirty(); |
return this; // self |
}, |
getDimensions: function(){ |
// summary: returns an object with properties "width" and "height" |
return this.rawNode ? {width: this.rawNode.width, height: this.rawNode.height} : null; // Object |
}, |
render: function(){ |
// summary: render the all shapes |
if(this.pendingImageCount){ return; } |
var ctx = this.rawNode.getContext("2d"); |
ctx.save(); |
ctx.clearRect(0, 0, this.rawNode.width, this.rawNode.height); |
for(var i = 0; i < this.children.length; ++i){ |
this.children[i].render(ctx); |
} |
ctx.restore(); |
if("pendingRender" in this){ |
clearTimeout(this.pendingRender); |
delete this.pendingRender; |
} |
}, |
makeDirty: function(){ |
// summary: internal method, which is called when we may need to redraw |
if(!this.pendingImagesCount && !("pendingRender" in this)){ |
this.pendingRender = setTimeout(dojo.hitch(this, this.render), 0); |
} |
}, |
downloadImage: function(img, url){ |
// summary: |
// internal method, which starts an image download and renders, when it is ready |
// img: Image: |
// the image object |
// url: String: |
// the url of the image |
var handler = dojo.hitch(this, this.onImageLoad); |
if(!this.pendingImageCount++ && "pendingRender" in this){ |
clearTimeout(this.pendingRender); |
delete this.pendingRender; |
} |
img.onload = handler; |
img.onerror = handler; |
img.onabort = handler; |
img.src = url; |
}, |
onImageLoad: function(){ |
if(!--this.pendingImageCount){ this.render(); } |
}, |
|
// events are not implemented |
getEventSource: function(){ return null; }, |
connect: function(){}, |
disconnect: function(){} |
}); |
|
g.createSurface = function(parentNode, width, height){ |
// summary: creates a surface (Canvas) |
// parentNode: Node: a parent node |
// width: String: width of surface, e.g., "100px" |
// height: String: height of surface, e.g., "100px" |
|
if(!width){ width = "100%"; } |
if(!height){ height = "100%"; } |
var s = new g.Surface(), |
p = dojo.byId(parentNode), |
c = p.ownerDocument.createElement("canvas"); |
c.width = width; |
c.height = height; |
p.appendChild(c); |
s.rawNode = c; |
s.surface = s; |
return s; // dojox.gfx.Surface |
}; |
|
// Extenders |
|
var C = gs.Container, Container = { |
add: function(shape){ |
this.surface.makeDirty(); |
return C.add.apply(this, arguments); |
}, |
remove: function(shape, silently){ |
this.surface.makeDirty(); |
return C.remove.apply(this, arguments); |
}, |
clear: function(){ |
this.surface.makeDirty(); |
return C.clear.apply(this, arguments); |
}, |
_moveChildToFront: function(shape){ |
this.surface.makeDirty(); |
return C._moveChildToFront.apply(this, arguments); |
}, |
_moveChildToBack: function(shape){ |
this.surface.makeDirty(); |
return C._moveChildToBack.apply(this, arguments); |
} |
}; |
|
dojo.mixin(gs.Creator, { |
// summary: Canvas shape creators |
createObject: function(shapeType, rawShape) { |
// summary: creates an instance of the passed shapeType class |
// shapeType: Function: a class constructor to create an instance of |
// rawShape: Object: properties to be passed in to the classes "setShape" method |
// overrideSize: Boolean: set the size explicitly, if true |
var shape = new shapeType(); |
shape.surface = this.surface; |
shape.setShape(rawShape); |
this.add(shape); |
return shape; // dojox.gfx.Shape |
} |
}); |
|
dojo.extend(g.Group, Container); |
dojo.extend(g.Group, gs.Creator); |
|
dojo.extend(g.Surface, Container); |
dojo.extend(g.Surface, gs.Creator); |
})(); |
|
} |