Beispiel #1
0
static void
push_clip_stack_accumulate(fz_device *dev, const fz_rect *rect, int accumulate)
{
	if (accumulate <= 1)
	{
		dev->scissor_accumulator = *rect;
		if (dev->container_len == dev->container_cap)
		{
			int newmax = dev->container_cap * 2;
			if (newmax == 0)
				newmax = 4;
			dev->container = fz_resize_array(dev->ctx, dev->container, newmax, sizeof(*dev->container));
			dev->container_cap = newmax;
		}
		if (dev->container_len > 0)
			dev->container[dev->container_len].scissor = dev->container[dev->container_len-1].scissor;
		else
			dev->container[dev->container_len].scissor = fz_infinite_rect;
		fz_intersect_rect(&dev->container[dev->container_len].scissor, rect);
		dev->container[dev->container_len].flags = fz_device_container_stack_is_clip_text;
		dev->container[dev->container_len].user = 0;
		dev->container_len++;
	}
	else
	{
		if (dev->container_len <= 0)
			return;
		fz_union_rect(&dev->scissor_accumulator, rect);
		fz_intersect_rect(&dev->container[dev->container_len-1].scissor, &dev->scissor_accumulator);
	}
}
Beispiel #2
0
static void
fz_list_clip_image_mask(fz_device *dev, fz_image *image, fz_rect *rect, fz_matrix ctm)
{
	fz_display_node *node;
	node = fz_new_display_node(dev->ctx, FZ_CMD_CLIP_IMAGE_MASK, ctm, NULL, NULL, 0);
	node->rect = fz_transform_rect(ctm, fz_unit_rect);
	if (rect)
		node->rect = fz_intersect_rect(node->rect, *rect);
	node->item.image = fz_keep_image(dev->ctx, image);
	fz_append_display_node(dev->user, node);
}
Beispiel #3
0
static void
fz_list_clip_image_mask(void *user, fz_pixmap *image, fz_rect *rect, 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);
	if (rect != NULL)
		node->rect = fz_intersect_rect(node->rect, *rect);
	node->item.image = fz_keep_pixmap(image);
	fz_append_display_node(user, node);
}
Beispiel #4
0
static void
fz_list_clip_path(void *user, fz_path *path, fz_rect *rect, 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);
	if (rect != NULL)
		node->rect = fz_intersect_rect(node->rect, *rect);
	node->item.path = fz_clone_path(path);
	node->flag = even_odd;
	fz_append_display_node(user, node);
}
Beispiel #5
0
static void
fz_list_clip_stroke_path(void *user, fz_path *path, fz_rect *rect, 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);
	if (rect != NULL)
		node->rect = fz_intersect_rect(node->rect, *rect);
	node->item.path = fz_clone_path(path);
	node->stroke = fz_clone_stroke_state(stroke);
	fz_append_display_node(user, node);
}
Beispiel #6
0
static void
fz_bbox_add_rect(fz_device *dev, fz_rect rect, int clip)
{
	fz_bbox_data *data = dev->user;

	if (0 < data->top && data->top <= STACK_SIZE)
		rect = fz_intersect_rect(rect, data->stack[data->top-1]);
	if (!clip && data->top <= STACK_SIZE && !data->ignore)
		*data->result = fz_union_bbox(*data->result, fz_bbox_covering_rect(rect));
	if (clip && ++data->top <= STACK_SIZE)
		data->stack[data->top-1] = rect;
}
Beispiel #7
0
static void
fz_bbox_add_rect(fz_device *dev, const fz_rect *rect, int clip)
{
	fz_bbox_data *data = dev->user;
	fz_rect r = *rect;

	if (0 < data->top && data->top <= STACK_SIZE)
		fz_intersect_rect(&r, &data->stack[data->top-1]);
	if (!clip && data->top <= STACK_SIZE && !data->ignore)
		fz_union_rect(data->result, &r);
	if (clip && ++data->top <= STACK_SIZE)
		data->stack[data->top-1] = r;
}
Beispiel #8
0
fz_rect *
fz_bound_shade(fz_context *ctx, fz_shade *shade, const fz_matrix *ctm, fz_rect *s)
{
	fz_matrix local_ctm;
	fz_rect rect;

	fz_concat(&local_ctm, &shade->matrix, ctm);
	*s = shade->bbox;
	if (shade->type != FZ_LINEAR && shade->type != FZ_RADIAL)
	{
		fz_bound_mesh(ctx, shade, &rect);
		fz_intersect_rect(s, &rect);
	}
	return fz_transform_rect(s, &local_ctm);
}
Beispiel #9
0
static float
calc_bbox_overlap(const fz_rect *bbox1, const fz_rect *bbox2)
{
	float area1, area2, area3;
	fz_rect intersect = *bbox1;

	fz_intersect_rect(&intersect, bbox2);
	if (fz_is_empty_rect(&intersect))
		return 0;

	area1 = (bbox1->x1 - bbox1->x0) * (bbox1->y1 - bbox1->y0);
	area2 = (bbox2->x1 - bbox2->x0) * (bbox2->y1 - bbox2->y0);
	area3 = (intersect.x1 - intersect.x0) * (intersect.y1 - intersect.y0);

	return area3 / fz_max(area1, area2);
}
Beispiel #10
0
static void
svg_dev_fill_shade(fz_context *ctx, fz_device *dev, fz_shade *shade, const fz_matrix *ctm, float alpha)
{
	svg_device *sdev = (svg_device*)dev;
	fz_output *out = sdev->out;

	fz_rect rect;
	fz_irect bbox;
	fz_pixmap *pix;
	fz_buffer *buf = NULL;

	fz_var(buf);

	if (dev->container_len == 0)
		return;

	fz_round_rect(&bbox, fz_intersect_rect(fz_bound_shade(ctx, shade, ctm, &rect), &dev->container[dev->container_len-1].scissor));
	if (fz_is_empty_irect(&bbox))
		return;
	pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), &bbox);
	fz_clear_pixmap(ctx, pix);

	fz_try(ctx)
	{
		fz_paint_shade(ctx, shade, ctm, pix, &bbox);
		buf = fz_new_buffer_from_pixmap_as_png(ctx, pix);
		if (alpha != 1.0f)
			fz_printf(ctx, out, "<g opacity=\"%g\">", alpha);
		fz_printf(ctx, out, "<image x=\"%dpx\" y=\"%dpx\" width=\"%dpx\" height=\"%dpx\" xlink:href=\"data:image/png;base64,", pix->x, pix->y, pix->w, pix->h);
		send_data_base64(ctx, out, buf);
		fz_printf(ctx, out, "\"/>\n");
		if (alpha != 1.0f)
			fz_printf(ctx, out, "</g>");
	}
	fz_always(ctx)
	{
		fz_drop_buffer(ctx, buf);
		fz_drop_pixmap(ctx, pix);
	}
	fz_catch(ctx)
	{
		fz_rethrow(ctx);
	}
}
Beispiel #11
0
static void
fz_bbox_add_rect(fz_context *ctx, fz_device *dev, const fz_rect *rect, int clip)
{
	fz_bbox_device *bdev = (fz_bbox_device*)dev;
	fz_rect r = *rect;

	if (0 < bdev->top && bdev->top <= STACK_SIZE)
	{
		fz_intersect_rect(&r, &bdev->stack[bdev->top-1]);
	}
	if (!clip && bdev->top <= STACK_SIZE && !bdev->ignore)
	{
		fz_union_rect(bdev->result, &r);
	}
	if (clip && ++bdev->top <= STACK_SIZE)
	{
		bdev->stack[bdev->top-1] = r;
	}
}
Beispiel #12
0
static void
fz_list_clip_stroke_path(fz_device *dev, fz_path *path, const fz_rect *rect, fz_stroke_state *stroke, const fz_matrix *ctm)
{
	fz_display_node *node;
	fz_context *ctx = dev->ctx;
	node = fz_new_display_node(ctx, FZ_CMD_CLIP_STROKE_PATH, ctm, NULL, NULL, 0);
	fz_try(ctx)
	{
		fz_bound_path(dev->ctx, path, stroke, ctm, &node->rect);
		if (rect)
			fz_intersect_rect(&node->rect, rect);
		node->item.path = fz_clone_path(dev->ctx, path);
		node->stroke = fz_keep_stroke_state(dev->ctx, stroke);
	}
	fz_catch(ctx)
	{
		fz_free_display_node(ctx, node);
		fz_rethrow(ctx);
	}
	fz_append_display_node(dev->user, node);
}
Beispiel #13
0
static void
fz_list_clip_path(fz_device *dev, fz_path *path, fz_rect *rect, int even_odd, fz_matrix ctm)
{
	fz_display_node *node;
	fz_context *ctx = dev->ctx;
	node = fz_new_display_node(ctx, FZ_CMD_CLIP_PATH, ctm, NULL, NULL, 0);
	fz_try(ctx)
	{
		node->rect = fz_bound_path(dev->ctx, path, NULL, ctm);
		if (rect)
			node->rect = fz_intersect_rect(node->rect, *rect);
		node->item.path = fz_clone_path(dev->ctx, path);
		node->flag = even_odd;
	}
	fz_catch(ctx)
	{
		fz_free_display_node(ctx, node);
		fz_rethrow(ctx);
	}
	fz_append_display_node(dev->user, node);
}
Beispiel #14
0
fz_rect *
fz_bound_shade(fz_context *ctx, fz_shade *shade, const fz_matrix *ctm, fz_rect *s)
{
	fz_matrix local_ctm;
	struct bound_mesh_data bmd;

	fz_concat(&local_ctm, &shade->matrix, ctm);
	*s = shade->bbox;
	fz_transform_rect(s, &local_ctm);
	if (shade->type == FZ_LINEAR)
		return s;
	if (shade->type == FZ_RADIAL)
		return s;

	bmd.rect = fz_empty_rect;
	bmd.first = 1;
	fz_process_mesh(ctx, shade, &local_ctm, &bound_tri, &bmd);

	fz_intersect_rect(s, &bmd.rect);
	return s;
}
Beispiel #15
0
static void
push_clip_stack(fz_device *dev, const fz_rect *rect, int flags)
{
	if (dev->container_len == dev->container_cap)
	{
		int newmax = dev->container_cap * 2;
		if (newmax == 0)
			newmax = 4;
		dev->container = fz_resize_array(dev->ctx, dev->container, newmax, sizeof(*dev->container));
		dev->container_cap = newmax;
	}
	if (dev->container_len == 0)
		dev->container[0].scissor = *rect;
	else
	{
		dev->container[dev->container_len].scissor = dev->container[dev->container_len-1].scissor;
		fz_intersect_rect(&dev->container[dev->container_len].scissor, rect);
	}
	dev->container[dev->container_len].flags = flags;
	dev->container[dev->container_len].user = 0;
	dev->container_len++;
}
Beispiel #16
0
pdf_page *
pdf_load_page_by_obj(pdf_document *doc, int number, pdf_obj *pageref)
{
	fz_context *ctx = doc->ctx;
	pdf_page *page;
	pdf_annot *annot;
	pdf_obj *pageobj, *obj;
	fz_rect mediabox, cropbox, realbox;
	float userunit;
	fz_matrix mat;

	/* SumatraPDF: allow replacing potentially slow pdf_lookup_page_obj */
	pageobj = pdf_resolve_indirect(pageref);

	page = fz_malloc_struct(ctx, pdf_page);
	page->resources = NULL;
	page->contents = NULL;
	page->transparency = 0;
	page->links = NULL;
	page->annots = NULL;
	page->annot_tailp = &page->annots;
	page->deleted_annots = NULL;
	page->tmp_annots = NULL;
	page->me = pdf_keep_obj(pageobj);
	page->incomplete = 0;

	obj = pdf_dict_gets(pageobj, "UserUnit");
	if (pdf_is_real(obj))
		userunit = pdf_to_real(obj);
	else
		userunit = 1;

	pdf_to_rect(ctx, pdf_lookup_inherited_page_item(doc, pageobj, "MediaBox"), &mediabox);
	if (fz_is_empty_rect(&mediabox))
	{
		fz_warn(ctx, "cannot find page size for page %d", number + 1);
		mediabox.x0 = 0;
		mediabox.y0 = 0;
		mediabox.x1 = 612;
		mediabox.y1 = 792;
	}

	pdf_to_rect(ctx, pdf_lookup_inherited_page_item(doc, pageobj, "CropBox"), &cropbox);
	if (!fz_is_empty_rect(&cropbox))
		fz_intersect_rect(&mediabox, &cropbox);

	page->mediabox.x0 = fz_min(mediabox.x0, mediabox.x1) * userunit;
	page->mediabox.y0 = fz_min(mediabox.y0, mediabox.y1) * userunit;
	page->mediabox.x1 = fz_max(mediabox.x0, mediabox.x1) * userunit;
	page->mediabox.y1 = fz_max(mediabox.y0, mediabox.y1) * userunit;

	if (page->mediabox.x1 - page->mediabox.x0 < 1 || page->mediabox.y1 - page->mediabox.y0 < 1)
	{
		fz_warn(ctx, "invalid page size in page %d", number + 1);
		page->mediabox = fz_unit_rect;
	}

	page->rotate = pdf_to_int(pdf_lookup_inherited_page_item(doc, pageobj, "Rotate"));
	/* Snap page->rotate to 0, 90, 180 or 270 */
	if (page->rotate < 0)
		page->rotate = 360 - ((-page->rotate) % 360);
	if (page->rotate >= 360)
		page->rotate = page->rotate % 360;
	page->rotate = 90*((page->rotate + 45)/90);
	if (page->rotate > 360)
		page->rotate = 0;

	fz_pre_rotate(fz_scale(&page->ctm, 1, -1), -page->rotate);
	realbox = page->mediabox;
	fz_transform_rect(&realbox, &page->ctm);
	fz_pre_scale(fz_translate(&mat, -realbox.x0, -realbox.y0), userunit, userunit);
	fz_concat(&page->ctm, &page->ctm, &mat);

	fz_try(ctx)
	{
		obj = pdf_dict_gets(pageobj, "Annots");
		if (obj)
		{
			page->links = pdf_load_link_annots(doc, obj, &page->ctm);
			pdf_load_annots(doc, page, obj);
		}
	}
	fz_catch(ctx)
	{
		if (fz_caught(ctx) != FZ_ERROR_TRYLATER)
			/* SumatraPDF: ignore annotations in case of unexpected errors */
			fz_warn(ctx, "unexpectedly failed to load page annotations");
		page->incomplete |= PDF_PAGE_INCOMPLETE_ANNOTS;
	}

	page->duration = pdf_to_real(pdf_dict_gets(pageobj, "Dur"));

	obj = pdf_dict_gets(pageobj, "Trans");
	page->transition_present = (obj != NULL);
	if (obj)
	{
		pdf_load_transition(doc, page, obj);
	}

	// TODO: inherit
	page->resources = pdf_lookup_inherited_page_item(doc, pageobj, "Resources");
	if (page->resources)
		pdf_keep_obj(page->resources);

	obj = pdf_dict_gets(pageobj, "Contents");
	fz_try(ctx)
	{
		page->contents = pdf_keep_obj(obj);

		if (pdf_resources_use_blending(doc, page->resources))
			page->transparency = 1;
		/* cf. http://code.google.com/p/sumatrapdf/issues/detail?id=2107 */
		else if (!strcmp(pdf_to_name(pdf_dict_getp(pageobj, "Group/S")), "Transparency"))
			page->transparency = 1;

		for (annot = page->annots; annot && !page->transparency; annot = annot->next)
			if (annot->ap && pdf_resources_use_blending(doc, annot->ap->resources))
				page->transparency = 1;
	}
	fz_catch(ctx)
	{
		if (fz_caught(ctx) != FZ_ERROR_TRYLATER)
		{
			pdf_free_page(doc, page);
			fz_rethrow_message(ctx, "cannot load page %d contents (%d 0 R)", number + 1, pdf_to_num(pageref));
		}
		page->incomplete |= PDF_PAGE_INCOMPLETE_CONTENTS;
	}

	return page;
}
Beispiel #17
0
static void
fz_append_display_node(fz_display_list *list, fz_display_node *node)
{
	switch (node->cmd) {
	case FZ_CMD_CLIP_PATH:
	case FZ_CMD_CLIP_STROKE_PATH:
	case FZ_CMD_CLIP_IMAGE_MASK:
		list->stack[list->top].update = &node->rect;
		list->stack[list->top].rect = fz_empty_rect;
		list->top++;
		break;
	case FZ_CMD_CLIP_TEXT:
	case FZ_CMD_CLIP_STROKE_TEXT:
		list->stack[list->top].update = NULL;
		list->stack[list->top].rect = fz_empty_rect;
		list->top++;
		break;
	case FZ_CMD_BEGIN_TILE:
		list->tiled++;
		if (list->top > 0) {
			list->stack[list->top-1].rect = fz_infinite_rect;
		}
		break;
	case FZ_CMD_END_TILE:
		list->tiled--;
		break;
	case FZ_CMD_END_GROUP:
		break;
	case FZ_CMD_POP_CLIP:
		if (list->top > 0) {
			fz_rect *update;
			list->top--;
			update = list->stack[list->top].update;
			if (list->tiled == 0) {
				if (update != NULL) {
					*update = fz_intersect_rect(*update, list->stack[list->top].rect);
					node->rect = *update;
				} else {
					node->rect = list->stack[list->top].rect;
				}
			} else {
				node->rect = fz_infinite_rect;
			}
		}
		/*@fallthrough@*/
	default:
		if ((list->top > 0) && (list->tiled == 0)) {
			list->stack[list->top-1].rect = fz_union_rect(list->stack[list->top-1].rect, node->rect);
		}
		break;
	}
	if (!list->first)
	{
		list->first = node;
		list->last = node;
	}
	else
	{
		list->last->next = node;
		list->last = node;
	}
}
Beispiel #18
0
static void
fz_append_display_node(fz_display_list *list, fz_display_node *node)
{
	switch (node->cmd)
	{
	case FZ_CMD_CLIP_PATH:
	case FZ_CMD_CLIP_STROKE_PATH:
	case FZ_CMD_CLIP_IMAGE_MASK:
		if (list->top < STACK_SIZE)
		{
			list->stack[list->top].update = &node->rect;
			list->stack[list->top].rect = fz_empty_rect;
		}
		list->top++;
		break;
	case FZ_CMD_CLIP_TEXT:
		/* don't reset the clip rect for accumulated text */
		if (node->flag == 2)
			break;
		/* fallthrough */
	case FZ_CMD_END_MASK:
	case FZ_CMD_CLIP_STROKE_TEXT:
		if (list->top < STACK_SIZE)
		{
			list->stack[list->top].update = NULL;
			list->stack[list->top].rect = fz_empty_rect;
		}
		list->top++;
		break;
	case FZ_CMD_BEGIN_TILE:
		list->tiled++;
		if (list->top > 0 && list->top <= STACK_SIZE)
		{
			list->stack[list->top-1].rect = fz_infinite_rect;
		}
		break;
	case FZ_CMD_END_TILE:
		list->tiled--;
		break;
	case FZ_CMD_END_GROUP:
		break;
	case FZ_CMD_POP_CLIP:
		if (list->top > STACK_SIZE)
		{
			list->top--;
			node->rect = fz_infinite_rect;
		}
		else if (list->top > 0)
		{
			fz_rect *update;
			list->top--;
			update = list->stack[list->top].update;
			if (list->tiled == 0)
			{
				if (update)
				{
					fz_intersect_rect(update, &list->stack[list->top].rect);
					node->rect = *update;
				}
				else
					node->rect = list->stack[list->top].rect;
			}
			else
				node->rect = fz_infinite_rect;
		}
		/* fallthrough */
	default:
		if (list->top > 0 && list->tiled == 0 && list->top <= STACK_SIZE)
			fz_union_rect(&list->stack[list->top-1].rect, &node->rect);
		break;
	}
	if (!list->first)
	{
		list->first = node;
		list->last = node;
	}
	else
	{
		list->last->next = node;
		list->last = node;
	}
	list->len++;
}
Beispiel #19
0
static int _pdf_doc_load(struct _pdf_doc *self, mume_stream_t *stm)
{
    fz_error error;
    fz_stream *fzstm;
    fz_rect *mbox;
    fz_obj *page_obj, *box_obj;
    int i, c;

    _pdf_doc_clear(self);

    fzstm = fz_new_stream(stm, _pdf_stream_read, _pdf_stream_close);
    mume_stream_reference(stm);
    fzstm->seek = _pdf_stream_seek;
    error = pdf_open_xref_with_stream(&self->xref, fzstm, NULL);
    fz_close(fzstm);
    if (error) {
        mume_error(("Read xref failed\n", error));
        return 0;
    }

    assert(!pdf_needs_password(self->xref));

    /* Load meta information. */
    error = pdf_load_page_tree(self->xref);
    if (error) {
        mume_error(("Cannot load page tree\n"));
        return 0;
    }

    c = pdf_count_pages(self->xref);
    self->glyph_cache = fz_new_glyph_cache();
    self->pages = calloc_abort(c, sizeof(pdf_page*));
    self->disps = calloc_abort(c, sizeof(fz_display_list*));
    self->media_boxes = malloc_abort(c * sizeof(fz_rect));
    self->page_rotates = malloc_abort(c * sizeof(int));

    /* Extract each pages' media box and rotation. */
    for (i = 0; i < c; ++i) {
        mbox = self->media_boxes + i;
        page_obj = self->xref->page_objs[i];
        if (!page_obj) {
            *mbox = fz_empty_rect;
            continue;
        }

        box_obj = fz_dict_gets(page_obj, "MediaBox");
        *mbox = pdf_to_rect(box_obj);
        if (fz_is_empty_rect(*mbox)) {
            fz_warn("Cannot find page bounds, guessing page bounds.");
            mbox->x1 = 612;
            mbox->y1 = 792;
        }

        box_obj = fz_dict_gets(page_obj, "CropBox");
        if (fz_is_array(box_obj))
            *mbox = fz_intersect_rect(*mbox, pdf_to_rect(box_obj));

        self->page_rotates[i] = fz_to_int(
            fz_dict_gets(page_obj, "Rotate"));

        if (self->page_rotates[i] % 90)
            self->page_rotates[i] = 0;
    }

    return 1;
}
Beispiel #20
0
void
fz_run_display_list(fz_display_list *list, fz_device *dev, const fz_matrix *top_ctm, const fz_rect *scissor, fz_cookie *cookie)
{
	fz_display_node *node;
	fz_matrix ctm;
	int clipped = 0;
	int tiled = 0;
	int progress = 0;
	fz_context *ctx = dev->ctx;

	if (!scissor)
		scissor = &fz_infinite_rect;

	if (cookie)
	{
		cookie->progress_max = list->len;
		cookie->progress = 0;
	}

	for (node = list->first; node; node = node->next)
	{
		int empty;

		fz_rect node_rect = node->rect;
		fz_transform_rect(&node_rect, top_ctm);

		/* Check the cookie for aborting */
		if (cookie)
		{
			if (cookie->abort)
				break;
			cookie->progress = progress++;
		}

		/* cull objects to draw using a quick visibility test */

		if (tiled ||
			node->cmd == FZ_CMD_BEGIN_TILE || node->cmd == FZ_CMD_END_TILE ||
			node->cmd == FZ_CMD_BEGIN_PAGE || node->cmd == FZ_CMD_END_PAGE)
		{
			empty = 0;
		}
		else
		{
			fz_rect rect = node_rect;
			fz_intersect_rect(&rect, scissor);
			empty = fz_is_empty_rect(&rect);
		}

		if (clipped || empty)
		{
			switch (node->cmd)
			{
			case FZ_CMD_CLIP_PATH:
			case FZ_CMD_CLIP_STROKE_PATH:
			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_CLIP_TEXT:
				/* Accumulated text has no extra pops */
				if (node->flag != 2)
					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:
		fz_concat(&ctm, &node->ctm, top_ctm);

		fz_try(ctx)
		{
			switch (node->cmd)
			{
			case FZ_CMD_BEGIN_PAGE:
				fz_begin_page(dev, &node_rect, &ctm);
				break;
			case FZ_CMD_END_PAGE:
				fz_end_page(dev);
				break;
			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_rect, node->flag, &ctm);
				break;
			case FZ_CMD_CLIP_STROKE_PATH:
				fz_clip_stroke_path(dev, node->item.path, &node_rect, 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:
				if ((dev->hints & FZ_IGNORE_SHADE) == 0)
					fz_fill_shade(dev, node->item.shade, &ctm, node->alpha);
				break;
			case FZ_CMD_FILL_IMAGE:
				if ((dev->hints & FZ_IGNORE_IMAGE) == 0)
					fz_fill_image(dev, node->item.image, &ctm, node->alpha);
				break;
			case FZ_CMD_FILL_IMAGE_MASK:
				if ((dev->hints & FZ_IGNORE_IMAGE) == 0)
					fz_fill_image_mask(dev, node->item.image, &ctm,
						node->colorspace, node->color, node->alpha);
				break;
			case FZ_CMD_CLIP_IMAGE_MASK:
				if ((dev->hints & FZ_IGNORE_IMAGE) == 0)
					fz_clip_image_mask(dev, node->item.image, &node_rect, &ctm);
				break;
			case FZ_CMD_POP_CLIP:
				fz_pop_clip(dev);
				break;
			case FZ_CMD_BEGIN_MASK:
				fz_begin_mask(dev, &node_rect, node->flag, node->colorspace, node->color);
				break;
			case FZ_CMD_END_MASK:
				fz_end_mask(dev);
				break;
			case FZ_CMD_BEGIN_GROUP:
				fz_begin_group(dev, &node_rect,
					(node->flag & ISOLATED) != 0, (node->flag & KNOCKOUT) != 0,
					node->item.blendmode, node->alpha);
				break;
			case FZ_CMD_END_GROUP:
				fz_end_group(dev);
				break;
			case FZ_CMD_BEGIN_TILE:
			{
				int cached;
				fz_rect tile_rect;
				tiled++;
				tile_rect.x0 = node->color[2];
				tile_rect.y0 = node->color[3];
				tile_rect.x1 = node->color[4];
				tile_rect.y1 = node->color[5];
				cached = fz_begin_tile_id(dev, &node->rect, &tile_rect, node->color[0], node->color[1], &ctm, node->flag);
				if (cached)
					node = skip_to_end_tile(node, &progress);
				break;
			}
			case FZ_CMD_END_TILE:
				tiled--;
				fz_end_tile(dev);
				break;
			/* SumatraPDF: support transfer functions */
			case FZ_CMD_APPLY_TRANSFER_FUNCTION:
				fz_apply_transfer_function(dev, node->item.tr, node->flag);
				break;
			}
		}
		fz_catch(ctx)
		{
			/* Swallow the error */
			if (cookie)
				cookie->errors++;
			fz_warn(ctx, "Ignoring error during interpretation");
		}
	}
}