Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/source/pdf/pdf-op-run.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/source/pdf/pdf-op-run.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,3383 @@ +// Copyright (C) 2004-2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see <https://www.artifex.com/> or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +#include "mupdf/fitz.h" +#include "mupdf/pdf.h" + +#include <string.h> +#include <math.h> + +#include "mupdf/ucdn.h" + +#define TILE + +/* Enable this to watch changes in the structure stack. */ +#undef DEBUG_STRUCTURE + +/* + * Emit graphics calls to device. + */ + +typedef struct pdf_run_processor pdf_run_processor; + +static void pdf_run_xobject(fz_context *ctx, pdf_run_processor *proc, pdf_obj *xobj, pdf_obj *page_resources, fz_matrix transform, int is_smask); + +enum +{ + PDF_FILL, + PDF_STROKE, +}; + +enum +{ + PDF_MAT_NONE, + PDF_MAT_COLOR, + PDF_MAT_PATTERN, + PDF_MAT_SHADE, +}; + +typedef struct +{ + int kind; + fz_colorspace *colorspace; + pdf_pattern *pattern; + fz_shade *shade; + int gstate_num; + fz_color_params color_params; + float alpha; + float v[FZ_MAX_COLORS]; +} pdf_material; + +struct pdf_gstate +{ + fz_matrix ctm; + int clip_depth; + + /* path stroking */ + fz_stroke_state *stroke_state; + + /* materials */ + pdf_material stroke; + pdf_material fill; + + /* pattern paint type 2 */ + int ismask; + + /* text state */ + pdf_text_state text; + + /* transparency */ + int blendmode; + pdf_obj *softmask; + pdf_obj *softmask_resources; + pdf_obj *softmask_tr; + fz_matrix softmask_ctm; + fz_colorspace *softmask_cs; + float softmask_bc[FZ_MAX_COLORS]; + int luminosity; +}; + +typedef struct resources_stack +{ + struct resources_stack *next; + pdf_obj *resources; +} resources_stack; + +typedef struct marked_content_stack +{ + struct marked_content_stack *next; + pdf_obj *tag; + pdf_obj *val; + int structure_pushed; +} marked_content_stack; + +typedef struct begin_layer_stack +{ + struct begin_layer_stack *next; + char *layer; +} begin_layer_stack; + +struct pdf_run_processor +{ + pdf_processor super; + pdf_document *doc; + fz_device *dev; + fz_cookie *cookie; + + fz_default_colorspaces *default_cs; + + resources_stack *rstack; + + /* path object state */ + fz_path *path; + int clip; + int clip_even_odd; + + /* text object state */ + pdf_text_object_state tos; + int bidi; + + /* graphics state */ + pdf_gstate *gstate; + int gcap; + int gtop; + int gbot; + int gparent; + + /* xobject cycle detector */ + pdf_cycle_list *cycle; + + pdf_obj *role_map; + + marked_content_stack *marked_content; + pdf_obj *mcid_sent; + pdf_obj *pending_mcid_pop; + + int struct_parent; + int broken_struct_tree; + + /* Pending begin layers */ + begin_layer_stack *begin_layer; + begin_layer_stack **next_begin_layer; + + int mc_depth; + /* The nest_mark array holds a record of the way in which clips and + * marked content are nested to ensure we pop stuff in the same order + * that we push it - i.e. to keep calls nested nicely. An entry x, + * where x >= 0 represents that a push has happened for mc_depth == x. + * An entry x, where x < 0 means that -x clips have happened at this + * position. */ + int nest_depth; + int nest_mark[1024]; +}; + +/* Forward definition */ +static void +pop_any_pending_mcid_changes(fz_context *ctx, pdf_run_processor *pr); + +static void +push_begin_layer(fz_context *ctx, pdf_run_processor *proc, const char *str) +{ + begin_layer_stack *s = fz_malloc_struct(ctx, begin_layer_stack); + + fz_try(ctx) + s->layer = fz_strdup(ctx, str); + fz_catch(ctx) + { + fz_free(ctx, s); + fz_rethrow(ctx); + } + + s->next = NULL; + *proc->next_begin_layer = s; + proc->next_begin_layer = &s->next; +} + +static void +flush_begin_layer(fz_context *ctx, pdf_run_processor *proc) +{ + begin_layer_stack *s; + + while (proc->begin_layer) + { + s = proc->begin_layer; + + if (proc->nest_depth == nelem(proc->nest_mark)) + fz_throw(ctx, FZ_ERROR_LIMIT, "layer/clip nesting too deep"); + + proc->nest_mark[proc->nest_depth++] = ++proc->mc_depth; + + fz_begin_layer(ctx, proc->dev, s->layer); + proc->begin_layer = s->next; + fz_free(ctx, s->layer); + fz_free(ctx, s); + } + proc->next_begin_layer = &proc->begin_layer; +} + +static void nest_layer_clip(fz_context *ctx, pdf_run_processor *proc) +{ + if (proc->nest_depth == nelem(proc->nest_mark)) + fz_throw(ctx, FZ_ERROR_LIMIT, "layer/clip nesting too deep"); + if (proc->nest_depth > 0 && proc->nest_mark[proc->nest_depth-1] < 0) + { + /* The last mark was a clip. Just increase that count. */ + proc->nest_mark[proc->nest_depth-1]--; + } + else + { + /* Create a new entry for a single clip. */ + proc->nest_mark[proc->nest_depth++] = -1; + } +} + +static void +do_end_layer(fz_context *ctx, pdf_run_processor *proc) +{ + if (proc->nest_depth > 0 && proc->nest_mark[proc->nest_depth-1] == proc->mc_depth) + { + fz_end_layer(ctx, proc->dev); + proc->nest_depth--; + } + else + { + /* If EMC is unbalanced with q/Q, we will emit the end layer + * device call before or after the Q operator instead of its true location + */ + fz_warn(ctx, "invalid marked content and clip nesting"); + } + + if (proc->mc_depth > 0) + proc->mc_depth--; +} + +typedef struct +{ + pdf_obj *softmask; + fz_colorspace *softmask_cs; + pdf_obj *page_resources; + fz_matrix ctm; +} softmask_save; + +static fz_function * +load_transfer_function(fz_context *ctx, pdf_obj *obj) +{ + if (obj == NULL || pdf_name_eq(ctx, obj, PDF_NAME(Identity))) + return NULL; + + return (fz_function *)pdf_load_function(ctx, obj, 1, 1); +} + +static pdf_gstate * +begin_softmask(fz_context *ctx, pdf_run_processor *pr, softmask_save *save, fz_rect bbox) +{ + pdf_gstate *gstate = pr->gstate + pr->gtop; + pdf_obj *softmask = gstate->softmask; + fz_colorspace *softmask_cs = gstate->softmask_cs; + fz_rect mask_bbox; + fz_matrix tos_save[2], save_ctm; + fz_matrix mask_matrix; + fz_colorspace *mask_colorspace; + int saved_blendmode; + fz_function *tr = NULL; + + fz_var(tr); + + save->softmask = softmask; + if (softmask == NULL) + return gstate; + save->softmask_cs = softmask_cs; + save->page_resources = gstate->softmask_resources; + save->ctm = gstate->softmask_ctm; + save_ctm = gstate->ctm; + + mask_bbox = pdf_xobject_bbox(ctx, softmask); + mask_matrix = pdf_xobject_matrix(ctx, softmask); + + pdf_tos_save(ctx, &pr->tos, tos_save); + + mask_colorspace = gstate->softmask_cs; + if (gstate->luminosity && !mask_colorspace) + mask_colorspace = fz_device_gray(ctx); + + if (gstate->luminosity) + mask_bbox = fz_infinite_rect; + else + { + mask_bbox = fz_transform_rect(mask_bbox, mask_matrix); + mask_bbox = fz_transform_rect(mask_bbox, gstate->softmask_ctm); + } + mask_bbox = fz_intersect_rect(mask_bbox, bbox); + gstate->softmask = NULL; + gstate->softmask_cs = NULL; + gstate->softmask_resources = NULL; + gstate->ctm = gstate->softmask_ctm; + + saved_blendmode = gstate->blendmode; + + fz_try(ctx) + { + if (gstate->softmask_tr) + { + tr = load_transfer_function(ctx, gstate->softmask_tr); + pdf_drop_obj(ctx, gstate->softmask_tr); + gstate->softmask_tr = NULL; + } + + fz_begin_mask(ctx, pr->dev, mask_bbox, gstate->luminosity, mask_colorspace, gstate->softmask_bc, gstate->fill.color_params); + gstate->blendmode = 0; + pdf_run_xobject(ctx, pr, softmask, save->page_resources, fz_identity, 1); + gstate = pr->gstate + pr->gtop; + gstate->blendmode = saved_blendmode; + fz_end_mask_tr(ctx, pr->dev, tr); + } + fz_always(ctx) + { + fz_drop_function(ctx, tr); + } + fz_catch(ctx) + fz_rethrow(ctx); + + pdf_tos_restore(ctx, &pr->tos, tos_save); + + gstate = pr->gstate + pr->gtop; + gstate->ctm = save_ctm; + + return gstate; +} + +static void +end_softmask(fz_context *ctx, pdf_run_processor *pr, softmask_save *save) +{ + pdf_gstate *gstate = pr->gstate + pr->gtop; + + if (save->softmask == NULL) + return; + + gstate->softmask = save->softmask; + gstate->softmask_cs = save->softmask_cs; + gstate->softmask_resources = save->page_resources; + gstate->softmask_ctm = save->ctm; + save->softmask = NULL; + save->page_resources = NULL; + + fz_pop_clip(ctx, pr->dev); +} + +static pdf_gstate * +pdf_begin_group(fz_context *ctx, pdf_run_processor *pr, fz_rect bbox, softmask_save *softmask) +{ + pdf_gstate *gstate = begin_softmask(ctx, pr, softmask, bbox); + + if (gstate->blendmode) + fz_begin_group(ctx, pr->dev, bbox, NULL, 0, 0, gstate->blendmode, 1); + + return pr->gstate + pr->gtop; +} + +static void +pdf_end_group(fz_context *ctx, pdf_run_processor *pr, softmask_save *softmask) +{ + pdf_gstate *gstate = pr->gstate + pr->gtop; + + if (gstate->blendmode) + fz_end_group(ctx, pr->dev); + + end_softmask(ctx, pr, softmask); +} + +static void +pdf_show_shade(fz_context *ctx, pdf_run_processor *pr, fz_shade *shd) +{ + pdf_gstate *gstate = pr->gstate + pr->gtop; + fz_rect bbox; + softmask_save softmask = { NULL }; + + if (pr->super.hidden) + return; + + bbox = fz_bound_shade(ctx, shd, gstate->ctm); + + fz_try(ctx) + { + gstate = pdf_begin_group(ctx, pr, bbox, &softmask); + + /* FIXME: The gstate->ctm in the next line may be wrong; maybe + * it should be the parent gstates ctm? */ + fz_fill_shade(ctx, pr->dev, shd, gstate->ctm, gstate->fill.alpha, gstate->fill.color_params); + + pdf_end_group(ctx, pr, &softmask); + } + fz_catch(ctx) + { + pdf_drop_obj(ctx, softmask.softmask); + fz_drop_colorspace(ctx, softmask.softmask_cs); + pdf_drop_obj(ctx, softmask.page_resources); + fz_rethrow(ctx); + } +} + +static pdf_material * +pdf_keep_material(fz_context *ctx, pdf_material *mat) +{ + if (mat->colorspace) + fz_keep_colorspace(ctx, mat->colorspace); + if (mat->pattern) + pdf_keep_pattern(ctx, mat->pattern); + if (mat->shade) + fz_keep_shade(ctx, mat->shade); + return mat; +} + +static pdf_material * +pdf_drop_material(fz_context *ctx, pdf_material *mat) +{ + fz_drop_colorspace(ctx, mat->colorspace); + pdf_drop_pattern(ctx, mat->pattern); + fz_drop_shade(ctx, mat->shade); + return mat; +} + +static void +pdf_copy_pattern_gstate(fz_context *ctx, pdf_gstate *dst, const pdf_gstate *src) +{ + pdf_font_desc *old_font = dst->text.font; + + dst->ctm = src->ctm; + + dst->text = src->text; + pdf_keep_font(ctx, src->text.font); + pdf_drop_font(ctx, old_font); + + pdf_drop_obj(ctx, dst->softmask); + dst->softmask = pdf_keep_obj(ctx, src->softmask); + + pdf_drop_obj(ctx, dst->softmask_resources); + dst->softmask_resources = pdf_keep_obj(ctx, src->softmask_resources); + + fz_drop_colorspace(ctx, dst->softmask_cs); + dst->softmask_cs = fz_keep_colorspace(ctx, src->softmask_cs); + + fz_drop_stroke_state(ctx, dst->stroke_state); + dst->stroke_state = fz_keep_stroke_state(ctx, src->stroke_state); +} + +static void +pdf_unset_pattern(fz_context *ctx, pdf_run_processor *pr, int what) +{ + pdf_gstate *gs = pr->gstate + pr->gtop; + pdf_material *mat; + mat = what == PDF_FILL ? &gs->fill : &gs->stroke; + if (mat->kind == PDF_MAT_PATTERN) + { + pdf_drop_pattern(ctx, mat->pattern); + mat->pattern = NULL; + mat->kind = PDF_MAT_COLOR; + } +} + +static void +pdf_keep_gstate(fz_context *ctx, pdf_gstate *gs) +{ + pdf_keep_material(ctx, &gs->stroke); + pdf_keep_material(ctx, &gs->fill); + if (gs->text.font) + pdf_keep_font(ctx, gs->text.font); + if (gs->softmask) + pdf_keep_obj(ctx, gs->softmask); + if (gs->softmask_cs) + fz_keep_colorspace(ctx, gs->softmask_cs); + if (gs->softmask_resources) + pdf_keep_obj(ctx, gs->softmask_resources); + fz_keep_stroke_state(ctx, gs->stroke_state); + pdf_keep_obj(ctx, gs->softmask_tr); +} + +static void +pdf_drop_gstate(fz_context *ctx, pdf_gstate *gs) +{ + pdf_drop_material(ctx, &gs->stroke); + pdf_drop_material(ctx, &gs->fill); + pdf_drop_font(ctx, gs->text.font); + pdf_drop_obj(ctx, gs->softmask); + fz_drop_colorspace(ctx, gs->softmask_cs); + pdf_drop_obj(ctx, gs->softmask_resources); + fz_drop_stroke_state(ctx, gs->stroke_state); + pdf_drop_obj(ctx, gs->softmask_tr); +} + +static void +pdf_gsave(fz_context *ctx, pdf_run_processor *pr) +{ + if (pr->gtop == pr->gcap-1) + { + if (pr->gcap * 2 >= 4096) + fz_throw(ctx, FZ_ERROR_LIMIT, "too many nested graphics states"); + + pr->gstate = fz_realloc_array(ctx, pr->gstate, pr->gcap*2, pdf_gstate); + pr->gcap *= 2; + } + + memcpy(&pr->gstate[pr->gtop + 1], &pr->gstate[pr->gtop], sizeof(pdf_gstate)); + + pr->gtop++; + pdf_keep_gstate(ctx, &pr->gstate[pr->gtop]); +} + +static void +pdf_grestore(fz_context *ctx, pdf_run_processor *pr) +{ + pdf_gstate *gs = pr->gstate + pr->gtop; + int clip_depth = gs->clip_depth; + + if (pr->gtop <= pr->gbot) + { + fz_warn(ctx, "gstate underflow in content stream"); + return; + } + + pdf_drop_gstate(ctx, gs); + pr->gtop --; + + gs = pr->gstate + pr->gtop; + while (clip_depth > gs->clip_depth) + { + fz_try(ctx) + { + // End layer early (before Q) if unbalanced Q appears between BMC and EMC. + while (pr->nest_depth > 0 && pr->nest_mark[pr->nest_depth-1] >= 0) + { + fz_end_layer(ctx, pr->dev); + pr->nest_depth--; + } + + if (pr->nest_depth > 0) + { + /* So this one must be a clip record. */ + fz_pop_clip(ctx, pr->dev); + /* Pop a single clip record off. */ + pr->nest_mark[pr->nest_depth-1]++; + if (pr->nest_mark[pr->nest_depth-1] == 0) + pr->nest_depth--; + } + + // End layer late (after Q) if unbalanced EMC appears between q and Q. + while (pr->nest_depth > 0 && pr->nest_mark[pr->nest_depth-1] > pr->mc_depth) + { + fz_end_layer(ctx, pr->dev); + pr->nest_depth--; + } + } + fz_catch(ctx) + { + /* Silently swallow the problem - restores must + * never throw! */ + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); // FIXME - unsure if we can throw here? + fz_report_error(ctx); + } + clip_depth--; + } +} + +static pdf_gstate * +pdf_show_pattern(fz_context *ctx, pdf_run_processor *pr, pdf_pattern *pat, int pat_gstate_num, fz_rect area, int what) +{ + pdf_gstate *gstate; + pdf_gstate *pat_gstate; + int gparent_save; + fz_matrix ptm, invptm, gparent_save_ctm; + int x0, y0, x1, y1; + float fx0, fy0, fx1, fy1; + fz_rect local_area; + int oldbot; + int id; + + pdf_gsave(ctx, pr); + gstate = pr->gstate + pr->gtop; + pat_gstate = pr->gstate + pat_gstate_num; + + /* Patterns are run with the gstate of the parent */ + pdf_copy_pattern_gstate(ctx, gstate, pat_gstate); + + if (pat->ismask) + { + /* Inhibit any changes to the color since we're drawing an uncolored pattern. */ + gstate->ismask = 1; + pdf_unset_pattern(ctx, pr, PDF_FILL); + pdf_unset_pattern(ctx, pr, PDF_STROKE); + if (what == PDF_FILL) + { + pdf_drop_material(ctx, &gstate->stroke); + pdf_keep_material(ctx, &gstate->fill); + gstate->stroke = gstate->fill; + } + if (what == PDF_STROKE) + { + pdf_drop_material(ctx, &gstate->fill); + pdf_keep_material(ctx, &gstate->stroke); + gstate->fill = gstate->stroke; + } + id = 0; /* don't cache uncolored patterns, since we colorize them when drawing */ + } + else + { + // TODO: unset only the current fill/stroke or both? + pdf_unset_pattern(ctx, pr, what); + id = pat->id; + } + + /* don't apply soft masks to objects in the pattern as well */ + if (gstate->softmask) + { + pdf_drop_obj(ctx, gstate->softmask); + gstate->softmask = NULL; + } + + ptm = fz_concat(pat->matrix, pat_gstate->ctm); + invptm = fz_invert_matrix(ptm); + + /* The parent_ctm is amended with our pattern matrix */ + gparent_save = pr->gparent; + pr->gparent = pr->gtop-1; + gparent_save_ctm = pr->gstate[pr->gparent].ctm; + pr->gstate[pr->gparent].ctm = ptm; + + /* patterns are painted using the parent_ctm. area = bbox of + * shape to be filled in device space. Map it back to pattern + * space. */ + local_area = fz_transform_rect(area, invptm); + + fx0 = (local_area.x0 - pat->bbox.x0) / pat->xstep; + fy0 = (local_area.y0 - pat->bbox.y0) / pat->ystep; + fx1 = (local_area.x1 - pat->bbox.x0) / pat->xstep; + fy1 = (local_area.y1 - pat->bbox.y0) / pat->ystep; + if (fx0 > fx1) + { + float t = fx0; fx0 = fx1; fx1 = t; + } + if (fy0 > fy1) + { + float t = fy0; fy0 = fy1; fy1 = t; + } + +#ifdef TILE + /* We have tried various formulations in the past, but this one is + * best we've found; only use it as a tile if a whole repeat is + * required in at least one direction. Note, that this allows for + * 'sections' of 4 tiles to be show, but all non-overlapping. */ + if (fx1-fx0 > 1 || fy1-fy0 > 1) +#else + if (0) +#endif + { + int cached = fz_begin_tile_tid(ctx, pr->dev, local_area, pat->bbox, pat->xstep, pat->ystep, ptm, id, pat->document->super.id); + if (!cached) + { + gstate->ctm = ptm; + + oldbot = pr->gbot; + pr->gbot = pr->gtop; + + pdf_gsave(ctx, pr); + pdf_process_contents(ctx, (pdf_processor*)pr, pat->document, pat->resources, pat->contents, NULL, NULL); + pdf_grestore(ctx, pr); + + while (pr->gtop > pr->gbot) + pdf_grestore(ctx, pr); + pr->gbot = oldbot; + } + fz_end_tile(ctx, pr->dev); + } + else + { + int x, y; + + /* When calculating the number of tiles required, we adjust by + * a small amount to allow for rounding errors. By choosing + * this amount to be smaller than 1/256, we guarantee we won't + * cause problems that will be visible even under our most + * extreme antialiasing. */ + x0 = floorf(fx0 + 0.001f); + y0 = floorf(fy0 + 0.001f); + x1 = ceilf(fx1 - 0.001f); + y1 = ceilf(fy1 - 0.001f); + /* The above adjustments cause problems for sufficiently + * large values for xstep/ystep which may be used if the + * pattern is expected to be rendered exactly once. */ + if (fx1 > fx0 && x1 == x0) + x1 = x0 + 1; + if (fy1 > fy0 && y1 == y0) + y1 = y0 + 1; + + for (y = y0; y < y1; y++) + { + for (x = x0; x < x1; x++) + { + /* Calls to pdf_process_contents may cause the + * gstate array to be realloced to be larger. + * That can invalidate gstate. Hence reload + * it each time round the loop. */ + gstate = pr->gstate + pr->gtop; + gstate->ctm = fz_pre_translate(ptm, x * pat->xstep, y * pat->ystep); + + oldbot = pr->gbot; + pr->gbot = pr->gtop; + + pdf_gsave(ctx, pr); + pdf_process_contents(ctx, (pdf_processor*)pr, pat->document, pat->resources, pat->contents, NULL, NULL); + pdf_grestore(ctx, pr); + + while (pr->gtop > pr->gbot) + pdf_grestore(ctx, pr); + pr->gbot = oldbot; + } + } + } + + pr->gstate[pr->gparent].ctm = gparent_save_ctm; + pr->gparent = gparent_save; + + pdf_grestore(ctx, pr); + + return pr->gstate + pr->gtop; +} + +static void +pdf_show_image_imp(fz_context *ctx, pdf_run_processor *pr, fz_image *image, fz_matrix image_ctm, fz_rect bbox) +{ + pdf_gstate *gstate = pr->gstate + pr->gtop; + fz_color_params cp = gstate->fill.color_params; + + if (image->has_intent) + cp.ri = image->intent; + + if (image->colorspace) + { + fz_fill_image(ctx, pr->dev, image, image_ctm, gstate->fill.alpha, cp); + } + else if (gstate->fill.kind == PDF_MAT_COLOR) + { + fz_fill_image_mask(ctx, pr->dev, image, image_ctm, gstate->fill.colorspace, gstate->fill.v, gstate->fill.alpha, cp); + } + else if (gstate->fill.kind == PDF_MAT_PATTERN && gstate->fill.pattern) + { + fz_clip_image_mask(ctx, pr->dev, image, image_ctm, bbox); + gstate = pdf_show_pattern(ctx, pr, gstate->fill.pattern, gstate->fill.gstate_num, bbox, PDF_FILL); + fz_pop_clip(ctx, pr->dev); + } + else if (gstate->fill.kind == PDF_MAT_SHADE && gstate->fill.shade) + { + fz_clip_image_mask(ctx, pr->dev, image, image_ctm, bbox); + fz_fill_shade(ctx, pr->dev, gstate->fill.shade, pr->gstate[gstate->fill.gstate_num].ctm, gstate->fill.alpha, cp); + fz_pop_clip(ctx, pr->dev); + } +} + +static void +pdf_show_image(fz_context *ctx, pdf_run_processor *pr, fz_image *image) +{ + pdf_gstate *gstate = pr->gstate + pr->gtop; + fz_matrix image_ctm; + fz_rect bbox; + + if (pr->super.hidden) + return; + + /* image can be NULL here if we are, for example, running to an + * stext device. */ + if (image == NULL) + return; + + pop_any_pending_mcid_changes(ctx, pr); + flush_begin_layer(ctx, pr); + + /* PDF has images bottom-up, so flip them right side up here */ + image_ctm = fz_pre_scale(fz_pre_translate(gstate->ctm, 0, 1), 1, -1); + + bbox = fz_transform_rect(fz_unit_rect, image_ctm); + + if (image->mask && gstate->blendmode) + { + /* apply blend group even though we skip the soft mask */ + fz_begin_group(ctx, pr->dev, bbox, NULL, 0, 0, gstate->blendmode, 1); + fz_clip_image_mask(ctx, pr->dev, image->mask, image_ctm, bbox); + pdf_show_image_imp(ctx, pr, image, image_ctm, bbox); + fz_pop_clip(ctx, pr->dev); + fz_end_group(ctx, pr->dev); + } + else if (image->mask) + { + fz_clip_image_mask(ctx, pr->dev, image->mask, image_ctm, bbox); + pdf_show_image_imp(ctx, pr, image, image_ctm, bbox); + fz_pop_clip(ctx, pr->dev); + } + else + { + softmask_save softmask = { NULL }; + fz_try(ctx) + { + gstate = pdf_begin_group(ctx, pr, bbox, &softmask); + pdf_show_image_imp(ctx, pr, image, image_ctm, bbox); + pdf_end_group(ctx, pr, &softmask); + } + fz_catch(ctx) + { + pdf_drop_obj(ctx, softmask.softmask); + fz_drop_colorspace(ctx, softmask.softmask_cs); + pdf_drop_obj(ctx, softmask.page_resources); + fz_rethrow(ctx); + } + } +} + +static void +pdf_show_path(fz_context *ctx, pdf_run_processor *pr, int doclose, int dofill, int dostroke, int even_odd) +{ + pdf_gstate *gstate = pr->gstate + pr->gtop; + fz_path *path; + fz_rect bbox; + softmask_save softmask = { NULL }; + int knockout_group = 0; + + pop_any_pending_mcid_changes(ctx, pr); + flush_begin_layer(ctx, pr); + + if (dostroke) { + if (pr->dev->flags & (FZ_DEVFLAG_STROKECOLOR_UNDEFINED | FZ_DEVFLAG_LINEJOIN_UNDEFINED | FZ_DEVFLAG_LINEWIDTH_UNDEFINED | FZ_DEVFLAG_DASH_PATTERN_UNDEFINED)) + pr->dev->flags |= FZ_DEVFLAG_UNCACHEABLE; + else if (gstate->stroke_state->dash_len != 0 && pr->dev->flags & (FZ_DEVFLAG_STARTCAP_UNDEFINED | FZ_DEVFLAG_DASHCAP_UNDEFINED | FZ_DEVFLAG_ENDCAP_UNDEFINED)) + pr->dev->flags |= FZ_DEVFLAG_UNCACHEABLE; + else if (gstate->stroke_state->linejoin == FZ_LINEJOIN_MITER && (pr->dev->flags & FZ_DEVFLAG_MITERLIMIT_UNDEFINED)) + pr->dev->flags |= FZ_DEVFLAG_UNCACHEABLE; + } + if (dofill) { + if (pr->dev->flags & FZ_DEVFLAG_FILLCOLOR_UNDEFINED) + pr->dev->flags |= FZ_DEVFLAG_UNCACHEABLE; + } + + path = pr->path; + pr->path = fz_new_path(ctx); + + fz_try(ctx) + { + if (doclose) + fz_closepath(ctx, path); + + bbox = fz_bound_path(ctx, path, (dostroke ? gstate->stroke_state : NULL), gstate->ctm); + + if (pr->super.hidden) + dostroke = dofill = 0; + + if (dofill || dostroke) + gstate = pdf_begin_group(ctx, pr, bbox, &softmask); + + if (dofill && dostroke) + { + /* We may need to push a knockout group */ + if (gstate->stroke.alpha == 0) + { + /* No need for group, as stroke won't do anything */ + } + else if (gstate->stroke.alpha == 1.0f && gstate->blendmode == FZ_BLEND_NORMAL) + { + /* No need for group, as stroke won't show up */ + } + else + { + knockout_group = 1; + fz_begin_group(ctx, pr->dev, bbox, NULL, 0, 1, FZ_BLEND_NORMAL, 1); + } + } + + if (dofill) + { + switch (gstate->fill.kind) + { + case PDF_MAT_NONE: + break; + case PDF_MAT_COLOR: + fz_fill_path(ctx, pr->dev, path, even_odd, gstate->ctm, + gstate->fill.colorspace, gstate->fill.v, gstate->fill.alpha, gstate->fill.color_params); + break; + case PDF_MAT_PATTERN: + if (gstate->fill.pattern) + { + fz_clip_path(ctx, pr->dev, path, even_odd, gstate->ctm, bbox); + gstate = pdf_show_pattern(ctx, pr, gstate->fill.pattern, gstate->fill.gstate_num, bbox, PDF_FILL); + fz_pop_clip(ctx, pr->dev); + } + break; + case PDF_MAT_SHADE: + if (gstate->fill.shade) + { + fz_clip_path(ctx, pr->dev, path, even_odd, gstate->ctm, bbox); + /* The cluster and page 2 of patterns.pdf shows that fz_fill_shade should NOT be called with gstate->ctm. */ + fz_fill_shade(ctx, pr->dev, gstate->fill.shade, pr->gstate[gstate->fill.gstate_num].ctm, gstate->fill.alpha, gstate->fill.color_params); + fz_pop_clip(ctx, pr->dev); + } + break; + } + } + + if (dostroke) + { + switch (gstate->stroke.kind) + { + case PDF_MAT_NONE: + break; + case PDF_MAT_COLOR: + fz_stroke_path(ctx, pr->dev, path, gstate->stroke_state, gstate->ctm, + gstate->stroke.colorspace, gstate->stroke.v, gstate->stroke.alpha, gstate->stroke.color_params); + break; + case PDF_MAT_PATTERN: + if (gstate->stroke.pattern) + { + fz_clip_stroke_path(ctx, pr->dev, path, gstate->stroke_state, gstate->ctm, bbox); + gstate = pdf_show_pattern(ctx, pr, gstate->stroke.pattern, gstate->stroke.gstate_num, bbox, PDF_STROKE); + fz_pop_clip(ctx, pr->dev); + } + break; + case PDF_MAT_SHADE: + if (gstate->stroke.shade) + { + fz_clip_stroke_path(ctx, pr->dev, path, gstate->stroke_state, gstate->ctm, bbox); + fz_fill_shade(ctx, pr->dev, gstate->stroke.shade, pr->gstate[gstate->stroke.gstate_num].ctm, gstate->stroke.alpha, gstate->stroke.color_params); + fz_pop_clip(ctx, pr->dev); + } + break; + } + } + + if (knockout_group) + fz_end_group(ctx, pr->dev); + + if (dofill || dostroke) + pdf_end_group(ctx, pr, &softmask); + + if (pr->clip) + { + nest_layer_clip(ctx, pr); + gstate->clip_depth++; + fz_clip_path(ctx, pr->dev, path, pr->clip_even_odd, gstate->ctm, bbox); + pr->clip = 0; + } + } + fz_always(ctx) + { + fz_drop_path(ctx, path); + } + fz_catch(ctx) + { + pdf_drop_obj(ctx, softmask.softmask); + fz_drop_colorspace(ctx, softmask.softmask_cs); + pdf_drop_obj(ctx, softmask.page_resources); + fz_rethrow(ctx); + } +} + +/* + * Assemble and emit text + */ + +static pdf_gstate * +pdf_flush_text(fz_context *ctx, pdf_run_processor *pr) +{ + pdf_gstate *gstate = pr->gstate + pr->gtop; + fz_text *text; + int dofill; + int dostroke; + int doclip; + int doinvisible; + softmask_save softmask = { NULL }; + int knockout_group = 0; + + text = pdf_tos_get_text(ctx, &pr->tos); + if (!text) + return gstate; + + pop_any_pending_mcid_changes(ctx, pr); + /* If we are going to output text, we need to have flushed any begin layers first. */ + flush_begin_layer(ctx, pr); + + dofill = dostroke = doclip = doinvisible = 0; + switch (pr->tos.text_mode) + { + case 0: dofill = 1; break; + case 1: dostroke = 1; break; + case 2: dofill = dostroke = 1; break; + case 3: doinvisible = 1; break; + case 4: dofill = doclip = 1; break; + case 5: dostroke = doclip = 1; break; + case 6: dofill = dostroke = doclip = 1; break; + case 7: doclip = 1; break; + } + + if (pr->super.hidden) + dostroke = dofill = 0; + + fz_try(ctx) + { + fz_rect tb = fz_transform_rect(pr->tos.text_bbox, gstate->ctm); + if (dostroke) + tb = fz_adjust_rect_for_stroke(ctx, tb, gstate->stroke_state, gstate->ctm); + + /* Don't bother sending a text group with nothing in it */ + if (!text->head) + break; + + if (dofill || dostroke) + gstate = pdf_begin_group(ctx, pr, tb, &softmask); + + if (dofill && dostroke) + { + /* We may need to push a knockout group */ + if (gstate->stroke.alpha == 0) + { + /* No need for group, as stroke won't do anything */ + } + else if (gstate->stroke.alpha == 1.0f && gstate->blendmode == FZ_BLEND_NORMAL) + { + /* No need for group, as stroke won't show up */ + } + else + { + knockout_group = 1; + fz_begin_group(ctx, pr->dev, tb, NULL, 0, 1, FZ_BLEND_NORMAL, 1); + } + } + + if (doinvisible) + fz_ignore_text(ctx, pr->dev, text, gstate->ctm); + + if (dofill) + { + switch (gstate->fill.kind) + { + case PDF_MAT_NONE: + break; + case PDF_MAT_COLOR: + fz_fill_text(ctx, pr->dev, text, gstate->ctm, + gstate->fill.colorspace, gstate->fill.v, gstate->fill.alpha, gstate->fill.color_params); + break; + case PDF_MAT_PATTERN: + if (gstate->fill.pattern) + { + fz_clip_text(ctx, pr->dev, text, gstate->ctm, tb); + gstate = pdf_show_pattern(ctx, pr, gstate->fill.pattern, gstate->fill.gstate_num, tb, PDF_FILL); + fz_pop_clip(ctx, pr->dev); + } + break; + case PDF_MAT_SHADE: + if (gstate->fill.shade) + { + fz_clip_text(ctx, pr->dev, text, gstate->ctm, tb); + /* Page 2 of patterns.pdf shows that fz_fill_shade should NOT be called with gstate->ctm */ + fz_fill_shade(ctx, pr->dev, gstate->fill.shade, pr->gstate[gstate->fill.gstate_num].ctm, gstate->fill.alpha, gstate->fill.color_params); + fz_pop_clip(ctx, pr->dev); + } + break; + } + } + + if (dostroke) + { + switch (gstate->stroke.kind) + { + case PDF_MAT_NONE: + break; + case PDF_MAT_COLOR: + fz_stroke_text(ctx, pr->dev, text, gstate->stroke_state, gstate->ctm, + gstate->stroke.colorspace, gstate->stroke.v, gstate->stroke.alpha, gstate->stroke.color_params); + break; + case PDF_MAT_PATTERN: + if (gstate->stroke.pattern) + { + fz_clip_stroke_text(ctx, pr->dev, text, gstate->stroke_state, gstate->ctm, tb); + gstate = pdf_show_pattern(ctx, pr, gstate->stroke.pattern, gstate->stroke.gstate_num, tb, PDF_STROKE); + fz_pop_clip(ctx, pr->dev); + } + break; + case PDF_MAT_SHADE: + if (gstate->stroke.shade) + { + fz_clip_stroke_text(ctx, pr->dev, text, gstate->stroke_state, gstate->ctm, tb); + fz_fill_shade(ctx, pr->dev, gstate->stroke.shade, pr->gstate[gstate->stroke.gstate_num].ctm, gstate->stroke.alpha, gstate->stroke.color_params); + fz_pop_clip(ctx, pr->dev); + } + break; + } + } + + if (knockout_group) + fz_end_group(ctx, pr->dev); + + if (dofill || dostroke) + pdf_end_group(ctx, pr, &softmask); + + if (doclip) + { + nest_layer_clip(ctx, pr); + gstate->clip_depth++; + fz_clip_text(ctx, pr->dev, text, gstate->ctm, tb); + } + } + fz_always(ctx) + { + fz_drop_text(ctx, text); + } + fz_catch(ctx) + { + pdf_drop_obj(ctx, softmask.softmask); + fz_drop_colorspace(ctx, softmask.softmask_cs); + pdf_drop_obj(ctx, softmask.page_resources); + fz_rethrow(ctx); + } + + return pr->gstate + pr->gtop; +} + +static int +guess_bidi_level(int bidiclass, int cur_bidi) +{ + switch (bidiclass) + { + /* strong */ + case UCDN_BIDI_CLASS_L: return 0; + case UCDN_BIDI_CLASS_R: return 1; + case UCDN_BIDI_CLASS_AL: return 1; + + /* weak */ + case UCDN_BIDI_CLASS_EN: + case UCDN_BIDI_CLASS_ES: + case UCDN_BIDI_CLASS_ET: + return 0; + case UCDN_BIDI_CLASS_AN: + return 1; + case UCDN_BIDI_CLASS_CS: + case UCDN_BIDI_CLASS_NSM: + case UCDN_BIDI_CLASS_BN: + return cur_bidi; + + /* neutral */ + case UCDN_BIDI_CLASS_B: + case UCDN_BIDI_CLASS_S: + case UCDN_BIDI_CLASS_WS: + case UCDN_BIDI_CLASS_ON: + return cur_bidi; + + /* embedding, override, pop ... we don't support them */ + default: + return 0; + } +} + +static void +pdf_show_char(fz_context *ctx, pdf_run_processor *pr, int cid, fz_text_language lang) +{ + pdf_gstate *gstate = pr->gstate + pr->gtop; + pdf_font_desc *fontdesc = gstate->text.font; + fz_matrix trm; + float adv; + int gid; + int ucsbuf[PDF_MRANGE_CAP]; + int ucslen; + int i; + int render_direct; + int type3_hitr; + + gid = pdf_tos_make_trm(ctx, &pr->tos, &gstate->text, fontdesc, cid, &trm, &adv); + + /* If we are uncachable, then render direct. */ + render_direct = !fz_glyph_cacheable(ctx, fontdesc->font, gid); + + /* PDF spec: ISO 32000-2 latest version at the time of writing: + * Section 9.3.6: + * Where text is drawn using a Type 3 font: + * + if text rendering mode is set to a value of 3 or 7, the text shall not be rendered. + * + if text rendering mode is set to a value other than 3 or 7, the text shall be rendered using the glyph descriptions in the Type 3 font. + * + If text rendering mode is set to a value of 4, 5, 6 or 7, nothing shall be added to the clipping path. + */ + type3_hitr = (fontdesc->font->t3procs && pr->tos.text_mode >= 4); + + /* flush buffered text if rendermode has changed */ + if (!pr->tos.text || gstate->text.render != pr->tos.text_mode || render_direct || type3_hitr) + { + gstate = pdf_flush_text(ctx, pr); + pdf_tos_reset(ctx, &pr->tos, gstate->text.render); + } + + /* If Type3 and tr >= 4, then ignore the clipping path part. */ + if (type3_hitr) + pr->tos.text_mode -= 4; + + if (render_direct && pr->tos.text_mode != 3 /* or 7, by type3_hitr */) + { + /* Render the glyph stream direct here (only happens for + * type3 glyphs that seem to inherit current graphics + * attributes, or type 3 glyphs within type3 glyphs). */ + fz_matrix composed = fz_concat(trm, gstate->ctm); + /* Whatever problems the underlying char has is no concern of + * ours. Store the flags, restore them afterwards. */ + int old_flags = pr->dev->flags; + pdf_gstate *fill_gstate = NULL; + pdf_gstate *stroke_gstate = NULL; + pdf_gsave(ctx, pr); + gstate = pr->gstate + pr->gtop; + if (gstate->fill.kind == PDF_MAT_PATTERN && gstate->fill.gstate_num >= 0) + fill_gstate = pr->gstate + gstate->fill.gstate_num; + if (gstate->stroke.kind == PDF_MAT_PATTERN && gstate->stroke.gstate_num >= 0) + stroke_gstate = pr->gstate + gstate->stroke.gstate_num; + pdf_drop_font(ctx, gstate->text.font); + gstate->text.font = NULL; /* don't inherit the current font... */ + fz_render_t3_glyph_direct(ctx, pr->dev, fontdesc->font, gid, composed, gstate, pr->default_cs, fill_gstate, stroke_gstate); + pr->dev->flags = old_flags; + pdf_grestore(ctx, pr); + /* Render text invisibly so that it can still be extracted. */ + pr->tos.text_mode = 3; + } + + ucslen = 0; + if (fontdesc->to_unicode) + ucslen = pdf_lookup_cmap_full(fontdesc->to_unicode, cid, ucsbuf); + + /* convert ascii whitespace control characters to spaces */ + if (ucslen == 1 && (ucsbuf[0] >= 8 && ucsbuf[0] <= 13)) + ucsbuf[0] = ' '; + + /* ignore obviously bad values in ToUnicode, fall back to the cid_to_ucs table */ + if (ucslen == 1 && (ucsbuf[0] < 32 || (ucsbuf[0] >= 127 && ucsbuf[0] < 160))) + ucslen = 0; + + if (ucslen == 0 && (size_t)cid < fontdesc->cid_to_ucs_len) + { + ucsbuf[0] = fontdesc->cid_to_ucs[cid]; + ucslen = 1; + } + if (ucslen == 0 || (ucslen == 1 && ucsbuf[0] == 0)) + { + ucsbuf[0] = FZ_REPLACEMENT_CHARACTER; + ucslen = 1; + } + + /* guess bidi level from unicode value */ + pr->bidi = guess_bidi_level(ucdn_get_bidi_class(ucsbuf[0]), pr->bidi); + + /* add glyph to textobject */ + fz_show_glyph_aux(ctx, pr->tos.text, fontdesc->font, trm, adv, gid, ucsbuf[0], cid, fontdesc->wmode, pr->bidi, FZ_BIDI_NEUTRAL, lang); + + /* add filler glyphs for one-to-many unicode mapping */ + for (i = 1; i < ucslen; i++) + fz_show_glyph_aux(ctx, pr->tos.text, fontdesc->font, trm, 0, -1, ucsbuf[i], -1, fontdesc->wmode, pr->bidi, FZ_BIDI_NEUTRAL, lang); + + pdf_tos_move_after_char(ctx, &pr->tos); +} + +static void +pdf_show_space(fz_context *ctx, pdf_run_processor *pr, float tadj) +{ + pdf_gstate *gstate = pr->gstate + pr->gtop; + pdf_font_desc *fontdesc = gstate->text.font; + + if (fontdesc->wmode == 0) + pr->tos.tm = fz_pre_translate(pr->tos.tm, tadj * gstate->text.scale, 0); + else + pr->tos.tm = fz_pre_translate(pr->tos.tm, 0, tadj); +} + +static int +int_in_singleton_or_array(fz_context *ctx, pdf_obj *k, int id) +{ + /* In the most common case the /K value will be id. */ + if (pdf_is_int(ctx, k) && pdf_to_int(ctx, k) == id) + return 1; + + /* In the next most common case, there will be an array of + * items, one of which is k. */ + if (pdf_is_array(ctx, k)) + { + int i, n = pdf_array_len(ctx, k); + + for (i = 0; i < n; i++) + { + pdf_obj *o = pdf_array_get(ctx, k, i); + if (pdf_is_int(ctx, o) && pdf_to_int(ctx, o) == id) + return 1; + } + } + + return 0; +} + +pdf_obj * +pdf_lookup_mcid_in_mcids(fz_context *ctx, int id, pdf_obj *mcids) +{ + pdf_obj *mcid = pdf_array_get(ctx, mcids, id); + pdf_obj *k = pdf_dict_get(ctx, mcid, PDF_NAME(K)); + int i, n; + + if (int_in_singleton_or_array(ctx, k, id)) + return mcid; + + /* At this point, something has gone wrong. One common case that + * appears to fail is where the MCIDs array has the right things + * in, but at the wrong indexes. So do some searching. */ + n = pdf_array_len(ctx, mcids); + for (i = 0; i < n; i++) + { + pdf_obj *o = pdf_array_get(ctx, mcids, i); + if (int_in_singleton_or_array(ctx, pdf_dict_get(ctx, o, PDF_NAME(K)), id)) + return o; + } + + return NULL; +} + +static pdf_obj * +lookup_mcid(fz_context *ctx, pdf_run_processor *proc, pdf_obj *val) +{ + pdf_obj *mcid; + int id; + pdf_obj *mcids; + + if (proc->struct_parent == -1) + return NULL; + + mcid = pdf_dict_get(ctx, val, PDF_NAME(MCID)); + if (!mcid) + return NULL; + + if (!pdf_is_number(ctx, mcid)) + return NULL; + + id = pdf_to_int(ctx, mcid); + mcids = pdf_lookup_number(ctx, pdf_dict_getl(ctx, pdf_trailer(ctx, proc->doc), PDF_NAME(Root), PDF_NAME(StructTreeRoot), PDF_NAME(ParentTree), NULL), proc->struct_parent); + return pdf_lookup_mcid_in_mcids(ctx, id, mcids); +} + +static fz_text_language +find_lang_from_mc(fz_context *ctx, pdf_run_processor *pr) +{ + marked_content_stack *mc; + + for (mc = pr->marked_content; mc != NULL; mc = mc->next) + { + size_t len; + const char *lang; + + lang = pdf_dict_get_string(ctx, mc->val, PDF_NAME(Lang), &len); + if (!lang) + lang = pdf_dict_get_string(ctx, lookup_mcid(ctx, pr, mc->val), PDF_NAME(Lang), &len); + if (lang) + { + char text[8]; + memcpy(text, lang, len < 8 ? len : 7); + text[len < 8 ? len : 7] = 0; + return fz_text_language_from_string(text); + } + } + + return FZ_LANG_UNSET; +} + +static void +show_string(fz_context *ctx, pdf_run_processor *pr, unsigned char *buf, size_t len) +{ + pdf_gstate *gstate = pr->gstate + pr->gtop; + pdf_font_desc *fontdesc = gstate->text.font; + unsigned char *end = buf + len; + unsigned int cpt; + int cid; + fz_text_language lang = find_lang_from_mc(ctx, pr); + + pop_any_pending_mcid_changes(ctx, pr); + flush_begin_layer(ctx, pr); + + while (buf < end) + { + int w = pdf_decode_cmap(fontdesc->encoding, buf, end, &cpt); + buf += w; + + cid = pdf_lookup_cmap(fontdesc->encoding, cpt); + if (cid >= 0) + pdf_show_char(ctx, pr, cid, lang); + else + fz_warn(ctx, "cannot encode character"); + if (cpt == 32 && w == 1) + { + /* Bug 703151: pdf_show_char can realloc gstate. */ + gstate = pr->gstate + pr->gtop; + pdf_show_space(ctx, pr, gstate->text.word_space); + } + } +} + +static void +pdf_show_string(fz_context *ctx, pdf_run_processor *pr, unsigned char *buf, size_t len) +{ + pdf_gstate *gstate = pr->gstate + pr->gtop; + pdf_font_desc *fontdesc = gstate->text.font; + + if (!fontdesc) + { + fz_warn(ctx, "cannot draw text since font and size not set"); + return; + } + + show_string(ctx, pr, buf, len); +} + +static void +pdf_show_text(fz_context *ctx, pdf_run_processor *pr, pdf_obj *text) +{ + pdf_gstate *gstate = pr->gstate + pr->gtop; + pdf_font_desc *fontdesc = gstate->text.font; + int i; + + if (!fontdesc) + { + fz_warn(ctx, "cannot draw text since font and size not set"); + return; + } + + if (pdf_is_array(ctx, text)) + { + int n = pdf_array_len(ctx, text); + for (i = 0; i < n; i++) + { + pdf_obj *item = pdf_array_get(ctx, text, i); + if (pdf_is_string(ctx, item)) + show_string(ctx, pr, (unsigned char *)pdf_to_str_buf(ctx, item), pdf_to_str_len(ctx, item)); + else + pdf_show_space(ctx, pr, - pdf_to_real(ctx, item) * gstate->text.size * 0.001f); + } + } + else if (pdf_is_string(ctx, text)) + { + pdf_show_string(ctx, pr, (unsigned char *)pdf_to_str_buf(ctx, text), pdf_to_str_len(ctx, text)); + } +} + +/* + * Interpreter and graphics state stack. + */ + +static void +pdf_init_gstate(fz_context *ctx, pdf_gstate *gs, fz_matrix ctm) +{ + gs->ctm = ctm; + gs->clip_depth = 0; + + gs->stroke_state = fz_new_stroke_state(ctx); + + gs->stroke.kind = PDF_MAT_COLOR; + gs->stroke.colorspace = fz_keep_colorspace(ctx, fz_device_gray(ctx)); + gs->stroke.v[0] = 0; + gs->stroke.pattern = NULL; + gs->stroke.shade = NULL; + gs->stroke.alpha = 1; + gs->stroke.gstate_num = -1; + + gs->fill.kind = PDF_MAT_COLOR; + gs->fill.colorspace = fz_keep_colorspace(ctx, fz_device_gray(ctx)); + gs->fill.v[0] = 0; + gs->fill.pattern = NULL; + gs->fill.shade = NULL; + gs->fill.alpha = 1; + gs->fill.gstate_num = -1; + + gs->text.char_space = 0; + gs->text.word_space = 0; + gs->text.scale = 1; + gs->text.leading = 0; + gs->text.font = NULL; + gs->text.size = -1; + gs->text.render = 0; + gs->text.rise = 0; + + gs->blendmode = 0; + gs->softmask = NULL; + gs->softmask_cs = NULL; + gs->softmask_resources = NULL; + gs->softmask_ctm = fz_identity; + gs->luminosity = 0; + + gs->fill.color_params = fz_default_color_params; + gs->stroke.color_params = fz_default_color_params; + + gs->ismask = 0; +} + +static void +pdf_copy_gstate(fz_context *ctx, pdf_gstate *dst, pdf_gstate *src) +{ + pdf_drop_gstate(ctx, dst); + *dst = *src; + pdf_keep_gstate(ctx, dst); +} + +/* + * Material state + */ + +static void +pdf_set_colorspace(fz_context *ctx, pdf_run_processor *pr, int what, fz_colorspace *colorspace) +{ + pdf_gstate *gstate = pr->gstate + pr->gtop; + pdf_material *mat; + int n = fz_colorspace_n(ctx, colorspace); + + gstate = pdf_flush_text(ctx, pr); + + /* Don't change color if we're drawing an uncolored pattern tile! */ + if (gstate->ismask) + return; + + mat = what == PDF_FILL ? &gstate->fill : &gstate->stroke; + + fz_drop_colorspace(ctx, mat->colorspace); + + mat->kind = PDF_MAT_COLOR; + mat->colorspace = fz_keep_colorspace(ctx, colorspace); + + mat->v[0] = 0; + mat->v[1] = 0; + mat->v[2] = 0; + mat->v[3] = 1; + + if (pdf_is_tint_colorspace(ctx, colorspace)) + { + int i; + for (i = 0; i < n; i++) + mat->v[i] = 1.0f; + } +} + +static void +pdf_set_color(fz_context *ctx, pdf_run_processor *pr, int what, float *v) +{ + pdf_gstate *gstate = pr->gstate + pr->gtop; + pdf_material *mat; + + gstate = pdf_flush_text(ctx, pr); + + /* Don't change color if we're drawing an uncolored pattern tile! */ + if (gstate->ismask) + return; + + mat = what == PDF_FILL ? &gstate->fill : &gstate->stroke; + + switch (mat->kind) + { + case PDF_MAT_PATTERN: + case PDF_MAT_COLOR: + fz_clamp_color(ctx, mat->colorspace, v, mat->v); + break; + default: + fz_warn(ctx, "color incompatible with material"); + } + + mat->gstate_num = pr->gparent; +} + +static void +pdf_set_shade(fz_context *ctx, pdf_run_processor *pr, int what, fz_shade *shade) +{ + pdf_gstate *gs; + pdf_material *mat; + + gs = pdf_flush_text(ctx, pr); + + mat = what == PDF_FILL ? &gs->fill : &gs->stroke; + + fz_drop_shade(ctx, mat->shade); + + mat->kind = PDF_MAT_SHADE; + mat->shade = fz_keep_shade(ctx, shade); + + mat->gstate_num = pr->gparent; +} + +static void +pdf_set_pattern(fz_context *ctx, pdf_run_processor *pr, int what, pdf_pattern *pat, float *v) +{ + pdf_gstate *gs; + pdf_material *mat; + + gs = pdf_flush_text(ctx, pr); + + mat = what == PDF_FILL ? &gs->fill : &gs->stroke; + + pdf_drop_pattern(ctx, mat->pattern); + mat->pattern = NULL; + + mat->kind = PDF_MAT_PATTERN; + if (pat) + mat->pattern = pdf_keep_pattern(ctx, pat); + + if (v) + pdf_set_color(ctx, pr, what, v); + + mat->gstate_num = pr->gparent; +} + +static void +begin_metatext(fz_context *ctx, pdf_run_processor *proc, pdf_obj *val, pdf_obj *mcid, fz_metatext meta, pdf_obj *name) +{ + pdf_obj *text = pdf_dict_get(ctx, val, name); + + if (!text) + text = pdf_dict_get(ctx, mcid, name); + if (!text) + return; + + pdf_flush_text(ctx, proc); + + fz_begin_metatext(ctx, proc->dev, meta, pdf_to_text_string(ctx, text)); +} + +static void +end_metatext(fz_context *ctx, pdf_run_processor *proc, pdf_obj *val, pdf_obj *mcid, pdf_obj *name) +{ + pdf_obj *text = pdf_dict_get(ctx, val, name); + + if (!text) + text = pdf_dict_get(ctx, mcid, name); + if (!text) + return; + + pdf_flush_text(ctx, proc); + + fz_end_metatext(ctx, proc->dev); +} + +static void +begin_oc(fz_context *ctx, pdf_run_processor *proc, pdf_obj *val, pdf_cycle_list *cycle_up) +{ + /* val has been resolved to a dict for us by the originally specified name + * having been looked up in Properties already for us. Either there will + * be a Name entry, or there will be an OCGs and it'll be a group one. */ + pdf_cycle_list cycle; + pdf_obj *obj; + int i, n; + + if (pdf_cycle(ctx, &cycle, cycle_up, val)) + return; + + obj = pdf_dict_get(ctx, val, PDF_NAME(Name)); + if (obj) + { + const char *name = ""; + pdf_flush_text(ctx, proc); + if (pdf_is_name(ctx, obj)) + name = pdf_to_name(ctx, obj); + else if (pdf_is_string(ctx, obj)) + name = pdf_to_text_string(ctx, obj); + + push_begin_layer(ctx, proc, name); + return; + } + + obj = pdf_dict_get(ctx, val, PDF_NAME(OCGs)); + n = pdf_array_len(ctx, obj); + for (i = 0; i < n; i++) + { + begin_oc(ctx, proc, pdf_array_get(ctx, obj, i), &cycle); + } +} + +static void +end_oc(fz_context *ctx, pdf_run_processor *proc, pdf_obj *val, pdf_cycle_list *cycle_up) +{ + /* val has been resolved to a dict for us by the originally specified name + * having been looked up in Properties already for us. Either there will + * be a Name entry, or there will be an OCGs and it'll be a group one. */ + pdf_cycle_list cycle; + pdf_obj *obj; + int i, n; + + if (pdf_cycle(ctx, &cycle, cycle_up, val)) + return; + + obj = pdf_dict_get(ctx, val, PDF_NAME(Name)); + if (obj) + { + flush_begin_layer(ctx, proc); + do_end_layer(ctx, proc); + return; + } + + obj = pdf_dict_get(ctx, val, PDF_NAME(OCGs)); + n = pdf_array_len(ctx, obj); + for (i = n-1; i >= 0; i--) + { + end_oc(ctx, proc, pdf_array_get(ctx, obj, i), &cycle); + } +} + +static void +begin_layer(fz_context *ctx, pdf_run_processor *proc, pdf_obj *val) +{ + /* val has been resolved to a dict for us by the originally specified name + * having been looked up in Properties already for us. Go with the 'Title' + * entry. */ + pdf_obj *obj = pdf_dict_get(ctx, val, PDF_NAME(Title)); + if (obj) + { + pdf_flush_text(ctx, proc); + push_begin_layer(ctx, proc, pdf_to_text_string(ctx, obj)); + } +} + +static void +end_layer(fz_context *ctx, pdf_run_processor *proc, pdf_obj *val) +{ + /* val has been resolved to a dict for us by the originally specified name + * having been looked up in Properties already for us. Go with the 'Title' + * entry. */ + pdf_obj *obj = pdf_dict_get(ctx, val, PDF_NAME(Title)); + if (obj) + { + do_end_layer(ctx, proc); + } +} + +#ifdef DEBUG_STRUCTURE +static void +structure_dump(fz_context *ctx, const char *str, pdf_obj *obj) +{ + fprintf(stderr, "%s STACK=", str); + + if (obj == NULL) + { + fprintf(stderr, "empty\n"); + return; + } + + do + { + pdf_obj *s = pdf_dict_get(ctx, obj, PDF_NAME(S)); + int n = pdf_to_num(ctx, obj); + fprintf(stderr, " %d", n); + if (s) + fprintf(stderr, "[%s]", pdf_to_name(ctx, s)); + obj = pdf_dict_get(ctx, obj, PDF_NAME(P)); + } + while (obj); + fprintf(stderr, "\n"); +} +#endif + +static void +pop_structure_to(fz_context *ctx, pdf_run_processor *proc, pdf_obj *common) +{ + pdf_obj *struct_tree_root = pdf_dict_getl(ctx, pdf_trailer(ctx, proc->doc), PDF_NAME(Root), PDF_NAME(StructTreeRoot), NULL); + +#ifdef DEBUG_STRUCTURE + structure_dump(ctx, "pop_structure_to (before)", proc->mcid_sent); + + { + int n = pdf_to_num(ctx, common); + fprintf(stderr, "Popping until %d\n", n); + } +#endif + + while (proc->mcid_sent != NULL && pdf_objcmp(ctx, proc->mcid_sent, common)) + { + pdf_obj *p = pdf_dict_get(ctx, proc->mcid_sent, PDF_NAME(P)); + pdf_obj *tag = pdf_dict_get(ctx, proc->mcid_sent, PDF_NAME(S)); + fz_structure standard = pdf_structure_type(ctx, proc->role_map, tag); +#ifdef DEBUG_STRUCTURE + fprintf(stderr, "sending pop [tag=%s][std=%d]\n", pdf_to_name(ctx, tag) ? pdf_to_name(ctx, tag) : "null", standard); +#endif + if (standard != FZ_STRUCTURE_INVALID) + fz_end_structure(ctx, proc->dev); + pdf_drop_obj(ctx, proc->mcid_sent); + proc->mcid_sent = pdf_keep_obj(ctx, p); + if (!pdf_objcmp(ctx, p, struct_tree_root)) + { + pdf_drop_obj(ctx, proc->mcid_sent); + proc->mcid_sent = NULL; + break; + } + } +#ifdef DEBUG_STRUCTURE + structure_dump(ctx, "pop_structure_to (after)", proc->mcid_sent); +#endif +} + +static void +pop_any_pending_mcid_changes(fz_context *ctx, pdf_run_processor *pr) +{ + if (pr->pending_mcid_pop == NULL) + return; + + pop_structure_to(ctx, pr, pr->pending_mcid_pop); + pr->pending_mcid_pop = NULL; +} + +struct line +{ + pdf_obj *obj; + struct line *child; +}; + +static pdf_obj * +find_most_recent_common_ancestor_imp(fz_context *ctx, pdf_obj *a, struct line *line_a, pdf_obj *b, struct line *line_b, pdf_cycle_list *cycle_up_a, pdf_cycle_list *cycle_up_b) +{ + struct line line; + pdf_obj *common = NULL; + pdf_cycle_list cycle; + pdf_obj *parent; + + /* First ascend one lineage. */ + if (pdf_is_dict(ctx, a)) + { + if (pdf_cycle(ctx, &cycle, cycle_up_a, a)) + fz_throw(ctx, FZ_ERROR_FORMAT, "cycle in structure tree"); + line.obj = a; + line.child = line_a; + parent = pdf_dict_get(ctx, a, PDF_NAME(P)); + return find_most_recent_common_ancestor_imp(ctx, parent, &line, b, NULL, &cycle, NULL); + } + /* Then ascend the other lineage. */ + else if (pdf_is_dict(ctx, b)) + { + if (pdf_cycle(ctx, &cycle, cycle_up_b, b)) + fz_throw(ctx, FZ_ERROR_FORMAT, "cycle in structure tree"); + line.obj = b; + line.child = line_b; + parent = pdf_dict_get(ctx, b, PDF_NAME(P)); + return find_most_recent_common_ancestor_imp(ctx, a, line_a, parent, &line, cycle_up_a, &cycle); + } + + /* Once both lineages are know, traverse top-down to find most recent common ancestor. */ + while (line_a && line_b && !pdf_objcmp(ctx, line_a->obj, line_b->obj)) + { + common = line_a->obj; + line_a = line_a->child; + line_b = line_b->child; + } + return common; +} + +static pdf_obj * +find_most_recent_common_ancestor(fz_context *ctx, pdf_obj *a, pdf_obj *b) +{ + if (!pdf_is_dict(ctx, a) || !pdf_is_dict(ctx, b)) + return NULL; + return find_most_recent_common_ancestor_imp(ctx, a, NULL, b, NULL, NULL, NULL); +} + +static int +get_struct_index(fz_context *ctx, pdf_obj *send) +{ + pdf_obj *p = pdf_dict_get(ctx, send, PDF_NAME(P)); + pdf_obj *k; + int i, n; + + if (p == NULL) + return 0; /* Presumably the StructTreeRoot */ + + /* So, get the kids array. */ + k = pdf_dict_get(ctx, p, PDF_NAME(K)); + n = pdf_array_len(ctx, k); + if (n == 0) + { + /* Not an array, presumably a singleton. */ + if (pdf_objcmp(ctx, k, send) == 0) + return 0; + return -1; + } + for (i = 0; i < n; i++) + { + if (pdf_objcmp(ctx, pdf_array_get(ctx, k, i), send) == 0) + return i; + } + return -1; +} + +static int +send_begin_structure(fz_context *ctx, pdf_run_processor *proc, pdf_obj *mc_dict) +{ + pdf_obj *common = NULL; + +#ifdef DEBUG_STRUCTURE + fprintf(stderr, "send_begin_structure %d\n", pdf_to_num(ctx, mc_dict)); + structure_dump(ctx, "on entry", proc->mcid_sent); +#endif + + /* We are currently nested in A,B,C,...E,F,mcid_sent. We want to update to + * being in A,B,C,...G,H,mc_dict. So we need to find the lowest common point. */ + common = find_most_recent_common_ancestor(ctx, proc->mcid_sent, mc_dict); + + /* So, we need to pop everything up to common (i.e. everything below common will be closed). */ + pop_structure_to(ctx, proc, common); + +#ifdef DEBUG_STRUCTURE + structure_dump(ctx, "after popping", proc->mcid_sent); +#endif + /* Now we need to send everything between common (proc->mcid_sent) and mc_dict. + * Again, n^2 will do... */ + while (pdf_objcmp(ctx, proc->mcid_sent, mc_dict)) + { + pdf_obj *send = mc_dict; + fz_structure standard; + pdf_obj *tag; + int idx; + pdf_obj *slowptr = send; + int slow = 0; + + /* Run up the ancestor stack, looking for the first child of mcid_sent. + * That's the one we need to send next. */ + while (1) { + pdf_obj *p = pdf_dict_get(ctx, send, PDF_NAME(P)); + + /* If we ever fail to find a dict, then do not step down lest + * we can't get back later! */ + if (!pdf_is_dict(ctx, send)) + { + fz_warn(ctx, "Bad parent link in structure tree. Ignoring structure."); + proc->broken_struct_tree = 1; + return 0; + } + /* If p is the one we last sent, then we want to send 'send' + * next. Exit the loop. */ + if (!pdf_objcmp(ctx, p, proc->mcid_sent)) + break; + + /* We need to go at least one step further up the stack. */ + send = p; + + /* Check for a loop in the parent tree. */ + slow ^= 1; + if (slow == 0) + slowptr = pdf_dict_get(ctx, slowptr, PDF_NAME(P)); + if (!pdf_objcmp(ctx, send, slowptr)) + { + fz_warn(ctx, "Loop found in structure tree. Ignoring structure."); + proc->broken_struct_tree = 1; + return 0; + } + } + + idx = get_struct_index(ctx, send); + tag = pdf_dict_get(ctx, send, PDF_NAME(S)); + standard = pdf_structure_type(ctx, proc->role_map, tag); +#ifdef DEBUG_STRUCTURE + fprintf(stderr, "sending %d[idx=%d][tag=%s][std=%d]\n", pdf_to_num(ctx, send), idx, pdf_to_name(ctx, tag) ? pdf_to_name(ctx, tag) : "null", standard); +#endif + if (standard != FZ_STRUCTURE_INVALID) + fz_begin_structure(ctx, proc->dev, standard, pdf_to_name(ctx, tag), idx); + + pdf_drop_obj(ctx, proc->mcid_sent); + proc->mcid_sent = pdf_keep_obj(ctx, send); + } +#ifdef DEBUG_STRUCTURE + structure_dump(ctx, "on exit", proc->mcid_sent); +#endif + + return 1; +} + +static void +push_marked_content(fz_context *ctx, pdf_run_processor *proc, const char *tagstr, pdf_obj *val) +{ + pdf_obj *tag; + marked_content_stack *mc = NULL; + int drop_tag = 1; + pdf_obj *mc_dict = NULL; + + /* Ignore any pending pops. */ + proc->pending_mcid_pop = NULL; + + /* Flush any pending text so it's not in the wrong layer. */ + pdf_flush_text(ctx, proc); + + if (!tagstr) + tagstr = "Untitled"; + tag = pdf_new_name(ctx, tagstr); + + fz_var(drop_tag); + + fz_try(ctx) + { + /* First, push it on the stack. */ + mc = fz_malloc_struct(ctx, marked_content_stack); + mc->next = proc->marked_content; + mc->tag = tag; + mc->val = pdf_keep_obj(ctx, val); + mc->structure_pushed = 0; + proc->marked_content = mc; + drop_tag = 0; + + /* Check to see if val contains an MCID. */ + mc_dict = lookup_mcid(ctx, proc, val); + + /* Start any optional content layers. */ + if (pdf_name_eq(ctx, tag, PDF_NAME(OC))) + begin_oc(ctx, proc, val, NULL); + + /* Special handling for common non-spec extension. */ + if (pdf_name_eq(ctx, tag, PDF_NAME(Layer))) + begin_layer(ctx, proc, val); + + /* Structure */ + if (mc_dict && !proc->broken_struct_tree) + { + fz_try(ctx) + mc->structure_pushed = send_begin_structure(ctx, proc, mc_dict); + fz_catch(ctx) + { + fz_report_error(ctx); + fz_warn(ctx, "structure tree broken, assume tree is missing"); + proc->broken_struct_tree = 1; + } + } + + /* Previously, I'd tried to send stuff like: + * /Artifact <</Type/Pagination>>BDC + * as a structure entry, lured by the fact that 'Artifact' is a + * structure tag. I now believe this is wrong. Only stuff with + * an MCID pointer should be sent using the structure mechanism. + */ + + /* ActualText */ + begin_metatext(ctx, proc, val, mc_dict, FZ_METATEXT_ACTUALTEXT, PDF_NAME(ActualText)); + + /* Alt */ + begin_metatext(ctx, proc, val, mc_dict, FZ_METATEXT_ALT, PDF_NAME(Alt)); + + /* Abbreviation */ + begin_metatext(ctx, proc, val, mc_dict, FZ_METATEXT_ABBREVIATION, PDF_NAME(E)); + + /* Title */ + begin_metatext(ctx, proc, val, mc_dict, FZ_METATEXT_TITLE, PDF_NAME(T)); + } + fz_catch(ctx) + { + if (drop_tag) + pdf_drop_obj(ctx, tag); + fz_rethrow(ctx); + } +} + +static void +pop_marked_content(fz_context *ctx, pdf_run_processor *proc, int neat) +{ + marked_content_stack *mc = proc->marked_content; + pdf_obj *val, *tag; + pdf_obj *mc_dict = NULL; + int pushed; + + if (mc == NULL) + return; + + proc->marked_content = mc->next; + tag = mc->tag; + val = mc->val; + pushed = mc->structure_pushed; + fz_free(ctx, mc); + + /* If we're not interested in neatly closing any open layers etc + * in the processor, (maybe we've had errors already), then just + * exit here. */ + if (!neat) + { + pdf_drop_obj(ctx, tag); + pdf_drop_obj(ctx, val); + return; + } + + /* Close structure/layers here, in reverse order to how we opened them. */ + fz_try(ctx) + { + /* Make sure that any pending text is written into the correct layer. */ + pdf_flush_text(ctx, proc); + + /* Check to see if val contains an MCID. */ + mc_dict = lookup_mcid(ctx, proc, val); + + /* Title */ + end_metatext(ctx, proc, val, mc_dict, PDF_NAME(T)); + + /* Abbreviation */ + end_metatext(ctx, proc, val, mc_dict, PDF_NAME(E)); + + /* Alt */ + end_metatext(ctx, proc, val, mc_dict, PDF_NAME(Alt)); + + /* ActualText */ + end_metatext(ctx, proc, val, mc_dict, PDF_NAME(ActualText)); + + /* Structure */ + if (mc_dict && !proc->broken_struct_tree && pushed) + { + /* Is there a nested mc_dict? If so we want to pop back to that. + * If not, we want to pop back to the top. + * proc->marked_content = the previous one, but maybe not the + * previous one with an mc_dict. So we may need to search further. + */ + pdf_obj *previous_mcid = NULL; + marked_content_stack *mc_with_mcid = proc->marked_content; + while (mc_with_mcid) + { + previous_mcid = lookup_mcid(ctx, proc, mc_with_mcid->val); + if (previous_mcid != NULL) + break; + mc_with_mcid = mc_with_mcid->next; + } + + proc->pending_mcid_pop = previous_mcid; + } + + /* Finally, close any layers. */ + if (pdf_name_eq(ctx, tag, PDF_NAME(Layer))) + end_layer(ctx, proc, val); + + if (pdf_name_eq(ctx, tag, PDF_NAME(OC))) + end_oc(ctx, proc, val, NULL); + } + fz_always(ctx) + { + pdf_drop_obj(ctx, tag); + pdf_drop_obj(ctx, val); + } + fz_catch(ctx) + fz_rethrow(ctx); +} + +static void +clear_marked_content(fz_context *ctx, pdf_run_processor *pr) +{ + if (pr->marked_content == NULL) + return; + + fz_try(ctx) + while (pr->marked_content) + pop_marked_content(ctx, pr, 1); + fz_always(ctx) + while (pr->marked_content) + pop_marked_content(ctx, pr, 0); + fz_catch(ctx) + fz_rethrow(ctx); +} + +static void +pdf_run_xobject(fz_context *ctx, pdf_run_processor *pr, pdf_obj *xobj, pdf_obj *page_resources, fz_matrix transform, int is_smask) +{ + pdf_cycle_list cycle_here; + pdf_gstate *gstate = NULL; + int oldtop = 0; + int oldbot = -1; + softmask_save softmask = { NULL }; + int gparent_save; + fz_matrix gparent_save_ctm; + pdf_obj *resources; + fz_rect xobj_bbox; + fz_matrix xobj_matrix; + int transparency = 0; + pdf_document *doc; + fz_colorspace *cs = NULL; + fz_default_colorspaces *save_default_cs = NULL; + fz_default_colorspaces *xobj_default_cs = NULL; + marked_content_stack *save_marked_content = NULL; + int save_struct_parent; + pdf_obj *oc; + + /* Avoid infinite recursion */ + pdf_cycle_list *cycle_up = pr->cycle; + if (xobj == NULL || pdf_cycle(ctx, &cycle_here, cycle_up, xobj)) + return; + pr->cycle = &cycle_here; + + pop_any_pending_mcid_changes(ctx, pr); + flush_begin_layer(ctx, pr); + + fz_var(cs); + fz_var(xobj_default_cs); + + gparent_save = pr->gparent; + pr->gparent = pr->gtop; + oldtop = pr->gtop; + + save_default_cs = pr->default_cs; + save_marked_content = pr->marked_content; + pr->marked_content = NULL; + save_struct_parent = pr->struct_parent; + + fz_try(ctx) + { + pr->struct_parent = pdf_dict_get_int_default(ctx, xobj, PDF_NAME(StructParent), -1); + + oc = pdf_dict_get(ctx, xobj, PDF_NAME(OC)); + if (oc) + begin_oc(ctx, pr, oc, NULL); + + pdf_gsave(ctx, pr); + + gstate = pr->gstate + pr->gtop; + + xobj_bbox = pdf_xobject_bbox(ctx, xobj); + xobj_matrix = pdf_xobject_matrix(ctx, xobj); + transparency = pdf_xobject_transparency(ctx, xobj); + + /* apply xobject's transform matrix */ + transform = fz_concat(xobj_matrix, transform); + gstate->ctm = fz_concat(transform, gstate->ctm); + + /* The gparent is updated with the modified ctm */ + gparent_save_ctm = pr->gstate[pr->gparent].ctm; + pr->gstate[pr->gparent].ctm = gstate->ctm; + + /* apply soft mask, create transparency group and reset state */ + if (transparency) + { + int isolated = pdf_xobject_isolated(ctx, xobj); + + fz_rect bbox = fz_transform_rect(xobj_bbox, gstate->ctm); + + gstate = begin_softmask(ctx, pr, &softmask, bbox); + + if (isolated) + cs = pdf_xobject_colorspace(ctx, xobj); + fz_begin_group(ctx, pr->dev, bbox, + cs, + (is_smask ? 1 : isolated), + pdf_xobject_knockout(ctx, xobj), + gstate->blendmode, gstate->fill.alpha); + + gstate->blendmode = 0; + gstate->stroke.alpha = 1; + gstate->fill.alpha = 1; + } + + pdf_gsave(ctx, pr); /* Save here so the clippath doesn't persist */ + + /* clip to the bounds */ + fz_moveto(ctx, pr->path, xobj_bbox.x0, xobj_bbox.y0); + fz_lineto(ctx, pr->path, xobj_bbox.x1, xobj_bbox.y0); + fz_lineto(ctx, pr->path, xobj_bbox.x1, xobj_bbox.y1); + fz_lineto(ctx, pr->path, xobj_bbox.x0, xobj_bbox.y1); + fz_closepath(ctx, pr->path); + pr->clip = 1; + pdf_show_path(ctx, pr, 0, 0, 0, 0); + + /* run contents */ + + resources = pdf_xobject_resources(ctx, xobj); + if (!resources) + resources = page_resources; + + fz_try(ctx) + xobj_default_cs = pdf_update_default_colorspaces(ctx, pr->default_cs, resources); + fz_catch(ctx) + { + fz_rethrow_unless(ctx, FZ_ERROR_TRYLATER); + fz_ignore_error(ctx); + if (pr->cookie) + pr->cookie->incomplete = 1; + } + if (xobj_default_cs != save_default_cs) + { + fz_set_default_colorspaces(ctx, pr->dev, xobj_default_cs); + pr->default_cs = xobj_default_cs; + } + + doc = pdf_get_bound_document(ctx, xobj); + + oldbot = pr->gbot; + pr->gbot = pr->gtop; + + pdf_process_contents(ctx, (pdf_processor*)pr, doc, resources, xobj, pr->cookie, NULL); + + /* Undo any gstate mismatches due to the pdf_process_contents call */ + if (oldbot != -1) + { + while (pr->gtop > pr->gbot) + { + pdf_grestore(ctx, pr); + } + pr->gbot = oldbot; + } + + pdf_grestore(ctx, pr); /* Remove the state we pushed for the clippath */ + + /* wrap up transparency stacks */ + if (transparency) + { + fz_end_group(ctx, pr->dev); + end_softmask(ctx, pr, &softmask); + } + + pr->gstate[pr->gparent].ctm = gparent_save_ctm; + pr->gparent = gparent_save; + + while (oldtop < pr->gtop) + pdf_grestore(ctx, pr); + + if (oc) + end_oc(ctx, pr, oc, NULL); + + if (xobj_default_cs != save_default_cs) + { + fz_set_default_colorspaces(ctx, pr->dev, save_default_cs); + } + } + fz_always(ctx) + { + clear_marked_content(ctx, pr); + pr->marked_content = save_marked_content; + pr->default_cs = save_default_cs; + fz_drop_default_colorspaces(ctx, xobj_default_cs); + fz_drop_colorspace(ctx, cs); + pr->cycle = cycle_up; + pr->struct_parent = save_struct_parent; + } + fz_catch(ctx) + { + pdf_drop_obj(ctx, softmask.softmask); + fz_drop_colorspace(ctx, softmask.softmask_cs); + pdf_drop_obj(ctx, softmask.page_resources); + /* Note: Any SYNTAX errors should have been swallowed + * by pdf_process_contents, but in case any escape from other + * functions, recast the error type here to be safe. */ + fz_morph_error(ctx, FZ_ERROR_SYNTAX, FZ_ERROR_FORMAT); + fz_rethrow(ctx); + } +} + +/* general graphics state */ + +static void pdf_run_w(fz_context *ctx, pdf_processor *proc, float linewidth) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pdf_flush_text(ctx, pr); + + pop_any_pending_mcid_changes(ctx, pr); + flush_begin_layer(ctx, pr); + + pr->dev->flags &= ~FZ_DEVFLAG_LINEWIDTH_UNDEFINED; + gstate->stroke_state = fz_unshare_stroke_state(ctx, gstate->stroke_state); + gstate->stroke_state->linewidth = linewidth; +} + +static void pdf_run_j(fz_context *ctx, pdf_processor *proc, int linejoin) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pdf_flush_text(ctx, pr); + + pr->dev->flags &= ~FZ_DEVFLAG_LINEJOIN_UNDEFINED; + gstate->stroke_state = fz_unshare_stroke_state(ctx, gstate->stroke_state); + gstate->stroke_state->linejoin = linejoin; +} + +static void pdf_run_J(fz_context *ctx, pdf_processor *proc, int linecap) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pdf_flush_text(ctx, pr); + + pr->dev->flags &= ~(FZ_DEVFLAG_STARTCAP_UNDEFINED | FZ_DEVFLAG_DASHCAP_UNDEFINED | FZ_DEVFLAG_ENDCAP_UNDEFINED); + gstate->stroke_state = fz_unshare_stroke_state(ctx, gstate->stroke_state); + gstate->stroke_state->start_cap = linecap; + gstate->stroke_state->dash_cap = linecap; + gstate->stroke_state->end_cap = linecap; +} + +static void pdf_run_M(fz_context *ctx, pdf_processor *proc, float miterlimit) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pdf_flush_text(ctx, pr); + + pr->dev->flags &= ~FZ_DEVFLAG_MITERLIMIT_UNDEFINED; + gstate->stroke_state = fz_unshare_stroke_state(ctx, gstate->stroke_state); + gstate->stroke_state->miterlimit = miterlimit; +} + +static void pdf_run_d(fz_context *ctx, pdf_processor *proc, pdf_obj *array, float phase) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pdf_flush_text(ctx, pr); + int len, i; + + pr->dev->flags &= ~FZ_DEVFLAG_DASH_PATTERN_UNDEFINED; + len = pdf_array_len(ctx, array); + gstate->stroke_state = fz_unshare_stroke_state_with_dash_len(ctx, gstate->stroke_state, len); + for (i = 0; i < len; i++) + gstate->stroke_state->dash_list[i] = pdf_array_get_real(ctx, array, i); + gstate->stroke_state->dash_phase = phase; +} + +static void pdf_run_ri(fz_context *ctx, pdf_processor *proc, const char *intent) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pdf_flush_text(ctx, pr); + gstate->fill.color_params.ri = fz_lookup_rendering_intent(intent); + gstate->stroke.color_params.ri = gstate->fill.color_params.ri; +} + +static void pdf_run_gs_OP(fz_context *ctx, pdf_processor *proc, int b) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pdf_flush_text(ctx, pr); + gstate->stroke.color_params.op = b; + gstate->fill.color_params.op = b; +} + +static void pdf_run_gs_op(fz_context *ctx, pdf_processor *proc, int b) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pdf_flush_text(ctx, pr); + gstate->fill.color_params.op = b; +} + +static void pdf_run_gs_OPM(fz_context *ctx, pdf_processor *proc, int i) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pdf_flush_text(ctx, pr); + gstate->stroke.color_params.opm = i; + gstate->fill.color_params.opm = i; +} + +static void pdf_run_gs_UseBlackPtComp(fz_context *ctx, pdf_processor *proc, pdf_obj *obj) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pdf_flush_text(ctx, pr); + int on = pdf_name_eq(ctx, obj, PDF_NAME(ON)); + /* The spec says that "ON" means on, "OFF" means "Off", and + * "Default" or anything else means "Meh, do what you want." */ + gstate->stroke.color_params.bp = on; + gstate->fill.color_params.bp = on; +} + +static void pdf_run_i(fz_context *ctx, pdf_processor *proc, float flatness) +{ +} + +static void pdf_run_gs_begin(fz_context *ctx, pdf_processor *proc, const char *name, pdf_obj *extgstate) +{ +} + +static void pdf_run_gs_end(fz_context *ctx, pdf_processor *proc) +{ +} + +/* transparency graphics state */ + +static void pdf_run_gs_BM(fz_context *ctx, pdf_processor *proc, const char *blendmode) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pdf_flush_text(ctx, pr); + gstate->blendmode = fz_lookup_blendmode(blendmode); +} + +static void pdf_run_gs_CA(fz_context *ctx, pdf_processor *proc, float alpha) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pdf_flush_text(ctx, pr); + gstate->stroke.alpha = fz_clamp(alpha, 0, 1); +} + +static void pdf_run_gs_ca(fz_context *ctx, pdf_processor *proc, float alpha) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pdf_flush_text(ctx, pr); + gstate->fill.alpha = fz_clamp(alpha, 0, 1); +} + +static void pdf_run_gs_SMask(fz_context *ctx, pdf_processor *proc, pdf_obj *smask, fz_colorspace *smask_cs, float *bc, int luminosity, pdf_obj *tr) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pdf_flush_text(ctx, pr); + int i; + + if (gstate->softmask) + { + pdf_drop_obj(ctx, gstate->softmask); + gstate->softmask = NULL; + fz_drop_colorspace(ctx, gstate->softmask_cs); + gstate->softmask_cs = NULL; + pdf_drop_obj(ctx, gstate->softmask_resources); + gstate->softmask_resources = NULL; + } + + if (smask) + { + int cs_n = fz_colorspace_n(ctx, smask_cs); + gstate->softmask_ctm = gstate->ctm; + gstate->softmask = pdf_keep_obj(ctx, smask); + gstate->softmask_cs = fz_keep_colorspace(ctx, smask_cs); + gstate->softmask_resources = pdf_keep_obj(ctx, pr->rstack->resources); + pdf_drop_obj(ctx, gstate->softmask_tr); + gstate->softmask_tr = NULL; + if (tr) + gstate->softmask_tr = pdf_keep_obj(ctx, tr); + for (i = 0; i < cs_n; ++i) + gstate->softmask_bc[i] = bc[i]; + gstate->luminosity = luminosity; + } +} + +/* special graphics state */ + +static void pdf_run_q(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + flush_begin_layer(ctx, pr); + pdf_gsave(ctx, pr); +} + +static void pdf_run_Q(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_flush_text(ctx, pr); + pdf_grestore(ctx, pr); +} + +static void pdf_run_cm(fz_context *ctx, pdf_processor *proc, float a, float b, float c, float d, float e, float f) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pdf_flush_text(ctx, pr); + fz_matrix m; + + m.a = a; + m.b = b; + m.c = c; + m.d = d; + m.e = e; + m.f = f; + gstate->ctm = fz_concat(m, gstate->ctm); +} + +/* path construction */ + +static void pdf_run_m(fz_context *ctx, pdf_processor *proc, float x, float y) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + fz_moveto(ctx, pr->path, x, y); +} + +static void pdf_run_l(fz_context *ctx, pdf_processor *proc, float x, float y) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + fz_lineto(ctx, pr->path, x, y); +} +static void pdf_run_c(fz_context *ctx, pdf_processor *proc, float x1, float y1, float x2, float y2, float x3, float y3) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + fz_curveto(ctx, pr->path, x1, y1, x2, y2, x3, y3); +} + +static void pdf_run_v(fz_context *ctx, pdf_processor *proc, float x2, float y2, float x3, float y3) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + fz_curvetov(ctx, pr->path, x2, y2, x3, y3); +} + +static void pdf_run_y(fz_context *ctx, pdf_processor *proc, float x1, float y1, float x3, float y3) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + fz_curvetoy(ctx, pr->path, x1, y1, x3, y3); +} + +static void pdf_run_h(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + fz_closepath(ctx, pr->path); +} + +static void pdf_run_re(fz_context *ctx, pdf_processor *proc, float x, float y, float w, float h) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + fz_rectto(ctx, pr->path, x, y, x+w, y+h); +} + +/* path painting */ + +static void pdf_run_S(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_show_path(ctx, pr, 0, 0, 1, 0); +} + +static void pdf_run_s(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_show_path(ctx, pr, 1, 0, 1, 0); +} + +static void pdf_run_F(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_show_path(ctx, pr, 0, 1, 0, 0); +} + +static void pdf_run_f(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_show_path(ctx, pr, 0, 1, 0, 0); +} + +static void pdf_run_fstar(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_show_path(ctx, pr, 0, 1, 0, 1); +} + +static void pdf_run_B(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_show_path(ctx, pr, 0, 1, 1, 0); +} + +static void pdf_run_Bstar(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_show_path(ctx, pr, 0, 1, 1, 1); +} + +static void pdf_run_b(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_show_path(ctx, pr, 1, 1, 1, 0); +} + +static void pdf_run_bstar(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_show_path(ctx, pr, 1, 1, 1, 1); +} + +static void pdf_run_n(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_show_path(ctx, pr, 0, 0, 0, 0); +} + +/* clipping paths */ + +static void pdf_run_W(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_flush_text(ctx, pr); + pr->clip = 1; + pr->clip_even_odd = 0; +} + +static void pdf_run_Wstar(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_flush_text(ctx, pr); + pr->clip = 1; + pr->clip_even_odd = 1; +} + +/* text objects */ + +static void pdf_run_BT(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pr->tos.tm = fz_identity; + pr->tos.tlm = fz_identity; + pr->bidi = 0; +} + +static void pdf_run_ET(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_flush_text(ctx, pr); +} + +/* text state */ + +static void pdf_run_Tc(fz_context *ctx, pdf_processor *proc, float charspace) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pr->gstate + pr->gtop; + gstate->text.char_space = charspace; +} + +static void pdf_run_Tw(fz_context *ctx, pdf_processor *proc, float wordspace) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pr->gstate + pr->gtop; + gstate->text.word_space = wordspace; +} + +static void pdf_run_Tz(fz_context *ctx, pdf_processor *proc, float scale) +{ + /* scale is as written in the file. It is 100 times smaller in + * the gstate. */ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pr->gstate + pr->gtop; + gstate->text.scale = scale / 100; +} + +static void pdf_run_TL(fz_context *ctx, pdf_processor *proc, float leading) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pr->gstate + pr->gtop; + gstate->text.leading = leading; +} + +static void pdf_run_Tf(fz_context *ctx, pdf_processor *proc, const char *name, pdf_font_desc *font, float size) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pr->gstate + pr->gtop; + pdf_drop_font(ctx, gstate->text.font); + gstate->text.font = pdf_keep_font(ctx, font); + gstate->text.size = size; +} + +static void pdf_run_Tr(fz_context *ctx, pdf_processor *proc, int render) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pr->gstate + pr->gtop; + gstate->text.render = render; +} + +static void pdf_run_Ts(fz_context *ctx, pdf_processor *proc, float rise) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pr->gstate + pr->gtop; + gstate->text.rise = rise; +} + +/* text positioning */ + +static void pdf_run_Td(fz_context *ctx, pdf_processor *proc, float tx, float ty) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_tos_translate(&pr->tos, tx, ty); +} + +static void pdf_run_TD(fz_context *ctx, pdf_processor *proc, float tx, float ty) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pr->gstate + pr->gtop; + gstate->text.leading = -ty; + pdf_tos_translate(&pr->tos, tx, ty); +} + +static void pdf_run_Tm(fz_context *ctx, pdf_processor *proc, float a, float b, float c, float d, float e, float f) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_tos_set_matrix(&pr->tos, a, b, c, d, e, f); +} + +static void pdf_run_Tstar(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pr->gstate + pr->gtop; + pdf_tos_newline(&pr->tos, gstate->text.leading); +} + +/* text showing */ + +static void pdf_run_TJ(fz_context *ctx, pdf_processor *proc, pdf_obj *obj) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_show_text(ctx, pr, obj); +} + +static void pdf_run_Tj(fz_context *ctx, pdf_processor *proc, char *string, size_t string_len) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_show_string(ctx, pr, (unsigned char *)string, string_len); +} + +static void pdf_run_squote(fz_context *ctx, pdf_processor *proc, char *string, size_t string_len) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pr->gstate + pr->gtop; + pdf_tos_newline(&pr->tos, gstate->text.leading); + pdf_show_string(ctx, pr, (unsigned char*)string, string_len); +} + +static void pdf_run_dquote(fz_context *ctx, pdf_processor *proc, float aw, float ac, char *string, size_t string_len) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_gstate *gstate = pr->gstate + pr->gtop; + gstate->text.word_space = aw; + gstate->text.char_space = ac; + pdf_tos_newline(&pr->tos, gstate->text.leading); + pdf_show_string(ctx, pr, (unsigned char*)string, string_len); +} + +/* type 3 fonts */ + +static void pdf_run_d0(fz_context *ctx, pdf_processor *proc, float wx, float wy) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pr->dev->flags |= FZ_DEVFLAG_COLOR; +} + +static void pdf_run_d1(fz_context *ctx, pdf_processor *proc, float wx, float wy, float llx, float lly, float urx, float ury) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pr->dev->flags |= FZ_DEVFLAG_MASK | FZ_DEVFLAG_BBOX_DEFINED; + pr->dev->d1_rect.x0 = fz_min(llx, urx); + pr->dev->d1_rect.y0 = fz_min(lly, ury); + pr->dev->d1_rect.x1 = fz_max(llx, urx); + pr->dev->d1_rect.y1 = fz_max(lly, ury); +} + +/* color */ + +static void pdf_run_CS(fz_context *ctx, pdf_processor *proc, const char *name, fz_colorspace *colorspace) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pr->dev->flags &= ~FZ_DEVFLAG_STROKECOLOR_UNDEFINED; + if (pr->dev->flags & FZ_DEVFLAG_MASK) + return; + if (!strcmp(name, "Pattern")) + pdf_set_pattern(ctx, pr, PDF_STROKE, NULL, NULL); + else + pdf_set_colorspace(ctx, pr, PDF_STROKE, colorspace); +} + +static void pdf_run_cs(fz_context *ctx, pdf_processor *proc, const char *name, fz_colorspace *colorspace) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pr->dev->flags &= ~FZ_DEVFLAG_FILLCOLOR_UNDEFINED; + if (pr->dev->flags & FZ_DEVFLAG_MASK) + return; + if (!strcmp(name, "Pattern")) + pdf_set_pattern(ctx, pr, PDF_FILL, NULL, NULL); + else + pdf_set_colorspace(ctx, pr, PDF_FILL, colorspace); +} + +static void pdf_run_SC_color(fz_context *ctx, pdf_processor *proc, int n, float *color) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pr->dev->flags &= ~FZ_DEVFLAG_STROKECOLOR_UNDEFINED; + if (pr->dev->flags & FZ_DEVFLAG_MASK) + return; + pdf_set_color(ctx, pr, PDF_STROKE, color); +} + +static void pdf_run_sc_color(fz_context *ctx, pdf_processor *proc, int n, float *color) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pr->dev->flags &= ~FZ_DEVFLAG_FILLCOLOR_UNDEFINED; + if (pr->dev->flags & FZ_DEVFLAG_MASK) + return; + pdf_set_color(ctx, pr, PDF_FILL, color); +} + +static void pdf_run_SC_pattern(fz_context *ctx, pdf_processor *proc, const char *name, pdf_pattern *pat, int n, float *color) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pr->dev->flags &= ~FZ_DEVFLAG_STROKECOLOR_UNDEFINED; + if (pr->dev->flags & FZ_DEVFLAG_MASK) + return; + pdf_set_pattern(ctx, pr, PDF_STROKE, pat, color); +} + +static void pdf_run_sc_pattern(fz_context *ctx, pdf_processor *proc, const char *name, pdf_pattern *pat, int n, float *color) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pr->dev->flags &= ~FZ_DEVFLAG_FILLCOLOR_UNDEFINED; + if (pr->dev->flags & FZ_DEVFLAG_MASK) + return; + pdf_set_pattern(ctx, pr, PDF_FILL, pat, color); +} + +static void pdf_run_SC_shade(fz_context *ctx, pdf_processor *proc, const char *name, fz_shade *shade) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pr->dev->flags &= ~FZ_DEVFLAG_STROKECOLOR_UNDEFINED; + if (pr->dev->flags & FZ_DEVFLAG_MASK) + return; + pdf_set_shade(ctx, pr, PDF_STROKE, shade); +} + +static void pdf_run_sc_shade(fz_context *ctx, pdf_processor *proc, const char *name, fz_shade *shade) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pr->dev->flags &= ~FZ_DEVFLAG_FILLCOLOR_UNDEFINED; + if (pr->dev->flags & FZ_DEVFLAG_MASK) + return; + pdf_set_shade(ctx, pr, PDF_FILL, shade); +} + +static void pdf_run_G(fz_context *ctx, pdf_processor *proc, float g) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pr->dev->flags &= ~FZ_DEVFLAG_STROKECOLOR_UNDEFINED; + if (pr->dev->flags & FZ_DEVFLAG_MASK) + return; + pdf_set_colorspace(ctx, pr, PDF_STROKE, fz_device_gray(ctx)); + pdf_set_color(ctx, pr, PDF_STROKE, &g); +} + +static void pdf_run_g(fz_context *ctx, pdf_processor *proc, float g) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pr->dev->flags &= ~FZ_DEVFLAG_FILLCOLOR_UNDEFINED; + if (pr->dev->flags & FZ_DEVFLAG_MASK) + return; + pdf_set_colorspace(ctx, pr, PDF_FILL, fz_device_gray(ctx)); + pdf_set_color(ctx, pr, PDF_FILL, &g); +} + +static void pdf_run_K(fz_context *ctx, pdf_processor *proc, float c, float m, float y, float k) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + float color[4] = {c, m, y, k}; + pr->dev->flags &= ~FZ_DEVFLAG_STROKECOLOR_UNDEFINED; + if (pr->dev->flags & FZ_DEVFLAG_MASK) + return; + pdf_set_colorspace(ctx, pr, PDF_STROKE, fz_device_cmyk(ctx)); + pdf_set_color(ctx, pr, PDF_STROKE, color); +} + +static void pdf_run_k(fz_context *ctx, pdf_processor *proc, float c, float m, float y, float k) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + float color[4] = {c, m, y, k}; + pr->dev->flags &= ~FZ_DEVFLAG_FILLCOLOR_UNDEFINED; + if (pr->dev->flags & FZ_DEVFLAG_MASK) + return; + pdf_set_colorspace(ctx, pr, PDF_FILL, fz_device_cmyk(ctx)); + pdf_set_color(ctx, pr, PDF_FILL, color); +} + +static void pdf_run_RG(fz_context *ctx, pdf_processor *proc, float r, float g, float b) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + float color[3] = {r, g, b}; + pr->dev->flags &= ~FZ_DEVFLAG_STROKECOLOR_UNDEFINED; + if (pr->dev->flags & FZ_DEVFLAG_MASK) + return; + pdf_set_colorspace(ctx, pr, PDF_STROKE, fz_device_rgb(ctx)); + pdf_set_color(ctx, pr, PDF_STROKE, color); +} + +static void pdf_run_rg(fz_context *ctx, pdf_processor *proc, float r, float g, float b) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + float color[3] = {r, g, b}; + pr->dev->flags &= ~FZ_DEVFLAG_FILLCOLOR_UNDEFINED; + if (pr->dev->flags & FZ_DEVFLAG_MASK) + return; + pdf_set_colorspace(ctx, pr, PDF_FILL, fz_device_rgb(ctx)); + pdf_set_color(ctx, pr, PDF_FILL, color); +} + +/* shadings, images, xobjects */ + +static void pdf_run_BI(fz_context *ctx, pdf_processor *proc, fz_image *image, const char *colorspace) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_show_image(ctx, pr, image); +} + +static void pdf_run_sh(fz_context *ctx, pdf_processor *proc, const char *name, fz_shade *shade) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + + pop_any_pending_mcid_changes(ctx, pr); + flush_begin_layer(ctx, pr); + pdf_show_shade(ctx, pr, shade); +} + +static void pdf_run_Do_image(fz_context *ctx, pdf_processor *proc, const char *name, fz_image *image) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_show_image(ctx, pr, image); +} + +static void pdf_run_Do_form(fz_context *ctx, pdf_processor *proc, const char *name, pdf_obj *xobj) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_run_xobject(ctx, (pdf_run_processor*)proc, xobj, pr->rstack->resources, fz_identity, 0); +} + +/* marked content */ + +static void pdf_run_BMC(fz_context *ctx, pdf_processor *proc, const char *tag) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + push_marked_content(ctx, pr, tag, NULL); +} + +static void pdf_run_BDC(fz_context *ctx, pdf_processor *proc, const char *tag, pdf_obj *raw, pdf_obj *cooked) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + push_marked_content(ctx, pr, tag, cooked); +} + +static void pdf_run_EMC(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + + pop_marked_content(ctx, pr, 1); +} + +static void pdf_run_MP(fz_context *ctx, pdf_processor *proc, const char *tag) +{ + pdf_run_BMC(ctx, proc, tag); + pdf_run_EMC(ctx, proc); +} + +static void pdf_run_DP(fz_context *ctx, pdf_processor *proc, const char *tag, pdf_obj *raw, pdf_obj *cooked) +{ + pdf_run_BDC(ctx, proc, tag, raw, cooked); + pdf_run_EMC(ctx, proc); +} + +/* compatibility */ + +static void pdf_run_BX(fz_context *ctx, pdf_processor *proc) +{ +} + +static void pdf_run_EX(fz_context *ctx, pdf_processor *proc) +{ +} + +static void pdf_run_END(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + pdf_flush_text(ctx, pr); +} + +static void +pdf_close_run_processor(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + + while (pr->gtop) + pdf_grestore(ctx, pr); + + while (pr->nest_depth > 0) + { + if (pr->nest_mark[pr->nest_depth-1] < 0) + { + /* It's a clip. */ + fz_pop_clip(ctx, pr->dev); + pr->nest_mark[pr->nest_depth-1]++; + if (pr->nest_mark[pr->nest_depth-1] == 0) + pr->nest_depth--; + } + else + { + /* It's a layer. */ + fz_end_layer(ctx, pr->dev); + pr->nest_depth--; + } + } + + pop_structure_to(ctx, pr, NULL); + + clear_marked_content(ctx, pr); +} + +static void +pdf_drop_run_processor(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + + while (pr->gtop >= 0) + { + pdf_drop_gstate(ctx, &pr->gstate[pr->gtop]); + pr->gtop--; + } + + fz_drop_path(ctx, pr->path); + fz_drop_text(ctx, pr->tos.text); + + fz_drop_default_colorspaces(ctx, pr->default_cs); + + fz_free(ctx, pr->gstate); + + while (pr->rstack) + { + resources_stack *stk = pr->rstack; + pr->rstack = stk->next; + pdf_drop_obj(ctx, stk->resources); + fz_free(ctx, stk); + } + + while (pr->begin_layer) + { + begin_layer_stack *stk = pr->begin_layer; + pr->begin_layer = stk->next; + fz_free(ctx, stk->layer); + fz_free(ctx, stk); + } + + while (pr->marked_content) + pop_marked_content(ctx, pr, 0); + + pdf_drop_obj(ctx, pr->mcid_sent); + + pdf_drop_document(ctx, pr->doc); + pdf_drop_obj(ctx, pr->role_map); +} + +static void +pdf_run_push_resources(fz_context *ctx, pdf_processor *proc, pdf_obj *resources) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + resources_stack *stk = fz_malloc_struct(ctx, resources_stack); + + stk->next = pr->rstack; + pr->rstack = stk; + stk->resources = pdf_keep_obj(ctx, resources); +} + +static pdf_obj * +pdf_run_pop_resources(fz_context *ctx, pdf_processor *proc) +{ + pdf_run_processor *pr = (pdf_run_processor *)proc; + resources_stack *stk = pr->rstack; + + if (stk) + { + pr->rstack = stk->next; + pdf_drop_obj(ctx, stk->resources); + fz_free(ctx, stk); + } + + return NULL; +} + +/* + Create a new "run" processor. This maps + from PDF operators to fz_device level calls. + + dev: The device to which the resulting device calls are to be + sent. + + ctm: The initial transformation matrix to use. + + usage: A NULL terminated string that describes the 'usage' of + this interpretation. Typically 'View', though 'Print' is also + defined within the PDF reference manual, and others are possible. + + gstate: The initial graphics state. +*/ +pdf_processor * +pdf_new_run_processor(fz_context *ctx, pdf_document *doc, fz_device *dev, fz_matrix ctm, int struct_parent, const char *usage, pdf_gstate *gstate, fz_default_colorspaces *default_cs, fz_cookie *cookie, pdf_gstate *fill_gstate, pdf_gstate *stroke_gstate) +{ + pdf_run_processor *proc = pdf_new_processor(ctx, sizeof *proc); + { + proc->super.usage = usage; + + proc->super.close_processor = pdf_close_run_processor; + proc->super.drop_processor = pdf_drop_run_processor; + + proc->super.push_resources = pdf_run_push_resources; + proc->super.pop_resources = pdf_run_pop_resources; + + /* general graphics state */ + proc->super.op_w = pdf_run_w; + proc->super.op_j = pdf_run_j; + proc->super.op_J = pdf_run_J; + proc->super.op_M = pdf_run_M; + proc->super.op_d = pdf_run_d; + proc->super.op_ri = pdf_run_ri; + proc->super.op_i = pdf_run_i; + proc->super.op_gs_begin = pdf_run_gs_begin; + proc->super.op_gs_end = pdf_run_gs_end; + + /* transparency graphics state */ + proc->super.op_gs_BM = pdf_run_gs_BM; + proc->super.op_gs_CA = pdf_run_gs_CA; + proc->super.op_gs_ca = pdf_run_gs_ca; + proc->super.op_gs_SMask = pdf_run_gs_SMask; + + /* special graphics state */ + proc->super.op_q = pdf_run_q; + proc->super.op_Q = pdf_run_Q; + proc->super.op_cm = pdf_run_cm; + + /* path construction */ + proc->super.op_m = pdf_run_m; + proc->super.op_l = pdf_run_l; + proc->super.op_c = pdf_run_c; + proc->super.op_v = pdf_run_v; + proc->super.op_y = pdf_run_y; + proc->super.op_h = pdf_run_h; + proc->super.op_re = pdf_run_re; + + /* path painting */ + proc->super.op_S = pdf_run_S; + proc->super.op_s = pdf_run_s; + proc->super.op_F = pdf_run_F; + proc->super.op_f = pdf_run_f; + proc->super.op_fstar = pdf_run_fstar; + proc->super.op_B = pdf_run_B; + proc->super.op_Bstar = pdf_run_Bstar; + proc->super.op_b = pdf_run_b; + proc->super.op_bstar = pdf_run_bstar; + proc->super.op_n = pdf_run_n; + + /* clipping paths */ + proc->super.op_W = pdf_run_W; + proc->super.op_Wstar = pdf_run_Wstar; + + /* text objects */ + proc->super.op_BT = pdf_run_BT; + proc->super.op_ET = pdf_run_ET; + + /* text state */ + proc->super.op_Tc = pdf_run_Tc; + proc->super.op_Tw = pdf_run_Tw; + proc->super.op_Tz = pdf_run_Tz; + proc->super.op_TL = pdf_run_TL; + proc->super.op_Tf = pdf_run_Tf; + proc->super.op_Tr = pdf_run_Tr; + proc->super.op_Ts = pdf_run_Ts; + + /* text positioning */ + proc->super.op_Td = pdf_run_Td; + proc->super.op_TD = pdf_run_TD; + proc->super.op_Tm = pdf_run_Tm; + proc->super.op_Tstar = pdf_run_Tstar; + + /* text showing */ + proc->super.op_TJ = pdf_run_TJ; + proc->super.op_Tj = pdf_run_Tj; + proc->super.op_squote = pdf_run_squote; + proc->super.op_dquote = pdf_run_dquote; + + /* type 3 fonts */ + proc->super.op_d0 = pdf_run_d0; + proc->super.op_d1 = pdf_run_d1; + + /* color */ + proc->super.op_CS = pdf_run_CS; + proc->super.op_cs = pdf_run_cs; + proc->super.op_SC_color = pdf_run_SC_color; + proc->super.op_sc_color = pdf_run_sc_color; + proc->super.op_SC_pattern = pdf_run_SC_pattern; + proc->super.op_sc_pattern = pdf_run_sc_pattern; + proc->super.op_SC_shade = pdf_run_SC_shade; + proc->super.op_sc_shade = pdf_run_sc_shade; + + proc->super.op_G = pdf_run_G; + proc->super.op_g = pdf_run_g; + proc->super.op_RG = pdf_run_RG; + proc->super.op_rg = pdf_run_rg; + proc->super.op_K = pdf_run_K; + proc->super.op_k = pdf_run_k; + + /* shadings, images, xobjects */ + proc->super.op_sh = pdf_run_sh; + if (dev->fill_image || dev->fill_image_mask || dev->clip_image_mask) + { + proc->super.op_BI = pdf_run_BI; + proc->super.op_Do_image = pdf_run_Do_image; + } + proc->super.op_Do_form = pdf_run_Do_form; + + /* marked content */ + proc->super.op_MP = pdf_run_MP; + proc->super.op_DP = pdf_run_DP; + proc->super.op_BMC = pdf_run_BMC; + proc->super.op_BDC = pdf_run_BDC; + proc->super.op_EMC = pdf_run_EMC; + + /* compatibility */ + proc->super.op_BX = pdf_run_BX; + proc->super.op_EX = pdf_run_EX; + + /* extgstate */ + proc->super.op_gs_OP = pdf_run_gs_OP; + proc->super.op_gs_op = pdf_run_gs_op; + proc->super.op_gs_OPM = pdf_run_gs_OPM; + proc->super.op_gs_UseBlackPtComp = pdf_run_gs_UseBlackPtComp; + + proc->super.op_END = pdf_run_END; + } + + proc->super.requirements = 0; + if ((dev->hints & FZ_DONT_DECODE_IMAGES) == 0) + proc->super.requirements |= PDF_PROCESSOR_REQUIRES_DECODED_IMAGES; + + proc->doc = pdf_keep_document(ctx, doc); + proc->dev = dev; + proc->cookie = cookie; + + proc->default_cs = fz_keep_default_colorspaces(ctx, default_cs); + + proc->path = NULL; + proc->clip = 0; + proc->clip_even_odd = 0; + + proc->tos.text = NULL; + proc->tos.tlm = fz_identity; + proc->tos.tm = fz_identity; + proc->tos.text_mode = 0; + + proc->gtop = -1; + + proc->marked_content = NULL; + + proc->next_begin_layer = &proc->begin_layer; + + fz_try(ctx) + { + proc->path = fz_new_path(ctx); + + proc->gcap = 64; + proc->gstate = fz_malloc_struct_array(ctx, proc->gcap, pdf_gstate); + + proc->gtop = 0; + pdf_init_gstate(ctx, &proc->gstate[0], ctm); + + if (fill_gstate) + { + pdf_copy_gstate(ctx, &proc->gstate[0], fill_gstate); + proc->gstate[0].clip_depth = 0; + proc->gtop++; + } + if (stroke_gstate) + { + pdf_copy_gstate(ctx, &proc->gstate[proc->gtop], stroke_gstate); + proc->gstate[proc->gtop].clip_depth = 0; + proc->gtop++; + } + if (gstate) + { + pdf_copy_gstate(ctx, &proc->gstate[proc->gtop], gstate); + proc->gstate[proc->gtop].clip_depth = 0; + proc->gstate[proc->gtop].ctm = ctm; + } + proc->gparent = proc->gtop; + if (fill_gstate) + proc->gstate[proc->gtop].fill.gstate_num = 0; + if (stroke_gstate) + proc->gstate[proc->gtop].fill.gstate_num = (fill_gstate != NULL); + + /* We need to save an extra level to allow for the parent gstate level. */ + pdf_gsave(ctx, proc); + + /* Structure details */ + { + pdf_obj *struct_tree_root = pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root), PDF_NAME(StructTreeRoot), NULL); + proc->struct_parent = struct_parent; + proc->role_map = pdf_keep_obj(ctx, pdf_dict_get(ctx, struct_tree_root, PDF_NAME(RoleMap))); + + /* Annotations and XObjects can be their own content items. We spot this by + * the struct_parent looking up to be a singular object. */ + if (struct_parent != -1 && struct_tree_root) + { + pdf_obj *struct_obj = pdf_lookup_number(ctx, pdf_dict_get(ctx, struct_tree_root, PDF_NAME(ParentTree)), struct_parent); + if (pdf_is_dict(ctx, struct_obj)) + send_begin_structure(ctx, proc, struct_obj); + /* We always end structure as required on closedown, so this is safe. */ + } + } + } + fz_catch(ctx) + { + pdf_drop_run_processor(ctx, (pdf_processor *) proc); + fz_free(ctx, proc); + fz_rethrow(ctx); + } + + return (pdf_processor*)proc; +}
