From ec055da6794984133d48cc376f04e10af62b64dc Mon Sep 17 00:00:00 2001 From: steve algernon Date: Mon, 27 Jun 2022 09:22:46 -0700 Subject: adding rastertopdf filter - currently unused in the build --- filter/rastertopdf.cpp | 478 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 478 insertions(+) create mode 100644 filter/rastertopdf.cpp diff --git a/filter/rastertopdf.cpp b/filter/rastertopdf.cpp new file mode 100644 index 000000000..55c39b136 --- /dev/null +++ b/filter/rastertopdf.cpp @@ -0,0 +1,478 @@ +/* + * Raster filter to pdf + * + * Copyright © 2022 by Apple Inc. + * + * Licensed under Apache License v2.0. See the file "LICENSE" for more + * information. + * + * see for complete spec + * see for zlib documentation + * + * To build with zlib: + * g++ -c -g -Os -o rastertopdf.o rastertopdf.cpp + * cc -o rasterToPDF rastertopdf.o -lz -lstdc++ `cups-config --libs` + * + * To build without zlib: This will produce very large pdf files. + * g++ -DDeflateData=0 -c -g -Os -o rastertopdf.o rastertopdf.cpp + * cc -o rasterToPDF rastertopdf.o -lstdc++ `cups-config --libs` + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifndef __has_include + static_assert(false, "__has_include not supported"); +#endif + +#if ( !defined(DeflateData) ) + #if __has_include() + #define DeflateData 1 + #include + #else + #define DeflateData 0 + #endif +#elif ( DeflateData ) + #if __has_include() + #include + #else + #warning 'zlib.h' does not exits. + #undef DeflateData + #define DeflateData 0 + #endif +#endif + +static int Canceled = 0; /* Has the current job been canceled? */ + +// MARK: - Misc - +static int rasterToPDFColorSpace( cups_cspace_t colorSpace, int bitsPerPixel, int *bitsPerComponent, char *cs, size_t csLen ) +{ + switch (colorSpace) + { + case CUPS_CSPACE_W: + case CUPS_CSPACE_SW: + *bitsPerComponent = bitsPerPixel; + strncpy( cs, "[/CalGray << /Gamma 2.2 /WhitePoint[ 0.9505 1.0 1.089 ] >>]", csLen ); + break; + + case CUPS_CSPACE_RGB: + case CUPS_CSPACE_SRGB: + *bitsPerComponent = bitsPerPixel/3; + strncpy( cs, "[/CalRGB <<\n" + " /Gamma[ 2.2 2.2 2.2 ]\n" + " /Matrix[ 0.4124 0.2126 0.0193\n" + " 0.3576 0.7152 0.1192\n" + " 0.1805 0.0722 0.9505 ]\n" + " /WhitePoint[ 0.9505 1.0 1.089 ]\n" + ">>]", csLen ); + break; + + default : + // AirPrint only requires sRGB and 2.2 gray. + // NOTE: This is not a general solution. + fprintf(stderr, "DEBUG: Unsupported colorspace %u.\n", colorSpace); + return -1; + } + return 0; +} + +static void compressImageData(const unsigned char *inData, + size_t inSize, + unsigned char **outData, + size_t *outSize ) +{ + if (outData == NULL || outSize == NULL) + { + fprintf(stderr, "Invalid Parameters, Line:%d\n", __LINE__); + exit( EXIT_FAILURE ); + } +#if DeflateData + int err = ENOMEM; + + *outSize = compressBound( (uLongf)inSize ); + *outData = (unsigned char *)malloc( *outSize ); + + if (*outData != NULL) err = compress( *outData, outSize, inData, inSize ); + + if (err != 0) + { + fprintf( stderr, "Failed to %s data, Line:%d\n", (*outData ? "compress" : "allocate"), __LINE__); + + if (*outData) free( *outData ); + + *outData = (unsigned char *)inData; + *outSize = inSize; + } +#else + *outData = (unsigned char *)inData; + *outSize = inSize; +#endif +} + +// MARK: - PDF Stuff - +static long 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 ) +{ + unsigned char *data = NULL; + size_t size; + long objectOffset = 0; + + compressImageData( rasterData, rasterDataSize, &data, &size ); + + fprintf(pdfFile, "\n%u 0 obj\n", imageReference ); + objectOffset = ftell(pdfFile); + fprintf(pdfFile, "<< /Type /XObject\n" + " /Subtype /Image\n" + " /Width %u\n" + " /Height %u\n" + " /Interpolate %s\n" + " /ColorSpace %s\n" + " /BitsPerComponent %d\n" + " /Length %zu\n", width, height, (interpolate ? "true" : "false"), colorspace, bitsPerComponent, rasterDataSize ); + + if (rasterData != data) + fprintf(pdfFile, " /Filter /FlateDecode\n"); + + fprintf(pdfFile, ">>\nstream\n" ); + fwrite( data, size, 1, pdfFile ); + fprintf(pdfFile, "\nendstream" + "\nendobj\n"); + + // free the data the was allocated in compressImageData + if (rasterData != data) free( data ); + + return objectOffset; +} + +static long writePageStream(FILE *pdfFile, + unsigned int streamReference, + int width, + int height, + int pageNumber) +{ + long objectOffset = 0; + char imageStream[64]; + + snprintf( imageStream, sizeof( imageStream ), "q %d 0 0 %d 0 0 cm /Im%u Do Q", width, height, pageNumber ); + + fprintf(pdfFile, "\n%u 0 obj\n", streamReference ); + objectOffset = ftell(pdfFile); + fprintf(pdfFile, "<< /Length %zu >>\n" + "stream\n" + "%s" + "\nendstream" + "\nendobj\n", strlen(imageStream), imageStream ); + + return objectOffset; +} + +static long writePageObject(FILE *pdfFile, + unsigned int pageReference, + unsigned int resouceReference, + unsigned int contentReference, + int width, + int height) +{ + long objectOffset = 0; + + fprintf(pdfFile, "\n%u 0 obj\n", pageReference ); + objectOffset = ftell(pdfFile); + fprintf(pdfFile, "<< /Type /Page\n" + " /Parent 2 0 R\n" + " /Resources %u 0 R\n" + " /Contents %u 0 R\n" + " /MediaBox [0 0 %d %d]\n" + ">>\nendobj\n", resouceReference, contentReference, width, height ); + + return objectOffset; +} + +static long writeResourceObject(FILE *pdfFile, + unsigned int rsrcReference, + unsigned int contentReference, + unsigned int page ) +{ + long objectOffset = 0; + + fprintf(pdfFile, "\n%u 0 obj\n", rsrcReference ); + objectOffset = ftell(pdfFile); + fprintf(pdfFile, "<< /ProcSet [ /PDF /ImageB /ImageC /ImageI ] /XObject << /Im%u %u 0 R >> >>\nendobj\n", page, contentReference ); + + return objectOffset; +} + +static long writePagesObject( FILE *pdfFile, std::vector pages ) +{ + long objectOffset = 0; + + fprintf(pdfFile, "\n2 0 obj\n"); + objectOffset = ftell(pdfFile); + fprintf(pdfFile, "<< /Type /Pages /Count %lu /Kids [", (unsigned long)pages.size()); + for (unsigned int i : pages ) + { + fprintf( pdfFile, " %d 0 R", i ); + } + fprintf(pdfFile, " ] >>\nendobj\n"); + + return objectOffset; +} + +static long writeCatalogObject( FILE *pdfFile, unsigned int objectReference ) +{ + long objectOffset = 0; + + fprintf(pdfFile, "\n%u 0 obj\n", objectReference); + objectOffset = ftell(pdfFile); + fprintf(pdfFile, "<< /Type /Catalog /Pages 2 0 R >>\n"); + fprintf(pdfFile, "endobj\n"); + + return objectOffset; +} + +static void writeTrailerObject(FILE *pdfFile, + unsigned int catalogReference, + unsigned long numObjects, + long startXOffset) +{ + fprintf( pdfFile, "trailer\n" + "<< /Root %u 0 R\n" + " /Size %lu >>\n" + "startxref\n" + "%ld\n" + "%%%%EOF\n", catalogReference, numObjects, startXOffset); +} + +static long writeXRefTable( FILE *pdfFile, std::vector offsets, long startOffset ) +{ + long objectOffset = ftell(pdfFile); + fprintf( pdfFile, "xref\n" + "0 %lu\n" + "0000000000 65535 f\n", (unsigned long)(offsets.size() + 1) ); + for (long offset : offsets ) + { + fprintf( pdfFile, "%010ld 00000 n\n", offset - startOffset ); + } + return objectOffset; +} + +static long writeHeader( FILE *pdfFile ) +{ + fprintf(pdfFile, "%%PDF-1.3\n"); + + return ftell(pdfFile); +} + +// MARK: - Work - +static int convertCUPSRasterToPDF( int rasterIn ) +{ + #define kInitialImageReferenceID 10 + int err = 0; + int pages = 0; + unsigned int objectReference = kInitialImageReferenceID; + unsigned int catalogReference = objectReference++; + + long startOffset; + long offset; + + float width = 0; + float height = 0; + + size_t largestAllocatedMemory = 0; + unsigned char *rasterData = NULL; + + std::vector pageReferences; + std::vector objectOffsets; + cups_raster_t *rasterFile = NULL; + cups_page_header2_t pageHeader; + + FILE *pdfFile = stdout; + + rasterFile = cupsRasterOpen(rasterIn, CUPS_RASTER_READ); + if (rasterFile == NULL) + { + err = errno; + fprintf(stderr, "ERROR: Error reading raster data.\n"); + perror("DEBUG: cupsRasterOpen failed to open the file"); + goto bail; + } + + startOffset = writeHeader( pdfFile ); + while ( !Canceled && cupsRasterReadHeader2(rasterFile, &pageHeader) ) + { + char colorspace[256]; + int bitsPerComponent = 8; + + fprintf(stderr, "PAGE: %d %d\n", pages+1, pageHeader.NumCopies); + fprintf(stderr, "DEBUG:%04d] pageHeader.colorSpace=%u, .bitsPerPixel=%u, .duplexMode=%u\n", + pages, pageHeader.cupsColorSpace, pageHeader.cupsBitsPerPixel, pageHeader.Duplex); + fprintf(stderr, "DEBUG: pageHeader.width=%u, .height=%u, .resolution=%u x %u\n", + pageHeader.cupsWidth, pageHeader.cupsHeight, pageHeader.HWResolution[0], pageHeader.HWResolution[1]); + + int status = rasterToPDFColorSpace( pageHeader.cupsColorSpace, pageHeader.cupsBitsPerPixel, &bitsPerComponent, colorspace, sizeof(colorspace) ); + if (status) + { + fprintf( stderr, "INFO: Unable to determine a colorspace. skipping this page.\n" ); + continue; + } + + size_t imageSize = pageHeader.cupsHeight * pageHeader.cupsBytesPerLine; + if (imageSize > largestAllocatedMemory) + { + rasterData = (unsigned char *)(rasterData == NULL ? malloc(imageSize) : realloc(rasterData, imageSize)); + largestAllocatedMemory = imageSize; + } + + if (rasterData == NULL) + { + fprintf(stderr, "ERROR: Unable to allocate memory for page info\n"); + err = -1; + break; + } + + size_t result = (size_t) cupsRasterReadPixels(rasterFile, rasterData, (unsigned int)imageSize); + if (result != imageSize) + { + err = -2; + fprintf(stderr, "ERROR: Unable to read print data.\n"); + fprintf(stderr, "DEBUG: cupsRasterReadPixels faild on page:%d (%zu of %zu bytes read)\n", pages+1, result, imageSize ); + break; + } + + width = 72.0 * pageHeader.cupsWidth / pageHeader.HWResolution[1]; + height = 72.0 * pageHeader.cupsHeight / pageHeader.HWResolution[0]; + + unsigned int pageReference = objectReference++; + unsigned int rsrcReference = objectReference++; + unsigned int streamReference = objectReference++; + unsigned int imageReference = objectReference++; + int interpolate = 0; + + offset = writePageStream(pdfFile, streamReference, width, height, pages+1 ); + objectOffsets.push_back( offset ); + + offset = writePageObject(pdfFile, + pageReference, + rsrcReference, + streamReference, + width, + height); + objectOffsets.push_back( offset ); + + offset = writeResourceObject(pdfFile, rsrcReference, imageReference, pages+1 ); + objectOffsets.push_back( offset ); + + offset = writeImageObject(pdfFile, + imageReference, + pageHeader.cupsWidth, + pageHeader.cupsHeight, + interpolate, + bitsPerComponent, + colorspace, + rasterData, + imageSize); + objectOffsets.push_back( offset ); + + pageReferences.push_back( pageReference ); + pages++; + } + + offset = writePagesObject( pdfFile, pageReferences ); + objectOffsets.push_back( offset ); + + offset = writeCatalogObject( pdfFile, catalogReference ); + objectOffsets.push_back( offset ); + + offset = writeXRefTable( pdfFile, objectOffsets, startOffset ); + + writeTrailerObject( pdfFile, catalogReference, + objectOffsets.size() + 1, offset - startOffset ); +bail: + if ( pdfFile != NULL ) fclose( pdfFile ); + if ( rasterFile != NULL ) cupsRasterClose(rasterFile); + if ( rasterIn != -1 ) close( rasterIn ); + if ( rasterData ) free( rasterData ); + + return err; +} + +static void sigterm_handler(int sig) +{ + (void)sig; + + Canceled = 1; +} + +static void installSignalHandler( void ) +{ +#ifdef HAVE_SIGSET + sigset(SIGTERM, sigterm_handler); +#elif defined(HAVE_SIGACTION) + struct sigaction action; /* Actions for POSIX signals */ + memset(&action, 0, sizeof(action)); + + sigemptyset(&action.sa_mask); + sigaddset(&action.sa_mask, SIGTERM); + + action.sa_handler = sigterm_handler; + sigaction(SIGTERM, &action, NULL); +#else + signal(SIGTERM, sigterm_handler); +#endif /* HAVE_SIGSET */ +} + +// MARK: - +int main(int argc, const char * argv[]) +{ + int err = 0; + + /* + * Make sure status messages are not buffered... + */ + setbuf(stderr, NULL); + + /* + * Check the command-line... + */ + if (argc < 6 || argc > 7) + { + fprintf(stderr, "Usage: %s job-id user title copies options [file]\n", + argv[0]); + return (CUPS_BACKEND_FAILED); + } + + /* + * Register a signal handler to eject the current page if the + * job is cancelled. + */ + installSignalHandler(); + + int fd = fileno(stdin); + if (argc == 7) + { + if ((fd = open(argv[6], O_RDONLY)) < 0) + { + perror("ERROR: Unable to open file"); + return (1); + } + } + + err = convertCUPSRasterToPDF( fd ); + return err; +} -- cgit v1.2.1