diff options
author | Nancy Durgin <nancy.durgin@artifex.com> | 2021-03-10 14:48:33 -0800 |
---|---|---|
committer | Nancy Durgin <nancy.durgin@artifex.com> | 2021-03-16 12:15:22 -0700 |
commit | 93e3a2c7c3aa939b7cfbd3173d0997003447341d (patch) | |
tree | 72d97502f3d16ccfec756e007092ffb4d5e8a9f5 | |
parent | caacba2d0e132f9c07c12fb8b3f028e8c2d8815b (diff) | |
download | ghostpdl-93e3a2c7c3aa939b7cfbd3173d0997003447341d.tar.gz |
Implement OutputIntents
Handle the UsePDFX3Profile and UseOutputIntent command line args
Seems to be working. Some warning messages will differ from gs version.
Tested with: tests_private/icc/071_OutputIntent_Custom-v1_x3.pdf
-rw-r--r-- | pdf/ghostpdf.h | 3 | ||||
-rw-r--r-- | pdf/pdf_colour.c | 223 | ||||
-rw-r--r-- | pdf/pdf_colour.h | 2 | ||||
-rw-r--r-- | pdf/pdf_doc.c | 78 | ||||
-rw-r--r-- | pdf/pdf_misc.c | 9 | ||||
-rw-r--r-- | pdf/pdf_misc.h | 1 | ||||
-rw-r--r-- | pdf/pdftop.c | 22 |
7 files changed, 333 insertions, 5 deletions
diff --git a/pdf/ghostpdf.h b/pdf/ghostpdf.h index 0166145fe..bb125f279 100644 --- a/pdf/ghostpdf.h +++ b/pdf/ghostpdf.h @@ -230,6 +230,9 @@ typedef struct cmd_args_s { bool nouserunit; bool renderttnotdef; bool pdfinfo; + bool UsePDFX3Profile; + int PDFX3Profile_num; + char *UseOutputIntent; pdf_overprint_control_t overprint_control; /* Overprint -- enabled, disabled, simulated */ char *PageList; } cmd_args_t; diff --git a/pdf/pdf_colour.c b/pdf/pdf_colour.c index 271987a73..91653f6f8 100644 --- a/pdf/pdf_colour.c +++ b/pdf/pdf_colour.c @@ -990,7 +990,8 @@ static int pdfi_create_icc(pdf_context *ctx, char *Name, stream *s, int ncomps, return code; } -static int pdfi_create_iccprofile(pdf_context *ctx, pdf_stream *ICC_obj, char *cname, int64_t Length, int N, int *icc_N, float *range, gs_color_space **ppcs) +static int pdfi_create_iccprofile(pdf_context *ctx, pdf_stream *ICC_obj, char *cname, + int64_t Length, int N, int *icc_N, float *range, gs_color_space **ppcs) { pdf_c_stream *profile_stream = NULL; byte *profile_buffer; @@ -2367,3 +2368,223 @@ int pdfi_setfillcolor_space(pdf_context *ctx, pdf_dict *stream_dict, pdf_dict *p return code; return 0; } + + +/* + * Set device outputintent from stream + * see zicc.c/zset_outputintent() + */ +static int pdfi_device_setoutputintent(pdf_context *ctx, pdf_dict *profile_dict, stream *stream) +{ + int code = 0; + gs_gstate *pgs = ctx->pgs; + gx_device *dev = gs_currentdevice(pgs); + cmm_dev_profile_t *dev_profile; + int64_t N; + int ncomps, dev_comps; + int expected = 0; + cmm_profile_t *picc_profile = NULL; + cmm_profile_t *source_profile = NULL; + gsicc_manager_t *icc_manager = pgs->icc_manager; + gs_color_space_index index; + + if_debug0m(gs_debug_flag_icc, ctx->memory, "[icc] Using OutputIntent\n"); + + /* get dev_profile and try initing it if fail first time */ + code = dev_proc(dev, get_profile)(dev, &dev_profile); + if (code < 0) + return code; + + if (dev_profile == NULL) { + code = gsicc_init_device_profile_struct(dev, NULL, 0); + if (code < 0) + return code; + code = dev_proc(dev, get_profile)(dev, &dev_profile); + if (code < 0) + return code; + } + if (dev_profile->oi_profile != NULL) { + return 0; /* Allow only one setting of this object */ + } + + code = pdfi_dict_get_int(ctx, profile_dict, "N", &N); + if (code < 0) + goto exit; + ncomps = (int)N; + + picc_profile = gsicc_profile_new(stream, gs_gstate_memory(pgs), NULL, 0); + if (picc_profile == NULL) { + code = gs_note_error(gs_error_VMerror); + goto exit; + } + picc_profile->num_comps = ncomps; + picc_profile->profile_handle = + gsicc_get_profile_handle_buffer(picc_profile->buffer, + picc_profile->buffer_size, + gs_gstate_memory(pgs)); + if (picc_profile->profile_handle == NULL) { + code = gs_note_error(gs_error_VMerror); + goto exit; + } + + picc_profile->data_cs = + gscms_get_profile_data_space(picc_profile->profile_handle, + picc_profile->memory); + switch (picc_profile->data_cs) { + case gsCIEXYZ: + case gsCIELAB: + case gsRGB: + expected = 3; + source_profile = icc_manager->default_rgb; + break; + case gsGRAY: + expected = 1; + source_profile = icc_manager->default_gray; + break; + case gsCMYK: + expected = 4; + source_profile = icc_manager->default_cmyk; + break; + case gsNCHANNEL: + expected = 0; + break; + case gsNAMED: + case gsUNDEFINED: + break; + } + if (expected && ncomps != expected) { + code = gs_note_error(gs_error_rangecheck); + goto exit; + } + gsicc_init_hash_cs(picc_profile, pgs); + + /* All is well with the profile. Lets set the stuff that needs to be set */ + dev_profile->oi_profile = picc_profile; + rc_increment(picc_profile); + picc_profile->name = (char *) gs_alloc_bytes(picc_profile->memory, + MAX_DEFAULT_ICC_LENGTH, + "pdfi_color_setoutputintent"); + strncpy(picc_profile->name, OI_PROFILE, strlen(OI_PROFILE)); + picc_profile->name[strlen(OI_PROFILE)] = 0; + picc_profile->name_length = strlen(OI_PROFILE); + /* Set the range of the profile */ + gsicc_set_icc_range(&picc_profile); + + /* If the output device has a different number of components, then we are + going to set the output intent as the proofing profile, unless the + proofing profile has already been set. + + If the device has the same number of components (and color model) then as + the profile we will use this as the output profile, unless someone has + explicitly set the output profile. + + Finally, we will use the output intent profile for the default profile + of the proper Device profile in the icc manager, again, unless someone + has explicitly set this default profile. + */ + dev_comps = dev_profile->device_profile[GS_DEFAULT_DEVICE_PROFILE]->num_comps; + index = gsicc_get_default_type(dev_profile->device_profile[GS_DEFAULT_DEVICE_PROFILE]); + if (ncomps == dev_comps && index < gs_color_space_index_DevicePixel) { + /* The OI profile is the same type as the profile for the device and a + "default" profile for the device was not externally set. So we go + ahead and use the OI profile as the device profile. Care needs to be + taken here to keep from screwing up any device parameters. We will + use a keyword of OIProfile for the user/device parameter to indicate + its usage. Also, note conflicts if one is setting object dependent + color management */ + dev_profile->device_profile[GS_DEFAULT_DEVICE_PROFILE] = picc_profile; + rc_increment(picc_profile); + if_debug0m(gs_debug_flag_icc, ctx->memory, "[icc] OutputIntent used for device profile\n"); + } else { + if (dev_profile->proof_profile == NULL) { + /* This means that we should use the OI profile as the proofing + profile. Note that if someone already has specified a + proofing profile it is unclear what they are trying to do + with the output intent. In this case, we will use it + just for the source data below */ + dev_profile->proof_profile = picc_profile; + rc_increment(picc_profile); + if_debug0m(gs_debug_flag_icc, ctx->memory, "[icc] OutputIntent used for proof profile\n"); + } + } + /* Now the source colors. See which source color space needs to use the + output intent ICC profile */ + index = gsicc_get_default_type(source_profile); + if (index < gs_color_space_index_DevicePixel) { + /* source_profile is currently the default. Set it to the OI profile */ + switch (picc_profile->data_cs) { + case gsGRAY: + if_debug0m(gs_debug_flag_icc, ctx->memory, "[icc] OutputIntent used source Gray\n"); + icc_manager->default_gray = picc_profile; + rc_increment(picc_profile); + break; + case gsRGB: + if_debug0m(gs_debug_flag_icc, ctx->memory, "[icc] OutputIntent used source RGB\n"); + icc_manager->default_rgb = picc_profile; + rc_increment(picc_profile); + break; + case gsCMYK: + if_debug0m(gs_debug_flag_icc, ctx->memory, "[icc] OutputIntent used source CMYK\n"); + icc_manager->default_cmyk = picc_profile; + rc_increment(picc_profile); + break; + default: + break; + } + } + + exit: + if (picc_profile != NULL) + rc_decrement(picc_profile, "pdfi_color_setoutputintent"); + return code; +} + +/* + * intent_dict -- the outputintent dictionary + * profile -- the color profile (a stream) + * + */ +int pdfi_color_setoutputintent(pdf_context *ctx, pdf_dict *intent_dict, pdf_stream *profile) +{ + pdf_c_stream *profile_stream = NULL; + byte *profile_buffer; + gs_offset_t savedoffset; + int code, code1; + int64_t Length; + pdf_dict *profile_dict; + + code = pdfi_dict_from_obj(ctx, (pdf_obj *)profile, &profile_dict); + if (code < 0) + return code; + + /* Save the current stream position, and move to the start of the profile stream */ + savedoffset = pdfi_tell(ctx->main_stream); + pdfi_seek(ctx, ctx->main_stream, pdfi_stream_offset(ctx, profile), SEEK_SET); + + Length = pdfi_stream_length(ctx, profile); + + /* The ICC profile reading code (irritatingly) requires a seekable stream, because it + * rewinds it to the start, then seeks to the end to find the size, then rewinds the + * stream again. + * Ideally we would use a ReusableStreamDecode filter here, but that is largely + * implemented in PostScript (!) so we can't use it. What we can do is create a + * string sourced stream in memory, which is at least seekable. + */ + code = pdfi_open_memory_stream_from_filtered_stream(ctx, profile, Length, + &profile_buffer, ctx->main_stream, + &profile_stream); + if (code < 0) + goto exit; + + /* Create and set the device profile */ + code = pdfi_device_setoutputintent(ctx, profile_dict, profile_stream->s); + + code1 = pdfi_close_memory_stream(ctx, profile_buffer, profile_stream); + + if (code == 0) + code = code1; + + exit: + pdfi_seek(ctx, ctx->main_stream, savedoffset, SEEK_SET); + return code; +} diff --git a/pdf/pdf_colour.h b/pdf/pdf_colour.h index ee45be46e..a8fef081e 100644 --- a/pdf/pdf_colour.h +++ b/pdf/pdf_colour.h @@ -55,4 +55,6 @@ int pdfi_create_icc_colorspace_from_stream(pdf_context *ctx, pdf_c_stream *strea /* Page level spot colour detection and enumeration */ int pdfi_check_ColorSpace_for_spots(pdf_context *ctx, pdf_obj *space, pdf_dict *parent_dict, pdf_dict *page_dict, pdf_dict *spot_dict); +int pdfi_color_setoutputintent(pdf_context *ctx, pdf_dict *intent_dict, pdf_stream *profile); + #endif diff --git a/pdf/pdf_doc.c b/pdf/pdf_doc.c index ffcd2a8f6..b026cf053 100644 --- a/pdf/pdf_doc.c +++ b/pdf/pdf_doc.c @@ -28,6 +28,7 @@ #include "pdf_repair.h" #include "pdf_doc.h" #include "pdf_mark.h" +#include "pdf_colour.h" int pdfi_read_Root(pdf_context *ctx) { @@ -1023,25 +1024,94 @@ static int pdfi_doc_PageLabels(pdf_context *ctx) static int pdfi_doc_OutputIntents(pdf_context *ctx) { int code; - pdf_dict *OutputIntents = NULL; + pdf_array *OutputIntents = NULL; + pdf_dict *intent = NULL; + pdf_string *name = NULL; + pdf_stream *DestOutputProfile = NULL; + uint64_t index; - /* Sample file tests_private/comparefiles/Bug689830.pdf contains an /OutputIntents entry. + /* NOTE: subtle difference in error handling -- we are checking for OutputIntents first, + * so this will just ignore UsePDFX3Profile or UseOutputIntent params without warning, + * if OutputIntents doesn't exist. Seems fine to me. */ - code = pdfi_dict_knownget_type(ctx, ctx->Root, "OutputIntents", PDF_DICT, + code = pdfi_dict_knownget_type(ctx, ctx->Root, "OutputIntents", PDF_ARRAY, (pdf_obj **)&OutputIntents); if (code <= 0) { goto exit; } - /* TODO: Implement writeoutputintents if somebody ever complains... * See pdf_main.ps/writeoutputintents * I am not aware of a device that supports "/OutputIntent" so * couldn't figure out what to do for this. */ + /* Handle UsePDFX3Profile and UseOutputIntent command line options */ + if (ctx->args.UsePDFX3Profile) { + /* This is an index into the array */ + code = pdfi_array_get_type(ctx, OutputIntents, ctx->args.PDFX3Profile_num, + PDF_DICT, (pdf_obj **)&intent); + if (code < 0) { + dmprintf1(ctx->memory, + "*** WARNING UsePDFX3Profile specified invalid index %d for OutputIntents\n", + ctx->args.PDFX3Profile_num); + goto exit; + } + } else if (ctx->args.UseOutputIntent != NULL) { + /* This is a name to look up in the array */ + for (index=0; index<pdfi_array_size(OutputIntents); index ++) { + code = pdfi_array_get_type(ctx, OutputIntents, index, PDF_DICT, (pdf_obj **)&intent); + if (code < 0) goto exit; + + code = pdfi_dict_knownget_type(ctx, intent, "OutputConditionIdentifier", PDF_STRING, + (pdf_obj **)&name); + if (code < 0) goto exit; + if (code == 0) + continue; + + /* If the ID is "Custom" then check "Info" instead */ + if (pdfi_string_is(name, "Custom")) { + pdfi_countdown(name); + name = NULL; + code = pdfi_dict_knownget_type(ctx, intent, "Info", PDF_STRING, (pdf_obj **)&name); + if (code < 0) goto exit; + if (code == 0) + continue; + } + + /* Check for a match */ + if (pdfi_string_is(name, ctx->args.UseOutputIntent)) + break; + + pdfi_countdown(intent); + intent = NULL; + pdfi_countdown(name); + name = NULL; + } + code = 0; + } else { + /* No command line arg was specified, so nothing to do */ + code = 0; + goto exit; + } + + /* Now if intent is non-null, we found the selected intent dictionary */ + if (intent == NULL) + goto exit; + + /* Load the profile, if it exists */ + code = pdfi_dict_knownget_type(ctx, intent, "DestOutputProfile", PDF_STREAM, (pdf_obj **)&DestOutputProfile); + /* TODO: Flag an error if it doesn't exist? Only required in some cases */ + if (code <= 0) goto exit; + + /* Set the intent to the profile */ + code = pdfi_color_setoutputintent(ctx, intent, DestOutputProfile); + exit: pdfi_countdown(OutputIntents); + pdfi_countdown(intent); + pdfi_countdown(name); + pdfi_countdown(DestOutputProfile); return code; } diff --git a/pdf/pdf_misc.c b/pdf/pdf_misc.c index 1b82778a5..bc8f6d984 100644 --- a/pdf/pdf_misc.c +++ b/pdf/pdf_misc.c @@ -90,6 +90,15 @@ pdfi_name_strcmp(const pdf_name *n, const char *s) } bool +pdfi_string_is(const pdf_string *n, const char *s) +{ + int len = strlen(s); + if (n->length == len) + return (memcmp(n->data, s, len) == 0); + return false; +} + +bool pdfi_name_is(const pdf_name *n, const char *s) { int len = strlen(s); diff --git a/pdf/pdf_misc.h b/pdf/pdf_misc.h index 747f918cf..1ad2fbdc1 100644 --- a/pdf/pdf_misc.h +++ b/pdf/pdf_misc.h @@ -18,6 +18,7 @@ int pdfi_get_current_bbox(pdf_context *ctx, gs_rect *bbox, bool stroked); int pdfi_name_strcmp(const pdf_name *n, const char *s); +bool pdfi_string_is(const pdf_string *n, const char *s); bool pdfi_name_is(const pdf_name *n, const char *s); int pdfi_name_cmp(const pdf_name *n1, const pdf_name *n2); int pdfi_string_cmp(const pdf_string *n1, const pdf_string *n2); diff --git a/pdf/pdftop.c b/pdf/pdftop.c index 1d364775c..2fa4f2571 100644 --- a/pdf/pdftop.c +++ b/pdf/pdftop.c @@ -425,6 +425,7 @@ pdf_impl_set_param(pl_interp_implementation_t *impl, gs_param_enumerator_t enumerator; gs_param_key_t key; int code; + int len; param_init_enumerator(&enumerator); while ((code = param_get_next_key(plist, &enumerator, &key)) == 0) { @@ -563,6 +564,27 @@ pdf_impl_set_param(pl_interp_implementation_t *impl, if (code < 0) return code; } + if (!strncmp(param, "UsePDFX3Profile", strlen("UsePDFX3Profile"))) { + /* This is a weird one because it can be either a bool or an int. + * If it's a bool=true, then it defaults to PDFX3Profile_num = 0 + * If it's an int, then we set the flag to true and use the + * specified number. + * PS: Yuck! + */ + code = plist_value_get_bool(&pvalue, &ctx->args.UsePDFX3Profile); + if (code == gs_error_typecheck) { + code = plist_value_get_int(&pvalue, &ctx->args.PDFX3Profile_num); + if (code == 0) + ctx->args.UsePDFX3Profile = true; + } + if (code < 0) + return code; + } + if (!strncmp(param, "UseOutputIntent", strlen("UseOutputIntent"))) { + code = plist_value_get_string_or_name(ctx, &pvalue, &ctx->args.UseOutputIntent, &len); + if (code < 0) + return code; + } } return 0; } |