diff options
Diffstat (limited to 'pdf/pdf_check.c')
-rw-r--r-- | pdf/pdf_check.c | 1190 |
1 files changed, 1190 insertions, 0 deletions
diff --git a/pdf/pdf_check.c b/pdf/pdf_check.c new file mode 100644 index 00000000..13d974cb --- /dev/null +++ b/pdf/pdf_check.c @@ -0,0 +1,1190 @@ +/* Copyright (C) 2019-2021 Artifex Software, Inc. + All Rights Reserved. + + This software is provided AS-IS with no warranty, either express or + implied. + + This software is distributed under license and may not be copied, + modified or distributed except as expressly authorized under the terms + of the license contained in the file LICENSE in this distribution. + + Refer to licensing information at http://www.artifex.com or contact + Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato, + CA 94945, U.S.A., +1(415)492-9861, for further information. +*/ + +/* Checks for transparency and spots for the PDF interpreter */ + +#include "pdf_int.h" +#include "pdf_stack.h" +#include "pdf_page.h" +#include "pdf_file.h" +#include "pdf_dict.h" +#include "pdf_array.h" +#include "pdf_loop_detect.h" +#include "pdf_colour.h" +#include "pdf_trans.h" +#include "pdf_gstate.h" +#include "pdf_misc.h" +#include "pdf_check.h" +#include "pdf_device.h" +#include "gspaint.h" /* For gs_erasepage() */ + +/* For performance and resource reasons we do not want to install the transparency blending + * compositor unless we need it. Similarly, if a device handles spot colours it can minimise + * memory usage if it knows ahead of time how many spot colours there will be. + * + * The PDF interpreter written in PostScript performed these as two separate tasks, on opening + * a PDF file it would count spot colour usage and then for each page it would chek if the page + * used any transparency. The code below is used to check for both transparency and spot colours. + * If the int pointer to num_spots is NULL then we aren't interested in spot colours (not supported + * by the device), if the int pointer to transparent is NULL then we aren't interested in transparency + * (-dNOTRANSPARENCY is set). + * + * Currently the code is run when we open the PDF file, the existence of transparency on any given + * page is recorded in a bit array for later use. If it turns out that checking every page when we + * open the file is a performance burden then we could do it on a per-page basis instead. NB if + * the device supports spot colours then we do need to scan the file before starting any page. + * + * The technique is fairly straight-forward, we start with each page, and open its Resources + * dictionary, we then check by type each possible resource. Some resources (eg Pattern, XObject) + * can themselves contain Resources, in which case we recursively check that dictionary. Note; not + * all Resource types need to be checked for both transparency and spot colours, some types can + * only contain one or the other. + * + * Routines with the name pdfi_check_xxx_dict are intended to check a Resource dictionary entry, this + * will be a dictioanry of names and values, where the values are objects of the given Resource type. + * + */ + +/* Structure for keeping track of resources as they are being checked */ +/* CheckedResources is a bit of a hack, for the file Bug697655.pdf. That file has many (unused) + * XObjects which use Resources and the Resources use other XObjects and so on nested + * very deeply. This takes *hours* to check. Ghostscript gets around this because it + * stores all the objects (which we don't want to do because its wasteful) and checking + * to see if its already tested a given resource for spots/transparency. + * This is a temporary allocation, big enough to hold all the objects in the file (1 per bit) + * each time we have fully checked a resource we add it here, when checking a resoruce we + * first check this list to see if its already been checked, in which case we can skip + * it. When done we release the memory. + */ +typedef struct { + bool transparent; + bool has_overprint; /* Does it have OP or op in an ExtGState? */ + pdf_dict *spot_dict; + uint32_t size; + byte *CheckedResources; +} pdfi_check_tracker_t; + +static int pdfi_check_Resources(pdf_context *ctx, pdf_dict *Resources_dict, pdf_dict *page_dict, pdfi_check_tracker_t *tracker); + +static inline bool resource_is_checked(pdfi_check_tracker_t *tracker, pdf_obj *o) +{ + uint32_t byte_offset; + byte bit_offset; + + if(tracker->CheckedResources == NULL) + return 0; + + /* objects with object number 0 are directly defined, we can't + * store those so just return immediately + */ + if (o->object_num > 0 && (o->object_num >> 3) < tracker->size) { + /* CheckedResources is a byte array, each byte represents + * 8 objects. So the object number / 8 is the byte offset + * into the array, and then object number % 8 is the bit + * within that byte that we want. + */ + bit_offset = 0x01 << (o->object_num % 8); + byte_offset = o->object_num >> 3; + + /* If its already set, then return that. */ + if (tracker->CheckedResources[byte_offset] & bit_offset) + return true; + else + /* Otherwise set it for futre reference */ + tracker->CheckedResources[byte_offset] |= bit_offset; + } + return false; +} + + +static int +pdfi_check_free_tracker(pdf_context *ctx, pdfi_check_tracker_t *tracker) +{ + gs_free_object(ctx->memory, tracker->CheckedResources, "pdfi_check_free_tracker(flags)"); + pdfi_countdown(tracker->spot_dict); + memset(tracker, 0, sizeof(*tracker)); + return 0; +} + +static int +pdfi_check_init_tracker(pdf_context *ctx, pdfi_check_tracker_t *tracker) +{ + int code = 0; + + memset(tracker, 0, sizeof(*tracker)); + + tracker->size = (ctx->xref_table->xref_size + 7) / 8; + tracker->CheckedResources = gs_alloc_bytes(ctx->memory, tracker->size, + "pdfi_check_init_tracker(flags)"); + if (tracker->CheckedResources == NULL) + return_error(gs_error_VMerror); + + memset(tracker->CheckedResources, 0x00, tracker->size); + + if (ctx->device_state.spot_capable || ctx->args.overprint_control == PDF_OVERPRINT_SIMULATE) { + code = pdfi_dict_alloc(ctx, 32, &tracker->spot_dict); + if (code < 0) + goto cleanup; + pdfi_countup(tracker->spot_dict); + } + + return 0; + + cleanup: + pdfi_check_free_tracker(ctx, tracker); + return code; +} + +/* + * Check the Resources dictionary ColorSpace entry. pdfi_check_ColorSpace_for_spots is defined + * in pdf_colour.c + */ +static int pdfi_check_ColorSpace_dict(pdf_context *ctx, pdf_dict *cspace_dict, + pdf_dict *page_dict, pdfi_check_tracker_t *tracker) +{ + int code; + uint64_t i, index; + pdf_obj *Key = NULL, *Value = NULL; + + if (resource_is_checked(tracker, (pdf_obj *)cspace_dict)) + return 0; + + if (pdfi_dict_entries(cspace_dict) > 0) { + code = pdfi_loop_detector_mark(ctx); /* Mark the start of the ColorSpace dictionary loop */ + if (code < 0) + return code; + + code = pdfi_dict_first(ctx, cspace_dict, &Key, &Value, &index); + if (code < 0) + goto error1; + + i = 1; + do { + code = pdfi_check_ColorSpace_for_spots(ctx, Value, cspace_dict, page_dict, tracker->spot_dict); + if (code < 0) + goto error2; + + pdfi_countdown(Key); + Key = NULL; + pdfi_countdown(Value); + Value = NULL; + + (void)pdfi_loop_detector_cleartomark(ctx); /* Clear to the mark for the Shading dictionary loop */ + + code = pdfi_loop_detector_mark(ctx); /* Mark the new start of the Shading dictionary loop */ + if (code < 0) + goto error1; + + do { + if (i++ >= pdfi_dict_entries(cspace_dict)) { + code = 0; + goto transparency_exit; + } + + code = pdfi_dict_next(ctx, cspace_dict, &Key, &Value, &index); + if (code == 0 && Value->type == PDF_ARRAY) + break; + pdfi_countdown(Key); + Key = NULL; + pdfi_countdown(Value); + Value = NULL; + } while(1); + }while (1); + } + return 0; + +transparency_exit: +error2: + pdfi_countdown(Key); + pdfi_countdown(Value); + +error1: + (void)pdfi_loop_detector_cleartomark(ctx); /* Clear to the mark for the current resource loop */ + return code; +} + +/* + * Process an individual Shading dictionary to see if it contains a ColorSpace with a spot colour + */ +static int pdfi_check_Shading(pdf_context *ctx, pdf_obj *shading, + pdf_dict *page_dict, pdfi_check_tracker_t *tracker) +{ + int code; + pdf_obj *o = NULL; + pdf_dict *shading_dict = NULL; + + if (resource_is_checked(tracker, shading)) + return 0; + + code = pdfi_dict_from_obj(ctx, shading, &shading_dict); + if (code < 0) + return code; + + code = pdfi_dict_knownget(ctx, shading_dict, "ColorSpace", (pdf_obj **)&o); + if (code > 0) { + code = pdfi_check_ColorSpace_for_spots(ctx, o, shading_dict, page_dict, tracker->spot_dict); + pdfi_countdown(o); + return code; + } + return 0; +} + +/* + * Check the Resources dictionary Shading entry. + */ +static int pdfi_check_Shading_dict(pdf_context *ctx, pdf_dict *shading_dict, + pdf_dict *page_dict, pdfi_check_tracker_t *tracker) +{ + int code; + uint64_t i, index; + pdf_obj *Key = NULL, *Value = NULL; + + if (resource_is_checked(tracker, (pdf_obj *)shading_dict)) + return 0; + + if (pdfi_dict_entries(shading_dict) > 0) { + code = pdfi_loop_detector_mark(ctx); /* Mark the start of the Shading dictionary loop */ + if (code < 0) + return code; + + code = pdfi_dict_first(ctx, shading_dict, &Key, &Value, &index); + if (code < 0 || !(Value->type == PDF_DICT || Value->type == PDF_STREAM)) + goto error1; + + i = 1; + do { + code = pdfi_check_Shading(ctx, Value, page_dict, tracker); + if (code < 0) + goto error2; + + pdfi_countdown(Key); + Key = NULL; + pdfi_countdown(Value); + Value = NULL; + + (void)pdfi_loop_detector_cleartomark(ctx); /* Clear to the mark for the Shading dictionary loop */ + + code = pdfi_loop_detector_mark(ctx); /* Mark the new start of the Shading dictionary loop */ + if (code < 0) + goto error1; + + do { + if (i++ >= pdfi_dict_entries(shading_dict)) { + code = 0; + goto transparency_exit; + } + + code = pdfi_dict_next(ctx, shading_dict, &Key, &Value, &index); + if (code == 0 && Value->type == PDF_DICT) + break; + pdfi_countdown(Key); + Key = NULL; + pdfi_countdown(Value); + Value = NULL; + } while(1); + }while (1); + } + return 0; + +transparency_exit: +error2: + pdfi_countdown(Key); + pdfi_countdown(Value); + +error1: + (void)pdfi_loop_detector_cleartomark(ctx); /* Clear to the mark for the current resource loop */ + return code; +} + +/* + * This routine checks an XObject to see if it contains any spot + * colour definitions, or transparency usage. + */ +static int pdfi_check_XObject(pdf_context *ctx, pdf_dict *xobject, pdf_dict *page_dict, + pdfi_check_tracker_t *tracker) +{ + int code = 0; + pdf_name *n = NULL; + bool known = false; + double f; + + if (resource_is_checked(tracker, (pdf_obj *)xobject)) + return 0; + + code = pdfi_dict_get_type(ctx, xobject, "Subtype", PDF_NAME, (pdf_obj **)&n); + if (code >= 0) { + if (pdfi_name_is((const pdf_name *)n, "Image")) { + pdf_obj *CS = NULL; + + pdfi_countdown(n); + n = NULL; + code = pdfi_dict_known(ctx, xobject, "SMask", &known); + if (code >= 0) { + if (known == true) { + tracker->transparent = true; + if (tracker->spot_dict == NULL) + goto transparency_exit; + } + code = pdfi_dict_knownget_number(ctx, xobject, "SMaskInData", &f); + if (code > 0) { + code = 0; + if (f != 0.0) + tracker->transparent = true; + if (tracker->spot_dict == NULL) + goto transparency_exit; + } + /* Check the image dictionary for a ColorSpace entry, if we are checking spot names */ + if (tracker->spot_dict) { + code = pdfi_dict_knownget(ctx, xobject, "ColorSpace", (pdf_obj **)&CS); + if (code > 0) { + /* We don't care if there's an error here, it'll be picked up if we use the ColorSpace later */ + (void)pdfi_check_ColorSpace_for_spots(ctx, CS, xobject, page_dict, tracker->spot_dict); + pdfi_countdown(CS); + } + } + } + } else { + if (pdfi_name_is((const pdf_name *)n, "Form")) { + pdf_dict *group_dict = NULL, *resource_dict = NULL; + pdf_obj *CS = NULL; + + pdfi_countdown(n); + code = pdfi_dict_knownget_type(ctx, xobject, "Group", PDF_DICT, (pdf_obj **)&group_dict); + if (code > 0) { + tracker->transparent = true; + if (tracker->spot_dict == NULL) { + pdfi_countdown(group_dict); + goto transparency_exit; + } + + /* Start a new loop detector group to avoid this being detected in the Resources check below */ + code = pdfi_loop_detector_mark(ctx); /* Mark the start of the XObject dictionary loop */ + if (code == 0) { + code = pdfi_dict_knownget(ctx, group_dict, "CS", &CS); + if (code > 0) + /* We don't care if there's an error here, it'll be picked up if we use the ColorSpace later */ + (void)pdfi_check_ColorSpace_for_spots(ctx, CS, group_dict, page_dict, tracker->spot_dict); + (void)pdfi_loop_detector_cleartomark(ctx); /* Clear to the mark for the XObject dictionary loop */ + } + pdfi_countdown(group_dict); + pdfi_countdown(CS); + } + + code = pdfi_dict_knownget_type(ctx, xobject, "Resources", PDF_DICT, (pdf_obj **)&resource_dict); + if (code > 0) { + code = pdfi_check_Resources(ctx, resource_dict, page_dict, tracker); + pdfi_countdown(resource_dict); + if (code < 0) + goto transparency_exit; + } + } else + pdfi_countdown(n); + } + } + + return 0; + +transparency_exit: + return code; +} + +/* + * Check the Resources dictionary XObject entry. + */ +static int pdfi_check_XObject_dict(pdf_context *ctx, pdf_dict *xobject_dict, pdf_dict *page_dict, + pdfi_check_tracker_t *tracker) +{ + int code; + uint64_t i, index; + pdf_obj *Key = NULL, *Value = NULL; + pdf_dict *Value_dict = NULL; + + if (resource_is_checked(tracker, (pdf_obj *)xobject_dict)) + return 0; + + if (pdfi_dict_entries(xobject_dict) > 0) { + code = pdfi_loop_detector_mark(ctx); /* Mark the start of the XObject dictionary loop */ + if (code < 0) + return code; + + code = pdfi_dict_first(ctx, xobject_dict, &Key, &Value, &index); + if (code < 0) + goto error_exit; + if (Value->type != PDF_STREAM) + goto error_exit; + + i = 1; + do { + code = pdfi_dict_from_obj(ctx, Value, &Value_dict); + if (code < 0) + goto error_exit; + + code = pdfi_check_XObject(ctx, Value_dict, page_dict, tracker); + if (code < 0) + goto error_exit; + + (void)pdfi_loop_detector_cleartomark(ctx); /* Clear to the mark for the XObject dictionary loop */ + + code = pdfi_loop_detector_mark(ctx); /* Mark the new start of the XObject dictionary loop */ + if (code < 0) + goto error_exit; + + pdfi_countdown(Key); + Key = NULL; + pdfi_countdown(Value); + Value = NULL; + Value_dict = NULL; + + do { + if (i++ >= pdfi_dict_entries(xobject_dict)) { + code = 0; + goto transparency_exit; + } + + code = pdfi_dict_next(ctx, xobject_dict, &Key, &Value, &index); + if (code == 0 && Value->type == PDF_STREAM) + break; + pdfi_countdown(Key); + Key = NULL; + pdfi_countdown(Value); + Value = NULL; + } while(1); + }while(1); + } + return 0; + +transparency_exit: +error_exit: + pdfi_countdown(Key); + pdfi_countdown(Value); + (void)pdfi_loop_detector_cleartomark(ctx); /* Clear to the mark for the current resource loop */ + return code; +} + +/* + * This routine checks an ExtGState dictionary to see if it contains transparency or OP + */ +static int pdfi_check_ExtGState(pdf_context *ctx, pdf_dict *extgstate_dict, pdf_dict *page_dict, + pdfi_check_tracker_t *tracker) +{ + int code; + pdf_obj *o = NULL; + double f; + bool overprint; + + if (resource_is_checked(tracker, (pdf_obj *)extgstate_dict)) + return 0; + + if (pdfi_dict_entries(extgstate_dict) > 0) { + /* See if /OP or /op is true */ + code = pdfi_dict_get_bool(ctx, extgstate_dict, "OP", &overprint); + if (code == 0 && overprint) + tracker->has_overprint = true; + code = pdfi_dict_get_bool(ctx, extgstate_dict, "op", &overprint); + if (code == 0 && overprint) + tracker->has_overprint = true; + + /* Check SMask */ + code = pdfi_dict_knownget(ctx, extgstate_dict, "SMask", &o); + if (code > 0) { + if (o->type == PDF_NAME) { + if (!pdfi_name_is((pdf_name *)o, "None")) { + pdfi_countdown(o); + tracker->transparent = true; + return 0; + } + } else { + if (o->type == PDF_DICT) { + pdf_obj *G = NULL; + + tracker->transparent = true; + + if (tracker->spot_dict != NULL) { + /* Check if the SMask has a /G (Group) */ + code = pdfi_dict_knownget(ctx, (pdf_dict *)o, "G", &G); + if (code > 0) { + code = pdfi_check_XObject(ctx, (pdf_dict *)G, page_dict, + tracker); + pdfi_countdown(G); + } + } + pdfi_countdown(o); + return code; + } + } + } + pdfi_countdown(o); + o = NULL; + + code = pdfi_dict_knownget_type(ctx, extgstate_dict, "BM", PDF_NAME, &o); + if (code > 0) { + if (!pdfi_name_is((pdf_name *)o, "Normal")) { + if (!pdfi_name_is((pdf_name *)o, "Compatible")) { + pdfi_countdown(o); + tracker->transparent = true; + return 0; + } + } + } + pdfi_countdown(o); + o = NULL; + + code = pdfi_dict_knownget_number(ctx, extgstate_dict, "CA", &f); + if (code > 0) { + if (f != 1.0) { + tracker->transparent = true; + return 0; + } + } + + code = pdfi_dict_knownget_number(ctx, extgstate_dict, "ca", &f); + if (code > 0) { + if (f != 1.0) { + tracker->transparent = true; + return 0; + } + } + + } + return 0; +} + +/* + * Check the Resources dictionary ExtGState entry. + */ +static int pdfi_check_ExtGState_dict(pdf_context *ctx, pdf_dict *extgstate_dict, pdf_dict *page_dict, + pdfi_check_tracker_t *tracker) +{ + int code; + uint64_t i, index; + pdf_obj *Key = NULL, *Value = NULL; + + if (resource_is_checked(tracker, (pdf_obj *)extgstate_dict)) + return 0; + + if (pdfi_dict_entries(extgstate_dict) > 0) { + code = pdfi_loop_detector_mark(ctx); /* Mark the start of the ColorSpace dictionary loop */ + if (code < 0) + return code; + + code = pdfi_dict_first(ctx, extgstate_dict, &Key, &Value, &index); + if (code < 0) + goto error1; + + i = 1; + do { + + (void)pdfi_check_ExtGState(ctx, (pdf_dict *)Value, page_dict, tracker); + if (tracker->transparent == true && tracker->spot_dict == NULL) + goto transparency_exit; + + pdfi_countdown(Key); + Key = NULL; + pdfi_countdown(Value); + Value = NULL; + + (void)pdfi_loop_detector_cleartomark(ctx); /* Clear to the mark for the ExtGState dictionary loop */ + + code = pdfi_loop_detector_mark(ctx); /* Mark the new start of the ExtGState dictionary loop */ + if (code < 0) + goto error1; + + do { + if (i++ >= pdfi_dict_entries(extgstate_dict)) { + code = 0; + goto transparency_exit; + } + + code = pdfi_dict_next(ctx, extgstate_dict, &Key, &Value, &index); + if (code == 0 && Value->type == PDF_DICT) + break; + pdfi_countdown(Key); + Key = NULL; + pdfi_countdown(Value); + Value = NULL; + } while(1); + }while (1); + } + return 0; + +transparency_exit: + pdfi_countdown(Key); + pdfi_countdown(Value); + +error1: + (void)pdfi_loop_detector_cleartomark(ctx); /* Clear to the mark for the current resource loop */ + return code; +} + +/* + * This routine checks a Pattern dictionary to see if it contains any spot + * colour definitions, or transparency usage. + */ +static int pdfi_check_Pattern(pdf_context *ctx, pdf_dict *pattern, pdf_dict *page_dict, + pdfi_check_tracker_t *tracker) +{ + int code = 0; + pdf_obj *o = NULL; + + if (resource_is_checked(tracker, (pdf_obj *)pattern)) + return 0; + + if (tracker->spot_dict != NULL) { + code = pdfi_dict_knownget(ctx, pattern, "Shading", &o); + if (code > 0) + (void)pdfi_check_Shading(ctx, o, page_dict, tracker); + pdfi_countdown(o); + o = NULL; + } + + code = pdfi_dict_knownget_type(ctx, pattern, "Resources", PDF_DICT, &o); + if (code > 0) + (void)pdfi_check_Resources(ctx, (pdf_dict *)o, page_dict, tracker); + pdfi_countdown(o); + o = NULL; + if (tracker->transparent == true && tracker->spot_dict == NULL) + goto transparency_exit; + + code = pdfi_dict_knownget_type(ctx, pattern, "ExtGState", PDF_DICT, &o); + if (code > 0) + (void)pdfi_check_ExtGState(ctx, (pdf_dict *)o, page_dict, tracker); + pdfi_countdown(o); + o = NULL; + +transparency_exit: + return 0; +} + +/* + * This routine checks a Pattern dictionary for transparency. + */ +int pdfi_check_Pattern_transparency(pdf_context *ctx, pdf_dict *pattern, pdf_dict *page_dict, + bool *transparent) +{ + int code; + pdfi_check_tracker_t tracker = {0, 0, NULL, 0, NULL}; + + /* NOTE: We use a "null" tracker that won't do any optimization to prevent + * checking the same resource twice. + * This means we don't get the optimization, but we also don't have the overhead + * of setting it up. + * If we do want the optimization, call pdfi_check_init_tracker() and + * pdfi_check_free_tracker() as in pdfi_check_page(), below. + */ + code = pdfi_check_Pattern(ctx, pattern, page_dict, &tracker); + if (code == 0) + *transparent = tracker.transparent; + else + *transparent = false; + return code; +} + +/* + * Check the Resources dictionary Pattern entry. + */ +static int pdfi_check_Pattern_dict(pdf_context *ctx, pdf_dict *pattern_dict, pdf_dict *page_dict, + pdfi_check_tracker_t *tracker) +{ + int code; + uint64_t i, index; + pdf_obj *Key = NULL, *Value = NULL; + pdf_dict *instance_dict = NULL; + + if (resource_is_checked(tracker, (pdf_obj *)pattern_dict)) + return 0; + + if (pdfi_dict_entries(pattern_dict) > 0) { + code = pdfi_loop_detector_mark(ctx); /* Mark the start of the Pattern dictionary loop */ + if (code < 0) + return code; + + code = pdfi_dict_first(ctx, pattern_dict, &Key, &Value, &index); + if (code < 0) + goto error1; + + if(Value->type != PDF_DICT && Value->type != PDF_STREAM) + goto transparency_exit; + + i = 1; + do { + code = pdfi_dict_from_obj(ctx, Value, &instance_dict); + if (code < 0) + goto transparency_exit; + + code = pdfi_check_Pattern(ctx, instance_dict, page_dict, tracker); + if (code < 0) + goto transparency_exit; + + pdfi_countdown(Key); + Key = NULL; + pdfi_countdown(Value); + instance_dict = NULL; + Value = NULL; + (void)pdfi_loop_detector_cleartomark(ctx); /* Clear to the mark for the Shading dictionary loop */ + + code = pdfi_loop_detector_mark(ctx); /* Mark the new start of the Shading dictionary loop */ + if (code < 0) + goto error1; + + do { + if (i++ >= pdfi_dict_entries(pattern_dict)) { + code = 0; + goto transparency_exit; + } + + code = pdfi_dict_next(ctx, pattern_dict, &Key, &Value, &index); + if (code == 0 && (Value->type == PDF_DICT || Value->type == PDF_STREAM)) + break; + pdfi_countdown(Key); + Key = NULL; + pdfi_countdown(Value); + Value = NULL; + } while(1); + }while (1); + } + return 0; + +transparency_exit: + pdfi_countdown(Key); + pdfi_countdown(Value); + pdfi_countdown(instance_dict); + +error1: + (void)pdfi_loop_detector_cleartomark(ctx); /* Clear to the mark for the current resource loop */ + return code; +} + +/* + * This routine checks a Font dictionary to see if it contains any spot + * colour definitions, or transparency usage. + */ +static int pdfi_check_Font(pdf_context *ctx, pdf_dict *font, pdf_dict *page_dict, + pdfi_check_tracker_t *tracker) +{ + int code = 0; + pdf_obj *o = NULL; + + if (resource_is_checked(tracker, (pdf_obj *)font)) + return 0; + + if (font->type != PDF_DICT) + return_error(gs_error_typecheck); + + code = pdfi_dict_knownget_type(ctx, font, "Subtype", PDF_NAME, &o); + if (code > 0) { + if (pdfi_name_is((pdf_name *)o, "Type3")) { + pdfi_countdown(o); + o = NULL; + + code = pdfi_dict_knownget_type(ctx, font, "Resources", PDF_DICT, &o); + if (code > 0) + (void)pdfi_check_Resources(ctx, (pdf_dict *)o, page_dict, tracker); + } + } + pdfi_countdown(o); + o = NULL; + + return 0; +} + +/* + * Check the Resources dictionary Font entry. + */ +static int pdfi_check_Font_dict(pdf_context *ctx, pdf_dict *font_dict, pdf_dict *page_dict, + pdfi_check_tracker_t *tracker) +{ + int code; + uint64_t i, index; + pdf_obj *Key = NULL, *Value = NULL; + + if (resource_is_checked(tracker, (pdf_obj *)font_dict)) + return 0; + + if (pdfi_dict_entries(font_dict) > 0) { + code = pdfi_loop_detector_mark(ctx); /* Mark the start of the Font dictionary loop */ + if (code < 0) + return code; + + code = pdfi_dict_first(ctx, font_dict, &Key, &Value, &index); + if (code < 0) + goto error1; + + i = 1; + do { + code = pdfi_check_Font(ctx, (pdf_dict *)Value, page_dict, tracker); + + pdfi_countdown(Key); + Key = NULL; + pdfi_countdown(Value); + Value = NULL; + + (void)pdfi_loop_detector_cleartomark(ctx); /* Clear to the mark for the Font dictionary loop */ + + code = pdfi_loop_detector_mark(ctx); /* Mark the new start of the Font dictionary loop */ + if (code < 0) + goto error1; + + do { + if (i++ >= pdfi_dict_entries(font_dict)) { + code = 0; + goto transparency_exit; + } + + code = pdfi_dict_next(ctx, font_dict, &Key, &Value, &index); + if (code == 0 && Value->type == PDF_DICT) + break; + pdfi_countdown(Key); + Key = NULL; + pdfi_countdown(Value); + Value = NULL; + } while(1); + }while (1); + } + return 0; + +transparency_exit: + pdfi_countdown(Key); + pdfi_countdown(Value); + +error1: + (void)pdfi_loop_detector_cleartomark(ctx); /* Clear to the mark for the current resource loop */ + return code; +} + +static int pdfi_check_Resources(pdf_context *ctx, pdf_dict *Resources_dict, + pdf_dict *page_dict, pdfi_check_tracker_t *tracker) +{ + int code; + pdf_obj *d = NULL; + + if (resource_is_checked(tracker, (pdf_obj *)Resources_dict)) + return 0; + + /* First up, check any colour spaces, for new spot colours. + * We only do this if asked because its expensive. spot_dict being NULL + * means we aren't interested in spot colours (not a DeviceN or Separation device) + */ + if (tracker->spot_dict != NULL) { + code = pdfi_dict_knownget_type(ctx, Resources_dict, "ColorSpace", PDF_DICT, &d); + if (code > 0) + (void)pdfi_check_ColorSpace_dict(ctx, (pdf_dict *)d, page_dict, tracker); + + pdfi_countdown(d); + d = NULL; + + code = pdfi_dict_knownget_type(ctx, Resources_dict, "Shading", PDF_DICT, &d); + if (code > 0) + (void)pdfi_check_Shading_dict(ctx, (pdf_dict *)d, page_dict, tracker); + pdfi_countdown(d); + d = NULL; + } + + code = pdfi_dict_knownget_type(ctx, Resources_dict, "XObject", PDF_DICT, &d); + if (code > 0) + (void)pdfi_check_XObject_dict(ctx, (pdf_dict *)d, page_dict, tracker); + pdfi_countdown(d); + d = NULL; + + code = pdfi_dict_knownget_type(ctx, Resources_dict, "Pattern", PDF_DICT, &d); + if (code > 0) + (void)pdfi_check_Pattern_dict(ctx, (pdf_dict *)d, page_dict, tracker); + pdfi_countdown(d); + d = NULL; + + code = pdfi_dict_knownget_type(ctx, Resources_dict, "Font", PDF_DICT, &d); + if (code > 0) + (void)pdfi_check_Font_dict(ctx, (pdf_dict *)d, page_dict, tracker); + /* From this point onwards, if we detect transparency (or have already detected it) we + * can exit, we have already counted up any spot colours. + */ + pdfi_countdown(d); + d = NULL; + + code = pdfi_dict_knownget_type(ctx, Resources_dict, "ExtGState", PDF_DICT, &d); + if (code > 0) + (void)pdfi_check_ExtGState_dict(ctx, (pdf_dict *)d, page_dict, tracker); + pdfi_countdown(d); + d = NULL; + + return 0; +} + +static int pdfi_check_annot_for_transparency(pdf_context *ctx, pdf_dict *annot, pdf_dict *page_dict, + pdfi_check_tracker_t *tracker) +{ + int code; + pdf_name *n; + pdf_obj *N = NULL; + pdf_dict *ap = NULL; + pdf_dict *Resources = NULL; + double f; + + if (resource_is_checked(tracker, (pdf_obj *)annot)) + return 0; + + /* Check #1 Does the (Normal) Appearnce stream use any Resources which include transparency. + * We check this first, because this also checks for spot colour spaces. Once we've done that we + * can exit the checks as soon as we detect transparency. + */ + code = pdfi_dict_knownget_type(ctx, annot, "AP", PDF_DICT, (pdf_obj **)&ap); + if (code > 0) + { + /* Fetch without resolving indirect ref because pdfmark wants it that way later */ + code = pdfi_dict_get_no_store_R(ctx, ap, "N", (pdf_obj **)&N); + if (code >= 0) { + pdf_dict *dict = NULL; + + code = pdfi_dict_from_obj(ctx, N, &dict); + if (code == 0) + code = pdfi_dict_knownget_type(ctx, dict, "Resources", PDF_DICT, (pdf_obj **)&Resources); + if (code > 0) + code = pdfi_check_Resources(ctx, (pdf_dict *)Resources, page_dict, tracker); + } + if (code == gs_error_undefined) + code = 0; + } + pdfi_countdown(ap); + pdfi_countdown(N); + pdfi_countdown(Resources); + + if (code < 0) + return code; + /* We've checked the Resources, and nothing else in an annotation can define spot colours, so + * if we detected transparency in the Resources we need not check further. + */ + if (tracker->transparent == true) + return 0; + + code = pdfi_dict_get_type(ctx, annot, "Subtype", PDF_NAME, (pdf_obj **)&n); + if (code < 0) { + if (ctx->args.pdfstoponerror) + return code; + } else { + /* Check #2, Highlight annotations are always preformed with transparency */ + if (pdfi_name_is((const pdf_name *)n, "Highlight")) { + pdfi_countdown(n); + tracker->transparent = true; + return 0; + } + pdfi_countdown(n); + n = NULL; + + /* Check #3 Blend Mode (BM) not being 'Normal' or 'Compatible' */ + code = pdfi_dict_knownget_type(ctx, annot, "BM", PDF_NAME, (pdf_obj **)&n); + if (code > 0) { + if (!pdfi_name_is((const pdf_name *)n, "Normal")) { + if (!pdfi_name_is((const pdf_name *)n, "Compatible")) { + pdfi_countdown(n); + tracker->transparent = true; + return 0; + } + } + code = 0; + } + pdfi_countdown(n); + if (code < 0) + return code; + + /* Check #4 stroke constant alpha (CA) is not 1 (100% opaque) */ + code = pdfi_dict_knownget_number(ctx, annot, "CA", &f); + if (code > 0) { + if (f != 1.0) { + tracker->transparent = true; + return 0; + } + } + if (code < 0) + return code; + + /* Check #5 non-stroke constant alpha (ca) is not 1 (100% opaque) */ + code = pdfi_dict_knownget_number(ctx, annot, "ca", &f); + if (code > 0) { + if (f != 1.0) { + tracker->transparent = true; + return 0; + } + } + if (code < 0) + return code; + } + + return 0; +} + +static int pdfi_check_Annots_for_transparency(pdf_context *ctx, pdf_array *annots_array, + pdf_dict *page_dict, pdfi_check_tracker_t *tracker) +{ + int i, code = 0; + pdf_dict *annot = NULL; + + if (resource_is_checked(tracker, (pdf_obj *)annots_array)) + return 0; + + for (i=0; i < pdfi_array_size(annots_array); i++) { + code = pdfi_array_get_type(ctx, annots_array, (uint64_t)i, PDF_DICT, (pdf_obj **)&annot); + if (code >= 0) { + code = pdfi_check_annot_for_transparency(ctx, annot, page_dict, tracker); + if (code < 0 && ctx->args.pdfstoponerror) + goto exit; + + /* If we've found transparency, and don't need to continue checkign for spot colours + * just exit as fast as possible. + */ + if (tracker->transparent == true && tracker->spot_dict == NULL) + goto exit; + + pdfi_countdown(annot); + annot = NULL; + } + if (code < 0 && ctx->args.pdfstoponerror) + goto exit; + code = 0; + } +exit: + pdfi_countdown(annot); + return code; +} + +/* Check for transparency and spots on page. + * + * Sets ctx->spot_capable_device + * Builds a dictionary of the unique spot names in spot_dict + * Set 'transparent' to true if there is transparency on the page + * + * From the original PDF interpreter written in PostScript: + * Note: we deliberately don't check to see whether a Group is defined, + * because Adobe Illustrator 10 (and possibly other applications) define + * a page-level group whether transparency is actually used or not. + * Ignoring the presence of Group is justified because, in the absence + * of any other transparency features, they have no effect. + */ +static int pdfi_check_page_inner(pdf_context *ctx, pdf_dict *page_dict, + pdfi_check_tracker_t *tracker) +{ + int code; + pdf_dict *Resources = NULL; + pdf_array *Annots = NULL; + pdf_dict *Group = NULL; + pdf_obj *CS = NULL; + + tracker->transparent = false; + + /* Check if the page dictionary has a page Group entry (for spots). + * Page group should mean the page has transparency but we ignore it for the purposes + * of transparency detection. See above. + */ + if (tracker->spot_dict) { + code = pdfi_dict_knownget_type(ctx, page_dict, "Group", PDF_DICT, (pdf_obj **)&Group); + if (code > 0) { + /* If Group has a ColorSpace (CS), then check it for spot colours */ + code = pdfi_dict_knownget(ctx, Group, "CS", &CS); + if (code > 0) + code = pdfi_check_ColorSpace_for_spots(ctx, CS, Group, page_dict, tracker->spot_dict); + if (code < 0 && ctx->args.pdfstoponerror) + goto exit; + } + } + + /* Now check any Resources dictionary in the Page dictionary */ + code = pdfi_dict_knownget_type(ctx, page_dict, "Resources", PDF_DICT, (pdf_obj **)&Resources); + if (code > 0) + code = pdfi_check_Resources(ctx, Resources, page_dict, tracker); + if ((code < 0 && ctx->args.pdfstoponerror) || (code == gs_error_stackoverflow)) + goto exit; + + /* If we are drawing Annotations, check to see if the page uses any Annots */ + if (ctx->args.showannots) { + code = pdfi_dict_knownget_type(ctx, page_dict, "Annots", PDF_ARRAY, (pdf_obj **)&Annots); + if (code > 0) + code = pdfi_check_Annots_for_transparency(ctx, Annots, page_dict, + tracker); + if (code < 0 && ctx->args.pdfstoponerror) + goto exit; + } + + code = 0; + exit: + pdfi_countdown(Resources); + pdfi_countdown(Annots); + pdfi_countdown(CS); + pdfi_countdown(Group); + return code; +} + +/* Checks page for transparency, and sets up device for spots, if applicable + * Sets ctx->page.has_transparency and ctx->page.num_spots + * do_setup -- indicates whether to actually set up the device with the spot count. + */ +int pdfi_check_page(pdf_context *ctx, pdf_dict *page_dict, bool do_setup) +{ + int code; + int spots = 0; + pdfi_check_tracker_t tracker; + + ctx->page.num_spots = 0; + ctx->page.has_transparency = false; + + /* Need to do this here so that pdfi_check_init_tracker will be setup + * for spot colours, if the device is spot colour capable. + * It is also called in pdfi_page_render() and pdfi_process_pdf_file() + * TODO: Should probably look into that.. + */ + pdfi_device_set_flags(ctx); + code = pdfi_check_init_tracker(ctx, &tracker); + + /* Check for spots and transparency in this page */ + code = pdfi_check_page_inner(ctx, page_dict, &tracker); + if (code < 0) + goto exit; + + /* Count the spots */ + if (tracker.spot_dict) + spots = pdfi_dict_entries(tracker.spot_dict); + + /* If setup requested, tell the device about spots and transparency */ + if (do_setup) { + gs_c_param_list list; + + gs_c_param_list_write(&list, ctx->memory); + + /* If there are spot colours (and by inference, the device renders spot plates) then + * send the number of Spots to the device, so it can setup correctly. + */ + if (tracker.spot_dict) + param_write_int((gs_param_list *)&list, "PageSpotColors", &spots); + + code = param_write_bool((gs_param_list *)&list, "PageUsesTransparency", + &tracker.transparent); + gs_c_param_list_read(&list); + code = gs_putdeviceparams(ctx->pgs->device, (gs_param_list *)&list); + gs_c_param_list_release(&list); + if (code > 0) { + /* The device was closed, we need to reopen it */ + code = gs_setdevice_no_erase(ctx->pgs, ctx->pgs->device); + if (code < 0) + goto exit; + gs_erasepage(ctx->pgs); + } + } + + /* Set our values in the context, for caller */ + ctx->page.has_transparency = tracker.transparent; + ctx->page.num_spots = spots; + ctx->page.has_OP = tracker.has_overprint; + + exit: + (void)pdfi_check_free_tracker(ctx, &tracker); + return code; +} |