summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKen Sharp <ken.sharp@artifex.com>2023-02-28 15:36:56 +0000
committerKen Sharp <ken.sharp@artifex.com>2023-02-28 15:39:09 +0000
commit6894a2826210baf24f9ccd024bbca211b17d1f9b (patch)
treeeeb4b964e285b4173f0170a6cee990c737ed6058
parent1a722c886c4d3fbd9931b627a17270aca9701606 (diff)
downloadghostpdl-6894a2826210baf24f9ccd024bbca211b17d1f9b.tar.gz
GhostPDF + pdfwrite - emit an Alternate for some ICCBased spaces.
Bug #705865 "PDF Writer is dropping the /Alternate color space for ICC based color profile" This commit starts off with the bug referenced above. Prior to this commit the PDF interpreter never set the /Alternate space for an ICCBased colour space. For rendering there is no need since Ghostscript will always use the ICC profile, or will set the /Alternate instead (if there is an error in the ICC profile) or will use the number of components (/N) to set a Device space if there is a fault in the ICC profile and no /Alternate. However, this means pdfwrite never sees an Alternate space to write out for an ICCBased space. This should not be a problem since the /Alternate is optional for an ICCBased space and indeed the PDF 2.0 specification states "PDF readers should not use this colour space". The file for the bug has a /ICCBased space where the /Alternate is an Lab space. Obviously any PDF consumer should use the ICCBased space but it seems that Chrome, Firefox, possibly other browsers cannot handle ICCBased colour and so drop back to the Alternate. Surprisingly they can handle Lab and get the expected colour. Obviously if we drop the /Alternate then these consumers cannot use Lab and have a last ditch fallback to RGB (based on the number of components, and that *is* in the spec). But RGB != Lab and so the colours are incorrect. Ideally we would simply use the existing colour space code and attach the alternate space to the ICCBased space's 'base_space' member. That would write everything perfectly well. But we can't do that because when we are called from Ghostscript the ICC profile cache is in GC'ed memory. So we have to create the ICCBased colour space in GC'ed memory too. We have special hackery in the PDF interpreter colour code to do exactly that. Colour spaces call back to the PDF interpreter when freed (with more hackery for ICCBased spaces), but if we create colour spaces in non-GC (PDF interpreter) memory and attach them to the ICCBased space in GC'ed memory then they can outlive the PDF interpreter, leading to crashes. I did start down the road of making all colour spaces in GC-ed memory, but that rapidly spiralled out of control because names needed to be allocated in GC'ed memory, and functions and well, all kinds of things. Without that we got crashes, and it quickly became clear the only real way to make this work would be to allocate everything in GC'ed memory which we really don't want to do. So instead I added a new enumerated type member to the colour space, in that member, if the current colour space is ICCBased, we store the type of Alternate that was supplied (if any). We only support DeviceGray, DeviceRGB, DeviceCMYK, CalGray, CalRGB and Lab. I also added the relevant parameters to the 'params' union of the colour space. In the PDF interpreter; add code to spot the afore-mentioned Alternate spaces if present, and if we haven't been forced to fall back to using the Alternate (or /N) because the ICC profile is broken. When we spot one of those spaces, set the colour space ICC_Alternate_space member appropriately and for the paramaterised spaces gather the parameter values and store them. In the pdfwrite device; if we are writing out an ICCBased space, and it's ICC_Alternate_space member is not gs_ICC_Alternate_none, create a ColorSpace resource and call the relevant space-specific code to create a colour space array with a name and dictionary containing the required parameters. Attach the resulting object to the ICCBased colour space by inserting it into the array with a /Alternate key. This also meant I needed to alter the parameters passed internally to pdf_make_iccbased so that we pass in the original colour space instead of the alternate space (which is always NULL currently). There are also a couple of fixes; when finalising a colour space check that the colour space is a DeviceN space before checking if the device_n structure in the params union has a non-zero devn_process_space. The new members in the union meant we could get here and think we needed to free the devn_process_space, causing a crash. In the PDF interpreter; there's a little clause in the PDF specification which mentions a CalCMYK space. Apparently this was never properly specified and so should be treated as DeviceCMYK. The PDF interpreter now does so. Finally another obsrvation; the initial code wrote the /Alternate space as a named colour space, eg: 19 0 obj <</N 3 /Alternate /R18 /Length 1972>>stream .... Where R18 is defined in the Page's ColorSpace Resources as a named resource: <</R18 18 0 R/R17 17 0 R/R20 20 0 R/R22 22 0 R>> endobj But this does not work with Chrome (I didn't test Firefox). For this to work with Chrome we have to reference the object directly, which should not be required IMO. I believe this to be (another) bug in Chrome's PDF handling.
-rw-r--r--base/gscspace.c11
-rw-r--r--base/gscspace.h40
-rw-r--r--devices/vector/gdevpdfk.c327
-rw-r--r--pdf/pdf_colour.c343
4 files changed, 696 insertions, 25 deletions
diff --git a/base/gscspace.c b/base/gscspace.c
index d662bc025..9a707a0c0 100644
--- a/base/gscspace.c
+++ b/base/gscspace.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2001-2021 Artifex Software, Inc.
+/* Copyright (C) 2001-2023 Artifex Software, Inc.
All Rights Reserved.
This software is provided AS-IS with no warranty, either express or
@@ -109,9 +109,11 @@ gs_cspace_final(const gs_memory_t *cmem, void *vptr)
if_debug2m('c', cmem, "[c]cspace final "PRI_INTPTR" %d\n", (intptr_t)pcs, (int)pcs->id);
rc_decrement_only_cs(pcs->base_space, "gs_cspace_final");
pcs->base_space = NULL;
- if (pcs->params.device_n.devn_process_space != NULL) {
- rc_decrement_only_cs(pcs->params.device_n.devn_process_space, "gs_cspace_final");
- pcs->params.device_n.devn_process_space = NULL;
+ if (gs_color_space_get_index(pcs) == gs_color_space_index_DeviceN) {
+ if (pcs->params.device_n.devn_process_space != NULL) {
+ rc_decrement_only_cs(pcs->params.device_n.devn_process_space, "gs_cspace_final");
+ pcs->params.device_n.devn_process_space = NULL;
+ }
}
/* No need to decrement the ICC profile data. It is handled
by the finalize of the ICC space which is called above using
@@ -135,6 +137,7 @@ gs_cspace_alloc_with_id(gs_memory_t *mem, ulong id,
pcs->interpreter_data = NULL;
pcs->interpreter_free_cspace_proc = NULL;
pcs->cmm_icc_profile_data = NULL;
+ pcs->ICC_Alternate_space = gs_ICC_Alternate_None;
pcs->icc_equivalent = NULL;
pcs->params.device_n.devn_process_space = NULL;
return pcs;
diff --git a/base/gscspace.h b/base/gscspace.h
index 91bb7c784..9e9e46263 100644
--- a/base/gscspace.h
+++ b/base/gscspace.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2001-2022 Artifex Software, Inc.
+/* Copyright (C) 2001-2023 Artifex Software, Inc.
All Rights Reserved.
This software is provided AS-IS with no warranty, either express or
@@ -285,6 +285,25 @@ typedef struct gs_pattern_params_s {
bool has_base_space; /* {csrc} can't we just NULL-check the base_space? */
} gs_pattern_params;
+typedef struct gs_calgray_params_s {
+ float WhitePoint[3];
+ float BlackPoint[3];
+ float Gamma;
+} gs_calgray_params;
+
+typedef struct gs_calrgb_params_s {
+ float WhitePoint[3];
+ float BlackPoint[3];
+ float Gamma[3];
+ float Matrix[9];
+} gs_calrgb_params;
+
+typedef struct gs_lab_params_s {
+ float WhitePoint[3];
+ float BlackPoint[3];
+ float Range[4];
+} gs_lab_params;
+
/* id's 1 through 4 are reserved for static colorspaces; thus, dynamically
assigned id's must begin at 5. */
#define cs_DeviceGray_id 1
@@ -293,6 +312,16 @@ typedef struct gs_pattern_params_s {
typedef void (*gs_cspace_free_proc_t) (gs_memory_t * mem, void *pcs);
+typedef enum {
+ gs_ICC_Alternate_None,
+ gs_ICC_Alternate_DeviceGray,
+ gs_ICC_Alternate_DeviceRGB,
+ gs_ICC_Alternate_DeviceCMYK,
+ gs_ICC_Alternate_CalGray,
+ gs_ICC_Alternate_CalRGB,
+ gs_ICC_Alternate_Lab,
+} gs_ICC_Alternate_space;
+
/*
* The colorspace object. For pattern and indexed colorspaces, the
* base_space refers to the underlying colorspace. For separation,
@@ -306,6 +335,7 @@ struct gs_color_space_s {
gs_id id;
gs_color_space *base_space;
gs_color_space *icc_equivalent;
+ gs_ICC_Alternate_space ICC_Alternate_space;
client_color_space_data_t *pclient_color_space_data;
void *interpreter_data;
gs_cspace_free_proc_t interpreter_free_cspace_proc;
@@ -320,7 +350,13 @@ struct gs_color_space_s {
gs_device_n_params device_n;
gs_indexed_params indexed;
gs_pattern_params pattern;
-
+ /* These are only used for the Alternate space of an ICCBased
+ * space, for the benefit of pdfwrite. For rendering, these
+ * spaces are converted into ICCBased spaces.
+ */
+ gs_calgray_params calgray;
+ gs_calrgb_params calrgb;
+ gs_lab_params lab;
} params;
};
diff --git a/devices/vector/gdevpdfk.c b/devices/vector/gdevpdfk.c
index c682c6ea3..05121bccd 100644
--- a/devices/vector/gdevpdfk.c
+++ b/devices/vector/gdevpdfk.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2001-2021 Artifex Software, Inc.
+/* Copyright (C) 2001-2023 Artifex Software, Inc.
All Rights Reserved.
This software is provided AS-IS with no warranty, either express or
@@ -238,6 +238,213 @@ pdf_convert_cie_to_lab(gx_device_pdf *pdev, cos_array_t *pca,
/* ------ ICCBased space writing and synthesis ------ */
+/* Define standard and short color space names. */
+const pdf_color_space_names_t base_names = {
+ PDF_COLOR_SPACE_NAMES
+};
+
+int put_calgray_color_space(gx_device_pdf *pdev, const gs_gstate * pgs, const gs_color_space *pcs, cos_array_t *pca)
+{
+ int code, i;
+ cos_value_t v;
+ cos_dict_t *pcd;
+ cos_array_t *WP = NULL, *BP = NULL, *Gamma = NULL, *Matrix;
+
+ pcd = cos_dict_alloc(pdev, "write_calgray_color_space");
+ if (pcd == NULL)
+ return_error(gs_error_VMerror);
+
+ WP = cos_array_from_floats(pdev, pcs->params.calgray.WhitePoint, 3,
+ "write_calgray_color_space");
+ if (WP == NULL) {
+ cos_free((cos_object_t *)pcd, "write_calgray_color_space");
+ return_error(gs_error_VMerror);
+ }
+ BP = cos_array_from_floats(pdev, pcs->params.calgray.BlackPoint, 3,
+ "write_calgray_color_space");
+ if (BP == NULL) {
+ cos_free((cos_object_t *)pcd, "write_calgray_color_space");
+ cos_free((cos_object_t *)WP, "write_calgray_color_space");
+ return_error(gs_error_VMerror);
+ }
+
+ code = cos_dict_put_c_key(pcd, "/BlackPoint", COS_OBJECT_VALUE(&v, BP));
+ if (code < 0)
+ goto error;
+
+ code = cos_dict_put_c_key(pcd, "/WhitePoint", COS_OBJECT_VALUE(&v, WP));
+ if (code < 0)
+ goto error;
+
+ code = cos_dict_put_c_key_real(pcd, "/Gamma", pcs->params.calgray.Gamma);
+
+ code = cos_array_add_c_string(pca, "/CalGray");
+ if (code < 0)
+ goto error;
+
+ code = cos_array_add(pca, COS_OBJECT_VALUE(&v, pcd));
+ if (code < 0)
+ goto error;
+
+ return 0;
+
+error:
+ cos_free((cos_object_t *)pcd, "write_calgray_color_space");
+ cos_free((cos_object_t *)WP, "write_calgray_color_space");
+ cos_free((cos_object_t *)BP, "write_calgray_color_space");
+ return code;
+}
+
+int put_calrgb_color_space(gx_device_pdf *pdev, const gs_gstate * pgs, const gs_color_space *pcs, cos_array_t *pca)
+{
+ int code, i;
+ cos_value_t v;
+ cos_dict_t *pcd = NULL;
+ cos_array_t *WP = NULL, *BP = NULL, *Gamma = NULL, *Matrix;
+
+ pcd = cos_dict_alloc(pdev, "write_calrgb_color_space");
+ if (pcd == NULL)
+ return_error(gs_error_VMerror);
+
+ WP = cos_array_from_floats(pdev, pcs->params.calrgb.WhitePoint, 3,
+ "write_calrgb_color_space");
+ if (WP == NULL) {
+ cos_free((cos_object_t *)pcd, "write_calgray_color_space");
+ cos_free((cos_object_t *)pcd, "write_calrgb_color_space");
+ return_error(gs_error_VMerror);
+ }
+ BP = cos_array_from_floats(pdev, pcs->params.calrgb.BlackPoint, 3,
+ "write_calrgb_color_space");
+ if (BP == NULL) {
+ cos_free((cos_object_t *)pcd, "write_calrgb_color_space");
+ cos_free((cos_object_t *)WP, "write_calrgb_color_space");
+ return_error(gs_error_VMerror);
+ }
+
+ Gamma = cos_array_from_floats(pdev, pcs->params.calrgb.Gamma, 3,
+ "write_calrgb_color_space");
+ if (Gamma == NULL) {
+ cos_free((cos_object_t *)BP, "write_calrgb_color_space");
+ cos_free((cos_object_t *)pcd, "write_calrgb_color_space");
+ cos_free((cos_object_t *)WP, "write_calrgb_color_space");
+ return_error(gs_error_VMerror);
+ }
+
+ Matrix = cos_array_from_floats(pdev, pcs->params.calrgb.Matrix, 9,
+ "write_calrgb_color_space");
+ if (Matrix == NULL) {
+ cos_free((cos_object_t *)Gamma, "write_calrgb_color_space");
+ cos_free((cos_object_t *)BP, "write_calrgb_color_space");
+ cos_free((cos_object_t *)pcd, "write_calrgb_color_space");
+ cos_free((cos_object_t *)WP, "write_calrgb_color_space");
+ return_error(gs_error_VMerror);
+ }
+
+ code = cos_dict_put_c_key(pcd, "/BlackPoint", COS_OBJECT_VALUE(&v, BP));
+ if (code < 0)
+ goto error;
+
+ code = cos_dict_put_c_key(pcd, "/WhitePoint", COS_OBJECT_VALUE(&v, WP));
+ if (code < 0)
+ goto error;
+
+ code = cos_dict_put_c_key(pcd, "/Gamma", COS_OBJECT_VALUE(&v, Gamma));
+ if (code < 0)
+ goto error;
+
+ code = cos_dict_put_c_key(pcd, "/Matrix", COS_OBJECT_VALUE(&v, Matrix));
+ if (code < 0)
+ goto error;
+
+ code = cos_array_add_c_string(pca, "/CalRGB");
+ if (code < 0)
+ goto error;
+
+ code = cos_array_add(pca, COS_OBJECT_VALUE(&v, pcd));
+ if (code < 0)
+ goto error;
+
+
+ return 0;
+
+error:
+ cos_free((cos_object_t *)pcd, "write_calrgb_color_space");
+ cos_free((cos_object_t *)WP, "write_calrgb_color_space");
+ cos_free((cos_object_t *)BP, "write_calrgb_color_space");
+ cos_free((cos_object_t *)Gamma, "write_calrgb_color_space");
+ cos_free((cos_object_t *)Matrix, "write_calrgb_color_space");
+ return code;
+}
+
+int put_lab_color_space(gx_device_pdf *pdev, const gs_gstate * pgs, const gs_color_space *pcs, cos_array_t *pca)
+{
+ int code, i;
+ cos_value_t v;
+ cos_dict_t *pcd;
+ cos_array_t *WP = NULL, *BP = NULL, *range = NULL;
+
+ pcd = cos_dict_alloc(pdev, "write_lab_color_space");
+ if (pcd == NULL)
+ return_error(gs_error_VMerror);
+
+ range = cos_array_alloc(pdev, "write_lab_color_space");
+ if (range == NULL){
+ cos_free((cos_object_t *)pcd, "write_calgray_color_space");
+ return_error(gs_error_VMerror);
+ }
+
+ WP = cos_array_from_floats(pdev, pcs->params.lab.WhitePoint, 3,
+ "write_lab_color_space");
+ if (WP == NULL) {
+ cos_free((cos_object_t *)pcd, "write_calgray_color_space");
+ cos_free((cos_object_t *)range, "write_lab_color_space");
+ return_error(gs_error_VMerror);
+ }
+ BP = cos_array_from_floats(pdev, pcs->params.lab.BlackPoint, 3,
+ "write_lab_color_space");
+ if (BP == NULL) {
+ cos_free((cos_object_t *)pcd, "write_calgray_color_space");
+ cos_free((cos_object_t *)range, "write_lab_color_space");
+ cos_free((cos_object_t *)WP, "write_lab_color_space");
+ return_error(gs_error_VMerror);
+ }
+
+ for (i = 0;i < 4;i++) {
+ code = cos_array_add_real(range, pcs->params.lab.Range[i]);
+ if (code < 0)
+ goto error;
+ }
+
+ code = cos_dict_put_c_key(pcd, "/BlackPoint", COS_OBJECT_VALUE(&v, BP));
+ if (code < 0)
+ goto error;
+
+ code = cos_dict_put_c_key(pcd, "/WhitePoint", COS_OBJECT_VALUE(&v, WP));
+ if (code < 0)
+ goto error;
+
+ code = cos_dict_put_c_key(pcd, "/Range", COS_OBJECT_VALUE(&v, range));
+ if (code < 0)
+ goto error;
+
+ code = cos_array_add_c_string(pca, "/Lab");
+ if (code < 0)
+ goto error;
+
+ code = cos_array_add(pca, COS_OBJECT_VALUE(&v, pcd));
+ if (code < 0)
+ goto error;
+
+ return 0;
+
+error:
+ cos_free((cos_object_t *)pcd, "write_calgray_color_space");
+ cos_free((cos_object_t *)range, "write_lab_color_space");
+ cos_free((cos_object_t *)WP, "write_lab_color_space");
+ cos_free((cos_object_t *)BP, "write_lab_color_space");
+ return code;
+}
+
/*
* Create an ICCBased color space object (internal). The client must write
* the profile data on *ppcstrm.
@@ -246,7 +453,7 @@ static int
pdf_make_iccbased(gx_device_pdf *pdev, const gs_gstate * pgs,
cos_array_t *pca, int ncomps,
const gs_range *prange /*[4]*/,
- const gs_color_space *pcs_alt,
+ const gs_color_space *pcs,
cos_stream_t **ppcstrm,
const gs_range_t **pprange /* if scaling is needed */)
@@ -280,16 +487,16 @@ pdf_make_iccbased(gx_device_pdf *pdev, const gs_gstate * pgs,
goto fail;
/* In the new design there may not be a specified alternate color space */
- if (pcs_alt != NULL){
+ if (pcs->base_space != NULL){
/* Output the alternate color space, if necessary. */
- switch (gs_color_space_get_index(pcs_alt)) {
+ switch (gs_color_space_get_index(pcs->base_space)) {
case gs_color_space_index_DeviceGray:
case gs_color_space_index_DeviceRGB:
case gs_color_space_index_DeviceCMYK:
break; /* implicit (default) */
default:
- if ((code = pdf_color_space_named(pdev, pgs, &v, NULL, pcs_alt,
+ if ((code = pdf_color_space_named(pdev, pgs, &v, NULL, pcs->base_space,
&pdf_color_space_names, false, NULL, 0, true)) < 0 ||
(code = cos_dict_put_c_key(cos_stream_dict(pcstrm), "/Alternate",
&v)) < 0
@@ -298,12 +505,101 @@ pdf_make_iccbased(gx_device_pdf *pdev, const gs_gstate * pgs,
}
} else {
- if (ncomps != 1 && ncomps != 3 && ncomps != 4) {
- /* We can only use a default for Gray, RGB or CMYK. For anything else we need
- * to convert to the base space, we can't legally preserve the ICC profile.
- */
- code = gs_error_rangecheck;
- goto fail;
+ cos_value_t alt_v;
+
+ if (pcs->ICC_Alternate_space != gs_ICC_Alternate_None) {
+ switch(pcs->ICC_Alternate_space) {
+ case gs_ICC_Alternate_DeviceGray:
+ cos_c_string_value(&alt_v, base_names.DeviceGray);
+ break;
+ case gs_ICC_Alternate_DeviceRGB:
+ cos_c_string_value(&alt_v, base_names.DeviceRGB);
+ break;
+ case gs_ICC_Alternate_DeviceCMYK:
+ cos_c_string_value(&alt_v, base_names.DeviceCMYK);
+ break;
+ case gs_ICC_Alternate_CalGray:
+ {
+ pdf_resource_t *pres;
+ cos_array_t *pca1;
+
+ code = pdf_alloc_resource(pdev, resourceColorSpace, gs_no_id, &pres, -1);
+ if (code < 0)
+ goto fail;
+ cos_become(pres->object, cos_type_array);
+ pca1 = (cos_array_t *)pres->object;
+
+ code = put_calgray_color_space(pdev, pgs, pcs, pca1);
+ if (code < 0)
+ goto fail;
+
+ code = pdf_substitute_resource(pdev, &pres, resourcePattern, NULL, false);
+ if (code < 0)
+ return code;
+ pres->where_used |= pdev->used_mask;
+ cos_object_value(&alt_v, pres->object);
+ }
+ break;
+ case gs_ICC_Alternate_CalRGB:
+ {
+ pdf_resource_t *pres;
+ cos_array_t *pca1;
+
+ code = pdf_alloc_resource(pdev, resourceColorSpace, gs_no_id, &pres, -1);
+ if (code < 0)
+ goto fail;
+ cos_become(pres->object, cos_type_array);
+ pca1 = (cos_array_t *)pres->object;
+
+ code = put_calrgb_color_space(pdev, pgs, pcs, pca1);
+ if (code < 0)
+ goto fail;
+
+ code = pdf_substitute_resource(pdev, &pres, resourceColorSpace, NULL, false);
+ if (code < 0)
+ return code;
+ pres->where_used |= pdev->used_mask;
+ cos_object_value(&alt_v, pres->object);
+ }
+ break;
+ case gs_ICC_Alternate_Lab:
+ {
+ pdf_resource_t *pres;
+ cos_array_t *pca1;
+
+ code = pdf_alloc_resource(pdev, resourceColorSpace, gs_no_id, &pres, -1);
+ if (code < 0)
+ goto fail;
+ cos_become(pres->object, cos_type_array);
+ pca1 = (cos_array_t *)pres->object;
+
+ code = put_lab_color_space(pdev, pgs, pcs, pca1);
+ if (code < 0)
+ goto fail;
+
+ code = pdf_substitute_resource(pdev, &pres, resourceColorSpace, NULL, false);
+ if (code < 0)
+ return code;
+ pres->where_used |= pdev->used_mask;
+ cos_object_value(&alt_v, pres->object);
+ }
+ break;
+ default:
+ code = gs_error_rangecheck;
+ goto fail;
+ break;
+ }
+ code = cos_dict_put_c_key(cos_stream_dict(pcstrm), "/Alternate", &alt_v);
+ if (code < 0)
+ goto fail;
+ } else {
+ if (ncomps != 1 && ncomps != 3 && ncomps != 4) {
+ /* We can only use a default for Gray, RGB or CMYK. For anything else we need
+ * to convert to the base space, we can't legally preserve the ICC profile.
+ */
+ code = gs_error_rangecheck;
+ goto fail;
+ }
}
}
@@ -592,7 +888,7 @@ adjust_wp(const gs_vector3 *color_in, const gs_vector3 *wp_in,
static int
pdf_convert_cie_to_iccbased(gx_device_pdf *pdev, cos_array_t *pca,
- const gs_color_space *pcs, const char *dcsname,
+ gs_color_space *pcs, const char *dcsname,
const gs_cie_common *pciec, const gs_range *prange,
cie_cache_one_step_t one_step,
const gs_matrix3 *pmat, const gs_range_t **pprange)
@@ -670,7 +966,8 @@ pdf_convert_cie_to_iccbased(gx_device_pdf *pdev, cos_array_t *pca,
white_d50.w = 0.8249f;
pdf_cspace_init_Device(pdev->memory, &alt_space, ncomps); /* can't fail */
- code = pdf_make_iccbased(pdev, NULL, pca, ncomps, prange, alt_space,
+ pcs->base_space = alt_space;
+ code = pdf_make_iccbased(pdev, NULL, pca, ncomps, prange, pcs,
&pcstrm, pprange);
rc_decrement_cs(alt_space, "pdf_convert_cie_to_iccbased");
if (code < 0)
@@ -814,7 +1111,7 @@ pdf_iccbased_color_space(gx_device_pdf *pdev, const gs_gstate * pgs, cos_value_t
code =
pdf_make_iccbased(pdev, pgs, pca, pcs->cmm_icc_profile_data->num_comps,
pcs->cmm_icc_profile_data->Range.ranges,
- pcs->base_space,
+ pcs,
&pcstrm, NULL);
if (code < 0)
@@ -896,7 +1193,7 @@ pdf_convert_cie_space(gx_device_pdf *pdev, cos_array_t *pca,
/* PDF 1.2 or earlier, use a Lab space. */
pdf_convert_cie_to_lab(pdev, pca, pcs, pciec, prange) :
/* PDF 1.3 or later, use an ICCBased space. */
- pdf_convert_cie_to_iccbased(pdev, pca, pcs, dcsname, pciec, prange,
+ pdf_convert_cie_to_iccbased(pdev, pca, (gs_color_space *)pcs, dcsname, pciec, prange,
one_step, pmat, pprange)
);
}
diff --git a/pdf/pdf_colour.c b/pdf/pdf_colour.c
index 9d1a7b36a..f47313d02 100644
--- a/pdf/pdf_colour.c
+++ b/pdf/pdf_colour.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2018-2022 Artifex Software, Inc.
+/* Copyright (C) 2018-2023 Artifex Software, Inc.
All Rights Reserved.
This software is provided AS-IS with no warranty, either express or
@@ -153,6 +153,8 @@ static int pdfi_check_for_spots_by_array(pdf_context *ctx, pdf_array *color_arra
goto exit;
} else if (pdfi_name_is(space, "CalGray")) {
goto exit;
+ } else if (pdfi_name_is(space, "CalCMYK")) {
+ goto exit;
} else if (pdfi_name_is(space, "ICCBased")) {
goto exit;
} else if (pdfi_name_is(space, "DeviceRGB")) {
@@ -1092,13 +1094,286 @@ static int pdfi_create_iccprofile(pdf_context *ctx, pdf_stream *ICC_obj, char *c
return code;
}
+static int pdfi_set_CalGray_params(pdf_context *ctx, gs_color_space *pcs, pdf_dict *ParamsDict)
+{
+ int code = 0, i;
+ double f;
+ /* The default values here are as per the PDF 1.7 specification, there is
+ * no default for the WhitePoint as it is a required entry.
+ */
+ float WhitePoint[3], BlackPoint[3] = {0.0f, 0.0f, 0.0f}, Gamma = 1.0f;
+ pdf_array *PDFArray = NULL;
+
+ code = pdfi_dict_get_type(ctx, ParamsDict, "WhitePoint", PDF_ARRAY, (pdf_obj **)&PDFArray);
+ if (code < 0) {
+ pdfi_countdown(PDFArray);
+ goto exit;
+ }
+ if (pdfi_array_size(PDFArray) != 3){
+ code = gs_note_error(gs_error_rangecheck);
+ goto exit;
+ }
+
+ for (i=0; i < 3; i++) {
+ code = pdfi_array_get_number(ctx, PDFArray, (uint64_t)i, &f);
+ if (code < 0)
+ goto exit;
+ WhitePoint[i] = (float)f;
+ }
+ pdfi_countdown(PDFArray);
+ PDFArray = NULL;
+
+ /* Check the WhitePoint values, the PDF 1.7 reference states that
+ * Xw ad Zw must be positive and Yw must be 1.0
+ */
+ if (WhitePoint[0] < 0 || WhitePoint[2] < 0 || WhitePoint[1] != 1.0f) {
+ code = gs_note_error(gs_error_rangecheck);
+ goto exit;
+ }
+
+ if (pdfi_dict_knownget_type(ctx, ParamsDict, "BlackPoint", PDF_ARRAY, (pdf_obj **)&PDFArray) > 0) {
+ if (pdfi_array_size(PDFArray) != 3){
+ code = gs_note_error(gs_error_rangecheck);
+ goto exit;
+ }
+ for (i=0; i < 3; i++) {
+ code = pdfi_array_get_number(ctx, PDFArray, (uint64_t)i, &f);
+ if (code < 0)
+ goto exit;
+ /* The PDF 1.7 reference states that all three components of the BlackPoint
+ * (if present) must be positive.
+ */
+ if (f < 0) {
+ code = gs_note_error(gs_error_rangecheck);
+ goto exit;
+ }
+ BlackPoint[i] = (float)f;
+ }
+ pdfi_countdown(PDFArray);
+ PDFArray = NULL;
+ }
+
+ if (pdfi_dict_knownget_number(ctx, ParamsDict, "Gamma", &f) > 0)
+ Gamma = (float)f;
+ /* The PDF 1.7 reference states that Gamma
+ * (if present) must be positive.
+ */
+ if (Gamma < 0) {
+ code = gs_note_error(gs_error_rangecheck);
+ goto exit;
+ }
+ code = 0;
+
+ for (i = 0;i < 3; i++) {
+ pcs->params.calgray.WhitePoint[i] = WhitePoint[i];
+ pcs->params.calgray.BlackPoint[i] = BlackPoint[i];
+ }
+ pcs->params.calgray.Gamma = Gamma;
+
+exit:
+ pdfi_countdown(PDFArray);
+ return code;
+}
+
+static int pdfi_set_CalRGB_params(pdf_context *ctx, gs_color_space *pcs, pdf_dict *ParamsDict)
+{
+ int code = 0, i;
+ pdf_array *PDFArray = NULL;
+ /* The default values here are as per the PDF 1.7 specification, there is
+ * no default for the WhitePoint as it is a required entry
+ */
+ float WhitePoint[3], BlackPoint[3] = {0.0f, 0.0f, 0.0f}, Gamma[3] = {1.0f, 1.0f, 1.0f};
+ float Matrix[9] = {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f};
+ double f;
+
+ code = pdfi_dict_get_type(ctx, ParamsDict, "WhitePoint", PDF_ARRAY, (pdf_obj **)&PDFArray);
+ if (code < 0) {
+ pdfi_countdown(PDFArray);
+ goto exit;
+ }
+ if (pdfi_array_size(PDFArray) != 3){
+ code = gs_note_error(gs_error_rangecheck);
+ goto exit;
+ }
+
+ for (i=0; i < 3; i++) {
+ code = pdfi_array_get_number(ctx, PDFArray, (uint64_t)i, &f);
+ if (code < 0)
+ goto exit;
+ WhitePoint[i] = (float)f;
+ }
+ pdfi_countdown(PDFArray);
+ PDFArray = NULL;
+
+ /* Check the WhitePoint values, the PDF 1.7 reference states that
+ * Xw ad Zw must be positive and Yw must be 1.0
+ */
+ if (WhitePoint[0] < 0 || WhitePoint[2] < 0 || WhitePoint[1] != 1.0f) {
+ code = gs_note_error(gs_error_rangecheck);
+ goto exit;
+ }
+
+ if (pdfi_dict_knownget_type(ctx, ParamsDict, "BlackPoint", PDF_ARRAY, (pdf_obj **)&PDFArray) > 0) {
+ if (pdfi_array_size(PDFArray) != 3){
+ code = gs_note_error(gs_error_rangecheck);
+ goto exit;
+ }
+ for (i=0; i < 3; i++) {
+ code = pdfi_array_get_number(ctx, PDFArray, (uint64_t)i, &f);
+ if (code < 0)
+ goto exit;
+ /* The PDF 1.7 reference states that all three components of the BlackPoint
+ * (if present) must be positive.
+ */
+ if (f < 0) {
+ code = gs_note_error(gs_error_rangecheck);
+ goto exit;
+ }
+ BlackPoint[i] = (float)f;
+ }
+ pdfi_countdown(PDFArray);
+ PDFArray = NULL;
+ }
+
+ if (pdfi_dict_knownget_type(ctx, ParamsDict, "Gamma", PDF_ARRAY, (pdf_obj **)&PDFArray) > 0) {
+ if (pdfi_array_size(PDFArray) != 3){
+ code = gs_note_error(gs_error_rangecheck);
+ goto exit;
+ }
+ for (i=0; i < 3; i++) {
+ code = pdfi_array_get_number(ctx, PDFArray, (uint64_t)i, &f);
+ if (code < 0)
+ goto exit;
+ Gamma[i] = (float)f;
+ }
+ pdfi_countdown(PDFArray);
+ PDFArray = NULL;
+ }
+
+ if (pdfi_dict_knownget_type(ctx, ParamsDict, "Matrix", PDF_ARRAY, (pdf_obj **)&PDFArray) > 0) {
+ if (pdfi_array_size(PDFArray) != 9){
+ code = gs_note_error(gs_error_rangecheck);
+ goto exit;
+ }
+ for (i=0; i < 9; i++) {
+ code = pdfi_array_get_number(ctx, PDFArray, (uint64_t)i, &f);
+ if (code < 0)
+ goto exit;
+ Matrix[i] = (float)f;
+ }
+ pdfi_countdown(PDFArray);
+ PDFArray = NULL;
+ }
+ code = 0;
+
+ for (i = 0;i < 3; i++) {
+ pcs->params.calrgb.WhitePoint[i] = WhitePoint[i];
+ pcs->params.calrgb.BlackPoint[i] = BlackPoint[i];
+ pcs->params.calrgb.Gamma[i] = Gamma[i];
+ }
+ for (i = 0;i < 9; i++)
+ pcs->params.calrgb.Matrix[i] = Matrix[i];
+
+exit:
+ pdfi_countdown(PDFArray);
+ return code;
+}
+
+static int pdfi_set_Lab_params(pdf_context *ctx, gs_color_space *pcs, pdf_dict *ParamsDict)
+{
+ int code = 0, i;
+ pdf_array *PDFArray = NULL;
+ /* The default values here are as per the PDF 1.7 specification, there is
+ * no default for the WhitePoint as it is a required entry
+ */
+ float WhitePoint[3], BlackPoint[3] = {0.0f, 0.0f, 0.0f}, Range[4] = {-100.0, 100.0, -100.0, 100.0};
+ double f;
+
+ code = pdfi_dict_get_type(ctx, ParamsDict, "WhitePoint", PDF_ARRAY, (pdf_obj **)&PDFArray);
+ if (code < 0) {
+ pdfi_countdown(PDFArray);
+ goto exit;
+ }
+ if (pdfi_array_size(PDFArray) != 3){
+ code = gs_note_error(gs_error_rangecheck);
+ goto exit;
+ }
+
+ for (i=0; i < 3; i++) {
+ code = pdfi_array_get_number(ctx, PDFArray, (uint64_t)i, &f);
+ if (code < 0)
+ goto exit;
+ WhitePoint[i] = (float)f;
+ }
+ pdfi_countdown(PDFArray);
+ PDFArray = NULL;
+
+ /* Check the WhitePoint values, the PDF 1.7 reference states that
+ * Xw ad Zw must be positive and Yw must be 1.0
+ */
+ if (WhitePoint[0] < 0 || WhitePoint[2] < 0 || WhitePoint[1] != 1.0f) {
+ code = gs_note_error(gs_error_rangecheck);
+ goto exit;
+ }
+
+ if (pdfi_dict_knownget_type(ctx, ParamsDict, "BlackPoint", PDF_ARRAY, (pdf_obj **)&PDFArray) > 0) {
+ if (pdfi_array_size(PDFArray) != 3){
+ code = gs_note_error(gs_error_rangecheck);
+ goto exit;
+ }
+ for (i=0; i < 3; i++) {
+ code = pdfi_array_get_number(ctx, PDFArray, (uint64_t)i, &f);
+ if (code < 0)
+ goto exit;
+ /* The PDF 1.7 reference states that all three components of the BlackPoint
+ * (if present) must be positive.
+ */
+ if (f < 0) {
+ code = gs_note_error(gs_error_rangecheck);
+ goto exit;
+ }
+ BlackPoint[i] = (float)f;
+ }
+ pdfi_countdown(PDFArray);
+ PDFArray = NULL;
+ }
+
+ if (pdfi_dict_knownget_type(ctx, ParamsDict, "Range", PDF_ARRAY, (pdf_obj **)&PDFArray) > 0) {
+ if (pdfi_array_size(PDFArray) != 4){
+ code = gs_note_error(gs_error_rangecheck);
+ goto exit;
+ }
+
+ for (i=0; i < 4; i++) {
+ code = pdfi_array_get_number(ctx, PDFArray, (uint64_t)i, &f);
+ if (code < 0)
+ goto exit;
+ Range[i] = f;
+ }
+ pdfi_countdown(PDFArray);
+ PDFArray = NULL;
+ }
+ code = 0;
+
+ for (i = 0;i < 3; i++) {
+ pcs->params.lab.WhitePoint[i] = WhitePoint[i];
+ pcs->params.lab.BlackPoint[i] = BlackPoint[i];
+ }
+ for (i = 0;i < 4; i++)
+ pcs->params.lab.Range[i] = Range[i];
+
+exit:
+ pdfi_countdown(PDFArray);
+ return code;
+}
+
static int pdfi_create_iccbased(pdf_context *ctx, pdf_array *color_array, int index, pdf_dict *stream_dict, pdf_dict *page_dict, gs_color_space **ppcs, bool inline_image)
{
pdf_stream *ICC_obj = NULL;
pdf_dict *dict; /* Alias to avoid tons of casting */
pdf_array *a;
int64_t Length, N;
- pdf_obj *Name = NULL;
+ pdf_obj *Name = NULL, *Alt = NULL;
char *cname = NULL;
int code;
bool known = true;
@@ -1252,7 +1527,66 @@ static int pdfi_create_iccbased(pdf_context *ctx, pdf_array *color_array, int in
code = gs_note_error(gs_error_undefined);
break;
}
+ } else {
+ if (pcs->ICC_Alternate_space == gs_ICC_Alternate_None) {
+ code = pdfi_dict_knownget(ctx, dict, "Alternate", (pdf_obj **)&Alt);
+ if (code >= 0) {
+ switch(pdfi_type_of(Alt)) {
+ case PDF_NAME:
+ /* Simple named spaces must be Gray, RGB or CMYK, we ignore /Indexed */
+ if (pdfi_name_is((const pdf_name *)Alt, "DeviceGray"))
+ pcs->ICC_Alternate_space = gs_ICC_Alternate_DeviceGray;
+ else if (pdfi_name_is((const pdf_name *)Alt, "DeviceRGB"))
+ pcs->ICC_Alternate_space = gs_ICC_Alternate_DeviceRGB;
+ else if (pdfi_name_is((const pdf_name *)Alt, "DeviceCMYK"))
+ pcs->ICC_Alternate_space = gs_ICC_Alternate_DeviceCMYK;
+ break;
+ case PDF_ARRAY:
+ {
+ pdf_obj *AltName = NULL, *ParamsDict = NULL;
+
+ code = pdfi_array_get_type(ctx, (pdf_array *)Alt, 0, PDF_NAME, &AltName);
+ if (code >= 0) {
+ code = pdfi_array_get_type(ctx, (pdf_array *)Alt, 1, PDF_DICT, &ParamsDict);
+ if (code >= 0) {
+ if (pdfi_name_is((const pdf_name *)AltName, "CalGray")) {
+ code = pdfi_set_CalGray_params(ctx, pcs, (pdf_dict *)ParamsDict);
+ if (code >= 0)
+ pcs->ICC_Alternate_space = gs_ICC_Alternate_CalGray;
+ } else {
+ if (pdfi_name_is((const pdf_name *)AltName, "CalRGB")) {
+ code = pdfi_set_CalRGB_params(ctx, pcs, (pdf_dict *)ParamsDict);
+ if (code >= 0)
+ pcs->ICC_Alternate_space = gs_ICC_Alternate_CalRGB;
+ } else {
+ if (pdfi_name_is((const pdf_name *)AltName, "CalCMYK")) {
+ pcs->ICC_Alternate_space = gs_ICC_Alternate_DeviceCMYK;
+ } else {
+ if (pdfi_name_is((const pdf_name *)AltName, "Lab")) {
+ code = pdfi_set_Lab_params(ctx, pcs, (pdf_dict *)ParamsDict);
+ if (code >= 0)
+ pcs->ICC_Alternate_space = gs_ICC_Alternate_Lab;
+ }
+ }
+ }
+ }
+ }
+ }
+ code = 0;
+ pdfi_countdown(ParamsDict);
+ pdfi_countdown(AltName);
+ }
+ break;
+ default:
+ /* Probably an error, but just ignore it */
+ break;
+ }
+ pdfi_countdown(Alt);
+ Alt = NULL;
+ }
+ }
}
+
if (ppcs!= NULL) {
*ppcs = pcs;
if (pcs != NULL)
@@ -1270,6 +1604,7 @@ static int pdfi_create_iccbased(pdf_context *ctx, pdf_array *color_array, int in
done:
if (cname)
gs_free_object(ctx->memory, cname, "pdfi_create_iccbased (profile name)");
+ pdfi_countdown(Alt);
pdfi_countdown(Name);
pdfi_countdown(ICC_obj);
return code;
@@ -2353,7 +2688,7 @@ pdfi_create_colorspace_by_array(pdf_context *ctx, pdf_array *color_array, int in
return_error(gs_error_syntaxerror);
}
code = pdfi_create_DeviceRGB(ctx, ppcs);
- } else if (pdfi_name_is(space, "CMYK") || pdfi_name_is(space, "DeviceCMYK")) {
+ } else if (pdfi_name_is(space, "CMYK") || pdfi_name_is(space, "DeviceCMYK") || pdfi_name_is(space, "CalCMYK")) {
if (pdfi_name_is(space, "CMYK") && !inline_image) {
pdfi_set_warning(ctx, 0, NULL, W_PDF_BAD_INLINECOLORSPACE, "pdfi_create_colorspace_by_array", NULL);
if (ctx->args.pdfstoponwarning)
@@ -2420,7 +2755,7 @@ pdfi_create_colorspace_by_name(pdf_context *ctx, pdf_name *name,
return_error(gs_error_syntaxerror);
}
code = pdfi_create_DeviceRGB(ctx, ppcs);
- } else if (pdfi_name_is(name, "CMYK") || pdfi_name_is(name, "DeviceCMYK")) {
+ } else if (pdfi_name_is(name, "CMYK") || pdfi_name_is(name, "DeviceCMYK") || pdfi_name_is(name, "CalCMYK")) {
if (pdfi_name_is(name, "CMYK") && !inline_image) {
pdfi_set_warning(ctx, 0, NULL, W_PDF_BAD_INLINECOLORSPACE, "pdfi_create_colorspace_by_name", NULL);
if (ctx->args.pdfstoponwarning)