summaryrefslogtreecommitdiff
path: root/draw
diff options
context:
space:
mode:
authorTor Andersson <tor.andersson@artifex.com>2011-04-05 12:04:13 +0200
committerTor Andersson <tor.andersson@artifex.com>2011-04-05 12:04:13 +0200
commitc61a13b445c2d4d7b6468a0f2fe7ab998e210e90 (patch)
tree358cc5fc883fdf34f8fecad0965d5fc5426e75c3 /draw
parent68f9077504d387a396efd0af6b8ee4147c3451f3 (diff)
downloadmupdf-c61a13b445c2d4d7b6468a0f2fe7ab998e210e90.tar.xz
Makefile tweaks.
Diffstat (limited to 'draw')
-rw-r--r--draw/draw_device.c1029
1 files changed, 1029 insertions, 0 deletions
diff --git a/draw/draw_device.c b/draw/draw_device.c
new file mode 100644
index 00000000..05f96f28
--- /dev/null
+++ b/draw/draw_device.c
@@ -0,0 +1,1029 @@
+#include "fitz.h"
+
+#define QUANT(x,a) (((int)((x) * (a))) / (a))
+#define HSUBPIX 5.0
+#define VSUBPIX 5.0
+
+#define STACK_SIZE 96
+
+typedef struct fz_draw_device_s fz_draw_device;
+
+struct fz_draw_device_s
+{
+ fz_glyph_cache *cache;
+ fz_gel *gel;
+ fz_ael *ael;
+
+ fz_pixmap *dest;
+ fz_bbox scissor;
+
+ int top;
+ struct {
+ fz_bbox scissor;
+ fz_pixmap *dest;
+ fz_pixmap *mask;
+ fz_blendmode blendmode;
+ int luminosity;
+ float alpha;
+ fz_matrix ctm;
+ float xstep, ystep;
+ fz_rect area;
+ } stack[STACK_SIZE];
+};
+
+static void
+fz_draw_fill_path(void *user, fz_path *path, int even_odd, fz_matrix ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_draw_device *dev = user;
+ fz_colorspace *model = dev->dest->colorspace;
+ float expansion = fz_matrix_expansion(ctm);
+ float flatness = 0.3f / expansion;
+ unsigned char colorbv[FZ_MAX_COLORS + 1];
+ float colorfv[FZ_MAX_COLORS];
+ fz_bbox bbox;
+ int i;
+
+ fz_reset_gel(dev->gel, dev->scissor);
+ fz_flatten_fill_path(dev->gel, path, ctm, flatness);
+ fz_sort_gel(dev->gel);
+
+ bbox = fz_bound_gel(dev->gel);
+ bbox = fz_intersect_bbox(bbox, dev->scissor);
+
+ if (fz_is_empty_rect(bbox))
+ return;
+
+ fz_convert_color(colorspace, color, model, colorfv);
+ for (i = 0; i < model->n; i++)
+ colorbv[i] = colorfv[i] * 255;
+ colorbv[i] = alpha * 255;
+
+ fz_scan_convert(dev->gel, dev->ael, even_odd, bbox, dev->dest, colorbv);
+}
+
+static void
+fz_draw_stroke_path(void *user, fz_path *path, fz_stroke_state *stroke, fz_matrix ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_draw_device *dev = user;
+ fz_colorspace *model = dev->dest->colorspace;
+ float expansion = fz_matrix_expansion(ctm);
+ float flatness = 0.3f / expansion;
+ float linewidth = stroke->linewidth;
+ unsigned char colorbv[FZ_MAX_COLORS + 1];
+ float colorfv[FZ_MAX_COLORS];
+ fz_bbox bbox;
+ int i;
+
+ if (linewidth * expansion < 0.1f)
+ linewidth = 1 / expansion;
+
+ fz_reset_gel(dev->gel, dev->scissor);
+ if (stroke->dash_len > 0)
+ fz_flatten_dash_path(dev->gel, path, stroke, ctm, flatness, linewidth);
+ else
+ fz_flatten_stroke_path(dev->gel, path, stroke, ctm, flatness, linewidth);
+ fz_sort_gel(dev->gel);
+
+ bbox = fz_bound_gel(dev->gel);
+ bbox = fz_intersect_bbox(bbox, dev->scissor);
+
+ if (fz_is_empty_rect(bbox))
+ return;
+
+ fz_convert_color(colorspace, color, model, colorfv);
+ for (i = 0; i < model->n; i++)
+ colorbv[i] = colorfv[i] * 255;
+ colorbv[i] = alpha * 255;
+
+ fz_scan_convert(dev->gel, dev->ael, 0, bbox, dev->dest, colorbv);
+}
+
+static void
+fz_draw_clip_path(void *user, fz_path *path, int even_odd, fz_matrix ctm)
+{
+ fz_draw_device *dev = user;
+ fz_colorspace *model = dev->dest->colorspace;
+ float expansion = fz_matrix_expansion(ctm);
+ float flatness = 0.3f / expansion;
+ fz_pixmap *mask, *dest;
+ fz_bbox bbox;
+
+ if (dev->top == STACK_SIZE)
+ {
+ fz_warn("assert: too many buffers on stack");
+ return;
+ }
+
+ fz_reset_gel(dev->gel, dev->scissor);
+ fz_flatten_fill_path(dev->gel, path, ctm, flatness);
+ fz_sort_gel(dev->gel);
+
+ bbox = fz_bound_gel(dev->gel);
+ bbox = fz_intersect_bbox(bbox, dev->scissor);
+
+ if (fz_is_empty_rect(bbox) || fz_is_rect_gel(dev->gel))
+ {
+ dev->stack[dev->top].scissor = dev->scissor;
+ dev->stack[dev->top].mask = NULL;
+ dev->stack[dev->top].dest = NULL;
+ dev->scissor = bbox;
+ dev->top++;
+ return;
+ }
+
+ mask = fz_new_pixmap_with_rect(NULL, bbox);
+ dest = fz_new_pixmap_with_rect(model, bbox);
+
+ fz_clear_pixmap(mask);
+ fz_clear_pixmap(dest);
+
+ fz_scan_convert(dev->gel, dev->ael, even_odd, bbox, mask, NULL);
+
+ dev->stack[dev->top].scissor = dev->scissor;
+ dev->stack[dev->top].mask = mask;
+ dev->stack[dev->top].dest = dev->dest;
+ dev->scissor = bbox;
+ dev->dest = dest;
+ dev->top++;
+}
+
+static void
+fz_draw_clip_stroke_path(void *user, fz_path *path, fz_stroke_state *stroke, fz_matrix ctm)
+{
+ fz_draw_device *dev = user;
+ fz_colorspace *model = dev->dest->colorspace;
+ float expansion = fz_matrix_expansion(ctm);
+ float flatness = 0.3f / expansion;
+ float linewidth = stroke->linewidth;
+ fz_pixmap *mask, *dest;
+ fz_bbox bbox;
+
+ if (dev->top == STACK_SIZE)
+ {
+ fz_warn("assert: too many buffers on stack");
+ return;
+ }
+
+ if (linewidth * expansion < 0.1f)
+ linewidth = 1 / expansion;
+
+ fz_reset_gel(dev->gel, dev->scissor);
+ if (stroke->dash_len > 0)
+ fz_flatten_dash_path(dev->gel, path, stroke, ctm, flatness, linewidth);
+ else
+ fz_flatten_stroke_path(dev->gel, path, stroke, ctm, flatness, linewidth);
+ fz_sort_gel(dev->gel);
+
+ bbox = fz_bound_gel(dev->gel);
+ bbox = fz_intersect_bbox(bbox, dev->scissor);
+
+ mask = fz_new_pixmap_with_rect(NULL, bbox);
+ dest = fz_new_pixmap_with_rect(model, bbox);
+
+ fz_clear_pixmap(mask);
+ fz_clear_pixmap(dest);
+
+ if (!fz_is_empty_rect(bbox))
+ fz_scan_convert(dev->gel, dev->ael, 0, bbox, mask, NULL);
+
+ dev->stack[dev->top].scissor = dev->scissor;
+ dev->stack[dev->top].mask = mask;
+ dev->stack[dev->top].dest = dev->dest;
+ dev->scissor = bbox;
+ dev->dest = dest;
+ dev->top++;
+}
+
+static void
+draw_glyph(unsigned char *colorbv, fz_pixmap *dst, fz_pixmap *msk,
+ int xorig, int yorig, fz_bbox scissor)
+{
+ unsigned char *dp, *mp;
+ fz_bbox bbox;
+ int x, y, w, h;
+
+ bbox = fz_bound_pixmap(msk);
+ bbox.x0 += xorig;
+ bbox.y0 += yorig;
+ bbox.x1 += xorig;
+ bbox.y1 += yorig;
+
+ bbox = fz_intersect_bbox(bbox, scissor); /* scissor < dst */
+ x = bbox.x0;
+ y = bbox.y0;
+ w = bbox.x1 - bbox.x0;
+ h = bbox.y1 - bbox.y0;
+
+ mp = msk->samples + ((y - msk->y - yorig) * msk->w + (x - msk->x - xorig));
+ dp = dst->samples + ((y - dst->y) * dst->w + (x - dst->x)) * dst->n;
+
+ assert(msk->n == 1);
+
+ while (h--)
+ {
+ if (dst->colorspace)
+ fz_paint_span_with_color(dp, mp, dst->n, w, colorbv);
+ else
+ fz_paint_span(dp, mp, 1, w, 255);
+ dp += dst->w * dst->n;
+ mp += msk->w;
+ }
+}
+
+static void
+fz_draw_fill_text(void *user, fz_text *text, fz_matrix ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_draw_device *dev = user;
+ fz_colorspace *model = dev->dest->colorspace;
+ unsigned char colorbv[FZ_MAX_COLORS + 1];
+ float colorfv[FZ_MAX_COLORS];
+ fz_matrix tm, trm;
+ fz_pixmap *glyph;
+ int i, x, y, gid;
+
+ fz_convert_color(colorspace, color, model, colorfv);
+ for (i = 0; i < model->n; i++)
+ colorbv[i] = colorfv[i] * 255;
+ colorbv[i] = alpha * 255;
+
+ tm = text->trm;
+
+ for (i = 0; i < text->len; i++)
+ {
+ gid = text->items[i].gid;
+ if (gid < 0)
+ continue;
+
+ tm.e = text->items[i].x;
+ tm.f = text->items[i].y;
+ trm = fz_concat(tm, ctm);
+ x = floorf(trm.e);
+ y = floorf(trm.f);
+ trm.e = QUANT(trm.e - floorf(trm.e), HSUBPIX);
+ trm.f = QUANT(trm.f - floorf(trm.f), VSUBPIX);
+
+ glyph = fz_render_glyph(dev->cache, text->font, gid, trm);
+ if (glyph)
+ {
+ draw_glyph(colorbv, dev->dest, glyph, x, y, dev->scissor);
+ fz_drop_pixmap(glyph);
+ }
+ }
+}
+
+static void
+fz_draw_stroke_text(void *user, fz_text *text, fz_stroke_state *stroke, fz_matrix ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_draw_device *dev = user;
+ fz_colorspace *model = dev->dest->colorspace;
+ unsigned char colorbv[FZ_MAX_COLORS + 1];
+ float colorfv[FZ_MAX_COLORS];
+ fz_matrix tm, trm;
+ fz_pixmap *glyph;
+ int i, x, y, gid;
+
+ fz_convert_color(colorspace, color, model, colorfv);
+ for (i = 0; i < model->n; i++)
+ colorbv[i] = colorfv[i] * 255;
+ colorbv[i] = alpha * 255;
+
+ tm = text->trm;
+
+ for (i = 0; i < text->len; i++)
+ {
+ gid = text->items[i].gid;
+ if (gid < 0)
+ continue;
+
+ tm.e = text->items[i].x;
+ tm.f = text->items[i].y;
+ trm = fz_concat(tm, ctm);
+ x = floorf(trm.e);
+ y = floorf(trm.f);
+ trm.e = QUANT(trm.e - floorf(trm.e), HSUBPIX);
+ trm.f = QUANT(trm.f - floorf(trm.f), VSUBPIX);
+
+ glyph = fz_render_stroked_glyph(dev->cache, text->font, gid, trm, ctm, stroke);
+ if (glyph)
+ {
+ draw_glyph(colorbv, dev->dest, glyph, x, y, dev->scissor);
+ fz_drop_pixmap(glyph);
+ }
+ }
+}
+
+static void
+fz_draw_clip_text(void *user, fz_text *text, fz_matrix ctm, int accumulate)
+{
+ fz_draw_device *dev = user;
+ fz_colorspace *model = dev->dest->colorspace;
+ fz_bbox bbox;
+ fz_pixmap *mask, *dest;
+ fz_matrix tm, trm;
+ fz_pixmap *glyph;
+ int i, x, y, gid;
+
+ /* If accumulate == 0 then this text object is guaranteed complete */
+ /* If accumulate == 1 then this text object is the first (or only) in a sequence */
+ /* If accumulate == 2 then this text object is a continuation */
+
+ if (dev->top == STACK_SIZE)
+ {
+ fz_warn("assert: too many buffers on stack");
+ return;
+ }
+
+ if (accumulate == 0)
+ {
+ /* make the mask the exact size needed */
+ bbox = fz_round_rect(fz_bound_text(text, ctm));
+ bbox = fz_intersect_bbox(bbox, dev->scissor);
+ }
+ else
+ {
+ /* be conservative about the size of the mask needed */
+ bbox = dev->scissor;
+ }
+
+ if (accumulate == 0 || accumulate == 1)
+ {
+ mask = fz_new_pixmap_with_rect(NULL, bbox);
+ dest = fz_new_pixmap_with_rect(model, bbox);
+
+ fz_clear_pixmap(mask);
+ fz_clear_pixmap(dest);
+
+ dev->stack[dev->top].scissor = dev->scissor;
+ dev->stack[dev->top].mask = mask;
+ dev->stack[dev->top].dest = dev->dest;
+ dev->scissor = bbox;
+ dev->dest = dest;
+ dev->top++;
+ }
+ else
+ {
+ mask = dev->stack[dev->top-1].mask;
+ }
+
+ if (!fz_is_empty_rect(bbox))
+ {
+ tm = text->trm;
+
+ for (i = 0; i < text->len; i++)
+ {
+ gid = text->items[i].gid;
+ if (gid < 0)
+ continue;
+
+ tm.e = text->items[i].x;
+ tm.f = text->items[i].y;
+ trm = fz_concat(tm, ctm);
+ x = floorf(trm.e);
+ y = floorf(trm.f);
+ trm.e = QUANT(trm.e - floorf(trm.e), HSUBPIX);
+ trm.f = QUANT(trm.f - floorf(trm.f), VSUBPIX);
+
+ glyph = fz_render_glyph(dev->cache, text->font, gid, trm);
+ if (glyph)
+ {
+ draw_glyph(NULL, mask, glyph, x, y, bbox);
+ fz_drop_pixmap(glyph);
+ }
+ }
+ }
+}
+
+static void
+fz_draw_clip_stroke_text(void *user, fz_text *text, fz_stroke_state *stroke, fz_matrix ctm)
+{
+ fz_draw_device *dev = user;
+ fz_colorspace *model = dev->dest->colorspace;
+ fz_bbox bbox;
+ fz_pixmap *mask, *dest;
+ fz_matrix tm, trm;
+ fz_pixmap *glyph;
+ int i, x, y, gid;
+
+ if (dev->top == STACK_SIZE)
+ {
+ fz_warn("assert: too many buffers on stack");
+ return;
+ }
+
+ /* make the mask the exact size needed */
+ bbox = fz_round_rect(fz_bound_text(text, ctm));
+ bbox = fz_intersect_bbox(bbox, dev->scissor);
+
+ mask = fz_new_pixmap_with_rect(NULL, bbox);
+ dest = fz_new_pixmap_with_rect(model, bbox);
+
+ fz_clear_pixmap(mask);
+ fz_clear_pixmap(dest);
+
+ dev->stack[dev->top].scissor = dev->scissor;
+ dev->stack[dev->top].mask = mask;
+ dev->stack[dev->top].dest = dev->dest;
+ dev->scissor = bbox;
+ dev->dest = dest;
+ dev->top++;
+
+ if (!fz_is_empty_rect(bbox))
+ {
+ tm = text->trm;
+
+ for (i = 0; i < text->len; i++)
+ {
+ gid = text->items[i].gid;
+ if (gid < 0)
+ continue;
+
+ tm.e = text->items[i].x;
+ tm.f = text->items[i].y;
+ trm = fz_concat(tm, ctm);
+ x = floorf(trm.e);
+ y = floorf(trm.f);
+ trm.e = QUANT(trm.e - floorf(trm.e), HSUBPIX);
+ trm.f = QUANT(trm.f - floorf(trm.f), VSUBPIX);
+
+ glyph = fz_render_stroked_glyph(dev->cache, text->font, gid, trm, ctm, stroke);
+ if (glyph)
+ {
+ draw_glyph(NULL, mask, glyph, x, y, bbox);
+ fz_drop_pixmap(glyph);
+ }
+ }
+ }
+}
+
+static void
+fz_draw_ignore_text(void *user, fz_text *text, fz_matrix ctm)
+{
+}
+
+static void
+fz_draw_fill_shade(void *user, fz_shade *shade, fz_matrix ctm, float alpha)
+{
+ fz_draw_device *dev = user;
+ fz_colorspace *model = dev->dest->colorspace;
+ fz_pixmap *dest = dev->dest;
+ fz_rect bounds;
+ fz_bbox bbox, scissor;
+ float colorfv[FZ_MAX_COLORS];
+ unsigned char colorbv[FZ_MAX_COLORS + 1];
+
+ bounds = fz_bound_shade(shade, ctm);
+ bbox = fz_intersect_bbox(fz_round_rect(bounds), dev->scissor);
+ scissor = dev->scissor;
+
+ // TODO: proper clip by shade->bbox
+
+ if (fz_is_empty_rect(bbox))
+ return;
+
+ if (!model)
+ {
+ fz_warn("cannot render shading directly to an alpha mask");
+ return;
+ }
+
+ if (alpha < 1)
+ {
+ dest = fz_new_pixmap_with_rect(dev->dest->colorspace, bbox);
+ fz_clear_pixmap(dest);
+ }
+
+ if (shade->use_background)
+ {
+ unsigned char *s;
+ int x, y, n, i;
+ fz_convert_color(shade->colorspace, shade->background, model, colorfv);
+ for (i = 0; i < model->n; i++)
+ colorbv[i] = colorfv[i] * 255;
+ colorbv[i] = 255;
+
+ n = dest->n;
+ for (y = scissor.y0; y < scissor.y1; y++)
+ {
+ s = dest->samples + ((scissor.x0 - dest->x) + (y - dest->y) * dest->w) * dest->n;
+ for (x = scissor.x0; x < scissor.x1; x++)
+ {
+ for (i = 0; i < n; i++)
+ *s++ = colorbv[i];
+ }
+ }
+ }
+
+ fz_paint_shade(shade, ctm, dest, bbox);
+
+ if (alpha < 1)
+ {
+ fz_paint_pixmap(dev->dest, dest, alpha * 255);
+ fz_drop_pixmap(dest);
+ }
+}
+
+static fz_pixmap *
+fz_transform_pixmap(fz_pixmap *image, fz_matrix *ctm, int x, int y, int dx, int dy)
+{
+ fz_pixmap *scaled;
+
+ if ((ctm->a != 0) && (ctm->b == 0) && (ctm->c == 0) && (ctm->d != 0))
+ {
+ /* Unrotated or X flip or Yflip or XYflip */
+ scaled = fz_scale_pixmap(image, ctm->e, ctm->f, ctm->a, ctm->d);
+ if (scaled == NULL)
+ return NULL;
+ ctm->a = scaled->w;
+ ctm->d = scaled->h;
+ ctm->e = scaled->x;
+ ctm->f = scaled->y;
+ return scaled;
+ }
+ if ((ctm->a == 0) && (ctm->b != 0) && (ctm->c != 0) && (ctm->d == 0))
+ {
+ /* Other orthogonal flip/rotation cases */
+ scaled = fz_scale_pixmap(image, ctm->f, ctm->e, ctm->b, ctm->c);
+ if (scaled == NULL)
+ return NULL;
+ ctm->b = scaled->w;
+ ctm->c = scaled->h;
+ ctm->f = scaled->x;
+ ctm->e = scaled->y;
+ return scaled;
+ }
+ /* Downscale, non rectilinear case */
+ if ((dx > 0) && (dy > 0))
+ {
+ scaled = fz_scale_pixmap(image, 0, 0, (float)dx, (float)dy);
+ return scaled;
+ }
+ return NULL;
+}
+
+static void
+fz_draw_fill_image(void *user, fz_pixmap *image, fz_matrix ctm, float alpha)
+{
+ fz_draw_device *dev = user;
+ fz_colorspace *model = dev->dest->colorspace;
+ fz_pixmap *converted = NULL;
+ fz_pixmap *scaled = NULL;
+ int after;
+ int dx, dy;
+
+ if (!model)
+ {
+ fz_warn("cannot render image directly to an alpha mask");
+ return;
+ }
+
+ if (image->w == 0 || image->h == 0)
+ return;
+
+ /* convert images with more components (cmyk->rgb) before scaling */
+ /* convert images with fewer components (gray->rgb after scaling */
+ /* convert images with expensive colorspace transforms after scaling */
+
+ after = 0;
+ if (image->colorspace == fz_device_gray)
+ after = 1;
+
+ if (image->colorspace != model && !after)
+ {
+ converted = fz_new_pixmap(model, image->x, image->y, image->w, image->h);
+ fz_convert_pixmap(image, converted);
+ image = converted;
+ }
+
+ dx = sqrtf(ctm.a * ctm.a + ctm.b * ctm.b);
+ dy = sqrtf(ctm.c * ctm.c + ctm.d * ctm.d);
+ if (dx < image->w && dy < image->h)
+ {
+ scaled = fz_transform_pixmap(image, &ctm, dev->dest->x, dev->dest->y, dx, dy);
+ if (scaled == NULL)
+ {
+ if (dx < 1)
+ dx = 1;
+ if (dy < 1)
+ dy = 1;
+ scaled = fz_scale_pixmap(image, image->x, image->y, dx, dy);
+ }
+ if (scaled != NULL)
+ image = scaled;
+ }
+
+ if (image->colorspace != model && after)
+ {
+ converted = fz_new_pixmap(model, image->x, image->y, image->w, image->h);
+ fz_convert_pixmap(image, converted);
+ image = converted;
+ }
+
+ fz_paint_image(dev->dest, dev->scissor, image, ctm, alpha * 255);
+
+ if (scaled)
+ fz_drop_pixmap(scaled);
+ if (converted)
+ fz_drop_pixmap(converted);
+}
+
+static void
+fz_draw_fill_image_mask(void *user, fz_pixmap *image, fz_matrix ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_draw_device *dev = user;
+ fz_colorspace *model = dev->dest->colorspace;
+ unsigned char colorbv[FZ_MAX_COLORS + 1];
+ float colorfv[FZ_MAX_COLORS];
+ fz_pixmap *scaled = NULL;
+ int dx, dy;
+ int i;
+
+ if (image->w == 0 || image->h == 0)
+ return;
+
+ dx = sqrtf(ctm.a * ctm.a + ctm.b * ctm.b);
+ dy = sqrtf(ctm.c * ctm.c + ctm.d * ctm.d);
+ if (dx < image->w && dy < image->h)
+ {
+ scaled = fz_transform_pixmap(image, &ctm, dev->dest->x, dev->dest->y, dx, dy);
+ if (scaled == NULL)
+ {
+ if (dx < 1)
+ dx = 1;
+ if (dy < 1)
+ dy = 1;
+ scaled = fz_scale_pixmap(image, image->x, image->y, dx, dy);
+ }
+ if (scaled != NULL)
+ image = scaled;
+ }
+
+ fz_convert_color(colorspace, color, model, colorfv);
+ for (i = 0; i < model->n; i++)
+ colorbv[i] = colorfv[i] * 255;
+ colorbv[i] = alpha * 255;
+
+ fz_paint_image_with_color(dev->dest, dev->scissor, image, ctm, colorbv);
+
+ if (scaled)
+ fz_drop_pixmap(scaled);
+}
+
+static void
+fz_draw_clip_image_mask(void *user, fz_pixmap *image, fz_matrix ctm)
+{
+ fz_draw_device *dev = user;
+ fz_colorspace *model = dev->dest->colorspace;
+ fz_bbox bbox;
+ fz_pixmap *mask, *dest;
+ fz_pixmap *scaled = NULL;
+ int dx, dy;
+
+ if (dev->top == STACK_SIZE)
+ {
+ fz_warn("assert: too many buffers on stack");
+ return;
+ }
+
+ if (image->w == 0 || image->h == 0)
+ {
+ dev->stack[dev->top].scissor = dev->scissor;
+ dev->stack[dev->top].mask = NULL;
+ dev->stack[dev->top].dest = NULL;
+ dev->scissor = fz_empty_bbox;
+ dev->top++;
+ return;
+ }
+
+ bbox = fz_round_rect(fz_transform_rect(ctm, fz_unit_rect));
+ bbox = fz_intersect_bbox(bbox, dev->scissor);
+
+ mask = fz_new_pixmap_with_rect(NULL, bbox);
+ dest = fz_new_pixmap_with_rect(model, bbox);
+
+ fz_clear_pixmap(mask);
+ fz_clear_pixmap(dest);
+
+ dx = sqrtf(ctm.a * ctm.a + ctm.b * ctm.b);
+ dy = sqrtf(ctm.c * ctm.c + ctm.d * ctm.d);
+ if (dx < image->w && dy < image->h)
+ {
+ scaled = fz_transform_pixmap(image, &ctm, dev->dest->x, dev->dest->y, dx, dy);
+ if (scaled == NULL)
+ {
+ if (dx < 1)
+ dx = 1;
+ if (dy < 1)
+ dy = 1;
+ scaled = fz_scale_pixmap(image, image->x, image->y, dx, dy);
+ }
+ if (scaled != NULL)
+ image = scaled;
+ }
+
+ fz_paint_image(mask, bbox, image, ctm, 255);
+
+ if (scaled)
+ fz_drop_pixmap(scaled);
+
+ dev->stack[dev->top].scissor = dev->scissor;
+ dev->stack[dev->top].mask = mask;
+ dev->stack[dev->top].dest = dev->dest;
+ dev->scissor = bbox;
+ dev->dest = dest;
+ dev->top++;
+}
+
+static void
+fz_draw_pop_clip(void *user)
+{
+ fz_draw_device *dev = user;
+ fz_pixmap *mask, *dest;
+ if (dev->top > 0)
+ {
+ dev->top--;
+ dev->scissor = dev->stack[dev->top].scissor;
+ mask = dev->stack[dev->top].mask;
+ dest = dev->stack[dev->top].dest;
+ if (mask && dest)
+ {
+ fz_pixmap *scratch = dev->dest;
+ fz_paint_pixmap_with_mask(dest, scratch, mask);
+ fz_drop_pixmap(mask);
+ fz_drop_pixmap(scratch);
+ dev->dest = dest;
+ }
+ }
+}
+
+static void
+fz_draw_begin_mask(void *user, fz_rect rect, int luminosity, fz_colorspace *colorspace, float *colorfv)
+{
+ fz_draw_device *dev = user;
+ fz_pixmap *dest;
+ fz_bbox bbox;
+
+ if (dev->top == STACK_SIZE)
+ {
+ fz_warn("assert: too many buffers on stack");
+ return;
+ }
+
+ bbox = fz_round_rect(rect);
+ bbox = fz_intersect_bbox(bbox, dev->scissor);
+ dest = fz_new_pixmap_with_rect(fz_device_gray, bbox);
+
+ if (luminosity)
+ {
+ float bc;
+ if (!colorspace)
+ colorspace = fz_device_gray;
+ fz_convert_color(colorspace, colorfv, fz_device_gray, &bc);
+ fz_clear_pixmap_with_color(dest, bc * 255);
+ }
+ else
+ fz_clear_pixmap(dest);
+
+ dev->stack[dev->top].scissor = dev->scissor;
+ dev->stack[dev->top].dest = dev->dest;
+ dev->stack[dev->top].luminosity = luminosity;
+ dev->top++;
+
+ dev->scissor = bbox;
+ dev->dest = dest;
+}
+
+static void
+fz_draw_end_mask(void *user)
+{
+ fz_draw_device *dev = user;
+ fz_pixmap *mask = dev->dest;
+ fz_pixmap *temp, *dest;
+ fz_bbox bbox;
+ int luminosity;
+
+ if (dev->top == STACK_SIZE)
+ {
+ fz_warn("assert: too many buffers on stack");
+ return;
+ }
+
+ if (dev->top > 0)
+ {
+ /* pop soft mask buffer */
+ dev->top--;
+ luminosity = dev->stack[dev->top].luminosity;
+ dev->scissor = dev->stack[dev->top].scissor;
+ dev->dest = dev->stack[dev->top].dest;
+
+ /* convert to alpha mask */
+ temp = fz_alpha_from_gray(mask, luminosity);
+ fz_drop_pixmap(mask);
+
+ /* create new dest scratch buffer */
+ bbox = fz_bound_pixmap(temp);
+ dest = fz_new_pixmap_with_rect(dev->dest->colorspace, bbox);
+ fz_clear_pixmap(dest);
+
+ /* push soft mask as clip mask */
+ dev->stack[dev->top].scissor = dev->scissor;
+ dev->stack[dev->top].mask = temp;
+ dev->stack[dev->top].dest = dev->dest;
+ dev->scissor = bbox;
+ dev->dest = dest;
+ dev->top++;
+ }
+}
+
+static void
+fz_draw_begin_group(void *user, fz_rect rect, int isolated, int knockout, fz_blendmode blendmode, float alpha)
+{
+ fz_draw_device *dev = user;
+ fz_colorspace *model = dev->dest->colorspace;
+ fz_bbox bbox;
+ fz_pixmap *dest;
+
+ if (dev->top == STACK_SIZE)
+ {
+ fz_warn("assert: too many buffers on stack");
+ return;
+ }
+
+ bbox = fz_round_rect(rect);
+ bbox = fz_intersect_bbox(bbox, dev->scissor);
+ dest = fz_new_pixmap_with_rect(model, bbox);
+
+ fz_clear_pixmap(dest);
+
+ dev->stack[dev->top].alpha = alpha;
+ dev->stack[dev->top].blendmode = blendmode;
+ dev->stack[dev->top].scissor = dev->scissor;
+ dev->stack[dev->top].dest = dev->dest;
+ dev->top++;
+
+ dev->scissor = bbox;
+ dev->dest = dest;
+}
+
+static void
+fz_draw_end_group(void *user)
+{
+ fz_draw_device *dev = user;
+ fz_pixmap *group = dev->dest;
+ fz_blendmode blendmode;
+ float alpha;
+
+ if (dev->top > 0)
+ {
+ dev->top--;
+ alpha = dev->stack[dev->top].alpha;
+ blendmode = dev->stack[dev->top].blendmode;
+ dev->dest = dev->stack[dev->top].dest;
+ dev->scissor = dev->stack[dev->top].scissor;
+
+ if (blendmode == FZ_BLEND_NORMAL)
+ fz_paint_pixmap(dev->dest, group, alpha * 255);
+ else
+ fz_blend_pixmap(dev->dest, group, alpha * 255, blendmode);
+
+ fz_drop_pixmap(group);
+ }
+}
+
+static void
+fz_draw_begin_tile(void *user, fz_rect area, fz_rect view, float xstep, float ystep, fz_matrix ctm)
+{
+ fz_draw_device *dev = user;
+ fz_colorspace *model = dev->dest->colorspace;
+ fz_pixmap *dest;
+ fz_bbox bbox;
+
+ /* area, view, xstep, ystep are in pattern space */
+ /* ctm maps from pattern space to device space */
+
+ if (dev->top == STACK_SIZE)
+ {
+ fz_warn("assert: too many buffers on stack");
+ return;
+ }
+
+ bbox = fz_round_rect(fz_transform_rect(ctm, view));
+ dest = fz_new_pixmap_with_rect(model, bbox);
+ fz_clear_pixmap(dest);
+
+ dev->stack[dev->top].scissor = dev->scissor;
+ dev->stack[dev->top].dest = dev->dest;
+ dev->stack[dev->top].xstep = xstep;
+ dev->stack[dev->top].ystep = ystep;
+ dev->stack[dev->top].area = area;
+ dev->stack[dev->top].ctm = ctm;
+ dev->top++;
+
+ dev->scissor = bbox;
+ dev->dest = dest;
+}
+
+static void
+fz_draw_end_tile(void *user)
+{
+ fz_draw_device *dev = user;
+ fz_pixmap *tile = dev->dest;
+ float xstep, ystep;
+ fz_matrix ctm, ttm;
+ fz_rect area;
+ int x0, y0, x1, y1, x, y;
+
+ if (dev->top > 0)
+ {
+ dev->top--;
+ xstep = dev->stack[dev->top].xstep;
+ ystep = dev->stack[dev->top].ystep;
+ area = dev->stack[dev->top].area;
+ ctm = dev->stack[dev->top].ctm;
+ dev->scissor = dev->stack[dev->top].scissor;
+ dev->dest = dev->stack[dev->top].dest;
+
+ x0 = floorf(area.x0 / xstep);
+ y0 = floorf(area.y0 / ystep);
+ x1 = ceilf(area.x1 / xstep);
+ y1 = ceilf(area.y1 / ystep);
+
+ ctm.e = tile->x;
+ ctm.f = tile->y;
+
+ for (y = y0; y < y1; y++)
+ {
+ for (x = x0; x < x1; x++)
+ {
+ ttm = fz_concat(fz_translate(x * xstep, y * ystep), ctm);
+ tile->x = ttm.e;
+ tile->y = ttm.f;
+ fz_paint_pixmap_with_rect(dev->dest, tile, 255, dev->scissor);
+ }
+ }
+
+ fz_drop_pixmap(tile);
+ }
+}
+
+static void
+fz_draw_free_user(void *user)
+{
+ fz_draw_device *dev = user;
+ /* TODO: pop and free the stacks */
+ fz_free_gel(dev->gel);
+ fz_free_ael(dev->ael);
+ fz_free(dev);
+}
+
+fz_device *
+fz_new_draw_device(fz_glyph_cache *cache, fz_pixmap *dest)
+{
+ fz_device *dev;
+ fz_draw_device *ddev = fz_malloc(sizeof(fz_draw_device));
+ ddev->cache = cache;
+ ddev->gel = fz_new_gel();
+ ddev->ael = fz_new_ael();
+ ddev->dest = dest;
+ ddev->top = 0;
+
+ ddev->scissor.x0 = dest->x;
+ ddev->scissor.y0 = dest->y;
+ ddev->scissor.x1 = dest->x + dest->w;
+ ddev->scissor.y1 = dest->y + dest->h;
+
+ dev = fz_new_device(ddev);
+ dev->free_user = fz_draw_free_user;
+
+ dev->fill_path = fz_draw_fill_path;
+ dev->stroke_path = fz_draw_stroke_path;
+ dev->clip_path = fz_draw_clip_path;
+ dev->clip_stroke_path = fz_draw_clip_stroke_path;
+
+ dev->fill_text = fz_draw_fill_text;
+ dev->stroke_text = fz_draw_stroke_text;
+ dev->clip_text = fz_draw_clip_text;
+ dev->clip_stroke_text = fz_draw_clip_stroke_text;
+ dev->ignore_text = fz_draw_ignore_text;
+
+ dev->fill_image_mask = fz_draw_fill_image_mask;
+ dev->clip_image_mask = fz_draw_clip_image_mask;
+ dev->fill_image = fz_draw_fill_image;
+ dev->fill_shade = fz_draw_fill_shade;
+
+ dev->pop_clip = fz_draw_pop_clip;
+
+ dev->begin_mask = fz_draw_begin_mask;
+ dev->end_mask = fz_draw_end_mask;
+ dev->begin_group = fz_draw_begin_group;
+ dev->end_group = fz_draw_end_group;
+
+ dev->begin_tile = fz_draw_begin_tile;
+ dev->end_tile = fz_draw_end_tile;
+
+ return dev;
+}