var icossin2 = function(dx, dy){ var theta = Math.atan2(dx, dy); return { cos: Math.cos(theta), sin: Math.sin(theta), theta: theta }} /* See: /sys/src/libdraw/drawrepl.c */var drawreplxy = function(min, max, x){ var sx; sx = (x - min) % (max - min); if(sx < 0) sx += max - min; return sx + min;} var drawrepl = function(r, p){ return new Point( drawreplxy(r.min.x, r.max.x, p.x), drawreplxy(r.min.y, r.max.y, p.y) );} /* See: /sys/src/libmemdraw/draw.c:/^drawclip */var drawclip = function(dst, ior, src, iop0, mask, iop1, iosr, iomr){ var r, p0, p1, sr, mr; var rmin, delta; var splitcoords; var omr; r = ior; p0 = iop0; p1 = iop1; sr = iosr; mr = iomr; if(r.min.x >= r.max.x || r.min.y >= r.max.y) return false; splitcoords = !eqpt(p0, p1); /* clip to destination */ rmin = Point.copy(r.min); if(!rectclip(r, dst.r) || !rectclip(r, dst.clipr)) return false; /* move mask point */ p1 = addpt(p1, subpt(r.min, rmin)); /* move source point */ p0 = addpt(p0, subpt(r.min, rmin)); /* map destination rectangle into source */ sr.min = Point.copy(p0); sr.max = addpt(p0, Dxy(r)); /* sr is r in source coordinates; clip to source */ if(!src.repl && !rectclip(sr, src.r)) return false; if(!rectclip(sr, src.clipr)) return false; /* compute and clip rectangle in mask */ if(splitcoords){ /* move mask point with source */ p1 = addpt(p1, subpt(sr.min, p0)); mr.min = Point.copy(p1); mr.max = addpt(p1, Dxy(sr)); omr = Rect.copy(mr); /* mr is now rectangle in mask; clip it */ if(!mask.repl && !rectclip(mr, mask.r)) return false; if(!rectclip(mr, mask.clipr)) return false; /* reflect any clips back to source */ sr.min = addpt(sr.min, subpt(mr.min, omr.min)); sr.max = addpt(sr.max, subpt(mr.max, omr.max)); p1 = Point.copy(mr.min); }else{ if(!mask.repl && !rectclip(sr, mask.r)) return false; if(!rectclip(sr, mask.clipr)) return false; p1 = Point.copy(sr.min); } /* move source clipping back to destination */ delta = subpt(r.min, p0); r = rectaddpt(sr, delta); /* move source rectangle so sr->min is in src->r */ if(src.repl){ delta = subpt(drawrepl(src.r, sr.min), sr.min); sr = rectaddpt(sr, delta); } p0 = sr.min; /* move mask point so it is in mask->r */ p1 = drawrepl(mask.r, p1); mr.min = p1; mr.max = addpt(p1, Dxy(sr)); Rect.copyTo(ior, r); Point.copyTo(iop0, p0); Point.copyTo(iop1, p1); Rect.copyTo(iosr, sr); Rect.copyTo(iomr, mr); return true;} /* XXX repl=1 images will not be aligned. Works fine for simple colour fill though. */var draw = function(dst, r, src, sp, op){ dst.ctx.save(); dst.ctx.globalCompositeOperation = Memdraw.Ops[op]; dst.ctx.beginPath(); dst.ctx.rrect(new Rect(subpt(r.min, dst.r.min), subpt(r.max, r.min))); dst.ctx.clip(); if(src.repl){ dst.ctx.fillStyle = dst.ctx.createPattern(src.canvas, "repeat"); dst.ctx.fill(); }else{ dst.ctx.pdrawImage(src.canvas, addpt(subpt(r.min, dst.r.min), subpt(src.r.min, sp))); } dst.ctx.restore(); if(dst.screen != undefined) dst.screen.dirty = true; return;} var maskalpha = function(img){ var data = img.ctx.getImageData(0, 0, img.canvas.width, img.canvas.height); for(var i = 0; i < data.data.length; i += 4){ data.data[i + 3] = (data.data[i + 0] + data.data[i + 1] + data.data[i + 2]) /3; } img.ctx.putImageData(data, 0, 0);} memopaque = new Draw9p.Image( 0, Chan.fmts.GREY1, 1, new Rect(new Point(0, 0), new Point(1, 1)), new Rect(new Point(-0x3FFFFFF, -0x3FFFFFF), new Point(0x3FFFFFF, 0x3FFFFFF)), 0xFFFFFFFF); var drawmasked = function(dst, ior, src, iosp, mask, iomp, op){ var r, sr, mr, sp, mp; var img; if(mask == undefined){ mask = memopaque; iomp = new Point(0, 0); } r = Rect.copy(ior); sr = new Rect(new Point(0, 0), new Point(0, 0)); mr = new Rect(new Point(0, 0), new Point(0, 0)); sp = Point.copy(iosp); mp = Point.copy(iomp); /* XXX modifies r, sp, mp, sr, mr */ if(!drawclip(dst, r, src, sp, mask, mp, sr, mr)) return false; maskalpha(mask); if(op == 11)return drawalpha(dst, r, src, sr, mask, mr, op) img = new Draw9p.Image(0, Chan.fmts.RGBA32, 0, r, r, 0x00000000); draw(img, r, mask, mp, Memdraw.Opdefs.SoverD.key); maskalpha(img); draw(img, r, src, sp, Memdraw.Opdefs.SinD.key); draw(dst, r, img, r.min, op); return true;} var load = function(dst, r, data, iscompressed){ var img = new Draw9p.Image(0, dst.chan, 0, r, r, 0); var w = r.max.x - r.min.x; var h = r.max.y - r.min.y; var arr = img.ctx.createImageData(w, h); var offset = Memdraw.Load(arr.data, w, h, img.chan, data, iscompressed); img.ctx.putImageData(arr, 0, 0); img.updata() drawmasked(dst, r, img, r.min, undefined, undefined, Memdraw.Opdefs.SoverD.key); /* Append canvas for debugging. */ //document.body.appendChild(dst.canvas); return offset;} var arrowend = function(tip, points, pp, end, sin, cos, radius){ var x1, x2, x3; if(end == Memdraw.End.arrow){ x1 = 8; x2 = 10; x3 = 3; }else{ x1 = (end >> 5) & 0x1FF; x2 = (end >>14) & 0x1FF; x3 = (end >> 23) & 0x1FF; } points[pp] = { /* upper side of shaft */ x: tip.x+((2*radius+1)*sin/2-x1*cos), y: tip.y-((2*radius+1)*cos/2+x1*sin) }; ++pp; points[pp] = { /* upper barb */ x: tip.x+((2*radius+2*x3+1)*sin/2-x2*cos), y: tip.y-((2*radius+2*x3+1)*cos/2+x2*sin) }; ++pp; points[pp] = { x: tip.x, y: tip.y }; ++pp; points[pp] = { /* lower barb */ x: tip.x+(-(2*radius+2*x3+1)*sin/2-x2*cos), y: tip.y-(-(2*radius+2*x3+1)*cos/2+x2*sin) }; ++pp; points[pp] = { /* lower side of shaft */ x: tip.x+(-(2*radius+1)*sin/2-x1*cos), y: tip.y+((2*radius+1)*cos/2-x1*sin) };} var discend = function(p, radius, dst, src, dsrc, op){ Memdraw.fillellipse(dst, p, radius, radius, 0, 2 * Math.PI, src, dsrc, op);} var drawchar = function(dst, p, src, sp, bg, bp, font, fc, op){ var r = { min: { x: p.x + fc.left, y: p.y - (font.ascent - fc.r.min.y) }, max: { x: (p.x + fc.left) + (fc.r.max.x - fc.r.min.x), y: (p.y - (font.ascent - fc.r.min.y)) + (fc.r.max.y - fc.r.min.y) } } var sp1 = { x: sp.x + fc.left, y: sp.y + fc.r.min.y } if(bg){ drawmasked(dst, r, bg, bp, undefined, undefined, op); } drawmasked(dst, r, src, sp1, font, fc.r.min, op); p.x += fc.width; sp.x += fc.width; return p;} Memdraw = { line: function(dst, p0, p1, end0, end1, radius, src, sp, op){ var angle = icossin2(p1.y - p0.y, p1.x - p0.x); var dx = (angle.sin * (2 * radius + 1))/2; var dy = (angle.cos * (2 * radius + 1))/2; var q = { /* 1/2 is cargo-cult from /sys/src/libmemdraw/line.c ; why? */ x: p0.x + 1/2 + angle.cos/2, y: p0.y + 1/2 + angle.sin/2 } var points = []; var pp = 0; switch(end0 & 0x1F){ case Memdraw.End.disc: discend(p0, radius, dst, src, sp, op); /* fall through */ case Memdraw.End.square: default: points[pp] = {x: q.x-dx, y: q.y+dy}; ++pp; points[pp] = {x: q.x+dx, y: q.y-dy}; ++pp; break; case Memdraw.End.arrow: arrowend(q, points, pp, end0, -angle.sin, -angle.cos, radius); this.fillpoly(dst, points.slice(0, 5), 0, src, sp, op); points[pp+1] = points[pp+4]; pp += 2; } q = { x: p1.x + 1/2 + angle.cos/2, y: p1.y + 1/2 + angle.sin/2 } switch(end1 & 0x1F){ case Memdraw.End.disc: discend(p1, radius, dst, src, sp, op); /* fall through */ case Memdraw.End.square: default: points[pp] = {x: q.x+dx, y: q.y-dy}; ++pp; points[pp] = {x: q.x-dx, y: q.y+dy}; ++pp; break; case Memdraw.End.arrow: arrowend(q, points, pp, end1, angle.sin, angle.cos, radius); this.fillpoly(dst, points.slice(pp, pp+5), 0, src, sp, op); points[pp+1] = points[pp+4]; pp += 2; } /* XXX setting w incorrectly! */ return this.fillpoly(dst, points.slice(0, pp), 0, src, sp, op); }, /* XXX behaves incorrectly for incomplete (non 2pi) ellipses. */ fillellipse: function(dst, c, horiz, vert, alpha, phi, src, sp, op){ var r, dxy; var mask; dxy = new Point(horiz, vert); r = new Rect(subpt(c, dxy), addpt(c, dxy)); mask = new Draw9p.Image(0, Chan.fmts.GREY1, 0, r, r, 0x00000000); mask.ctx.beginPath(); mask.ctx.ptranslate(subpt(c, r.min)); mask.ctx.scale(horiz, vert); mask.ctx.arc(0, 0, 1, alpha, phi, true); mask.ctx.fillStyle = "white"; mask.ctx.fill(); mask.updata(); drawmasked(dst, r, src, sp, mask, r.min, op); }, /* XXX ignores w (winding rule) */ fillpoly: function(dst, vertices, w, src, sp, op){ var r; var mask; if(vertices.length < 1){ return; } r = new Rect(vertices[0], vertices[0]); for(var i = 0; i < vertices.length; ++i){ rcombinept(r, vertices[i]); } mask = new Draw9p.Image(0, Chan.fmts.GREY1, 0, r, r, 0x00000000); mask.ctx.fillStyle = "white"; mask.ctx.beginPath(); mask.ctx.pmoveTo(subpt(vertices[0], r.min)); for(var i = 1; i < vertices.length; ++i){ mask.ctx.plineTo(subpt(vertices[i], r.min)); } mask.ctx.plineTo(subpt(vertices[0], r.min)); mask.ctx.fill(); mask.updata(); drawmasked(dst, r, src, sp, mask, r.min, op); return; }, poly: function(dst, points, end0, end1, radius, src, sp, op){ if(points.length < 2){ return; } for(var i = 1; i < points.length; ++i){ /* XXX calculate ends here; see C source. */ /* XXX calculate change in sp; requires point operations. */ this.line(dst, points[i-1], points[i], Memdraw.End.disc, Memdraw.End.disc, radius, src, sp, op); } }, load: function(dst, r, data, iscompressed){ return load(dst, r, data, iscompressed); }, string: function(dst, src, font, p, clipr, sp, bg, bp, index, op){ for(var i = 0; i < index.length; ++i){ if(index[i] == 0 || index[i] >= font.nchar){ throw("font cache index out of bounds"); } drawchar(dst, p, src, sp, bg, bp, font, font.fchar[index[i]], op); } }, Opdefs: { Clear: {key: 0, op: undefined}, SinD: {key: 8, op: "source-in"}, DinS: {key: 4, op: "destination-in"}, SoutD: {key: 2, op: "source-out"}, DoutS: {key: 1, op: "destination-out"}, S: {key: 10, op: "copy"}, /* SinD | SoutD */ SoverD: {key: 11, op: "source-over"}, /* SinD | SoutD | DoutS */ SatopD: {key: 9, op: "source-atop"}, /* SinD | DoutS */ SxorD: {key: 3, op: "xor"}, /* SoutD | DoutS */ D: {key: 5, op: undefined}, /* DinS | DoutS */ DoverS: {key: 7, op: "destination-over"}, /* DinS | DoutS | SoutD */ DatopS: {key: 6, op: "destination-atop"}, /* DinS | SoutD */ DxorS: {key: 3, op: "xor"}, /* DoutS | SoutD */ /* Ncomp: 12 */ }} Memdraw.Ops = (function(o){ var ops = []; for(var k in o){ ops[o[k].key] = o[k].op; } return ops;})(Memdraw.Opdefs); Memdraw.End = { square: 0, disc: 1, arrow: 2, mask: 0x1F} Memdraw.ARROW = function(a, b, c){ return Memdraw.End.arrow | (a << 5) | (b << 14) | (c << 23);}
var icossin2 = function(dx, dy){
var theta = Math.atan2(dx, dy);
return {
cos: Math.cos(theta),
sin: Math.sin(theta),
theta: theta
}
}
/* See: /sys/src/libdraw/drawrepl.c */
var drawreplxy = function(min, max, x){
var sx;
sx = (x - min) % (max - min);
if(sx < 0)
sx += max - min;
return sx + min;
}
var drawrepl = function(r, p){
return new Point(
drawreplxy(r.min.x, r.max.x, p.x),
drawreplxy(r.min.y, r.max.y, p.y)
);
}
/* See: /sys/src/libmemdraw/draw.c:/^drawclip */
var drawclip = function(dst, ior, src, iop0, mask, iop1, iosr, iomr){
var r, p0, p1, sr, mr;
var rmin, delta;
var splitcoords;
var omr;
r = ior;
p0 = iop0;
p1 = iop1;
sr = iosr;
mr = iomr;
if(r.min.x >= r.max.x || r.min.y >= r.max.y)
return false;
splitcoords = !eqpt(p0, p1);
/* clip to destination */
rmin = Point.copy(r.min);
if(!rectclip(r, dst.r) || !rectclip(r, dst.clipr))
return false;
/* move mask point */
p1 = addpt(p1, subpt(r.min, rmin));
/* move source point */
p0 = addpt(p0, subpt(r.min, rmin));
/* map destination rectangle into source */
sr.min = Point.copy(p0);
sr.max = addpt(p0, Dxy(r));
/* sr is r in source coordinates; clip to source */
if(!src.repl && !rectclip(sr, src.r))
return false;
if(!rectclip(sr, src.clipr))
return false;
/* compute and clip rectangle in mask */
if(splitcoords){
/* move mask point with source */
p1 = addpt(p1, subpt(sr.min, p0));
mr.min = Point.copy(p1);
mr.max = addpt(p1, Dxy(sr));
omr = Rect.copy(mr);
/* mr is now rectangle in mask; clip it */
if(!mask.repl && !rectclip(mr, mask.r))
return false;
if(!rectclip(mr, mask.clipr))
return false;
/* reflect any clips back to source */
sr.min = addpt(sr.min, subpt(mr.min, omr.min));
sr.max = addpt(sr.max, subpt(mr.max, omr.max));
p1 = Point.copy(mr.min);
}else{
if(!mask.repl && !rectclip(sr, mask.r))
return false;
if(!rectclip(sr, mask.clipr))
return false;
p1 = Point.copy(sr.min);
}
/* move source clipping back to destination */
delta = subpt(r.min, p0);
r = rectaddpt(sr, delta);
/* move source rectangle so sr->min is in src->r */
if(src.repl){
delta = subpt(drawrepl(src.r, sr.min), sr.min);
sr = rectaddpt(sr, delta);
}
p0 = sr.min;
/* move mask point so it is in mask->r */
p1 = drawrepl(mask.r, p1);
mr.min = p1;
mr.max = addpt(p1, Dxy(sr));
Rect.copyTo(ior, r);
Point.copyTo(iop0, p0);
Point.copyTo(iop1, p1);
Rect.copyTo(iosr, sr);
Rect.copyTo(iomr, mr);
return true;
}
/* XXX repl=1 images will not be aligned. Works fine for simple colour fill though. */
var draw = function(dst, r, src, sp, op){
dst.ctx.save();
dst.ctx.globalCompositeOperation = Memdraw.Ops[op];
dst.ctx.beginPath();
dst.ctx.rrect(new Rect(subpt(r.min, dst.r.min), subpt(r.max, r.min)));
dst.ctx.clip();
if(src.repl){
dst.ctx.fillStyle = dst.ctx.createPattern(src.canvas, "repeat");
dst.ctx.fill();
}else{
dst.ctx.pdrawImage(src.canvas, addpt(subpt(r.min, dst.r.min), subpt(src.r.min, sp)));
}
dst.ctx.restore();
if(dst.screen != undefined)
dst.screen.dirty = true;
return;
}
var maskalpha = function(img){
var data = img.ctx.getImageData(0, 0, img.canvas.width, img.canvas.height);
for(var i = 0; i < data.data.length; i += 4){
data.data[i + 3] = (data.data[i + 0] + data.data[i + 1] + data.data[i + 2]) /3;
}
img.ctx.putImageData(data, 0, 0);
}
memopaque = new Draw9p.Image(
0, Chan.fmts.GREY1, 1,
new Rect(new Point(0, 0), new Point(1, 1)),
new Rect(new Point(-0x3FFFFFF, -0x3FFFFFF), new Point(0x3FFFFFF, 0x3FFFFFF)),
0xFFFFFFFF
);
var drawmasked = function(dst, ior, src, iosp, mask, iomp, op){
var r, sr, mr, sp, mp;
var img;
if(mask == undefined){
mask = memopaque;
iomp = new Point(0, 0);
}
r = Rect.copy(ior);
sr = new Rect(new Point(0, 0), new Point(0, 0));
mr = new Rect(new Point(0, 0), new Point(0, 0));
sp = Point.copy(iosp);
mp = Point.copy(iomp);
/* XXX modifies r, sp, mp, sr, mr */
if(!drawclip(dst, r, src, sp, mask, mp, sr, mr))
return false;
maskalpha(mask);
if(op == 11)return drawalpha(dst, r, src, sr, mask, mr, op)
img = new Draw9p.Image(0, Chan.fmts.RGBA32, 0, r, r, 0x00000000);
draw(img, r, mask, mp, Memdraw.Opdefs.SoverD.key);
maskalpha(img);
draw(img, r, src, sp, Memdraw.Opdefs.SinD.key);
draw(dst, r, img, r.min, op);
return true;
}
var load = function(dst, r, data, iscompressed){
var img = new Draw9p.Image(0, dst.chan, 0, r, r, 0);
var w = r.max.x - r.min.x;
var h = r.max.y - r.min.y;
var arr = img.ctx.createImageData(w, h);
var offset = Memdraw.Load(arr.data, w, h, img.chan, data, iscompressed);
img.ctx.putImageData(arr, 0, 0);
img.updata()
drawmasked(dst, r, img, r.min, undefined, undefined, Memdraw.Opdefs.SoverD.key);
/* Append canvas for debugging. */
//document.body.appendChild(dst.canvas);
return offset;
}
var arrowend = function(tip, points, pp, end, sin, cos, radius){
var x1, x2, x3;
if(end == Memdraw.End.arrow){
x1 = 8;
x2 = 10;
x3 = 3;
}else{
x1 = (end >> 5) & 0x1FF;
x2 = (end >>14) & 0x1FF;
x3 = (end >> 23) & 0x1FF;
}
points[pp] = { /* upper side of shaft */
x: tip.x+((2*radius+1)*sin/2-x1*cos),
y: tip.y-((2*radius+1)*cos/2+x1*sin)
};
++pp;
points[pp] = { /* upper barb */
x: tip.x+((2*radius+2*x3+1)*sin/2-x2*cos),
y: tip.y-((2*radius+2*x3+1)*cos/2+x2*sin)
};
++pp;
points[pp] = {
x: tip.x,
y: tip.y
};
++pp;
points[pp] = { /* lower barb */
x: tip.x+(-(2*radius+2*x3+1)*sin/2-x2*cos),
y: tip.y-(-(2*radius+2*x3+1)*cos/2+x2*sin)
};
++pp;
points[pp] = { /* lower side of shaft */
x: tip.x+(-(2*radius+1)*sin/2-x1*cos),
y: tip.y+((2*radius+1)*cos/2-x1*sin)
};
}
var discend = function(p, radius, dst, src, dsrc, op){
Memdraw.fillellipse(dst, p, radius, radius, 0, 2 * Math.PI, src, dsrc, op);
}
var drawchar = function(dst, p, src, sp, bg, bp, font, fc, op){
var r = {
min: {
x: p.x + fc.left,
y: p.y - (font.ascent - fc.r.min.y)
},
max: {
x: (p.x + fc.left) + (fc.r.max.x - fc.r.min.x),
y: (p.y - (font.ascent - fc.r.min.y)) + (fc.r.max.y - fc.r.min.y)
}
}
var sp1 = {
x: sp.x + fc.left,
y: sp.y + fc.r.min.y
}
if(bg){
drawmasked(dst, r, bg, bp, undefined, undefined, op);
}
drawmasked(dst, r, src, sp1, font, fc.r.min, op);
p.x += fc.width;
sp.x += fc.width;
return p;
}
Memdraw = {
line: function(dst, p0, p1, end0, end1, radius, src, sp, op){
var angle = icossin2(p1.y - p0.y, p1.x - p0.x);
var dx = (angle.sin * (2 * radius + 1))/2;
var dy = (angle.cos * (2 * radius + 1))/2;
var q = {
/* 1/2 is cargo-cult from /sys/src/libmemdraw/line.c ; why? */
x: p0.x + 1/2 + angle.cos/2,
y: p0.y + 1/2 + angle.sin/2
}
var points = [];
var pp = 0;
switch(end0 & 0x1F){
case Memdraw.End.disc:
discend(p0, radius, dst, src, sp, op);
/* fall through */
case Memdraw.End.square:
default:
points[pp] = {x: q.x-dx, y: q.y+dy};
++pp;
points[pp] = {x: q.x+dx, y: q.y-dy};
++pp;
break;
case Memdraw.End.arrow:
arrowend(q, points, pp, end0, -angle.sin, -angle.cos, radius);
this.fillpoly(dst, points.slice(0, 5), 0, src, sp, op);
points[pp+1] = points[pp+4];
pp += 2;
}
q = {
x: p1.x + 1/2 + angle.cos/2,
y: p1.y + 1/2 + angle.sin/2
}
switch(end1 & 0x1F){
case Memdraw.End.disc:
discend(p1, radius, dst, src, sp, op);
/* fall through */
case Memdraw.End.square:
default:
points[pp] = {x: q.x+dx, y: q.y-dy};
++pp;
points[pp] = {x: q.x-dx, y: q.y+dy};
++pp;
break;
case Memdraw.End.arrow:
arrowend(q, points, pp, end1, angle.sin, angle.cos, radius);
this.fillpoly(dst, points.slice(pp, pp+5), 0, src, sp, op);
points[pp+1] = points[pp+4];
pp += 2;
}
/* XXX setting w incorrectly! */
return this.fillpoly(dst, points.slice(0, pp), 0, src, sp, op);
},
/* XXX behaves incorrectly for incomplete (non 2pi) ellipses. */
fillellipse: function(dst, c, horiz, vert, alpha, phi, src, sp, op){
var r, dxy;
var mask;
dxy = new Point(horiz, vert);
r = new Rect(subpt(c, dxy), addpt(c, dxy));
mask = new Draw9p.Image(0, Chan.fmts.GREY1, 0, r, r, 0x00000000);
mask.ctx.beginPath();
mask.ctx.ptranslate(subpt(c, r.min));
mask.ctx.scale(horiz, vert);
mask.ctx.arc(0, 0, 1, alpha, phi, true);
mask.ctx.fillStyle = "white";
mask.ctx.fill();
mask.updata();
drawmasked(dst, r, src, sp, mask, r.min, op);
},
/* XXX ignores w (winding rule) */
fillpoly: function(dst, vertices, w, src, sp, op){
var r;
var mask;
if(vertices.length < 1){
return;
}
r = new Rect(vertices[0], vertices[0]);
for(var i = 0; i < vertices.length; ++i){
rcombinept(r, vertices[i]);
}
mask = new Draw9p.Image(0, Chan.fmts.GREY1, 0, r, r, 0x00000000);
mask.ctx.fillStyle = "white";
mask.ctx.beginPath();
mask.ctx.pmoveTo(subpt(vertices[0], r.min));
for(var i = 1; i < vertices.length; ++i){
mask.ctx.plineTo(subpt(vertices[i], r.min));
}
mask.ctx.plineTo(subpt(vertices[0], r.min));
mask.ctx.fill();
mask.updata();
drawmasked(dst, r, src, sp, mask, r.min, op);
return;
},
poly: function(dst, points, end0, end1, radius, src, sp, op){
if(points.length < 2){
return;
}
for(var i = 1; i < points.length; ++i){
/* XXX calculate ends here; see C source. */
/* XXX calculate change in sp; requires point operations. */
this.line(dst, points[i-1], points[i],
Memdraw.End.disc, Memdraw.End.disc,
radius, src, sp, op);
}
},
load: function(dst, r, data, iscompressed){
return load(dst, r, data, iscompressed);
},
string: function(dst, src, font, p, clipr, sp, bg, bp, index, op){
for(var i = 0; i < index.length; ++i){
if(index[i] == 0 || index[i] >= font.nchar){
throw("font cache index out of bounds");
}
drawchar(dst, p, src, sp, bg, bp, font, font.fchar[index[i]], op);
}
},
Opdefs: {
Clear: {key: 0, op: undefined},
SinD: {key: 8, op: "source-in"},
DinS: {key: 4, op: "destination-in"},
SoutD: {key: 2, op: "source-out"},
DoutS: {key: 1, op: "destination-out"},
S: {key: 10, op: "copy"}, /* SinD | SoutD */
SoverD: {key: 11, op: "source-over"}, /* SinD | SoutD | DoutS */
SatopD: {key: 9, op: "source-atop"}, /* SinD | DoutS */
SxorD: {key: 3, op: "xor"}, /* SoutD | DoutS */
D: {key: 5, op: undefined}, /* DinS | DoutS */
DoverS: {key: 7, op: "destination-over"}, /* DinS | DoutS | SoutD */
DatopS: {key: 6, op: "destination-atop"}, /* DinS | SoutD */
DxorS: {key: 3, op: "xor"}, /* DoutS | SoutD */
/* Ncomp: 12 */
}
}
Memdraw.Ops = (function(o){
var ops = [];
for(var k in o){
ops[o[k].key] = o[k].op;
}
return ops;
})(Memdraw.Opdefs);
Memdraw.End = {
square: 0,
disc: 1,
arrow: 2,
mask: 0x1F
}
Memdraw.ARROW = function(a, b, c){
return Memdraw.End.arrow | (a << 5) | (b << 14) | (c << 23);
}