xref: /plan9/sys/src/cmd/gs/src/gdevpdfp.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 1996, 2000, 2001 Aladdin Enterprises.  All rights reserved.
2 
3   This software is provided AS-IS with no warranty, either express or
4   implied.
5 
6   This software is distributed under license and may not be copied,
7   modified or distributed except as expressly authorized under the terms
8   of the license contained in the file LICENSE in this distribution.
9 
10   For more information about licensing, please refer to
11   http://www.ghostscript.com/licensing/. For information on
12   commercial licensing, go to http://www.artifex.com/licensing/ or
13   contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14   San Rafael, CA  94903, U.S.A., +1(415)492-9861.
15 */
16 
17 /* $Id: gdevpdfp.c,v 1.53 2005/09/12 11:34:50 leonardo Exp $ */
18 /* Get/put parameters for PDF-writing driver */
19 #include "memory_.h"
20 #include "string_.h"
21 #include "gx.h"
22 #include "gserrors.h"
23 #include "gdevpdfx.h"
24 #include "gdevpdfo.h"
25 #include "gdevpdfg.h"
26 #include "gsparamx.h"
27 
28 /*
29  * The pdfwrite device supports the following "real" parameters:
30  *      OutputFile <string>
31  *      all the Distiller parameters (also see gdevpsdp.c)
32  * Only some of the Distiller parameters actually have any effect.
33  *
34  * The device also supports the following write-only pseudo-parameters that
35  * serve only to communicate other information from the PostScript file.
36  * Their "value" is an array of strings, some of which may be the result
37  * of converting arbitrary PostScript objects to string form.
38  *      pdfmark - see gdevpdfm.c
39  *	DSC - processed in this file
40  */
41 private int pdf_dsc_process(gx_device_pdf * pdev,
42 			    const gs_param_string_array * pma);
43 
44 private const int CoreDistVersion = 5000;	/* Distiller 5.0 */
45 private const gs_param_item_t pdf_param_items[] = {
46 #define pi(key, type, memb) { key, type, offset_of(gx_device_pdf, memb) }
47 
48 	/* Acrobat Distiller 4 parameters */
49 
50     /*
51      * EndPage and StartPage are renamed because EndPage collides with
52      * a page device parameter.
53      */
54     pi("PDFEndPage", gs_param_type_int, EndPage),
55     pi("PDFStartPage", gs_param_type_int, StartPage),
56     pi("Optimize", gs_param_type_bool, Optimize),
57     pi("ParseDSCCommentsForDocInfo", gs_param_type_bool,
58        ParseDSCCommentsForDocInfo),
59     pi("ParseDSCComments", gs_param_type_bool, ParseDSCComments),
60     pi("EmitDSCWarnings", gs_param_type_bool, EmitDSCWarnings),
61     pi("CreateJobTicket", gs_param_type_bool, CreateJobTicket),
62     pi("PreserveEPSInfo", gs_param_type_bool, PreserveEPSInfo),
63     pi("AutoPositionEPSFiles", gs_param_type_bool, AutoPositionEPSFiles),
64     pi("PreserveCopyPage", gs_param_type_bool, PreserveCopyPage),
65     pi("UsePrologue", gs_param_type_bool, UsePrologue),
66 
67 	/* Acrobat Distiller 5 parameters */
68 
69     pi("OffOptimizations", gs_param_type_int, OffOptimizations),
70 
71 	/* Ghostscript-specific parameters */
72 
73     pi("ReAssignCharacters", gs_param_type_bool, ReAssignCharacters),
74     pi("ReEncodeCharacters", gs_param_type_bool, ReEncodeCharacters),
75     pi("FirstObjectNumber", gs_param_type_long, FirstObjectNumber),
76     pi("CompressFonts", gs_param_type_bool, CompressFonts),
77     pi("PrintStatistics", gs_param_type_bool, PrintStatistics),
78     pi("MaxInlineImageSize", gs_param_type_long, MaxInlineImageSize),
79 
80 	/* PDF Encryption */
81     pi("OwnerPassword", gs_param_type_string, OwnerPassword),
82     pi("UserPassword", gs_param_type_string, UserPassword),
83     pi("KeyLength", gs_param_type_int, KeyLength),
84     pi("Permissions", gs_param_type_int, Permissions),
85     pi("EncryptionR", gs_param_type_int, EncryptionR),
86     pi("NoEncrypt", gs_param_type_string, NoEncrypt),
87 
88 	/* Target viewer capabilities (Ghostscript-specific)  */
89     pi("ForOPDFRead", gs_param_type_bool, ForOPDFRead),
90     pi("PatternImagemask", gs_param_type_bool, PatternImagemask),
91     pi("MaxClipPathSize", gs_param_type_int, MaxClipPathSize),
92     pi("MaxShadingBitmapSize", gs_param_type_int, MaxShadingBitmapSize),
93     pi("MaxViewerMemorySize", gs_param_type_int, MaxViewerMemorySize),
94     pi("HaveTrueTypes", gs_param_type_bool, HaveTrueTypes),
95     pi("HaveCIDSystem", gs_param_type_bool, HaveCIDSystem),
96     pi("HaveTransparency", gs_param_type_bool, HaveTransparency),
97     pi("OPDFReadProcsetPath", gs_param_type_string, OPDFReadProcsetPath),
98     pi("CompressEntireFile", gs_param_type_bool, CompressEntireFile),
99     pi("PDFX", gs_param_type_bool, PDFX),
100 #undef pi
101     gs_param_item_end
102 };
103 
104 /*
105   Notes on implementing the remaining Distiller functionality
106   ===========================================================
107 
108   Architectural issues
109   --------------------
110 
111   Must optionally disable application of TR, BG, UCR similarly.  Affects:
112     PreserveHalftoneInfo
113     PreserveOverprintSettings
114     TransferFunctionInfo
115     UCRandBGInfo
116 
117   Current limitations
118   -------------------
119 
120   Non-primary elements in HalftoneType 5 are not written correctly
121 
122   Acrobat Distiller 3
123   -------------------
124 
125   ---- Image parameters ----
126 
127   AntiAlias{Color,Gray,Mono}Images
128 
129   ---- Other parameters ----
130 
131   CompressPages
132     Compress things other than page contents
133   * PreserveHalftoneInfo
134   PreserveOPIComments
135     ? see OPI spec?
136   * PreserveOverprintSettings
137   * TransferFunctionInfo
138   * UCRandBGInfo
139   ColorConversionStrategy
140     Select color space for drawing commands
141   ConvertImagesToIndexed
142     Postprocess image data *after* downsampling (requires an extra pass)
143 
144   Acrobat Distiller 4
145   -------------------
146 
147   ---- Other functionality ----
148 
149   Document structure pdfmarks
150 
151   ---- Parameters ----
152 
153   xxxDownsampleType = /Bicubic
154     Add new filter (or use siscale?) & to setup (gdevpsdi.c)
155   DetectBlends
156     Idiom recognition?  PatternType 2 patterns / shfill?  (see AD4)
157   DoThumbnails
158     Also output to memory device -- resolution issue
159 
160   ---- Job-level control ----
161 
162   EmitDSCWarnings
163     Require DSC parser / interceptor
164   CreateJobTicket
165     ?
166   AutoPositionEPSFiles
167     Require DSC parsing
168   PreserveCopyPage
169     Concatenate Contents streams
170   UsePrologue
171     Needs hack in top-level control?
172 
173 */
174 
175 /* ---------------- Get parameters ---------------- */
176 
177 /* Get parameters. */
178 int
gdev_pdf_get_params(gx_device * dev,gs_param_list * plist)179 gdev_pdf_get_params(gx_device * dev, gs_param_list * plist)
180 {
181     gx_device_pdf *pdev = (gx_device_pdf *) dev;
182     float cl = (float)pdev->CompatibilityLevel;
183     int code = gdev_psdf_get_params(dev, plist);
184     int cdv = CoreDistVersion;
185     int EmbedFontObjects = 1;
186 
187     if (code < 0 ||
188 	(code = param_write_int(plist, ".EmbedFontObjects", &EmbedFontObjects)) < 0 ||
189 	(code = param_write_int(plist, "CoreDistVersion", &cdv)) < 0 ||
190 	(code = param_write_float(plist, "CompatibilityLevel", &cl)) < 0 ||
191 	/* Indicate that we can process pdfmark and DSC. */
192 	(param_requested(plist, "pdfmark") > 0 &&
193 	 (code = param_write_null(plist, "pdfmark")) < 0) ||
194 	(param_requested(plist, "DSC") > 0 &&
195 	 (code = param_write_null(plist, "DSC")) < 0) ||
196 	(code = gs_param_write_items(plist, pdev, NULL, pdf_param_items)) < 0
197 	);
198     return code;
199 }
200 
201 /* ---------------- Put parameters ---------------- */
202 
203 /* Put parameters. */
204 int
gdev_pdf_put_params(gx_device * dev,gs_param_list * plist)205 gdev_pdf_put_params(gx_device * dev, gs_param_list * plist)
206 {
207     gx_device_pdf *pdev = (gx_device_pdf *) dev;
208     int ecode, code;
209     gx_device_pdf save_dev;
210     float cl = (float)pdev->CompatibilityLevel;
211     bool locked = pdev->params.LockDistillerParams;
212     gs_param_name param_name;
213 
214     /*
215      * If this is a pseudo-parameter (pdfmark or DSC),
216      * don't bother checking for any real ones.
217      */
218 
219     {
220 	gs_param_string_array ppa;
221 
222 	code = param_read_string_array(plist, (param_name = "pdfmark"), &ppa);
223 	switch (code) {
224 	    case 0:
225 		code = pdf_open_document(pdev);
226 		if (code < 0)
227 		    return code;
228 		code = pdfmark_process(pdev, &ppa);
229 		if (code >= 0)
230 		    return code;
231 		/* falls through for errors */
232 	    default:
233 		param_signal_error(plist, param_name, code);
234 		return code;
235 	    case 1:
236 		break;
237 	}
238 
239 	code = param_read_string_array(plist, (param_name = "DSC"), &ppa);
240 	switch (code) {
241 	    case 0:
242 		code = pdf_open_document(pdev);
243 		if (code < 0)
244 		    return code;
245 		code = pdf_dsc_process(pdev, &ppa);
246 		if (code >= 0)
247 		    return code;
248 		/* falls through for errors */
249 	    default:
250 		param_signal_error(plist, param_name, code);
251 		return code;
252 	    case 1:
253 		break;
254 	}
255     }
256 
257     /*
258      * Check for LockDistillerParams before doing anything else.
259      * If LockDistillerParams is true and is not being set to false,
260      * ignore all resettings of PDF-specific parameters.  Note that
261      * LockDistillerParams is read again, and reset if necessary, in
262      * psdf_put_params.
263      */
264     ecode = code = param_read_bool(plist, "LockDistillerParams", &locked);
265 
266     if (!(locked && pdev->params.LockDistillerParams)) {
267 	/* General parameters. */
268 
269 	{
270 	    int efo = 1;
271 
272 	    ecode = param_put_int(plist, (param_name = ".EmbedFontObjects"), &efo, ecode);
273 	    if (efo != 1)
274 		param_signal_error(plist, param_name, ecode = gs_error_rangecheck);
275 	}
276 	{
277 	    int cdv = CoreDistVersion;
278 
279 	    ecode = param_put_int(plist, (param_name = "CoreDistVersion"), &cdv, ecode);
280 	    if (cdv != CoreDistVersion)
281 		param_signal_error(plist, param_name, ecode = gs_error_rangecheck);
282 	}
283 
284 	save_dev = *pdev;
285 
286 	switch (code = param_read_float(plist, (param_name = "CompatibilityLevel"), &cl)) {
287 	    default:
288 		ecode = code;
289 		param_signal_error(plist, param_name, ecode);
290 	    case 0:
291 		/*
292 		 * Must be 1.2, 1.3, or 1.4.  Per Adobe documentation, substitute
293 		 * the nearest achievable value.
294 		 */
295 		if (cl < (float)1.15)
296 		    cl = (float)1.1;
297 		else if (cl < (float)1.25)
298 		    cl = (float)1.2;
299 		else if (cl >= (float)1.35)
300 		    cl = (float)1.4;
301 		else
302 		    cl = (float)1.3;
303 	    case 1:
304 		break;
305 	}
306 
307 	code = gs_param_read_items(plist, pdev, pdf_param_items);
308 	if (code < 0)
309 	    ecode = code;
310 	{
311 	    /*
312 	     * Setting FirstObjectNumber is only legal if the file
313 	     * has just been opened and nothing has been written,
314 	     * or if we are setting it to the same value.
315 	     */
316 	    long fon = pdev->FirstObjectNumber;
317 
318 	    if (fon != save_dev.FirstObjectNumber) {
319 		if (fon <= 0 || fon > 0x7fff0000 ||
320 		    (pdev->next_id != 0 &&
321 		     pdev->next_id !=
322 		     save_dev.FirstObjectNumber + pdf_num_initial_ids)
323 		    ) {
324 		    ecode = gs_error_rangecheck;
325 		    param_signal_error(plist, "FirstObjectNumber", ecode);
326 		}
327 	    }
328 	}
329 	{
330 	    /*
331 	     * Set ProcessColorModel now, because gx_default_put_params checks
332 	     * it.
333 	     */
334 	    static const char *const pcm_names[] = {
335 		"DeviceGray", "DeviceRGB", "DeviceCMYK", "DeviceN", 0
336 	    };
337 	    int pcm = -1;
338 
339 	    ecode = param_put_enum(plist, "ProcessColorModel", &pcm,
340 				   pcm_names, ecode);
341 	    if (pcm >= 0) {
342 		pdf_set_process_color_model(pdev, pcm);
343 		pdf_set_initial_color(pdev, &pdev->saved_fill_color, &pdev->saved_stroke_color,
344 				&pdev->fill_used_process_color, &pdev->stroke_used_process_color);
345 	    }
346 	}
347     }
348     if (ecode < 0)
349 	goto fail;
350     /*
351      * We have to set version to the new value, because the set of
352      * legal parameter values for psdf_put_params varies according to
353      * the version.
354      */
355     if (pdev->PDFX)
356 	cl = (float)1.3; /* Instead pdev->CompatibilityLevel = 1.2; - see below. */
357     pdev->version = (cl < 1.2 ? psdf_version_level2 : psdf_version_ll3);
358     if (pdev->ForOPDFRead) {
359 	pdev->ResourcesBeforeUsage = true;
360 	pdev->HaveCFF = false;
361 	pdev->HavePDFWidths = false;
362 	pdev->HaveStrokeColor = false;
363 	cl = (float)1.2; /* Instead pdev->CompatibilityLevel = 1.2; - see below. */
364 	pdev->MaxInlineImageSize = max_long; /* Save printer's RAM from saving temporary image data.
365 					        Immediate images doen't need buffering. */
366 	pdev->version = psdf_version_level2;
367     } else {
368 	pdev->ResourcesBeforeUsage = false;
369 	pdev->HaveCFF = true;
370 	pdev->HavePDFWidths = true;
371 	pdev->HaveStrokeColor = true;
372     }
373     ecode = gdev_psdf_put_params(dev, plist);
374     if (ecode < 0)
375 	goto fail;
376     if (pdev->HaveTrueTypes && pdev->version == psdf_version_level2) {
377 	pdev->version = psdf_version_level2_with_TT ;
378     }
379     /*
380      * Acrobat Reader doesn't handle user-space coordinates larger than
381      * MAX_USER_COORD.  To compensate for this, reduce the resolution so
382      * that the page size in device space (which we equate to user space) is
383      * significantly less than MAX_USER_COORD.  Note that this still does
384      * not protect us against input files that use coordinates far outside
385      * the page boundaries.
386      */
387 #define MAX_EXTENT ((int)(MAX_USER_COORD * 0.9))
388     /* Changing resolution or page size requires closing the device, */
389     if (dev->height > MAX_EXTENT || dev->width > MAX_EXTENT) {
390 	double factor =
391 	    max(dev->height / (double)MAX_EXTENT,
392 		dev->width / (double)MAX_EXTENT);
393 
394 	gx_device_set_resolution(dev, dev->HWResolution[0] / factor,
395 				 dev->HWResolution[1] / factor);
396     }
397 #undef MAX_EXTENT
398     if (pdev->FirstObjectNumber != save_dev.FirstObjectNumber) {
399 	if (pdev->xref.file != 0) {
400 	    fseek(pdev->xref.file, 0L, SEEK_SET);
401 	    pdf_initialize_ids(pdev);
402 	}
403     }
404     /* Handle the float/double mismatch. */
405     pdev->CompatibilityLevel = (int)(cl * 10 + 0.5) / 10.0;
406     return 0;
407  fail:
408     /* Restore all the parameters to their original state. */
409     pdev->version = save_dev.version;
410     pdf_set_process_color_model(pdev, save_dev.pcm_color_info_index);
411     pdev->saved_fill_color = save_dev.saved_fill_color;
412     pdev->saved_stroke_color = save_dev.saved_fill_color;
413     {
414 	const gs_param_item_t *ppi = pdf_param_items;
415 
416 	for (; ppi->key; ++ppi)
417 	    memcpy((char *)pdev + ppi->offset,
418 		   (char *)&save_dev + ppi->offset,
419 		   gs_param_type_sizes[ppi->type]);
420     }
421     return ecode;
422 }
423 
424 /* ---------------- Process DSC comments ---------------- */
425 
426 private int
pdf_dsc_process(gx_device_pdf * pdev,const gs_param_string_array * pma)427 pdf_dsc_process(gx_device_pdf * pdev, const gs_param_string_array * pma)
428 {
429     /*
430      * The Adobe "Distiller Parameters" documentation says that Distiller
431      * looks at DSC comments, but it doesn't say which ones.  We look at
432      * the ones that we see how to map directly to obvious PDF constructs.
433      */
434     int code = 0;
435     int i;
436 
437     /*
438      * If ParseDSCComments is false, all DSC comments are ignored, even if
439      * ParseDSCComentsForDocInfo or PreserveEPSInfo is true.
440      */
441     if (!pdev->ParseDSCComments)
442 	return 0;
443 
444     for (i = 0; i + 1 < pma->size && code >= 0; i += 2) {
445 	const gs_param_string *pkey = &pma->data[i];
446 	const gs_param_string *pvalue = &pma->data[i + 1];
447 	const char *key;
448 	int code;
449 
450 	/*
451 	 * %%For, %%Creator, and %%Title are recognized only if either
452 	 * ParseDSCCommentsForDocInfo or PreserveEPSInfo is true.
453 	 * The other DSC comments are always recognized.
454 	 *
455 	 * Acrobat Distiller sets CreationDate and ModDate to the current
456 	 * time, not the value of %%CreationDate.  We think this is wrong,
457 	 * but we do the same -- we ignore %%CreationDate here.
458 	 */
459 
460 	if (pdf_key_eq(pkey, "Creator"))
461 	    key = "/Creator";
462 	else if (pdf_key_eq(pkey, "Title"))
463 	    key = "/Title";
464 	else if (pdf_key_eq(pkey, "For"))
465 	    key = "/Author";
466 	else {
467 	    pdf_page_dsc_info_t *ppdi;
468 
469 	    if ((ppdi = &pdev->doc_dsc_info,
470 		 pdf_key_eq(pkey, "Orientation")) ||
471 		(ppdi = &pdev->page_dsc_info,
472 		 pdf_key_eq(pkey, "PageOrientation"))
473 		) {
474 		if (pvalue->size == 1 && pvalue->data[0] >= '0' &&
475 		    pvalue->data[0] <= '3'
476 		    )
477 		    ppdi->orientation = pvalue->data[0] - '0';
478 		else
479 		    ppdi->orientation = -1;
480 	    } else if ((ppdi = &pdev->doc_dsc_info,
481 			pdf_key_eq(pkey, "ViewingOrientation")) ||
482 		       (ppdi = &pdev->page_dsc_info,
483 			pdf_key_eq(pkey, "PageViewingOrientation"))
484 		       ) {
485 		gs_matrix mat;
486 		int orient;
487 
488 		if (sscanf((const char *)pvalue->data, "[%g %g %g %g]",
489 			   &mat.xx, &mat.xy, &mat.yx, &mat.yy) != 4
490 		    )
491 		    continue;	/* error */
492 		for (orient = 0; orient < 4; ++orient) {
493 		    if (mat.xx == 1 && mat.xy == 0 && mat.yx == 0 && mat.yy == 1)
494 			break;
495 		    gs_matrix_rotate(&mat, -90.0, &mat);
496 		}
497 		if (orient == 4) /* error */
498 		    orient = -1;
499 		ppdi->viewing_orientation = orient;
500 	    } else {
501 		gs_rect box;
502 
503 		if (pdf_key_eq(pkey, "EPSF")) {
504 		    pdev->is_EPS = (pvalue->size >= 1 && pvalue->data[0] != '0');
505 		    continue;
506 		}
507 		/*
508 		 * We only parse the BoundingBox for the sake of
509 		 * AutoPositionEPSFiles.
510 		 */
511 		if (pdf_key_eq(pkey, "BoundingBox"))
512 		    ppdi = &pdev->doc_dsc_info;
513 		else if (pdf_key_eq(pkey, "PageBoundingBox"))
514 		    ppdi = &pdev->page_dsc_info;
515 		else
516 		    continue;
517 		if (sscanf((const char *)pvalue->data, "[%lg %lg %lg %lg]",
518 			   &box.p.x, &box.p.y, &box.q.x, &box.q.y) != 4
519 		    )
520 		    continue;	/* error */
521 		ppdi->bounding_box = box;
522 	    }
523 	    continue;
524 	}
525 
526 	if (pdev->ParseDSCCommentsForDocInfo || pdev->PreserveEPSInfo)
527 	    code = cos_dict_put_c_key_string(pdev->Info, key,
528 					     pvalue->data, pvalue->size);
529     }
530     return code;
531 }
532