summaryrefslogtreecommitdiff
path: root/raster/render.c
diff options
context:
space:
mode:
Diffstat (limited to 'raster/render.c')
-rw-r--r--raster/render.c876
1 files changed, 876 insertions, 0 deletions
diff --git a/raster/render.c b/raster/render.c
new file mode 100644
index 00000000..cf69e942
--- /dev/null
+++ b/raster/render.c
@@ -0,0 +1,876 @@
+#include <fitz.h>
+
+#define noDEBUG(args...) printf(args)
+#ifndef DEBUG
+#define DEBUG(args...)
+#endif
+
+#define QUANT(x,a) (((int)((x) * (a))) / (a))
+#define HSUBPIX 5.0
+#define VSUBPIX 5.0
+
+#define FNONE 0
+#define FOVER 1
+#define FRGB 4
+
+static fz_error *rendernode(fz_renderer *gc, fz_node *node, fz_matrix ctm);
+
+fz_error *
+fz_newrenderer(fz_renderer **gcp, fz_colorspace *pcm, int maskonly, int gcmem)
+{
+ fz_error *error;
+ fz_renderer *gc;
+
+ gc = fz_malloc(sizeof(fz_renderer));
+ if (!gc)
+ return fz_outofmem;
+
+ gc->maskonly = maskonly;
+ gc->model = pcm;
+ gc->cache = nil;
+ gc->gel = nil;
+ gc->ael = nil;
+
+ error = fz_newglyphcache(&gc->cache, gcmem / 24, gcmem);
+ if (error)
+ goto cleanup;
+
+ error = fz_newgel(&gc->gel);
+ if (error)
+ goto cleanup;
+
+ error = fz_newael(&gc->ael);
+ if (error)
+ goto cleanup;
+
+ gc->dest = nil;
+ gc->over = nil;
+ gc->rgb[0] = 0;
+ gc->rgb[1] = 0;
+ gc->rgb[2] = 0;
+ gc->flag = 0;
+
+ *gcp = gc;
+ return nil;
+
+cleanup:
+ if (gc->model) fz_dropcolorspace(gc->model);
+ if (gc->cache) fz_dropglyphcache(gc->cache);
+ if (gc->gel) fz_dropgel(gc->gel);
+ if (gc->ael) fz_dropael(gc->ael);
+ fz_free(gc);
+ return error;
+}
+
+void
+fz_droprenderer(fz_renderer *gc)
+{
+ if (gc->dest) fz_droppixmap(gc->dest);
+ if (gc->over) fz_droppixmap(gc->over);
+
+ if (gc->model) fz_dropcolorspace(gc->model);
+ if (gc->cache) fz_dropglyphcache(gc->cache);
+ if (gc->gel) fz_dropgel(gc->gel);
+ if (gc->ael) fz_dropael(gc->ael);
+ fz_free(gc);
+}
+
+/*
+ * Transform
+ */
+
+static fz_error *
+rendertransform(fz_renderer *gc, fz_transformnode *transform, fz_matrix ctm)
+{
+ fz_error *error;
+DEBUG("transform [%g %g %g %g %g %g]\n",
+transform->m.a, transform->m.b,
+transform->m.c, transform->m.d,
+transform->m.e, transform->m.f);
+DEBUG("{\n");
+ ctm = fz_concat(transform->m, ctm);
+ error = rendernode(gc, transform->super.first, ctm);
+DEBUG("}\n");
+ return error;
+}
+
+/*
+ * Color
+ */
+
+static fz_error *
+rendercolor(fz_renderer *gc, fz_colornode *color, fz_matrix ctm)
+{
+ fz_error *error;
+ float rgb[3];
+ unsigned char *p;
+ int n;
+
+ if (gc->maskonly)
+ return fz_throw("assert: mask only renderer");
+ if (gc->model->n != 3)
+ return fz_throw("assert: non-rgb renderer");
+
+ fz_convertcolor(color->cs, color->samples, gc->model, rgb);
+ gc->rgb[0] = rgb[0] * 255;
+ gc->rgb[1] = rgb[1] * 255;
+ gc->rgb[2] = rgb[2] * 255;
+
+DEBUG("color %s [%d %d %d];\n", color->cs->name, gc->rgb[0], gc->rgb[1], gc->rgb[2]);
+
+ if (gc->flag == FOVER)
+ {
+ p = gc->over->samples;
+ n = gc->over->w * gc->over->h;
+ }
+ else
+ {
+ error = fz_newpixmapwithrect(&gc->dest, gc->clip, 4);
+ if (error)
+ return error;
+ p = gc->dest->samples;
+ n = gc->dest->w * gc->dest->h;
+ }
+
+ while (n--)
+ {
+ p[0] = 255;
+ p[1] = gc->rgb[0];
+ p[2] = gc->rgb[1];
+ p[3] = gc->rgb[2];
+ p += 4;
+ }
+
+ return nil;
+}
+
+/*
+ * Path
+ */
+
+enum { HS = 17, VS = 15, SF = 1 };
+
+static fz_error *
+renderpath(fz_renderer *gc, fz_pathnode *path, fz_matrix ctm)
+{
+ fz_error *error;
+ float flatness;
+ fz_irect gbox;
+ fz_irect clip;
+
+ flatness = 0.3 / fz_matrixexpansion(ctm);
+ if (flatness < 0.1)
+ flatness = 0.1;
+
+ fz_resetgel(gc->gel, HS, VS);
+
+ if (path->paint == FZ_STROKE)
+ {
+ if (path->dash)
+ error = fz_dashpath(gc->gel, path, ctm, flatness);
+ else
+ error = fz_strokepath(gc->gel, path, ctm, flatness);
+ }
+ else
+ error = fz_fillpath(gc->gel, path, ctm, flatness);
+ if (error)
+ return error;
+
+ fz_sortgel(gc->gel);
+
+ gbox = fz_boundgel(gc->gel);
+ clip = fz_intersectirects(gc->clip, gbox);
+
+ if (fz_isemptyrect(clip))
+ return nil;
+
+DEBUG("path %s;\n", path->paint == FZ_STROKE ? "stroke" : "fill");
+
+ if (gc->flag & FRGB)
+ {
+ return fz_scanconvert(gc->gel, gc->ael, path->paint == FZ_EOFILL,
+ clip, gc->over, gc->rgb, 1);
+ }
+ else if (gc->flag & FOVER)
+ {
+ return fz_scanconvert(gc->gel, gc->ael, path->paint == FZ_EOFILL,
+ clip, gc->over, nil, 1);
+ }
+ else
+ {
+ error = fz_newpixmapwithrect(&gc->dest, clip, 1);
+ if (error)
+ return error;
+ fz_clearpixmap(gc->dest);
+ return fz_scanconvert(gc->gel, gc->ael, path->paint == FZ_EOFILL,
+ clip, gc->dest, nil, 0);
+ }
+}
+
+/*
+ * Text
+ */
+
+static void drawglyph(fz_renderer *gc, fz_pixmap *dst, fz_glyph *src, int xorig, int yorig)
+{
+ unsigned char *dp, *sp;
+ int w, h;
+
+ int dx0 = dst->x;
+ int dy0 = dst->y;
+ int dx1 = dst->x + dst->w;
+ int dy1 = dst->y + dst->h;
+
+ int x0 = xorig + src->x;
+ int y0 = yorig + src->y;
+ int x1 = x0 + src->w;
+ int y1 = y0 + src->h;
+
+ int sx0 = 0;
+ int sy0 = 0;
+ int sx1 = src->w;
+ int sy1 = src->h;
+
+ if (x1 <= dx0 || x0 >= dx1) return;
+ if (y1 <= dy0 || y0 >= dy1) return;
+ if (x0 < dx0) { sx0 += dx0 - x0; x0 = dx0; }
+ if (y0 < dy0) { sy0 += dy0 - y0; y0 = dy0; }
+ if (x1 > dx1) { sx1 += dx1 - x1; x1 = dx1; }
+ if (y1 > dy1) { sy1 += dy1 - y1; y1 = dy1; }
+
+ sp = src->samples + (sy0 * src->w + sx0);
+ dp = dst->samples + ((y0 - dst->y) * dst->w + (x0 - dst->x)) * dst->n;
+
+ w = sx1 - sx0;
+ h = sy1 - sy0;
+
+ switch (gc->flag)
+ {
+ case FNONE:
+ assert(dst->n == 1);
+ fz_text_1o1(sp, src->w, dp, dst->w, w, h);
+ break;
+
+ case FOVER:
+ assert(dst->n == 1);
+ fz_text_1o1(sp, src->w, dp, dst->w, w, h);
+ break;
+
+ case FOVER | FRGB:
+ assert(dst->n == 4);
+ fz_text_w3i1o4(gc->rgb, sp, src->w, dp, dst->w * 4, w, h);
+ break;
+
+ default:
+ assert(!"impossible flag in text span function");
+ }
+}
+
+static fz_error *
+rendertext(fz_renderer *gc, fz_textnode *text, fz_matrix ctm)
+{
+ fz_error *error;
+ fz_irect tbox;
+ fz_irect clip;
+ fz_matrix tm, trm;
+ fz_glyph glyph;
+ int i, x, y, cid;
+
+ tbox = fz_roundrect(fz_boundnode((fz_node*)text, ctm));
+ clip = fz_intersectirects(gc->clip, tbox);
+
+DEBUG("text %s n=%d [%g %g %g %g];\n",
+text->font->name, text->len,
+text->trm.a, text->trm.b, text->trm.c, text->trm.d);
+
+ if (fz_isemptyrect(clip))
+ return nil;
+
+ if (!(gc->flag & FOVER))
+ {
+ error = fz_newpixmapwithrect(&gc->dest, clip, 1);
+ if (error)
+ return error;
+ fz_clearpixmap(gc->dest);
+ }
+
+ tm = text->trm;
+
+ for (i = 0; i < text->len; i++)
+ {
+ cid = text->els[i].cid;
+ tm.e = text->els[i].x;
+ tm.f = text->els[i].y;
+ trm = fz_concat(tm, ctm);
+ x = fz_floor(trm.e);
+ y = fz_floor(trm.f);
+ trm.e = QUANT(trm.e - fz_floor(trm.e), HSUBPIX);
+ trm.f = QUANT(trm.f - fz_floor(trm.f), VSUBPIX);
+
+ error = fz_renderglyph(gc->cache, &glyph, text->font, cid, trm);
+ if (error)
+ return error;
+
+ if (!(gc->flag & FOVER))
+ drawglyph(gc, gc->dest, &glyph, x, y);
+ else
+ drawglyph(gc, gc->over, &glyph, x, y);
+ }
+
+ return nil;
+}
+
+/*
+ * Image
+ */
+
+static inline void
+calcimagescale(fz_matrix ctm, int w, int h, int *odx, int *ody)
+{
+ float sx, sy;
+ int dx, dy;
+
+ sx = sqrt(ctm.a * ctm.a + ctm.b * ctm.b);
+ dx = 1;
+ while (((w+dx-1)/dx)/sx > 2.0 && (w+dx-1)/dx > 1)
+ dx++;
+
+ sy = sqrt(ctm.c * ctm.c + ctm.d * ctm.d);
+ dy = 1;
+ while (((h+dy-1)/dy)/sy > 2.0 && (h+dy-1)/dy > 1)
+ dy++;
+
+ *odx = dx;
+ *ody = dy;
+}
+
+static fz_error *
+renderimage(fz_renderer *gc, fz_imagenode *node, fz_matrix ctm)
+{
+ fz_error *error;
+ fz_image *image = node->image;
+ fz_irect bbox;
+ fz_irect clip;
+ int dx, dy;
+ fz_pixmap *tile;
+ fz_pixmap *temp;
+ fz_matrix imgmat;
+ fz_matrix invmat;
+ int fa, fb, fc, fd;
+ int u0, v0;
+ int x0, y0;
+ int w, h;
+
+DEBUG("image %dx%d %d+%d %s\n{\n", image->w, image->h, image->n, image->a, image->cs?image->cs->name:"(nil)");
+
+ bbox = fz_roundrect(fz_boundnode((fz_node*)node, ctm));
+ clip = fz_intersectirects(gc->clip, bbox);
+
+ if (fz_isemptyrect(clip))
+ return nil;
+
+ calcimagescale(ctm, image->w, image->h, &dx, &dy);
+
+DEBUG(" load image\n");
+ error = fz_newpixmap(&tile, 0, 0, image->w, image->h, image->n + 1);
+ if (error)
+ return error;
+
+ error = image->loadtile(image, tile);
+ if (error)
+ goto cleanup;
+
+ if (dx != 1 || dy != 1)
+ {
+DEBUG(" scale image 1/%d 1/%d\n", dx, dy);
+ error = fz_scalepixmap(&temp, tile, dx, dy);
+ if (error)
+ goto cleanup;
+ fz_droppixmap(tile);
+ tile = temp;
+ }
+
+ if (image->cs && image->cs != gc->model)
+ {
+DEBUG(" convert from %s to %s\n", image->cs->name, gc->model->name);
+ error = fz_newpixmap(&temp, tile->x, tile->y, tile->w, tile->h, gc->model->n + 1);
+ if (error)
+ goto cleanup;
+ fz_convertpixmap(image->cs, tile, gc->model, temp);
+ fz_droppixmap(tile);
+ tile = temp;
+ }
+
+ imgmat.a = 1.0 / tile->w;
+ imgmat.b = 0.0;
+ imgmat.c = 0.0;
+ imgmat.d = -1.0 / tile->h;
+ imgmat.e = 0.0;
+ imgmat.f = 1.0;
+ invmat = fz_invertmatrix(fz_concat(imgmat, ctm));
+
+ w = clip.x1 - clip.x0;
+ h = clip.y1 - clip.y0;
+ x0 = clip.x0;
+ y0 = clip.y0;
+ u0 = (invmat.a * (x0+0.5) + invmat.c * (y0+0.5) + invmat.e) * 65536;
+ v0 = (invmat.b * (x0+0.5) + invmat.d * (y0+0.5) + invmat.f) * 65536;
+ fa = invmat.a * 65536;
+ fb = invmat.b * 65536;
+ fc = invmat.c * 65536;
+ fd = invmat.d * 65536;
+
+#define PSRC tile->samples, tile->w, tile->h
+#define PDST(p) p->samples + ((y0-p->y) * p->w + (x0-p->x)) * p->n, p->w * p->n
+#define PCTM u0, v0, fa, fb, fc, fd, w, h
+
+ switch (gc->flag)
+ {
+ case FNONE:
+ {
+DEBUG(" fnone %d x %d\n", w, h);
+ if (image->cs)
+ error = fz_newpixmapwithrect(&gc->dest, clip, gc->model->n + 1);
+ else
+ error = fz_newpixmapwithrect(&gc->dest, clip, 1);
+ if (error)
+ goto cleanup;
+
+ if (image->cs)
+ fz_img_4c4(PSRC, PDST(gc->dest), PCTM);
+ else
+ fz_img_1c1(PSRC, PDST(gc->dest), PCTM);
+ }
+ break;
+
+ case FOVER:
+ {
+DEBUG(" fover %d x %d\n", w, h);
+ if (image->cs)
+ fz_img_4o4(PSRC, PDST(gc->over), PCTM);
+ else
+ fz_img_1o1(PSRC, PDST(gc->over), PCTM);
+ }
+ break;
+
+ case FOVER | FRGB:
+DEBUG(" fover+rgb %d x %d\n", w, h);
+ fz_img_w3i1o4(gc->rgb, PSRC, PDST(gc->over), PCTM);
+ break;
+
+ default:
+ assert(!"impossible flag in image span function");
+ }
+
+DEBUG("}\n");
+
+ fz_droppixmap(tile);
+ return nil;
+
+cleanup:
+ fz_droppixmap(tile);
+ return error;
+}
+
+/*
+ * Shade
+ */
+
+static fz_error *
+rendershade(fz_renderer *gc, fz_shadenode *node, fz_matrix ctm)
+{
+ fz_error *error;
+ fz_irect bbox;
+
+ assert(!gc->maskonly);
+
+ DEBUG("shade;\n");
+
+ bbox = fz_roundrect(fz_boundnode((fz_node*)node, ctm));
+ bbox = fz_intersectirects(gc->clip, bbox);
+
+ error = fz_newpixmapwithrect(&gc->dest, bbox, gc->model->n + 1);
+ if (error)
+ return error;
+
+ return fz_rendershade(node->shade, ctm, gc->model, gc->dest);
+}
+
+/*
+ * Over, Mask and Blend
+ */
+
+static void
+blendover(fz_renderer *gc, fz_pixmap *src, fz_pixmap *dst)
+{
+ unsigned char *sp, *dp;
+ fz_irect sr, dr;
+ int x, y, w, h;
+
+ sr.x0 = src->x;
+ sr.y0 = src->y;
+ sr.x1 = src->x + src->w;
+ sr.y1 = src->y + src->h;
+
+ dr.x0 = dst->x;
+ dr.y0 = dst->y;
+ dr.x1 = dst->x + dst->w;
+ dr.y1 = dst->y + dst->h;
+
+ dr = fz_intersectirects(sr, dr);
+ x = dr.x0;
+ y = dr.y0;
+ w = dr.x1 - dr.x0;
+ h = dr.y1 - dr.y0;
+
+ sp = src->samples + ((y - src->y) * src->w + (x - src->x)) * src->n;
+ dp = dst->samples + ((y - dst->y) * dst->w + (x - dst->x)) * dst->n;
+
+ if (src->n == 1 && dst->n == 1)
+ fz_duff_1o1(sp, src->w, dp, dst->w, w, h);
+ else if (src->n == 4 && dst->n == 4)
+ fz_duff_4o4(sp, src->w * 4, dp, dst->w * 4, w, h);
+ else if (src->n == dst->n)
+ fz_duff_non(sp, src->w * src->n, src->n, dp, dst->w * dst->n, w, h);
+ else
+ assert(!"blendover src and dst mismatch");
+}
+
+static void
+blendmask(fz_renderer *gc, fz_pixmap *src, fz_pixmap *msk, fz_pixmap *dst, int over)
+{
+ unsigned char *sp, *dp, *mp;
+ fz_irect sr, dr, mr;
+ int x, y, w, h;
+
+ sr.x0 = src->x;
+ sr.y0 = src->y;
+ sr.x1 = src->x + src->w;
+ sr.y1 = src->y + src->h;
+
+ dr.x0 = dst->x;
+ dr.y0 = dst->y;
+ dr.x1 = dst->x + dst->w;
+ dr.y1 = dst->y + dst->h;
+
+ mr.x0 = msk->x;
+ mr.y0 = msk->y;
+ mr.x1 = msk->x + msk->w;
+ mr.y1 = msk->y + msk->h;
+
+ dr = fz_intersectirects(sr, dr);
+ dr = fz_intersectirects(dr, mr);
+ x = dr.x0;
+ y = dr.y0;
+ w = dr.x1 - dr.x0;
+ h = dr.y1 - dr.y0;
+
+ sp = src->samples + ((y - src->y) * src->w + (x - src->x)) * src->n;
+ mp = msk->samples + ((y - msk->y) * msk->w + (x - msk->x)) * msk->n;
+ dp = dst->samples + ((y - dst->y) * dst->w + (x - dst->x)) * dst->n;
+
+ if (over)
+ {
+ if (src->n == 1 && msk->n == 1 && dst->n == 1)
+ fz_duff_1i1o1(sp, src->w, mp, msk->w, dp, dst->w, w, h);
+ else if (src->n == 4 && msk->n == 1 && dst->n == 4)
+ fz_duff_4i1o4(sp, src->w * 4, mp, msk->w, dp, dst->w * 4, w, h);
+ else if (src->n == dst->n)
+ fz_duff_nimon(sp, src->w * src->n, src->n, mp, msk->w * msk->n, msk->n, dp, dst->w * dst->n, w, h);
+ else
+ assert(!"blendmaskover src and msk and dst mismatch");
+ }
+ else
+ {
+ if (src->n == 1 && msk->n == 1 && dst->n == 1)
+ fz_duff_1i1c1(sp, src->w, mp, msk->w, dp, dst->w, w, h);
+ else if (src->n == 4 && msk->n == 1 && dst->n == 4)
+ fz_duff_4i1c4(sp, src->w * 4, mp, msk->w, dp, dst->w * 4, w, h);
+ else if (src->n == dst->n)
+ fz_duff_nimcn(sp, src->w * src->n, src->n, mp, msk->w * msk->n, msk->n, dp, dst->w * dst->n, w, h);
+ else
+ assert(!"blendmask src and msk and dst mismatch");
+ }
+}
+
+static fz_error *
+renderover(fz_renderer *gc, fz_overnode *over, fz_matrix ctm)
+{
+ fz_error *error;
+ fz_node *child;
+ int cluster = 0;
+
+ if (!gc->over)
+ {
+DEBUG("over cluster %d\n{\n", gc->maskonly ? 1 : 4);
+ cluster = 1;
+ if (gc->maskonly)
+ error = fz_newpixmapwithrect(&gc->over, gc->clip, 1);
+ else
+ error = fz_newpixmapwithrect(&gc->over, gc->clip, 4);
+ if (error)
+ return error;
+ fz_clearpixmap(gc->over);
+ }
+else DEBUG("over\n{\n");
+
+ for (child = over->super.first; child; child = child->next)
+ {
+ error = rendernode(gc, child, ctm);
+ if (error)
+ return error;
+ if (gc->dest)
+ {
+ blendover(gc, gc->dest, gc->over);
+ fz_droppixmap(gc->dest);
+ gc->dest = nil;
+ }
+ }
+
+ if (cluster)
+ {
+ gc->dest = gc->over;
+ gc->over = nil;
+ }
+
+DEBUG("}\n");
+
+ return nil;
+}
+
+static fz_error *
+rendermask(fz_renderer *gc, fz_masknode *mask, fz_matrix ctm)
+{
+ fz_error *error;
+ int oldmaskonly;
+ fz_pixmap *oldover;
+ fz_irect oldclip;
+ fz_irect bbox;
+ fz_irect clip;
+ fz_pixmap *shapepix = nil;
+ fz_pixmap *colorpix = nil;
+ fz_node *shape;
+ fz_node *color;
+ float rgb[3];
+
+ shape = mask->super.first;
+ color = shape->next;
+
+ /* special case black voodo */
+ if (gc->flag & FOVER)
+ {
+ if (fz_iscolornode(color))
+ {
+ fz_colornode *colorn = (fz_colornode*)color;
+
+ fz_convertcolor(colorn->cs, colorn->samples, gc->model, rgb);
+ gc->rgb[0] = rgb[0] * 255;
+ gc->rgb[1] = rgb[1] * 255;
+ gc->rgb[2] = rgb[2] * 255;
+ gc->flag |= FRGB;
+
+ /* we know these can handle the FRGB shortcut */
+ if (fz_ispathnode(shape))
+ return renderpath(gc, (fz_pathnode*)shape, ctm);
+ if (fz_istextnode(shape))
+ return rendertext(gc, (fz_textnode*)shape, ctm);
+ if (fz_isimagenode(shape))
+ return renderimage(gc, (fz_imagenode*)shape, ctm);
+ }
+ }
+
+ oldclip = gc->clip;
+ oldover = gc->over;
+
+ bbox = fz_roundrect(fz_boundnode(shape, ctm));
+ clip = fz_intersectirects(bbox, gc->clip);
+ bbox = fz_roundrect(fz_boundnode(color, ctm));
+ clip = fz_intersectirects(bbox, clip);
+
+ if (fz_isemptyrect(clip))
+ return nil;
+
+DEBUG("mask [%d %d %d %d]\n{\n", clip.x0, clip.y0, clip.x1, clip.y1);
+
+{
+fz_irect sbox = fz_roundrect(fz_boundnode(shape, ctm));
+fz_irect cbox = fz_roundrect(fz_boundnode(color, ctm));
+if (cbox.x0 >= sbox.x0 && cbox.x1 <= sbox.x1)
+if (cbox.y0 >= sbox.y0 && cbox.y1 <= sbox.y1)
+DEBUG("potentially useless mask\n");
+}
+
+ gc->clip = clip;
+ gc->over = nil;
+
+ oldmaskonly = gc->maskonly;
+ gc->maskonly = 1;
+
+ error = rendernode(gc, shape, ctm);
+ if (error)
+ goto cleanup;
+ shapepix = gc->dest;
+ gc->dest = nil;
+
+ gc->maskonly = oldmaskonly;
+
+ error = rendernode(gc, color, ctm);
+ if (error)
+ goto cleanup;
+ colorpix = gc->dest;
+ gc->dest = nil;
+
+ gc->clip = oldclip;
+ gc->over = oldover;
+
+ if (shapepix && colorpix)
+ {
+ if (gc->over)
+ {
+ blendmask(gc, colorpix, shapepix, gc->over, 1);
+ }
+ else
+ {
+ clip.x0 = MAX(colorpix->x, shapepix->x);
+ clip.y0 = MAX(colorpix->y, shapepix->y);
+ clip.x1 = MIN(colorpix->x+colorpix->w, shapepix->x+shapepix->w);
+ clip.y1 = MIN(colorpix->y+colorpix->h, shapepix->y+shapepix->h);
+ error = fz_newpixmapwithrect(&gc->dest, clip, colorpix->n);
+ if (error)
+ goto cleanup;
+ blendmask(gc, colorpix, shapepix, gc->dest, 0);
+ }
+ }
+
+DEBUG("}\n");
+
+ if (shapepix) fz_droppixmap(shapepix);
+ if (colorpix) fz_droppixmap(colorpix);
+ return nil;
+
+cleanup:
+ if (shapepix) fz_droppixmap(shapepix);
+ if (colorpix) fz_droppixmap(colorpix);
+ return error;
+}
+
+/*
+ * Dispatch
+ */
+
+static fz_error *
+rendernode(fz_renderer *gc, fz_node *node, fz_matrix ctm)
+{
+ if (!node)
+ return nil;
+
+ gc->flag = FNONE;
+ if (gc->over)
+ gc->flag |= FOVER;
+
+ switch (node->kind)
+ {
+ case FZ_NOVER:
+ return renderover(gc, (fz_overnode*)node, ctm);
+ case FZ_NMASK:
+ return rendermask(gc, (fz_masknode*)node, ctm);
+ case FZ_NTRANSFORM:
+ return rendertransform(gc, (fz_transformnode*)node, ctm);
+ case FZ_NCOLOR:
+ return rendercolor(gc, (fz_colornode*)node, ctm);
+ case FZ_NPATH:
+ return renderpath(gc, (fz_pathnode*)node, ctm);
+ case FZ_NTEXT:
+ return rendertext(gc, (fz_textnode*)node, ctm);
+ case FZ_NIMAGE:
+ return renderimage(gc, (fz_imagenode*)node, ctm);
+ case FZ_NSHADE:
+ return rendershade(gc, (fz_shadenode*)node, ctm);
+ case FZ_NLINK:
+ return rendernode(gc, ((fz_linknode*)node)->tree->root, ctm);
+ case FZ_NMETA:
+ return rendernode(gc, node->first, ctm);
+ }
+
+ return nil;
+}
+
+fz_error *
+fz_rendertree(fz_pixmap **outp,
+ fz_renderer *gc, fz_tree *tree, fz_matrix ctm,
+ fz_irect bbox, int white)
+{
+ fz_error *error;
+
+ gc->clip = bbox;
+ gc->over = nil;
+
+ if (gc->maskonly)
+ error = fz_newpixmapwithrect(&gc->over, bbox, 1);
+ else
+ error = fz_newpixmapwithrect(&gc->over, bbox, 4);
+ if (error)
+ return error;
+
+ if (white)
+ memset(gc->over->samples, 0xff, gc->over->w * gc->over->h * gc->over->n);
+ else
+ memset(gc->over->samples, 0x00, gc->over->w * gc->over->h * gc->over->n);
+
+DEBUG("tree %d [%d %d %d %d]\n{\n",
+gc->maskonly ? 1 : 4,
+bbox.x0, bbox.y0, bbox.x1, bbox.y1);
+
+ error = rendernode(gc, tree->root, ctm);
+ if (error)
+ return error;
+
+DEBUG("}\n");
+
+ if (gc->dest)
+ {
+ blendover(gc, gc->dest, gc->over);
+ fz_droppixmap(gc->dest);
+ gc->dest = nil;
+ }
+
+ *outp = gc->over;
+ gc->over = nil;
+
+ return nil;
+}
+
+fz_error *
+fz_rendertreeover(fz_renderer *gc, fz_pixmap *dest, fz_tree *tree, fz_matrix ctm)
+{
+ fz_error *error;
+
+ assert(!gc->maskonly);
+ assert(dest->n == 4);
+
+ gc->clip.x0 = dest->x;
+ gc->clip.y0 = dest->y;
+ gc->clip.x1 = dest->x + dest->w;
+ gc->clip.y1 = dest->y + dest->h;
+
+ gc->over = dest;
+
+ error = rendernode(gc, tree->root, ctm);
+ if (error)
+ {
+ gc->over = nil;
+ return error;
+ }
+
+ if (gc->dest)
+ {
+ blendover(gc, gc->dest, gc->over);
+ fz_droppixmap(gc->dest);
+ gc->dest = nil;
+ }
+
+ gc->over = nil;
+
+ return nil;
+}
+