1#include "mupdf/fitz.h"
2#include "mupdf/pdf.h"
3
4#include <string.h>
5
6/* ICCBased */
7static fz_colorspace *
8load_icc_based(fz_context *ctx, pdf_obj *dict, int allow_alt)
9{
10 int n = pdf_dict_get_int(ctx, dict, PDF_NAME(N));
11 fz_colorspace *alt = NULL;
12 fz_colorspace *cs = NULL;
13 pdf_obj *obj;
14
15 /* Look at Alternate to detect type (especially Lab). */
16 if (allow_alt)
17 {
18 obj = pdf_dict_get(ctx, dict, PDF_NAME(Alternate));
19 if (obj)
20 {
21 fz_try(ctx)
22 alt = pdf_load_colorspace(ctx, obj);
23 fz_catch(ctx)
24 {
25 fz_rethrow_if(ctx, FZ_ERROR_TRYLATER);
26 fz_warn(ctx, "ignoring broken ICC Alternate colorspace");
27 }
28 }
29 }
30
31#if FZ_ENABLE_ICC
32 {
33 fz_buffer *buf = NULL;
34 fz_var(buf);
35 fz_try(ctx)
36 {
37 buf = pdf_load_stream(ctx, dict);
38 cs = fz_new_icc_colorspace(ctx, alt ? alt->type : FZ_COLORSPACE_NONE, 0, NULL, buf);
39 if (cs->n != n)
40 fz_warn(ctx, "ICC colorspace N=%d does not match profile N=%d", n, cs->n);
41 }
42 fz_always(ctx)
43 fz_drop_buffer(ctx, buf);
44 fz_catch(ctx)
45 {
46 fz_rethrow_if(ctx, FZ_ERROR_TRYLATER);
47 fz_warn(ctx, "ignoring broken ICC profile");
48 }
49 }
50#endif
51
52 if (!cs)
53 cs = alt;
54 else
55 fz_drop_colorspace(ctx, alt);
56
57 if (!cs)
58 {
59 if (n == 1) cs = fz_keep_colorspace(ctx, fz_device_gray(ctx));
60 else if (n == 3) cs = fz_keep_colorspace(ctx, fz_device_rgb(ctx));
61 else if (n == 4) cs = fz_keep_colorspace(ctx, fz_device_cmyk(ctx));
62 else fz_throw(ctx, FZ_ERROR_SYNTAX, "invalid ICC colorspace");
63 }
64
65 return cs;
66}
67
68static void
69devicen_eval(fz_context *ctx, void *tint, const float *sv, int sn, float *dv, int dn)
70{
71 pdf_eval_function(ctx, tint, sv, sn, dv, dn);
72}
73
74static void
75devicen_drop(fz_context *ctx, void *tint)
76{
77 pdf_drop_function(ctx, tint);
78}
79
80static fz_colorspace *
81load_devicen(fz_context *ctx, pdf_obj *array, int is_devn)
82{
83 fz_colorspace *base = NULL;
84 fz_colorspace *cs = NULL;
85 pdf_obj *nameobj = pdf_array_get(ctx, array, 1);
86 pdf_obj *baseobj = pdf_array_get(ctx, array, 2);
87 pdf_obj *tintobj = pdf_array_get(ctx, array, 3);
88 char name[100];
89 int i, n;
90
91 if (pdf_is_array(ctx, nameobj))
92 {
93 n = pdf_array_len(ctx, nameobj);
94 if (n < 1)
95 fz_throw(ctx, FZ_ERROR_SYNTAX, "too few components in DeviceN colorspace");
96 if (n > FZ_MAX_COLORS)
97 fz_throw(ctx, FZ_ERROR_SYNTAX, "too many components in DeviceN colorspace");
98 }
99 else
100 {
101 n = 1;
102 }
103
104 base = pdf_load_colorspace(ctx, baseobj);
105 fz_try(ctx)
106 {
107 if (is_devn)
108 {
109 fz_snprintf(name, sizeof name, "DeviceN(%d,%s", n, base->name);
110 for (i = 0; i < n; i++) {
111 fz_strlcat(name, ",", sizeof name);
112 fz_strlcat(name, pdf_array_get_name(ctx, nameobj, i), sizeof name);
113 }
114 fz_strlcat(name, ")", sizeof name);
115 }
116 else
117 {
118 fz_snprintf(name, sizeof name, "Separation(%s,%s)", base->name, pdf_to_name(ctx, nameobj));
119 }
120
121 cs = fz_new_colorspace(ctx, FZ_COLORSPACE_SEPARATION, 0, n, name);
122 cs->u.separation.eval = devicen_eval;
123 cs->u.separation.drop = devicen_drop;
124 cs->u.separation.base = fz_keep_colorspace(ctx, base);
125 cs->u.separation.tint = pdf_load_function(ctx, tintobj, n, cs->u.separation.base->n);
126 if (pdf_is_array(ctx, nameobj))
127 for (i = 0; i < n; i++)
128 fz_colorspace_name_colorant(ctx, cs, i, pdf_to_name(ctx, pdf_array_get(ctx, nameobj, i)));
129 else
130 fz_colorspace_name_colorant(ctx, cs, 0, pdf_to_name(ctx, nameobj));
131 }
132 fz_always(ctx)
133 {
134 fz_drop_colorspace(ctx, base);
135 }
136 fz_catch(ctx)
137 {
138 fz_drop_colorspace(ctx, cs);
139 fz_rethrow(ctx);
140 }
141
142 return cs;
143}
144
145int
146pdf_is_tint_colorspace(fz_context *ctx, fz_colorspace *cs)
147{
148 return cs && cs->type == FZ_COLORSPACE_SEPARATION;
149}
150
151/* Indexed */
152
153static fz_colorspace *
154load_indexed(fz_context *ctx, pdf_obj *array)
155{
156 pdf_obj *baseobj = pdf_array_get(ctx, array, 1);
157 pdf_obj *highobj = pdf_array_get(ctx, array, 2);
158 pdf_obj *lookupobj = pdf_array_get(ctx, array, 3);
159 fz_colorspace *base = NULL;
160 fz_colorspace *cs;
161 int i, n, high;
162 unsigned char *lookup = NULL;
163
164 fz_var(base);
165 fz_var(lookup);
166
167 fz_try(ctx)
168 {
169 base = pdf_load_colorspace(ctx, baseobj);
170
171 high = pdf_to_int(ctx, highobj);
172 high = fz_clampi(high, 0, 255);
173 n = base->n * (high + 1);
174 lookup = fz_malloc(ctx, n);
175
176 if (pdf_is_string(ctx, lookupobj))
177 {
178 int sn = fz_mini(n, pdf_to_str_len(ctx, lookupobj));
179 unsigned char *buf = (unsigned char *) pdf_to_str_buf(ctx, lookupobj);
180 for (i = 0; i < sn; ++i)
181 lookup[i] = buf[i];
182 for (; i < n; ++i)
183 lookup[i] = 0;
184 }
185 else if (pdf_is_indirect(ctx, lookupobj))
186 {
187 fz_stream *file = NULL;
188
189 fz_var(file);
190
191 fz_try(ctx)
192 {
193 file = pdf_open_stream(ctx, lookupobj);
194 i = (int)fz_read(ctx, file, lookup, n);
195 if (i < n)
196 memset(lookup+i, 0, n-i);
197 }
198 fz_always(ctx)
199 {
200 fz_drop_stream(ctx, file);
201 }
202 fz_catch(ctx)
203 {
204 fz_rethrow(ctx);
205 }
206 }
207 else
208 {
209 fz_throw(ctx, FZ_ERROR_SYNTAX, "cannot parse colorspace lookup table");
210 }
211
212 cs = fz_new_indexed_colorspace(ctx, base, high, lookup);
213 }
214 fz_always(ctx)
215 fz_drop_colorspace(ctx, base);
216 fz_catch(ctx)
217 {
218 fz_free(ctx, lookup);
219 fz_rethrow(ctx);
220 }
221
222 return cs;
223}
224
225static void
226pdf_load_cal_common(fz_context *ctx, pdf_obj *dict, float *wp, float *bp, float *gamma)
227{
228 pdf_obj *obj;
229 int i;
230
231 obj = pdf_dict_get(ctx, dict, PDF_NAME(WhitePoint));
232 if (pdf_array_len(ctx, obj) != 3)
233 fz_throw(ctx, FZ_ERROR_SYNTAX, "WhitePoint must be a 3-element array");
234
235 for (i = 0; i < 3; i++)
236 {
237 wp[i] = pdf_array_get_real(ctx, obj, i);
238 if (wp[i] < 0)
239 fz_throw(ctx, FZ_ERROR_SYNTAX, "WhitePoint numbers must be positive");
240 }
241 if (wp[1] != 1)
242 fz_throw(ctx, FZ_ERROR_SYNTAX, "WhitePoint Yw must be 1.0");
243
244 obj = pdf_dict_get(ctx, dict, PDF_NAME(BlackPoint));
245 if (pdf_array_len(ctx, obj) == 3)
246 {
247 for (i = 0; i < 3; i++)
248 {
249 bp[i] = pdf_array_get_real(ctx, obj, i);
250 if (bp[i] < 0)
251 fz_throw(ctx, FZ_ERROR_SYNTAX, "BlackPoint numbers must be positive");
252 }
253 }
254
255 obj = pdf_dict_get(ctx, dict, PDF_NAME(Gamma));
256 if (pdf_is_number(ctx, obj))
257 {
258 gamma[0] = pdf_to_real(ctx, obj);
259 gamma[1] = gamma[2];
260 if (gamma[0] <= 0)
261 fz_throw(ctx, FZ_ERROR_SYNTAX, "Gamma must be greater than zero");
262 }
263 else if (pdf_array_len(ctx, obj) == 3)
264 {
265 for (i = 0; i < 3; i++)
266 {
267 gamma[i] = pdf_array_get_real(ctx, obj, i);
268 if (gamma[i] <= 0)
269 fz_throw(ctx, FZ_ERROR_SYNTAX, "Gamma must be greater than zero");
270 }
271 }
272}
273
274static fz_colorspace *
275pdf_load_cal_gray(fz_context *ctx, pdf_obj *dict)
276{
277 float wp[3];
278 float bp[3] = { 0, 0, 0 };
279 float gamma[3] = { 1, 1, 1 };
280
281 if (dict == NULL)
282 return fz_keep_colorspace(ctx, fz_device_gray(ctx));
283
284 fz_try(ctx)
285 pdf_load_cal_common(ctx, dict, wp, bp, gamma);
286 fz_catch(ctx)
287 return fz_keep_colorspace(ctx, fz_device_gray(ctx));
288 return fz_new_cal_gray_colorspace(ctx, wp, bp, gamma[0]);
289}
290
291static fz_colorspace *
292pdf_load_cal_rgb(fz_context *ctx, pdf_obj *dict)
293{
294 pdf_obj *obj;
295 float matrix[9] = { 1, 0, 0, 0, 1, 0, 0, 0, 1 };
296 float wp[3];
297 float bp[3] = { 0, 0, 0 };
298 float gamma[3] = { 1, 1, 1 };
299 int i;
300
301 if (dict == NULL)
302 return fz_keep_colorspace(ctx, fz_device_rgb(ctx));
303
304 fz_try(ctx)
305 {
306 pdf_load_cal_common(ctx, dict, wp, bp, gamma);
307 obj = pdf_dict_get(ctx, dict, PDF_NAME(Matrix));
308 if (pdf_array_len(ctx, obj) == 9)
309 {
310 for (i = 0; i < 9; i++)
311 matrix[i] = pdf_array_get_real(ctx, obj, i);
312 }
313 }
314 fz_catch(ctx)
315 return fz_keep_colorspace(ctx, fz_device_rgb(ctx));
316 return fz_new_cal_rgb_colorspace(ctx, wp, bp, gamma, matrix);
317}
318
319/* Parse and create colorspace from PDF object */
320
321static fz_colorspace *
322pdf_load_colorspace_imp(fz_context *ctx, pdf_obj *obj)
323{
324 if (pdf_obj_marked(ctx, obj))
325 fz_throw(ctx, FZ_ERROR_SYNTAX, "recursion in colorspace definition");
326
327 if (pdf_is_name(ctx, obj))
328 {
329 if (pdf_name_eq(ctx, obj, PDF_NAME(Pattern)))
330 return fz_keep_colorspace(ctx, fz_device_gray(ctx));
331 else if (pdf_name_eq(ctx, obj, PDF_NAME(G)))
332 return fz_keep_colorspace(ctx, fz_device_gray(ctx));
333 else if (pdf_name_eq(ctx, obj, PDF_NAME(RGB)))
334 return fz_keep_colorspace(ctx, fz_device_rgb(ctx));
335 else if (pdf_name_eq(ctx, obj, PDF_NAME(CMYK)))
336 return fz_keep_colorspace(ctx, fz_device_cmyk(ctx));
337 else if (pdf_name_eq(ctx, obj, PDF_NAME(DeviceGray)))
338 return fz_keep_colorspace(ctx, fz_device_gray(ctx));
339 else if (pdf_name_eq(ctx, obj, PDF_NAME(DeviceRGB)))
340 return fz_keep_colorspace(ctx, fz_device_rgb(ctx));
341 else if (pdf_name_eq(ctx, obj, PDF_NAME(DeviceCMYK)))
342 return fz_keep_colorspace(ctx, fz_device_cmyk(ctx));
343 else
344 fz_throw(ctx, FZ_ERROR_SYNTAX, "unknown colorspace: %s", pdf_to_name(ctx, obj));
345 }
346
347 else if (pdf_is_array(ctx, obj))
348 {
349 pdf_obj *name = pdf_array_get(ctx, obj, 0);
350
351 if (pdf_is_name(ctx, name))
352 {
353 /* load base colorspace instead */
354 if (pdf_name_eq(ctx, name, PDF_NAME(G)))
355 return fz_keep_colorspace(ctx, fz_device_gray(ctx));
356 else if (pdf_name_eq(ctx, name, PDF_NAME(RGB)))
357 return fz_keep_colorspace(ctx, fz_device_rgb(ctx));
358 else if (pdf_name_eq(ctx, name, PDF_NAME(CMYK)))
359 return fz_keep_colorspace(ctx, fz_device_cmyk(ctx));
360 else if (pdf_name_eq(ctx, name, PDF_NAME(DeviceGray)))
361 return fz_keep_colorspace(ctx, fz_device_gray(ctx));
362 else if (pdf_name_eq(ctx, name, PDF_NAME(DeviceRGB)))
363 return fz_keep_colorspace(ctx, fz_device_rgb(ctx));
364 else if (pdf_name_eq(ctx, name, PDF_NAME(DeviceCMYK)))
365 return fz_keep_colorspace(ctx, fz_device_cmyk(ctx));
366 else if (pdf_name_eq(ctx, name, PDF_NAME(CalGray)))
367 return pdf_load_cal_gray(ctx, pdf_array_get(ctx, obj, 1));
368 else if (pdf_name_eq(ctx, name, PDF_NAME(CalRGB)))
369 return pdf_load_cal_rgb(ctx, pdf_array_get(ctx, obj, 1));
370 else if (pdf_name_eq(ctx, name, PDF_NAME(CalCMYK)))
371 return fz_keep_colorspace(ctx, fz_device_cmyk(ctx));
372 else if (pdf_name_eq(ctx, name, PDF_NAME(Lab)))
373 return fz_keep_colorspace(ctx, fz_device_lab(ctx));
374 else
375 {
376 fz_colorspace *cs;
377 fz_try(ctx)
378 {
379 if (pdf_mark_obj(ctx, obj))
380 fz_throw(ctx, FZ_ERROR_SYNTAX, "recursive colorspace");
381 if (pdf_name_eq(ctx, name, PDF_NAME(ICCBased)))
382 cs = load_icc_based(ctx, pdf_array_get(ctx, obj, 1), 1);
383
384 else if (pdf_name_eq(ctx, name, PDF_NAME(Indexed)))
385 cs = load_indexed(ctx, obj);
386 else if (pdf_name_eq(ctx, name, PDF_NAME(I)))
387 cs = load_indexed(ctx, obj);
388
389 else if (pdf_name_eq(ctx, name, PDF_NAME(Separation)))
390 cs = load_devicen(ctx, obj, 0);
391
392 else if (pdf_name_eq(ctx, name, PDF_NAME(DeviceN)))
393 cs = load_devicen(ctx, obj, 1);
394 else if (pdf_name_eq(ctx, name, PDF_NAME(Pattern)))
395 {
396 pdf_obj *pobj;
397
398 pobj = pdf_array_get(ctx, obj, 1);
399 if (!pobj)
400 {
401 cs = fz_keep_colorspace(ctx, fz_device_gray(ctx));
402 break;
403 }
404
405 cs = pdf_load_colorspace(ctx, pobj);
406 }
407 else
408 fz_throw(ctx, FZ_ERROR_SYNTAX, "unknown colorspace %s", pdf_to_name(ctx, name));
409 }
410 fz_always(ctx)
411 {
412 pdf_unmark_obj(ctx, obj);
413 }
414 fz_catch(ctx)
415 {
416 fz_rethrow(ctx);
417 }
418 return cs;
419 }
420 }
421 }
422
423 /* We have seen files where /DefaultRGB is specified as 1 0 R,
424 * and 1 0 obj << /Length 3144 /Alternate /DeviceRGB /N 3 >>
425 * stream ...iccprofile... endstream endobj.
426 * This *should* be [ /ICCBased 1 0 R ], but Acrobat seems to
427 * handle it, so do our best. */
428 else if (pdf_is_dict(ctx, obj))
429 {
430 return load_icc_based(ctx, obj, 1);
431 }
432
433 fz_throw(ctx, FZ_ERROR_SYNTAX, "could not parse color space (%d 0 R)", pdf_to_num(ctx, obj));
434}
435
436fz_colorspace *
437pdf_load_colorspace(fz_context *ctx, pdf_obj *obj)
438{
439 fz_colorspace *cs;
440
441 if ((cs = pdf_find_item(ctx, fz_drop_colorspace_imp, obj)) != NULL)
442 {
443 return cs;
444 }
445
446 cs = pdf_load_colorspace_imp(ctx, obj);
447
448 pdf_store_item(ctx, obj, cs, 1000);
449
450 return cs;
451}
452
453#if FZ_ENABLE_ICC
454
455static fz_colorspace *
456pdf_load_output_intent(fz_context *ctx, pdf_document *doc)
457{
458 pdf_obj *root = pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root));
459 pdf_obj *intents = pdf_dict_get(ctx, root, PDF_NAME(OutputIntents));
460 pdf_obj *intent_dict;
461 pdf_obj *dest_profile;
462 fz_colorspace *cs = NULL;
463
464 /* An array of intents */
465 if (!intents)
466 return NULL;
467
468 /* For now, always just use the first intent. I have never even seen a file
469 * with multiple intents but it could happen */
470 intent_dict = pdf_array_get(ctx, intents, 0);
471 if (!intent_dict)
472 return NULL;
473 dest_profile = pdf_dict_get(ctx, intent_dict, PDF_NAME(DestOutputProfile));
474 if (!dest_profile)
475 return NULL;
476
477 fz_var(cs);
478
479 fz_try(ctx)
480 cs = load_icc_based(ctx, dest_profile, 0);
481 fz_catch(ctx)
482 {
483 fz_rethrow_if(ctx, FZ_ERROR_TRYLATER);
484 fz_warn(ctx, "Attempt to read Output Intent failed");
485 }
486 return cs;
487}
488
489fz_colorspace *
490pdf_document_output_intent(fz_context *ctx, pdf_document *doc)
491{
492 if (!doc->oi)
493 doc->oi = pdf_load_output_intent(ctx, doc);
494 return doc->oi;
495}
496
497#else
498
499fz_colorspace *
500pdf_document_output_intent(fz_context *ctx, pdf_document *doc)
501{
502 return NULL;
503}
504
505#endif
506