eBookReaderSwitch/source/pdf/pdf-colorspace.c

506 lines
12 KiB
C

#include "mupdf/fitz.h"
#include "mupdf/pdf.h"
#include <string.h>
/* ICCBased */
static fz_colorspace *
load_icc_based(fz_context *ctx, pdf_obj *dict, int allow_alt)
{
int n = pdf_dict_get_int(ctx, dict, PDF_NAME(N));
fz_colorspace *alt = NULL;
fz_colorspace *cs = NULL;
pdf_obj *obj;
/* Look at Alternate to detect type (especially Lab). */
if (allow_alt)
{
obj = pdf_dict_get(ctx, dict, PDF_NAME(Alternate));
if (obj)
{
fz_try(ctx)
alt = pdf_load_colorspace(ctx, obj);
fz_catch(ctx)
{
fz_rethrow_if(ctx, FZ_ERROR_TRYLATER);
fz_warn(ctx, "ignoring broken ICC Alternate colorspace");
}
}
}
#if FZ_ENABLE_ICC
{
fz_buffer *buf = NULL;
fz_var(buf);
fz_try(ctx)
{
buf = pdf_load_stream(ctx, dict);
cs = fz_new_icc_colorspace(ctx, alt ? alt->type : FZ_COLORSPACE_NONE, 0, NULL, buf);
if (cs->n != n)
fz_warn(ctx, "ICC colorspace N=%d does not match profile N=%d", n, cs->n);
}
fz_always(ctx)
fz_drop_buffer(ctx, buf);
fz_catch(ctx)
{
fz_rethrow_if(ctx, FZ_ERROR_TRYLATER);
fz_warn(ctx, "ignoring broken ICC profile");
}
}
#endif
if (!cs)
cs = alt;
else
fz_drop_colorspace(ctx, alt);
if (!cs)
{
if (n == 1) cs = fz_keep_colorspace(ctx, fz_device_gray(ctx));
else if (n == 3) cs = fz_keep_colorspace(ctx, fz_device_rgb(ctx));
else if (n == 4) cs = fz_keep_colorspace(ctx, fz_device_cmyk(ctx));
else fz_throw(ctx, FZ_ERROR_SYNTAX, "invalid ICC colorspace");
}
return cs;
}
static void
devicen_eval(fz_context *ctx, void *tint, const float *sv, int sn, float *dv, int dn)
{
pdf_eval_function(ctx, tint, sv, sn, dv, dn);
}
static void
devicen_drop(fz_context *ctx, void *tint)
{
pdf_drop_function(ctx, tint);
}
static fz_colorspace *
load_devicen(fz_context *ctx, pdf_obj *array, int is_devn)
{
fz_colorspace *base = NULL;
fz_colorspace *cs = NULL;
pdf_obj *nameobj = pdf_array_get(ctx, array, 1);
pdf_obj *baseobj = pdf_array_get(ctx, array, 2);
pdf_obj *tintobj = pdf_array_get(ctx, array, 3);
char name[100];
int i, n;
if (pdf_is_array(ctx, nameobj))
{
n = pdf_array_len(ctx, nameobj);
if (n < 1)
fz_throw(ctx, FZ_ERROR_SYNTAX, "too few components in DeviceN colorspace");
if (n > FZ_MAX_COLORS)
fz_throw(ctx, FZ_ERROR_SYNTAX, "too many components in DeviceN colorspace");
}
else
{
n = 1;
}
base = pdf_load_colorspace(ctx, baseobj);
fz_try(ctx)
{
if (is_devn)
{
fz_snprintf(name, sizeof name, "DeviceN(%d,%s", n, base->name);
for (i = 0; i < n; i++) {
fz_strlcat(name, ",", sizeof name);
fz_strlcat(name, pdf_array_get_name(ctx, nameobj, i), sizeof name);
}
fz_strlcat(name, ")", sizeof name);
}
else
{
fz_snprintf(name, sizeof name, "Separation(%s,%s)", base->name, pdf_to_name(ctx, nameobj));
}
cs = fz_new_colorspace(ctx, FZ_COLORSPACE_SEPARATION, 0, n, name);
cs->u.separation.eval = devicen_eval;
cs->u.separation.drop = devicen_drop;
cs->u.separation.base = fz_keep_colorspace(ctx, base);
cs->u.separation.tint = pdf_load_function(ctx, tintobj, n, cs->u.separation.base->n);
if (pdf_is_array(ctx, nameobj))
for (i = 0; i < n; i++)
fz_colorspace_name_colorant(ctx, cs, i, pdf_to_name(ctx, pdf_array_get(ctx, nameobj, i)));
else
fz_colorspace_name_colorant(ctx, cs, 0, pdf_to_name(ctx, nameobj));
}
fz_always(ctx)
{
fz_drop_colorspace(ctx, base);
}
fz_catch(ctx)
{
fz_drop_colorspace(ctx, cs);
fz_rethrow(ctx);
}
return cs;
}
int
pdf_is_tint_colorspace(fz_context *ctx, fz_colorspace *cs)
{
return cs && cs->type == FZ_COLORSPACE_SEPARATION;
}
/* Indexed */
static fz_colorspace *
load_indexed(fz_context *ctx, pdf_obj *array)
{
pdf_obj *baseobj = pdf_array_get(ctx, array, 1);
pdf_obj *highobj = pdf_array_get(ctx, array, 2);
pdf_obj *lookupobj = pdf_array_get(ctx, array, 3);
fz_colorspace *base = NULL;
fz_colorspace *cs;
int i, n, high;
unsigned char *lookup = NULL;
fz_var(base);
fz_var(lookup);
fz_try(ctx)
{
base = pdf_load_colorspace(ctx, baseobj);
high = pdf_to_int(ctx, highobj);
high = fz_clampi(high, 0, 255);
n = base->n * (high + 1);
lookup = fz_malloc(ctx, n);
if (pdf_is_string(ctx, lookupobj))
{
int sn = fz_mini(n, pdf_to_str_len(ctx, lookupobj));
unsigned char *buf = (unsigned char *) pdf_to_str_buf(ctx, lookupobj);
for (i = 0; i < sn; ++i)
lookup[i] = buf[i];
for (; i < n; ++i)
lookup[i] = 0;
}
else if (pdf_is_indirect(ctx, lookupobj))
{
fz_stream *file = NULL;
fz_var(file);
fz_try(ctx)
{
file = pdf_open_stream(ctx, lookupobj);
i = (int)fz_read(ctx, file, lookup, n);
if (i < n)
memset(lookup+i, 0, n-i);
}
fz_always(ctx)
{
fz_drop_stream(ctx, file);
}
fz_catch(ctx)
{
fz_rethrow(ctx);
}
}
else
{
fz_throw(ctx, FZ_ERROR_SYNTAX, "cannot parse colorspace lookup table");
}
cs = fz_new_indexed_colorspace(ctx, base, high, lookup);
}
fz_always(ctx)
fz_drop_colorspace(ctx, base);
fz_catch(ctx)
{
fz_free(ctx, lookup);
fz_rethrow(ctx);
}
return cs;
}
static void
pdf_load_cal_common(fz_context *ctx, pdf_obj *dict, float *wp, float *bp, float *gamma)
{
pdf_obj *obj;
int i;
obj = pdf_dict_get(ctx, dict, PDF_NAME(WhitePoint));
if (pdf_array_len(ctx, obj) != 3)
fz_throw(ctx, FZ_ERROR_SYNTAX, "WhitePoint must be a 3-element array");
for (i = 0; i < 3; i++)
{
wp[i] = pdf_array_get_real(ctx, obj, i);
if (wp[i] < 0)
fz_throw(ctx, FZ_ERROR_SYNTAX, "WhitePoint numbers must be positive");
}
if (wp[1] != 1)
fz_throw(ctx, FZ_ERROR_SYNTAX, "WhitePoint Yw must be 1.0");
obj = pdf_dict_get(ctx, dict, PDF_NAME(BlackPoint));
if (pdf_array_len(ctx, obj) == 3)
{
for (i = 0; i < 3; i++)
{
bp[i] = pdf_array_get_real(ctx, obj, i);
if (bp[i] < 0)
fz_throw(ctx, FZ_ERROR_SYNTAX, "BlackPoint numbers must be positive");
}
}
obj = pdf_dict_get(ctx, dict, PDF_NAME(Gamma));
if (pdf_is_number(ctx, obj))
{
gamma[0] = pdf_to_real(ctx, obj);
gamma[1] = gamma[2];
if (gamma[0] <= 0)
fz_throw(ctx, FZ_ERROR_SYNTAX, "Gamma must be greater than zero");
}
else if (pdf_array_len(ctx, obj) == 3)
{
for (i = 0; i < 3; i++)
{
gamma[i] = pdf_array_get_real(ctx, obj, i);
if (gamma[i] <= 0)
fz_throw(ctx, FZ_ERROR_SYNTAX, "Gamma must be greater than zero");
}
}
}
static fz_colorspace *
pdf_load_cal_gray(fz_context *ctx, pdf_obj *dict)
{
float wp[3];
float bp[3] = { 0, 0, 0 };
float gamma[3] = { 1, 1, 1 };
if (dict == NULL)
return fz_keep_colorspace(ctx, fz_device_gray(ctx));
fz_try(ctx)
pdf_load_cal_common(ctx, dict, wp, bp, gamma);
fz_catch(ctx)
return fz_keep_colorspace(ctx, fz_device_gray(ctx));
return fz_new_cal_gray_colorspace(ctx, wp, bp, gamma[0]);
}
static fz_colorspace *
pdf_load_cal_rgb(fz_context *ctx, pdf_obj *dict)
{
pdf_obj *obj;
float matrix[9] = { 1, 0, 0, 0, 1, 0, 0, 0, 1 };
float wp[3];
float bp[3] = { 0, 0, 0 };
float gamma[3] = { 1, 1, 1 };
int i;
if (dict == NULL)
return fz_keep_colorspace(ctx, fz_device_rgb(ctx));
fz_try(ctx)
{
pdf_load_cal_common(ctx, dict, wp, bp, gamma);
obj = pdf_dict_get(ctx, dict, PDF_NAME(Matrix));
if (pdf_array_len(ctx, obj) == 9)
{
for (i = 0; i < 9; i++)
matrix[i] = pdf_array_get_real(ctx, obj, i);
}
}
fz_catch(ctx)
return fz_keep_colorspace(ctx, fz_device_rgb(ctx));
return fz_new_cal_rgb_colorspace(ctx, wp, bp, gamma, matrix);
}
/* Parse and create colorspace from PDF object */
static fz_colorspace *
pdf_load_colorspace_imp(fz_context *ctx, pdf_obj *obj)
{
if (pdf_obj_marked(ctx, obj))
fz_throw(ctx, FZ_ERROR_SYNTAX, "recursion in colorspace definition");
if (pdf_is_name(ctx, obj))
{
if (pdf_name_eq(ctx, obj, PDF_NAME(Pattern)))
return fz_keep_colorspace(ctx, fz_device_gray(ctx));
else if (pdf_name_eq(ctx, obj, PDF_NAME(G)))
return fz_keep_colorspace(ctx, fz_device_gray(ctx));
else if (pdf_name_eq(ctx, obj, PDF_NAME(RGB)))
return fz_keep_colorspace(ctx, fz_device_rgb(ctx));
else if (pdf_name_eq(ctx, obj, PDF_NAME(CMYK)))
return fz_keep_colorspace(ctx, fz_device_cmyk(ctx));
else if (pdf_name_eq(ctx, obj, PDF_NAME(DeviceGray)))
return fz_keep_colorspace(ctx, fz_device_gray(ctx));
else if (pdf_name_eq(ctx, obj, PDF_NAME(DeviceRGB)))
return fz_keep_colorspace(ctx, fz_device_rgb(ctx));
else if (pdf_name_eq(ctx, obj, PDF_NAME(DeviceCMYK)))
return fz_keep_colorspace(ctx, fz_device_cmyk(ctx));
else
fz_throw(ctx, FZ_ERROR_SYNTAX, "unknown colorspace: %s", pdf_to_name(ctx, obj));
}
else if (pdf_is_array(ctx, obj))
{
pdf_obj *name = pdf_array_get(ctx, obj, 0);
if (pdf_is_name(ctx, name))
{
/* load base colorspace instead */
if (pdf_name_eq(ctx, name, PDF_NAME(G)))
return fz_keep_colorspace(ctx, fz_device_gray(ctx));
else if (pdf_name_eq(ctx, name, PDF_NAME(RGB)))
return fz_keep_colorspace(ctx, fz_device_rgb(ctx));
else if (pdf_name_eq(ctx, name, PDF_NAME(CMYK)))
return fz_keep_colorspace(ctx, fz_device_cmyk(ctx));
else if (pdf_name_eq(ctx, name, PDF_NAME(DeviceGray)))
return fz_keep_colorspace(ctx, fz_device_gray(ctx));
else if (pdf_name_eq(ctx, name, PDF_NAME(DeviceRGB)))
return fz_keep_colorspace(ctx, fz_device_rgb(ctx));
else if (pdf_name_eq(ctx, name, PDF_NAME(DeviceCMYK)))
return fz_keep_colorspace(ctx, fz_device_cmyk(ctx));
else if (pdf_name_eq(ctx, name, PDF_NAME(CalGray)))
return pdf_load_cal_gray(ctx, pdf_array_get(ctx, obj, 1));
else if (pdf_name_eq(ctx, name, PDF_NAME(CalRGB)))
return pdf_load_cal_rgb(ctx, pdf_array_get(ctx, obj, 1));
else if (pdf_name_eq(ctx, name, PDF_NAME(CalCMYK)))
return fz_keep_colorspace(ctx, fz_device_cmyk(ctx));
else if (pdf_name_eq(ctx, name, PDF_NAME(Lab)))
return fz_keep_colorspace(ctx, fz_device_lab(ctx));
else
{
fz_colorspace *cs;
fz_try(ctx)
{
if (pdf_mark_obj(ctx, obj))
fz_throw(ctx, FZ_ERROR_SYNTAX, "recursive colorspace");
if (pdf_name_eq(ctx, name, PDF_NAME(ICCBased)))
cs = load_icc_based(ctx, pdf_array_get(ctx, obj, 1), 1);
else if (pdf_name_eq(ctx, name, PDF_NAME(Indexed)))
cs = load_indexed(ctx, obj);
else if (pdf_name_eq(ctx, name, PDF_NAME(I)))
cs = load_indexed(ctx, obj);
else if (pdf_name_eq(ctx, name, PDF_NAME(Separation)))
cs = load_devicen(ctx, obj, 0);
else if (pdf_name_eq(ctx, name, PDF_NAME(DeviceN)))
cs = load_devicen(ctx, obj, 1);
else if (pdf_name_eq(ctx, name, PDF_NAME(Pattern)))
{
pdf_obj *pobj;
pobj = pdf_array_get(ctx, obj, 1);
if (!pobj)
{
cs = fz_keep_colorspace(ctx, fz_device_gray(ctx));
break;
}
cs = pdf_load_colorspace(ctx, pobj);
}
else
fz_throw(ctx, FZ_ERROR_SYNTAX, "unknown colorspace %s", pdf_to_name(ctx, name));
}
fz_always(ctx)
{
pdf_unmark_obj(ctx, obj);
}
fz_catch(ctx)
{
fz_rethrow(ctx);
}
return cs;
}
}
}
/* We have seen files where /DefaultRGB is specified as 1 0 R,
* and 1 0 obj << /Length 3144 /Alternate /DeviceRGB /N 3 >>
* stream ...iccprofile... endstream endobj.
* This *should* be [ /ICCBased 1 0 R ], but Acrobat seems to
* handle it, so do our best. */
else if (pdf_is_dict(ctx, obj))
{
return load_icc_based(ctx, obj, 1);
}
fz_throw(ctx, FZ_ERROR_SYNTAX, "could not parse color space (%d 0 R)", pdf_to_num(ctx, obj));
}
fz_colorspace *
pdf_load_colorspace(fz_context *ctx, pdf_obj *obj)
{
fz_colorspace *cs;
if ((cs = pdf_find_item(ctx, fz_drop_colorspace_imp, obj)) != NULL)
{
return cs;
}
cs = pdf_load_colorspace_imp(ctx, obj);
pdf_store_item(ctx, obj, cs, 1000);
return cs;
}
#if FZ_ENABLE_ICC
static fz_colorspace *
pdf_load_output_intent(fz_context *ctx, pdf_document *doc)
{
pdf_obj *root = pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root));
pdf_obj *intents = pdf_dict_get(ctx, root, PDF_NAME(OutputIntents));
pdf_obj *intent_dict;
pdf_obj *dest_profile;
fz_colorspace *cs = NULL;
/* An array of intents */
if (!intents)
return NULL;
/* For now, always just use the first intent. I have never even seen a file
* with multiple intents but it could happen */
intent_dict = pdf_array_get(ctx, intents, 0);
if (!intent_dict)
return NULL;
dest_profile = pdf_dict_get(ctx, intent_dict, PDF_NAME(DestOutputProfile));
if (!dest_profile)
return NULL;
fz_var(cs);
fz_try(ctx)
cs = load_icc_based(ctx, dest_profile, 0);
fz_catch(ctx)
{
fz_rethrow_if(ctx, FZ_ERROR_TRYLATER);
fz_warn(ctx, "Attempt to read Output Intent failed");
}
return cs;
}
fz_colorspace *
pdf_document_output_intent(fz_context *ctx, pdf_document *doc)
{
if (!doc->oi)
doc->oi = pdf_load_output_intent(ctx, doc);
return doc->oi;
}
#else
fz_colorspace *
pdf_document_output_intent(fz_context *ctx, pdf_document *doc)
{
return NULL;
}
#endif