]> git.ipfire.org Git - thirdparty/cups.git/blob - filter/rastertopdf.cpp
Update ipp documentation to reflect the behavior of configuring WiFi on IPP USB printers.
[thirdparty/cups.git] / filter / rastertopdf.cpp
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 -
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
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 -
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
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
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
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
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
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
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
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
275 static long writeHeader( FILE *pdfFile )
276 {
277 fprintf(pdfFile, "%%PDF-1.3\n");
278
279 return ftell(pdfFile);
280 }
281
282 // MARK: - Work -
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
415 static void sigterm_handler(int sig)
416 {
417 (void)sig;
418
419 Canceled = 1;
420 }
421
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: -
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 }