1 /**
2 * This program is free software: you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation, either version 3 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *
15 * @brief Convert PWG Raster to a PDF/PCLm file
16 * @file rastertopdf.cpp
17 * @author Neil 'Superna' Armstrong <superna9999@gmail.com> (C) 2010
18 * @author Tobias Hoffmann <smilingthax@gmail.com> (c) 2012
19 * @author Till Kamppeter <till.kamppeter@gmail.com> (c) 2014
20 * @author Sahil Arora <sahilarora.535@gmail.com> (c) 2017
21 */
22
23 #include <config.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
27 #include <stdio.h>
28 #include <stdint.h>
29 #include <unistd.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <limits>
33 #include <signal.h>
34 #include <cups/cups.h>
35 #include <cups/raster.h>
36 #include <cupsfilters/colormanager.h>
37 #include <cupsfilters/image.h>
38
39 #include <arpa/inet.h> // ntohl
40
41 #include <vector>
42 #include <qpdf/QPDF.hh>
43 #include <qpdf/QPDFWriter.hh>
44 #include <qpdf/QUtil.hh>
45
46 #include <qpdf/Pl_Flate.hh>
47 #include <qpdf/Pl_Buffer.hh>
48 #ifdef QPDF_HAVE_PCLM
49 #include <qpdf/Pl_RunLength.hh>
50 #include <qpdf/Pl_DCT.hh>
51 #endif
52
53 #ifdef USE_LCMS1
54 #include <lcms.h>
55 #define cmsColorSpaceSignature icColorSpaceSignature
56 #define cmsSetLogErrorHandler cmsSetErrorHandler
57 #define cmsSigXYZData icSigXYZData
58 #define cmsSigLuvData icSigLuvData
59 #define cmsSigLabData icSigLabData
60 #define cmsSigYCbCrData icSigYCbCrData
61 #define cmsSigYxyData icSigYxyData
62 #define cmsSigRgbData icSigRgbData
63 #define cmsSigHsvData icSigHsvData
64 #define cmsSigHlsData icSigHlsData
65 #define cmsSigCmyData icSigCmyData
66 #define cmsSig3colorData icSig3colorData
67 #define cmsSigGrayData icSigGrayData
68 #define cmsSigCmykData icSigCmykData
69 #define cmsSig4colorData icSig4colorData
70 #define cmsSig2colorData icSig2colorData
71 #define cmsSig5colorData icSig5colorData
72 #define cmsSig6colorData icSig6colorData
73 #define cmsSig7colorData icSig7colorData
74 #define cmsSig8colorData icSig8colorData
75 #define cmsSig9colorData icSig9colorData
76 #define cmsSig10colorData icSig10colorData
77 #define cmsSig11colorData icSig11colorData
78 #define cmsSig12colorData icSig12colorData
79 #define cmsSig13colorData icSig13colorData
80 #define cmsSig14colorData icSig14colorData
81 #define cmsSig15colorData icSig15colorData
82 #define cmsSaveProfileToMem _cmsSaveProfileToMem
83 #else
84 #include <lcms2.h>
85 #endif
86
87 #define DEFAULT_PDF_UNIT 72 // 1/72 inch
88
89 #define PROGRAM "rastertopdf"
90
91 #define dprintf(format, ...) fprintf(stderr, "DEBUG2: (" PROGRAM ") " format, __VA_ARGS__)
92
93 #define iprintf(format, ...) fprintf(stderr, "INFO: (" PROGRAM ") " format, __VA_ARGS__)
94
95 typedef enum {
96 OUTPUT_FORMAT_PDF,
97 OUTPUT_FORMAT_PCLM
98 } OutFormatType;
99
100 // Compression method for providing data to PCLm Streams.
101 typedef enum {
102 DCT_DECODE = 0,
103 FLATE_DECODE,
104 RLE_DECODE
105 } CompressionMethod;
106
107 // Color conversion function
108 typedef unsigned char *(*convertFunction)(unsigned char *src,
109 unsigned char *dst, unsigned int pixels);
110
111 // Bit conversion function
112 typedef unsigned char *(*bitFunction)(unsigned char *src,
113 unsigned char *dst, unsigned int pixels);
114
115 // PDF color conversion function
116 typedef void (*pdfConvertFunction)(struct pdf_info * info);
117
118 cmsHPROFILE colorProfile = NULL; // ICC Profile to be applied to PDF
119 int cm_disabled = 0; // Flag rasied if color management is disabled
120 cm_calibration_t cm_calibrate; // Status of CUPS color management ("on" or "off")
121 convertFunction conversion_function; // Raster color conversion function
122 bitFunction bit_function; // Raster bit function
123
124
125 #ifdef USE_LCMS1
lcmsErrorHandler(int ErrorCode,const char * ErrorText)126 static int lcmsErrorHandler(int ErrorCode, const char *ErrorText)
127 {
128 fprintf(stderr, "ERROR: %s\n",ErrorText);
129 return 1;
130 }
131 #else
lcmsErrorHandler(cmsContext contextId,cmsUInt32Number ErrorCode,const char * ErrorText)132 static void lcmsErrorHandler(cmsContext contextId, cmsUInt32Number ErrorCode,
133 const char *ErrorText)
134 {
135 fprintf(stderr, "ERROR: %s\n",ErrorText);
136 }
137 #endif
138
139
140
141 // Bit conversion functions
142
invertBits(unsigned char * src,unsigned char * dst,unsigned int pixels)143 unsigned char *invertBits(unsigned char *src, unsigned char *dst, unsigned int pixels)
144 {
145 unsigned int i;
146
147 // Invert black to grayscale...
148 for (i = pixels, dst = src; i > 0; i --, dst ++)
149 *dst = ~*dst;
150
151 return dst;
152 }
153
noBitConversion(unsigned char * src,unsigned char * dst,unsigned int pixels)154 unsigned char *noBitConversion(unsigned char *src, unsigned char *dst, unsigned int pixels)
155 {
156 return src;
157 }
158
159 // Color conversion functions
160
rgbToCmyk(unsigned char * src,unsigned char * dst,unsigned int pixels)161 unsigned char *rgbToCmyk(unsigned char *src, unsigned char *dst, unsigned int pixels)
162 {
163 cupsImageRGBToCMYK(src,dst,pixels);
164 return dst;
165 }
whiteToCmyk(unsigned char * src,unsigned char * dst,unsigned int pixels)166 unsigned char *whiteToCmyk(unsigned char *src, unsigned char *dst, unsigned int pixels)
167 {
168 cupsImageWhiteToCMYK(src,dst,pixels);
169 return dst;
170 }
171
cmykToRgb(unsigned char * src,unsigned char * dst,unsigned int pixels)172 unsigned char *cmykToRgb(unsigned char *src, unsigned char *dst, unsigned int pixels)
173 {
174 cupsImageCMYKToRGB(src,dst,pixels);
175 return dst;
176 }
177
whiteToRgb(unsigned char * src,unsigned char * dst,unsigned int pixels)178 unsigned char *whiteToRgb(unsigned char *src, unsigned char *dst, unsigned int pixels)
179 {
180 cupsImageWhiteToRGB(src,dst,pixels);
181 return dst;
182 }
183
rgbToWhite(unsigned char * src,unsigned char * dst,unsigned int pixels)184 unsigned char *rgbToWhite(unsigned char *src, unsigned char *dst, unsigned int pixels)
185 {
186 cupsImageRGBToWhite(src,dst,pixels);
187 return dst;
188 }
189
cmykToWhite(unsigned char * src,unsigned char * dst,unsigned int pixels)190 unsigned char *cmykToWhite(unsigned char *src, unsigned char *dst, unsigned int pixels)
191 {
192 cupsImageCMYKToWhite(src,dst,pixels);
193 return dst;
194 }
195
noColorConversion(unsigned char * src,unsigned char * dst,unsigned int pixels)196 unsigned char *noColorConversion(unsigned char *src,
197 unsigned char *dst, unsigned int pixels)
198 {
199 return src;
200 }
201
202 /**
203 * 'split_strings()' - Split a string to a vector of strings given some delimiters
204 * O - std::vector of std::string after splitting
205 * I - input string to be split
206 * I - string containing delimiters
207 */
208 static std::vector<std::string>
split_strings(std::string const & str,std::string delimiters=",")209 split_strings(std::string const &str, std::string delimiters = ",")
210 {
211 std::vector<std::string> vec(0);
212 std::string value = "";
213 bool push_flag = false;
214
215 for (size_t i = 0; i < str.size(); i ++)
216 {
217 if (push_flag && !(value.empty()))
218 {
219 vec.push_back(value);
220 push_flag = false;
221 value.clear();
222 }
223
224 if (delimiters.find(str[i]) != std::string::npos)
225 push_flag = true;
226 else
227 value += str[i];
228 }
229 if (!value.empty())
230 vec.push_back(value);
231 return vec;
232 }
233
234 /**
235 * 'num_digits()' - Calculates the number of digits in an integer
236 * O - number of digits in the input integer
237 * I - the integer whose digits needs to be calculated
238 */
num_digits(int n)239 int num_digits(int n)
240 {
241 if (n == 0) return 1;
242 int digits = 0;
243 while (n)
244 {
245 ++digits;
246 n /= 10;
247 }
248 return digits;
249 }
250
251 /**
252 * 'int_to_fwstring()' - Convert a number to fixed width string by padding with zeroes
253 * O - converted string
254 * I - the integee which needs to be converted to string
255 * I - width of string required
256 */
int_to_fwstring(int n,int width)257 std::string int_to_fwstring(int n, int width)
258 {
259 int num_zeroes = width - num_digits(n);
260 if (num_zeroes < 0)
261 num_zeroes = 0;
262 return std::string(num_zeroes, '0') + QUtil::int_to_string(n);
263 }
264
die(const char * str)265 void die(const char * str)
266 {
267 fprintf(stderr, "ERROR: (" PROGRAM ") %s\n", str);
268 exit(1);
269 }
270
271
272 //------------- PDF ---------------
273
274 struct pdf_info
275 {
pdf_infopdf_info276 pdf_info()
277 : pagecount(0),
278 width(0),height(0),
279 line_bytes(0),
280 bpp(0), bpc(0),
281 pclm_num_strips(0),
282 pclm_strip_height_preferred(16), /* default strip height */
283 pclm_strip_height(0),
284 pclm_strip_height_supported(1, 16),
285 pclm_compression_method_preferred(0),
286 pclm_source_resolution_supported(0),
287 pclm_source_resolution_default(""),
288 pclm_raster_back_side(""),
289 pclm_strip_data(0),
290 render_intent(""),
291 color_space(CUPS_CSPACE_K),
292 page_width(0),page_height(0),
293 outformat(OUTPUT_FORMAT_PDF)
294 {
295 }
296
297 QPDF pdf;
298 QPDFObjectHandle page;
299 unsigned pagecount;
300 unsigned width;
301 unsigned height;
302 unsigned line_bytes;
303 unsigned bpp;
304 unsigned bpc;
305 unsigned pclm_num_strips;
306 unsigned pclm_strip_height_preferred;
307 std::vector<unsigned> pclm_strip_height;
308 std::vector<unsigned> pclm_strip_height_supported;
309 std::vector<CompressionMethod> pclm_compression_method_preferred;
310 std::vector<std::string> pclm_source_resolution_supported;
311 std::string pclm_source_resolution_default;
312 std::string pclm_raster_back_side;
313 std::vector< PointerHolder<Buffer> > pclm_strip_data;
314 std::string render_intent;
315 cups_cspace_t color_space;
316 PointerHolder<Buffer> page_data;
317 double page_width,page_height;
318 OutFormatType outformat;
319 };
320
create_pdf_file(struct pdf_info * info,const OutFormatType & outformat)321 int create_pdf_file(struct pdf_info * info, const OutFormatType & outformat)
322 {
323 try {
324 info->pdf.emptyPDF();
325 info->outformat = outformat;
326 } catch (...) {
327 return 1;
328 }
329 return 0;
330 }
331
makeRealBox(double x1,double y1,double x2,double y2)332 QPDFObjectHandle makeRealBox(double x1, double y1, double x2, double y2)
333 {
334 QPDFObjectHandle ret=QPDFObjectHandle::newArray();
335 ret.appendItem(QPDFObjectHandle::newReal(x1));
336 ret.appendItem(QPDFObjectHandle::newReal(y1));
337 ret.appendItem(QPDFObjectHandle::newReal(x2));
338 ret.appendItem(QPDFObjectHandle::newReal(y2));
339 return ret;
340 }
341
makeIntegerBox(int x1,int y1,int x2,int y2)342 QPDFObjectHandle makeIntegerBox(int x1, int y1, int x2, int y2)
343 {
344 QPDFObjectHandle ret = QPDFObjectHandle::newArray();
345 ret.appendItem(QPDFObjectHandle::newInteger(x1));
346 ret.appendItem(QPDFObjectHandle::newInteger(y1));
347 ret.appendItem(QPDFObjectHandle::newInteger(x2));
348 ret.appendItem(QPDFObjectHandle::newInteger(y2));
349 return ret;
350 }
351
352
353
354
355 // PDF color conversion functons...
356
modify_pdf_color(struct pdf_info * info,int bpp,int bpc,convertFunction fn)357 void modify_pdf_color(struct pdf_info * info, int bpp, int bpc, convertFunction fn)
358 {
359 unsigned old_bpp = info->bpp;
360 unsigned old_bpc = info->bpc;
361 double old_ncolor = old_bpp/old_bpc;
362
363 unsigned old_line_bytes = info->line_bytes;
364
365 double new_ncolor = (bpp/bpc);
366
367 info->line_bytes = (unsigned)old_line_bytes*(new_ncolor/old_ncolor);
368 info->bpp = bpp;
369 info->bpc = bpc;
370 conversion_function = fn;
371
372 return;
373 }
374
convertPdf_NoConversion(struct pdf_info * info)375 void convertPdf_NoConversion(struct pdf_info * info)
376 {
377 conversion_function = noColorConversion;
378 bit_function = noBitConversion;
379 }
380
convertPdf_Cmyk8ToWhite8(struct pdf_info * info)381 void convertPdf_Cmyk8ToWhite8(struct pdf_info * info)
382 {
383 modify_pdf_color(info, 8, 8, cmykToWhite);
384 bit_function = noBitConversion;
385 }
386
convertPdf_Rgb8ToWhite8(struct pdf_info * info)387 void convertPdf_Rgb8ToWhite8(struct pdf_info * info)
388 {
389 modify_pdf_color(info, 8, 8, rgbToWhite);
390 bit_function = noBitConversion;
391 }
392
convertPdf_Cmyk8ToRgb8(struct pdf_info * info)393 void convertPdf_Cmyk8ToRgb8(struct pdf_info * info)
394 {
395 modify_pdf_color(info, 24, 8, cmykToRgb);
396 bit_function = noBitConversion;
397 }
398
convertPdf_White8ToRgb8(struct pdf_info * info)399 void convertPdf_White8ToRgb8(struct pdf_info * info)
400 {
401 modify_pdf_color(info, 24, 8, whiteToRgb);
402 bit_function = invertBits;
403 }
404
convertPdf_Rgb8ToCmyk8(struct pdf_info * info)405 void convertPdf_Rgb8ToCmyk8(struct pdf_info * info)
406 {
407 modify_pdf_color(info, 32, 8, rgbToCmyk);
408 bit_function = noBitConversion;
409 }
410
convertPdf_White8ToCmyk8(struct pdf_info * info)411 void convertPdf_White8ToCmyk8(struct pdf_info * info)
412 {
413 modify_pdf_color(info, 32, 8, whiteToCmyk);
414 bit_function = invertBits;
415 }
416
convertPdf_InvertColors(struct pdf_info * info)417 void convertPdf_InvertColors(struct pdf_info * info)
418 {
419 conversion_function = noColorConversion;
420 bit_function = invertBits;
421 }
422
423
424 #define PRE_COMPRESS
425
426 // Create an '/ICCBased' array and embed a previously
427 // set ICC Profile in the PDF
embedIccProfile(QPDF & pdf)428 QPDFObjectHandle embedIccProfile(QPDF &pdf)
429 {
430 if (colorProfile == NULL) {
431 return QPDFObjectHandle::newNull();
432 }
433
434 // Return handler
435 QPDFObjectHandle ret;
436 // ICCBased array
437 QPDFObjectHandle array = QPDFObjectHandle::newArray();
438 // Profile stream dictionary
439 QPDFObjectHandle iccstream;
440
441 std::map<std::string,QPDFObjectHandle> dict;
442 std::map<std::string,QPDFObjectHandle> streamdict;
443 std::string n_value = "";
444 std::string alternate_cs = "";
445 PointerHolder<Buffer>ph;
446
447 #ifdef USE_LCMS1
448 size_t profile_size;
449 #else
450 unsigned int profile_size;
451 #endif
452
453 cmsColorSpaceSignature css = cmsGetColorSpace(colorProfile);
454
455 // Write color component # for /ICCBased array in stream dictionary
456 switch(css){
457 case cmsSigGrayData:
458 n_value = "1";
459 alternate_cs = "/DeviceGray";
460 break;
461 case cmsSigRgbData:
462 n_value = "3";
463 alternate_cs = "/DeviceRGB";
464 break;
465 case cmsSigCmykData:
466 n_value = "4";
467 alternate_cs = "/DeviceCMYK";
468 break;
469 default:
470 fputs("DEBUG: Failed to embed ICC Profile.\n", stderr);
471 return QPDFObjectHandle::newNull();
472 }
473
474 streamdict["/Alternate"]=QPDFObjectHandle::newName(alternate_cs);
475 streamdict["/N"]=QPDFObjectHandle::newName(n_value);
476
477 // Read profile into memory
478 cmsSaveProfileToMem(colorProfile, NULL, &profile_size);
479 unsigned char *buff =
480 (unsigned char *)calloc(profile_size, sizeof(unsigned char));
481 cmsSaveProfileToMem(colorProfile, buff, &profile_size);
482
483 // Write ICC profile buffer into PDF
484 ph = new Buffer(buff, profile_size);
485 iccstream = QPDFObjectHandle::newStream(&pdf, ph);
486 iccstream.replaceDict(QPDFObjectHandle::newDictionary(streamdict));
487
488 array.appendItem(QPDFObjectHandle::newName("/ICCBased"));
489 array.appendItem(iccstream);
490
491 // Return a PDF object reference to an '/ICCBased' array
492 ret = pdf.makeIndirectObject(array);
493
494 free(buff);
495 fputs("DEBUG: ICC Profile embedded in PDF.\n", stderr);
496
497 return ret;
498 }
499
embedSrgbProfile(QPDF & pdf)500 QPDFObjectHandle embedSrgbProfile(QPDF &pdf)
501 {
502 QPDFObjectHandle iccbased_reference;
503
504 // Create an sRGB profile from lcms
505 colorProfile = cmsCreate_sRGBProfile();
506 // Embed it into the profile
507 iccbased_reference = embedIccProfile(pdf);
508
509 return iccbased_reference;
510 }
511
512 /*
513 Calibration function for non-Lab PDF color spaces
514 Requires white point data, and if available, gamma or matrix numbers.
515
516 Output:
517 [/'color_space'
518 << /Gamma ['gamma[0]'...'gamma[n]']
519 /WhitePoint ['wp[0]' 'wp[1]' 'wp[2]']
520 /Matrix ['matrix[0]'...'matrix[n*n]']
521 >>
522 ]
523 */
getCalibrationArray(const char * color_space,double wp[],double gamma[],double matrix[],double bp[])524 QPDFObjectHandle getCalibrationArray(const char * color_space, double wp[],
525 double gamma[], double matrix[], double bp[])
526 {
527 // Check for invalid input
528 if ((!strcmp("/CalGray", color_space) && matrix != NULL) ||
529 wp == NULL)
530 return QPDFObjectHandle();
531
532 QPDFObjectHandle ret;
533 std::string csString = color_space;
534 std::string colorSpaceArrayString = "";
535
536 char gamma_str[128];
537 char bp_str[256];
538 char wp_str[256];
539 char matrix_str[512];
540
541
542 // Convert numbers into string data for /Gamma, /WhitePoint, and/or /Matrix
543
544
545 // WhitePoint
546 snprintf(wp_str, sizeof(wp_str), "/WhitePoint [%g %g %g]",
547 wp[0], wp[1], wp[2]);
548
549
550 // Gamma
551 if (!strcmp("/CalGray", color_space) && gamma != NULL)
552 snprintf(gamma_str, sizeof(gamma_str), "/Gamma %g",
553 gamma[0]);
554 else if (!strcmp("/CalRGB", color_space) && gamma != NULL)
555 snprintf(gamma_str, sizeof(gamma_str), "/Gamma [%g %g %g]",
556 gamma[0], gamma[1], gamma[2]);
557 else
558 gamma_str[0] = '\0';
559
560
561 // BlackPoint
562 if (bp != NULL)
563 snprintf(bp_str, sizeof(bp_str), "/BlackPoint [%g %g %g]",
564 bp[0], bp[1], bp[2]);
565 else
566 bp_str[0] = '\0';
567
568
569 // Matrix
570 if (!strcmp("/CalRGB", color_space) && matrix != NULL) {
571 snprintf(matrix_str, sizeof(matrix_str), "/Matrix [%g %g %g %g %g %g %g %g %g]",
572 matrix[0], matrix[1], matrix[2],
573 matrix[3], matrix[4], matrix[5],
574 matrix[6], matrix[7], matrix[8]);
575 } else
576 matrix_str[0] = '\0';
577
578
579 // Write array string...
580 colorSpaceArrayString = "[" + csString + " <<"
581 + gamma_str + " " + wp_str + " " + matrix_str + " " + bp_str
582 + " >>]";
583
584 ret = QPDFObjectHandle::parse(colorSpaceArrayString);
585
586 return ret;
587 }
588
getCalRGBArray(double wp[3],double gamma[3],double matrix[9],double bp[3])589 QPDFObjectHandle getCalRGBArray(double wp[3], double gamma[3], double matrix[9], double bp[3])
590 {
591 QPDFObjectHandle ret = getCalibrationArray("/CalRGB", wp, gamma, matrix, bp);
592 return ret;
593 }
594
getCalGrayArray(double wp[3],double gamma[1],double bp[3])595 QPDFObjectHandle getCalGrayArray(double wp[3], double gamma[1], double bp[3])
596 {
597 QPDFObjectHandle ret = getCalibrationArray("/CalGray", wp, gamma, 0, bp);
598 return ret;
599 }
600
601 #ifdef QPDF_HAVE_PCLM
602 /**
603 * 'makePclmStrips()' - return an std::vector of QPDFObjectHandle, each containing the
604 * stream data of the various strips which make up a PCLm page.
605 * O - std::vector of QPDFObjectHandle
606 * I - QPDF object
607 * I - number of strips per page
608 * I - std::vector of PointerHolder<Buffer> containing data for each strip
609 * I - strip width
610 * I - strip height
611 * I - color space
612 * I - bits per component
613 */
614 std::vector<QPDFObjectHandle>
makePclmStrips(QPDF & pdf,unsigned num_strips,std::vector<PointerHolder<Buffer>> & strip_data,std::vector<CompressionMethod> & compression_methods,unsigned width,std::vector<unsigned> & strip_height,cups_cspace_t cs,unsigned bpc)615 makePclmStrips(QPDF &pdf, unsigned num_strips,
616 std::vector< PointerHolder<Buffer> > &strip_data,
617 std::vector<CompressionMethod> &compression_methods,
618 unsigned width, std::vector<unsigned>& strip_height, cups_cspace_t cs, unsigned bpc)
619 {
620 std::vector<QPDFObjectHandle> ret(num_strips);
621 for (size_t i = 0; i < num_strips; i ++)
622 ret[i] = QPDFObjectHandle::newStream(&pdf);
623
624 // Strip stream dictionary
625 std::map<std::string,QPDFObjectHandle> dict;
626
627 dict["/Type"]=QPDFObjectHandle::newName("/XObject");
628 dict["/Subtype"]=QPDFObjectHandle::newName("/Image");
629 dict["/Width"]=QPDFObjectHandle::newInteger(width);
630 dict["/BitsPerComponent"]=QPDFObjectHandle::newInteger(bpc);
631
632 J_COLOR_SPACE color_space;
633 unsigned components;
634 /* Write "/ColorSpace" dictionary based on raster input */
635 switch(cs) {
636 case CUPS_CSPACE_K:
637 case CUPS_CSPACE_SW:
638 dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceGray");
639 color_space = JCS_GRAYSCALE;
640 components = 1;
641 break;
642 case CUPS_CSPACE_RGB:
643 case CUPS_CSPACE_SRGB:
644 case CUPS_CSPACE_ADOBERGB:
645 dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceRGB");
646 color_space = JCS_RGB;
647 components = 3;
648 break;
649 default:
650 fputs("DEBUG: Color space not supported.\n", stderr);
651 return std::vector<QPDFObjectHandle>(num_strips, QPDFObjectHandle());
652 }
653
654 // We deliver already compressed content (instead of letting QPDFWriter do it)
655 // to avoid using excessive memory. For that we first get preferred compression
656 // method to pre-compress content for strip streams.
657
658 // Use the compression method with highest priority of the available methods
659 // __________________
660 // Priority | Method
661 // ------------------
662 // 0 | DCT
663 // 1 | FLATE
664 // 2 | RLE
665 // ------------------
666 CompressionMethod compression = compression_methods.front();
667 for (std::vector<CompressionMethod>::iterator it = compression_methods.begin();
668 it != compression_methods.end(); ++it)
669 compression = compression > *it ? compression : *it;
670
671 // write compressed stream data
672 for (size_t i = 0; i < num_strips; i ++)
673 {
674 dict["/Height"]=QPDFObjectHandle::newInteger(strip_height[i]);
675 ret[i].replaceDict(QPDFObjectHandle::newDictionary(dict));
676 Pl_Buffer psink("psink");
677 if (compression == FLATE_DECODE)
678 {
679 Pl_Flate pflate("pflate", &psink, Pl_Flate::a_deflate);
680 pflate.write(strip_data[i]->getBuffer(), strip_data[i]->getSize());
681 pflate.finish();
682 ret[i].replaceStreamData(PointerHolder<Buffer>(psink.getBuffer()),
683 QPDFObjectHandle::newName("/FlateDecode"),QPDFObjectHandle::newNull());
684 }
685 else if (compression == RLE_DECODE)
686 {
687 Pl_RunLength prle("prle", &psink, Pl_RunLength::a_encode);
688 prle.write(strip_data[i]->getBuffer(),strip_data[i]->getSize());
689 prle.finish();
690 ret[i].replaceStreamData(PointerHolder<Buffer>(psink.getBuffer()),
691 QPDFObjectHandle::newName("/RunLengthDecode"),QPDFObjectHandle::newNull());
692 }
693 else if (compression == DCT_DECODE)
694 {
695 Pl_DCT pdct("pdct", &psink, width, strip_height[i], components, color_space);
696 pdct.write(strip_data[i]->getBuffer(),strip_data[i]->getSize());
697 pdct.finish();
698 ret[i].replaceStreamData(PointerHolder<Buffer>(psink.getBuffer()),
699 QPDFObjectHandle::newName("/DCTDecode"),QPDFObjectHandle::newNull());
700 }
701 }
702 return ret;
703 }
704 #endif
705
makeImage(QPDF & pdf,PointerHolder<Buffer> page_data,unsigned width,unsigned height,std::string render_intent,cups_cspace_t cs,unsigned bpc)706 QPDFObjectHandle makeImage(QPDF &pdf, PointerHolder<Buffer> page_data, unsigned width,
707 unsigned height, std::string render_intent, cups_cspace_t cs, unsigned bpc)
708 {
709 QPDFObjectHandle ret = QPDFObjectHandle::newStream(&pdf);
710
711 QPDFObjectHandle icc_ref;
712
713 int use_blackpoint = 0;
714 std::map<std::string,QPDFObjectHandle> dict;
715
716 dict["/Type"]=QPDFObjectHandle::newName("/XObject");
717 dict["/Subtype"]=QPDFObjectHandle::newName("/Image");
718 dict["/Width"]=QPDFObjectHandle::newInteger(width);
719 dict["/Height"]=QPDFObjectHandle::newInteger(height);
720 dict["/BitsPerComponent"]=QPDFObjectHandle::newInteger(bpc);
721
722 if (!cm_disabled) {
723 // Write rendering intent into the PDF based on raster settings
724 if (render_intent == "Perceptual") {
725 dict["/Intent"]=QPDFObjectHandle::newName("/Perceptual");
726 } else if (render_intent == "Absolute") {
727 dict["/Intent"]=QPDFObjectHandle::newName("/AbsoluteColorimetric");
728 } else if (render_intent == "Relative") {
729 dict["/Intent"]=QPDFObjectHandle::newName("/RelativeColorimetric");
730 } else if (render_intent == "Saturation") {
731 dict["/Intent"]=QPDFObjectHandle::newName("/Saturation");
732 } else if (render_intent == "RelativeBpc") {
733 /* Enable blackpoint compensation */
734 dict["/Intent"]=QPDFObjectHandle::newName("/RelativeColorimetric");
735 use_blackpoint = 1;
736 }
737 }
738
739 /* Write "/ColorSpace" dictionary based on raster input */
740 if (colorProfile != NULL && !cm_disabled) {
741 icc_ref = embedIccProfile(pdf);
742
743 if (!icc_ref.isNull())
744 dict["/ColorSpace"]=icc_ref;
745 } else if (!cm_disabled) {
746 switch (cs) {
747 case CUPS_CSPACE_DEVICE1:
748 case CUPS_CSPACE_DEVICE2:
749 case CUPS_CSPACE_DEVICE3:
750 case CUPS_CSPACE_DEVICE4:
751 case CUPS_CSPACE_DEVICE5:
752 case CUPS_CSPACE_DEVICE6:
753 case CUPS_CSPACE_DEVICE7:
754 case CUPS_CSPACE_DEVICE8:
755 case CUPS_CSPACE_DEVICE9:
756 case CUPS_CSPACE_DEVICEA:
757 case CUPS_CSPACE_DEVICEB:
758 case CUPS_CSPACE_DEVICEC:
759 case CUPS_CSPACE_DEVICED:
760 case CUPS_CSPACE_DEVICEE:
761 case CUPS_CSPACE_DEVICEF:
762 // For right now, DeviceN will use /DeviceCMYK in the PDF
763 dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceCMYK");
764 break;
765 case CUPS_CSPACE_K:
766 dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceGray");
767 break;
768 case CUPS_CSPACE_SW:
769 if (use_blackpoint)
770 dict["/ColorSpace"]=getCalGrayArray(cmWhitePointSGray(), cmGammaSGray(),
771 cmBlackPointDefault());
772 else
773 dict["/ColorSpace"]=getCalGrayArray(cmWhitePointSGray(), cmGammaSGray(), 0);
774 break;
775 case CUPS_CSPACE_CMYK:
776 dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceCMYK");
777 break;
778 case CUPS_CSPACE_RGB:
779 dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceRGB");
780 break;
781 case CUPS_CSPACE_SRGB:
782 icc_ref = embedSrgbProfile(pdf);
783 if (!icc_ref.isNull())
784 dict["/ColorSpace"]=icc_ref;
785 else
786 dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceRGB");
787 break;
788 case CUPS_CSPACE_ADOBERGB:
789 if (use_blackpoint)
790 dict["/ColorSpace"]=getCalRGBArray(cmWhitePointAdobeRgb(), cmGammaAdobeRgb(),
791 cmMatrixAdobeRgb(), cmBlackPointDefault());
792 else
793 dict["/ColorSpace"]=getCalRGBArray(cmWhitePointAdobeRgb(),
794 cmGammaAdobeRgb(), cmMatrixAdobeRgb(), 0);
795 break;
796 default:
797 fputs("DEBUG: Color space not supported.\n", stderr);
798 return QPDFObjectHandle();
799 }
800 } else if (cm_disabled) {
801 switch(cs) {
802 case CUPS_CSPACE_K:
803 case CUPS_CSPACE_SW:
804 dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceGray");
805 break;
806 case CUPS_CSPACE_RGB:
807 case CUPS_CSPACE_SRGB:
808 case CUPS_CSPACE_ADOBERGB:
809 dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceRGB");
810 break;
811 case CUPS_CSPACE_DEVICE1:
812 case CUPS_CSPACE_DEVICE2:
813 case CUPS_CSPACE_DEVICE3:
814 case CUPS_CSPACE_DEVICE4:
815 case CUPS_CSPACE_DEVICE5:
816 case CUPS_CSPACE_DEVICE6:
817 case CUPS_CSPACE_DEVICE7:
818 case CUPS_CSPACE_DEVICE8:
819 case CUPS_CSPACE_DEVICE9:
820 case CUPS_CSPACE_DEVICEA:
821 case CUPS_CSPACE_DEVICEB:
822 case CUPS_CSPACE_DEVICEC:
823 case CUPS_CSPACE_DEVICED:
824 case CUPS_CSPACE_DEVICEE:
825 case CUPS_CSPACE_DEVICEF:
826 case CUPS_CSPACE_CMYK:
827 dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceCMYK");
828 break;
829 default:
830 fputs("DEBUG: Color space not supported.\n", stderr);
831 return QPDFObjectHandle();
832 }
833 } else
834 return QPDFObjectHandle();
835
836 ret.replaceDict(QPDFObjectHandle::newDictionary(dict));
837
838 #ifdef PRE_COMPRESS
839 // we deliver already compressed content (instead of letting QPDFWriter do it), to avoid using excessive memory
840 Pl_Buffer psink("psink");
841 Pl_Flate pflate("pflate",&psink,Pl_Flate::a_deflate);
842
843 pflate.write(page_data->getBuffer(),page_data->getSize());
844 pflate.finish();
845
846 ret.replaceStreamData(PointerHolder<Buffer>(psink.getBuffer()),
847 QPDFObjectHandle::newName("/FlateDecode"),QPDFObjectHandle::newNull());
848 #else
849 ret.replaceStreamData(page_data,QPDFObjectHandle::newNull(),QPDFObjectHandle::newNull());
850 #endif
851
852 return ret;
853 }
854
finish_page(struct pdf_info * info)855 void finish_page(struct pdf_info * info)
856 {
857 if (info->outformat == OUTPUT_FORMAT_PDF)
858 {
859 // Finish previous PDF Page
860 if(!info->page_data.getPointer())
861 return;
862
863 QPDFObjectHandle image = makeImage(info->pdf, info->page_data, info->width, info->height, info->render_intent, info->color_space, info->bpc);
864 if(!image.isInitialized()) die("Unable to load image data");
865
866 // add it
867 info->page.getKey("/Resources").getKey("/XObject").replaceKey("/I",image);
868 }
869 #ifdef QPDF_HAVE_PCLM
870 else if (info->outformat == OUTPUT_FORMAT_PCLM)
871 {
872 // Finish previous PCLm page
873 if (info->pclm_num_strips == 0)
874 return;
875
876 for (size_t i = 0; i < info->pclm_strip_data.size(); i ++)
877 if(!info->pclm_strip_data[i].getPointer())
878 return;
879
880 std::vector<QPDFObjectHandle> strips = makePclmStrips(info->pdf, info->pclm_num_strips, info->pclm_strip_data, info->pclm_compression_method_preferred, info->width, info->pclm_strip_height, info->color_space, info->bpc);
881 for (size_t i = 0; i < info->pclm_num_strips; i ++)
882 if(!strips[i].isInitialized()) die("Unable to load strip data");
883
884 // add it
885 for (size_t i = 0; i < info->pclm_num_strips; i ++)
886 info->page.getKey("/Resources").getKey("/XObject")
887 .replaceKey("/Image" +
888 int_to_fwstring(i,num_digits(info->pclm_num_strips - 1)),
889 strips[i]);
890 }
891 #endif
892
893 // draw it
894 std::string content;
895 if (info->outformat == OUTPUT_FORMAT_PDF)
896 {
897 content.append(QUtil::double_to_string(info->page_width) + " 0 0 " +
898 QUtil::double_to_string(info->page_height) + " 0 0 cm\n");
899 content.append("/I Do\n");
900 }
901 #ifdef QPDF_HAVE_PCLM
902 else if (info->outformat == OUTPUT_FORMAT_PCLM)
903 {
904 std::string res = info->pclm_source_resolution_default;
905
906 // resolution is in dpi, so remove the last three characters from
907 // resolution string to get resolution integer
908 unsigned resolution_integer = std::stoi(res.substr(0, res.size() - 3));
909 double d = (double)DEFAULT_PDF_UNIT / resolution_integer;
910 content.append(QUtil::double_to_string(d) + " 0 0 " + QUtil::double_to_string(d) + " 0 0 cm\n");
911 unsigned yAnchor = info->height;
912 for (unsigned i = 0; i < info->pclm_num_strips; i ++)
913 {
914 yAnchor -= info->pclm_strip_height[i];
915 content.append("/P <</MCID 0>> BDC q\n");
916 content.append(QUtil::int_to_string(info->width) + " 0 0 " +
917 QUtil::int_to_string(info->pclm_strip_height[i]) +
918 " 0 " + QUtil::int_to_string(yAnchor) + " cm\n");
919 content.append("/Image" +
920 int_to_fwstring(i, num_digits(info->pclm_num_strips - 1)) +
921 " Do Q\n");
922 }
923 }
924 #endif
925
926 QPDFObjectHandle page_contents = info->page.getKey("/Contents");
927 if (info->outformat == OUTPUT_FORMAT_PDF)
928 page_contents.replaceStreamData(content, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
929 #ifdef QPDF_HAVE_PCLM
930 else if (info->outformat == OUTPUT_FORMAT_PCLM)
931 page_contents.getArrayItem(0).replaceStreamData(content, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
932 #endif
933
934 // bookkeeping
935 info->page_data = PointerHolder<Buffer>();
936 #ifdef QPDF_HAVE_PCLM
937 info->pclm_strip_data.clear();
938 #endif
939 }
940
941
942 /* Perform modifications to PDF if color space conversions are needed */
prepare_pdf_page(struct pdf_info * info,unsigned width,unsigned height,unsigned bpl,unsigned bpp,unsigned bpc,std::string render_intent,cups_cspace_t color_space)943 int prepare_pdf_page(struct pdf_info * info, unsigned width, unsigned height, unsigned bpl,
944 unsigned bpp, unsigned bpc, std::string render_intent, cups_cspace_t color_space)
945 {
946 #define IMAGE_CMYK_8 (bpp == 32 && bpc == 8)
947 #define IMAGE_CMYK_16 (bpp == 64 && bpc == 16)
948 #define IMAGE_RGB_8 (bpp == 24 && bpc == 8)
949 #define IMAGE_RGB_16 (bpp == 48 && bpc == 16)
950 #define IMAGE_WHITE_1 (bpp == 1 && bpc == 1)
951 #define IMAGE_WHITE_8 (bpp == 8 && bpc == 8)
952 #define IMAGE_WHITE_16 (bpp == 16 && bpc == 16)
953
954 int error = 0;
955 pdfConvertFunction fn = convertPdf_NoConversion;
956 cmsColorSpaceSignature css;
957
958 /* Register available raster information into the PDF */
959 info->width = width;
960 info->height = height;
961 info->line_bytes = bpl;
962 info->bpp = bpp;
963 info->bpc = bpc;
964 info->render_intent = render_intent;
965 info->color_space = color_space;
966 if (info->outformat == OUTPUT_FORMAT_PCLM)
967 {
968 info->pclm_num_strips = (height / info->pclm_strip_height_preferred) +
969 (height % info->pclm_strip_height_preferred ? 1 : 0);
970 info->pclm_strip_height.resize(info->pclm_num_strips);
971 info->pclm_strip_data.resize(info->pclm_num_strips);
972 for (size_t i = 0; i < info->pclm_num_strips; i ++)
973 {
974 info->pclm_strip_height[i] = info->pclm_strip_height_preferred < height ?
975 info->pclm_strip_height_preferred : height;
976 height -= info->pclm_strip_height[i];
977 }
978 }
979
980 /* Invert grayscale by default */
981 if (color_space == CUPS_CSPACE_K)
982 fn = convertPdf_InvertColors;
983
984 if (colorProfile != NULL) {
985 css = cmsGetColorSpace(colorProfile);
986
987 // Convert image and PDF color space to an embedded ICC Profile color space
988 switch(css) {
989 // Convert PDF to Grayscale when using a gray profile
990 case cmsSigGrayData:
991 if (color_space == CUPS_CSPACE_CMYK)
992 fn = convertPdf_Cmyk8ToWhite8;
993 else if (color_space == CUPS_CSPACE_RGB)
994 fn = convertPdf_Rgb8ToWhite8;
995 else
996 fn = convertPdf_InvertColors;
997 info->color_space = CUPS_CSPACE_K;
998 break;
999 // Convert PDF to RGB when using an RGB profile
1000 case cmsSigRgbData:
1001 if (color_space == CUPS_CSPACE_CMYK)
1002 fn = convertPdf_Cmyk8ToRgb8;
1003 else if (color_space == CUPS_CSPACE_K)
1004 fn = convertPdf_White8ToRgb8;
1005 info->color_space = CUPS_CSPACE_RGB;
1006 break;
1007 // Convert PDF to CMYK when using an RGB profile
1008 case cmsSigCmykData:
1009 if (color_space == CUPS_CSPACE_RGB)
1010 fn = convertPdf_Rgb8ToCmyk8;
1011 else if (color_space == CUPS_CSPACE_K)
1012 fn = convertPdf_White8ToCmyk8;
1013 info->color_space = CUPS_CSPACE_CMYK;
1014 break;
1015 default:
1016 fputs("DEBUG: Unable to convert PDF from profile.\n", stderr);
1017 colorProfile = NULL;
1018 error = 1;
1019 }
1020 // Perform conversion of an image color space
1021 } else if (!cm_disabled) {
1022 switch (color_space) {
1023 // Convert image to CMYK
1024 case CUPS_CSPACE_CMYK:
1025 if (IMAGE_RGB_8)
1026 fn = convertPdf_Rgb8ToCmyk8;
1027 else if (IMAGE_RGB_16)
1028 fn = convertPdf_NoConversion;
1029 else if (IMAGE_WHITE_8)
1030 fn = convertPdf_White8ToCmyk8;
1031 else if (IMAGE_WHITE_16)
1032 fn = convertPdf_NoConversion;
1033 break;
1034 // Convert image to RGB
1035 case CUPS_CSPACE_ADOBERGB:
1036 case CUPS_CSPACE_RGB:
1037 case CUPS_CSPACE_SRGB:
1038 if (IMAGE_CMYK_8)
1039 fn = convertPdf_Cmyk8ToRgb8;
1040 else if (IMAGE_CMYK_16)
1041 fn = convertPdf_NoConversion;
1042 else if (IMAGE_WHITE_8)
1043 fn = convertPdf_White8ToRgb8;
1044 else if (IMAGE_WHITE_16)
1045 fn = convertPdf_NoConversion;
1046 break;
1047 // Convert image to Grayscale
1048 case CUPS_CSPACE_SW:
1049 case CUPS_CSPACE_K:
1050 if (IMAGE_CMYK_8)
1051 fn = convertPdf_Cmyk8ToWhite8;
1052 else if (IMAGE_CMYK_16)
1053 fn = convertPdf_NoConversion;
1054 else if (IMAGE_RGB_8)
1055 fn = convertPdf_Rgb8ToWhite8;
1056 else if (IMAGE_RGB_16)
1057 fn = convertPdf_NoConversion;
1058 break;
1059 case CUPS_CSPACE_DEVICE1:
1060 case CUPS_CSPACE_DEVICE2:
1061 case CUPS_CSPACE_DEVICE3:
1062 case CUPS_CSPACE_DEVICE4:
1063 case CUPS_CSPACE_DEVICE5:
1064 case CUPS_CSPACE_DEVICE6:
1065 case CUPS_CSPACE_DEVICE7:
1066 case CUPS_CSPACE_DEVICE8:
1067 case CUPS_CSPACE_DEVICE9:
1068 case CUPS_CSPACE_DEVICEA:
1069 case CUPS_CSPACE_DEVICEB:
1070 case CUPS_CSPACE_DEVICEC:
1071 case CUPS_CSPACE_DEVICED:
1072 case CUPS_CSPACE_DEVICEE:
1073 case CUPS_CSPACE_DEVICEF:
1074 // No conversion for right now
1075 fn = convertPdf_NoConversion;
1076 break;
1077 default:
1078 fputs("DEBUG: Color space not supported.\n", stderr);
1079 error = 1;
1080 break;
1081 }
1082 }
1083
1084 if (!error)
1085 fn(info);
1086
1087 return error;
1088 }
1089
add_pdf_page(struct pdf_info * info,int pagen,unsigned width,unsigned height,int bpp,int bpc,int bpl,std::string render_intent,cups_cspace_t color_space,unsigned xdpi,unsigned ydpi)1090 int add_pdf_page(struct pdf_info * info, int pagen, unsigned width,
1091 unsigned height, int bpp, int bpc, int bpl, std::string render_intent,
1092 cups_cspace_t color_space, unsigned xdpi, unsigned ydpi)
1093 {
1094 try {
1095 finish_page(info); // any active
1096
1097 prepare_pdf_page(info, width, height, bpl, bpp,
1098 bpc, render_intent, color_space);
1099
1100 if (info->height > (std::numeric_limits<unsigned>::max() / info->line_bytes)) {
1101 die("Page too big");
1102 }
1103 if (info->outformat == OUTPUT_FORMAT_PDF)
1104 info->page_data = PointerHolder<Buffer>(new Buffer(info->line_bytes*info->height));
1105 else if (info->outformat == OUTPUT_FORMAT_PCLM)
1106 {
1107 // reserve space for PCLm strips
1108 for (size_t i = 0; i < info->pclm_num_strips; i ++)
1109 info->pclm_strip_data[i] = PointerHolder<Buffer>(new Buffer(info->line_bytes*info->pclm_strip_height[i]));
1110 }
1111
1112 QPDFObjectHandle page = QPDFObjectHandle::parse(
1113 "<<"
1114 " /Type /Page"
1115 " /Resources <<"
1116 " /XObject << >> "
1117 " >>"
1118 " /MediaBox null "
1119 " /Contents null "
1120 ">>");
1121
1122 // Convert to pdf units
1123 info->page_width=((double)info->width/xdpi)*DEFAULT_PDF_UNIT;
1124 info->page_height=((double)info->height/ydpi)*DEFAULT_PDF_UNIT;
1125 if (info->outformat == OUTPUT_FORMAT_PDF)
1126 {
1127 page.replaceKey("/Contents",QPDFObjectHandle::newStream(&info->pdf)); // data will be provided later
1128 page.replaceKey("/MediaBox",makeRealBox(0,0,info->page_width,info->page_height));
1129 }
1130 else if (info->outformat == OUTPUT_FORMAT_PCLM)
1131 {
1132 page.replaceKey("/Contents",
1133 QPDFObjectHandle::newArray(std::vector<QPDFObjectHandle>(1, QPDFObjectHandle::newStream(&info->pdf))));
1134
1135 // box with dimensions rounded off to the nearest integer
1136 page.replaceKey("/MediaBox",makeIntegerBox(0,0,info->page_width + 0.5,info->page_height + 0.5));
1137 }
1138
1139 info->page = info->pdf.makeIndirectObject(page); // we want to keep a reference
1140 info->pdf.addPage(info->page, false);
1141 } catch (std::bad_alloc &ex) {
1142 die("Unable to allocate page data");
1143 } catch (...) {
1144 return 1;
1145 }
1146
1147 return 0;
1148 }
1149
close_pdf_file(struct pdf_info * info)1150 int close_pdf_file(struct pdf_info * info)
1151 {
1152 try {
1153 finish_page(info); // any active
1154
1155 QPDFWriter output(info->pdf,NULL);
1156 // output.setMinimumPDFVersion("1.4");
1157 #ifdef QPDF_HAVE_PCLM
1158 if (info->outformat == OUTPUT_FORMAT_PCLM)
1159 output.setPCLm(true);
1160 #endif
1161 output.write();
1162 } catch (...) {
1163 return 1;
1164 }
1165
1166 return 0;
1167 }
1168
pdf_set_line(struct pdf_info * info,unsigned line_n,unsigned char * line)1169 void pdf_set_line(struct pdf_info * info, unsigned line_n, unsigned char *line)
1170 {
1171 //dprintf("pdf_set_line(%d)\n", line_n);
1172
1173 if(line_n > info->height)
1174 {
1175 dprintf("Bad line %d\n", line_n);
1176 return;
1177 }
1178
1179 switch(info->outformat)
1180 {
1181 case OUTPUT_FORMAT_PDF:
1182 memcpy((info->page_data->getBuffer()+(line_n*info->line_bytes)), line, info->line_bytes);
1183 break;
1184 case OUTPUT_FORMAT_PCLM:
1185 // copy line data into appropriate pclm strip
1186 size_t strip_num = line_n / info->pclm_strip_height_preferred;
1187 unsigned line_strip = line_n - strip_num*info->pclm_strip_height_preferred;
1188 memcpy(((info->pclm_strip_data[strip_num])->getBuffer() + (line_strip*info->line_bytes)),
1189 line, info->line_bytes);
1190 break;
1191 }
1192 }
1193
convert_raster(cups_raster_t * ras,unsigned width,unsigned height,int bpp,int bpl,struct pdf_info * info)1194 int convert_raster(cups_raster_t *ras, unsigned width, unsigned height,
1195 int bpp, int bpl, struct pdf_info * info)
1196 {
1197 // We should be at raster start
1198 int i;
1199 unsigned cur_line = 0;
1200 unsigned char *PixelBuffer, *ptr = NULL, *buff;
1201
1202 PixelBuffer = (unsigned char *)malloc(bpl);
1203 buff = (unsigned char *)malloc(info->line_bytes);
1204
1205 do
1206 {
1207 // Read raster data...
1208 cupsRasterReadPixels(ras, PixelBuffer, bpl);
1209
1210 #if !ARCH_IS_BIG_ENDIAN
1211
1212 if (info->bpc == 16)
1213 {
1214 // Swap byte pairs for endianess (cupsRasterReadPixels() switches
1215 // from Big Endian back to the system's Endian)
1216 for (i = bpl, ptr = PixelBuffer; i > 0; i -= 2, ptr += 2)
1217 {
1218 unsigned char swap = *ptr;
1219 *ptr = *(ptr + 1);
1220 *(ptr + 1) = swap;
1221 }
1222 }
1223 #endif /* !ARCH_IS_BIG_ENDIAN */
1224
1225 // perform bit operations if necessary
1226 bit_function(PixelBuffer, ptr, bpl);
1227
1228 // write lines and color convert when necessary
1229 pdf_set_line(info, cur_line, conversion_function(PixelBuffer, buff, width));
1230 ++cur_line;
1231 }
1232 while(cur_line < height);
1233
1234 free(buff);
1235 free(PixelBuffer);
1236
1237 return 0;
1238 }
1239
setProfile(const char * path)1240 int setProfile(const char * path)
1241 {
1242 if (path != NULL)
1243 colorProfile = cmsOpenProfileFromFile(path,"r");
1244
1245 if (colorProfile != NULL) {
1246 fputs("DEBUG: Load profile successful.\n", stderr);
1247 return 0;
1248 }
1249 else {
1250 fputs("DEBUG: Unable to load profile.\n", stderr);
1251 return 1;
1252 }
1253 }
1254
1255 /* Obtain a source profile name using color qualifiers from raster file */
getIPPColorProfileName(const char * media_type,cups_cspace_t cs,unsigned dpi)1256 const char * getIPPColorProfileName(const char * media_type, cups_cspace_t cs, unsigned dpi)
1257 {
1258 std::string mediaType = "";
1259 std::string resolution = "";
1260 std::string colorModel = "";
1261
1262 std::string iccProfile = "";
1263
1264 // ColorModel
1265 switch (cs) {
1266 case CUPS_CSPACE_RGB:
1267 colorModel = "rgb";
1268 break;
1269 case CUPS_CSPACE_SRGB:
1270 colorModel = "srgb";
1271 break;
1272 case CUPS_CSPACE_ADOBERGB:
1273 colorModel = "adobergb";
1274 break;
1275 case CUPS_CSPACE_K:
1276 colorModel = "gray";
1277 break;
1278 case CUPS_CSPACE_CMYK:
1279 colorModel = "cmyk";
1280 break;
1281 default:
1282 colorModel = "";
1283 break;
1284 }
1285
1286 if (media_type != NULL)
1287 mediaType = media_type;
1288 if (dpi > 0)
1289 resolution = dpi;
1290
1291 // Requires color space and media type qualifiers
1292 if (resolution != "" || colorModel != "")
1293 return 0;
1294
1295 // profile-uri reference: "http://www.server.com/colorModel-Resolution-mediaType.icc
1296 if (mediaType != "")
1297 iccProfile = colorModel + "-" + resolution + ".icc";
1298 else
1299 iccProfile = colorModel + "-" + resolution + "-" + mediaType + ".icc";
1300
1301 return strdup(iccProfile.c_str());
1302 }
1303
main(int argc,char ** argv)1304 int main(int argc, char **argv)
1305 {
1306 char *outformat_env = NULL;
1307 OutFormatType outformat; /* Output format */
1308 int fd, Page, empty = 1;
1309 struct pdf_info pdf;
1310 FILE * input = NULL;
1311 cups_raster_t *ras; /* Raster stream for printing */
1312 cups_page_header2_t header; /* Page header from file */
1313 ppd_file_t *ppd; /* PPD file */
1314 ppd_attr_t *attr; /* PPD attribute */
1315 int num_options; /* Number of options */
1316 const char* profile_name; /* IPP Profile Name */
1317 cups_option_t *options; /* Options */
1318
1319 // Make sure status messages are not buffered...
1320 setbuf(stderr, NULL);
1321
1322 cmsSetLogErrorHandler(lcmsErrorHandler);
1323
1324 if (argc < 6 || argc > 7)
1325 {
1326 fprintf(stderr, "Usage: %s <job> <user> <job name> <copies> <option> [file]\n", argv[0]);
1327 return 1;
1328 }
1329
1330 /* Determine the output format via an environment variable set by a wrapper
1331 script */
1332 #ifdef QPDF_HAVE_PCLM
1333 if ((outformat_env = getenv("OUTFORMAT")) == NULL || strcasestr(outformat_env, "pdf"))
1334 outformat = OUTPUT_FORMAT_PDF;
1335 else if (strcasestr(outformat_env, "pclm"))
1336 outformat = OUTPUT_FORMAT_PCLM;
1337 else {
1338 fprintf(stderr, "ERROR: OUTFORMAT=\"%s\", cannot determine output format\n",
1339 outformat_env);
1340 return 1;
1341 }
1342 #else
1343 outformat = OUTPUT_FORMAT_PDF;
1344 #endif
1345 fprintf(stderr, "DEBUG: OUTFORMAT=\"%s\", output format will be %s\n",
1346 (outformat_env ? outformat_env : "<none>"),
1347 (outformat == OUTPUT_FORMAT_PDF ? "PDF" : "PCLM"));
1348
1349 num_options = cupsParseOptions(argv[5], 0, &options);
1350
1351 /* support the CUPS "cm-calibration" option */
1352 cm_calibrate = cmGetCupsColorCalibrateMode(options, num_options);
1353
1354 if (outformat == OUTPUT_FORMAT_PCLM ||
1355 cm_calibrate == CM_CALIBRATION_ENABLED)
1356 cm_disabled = 1;
1357 else
1358 cm_disabled = cmIsPrinterCmDisabled(getenv("PRINTER"));
1359
1360 // Open the PPD file...
1361 ppd = ppdOpenFile(getenv("PPD"));
1362
1363 if (ppd)
1364 {
1365 ppdMarkDefaults(ppd);
1366 cupsMarkOptions(ppd, num_options, options);
1367 }
1368 else
1369 {
1370 ppd_status_t status; /* PPD error */
1371 int linenum; /* Line number */
1372
1373 fputs("DEBUG: The PPD file could not be opened.\n", stderr);
1374
1375 status = ppdLastError(&linenum);
1376
1377 fprintf(stderr, "DEBUG: %s on line %d.\n", ppdErrorString(status), linenum);
1378 #ifdef QPDF_HAVE_PCLM
1379 if (outformat == OUTPUT_FORMAT_PCLM) {
1380 fprintf(stderr, "ERROR: PCLm output only possible with PPD file.\n");
1381 return 1;
1382 }
1383 #endif
1384 }
1385
1386 // Open the page stream...
1387 if (argc == 7)
1388 {
1389 input = fopen(argv[6], "rb");
1390 if (input == NULL) die("Unable to open PWG Raster file");
1391 }
1392 else
1393 input = stdin;
1394
1395 // Get fd from file
1396 fd = fileno(input);
1397
1398 // Transform
1399 ras = cupsRasterOpen(fd, CUPS_RASTER_READ);
1400
1401 // Process pages as needed...
1402 Page = 0;
1403
1404 /* Get PCLm attributes from PPD */
1405 if (ppd && outformat == OUTPUT_FORMAT_PCLM)
1406 {
1407 char *attr_name = (char *)"cupsPclmStripHeightPreferred";
1408 if ((attr = ppdFindAttr(ppd, attr_name, NULL)) != NULL)
1409 {
1410 fprintf(stderr, "DEBUG: PPD PCLm attribute \"%s\" with value \"%s\"\n",
1411 attr_name, attr->value);
1412 pdf.pclm_strip_height_preferred = atoi(attr->value);
1413 }
1414 else
1415 pdf.pclm_strip_height_preferred = 16; /* default strip height */
1416
1417 attr_name = (char *)"cupsPclmStripHeightSupported";
1418 if ((attr = ppdFindAttr(ppd, attr_name, NULL)) != NULL)
1419 {
1420 fprintf(stderr, "DEBUG: PPD PCLm attribute \"%s\" with value \"%s\"\n",
1421 attr_name, attr->value);
1422 pdf.pclm_strip_height_supported.clear(); // remove default value = 16
1423 std::vector<std::string> vec = split_strings(attr->value, ",");
1424 for (size_t i = 0; i < vec.size(); i ++)
1425 pdf.pclm_strip_height_supported.push_back(atoi(vec[i].c_str()));
1426 vec.clear();
1427 }
1428
1429 attr_name = (char *)"cupsPclmRasterBackSide";
1430 if ((attr = ppdFindAttr(ppd, attr_name, NULL)) != NULL)
1431 {
1432 fprintf(stderr, "DEBUG: PPD PCLm attribute \"%s\" with value \"%s\"\n",
1433 attr_name, attr->value);
1434 pdf.pclm_raster_back_side = attr->value;
1435 }
1436
1437 attr_name = (char *)"cupsPclmSourceResolutionDefault";
1438 if ((attr = ppdFindAttr(ppd, attr_name, NULL)) != NULL)
1439 {
1440 fprintf(stderr, "DEBUG: PPD PCLm attribute \"%s\" with value \"%s\"\n",
1441 attr_name, attr->value);
1442 pdf.pclm_source_resolution_default = attr->value;
1443 }
1444
1445 attr_name = (char *)"cupsPclmSourceResolutionSupported";
1446 if ((attr = ppdFindAttr(ppd, attr_name, NULL)) != NULL)
1447 {
1448 fprintf(stderr, "DEBUG: PPD PCLm attribute \"%s\" with value \"%s\"\n",
1449 attr_name, attr->value);
1450 pdf.pclm_source_resolution_supported = split_strings(attr->value, ",");
1451 }
1452
1453 attr_name = (char *)"cupsPclmCompressionMethodPreferred";
1454 if ((attr = ppdFindAttr(ppd, attr_name, NULL)) != NULL)
1455 {
1456 fprintf(stderr, "DEBUG: PPD PCLm attribute \"%s\" with value \"%s\"\n",
1457 attr_name, attr->value);
1458 std::vector<std::string> vec = split_strings(attr->value, ",");
1459
1460 // get all compression methods supported by the printer
1461 for (std::vector<std::string>::iterator it = vec.begin();
1462 it != vec.end(); ++it)
1463 {
1464 std::string compression_method = *it;
1465 for (char& x: compression_method)
1466 x = tolower(x);
1467 if (compression_method == "flate")
1468 pdf.pclm_compression_method_preferred.push_back(FLATE_DECODE);
1469 else if (compression_method == "rle")
1470 pdf.pclm_compression_method_preferred.push_back(RLE_DECODE);
1471 else if (compression_method == "jpeg")
1472 pdf.pclm_compression_method_preferred.push_back(DCT_DECODE);
1473 }
1474
1475 }
1476 // If the compression methods is none of the above or is erreneous
1477 // use FLATE as compression method and show a warning.
1478 if (pdf.pclm_compression_method_preferred.empty())
1479 {
1480 fprintf(stderr, "WARNING: (rastertopclm) Unable parse PPD attribute \"%s\". Using FLATE for encoding image streams.\n", attr_name);
1481 pdf.pclm_compression_method_preferred.push_back(FLATE_DECODE);
1482 }
1483 }
1484
1485 while (cupsRasterReadHeader2(ras, &header))
1486 {
1487 if (empty)
1488 {
1489 empty = 0;
1490 // We have a valid input page, so create PDF file
1491 if (create_pdf_file(&pdf, outformat) != 0)
1492 die("Unable to create PDF file");
1493 }
1494
1495 // Write a status message with the page number
1496 Page ++;
1497 fprintf(stderr, "INFO: Starting page %d.\n", Page);
1498
1499 // Use "profile=profile_name.icc" to embed 'profile_name.icc' into the PDF
1500 // for testing. Forces color management to enable.
1501 if (outformat == OUTPUT_FORMAT_PDF &&
1502 (profile_name = cupsGetOption("profile", num_options, options)) != NULL) {
1503 setProfile(profile_name);
1504 cm_disabled = 0;
1505 }
1506 if (colorProfile != NULL)
1507 fprintf(stderr, "DEBUG: TEST ICC Profile specified (color management forced ON): \n[%s]\n", profile_name);
1508
1509 // Add a new page to PDF file
1510 if (add_pdf_page(&pdf, Page, header.cupsWidth, header.cupsHeight,
1511 header.cupsBitsPerPixel, header.cupsBitsPerColor,
1512 header.cupsBytesPerLine, header.cupsRenderingIntent,
1513 header.cupsColorSpace, header.HWResolution[0],
1514 header.HWResolution[1]) != 0)
1515 die("Unable to start new PDF page");
1516
1517 // Write the bit map into the PDF file
1518 if (convert_raster(ras, header.cupsWidth, header.cupsHeight,
1519 header.cupsBitsPerPixel, header.cupsBytesPerLine,
1520 &pdf) != 0)
1521 die("Failed to convert page bitmap");
1522 }
1523
1524 if (empty)
1525 {
1526 fprintf(stderr, "DEBUG: Input is empty, outputting empty file.\n");
1527 cupsRasterClose(ras);
1528 return 0;
1529 }
1530
1531 close_pdf_file(&pdf); // will output to stdout
1532
1533 if (colorProfile != NULL) {
1534 cmsCloseProfile(colorProfile);
1535 }
1536
1537 cupsFreeOptions(num_options, options);
1538
1539 cupsRasterClose(ras);
1540
1541 if (fd != 0)
1542 close(fd);
1543
1544 return (Page == 0);
1545 }
1546