#include "fitz.h" typedef struct fz_display_node_s fz_display_node; typedef enum fz_display_command_e { FZ_CMD_FILL_PATH, FZ_CMD_STROKE_PATH, FZ_CMD_CLIP_PATH, FZ_CMD_CLIP_STROKE_PATH, FZ_CMD_FILL_TEXT, FZ_CMD_STROKE_TEXT, FZ_CMD_CLIP_TEXT, FZ_CMD_CLIP_STROKE_TEXT, FZ_CMD_IGNORE_TEXT, FZ_CMD_FILL_SHADE, FZ_CMD_FILL_IMAGE, FZ_CMD_FILL_IMAGE_MASK, FZ_CMD_CLIP_IMAGE_MASK, FZ_CMD_POP_CLIP, FZ_CMD_BEGIN_MASK, FZ_CMD_END_MASK, FZ_CMD_BEGIN_GROUP, FZ_CMD_END_GROUP, FZ_CMD_BEGIN_TILE, FZ_CMD_END_TILE } fz_display_command; struct fz_display_node_s { fz_display_command cmd; fz_display_node *next; fz_rect rect; union { fz_path *path; fz_text *text; fz_shade *shade; fz_pixmap *image; int blendmode; } item; fz_stroke_state *stroke; int flag; /* even_odd, accumulate, isolated/knockout... */ fz_matrix ctm; fz_colorspace *colorspace; float alpha; float color[FZ_MAX_COLORS]; }; struct fz_display_list_s { fz_display_node *first; fz_display_node *last; }; enum { ISOLATED = 1, KNOCKOUT = 2 }; static fz_display_node * fz_new_display_node(fz_display_command cmd, fz_matrix ctm, fz_colorspace *colorspace, float *color, float alpha) { fz_display_node *node; int i; node = fz_malloc(sizeof(fz_display_node)); node->cmd = cmd; node->next = NULL; node->rect = fz_empty_rect; node->item.path = NULL; node->stroke = NULL; node->flag = 0; node->ctm = ctm; if (colorspace) { node->colorspace = fz_keep_colorspace(colorspace); if (color) { for (i = 0; i < node->colorspace->n; i++) node->color[i] = color[i]; } } else { node->colorspace = NULL; } node->alpha = alpha; return node; } static fz_stroke_state * fz_clone_stroke_state(fz_stroke_state *stroke) { fz_stroke_state *newstroke = fz_malloc(sizeof(fz_stroke_state)); *newstroke = *stroke; return newstroke; } static void fz_append_display_node(fz_display_list *list, fz_display_node *node) { if (!list->first) { list->first = node; list->last = node; } else { list->last->next = node; list->last = node; } } static void fz_free_display_node(fz_display_node *node) { switch (node->cmd) { case FZ_CMD_FILL_PATH: case FZ_CMD_STROKE_PATH: case FZ_CMD_CLIP_PATH: case FZ_CMD_CLIP_STROKE_PATH: fz_free_path(node->item.path); break; case FZ_CMD_FILL_TEXT: case FZ_CMD_STROKE_TEXT: case FZ_CMD_CLIP_TEXT: case FZ_CMD_CLIP_STROKE_TEXT: case FZ_CMD_IGNORE_TEXT: fz_free_text(node->item.text); break; case FZ_CMD_FILL_SHADE: fz_drop_shade(node->item.shade); break; case FZ_CMD_FILL_IMAGE: case FZ_CMD_FILL_IMAGE_MASK: case FZ_CMD_CLIP_IMAGE_MASK: fz_drop_pixmap(node->item.image); break; case FZ_CMD_POP_CLIP: case FZ_CMD_BEGIN_MASK: case FZ_CMD_END_MASK: case FZ_CMD_BEGIN_GROUP: case FZ_CMD_END_GROUP: case FZ_CMD_BEGIN_TILE: case FZ_CMD_END_TILE: break; } if (node->stroke) fz_free(node->stroke); if (node->colorspace) fz_drop_colorspace(node->colorspace); fz_free(node); } static void fz_list_fill_path(void *user, fz_path *path, int even_odd, fz_matrix ctm, fz_colorspace *colorspace, float *color, float alpha) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_FILL_PATH, ctm, colorspace, color, alpha); node->rect = fz_bound_path(path, NULL, ctm); node->item.path = fz_clone_path(path); node->flag = even_odd; fz_append_display_node(user, node); } static void fz_list_stroke_path(void *user, fz_path *path, fz_stroke_state *stroke, fz_matrix ctm, fz_colorspace *colorspace, float *color, float alpha) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_STROKE_PATH, ctm, colorspace, color, alpha); node->rect = fz_bound_path(path, stroke, ctm); node->item.path = fz_clone_path(path); node->stroke = fz_clone_stroke_state(stroke); fz_append_display_node(user, node); } static void fz_list_clip_path(void *user, fz_path *path, int even_odd, fz_matrix ctm) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_CLIP_PATH, ctm, NULL, NULL, 0); node->rect = fz_bound_path(path, NULL, ctm); node->item.path = fz_clone_path(path); node->flag = even_odd; fz_append_display_node(user, node); } static void fz_list_clip_stroke_path(void *user, fz_path *path, fz_stroke_state *stroke, fz_matrix ctm) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_CLIP_STROKE_PATH, ctm, NULL, NULL, 0); node->rect = fz_bound_path(path, stroke, ctm); node->item.path = fz_clone_path(path); node->stroke = fz_clone_stroke_state(stroke); fz_append_display_node(user, node); } static void fz_list_fill_text(void *user, fz_text *text, fz_matrix ctm, fz_colorspace *colorspace, float *color, float alpha) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_FILL_TEXT, ctm, colorspace, color, alpha); node->rect = fz_bound_text(text, ctm); node->item.text = fz_clone_text(text); fz_append_display_node(user, node); } static void fz_list_stroke_text(void *user, fz_text *text, fz_stroke_state *stroke, fz_matrix ctm, fz_colorspace *colorspace, float *color, float alpha) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_STROKE_TEXT, ctm, colorspace, color, alpha); node->rect = fz_bound_text(text, ctm); node->item.text = fz_clone_text(text); node->stroke = fz_clone_stroke_state(stroke); fz_append_display_node(user, node); } static void fz_list_clip_text(void *user, fz_text *text, fz_matrix ctm, int accumulate) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_CLIP_TEXT, ctm, NULL, NULL, 0); node->rect = fz_bound_text(text, ctm); node->item.text = fz_clone_text(text); node->flag = accumulate; /* when accumulating, be conservative about culling */ if (accumulate) node->rect = fz_infinite_rect; fz_append_display_node(user, node); } static void fz_list_clip_stroke_text(void *user, fz_text *text, fz_stroke_state *stroke, fz_matrix ctm) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_CLIP_STROKE_TEXT, ctm, NULL, NULL, 0); node->rect = fz_bound_text(text, ctm); node->item.text = fz_clone_text(text); node->stroke = fz_clone_stroke_state(stroke); fz_append_display_node(user, node); } static void fz_list_ignore_text(void *user, fz_text *text, fz_matrix ctm) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_IGNORE_TEXT, ctm, NULL, NULL, 0); node->rect = fz_bound_text(text, ctm); node->item.text = fz_clone_text(text); fz_append_display_node(user, node); } static void fz_list_pop_clip(void *user) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_POP_CLIP, fz_identity, NULL, NULL, 0); /* TODO: scan back for matching pushclip and calculate bbox of contents */ fz_append_display_node(user, node); } static void fz_list_fill_shade(void *user, fz_shade *shade, fz_matrix ctm, float alpha) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_FILL_SHADE, ctm, NULL, NULL, alpha); node->rect = fz_bound_shade(shade, ctm); node->item.shade = fz_keep_shade(shade); fz_append_display_node(user, node); } static void fz_list_fill_image(void *user, fz_pixmap *image, fz_matrix ctm, float alpha) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_FILL_IMAGE, ctm, NULL, NULL, alpha); node->rect = fz_transform_rect(ctm, fz_unit_rect); node->item.image = fz_keep_pixmap(image); fz_append_display_node(user, node); } static void fz_list_fill_image_mask(void *user, fz_pixmap *image, fz_matrix ctm, fz_colorspace *colorspace, float *color, float alpha) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_FILL_IMAGE_MASK, ctm, colorspace, color, alpha); node->rect = fz_transform_rect(ctm, fz_unit_rect); node->item.image = fz_keep_pixmap(image); fz_append_display_node(user, node); } static void fz_list_clip_image_mask(void *user, fz_pixmap *image, fz_matrix ctm) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_CLIP_IMAGE_MASK, ctm, NULL, NULL, 0); node->rect = fz_transform_rect(ctm, fz_unit_rect); node->item.image = fz_keep_pixmap(image); fz_append_display_node(user, node); } static void fz_list_begin_mask(void *user, fz_rect rect, int luminosity, fz_colorspace *colorspace, float *color) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_BEGIN_MASK, fz_identity, colorspace, color, 0); node->rect = rect; node->flag = luminosity; fz_append_display_node(user, node); } static void fz_list_end_mask(void *user) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_END_MASK, fz_identity, NULL, NULL, 0); fz_append_display_node(user, node); } static void fz_list_begin_group(void *user, fz_rect rect, int isolated, int knockout, int blendmode, float alpha) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_BEGIN_GROUP, fz_identity, NULL, NULL, alpha); node->rect = rect; node->item.blendmode = blendmode; node->flag |= isolated ? ISOLATED : 0; node->flag |= knockout ? KNOCKOUT : 0; fz_append_display_node(user, node); } static void fz_list_end_group(void *user) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_END_GROUP, fz_identity, NULL, NULL, 0); fz_append_display_node(user, node); } static void fz_list_begin_tile(void *user, fz_rect area, fz_rect view, float xstep, float ystep, fz_matrix ctm) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_BEGIN_TILE, ctm, NULL, NULL, 0); node->rect = area; node->color[0] = xstep; node->color[1] = ystep; node->color[2] = view.x0; node->color[3] = view.y0; node->color[4] = view.x1; node->color[5] = view.y1; fz_append_display_node(user, node); } static void fz_list_end_tile(void *user) { fz_display_node *node; node = fz_new_display_node(FZ_CMD_END_TILE, fz_identity, NULL, NULL, 0); fz_append_display_node(user, node); } fz_device * fz_new_list_device(fz_display_list *list) { fz_device *dev = fz_new_device(list); dev->fill_path = fz_list_fill_path; dev->stroke_path = fz_list_stroke_path; dev->clip_path = fz_list_clip_path; dev->clip_stroke_path = fz_list_clip_stroke_path; dev->fill_text = fz_list_fill_text; dev->stroke_text = fz_list_stroke_text; dev->clip_text = fz_list_clip_text; dev->clip_stroke_text = fz_list_clip_stroke_text; dev->ignore_text = fz_list_ignore_text; dev->fill_shade = fz_list_fill_shade; dev->fill_image = fz_list_fill_image; dev->fill_image_mask = fz_list_fill_image_mask; dev->clip_image_mask = fz_list_clip_image_mask; dev->pop_clip = fz_list_pop_clip; dev->begin_mask = fz_list_begin_mask; dev->end_mask = fz_list_end_mask; dev->begin_group = fz_list_begin_group; dev->end_group = fz_list_end_group; dev->begin_tile = fz_list_begin_tile; dev->end_tile = fz_list_end_tile; return dev; } fz_display_list * fz_new_display_list(void) { fz_display_list *list = fz_malloc(sizeof(fz_display_list)); list->first = NULL; list->last = NULL; return list; } void fz_free_display_list(fz_display_list *list) { fz_display_node *node = list->first; while (node) { fz_display_node *next = node->next; fz_free_display_node(node); node = next; } fz_free(list); } void fz_execute_display_list(fz_display_list *list, fz_device *dev, fz_matrix top_ctm, fz_bbox scissor) { fz_display_node *node; fz_matrix ctm; fz_rect rect; fz_bbox bbox; int clipped = 0; int tiled = 0; int empty; if (!fz_is_infinite_bbox(scissor)) { /* add some fuzz at the edges, as especially glyph rects * are sometimes not actually completely bounding the glyph */ scissor.x0 -= 20; scissor.y0 -= 20; scissor.x1 += 20; scissor.y1 += 20; } for (node = list->first; node; node = node->next) { /* cull objects to draw using a quick visibility test */ if (tiled || node->cmd == FZ_CMD_BEGIN_TILE || node->cmd == FZ_CMD_END_TILE) { empty = 0; } else { bbox = fz_round_rect(fz_transform_rect(top_ctm, node->rect)); bbox = fz_intersect_bbox(bbox, scissor); empty = fz_is_empty_bbox(bbox); } if (clipped || empty) { switch (node->cmd) { case FZ_CMD_CLIP_PATH: case FZ_CMD_CLIP_STROKE_PATH: case FZ_CMD_CLIP_TEXT: case FZ_CMD_CLIP_STROKE_TEXT: case FZ_CMD_CLIP_IMAGE_MASK: case FZ_CMD_BEGIN_MASK: case FZ_CMD_BEGIN_GROUP: clipped++; continue; case FZ_CMD_POP_CLIP: case FZ_CMD_END_GROUP: if (!clipped) goto visible; clipped--; continue; case FZ_CMD_END_MASK: if (!clipped) goto visible; continue; default: continue; } } visible: ctm = fz_concat(node->ctm, top_ctm); switch (node->cmd) { case FZ_CMD_FILL_PATH: fz_fill_path(dev, node->item.path, node->flag, ctm, node->colorspace, node->color, node->alpha); break; case FZ_CMD_STROKE_PATH: fz_stroke_path(dev, node->item.path, node->stroke, ctm, node->colorspace, node->color, node->alpha); break; case FZ_CMD_CLIP_PATH: fz_clip_path(dev, node->item.path, node->flag, ctm); break; case FZ_CMD_CLIP_STROKE_PATH: fz_clip_stroke_path(dev, node->item.path, node->stroke, ctm); break; case FZ_CMD_FILL_TEXT: fz_fill_text(dev, node->item.text, ctm, node->colorspace, node->color, node->alpha); break; case FZ_CMD_STROKE_TEXT: fz_stroke_text(dev, node->item.text, node->stroke, ctm, node->colorspace, node->color, node->alpha); break; case FZ_CMD_CLIP_TEXT: fz_clip_text(dev, node->item.text, ctm, node->flag); break; case FZ_CMD_CLIP_STROKE_TEXT: fz_clip_stroke_text(dev, node->item.text, node->stroke, ctm); break; case FZ_CMD_IGNORE_TEXT: fz_ignore_text(dev, node->item.text, ctm); break; case FZ_CMD_FILL_SHADE: fz_fill_shade(dev, node->item.shade, ctm, node->alpha); break; case FZ_CMD_FILL_IMAGE: fz_fill_image(dev, node->item.image, ctm, node->alpha); break; case FZ_CMD_FILL_IMAGE_MASK: fz_fill_image_mask(dev, node->item.image, ctm, node->colorspace, node->color, node->alpha); break; case FZ_CMD_CLIP_IMAGE_MASK: fz_clip_image_mask(dev, node->item.image, ctm); break; case FZ_CMD_POP_CLIP: fz_pop_clip(dev); break; case FZ_CMD_BEGIN_MASK: rect = fz_transform_rect(top_ctm, node->rect); fz_begin_mask(dev, rect, node->flag, node->colorspace, node->color); break; case FZ_CMD_END_MASK: fz_end_mask(dev); break; case FZ_CMD_BEGIN_GROUP: rect = fz_transform_rect(top_ctm, node->rect); fz_begin_group(dev, rect, node->flag & ISOLATED, node->flag & KNOCKOUT, node->item.blendmode, node->alpha); break; case FZ_CMD_END_GROUP: fz_end_group(dev); break; case FZ_CMD_BEGIN_TILE: tiled++; rect.x0 = node->color[2]; rect.y0 = node->color[3]; rect.x1 = node->color[4]; rect.y1 = node->color[5]; fz_begin_tile(dev, node->rect, rect, node->color[0], node->color[1], ctm); break; case FZ_CMD_END_TILE: tiled--; fz_end_tile(dev); break; } } }