1583 lines
38 KiB
C
1583 lines
38 KiB
C
#include "fitz.h"
|
|
|
|
#define QUANT(x,a) (((int)((x) * (a))) / (a))
|
|
#define HSUBPIX 5.0
|
|
#define VSUBPIX 5.0
|
|
|
|
#define STACK_SIZE 96
|
|
|
|
/* Enable the following to attempt to support knockout and/or isolated
|
|
* blending groups. This code is known to give incorrect results currently
|
|
* so disabled by default. See bug 692377. */
|
|
#undef ATTEMPT_KNOCKOUT_AND_ISOLATED
|
|
|
|
/* Enable the following to help debug group blending. */
|
|
#undef DUMP_GROUP_BLENDS
|
|
|
|
/* Note #1: At various points in this code (notably when clipping with non
|
|
* rectangular masks), we create a new (empty) destination pixmap. We then
|
|
* render this pixmap, then plot it back into the original destination
|
|
* through a mask. This works well for normal blending, but falls down for
|
|
* non-zero blending modes; effectively we are forcing ourselves to use an
|
|
* isolated group.
|
|
*
|
|
* The fix for this would be to copy the contents from the underlying dest
|
|
* into the newly created dest. This would enable us to use a non
|
|
* FZ_BLEND_ISOLATED blendmode. Unfortunately, tt would break tiling, as
|
|
* we could no longer render once and blend back multiple times.
|
|
*/
|
|
|
|
|
|
typedef struct fz_draw_device_s fz_draw_device;
|
|
|
|
enum {
|
|
FZ_DRAWDEV_FLAGS_TYPE3 = 1,
|
|
};
|
|
|
|
struct fz_draw_device_s
|
|
{
|
|
fz_glyph_cache *cache;
|
|
fz_gel *gel;
|
|
|
|
fz_pixmap *dest;
|
|
fz_pixmap *shape;
|
|
fz_bbox scissor;
|
|
|
|
int flags;
|
|
int top;
|
|
int blendmode;
|
|
struct {
|
|
fz_bbox scissor;
|
|
fz_pixmap *dest;
|
|
fz_pixmap *mask;
|
|
fz_pixmap *shape;
|
|
int blendmode;
|
|
int luminosity;
|
|
float alpha;
|
|
fz_matrix ctm;
|
|
float xstep, ystep;
|
|
fz_rect area;
|
|
} stack[STACK_SIZE];
|
|
};
|
|
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
static int group_dump_count = 0;
|
|
|
|
static void fz_dump_blend(fz_pixmap *pix, const char *s)
|
|
{
|
|
char name[80];
|
|
|
|
if (pix == NULL)
|
|
return;
|
|
|
|
sprintf(name, "dump%02d.png", group_dump_count);
|
|
if (s)
|
|
printf("%s%02d", s, group_dump_count);
|
|
group_dump_count++;
|
|
|
|
fz_write_png(pix, name, (pix->n > 1));
|
|
}
|
|
|
|
static void dump_spaces(int x, const char *s)
|
|
{
|
|
int i;
|
|
for (i = 0; i < x; i++)
|
|
printf(" ");
|
|
printf("%s", s);
|
|
}
|
|
|
|
#endif
|
|
|
|
static void fz_knockout_begin(void *user)
|
|
{
|
|
fz_draw_device *dev = user;
|
|
fz_bbox bbox;
|
|
fz_pixmap *dest, *shape;
|
|
int isolated = dev->blendmode & FZ_BLEND_ISOLATED;
|
|
|
|
if ((dev->blendmode & FZ_BLEND_KNOCKOUT) == 0)
|
|
return;
|
|
|
|
if (dev->top == STACK_SIZE)
|
|
{
|
|
fz_warn("assert: too many buffers on stack");
|
|
return;
|
|
}
|
|
|
|
bbox = fz_bound_pixmap(dev->dest);
|
|
bbox = fz_intersect_bbox(bbox, dev->scissor);
|
|
dest = fz_new_pixmap_with_rect(dev->dest->colorspace, bbox);
|
|
|
|
if (isolated)
|
|
{
|
|
fz_clear_pixmap(dest);
|
|
}
|
|
else
|
|
{
|
|
fz_pixmap *prev;
|
|
int i = dev->top;
|
|
do
|
|
prev = dev->stack[--i].dest;
|
|
while (prev == NULL);
|
|
fz_copy_pixmap_rect(dest, prev, bbox);
|
|
}
|
|
|
|
if (dev->blendmode == 0 && isolated)
|
|
{
|
|
/* We can render direct to any existing shape plane. If there
|
|
* isn't one, we don't need to make one. */
|
|
shape = dev->shape;
|
|
}
|
|
else
|
|
{
|
|
shape = fz_new_pixmap_with_rect(NULL, bbox);
|
|
fz_clear_pixmap(shape);
|
|
}
|
|
dev->stack[dev->top].blendmode = dev->blendmode;
|
|
dev->stack[dev->top].scissor = dev->scissor;
|
|
dev->stack[dev->top].dest = dev->dest;
|
|
dev->stack[dev->top].shape = dev->shape;
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
dump_spaces(dev->top, "Knockout begin\n");
|
|
#endif
|
|
dev->top++;
|
|
|
|
dev->scissor = bbox;
|
|
dev->dest = dest;
|
|
dev->shape = shape;
|
|
dev->blendmode &= ~FZ_BLEND_MODEMASK;
|
|
}
|
|
|
|
static void fz_knockout_end(void *user)
|
|
{
|
|
fz_draw_device *dev = user;
|
|
fz_pixmap *group = dev->dest;
|
|
fz_pixmap *shape = dev->shape;
|
|
int blendmode;
|
|
int isolated;
|
|
|
|
if ((dev->blendmode & FZ_BLEND_KNOCKOUT) == 0)
|
|
return;
|
|
|
|
if (dev->top == STACK_SIZE)
|
|
{
|
|
fz_warn("assert: too many buffers on stack");
|
|
return;
|
|
}
|
|
|
|
if (dev->top > 0)
|
|
{
|
|
dev->top--;
|
|
blendmode = dev->blendmode & FZ_BLEND_MODEMASK;
|
|
isolated = dev->blendmode & FZ_BLEND_ISOLATED;
|
|
dev->blendmode = dev->stack[dev->top].blendmode;
|
|
dev->shape = dev->stack[dev->top].shape;
|
|
dev->dest = dev->stack[dev->top].dest;
|
|
dev->scissor = dev->stack[dev->top].scissor;
|
|
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
dump_spaces(dev->top, "");
|
|
fz_dump_blend(group, "Blending ");
|
|
if (shape)
|
|
fz_dump_blend(shape, "/");
|
|
fz_dump_blend(dev->dest, " onto ");
|
|
if (dev->shape)
|
|
fz_dump_blend(dev->shape, "/");
|
|
if (blendmode != 0)
|
|
printf(" (blend %d)", blendmode);
|
|
if (isolated != 0)
|
|
printf(" (isolated)");
|
|
printf(" (knockout)");
|
|
#endif
|
|
if ((blendmode == 0) && (shape == NULL))
|
|
fz_paint_pixmap(dev->dest, group, 255);
|
|
else
|
|
fz_blend_pixmap(dev->dest, group, 255, blendmode, isolated, shape);
|
|
|
|
fz_drop_pixmap(group);
|
|
if (shape != dev->shape)
|
|
{
|
|
if (dev->shape)
|
|
{
|
|
fz_paint_pixmap(dev->shape, shape, 255);
|
|
}
|
|
fz_drop_pixmap(shape);
|
|
}
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
fz_dump_blend(dev->dest, " to get ");
|
|
if (dev->shape)
|
|
fz_dump_blend(dev->shape, "/");
|
|
printf("\n");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
if (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_begin(dev);
|
|
|
|
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, even_odd, bbox, dev->dest, colorbv);
|
|
if (dev->shape)
|
|
{
|
|
fz_reset_gel(dev->gel, dev->scissor);
|
|
fz_flatten_fill_path(dev->gel, path, ctm, flatness);
|
|
fz_sort_gel(dev->gel);
|
|
|
|
colorbv[0] = alpha * 255;
|
|
fz_scan_convert(dev->gel, even_odd, bbox, dev->shape, colorbv);
|
|
}
|
|
|
|
if (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_end(dev);
|
|
}
|
|
|
|
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;
|
|
|
|
if (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_begin(dev);
|
|
|
|
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, 0, bbox, dev->dest, colorbv);
|
|
if (dev->shape)
|
|
{
|
|
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);
|
|
|
|
colorbv[0] = 255;
|
|
fz_scan_convert(dev->gel, 0, bbox, dev->shape, colorbv);
|
|
}
|
|
|
|
if (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_end(dev);
|
|
}
|
|
|
|
static void
|
|
fz_draw_clip_path(void *user, fz_path *path, fz_rect *rect, 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, *shape;
|
|
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 (rect)
|
|
bbox = fz_intersect_bbox(bbox, fz_round_rect(*rect));
|
|
|
|
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->stack[dev->top].shape = dev->shape;
|
|
dev->stack[dev->top].blendmode = dev->blendmode;
|
|
dev->scissor = bbox;
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
dump_spaces(dev->top, "Clip (rectangular) begin\n");
|
|
#endif
|
|
dev->top++;
|
|
return;
|
|
}
|
|
|
|
mask = fz_new_pixmap_with_rect(NULL, bbox);
|
|
fz_clear_pixmap(mask);
|
|
dest = fz_new_pixmap_with_rect(model, bbox);
|
|
/* FIXME: See note #1 */
|
|
fz_clear_pixmap(dest);
|
|
if (dev->shape)
|
|
{
|
|
shape = fz_new_pixmap_with_rect(NULL, bbox);
|
|
fz_clear_pixmap(shape);
|
|
}
|
|
else
|
|
shape = NULL;
|
|
|
|
fz_scan_convert(dev->gel, 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->stack[dev->top].shape = dev->shape;
|
|
/* FIXME: See note #1 */
|
|
dev->stack[dev->top].blendmode = dev->blendmode | FZ_BLEND_ISOLATED;
|
|
dev->scissor = bbox;
|
|
dev->dest = dest;
|
|
dev->shape = shape;
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
dump_spaces(dev->top, "Clip (non-rectangular) begin\n");
|
|
#endif
|
|
dev->top++;
|
|
}
|
|
|
|
static void
|
|
fz_draw_clip_stroke_path(void *user, fz_path *path, fz_rect *rect, 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, *shape;
|
|
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);
|
|
if (rect)
|
|
bbox = fz_intersect_bbox(bbox, fz_round_rect(*rect));
|
|
|
|
mask = fz_new_pixmap_with_rect(NULL, bbox);
|
|
fz_clear_pixmap(mask);
|
|
dest = fz_new_pixmap_with_rect(model, bbox);
|
|
/* FIXME: See note #1 */
|
|
fz_clear_pixmap(dest);
|
|
if (dev->shape)
|
|
{
|
|
shape = fz_new_pixmap_with_rect(NULL, bbox);
|
|
fz_clear_pixmap(shape);
|
|
}
|
|
else
|
|
shape = NULL;
|
|
|
|
if (!fz_is_empty_rect(bbox))
|
|
fz_scan_convert(dev->gel, 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->stack[dev->top].shape = dev->shape;
|
|
/* FIXME: See note #1 */
|
|
dev->stack[dev->top].blendmode = dev->blendmode | FZ_BLEND_ISOLATED;
|
|
dev->scissor = bbox;
|
|
dev->dest = dest;
|
|
dev->shape = shape;
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
dump_spaces(dev->top, "Clip (stroke) begin\n");
|
|
#endif
|
|
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];
|
|
unsigned char shapebv;
|
|
float colorfv[FZ_MAX_COLORS];
|
|
fz_matrix tm, trm;
|
|
fz_pixmap *glyph;
|
|
int i, x, y, gid;
|
|
|
|
if (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_begin(dev);
|
|
|
|
fz_convert_color(colorspace, color, model, colorfv);
|
|
for (i = 0; i < model->n; i++)
|
|
colorbv[i] = colorfv[i] * 255;
|
|
colorbv[i] = alpha * 255;
|
|
shapebv = 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, model);
|
|
if (glyph)
|
|
{
|
|
if (glyph->n == 1)
|
|
{
|
|
draw_glyph(colorbv, dev->dest, glyph, x, y, dev->scissor);
|
|
if (dev->shape)
|
|
draw_glyph(&shapebv, dev->shape, glyph, x, y, dev->scissor);
|
|
}
|
|
else
|
|
{
|
|
fz_matrix ctm = {glyph->w, 0.0, 0.0, -glyph->h, x + glyph->x, y + glyph->y + glyph->h};
|
|
fz_paint_image(dev->dest, dev->scissor, dev->shape, glyph, ctm, alpha * 255);
|
|
}
|
|
fz_drop_pixmap(glyph);
|
|
}
|
|
}
|
|
|
|
if (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_end(dev);
|
|
}
|
|
|
|
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;
|
|
|
|
if (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_begin(dev);
|
|
|
|
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);
|
|
if (dev->shape)
|
|
draw_glyph(colorbv, dev->shape, glyph, x, y, dev->scissor);
|
|
fz_drop_pixmap(glyph);
|
|
}
|
|
}
|
|
|
|
if (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_end(dev);
|
|
}
|
|
|
|
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, *shape;
|
|
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);
|
|
fz_clear_pixmap(mask);
|
|
dest = fz_new_pixmap_with_rect(model, bbox);
|
|
/* FIXME: See note #1 */
|
|
fz_clear_pixmap(dest);
|
|
if (dev->shape)
|
|
{
|
|
shape = fz_new_pixmap_with_rect(NULL, bbox);
|
|
fz_clear_pixmap(shape);
|
|
}
|
|
else
|
|
shape = NULL;
|
|
|
|
dev->stack[dev->top].scissor = dev->scissor;
|
|
dev->stack[dev->top].mask = mask;
|
|
dev->stack[dev->top].dest = dev->dest;
|
|
dev->stack[dev->top].shape = dev->shape;
|
|
/* FIXME: See note #1 */
|
|
dev->stack[dev->top].blendmode = dev->blendmode | FZ_BLEND_ISOLATED;
|
|
dev->scissor = bbox;
|
|
dev->dest = dest;
|
|
dev->shape = shape;
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
dump_spaces(dev->top, "Clip (text) begin\n");
|
|
#endif
|
|
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, model);
|
|
if (glyph)
|
|
{
|
|
draw_glyph(NULL, mask, glyph, x, y, bbox);
|
|
if (dev->shape)
|
|
draw_glyph(NULL, dev->shape, 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, *shape;
|
|
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);
|
|
fz_clear_pixmap(mask);
|
|
dest = fz_new_pixmap_with_rect(model, bbox);
|
|
/* FIXME: See note #1 */
|
|
fz_clear_pixmap(dest);
|
|
if (dev->shape)
|
|
{
|
|
shape = fz_new_pixmap_with_rect(NULL, bbox);
|
|
fz_clear_pixmap(shape);
|
|
}
|
|
else
|
|
shape = dev->shape;
|
|
|
|
dev->stack[dev->top].scissor = dev->scissor;
|
|
dev->stack[dev->top].mask = mask;
|
|
dev->stack[dev->top].dest = dev->dest;
|
|
dev->stack[dev->top].shape = dev->shape;
|
|
/* FIXME: See note #1 */
|
|
dev->stack[dev->top].blendmode = dev->blendmode | FZ_BLEND_ISOLATED;
|
|
dev->scissor = bbox;
|
|
dev->dest = dest;
|
|
dev->shape = shape;
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
dump_spaces(dev->top, "Clip (stroke text) begin\n");
|
|
#endif
|
|
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);
|
|
if (dev->shape)
|
|
draw_glyph(NULL, dev->shape, 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 (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_begin(dev);
|
|
|
|
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];
|
|
}
|
|
}
|
|
if (dev->shape)
|
|
{
|
|
for (y = scissor.y0; y < scissor.y1; y++)
|
|
{
|
|
s = dev->shape->samples + (scissor.x0 - dev->shape->x) + (y - dev->shape->y) * dev->shape->w;
|
|
for (x = scissor.x0; x < scissor.x1; x++)
|
|
{
|
|
*s++ = 255;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fz_paint_shade(shade, ctm, dest, bbox);
|
|
if (dev->shape)
|
|
fz_clear_pixmap_rect_with_color(dev->shape, 255, bbox);
|
|
|
|
if (alpha < 1)
|
|
{
|
|
fz_paint_pixmap(dev->dest, dest, alpha * 255);
|
|
fz_drop_pixmap(dest);
|
|
}
|
|
|
|
if (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_end(dev);
|
|
}
|
|
|
|
static fz_pixmap *
|
|
fz_transform_pixmap(fz_pixmap *image, fz_matrix *ctm, int x, int y, int dx, int dy, int gridfit)
|
|
{
|
|
fz_pixmap *scaled;
|
|
|
|
if (ctm->a != 0 && ctm->b == 0 && ctm->c == 0 && ctm->d != 0)
|
|
{
|
|
/* Unrotated or X-flip or Y-flip or XY-flip */
|
|
scaled = fz_scale_pixmap_gridfit(image, ctm->e, ctm->f, ctm->a, ctm->d, gridfit);
|
|
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_gridfit(image, ctm->f, ctm->e, ctm->b, ctm->c, gridfit);
|
|
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 */
|
|
|
|
if (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_begin(dev);
|
|
|
|
after = 0;
|
|
if (image->colorspace == fz_device_gray)
|
|
after = 1;
|
|
|
|
if (image->colorspace != model && !after)
|
|
{
|
|
converted = fz_new_pixmap_with_rect(model, fz_bound_pixmap(image));
|
|
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)
|
|
{
|
|
int gridfit = alpha == 1.0f && !(dev->flags & FZ_DRAWDEV_FLAGS_TYPE3);
|
|
scaled = fz_transform_pixmap(image, &ctm, dev->dest->x, dev->dest->y, dx, dy, gridfit);
|
|
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)
|
|
{
|
|
if ((image->colorspace == fz_device_gray && model == fz_device_rgb) ||
|
|
(image->colorspace == fz_device_gray && model == fz_device_bgr))
|
|
{
|
|
/* We have special case rendering code for gray -> rgb/bgr */
|
|
}
|
|
else
|
|
{
|
|
converted = fz_new_pixmap_with_rect(model, fz_bound_pixmap(image));
|
|
fz_convert_pixmap(image, converted);
|
|
image = converted;
|
|
}
|
|
}
|
|
|
|
fz_paint_image(dev->dest, dev->scissor, dev->shape, image, ctm, alpha * 255);
|
|
|
|
if (scaled)
|
|
fz_drop_pixmap(scaled);
|
|
if (converted)
|
|
fz_drop_pixmap(converted);
|
|
|
|
if (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_end(dev);
|
|
}
|
|
|
|
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;
|
|
|
|
if (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_begin(dev);
|
|
|
|
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)
|
|
{
|
|
int gridfit = alpha == 1.0f && !(dev->flags & FZ_DRAWDEV_FLAGS_TYPE3);
|
|
scaled = fz_transform_pixmap(image, &ctm, dev->dest->x, dev->dest->y, dx, dy, gridfit);
|
|
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, dev->shape, image, ctm, colorbv);
|
|
|
|
if (scaled)
|
|
fz_drop_pixmap(scaled);
|
|
|
|
if (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_begin(dev);
|
|
}
|
|
|
|
static void
|
|
fz_draw_clip_image_mask(void *user, fz_pixmap *image, fz_rect *rect, fz_matrix ctm)
|
|
{
|
|
fz_draw_device *dev = user;
|
|
fz_colorspace *model = dev->dest->colorspace;
|
|
fz_bbox bbox;
|
|
fz_pixmap *mask, *dest, *shape;
|
|
fz_pixmap *scaled = NULL;
|
|
int dx, dy;
|
|
|
|
if (dev->top == STACK_SIZE)
|
|
{
|
|
fz_warn("assert: too many buffers on stack");
|
|
return;
|
|
}
|
|
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
dump_spaces(dev->top, "Clip (image mask) begin\n");
|
|
#endif
|
|
|
|
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->stack[dev->top].blendmode = dev->blendmode;
|
|
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);
|
|
if (rect)
|
|
bbox = fz_intersect_bbox(bbox, fz_round_rect(*rect));
|
|
|
|
mask = fz_new_pixmap_with_rect(NULL, bbox);
|
|
fz_clear_pixmap(mask);
|
|
dest = fz_new_pixmap_with_rect(model, bbox);
|
|
/* FIXME: See note #1 */
|
|
fz_clear_pixmap(dest);
|
|
if (dev->shape)
|
|
{
|
|
shape = fz_new_pixmap_with_rect(NULL, bbox);
|
|
fz_clear_pixmap(shape);
|
|
}
|
|
else
|
|
shape = NULL;
|
|
|
|
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)
|
|
{
|
|
int gridfit = !(dev->flags & FZ_DRAWDEV_FLAGS_TYPE3);
|
|
scaled = fz_transform_pixmap(image, &ctm, dev->dest->x, dev->dest->y, dx, dy, gridfit);
|
|
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, dev->shape, 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->stack[dev->top].shape = dev->shape;
|
|
/* FIXME: See note #1 */
|
|
dev->stack[dev->top].blendmode = dev->blendmode | FZ_BLEND_ISOLATED;
|
|
dev->scissor = bbox;
|
|
dev->dest = dest;
|
|
dev->shape = shape;
|
|
dev->top++;
|
|
}
|
|
|
|
static void
|
|
fz_draw_pop_clip(void *user)
|
|
{
|
|
fz_draw_device *dev = user;
|
|
fz_pixmap *mask, *dest, *shape;
|
|
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;
|
|
shape = dev->stack[dev->top].shape;
|
|
dev->blendmode = dev->stack[dev->top].blendmode;
|
|
|
|
/* We can get here with mask == NULL if the clipping actually
|
|
* resolved to a rectangle earlier. In this case, we will
|
|
* have a dest, and the shape will be unchanged.
|
|
*/
|
|
if (mask)
|
|
{
|
|
assert(dest);
|
|
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
dump_spaces(dev->top, "");
|
|
fz_dump_blend(dev->dest, "Clipping ");
|
|
if (dev->shape)
|
|
fz_dump_blend(dev->shape, "/");
|
|
fz_dump_blend(dest, " onto ");
|
|
if (shape)
|
|
fz_dump_blend(shape, "/");
|
|
fz_dump_blend(mask, " with ");
|
|
#endif
|
|
fz_paint_pixmap_with_mask(dest, dev->dest, mask);
|
|
if (shape != NULL)
|
|
{
|
|
assert(shape != dev->shape);
|
|
fz_paint_pixmap_with_mask(shape, dev->shape, mask);
|
|
fz_drop_pixmap(dev->shape);
|
|
dev->shape = shape;
|
|
}
|
|
fz_drop_pixmap(mask);
|
|
fz_drop_pixmap(dev->dest);
|
|
dev->dest = dest;
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
fz_dump_blend(dev->dest, " to get ");
|
|
if (dev->shape)
|
|
fz_dump_blend(dev->shape, "/");
|
|
printf("\n");
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
dump_spaces(dev->top, "Clip End\n");
|
|
#endif
|
|
assert(dest == NULL);
|
|
assert(shape == dev->shape);
|
|
}
|
|
}
|
|
}
|
|
|
|
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_pixmap *shape = dev->shape;
|
|
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 (dev->shape)
|
|
{
|
|
/* FIXME: If we ever want to support AIS true, then we
|
|
* probably want to create a shape pixmap here, using:
|
|
* shape = fz_new_pixmap_with_rect(NULL, bbox);
|
|
* then, in the end_mask code, we create the mask from this
|
|
* rather than dest.
|
|
*/
|
|
shape = NULL;
|
|
}
|
|
|
|
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);
|
|
if (shape)
|
|
fz_clear_pixmap_with_color(shape, 255);
|
|
}
|
|
else
|
|
{
|
|
fz_clear_pixmap(dest);
|
|
if (shape)
|
|
fz_clear_pixmap(shape);
|
|
}
|
|
|
|
dev->stack[dev->top].scissor = dev->scissor;
|
|
dev->stack[dev->top].dest = dev->dest;
|
|
dev->stack[dev->top].luminosity = luminosity;
|
|
dev->stack[dev->top].shape = dev->shape;
|
|
dev->stack[dev->top].blendmode = dev->blendmode;
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
dump_spaces(dev->top, "Mask begin\n");
|
|
#endif
|
|
dev->top++;
|
|
|
|
dev->scissor = bbox;
|
|
dev->dest = dest;
|
|
dev->shape = shape;
|
|
}
|
|
|
|
static void
|
|
fz_draw_end_mask(void *user)
|
|
{
|
|
fz_draw_device *dev = user;
|
|
fz_pixmap *mask = dev->dest;
|
|
fz_pixmap *maskshape = dev->shape;
|
|
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;
|
|
dev->shape = dev->stack[dev->top].shape;
|
|
|
|
/* convert to alpha mask */
|
|
temp = fz_alpha_from_gray(mask, luminosity);
|
|
fz_drop_pixmap(mask);
|
|
fz_drop_pixmap(maskshape);
|
|
|
|
/* create new dest scratch buffer */
|
|
bbox = fz_bound_pixmap(temp);
|
|
dest = fz_new_pixmap_with_rect(dev->dest->colorspace, bbox);
|
|
/* FIXME: See note #1 */
|
|
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;
|
|
/* FIXME: See note #1 */
|
|
dev->stack[dev->top].blendmode = dev->blendmode | FZ_BLEND_ISOLATED;
|
|
/* If we have a shape, then it'll need to be masked with the
|
|
* clip mask when we pop. So create a new shape now. */
|
|
if (dev->shape)
|
|
{
|
|
dev->stack[dev->top].shape = dev->shape;
|
|
dev->shape = fz_new_pixmap_with_rect(NULL, bbox);
|
|
fz_clear_pixmap(dev->shape);
|
|
}
|
|
dev->scissor = bbox;
|
|
dev->dest = dest;
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
dump_spaces(dev->top, "Mask -> Clip\n");
|
|
#endif
|
|
dev->top++;
|
|
}
|
|
}
|
|
|
|
static void
|
|
fz_draw_begin_group(void *user, fz_rect rect, int isolated, int knockout, int blendmode, float alpha)
|
|
{
|
|
fz_draw_device *dev = user;
|
|
fz_colorspace *model = dev->dest->colorspace;
|
|
fz_bbox bbox;
|
|
fz_pixmap *dest, *shape;
|
|
|
|
if (dev->top == STACK_SIZE)
|
|
{
|
|
fz_warn("assert: too many buffers on stack");
|
|
return;
|
|
}
|
|
|
|
if (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_begin(dev);
|
|
|
|
bbox = fz_round_rect(rect);
|
|
bbox = fz_intersect_bbox(bbox, dev->scissor);
|
|
dest = fz_new_pixmap_with_rect(model, bbox);
|
|
|
|
#ifndef ATTEMPT_KNOCKOUT_AND_ISOLATED
|
|
knockout = 0;
|
|
isolated = 1;
|
|
#endif
|
|
|
|
if (isolated)
|
|
{
|
|
fz_clear_pixmap(dest);
|
|
}
|
|
else
|
|
{
|
|
fz_copy_pixmap_rect(dest, dev->dest, bbox);
|
|
}
|
|
|
|
if (blendmode == 0 && alpha == 1.0 && isolated)
|
|
{
|
|
/* We can render direct to any existing shape plane. If there
|
|
* isn't one, we don't need to make one. */
|
|
shape = dev->shape;
|
|
}
|
|
else
|
|
{
|
|
shape = fz_new_pixmap_with_rect(NULL, bbox);
|
|
fz_clear_pixmap(shape);
|
|
}
|
|
|
|
dev->stack[dev->top].alpha = alpha;
|
|
dev->stack[dev->top].blendmode = dev->blendmode;
|
|
dev->stack[dev->top].scissor = dev->scissor;
|
|
dev->stack[dev->top].dest = dev->dest;
|
|
dev->stack[dev->top].shape = dev->shape;
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
dump_spaces(dev->top, "Group Begin\n");
|
|
#endif
|
|
dev->top++;
|
|
|
|
dev->scissor = bbox;
|
|
dev->dest = dest;
|
|
dev->shape = shape;
|
|
dev->blendmode = blendmode | (isolated ? FZ_BLEND_ISOLATED : 0) | (knockout ? FZ_BLEND_KNOCKOUT : 0);
|
|
}
|
|
|
|
static void
|
|
fz_draw_end_group(void *user)
|
|
{
|
|
fz_draw_device *dev = user;
|
|
fz_pixmap *group = dev->dest;
|
|
fz_pixmap *shape = dev->shape;
|
|
int blendmode;
|
|
int isolated;
|
|
float alpha;
|
|
|
|
if (dev->top > 0)
|
|
{
|
|
dev->top--;
|
|
alpha = dev->stack[dev->top].alpha;
|
|
blendmode = dev->blendmode & FZ_BLEND_MODEMASK;
|
|
isolated = dev->blendmode & FZ_BLEND_ISOLATED;
|
|
dev->blendmode = dev->stack[dev->top].blendmode;
|
|
dev->shape = dev->stack[dev->top].shape;
|
|
dev->dest = dev->stack[dev->top].dest;
|
|
dev->scissor = dev->stack[dev->top].scissor;
|
|
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
dump_spaces(dev->top, "");
|
|
fz_dump_blend(group, "Blending ");
|
|
if (shape)
|
|
fz_dump_blend(shape, "/");
|
|
fz_dump_blend(dev->dest, " onto ");
|
|
if (dev->shape)
|
|
fz_dump_blend(dev->shape, "/");
|
|
if (alpha != 1.0f)
|
|
printf(" (alpha %g)", alpha);
|
|
if (blendmode != 0)
|
|
printf(" (blend %d)", blendmode);
|
|
if (isolated != 0)
|
|
printf(" (isolated)");
|
|
if (blendmode & FZ_BLEND_KNOCKOUT)
|
|
printf(" (knockout)");
|
|
#endif
|
|
if ((blendmode == 0) && (shape == NULL))
|
|
fz_paint_pixmap(dev->dest, group, alpha * 255);
|
|
else
|
|
fz_blend_pixmap(dev->dest, group, alpha * 255, blendmode, isolated, shape);
|
|
|
|
fz_drop_pixmap(group);
|
|
if (shape != dev->shape)
|
|
{
|
|
if (dev->shape)
|
|
{
|
|
fz_paint_pixmap(dev->shape, shape, alpha * 255);
|
|
}
|
|
fz_drop_pixmap(shape);
|
|
}
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
fz_dump_blend(dev->dest, " to get ");
|
|
if (dev->shape)
|
|
fz_dump_blend(dev->shape, "/");
|
|
printf("\n");
|
|
#endif
|
|
}
|
|
|
|
if (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_end(dev);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_begin(dev);
|
|
|
|
bbox = fz_round_rect(fz_transform_rect(ctm, view));
|
|
dest = fz_new_pixmap_with_rect(model, bbox);
|
|
/* FIXME: See note #1 */
|
|
fz_clear_pixmap(dest);
|
|
|
|
dev->stack[dev->top].scissor = dev->scissor;
|
|
dev->stack[dev->top].dest = dev->dest;
|
|
dev->stack[dev->top].shape = dev->shape;
|
|
/* FIXME: See note #1 */
|
|
dev->stack[dev->top].blendmode = dev->blendmode | FZ_BLEND_ISOLATED;
|
|
dev->stack[dev->top].xstep = xstep;
|
|
dev->stack[dev->top].ystep = ystep;
|
|
dev->stack[dev->top].area = area;
|
|
dev->stack[dev->top].ctm = ctm;
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
dump_spaces(dev->top, "Tile begin\n");
|
|
#endif
|
|
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--;
|
|
#ifdef DUMP_GROUP_BLENDS
|
|
dump_spaces(dev->top, "Tile end\n");
|
|
#endif
|
|
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;
|
|
dev->blendmode = dev->stack[dev->top].blendmode;
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
if (dev->blendmode & FZ_BLEND_KNOCKOUT)
|
|
fz_knockout_begin(dev);
|
|
}
|
|
|
|
static void
|
|
fz_draw_free_user(void *user)
|
|
{
|
|
fz_draw_device *dev = user;
|
|
/* TODO: pop and free the stacks */
|
|
if (dev->top > 0)
|
|
fz_warn("items left on stack in draw device: %d", dev->top);
|
|
fz_free_gel(dev->gel);
|
|
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->dest = dest;
|
|
ddev->shape = NULL;
|
|
ddev->top = 0;
|
|
ddev->blendmode = 0;
|
|
ddev->flags = 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;
|
|
}
|
|
|
|
fz_device *
|
|
fz_new_draw_device_type3(fz_glyph_cache *cache, fz_pixmap *dest)
|
|
{
|
|
fz_device *dev = fz_new_draw_device(cache, dest);
|
|
fz_draw_device *ddev = dev->user;
|
|
ddev->flags |= FZ_DRAWDEV_FLAGS_TYPE3;
|
|
return dev;
|
|
}
|