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