xref: /aosp_15_r20/external/libcups/filter/rastertopdf.cpp (revision 5e7646d21f1134fb0638875d812ef646c12ab91e)
1 /*
2  * Raster filter to pdf
3  *
4  * Copyright © 2022 by Apple Inc.
5  *
6  * Licensed under Apache License v2.0.  See the file "LICENSE" for more
7  * information.
8  *
9  * see <https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf> for complete spec
10  * see <https://zlib.net/manual.html> for zlib documentation
11  *
12  * To build with zlib:
13  *    g++ -c -g -Os -o rastertopdf.o rastertopdf.cpp
14  *    cc -o rasterToPDF rastertopdf.o -lz -lstdc++ `cups-config --libs`
15  *
16  * To build without zlib: This will produce very large pdf files.
17  *    g++ -DDeflateData=0 -c -g -Os -o rastertopdf.o rastertopdf.cpp
18  *    cc -o rasterToPDF rastertopdf.o -lstdc++ `cups-config --libs`
19  */
20 
21 #include <cups/cups.h>
22 #include <cups/raster.h>
23 #include <cups/backend.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <libgen.h>
27 #include <limits.h>
28 #include <signal.h>
29 #include <stdio.h>
30 #include <sys/stat.h>
31 #include <vector>
32 
33 
34 #ifndef __has_include
35     static_assert(false, "__has_include not supported");
36 #endif
37 
38 #if ( !defined(DeflateData) )
39     #if __has_include(<zlib.h>)
40         #define DeflateData 1
41         #include <zlib.h>
42     #else
43         #define DeflateData 0
44     #endif
45 #elif ( DeflateData )
46     #if __has_include(<zlib.h>)
47         #include <zlib.h>
48     #else
49         #warning 'zlib.h' does not exits.
50         #undef DeflateData
51         #define DeflateData 0
52     #endif
53 #endif
54 
55 static int Canceled = 0;        /* Has the current job been canceled? */
56 
57 // MARK: - Misc  -
rasterToPDFColorSpace(cups_cspace_t colorSpace,int bitsPerPixel,int * bitsPerComponent,char * cs,size_t csLen)58 static int rasterToPDFColorSpace( cups_cspace_t colorSpace, int bitsPerPixel, int *bitsPerComponent, char *cs, size_t csLen )
59 {
60     switch (colorSpace)
61     {
62         case CUPS_CSPACE_W:
63         case CUPS_CSPACE_SW:
64             *bitsPerComponent = bitsPerPixel;
65             strncpy( cs, "[/CalGray << /Gamma 2.2 /WhitePoint[ 0.9505 1.0 1.089 ] >>]", csLen );
66         break;
67 
68         case CUPS_CSPACE_RGB:
69         case CUPS_CSPACE_SRGB:
70             *bitsPerComponent = bitsPerPixel/3;
71             strncpy( cs, "[/CalRGB <<\n"
72                          "   /Gamma[ 2.2 2.2 2.2 ]\n"
73                          "   /Matrix[ 0.4124 0.2126 0.0193\n"
74                          "            0.3576 0.7152 0.1192\n"
75                          "            0.1805 0.0722 0.9505 ]\n"
76                          "   /WhitePoint[ 0.9505 1.0 1.089 ]\n"
77                          ">>]", csLen );
78         break;
79 
80         default :
81             // AirPrint only requires sRGB and 2.2 gray.
82             // NOTE: This is not a general solution.
83             fprintf(stderr, "DEBUG: Unsupported colorspace %u.\n", colorSpace);
84             return -1;
85     }
86     return 0;
87 }
88 
compressImageData(const unsigned char * inData,size_t inSize,unsigned char ** outData,size_t * outSize)89 static void compressImageData(const unsigned char *inData,
90                               size_t inSize,
91                               unsigned char **outData,
92                               size_t *outSize )
93 {
94     if (outData == NULL || outSize == NULL)
95     {
96         fprintf(stderr, "Invalid Parameters, Line:%d\n", __LINE__);
97         exit( EXIT_FAILURE );
98     }
99 #if DeflateData
100     int err = ENOMEM;
101 
102     *outSize = compressBound( (uLongf)inSize );
103     *outData = (unsigned char *)malloc( *outSize );
104 
105     if (*outData != NULL) err = compress( *outData, outSize, inData, inSize );
106 
107     if (err != 0)
108     {
109         fprintf( stderr, "Failed to %s data, Line:%d\n", (*outData ? "compress" : "allocate"), __LINE__);
110 
111         if (*outData) free( *outData );
112 
113         *outData = (unsigned char *)inData;
114         *outSize = inSize;
115     }
116 #else
117     *outData = (unsigned char *)inData;
118     *outSize = inSize;
119 #endif
120 }
121 
122 // MARK: - PDF Stuff -
writeImageObject(FILE * pdfFile,unsigned int imageReference,unsigned int width,unsigned int height,int interpolate,int bitsPerComponent,char colorspace[64],const unsigned char * rasterData,size_t rasterDataSize)123 static long writeImageObject(FILE *pdfFile,
124                              unsigned int imageReference,
125                              unsigned int width,
126                              unsigned int height,
127                              int interpolate,
128                              int bitsPerComponent,
129                              char colorspace[64],
130                              const unsigned char *rasterData,
131                              size_t rasterDataSize )
132 {
133     unsigned char *data = NULL;
134     size_t size;
135     long objectOffset = 0;
136 
137     compressImageData( rasterData, rasterDataSize, &data, &size );
138 
139     fprintf(pdfFile, "\n%u 0 obj\n", imageReference );
140     objectOffset = ftell(pdfFile);
141     fprintf(pdfFile, "<< /Type /XObject\n"
142                      "   /Subtype /Image\n"
143                      "   /Width %u\n"
144                      "   /Height %u\n"
145                      "   /Interpolate %s\n"
146                      "   /ColorSpace %s\n"
147                      "   /BitsPerComponent %d\n"
148                      "   /Length %zu\n", width, height, (interpolate ? "true" : "false"), colorspace, bitsPerComponent, rasterDataSize );
149 
150     if (rasterData != data)
151         fprintf(pdfFile, "   /Filter /FlateDecode\n");
152 
153     fprintf(pdfFile, ">>\nstream\n" );
154     fwrite( data, size, 1, pdfFile );
155     fprintf(pdfFile, "\nendstream"
156                      "\nendobj\n");
157 
158     // free the data the was allocated in compressImageData
159     if (rasterData != data) free( data );
160 
161     return objectOffset;
162 }
163 
writePageStream(FILE * pdfFile,unsigned int streamReference,int width,int height,int pageNumber)164 static long writePageStream(FILE *pdfFile,
165                             unsigned int streamReference,
166                             int width,
167                             int height,
168                             int pageNumber)
169 {
170     long objectOffset = 0;
171     char imageStream[64];
172 
173     snprintf( imageStream, sizeof( imageStream ), "q %d 0 0 %d 0 0 cm /Im%u Do Q", width, height, pageNumber );
174 
175     fprintf(pdfFile, "\n%u 0 obj\n", streamReference );
176     objectOffset = ftell(pdfFile);
177     fprintf(pdfFile, "<< /Length %zu >>\n"
178                      "stream\n"
179                      "%s"
180                      "\nendstream"
181                      "\nendobj\n", strlen(imageStream), imageStream );
182 
183     return objectOffset;
184 }
185 
writePageObject(FILE * pdfFile,unsigned int pageReference,unsigned int resouceReference,unsigned int contentReference,int width,int height)186 static long writePageObject(FILE *pdfFile,
187                             unsigned int pageReference,
188                             unsigned int resouceReference,
189                             unsigned int contentReference,
190                             int width,
191                             int height)
192 {
193     long objectOffset = 0;
194 
195     fprintf(pdfFile, "\n%u 0 obj\n", pageReference );
196     objectOffset = ftell(pdfFile);
197     fprintf(pdfFile, "<< /Type /Page\n"
198                      "   /Parent 2 0 R\n"
199                      "   /Resources %u 0 R\n"
200                      "   /Contents %u 0 R\n"
201                      "   /MediaBox [0 0 %d %d]\n"
202                      ">>\nendobj\n", resouceReference, contentReference, width, height );
203 
204     return objectOffset;
205 }
206 
writeResourceObject(FILE * pdfFile,unsigned int rsrcReference,unsigned int contentReference,unsigned int page)207 static long writeResourceObject(FILE *pdfFile,
208                                 unsigned int rsrcReference,
209                                 unsigned int contentReference,
210                                 unsigned int page )
211 {
212     long objectOffset = 0;
213 
214     fprintf(pdfFile, "\n%u 0 obj\n", rsrcReference );
215     objectOffset = ftell(pdfFile);
216     fprintf(pdfFile, "<< /ProcSet [ /PDF /ImageB /ImageC /ImageI ] /XObject << /Im%u %u 0 R >> >>\nendobj\n", page, contentReference );
217 
218     return objectOffset;
219 }
220 
writePagesObject(FILE * pdfFile,std::vector<unsigned int> pages)221 static long writePagesObject( FILE *pdfFile, std::vector<unsigned int> pages )
222 {
223     long objectOffset = 0;
224 
225     fprintf(pdfFile, "\n2 0 obj\n");
226     objectOffset = ftell(pdfFile);
227     fprintf(pdfFile, "<< /Type /Pages /Count %lu /Kids [", (unsigned long)pages.size());
228     for (unsigned int i : pages )
229     {
230         fprintf( pdfFile, " %d 0 R", i );
231     }
232     fprintf(pdfFile, " ] >>\nendobj\n");
233 
234     return objectOffset;
235 }
236 
writeCatalogObject(FILE * pdfFile,unsigned int objectReference)237 static long writeCatalogObject( FILE *pdfFile, unsigned int objectReference )
238 {
239     long objectOffset = 0;
240 
241     fprintf(pdfFile, "\n%u 0 obj\n", objectReference);
242     objectOffset = ftell(pdfFile);
243     fprintf(pdfFile, "<< /Type /Catalog /Pages 2 0 R >>\n");
244     fprintf(pdfFile, "endobj\n");
245 
246     return objectOffset;
247 }
248 
writeTrailerObject(FILE * pdfFile,unsigned int catalogReference,unsigned long numObjects,long startXOffset)249 static void writeTrailerObject(FILE *pdfFile,
250                                unsigned int catalogReference,
251                                unsigned long numObjects,
252                                long startXOffset)
253 {
254     fprintf( pdfFile, "trailer\n"
255                        "<< /Root %u 0 R\n"
256                        "   /Size %lu >>\n"
257                        "startxref\n"
258                        "%ld\n"
259                        "%%%%EOF\n", catalogReference, numObjects, startXOffset);
260 }
261 
writeXRefTable(FILE * pdfFile,std::vector<long> offsets,long startOffset)262 static long writeXRefTable( FILE *pdfFile, std::vector<long> offsets, long startOffset )
263 {
264     long objectOffset = ftell(pdfFile);
265     fprintf( pdfFile, "xref\n"
266                       "0 %lu\n"
267                       "0000000000 65535 f\n", (unsigned long)(offsets.size() + 1) );
268     for (long offset : offsets )
269     {
270         fprintf( pdfFile, "%010ld 00000 n\n", offset - startOffset );
271     }
272     return objectOffset;
273 }
274 
writeHeader(FILE * pdfFile)275 static long writeHeader( FILE *pdfFile )
276 {
277     fprintf(pdfFile, "%%PDF-1.3\n");
278 
279     return ftell(pdfFile);
280 }
281 
282 // MARK: - Work -
convertCUPSRasterToPDF(int rasterIn)283 static int convertCUPSRasterToPDF( int rasterIn )
284 {
285     #define kInitialImageReferenceID 10
286     int err = 0;
287     int pages = 0;
288     unsigned int objectReference = kInitialImageReferenceID;
289     unsigned int catalogReference = objectReference++;
290 
291     long startOffset;
292     long offset;
293 
294     float width = 0;
295     float height = 0;
296 
297     size_t largestAllocatedMemory = 0;
298     unsigned char *rasterData = NULL;
299 
300     std::vector<unsigned int> pageReferences;
301     std::vector<long> objectOffsets;
302     cups_raster_t *rasterFile = NULL;
303     cups_page_header2_t pageHeader;
304 
305     FILE *pdfFile = stdout;
306 
307     rasterFile = cupsRasterOpen(rasterIn, CUPS_RASTER_READ);
308     if (rasterFile == NULL)
309     {
310         err = errno;
311         fprintf(stderr, "ERROR: Error reading raster data.\n");
312         perror("DEBUG: cupsRasterOpen failed to open the file");
313         goto bail;
314     }
315 
316     startOffset = writeHeader( pdfFile );
317     while ( !Canceled && cupsRasterReadHeader2(rasterFile, &pageHeader) )
318     {
319         char colorspace[256];
320         int bitsPerComponent = 8;
321 
322         fprintf(stderr, "PAGE: %d %d\n", pages+1, pageHeader.NumCopies);
323         fprintf(stderr, "DEBUG:%04d] pageHeader.colorSpace=%u, .bitsPerPixel=%u, .duplexMode=%u\n",
324                 pages, pageHeader.cupsColorSpace, pageHeader.cupsBitsPerPixel, pageHeader.Duplex);
325         fprintf(stderr, "DEBUG:      pageHeader.width=%u, .height=%u, .resolution=%u x %u\n",
326                 pageHeader.cupsWidth, pageHeader.cupsHeight, pageHeader.HWResolution[0], pageHeader.HWResolution[1]);
327 
328         int status = rasterToPDFColorSpace( pageHeader.cupsColorSpace, pageHeader.cupsBitsPerPixel, &bitsPerComponent, colorspace, sizeof(colorspace) );
329         if (status)
330         {
331             fprintf( stderr, "INFO: Unable to determine a colorspace. skipping this page.\n" );
332             continue;
333         }
334 
335         size_t imageSize = pageHeader.cupsHeight * pageHeader.cupsBytesPerLine;
336         if (imageSize > largestAllocatedMemory)
337         {
338             rasterData = (unsigned char *)(rasterData == NULL ? malloc(imageSize) : realloc(rasterData, imageSize));
339             largestAllocatedMemory = imageSize;
340         }
341 
342         if (rasterData == NULL)
343         {
344             fprintf(stderr, "ERROR: Unable to allocate memory for page info\n");
345             err = -1;
346             break;
347         }
348 
349         size_t result = (size_t) cupsRasterReadPixels(rasterFile, rasterData, (unsigned int)imageSize);
350         if (result != imageSize)
351         {
352             err = -2;
353             fprintf(stderr, "ERROR: Unable to read print data.\n");
354             fprintf(stderr, "DEBUG: cupsRasterReadPixels faild on page:%d (%zu of %zu bytes read)\n", pages+1, result, imageSize );
355             break;
356         }
357 
358         width = 72.0 * pageHeader.cupsWidth / pageHeader.HWResolution[1];
359         height = 72.0 * pageHeader.cupsHeight / pageHeader.HWResolution[0];
360 
361         unsigned int pageReference  = objectReference++;
362         unsigned int rsrcReference  = objectReference++;
363         unsigned int streamReference = objectReference++;
364         unsigned int imageReference = objectReference++;
365         int interpolate = 0;
366 
367         offset = writePageStream(pdfFile, streamReference, width, height, pages+1 );
368         objectOffsets.push_back( offset );
369 
370         offset = writePageObject(pdfFile,
371                                  pageReference,
372                                  rsrcReference,
373                                  streamReference,
374                                  width,
375                                  height);
376         objectOffsets.push_back( offset );
377 
378         offset = writeResourceObject(pdfFile, rsrcReference, imageReference, pages+1 );
379         objectOffsets.push_back( offset );
380 
381         offset = writeImageObject(pdfFile,
382                                   imageReference,
383                                   pageHeader.cupsWidth,
384                                   pageHeader.cupsHeight,
385                                   interpolate,
386                                   bitsPerComponent,
387                                   colorspace,
388                                   rasterData,
389                                   imageSize);
390         objectOffsets.push_back( offset );
391 
392         pageReferences.push_back( pageReference );
393         pages++;
394     }
395 
396     offset = writePagesObject( pdfFile, pageReferences );
397     objectOffsets.push_back( offset );
398 
399     offset = writeCatalogObject( pdfFile, catalogReference );
400     objectOffsets.push_back( offset );
401 
402     offset = writeXRefTable( pdfFile, objectOffsets, startOffset );
403 
404     writeTrailerObject( pdfFile, catalogReference,
405                        objectOffsets.size() + 1, offset - startOffset );
406 bail:
407     if ( pdfFile != NULL )    fclose( pdfFile );
408     if ( rasterFile != NULL ) cupsRasterClose(rasterFile);
409     if ( rasterIn != -1 )     close( rasterIn );
410     if ( rasterData )         free( rasterData );
411 
412     return err;
413 }
414 
sigterm_handler(int sig)415 static void sigterm_handler(int sig)
416 {
417   (void)sig;
418 
419   Canceled = 1;
420 }
421 
installSignalHandler(void)422 static void installSignalHandler( void )
423 {
424 #ifdef HAVE_SIGSET
425     sigset(SIGTERM, sigterm_handler);
426 #elif defined(HAVE_SIGACTION)
427     struct sigaction action;        /* Actions for POSIX signals */
428     memset(&action, 0, sizeof(action));
429 
430     sigemptyset(&action.sa_mask);
431     sigaddset(&action.sa_mask, SIGTERM);
432 
433     action.sa_handler = sigterm_handler;
434     sigaction(SIGTERM, &action, NULL);
435 #else
436     signal(SIGTERM, sigterm_handler);
437 #endif /* HAVE_SIGSET */
438 }
439 
440 // MARK: -
main(int argc,const char * argv[])441 int main(int argc, const char * argv[])
442 {
443     int err = 0;
444 
445     /*
446      * Make sure status messages are not buffered...
447      */
448     setbuf(stderr, NULL);
449 
450     /*
451      * Check the command-line...
452      */
453     if (argc < 6 || argc > 7)
454     {
455         fprintf(stderr, "Usage: %s job-id user title copies options [file]\n",
456                 argv[0]);
457         return (CUPS_BACKEND_FAILED);
458     }
459 
460     /*
461      * Register a signal handler to eject the current page if the
462      * job is cancelled.
463      */
464      installSignalHandler();
465 
466      int fd = fileno(stdin);
467      if (argc == 7)
468      {
469           if ((fd = open(argv[6], O_RDONLY)) < 0)
470           {
471                perror("ERROR: Unable to open file");
472                return (1);
473           }
474      }
475 
476      err = convertCUPSRasterToPDF( fd );
477      return err;
478 }
479