summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNancy Durgin <nancy.durgin@artifex.com>2021-03-10 14:48:33 -0800
committerNancy Durgin <nancy.durgin@artifex.com>2021-03-16 12:15:22 -0700
commit93e3a2c7c3aa939b7cfbd3173d0997003447341d (patch)
tree72d97502f3d16ccfec756e007092ffb4d5e8a9f5
parentcaacba2d0e132f9c07c12fb8b3f028e8c2d8815b (diff)
downloadghostpdl-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.h3
-rw-r--r--pdf/pdf_colour.c223
-rw-r--r--pdf/pdf_colour.h2
-rw-r--r--pdf/pdf_doc.c78
-rw-r--r--pdf/pdf_misc.c9
-rw-r--r--pdf/pdf_misc.h1
-rw-r--r--pdf/pdftop.c22
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;
}