diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f0b947d1a..7ca2e0e53 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -24,21 +24,16 @@ jobs: sudo apt-get install -y avahi-daemon libavahi-client-dev libssl-dev libpam-dev libusb-1.0-0-dev zlib1g-dev sudo apt install autotools-dev autopoint cmake libtool pkg-config libcups2-dev libexif-dev liblcms2-dev libfontconfig1-dev sudo apt install libfreetype6-dev build-essential qtbase5-dev qtchooser libcairo2-dev libboost-system-dev libboost-thread-dev libboost-program-options-dev libboost-test-dev libopenjp2-7-dev liblcms2-dev libjpeg-dev - - name: Install libqpdf > 11.0.0 + - name: Install pdfio >= 1.4.0 run: | cd .. - mkdir qpdf - wget -O qpdf-11.6.3.tar.gz https://sourceforge.net/projects/qpdf/files/qpdf/11.6.3/qpdf-11.6.3.tar.gz - tar -xzf qpdf-11.6.3.tar.gz - cd qpdf-11.6.3 - mkdir build && - cd build && - cmake -DCMAKE_INSTALL_PREFIX=/usr \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_STATIC_LIBS=OFF \ - -DCMAKE_INSTALL_DOCDIR=/usr/share/doc/qpdf-11.6.3 \ - .. && - make + mkdir pdfio + wget https://github.com/michaelrsweet/pdfio/releases/download/v1.4.0/pdfio-1.4.0.tar.gz + tar -xzf pdfio-1.4.0.tar.gz + cd pdfio-1.4.0 + ./configure --prefix=/usr --enable-shared + make all + make test sudo make install cd .. cd .. @@ -57,4 +52,4 @@ jobs: - name: make run: make - name: Run Tests - run: make check || cat test/error_log* \ No newline at end of file + run: make check || cat test/error_log* diff --git a/Makefile.am b/Makefile.am index ddebb5be1..e1c22ea6d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -178,32 +178,31 @@ libcupsfilters_la_SOURCES = \ cupsfilters/lut.c \ cupsfilters/mupdftopwg.c \ cupsfilters/pack.c \ - cupsfilters/pclmtoraster.cxx \ - cupsfilters/pdf.cxx \ - cupsfilters/pdftopdf/pdftopdf.cxx \ + cupsfilters/pclmtoraster.c \ + cupsfilters/pdf.c \ + cupsfilters/pdftopdf/pdftopdf.c \ cupsfilters/pdftopdf/pdftopdf-private.h \ - cupsfilters/pdftopdf/pdftopdf-processor.cxx \ - cupsfilters/pdftopdf/pdftopdf-processor-private.h \ - cupsfilters/pdftopdf/qpdf-pdftopdf-processor.cxx \ - cupsfilters/pdftopdf/qpdf-pdftopdf-processor-private.h \ - cupsfilters/pdftopdf/pptypes.cxx \ - cupsfilters/pdftopdf/pptypes-private.h \ - cupsfilters/pdftopdf/nup.cxx \ - cupsfilters/pdftopdf/nup-private.h \ - cupsfilters/pdftopdf/intervalset.cxx \ - cupsfilters/pdftopdf/intervalset-private.h \ - cupsfilters/pdftopdf/qpdf-tools.cxx \ - cupsfilters/pdftopdf/qpdf-tools-private.h \ - cupsfilters/pdftopdf/qpdf-xobject.cxx \ - cupsfilters/pdftopdf/qpdf-xobject-private.h \ - cupsfilters/pdftopdf/qpdf-pdftopdf.cxx \ - cupsfilters/pdftopdf/qpdf-pdftopdf-private.h \ - cupsfilters/pdftopdf/qpdf-cm.cxx \ - cupsfilters/pdftopdf/qpdf-cm-private.h \ + cupsfilters/pdftopdf/pdftopdf-processor.c \ + cupsfilters/pdftopdf/pdfio-pdftopdf-processor.c \ + cupsfilters/pdftopdf/processor.h \ + cupsfilters/pdftopdf/pptypes.c \ + cupsfilters/pdftopdf/pptypes-private.h \ + cupsfilters/pdftopdf/nup.c \ + cupsfilters/pdftopdf/nup-private.h \ + cupsfilters/pdftopdf/intervalset.c \ + cupsfilters/pdftopdf/intervalset-private.h \ + cupsfilters/pdftopdf/pdfio-tools.c \ + cupsfilters/pdftopdf/pdfio-tools-private.h \ + cupsfilters/pdftopdf/pdfio-xobject.c \ + cupsfilters/pdftopdf/pdfio-xobject-private.h \ + cupsfilters/pdftopdf/pdfio-pdftopdf.c \ + cupsfilters/pdftopdf/pdfio-pdftopdf-private.h \ + cupsfilters/pdftopdf/pdfio-cm.c \ + cupsfilters/pdftopdf/pdfio-cm-private.h \ cupsfilters/pdftoraster.cxx \ cupsfilters/pdfutils.c \ cupsfilters/pdfutils-private.h \ - cupsfilters/pwgtopdf.cxx \ + cupsfilters/pwgtopdf.c \ cupsfilters/pwgtoraster.c \ cupsfilters/raster.c \ cupsfilters/rastertopwg.c \ @@ -217,7 +216,7 @@ libcupsfilters_la_LIBADD = \ $(FONTCONFIG_LIBS) \ $(CUPS_LIBS) \ $(LCMS_LIBS) \ - $(LIBQPDF_LIBS) \ + $(LIBPDFIO_LIBS) \ $(LIBJPEG_LIBS) \ $(EXIF_LIBS) \ $(LIBPNG_LIBS) \ @@ -232,7 +231,7 @@ libcupsfilters_la_CFLAGS = \ $(FONTCONFIG_CFLAGS) \ $(CUPS_CFLAGS) \ $(LCMS_CFLAGS) \ - $(LIBQPDF_CFLAGS) \ + $(LIBPDFIO_CFLAGS) \ $(LIBJPEG_CFLAGS) \ $(EXIF_CFLAGS) \ $(LIBPNG_CFLAGS) \ @@ -320,11 +319,10 @@ test_ps_LDADD = libcupsfilters.la testfilters_SOURCES = \ cupsfilters/testfilters.c \ $(pkgfiltersinclude_DATA) - testfilters_LDADD = \ libcupsfilters.la \ - -lm -ldl -lcups - + $(CUPS_LIBS) \ + -lm -ldl testfilters_LDFLAGS = \ -D_GNU_SOURCE \ -L/usr/lib diff --git a/configure.ac b/configure.ac index cfbb2d7ed..198d53c73 100644 --- a/configure.ac +++ b/configure.ac @@ -291,7 +291,7 @@ AS_IF([test x"$lcms2" = "xno"], [ AC_DEFINE([USE_LCMS1], [1], [Defines if use lcms1]) ]) PKG_CHECK_MODULES([FONTCONFIG], [fontconfig >= 2.0.0]) -PKG_CHECK_MODULES([LIBQPDF], [libqpdf >= 11.0.0]) +PKG_CHECK_MODULES([LIBPDFIO], [pdfio >= 1.3.0]) # ================= # Check for Poppler diff --git a/cupsfilters/ghostscript.c b/cupsfilters/ghostscript.c index 18e016386..5d17d768d 100644 --- a/cupsfilters/ghostscript.c +++ b/cupsfilters/ghostscript.c @@ -997,7 +997,7 @@ cfFilterGhostscript(int inputfd, // I - File descriptor input if (doc_type == GS_DOC_TYPE_PDF) { - int pages = cfPDFPagesFP(fp); + int pages = cfPDFPagesFP(filename); if (pages == 0) { diff --git a/cupsfilters/pclmtoraster.cxx b/cupsfilters/pclmtoraster.c similarity index 80% rename from cupsfilters/pclmtoraster.cxx rename to cupsfilters/pclmtoraster.c index a04913712..d776d4067 100644 --- a/cupsfilters/pclmtoraster.cxx +++ b/cupsfilters/pclmtoraster.c @@ -2,6 +2,7 @@ // PCLm/Raster-only PDF to Raster filter function for libcupsfilters. // // Copyright © 2020 by Vikrant Malik +// Copyright 2024 Uddhav Phatak // // Licensed under Apache License v2.0. See the file "LICENSE" for more // information. @@ -20,48 +21,62 @@ #include #include #include -#include -#include +#include +#include -#define MAX_BYTES_PER_PIXEL 32 +#define MAX_BYTES_PER_PIXEL 32 -typedef struct pclmtoraster_data_s -{ - cf_filter_out_format_t outformat = CF_FILTER_OUT_FORMAT_PWG_RASTER; - int numcolors = 0; - int rowsize = 0; +typedef struct pclmtoraster_data_s { + int outformat; + int numcolors; + int rowsize; cups_page_header_t header; char pageSizeRequested[64]; - int bi_level = 0; + int bi_level; // image swapping - bool swap_image_x = false; - bool swap_image_y = false; - // margin swapping - bool swap_margin_x = false; - bool swap_margin_y = false; + int swap_image_x; + int swap_image_y; + int swap_margin_x; + int swap_margin_y; unsigned int nplanes; unsigned int nbands; - unsigned int bytesPerLine; // number of bytes per line + unsigned int bytesPerLine; + char colorspace[32]; // Use fixed-size string +} pclmtoraster_data_t; + +void +init_pclmtoraster_data_t(pclmtoraster_data_t *data) +{ + data->outformat = CF_FILTER_OUT_FORMAT_PWG_RASTER; + data->numcolors = 0; + data->rowsize = 0; + data->bi_level = 0; + // image swapping + data->swap_image_x = false; + data->swap_image_y = false; + // margin swapping + data->swap_margin_x = false; + data->swap_margin_y = false; // Note: When CUPS_ORDER_BANDED, // cupsBytesPerLine = bytesPerLine * cupsNumColors - std::string colorspace; // Colorspace of raster data -} pclmtoraster_data_t; + strncpy(data->colorspace, "\0", sizeof(data->colorspace)); +} typedef unsigned char *(*convert_cspace_func)(unsigned char *src, - unsigned char *dst, - unsigned int row, - unsigned int pixels, - pclmtoraster_data_t *data); + unsigned char *dst, + unsigned int row, + unsigned int pixels, + pclmtoraster_data_t *data); typedef unsigned char *(*convert_line_func)(unsigned char *src, - unsigned char *dst, - unsigned char *buf, - unsigned int row, - unsigned int plane, - pclmtoraster_data_t *data, - convert_cspace_func convertcspace); + unsigned char *dst, + unsigned char *buf, + unsigned int row, + unsigned int plane, + pclmtoraster_data_t *data, + convert_cspace_func convertcspace); typedef struct pclm_conversion_function_s { @@ -69,7 +84,6 @@ typedef struct pclm_conversion_function_s convert_line_func convertline; // Function tom modify raster data of a line } pclm_conversion_function_t; - static int parse_opts(cf_filter_data_t *data, cf_filter_out_format_t outformat, @@ -84,7 +98,6 @@ parse_opts(cf_filter_data_t *data, cups_page_header_t *header = &(pclmtoraster_data->header); cups_cspace_t cspace = (cups_cspace_t)(-1); - pclmtoraster_data->outformat = outformat; // @@ -104,7 +117,7 @@ parse_opts(cf_filter_data_t *data, cfRasterPrepareHeader(header, data, outformat, outformat, 0, &cspace); - if (header->Duplex) + if(header->Duplex) { int backside; // analyze options relevant to Duplex @@ -115,7 +128,7 @@ parse_opts(cf_filter_data_t *data, FM_FALSE, FM_TRUE } flippedMargin = FM_NO; - + backside = cfGetBackSideOrientation(data); if (backside >= 0) @@ -125,71 +138,71 @@ parse_opts(cf_filter_data_t *data, FM_NO)); backside &= 7; - if (backside == CF_BACKSIDE_MANUAL_TUMBLE && header->Tumble) + if(backside == CF_BACKSIDE_MANUAL_TUMBLE && header->Tumble) { - pclmtoraster_data->swap_image_x = pclmtoraster_data->swap_image_y = - true; - pclmtoraster_data->swap_margin_x = pclmtoraster_data->swap_margin_y = - true; - if (flippedMargin == FM_TRUE) + pclmtoraster_data->swap_image_x = true; + pclmtoraster_data->swap_image_y = true; + pclmtoraster_data->swap_margin_x = true; + pclmtoraster_data->swap_margin_y = true; + if (flippedMargin == FM_TRUE) pclmtoraster_data->swap_margin_y = false; } - else if (backside == CF_BACKSIDE_ROTATED && !header->Tumble) + else if(backside == CF_BACKSIDE_ROTATED && !header->Tumble) { - pclmtoraster_data->swap_image_x = pclmtoraster_data->swap_image_y = - true; - pclmtoraster_data->swap_margin_x = pclmtoraster_data->swap_margin_y = - true; - if (flippedMargin == FM_TRUE) - pclmtoraster_data->swap_margin_y = false; + pclmtoraster_data->swap_image_x = true; + pclmtoraster_data->swap_image_y = true; + pclmtoraster_data->swap_margin_x = true; + pclmtoraster_data->swap_margin_y = true; + if (flippedMargin == FM_TRUE) + pclmtoraster_data->swap_margin_y = false; } else if (backside == CF_BACKSIDE_FLIPPED) { - if (header->Tumble) - { + if (header->Tumble) + { pclmtoraster_data->swap_image_x = true; - pclmtoraster_data->swap_margin_x = - pclmtoraster_data->swap_margin_y = true; - } - else + pclmtoraster_data->swap_margin_x = true; + pclmtoraster_data->swap_margin_y = true; + } + else pclmtoraster_data->swap_image_y = true; + if (flippedMargin == FM_FALSE) - pclmtoraster_data->swap_margin_y = - !(pclmtoraster_data->swap_margin_y); + pclmtoraster_data->swap_margin_y = !pclmtoraster_data->swap_margin_y; } } } if ((val = cupsGetOption("print-color-mode", num_options, options)) != NULL && - !strncasecmp(val, "bi-level", 8)) + !strncasecmp(val, "bi-level", 8)) pclmtoraster_data->bi_level = 1; strncpy(pclmtoraster_data->pageSizeRequested, header->cupsPageSizeName, 63); pclmtoraster_data->pageSizeRequested[63] = '\0'; if (log) log(ld, CF_LOGLEVEL_DEBUG, - "cfFilterPCLmToRaster: Page size requested: %s.", + "cfFilterPCLmToRaster: Page size requested: %s.", header->cupsPageSizeName); - return(0); + return 0; } - static bool -media_box_lookup(QPDFObjectHandle object, - float rect[4]) +media_box_lookup(pdfio_obj_t *object, + float rect[4]) { - // preliminary checks - if (!object.isDictionary() || !object.hasKey("/MediaBox")) - return (false); + pdfio_rect_t mediaBox; + pdfio_dict_t *object_dict = pdfioObjGetDict(object); + if(pdfioDictGetRect(object_dict, "MediaBox", &mediaBox)) + return false; - // assign mediabox values to rect - std::vector mediabox = - object.getKey("/MediaBox").getArrayAsVector(); - for (int i = 0; i < 4; ++i) - rect[i] = mediabox[i].getNumericValue(); + pdfioDictGetRect(object_dict, "MediaBox", &mediaBox); - return (mediabox.size() == 4); -} + rect[0] = mediaBox.x1; + rect[1] = mediaBox.y1; + rect[2] = mediaBox.x2; + rect[3] = mediaBox.y2; + return true; +} // // 'rotate_bitmap()' - Function to rotate a bitmap @@ -203,7 +216,7 @@ rotate_bitmap(unsigned char *src, // I - Input string unsigned int height, // I - Height of raster image in pixels unsigned int width, // I - Width of raster image in pixels int rowsize, // I - Length of one row of pixels - std::string colorspace,// I - Colorspace of input bitmap + char* colorspace,// I - Colorspace of input bitmap cf_logfunc_t log, // I - Log function void *ld) // I - Aux. data for log function { @@ -211,51 +224,51 @@ rotate_bitmap(unsigned char *src, // I - Input string unsigned char *dp = dst; unsigned char *temp = dst; - if (rotate == 0) + if(rotate == 0) return (src); - else if (rotate == 180) + else if(rotate == 180) { - if (colorspace == "/DeviceGray") + if (strcmp(colorspace, "/DeviceGray") == 0) { bp = src + height * rowsize - 1; dp = dst; - for (unsigned int h = 0; h < height; h ++) - for (unsigned int w = 0; w < width; w ++, bp --, dp ++) - *dp = *bp; - } - else if (colorspace == "/DeviceCMYK") + for (unsigned int h = 0; h < height; h++) + for (unsigned int w = 0; w < width; w++, bp--, dp++) + *dp = *bp; + } + else if (strcmp(colorspace, "/DeviceCMYK") == 0) { bp = src + height * rowsize - 4; dp = dst; for (unsigned int h = 0; h < height; h ++) { - for (unsigned int w = 0; w < width; w ++, bp -= 4, dp += 4) + for (unsigned int w = 0; w < width; w ++, bp -= 4, dp += 4) { - dp[0] = bp[0]; - dp[1] = bp[1]; - dp[2] = bp[2]; - dp[3] = bp[3]; - } + dp[0] = bp[0]; + dp[1] = bp[1]; + dp[2] = bp[2]; + dp[3] = bp[3]; + } } } - else if (colorspace == "/DeviceRGB") + else if (strcmp(colorspace, "/DeviceRGB") == 0) { bp = src + height * rowsize - 3; dp = dst; for (unsigned int h = 0; h < height; h ++) { - for (unsigned int w = 0; w < width; w ++, bp -= 3, dp += 3) + for (unsigned int w = 0; w < width; w ++, bp -= 3, dp += 3) { - dp[0] = bp[0]; - dp[1] = bp[1]; - dp[2] = bp[2]; - } + dp[0] = bp[0]; + dp[1] = bp[1]; + dp[2] = bp[2]; + } } } } else if (rotate == 270) { - if (colorspace == "/DeviceGray") + if (strcmp(colorspace, "/DeviceGray") == 0) { bp = src; dp = dst; @@ -266,7 +279,7 @@ rotate_bitmap(unsigned char *src, // I - Input string *dp = *bp; } } - else if (colorspace == "/DeviceCMYK") + else if (strcmp(colorspace, "/DeviceCMYK") == 0) { for (unsigned int h = 0; h < height; h++) { @@ -280,7 +293,7 @@ rotate_bitmap(unsigned char *src, // I - Input string } } } - else if (colorspace == "/DeviceRGB") + else if (strcmp(colorspace, "/DeviceRGB") == 0) { bp = src; dp = dst; @@ -298,7 +311,7 @@ rotate_bitmap(unsigned char *src, // I - Input string } else if (rotate == 90) { - if (colorspace == "/DeviceGray") + if (strcmp(colorspace, "/DeviceGray") == 0) { for (unsigned int h = 0; h < height; h ++) { @@ -307,7 +320,7 @@ rotate_bitmap(unsigned char *src, // I - Input string *dp = *bp; } } - else if (colorspace == "/DeviceCMYK") + else if (strcmp(colorspace, "/DeviceCMYK") == 0) { for (unsigned int h = 0; h < height; h ++) { @@ -321,7 +334,7 @@ rotate_bitmap(unsigned char *src, // I - Input string } } } - else if (colorspace == "/DeviceRGB") + else if (strcmp(colorspace, "/DeviceRGB") == 0) { for (unsigned int h = 0; h < height; h ++) { @@ -341,12 +354,11 @@ rotate_bitmap(unsigned char *src, // I - Input string "cfFilterPCLmToRaster: Incorrect Rotate Value %d, not rotating", rotate); return (src); - } + } return (temp); } - static unsigned char * rgb_to_rgbw_line(unsigned char *src, unsigned char *dst, @@ -360,7 +372,6 @@ rgb_to_rgbw_line(unsigned char *src, return (dst); } - static unsigned char * rgb_to_cmyk_line(unsigned char *src, unsigned char *dst, @@ -643,7 +654,7 @@ convert_reverse_line(unsigned char *src, // I - Input line // is not required, or if dithering is required, for faster processing of // raster output. // - + unsigned int pixels = data->header.cupsWidth; if (data->header.cupsBitsPerColor == 1 && data->header.cupsNumColors == 1) { @@ -692,18 +703,18 @@ select_convert_func(int pgno, // I - Page number { // Set rowsize and numcolors based on colorspace of raster data cups_page_header_t header = data->header; - std::string colorspace = data->colorspace; - if (colorspace == "/DeviceRGB") + char *colorspace = data->colorspace; + if (strcmp(colorspace, "/DeviceRGB") == 0) { data->rowsize = header.cupsWidth * 3; data->numcolors = 3; } - else if (colorspace == "/DeviceCMYK") + else if (strcmp(colorspace, "/DeviceCMYK") == 0) { data->rowsize = header.cupsWidth * 4; data->numcolors = 4; } - else if (colorspace == "/DeviceGray") + else if (strcmp(colorspace, "/DeviceGray") == 0) { data->rowsize = header.cupsWidth; data->numcolors = 1; @@ -713,8 +724,8 @@ select_convert_func(int pgno, // I - Page number if (log) log(ld, CF_LOGLEVEL_ERROR, "cfFilterPCLmToRaster: Colorspace %s not supported, " "defaulting to /deviceRGB", - colorspace.c_str()); - data->colorspace = "/DeviceRGB"; + colorspace); + strcpy(data->colorspace, "/DeviceRGB"); data->rowsize = header.cupsWidth * 3; data->numcolors = 3; } @@ -724,49 +735,49 @@ select_convert_func(int pgno, // I - Page number switch (header.cupsColorSpace) { case CUPS_CSPACE_K: - if (colorspace == "/DeviceRGB") + if (strcmp(colorspace, "/DeviceRGB") == 0) convert->convertcspace = rgb_to_black_line; - else if (colorspace == "/DeviceCMYK") + else if (strcmp(colorspace, "/DeviceCMYK") == 0) convert->convertcspace = cmyk_to_black_line; - else if (colorspace == "/DeviceGray") + else if (strcmp(colorspace, "/DeviceGray") == 0) convert->convertcspace = gray_to_black_line; break; case CUPS_CSPACE_W: case CUPS_CSPACE_SW: - if (colorspace == "/DeviceRGB") + if (strcmp(colorspace, "/DeviceRGB") == 0) convert->convertcspace = rgb_to_white_line; - else if (colorspace == "/DeviceCMYK") + else if (strcmp(colorspace, "/DeviceCMYK") == 0) convert->convertcspace = cmyk_to_white_line; break; case CUPS_CSPACE_CMY: - if (colorspace == "/DeviceRGB") + if (strcmp(colorspace, "/DeviceRGB") == 0) convert->convertcspace = rgb_to_cmy_line; - else if (colorspace == "/DeviceCMYK") + else if (strcmp(colorspace, "/DeviceCMYK") == 0) convert->convertcspace = cmyk_to_cmy_line; - else if (colorspace == "/DeviceGray") + else if (strcmp(colorspace, "/DeviceGray") == 0) convert->convertcspace = gray_to_cmy_line; break; case CUPS_CSPACE_CMYK: - if (colorspace == "/DeviceRGB") + if (strcmp(colorspace, "/DeviceRGB") == 0) convert->convertcspace = rgb_to_cmyk_line; - else if (colorspace == "/DeviceGray") + else if (strcmp(colorspace, "/DeviceGray") == 0) convert->convertcspace = gray_to_cmyk_line; break; case CUPS_CSPACE_RGBW: - if (colorspace == "/DeviceRGB") + if (strcmp(colorspace, "/DeviceRGB") == 0) convert->convertcspace = rgb_to_rgbw_line; - else if (colorspace == "/DeviceCMYK") + else if (strcmp(colorspace, "/DeviceCMYK") == 0) convert->convertcspace = cmyk_to_rgbw_line; - else if (colorspace == "/DeviceGray") + else if (strcmp(colorspace, "/DeviceGray") == 0) convert->convertcspace = gray_to_rgbw_line; break; case CUPS_CSPACE_RGB: case CUPS_CSPACE_ADOBERGB: case CUPS_CSPACE_SRGB: default: - if (colorspace == "/DeviceCMYK") + if (strcmp(colorspace, "/DeviceCMYK") == 0) convert->convertcspace = cmyk_to_rgb_line; - else if (colorspace == "/DeviceGray") + else if (strcmp(colorspace, "/DeviceGray") == 0) convert->convertcspace = gray_to_rgb_line; break; } @@ -778,6 +789,43 @@ select_convert_func(int pgno, // I - Page number convert->convertline = convert_line; } +bool +process_image(pdfio_dict_t *dict, const char *key, pclmtoraster_data_t *data, int pixel_count, unsigned char *bitmap) +{ + char *buffer; + pdfio_obj_t *image = pdfioDictGetObj(dict, key); + + //... verify the object has type "Image", then do something with the image object ... + + if (strcmp(pdfioObjGetType(image), "image") == 0) + { + pdfio_dict_t *imgdict = pdfioObjGetDict(image); + if (!imgdict) + return true; + + pdfio_stream_t *img_str = pdfioObjOpenStream(image, true); + size_t bufsize = pdfioStreamRead(img_str, buffer, 1024); + + int width = pdfioDictGetNumber(imgdict, "Width"); + int height = pdfioDictGetNumber(imgdict, "Height"); + + data->header.cupsHeight += height; + + if (pixel_count == 0) + bitmap = (unsigned char *)malloc(bufsize); + + else + bitmap = (unsigned char *)realloc(bitmap, pixel_count + bufsize); + + memcpy(bitmap + pixel_count, buffer, bufsize); + pixel_count += bufsize; + + if (width > data->header.cupsWidth) + data->header.cupsWidth = width; + } + + return (true); +} // // 'out_page()' - Function to convert a single page of raster-only PDF/PCLm @@ -786,7 +834,7 @@ select_convert_func(int pgno, // I - Page number static int // O - Exit status out_page(cups_raster_t* raster, // I - Raster stream - QPDFObjectHandle page, // I - QPDF Page Object + pdfio_obj_t* page, // I - PDFio Page Object int pgno, // I - Page number cf_logfunc_t log, // I - Log function void* ld, // I - Aux. data for log function @@ -795,26 +843,24 @@ out_page(cups_raster_t* raster, // I - Raster stream pclm_conversion_function_t *convert)// I - Conversion functions { int i; - long long rotate = 0, - height, - width; + long long rotate = 0; float paperdimensions[2], margins[4], l, swap; - int bufsize = 0, pixel_count = 0, - temp = 0; + int pixel_count = 0, temp = 0; float mediaBox[4]; unsigned char *bitmap = NULL, *colordata = NULL, *lineBuf = NULL, *line = NULL, *dp = NULL; - QPDFObjectHandle image; - QPDFObjectHandle imgdict; - QPDFObjectHandle colorspace_obj; + pdfio_obj_t *colorspace_obj; // Check if page is rotated. - if (page.getKey("/Rotate").isInteger()) - rotate = page.getKey("/Rotate").getIntValueAsInt(); + pdfio_dict_t *pageDict = pdfioObjGetDict(page); + if(pdfioDictGetNumber(pageDict, "Rotate")) + { + rotate = pdfioDictGetNumber(pageDict, "Rotate"); + } // Get pagesize by the mediabox key of the page. if (!media_box_lookup(page, mediaBox)) @@ -933,38 +979,21 @@ out_page(cups_raster_t* raster, // I - Raster stream data->header.cupsHeight = 0; // Loop over all raster images in a page and store them in bitmap. - std::map images = page.getPageImages(); - for (auto const& iter: images) - { - image = iter.second; - imgdict = image.getDict(); // XObject dictionary - - std::shared_ptr actual_data = image.getStreamData(qpdf_dl_all); - width = imgdict.getKey("/Width").getIntValue(); - height = imgdict.getKey("/Height").getIntValue(); - colorspace_obj = imgdict.getKey("/ColorSpace"); - data->header.cupsHeight += height; - bufsize = actual_data->getSize(); - - if (!pixel_count) - bitmap = (unsigned char *) malloc(bufsize); - else - bitmap = (unsigned char *) realloc(bitmap, pixel_count + bufsize); - memcpy(bitmap + pixel_count, actual_data->getBuffer(), bufsize); - pixel_count += bufsize; - - if (width > data->header.cupsWidth) - data->header.cupsWidth = width; - } + + pdfio_dict_t *resources = pdfioDictGetDict(pdfioObjGetDict(page), "Resources"); + pdfio_dict_t *xobjects = pdfioDictGetDict(resources, "XObject"); + + // Iterate over the XObject dictionary to find images + pdfioDictIterateKeys(xobjects, (pdfio_dict_cb_t)process_image, data); // Swap width and height in landscape images - if (rotate == 270 || rotate == 90) + if(rotate == 270 || rotate == 90) { temp = data->header.cupsHeight; data->header.cupsHeight = data->header.cupsWidth; data->header.cupsWidth = temp; } - + data->bytesPerLine = data->header.cupsBytesPerLine = (data->header.cupsBitsPerPixel * data->header.cupsWidth + 7) / 8; if (data->header.cupsColorOrder == CUPS_ORDER_BANDED) @@ -977,9 +1006,15 @@ out_page(cups_raster_t* raster, // I - Raster stream return (1); } - data->colorspace = (colorspace_obj.isName() ? - colorspace_obj.getName() : "/DeviceRGB"); - // Default for pclm files in DeviceRGB + colorspace_obj = pdfioDictGetObj(pdfioObjGetDict(page), "ColorSpace"); + + if (colorspace_obj) { + if (strcmp(pdfioObjGetType(colorspace_obj), "Name") == 0) + strncpy(data->colorspace, pdfioDictGetName(pdfioObjGetDict(colorspace_obj), "Type"), sizeof(data->colorspace) - 1); + else + strncpy(data->colorspace, "DeviceRGB", sizeof(data->colorspace) - 1); + } + // Select convertline and convertscpace function select_convert_func(pgno, log, ld, data, convert); @@ -1007,8 +1042,9 @@ out_page(cups_raster_t* raster, // I - Raster stream colordata = bitmap; // Write page image - lineBuf = new unsigned char [data->bytesPerLine]; - line = new unsigned char [data->bytesPerLine]; + lineBuf = (unsigned char *)malloc(data->bytesPerLine * sizeof(unsigned char)); + line = (unsigned char *)malloc(data->bytesPerLine * sizeof(unsigned char)); + if (data->header.Duplex && (pgno & 1) && data->swap_image_y) { for (unsigned int plane = 0; plane < data->nplanes; plane ++) @@ -1044,8 +1080,8 @@ out_page(cups_raster_t* raster, // I - Raster stream } } } - delete[] lineBuf; - delete[] line; + free(lineBuf); + free(line); free(bitmap); return (0); @@ -1074,7 +1110,7 @@ cfFilterPCLmToRaster(int inputfd, // I - File descriptor input stream char buffer[8192]; // Copy buffer int bytes; // Bytes copied int npages = 0; - QPDF *pdf = new QPDF(); + pdfio_file_t *pdf = (pdfio_file_t *)malloc(sizeof(pdfio_file_t *));; cups_raster_t *raster; pclmtoraster_data_t pclmtoraster_data; pclm_conversion_function_t convert; @@ -1135,11 +1171,11 @@ cfFilterPCLmToRaster(int inputfd, // I - File descriptor input stream close(fd); filename = tempfile; - pdf->processFile(filename); + pdf = pdfioFileOpen(filename, NULL, NULL, NULL, NULL); if (parse_opts(data, outformat, &pclmtoraster_data) != 0) { - delete(pdf); + free(pdf); unlink(tempfile); return (1); } @@ -1153,7 +1189,7 @@ cfFilterPCLmToRaster(int inputfd, // I - File descriptor input stream if(log) log(ld, CF_LOGLEVEL_ERROR, "cfFilterPCLmToRaster: Specified color format is not supported: %s", strerror(errno)); - delete(pdf); + free(pdf); unlink(tempfile); return (1); } @@ -1179,16 +1215,18 @@ cfFilterPCLmToRaster(int inputfd, // I - File descriptor input stream if(log) log(ld, CF_LOGLEVEL_ERROR, "cfFilterPCLmToRaster: Can't open raster stream: %s", strerror(errno)); - delete(pdf); + free(pdf); unlink(tempfile); return (1); } - std::vector pages = pdf->getAllPages(); - npages = pages.size(); + + npages = pdfioFileGetNumPages(pdf); for (int i = 0; i < npages; ++i) { + pdfio_obj_t *pages = pdfioFileGetPage(pdf, i); + if (iscanceled && iscanceled(icd)) { if (log) log(ld, CF_LOGLEVEL_DEBUG, @@ -1198,18 +1236,19 @@ cfFilterPCLmToRaster(int inputfd, // I - File descriptor input stream if (log) log(ld, CF_LOGLEVEL_INFO, "cfFilterPCLmToRaster: Starting page %d.", i + 1); - if (out_page(raster, pages[i], i, log, ld, &pclmtoraster_data,data, + if (out_page(raster, pages, i, log, ld, &pclmtoraster_data,data, + &convert) != 0) + break; + + if (log) log(ld, CF_LOGLEVEL_INFO, + "cfFilterPCLmToRaster: Starting page %d.", (i + 1)); + if (out_page(raster, pages, i, log, ld, &pclmtoraster_data,data, &convert) != 0) break; } cupsRasterClose(raster); - delete pdf; + free(pdf); unlink(tempfile); return (0); } - -void operator delete[](void *p) throw () -{ - free(p); -} diff --git a/cupsfilters/pdf.c b/cupsfilters/pdf.c new file mode 100644 index 000000000..dafe4ae05 --- /dev/null +++ b/cupsfilters/pdf.c @@ -0,0 +1,372 @@ +// +// Copyright 2012 Canonical Ltd. +// Copyright 2013 ALT Linux, Andrew V. Stepanov +// Copyright 2018 Sahil Arora +// Copyright 2024 Uddhav Phatak +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + + +#include +#include +#include +#include "pdf.h" + +#include +#include + +// +// 'make_real_box()' - Return a PDFio rect object of real values for a box. +// + +static pdfio_rect_t // O - PDFioObjectHandle for a rect +make_real_box(float values[4]) // I - Dimensions of the box in a float array +{ + pdfio_rect_t rect; + + rect.x1 = values[0]; + rect.y1 = values[1]; + rect.x2 = values[2]; + rect.y2 = values[3]; + + return rect; +} + +// +// 'cfPDFLoadTemplate()' - Load an existing PDF file using PDFio. +// + +cf_pdf_t* +cfPDFLoadTemplate(const char *filename) +{ + pdfio_file_t *pdf = pdfioFileOpen(filename, NULL, NULL, NULL, NULL); + + if (!pdf) + return NULL; + + if (pdfioFileGetNumPages(pdf) != 1) + { + pdfioFileClose(pdf); + return NULL; + } + + return (cf_pdf_t *)pdf; +} + +// +// 'cfPDFFree()' - Free pointer used by PDF object +// + +void +cfPDFFree(cf_pdf_t *pdf) +{ + if (pdf) + { + pdfioFileClose((pdfio_file_t *)pdf); + } +} + +// +// 'cfPDFPages()' - Count number of pages in file using PDFio. +// + +int +cfPDFPages(const char *filename) +{ + pdfio_file_t *pdf = pdfioFileOpen(filename, NULL, NULL, NULL, NULL); + + if (!pdf) + { + return -1; + } + + int pages = pdfioFileGetNumPages(pdf); + pdfioFileClose(pdf); + return pages; +} + +// +// 'cfPDFPagesFP()' - Count number of pages in file using PDFio. +// + +int +cfPDFPagesFP(char *file) +{ + pdfio_file_t *pdf = pdfioFileOpen(file, NULL, NULL, NULL, NULL); + + if (!pdf) + return -1; + + int pages = pdfioFileGetNumPages(pdf); + pdfioFileClose(pdf); + fprintf(stderr, "uddhav the number of pages are:%d", pages); + return pages; +} + +// +// 'cfPDFPrependStream()' - Prepend a stream to the contents of a specified +// page in PDF file. +// + +int +cfPDFPrependStream(cf_pdf_t *pdf, + unsigned page_num, + const char *buf, + size_t len) +{ + + if(pdfioFileGetNumPages((pdfio_file_t *)pdf)==0) + return 1; + + pdfio_obj_t *page = pdfioFileGetPage((pdfio_file_t *)pdf, page_num - 1); + pdfio_dict_t *pageDict = pdfioObjGetDict(page); + + pdfio_stream_t *existing_stream = pdfioPageOpenStream(page, 0, true); + if (!existing_stream) + return 1; + + pdfio_obj_t *new_stream_obj = pdfioFileCreateObj((pdfio_file_t *)pdf, pageDict); + if (!new_stream_obj) + { + pdfioStreamClose(existing_stream); + return 1; + } + + pdfio_stream_t *new_stream = pdfioObjCreateStream(new_stream_obj, PDFIO_FILTER_FLATE); + if (!new_stream) + { + pdfioStreamClose(existing_stream); + return 1; + } + + pdfioStreamWrite(new_stream, buf, len); + pdfioStreamClose(new_stream); + + pdfio_stream_t *combined_stream = pdfioObjCreateStream(page, PDFIO_FILTER_FLATE); + if (!combined_stream) + { + pdfioStreamClose(existing_stream); + return 1; + } + + char buffer[1024]; + size_t read_len; + while ((read_len = pdfioStreamRead(existing_stream, buffer, sizeof(buffer))) > 0) + pdfioStreamWrite(combined_stream, buffer, read_len); + + pdfioStreamClose(existing_stream); + pdfioStreamClose(combined_stream); + + return 0; +} + +// +// 'cfPDFAddType1Font()' - Add the specified type1 font face to the specified +// page in a PDF document. +// + +int +cfPDFAddType1Font(cf_pdf_t *pdf, + unsigned page_num, + const char *name) +{ + pdfio_obj_t *page = pdfioFileGetPage((pdfio_file_t *)pdf, page_num); + pdfio_dict_t *pageDict = pdfioObjGetDict(page); + if (!page) + return 1; + + pdfio_dict_t *resources = pdfioDictGetDict(pageDict, "Resources"); + + if (!resources) + { + resources = pdfioDictCreate((pdfio_file_t *)pdf); + pdfioDictSetDict(pageDict, "Resources", resources); + } + + pdfio_dict_t *fonts = pdfioDictGetDict(resources, "Font"); + if (!fonts) + { + fonts = pdfioDictCreate((pdfio_file_t *)pdf); + pdfioDictSetDict(resources, "Font", fonts); + } + + pdfio_dict_t *font = pdfioDictCreate((pdfio_file_t *)pdf); + if (!font) + return 1; + + pdfioDictSetName(font, "Type", "Font"); + pdfioDictSetName(font, "Subtype", "Type1"); + char basefont[256]; + snprintf(basefont, sizeof(basefont), "/%s", name); + pdfioDictSetName(font, "BaseFont", basefont); + + pdfioDictSetDict(fonts, "bannertopdf-font", font); + + return 0; +} + +// +// 'dict_lookup_rect()' - Lookup for an array of rectangle dimensions in a PDFio +// dictionary object. If it is found, store the values in +// an array and return true, else return false. +// + +static bool +dict_lookup_rect(pdfio_obj_t *object, // I - PDF dictionary object + const char *key, // I - Key to lookup + float rect[4], // O - Array to store values if key is found + bool inheritable) // I - Whether to look for inheritable values +{ + pdfio_dict_t *dict = pdfioObjGetDict(object); + if (!dict) + return false; + + pdfio_obj_t *value = pdfioDictGetObj(dict, key); + if (!value && inheritable) + return false; + + pdfio_array_t *array = pdfioObjGetArray(value); + if (!array || pdfioArrayGetSize(array) != 4) + return false; + + for (int i = 0; i < 4; i++) + { + pdfio_obj_t *elem = pdfioArrayGetObj(array, i); + + if (pdfioArrayGetType(array, i) == PDFIO_VALTYPE_NUMBER) + { + rect[i] = pdfioObjGetNumber(elem); + } + else + return false; // If any value is not numeric, return false + } + + return true; +} + +// +// 'fit_rect()' - Update the scale of the page using old media box dimensions +// and new media box dimensions. +// + +static void +fit_rect(float oldrect[4], // I - Old media box + float newrect[4], // I - New media box + float *scale) // I - Pointer to scale which needs to be updated +{ + float oldwidth = oldrect[2] - oldrect[0]; + float oldheight = oldrect[3] - oldrect[1]; + float newwidth = newrect[2] - newrect[0]; + float newheight = newrect[3] - newrect[1]; + + *scale = newwidth / oldwidth; + if (oldheight * (*scale) > newheight) + *scale = newheight / oldheight; +} + +// +// 'cfPDFResizePage()' - Resize page in a PDF with the given dimensions. +// + +int cfPDFResizePage(cf_pdf_t *pdf, // I - Pointer to PDFio file object + unsigned page_num, // I - Page number (1-based index) + float width, // I - New width of the page + float length, // I - New length of the page + float *scale) // O - Scale of the page to be updated +{ + pdfio_obj_t *page = pdfioFileGetPage((pdfio_file_t *)pdf, page_num - 1); + if (!page) + return 1; + + float new_mediabox[4] = {0.0, 0.0, width, length}; + float old_mediabox[4]; + pdfio_rect_t media_box; + + if (!dict_lookup_rect(page, "/MediaBox", old_mediabox, true)) + return (1); + + fit_rect(old_mediabox, new_mediabox, scale); + media_box = make_real_box(new_mediabox); + + pdfio_dict_t *pageDict = pdfioObjGetDict(page); + if (pageDict) + { + pdfioDictSetRect(pageDict, "CropBox", &media_box); + pdfioDictSetRect(pageDict, "TrimBox", &media_box); + pdfioDictSetRect(pageDict, "BleedBox", &media_box); + pdfioDictSetRect(pageDict, "ArtBox", &media_box); + } + + return 0; +} + +// +// 'cfPDFDuplicatePage()' - Duplicate a specified pdf page in a PDF +// + +int +cfPDFDuplicatePage(cf_pdf_t *pdf, + unsigned page_num, + unsigned count) +{ + pdfio_obj_t *page = pdfioFileGetPage((pdfio_file_t *)pdf, page_num - 1); + + if (!page) + return 1; + + for (unsigned i = 0; i < count; ++i) + { + pdfioPageCopy((pdfio_file_t *)pdf, page); + } + + return 0; +} + +// +// 'cfPDFWrite()' - Write the contents of PDF object to an already open FILE*. +// + +void +cfPDFWrite(cf_pdf_t *pdf, + FILE *file) +{ +// pdfioFileCreateImageObjFromFile((pdfio_file_t *)pdf, file, false); +} + +// +// 'lookup_opt()' - Get value according to key in the options list. +// + +static const char* +lookup_opt(cf_opt_t *opt, + const char *key) +{ + if (!opt || !key) + return NULL; + + while (opt) + { + if (opt->key && opt->val) + { + if (strcmp(opt->key, key) == 0) + { + return opt->val; + } + } + opt = opt->next; + } + + return (""); +} + +// +// 'cfPDFFillForm()' - Fill recognized fields with information +// + +int cfPDFFillForm(cf_pdf_t *doc, cf_opt_t *opt) { + // TODO: PDFio does not directly support form filling. + return 0; +} + diff --git a/cupsfilters/pdf.cxx b/cupsfilters/pdf.cxx deleted file mode 100644 index 03e31b5e5..000000000 --- a/cupsfilters/pdf.cxx +++ /dev/null @@ -1,477 +0,0 @@ -// -// Copyright 2012 Canonical Ltd. -// Copyright 2013 ALT Linux, Andrew V. Stepanov -// Copyright 2018 Sahil Arora -// -// Licensed under Apache License v2.0. See the file "LICENSE" for more -// information. -// - -#include -#include "pdf.h" -#include -#include -#include -#include -#include -#include -#include -#include - -// -// Useful reference: -// -// http://www.gnupdf.org/Indirect_Object -// http://www.gnupdf.org/Introduction_to_PDF -// http://blog.idrsolutions.com/2011/05/understanding-the-pdf-file-format-%E2%80%93-pdf-xref-tables-explained -// http://labs.appligent.com/pdfblog/pdf-hello-world/ -// https://github.com/OpenPrinting/cups-filters/pull/25 -// - - -// -// 'make_real_box()' - Return a QPDF array object of real values for a box. -// - -static QPDFObjectHandle // O - QPDFObjectHandle for an array -make_real_box(float values[4]) // I - Dimensions of the box in a float array -{ - QPDFObjectHandle ret = QPDFObjectHandle::newArray(); - for (int i = 0; i < 4; ++i) - ret.appendItem(QPDFObjectHandle::newReal(values[i])); - return (ret); -} - - -// -// 'cfPDFLoadTemplate()' - Load an existing PDF file and do initial parsing -// using QPDF. -// - -extern "C" cf_pdf_t * -cfPDFLoadTemplate(const char *filename) // I - Filename to open -{ - QPDF *pdf = new QPDF(); - - try - { - pdf->processFile(filename); - } - catch(...) - { - delete pdf; - return (NULL); - } - - unsigned pages = (pdf->getAllPages()).size(); - - if (pages != 1) - { - delete pdf; - return (NULL); - } - - return (pdf); -} - - -// -// 'cfPDFFree()' - Free pointer used by PDF object -// - -extern "C" void -cfPDFFree(cf_pdf_t *pdf) // I - Pointer to PDF object -{ - delete pdf; -} - - -// -// 'cfPDFPages()' - Count number of pages in file using QPDF. -// - -int // O - Number of pages or -1 on error -cfPDFPages(const char *filename) // I - Filename to open -{ - QPDF *pdf = new QPDF(); - - if (pdf) - { - try - { - pdf->processFile(filename); - } - catch(...) - { - cfPDFFree(pdf); - return (-1); - } - int pages = (pdf->getAllPages()).size(); - cfPDFFree(pdf); - return (pages); - } else - return (-1); -} - - -// -// 'cfPDFPagesFP()' - Count number of pages in file -// using QPDF. -// - -int // O - Number of pages or -1 on error -cfPDFPagesFP(FILE *file) // I - Pointer to opened PDF file (stdio FILE*) -{ - QPDF *pdf = new QPDF(); - - if (pdf) - { - try - { - pdf->processFile("", file, false); - } - catch(...) - { - cfPDFFree(pdf); - return (-1); - } - int pages = (pdf->getAllPages()).size(); - cfPDFFree(pdf); - return (pages); - } else - return (-1); -} - - -// -// 'cfPDFPrependStream' - Prepend a stream to the contents of a specified -// page in PDF file. -// - -extern "C" int -cfPDFPrependStream(cf_pdf_t *pdf, // I - Pointer to QPDF object - unsigned page_num, // I - page number of page to prepend - // stream to - char const *buf, // I - buffer containing data to be - // prepended - size_t len) // I - length of buffer -{ - std::vector pages = pdf->getAllPages(); - - if (pages.empty() || page_num > pages.size()) - return (1); - - QPDFObjectHandle page = pages[page_num - 1]; - - // get page contents stream / array - QPDFObjectHandle contents = page.getKey("/Contents"); - if (!contents.isStream() && !contents.isArray()) - return (1); - - // prepare the new stream which is to be prepended - std::shared_ptr stream_data = std::shared_ptr(new Buffer(len)); - memcpy(stream_data->getBuffer(), buf, len); - QPDFObjectHandle stream = QPDFObjectHandle::newStream(pdf, stream_data); - stream = pdf->makeIndirectObject(stream); - - // if the contents is an array, prepend the new stream to the array, - // else convert the contents to an array and the do the same. - if (contents.isStream()) - { - QPDFObjectHandle old_streamdata = contents; - contents = QPDFObjectHandle::newArray(); - contents.appendItem(old_streamdata); - } - - contents.insertItem(0, stream); - page.replaceKey("/Contents", contents); - - return (0); -} - - -// -// 'cfPDFAddType1Font()' - Add the specified type1 fontface to the specified -// page in a PDF document. -// - -extern "C" int -cfPDFAddType1Font(cf_pdf_t *pdf, // I - QPDF object - unsigned page_num, // I - Page number of the page to which - // the font is to be added - const char *name) // I - name of the font to be added -{ - std::vector pages = pdf->getAllPages(); - - if (pages.empty() || page_num > pages.size()) - return (1); - - QPDFObjectHandle page = pages[page_num - 1]; - - QPDFObjectHandle resources = page.getKey("/Resources"); - if (!resources.isDictionary()) - return (1); - - QPDFObjectHandle font = QPDFObjectHandle::newDictionary(); - font.replaceKey("/Type", QPDFObjectHandle::newName("/Font")); - font.replaceKey("/Subtype", QPDFObjectHandle::newName("/Type1")); - font.replaceKey("/BaseFont", - QPDFObjectHandle::newName(std::string("/") + - std::string(name))); - - QPDFObjectHandle fonts = resources.getKey("/Font"); - if (fonts.isNull()) - fonts = QPDFObjectHandle::newDictionary(); - else if (!fonts.isDictionary()) - return (1); - - font = pdf->makeIndirectObject(font); - fonts.replaceKey("/bannertopdf-font", font); - resources.replaceKey("/Font", fonts); - - return (0); -} - - -// -// 'dict_lookup_rect()' - Lookup for an array of rectangle dimensions in a QPDF -// dictionary object. If it is found, store the values in -// an array and return true, else return false. -// - -static bool -dict_lookup_rect(QPDFObjectHandle object, // O - Key is found in the dictionary? - std::string const& key, // I - PDF dictionary object - float rect[4], // I - Key to lookup - bool inheritable) // I - Array to store values if key - // is found -{ - // preliminary checks - if (!object.isDictionary()) - return (false); - - QPDFObjectHandle value; - if (!object.hasKey(key) && inheritable) - { - QPDFFormFieldObjectHelper helper(object); - value = helper.getInheritableFieldValue(key); - if (value.isNull()) - return (false); - } - else - value = object.getKey(key); - - // check if the key is array or some other type - if (!value.isArray()) - return (false); - - // get values in a vector and assign it to rect - std::vector array = value.getArrayAsVector(); - for (int i = 0; i < 4; ++i) - { - // if the value in the array is not real, we have an invalid array - if (!array[i].isReal() && !array[i].isInteger()) - return (false); - - rect[i] = array[i].getNumericValue(); - } - - return (array.size() == 4); -} - - -// -// 'fit_rect()' - Update the scale of the page using old media box dimensions -// and new media box dimensions. -// - -static void -fit_rect(float oldrect[4], // I - Old media box - float newrect[4], // I - New media box - float *scale) // I - Pointer to scale which needs to be updated -{ - float oldwidth = oldrect[2] - oldrect[0]; - float oldheight = oldrect[3] - oldrect[1]; - float newwidth = newrect[2] - newrect[0]; - float newheight = newrect[3] - newrect[1]; - - *scale = newwidth / oldwidth; - if (oldheight * *scale > newheight) - *scale = newheight / oldheight; -} - - -// -// 'cfPDFResizePage()' - Resize page in a PDF with the given dimensions. -// - -extern "C" int -cfPDFResizePage(cf_pdf_t *pdf, // I - Pointer to QPDF object - unsigned page_num, // I - Page number - float width, // I - Width of page to set - float length, // I - Length of page to set - float *scale) // I - Scale of page to set -{ - std::vector pages = pdf->getAllPages(); - - if (pages.empty() || page_num > pages.size()) - return (1); - - QPDFObjectHandle page = pages[page_num - 1]; - float new_mediabox[4] = { 0.0, 0.0, width, length }; - float old_mediabox[4]; - QPDFObjectHandle media_box; - - if (!dict_lookup_rect(page, "/MediaBox", old_mediabox, true)) - return (1); - - fit_rect(old_mediabox, new_mediabox, scale); - media_box = make_real_box(new_mediabox); - - page.replaceKey("/ArtBox", media_box); - page.replaceKey("/BleedBox", media_box); - page.replaceKey("/CropBox", media_box); - page.replaceKey("/MediaBox", media_box); - page.replaceKey("/TrimBox", media_box); - - return (0); -} - - -// -// 'cfPDFDuplicatePage()' - Duplicate a specified pdf page in a PDF -// - -extern "C" int -cfPDFDuplicatePage (cf_pdf_t *pdf, // I - Pointer to QPDF object - unsigned page_num, // I - Page number of the page to be - // duplicated - unsigned count) // I - Number of copies to be duplicated -{ - std::vector pages = pdf->getAllPages(); - - if (pages.empty() || page_num > pages.size()) - return (1); - - QPDFObjectHandle page = pages[page_num - 1]; - for (unsigned i = 0; i < count; ++i) - { - page = pdf->makeIndirectObject(page); - pdf->addPage(page, false); - } - - return (0); -} - - -// -// 'cfPDFWrite()' - Write the contents of PDF object to an already open FILE*. -// - -extern "C" void -cfPDFWrite(cf_pdf_t *pdf, // I - Pointer to QPDF structure - FILE *file) // I - File pointer to write to -{ - QPDFWriter output(*pdf, "cfPDFWrite", file, false); - output.write(); -} - - -// -// 'lookup_opt()' - Get value according to key in the options list. -// - -static std::string // O - character string which corresponds - // to the value of the key or NULL if - // key is not found in the list. -lookup_opt(cf_opt_t *opt, // I - pointer to the cf_opt_t type list - std::string const& key) // I - key to be found in the list -{ - if (!opt || key.empty()) - return (""); - - while (opt) - { - if (opt->key && opt->val) - { - if (strcmp(opt->key, key.c_str()) == 0) - return (std::string(opt->val)); - } - opt = opt->next; - } - - return (""); -} - - -// -// 'cfPDFFillForm()' - 1. Look for form in PDF template file -// 2. Look for form fields' names -// 3. Fill recognized fields with information -// - -extern "C" int // O - Status of form fill - 0 for success, - // 1 for failure -cfPDFFillForm(cf_pdf_t *doc, // I - Pointer to the QPDF structure - cf_opt_t *opt) // I - Pointer to the cf_opt_t type list -{ - // Initialize AcroFormDocumentHelper and PageDocumentHelper objects - // to work with forms in the PDF - QPDFAcroFormDocumentHelper afdh(*doc); - QPDFPageDocumentHelper pdh(*doc); - - // Check if the PDF has a form or not - if (!afdh.hasAcroForm()) - return 1; - - // Get the first page from the PDF to fill the form. Since this - // is a banner file, it must contain only a single page, and that - // check has already been performed in the `cfPDFLoadTemplate()` function - std::vector pages = pdh.getAllPages(); - if (pages.empty()) - return 1; - QPDFPageObjectHelper page = pages.front(); - - // Get the annotations in the page - std::vector annotations = - afdh.getWidgetAnnotationsForPage(page); - - for (std::vector::iterator annot_iter = - annotations.begin(); - annot_iter != annotations.end(); - ++annot_iter) - { - // For each annotation, find its associated field. If it's a - // text field, we try to set its value. This will automatically - // update the document to indicate that appearance streams need - // to be regenerated. At the time of this writing, QPDF doesn't - // have any helper code to assist with appearance stream generation, - // though there's nothing that prevents it from being possible. - QPDFFormFieldObjectHelper ffh = - afdh.getFieldForAnnotation(*annot_iter); - if (ffh.getFieldType() == "/Tx") - { - // Look in the options setting for the value of this field and fill the - // value accordingly. This will automatically set /NeedAppearances to - // true. - std::string const name = ffh.getFullyQualifiedName(); - std::string fill_with = lookup_opt(opt, name); - if (fill_with.empty()) - { - std::cerr << "DEBUG: Lack information for widget: " << name << ".\n"; - fill_with = "N/A"; - } - - // Convert the 'fill_with' string to UTF16 before filling to the widget - QPDFObjectHandle fill_with_utf_16 = - QPDFObjectHandle::newUnicodeString(fill_with); - ffh.setV(fill_with_utf_16); - std::cerr << "DEBUG: Fill widget name " << name << " with value " - << fill_with_utf_16.getUTF8Value() << ".\n"; - } - } - - // Status 0 notifies that the function successfully filled all the - // identifiable fields in the form - return (0); -} diff --git a/cupsfilters/pdf.h b/cupsfilters/pdf.h index 14f198200..92e381d31 100644 --- a/cupsfilters/pdf.h +++ b/cupsfilters/pdf.h @@ -1,6 +1,7 @@ // // Copyright 2012 Canonical Ltd. // Copyright 2018 Sahil Arora +// Copyright 2024 Uddhav Phatak // // Licensed under Apache License v2.0. See the file "LICENSE" for more // information. @@ -11,11 +12,7 @@ #include -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct QPDF cf_pdf_t; +typedef struct pdfio_file_t cf_pdf_t; typedef struct _cf_opt cf_opt_t; @@ -25,26 +22,42 @@ typedef struct _cf_opt cf_opt_t; struct _cf_opt { - const char* key; - const char* val; - cf_opt_t *next; + const char* key; + const char* val; + cf_opt_t *next; }; cf_pdf_t *cfPDFLoadTemplate(const char *filename); void cfPDFFree(cf_pdf_t *pdf); -void cfPDFWrite(cf_pdf_t *doc, FILE *file); -int cfPDFPrependStream(cf_pdf_t *doc, unsigned page, char const *buf, + +void cfPDFWrite(cf_pdf_t *doc, + FILE *file); + +int cfPDFPrependStream(cf_pdf_t *doc, + unsigned page, + const char *buf, size_t len); -int cfPDFAddType1Font(cf_pdf_t *doc, unsigned page, const char *name); -int cfPDFResizePage(cf_pdf_t *doc, unsigned page, float width, float length, + +int cfPDFAddType1Font(cf_pdf_t *doc, + unsigned page, + const char *name); + +int cfPDFResizePage(cf_pdf_t *doc, + unsigned page, + float width, + float length, float *scale); -int cfPDFDuplicatePage(cf_pdf_t *doc, unsigned page, unsigned count); -int cfPDFFillForm(cf_pdf_t *doc, cf_opt_t *opt); + +int cfPDFDuplicatePage(cf_pdf_t *doc, + unsigned page, + unsigned count); + +int cfPDFFillForm(cf_pdf_t *doc, + cf_opt_t *opt); + int cfPDFPages(const char *filename); -int cfPDFPagesFP(FILE *file); -#ifdef __cplusplus -} -#endif +int cfPDFPagesFP(char *file); #endif // !_CUPS_FILTERS_PDF_H_ + diff --git a/cupsfilters/pdftopdf/intervalset-private.h b/cupsfilters/pdftopdf/intervalset-private.h index 0fc48ca65..d13592ec7 100644 --- a/cupsfilters/pdftopdf/intervalset-private.h +++ b/cupsfilters/pdftopdf/intervalset-private.h @@ -1,4 +1,6 @@ // +// Copyright 2024 Uddhav Phatak -#include +#include + +typedef struct +{ + int start; + int end; +} interval_t; -class _cfPDFToPDFIntervalSet +typedef struct { - typedef int key_t; // TODO?! template - typedef std::pair value_t; - typedef std::vector data_t; - public: - static const key_t npos; - - void clear(); - // [start; end) ! - void add(key_t start, key_t end = npos); - void finish(); - - size_t size() const { return data.size(); } - - // only after finish() has been called: - bool contains(key_t val) const; - key_t next(key_t val) const; - - void dump(pdftopdf_doc_t *doc) const; - private: - // currently not used - bool intersect(const value_t &a, const value_t &b) const; - void unite(value_t &aret, const value_t &b) const; - private: - data_t data; -}; + interval_t *data; + size_t size; + size_t capacity; +} _cfPDFToPDFIntervalSet; + +extern const int _cfPDFToPDFIntervalSet_npos; + +void _cfPDFToPDFIntervalSet_init(_cfPDFToPDFIntervalSet *set); +void _cfPDFToPDFIntervalSet_clear(_cfPDFToPDFIntervalSet *set); +void _cfPDFToPDFIntervalSet_add(_cfPDFToPDFIntervalSet *set, int start, int end); +void _cfPDFToPDFIntervalSet_add_single(_cfPDFToPDFIntervalSet *set, int start); +void _cfPDFToPDFIntervalSet_finish(_cfPDFToPDFIntervalSet *set); + +size_t _cfPDFToPDFIntervalSet_size(const _cfPDFToPDFIntervalSet *set); + +bool _cfPDFToPDFIntervalSet_contains(const _cfPDFToPDFIntervalSet *set, int val); +int _cfPDFToPDFIntervalSet_next(const _cfPDFToPDFIntervalSet *set, int val); + +void _cfPDFToPDFIntervalSet_dump(const _cfPDFToPDFIntervalSet *set, pdftopdf_doc_t *doc); #endif // !_CUPS_FILTERS_PDFTOPDF_INTERVALSET_H_ + diff --git a/cupsfilters/pdftopdf/intervalset.c b/cupsfilters/pdftopdf/intervalset.c new file mode 100644 index 000000000..dbc20b474 --- /dev/null +++ b/cupsfilters/pdftopdf/intervalset.c @@ -0,0 +1,180 @@ +// +// Copyright 2024 Uddhav Phatak +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "intervalset-private.h" +#include +#include +#include +#include +#include +#include "cupsfilters/debug-internal.h" + +const int _cfPDFToPDFIntervalSet_npos = INT_MAX; + +void +_cfPDFToPDFIntervalSet_init(_cfPDFToPDFIntervalSet *set) // {{{ +{ + set->data = NULL; + set->size = 0; + set->capacity = 0; +} +// }}} + +void +_cfPDFToPDFIntervalSet_clear(_cfPDFToPDFIntervalSet *set) // {{{ +{ + free(set->data); + set->data = NULL; + set->size = 0; + set->capacity = 0; +} +// }}} + +void +_cfPDFToPDFIntervalSet_add(_cfPDFToPDFIntervalSet *set, int start, int end) // {{{ +{ + if (start >= end) + return; + + if (set->size == set->capacity) + { + set->capacity = set->capacity == 0 ? 4 : set->capacity * 2; + set->data = realloc(set->data, set->capacity * sizeof(interval_t)); + } + + set->data[set->size].start = start; + set->data[set->size].end = end; + set->size++; +} +// }}} + +void +_cfPDFToPDFIntervalSet_add_single(_cfPDFToPDFIntervalSet *set, + int start) // {{{ +{ + key_t end = _cfPDFToPDFIntervalSet_npos; + + if (start >= end) + return; + + if (set->size == set->capacity) + { + set->capacity = set->capacity == 0 ? 4 : set->capacity * 2; + set->data = realloc(set->data, set->capacity * sizeof(interval_t)); + if (set->data == NULL) + return; + } + + set->data[set->size].start = start; + set->data[set->size].end = end; + set->size++; +} +// }}} + +static int +compare_intervals(const void *a, const void *b) // {{{ +{ + interval_t *ia = (interval_t *)a; + interval_t *ib = (interval_t *)b; + if (ia->start != ib->start) + return ia->start - ib->start; + return ia->end - ib->end; +} +// }}} + +void +_cfPDFToPDFIntervalSet_finish(_cfPDFToPDFIntervalSet *set) // {{{ +{ + if (set->size == 0) + return; + + qsort(set->data, set->size, sizeof(interval_t), compare_intervals); + + size_t new_size = 0; + for (size_t i = 1; i < set->size; i++) + { + if (set->data[new_size].end >= set->data[i].start) + { + if (set->data[new_size].end < set->data[i].end) + { + set->data[new_size].end = set->data[i].end; + } + } + else + { + new_size++; + set->data[new_size] = set->data[i]; + } + } + set->size = new_size + 1; +} +// }}}} + +size_t +_cfPDFToPDFIntervalSet_size(const _cfPDFToPDFIntervalSet *set) // {{{ +{ + return set->size; +} +// }}} + +bool +_cfPDFToPDFIntervalSet_contains(const _cfPDFToPDFIntervalSet *set, + int val) // {{{ +{ + for (size_t i = 0; i < set->size; i++) + { + if (val >= set->data[i].start && val < set->data[i].end) + { + return true; + } + } + return false; +} +// }}} + +int +_cfPDFToPDFIntervalSet_next(const _cfPDFToPDFIntervalSet *set, + int val) // {{{ +{ + val++; + for (size_t i = 0; i < set->size; i++) + { + if (val < set->data[i].end) + { + if (val >= set->data[i].start) + { + return val; + } + else + { + return set->data[i].start; + } + } + } + return _cfPDFToPDFIntervalSet_npos; +} +// }}} + +void +_cfPDFToPDFIntervalSet_dump(const _cfPDFToPDFIntervalSet *set, + pdftopdf_doc_t *doc) // {{{ +{ + if (set->size == 0) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: (empty)"); + return; + } + + for (size_t i = 0; i < set->size; i++) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: [%d,%d)", + set->data[i].start, set->data[i].end); + } +} +// }}} diff --git a/cupsfilters/pdftopdf/intervalset.cxx b/cupsfilters/pdftopdf/intervalset.cxx deleted file mode 100644 index 38b1b7797..000000000 --- a/cupsfilters/pdftopdf/intervalset.cxx +++ /dev/null @@ -1,149 +0,0 @@ -// -// Licensed under Apache License v2.0. See the file "LICENSE" for more -// information. -// - -#include "intervalset-private.h" -#include -#include "cupsfilters/debug-internal.h" -#include -#include - -const _cfPDFToPDFIntervalSet::key_t _cfPDFToPDFIntervalSet::npos = - std::numeric_limits<_cfPDFToPDFIntervalSet::key_t>::max(); - -void -_cfPDFToPDFIntervalSet::clear() // {{{ -{ - data.clear(); -} -// }}} - -void -_cfPDFToPDFIntervalSet::add(key_t start, - key_t end) // {{{ -{ - if (start < end) - data.push_back(std::make_pair(start, end)); -} -// }}} - -void -_cfPDFToPDFIntervalSet::finish() // {{{ -{ - data_t::iterator it = data.begin(), - end = data.end(), - pos = it; - if (it == end) - return; - - std::sort(it, end); - - while (1) - { - ++ it; - if (it == end) - { - ++ pos; - break; - } - if (pos->second >= it->first) - pos->second = it->second; - else - { - ++ pos; - if (pos != it) - *pos = *it; - } - } - - data.erase(pos, data.end()); -} -// }}} - -bool -_cfPDFToPDFIntervalSet::contains(key_t val) const // {{{ -{ - data_t::const_iterator it = - std::upper_bound(data.begin(), data.end(), std::make_pair(val, npos)); - if (it == data.begin()) - return false; - -- it; - return (val < it->second); -} -// }}} - -_cfPDFToPDFIntervalSet::key_t -_cfPDFToPDFIntervalSet::next(key_t val) const // {{{ -{ - val ++; - data_t::const_iterator it = - std::upper_bound(data.begin(), data.end(), std::make_pair(val, npos)); - if (it == data.begin()) - { - if (it == data.end()) // empty - return (npos); - return (it->first); - } - -- it; - if (val < it->second) - return (val); - ++ it; - if (it == data.end()) - return npos; - return (it->first); -} -// }}} - -bool -_cfPDFToPDFIntervalSet::intersect(const value_t &a, - const value_t &b) const // {{{ -{ - return (((a.first >= b.first) && (a.first < b.second)) || - ((b.first >= a.first) && (b.first < a.second))); -} -// }}} - -void -_cfPDFToPDFIntervalSet::unite(value_t &aret, - const value_t &b) const // {{{ -{ - DEBUG_assert(intersect(aret, b)); - if (b.first < aret.first) - aret.first = b.first; - if (b.second > aret.second) - aret.second = b.second; -} -// }}} - -void -_cfPDFToPDFIntervalSet::dump(pdftopdf_doc_t *doc) const // {{{ -{ - int len = data.size(); - if (len == 0) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: (empty)"); - return; - } - len --; - for (int iA = 0; iA < len; iA ++) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: [%d,%d)", - data[iA].first, data[iA].second); - } - if (data[len].second == npos) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: [%d,inf)", - data[len].first); - } - else - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: [%d,%d)", - data[len].first, data[len].second); - } -} -// }}} diff --git a/cupsfilters/pdftopdf/nup-private.h b/cupsfilters/pdftopdf/nup-private.h index 53f688b2f..b64be8974 100644 --- a/cupsfilters/pdftopdf/nup-private.h +++ b/cupsfilters/pdftopdf/nup-private.h @@ -1,4 +1,6 @@ // +//Copyright 2024 Uddhav Phatak +// // Licensed under Apache License v2.0. See the file "LICENSE" for more // information. // @@ -7,102 +9,47 @@ #define _CUPS_FILTERS_PDFTOPDF_NUP_H_ #include "pptypes-private.h" -#include +#include -// you have to provide this -struct _cfPDFToPDFNupParameters +typedef struct _cfPDFToPDFNupParameters { - _cfPDFToPDFNupParameters() - : nupX(1), nupY(1), - width(NAN), height(NAN), - landscape(false), - first(X), - xstart(LEFT), ystart(TOP), - xalign(CENTER), yalign(CENTER) - {} - - // --- "calculated" parameters --- int nupX, nupY; float width, height; - bool landscape; // post-rotate! + bool landscape; - // --- other settings --- - // ordering pdftopdf_axis_e first; pdftopdf_position_e xstart, ystart; - pdftopdf_position_e xalign, yalign; +} _cfPDFToPDFNupParameters; - static bool possible(int nup); // TODO? float in_ratio, float out_ratio - static void preset(int nup, _cfPDFToPDFNupParameters &ret); - static float calculate(int nup, float in_ratio, float out_ratio, - _cfPDFToPDFNupParameters &ret); // returns "quality", - // 1 is best - - void dump(pdftopdf_doc_t *doc) const; -}; +void _cfPDFToPDFNupParameters_init(_cfPDFToPDFNupParameters *nupParams); +bool _cfPDFToPDFNupParameters_possible(int nup); +void _cfPDFToPDFNupParameters_preset(int nup, _cfPDFToPDFNupParameters *ret); +void _cfPDFToPDFNupParameters_dump(const _cfPDFToPDFNupParameters *nupParams, pdftopdf_doc_t *doc); -// you get this -struct _cfPDFToPDFNupPageEdit +typedef struct _cfPDFToPDFNupPageEdit { - // required transformation: first translate, then scale - float xpos, ypos; // TODO: already given by sub.left, sub.bottom - // [but for rotation?] - float scale; // uniform + float xpos, ypos; + float scale; - // ? "landscape" e.g. to rotate labels + _cfPDFToPDFPageRect sub; +} _cfPDFToPDFNupPageEdit; - // for border, clip, ... - // also stores in_width/in_height, unscaled! - // everything in "outer"-page coordinates - _cfPDFToPDFPageRect sub; +void _cfPDFToPDFNupPageEdit_dump(const _cfPDFToPDFNupPageEdit *edit, pdftopdf_doc_t *doc); - void dump(pdftopdf_doc_t *doc) const; -}; - -// -// This class does the number-up calculation. Example: -// -// _cfPDFToPDFNupParameters param; -// param.xyz = ...; // fill it with your data! -// -// _cfPDFToPDFNupState nup(param); -// _cfPDFToPDFNupPageEdit edit; -// for (auto page : your_pages) -// { -// bool newPage = nup.mext_page(page.w, page.h, edit); // w, h from input -// // page -// // create newPage, if required; then place current page as specified -// // in edit -// } -// - -class _cfPDFToPDFNupState +typedef struct _cfPDFToPDFNupState { -public: - _cfPDFToPDFNupState(const _cfPDFToPDFNupParameters ¶m); - - void reset(); - - // will overwrite ret with the new parameters - // returns true, if a new output page should be started first - bool mext_page(float in_width, float in_height, _cfPDFToPDFNupPageEdit &ret); + _cfPDFToPDFNupParameters param; + int in_pages, out_pages; + int nup; + int subpage; +} _cfPDFToPDFNupState; -private: - std::pair convert_order(int subpage) const; - void calculate_edit(int subx, int suby, _cfPDFToPDFNupPageEdit &ret) const; +void _cfPDFToPDFNupState_init(_cfPDFToPDFNupState *state, const _cfPDFToPDFNupParameters *param); +void _cfPDFToPDFNupState_reset(_cfPDFToPDFNupState *state); +bool _cfPDFToPDFNupState_next_page(_cfPDFToPDFNupState *state, float in_width, float in_height, _cfPDFToPDFNupPageEdit *ret); -private: - _cfPDFToPDFNupParameters param; - - int in_pages, out_pages; - int nup; // max. per page (== nupX * nupY) - int subpage; // on the current output-page -}; - -// TODO? elsewhere -// parsing functions for cups parameters (will not calculate nupX, nupY!) -bool _cfPDFToPDFParseNupLayout(const char *val, _cfPDFToPDFNupParameters &ret); - // lrtb, btlr, ... +bool _cfPDFToPDFParseNupLayout(const char *val, _cfPDFToPDFNupParameters *ret); #endif // !_CUPS_FILTERS_PDFTOPDF_NUP_H_ + diff --git a/cupsfilters/pdftopdf/nup.c b/cupsfilters/pdftopdf/nup.c new file mode 100644 index 000000000..187b4d3f1 --- /dev/null +++ b/cupsfilters/pdftopdf/nup.c @@ -0,0 +1,341 @@ +// +// Copyright 2024 Uddhav Phatak +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "nup-private.h" +#include +#include +#include +#include "cupsfilters/debug-internal.h" + +void +_cfPDFToPDFNupParameters_init(_cfPDFToPDFNupParameters *nupParams) // {{{ +{ + nupParams->nupX = 1; + nupParams->nupY = 1; + nupParams->width = NAN; + nupParams->height = NAN; + nupParams->landscape = false; + nupParams->first = X; + nupParams->xstart = LEFT; + nupParams->ystart = TOP; + nupParams->xalign = CENTER; + nupParams->yalign = CENTER; +} +// }}} + +void +_cfPDFToPDFNupParameters_dump(const _cfPDFToPDFNupParameters *nupParams, + pdftopdf_doc_t *doc) // {{{ +{ + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, "cfFilterPDFToPDF: NupX: %d, NupY: %d, " + "width: %f, height: %f", + nupParams->nupX, nupParams->nupY, + nupParams->width, nupParams->height); + + int opos = -1, + fpos = -1, + spos = -1; + + if (nupParams->xstart == LEFT) + fpos = 0; + else if (nupParams->xstart == RIGHT) + fpos = 1; + + if (nupParams->ystart == LEFT) + spos = 0; + else if (nupParams->ystart == RIGHT) + spos = 1; + + if (nupParams->first == X) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: First Axis: X"); + opos = 0; + } + else if (nupParams->first == Y) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: First Axis: Y"); + opos = 2; + int temp = fpos; + fpos = spos; + spos = temp; + } + + if ((opos == -1) || (fpos == -1) || (spos == -1)) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Bad Spec: %d; start: %d, %d", + nupParams->first, nupParams->xstart, nupParams->ystart); + } + else + { + static const char *order[4] = {"lr", "rl", "bt", "tb"}; + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Order: %s%s", + order[opos + fpos], + order[(opos + 2) % 4 + spos]); + } + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Alignment:"); + _cfPDFToPDFPositionAndAxisDump(nupParams->xalign, X, doc); + _cfPDFToPDFPositionAndAxisDump(nupParams->yalign, Y, doc); +} +// }}} + +bool +_cfPDFToPDFNupParameters_possible(int nup) // {{{ +{ + return ((nup >= 1) && (nup <= 16) && + ((nup != 5) && (nup != 7) && (nup != 11) && (nup != 13) && + (nup != 14))); +} +// }}} + +void +_cfPDFToPDFNupParameters_preset(int nup, + _cfPDFToPDFNupParameters *ret) // {{{ +{ + switch (nup) + { + case 1: + ret->nupX = 1; + ret->nupY = 1; + break; + case 2: + ret->nupX = 2; + ret->nupY = 1; + ret->landscape = true; + break; + case 3: + ret->nupX = 3; + ret->nupY = 1; + ret->landscape = true; + break; + case 4: + ret->nupX = 2; + ret->nupY = 2; + break; + case 6: + ret->nupX = 3; + ret->nupY = 2; + ret->landscape = true; + break; + case 8: + ret->nupX = 4; + ret->nupY = 2; + ret->landscape = true; + break; + case 9: + ret->nupX = 3; + ret->nupY = 3; + break; + case 10: + ret->nupX = 5; + ret->nupY = 2; + ret->landscape = true; + break; + case 12: + ret->nupX = 3; + ret->nupY = 4; + break; + case 15: + ret->nupX = 5; + ret->nupY = 3; + ret->landscape = true; + break; + case 16: + ret->nupX = 4; + ret->nupY = 4; + break; + } +} +// }}} + +void +_cfPDFToPDFNupState_init(_cfPDFToPDFNupState *state, + const _cfPDFToPDFNupParameters *param) // {{{ +{ + state->param = *param; + state->in_pages = 0; + state->out_pages = 0; + state->nup = param->nupX * param->nupY; + state->subpage = state->nup; +} +// }}} + +void +_cfPDFToPDFNupState_reset(_cfPDFToPDFNupState *state) // {{{ +{ + state->in_pages = 0; + state->out_pages = 0; + state->subpage = state->nup; +} +// }}} + +void +_cfPDFToPDFNupPageEdit_dump(const _cfPDFToPDFNupPageEdit *edit, + pdftopdf_doc_t *doc) // {{{ +{ + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: xpos: %f, ypos: %f, scale: %f", + edit->xpos, edit->ypos, edit->scale); + _cfPDFToPDFPageRect_dump(&edit->sub, doc); +} +// }}} + +typedef struct { + int first; + int second; +} int_pair; + +static int_pair +_cfPDFToPDFNupState_convert_order(const _cfPDFToPDFNupState *state, + int subpage) // {{{ +{ + int subx, suby; + if (state->param.first == X) + { + subx = subpage % state->param.nupX; + suby = subpage / state->param.nupX; + } + else + { + subx = subpage / state->param.nupY; + suby = subpage % state->param.nupY; + } + + subx = (state->param.nupX - 1) * (state->param.xstart + 1) / 2 - state->param.xstart * subx; + suby = (state->param.nupY - 1) * (state->param.ystart + 1) / 2 - state->param.ystart * suby; + + int_pair result; + result.first = subx; + result.second = suby; + + return result; +} +// }}} + +static float +lin(pdftopdf_position_e pos, float size) // {{{ +{ + if (pos == -1) + return 0; + else if (pos == 0) + return size / 2; + else if (pos == 1) + return size; + return size * (pos + 1) / 2; +} +// }}} + +void +_cfPDFToPDFNupState_calculate_edit(const _cfPDFToPDFNupState *state, + int subx, int suby, + _cfPDFToPDFNupPageEdit *ret) // {{{ +{ + const float width = state->param.width / state->param.nupX; + const float height = state->param.height / state->param.nupY; + + ret->xpos = subx * width; + ret->ypos = suby * height; + + const float scalex = width / ret->sub.width; + const float scaley = height / ret->sub.height; + float subwidth = ret->sub.width * scaley; + float subheight = ret->sub.height * scalex; + + if (scalex > scaley) + { + ret->scale = scaley; + subheight = height; + ret->xpos += lin(state->param.xalign, width - subwidth); + } + else + { + ret->scale = scalex; + subwidth = width; + ret->ypos += lin(state->param.yalign, height - subheight); + } + + ret->sub.left = ret->xpos; + ret->sub.bottom = ret->ypos; + ret->sub.right = ret->sub.left + subwidth; + ret->sub.top = ret->sub.bottom + subheight; +} +// }}} + +bool +_cfPDFToPDFNupState_next_page(_cfPDFToPDFNupState *state, + float in_width, float in_height, + _cfPDFToPDFNupPageEdit *ret) // {{{ +{ + state->in_pages++; + state->subpage++; + if (state->subpage >= state->nup) + { + state->subpage = 0; + state->out_pages++; + } + + ret->sub.width = in_width; + ret->sub.height = in_height; + + int_pair *sub = (int_pair*)malloc(sizeof(int_pair)); + *sub = _cfPDFToPDFNupState_convert_order(state, state->subpage); + _cfPDFToPDFNupState_calculate_edit(state, sub->first, sub->second, ret); + + free(sub); + return (state->subpage == 0); +} +// }}} + +static int_pair +parsePosition(char a, char b) // {{{ +{ + a |= 0x20; // make lowercase + b |= 0x20; + if ((a == 'l') && (b == 'r')) + return (int_pair){X, LEFT}; + else if ((a == 'r') && (b == 'l')) + return (int_pair){X, RIGHT}; + else if ((a == 't') && (b == 'b')) + return (int_pair){Y, TOP}; + else if ((a == 'b') && (b == 't')) + return (int_pair){Y, BOTTOM}; + return (int_pair){X, CENTER}; +} +// }}} + +bool +_cfPDFToPDFParseNupLayout(const char *val, + _cfPDFToPDFNupParameters *ret) // {{{ +{ + int_pair pos0 = parsePosition(val[0], val[1]); + if (pos0.second == CENTER) + return false; + int_pair pos1 = parsePosition(val[2], val[3]); + if ((pos1.second == CENTER) || (pos0.first == pos1.first)) + return false; + + ret->first = pos0.first; + if (ret->first == X) + { + ret->xstart = pos0.second; + ret->ystart = pos1.second; + } + else + { + ret->xstart = pos1.second; + ret->ystart = pos0.second; + } + + return (val[4] == 0); +} +// }}} + diff --git a/cupsfilters/pdftopdf/nup.cxx b/cupsfilters/pdftopdf/nup.cxx deleted file mode 100644 index 2e1dc341d..000000000 --- a/cupsfilters/pdftopdf/nup.cxx +++ /dev/null @@ -1,312 +0,0 @@ -// -// Licensed under Apache License v2.0. See the file "LICENSE" for more -// information. -// - -#include "nup-private.h" -#include -#include "cupsfilters/debug-internal.h" -#include -#include - -void -_cfPDFToPDFNupParameters::dump(pdftopdf_doc_t *doc) const // {{{ -{ - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: NupX: %d, NupY: %d, " - "width: %f, height: %f", - nupX, nupY, - width, height); - - int opos = -1, - fpos = -1, - spos = -1; - - if (xstart == pdftopdf_position_e::LEFT) // or Bottom - fpos = 0; - else if (xstart == pdftopdf_position_e::RIGHT) // or Top - fpos = 1; - if (ystart == pdftopdf_position_e::LEFT) // or Bottom - spos = 0; - else if (ystart == pdftopdf_position_e::RIGHT) // or Top - spos = 1; - if (first == pdftopdf_axis_e::X) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: First Axis: X"); - opos = 0; - } - else if (first == pdftopdf_axis_e::Y) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: First Axis: Y"); - opos = 2; - std::swap(fpos, spos); - } - - if ((opos == -1) || (fpos == -1) || (spos == -1)) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Bad Spec: %d; start: %d, %d", - first, xstart, ystart); - } - else - { - static const char *order[4] = {"lr", "rl", "bt", "tb"}; - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Order: %s%s", - order[opos + fpos], - order[(opos + 2) % 4 + spos]); - } - - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Alignment:"); - _cfPDFToPDFPositionDump(xalign, pdftopdf_axis_e::X,doc); - _cfPDFToPDFPositionDump(yalign, pdftopdf_axis_e::Y,doc); -} -// }}} - -bool -_cfPDFToPDFNupParameters::possible(int nup) // {{{ -{ - // 1 2 3 4 6 8 9 10 12 15 16 - return ((nup >= 1) && (nup <= 16) && - ((nup != 5) && (nup != 7) && (nup != 11) && (nup != 13) && - (nup != 14))); -} -// }}} - -void -_cfPDFToPDFNupParameters::preset(int nup, - _cfPDFToPDFNupParameters &ret) // {{{ -{ - switch (nup) - { - case 1: - ret.nupX=1; - ret.nupY=1; - break; - case 2: - ret.nupX=2; - ret.nupY=1; - ret.landscape=true; - break; - case 3: - ret.nupX=3; - ret.nupY=1; - ret.landscape=true; - break; - case 4: - ret.nupX=2; - ret.nupY=2; - break; - case 6: - ret.nupX=3; - ret.nupY=2; - ret.landscape=true; - break; - case 8: - ret.nupX=4; - ret.nupY=2; - ret.landscape=true; - break; - case 9: - ret.nupX=3; - ret.nupY=3; - break; - case 10: - ret.nupX=5; - ret.nupY=2; - ret.landscape=true; - break; - case 12: - ret.nupX=3; - ret.nupY=4; - break; - case 15: - ret.nupX=5; - ret.nupY=3; - ret.landscape=true; - break; - case 16: - ret.nupX=4; - ret.nupY=4; - break; - } -} -// }}} - - -_cfPDFToPDFNupState::_cfPDFToPDFNupState(const _cfPDFToPDFNupParameters ¶m) // {{{ - : param(param), - in_pages(0), out_pages(0), - nup(param.nupX * param.nupY), - subpage(nup) -{ - DEBUG_assert((param.nupX > 0) && (param.nupY > 0)); -} -// }}} - -void -_cfPDFToPDFNupState::reset() // {{{ -{ - in_pages = 0; - out_pages = 0; -// nup = param.nupX * param.nupY; - subpage = nup; -} -// }}} - -void _cfPDFToPDFNupPageEdit::dump(pdftopdf_doc_t *doc) const // {{{ -{ - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: xpos: %f, ypos: %f, scale: %f", - xpos, ypos, scale); - sub.dump(doc); -} -// }}} - -std::pair -_cfPDFToPDFNupState::convert_order(int subpage) const // {{{ -{ - int subx, suby; - if (param.first==pdftopdf_axis_e::X) - { - subx = subpage % param.nupX; - suby = subpage / param.nupX; - } - else - { - subx = subpage / param.nupY; - suby = subpage % param.nupY; - } - - subx = (param.nupX - 1) * (param.xstart + 1) / 2 - param.xstart * subx; - suby = (param.nupY - 1) * (param.ystart + 1) / 2 - param.ystart * suby; - - return (std::make_pair(subx, suby)); -} -// }}} - -static inline float -lin(pdftopdf_position_e pos, - float size) // {{{ -{ - if (pos == -1) - return (0); - else if (pos == 0) - return (size / 2); - else if (pos == 1) - return (size); - return (size * (pos + 1) / 2); -} -// }}} - -void -_cfPDFToPDFNupState::calculate_edit(int subx, - int suby, - _cfPDFToPDFNupPageEdit &ret) const // {{{ -{ - // dimensions of a "nup cell" - const float width = param.width / param.nupX, - height = param.height / param.nupY; - - // first calculate only for bottom-left corner - ret.xpos = subx * width; - ret.ypos = suby * height; - - const float scalex = width / ret.sub.width, - scaley = height / ret.sub.height; - float subwidth = ret.sub.width * scaley, - subheight = ret.sub.height * scalex; - - // TODO? if ((!fitPlot) && (ret.scale > 1)) ret.scale = 1.0; - if (scalex > scaley) - { - ret.scale = scaley; - subheight = height; - ret.xpos += lin(param.xalign, width-subwidth); - } - else - { - ret.scale = scalex; - subwidth = width; - ret.ypos += lin(param.yalign, height-subheight); - } - - ret.sub.left = ret.xpos; - ret.sub.bottom = ret.ypos; - ret.sub.right = ret.sub.left + subwidth; - ret.sub.top = ret.sub.bottom + subheight; -} -// }}} - -bool -_cfPDFToPDFNupState::mext_page(float in_width, - float in_height, - _cfPDFToPDFNupPageEdit &ret) // {{{ -{ - in_pages ++; - subpage ++; - if (subpage >= nup) - { - subpage = 0; - out_pages ++; - } - - ret.sub.width = in_width; - ret.sub.height = in_height; - - auto sub = convert_order(subpage); - calculate_edit(sub.first, sub.second, ret); - - return (subpage == 0); -} -// }}} - - -static std::pair -parsePosition(char a, - char b) // {{{ returns ,CENTER(0) on invalid -{ - a |= 0x20; // make lowercase - b |= 0x20; - if ((a == 'l') && (b == 'r')) - return (std::make_pair(pdftopdf_axis_e::X, pdftopdf_position_e::LEFT)); - else if ((a == 'r') && (b == 'l')) - return (std::make_pair(pdftopdf_axis_e::X, pdftopdf_position_e::RIGHT)); - else if ((a == 't') && (b == 'b')) - return (std::make_pair(pdftopdf_axis_e::Y, pdftopdf_position_e::TOP)); - else if ((a == 'b') && (b == 't')) - return (std::make_pair(pdftopdf_axis_e::Y, pdftopdf_position_e::BOTTOM)); - return (std::make_pair(pdftopdf_axis_e::X, pdftopdf_position_e::CENTER)); -} -// }}} - -bool -_cfPDFToPDFParseNupLayout(const char *val, - _cfPDFToPDFNupParameters &ret) // {{{ -{ - DEBUG_assert(val); - auto pos0 = parsePosition(val[0], val[1]); - if (pos0.second == CENTER) - return (false); - auto pos1 = parsePosition(val[2], val[3]); - if ((pos1.second == CENTER) || (pos0.first == pos1.first)) - return (false); - - ret.first = pos0.first; - if (ret.first == pdftopdf_axis_e::X) - { - ret.xstart = pos0.second; - ret.ystart = pos1.second; - } - else - { - ret.xstart = pos1.second; - ret.ystart = pos0.second; - } - - return (val[4] == 0); // everything seen? -} -// }}} diff --git a/cupsfilters/pdftopdf/pdfio-cm-private.h b/cupsfilters/pdftopdf/pdfio-cm-private.h new file mode 100644 index 000000000..6d2a0558e --- /dev/null +++ b/cupsfilters/pdftopdf/pdfio-cm-private.h @@ -0,0 +1,19 @@ +// +// Copyright 2024 Uddhav Phatak +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#ifndef _CUPS_FILTERS_PDFTOPDF_PDFIO_CM_H_ +#define _CUPS_FILTERS_PDFTOPDF_PDFIO_CM_H_ + +#include + +bool _cfPDFToPDFHasOutputIntent(pdfio_file_t *pdf); +void _cfPDFToPDFAddOutputIntent(pdfio_file_t *pdf, const char *filename); + +void _cfPDFToPDFAddDefaultRGB(pdfio_file_t *pdf, pdfio_obj_t *icc_obj); +pdfio_obj_t* _cfPDFToPDFSetDefaultICC(pdfio_file_t *pdf, const char *filename); + +#endif // !_CUPS_FILTERS_PDFTOPDF_PDFIO_CM_H_ diff --git a/cupsfilters/pdftopdf/pdfio-cm.c b/cupsfilters/pdftopdf/pdfio-cm.c new file mode 100644 index 000000000..b258bc7ca --- /dev/null +++ b/cupsfilters/pdftopdf/pdfio-cm.c @@ -0,0 +1,118 @@ +// +// Copyright 2024 Uddhav Phatak +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "pdfio-cm-private.h" +#include +#include "cupsfilters/debug-internal.h" +#include +#include +#include +#include +#include +#include +#include + +bool +_cfPDFToPDFHasOutputIntent(pdfio_file_t *pdf) // {{{ +{ + pdfio_dict_t *catalog = pdfioFileGetCatalog(pdf); + if(!pdfioDictGetArray(catalog, "OutputIntents")) + return false; + return true; +} +// }}} + +void +_cfPDFToPDFAddOutputIntent(pdfio_file_t *pdf, + const char *filename) // {{{ +{ + pdfio_obj_t *outicc = pdfioFileCreateICCObjFromFile(pdf, filename, 4); + + pdfio_dict_t *intent = pdfioDictCreate(pdf); + pdfioDictSetName(intent, "Type", "OutputIntent"); + pdfioDictSetName(intent, "S", "GTS_PDFX"); + pdfioDictSetString(intent, "OutputCondition", "(Commercial and specialty printing)"); + pdfioDictSetString(intent, "Info", "(none)"); + pdfioDictSetString(intent, "OutputConditionIdentifier", "(CGATS TR001i)"); + pdfioDictSetString(intent, "RegistryName", "(http://www.color.orgi)"); + pdfioDictSetObj(intent, "DestOutputProfile", outicc); + + pdfio_dict_t *catalog = pdfioFileGetCatalog(pdf); + pdfio_array_t *outputIntents = pdfioDictGetArray(catalog, "OutputIntents"); + if (!outputIntents) + { + outputIntents = pdfioArrayCreate(pdf); + pdfioDictSetArray(catalog, "OutputIntents", outputIntents); + } + + pdfioArrayAppendDict(outputIntents, intent); +} +// }}} + +// +// for color management: +// Use /DefaultGray, /DefaultRGB, /DefaultCMYK ... from *current* resource +// dictionary ... +// i.e. set +// /Resources << +// /ColorSpace << --- can use just one indirect ref for this (probably) +// /DefaultRGB [/ICCBased 5 0 R] ... sensible use is sRGB for DefaultRGB, etc. +// >> +// >> +// for every page (what with form /XObjects?) and most importantly RGB +// (leave CMYK, Gray for now, as this is already printer native(?)) +// +// ? also every form XObject, pattern, type3 font, annotation appearance +// stream(=form xobject +X) +// +// ? what if page already defines /Default? -- probably keep! +// +// ? maybe we need to set /ColorSpace in /Images ? +// [gs idea is to just add the /Default-key and then reprocess...] +// + +void +_cfPDFToPDFAddDefaultRGB(pdfio_file_t *pdf, + pdfio_obj_t *icc_obj) // {{{ +{ + pdfio_array_t *icc_array = pdfioArrayCreate(pdf); + + pdfioArrayAppendName(icc_array, "ICCBased"); + pdfioArrayAppendObj(icc_array, icc_obj); + + size_t num_pages = pdfioFileGetNumPages(pdf); + for (size_t i = 0; i < num_pages; i++) + { + pdfio_obj_t *page_obj = pdfioFileGetPage(pdf, i + 1); + pdfio_dict_t *page_dict = pdfioObjGetDict(page_obj); + + pdfio_dict_t *rdict = pdfioDictGetDict(page_dict, "Resources"); + if (!rdict) + { + rdict = pdfioDictCreate(pdf); + pdfioDictSetDict(page_dict, "Resources", rdict); + } + + pdfio_dict_t *cdict = pdfioDictGetDict(rdict, "ColorSpace"); + if (!cdict) + { + cdict = pdfioDictCreate(pdf); + pdfioDictSetDict(rdict, "ColorSpace", cdict); + } + + pdfioDictSetArray(cdict, "DefaultRGB", icc_array); + } +} +// }}} + +pdfio_obj_t* +_cfPDFToPDFSetDefaultICC(pdfio_file_t *pdf, + const char *filename) // {{{ +{ + return pdfioFileCreateICCObjFromFile(pdf, filename, 3); +} +// }}} diff --git a/cupsfilters/pdftopdf/pdfio-pdftopdf-private.h b/cupsfilters/pdftopdf/pdfio-pdftopdf-private.h new file mode 100644 index 000000000..02bb627d4 --- /dev/null +++ b/cupsfilters/pdftopdf/pdfio-pdftopdf-private.h @@ -0,0 +1,47 @@ +// +// Copyright 2024 Uddhav Phatak +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "pptypes-private.h" +#include +#include +#include + +#ifndef _CUPS_FILTERS_PDFTOPDF_PDFIO_PDFTOPDF_H +#define _CUPS_FILTERS_PDFTOPDF_PDFIO_PDFTOPDF_H + +// helper functions + +_cfPDFToPDFPageRect _cfPDFToPDFGetBoxAsRect(pdfio_rect_t *box); +pdfio_rect_t* _cfPDFToPDFGetRectAsBox(_cfPDFToPDFPageRect *rect); + +// Note that PDF specification is CW, but our Rotation is CCW +pdftopdf_rotation_e _cfPDFToPDFGetRotate(pdfio_obj_t *page); +double _cfPDFToPDFMakeRotate(pdftopdf_rotation_e rot); + +double _cfPDFToPDFGetUserUnit(pdfio_obj_t *page); + +// PDF CTM +typedef struct { + double ctm[6]; +} _cfPDFToPDFMatrix; + +void _cfPDFToPDFMatrix_init(_cfPDFToPDFMatrix *matrix); // identity +void _cfPDFToPDFMatrix_init_with_array(_cfPDFToPDFMatrix *matrix, pdfio_array_t *array); + +void _cfPDFToPDFMatrix_rotate(_cfPDFToPDFMatrix *matrix, pdftopdf_rotation_e rot); +void _cfPDFToPDFMatrix_rotate_move(_cfPDFToPDFMatrix *matrix, pdftopdf_rotation_e rot, double width, double height); +void _cfPDFToPDFMatrix_rotate_rad(_cfPDFToPDFMatrix *matrix, double rad); + +void _cfPDFToPDFMatrix_translate(_cfPDFToPDFMatrix *matrix, double tx, double ty); +void _cfPDFToPDFMatrix_scale(_cfPDFToPDFMatrix *matrix, double sx, double sy); + +void _cfPDFToPDFMatrix_multiply(_cfPDFToPDFMatrix *lhs, const _cfPDFToPDFMatrix *rhs); + +void _cfPDFToPDFMatrix_get(const _cfPDFToPDFMatrix *matrix, double *array); +void _cfPDFToPDFMatrix_get_string(const _cfPDFToPDFMatrix *matrix, char *buffer, size_t bufsize); + +#endif // !_CUPS_FILTERS_PDFTOPDF_PDFIO_PDFTOPDF_H diff --git a/cupsfilters/pdftopdf/pdfio-pdftopdf-processor.c b/cupsfilters/pdftopdf/pdfio-pdftopdf-processor.c new file mode 100644 index 000000000..bc1459588 --- /dev/null +++ b/cupsfilters/pdftopdf/pdfio-pdftopdf-processor.c @@ -0,0 +1,1143 @@ +// +// Copyright 2024 Uddhav Phatak +#include +#include +#include +#include "pdfio.h" // Replace with appropriate PDFIO headers +#include "pdfio-content.h" +#include "pdfio-pdftopdf-private.h" +#include "pdfio-tools-private.h" +#include "pdfio-xobject-private.h" +#include "processor.h" +#include "pdfio-cm-private.h" + +#define DEBUG_assert(x) do { if (!(x)) abort(); } while (0) + +//map definition +unsigned int +hash(const char *key) // {{{ +{ + unsigned int hash = 0; + while (*key) { + hash = (hash << 5) + *key++; + } + return hash % HASH_TABLE_SIZE; +} +// }}} + +// Initialize a new hash table +HashTable* +hashCreate_hash_table() // {{{ +{ + HashTable *table = malloc(sizeof(HashTable)); + for (int i = 0; i < HASH_TABLE_SIZE; i++) + { + table->buckets[i] = NULL; + } + table->count = 0; // Initialize the count of filled elements + return table; +} +// }}} + +// Create a new key-value pair +KeyValuePair* +create_key_value_pair(const char *key, pdfio_obj_t *value) // {{{ +{ + KeyValuePair *new_pair = malloc(sizeof(KeyValuePair)); + new_pair->key = strdup(key); // Duplicate the key string + new_pair->value = value; + new_pair->next = NULL; + return new_pair; +} +// }}} + +// Insert a key-value pair into the hash table +void +hashInsert(HashTable *table, const char *key, pdfio_obj_t *value) // {{{ +{ + unsigned int index = hash(key); + KeyValuePair *new_pair = create_key_value_pair(key, value); + + // Handle collisions using chaining (linked list) + if (table->buckets[index] == NULL) + { + table->buckets[index] = new_pair; + } + else + { + KeyValuePair *current = table->buckets[index]; + while (current->next != NULL) + { + current = current->next; + } + current->next = new_pair; + } + table->count++; // Increment the count of filled elements +} +// }}} + +// Retrieve a value by key from the hash table +pdfio_obj_t* +hashGet(HashTable *table, const char *key) // {{{ +{ + unsigned int index = hash(key); + KeyValuePair *current = table->buckets[index]; + + while (current != NULL) { + if (strcmp(current->key, key) == 0) { + return current->value; + } + current = current->next; + } + return NULL; // Key not found +} +// }}} + +// Get the number of elements currently filled in the hash table +int +hashGet_filled_count(HashTable *table) // {{{ +{ + return table->count; +} +// }}} + +// Free the hash table +void +hashFree_hash_table(HashTable *table) // {{{ +{ + for (int i = 0; i < HASH_TABLE_SIZE; i++) + { + KeyValuePair *current = table->buckets[i]; + while (current != NULL) + { + KeyValuePair *tmp = current; + current = current->next; + free(tmp->key); + free(tmp); + } + } + free(table); +} +// }}} + +// main code starts + +// Use: append_debug_box(content, &pe.sub, xpos, ypos); +static void +append_debug_box(char *content, + const _cfPDFToPDFPageRect *box, + float xshift, float yshift) // {{{ +{ + char buf[1024]; + snprintf(buf, sizeof(buf), + "q 1 w 0.1 G\n %f %f m %f %f l S \n %f %f m %f %f l S \n %f %f %f %f re S Q\n", + box->left + xshift, box->bottom + yshift, + box->right + xshift, box->top + yshift, + box->right + xshift, box->bottom + yshift, + box->left + xshift, box->top + yshift, + box->left + xshift, box->bottom + yshift, + box->right - box->left, box->top - box->bottom); + + size_t new_size = strlen(content) + strlen(buf) + 1; + + char *new_content = realloc(content, new_size); + if (!new_content) { + fprintf(stderr, "Memory allocation failed\n"); + return; + } + + content = new_content; // Update the pointer to the new allocated memory + + strcat(content, buf); +} +// }}} + +void +_cfPDFToPDFPageHandle_existingMode(_cfPDFToPDFPageHandle *handle, + pdfio_obj_t *page, + int orig_no) // {{{ +{ + handle->page = page; + + if (orig_no == -1) + handle->no = -1; // Default value handling + else + handle->no = orig_no; + + handle->rotation = ROT_0; + + handle->xobjs = NULL; + handle->xobjs->count = 0; + handle->content = NULL; +} +// }}} + +void +_cfPDFToPDFPageHandle_create_newMode(_cfPDFToPDFPageHandle *handle, + pdfio_file_t *pdf, + float width, float height) // {{{ +{ + handle->no = 0; + handle->rotation = ROT_0; + handle->xobjs = hashCreate_hash_table(); + + handle->content = strdup("q\n"); // Allocate and copy content string + + pdfio_dict_t *pageDict = pdfioDictCreate(pdf); + + pdfio_rect_t *media_box = _cfPDFToPDFMakeBox(0, 0, width, height); + + pdfio_dict_t *resources_dict = pdfioDictCreate(pdf); + pdfioDictClear(resources_dict, "XObject"); + + pdfioDictSetName(pageDict, "Type", "Page"); + pdfioDictSetRect(pageDict, "MediaBox", media_box); + pdfioDictSetDict(pageDict, "Resources", resources_dict); + + // Add /Resources dictionary with empty /XObject entry + pdfio_dict_t *resource_dict = pdfioDictCreate(pdf); + pdfioDictClear(resource_dict, "XObject"); + pdfioDictSetDict(pageDict, "Resources", resource_dict); + + pdfio_stream_t *content_stream = pdfioFileCreatePage(pdf, pageDict); + pdfioStreamWrite(content_stream, handle->content, strlen(handle->content)); + pdfioStreamClose(content_stream); +} +// }}} + +void +_cfPDFToPDFPageHandle_destroy(_cfPDFToPDFPageHandle *handle) // {{{ +{ + free(handle->xobjs); + free(handle->content); +} +// }}} + +bool +_cfPDFToPDFPageHandle_is_existing(_cfPDFToPDFPageHandle *handle) // {{{ +{ + return (handle->content == NULL || + strlen((const char *)handle->content) == 0); +} +// }}} + + +_cfPDFToPDFPageRect +_cfPDFToPDFPageHandle_get_rect(const _cfPDFToPDFPageHandle *handle) // {{{ +{ + pdfio_rect_t trimBox = _cfPDFToPDFGetTrimBox(handle->page); + _cfPDFToPDFPageRect ret = _cfPDFToPDFGetBoxAsRect(&trimBox); + _cfPDFToPDFPageRect_translate(&ret, -ret.left, -ret.bottom); + _cfPDFToPDFPageRect_rotate_move(&ret, _cfPDFToPDFGetRotate(handle->page), ret.width, ret.height); + _cfPDFToPDFPageRect_scale(&ret, _cfPDFToPDFGetUserUnit(handle->page)); + + return (ret); +} +// }}} + +pdfio_obj_t* +_cfPDFToPDFPageHandle_get(_cfPDFToPDFPageHandle *handle) // {{{ +{ + pdfio_dict_t *resources, *contents_dict; + pdfio_stream_t *contents_stream; + pdfio_obj_t *ret = handle->page; + + if (!_cfPDFToPDFPageHandle_is_existing(handle)) + { + resources = pdfioDictGetDict(pdfioObjGetDict(ret), "Resources"); + if (resources) + { + char name_buffer[handle->xobjs->count]; + int xobj_index = 0; + for (int i = 0; i < HASH_TABLE_SIZE && xobj_index < handle->xobjs->count; i++) + { + KeyValuePair *current = handle->xobjs->buckets[i]; + + while (current != NULL) + { + snprintf(name_buffer, sizeof(name_buffer), "/X%d", xobj_index + 1); + pdfioDictSetObj(resources, name_buffer, current->value); + current = current->next; + xobj_index++; + } + } + pdfioDictSetDict(resources, "XObject", resources); + } + + contents_stream = pdfioPageOpenStream(ret, 0, true); + if (contents_stream) + { + pdfioStreamPuts(contents_stream, "Q\n"); + pdfioStreamClose(contents_stream); + } + + contents_dict = pdfioDictGetDict(pdfioObjGetDict(ret), "Contents"); + if (contents_dict) + { + pdfioDictClear(contents_dict, "Filter"); + pdfioDictClear(contents_dict, "DecodeParms"); + } + + pdfioDictSetNumber(pdfioObjGetDict(ret), "/Rotate", _cfPDFToPDFMakeRotate(handle->rotation)); + } + else + { + pdftopdf_rotation_e rot = pdftopdf_rotation_add(_cfPDFToPDFGetRotate(handle->page), + handle->rotation); + pdfioDictSetNumber(pdfioObjGetDict(handle->page), "Rotate", rot); + } + + handle->page = NULL; + return ret; +} +// }}} + +static _cfPDFToPDFPageRect +ungetRect(_cfPDFToPDFPageRect rect, + const _cfPDFToPDFPageHandle *ph, + pdftopdf_rotation_e rotation, + pdfio_obj_t *page) // {{{ +{ + + _cfPDFToPDFPageRect pg1 = _cfPDFToPDFPageHandle_get_rect(ph); + pdfio_rect_t TrimBox = _cfPDFToPDFGetTrimBox(page); + _cfPDFToPDFPageRect pg2 = _cfPDFToPDFGetBoxAsRect(&TrimBox); + rect.width = pg1.width; + rect.height = pg1.height; + _cfPDFToPDFPageRect_rotate_move(&rect, pdftopdf_rotation_neg(_cfPDFToPDFGetRotate(page)), pg1.width, pg1.height); + + _cfPDFToPDFPageRect_scale(&rect, 1.0/_cfPDFToPDFGetUserUnit(page)); + _cfPDFToPDFPageRect_translate(&rect, pg2.left, pg2.bottom); + + return rect; +} +// }}} + +void +_cfPDFToPDFPageHandle_add_border_rect(_cfPDFToPDFPageHandle *handle, + pdfio_file_t *pdf, + const _cfPDFToPDFPageRect givenRect, + pdftopdf_border_type_e border, + float fscale) // {{{ +{ + double lw = (border & THICK) ? 0.5 : 0.24; + double line_width = lw * fscale; + double margin = 2.25 * fscale; + + _cfPDFToPDFPageRect rect = ungetRect(givenRect, handle, handle->rotation, handle->page); + + char boxcmd[1024]; + char buffer[64]; + + snprintf(boxcmd, sizeof(boxcmd), "q\n"); + + sprintf(buffer, "%.2f", line_width); + strncat(boxcmd, buffer, sizeof(boxcmd) - strlen(boxcmd) - 1); + strncat(boxcmd, " w 0 G \n", sizeof(boxcmd) - strlen(boxcmd) - 1); + + sprintf(buffer, "%.2f", rect.left + margin); + strncat(boxcmd, buffer, sizeof(boxcmd) - strlen(boxcmd) - 1); + strncat(boxcmd, " ", sizeof(boxcmd) - strlen(boxcmd) - 1); + + sprintf(buffer, "%.2f", rect.bottom + margin); + strncat(boxcmd, buffer, sizeof(boxcmd) - strlen(boxcmd) - 1); + strncat(boxcmd, " ", sizeof(boxcmd) - strlen(boxcmd) - 1); + + sprintf(buffer, "%.2f", rect.right - rect.left + 2 * margin); + strncat(boxcmd, buffer, sizeof(boxcmd) - strlen(boxcmd) - 1); + strncat(boxcmd, " ", sizeof(boxcmd) - strlen(boxcmd) - 1); + + sprintf(buffer, "%.2f", rect.top - rect.bottom - 2 * margin); + strncat(boxcmd, buffer, sizeof(boxcmd) - strlen(boxcmd) - 1); + strncat(boxcmd, " re S \n", sizeof(boxcmd) - strlen(boxcmd) - 1); + + if (border & TWO) + { + margin += 2 * fscale; + sprintf(buffer, "%.2f", rect.left + margin); + strncat(boxcmd, buffer, sizeof(boxcmd) - strlen(boxcmd) - 1); + strncat(boxcmd, " ", sizeof(boxcmd) - strlen(boxcmd) - 1); + + sprintf(buffer, "%.2f", rect.bottom + margin); + strncat(boxcmd, buffer, sizeof(boxcmd) - strlen(boxcmd) - 1); + strncat(boxcmd, " ", sizeof(boxcmd) - strlen(boxcmd) - 1); + + sprintf(buffer, "%.2f", rect.right - rect.left - 2 * margin); + strncat(boxcmd, buffer, sizeof(boxcmd) - strlen(boxcmd) - 1); + strncat(boxcmd, " ", sizeof(boxcmd) - strlen(boxcmd) - 1); + + sprintf(buffer, "%.2f", rect.top - rect.bottom - 2 * margin); + strncat(boxcmd, buffer, sizeof(boxcmd) - strlen(boxcmd) - 1); + strncat(boxcmd, " re S \n", sizeof(boxcmd) - strlen(boxcmd) - 1); + } + strncat(boxcmd, "Q\n", sizeof(boxcmd) - strlen(boxcmd) - 1); + +#ifdef DEBUG + const char *pre = "%pdftopdf q\nq\n"; + const char *post = "%pdftopdf Q\nQ\n"; + + pdfio_dict_t *stm1_dict = pdfioDictCreate(pdf); + pdfio_obj_t *stm1_obj = pdfioFileCreateObj(pdf, stm1_dict); + pdfio_stream_t *stm1 = pdfioObjCreateStream(stm1_obj, PDFIO_FILTER_NONE); + if (stm1) + { + pdfioStreamWrite(stm1, pre, strlen(pre)); + pdfioStreamClose(stm1); + } + else + fprintf(stderr, "Failed to create PDF stream for pre content\n"); + + char combined[2048]; + snprintf(combined, sizeof(combined), "%s%s", post, boxcmd); + + pdfio_dict_t *stm2_dict = pdfioDictCreate(pdf); + pdfio_obj_t *stm2_obj = pdfioFileCreateObj(pdf, stm2_dict); + pdfio_stream_t *stm2 = pdfioObjCreateStream(stm2_obj, PDFIO_FILTER_NONE); + if (stm2) + { + pdfioStreamWrite(stm2, combined, strlen(combined)); + pdfioStreamClose(stm2); + } + else + fprintf(stderr, "Failed to create PDF stream for post content\n"); + + +#else + pdfio_dict_t *stm_dict = pdfioDictCreate(pdf); + pdfio_obj_t *stm_obj = pdfioFileCreateObj(pdf, stm_dict); + pdfio_stream_t *stm = pdfioObjCreateStream(stm_obj, PDFIO_FILTER_NONE); + if (stm) + { + pdfioStreamWrite(stm, boxcmd, strlen(boxcmd)); + pdfioStreamClose(stm); + } + else + fprintf(stderr, "Failed to create PDF stream for boxcmd content\n"); +#endif +} +// }}} + +pdftopdf_rotation_e +_cfPDFToPDFPageHandle_crop(_cfPDFToPDFPageHandle *handle, + const _cfPDFToPDFPageRect *cropRect, + pdftopdf_rotation_e orientation, + pdftopdf_rotation_e param_orientation, + pdftopdf_position_e xpos, pdftopdf_position_e ypos, + bool scale, bool autorotate, + pdftopdf_doc_t *doc) // {{{ +{ + pdftopdf_rotation_e save_rotate = _cfPDFToPDFGetRotate(handle->page); + + pdfio_dict_t *pageDict = pdfioObjGetDict(handle->page); + if (orientation == ROT_0 || orientation == ROT_180) + pdfioDictSetNumber(pageDict, "Rotate", _cfPDFToPDFMakeRotate(ROT_90)); + else + pdfioDictSetNumber(pageDict, "Rotate", _cfPDFToPDFMakeRotate(ROT_0)); + + pdfio_rect_t trimBox = _cfPDFToPDFGetTrimBox(handle->page); + _cfPDFToPDFPageRect currpage = _cfPDFToPDFGetBoxAsRect(&trimBox); + + double width = currpage.right - currpage.left; + double height = currpage.top - currpage.bottom; + double pageWidth = cropRect->right - cropRect->left; + double pageHeight = cropRect->top - cropRect->bottom; + double final_w, final_h; + + pdftopdf_rotation_e pageRot = _cfPDFToPDFGetRotate(handle->page); + if ((autorotate && + (((pageRot == ROT_0 || pageRot == ROT_180) && pageWidth <= pageHeight) || + ((pageRot == ROT_90 || pageRot == ROT_270) && pageWidth > pageHeight))) || + (!autorotate && (param_orientation == ROT_90 || param_orientation == ROT_270))) + { + double temp=pageHeight; + pageHeight=pageWidth; + pageWidth=temp; + } + + if (scale) + { + if (width * pageHeight / pageWidth <= height) + { + final_w = width; + final_h = width * pageHeight / pageWidth; + } + else + { + final_w = height * pageWidth / pageHeight; + final_h = height; + } + } + else + { + final_w = pageWidth; + final_h = pageHeight; + } + + if (doc->logfunc) + { + doc->logfunc(doc->logdata, 1, "cfFilterPDFToPDF: After Cropping: %lf %lf %lf %lf", + width, height, final_w, final_h); + } + + double posw = (width - final_w) / 2; + double posh = (height - final_h) / 2; + + if (xpos == LEFT) + posw = 0; + else if (xpos == RIGHT) + posw *= 2; + + if (ypos == TOP) + posh *= 2; + else if (ypos == BOTTOM) + posh = 0; + + currpage.left += posw; + currpage.bottom += posh; + currpage.top = currpage.bottom + final_h; + currpage.right = currpage.left + final_w; + + pdfioDictSetRect(pageDict, "TrimBox", + _cfPDFToPDFMakeBox(currpage.left, currpage.bottom, currpage.right, currpage.top)); + pdfioDictSetNumber(pageDict, "Rotate", _cfPDFToPDFMakeRotate(save_rotate)); + + return _cfPDFToPDFGetRotate(handle->page); +} +// }}} + +bool +_cfPDFToPDFPageHandle_is_landscape(const _cfPDFToPDFPageHandle *handle, + pdftopdf_rotation_e orientation) // {{{ +{ + pdftopdf_rotation_e save_rotate = _cfPDFToPDFGetRotate(handle->page); + pdfio_dict_t *pageDict = pdfioObjGetDict(handle->page); + + if (orientation == ROT_0 || orientation == ROT_180) + pdfioDictSetNumber(pageDict, "Rotate", ROT_90); + else + pdfioDictSetNumber(pageDict, "Rotate", ROT_0); + + // Get the current page dimensions after rotation + pdfio_rect_t trimBox = _cfPDFToPDFGetTrimBox(handle->page); + _cfPDFToPDFPageRect currpage = _cfPDFToPDFGetBoxAsRect(&trimBox); + double width = currpage.right - currpage.left; + double height = currpage.top - currpage.bottom; + + pdfioDictSetNumber(pageDict, "Rotate", save_rotate); + + if (width > height) + return true; + return false; +} +// }}} + +void content_append(char **content, const char *text) +{ + if (!content || !text) + { + fprintf(stderr, "Error: Null pointer passed to content_append.\n"); + return; + } + + // Calculate current size and new size + size_t current_length = *content ? strlen(*content) : 0; + size_t text_length = strlen(text); + size_t new_length = current_length + text_length + 1; // +1 for null terminator + + // Reallocate memory for the new content + char *new_content = realloc(*content, new_length); + if (!new_content) + { + fprintf(stderr, "Error: Memory allocation failed in content_append.\n"); + return; + } + + // Append the text to the newly allocated memory + strcpy(new_content + current_length, text); + + // Update the content pointer + *content = new_content; +} + +void +_cfPDFToPDFPageHandle_add_subpage(_cfPDFToPDFPageHandle *handle, + _cfPDFToPDFPageHandle *sub, + pdfio_file_t *pdf, + float xpos, float ypos, float scale, + const _cfPDFToPDFPageRect *crop) // {{{ +{ + _cfPDFToPDFPageHandle *qsub = (_cfPDFToPDFPageHandle *)sub; + + // Generate xobject name + char xoname[32]; + snprintf(xoname, sizeof(xoname), "/X%d", (qsub->no != -1) ? qsub->no : ++(handle->no)); + + // Handle crop if provided + if (crop) + { + _cfPDFToPDFPageRect pg = _cfPDFToPDFPageHandle_get_rect(qsub); + _cfPDFToPDFPageRect tmp = *crop; + + tmp.width = tmp.right - tmp.left; + tmp.height = tmp.top - tmp.bottom; + pdftopdf_rotation_e tempRotation = _cfPDFToPDFGetRotate(sub->page); + _cfPDFToPDFPageRect_rotate_move(&tmp, pdftopdf_rotation_neg(tempRotation), tmp.width, tmp.height); + + if (pg.width < tmp.width) + pg.right = pg.left + tmp.width; + if (pg.height < tmp.height) + pg.top = pg.bottom + tmp.height; + + _cfPDFToPDFPageRect rect = ungetRect(pg, qsub, ROT_0, qsub->page); + + pdfio_rect_t *trimBox = _cfPDFToPDFMakeBox(rect.left, rect.bottom, rect.right, rect.top); + + pdfio_dict_t *pageDict = pdfioObjGetDict(sub->page); + pdfioDictSetRect(pageDict, "TrimBox", trimBox); + } + + // Apply transformations + _cfPDFToPDFMatrix mtx; + _cfPDFToPDFMatrix_init(&mtx); + _cfPDFToPDFMatrix_translate(&mtx, xpos, ypos); + _cfPDFToPDFMatrix_scale(&mtx, scale, scale); + _cfPDFToPDFMatrix_rotate(&mtx, qsub->rotation); + + if (crop) + { + _cfPDFToPDFMatrix_translate(&mtx, crop->left, crop->bottom); + } + + // Add content + content_append(&handle->content, "q\n "); + + char mtx_buffer[128]; // Adjust buffer size as needed + _cfPDFToPDFMatrix_get_string(&mtx, mtx_buffer, sizeof(mtx_buffer)); + content_append(&handle->content, mtx_buffer); + content_append(&handle->content, " cm\n "); + + if (crop) + { + char crop_cmd[128]; + snprintf(crop_cmd, sizeof(crop_cmd), "0 0 %f %f re W n\n ", + crop->right - crop->left, crop->top - crop->bottom); + content_append(&handle->content, crop_cmd); + } + + content_append(&handle->content, xoname); + content_append(&handle->content, " Do\n"); + content_append(&handle->content, "Q\n"); +} +// }}} + + +void +_cfPDFToPDFPageHandle_mirror(_cfPDFToPDFPageHandle *handle, + pdfio_file_t *pdf) +{ + _cfPDFToPDFPageRect orig = _cfPDFToPDFPageHandle_get_rect(handle); + if(_cfPDFToPDFPageHandle_is_existing(handle)) + { + char xoname[10]; + snprintf(xoname, sizeof(xoname), "/X%d", handle->no); + + pdfio_obj_t *subpage = _cfPDFToPDFPageHandle_get(handle);; + + _cfPDFToPDFPageHandle_create_newMode(handle, pdf, orig.width, orig.height); + hashInsert(handle->xobjs, xoname, _cfPDFToPDFMakeXObject(pdf, subpage)); + + + char temp_content[1024]; + snprintf(temp_content, sizeof(temp_content), "%s Do\n", xoname); + strcat(handle->content, temp_content); + } + + static const char *pre = "%pdftopdf cm\n"; + char mrcmd[100]; + snprintf(mrcmd, sizeof(mrcmd), "-1 0 0 1 %.2f 0 cm\n", orig.right); + + size_t new_len = strlen(pre) + strlen(mrcmd) + strlen(handle->content) + 1; + char *new_content = (char *)malloc(new_len); + snprintf(new_content, new_len, "%s%s%s", pre, mrcmd, handle->content); + + free(handle->content); // Free the old content + handle->content = new_content; +} + + +void +_cfPDFToPDFPageHandle_rotate(_cfPDFToPDFPageHandle *handle, + pdftopdf_rotation_e rot) +{ + handle->rotation = rot; +} + +void +_cfPDFToPDFPageHandle_add_label(_cfPDFToPDFPageHandle *handle, + pdfio_file_t *pdf, + const _cfPDFToPDFPageRect *rect, + const char *label) +{ + + _cfPDFToPDFPageRect rect_mod = ungetRect(*rect, handle, handle->rotation, handle->page); + + if (rect_mod.left > rect_mod.right || rect_mod.bottom > rect_mod.top) + { + fprintf(stderr, "Invalid rectangle dimensions!\n"); + return; + } + pdfio_dict_t *font_dict = pdfioDictCreate(pdf); + pdfioDictSetName(font_dict, "Type", "Font"); + pdfioDictSetName(font_dict, "Subtype", "Type1"); + pdfioDictSetName(font_dict, "Name", "pagelabel-font"); + pdfioDictSetName(font_dict, "BaseFont", "Helvetica"); + + pdfio_obj_t *font_obj = pdfioFileCreateObj(pdf, font_dict); + + pdfio_dict_t *resources = pdfioObjGetDict(handle->page); + if (resources == NULL) + { + resources = pdfioDictCreate(pdf); + pdfioDictSetDict(resources, "Font", pdfioDictCreate(pdf)); + } + + pdfio_dict_t *font_resources = pdfioDictGetDict(resources, "Font"); + if (font_resources == NULL) + { + font_resources = pdfioDictCreate(pdf); + pdfioDictSetDict(resources, "Font", font_resources); + } + pdfioDictSetObj(font_resources, "pagelabel-font", font_obj); + pdfioDictSetDict(resources, "Resources", resources); + + double margin = 2.25; + double height = 12; + + char boxcmd[1024] = "q\n"; + + snprintf(boxcmd + strlen(boxcmd), sizeof(boxcmd) - strlen(boxcmd), + "1 1 1 rg\n%f %f %f %f re f\n", + rect_mod.left + margin, rect_mod.top - height - 2 * margin, + rect_mod.right - rect_mod.left - 2 * margin, height + 2 * margin); + + snprintf(boxcmd + strlen(boxcmd), sizeof(boxcmd) - strlen(boxcmd), + "%f %f %f %f re f\n", + rect_mod.left + margin, rect_mod.bottom + height + margin, + rect_mod.right - rect_mod.left - 2 * margin, height + 2 * margin); + + snprintf(boxcmd + strlen(boxcmd), sizeof(boxcmd) - strlen(boxcmd), + "0 0 0 RG\n%f %f %f %f re S\n", + rect_mod.left + margin, rect_mod.top - height - 2 * margin, + rect_mod.right - rect_mod.left - 2 * margin, height + 2 * margin); + + snprintf(boxcmd + strlen(boxcmd), sizeof(boxcmd) - strlen(boxcmd), + "%f %f %f %f re S\n", + rect_mod.left + margin, rect_mod.bottom + height + margin, + rect_mod.right - rect_mod.left - 2 * margin, height + 2 * margin); + + snprintf(boxcmd + strlen(boxcmd), sizeof(boxcmd) - strlen(boxcmd), + "0 0 0 rg\nBT\n/f1 12 Tf\n%f %f Td\n(%s) Tj\nET\n", + rect_mod.left + 2 * margin, rect_mod.top - height - margin, label); + + snprintf(boxcmd + strlen(boxcmd), sizeof(boxcmd) - strlen(boxcmd), + "BT\n/f1 12 Tf\n%f %f Td\n(%s) Tj\nET\n", + rect_mod.left + 2 * margin, rect_mod.bottom + height + 2 * margin, label); + + strcat(boxcmd, "Q\n"); + + const char *pre = "%pdftopdf q\nq\n"; + char post[256]; + + snprintf(post, sizeof(post), "%%pdftopdf Q\nQ\n%s", boxcmd); + + pdfio_stream_t *stream1 = pdfioPageOpenStream(handle->page, PDFIO_FILTER_FLATE, true); + pdfioStreamPuts(stream1, pre); + pdfioStreamClose(stream1); + + pdfio_stream_t *stream2 = pdfioPageOpenStream(handle->page, PDFIO_FILTER_FLATE, true); + pdfioStreamPuts(stream2, post); + pdfioStreamClose(stream2); + +} + +void +_cfPDFToPDFPageHandle_debug(_cfPDFToPDFPageHandle *handle, + const _cfPDFToPDFPageRect *rect, + float xpos, float ypos) +{ + if (!_cfPDFToPDFPageHandle_is_existing(handle)) + return; + + append_debug_box(handle->content, rect, xpos, ypos); +} + +void _cfPDFToPDF_PDFioProcessor_close_file(_cfPDFToPDF_PDFioProcessor *handle) +{ + if (handle->pdf != NULL) + { + pdfioFileClose(handle->pdf); + handle->pdf = NULL; + } + handle->hasCM = false; +} + + +bool +_cfPDFToPDF_PDFioProcessor_load_file(_cfPDFToPDF_PDFioProcessor *handle, + FILE *f, pdftopdf_doc_t *doc, + pdftopdf_arg_ownership_e take, int flatten_forms) +{ + _cfPDFToPDF_PDFioProcessor_close_file(handle); + + if (!f) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "load_file(NULL, ...) not allowed"); + return false; + } + + handle->pdf = pdfioFileCreate("tempfile", NULL, NULL, NULL, NULL, NULL); + + if (handle->pdf == NULL) + { + if (take == CF_PDFTOPDF_TAKE_OWNERSHIP) + { + fclose(f); + } + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "Failed to open PDF file."); + return false; + } + + switch (take) + { + case CF_PDFTOPDF_WILL_STAY_ALIVE: + break; + + case CF_PDFTOPDF_TAKE_OWNERSHIP: + if (fclose(f) != 0) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "Failed to close file after loading."); + return false; + } + break; + + case CF_PDFTOPDF_MUST_DUPLICATE: + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "CF_PDFTOPDF_MUST_DUPLICATE is not supported."); + return false; + } + + _cfPDFToPDF_PDFioProcessor_start(handle, flatten_forms); + return true; +} + +bool _cfPDFToPDF_PDFioProcessor_load_filename(_cfPDFToPDF_PDFioProcessor *handle, + const char *name, + pdftopdf_doc_t *doc, + int flatten_forms) +{ + _cfPDFToPDF_PDFioProcessor_close_file(handle); + handle->pdf = pdfioFileOpen(name, NULL, NULL, NULL, NULL); + if (!handle->pdf) + { + if (doc->logfunc) doc->logfunc(doc->logdata, 3, + "cfFilterPDFToPDF: load_filename failed: Could not open file %s", + name); + return false; + } + + _cfPDFToPDF_PDFioProcessor_start(handle, flatten_forms); + + return true; +} + + +bool +_cfPDFToPDF_PDFioProcessor_check_print_permissions(_cfPDFToPDF_PDFioProcessor *handle, + pdftopdf_doc_t *doc) +{ + if (!handle->pdf) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "cfFilterPDFToPDF: No PDF loaded(_cfPDFToPDF_PDFioProcessor_new_page)"); + return false; + } + + int permissions = pdfioFileGetPermissions(handle->pdf, NULL); + + if ((permissions & PDFIO_PERMISSION_PRINT_HIGH) || (permissions & PDFIO_PERMISSION_PRINT)) + return true; + + return false; +} + +pdfio_obj_t** +get_all_pages(pdfio_file_t *pdf) +{ + size_t num_pages = pdfioFileGetNumPages(pdf); + pdfio_obj_t **pages = malloc(sizeof(pdfio_obj_t *) * num_pages); + for (size_t i = 0; i < num_pages; i++) + { + pages[i] = pdfioFileGetPage(pdf, i); + } + return pages; +} + + +void +_cfPDFToPDF_PDFioProcessor_start(_cfPDFToPDF_PDFioProcessor *proc, + int flatten_forms) +{ + if (!proc->pdf) + { + fprintf(stderr, "No PDF loaded.\n"); + return; + } + + // Get all pages + + proc->orig_pages = get_all_pages(proc->pdf); + + proc->orig_pages_size = pdfioFileGetNumPages(proc->pdf); + + pdfio_dict_t *root = pdfioFileGetCatalog(proc->pdf); + pdfioDictClear(root, "PageMode"); + pdfioDictClear(root, "Outlines"); + pdfioDictClear(root, "OpenAction"); + pdfioDictClear(root, "PageLabels"); +} + +_cfPDFToPDFPageHandle** +_cfPDFToPDF_PDFioProcessor_get_pages(_cfPDFToPDF_PDFioProcessor *handle, + pdftopdf_doc_t *doc, size_t *out_len) +{ + + _cfPDFToPDFPageHandle **ret = NULL; + + if (handle->orig_pages_size == 0 || handle->orig_pages == NULL) + { + if (doc->logfunc) + { + doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "cfFilterPDFToPDF: No PDF loaded"); + } + *out_len = 0; + return ret; + } + + int len = (int)handle->orig_pages_size; + *out_len = len; + + ret = (_cfPDFToPDFPageHandle **)malloc(len * sizeof(_cfPDFToPDFPageHandle *)); + if (!ret) + { + fprintf(stderr, "Memory allocation failed for pages array\n"); + } + for (int i = 0; i < len; i++) + { + ret[i] = (_cfPDFToPDFPageHandle *)malloc(sizeof(_cfPDFToPDFPageHandle)); + if (!ret[i]) + { + fprintf(stderr, "Memory allocation failed for page handle %d\n", i + 1); + for (int j = 0; j < i; j++) + { + free(ret[j]); + } + free(ret); + } + + ret[i]->page = handle->orig_pages[i]; + //ret[i]->orig_pages_size = i + 1; + } + + return ret; +} + +_cfPDFToPDFPageHandle* +_cfPDFToPDF_PDFioProcessor_new_page(_cfPDFToPDF_PDFioProcessor *handle, + float width, float height, + pdftopdf_doc_t *doc) +{ + if (!handle->pdf) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "cfFilterPDFToPDF: No PDF loaded(_cfPDFToPDF_PDFioProcessor_new_page)"); + return NULL; + } + _cfPDFToPDFPageHandle *page_handle = (_cfPDFToPDFPageHandle *)malloc(sizeof(_cfPDFToPDFPageHandle)); + + _cfPDFToPDFPageHandle_create_newMode(page_handle, handle->pdf, width, height); + + return page_handle; +} + +void +_cfPDFToPDF_PDFioProcessor_multiply(_cfPDFToPDF_PDFioProcessor *handle, + int copies, bool collate) +{ + int num_pages=pdfioFileGetNumPages(handle->pdf); + + pdfio_obj_t **pages = (pdfio_obj_t **)malloc(num_pages * sizeof(pdfio_obj_t *)); + + for (int i = 0; i < num_pages; i++) + { + pages[i] = pdfioFileGetPage(handle->pdf, i); + } + + if (collate) { + for (int iA = 1; iA < copies; iA++) + { + for (int iB = 0; iB < num_pages; iB++) + { + pdfioPageCopy(handle->pdf, pages[iB]); + } + } + } + + else + { + for (int iB = 0; iB < num_pages; iB++) + { + for (int iA = 1; iA < copies; iA++) + { + pdfioPageCopy(handle->pdf, pages[iB]); + } + } + } +} + +void +_cfPDFToPDF_PDFioProcessor_auto_rotate_all(_cfPDFToPDF_PDFioProcessor *handle, + bool dst_lscape, + pdftopdf_rotation_e normal_landscape) +{ + const int len = handle->orig_pages_size; + + for (int iA = 0; iA < len; iA ++) + { + pdfio_obj_t *page = handle->orig_pages[iA]; + + pdftopdf_rotation_e src_rot = _cfPDFToPDFGetRotate(page); + + pdfio_rect_t trimBox = _cfPDFToPDFGetTrimBox(page); + _cfPDFToPDFPageRect ret = _cfPDFToPDFGetBoxAsRect(&trimBox); + + _cfPDFToPDFPageRect_rotate_move(&ret, src_rot, ret.width, ret.height); + + const bool src_lscape = (ret.width > ret.height); + + if (src_lscape != dst_lscape) + { + pdftopdf_rotation_e rotation = normal_landscape; + + pdfio_dict_t *pageDict = pdfioObjGetDict(page); + pdfioDictSetNumber(pageDict, "Rotate", + _cfPDFToPDFMakeRotate(src_rot + rotation)); + } + } +} + +void +_cfPDFToPDF_PDFioProcessor_add_cm(_cfPDFToPDF_PDFioProcessor *handle, + const char *defaulticc, const char *outputicc) +{ + if (_cfPDFToPDFHasOutputIntent(handle->pdf)) + return; + + fprintf(stderr, "han, iske andar aaya "); + pdfio_obj_t *srcicc = _cfPDFToPDFSetDefaultICC(handle->pdf, defaulticc); + _cfPDFToPDFAddDefaultRGB(handle->pdf, srcicc); + _cfPDFToPDFAddOutputIntent(handle->pdf, outputicc); + + handle->hasCM = true; +} + +void +_cfPDFToPDF_PDFioProcessor_set_comments(_cfPDFToPDF_PDFioProcessor *handle, + char **comments, int num_comments) +{ + if (handle->extraheader) + { + free(handle->extraheader); + } + + handle->extraheader = (char *)malloc(1); + handle->extraheader[0] = '\0'; + + int total_length = 0; + for (int i = 0; i < num_comments; i++) + { + total_length += strlen(comments[i]) + 1; + } + + handle->extraheader = (char *)realloc(handle->extraheader, total_length + 1); + + for (int i = 0; i < num_comments; i++) + { + strcat(handle->extraheader, comments[i]); + strcat(handle->extraheader, "\n"); + } +} + +void +_cfPDFToPDF_PDFioProcessor_emit_file(_cfPDFToPDF_PDFioProcessor *handle, + FILE *f, pdftopdf_doc_t *doc, + pdftopdf_arg_ownership_e take) +{ +} + + +void +_cfPDFToPDF_PDFioProcessor_emit_filename(_cfPDFToPDF_PDFioProcessor *handle, + const char *output_filename, pdftopdf_doc_t *doc) +{ + pdfio_file_t *output_pdf = pdfioFileCreate(output_filename, NULL, NULL, NULL, NULL, NULL); + if (!output_pdf) { + fprintf(stderr, "Failed to create output PDF file: %s\n", output_filename); + } + size_t num_pages = pdfioFileGetNumPages(handle->pdf); + + for (size_t i = 0; i < num_pages; i++) + { + pdfio_obj_t *input_page = pdfioFileGetPage(handle->pdf, i); + if (!input_page) + { + fprintf(stderr, "Failed to get page %zu from input PDF\n", i); + pdfioFileClose(output_pdf); + } + + if (!pdfioPageCopy(output_pdf, input_page)) + { + fprintf(stderr, "Failed to copy object %zu to output PDF\n", i); + pdfioFileClose(output_pdf); + } + } + + if (!pdfioFileClose(output_pdf)) + { + fprintf(stderr, "Failed to save output PDF file\n"); + } +} + +bool +_cfPDFToPDF_PDFioProcessor_has_acro_form(_cfPDFToPDF_PDFioProcessor *handle) +{ + if (!handle->pdf) + { + return false; + } + + pdfio_dict_t *root = pdfioFileGetCatalog(handle->pdf); + + if (!pdfioDictGetDict(root, "AcroForm")) + return false; + return true; +} diff --git a/cupsfilters/pdftopdf/pdfio-pdftopdf.c b/cupsfilters/pdftopdf/pdfio-pdftopdf.c new file mode 100644 index 000000000..4f1d75c87 --- /dev/null +++ b/cupsfilters/pdftopdf/pdfio-pdftopdf.c @@ -0,0 +1,246 @@ +// +// Copyright 2024 Uddhav Phatak +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "pdfio-pdftopdf-private.h" +#include "pdfio-tools-private.h" +#include "cupsfilters/debug-internal.h" + +#include +#include + +_cfPDFToPDFPageRect +_cfPDFToPDFGetBoxAsRect(pdfio_rect_t *box) // {{{ +{ + _cfPDFToPDFPageRect ret; + + ret.left = box->x1; + ret.bottom = box->y1; + ret.right = box->x2; + ret.top = box->y2; + + ret.width = ret.right - ret.left; + ret.height = ret.top - ret.bottom; + + return ret; +} +// }}} + +pdfio_rect_t* +_cfPDFToPDFGetRectAsBox(_cfPDFToPDFPageRect *rect) // {{{ +{ + return (_cfPDFToPDFMakeBox(rect->left, rect->bottom, rect->right, rect->top)); +} +// }}} + +pdftopdf_rotation_e +_cfPDFToPDFGetRotate(pdfio_obj_t *page) // {{{ +{ + pdfio_dict_t *pageDict = pdfioObjGetDict(page); + double rotate = pdfioDictGetNumber(pageDict, "Rotate"); + if (!rotate) + return ROT_0; + + double rot = fmod(rotate, 360.0); + if (rot < 0) + rot += 360.0; + + if (rot == 90.0) // CW + return ROT_270; // CCW + else if (rot == 180.0) + return ROT_180; + else if (rot == 270.0) + return ROT_90; + else if (rot != 0.0) + fprintf(stderr, "Unexpected /Rotate value: %f\n", rot); + + return ROT_0; +} +// }}} + +double +_cfPDFToPDFGetUserUnit(pdfio_obj_t *page) // {{{ +{ + pdfio_dict_t *pageDict = pdfioObjGetDict(page); + double userUnit = pdfioDictGetNumber(pageDict, "UserUnit"); + if(!userUnit) + return 1.0; + return userUnit; +} +// }}} + +double +_cfPDFToPDFMakeRotate(pdftopdf_rotation_e rot) // {{{ +{ + switch (rot) + { + case ROT_0: + return 0; + case ROT_90: + return 270.0; + case ROT_180: + return 180.0; + case ROT_270: + return 90.0; + default: + fprintf(stderr, "Bad rotation value\n"); + return NAN; + } +} +// }}} + +void +_cfPDFToPDFMatrix_init(_cfPDFToPDFMatrix *matrix) // {{{ +{ + matrix->ctm[0] = 1.0; + matrix->ctm[1] = 0.0; + matrix->ctm[2] = 0.0; + matrix->ctm[3] = 1.0; + matrix->ctm[4] = 0.0; + matrix->ctm[5] = 0.0; +} +// }}} + +void +_cfPDFToPDFMatrix_init_with_array(_cfPDFToPDFMatrix *matrix, + pdfio_array_t *array) // {{{ +{ + if (pdfioArrayGetSize(array) != 6) + fprintf(stderr, "Not a ctm matrix"); + + for (int iA = 0; iA < 6; iA ++) + matrix->ctm[iA] = pdfioArrayGetNumber(array, iA); +} +// }}} + +void +_cfPDFToPDFMatrix_rotate(_cfPDFToPDFMatrix *matrix, + pdftopdf_rotation_e rot) // {{{ +{ + double tmp[6]; + memcpy(tmp, matrix->ctm, sizeof(tmp)); + + switch (rot) + { + case ROT_0: + break; + case ROT_90: + matrix->ctm[0] = tmp[2]; + matrix->ctm[1] = tmp[3]; + matrix->ctm[2] = -tmp[0]; + matrix->ctm[3] = -tmp[1]; + break; + case ROT_180: + matrix->ctm[0] = -tmp[0]; + matrix->ctm[3] = -tmp[3]; + break; + case ROT_270: + matrix->ctm[0] = -tmp[2]; + matrix->ctm[1] = -tmp[3]; + matrix->ctm[2] = tmp[0]; + matrix->ctm[3] = tmp[1]; + break; + default: + DEBUG_assert(0); + } +} +// }}} + +void +_cfPDFToPDFMatrix_rotate_move(_cfPDFToPDFMatrix *matrix, + pdftopdf_rotation_e rot, + double width, double height) // {{{ +{ + _cfPDFToPDFMatrix_rotate(matrix, rot); + switch (rot) + { + case ROT_0: + break; + case ROT_90: + _cfPDFToPDFMatrix_translate(matrix, width, 0); + break; + case ROT_180: + _cfPDFToPDFMatrix_translate(matrix, width, height); + break; + case ROT_270: + _cfPDFToPDFMatrix_translate(matrix, 0, height); + break; + } +} +// }}} + +void +_cfPDFToPDFMatrix_rotate_rad(_cfPDFToPDFMatrix *matrix, + double rad) // {{{ +{ + _cfPDFToPDFMatrix tmp; + _cfPDFToPDFMatrix_init(&tmp); + + tmp.ctm[0] = cos(rad); + tmp.ctm[1] = sin(rad); + tmp.ctm[2] = -sin(rad); + tmp.ctm[3] = cos(rad); + + _cfPDFToPDFMatrix_multiply(matrix, &tmp); +} +// }}} + +void +_cfPDFToPDFMatrix_translate(_cfPDFToPDFMatrix *matrix, + double tx, double ty) // {{{ +{ + matrix->ctm[4] += matrix->ctm[0] * tx + matrix->ctm[2] * ty; + matrix->ctm[5] += matrix->ctm[1] * tx + matrix->ctm[3] * ty; +} +// }}} + +void +_cfPDFToPDFMatrix_scale(_cfPDFToPDFMatrix *matrix, + double sx, double sy) // {{{ +{ + matrix->ctm[0] *= sx; + matrix->ctm[1] *= sx; + matrix->ctm[2] *= sy; + matrix->ctm[3] *= sy; +} +// }}} + +void +_cfPDFToPDFMatrix_multiply(_cfPDFToPDFMatrix *lhs, + const _cfPDFToPDFMatrix *rhs) // {{{ +{ + double tmp[6]; + memcpy(tmp, lhs->ctm, sizeof(tmp)); + + lhs->ctm[0] = tmp[0] * rhs->ctm[0] + tmp[2] * rhs->ctm[1]; + lhs->ctm[1] = tmp[1] * rhs->ctm[0] + tmp[3] * rhs->ctm[1]; + + lhs->ctm[2] = tmp[0] * rhs->ctm[2] + tmp[2] * rhs->ctm[3]; + lhs->ctm[3] = tmp[1] * rhs->ctm[2] + tmp[3] * rhs->ctm[3]; + + lhs->ctm[4] = tmp[0] * rhs->ctm[4] + tmp[2] * rhs->ctm[5] + tmp[4]; + lhs->ctm[5] = tmp[1] * rhs->ctm[4] + tmp[3] * rhs->ctm[5] + tmp[5]; +} +// }}} + +void +_cfPDFToPDFMatrix_get(const _cfPDFToPDFMatrix *matrix, + double *array) // {{{ +{ + memcpy(array, matrix->ctm, sizeof(double) * 6); +} +// }}} + +void +_cfPDFToPDFMatrix_get_string(const _cfPDFToPDFMatrix *matrix, + char *buffer, size_t bufsize) // {{{ +{ + snprintf(buffer, bufsize, "%f %f %f %f %f %f", + matrix->ctm[0], matrix->ctm[1], matrix->ctm[2], + matrix->ctm[3], matrix->ctm[4], matrix->ctm[5]); + buffer[bufsize - 1] = '\0'; // Ensure null-termination +} +// }}} diff --git a/cupsfilters/pdftopdf/pdfio-tools-private.h b/cupsfilters/pdftopdf/pdfio-tools-private.h new file mode 100644 index 000000000..2f2a4f958 --- /dev/null +++ b/cupsfilters/pdftopdf/pdfio-tools-private.h @@ -0,0 +1,22 @@ +// +// Copyright 2024 Uddhav Phatak +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#ifndef _CUPS_FILTERS_PDFTOPDF_PDFio_TOOLS_H_ +#define _CUPS_FILTERS_PDFTOPDF_PDFio_TOOLS_H_ + +#include + +pdfio_rect_t _cfPDFToPDFGetMediaBox(pdfio_obj_t *page); +pdfio_rect_t _cfPDFToPDFGetCropBox(pdfio_obj_t *page); +pdfio_rect_t _cfPDFToPDFGetBleedBox(pdfio_obj_t *page); +pdfio_rect_t _cfPDFToPDFGetTrimBox(pdfio_obj_t *page); +pdfio_rect_t _cfPDFToPDFGetArtBox(pdfio_obj_t *page); + +pdfio_rect_t* _cfPDFToPDFMakeBox(double x1, double y1, double x2, double y2); + +#endif // !_CUPS_FILTERS_PDFTOPDF_PDFio_TOOLS_H_ + diff --git a/cupsfilters/pdftopdf/pdfio-tools.c b/cupsfilters/pdftopdf/pdfio-tools.c new file mode 100644 index 000000000..af60eb4e3 --- /dev/null +++ b/cupsfilters/pdftopdf/pdfio-tools.c @@ -0,0 +1,79 @@ +// +// Copyright 2024 Uddhav Phatak +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "pdfio-tools-private.h" + +pdfio_rect_t +_cfPDFToPDFGetMediaBox(pdfio_obj_t *page) // {{{ +{ + pdfio_rect_t mediaBox; + pdfio_dict_t *page_dict = pdfioObjGetDict(page); + pdfioDictGetRect(page_dict, "MediaBox", &mediaBox); + return (mediaBox); +} +// }}} + +pdfio_rect_t +_cfPDFToPDFGetCropBox(pdfio_obj_t *page) // {{{ +{ + pdfio_rect_t cropBox; + pdfio_dict_t *page_dict = pdfioObjGetDict(page); + if (!pdfioDictGetRect(page_dict, "CropBox", &cropBox)) + return _cfPDFToPDFGetMediaBox(page); + return cropBox; +} +// }}} + +pdfio_rect_t +_cfPDFToPDFGetBleedBox(pdfio_obj_t *page) // {{{ +{ + pdfio_rect_t bleedBox; + pdfio_dict_t *page_dict = pdfioObjGetDict(page); + if (!pdfioDictGetRect(page_dict, "BleedBox", &bleedBox)) + return _cfPDFToPDFGetCropBox(page); + return bleedBox; +} +// }}} + +pdfio_rect_t +_cfPDFToPDFGetTrimBox(pdfio_obj_t *page) // {{{ +{ + pdfio_rect_t trimBox; + pdfio_dict_t *page_dict = pdfioObjGetDict(page); + if (!pdfioDictGetRect(page_dict, "TrimBox", &trimBox)) + return _cfPDFToPDFGetCropBox(page); + return trimBox; + +} +// }}} + +pdfio_rect_t +_cfPDFToPDFGetArtBox(pdfio_obj_t *page) // {{{ +{ + pdfio_rect_t artBox; + pdfio_dict_t *page_dict = pdfioObjGetDict(page); + if (!pdfioDictGetRect(page_dict, "ArtBox", &artBox)) + return _cfPDFToPDFGetCropBox(page); + return artBox; +} +// }}} + +pdfio_rect_t* +_cfPDFToPDFMakeBox(double x1, + double y1, + double x2, + double y2) // {{{ +{ + pdfio_rect_t *ret = (pdfio_rect_t *)malloc(sizeof(pdfio_rect_t)); + ret->x1 = x1; + ret->y1 = y1; + ret->x2 = x2; + ret->y2 = y2; + + return ret; +} +// }}} diff --git a/cupsfilters/pdftopdf/pdfio-xobject-private.h b/cupsfilters/pdftopdf/pdfio-xobject-private.h new file mode 100644 index 000000000..dacefa3e3 --- /dev/null +++ b/cupsfilters/pdftopdf/pdfio-xobject-private.h @@ -0,0 +1,15 @@ +// +// Copyright 2024 Uddhav Phatak +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#ifndef _CUPS_FILTERS_PDFTOPDF_PDFIO_XOBJECT_H_ +#define _CUPS_FILTERS_PDFTOPDF_PDFIO_XOBJECT_H_ + +#include + +pdfio_obj_t* _cfPDFToPDFMakeXObject(pdfio_file_t *pdf, pdfio_obj_t *page); + +#endif // _CUPS_FILTERS_PDFTOPDF_PDFIO_XOBJECT_H_ diff --git a/cupsfilters/pdftopdf/pdfio-xobject.c b/cupsfilters/pdftopdf/pdfio-xobject.c new file mode 100644 index 000000000..329c14ff9 --- /dev/null +++ b/cupsfilters/pdftopdf/pdfio-xobject.c @@ -0,0 +1,220 @@ +// +// Copyright 2024 Uddhav Phatak +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "pdfio-xobject-private.h" +#include "pdfio-tools-private.h" +#include "pdfio-pdftopdf-private.h" + +#include +#include +#include +#include +#include + +typedef struct +{ + size_t content_count; + pdfio_stream_t **contents; +} CombineFromContents_Provider; + +CombineFromContents_Provider* +CombineFromContents_Provider_new(pdfio_stream_t **contents, + size_t content_count) // {{{ +{ + CombineFromContents_Provider *provider = (CombineFromContents_Provider*)malloc(sizeof(CombineFromContents_Provider)); + + provider->content_count = content_count; + provider->contents = contents; + return provider; +} +// }}} + +void +CombineFromContents_Provider_free(CombineFromContents_Provider *provider) // {{{ +{ + if (provider) + { + free(provider->contents); + free(provider); + } +} +// }}} + +void +CombineFromContents_Provider_provideStreamData(CombineFromContents_Provider *provider, + pdfio_stream_t *pipeline) // {{{ +{ + char buffer[8192]; + size_t bytes; + + for (size_t i = 0; i < provider->content_count; i++) + { + pdfio_stream_t *stream = provider->contents[i]; + while ((bytes = pdfioStreamRead(stream, buffer, sizeof(buffer))) > 0) + { + pdfioStreamWrite(pipeline, buffer, bytes); + } + } +} +// }}} + +// +// To convert a page to an XObject there are several keys to consider: +// +// /Type /Page -> /Type /XObject (/Type optional for XObject) +// -> /Subtype /Form +// -> [/FormType 1] (optional) +// /Parent ? ? R -> remove +// /Resources dict -> copy +// /MediaBox rect [/CropBox /BleedBox /TrimBox /ArtBox] +// -> /BBox (use TrimBox [+ Bleed consideration?], +// with fallback to /MediaBox) +// note that /BBox is in *Form Space*, see /Matrix! +// [/BoxColorInfo dict] (used for guidelines that may be shown by viewer) +// -> ignore/remove +// [/Contents asfd] -> concatenate into stream data of the XObject +// (page is a dict, XObject a stream) +// +// [/Rotate 90] ... must be handled (either use CTM where XObject is +// /used/ -- or set /Matrix) +// [/UserUnit] (PDF 1.6) -> to /Matrix ? -- it MUST be handled. +// +// [/Group dict] -> copy +// [/Thumb stream] -> remove, not needed any more / would have to be +// regenerated (combined) +// [/B] article beads -- ignore for now +// [/Dur] -> remove (transition duration) +// [/Trans] -> remove (transitions) +// [/AA] -> remove (additional-actions) +// +// [/Metadata] what shall we do?? (kill: we can't combine XML) +// [/PieceInfo] -> remove, we can't combine private app data (?) +// [/LastModified date] (opt except /PieceInfo) -> see there +// +// [/PZ] -> remove, can't combine/keep (preferred zoom level) +// [/SeparationInfo] -> remove, no way to keep this (needed for separation) +// +// [/ID] related to web capture -- ignore/kill? +// [/StructParents] (opt except pdf contains "structural content items") +// -> copy (is this correct?) +// + +pdfio_obj_t* +_cfPDFToPDFMakeXObject(pdfio_file_t *pdf, + pdfio_obj_t *page) // {{{ +{ + pdfio_dict_t *page_dict = pdfioObjGetDict(page); + + // Create the XObject dictionary + pdfio_dict_t *dict = pdfioDictCreate(pdf); + + if (!dict) { + fprintf(stderr, "Failed to create dictionary for XObject.\n"); + return NULL; + } + + pdfioDictSetName(dict, "Type", "XObject"); + pdfioDictSetName(dict, "Subtype", "Form"); + + // Set BBox from TrimBox or MediaBox + pdfio_rect_t box = _cfPDFToPDFGetTrimBox(page); + pdfioDictSetRect(dict, "BBox", &box); + + // [/Matrix .] ... default is [1 0 0 1 0 0]; we incorporate /UserUnit and + _cfPDFToPDFMatrix mtx; + _cfPDFToPDFMatrix_init(&mtx); + + // /Rotate here + double user_unit = _cfPDFToPDFGetUserUnit(page); + _cfPDFToPDFMatrix_scale(&mtx, user_unit, user_unit); + + // transform, so that bbox is [0 0 w h] (in outer space, but after UserUnit) + pdftopdf_rotation_e rot = _cfPDFToPDFGetRotate(page); + + // calculate rotation effect on [0 0 w h] + _cfPDFToPDFPageRect bbox = _cfPDFToPDFGetBoxAsRect(&box), + tmp; + tmp.left = 0; + tmp.bottom = 0; + tmp.right = 0; + tmp.top = 0; + + _cfPDFToPDFPageRect_rotate_move(&tmp, rot, bbox.width, bbox.height); + + // tmp.rotate_move moves the bbox; we must achieve this move with the matrix. + _cfPDFToPDFMatrix_translate(&mtx, tmp.left, tmp.bottom);// 1. move origin to end up at + // left,bottom after rotation + + + _cfPDFToPDFMatrix_rotate(&mtx, rot); // 2. rotate coordinates according to /Rotate + _cfPDFToPDFMatrix_translate(&mtx, -bbox.left, -bbox.bottom);// 3. move origin from 0,0 to + // "form space" + + + pdfio_array_t *matrix_array = pdfioArrayCreate(pdf); + for (int i = 0; i < 6; i++) + { + pdfioArrayAppendNumber(matrix_array, mtx.ctm[i]); + } + pdfioDictSetArray(dict, "Matrix", matrix_array); + + pdfio_obj_t *resources = pdfioDictGetObj(page_dict, "Resources"); + if (resources) + { + pdfioDictSetObj(dict, "Resources", resources); + } + + pdfio_obj_t *group = pdfioDictGetObj(page_dict, "Group"); + if (group) + { + pdfioDictSetObj(dict, "Group", group); + } + + // Create the stream inside the XObject using pdfioFileCreateObj + pdfio_obj_t *xobject = pdfioFileCreateObj(pdf, dict); + if (!xobject) + { + fprintf(stderr, "Failed to create XObject.\n"); + return NULL; + } + + pdfio_stream_t **contents = NULL; + size_t content_count = pdfioPageGetNumStreams(page); + + for (size_t i = 0; i < content_count; i++) + { + contents[i] = pdfioPageOpenStream(page, i, false); + } + + if (!contents || content_count == 0) + { + fprintf(stderr, "No valid contents streams found in the page dictionary.\n"); + return NULL; + } + + // Write the content streams to the XObject stream + pdfio_stream_t *xobject_stream = pdfioObjOpenStream(xobject, true); + + CombineFromContents_Provider *provider = CombineFromContents_Provider_new(contents, content_count); + if (provider) + { + CombineFromContents_Provider_provideStreamData(provider, xobject_stream); + CombineFromContents_Provider_free(provider); + } + else + { + fprintf(stderr, "Failed to create CombineFromContents_Provider.\n"); + pdfioStreamClose(xobject_stream); + return NULL; + } + + // Finalize the XObject stream and return the object + pdfioStreamClose(xobject_stream); + + return xobject; +} +// }}} diff --git a/cupsfilters/pdftopdf/pdftopdf-private.h b/cupsfilters/pdftopdf/pdftopdf-private.h index 4e1d8fc5f..804408b0c 100644 --- a/cupsfilters/pdftopdf/pdftopdf-private.h +++ b/cupsfilters/pdftopdf/pdftopdf-private.h @@ -1,5 +1,6 @@ // // Copyright 2020 by Jai Luthra. +// Copyright 2024 Uddhav Phatak // // Licensed under Apache License v2.0. See the file "LICENSE" for more // information. diff --git a/cupsfilters/pdftopdf/pdftopdf-processor-private.h b/cupsfilters/pdftopdf/pdftopdf-processor-private.h deleted file mode 100644 index 92ed198e5..000000000 --- a/cupsfilters/pdftopdf/pdftopdf-processor-private.h +++ /dev/null @@ -1,239 +0,0 @@ -// -// Licensed under Apache License v2.0. See the file "LICENSE" for more -// information. -// - -#ifndef _CUPS_FILTERS_PDFTOPDF_PDFTOPDF_PROCESSOR_H -#define _CUPS_FILTERS_PDFTOPDF_PDFTOPDF_PROCESSOR_H - -#include "pptypes-private.h" -#include "nup-private.h" -#include "pdftopdf-private.h" -#include "intervalset-private.h" -#include -#include -#include -#include - -enum pdftopdf_booklet_mode_e { - CF_PDFTOPDF_BOOKLET_OFF, - CF_PDFTOPDF_BOOKLET_ON, - CF_PDFTOPDF_BOOKLET_JUST_SHUFFLE -}; - -struct _cfPDFToPDFProcessingParameters { -_cfPDFToPDFProcessingParameters() -: job_id(0), - num_copies(1), - user(0), - title(0), - pagesize_requested(false), - fitplot(false), - fillprint(false), //print-scaling = fill - cropfit(false), - autoprint(false), - autofit(false), - fidelity(false), - no_orientation(false), - orientation(ROT_0), - normal_landscape(ROT_270), - paper_is_landscape(false), - duplex(false), - border(NONE), - reverse(false), - - page_label(), - even_pages(true), - odd_pages(true), - - mirror(false), - - xpos(CENTER), - ypos(CENTER), - - collate(false), - even_duplex(false), - - booklet(CF_PDFTOPDF_BOOKLET_OFF), - book_signature(-1), - - auto_rotate(false), - - device_copies(1), - device_collate(false), - set_duplex(false), - - page_logging(-1) - { - page.width = 612.0; // Letter - page.height = 792.0; - page.top = page.height - 36.0; - page.bottom = 36.0; - page.left = 18.0; - page.right = page.width - 18.0; - - // everything - input_page_ranges.add(1); - input_page_ranges.finish(); - page_ranges.add(1); - page_ranges.finish(); - } - - int job_id, num_copies; - const char *user, *title; // will stay around - bool pagesize_requested; - bool fitplot; - bool fillprint; //print-scaling = fill - bool cropfit; // -o crop-to-fit - bool autoprint; // print-scaling = auto - bool autofit; // print-scaling = auto-fit - bool fidelity; - bool no_orientation; - _cfPDFToPDFPageRect page; - pdftopdf_rotation_e orientation,normal_landscape; // normal_landscape (i.e. default direction) is e.g. needed for number-up=2 - bool paper_is_landscape; - bool duplex; - pdftopdf_border_type_e border; - _cfPDFToPDFNupParameters nup; - bool reverse; - - std::string page_label; - bool even_pages, odd_pages; - _cfPDFToPDFIntervalSet page_ranges; - _cfPDFToPDFIntervalSet input_page_ranges; - - bool mirror; - - pdftopdf_position_e xpos, ypos; - - bool collate; - - bool even_duplex; // make number of pages a multiple of 2 - - pdftopdf_booklet_mode_e booklet; - int book_signature; - - bool auto_rotate; - - int device_copies; - bool device_collate; - bool set_duplex; - - int page_logging; - int copies_to_be_logged; - - // helper functions - bool with_page(int outno) const; // 1 based - bool have_page(int pageno) const; //1 based - void dump(pdftopdf_doc_t *doc) const; -}; - -enum pdftopdf_arg_ownership_e { - CF_PDFTOPDF_WILL_STAY_ALIVE, - CF_PDFTOPDF_MUST_DUPLICATE, - CF_PDFTOPDF_TAKE_OWNERSHIP -}; - -class _cfPDFToPDFPageHandle { - public: - virtual ~_cfPDFToPDFPageHandle() {} - - virtual _cfPDFToPDFPageRect get_rect() const = 0; - - // fscale: inverse_scale (from nup, fitplot) - - virtual void add_border_rect(const _cfPDFToPDFPageRect &rect, - pdftopdf_border_type_e border, float fscale) = 0; - - // TODO?! add standalone crop(...) method (not only for subpages) - - virtual pdftopdf_rotation_e crop(const _cfPDFToPDFPageRect &cropRect, - pdftopdf_rotation_e orientation, - pdftopdf_rotation_e param_orientation, - pdftopdf_position_e xpos, - pdftopdf_position_e ypos, - bool scale, bool autorotate, - pdftopdf_doc_t *doc) = 0; - - virtual bool is_landscape(pdftopdf_rotation_e orientation) = 0; - - virtual void add_subpage(const std::shared_ptr<_cfPDFToPDFPageHandle> &sub, - float xpos, float ypos, float scale, - const _cfPDFToPDFPageRect *crop=NULL) = 0; - - virtual void mirror() = 0; - - virtual void rotate(pdftopdf_rotation_e rot) = 0; - - virtual void add_label(const _cfPDFToPDFPageRect &rect, - const std::string label) = 0; -}; - -// TODO: ... error output? -class _cfPDFToPDFProcessor { // abstract interface - public: - virtual ~_cfPDFToPDFProcessor() {} - - // TODO: ... qpdf wants password at load time - virtual bool load_file(FILE *f,pdftopdf_doc_t *doc, - pdftopdf_arg_ownership_e take = - CF_PDFTOPDF_WILL_STAY_ALIVE, - int flatten_forms = 1) = 0; - - virtual bool load_filename(const char *name, pdftopdf_doc_t *doc, - int flatten_forms = 1) = 0; - - // TODO? virtual bool may_modify/may_print/? - virtual bool check_print_permissions(pdftopdf_doc_t *doc) = 0; - - virtual std::vector> - get_pages(pdftopdf_doc_t *doc) = 0; // shared_ptr because of type - // erasure (deleter) - - virtual std::shared_ptr<_cfPDFToPDFPageHandle> - new_page(float width, float height, pdftopdf_doc_t *doc) = 0; - - virtual void add_page(std::shared_ptr<_cfPDFToPDFPageHandle> page, - bool front) = 0; // at back/front -- either from - // get_pages() or - // new_page()+add_subpage()-calls - // (or [also allowed]: empty) - - // void remove_page(std::shared_ptr<_cfPDFToPDFPageHandle> ph); - // not needed: we construct from scratch, at least conceptually. - - virtual void multiply(int copies, bool collate) = 0; - - virtual void auto_rotate_all(bool dst_lscape, - pdftopdf_rotation_e normal_landscape) = 0; - // TODO elsewhere?! - virtual void add_cm(const char *defaulticc, const char *outputicc) = 0; - - virtual void set_comments(const std::vector &comments) = 0; - - virtual void emit_file(FILE *dst,pdftopdf_doc_t *doc, - pdftopdf_arg_ownership_e take = - CF_PDFTOPDF_WILL_STAY_ALIVE) = 0; - virtual void emit_filename(const char *name,pdftopdf_doc_t *doc) = 0; - // NULL -> stdout - - virtual bool has_acro_form() = 0; -}; - -class _cfPDFToPDFFactory { - public: - // never NULL, but may throw. - static _cfPDFToPDFProcessor *processor(); -}; - -//bool _cfPDFToPDFCheckBookletSignature(int signature) -// { return (signature%4==0); } - -std::vector _cfPDFToPDFBookletShuffle(int numPages, int signature = -1); - -// This is all we want: -bool _cfProcessPDFToPDF(_cfPDFToPDFProcessor &proc, - _cfPDFToPDFProcessingParameters ¶m, - pdftopdf_doc_t *doc); - -#endif // !_CUPS_FILTERS_PDFTOPDF_PDFTOPDF_PROCESSOR_H diff --git a/cupsfilters/pdftopdf/pdftopdf-processor.c b/cupsfilters/pdftopdf/pdftopdf-processor.c new file mode 100644 index 000000000..0d5f856b0 --- /dev/null +++ b/cupsfilters/pdftopdf/pdftopdf-processor.c @@ -0,0 +1,668 @@ +// +// Copyright 2024 Uddhav Phatak +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "processor.h" +#include +#include "cupsfilters/debug-internal.h" +#include +#include +#include +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +void +_cfPDFToPDFProcessingParameters_init(_cfPDFToPDFProcessingParameters *processingParams) // {{{ +{ + processingParams->job_id = 0; + processingParams->num_copies = 1; + processingParams->user = NULL; + processingParams->title = NULL; + processingParams->pagesize_requested = false; + processingParams->fitplot = false; + processingParams->fillprint = false; // print-scaling = fill + processingParams->cropfit = false; + processingParams->autoprint = false; + processingParams->autofit = false; + processingParams->fidelity = false; + processingParams->no_orientation = false; + processingParams->orientation = ROT_0; + processingParams->normal_landscape = ROT_270; + processingParams->paper_is_landscape = false; + processingParams->duplex = false; + processingParams->border = NONE; + processingParams->reverse = false; + + processingParams->page_label = NULL; + processingParams->even_pages = true; + processingParams->odd_pages = true; + + processingParams->mirror = false; + + processingParams->xpos = CENTER; + processingParams->ypos = CENTER; + + processingParams->collate = false; + processingParams->even_duplex = false; + + processingParams->booklet = CF_PDFTOPDF_BOOKLET_OFF; + processingParams->book_signature = -1; + + processingParams->auto_rotate = false; + + processingParams->device_copies = 1; + processingParams->device_collate = false; + processingParams->set_duplex = false; + + processingParams->page_logging = -1; + processingParams->copies_to_be_logged = 0; + + processingParams->page = (_cfPDFToPDFPageRect *)malloc(sizeof(_cfPDFToPDFPageRect)); + _cfPDFToPDFPageRect_init(processingParams->page); + + processingParams->page->width = 612.0; // Letter size width in points + processingParams->page->height = 792.0; // Letter size height in points + processingParams->page->top = processingParams->page->height - 36.0; + processingParams->page->bottom = 36.0; + processingParams->page->left = 18.0; + processingParams->page->right = processingParams->page->width - 18.0; + + processingParams->nup = (_cfPDFToPDFNupParameters *)malloc(sizeof(_cfPDFToPDFNupParameters)); + _cfPDFToPDFNupParameters_init(processingParams->nup); + + processingParams->input_page_ranges = (_cfPDFToPDFIntervalSet *)malloc(sizeof(_cfPDFToPDFIntervalSet)); + _cfPDFToPDFIntervalSet_init(processingParams->input_page_ranges); + _cfPDFToPDFIntervalSet_add_single(processingParams->input_page_ranges, 1); + _cfPDFToPDFIntervalSet_finish(processingParams->input_page_ranges); + + processingParams->page_ranges = (_cfPDFToPDFIntervalSet *)malloc(sizeof(_cfPDFToPDFIntervalSet)); + _cfPDFToPDFIntervalSet_init(processingParams->page_ranges); + _cfPDFToPDFIntervalSet_add_single(processingParams->page_ranges, 1); + _cfPDFToPDFIntervalSet_finish(processingParams->page_ranges); +} +// }}} + +void +_cfPDFToPDFProcessingParameters_free(_cfPDFToPDFProcessingParameters *processingParams) +{ + if (!processingParams) + return; + // Free `page` + if (processingParams->page) + free(processingParams->page); + + if (processingParams->nup) + free(processingParams->nup); + + if (processingParams->input_page_ranges) + { + _cfPDFToPDFIntervalSet_clear(processingParams->input_page_ranges); + free(processingParams->input_page_ranges); + } + + if (processingParams->page_ranges) + { + _cfPDFToPDFIntervalSet_clear(processingParams->page_ranges); + free(processingParams->page_ranges); + } + + // Free dynamically allocated strings + if (processingParams->page_label) + free(processingParams->page_label); + + // Free the structure itself + free(processingParams); +} + + +void +BookletMode_dump(pdftopdf_booklet_mode_e bkm, + pdftopdf_doc_t *doc) // {{{ +{ + static const char *bstr[3] = {"Off", "On", "Shuffle-Only"}; + + if ((bkm < CF_PDFTOPDF_BOOKLET_OFF) || + (bkm > CF_PDFTOPDF_BOOKLET_JUST_SHUFFLE)) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Booklet mode: (Bad booklet mode: %d)", + bkm); + } + else + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Booklet mode: %s", + bstr[bkm]); + } +} +// }}} + +bool +_cfPDFToPDFProcessingParameters_even_odd_page(const _cfPDFToPDFProcessingParameters *self, + int outno) // {{{ +{ + if (outno % 2 == 0) + { + if(!self->even_pages) + return (false); + } + else if (!self->odd_pages) + return (false); + + return (true); +} +// }}} + + +bool +_cfPDFToPDFProcessingParameters_with_page(const _cfPDFToPDFProcessingParameters *processingParams, + int outno) // {{{ +{ + if (outno % 2 == 0) + { + if (!processingParams->even_pages) + { + return false; + } + } else if (!processingParams->odd_pages) + { + return false; + } + return _cfPDFToPDFIntervalSet_contains(processingParams->page_ranges, outno); +} +// }}} + +bool +_cfPDFToPDFProcessingParameters_have_page(const _cfPDFToPDFProcessingParameters *processingParams, + int pageno) // {{{ +{ + return _cfPDFToPDFIntervalSet_contains(processingParams->input_page_ranges, pageno); +} +// }}} + +void +_cfPDFToPDFProcessingParameters_dump(const _cfPDFToPDFProcessingParameters *processingParams, + pdftopdf_doc_t *doc) // {{{ +{ + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: job_id: %d, num_copies: %d", + processingParams->job_id, processingParams->num_copies); + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: user: %s, title: %s", + (processingParams->user) ? processingParams->user : "(null)", + (processingParams->title) ? processingParams->title : "(null)"); + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: fitplot: %s", + (processingParams->fitplot) ? "true" : "false"); + + _cfPDFToPDFPageRect_dump(processingParams->page, doc); + _cfPDFToPDFRotationDump(processingParams->orientation, doc); + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: paper_is_landscape: %s", + (processingParams->paper_is_landscape) ? "true" : "false"); + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: duplex: %s", + (processingParams->duplex) ? "true" : "false"); + + _cfPDFToPDFBorderTypeDump(processingParams->border, doc); + _cfPDFToPDFNupParameters_dump(processingParams->nup, doc); + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: reverse: %s", + (processingParams->reverse) ? "true" : "false"); + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: even_pages: %s, odd_pages: %s", + (processingParams->even_pages) ? "true" : "false", + (processingParams->odd_pages) ? "true" : "false"); + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: input page range:"); + + _cfPDFToPDFIntervalSet_dump(processingParams->input_page_ranges, doc); + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: page range:"); + + _cfPDFToPDFIntervalSet_dump(processingParams->page_ranges, doc); + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: mirror: %s", + (processingParams->mirror) ? "true" : "false"); + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Position:"); + + _cfPDFToPDFPositionAndAxisDump(processingParams->xpos, X, doc); + _cfPDFToPDFPositionAndAxisDump(processingParams->ypos, Y, doc); + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: collate: %s", + (processingParams->collate) ? "true" : "false"); + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: even_duplex: %s", + (processingParams->even_duplex) ? "true" : "false"); + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: page_label: %s", + processingParams->page_label ? processingParams->page_label : "(none)"); + + BookletMode_dump(processingParams->booklet, doc); + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, "cfFilterPDFToPDF: booklet signature: %d", processingParams->book_signature); + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: auto_rotate: %s", + (processingParams->auto_rotate) ? "true" : "false"); + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: device_copies: %d", + processingParams->device_copies); + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: device_collate: %s", + (processingParams->device_collate) ? "true" : "false"); + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: set_duplex: %s", + (processingParams->set_duplex) ? "true" : "false"); +} +// }}} + +int* +_cfPDFToPDFBookletShuffle(int numPages, int signature, int* ret_size) +{ + if (signature < 0) { + signature = (numPages + 3) & ~0x3; // Round up to the nearest multiple of 4 + } + + int maxSize = numPages + signature - 1; + int* ret = (int*)malloc(maxSize * sizeof(int)); + if (ret == NULL) + { + *ret_size = 0; + return NULL; // Handle memory allocation failure + } + + int curpage = 0; + int index = 0; // Keeps track of the current index in the result array + while (curpage < numPages) + { + int firstpage = curpage; + int lastpage = curpage + signature - 1; + + while (firstpage < lastpage) + { + ret[index++] = lastpage--; + ret[index++] = firstpage++; + ret[index++] = firstpage++; + ret[index++] = lastpage--; + } + curpage += signature; + } + + *ret_size = index; // Set the size of the result + return ret; +} + +bool +_cfProcessPDFToPDF(_cfPDFToPDF_PDFioProcessor *proc, + _cfPDFToPDFProcessingParameters *param, + pdftopdf_doc_t *doc) +{ + if(!_cfPDFToPDF_PDFioProcessor_check_print_permissions(proc, doc)) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Not allowed to print"); + return false; + } + + const bool dst_lscape = + (param->paper_is_landscape == + ((param->orientation == ROT_0) || (param->orientation == ROT_180))); + + if(param->paper_is_landscape) + { + int temp = param->nup->nupX; + param->nup->nupX = param->nup->nupY; + param->nup->nupY = temp; + } + + if(param->auto_rotate) + { + _cfPDFToPDF_PDFioProcessor_auto_rotate_all(proc, dst_lscape, param->normal_landscape); + } + + size_t num_page = pdfioFileGetNumPages(proc->pdf); + + _cfPDFToPDFPageHandle **pages = malloc((num_page) * sizeof(_cfPDFToPDFPageHandle*)); + pages = _cfPDFToPDF_PDFioProcessor_get_pages(proc, doc, &num_page); + + _cfPDFToPDFPageHandle **input_page_range_list = malloc((num_page) * sizeof(_cfPDFToPDFPageHandle*)); + size_t input_page_range_list_count = 0; // Tracks the count of valid pages + + for (size_t i = 1; i <= num_page; i++) { + if (_cfPDFToPDFProcessingParameters_have_page(param, i)) { + input_page_range_list[input_page_range_list_count] = pages[i - 1]; + input_page_range_list_count++; + } + } + + input_page_range_list = realloc(input_page_range_list, (input_page_range_list_count) * sizeof(_cfPDFToPDFPageHandle*)); + + const int numOrigPages = input_page_range_list_count; + int *shuffle; + int shuffle_size = 0; // Declare shuffle_size as an integer + + if (param->booklet != CF_PDFTOPDF_BOOKLET_OFF) + { + shuffle = _cfPDFToPDFBookletShuffle(numOrigPages, param->book_signature, &shuffle_size); + if (param->booklet == CF_PDFTOPDF_BOOKLET_ON) + { + _cfPDFToPDFNupParameters_preset(2, param->nup); + } + } + else + { + shuffle = malloc(numOrigPages * sizeof(int)); + for (int i = 0; i < numOrigPages; i++) + { + shuffle[i] = i; + shuffle_size++; + } + } + + const int numPages = (shuffle_size > input_page_range_list_count) ? shuffle_size : input_page_range_list_count; + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: \"print-scaling\" IPP attribute: %s", + (param->autoprint ? "auto" : + (param->autofit ? "auto-fit" : + (param->fitplot ? "fit" : + (param->fillprint ? "fill" : + (param->cropfit ? "none" : + "Not defined, should never happen")))))); + + if (param->autoprint || param->autofit) + { + bool margin_defined = true; + bool document_large = false; + int pw = param->page->right - param->page->left; + int ph = param->page->top - param->page->bottom; + + if ((param->page->width == pw) && (param->page->height == ph)) + margin_defined = false; + + for (int i = 0; i < input_page_range_list_count; i ++) + { + _cfPDFToPDFPageRect r = _cfPDFToPDFPageHandle_get_rect(input_page_range_list[i]); + int w = r.width * 100 / 102; // 2% of tolerance + int h = r.height * 100 / 102; + if ((w > param->page->width || h > param->page->height) && + (h > param->page->width || w > param->page->height)) + { + if (doc->logfunc) + doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Page %d too large for output page size, scaling pages to fit.", + i + 1); + document_large = true; + } + } + if (param->fidelity && doc->logfunc) + doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: \"ipp-attribute-fidelity\" IPP attribute is set, scaling pages to fit."); + + if (param->autoprint) + { + if (param->fidelity || document_large) + { + if (margin_defined) + param->fitplot = true; + else + param->fillprint = true; + } + else + param->cropfit = true; + } + else + { + if (param->fidelity || document_large) + param->fitplot = true; + else + param->cropfit = true; + } + } + + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Print scaling mode: %s", + (param->fitplot ? + "Scale to fit printable area" : + (param->fillprint ? + "Scale to fill page and crop" : + (param->cropfit ? + "Do not scale, center, crop if needed" : + "Not defined, should never happen")))); + + // In Crop mode we do not scale the original document, it should keep the + // exact same size. With N-Up it should be scaled to fit exacly the halves, + // quarters, ... of the sheet, regardless of unprintable margins. + // Therefore we remove the unprintable margins to do all the math without + // them. + if (param->cropfit) + { + param->page->left = 0; + param->page->bottom = 0; + param->page->right = param->page->width; + param->page->top = param->page->height; + } + + if (param->pagesize_requested && (param->fillprint || param->cropfit)) + { + for (int i = 0; i < input_page_range_list_count; i ++) + { + _cfPDFToPDFPageHandle *page = input_page_range_list[i]; + pdftopdf_rotation_e orientation; + + if (_cfPDFToPDFPageHandle_is_landscape(page, param->orientation)) + orientation = param->normal_landscape; + else + orientation = ROT_0; + + _cfPDFToPDFPageHandle_crop(page, param->page, orientation, param->orientation, + param->xpos, param->ypos, + !param->cropfit, param->auto_rotate, + doc); + } + if (param->fillprint) + param->fitplot = true; + } + + _cfPDFToPDFPageHandle *curpage; + int outputpage = 0; + int outputno = 0; + + if ((param->nup->nupX == 1) && (param->nup->nupY == 1) && !param->fitplot) + { + param->nup->width = param->page->width; + param->nup->height = param->page->height; + } + else + { + param->nup->width = param->page->right - param->page->left; + param->nup->height = param->page->top - param->page->bottom; + } + + if ((param->orientation == ROT_90) || (param->orientation == ROT_270)) + { + int temp = param->nup->nupX; + param->nup->nupX = param->nup->nupY; + param->nup->nupY = temp; + + param->nup->landscape = !param->nup->landscape; + param->orientation = param->orientation - param->normal_landscape; + } + + double xpos = 0, ypos = 0; + if (param->nup->landscape) + { + // pages[iA]->rotate(param.normal_landscape); + param->orientation = param->orientation + param->normal_landscape; + // TODO? better + if (param->nup->nupX != 1 || param->nup->nupY != 1 || param->fitplot) + { + xpos = param->page->height - param->page->top; + ypos = param->page->left; + } + int temp = param->page->width; + param->page->width = param->page->height; + param->page->height = temp; + + temp = param->nup->width; + param->nup->width = param->nup->height; + param->nup->height = temp; + } + else + { + if (param->nup->nupX != 1 || param->nup->nupY != 1 || param->fitplot) + { + xpos = param->page->left; + ypos = param->page->bottom; // for whole page... TODO from position... + } + } + + _cfPDFToPDFNupState *nupState = (_cfPDFToPDFNupState *)malloc(sizeof(_cfPDFToPDFNupState)); + _cfPDFToPDFNupState_init(nupState, param->nup); + + _cfPDFToPDFNupPageEdit *pgedit = (_cfPDFToPDFNupPageEdit *)malloc(sizeof(_cfPDFToPDFNupPageEdit)); + + for (int iA = 0; iA < numPages; iA ++) + { + _cfPDFToPDFPageHandle *page; + if(shuffle[iA] >= numOrigPages) + { + page = _cfPDFToPDF_PDFioProcessor_new_page(proc, param->page->width, param->page->height, doc); + } + else + { + page = input_page_range_list[shuffle[iA]]; + } + + _cfPDFToPDFPageRect rect; + _cfPDFToPDFPageRect_init(&rect); + + if (!param->pagesize_requested) + { + param->page->width = param->page->right = rect.width; + param->page->height = param->page->top = rect.height; + } + + bool newPage = _cfPDFToPDFNupState_next_page(nupState, + rect.width, rect.height, + pgedit); + + if (newPage) + { + if ((curpage) && (_cfPDFToPDFProcessingParameters_with_page(param, outputpage))) + { + outputno ++; + if (_cfPDFToPDFProcessingParameters_even_odd_page(param, outputno)) + { + _cfPDFToPDFPageHandle_rotate(curpage, param->orientation); + if (param->mirror) + _cfPDFToPDFPageHandle_mirror(curpage, proc->pdf); + // TODO? update rect? --- not needed any more + //proc.add_page(curpage, param.reverse); // reverse -> insert at beginning + // Log page in /var/log/cups/page_log + if (param->page_logging == 1) + if (doc->logfunc) doc->logfunc(doc->logdata, + CF_LOGLEVEL_CONTROL, + "PAGE: %d %d", outputno, + param->copies_to_be_logged); + } + } + curpage = _cfPDFToPDF_PDFioProcessor_new_page(proc, param->page->width, param->page->width, doc); + outputpage++; + } + + if(shuffle[iA] >= numOrigPages) + continue; + + if (param->border != NONE) + // TODO FIXME... border gets cutted away, if orignal page had wrong size + // page->"uncrop"(rect); // page->setMedia() + // Note: currently "fixed" in add_subpage(...&rect); + _cfPDFToPDFPageHandle_add_border_rect(page, proc->pdf, rect, param->border, 1.0 / pgedit->scale); + + if (param->page_label[0] != '\0') + { + _cfPDFToPDFPageHandle_add_label(page, proc->pdf, param->page, param->page_label); + } + + if(param->cropfit) + { + if ((param->nup->nupX == 1) && (param->nup->nupY == 1)) + { + double xpos2, ypos2; + _cfPDFToPDFPageRect get_rect_height = _cfPDFToPDFPageHandle_get_rect(page); + _cfPDFToPDFPageRect get_rect_width = _cfPDFToPDFPageHandle_get_rect(page); + + if ((param->page->height - param->page->width) * + (get_rect_height.height - get_rect_width.width) < 0) + { + xpos2 = (param->page->width - (get_rect_height.height)) / 2; + ypos2 = (param->page->height - (get_rect_width.width)) / 2; + _cfPDFToPDFPageHandle_add_subpage(curpage, page, proc->pdf, ypos2 + xpos, xpos2 + ypos, 1, NULL); + } + else + { + xpos2 = (param->page->width - get_rect_width.width) / 2; + ypos2 = (param->page->height - get_rect_height.height) /2; + _cfPDFToPDFPageHandle_add_subpage(curpage, page, proc->pdf, xpos2 + xpos, ypos2 + ypos, 1, NULL); + } + } + else + { + _cfPDFToPDFPageHandle_add_subpage(curpage, page, proc->pdf, pgedit->xpos + xpos, pgedit->ypos + ypos, pgedit->scale, NULL); + } + } + else + _cfPDFToPDFPageHandle_add_subpage(curpage, page, proc->pdf, pgedit->xpos + xpos, pgedit->ypos + ypos, pgedit->scale, NULL); + +#ifdef DEBUG + _cfPDFToPDFPageHandle *dbg = (_cfPDFToPDFPageHandle *)curpage; + if (dbg && dbg->debug) + { + _cfPDFToPDFPageHandle_debug(dbg, sub, xpos,ypos); + } +#endif + } + if((curpage) && (_cfPDFToPDFProcessingParameters_with_page(param, outputpage))) + { + _cfPDFToPDFPageHandle_rotate(curpage, param->orientation); + if(param->mirror) + _cfPDFToPDFPageHandle_mirror(curpage, proc->pdf); + + // need to output empty page to not confuse duplex +// _cfPDFToPDF_PDFioProcessor_add_page(proc, _cfPDFToPDF_PDFioProcessor_new_page(proc, +// param->page.width, param->page.height, doc), param->reverse); + + // Log page in /var/log/cups/page_log + if(param->page_logging == 1) + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_CONTROL, + "PAGE: %d %d", outputno + 1, + param->copies_to_be_logged); + } + + _cfPDFToPDF_PDFioProcessor_multiply(proc, param->num_copies, param->collate); + free(pgedit); + free(nupState); + free(shuffle); + free(pages); + free(input_page_range_list); + return true; +} diff --git a/cupsfilters/pdftopdf/pdftopdf-processor.cxx b/cupsfilters/pdftopdf/pdftopdf-processor.cxx deleted file mode 100644 index 3927f379e..000000000 --- a/cupsfilters/pdftopdf/pdftopdf-processor.cxx +++ /dev/null @@ -1,512 +0,0 @@ -// -// Licensed under Apache License v2.0. See the file "LICENSE" for more -// information. -// - -#include "pdftopdf-processor-private.h" -#include "qpdf-pdftopdf-processor-private.h" -#include -#include "cupsfilters/debug-internal.h" -#include - -void -BookletMode_dump(pdftopdf_booklet_mode_e bkm, - pdftopdf_doc_t *doc) // {{{ -{ - static const char *bstr[3] = {"Off", "On", "Shuffle-Only"}; - - if ((bkm < CF_PDFTOPDF_BOOKLET_OFF) || - (bkm > CF_PDFTOPDF_BOOKLET_JUST_SHUFFLE)) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Booklet mode: (Bad booklet mode: %d)", - bkm); - } - else - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Booklet mode: %s", - bstr[bkm]); - } -} -// }}} - -bool -_cfPDFToPDFProcessingParameters::with_page(int outno) const // {{{ -{ - if (outno % 2 == 0) - { // 1-based - if (!even_pages) - return (false); - } - else if (!odd_pages) - return (false); - return (page_ranges.contains(outno)); -} -// }}} - -bool -_cfPDFToPDFProcessingParameters::have_page(int pageno) const -{ - return (input_page_ranges.contains(pageno)); -} - -void -_cfPDFToPDFProcessingParameters::dump(pdftopdf_doc_t *doc) const // {{{ -{ - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: job_id: %d, num_copies: %d", - job_id, num_copies); - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: user: %s, title: %s", - (user) ? user : "(null)", - (title) ? title : "(null)"); - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: fitplot: %s", - (fitplot) ? "true" : "false"); - - page.dump(doc); - - _cfPDFToPDFRotationDump(orientation, doc); - - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: paper_is_landscape: %s", - (paper_is_landscape) ? "true" : "false"); - - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: duplex: %s", - (duplex) ? "true" : "false"); - - _cfPDFToPDFBorderTypeDump(border, doc); - - nup.dump(doc); - - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: reverse: %s", - (reverse) ? "true" : "false"); - - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: even_pages: %s, odd_pages: %s", - (even_pages) ? "true" : "false", - (odd_pages) ? "true" : "false"); - - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: input page range:"); - input_page_ranges.dump(doc); - - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: page range:"); - page_ranges.dump(doc); - - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: mirror: %s", - (mirror) ? "true" : "false"); - - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Position:"); - _cfPDFToPDFPositionDump(xpos, pdftopdf_axis_e::X,doc); - _cfPDFToPDFPositionDump(ypos, pdftopdf_axis_e::Y,doc); - - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: collate: %s", - (collate) ? "true" : "false"); - - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: even_duplex: %s", - (even_duplex) ? "true" : "false"); - - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: page_label: %s", - page_label.empty () ? "(none)" : - page_label.c_str()); - - BookletMode_dump(booklet,doc); - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: booklet signature: %d", - book_signature); - - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: auto_rotate: %s", - (auto_rotate) ? "true" : "false"); - - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: device_copies: %d", - device_copies); - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: device_collate: %s", - (device_collate) ? "true" : "false"); - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: set_duplex: %s", - (set_duplex) ? "true" : "false"); -} -// }}} - - -_cfPDFToPDFProcessor -*_cfPDFToPDFFactory::processor() -{ - return new _cfPDFToPDFQPDFProcessor(); -} - -// -// (1-based) -// 9: [*] [1] [2] [*] [*] [3] [4] [9] [8] [5] [6] [7] -// 1 2 3 4 5 6 7 8 9 10 11 12 -// -// -> signature = 12 = 3*4 = ((9+3)/4)*4 -// -// NOTE: psbook always fills the sig completely (results in completely -// white pages (4-set), depending on the input) -// -// empty pages must be added for output values >= numPages -// - -std::vector -_cfPDFToPDFBookletShuffle(int numPages, - int signature) // {{{ -{ - if (signature < 0) - signature = (numPages + 3) & ~0x3; - DEBUG_assert(signature % 4 == 0); - - std::vector ret; - ret.reserve(numPages + signature - 1); - - int curpage = 0; - while (curpage < numPages) - { - // as long as pages to be done -- i.e. multiple times the signature - int firstpage = curpage, - lastpage = curpage + signature - 1; - // one signature - while (firstpage < lastpage) - { - ret.push_back(lastpage --); - ret.push_back(firstpage ++); - ret.push_back(firstpage ++); - ret.push_back(lastpage --); - } - curpage += signature; - } - return (ret); -} -// }}} - -bool -_cfProcessPDFToPDF(_cfPDFToPDFProcessor &proc, - _cfPDFToPDFProcessingParameters ¶m, - pdftopdf_doc_t *doc) // {{{ -{ - if (!proc.check_print_permissions(doc)) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Not allowed to print"); - return false; - } - - const bool dst_lscape = - (param.paper_is_landscape == - ((param.orientation == ROT_0) || (param.orientation == ROT_180))); - - if (param.paper_is_landscape) - std::swap(param.nup.nupX, param.nup.nupY); - - if (param.auto_rotate) - proc.auto_rotate_all(dst_lscape, param.normal_landscape); - - std::vector> pages = - proc.get_pages(doc); - - std::vector> input_page_range_list; - - for (int i = 1; i <= (int)pages.size(); i ++) - if (param.have_page(i)) - input_page_range_list.push_back(pages[i - 1]); - - const int numOrigPages = input_page_range_list.size(); - - // TODO FIXME? elsewhere - std::vector shuffle; - if (param.booklet != CF_PDFTOPDF_BOOKLET_OFF) - { - shuffle = _cfPDFToPDFBookletShuffle(numOrigPages, param.book_signature); - if (param.booklet == CF_PDFTOPDF_BOOKLET_ON) - { // override options - // We do not "sides=two-sided-short-edge" / DuplexTumble here. - // We assume it done by caller, for example ppdFilterLoadPPD() of libppd - // param.duplex=true; - // param.set_duplex=true; - _cfPDFToPDFNupParameters::preset(2, param.nup); // TODO?! better - } - } - else - { // 0 1 2 3 ... - shuffle.resize(numOrigPages); - std::iota(shuffle.begin(), shuffle.end(), 0); - } - const int numPages=std::max(shuffle.size(), input_page_range_list.size()); - - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: \"print-scaling\" IPP attribute: %s", - (param.autoprint ? "auto" : - (param.autofit ? "auto-fit" : - (param.fitplot ? "fit" : - (param.fillprint ? "fill" : - (param.cropfit ? "none" : - "Not defined, should never happen")))))); - - if (param.autoprint || param.autofit) - { - bool margin_defined = true; - bool document_large = false; - int pw = param.page.right - param.page.left; - int ph = param.page.top - param.page.bottom; - - if ((param.page.width == pw) && (param.page.height == ph)) - margin_defined = false; - - for (int i = 0; i < (int)input_page_range_list.size(); i ++) - { - _cfPDFToPDFPageRect r = input_page_range_list[i]->get_rect(); - int w = r.width * 100 / 102; // 2% of tolerance - int h = r.height * 100 / 102; - if ((w > param.page.width || h > param.page.height) && - (h > param.page.width || w > param.page.height)) - { - if (doc->logfunc) - doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Page %d too large for output page size, scaling pages to fit.", - i + 1); - document_large = true; - } - } - if (param.fidelity && doc->logfunc) - doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: \"ipp-attribute-fidelity\" IPP attribute is set, scaling pages to fit."); - - if (param.autoprint) - { - if (param.fidelity || document_large) - { - if (margin_defined) - param.fitplot = true; - else - param.fillprint = true; - } - else - param.cropfit = true; - } - else - { - if (param.fidelity || document_large) - param.fitplot = true; - else - param.cropfit = true; - } - } - - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Print scaling mode: %s", - (param.fitplot ? - "Scale to fit printable area" : - (param.fillprint ? - "Scale to fill page and crop" : - (param.cropfit ? - "Do not scale, center, crop if needed" : - "Not defined, should never happen")))); - - // In Crop mode we do not scale the original document, it should keep the - // exact same size. With N-Up it should be scaled to fit exacly the halves, - // quarters, ... of the sheet, regardless of unprintable margins. - // Therefore we remove the unprintable margins to do all the math without - // them. - if (param.cropfit) - { - param.page.left = 0; - param.page.bottom = 0; - param.page.right = param.page.width; - param.page.top = param.page.height; - } - - if (param.pagesize_requested && (param.fillprint || param.cropfit)) - { - for (int i = 0; i < (int)input_page_range_list.size(); i ++) - { - std::shared_ptr<_cfPDFToPDFPageHandle> page = input_page_range_list[i]; - pdftopdf_rotation_e orientation; - if (page->is_landscape(param.orientation)) - orientation = param.normal_landscape; - else - orientation = ROT_0; - page->crop(param.page, orientation, param.orientation, - param.xpos, param.ypos, - !param.cropfit, param.auto_rotate, doc); - } - if (param.fillprint) - param.fitplot = true; - } - - std::shared_ptr<_cfPDFToPDFPageHandle> curpage; - int outputpage = 0; - int outputno = 0; - - if ((param.nup.nupX == 1) && (param.nup.nupY == 1) && !param.fitplot) - { - param.nup.width = param.page.width; - param.nup.height = param.page.height; - } - else - { - param.nup.width = param.page.right - param.page.left; - param.nup.height = param.page.top - param.page.bottom; - } - - if ((param.orientation == ROT_90) || (param.orientation == ROT_270)) - { - std::swap(param.nup.nupX, param.nup.nupY); - param.nup.landscape = !param.nup.landscape; - param.orientation = param.orientation - param.normal_landscape; - } - - double xpos = 0, ypos = 0; - if (param.nup.landscape) - { - // pages[iA]->rotate(param.normal_landscape); - param.orientation = param.orientation + param.normal_landscape; - // TODO? better - if (param.nup.nupX != 1 || param.nup.nupY != 1 || param.fitplot) - { - xpos = param.page.height - param.page.top; - ypos = param.page.left; - } - std::swap(param.page.width, param.page.height); - std::swap(param.nup.width, param.nup.height); - } - else - { - if (param.nup.nupX != 1 || param.nup.nupY != 1 || param.fitplot) - { - xpos = param.page.left; - ypos = param.page.bottom; // for whole page... TODO from position... - } - } - - _cfPDFToPDFNupState nupstate(param.nup); - _cfPDFToPDFNupPageEdit pgedit; - for (int iA = 0; iA < numPages; iA ++) - { - std::shared_ptr<_cfPDFToPDFPageHandle> page; - if (shuffle[iA] >= numOrigPages) - // add empty page as filler - page = proc.new_page(param.page.width, param.page.height, doc); - else - page = input_page_range_list[shuffle[iA]]; - - _cfPDFToPDFPageRect rect; - rect = page->get_rect(); - //rect.dump(doc); - if (!param.pagesize_requested) - { - param.page.width = param.page.right = rect.width; - param.page.height = param.page.top = rect.height; - } - - bool newPage = nupstate.mext_page(rect.width, rect.height, pgedit); - if (newPage) - { - if ((curpage) && (param.with_page(outputpage))) - { - curpage->rotate(param.orientation); - if (param.mirror) - curpage->mirror(); - // TODO? update rect? --- not needed any more - proc.add_page(curpage, param.reverse); // reverse -> insert at beginning - // Log page in /var/log/cups/page_log - outputno ++; - if (param.page_logging == 1) - if (doc->logfunc) doc->logfunc(doc->logdata, - CF_LOGLEVEL_CONTROL, - "PAGE: %d %d", outputno, - param.copies_to_be_logged); - } - curpage = proc.new_page(param.page.width, param.page.height, doc); - outputpage++; - } - - if (shuffle[iA] >= numOrigPages) - continue; - - if (param.border != pdftopdf_border_type_e::NONE) - // TODO FIXME... border gets cutted away, if orignal page had wrong size - // page->"uncrop"(rect); // page->setMedia() - // Note: currently "fixed" in add_subpage(...&rect); - page->add_border_rect(rect, param.border, 1.0 / pgedit.scale); - - if (!param.page_label.empty()) - page->add_label(param.page, param.page_label); - - if (param.cropfit) - { - if ((param.nup.nupX == 1) && (param.nup.nupY == 1)) - { - double xpos2, ypos2; - if ((param.page.height - param.page.width) * - (page->get_rect().height - page->get_rect().width) < 0) - { - xpos2 = (param.page.width - (page->get_rect().height)) / 2; - ypos2 = (param.page.height - (page->get_rect().width)) / 2; - curpage->add_subpage(page, ypos2 + xpos, xpos2 + ypos, 1); - } - else - { - xpos2 = (param.page.width - (page->get_rect().width)) / 2; - ypos2 = (param.page.height - (page->get_rect().height)) / 2; - curpage->add_subpage(page, xpos2 + xpos, ypos2 + ypos, 1); - } - } - else - curpage->add_subpage(page, pgedit.xpos + xpos, pgedit.ypos + ypos, - pgedit.scale); - } - else - curpage->add_subpage(page, pgedit.xpos + xpos, pgedit.ypos + ypos, - pgedit.scale); - -#ifdef DEBUG - if (auto dbg=dynamic_cast<_cfPDFToPDFQPDFPageHandle *>(curpage.get())) - dbg->debug(pgedit.sub, xpos, ypos); -#endif - - //pgedit.dump(doc); - } - if ((curpage) && (param.with_page(outputpage))) - { - curpage->rotate(param.orientation); - if (param.mirror) - curpage->mirror(); - proc.add_page(curpage, param.reverse); // reverse -> insert at beginning - // Log page in /var/log/cups/page_log - outputno ++; - if (param.page_logging == 1) - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_CONTROL, - "PAGE: %d %d", outputno, - param.copies_to_be_logged); - } - - if ((param.even_duplex || !param.odd_pages) && (outputno & 1)) - { - // need to output empty page to not confuse duplex - proc.add_page(proc.new_page(param.page.width, - param.page.height, doc), param.reverse); - // Log page in /var/log/cups/page_log - if (param.page_logging == 1) - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_CONTROL, - "PAGE: %d %d", outputno + 1, - param.copies_to_be_logged); - } - - proc.multiply(param.num_copies, param.collate); - - return (true); -} -// }}} diff --git a/cupsfilters/pdftopdf/pdftopdf.c b/cupsfilters/pdftopdf/pdftopdf.c new file mode 100644 index 000000000..e82a40dc2 --- /dev/null +++ b/cupsfilters/pdftopdf/pdftopdf.c @@ -0,0 +1,1014 @@ +// +// Copyright 2024 Uddhav Phatak +// +// Copyright (c) 6-2011, BBR Inc. All rights reserved. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pdftopdf-private.h" +#include "processor.h" + +#include + +static bool +optGetInt(const char *name, + int num_options, + cups_option_t *options, + int *ret) // {{{ +{ + const char *val = cupsGetOption(name, num_options, options); + if (val) + { + *ret = atoi(val); + return true; + } + return false; +} +// }}} + +static bool +optGetFloat(const char *name, + int num_options, + cups_option_t *options, + float *ret) // {{{ +{ + const char *val = cupsGetOption(name, num_options, options); + if (val) + { + *ret = atof(val); + return true; + } + return false; +} +// }}} + +static bool +is_false(const char *value) // {{{ +{ + if (!value) + return false; + return ((strcasecmp(value, "no") == 0) || + (strcasecmp(value, "off") == 0) || + (strcasecmp(value, "false") == 0)); +} +// }}} + +static bool +is_true(const char *value) // {{{ +{ + if (!value) + return false; + return ((strcasecmp(value, "yes") == 0) || + (strcasecmp(value, "on") == 0) || + (strcasecmp(value, "true") == 0)); +} +// }}} + +static bool +parsePosition(const char *value, + pdftopdf_position_e *xpos, + pdftopdf_position_e *ypos) // {{{ +{ + // ['center','top','left','right','top-left','top-right','bottom', + // 'bottom-left','bottom-right'] + *xpos = CENTER; + *ypos = CENTER; + int next = 0; + + if (strcasecmp(value, "center") == 0) + return true; + else if (strncasecmp(value, "top", 3) == 0) + { + *ypos = TOP; + next = 3; + } + else if (strncasecmp(value, "bottom", 6) == 0) { + *ypos = BOTTOM; + next = 6; + } + + if (next) + { + if (value[next] == 0) + return true; + else if (value[next] != '-') + return false; + value += next + 1; + } + if (strcasecmp(value, "left") == 0) + *xpos = LEFT; + else if (strcasecmp(value, "right") == 0) + *xpos = RIGHT; + else + return false; + + return true; +} +// }}} + +static void +parseRanges(const char *range, _cfPDFToPDFIntervalSet *ret) // {{{ +{ + _cfPDFToPDFIntervalSet_clear(ret); + + if (!range) + { + _cfPDFToPDFIntervalSet_add(ret, 1, 1); + _cfPDFToPDFIntervalSet_finish(ret); + return; + } + + int lower, upper; + while (*range) + { + if (*range == '-') + { + range++; + upper = strtol(range, (char **)&range, 10); + if (upper >= 2147483647) + _cfPDFToPDFIntervalSet_add(ret, 1, 1); + else + _cfPDFToPDFIntervalSet_add(ret, 1, upper+1); + } + else + { + lower = strtol(range, (char **)&range, 10); + if (*range == '-') + { + range++; + if (!isdigit(*range)) + _cfPDFToPDFIntervalSet_add(ret, lower, lower); + else + { + upper = strtol(range, (char **)&range, 10); + if (upper >= 2147483647) + _cfPDFToPDFIntervalSet_add(ret, lower, lower); + else + _cfPDFToPDFIntervalSet_add(ret, lower, upper+1); + } + } + else + { + _cfPDFToPDFIntervalSet_add(ret, lower, lower+1); + } + } + if (*range != ',') + break; + range++; + } + _cfPDFToPDFIntervalSet_finish(ret); +} +// }}} + +static bool +_cfPDFToPDFParseBorder(const char *val, + pdftopdf_border_type_e *ret) // {{{ +{ + if (strcasecmp(val, "none") == 0) + *ret = NONE; + else if (strcasecmp(val, "single") == 0) + *ret = ONE_THIN; + else if (strcasecmp(val, "single-thick") == 0) + *ret = ONE_THICK; + else if (strcasecmp(val, "double") == 0) + *ret = TWO_THIN; + else if (strcasecmp(val, "double-thick") == 0) + *ret = TWO_THICK; + else + return false; + return true; +} +// }}} + +void +getParameters(cf_filter_data_t *data, + int num_options, + cups_option_t *options, + _cfPDFToPDFProcessingParameters *param, + pdftopdf_doc_t *doc) // {{{ +{ + char *final_content_type = data->final_content_type; + ipp_t *printer_attrs = data->printer_attrs; + ipp_t *job_attrs = data->job_attrs; + ipp_attribute_t *attr; + const char *val; + int ipprot; + int nup; + char rawlabel[256]; + char *classification; + char cookedlabel[256]; + + if ((val = cupsGetOption("copies", num_options, options)) != NULL || + (val = cupsGetOption("Copies", num_options, options)) != NULL || + (val = cupsGetOption("num-copies", num_options, options)) != NULL || + (val = cupsGetOption("NumCopies", num_options, options)) != NULL) + { + int copies = atoi(val); + if (copies > 0) + param->num_copies = copies; + } + + if (param->num_copies == 0) + param->num_copies = 1; + + if (printer_attrs != NULL && + (attr = ippFindAttribute(printer_attrs, + "landscape-orientation-requested-preferred", + IPP_TAG_ZERO)) != NULL && + ippGetInteger(attr, 0) == 5) + param->normal_landscape = ROT_270; + else + param->normal_landscape = ROT_90; + + param->orientation = ROT_0; + param->no_orientation = false; + if (optGetInt("orientation-requested", num_options, options, &ipprot)) + { + // IPP orientation values are: + // 3: 0 degrees, 4: 90 degrees, 5: -90 degrees, 6: 180 degrees + + if ((ipprot < 3) || (ipprot > 6)) + { + if (ipprot && doc->logfunc) + doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "cfFilterPDFToPDF: Bad value (%d) for " + "orientation-requested, using 0 degrees", + ipprot); + param->no_orientation = true; + } + else + { + static const pdftopdf_rotation_e + ipp2rot[4] = {ROT_0, ROT_90, ROT_270, ROT_180}; + param->orientation = ipp2rot[ipprot - 3]; + } + } + else if ((val = cupsGetOption("landscape", num_options, options)) != NULL) + { + if (!is_false(val)) + param->orientation = param->normal_landscape; + } + else + param->no_orientation = true; + + param->pagesize_requested = + (cfGetPageDimensions(printer_attrs, job_attrs, num_options, options, NULL, + 0, + &(param->page->width), &(param->page->height), + &(param->page->left), &(param->page->bottom), + &(param->page->right), &(param->page->top), + NULL, NULL) >= 1); + + cfSetPageDimensionsToDefault(&(param->page->width), &(param->page->height), + &(param->page->left), &(param->page->bottom), + &(param->page->right), &(param->page->top), + doc->logfunc, doc->logdata); + + param->page->right = param->page->width - param->page->right; + param->page->top = param->page->height - param->page->top; + + param->paper_is_landscape = (param->page->width > param->page->height); + + _cfPDFToPDFPageRect *tmp = (_cfPDFToPDFPageRect *)malloc(sizeof(_cfPDFToPDFPageRect)); + _cfPDFToPDFPageRect_init(tmp); + + optGetFloat("page-top", num_options, options, &tmp->top); + optGetFloat("page-left", num_options, options, &tmp->left); + optGetFloat("page-right", num_options, options, &tmp->right); + optGetFloat("page-bottom", num_options, options, &tmp->bottom); + + if ((val = cupsGetOption("media-top-margin", num_options, options)) + != NULL) + tmp->top = atof(val) * 72.0 / 2540.0; + if ((val = cupsGetOption("media-left-margin", num_options, options)) + != NULL) + tmp->left = atof(val) * 72.0 / 2540.0; + if ((val = cupsGetOption("media-right-margin", num_options, options)) + != NULL) + tmp->right = atof(val) * 72.0 / 2540.0; + if ((val = cupsGetOption("media-bottom-margin", num_options, options)) + != NULL) + tmp->bottom = atof(val) * 72.0 / 2540.0; + + if ((param->orientation == ROT_90) || (param->orientation == ROT_270)) + { // unrotate page + // NaN stays NaN + tmp->right = param->page->height - tmp->right; + tmp->top = param->page->width - tmp->top; + + _cfPDFToPDFPageRect_rotate_move(tmp, param->orientation, param->page->height, param->page->width); + } + else + { + tmp->right = param->page->width - tmp->right; + tmp->top = param->page->height - tmp->top; + + _cfPDFToPDFPageRect_rotate_move(tmp, param->orientation, param->page->width, param->page->height); + } + _cfPDFToPDFPageRect_set(param->page, tmp); + + if ((val = cfIPPAttrEnumValForPrinter(printer_attrs, job_attrs, "sides")) != + NULL && + strncmp(val, "two-sided-", 10) == 0) + param->duplex = true; + else if (is_true(cupsGetOption("Duplex", num_options, options))) + { + param->duplex = true; + param->set_duplex = true; + } + else if ((val = cupsGetOption("sides", num_options, options)) != NULL) + { + if ((strcasecmp(val, "two-sided-long-edge") == 0) || + (strcasecmp(val, "two-sided-short-edge") == 0)) + { + param->duplex = true; + param->set_duplex = true; + } + else if (strcasecmp(val, "one-sided") != 0) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "cfFilterPDFToPDF: Unsupported sides value %s, using sides=one-sided!", + val); + } + } + + // default nup is 1 + nup = 1; + if (optGetInt("number-up", num_options, options, &nup)) + { + if (!_cfPDFToPDFNupParameters_possible(nup)) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "cfFilterPDFToPDF: Unsupported number-up value %d, using number-up=1!", + nup); + nup = 1; + } + _cfPDFToPDFNupParameters_preset(nup, param->nup); + } + + if ((val = cupsGetOption("number-up-layout", num_options, options)) != NULL) + { + if (!_cfPDFToPDFParseNupLayout(val, param->nup)) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "cfFilterPDFToPDF: Unsupported number-up-layout %s, using number-up-layout=lrtb!", + val); + param->nup->first = X; + param->nup->xstart = LEFT; + param->nup->ystart = TOP; + } + } + + if ((val = cupsGetOption("page-border", num_options, options)) != NULL) + { + if (!_cfPDFToPDFParseBorder(val, &(param->border))) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "cfFilterPDFToPDF: Unsupported page-border value %s, using page-border=none!", + val); + param->border = NONE; + } + } + + if ((val = cupsGetOption("OutputOrder", num_options, options)) != NULL || + (val = cupsGetOption("output-order", num_options, options)) != NULL || + (val = cupsGetOption("page-delivery", num_options, options)) != NULL) + { + param->reverse = (strcasecmp(val, "Reverse") == 0 || + strcasecmp(val, "reverse-order") == 0); + } + else + { + param->reverse = cfIPPReverseOutput(printer_attrs, job_attrs); + } + + classification = getenv("CLASSIFICATION"); + if (classification) + strcpy(rawlabel, classification); + + if ((val = cupsGetOption("page-label", num_options, options)) != NULL) + { + if (strlen(rawlabel) > 0) strcat(rawlabel, " - "); + strcat(rawlabel, cupsGetOption("page-label", num_options, options)); + } + + char *rawptr = rawlabel; + char *cookedptr = cookedlabel; + while (*rawptr) + { + if (*rawptr < 32 || *rawptr > 126) + { + sprintf(cookedptr, "\\%03o", (unsigned int)*rawptr); + cookedptr += 4; + } + else + { + *cookedptr++ = *rawptr; + } + rawptr++; + } + *cookedptr = '\0'; + param->page_label = strdup(cookedlabel); + + if ((val = cupsGetOption("page-set", num_options, options)) != NULL) + { + if (strcasecmp(val, "even") == 0) + param->odd_pages = false; + else if (strcasecmp(val, "odd") == 0) + param->even_pages = false; + else if (strcasecmp(val, "all") != 0) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "cfFilterPDFToPDF: Unsupported page-set value %s, using page-set=all!", + val); + } + } + + if ((val = cupsGetOption("page-ranges", num_options, options)) != NULL) + parseRanges(val, param->page_ranges); + if ((val = cupsGetOption("input-page-ranges", num_options, options)) != NULL) + parseRanges(val, param->input_page_ranges); + + if ((val = cupsGetOption("mirror", num_options, options)) != NULL || + (val = cupsGetOption("mirror-print", num_options, options)) != NULL) + param->mirror = is_true(val); + + param->booklet = CF_PDFTOPDF_BOOKLET_OFF; + if ((val = cupsGetOption("booklet", num_options, options)) != NULL) + { + if (strcasecmp(val, "shuffle-only") == 0) + param->booklet = CF_PDFTOPDF_BOOKLET_JUST_SHUFFLE; + else if (is_true(val)) + param->booklet = CF_PDFTOPDF_BOOKLET_ON; + else if (!is_false(val)) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "cfFilterPDFToPDF: Unsupported booklet value %s, using booklet=off!", + val); + } + } + param->book_signature = -1; + if (optGetInt("booklet-signature", num_options, options, &(param->book_signature))) + { + if (param->book_signature == 0) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "cfFilterPDFToPDF: Unsupported booklet-signature value, using booklet-signature=-1 (all)!", + val); + param->book_signature = -1; + } + } + + if ((val = cupsGetOption("position", num_options, options)) != NULL) + { + if (!parsePosition(val, &(param->xpos), &(param->ypos))) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "cfFilterPDFToPDF: Unrecognized position value %s, using position=center!", + val); + param->xpos = CENTER; + param->ypos = CENTER; + } + } + + if (is_true(cupsGetOption("Collate", num_options, options))) + param->collate = true; + else if ((val = cupsGetOption("sheet-collate", num_options, options)) != NULL) + param->collate = (strcasecmp(val, "uncollated") != 0); + else if (((val = cupsGetOption("multiple-document-handling", + num_options, options)) != NULL && + (strcasecmp(val, "separate-documents-collated-copies") == 0 || + strcasecmp(val, "separate-documents-uncollated-copies") == 0 || + strcasecmp(val, "single-document") == 0 || + strcasecmp(val, "single-document-new-sheet") == 0)) || + (val = cfIPPAttrEnumValForPrinter(printer_attrs, job_attrs, + "multiple-document-handling")) != + NULL) + { + param->collate = + (strcasecmp(val, "separate-documents-uncollated-copies") != 0); + } + +#if 0 + if ((val = cupsGetOption("scaling", num_options, options)) != 0) + { + scalint = atoi(val) * 0.01; + fitplot = true + } + else if (fitplot) + scaling = 1.0; + + if ((val = cupsGetOption("natural-scaling", num_options, options)) != 0) + naturalScaling = atoi(val) * 0.01; +#endif + + // Make pages a multiple of two (only considered when duplex is on). + // i.e. printer has hardware-duplex, but needs pre-inserted filler pages + // FIXME? pdftopdf also supports it as cmdline option (via checkFeature()) + param->even_duplex = + (param->duplex && + is_true(cupsGetOption("even-duplex", num_options, options))); + + param->auto_rotate = param->no_orientation; + if ((val = cupsGetOption("pdftopdfAutoRotate", + num_options, options)) != NULL || + (val = cupsGetOption("pdfAutoRotate", num_options, options)) != NULL) + param->auto_rotate = !is_false(val); + + if ((val = cupsGetOption("ipp-attribute-fidelity", num_options, options)) != + NULL) + { + if (!strcasecmp(val, "true") || !strcasecmp(val, "yes") || + !strcasecmp(val, "on")) + param->fidelity = true; + } + + if (printer_attrs == NULL && !param->pagesize_requested && + param->booklet == CF_PDFTOPDF_BOOKLET_OFF && + param->nup->nupX == 1 && param->nup->nupY == 1) + param->cropfit = true; + + else if ((val = cupsGetOption("print-scaling", num_options, options)) != NULL) + { + // Standard IPP attribute + if (!strcasecmp(val, "auto")) + param->autoprint = true; + else if (!strcasecmp(val, "auto-fit")) + param->autofit = true; + else if (!strcasecmp(val, "fill")) + param->fillprint = true; + else if (!strcasecmp(val, "fit")) + param->fitplot = true; + else if (!strcasecmp(val, "none")) + param->cropfit = true; + else + param->autoprint = true; + } + else + { + // Legacy CUPS attributes + if ((val = cupsGetOption("fitplot", num_options, options)) == NULL) + { + if ((val = cupsGetOption("fit-to-page", num_options, options)) == NULL) + val = cupsGetOption("ipp-attribute-fidelity", num_options, options); + } + // TODO? pstops checks == "true", pdftops !is_false ... pstops says: + // fitplot only for PS (i.e. not for PDF, cmp. cgpdftopdf) + param->fitplot = (val && !is_false(val)); + + if ((val = cupsGetOption("fill", num_options, options)) != 0) + { + if (!strcasecmp(val, "true") || !strcasecmp(val, "yes")) + param->fillprint = true; + } + if ((val = cupsGetOption("crop-to-fit", num_options, options)) != NULL) + { + if (!strcasecmp(val, "true") || !strcasecmp(val, "yes")) + param->cropfit = 1; + } + if (!param->autoprint && !param->autofit && !param->fitplot && + !param->fillprint && !param->cropfit) + param->autoprint = true; + } + + // Certain features require a given page size for the page to be + // printed or all pages of the document being the same size. Here we + // set param.pagesize_requested so that the default page size is used + // when no size got specified by the user. + if (param->fitplot || param->fillprint || param->autoprint || param->autofit || + param->booklet != CF_PDFTOPDF_BOOKLET_OFF || + param->nup->nupX > 1 || param->nup->nupY > 1) + param->pagesize_requested = true; + + // + // Do we have to do the page logging in page_log? + // + // CUPS standard is that the last filter (not the backend, usually the + // printer driver) does page logging in the /var/log/cups/page_log file + // by outputting "PAGE: <# of current page> <# of copies>" to stderr. + // + // cfFilterPDFToPDF() would have to do this only for PDF printers as + // in this case cfFilterPDFToPDF() is the last filter, but some of + // the other filters are not able to do the logging because they do + // not have access to the number of pages of the file to be printed, + // so cfFilterPDFToPDF() overtakes their logging duty. + // + + // Check whether page logging is forced or suppressed by the options + + if ((val = cupsGetOption("pdf-filter-page-logging", + num_options, options)) != NULL) + { + if (strcasecmp(val, "auto") == 0) + { + param->page_logging = -1; + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Automatic page logging selected by options."); + } + else if (is_true(val)) + { + param->page_logging = 1; + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Forced page logging selected by options."); + } + else if (is_false(val)) + { + param->page_logging = 0; + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Suppressed page logging selected by options."); + } + else + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "cfFilterPDFToPDF: Unsupported page logging setting \"pdf-filter-page-logging=%s\", using \"auto\"!", + val); + param->page_logging = -1; + } + } + + if (param->page_logging == -1) + { + // We determine whether to log pages or not + // using the output data MIME type. log pages only when the output is + // either pdf or PWG Raster + if (final_content_type && + (strcasestr(final_content_type, "/pdf") || + strcasestr(final_content_type, "/vnd.cups-pdf") || + strcasestr(final_content_type, "/pwg-raster"))) + param->page_logging = 1; + else + param->page_logging = 0; + + // If final_content_type is not clearly available we are not sure whether + // to log pages or not + if (!final_content_type || + final_content_type[0] == '\0') + param->page_logging = -1; + + if (doc->logfunc) + { + doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Determined whether to log pages or not using output data type."); + doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "final_content_type = %s => page_logging = %d", + final_content_type ? final_content_type : "NULL", + param->page_logging); + } + + if (param->page_logging == -1) + param->page_logging = 0; + } + free(tmp); +} + +void +calculate(int num_options, + cups_option_t *options, + _cfPDFToPDFProcessingParameters *param, + char *final_content_type) +{ + const char *val; + bool hw_copies = false, + hw_collate = false; + + // Check options for caller's instructions about hardware copies/collate + if ((val = cupsGetOption("hardware-copies", + num_options, options)) != NULL) + // Use hardware copies according to the caller's instructions + hw_copies = is_true(val); + else + // Caller did not tell us whether the printer does Hardware copies + // or not, so we assume hardware copies on PDF printers, and software + // copies on other (usually raster) printers or if we do not know the + // final output format. + hw_copies = (final_content_type && + (strcasestr(final_content_type, "/pdf") || + strcasestr(final_content_type, "/vnd.cups-pdf"))); + + if (hw_copies) + { + if ((val = cupsGetOption("hardware-collate", + num_options, options)) != NULL) + // Use hardware collate according to the caller's instructions + hw_collate = is_true(val); + else + hw_collate = (final_content_type && + (strcasestr(final_content_type, "/pdf") || + strcasestr(final_content_type, "/vnd.cups-pdf") || + strcasestr(final_content_type, "/pwg-raster") || + strcasestr(final_content_type, "/urf") || + strcasestr(final_content_type, "/PCLm"))); + } + + if (param->reverse && param->duplex) + // Enable even_duplex or the first page may be empty. + param->even_duplex = true; // disabled later, if non-duplex + + if (param->num_copies == 1) + { + param->device_copies = 1; + // collate is never needed for a single copy + param->collate = false; // (does not make a big difference for us) + } + else if (hw_copies) + { // hw copy generation available + param->device_copies = param->num_copies; + if (param->collate) + { + param->device_collate = hw_collate; + if (!param->device_collate) + // printer can't hw collate -> we must copy collated in sw + param->device_copies = 1; + } // else: printer copies w/o collate and takes care of duplex/even_duplex + } + else + { // sw copies + param->device_copies = 1; + if (param->duplex) + { // &&(num_copies>1) + // sw collate + even_duplex must be forced to prevent copies on the + // back sides + param->collate = true; + param->device_collate = false; + } + } + + if (param->device_copies != 1) + param->num_copies = 1; + + if (param->duplex && + param->collate && !param->device_collate) + param->even_duplex = true; + + if (!param->duplex) + param->even_duplex = false; +} + +// check whether a given file is empty +bool +is_empty(FILE *f) +{ + char buf[1]; + if (fread(buf, 1, 1, f) == 0) + return true; + rewind(f); + return false; +} + +// coping inputfp data to temp_fp, so that we have a filename, as it is required in pdfioFileOpen API +int +copy_fd_to_tempfile(int inputfd, + FILE *temp_file, + pdftopdf_doc_t *doc) +{ + char buffer[BUFSIZ]; + ssize_t bytes_read, bytes_written; + + while ((bytes_read = read(inputfd, buffer, sizeof(buffer))) > 0) + { + bytes_written = fwrite(buffer, 1, bytes_read, temp_file); + if (bytes_written != bytes_read) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, "write to temporary file failed"); + return -1; + } + } + + if (bytes_read == -1) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, "Read from inputfd failed"); + return -1; + } + + return 0; +} + + +int +cfFilterPDFToPDF(int inputfd, + int outputfd, + int inputseekable, + cf_filter_data_t *data, + void *parameters) +{ + + pdftopdf_doc_t doc; + char *final_content_type = data->final_content_type; + FILE *inputfp, + *outputfp; + const char *t; + int streaming = 0; + size_t bytes; + char buf[BUFSIZ]; + cf_logfunc_t log = data->logfunc; + void *ld = data->logdata; + cf_filter_iscanceledfunc_t iscanceled = data->iscanceledfunc; + void *icd = data->iscanceleddata; + int num_options = 0; + cups_option_t *options = NULL; + + _cfPDFToPDFProcessingParameters *param = (_cfPDFToPDFProcessingParameters *)malloc(sizeof(_cfPDFToPDFProcessingParameters)); + _cfPDFToPDFProcessingParameters_init(param); + + + fprintf(stderr, "_cfPDFToPDFProcessingParameters_init has been done\n"); + + param->job_id = data->job_id; + param->user = data->job_user; + param->title = data->job_title; + param->num_copies = data->copies; + param->copies_to_be_logged = data->copies; + param->page->width = param->page->height = 0; + param->page->left = param->page->bottom = -1; + param->page->right = param->page->top = -1; + + doc.logfunc = log; + doc.logdata = ld; + doc.iscanceledfunc = iscanceled; + doc.iscanceleddata = icd; + + num_options = cfJoinJobOptionsAndAttrs(data, num_options, &options); + + getParameters(data, num_options, options, param, &doc); + calculate(num_options, options, param, final_content_type); + +#ifdef DEBUG + _cfPDFToPDFProcessingParameters_dump(param, &doc); +#endif + + // If we are in streaming mode we only apply JCL and do not run the + // job through QPDL (so no page management, form flattening, + // page size/orientation adjustment, ...) + if ((t = cupsGetOption("filter-streaming-mode", + num_options, options)) != NULL && + (strcasecmp(t, "false") && strcasecmp(t, "off") && + strcasecmp(t, "no"))) + { + streaming = 1; + if (log) log(ld, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Streaming mode: No PDF processing, only adding of JCL"); + } + + cupsFreeOptions(num_options, options); + + _cfPDFToPDF_PDFioProcessor proc; + + char temp_filename[] = "/tmp/tempfileXXXXXX"; + int temp_fd = mkstemp(temp_filename); + if (temp_fd == -1) + { + if (log) log(ld, CF_LOGLEVEL_ERROR, "tempfilename wasn't created"); + return 1; + } + + // Convert the temp_fd to a FILE* stream + inputfp = fdopen(temp_fd, "wb+"); + if (!inputfp) { + if (log) log(ld, CF_LOGLEVEL_ERROR, "Couldn't convert temp_fd to FILE* stream"); + close(temp_fd); + close(inputfd); + unlink(temp_filename); + return 1; + } + + if (copy_fd_to_tempfile(inputfd, inputfp, &doc) == -1) { + fprintf(stderr, "Failed to copy inputfd to temp file\n"); + fclose(inputfp); + close(inputfd); + unlink(temp_filename); // Clean up temporary file + return 1; + } + + rewind(inputfp); // Rewind the temp_file for reading + + if (!streaming) + { + if (is_empty(inputfp)) + { + fclose(inputfp); + close(inputfd); + unlink(temp_filename); + if (log) log(ld, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Input is empty, outputting empty file."); + return 0; + } + + //test + if (log) log(ld, CF_LOGLEVEL_DEBUG, + " cfFilterPDFToPDF: Processing PDF input with PDFio: Page-ranges, page-set, number-up, booklet, size adjustment, ..."); + + // Load the PDF input data into PDFio + if (!_cfPDFToPDF_PDFioProcessor_load_filename(&proc, temp_filename, &doc, 1)) + { + if (log) log(ld, CF_LOGLEVEL_DEBUG, "cfFilterPDFToPDF: error in _cfPDFToPDF_PDFioProcessor_load_filename"); + fclose(inputfp); + close(inputfd); + unlink(temp_filename); + return 1; + } + + // Process the PDF input data + if (!_cfProcessPDFToPDF(&proc, param, &doc)) + { + if (log) log(ld, CF_LOGLEVEL_DEBUG, "cfFilterPDFToPDF: error in _cfProcessPDFToPDF"); + return 2; + } + + // Pass information to subsequent filters via PDF comments + char *output[10]; + int output_len = 0; + + output[output_len++] = "% This file was generated by pdftopdf"; + + if (param->device_copies > 0) + { + char buf[256]; + snprintf(buf, sizeof(buf), "%d", param->device_copies); + output[output_len++] = strdup(buf); + + if (param->device_collate) + output[output_len++] = "%%PDFTOPDFCollate : true"; + else + output[output_len++] = "%%PDFTOPDFCollate : false"; + } + + _cfPDFToPDF_PDFioProcessor_set_comments(&proc, output, output_len); + } + + char temp_output_filename[] = "/tmp/outputfilenameXXXXXX"; + + int temp_output_fd = mkstemp(temp_output_filename); + if (temp_output_fd == -1) { + if (log) log(ld, CF_LOGLEVEL_ERROR, "Failed to create temporary output file"); + return 1; + } + + outputfp = fdopen(temp_output_fd, "wb+"); + if (!outputfp) { + if (log) log(ld, CF_LOGLEVEL_ERROR, "Failed to open temporary output file stream"); + close(temp_output_fd); + unlink(temp_output_filename); + return 1; + } + + if (!streaming) + { + _cfPDFToPDF_PDFioProcessor_emit_filename(&proc, temp_output_filename, &doc); + } + else + { + if (log) log(ld, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Passing on unchanged PDF data from input"); + while ((bytes = fread(buf, 1, sizeof(buf), inputfp)) > 0) + if (fwrite(buf, 1, bytes, outputfp) != bytes) + break; + fclose(inputfp); + close(inputfd); + unlink(temp_filename); + } + + fflush(outputfp); + rewind(outputfp); + char buffer[8192]; + ssize_t bytes_read; + while ((bytes_read = read(temp_output_fd, buffer, sizeof(buffer))) > 0) + { + if (write(outputfd, buffer, bytes_read) != bytes_read) { + if (log) log(ld, CF_LOGLEVEL_ERROR, "Failed to write to output_fd"); + fclose(outputfp); + close(outputfd); + unlink(temp_output_filename); + return 1; + } +} + +// free Param + _cfPDFToPDFProcessingParameters_free(param); + +// Clean up the temporary file + unlink(temp_output_filename); + fclose(outputfp); + close(inputfd); + unlink(temp_filename); + return 0; +} +// }}} diff --git a/cupsfilters/pdftopdf/pdftopdf.cxx b/cupsfilters/pdftopdf/pdftopdf.cxx deleted file mode 100644 index bdf2475d7..000000000 --- a/cupsfilters/pdftopdf/pdftopdf.cxx +++ /dev/null @@ -1,1017 +0,0 @@ -// -// Copyright (c) 2012 Tobias Hoffmann -// -// Copyright (c) 2006-2011, BBR Inc. All rights reserved. -// -// Licensed under Apache License v2.0. See the file "LICENSE" for more -// information. -// - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "pdftopdf-private.h" -#include "pdftopdf-processor-private.h" - -#include - - -// namespace {} - -static bool -optGetInt(const char *name, - int num_options, - cups_option_t *options, - int *ret) // {{{ -{ - DEBUG_assert(ret); - const char *val = cupsGetOption(name, num_options, options); - if (val) - { - *ret = atoi(val); - return (true); - } - return (false); -} -// }}} - -static bool -optGetFloat(const char *name, - int num_options, - cups_option_t *options, - float *ret) // {{{ -{ - DEBUG_assert(ret); - const char *val = cupsGetOption(name, num_options, options); - if (val) - { - *ret = atof(val); - return (true); - } - return (false); -} -// }}} - -static bool -is_false(const char *value) // {{{ -{ - if (!value) - return (false); - return ((strcasecmp(value, "no") == 0) || - (strcasecmp(value, "off") == 0) || - (strcasecmp(value, "false") == 0)); -} -// }}} - -static bool -is_true(const char *value) // {{{ -{ - if (!value) - return (false); - return ((strcasecmp(value, "yes") == 0) || - (strcasecmp(value, "on") == 0) || - (strcasecmp(value, "true") == 0)); -} -// }}} - - -static bool -parsePosition(const char *value, - pdftopdf_position_e &xpos, - pdftopdf_position_e &ypos) // {{{ -{ - // ['center','top','left','right','top-left','top-right','bottom', - // 'bottom-left','bottom-right'] - xpos = pdftopdf_position_e::CENTER; - ypos = pdftopdf_position_e::CENTER; - int next = 0; - if (strcasecmp(value, "center") == 0) - return (true); - else if (strncasecmp(value, "top", 3) == 0) - { - ypos = pdftopdf_position_e::TOP; - next = 3; - } - else if (strncasecmp(value, "bottom", 6) == 0) - { - ypos = pdftopdf_position_e::BOTTOM; - next = 6; - } - if (next) - { - if (value[next] == 0) - return (true); - else if (value[next] != '-') - return (false); - value += next + 1; - } - if (strcasecmp(value, "left") == 0) - xpos = pdftopdf_position_e::LEFT; - else if (strcasecmp(value, "right") == 0) - xpos = pdftopdf_position_e::RIGHT; - else - return (false); - return (true); -} -// }}} - -static void -parseRanges(const char *range, _cfPDFToPDFIntervalSet &ret) // {{{ -{ - ret.clear(); - if (!range) - { - ret.add(1); // everything - ret.finish(); - return; - } - - int lower, upper; - while (*range) - { - if (*range == '-') - { - range ++; - upper = strtol(range, (char **)&range, 10); - if (upper >= 2147483647) // see also cups/encode.c - ret.add(1); - else - ret.add(1, upper + 1); - } - else - { - lower = strtol(range, (char **)&range, 10); - if (*range == '-') - { - range ++; - if (!isdigit(*range)) - ret.add(lower); - else - { - upper=strtol(range, (char **)&range, 10); - if (upper>=2147483647) - ret.add(lower); - else - ret.add(lower, upper + 1); - } - } - else - ret.add(lower, lower + 1); - } - - if (*range != ',') - break; - range++; - } - ret.finish(); -} -// }}} - -static bool -_cfPDFToPDFParseBorder(const char *val, - pdftopdf_border_type_e &ret) // {{{ -{ - DEBUG_assert(val); - if (strcasecmp(val, "none") == 0) - ret = pdftopdf_border_type_e::NONE; - else if (strcasecmp(val, "single") == 0) - ret = pdftopdf_border_type_e::ONE_THIN; - else if (strcasecmp(val, "single-thick") == 0) - ret = pdftopdf_border_type_e::ONE_THICK; - else if (strcasecmp(val, "double") == 0) - ret = pdftopdf_border_type_e::TWO_THIN; - else if (strcasecmp(val, "double-thick") == 0) - ret = pdftopdf_border_type_e::TWO_THICK; - else - return (false); - return (true); -} -// }}} - -void -getParameters(cf_filter_data_t *data, - int num_options, - cups_option_t *options, - _cfPDFToPDFProcessingParameters ¶m, - pdftopdf_doc_t *doc) // {{{ -{ - char *final_content_type = data->final_content_type; - ipp_t *printer_attrs = data->printer_attrs; - ipp_t *job_attrs = data->job_attrs; - ipp_attribute_t *attr; - const char *val; - int ipprot; - int nup; - std::string rawlabel; - char *classification; - std::ostringstream cookedlabel; - - - if ((val = cupsGetOption("copies", num_options, options)) != NULL || - (val = cupsGetOption("Copies", num_options, options)) != NULL || - (val = cupsGetOption("num-copies", num_options, options)) != NULL || - (val = cupsGetOption("NumCopies", num_options, options)) != NULL) - { - int copies = atoi(val); - if (copies > 0) - param.num_copies = copies; - } - - if (param.num_copies == 0) - param.num_copies = 1; - - // direction the printer rotates landscape - // (landscape-orientation-requested-preferred: 4: 90 or 5: -90) - if (printer_attrs != NULL && - (attr = ippFindAttribute(printer_attrs, - "landscape-orientation-requested-preferred", - IPP_TAG_ZERO)) != NULL && - ippGetInteger(attr, 0) == 5) - param.normal_landscape = ROT_270; - else - param.normal_landscape = ROT_90; - - param.orientation = ROT_0; - param.no_orientation = false; - if (optGetInt("orientation-requested", num_options, options, &ipprot)) - { - // IPP orientation values are: - // 3: 0 degrees, 4: 90 degrees, 5: -90 degrees, 6: 180 degrees - - if ((ipprot < 3) || (ipprot > 6)) - { - if (ipprot && doc->logfunc) - doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: Bad value (%d) for " - "orientation-requested, using 0 degrees", - ipprot); - param.no_orientation = true; - } - else - { - static const pdftopdf_rotation_e - ipp2rot[4]={ROT_0, ROT_90, ROT_270, ROT_180}; - param.orientation = ipp2rot[ipprot - 3]; - } - } - else if ((val = cupsGetOption("landscape", num_options, options)) != NULL) - { - if (!is_false(val)) - param.orientation = param.normal_landscape; - } - else - param.no_orientation = true; - - param.pagesize_requested = - (cfGetPageDimensions(printer_attrs, job_attrs, num_options, options, NULL, - 0, - &(param.page.width), &(param.page.height), - &(param.page.left), &(param.page.bottom), - &(param.page.right), &(param.page.top), - NULL, NULL) >= 1); - - cfSetPageDimensionsToDefault(&(param.page.width), &(param.page.height), - &(param.page.left), &(param.page.bottom), - &(param.page.right), &(param.page.top), - doc->logfunc, doc->logdata); - - param.page.right = param.page.width - param.page.right; - param.page.top = param.page.height - param.page.top; - - param.paper_is_landscape = (param.page.width > param.page.height); - - _cfPDFToPDFPageRect tmp; // borders (before rotation) - - optGetFloat("page-top", num_options, options, &tmp.top); - optGetFloat("page-left", num_options, options, &tmp.left); - optGetFloat("page-right", num_options, options, &tmp.right); - optGetFloat("page-bottom", num_options, options, &tmp.bottom); - - if ((val = cupsGetOption("media-top-margin", num_options, options)) - != NULL) - tmp.top = atof(val) * 72.0 / 2540.0; - if ((val = cupsGetOption("media-left-margin", num_options, options)) - != NULL) - tmp.left = atof(val) * 72.0 / 2540.0; - if ((val = cupsGetOption("media-right-margin", num_options, options)) - != NULL) - tmp.right = atof(val) * 72.0 / 2540.0; - if ((val = cupsGetOption("media-bottom-margin", num_options, options)) - != NULL) - tmp.bottom = atof(val) * 72.0 / 2540.0; - - if ((param.orientation == ROT_90) || (param.orientation == ROT_270)) - { // unrotate page - // NaN stays NaN - tmp.right = param.page.height - tmp.right; - tmp.top = param.page.width - tmp.top; - tmp.rotate_move(param.orientation, param.page.height, param.page.width); - } - else - { - tmp.right = param.page.width - tmp.right; - tmp.top = param.page.height - tmp.top; - tmp.rotate_move(param.orientation, param.page.width, param.page.height); - } - - param.page.set(tmp); // replace values, where tmp.* != NaN - // (because tmp needed rotation, param.page not!) - - if ((val = cfIPPAttrEnumValForPrinter(printer_attrs, job_attrs, "sides")) != - NULL && - strncmp(val, "two-sided-", 10) == 0) - param.duplex = true; - else if (is_true(cupsGetOption("Duplex", num_options, options))) - { - param.duplex = true; - param.set_duplex = true; - } - else if ((val = cupsGetOption("sides", num_options, options)) != NULL) - { - if ((strcasecmp(val, "two-sided-long-edge") == 0) || - (strcasecmp(val, "two-sided-short-edge") == 0)) - { - param.duplex = true; - param.set_duplex = true; - } - else if (strcasecmp(val, "one-sided") != 0) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: Unsupported sides value %s, using sides=one-sided!", - val); - } - } - - // default nup is 1 - nup = 1; - if (optGetInt("number-up", num_options, options, &nup)) - { - if (!_cfPDFToPDFNupParameters::possible(nup)) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: Unsupported number-up value %d, using number-up=1!", - nup); - nup = 1; - } - // TODO ; TODO? nup enabled? ... fitplot - // _cfPDFToPDFNupParameters::calculate(nup, param.nup); - _cfPDFToPDFNupParameters::preset(nup, param.nup); - } - - if ((val = cupsGetOption("number-up-layout", num_options, options)) != NULL) - { - if (!_cfPDFToPDFParseNupLayout(val, param.nup)) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: Unsupported number-up-layout %s, using number-up-layout=lrtb!", - val); - param.nup.first = pdftopdf_axis_e::X; - param.nup.xstart = pdftopdf_position_e::LEFT; - param.nup.ystart = pdftopdf_position_e::TOP; - } - } - - if ((val = cupsGetOption("page-border", num_options, options)) != NULL) - { - if (!_cfPDFToPDFParseBorder(val, param.border)) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: Unsupported page-border value %s, using page-border=none!", - val); - param.border = pdftopdf_border_type_e::NONE; - } - } - - if ((val=cupsGetOption("OutputOrder",num_options,options)) != NULL || - (val=cupsGetOption("output-order",num_options,options)) != NULL || - (val=cupsGetOption("page-delivery",num_options,options)) != NULL) - { - param.reverse = (strcasecmp(val, "Reverse") == 0 || - strcasecmp(val, "reverse-order") == 0); - } - else - param.reverse = cfIPPReverseOutput(printer_attrs, job_attrs); - - classification = getenv("CLASSIFICATION"); - if (classification) - rawlabel.append (classification); - - if ((val = cupsGetOption("page-label", num_options, options)) != NULL) - { - if (!rawlabel.empty()) - rawlabel.append (" - "); - rawlabel.append(cupsGetOption("page-label", num_options, options)); - } - - for (std::string::iterator it = rawlabel.begin(); - it != rawlabel.end (); - ++ it) - { - if (*it < 32 || *it > 126) - cookedlabel << "\\" << std::oct << std::setfill('0') << std::setw(3) << (unsigned int) *it; - else - cookedlabel.put (*it); - } - param.page_label = cookedlabel.str (); - - if ((val = cupsGetOption("page-set", num_options, options)) != NULL) - { - if (strcasecmp(val, "even") == 0) - param.odd_pages = false; - else if (strcasecmp(val, "odd") == 0) - param.even_pages = false; - else if (strcasecmp(val, "all") != 0) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: Unsupported page-set value %s, using page-set=all!", - val); - } - } - - if ((val = cupsGetOption("page-ranges", num_options, options)) != NULL) - parseRanges(val, param.page_ranges); - - if ((val = cupsGetOption("input-page-ranges", num_options, options)) != NULL) - parseRanges(val, param.input_page_ranges); - - if ((val = cupsGetOption("mirror", num_options, options)) != NULL || - (val = cupsGetOption("mirror-print", num_options, options)) != NULL) - param.mirror = is_true(val); - - param.booklet = pdftopdf_booklet_mode_e::CF_PDFTOPDF_BOOKLET_OFF; - if ((val = cupsGetOption("booklet", num_options, options)) != NULL) - { - if (strcasecmp(val, "shuffle-only") == 0) - param.booklet = pdftopdf_booklet_mode_e::CF_PDFTOPDF_BOOKLET_JUST_SHUFFLE; - else if (is_true(val)) - param.booklet = pdftopdf_booklet_mode_e::CF_PDFTOPDF_BOOKLET_ON; - else if (!is_false(val)) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: Unsupported booklet value %s, using booklet=off!", - val); - } - } - param.book_signature = -1; - if (optGetInt("booklet-signature", num_options, options, - ¶m.book_signature)) - { - if (param.book_signature == 0) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: Unsupported booklet-signature value, using booklet-signature=-1 (all)!", - val); - param.book_signature = -1; - } - } - - if ((val = cupsGetOption("position", num_options, options)) != NULL) - { - if (!parsePosition(val, param.xpos, param.ypos)) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: Unrecognized position value %s, using position=center!", - val); - param.xpos = pdftopdf_position_e::CENTER; - param.ypos = pdftopdf_position_e::CENTER; - } - } - - // Collate - if (is_true(cupsGetOption("Collate", num_options, options))) - param.collate = true; - else if ((val = cupsGetOption("sheet-collate", num_options, options)) != NULL) - param.collate = (strcasecmp(val, "uncollated") != 0); - else if (((val = cupsGetOption("multiple-document-handling", - num_options, options)) != NULL && - (strcasecmp(val, "separate-documents-collated-copies") == 0 || - strcasecmp(val, "separate-documents-uncollated-copies") == 0 || - strcasecmp(val, "single-document") == 0 || - strcasecmp(val, "single-document-new-sheet") == 0)) || - (val = cfIPPAttrEnumValForPrinter(printer_attrs, job_attrs, - "multiple-document-handling")) != - NULL) - // - // This IPP attribute is unnecessarily complicated: - // single-document, separate-documents-collated-copies, - // single-document-new-sheet: - // -> collate (true) - // separate-documents-uncollated-copies: - // -> can be uncollated (false) - // - param.collate = - (strcasecmp(val, "separate-documents-uncollated-copies") != 0); - -#if 0 - // TODO: scaling - // TODO: natural-scaling - - // Scaling - if ((val = cupsGetOption("scaling", num_options, options)) != 0) - { - scaling = atoi(val) * 0.01; - fitplot = true; - } - else if (fitplot) - scaling = 1.0; - if ((val = cupsGetOption("natural-scaling", num_options, options)) != 0) - naturalScaling = atoi(val) * 0.01; -#endif - - // Make pages a multiple of two (only considered when duplex is on). - // i.e. printer has hardware-duplex, but needs pre-inserted filler pages - // FIXME? pdftopdf also supports it as cmdline option (via checkFeature()) - param.even_duplex = - (param.duplex && - is_true(cupsGetOption("even-duplex", num_options, options))); - - // TODO? pdftopdf* ? - // TODO?! pdftopdfAutoRotate - - // TODO?! Choose default by whether pdfautoratate filter has already been - // run (e.g. by mimetype) - param.auto_rotate = param.no_orientation; - if ((val = cupsGetOption("pdftopdfAutoRotate", - num_options, options)) != NULL || - (val = cupsGetOption("pdfAutoRotate", num_options, options)) != NULL) - param.auto_rotate = !is_false(val); - - if ((val = cupsGetOption("ipp-attribute-fidelity", num_options, options)) != - NULL) - { - if (!strcasecmp(val, "true") || !strcasecmp(val, "yes") || - !strcasecmp(val, "on")) - param.fidelity = true; - } - - if (printer_attrs == NULL && !param.pagesize_requested && - param.booklet == CF_PDFTOPDF_BOOKLET_OFF && - param.nup.nupX == 1 && param.nup.nupY && 1) - // With no printer capability info and, no given page size, and no - // requirement of all pages being the same size just use the input page sizes - param.cropfit = true; - else if ((val = cupsGetOption("print-scaling", num_options, options)) != NULL) - { - // Standard IPP attribute - if (!strcasecmp(val, "auto")) - param.autoprint = true; - else if (!strcasecmp(val, "auto-fit")) - param.autofit = true; - else if (!strcasecmp(val, "fill")) - param.fillprint = true; - else if (!strcasecmp(val, "fit")) - param.fitplot = true; - else if (!strcasecmp(val, "none")) - param.cropfit = true; - else - param.autoprint = true; - } - else - { - // Legacy CUPS attributes - if ((val = cupsGetOption("fitplot", num_options, options)) == NULL) - { - if ((val = cupsGetOption("fit-to-page", num_options, options)) == NULL) - val = cupsGetOption("ipp-attribute-fidelity", num_options, options); - } - // TODO? pstops checks == "true", pdftops !is_false ... pstops says: - // fitplot only for PS (i.e. not for PDF, cmp. cgpdftopdf) - param.fitplot = (val) && (!is_false(val)); - - if ((val = cupsGetOption("fill", num_options, options)) != 0) - { - if (!strcasecmp(val, "true") || !strcasecmp(val, "yes")) - param.fillprint = true; - } - if ((val = cupsGetOption("crop-to-fit", num_options, options)) != NULL) - { - if (!strcasecmp(val, "true") || !strcasecmp(val, "yes")) - param.cropfit=1; - } - if (!param.autoprint && !param.autofit && !param.fitplot && - !param.fillprint && !param.cropfit) - param.autoprint = true; - } - - // Certain features require a given page size for the page to be - // printed or all pages of the document being the same size. Here we - // set param.pagesize_requested so that the default page size is used - // when no size got specified by the user. - if (param.fitplot || param.fillprint || param.autoprint || param.autofit || - param.booklet != CF_PDFTOPDF_BOOKLET_OFF || - param.nup.nupX > 1 || param.nup.nupY > 1) - param.pagesize_requested = true; - - // - // Do we have to do the page logging in page_log? - // - // CUPS standard is that the last filter (not the backend, usually the - // printer driver) does page logging in the /var/log/cups/page_log file - // by outputting "PAGE: <# of current page> <# of copies>" to stderr. - // - // cfFilterPDFToPDF() would have to do this only for PDF printers as - // in this case cfFilterPDFToPDF() is the last filter, but some of - // the other filters are not able to do the logging because they do - // not have access to the number of pages of the file to be printed, - // so cfFilterPDFToPDF() overtakes their logging duty. - // - - // Check whether page logging is forced or suppressed by the options - if ((val = cupsGetOption("pdf-filter-page-logging", - num_options, options)) != NULL) - { - if (strcasecmp(val, "auto") == 0) - { - param.page_logging = -1; - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Automatic page logging selected by options."); - } - else if (is_true(val)) - { - param.page_logging = 1; - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Forced page logging selected by options."); - } - else if (is_false(val)) - { - param.page_logging = 0; - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Suppressed page logging selected by options."); - } - else - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: Unsupported page logging setting \"pdf-filter-page-logging=%s\", using \"auto\"!", - val); - param.page_logging = -1; - } - } - - if (param.page_logging == -1) - { - // We determine whether to log pages or not - // using the output data MIME type. log pages only when the output is - // either pdf or PWG Raster - if (final_content_type && - (strcasestr(final_content_type, "/pdf") || - strcasestr(final_content_type, "/vnd.cups-pdf") || - strcasestr(final_content_type, "/pwg-raster"))) - param.page_logging = 1; - else - param.page_logging = 0; - - // If final_content_type is not clearly available we are not sure whether - // to log pages or not - if ((char*)final_content_type == NULL || - sizeof(final_content_type) == 0 || - final_content_type[0] == '\0') - param.page_logging = -1; - if (doc->logfunc) - { - doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Determined whether to log pages or not using output data type."); - doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "final_content_type = %s => page_logging = %d", - final_content_type ? final_content_type : "NULL", - param.page_logging); - } - if (param.page_logging == -1) - param.page_logging = 0; - } -} -// }}} - -void -calculate(int num_options, - cups_option_t *options, - _cfPDFToPDFProcessingParameters ¶m, - char *final_content_type) // {{{ -{ - const char *val; - bool hw_copies = false, - hw_collate = false; - - - // Check options for caller's instructions about hardware copies/collate - if ((val = cupsGetOption("hardware-copies", - num_options, options)) != NULL) - // Use hardware copies according to the caller's instructions - hw_copies = is_true(val); - else - // Caller did not tell us whether the printer does Hardware copies - // or not, so we assume hardware copies on PDF printers, and software - // copies on other (usually raster) printers or if we do not know the - // final output format. - hw_copies = (final_content_type && - (strcasestr(final_content_type, "/pdf") || - strcasestr(final_content_type, "/vnd.cups-pdf"))); - if (hw_copies) - { - if ((val = cupsGetOption("hardware-collate", - num_options, options)) != NULL) - // Use hardware collate according to the caller's instructions - hw_collate = is_true(val); - else - // Check output format MIME type whether it is - // of a driverless IPP printer (PDF, Apple Raster, PWG Raster, PCLm). - // These printers do always hardware collate if they do hardware copies. - // https://github.com/apple/cups/issues/5433 - hw_collate = (final_content_type && - (strcasestr(final_content_type, "/pdf") || - strcasestr(final_content_type, "/vnd.cups-pdf") || - strcasestr(final_content_type, "/pwg-raster") || - strcasestr(final_content_type, "/urf") || - strcasestr(final_content_type, "/PCLm"))); - } - - if (param.reverse && param.duplex) - // Enable even_duplex or the first page may be empty. - param.even_duplex = true; // disabled later, if non-duplex - - if (param.num_copies == 1) - { - param.device_copies = 1; - // collate is never needed for a single copy - param.collate = false; // (does not make a big difference for us) - } - else if (hw_copies) - { // hw copy generation available - param.device_copies = param.num_copies; - if (param.collate) - { // collate requested by user - param.device_collate = hw_collate; - if (!param.device_collate) - // printer can't hw collate -> we must copy collated in sw - param.device_copies = 1; - } // else: printer copies w/o collate and takes care of duplex/even_duplex - } - else - { // sw copies - param.device_copies = 1; - if (param.duplex) - { // &&(num_copies>1) - // sw collate + even_duplex must be forced to prevent copies on the - // back sides - param.collate = true; - param.device_collate = false; - } - } - - if (param.device_copies != 1) // hw copy - param.num_copies = 1; // disable sw copy - - if (param.duplex && - param.collate && !param.device_collate) // software collate - param.even_duplex = true; // fillers always needed - - if (!param.duplex) - param.even_duplex = false; -} -// }}} - -// reads from stdin into temporary file. returns FILE * or NULL on error -FILE * -copy_fd_to_temp(int infd, - pdftopdf_doc_t *doc) // {{{ -{ - char buf[BUFSIZ]; - int n; - - // FIXME: what does >buf mean here? - int outfd = cupsCreateTempFd(NULL, NULL, buf, sizeof(buf)); - if (outfd < 0) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: Can't create temporary file"); - return (NULL); - } - // remove name - unlink(buf); - - // copy stdin to the tmp file - while ((n = read(infd, buf, BUFSIZ)) > 0) - { - if (write(outfd, buf, n) != n) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: Can't copy stdin to temporary file"); - close(outfd); - return (NULL); - } - } - if (lseek(outfd, 0, SEEK_SET) < 0) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: Can't rewind temporary file"); - close(outfd); - return (NULL); - } - - FILE *f; - if ((f = fdopen(outfd, "rb")) == 0) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: Can't fdopen temporary file"); - close(outfd); - return (NULL); - } - return f; -} -// }}} - -// check whether a given file is empty -bool is_empty(FILE *f) // {{{ -{ - char buf[1]; - - // Try to read a single byte of data - if (fread(buf, 1, 1, f) == 0) - return (true); - - rewind(f); - - return (false); -} -// }}} - - -int // O - Error status -cfFilterPDFToPDF(int inputfd, // I - File descriptor input stream - int outputfd, // I - File descriptor output stream - int inputseekable, // I - Is input stream seekable? - cf_filter_data_t *data, // I - Job and printer data - void *parameters) // I - Filter-specific parameters - // (unused) -{ - pdftopdf_doc_t doc; // Document information - char *final_content_type = data->final_content_type; - FILE *inputfp, - *outputfp; - const char *t; - int streaming = 0; - size_t bytes; - char buf[BUFSIZ]; - cf_logfunc_t log = data->logfunc; - void *ld = data->logdata; - cf_filter_iscanceledfunc_t iscanceled = data->iscanceledfunc; - void *icd = data->iscanceleddata; - int num_options = 0; - cups_option_t *options = NULL; - - - try - { - _cfPDFToPDFProcessingParameters param; - - param.job_id = data->job_id; - param.user = data->job_user; - param.title = data->job_title; - param.num_copies = data->copies; - param.copies_to_be_logged = data->copies; - param.page.width = param.page.height = 0; - param.page.left = param.page.bottom = -1; - param.page.right = param.page.top = -1; - - // TODO?! sanity checks - - doc.logfunc = log; - doc.logdata = ld; - doc.iscanceledfunc = iscanceled; - doc.iscanceleddata = icd; - - num_options = cfJoinJobOptionsAndAttrs(data, num_options, &options); - - getParameters(data, num_options, options, param, &doc); - - calculate(num_options, options, param, final_content_type); - -#ifdef DEBUG - param.dump(&doc); -#endif - - // If we are in streaming mode we only apply JCL and do not run the - // job through QPDL (so no page management, form flattening, - // page size/orientation adjustment, ...) - if ((t = cupsGetOption("filter-streaming-mode", - num_options, options)) != NULL && - (strcasecmp(t, "false") && strcasecmp(t, "off") && - strcasecmp(t, "no"))) - { - streaming = 1; - if (log) log(ld, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Streaming mode: No PDF processing, only adding of JCL"); - } - - cupsFreeOptions(num_options, options); - - std::unique_ptr<_cfPDFToPDFProcessor> proc(_cfPDFToPDFFactory::processor()); - - if ((inputseekable && inputfd > 0) || streaming) - { - if ((inputfp = fdopen(inputfd, "rb")) == NULL) - return (1); - } - else - { - if ((inputfp = copy_fd_to_temp(inputfd, &doc)) == NULL) - return (1); - } - - if (!streaming) - { - if (is_empty(inputfp)) - { - fclose(inputfp); - if (log) log(ld, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Input is empty, outputting empty file."); - return (0); - } - - if (log) log(ld, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Processing PDF input with QPDF: Page-ranges, page-set, number-up, booklet, size adjustment, ..."); - - // Load the PDF input data into QPDF - if (!proc->load_file(inputfp, &doc, CF_PDFTOPDF_WILL_STAY_ALIVE, 1)) - { - fclose(inputfp); - return (1); - } - - // Process the PDF input data - if (!_cfProcessPDFToPDF(*proc, param, &doc)) - return (2); - - // Pass information to subsequent filters via PDF comments - std::vector output; - - output.push_back("% This file was generated by pdftopdf"); - - // This is not standard, but like PostScript. - if (param.device_copies > 0) - { - char buf[256]; - snprintf(buf, sizeof(buf), "%d", param.device_copies); - output.push_back(std::string("%%PDFTOPDFNumCopies : ")+buf); - - if (param.device_collate) - output.push_back("%%PDFTOPDFCollate : true"); - else - output.push_back("%%PDFTOPDFCollate : false"); - } - - proc->set_comments(output); - } - - outputfp = fdopen(outputfd, "w"); - if (outputfp == NULL) - return (1); - - if (!streaming) - { - // Pass on the processed input data - proc->emit_file(outputfp, &doc, CF_PDFTOPDF_WILL_STAY_ALIVE); - // proc->emit_filename(NULL); - } - else - { - // Pass through the input data - if (log) log(ld, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Passing on unchanged PDF data from input"); - while ((bytes = fread(buf, 1, sizeof(buf), inputfp)) > 0) - if (fwrite(buf, 1, bytes, outputfp) != bytes) - break; - fclose(inputfp); - } - - fclose(outputfp); - } - catch (std::exception &e) - { - // TODO? exception type - if (log) log(ld, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: Exception: %s", e.what()); - return (5); - } - catch (...) - { - if (log) log(ld, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: Unknown exception caught. Exiting."); - return (6); - } - - return (0); -} diff --git a/cupsfilters/pdftopdf/pptypes-private.h b/cupsfilters/pdftopdf/pptypes-private.h index 0cacf8e0b..c0aa7cd02 100644 --- a/cupsfilters/pdftopdf/pptypes-private.h +++ b/cupsfilters/pdftopdf/pptypes-private.h @@ -1,4 +1,6 @@ // +// Copyright 2024 Uddhav Phatak +// // Licensed under Apache License v2.0. See the file "LICENSE" for more // information. // @@ -7,67 +9,72 @@ #define _CUPS_FILTERS_PDFTOPDF_PPTYPES_H_ #include "pdftopdf-private.h" -#include // NAN +#include -// namespace PPTypes {} TODO? +typedef enum +{ + X, + Y +} pdftopdf_axis_e; -enum pdftopdf_axis_e { X, Y }; -enum pdftopdf_position_e { // PS order +typedef enum +{ CENTER = 0, LEFT = -1, RIGHT = 1, TOP = 1, BOTTOM = -1 -}; +} pdftopdf_position_e; void _cfPDFToPDFPositionDump(pdftopdf_position_e pos, pdftopdf_doc_t *doc); -void _cfPDFToPDFPositionDump(pdftopdf_position_e pos, pdftopdf_axis_e axis, - pdftopdf_doc_t *doc); +void _cfPDFToPDFPositionAndAxisDump(pdftopdf_position_e pos, pdftopdf_axis_e axis, + pdftopdf_doc_t *doc); -enum pdftopdf_rotation_e { ROT_0, ROT_90, ROT_180, ROT_270 }; // CCW +typedef enum +{ + ROT_0, + ROT_90, + ROT_180, + ROT_270, +} pdftopdf_rotation_e; void _cfPDFToPDFRotationDump(pdftopdf_rotation_e rot, pdftopdf_doc_t *doc); -pdftopdf_rotation_e operator+(pdftopdf_rotation_e lhs, pdftopdf_rotation_e rhs); -pdftopdf_rotation_e operator-(pdftopdf_rotation_e lhs, pdftopdf_rotation_e rhs); -pdftopdf_rotation_e operator-(pdftopdf_rotation_e rhs); -//pdftopdf_rotation_e operator+=(pdftopdf_rotation_e &lhs, -// pdftopdf_rotation_e rhs); -enum pdftopdf_border_type_e { +pdftopdf_rotation_e pdftopdf_rotation_add(pdftopdf_rotation_e lhs, pdftopdf_rotation_e rhs); +pdftopdf_rotation_e pdftopdf_rotation_sub(pdftopdf_rotation_e lhs, pdftopdf_rotation_e rhs); +pdftopdf_rotation_e pdftopdf_rotation_neg(pdftopdf_rotation_e rhs); + +typedef enum +{ NONE = 0, ONE_THIN = 2, ONE_THICK = 3, - TWO_THIN = 4, + TWO_THIN = 4, TWO_THICK = 5, ONE = 0x02, TWO = 0x04, - THICK = 0x01 -}; + THICK = 0x01 +} pdftopdf_border_type_e; void _cfPDFToPDFBorderTypeDump(pdftopdf_border_type_e border, - pdftopdf_doc_t *doc); - -struct _cfPDFToPDFPageRect { -_cfPDFToPDFPageRect() - : top(NAN), - left(NAN), - right(NAN), - bottom(NAN), - width(NAN), - height(NAN) {} + pdftopdf_doc_t *doc); + +typedef struct +{ float top, left, right, bottom; // i.e. margins float width, height; +} _cfPDFToPDFPageRect; + +void _cfPDFToPDFPageRect_init(_cfPDFToPDFPageRect *rect); + +void _cfPDFToPDFPageRect_rotate_move(_cfPDFToPDFPageRect *rect, pdftopdf_rotation_e r, float pwidth, float pheight); + +void _cfPDFToPDFPageRect_scale(_cfPDFToPDFPageRect *rect, float mult); - void rotate_move(pdftopdf_rotation_e r, float pwidth, float pheight); - // pwidth original "page size" (i.e. before rotation) - void scale(float mult); - void translate(float tx, float ty); +void _cfPDFToPDFPageRect_translate(_cfPDFToPDFPageRect *rect, float tx, float ty); - void set(const _cfPDFToPDFPageRect &rhs); // only for rhs.* != NAN - void dump(pdftopdf_doc_t *doc) const; -}; +void _cfPDFToPDFPageRect_set(_cfPDFToPDFPageRect *rect, const _cfPDFToPDFPageRect *rhs); -// bool _cfPDFToPDFParseBorder(const char *val,pdftopdf_border_type_e &ret); -// // none, single, ..., double-thick +void _cfPDFToPDFPageRect_dump(const _cfPDFToPDFPageRect *rect, pdftopdf_doc_t *doc); #endif // !_CUPS_FILTERS_PDFTOPDF_PPTYPES_H_ diff --git a/cupsfilters/pdftopdf/pptypes.c b/cupsfilters/pdftopdf/pptypes.c new file mode 100644 index 000000000..5bd497f93 --- /dev/null +++ b/cupsfilters/pdftopdf/pptypes.c @@ -0,0 +1,279 @@ +// +//Copyright 2024 Uddhav Phatak +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "pptypes-private.h" +#include +#include +#include "cupsfilters/debug-internal.h" + +void +_cfPDFToPDFPositionDump(pdftopdf_position_e pos, + pdftopdf_doc_t *doc) // {{{ +{ + static const char *pstr[3] = {"Left/Bottom", "Center", "Right/Top"}; + if ((pos < LEFT) || (pos > RIGHT)) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: (bad position: %d)", pos); + } + else + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: %s", pstr[pos+1]); + } +} +// }}} + +void +_cfPDFToPDFPositionAndAxisDump(pdftopdf_position_e pos, + pdftopdf_axis_e axis, + pdftopdf_doc_t *doc) // {{{ +{ + if ((pos < LEFT) || (pos > RIGHT)) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Position %s: (bad position: %d)", + (axis == X) ? "X" : "Y", pos); + return; + } + + if (axis == X) + { + static const char *pxstr[3] = {"Left", "Center", "Right"}; + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Position X: %s", pxstr[pos + 1]); + } + + else + { + static const char *pystr[3] = {"Bottom", "Center", "Top"}; + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Position Y: %s", pystr[pos + 1]); + + } +} +// }}} + +void +_cfPDFToPDFRotationDump(pdftopdf_rotation_e rot, + pdftopdf_doc_t *doc) // {{{ +{ + static const char *rstr[4] = {"0 deg", "90 deg", "180 deg", "270 deg"}; // CCW + + if ((rot < ROT_0) || (rot > ROT_270)) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Rotation(CCW): (bad rotation: %d)", rot); + } + + else + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Rotation(CCW): %s", rstr[rot]); + } +} +// }}} + +pdftopdf_rotation_e +pdftopdf_rotation_add(pdftopdf_rotation_e lhs, + pdftopdf_rotation_e rhs) // {{{ +{ + return (pdftopdf_rotation_e)(((int)lhs + (int)rhs) % 4); +} +// }}} + +pdftopdf_rotation_e +pdftopdf_rotation_sub(pdftopdf_rotation_e lhs, + pdftopdf_rotation_e rhs) // {{{ +{ + return (pdftopdf_rotation_e)((((int)lhs - (int)rhs) % 4 + 4) % 4); +} +// }}} + +pdftopdf_rotation_e +pdftopdf_rotation_neg(pdftopdf_rotation_e rhs) // {{{ +{ + return (pdftopdf_rotation_e)((4 - (int)rhs) % 4); +} +// }}} + +void +_cfPDFToPDFBorderTypeDump(pdftopdf_border_type_e border, + pdftopdf_doc_t *doc) // {{{ +{ + if ((border < NONE) || (border == 1) || (border > TWO_THICK)) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Border: (bad border: %d)", border); + + } + else + { + static const char *bstr[6] = + {"None", NULL, "one thin", "one thick", "two thin", "two thick"}; + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: Border: %s", bstr[border]); + } +} +// }}} + +void +_cfPDFToPDFPageRect_init(_cfPDFToPDFPageRect *rect) // {{{ +{ + rect->top = NAN; + rect->left = NAN; + rect->right = NAN; + rect->bottom = NAN; + rect->width = NAN; + rect->height = NAN; +} +// {{{ + +void +swap_float(float a, float b) // {{{ +{ + float temp = a; + a = b; + b = temp; +} +// }}} + +void +_cfPDFToPDFPageRect_rotate_move(_cfPDFToPDFPageRect *rect, + pdftopdf_rotation_e r, + float pwidth, float pheight) // {{{ +{ + #if 1 + if (r >= ROT_180) + { + swap_float(rect->top, rect->bottom); + swap_float(rect->left, rect->right); + } + + if ((r == ROT_90) || (r == ROT_270)) + { + const float tmp = rect->bottom; + rect->bottom = rect->left; + rect->left = rect->top; + rect->top = rect->right; + rect->right = tmp; + + swap_float(rect->width, rect->height); + swap_float(pwidth, pheight); + } + + if ((r == ROT_90) || (r == ROT_180)) + { + rect->left = pwidth - rect->left; + rect->right = pwidth - rect->right; + } + + if ((r == ROT_270) || (r == ROT_180)) + { + rect->top = pheight - rect->top; + rect->bottom = pheight - rect->bottom; + } + + #else + switch(r) + { + case ROT_0: // no-op + break; + case ROT_90: + const float tmp0 = bottom; + bottom = left; + left = pheight - top; + top = right; + right = pheight - tmp0; + + swap_float(&width, &height); + break; + + case ROT_180: + const float tmp1 = left; + left = pwidth - right; + right = pwidth - tmp1; + + const float tmp2 = top; + top = pheight - bottom; + bottom = pheight - tmp2; + break; + + case ROT_270: + const float tmp3 = top; + top = pwidth - left; + left = bottom; + bottom = pwidth - right; + right = tmp3; + + swap_float(&width, &height); + break; + + } + #endif +} +// }}} + +void +_cfPDFToPDFPageRect_scale(_cfPDFToPDFPageRect *rect, + float mult) // {{{ +{ + if (mult == 1.0) + return; + + rect->bottom *= mult; + rect->left *= mult; + rect->top *= mult; + rect->right *= mult; + + rect->width *= mult; + rect->height *= mult; +} +// }}} + +void +_cfPDFToPDFPageRect_translate(_cfPDFToPDFPageRect *rect, + float tx, + float ty) // {{{ +{ + rect->left += tx; + rect->bottom += ty; + rect->right += tx; + rect->top += ty; +} +// }}} + +void +_cfPDFToPDFPageRect_set(_cfPDFToPDFPageRect *rect, + const _cfPDFToPDFPageRect *rhs) // {{{ +{ + if (!isnan(rhs->top)) + rect->top = rhs->top; + + if (!isnan(rhs->left)) + rect->left = rhs->left; + + if (!isnan(rhs->right)) + rect->right = rhs->right; + + if (!isnan(rhs->bottom)) + rect->bottom = rhs->bottom; +} +// }}} + +void +_cfPDFToPDFPageRect_dump(const _cfPDFToPDFPageRect *rect, + pdftopdf_doc_t *doc) // {{{ +{ + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPDFToPDF: top: %f, left: %f, right: %f, bottom: %f, " + "width: %f, height: %f", + rect->top, rect->left, rect->right, rect->bottom, + rect->width, rect->height); + +} +// }}} diff --git a/cupsfilters/pdftopdf/pptypes.cxx b/cupsfilters/pdftopdf/pptypes.cxx deleted file mode 100644 index f8158b481..000000000 --- a/cupsfilters/pdftopdf/pptypes.cxx +++ /dev/null @@ -1,238 +0,0 @@ -// -// Licensed under Apache License v2.0. See the file "LICENSE" for more -// information. -// - -#include "pptypes-private.h" -#include -#include -#include "cupsfilters/debug-internal.h" - -void -_cfPDFToPDFPositionDump(pdftopdf_position_e pos, - pdftopdf_doc_t *doc) // {{{ -{ - static const char *pstr[3] = {"Left/Bottom", "Center", "Right/Top"}; - if ((pos < LEFT) || (pos > RIGHT)) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: (bad position: %d)", pos); - } - else - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: %s", pstr[pos+1]); - } -} -// }}} - -void -_cfPDFToPDFPositionDump(pdftopdf_position_e pos, - pdftopdf_axis_e axis, - pdftopdf_doc_t *doc) // {{{ -{ - DEBUG_assert((axis == pdftopdf_axis_e::X) || (axis == pdftopdf_axis_e::Y)); - if ((pos < LEFT) || (pos > RIGHT)) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Position %s: (bad position: %d)", - (axis == pdftopdf_axis_e::X) ? "X" : "Y", pos); - return; - } - if (axis == pdftopdf_axis_e::X) - { - static const char *pxstr[3] = {"Left", "Center", "Right"}; - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Position X: %s", pxstr[pos+1]); - } - else - { - static const char *pystr[3] = {"Bottom", "Center", "Top"}; - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Position Y: %s", pystr[pos+1]); - } -} -// }}} - -void -_cfPDFToPDFRotationDump(pdftopdf_rotation_e rot, - pdftopdf_doc_t *doc) // {{{ -{ - static const char *rstr[4] = {"0 deg", "90 deg", "180 deg", "270 deg"}; // CCW - if ((rot < ROT_0) || (rot > ROT_270)) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Rotation(CCW): (bad rotation: %d)", rot); - } - else - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Rotation(CCW): %s", rstr[rot]); - } -} -// }}} - -pdftopdf_rotation_e -operator+(pdftopdf_rotation_e lhs, - pdftopdf_rotation_e rhs) // {{{ -{ - return (pdftopdf_rotation_e)(((int)lhs + (int)rhs) % 4); -} -// }}} - -pdftopdf_rotation_e -operator-(pdftopdf_rotation_e lhs, - pdftopdf_rotation_e rhs) // {{{ -{ - return (pdftopdf_rotation_e)((((int)lhs - (int)rhs) % 4 + 4) % 4); -} -// }}} - -pdftopdf_rotation_e -operator-(pdftopdf_rotation_e rhs) // {{{ -{ - return (pdftopdf_rotation_e)((4 - (int)rhs) % 4); -} -// }}} - -void -_cfPDFToPDFBorderTypeDump(pdftopdf_border_type_e border, - pdftopdf_doc_t *doc) // {{{ -{ - if ((border < NONE) || (border == 1) || (border > TWO_THICK)) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Border: (bad border: %d)", border); - } - else - { - static const char *bstr[6] = - {"None", NULL, "one thin", "one thick", "two thin", "two thick"}; - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: Border: %s", bstr[border]); - } -} -// }}} - -void -_cfPDFToPDFPageRect::rotate_move(pdftopdf_rotation_e r, - float pwidth, - float pheight) // {{{ -{ -#if 1 - if (r >= ROT_180) - { - std::swap(top, bottom); - std::swap(left, right); - } - if ((r == ROT_90) || (r == ROT_270)) - { - const float tmp = bottom; - bottom = left; - left = top; - top = right; - right = tmp; - - std::swap(width, height); - std::swap(pwidth, pheight); - } - if ((r == ROT_90) || (r == ROT_180)) - { - left = pwidth - left; - right = pwidth - right; - } - if ((r == ROT_270) || (r == ROT_180)) - { - top = pheight - top; - bottom = pheight - bottom; - } -#else - switch (r) - { - case ROT_0: // no-op - break; - case ROT_90: - const float tmp0 = bottom; - bottom = left; - left = pheight - top; - top = right; - right = pheight - tmp0; - - std::swap(width, height); - break; - case ROT_180: - const float tmp1 = left; - left = pwidth - right; - right = pwidth - tmp1; - - const float tmp2 = top; - top = pheight - bottom; - bottom = pheight - tmp2; - break; - case ROT_270: - const float tmp3 = top; - top = pwidth - left; - left = bottom; - bottom = pwidth - right; - right = tmp3; - - std::swap(width, height); - break; - } -#endif -} -// }}} - -void -_cfPDFToPDFPageRect::scale(float mult) // {{{ -{ - if (mult == 1.0) - return; - - DEBUG_assert(mult != 0.0); - - bottom *= mult; - left *= mult; - top *= mult; - right *= mult; - - width *= mult; - height *= mult; -} -// }}} - -void -_cfPDFToPDFPageRect::translate(float tx, - float ty) // {{{ -{ - left += tx; - bottom += ty; - right += tx; - top += ty; -} -// }}} - -void -_cfPDFToPDFPageRect::set(const _cfPDFToPDFPageRect &rhs) // {{{ -{ - if (!std::isnan(rhs.top)) - top = rhs.top; - if (!std::isnan(rhs.left)) - left = rhs.left; - if (!std::isnan(rhs.right)) - right = rhs.right; - if (!std::isnan(rhs.bottom)) - bottom = rhs.bottom; -} -// }}} - -void -_cfPDFToPDFPageRect::dump(pdftopdf_doc_t *doc) const // {{{ -{ - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: top: %f, left: %f, right: %f, bottom: %f, " - "width: %f, height: %f", - top, left, right, bottom, - width, height); -} -// }}} diff --git a/cupsfilters/pdftopdf/processor.h b/cupsfilters/pdftopdf/processor.h new file mode 100644 index 000000000..3d897a911 --- /dev/null +++ b/cupsfilters/pdftopdf/processor.h @@ -0,0 +1,255 @@ +// +// Copyright 2024 Uddhav Phatak +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#ifndef C_PDFTOPDF_PROCESSOR_PRIVATE_H +#define C_PDFTOPDF_PROCESSOR_PRIVATE_H + +#include "pptypes-private.h" +#include "pdfio.h" +#include "pptypes-private.h" +#include "nup-private.h" +#include "pdftopdf-private.h" +#include "intervalset-private.h" +#include +#include +#define HASH_TABLE_SIZE 2048 + +typedef struct KeyValuePair { + char *key; // Key (string) + pdfio_obj_t *value; // Value (PDF object handle) + struct KeyValuePair *next; // For handling collisions (chaining) +} KeyValuePair; + +typedef struct HashTable { + KeyValuePair *buckets[HASH_TABLE_SIZE]; // Array of pointers to key-value pairs + int count; // Number of filled elements in the hash table +} HashTable; + +HashTable *hashCreate_hash_table(); +void hashInsert(HashTable *table, const char *key, pdfio_obj_t *value); +pdfio_obj_t *hashGet(HashTable *table, const char *key); +int hashGet_filled_count(HashTable *table); +void hashFree_hash_table(HashTable *table); + +typedef struct _cfPDFToPDFPageHandle{ + // 1st mode: existing + pdfio_obj_t *page; + int no; + + // 2nd mode: create new + HashTable *xobjs; + char *content; + + pdftopdf_rotation_e rotation; +}_cfPDFToPDFPageHandle; + +void _cfPDFToPDFPageHandle_existingMode(_cfPDFToPDFPageHandle *handle, + pdfio_obj_t *page, + int orig_no); // 1st mode:existing + +void _cfPDFToPDFPageHandle_create_newMode(_cfPDFToPDFPageHandle *handle, + pdfio_file_t *pdf, + float width, float height); // 2nd mode:create new + +void _cfPDFToPDFPageHandle_destroy(_cfPDFToPDFPageHandle *handle); + +void _cfPDFToPDFPageHandle_debug(_cfPDFToPDFPageHandle *handle, + const _cfPDFToPDFPageRect *rect, + float xpos, float ypos); + +bool _cfPDFToPDFPageHandle_is_existing(_cfPDFToPDFPageHandle *handle); + +pdfio_obj_t* _cfPDFToPDFPageHandle_get(_cfPDFToPDFPageHandle *handle); + + +_cfPDFToPDFPageRect _cfPDFToPDFPageHandle_get_rect(const _cfPDFToPDFPageHandle *handle); + +void _cfPDFToPDFPageHandle_add_border_rect(_cfPDFToPDFPageHandle *handle, + pdfio_file_t *pdf, + const _cfPDFToPDFPageRect rect, + pdftopdf_border_type_e border, + float fscale); + + +pdftopdf_rotation_e _cfPDFToPDFPageHandle_crop(_cfPDFToPDFPageHandle *handle, + const _cfPDFToPDFPageRect *cropRect, + pdftopdf_rotation_e orientation, + pdftopdf_rotation_e param_orientation, + pdftopdf_position_e xpos, + pdftopdf_position_e ypos, + bool scale, bool autorotate, + pdftopdf_doc_t *doc); + + +void _cfPDFToPDFPageHandle_add_subpage(_cfPDFToPDFPageHandle *handle, + _cfPDFToPDFPageHandle *sub, + pdfio_file_t *pdf, + float xpos, float ypos, float scale, + const _cfPDFToPDFPageRect *crop); + +bool _cfPDFToPDFPageHandle_is_landscape(const _cfPDFToPDFPageHandle *handle, + pdftopdf_rotation_e orientation); + +void _cfPDFToPDFPageHandle_mirror(_cfPDFToPDFPageHandle *handle, + pdfio_file_t *pdf); + +void _cfPDFToPDFPageHandle_rotate(_cfPDFToPDFPageHandle *handle, pdftopdf_rotation_e rot); + +void _cfPDFToPDFPageHandle_add_label(_cfPDFToPDFPageHandle *handle, + pdfio_file_t *pdf, + const _cfPDFToPDFPageRect *rect, + const char *label); + +typedef enum pdftopdf_arg_ownership_e { + CF_PDFTOPDF_WILL_STAY_ALIVE, + CF_PDFTOPDF_MUST_DUPLICATE, + CF_PDFTOPDF_TAKE_OWNERSHIP +} pdftopdf_arg_ownership_e; + +typedef struct _cfPDFToPDF_PDFioProcessor{ + // Other members + _cfPDFToPDFPageHandle *pageHandle; + + pdfio_file_t *pdf; // Equivalent to std::unique_ptr + pdfio_obj_t **orig_pages; // Equivalent to std::vector + size_t orig_pages_size; // Current number of pages + + bool hasCM; + char *extraheader; +} _cfPDFToPDF_PDFioProcessor; + +//_cfPDFToPDFQPDFProcessor functions + + +void _cfPDFToPDF_PDFioProcessor_close_file(_cfPDFToPDF_PDFioProcessor *processor); + +bool _cfPDFToPDF_PDFioProcessor_load_file(_cfPDFToPDF_PDFioProcessor *processor, + FILE *f, pdftopdf_doc_t *doc, + pdftopdf_arg_ownership_e take, int flatten_forms); + +bool _cfPDFToPDF_PDFioProcessor_load_filename(_cfPDFToPDF_PDFioProcessor *processor, + const char *name, + pdftopdf_doc_t *doc, + int flatten_forms); + +void _cfPDFToPDF_PDFioProcessor_start(_cfPDFToPDF_PDFioProcessor *processor, + int flatten_forms); + +bool _cfPDFToPDF_PDFioProcessor_check_print_permissions(_cfPDFToPDF_PDFioProcessor *processor, + pdftopdf_doc_t *doc); + +_cfPDFToPDFPageHandle** _cfPDFToPDF_PDFioProcessor_get_pages(_cfPDFToPDF_PDFioProcessor *handle, + pdftopdf_doc_t *doc, + size_t *out_len); + +_cfPDFToPDFPageHandle* _cfPDFToPDF_PDFioProcessor_new_page(_cfPDFToPDF_PDFioProcessor *handle, + float width, float height, + pdftopdf_doc_t *doc); + +//void _cfPDFToPDF_PDFioProcessor_add_page(_cfPDFToPDF_PDFioProcessor *handle, +// _cfPDFToPDFPageHandle *page, +// bool front); + + +void _cfPDFToPDF_PDFioProcessor_multiply(_cfPDFToPDF_PDFioProcessor *handle, + int copies, bool collate); + +void _cfPDFToPDF_PDFioProcessor_auto_rotate_all(_cfPDFToPDF_PDFioProcessor *handle, + bool dst_lscape, + pdftopdf_rotation_e normal_landscape); + +void _cfPDFToPDF_PDFioProcessor_add_cm(_cfPDFToPDF_PDFioProcessor *handle, + const char *defaulticc, const char *outputicc); + +void _cfPDFToPDF_PDFioProcessor_set_comments(_cfPDFToPDF_PDFioProcessor *handle, + char **comments, int num_comments); +void _cfPDFToPDF_PDFioProcessor_emit_file(_cfPDFToPDF_PDFioProcessor *handle, + FILE *f, pdftopdf_doc_t *doc, + pdftopdf_arg_ownership_e take); + +void _cfPDFToPDF_PDFioProcessor_emit_filename(_cfPDFToPDF_PDFioProcessor *handle, + const char *name, pdftopdf_doc_t *doc); + +bool _cfPDFToPDF_PDFioProcessor_has_acro_form(_cfPDFToPDF_PDFioProcessor *handle); + +typedef enum { + CF_PDFTOPDF_BOOKLET_OFF, + CF_PDFTOPDF_BOOKLET_ON, + CF_PDFTOPDF_BOOKLET_JUST_SHUFFLE +} pdftopdf_booklet_mode_e; + +typedef struct { + int job_id, num_copies; + const char *user, *title; + bool pagesize_requested; + bool fitplot; + bool fillprint; // print-scaling = fill + bool cropfit; // -o crop-to-fit + bool autoprint; // print-scaling = auto + bool autofit; // print-scaling = auto-fit + bool fidelity; + bool no_orientation; + _cfPDFToPDFPageRect *page; + pdftopdf_rotation_e orientation, normal_landscape; // normal_landscape (i.e. default direction) is e.g. needed for number-up=2 + bool paper_is_landscape; + bool duplex; + pdftopdf_border_type_e border; + _cfPDFToPDFNupParameters *nup; + bool reverse; + + char *page_label; + bool even_pages, odd_pages; + _cfPDFToPDFIntervalSet *page_ranges; + _cfPDFToPDFIntervalSet *input_page_ranges; + + bool mirror; + + pdftopdf_position_e xpos, ypos; + + bool collate; + + bool even_duplex; // make number of pages a multiple of 2 + + pdftopdf_booklet_mode_e booklet; + int book_signature; + + bool auto_rotate; + + int device_copies; + bool device_collate; + bool set_duplex; + + int page_logging; + int copies_to_be_logged; +} _cfPDFToPDFProcessingParameters; + +//_cfPDFToPDFQPDFPageHandle functions +//inherited functions + +//C-pdftopdf-processor-private functions + +void _cfPDFToPDFProcessingParameters_init(_cfPDFToPDFProcessingParameters *processingParams); + +void _cfPDFToPDFProcessingParameters_free(_cfPDFToPDFProcessingParameters *processingParams); + +bool _cfPDFToPDFProcessingParameters_even_odd_page(const _cfPDFToPDFProcessingParameters *self, + int outno); + +bool _cfPDFToPDFProcessingParameters_with_page(const _cfPDFToPDFProcessingParameters *self, + int outno); + +bool _cfPDFToPDFProcessingParameters_have_page(const _cfPDFToPDFProcessingParameters *self, + int pageno); +void _cfPDFToPDFProcessingParameters_dump(const _cfPDFToPDFProcessingParameters *self, + pdftopdf_doc_t *doc); + +int* _cfPDFToPDFBookletShuffle(int numPages, int signature, int* ret_size); + +bool _cfProcessPDFToPDF( _cfPDFToPDF_PDFioProcessor *proc, + _cfPDFToPDFProcessingParameters *param, + pdftopdf_doc_t *doc); +#endif // C_PDFTOPDF_PROCESSOR_PRIVATE_H diff --git a/cupsfilters/pdftopdf/qpdf-cm-private.h b/cupsfilters/pdftopdf/qpdf-cm-private.h deleted file mode 100644 index 4f22e4688..000000000 --- a/cupsfilters/pdftopdf/qpdf-cm-private.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// Licensed under Apache License v2.0. See the file "LICENSE" for more -// information. -// - -#ifndef _CUPS_FILTERS_PDFTOPDF_QPDF_CM_H_ -#define _CUPS_FILTERS_PDFTOPDF_QPDF_CM_H_ - -#include - -bool _cfPDFToPDFHasOutputIntent(QPDF &pdf); -void _cfPDFToPDFAddOutputIntent(QPDF &pdf, const char *filename); - -void _cfPDFToPDFAddDefaultRGB(QPDF &pdf, QPDFObjectHandle srcicc); -QPDFObjectHandle _cfPDFToPDFSetDefaultICC(QPDF &pdf, const char *filename); - -#endif // !_CUPS_FILTERS_PDFTOPDF_QPDF_CM_H_ diff --git a/cupsfilters/pdftopdf/qpdf-cm.cxx b/cupsfilters/pdftopdf/qpdf-cm.cxx deleted file mode 100644 index 238a6b7db..000000000 --- a/cupsfilters/pdftopdf/qpdf-cm.cxx +++ /dev/null @@ -1,181 +0,0 @@ -// -// Licensed under Apache License v2.0. See the file "LICENSE" for more -// information. -// - -#include "qpdf-cm-private.h" -#include -#include "cupsfilters/debug-internal.h" -#include - -#include - -// TODO? instead use qpdf's StreamDataProvider, FileInputSource, Buffer etc. -static std::string -load_file(const char *filename) // {{{ -{ - if (!filename) - throw std::invalid_argument("NULL filename not allowed"); - - FILE *f = fopen(filename, "r"); - if (!f) - throw std::runtime_error(std::string("file ") + filename + " could not be opened"); - - const int bsize = 2048; - unsigned int pos = 0; - - std::string ret; - while (!feof(f)) - { - if ((UINT_MAX - bsize) < pos) - { - ret.resize(pos); - break; - } - - ret.resize(pos + bsize); - int res = fread(&ret[pos], 1, bsize, f); - - if ((UINT_MAX - res) < pos) - { - ret.resize(pos); - break; - } - - pos += res; - if (res < bsize) - { - ret.resize(pos); - break; - } - } - - fclose(f); - return (ret); -} -// }}} - - -// TODO? -// TODO? test -bool -_cfPDFToPDFHasOutputIntent(QPDF &pdf) // {{{ -{ - auto catalog = pdf.getRoot(); - if (!catalog.hasKey("/OutputIntents")) - return (false); - return (true); // TODO? -} -// }}} - -// TODO: test -// TODO? find existing , replace and return (?) -void -_cfPDFToPDFAddOutputIntent(QPDF &pdf, - const char *filename) // {{{ -{ - std::string icc = load_file(filename); - // TODO: check icc fitness - // ICC data, subject to "version limitations" per pdf version... - - QPDFObjectHandle outicc = QPDFObjectHandle::newStream(&pdf, icc); - - auto sdict = outicc.getDict(); - sdict.replaceKey("/N",QPDFObjectHandle::newInteger(4)); // must match ICC - // /Range ? // must match ICC, default [0.0 1.0 ...] - // /Alternate ? (/DeviceCMYK for N=4) - - auto intent = QPDFObjectHandle::parse( - "<<" - " /Type /OutputIntent" // Must be so (the standard requires). - " /S /GTS_PDFX" // Must be so (the standard requires). - " /OutputCondition (Commercial and specialty printing)" // TODO: Customize [optional(?)] - " /Info (none)" // TODO: Customize - " /OutputConditionIdentifier (CGATS TR001)" // TODO: FIXME: Customize - " /RegistryName (http://www.color.org)" // Must be so (the standard requires). - " /DestOutputProfile null " - ">>"); - intent.replaceKey("/DestOutputProfile", outicc); - - auto catalog = pdf.getRoot(); - if (!catalog.hasKey("/OutputIntents")) - catalog.replaceKey("/OutputIntents", QPDFObjectHandle::newArray()); - catalog.getKey("/OutputIntents").appendItem(intent); -} -// }}} - -// -// for color management: -// Use /DefaultGray, /DefaultRGB, /DefaultCMYK ... from *current* resource -// dictionary ... -// i.e. set -// /Resources << -// /ColorSpace << --- can use just one indirect ref for this (probably) -// /DefaultRGB [/ICCBased 5 0 R] ... sensible use is sRGB for DefaultRGB, etc. -// >> -// >> -// for every page (what with form /XObjects?) and most importantly RGB -// (leave CMYK, Gray for now, as this is already printer native(?)) -// -// ? also every form XObject, pattern, type3 font, annotation appearance -// stream(=form xobject +X) -// -// ? what if page already defines /Default? -- probably keep! -// -// ? maybe we need to set /ColorSpace in /Images ? -// [gs idea is to just add the /Default-key and then reprocess...] -// - - -// TODO? test -void -_cfPDFToPDFAddDefaultRGB(QPDF &pdf, QPDFObjectHandle srcicc) // {{{ -{ - srcicc.assertStream(); - - auto pages = pdf.getAllPages(); - for (auto it = pages.begin(), end = pages.end(); it != end; ++ it) - { - if (!it->hasKey("/Resources")) - it->replaceKey("/Resources", QPDFObjectHandle::newDictionary()); - - auto rdict = it->getKey("/Resources"); - - if (!rdict.hasKey("/ColorSpace")) - rdict.replaceKey("/ColorSpace", QPDFObjectHandle::newDictionary()); - - auto cdict = rdict.getKey("/ColorSpace"); - - if (!cdict.hasKey("/DefaultRGB")) - { - cdict.replaceKey("/DefaultRGB", QPDFObjectHandle::parse("[/ICCBased ]")); - cdict.getKey("/DefaultRGB").appendItem(srcicc); - } - } -} -// }}} - -// TODO? test -// TODO: find existing , replace and return (?) -// TODO: check icc fitness -QPDFObjectHandle -_cfPDFToPDFSetDefaultICC(QPDF &pdf, - const char *filename) // {{{ -{ - // TODO: find existing, replace and return (?) - - std::string icc = load_file(filename); - // TODO: check icc fitness - // ICC data, subject to "version limitations" per pdf version... - - QPDFObjectHandle ret = QPDFObjectHandle::newStream(&pdf, icc); - - auto sdict = ret.getDict(); - sdict.replaceKey("/N", QPDFObjectHandle::newInteger(3)); // must match ICC - // /Range ? // must match ICC, default [0.0 1.0 ...] - // /Alternate ? (/DeviceRGB for N=3) - - return ret; -} -// }}} - diff --git a/cupsfilters/pdftopdf/qpdf-pdftopdf-private.h b/cupsfilters/pdftopdf/qpdf-pdftopdf-private.h deleted file mode 100644 index 1146a8fea..000000000 --- a/cupsfilters/pdftopdf/qpdf-pdftopdf-private.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// Licensed under Apache License v2.0. See the file "LICENSE" for more -// information. -// - -#ifndef _CUPS_FILTERS_PDFTOPDF_QPDF_PDFTOPDF_H -#define _CUPS_FILTERS_PDFTOPDF_QPDF_PDFTOPDF_H - -#include -#include "pptypes-private.h" - -// helper functions - -_cfPDFToPDFPageRect _cfPDFToPDFGetBoxAsRect(QPDFObjectHandle box); -QPDFObjectHandle _cfPDFToPDFGetRectAsBox(const _cfPDFToPDFPageRect &rect); - -// Note that PDF specification is CW, but our Rotation is CCW -pdftopdf_rotation_e _cfPDFToPDFGetRotate(QPDFObjectHandle page); -QPDFObjectHandle _cfPDFToPDFMakeRotate(pdftopdf_rotation_e rot); // Integer - -double _cfPDFToPDFGetUserUnit(QPDFObjectHandle page); - -// PDF CTM -class _cfPDFToPDFMatrix { - public: - _cfPDFToPDFMatrix(); // identity - _cfPDFToPDFMatrix(QPDFObjectHandle ar); - - _cfPDFToPDFMatrix &rotate(pdftopdf_rotation_e rot); - _cfPDFToPDFMatrix &rotate_move(pdftopdf_rotation_e rot, double width, - double height); - _cfPDFToPDFMatrix &rotate(double rad); - // _cfPDFToPDFMatrix &rotate_deg(double deg); - - _cfPDFToPDFMatrix &translate(double tx, double ty); - _cfPDFToPDFMatrix &scale(double sx, double sy); - _cfPDFToPDFMatrix &scale(double s) { return (scale(s, s)); } - - _cfPDFToPDFMatrix &operator*=(const _cfPDFToPDFMatrix &rhs); - - QPDFObjectHandle get() const; - std::string get_string() const; - private: - double ctm[6]; -}; - -#endif // !_CUPS_FILTERS_PDFTOPDF_QPDF_PDFTOPDF_H diff --git a/cupsfilters/pdftopdf/qpdf-pdftopdf-processor-private.h b/cupsfilters/pdftopdf/qpdf-pdftopdf-processor-private.h deleted file mode 100644 index c8041205b..000000000 --- a/cupsfilters/pdftopdf/qpdf-pdftopdf-processor-private.h +++ /dev/null @@ -1,99 +0,0 @@ -// -// Licensed under Apache License v2.0. See the file "LICENSE" for more -// information. -// - -#ifndef _CUPS_FILTERS_PDFTOPDF_QPDF_PDFTOPDF_PROCESSOR_H -#define _CUPS_FILTERS_PDFTOPDF_QPDF_PDFTOPDF_PROCESSOR_H - -#include "pdftopdf-processor-private.h" -#include - -class _cfPDFToPDFQPDFPageHandle : public _cfPDFToPDFPageHandle { - public: - virtual _cfPDFToPDFPageRect get_rect() const; - virtual void add_border_rect(const _cfPDFToPDFPageRect &rect, - pdftopdf_border_type_e border, float fscale); - virtual void add_subpage(const std::shared_ptr<_cfPDFToPDFPageHandle> &sub, - float xpos, float ypos, float scale, - const _cfPDFToPDFPageRect *crop=NULL); - virtual void mirror(); - virtual void rotate(pdftopdf_rotation_e rot); - virtual void add_label(const _cfPDFToPDFPageRect &rect, - const std::string label); - virtual pdftopdf_rotation_e crop(const _cfPDFToPDFPageRect &cropRect, - pdftopdf_rotation_e orientation, - pdftopdf_rotation_e param_orientation, - pdftopdf_position_e xpos, - pdftopdf_position_e ypos, - bool scale, bool autorotate, - pdftopdf_doc_t *doc); - virtual bool is_landscape(pdftopdf_rotation_e orientation); - void debug(const _cfPDFToPDFPageRect &rect, float xpos, float ypos); - private: - bool is_existing() const; - QPDFObjectHandle get(); // only once! - private: - friend class _cfPDFToPDFQPDFProcessor; - // 1st mode: existing - _cfPDFToPDFQPDFPageHandle(QPDFObjectHandle page, int orig_no = -1); - QPDFObjectHandle page; - int no; - - // 2nd mode: create new - _cfPDFToPDFQPDFPageHandle(QPDF *pdf, float width, float height); - std::map xobjs; - std::string content; - - pdftopdf_rotation_e rotation; -}; - -class _cfPDFToPDFQPDFProcessor : public _cfPDFToPDFProcessor { - public: - virtual bool load_file(FILE *f,pdftopdf_doc_t *doc, - pdftopdf_arg_ownership_e - take = CF_PDFTOPDF_WILL_STAY_ALIVE, - int flatten_forms = 1); - virtual bool load_filename(const char *name, - pdftopdf_doc_t *doc, int flatten_forms = 1); - - // TODO: virtual bool may_modify/may_print/? - virtual bool check_print_permissions(pdftopdf_doc_t *doc); - - // virtual bool set_process(const _cfPDFToPDFProcessingParameters ¶m) = 0; - - virtual std::vector> - get_pages(pdftopdf_doc_t *doc); - virtual std::shared_ptr<_cfPDFToPDFPageHandle> new_page(float width, - float height, - pdftopdf_doc_t *doc); - - virtual void add_page(std::shared_ptr<_cfPDFToPDFPageHandle> page, - bool front); - - virtual void multiply(int copies, bool collate); - - virtual void auto_rotate_all(bool dst_lscape, - pdftopdf_rotation_e normal_landscape); - virtual void add_cm(const char *defaulticc, const char *outputicc); - - virtual void set_comments(const std::vector &comments); - - virtual void emit_file(FILE *dst, pdftopdf_doc_t *doc, - pdftopdf_arg_ownership_e - take = CF_PDFTOPDF_WILL_STAY_ALIVE); - virtual void emit_filename(const char *name, pdftopdf_doc_t *doc); - - virtual bool has_acro_form(); - private: - void close_file(); - void start(int flatten_forms); - private: - std::unique_ptr pdf; - std::vector orig_pages; - - bool hasCM; - std::string extraheader; -}; - -#endif // !_CUPS_FILTERS_PDFTOPDF_QPDF_PDFTOPDF_PROCESSOR_H diff --git a/cupsfilters/pdftopdf/qpdf-pdftopdf-processor.cxx b/cupsfilters/pdftopdf/qpdf-pdftopdf-processor.cxx deleted file mode 100644 index 25c48bbb4..000000000 --- a/cupsfilters/pdftopdf/qpdf-pdftopdf-processor.cxx +++ /dev/null @@ -1,920 +0,0 @@ -// -// Licensed under Apache License v2.0. See the file "LICENSE" for more -// information. -// - -#include -#include -#include "cupsfilters/debug-internal.h" -#include -#include -#include -#include -#include -#include "qpdf-tools-private.h" -#include "qpdf-xobject-private.h" -#include "qpdf-pdftopdf-private.h" -#include "qpdf-pdftopdf-processor-private.h" -#include "qpdf-cm-private.h" -#include "pdftopdf-private.h" - -// Use: content.append(debug_box(pe.sub, xpos, ypos)); -static std::string -debug_box(const _cfPDFToPDFPageRect &box, - float xshift, - float yshift) // {{{ -{ - return (std::string("q 1 w 0.1 G\n ") + - QUtil::double_to_string(box.left + xshift) + " " + - QUtil::double_to_string(box.bottom + yshift) + " m " + - QUtil::double_to_string(box.right + xshift) + " " + - QUtil::double_to_string(box.top + yshift) + " l " + "S \n " + - - QUtil::double_to_string(box.right + xshift) + " " + - QUtil::double_to_string(box.bottom + yshift) + " m " + - QUtil::double_to_string(box.left + xshift) + " " + - QUtil::double_to_string(box.top + yshift) + " l " + "S \n " + - - QUtil::double_to_string(box.left + xshift) + " " + - QUtil::double_to_string(box.bottom + yshift) + " " + - QUtil::double_to_string(box.right - box.left) + " " + - QUtil::double_to_string(box.top - box.bottom) + " re " + "S Q\n"); -} -// }}} - -_cfPDFToPDFQPDFPageHandle::_cfPDFToPDFQPDFPageHandle(QPDFObjectHandle page, - int orig_no) // {{{ - : page(page), - no(orig_no), - rotation(ROT_0) -{ -} -// }}} - -_cfPDFToPDFQPDFPageHandle::_cfPDFToPDFQPDFPageHandle(QPDF *pdf, - float width, - float height) // {{{ - : no(0), - rotation(ROT_0) -{ - DEBUG_assert(pdf); - page = QPDFObjectHandle::parse( - "<<" - " /Type /Page" - " /Resources <<" - " /XObject null " - " >>" - " /MediaBox null " - " /Contents null " - ">>"); - page.replaceKey("/MediaBox", _cfPDFToPDFMakeBox(0, 0, width, height)); - page.replaceKey("/Contents", QPDFObjectHandle::newStream(pdf)); - // xobjects: later (in get()) - content.assign("q\n"); // TODO? different/not needed - - page = pdf->makeIndirectObject(page); // stores *pdf -} -// }}} - -// Note: _cfPDFToPDFProcessor always works with "/Rotate"d and "/UserUnit"-scaled pages/coordinates/..., having 0,0 at left,bottom of the TrimBox -_cfPDFToPDFPageRect -_cfPDFToPDFQPDFPageHandle::get_rect() const // {{{ -{ - page.assertInitialized(); - _cfPDFToPDFPageRect ret = - _cfPDFToPDFGetBoxAsRect(_cfPDFToPDFGetTrimBox(page)); - ret.translate(-ret.left, -ret.bottom); - ret.rotate_move(_cfPDFToPDFGetRotate(page), ret.width, ret.height); - ret.scale(_cfPDFToPDFGetUserUnit(page)); - return (ret); -} -// }}} - -bool -_cfPDFToPDFQPDFPageHandle::is_existing() const // {{{ -{ - page.assertInitialized(); - return (content.empty()); -} -// }}} - -QPDFObjectHandle -_cfPDFToPDFQPDFPageHandle::get() // {{{ -{ - QPDFObjectHandle ret = page; - - if (!is_existing()) - { // finish up page - page.getKey("/Resources").replaceKey("/XObject", - QPDFObjectHandle::newDictionary(xobjs)); - content.append("Q\n"); - page.getKey("/Contents").replaceStreamData(content, - QPDFObjectHandle::newNull(), - QPDFObjectHandle::newNull()); - page.replaceKey("/Rotate", _cfPDFToPDFMakeRotate(rotation)); - } - else - { - pdftopdf_rotation_e rot = _cfPDFToPDFGetRotate(page) + rotation; - page.replaceKey("/Rotate", _cfPDFToPDFMakeRotate(rot)); - } - page = QPDFObjectHandle(); // i.e. uninitialized - return (ret); -} -// }}} - -// TODO: We probably need a function "ungetRect()" to transform to page/form -// space, as member -static _cfPDFToPDFPageRect -ungetRect(_cfPDFToPDFPageRect rect, - const _cfPDFToPDFQPDFPageHandle &ph, - pdftopdf_rotation_e rotation, - QPDFObjectHandle page) -{ - _cfPDFToPDFPageRect pg1 = ph.get_rect(); - _cfPDFToPDFPageRect pg2 = - _cfPDFToPDFGetBoxAsRect(_cfPDFToPDFGetTrimBox(page)); - - // we have to invert /Rotate, /UserUnit and the left,bottom (TrimBox) - // translation - //_cfPDFToPDFRotationDump(rotation); - //_cfPDFToPDFRotationDump(_cfPDFToPDFGetRotate(page)); - rect.width = pg1.width; - rect.height = pg1.height; - //std::swap(rect.width, rect.height); - //rect.rotate_move(-rotation, rect.width, rect.height); - - rect.rotate_move(-_cfPDFToPDFGetRotate(page), pg1.width, pg1.height); - rect.scale(1.0 / _cfPDFToPDFGetUserUnit(page)); - - //_cfPDFToPDFPageRect pg2=_cfPDFToPDFGetBoxAsRect(_cfPDFToPDFGetTrimBox(page)); - rect.translate(pg2.left, pg2.bottom); - //rect.dump(); - - return (rect); -} - -// TODO FIXME rotations are strange ... (via ungetRect) -// TODO? for non-existing (either drop comment or facility to create split -// streams needed) -void -_cfPDFToPDFQPDFPageHandle::add_border_rect(const _cfPDFToPDFPageRect &_rect, - pdftopdf_border_type_e border, - float fscale) // {{{ -{ - DEBUG_assert(is_existing()); - DEBUG_assert(border != pdftopdf_border_type_e::NONE); - - // straight from pstops - const double lw = (border & THICK) ? 0.5 : 0.24; - double line_width = lw * fscale; - double margin = 2.25 * fscale; - // (PageLeft + margin, PageBottom + margin) - // rect (PageRight - PageLeft - 2 * margin, ...) ... - // for nup > 1: PageLeft = 0, etc. - // if (double) margin += 2 * fscale ...rect... - - _cfPDFToPDFPageRect rect = ungetRect(_rect, *this, rotation, page); - - DEBUG_assert(rect.left <= rect.right); - DEBUG_assert(rect.bottom <= rect.top); - - std::string boxcmd="q\n"; - boxcmd += " " +QUtil::double_to_string(line_width) + " w 0 G \n"; - boxcmd += " " +QUtil::double_to_string(rect.left + margin) + " " + - QUtil::double_to_string(rect.bottom + margin) + " " + - QUtil::double_to_string(rect.right - rect.left - 2 * margin) + " " + - QUtil::double_to_string(rect.top - rect.bottom - 2 * margin) + " re S \n"; - if (border & TWO) - { - margin += 2 * fscale; - boxcmd += " " + QUtil::double_to_string(rect.left + margin) + " " + - QUtil::double_to_string(rect.bottom + margin) + " " + - QUtil::double_to_string(rect.right - rect.left - 2 * margin) + " " + - QUtil::double_to_string(rect.top - rect.bottom - 2 * margin) + " re S \n"; - } - boxcmd += "Q\n"; - - // if (!is_existing()) - // // TODO: only after - // return; - - DEBUG_assert(page.getOwningQPDF()); // existing pages are always indirect -#ifdef DEBUG // draw it on top - static const char *pre = "%pdftopdf q\n" - "q\n", - *post = "%pdftopdf Q\n" - "Q\n"; - - QPDFObjectHandle stm1 = QPDFObjectHandle::newStream(page.getOwningQPDF(), - pre), - stm2 = QPDFObjectHandle::newStream(page.getOwningQPDF(), - std::string(post) + - boxcmd); - - page.addPageContents(stm1, true); // before - page.addPageContents(stm2, false); // after -#else - QPDFObjectHandle stm = QPDFObjectHandle::newStream(page.getOwningQPDF(), - boxcmd); - page.addPageContents(stm, true); // before -#endif -} -// }}} - - -// -// This crop function is written for print-scaling=fill option. -// Trim Box is used for trimming the page in required size. -// scale tells if we need to scale input file. -// - -pdftopdf_rotation_e -_cfPDFToPDFQPDFPageHandle::crop(const _cfPDFToPDFPageRect &cropRect, - pdftopdf_rotation_e orientation, - pdftopdf_rotation_e param_orientation, - pdftopdf_position_e xpos, - pdftopdf_position_e ypos, - bool scale, - bool autorotate, - pdftopdf_doc_t *doc) -{ - page.assertInitialized(); - pdftopdf_rotation_e save_rotate = _cfPDFToPDFGetRotate(page); - if (orientation == ROT_0 || orientation == ROT_180) - page.replaceKey("/Rotate", _cfPDFToPDFMakeRotate(ROT_90)); - else - page.replaceKey("/Rotate", _cfPDFToPDFMakeRotate(ROT_0)); - - _cfPDFToPDFPageRect currpage = - _cfPDFToPDFGetBoxAsRect(_cfPDFToPDFGetTrimBox(page)); - double width = currpage.right - currpage.left; - double height = currpage.top - currpage.bottom; - double pageWidth = cropRect.right - cropRect.left; - double pageHeight = cropRect.top - cropRect.bottom; - double final_w, final_h; //Width and height of cropped image. - - pdftopdf_rotation_e pageRot = _cfPDFToPDFGetRotate(page); - if ((autorotate && - (((pageRot == ROT_0 || pageRot == ROT_180) && - pageWidth <= pageHeight) || - ((pageRot == ROT_90 || pageRot == ROT_270) && - pageWidth > pageHeight))) || - (!autorotate && - (param_orientation == ROT_90 || param_orientation == ROT_270))) - std::swap(pageHeight, pageWidth); - if (scale) - { - if(width * pageHeight / pageWidth <= height) - { - final_w = width; - final_h = width * pageHeight / pageWidth; - } - else - { - final_w = height * pageWidth / pageHeight; - final_h = height; - } - } - else - { - final_w = pageWidth; - final_h = pageHeight; - } - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: After Cropping: %lf %lf %lf %lf", - width, height, final_w, final_h); - double posw = (width - final_w) / 2, - posh = (height - final_h) / 2; - // posw, posh : pdftopdf_position_e along width and height respectively. - // Calculating required position. - if (xpos == pdftopdf_position_e::LEFT) - posw = 0; - else if (xpos == pdftopdf_position_e::RIGHT) - posw *= 2; - - if (ypos == pdftopdf_position_e::TOP) - posh *= 2; - else if (ypos == pdftopdf_position_e::BOTTOM) - posh = 0; - - // making _cfPDFToPDFPageRect for cropping. - currpage.left += posw; - currpage.bottom += posh; - currpage.top = currpage.bottom + final_h; - currpage.right = currpage.left + final_w; - // Cropping. - // TODO: Borders are covered by the image. buffer space? - page.replaceKey("/TrimBox", - _cfPDFToPDFMakeBox(currpage.left, currpage.bottom, - currpage.right, currpage.top)); - page.replaceKey("/Rotate", _cfPDFToPDFMakeRotate(save_rotate)); - - return (_cfPDFToPDFGetRotate(page)); -} - -bool -_cfPDFToPDFQPDFPageHandle::is_landscape(pdftopdf_rotation_e orientation) -{ - page.assertInitialized(); - pdftopdf_rotation_e save_rotate = _cfPDFToPDFGetRotate(page); - if (orientation == ROT_0 || orientation == ROT_180) - page.replaceKey("/Rotate", _cfPDFToPDFMakeRotate(ROT_90)); - else - page.replaceKey("/Rotate", _cfPDFToPDFMakeRotate(ROT_0)); - - _cfPDFToPDFPageRect currpage = - _cfPDFToPDFGetBoxAsRect(_cfPDFToPDFGetTrimBox(page)); - double width = currpage.right - currpage.left; - double height = currpage.top - currpage.bottom; - page.replaceKey("/Rotate", _cfPDFToPDFMakeRotate(save_rotate)); - if (width > height) - return (true); - return (false); -} - -// TODO: better cropping -// TODO: test/fix with qsub rotation -void -_cfPDFToPDFQPDFPageHandle::add_subpage - (const std::shared_ptr<_cfPDFToPDFPageHandle> &sub, - float xpos, - float ypos, - float scale, - const _cfPDFToPDFPageRect *crop) // {{{ -{ - auto qsub = dynamic_cast<_cfPDFToPDFQPDFPageHandle *>(sub.get()); - DEBUG_assert(qsub); - - std::string xoname = - "/X" + QUtil::int_to_string((qsub->no != -1) ? qsub->no : ++ no); - if (crop) - { - _cfPDFToPDFPageRect pg = qsub->get_rect(), - tmp = *crop; - // we need to fix a too small cropbox. - tmp.width = tmp.right - tmp.left; - tmp.height = tmp.top - tmp.bottom; - tmp.rotate_move(-_cfPDFToPDFGetRotate(qsub->page), tmp.width, tmp.height); - // TODO TODO (pg.width? / unneeded?) - // TODO: better - // TODO: we need to obey page./Rotate - if (pg.width < tmp.width) - pg.right = pg.left + tmp.width; - if (pg.height < tmp.height) - pg.top = pg.bottom + tmp.height; - - _cfPDFToPDFPageRect rect = ungetRect(pg, *qsub, ROT_0, qsub->page); - - qsub->page.replaceKey("/TrimBox", - _cfPDFToPDFMakeBox(rect.left, rect.bottom, - rect.right, rect.top)); - // TODO? do everything for cropping here? - } - xobjs[xoname] = _cfPDFToPDFMakeXObject(qsub->page.getOwningQPDF(), - qsub->page); // trick: should be the - // same as - // page->getOwningQPDF() - // [only after it's made - // indirect] - - _cfPDFToPDFMatrix mtx; - mtx.translate(xpos, ypos); - mtx.scale(scale); - mtx.rotate(qsub->rotation); // TODO? -sub.rotation ? - // TODO FIXME: this might need another - // translation!? - if (crop) // TODO? other technique: set trim-box before - // _cfPDFToPDFMakeXObject (but this modifies original page) - { - mtx.translate(crop->left, crop->bottom); - // crop->dump(); - } - - content.append("q\n "); - content.append(mtx.get_string() + " cm\n "); - if (crop) - { - content.append("0 0 " + QUtil::double_to_string(crop->right-crop->left) + - " " + QUtil::double_to_string(crop->top-crop->bottom) + - " re W n\n "); - //content.append("0 0 " + QUtil::double_to_string(crop->right-crop->left) + - // " " + QUtil::double_to_string(crop->top-crop->bottom) + - // " re S\n "); - } - content.append(xoname + " Do\n"); - content.append("Q\n"); -} -// }}} - -void -_cfPDFToPDFQPDFPageHandle::mirror() // {{{ -{ - _cfPDFToPDFPageRect orig = get_rect(); - - if (is_existing()) - { - // need to wrap in XObject to keep patterns correct - // TODO? refactor into internal ..._subpage fn ? - std::string xoname = "/X" + QUtil::int_to_string(no); - - QPDFObjectHandle subpage = get(); // this->page, with rotation - - // replace all our data - *this = _cfPDFToPDFQPDFPageHandle(subpage.getOwningQPDF(), - orig.width, orig.height); - - xobjs[xoname] = _cfPDFToPDFMakeXObject(subpage.getOwningQPDF(), subpage); - // we can only now set this->xobjs - - // content.append(std::string("1 0 0 1 0 0 cm\n "); - content.append(xoname + " Do\n"); - - DEBUG_assert(!is_existing()); - } - - static const char *pre = "%pdftopdf cm\n"; - // Note: we don't change (TODO need to?) the media box - std::string mrcmd("-1 0 0 1 " + - QUtil::double_to_string(orig.right) + " 0 cm\n"); - - content.insert(0, std::string(pre) + mrcmd); -} -// }}} - -void -_cfPDFToPDFQPDFPageHandle::rotate(pdftopdf_rotation_e rot) // {{{ -{ - rotation = rot; // "rotation += rot;" ? -} -// }}} - -void -_cfPDFToPDFQPDFPageHandle::add_label(const _cfPDFToPDFPageRect &_rect, - const std::string label) // {{{ -{ - DEBUG_assert(is_existing()); - - _cfPDFToPDFPageRect rect = ungetRect (_rect, *this, rotation, page); - - DEBUG_assert(rect.left <= rect.right); - DEBUG_assert(rect.bottom <= rect.top); - - // TODO: Only add in the font once, not once per page. - QPDFObjectHandle font = page.getOwningQPDF()->makeIndirectObject - (QPDFObjectHandle::parse( - "<<" - " /Type /Font" - " /Subtype /Type1" - " /Name /pagelabel-font" - " /BaseFont /Helvetica" // TODO: support UTF-8 labels? - ">>")); - QPDFObjectHandle resources = page.getKey ("/Resources"); - QPDFObjectHandle rfont = resources.getKey ("/Font"); - rfont.replaceKey ("/pagelabel-font", font); - - double margin = 2.25; - double height = 12; - - std::string boxcmd = "q\n"; - - // White filled rectangle (top) - boxcmd += " 1 1 1 rg\n"; - boxcmd += " " + - QUtil::double_to_string(rect.left + margin) + " " + - QUtil::double_to_string(rect.top - height - 2 * margin) + " " + - QUtil::double_to_string(rect.right - rect.left - 2 * margin) + " " + - QUtil::double_to_string(height + 2 * margin) + " re f\n"; - - // White filled rectangle (bottom) - boxcmd += " " + - QUtil::double_to_string(rect.left + margin) + " " + - QUtil::double_to_string(rect.bottom + height + margin) + " " + - QUtil::double_to_string(rect.right - rect.left - 2 * margin) + " " + - QUtil::double_to_string(height + 2 * margin) + " re f\n"; - - // Black outline (top) - boxcmd += " 0 0 0 RG\n"; - boxcmd += " " + - QUtil::double_to_string(rect.left + margin) + " " + - QUtil::double_to_string(rect.top - height - 2 * margin) + " " + - QUtil::double_to_string(rect.right - rect.left - 2 * margin) + " " + - QUtil::double_to_string(height + 2 * margin) + " re S\n"; - - // Black outline (bottom) - boxcmd += " " + - QUtil::double_to_string(rect.left + margin) + " " + - QUtil::double_to_string(rect.bottom + height + margin) + " " + - QUtil::double_to_string(rect.right - rect.left - 2 * margin) + " " + - QUtil::double_to_string(height + 2 * margin) + " re S\n"; - - // Black text (top) - boxcmd += " 0 0 0 rg\n"; - boxcmd += " BT\n"; - boxcmd += " /pagelabel-font 12 Tf\n"; - boxcmd += " " + - QUtil::double_to_string(rect.left + 2 * margin) + " " + - QUtil::double_to_string(rect.top - height - margin) + " Td\n"; - boxcmd += " (" + label + ") Tj\n"; - boxcmd += " ET\n"; - - // Black text (bottom) - boxcmd += " BT\n"; - boxcmd += " /pagelabel-font 12 Tf\n"; - boxcmd += " " + - QUtil::double_to_string(rect.left + 2 * margin) + " " + - QUtil::double_to_string(rect.bottom + height + 2 * margin) + " Td\n"; - boxcmd += " (" + label + ") Tj\n"; - boxcmd += " ET\n"; - - boxcmd += "Q\n"; - - DEBUG_assert(page.getOwningQPDF()); // existing pages are always indirect - static const char *pre = "%pdftopdf q\n" - "q\n", - *post="%pdftopdf Q\n" - "Q\n"; - - QPDFObjectHandle stm1 = QPDFObjectHandle::newStream(page.getOwningQPDF(), - std::string(pre)), - stm2 = QPDFObjectHandle::newStream(page.getOwningQPDF(), - std::string(post) + - boxcmd); - - page.addPageContents(stm1, true); // before - page.addPageContents(stm2, false); // after -} -// }}} - -void -_cfPDFToPDFQPDFPageHandle::debug(const _cfPDFToPDFPageRect &rect, - float xpos, - float ypos) // {{{ -{ - DEBUG_assert(!is_existing()); - content.append(debug_box(rect, xpos, ypos)); -} -// }}} - -void -_cfPDFToPDFQPDFProcessor::close_file() // {{{ -{ - pdf.reset(); - hasCM = false; -} -// }}} - -// TODO? try/catch for PDF parsing errors? - -bool -_cfPDFToPDFQPDFProcessor::load_file(FILE *f, - pdftopdf_doc_t *doc, - pdftopdf_arg_ownership_e take, - int flatten_forms) // {{{ -{ - close_file(); - - if (!f) - throw std::invalid_argument("load_file(NULL, ...) not allowed"); - try - { - pdf.reset(new QPDF); - } - catch (...) - { - if (take == CF_PDFTOPDF_TAKE_OWNERSHIP) - fclose(f); - throw; - } - - switch (take) - { - case CF_PDFTOPDF_WILL_STAY_ALIVE: - try - { - pdf->processFile("temp file", f, false); - } - catch (const std::exception &e) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: load_file failed: %s", e.what()); - return (false); - } - break; - case CF_PDFTOPDF_TAKE_OWNERSHIP: - try - { - pdf->processFile("temp file", f, true); - } - catch (const std::exception &e) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: load_file failed: %s", e.what()); - return (false); - } - break; - case CF_PDFTOPDF_MUST_DUPLICATE: - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: load_file with CF_PDFTOPDF_MUST_DUPLICATE is not supported"); - return (false); - } - - start(flatten_forms); - return (true); -} -// }}} - -bool -_cfPDFToPDFQPDFProcessor::load_filename(const char *name, - pdftopdf_doc_t *doc, - int flatten_forms) // {{{ -{ - close_file(); - - try - { - pdf.reset(new QPDF); - pdf->processFile(name); - } - catch (const std::exception &e) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: load_filename failed: %s",e.what()); - return (false); - } - - start(flatten_forms); - return (true); -} -// }}} - -void -_cfPDFToPDFQPDFProcessor::start(int flatten_forms) // {{{ -{ - DEBUG_assert(pdf); - - if (flatten_forms) - { - QPDFAcroFormDocumentHelper afdh(*pdf); - afdh.generateAppearancesIfNeeded(); - - QPDFPageDocumentHelper dh(*pdf); - dh.flattenAnnotations(an_print); - } - - pdf->pushInheritedAttributesToPage(); - orig_pages = pdf->getAllPages(); - - // remove them (just unlink, data still there) - const int len = orig_pages.size(); - for (int iA = 0; iA < len; iA ++) - pdf->removePage(orig_pages[iA]); - - // we remove stuff that becomes defunct (probably) TODO - pdf->getRoot().removeKey("/PageMode"); - pdf->getRoot().removeKey("/Outlines"); - pdf->getRoot().removeKey("/OpenAction"); - pdf->getRoot().removeKey("/PageLabels"); -} -// }}} - -bool -_cfPDFToPDFQPDFProcessor::check_print_permissions(pdftopdf_doc_t *doc) // {{{ -{ - if (!pdf) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: No PDF loaded"); - return (false); - } - return (pdf->allowPrintHighRes() || - pdf->allowPrintLowRes()); // from legacy pdftopdf -} -// }}} - -std::vector> -_cfPDFToPDFQPDFProcessor::get_pages(pdftopdf_doc_t *doc) // {{{ -{ - std::vector> ret; - if (!pdf) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: No PDF loaded"); - DEBUG_assert(0); - return (ret); - } - const int len = orig_pages.size(); - ret.reserve(len); - for (int iA = 0; iA < len; iA ++) - ret.push_back(std::shared_ptr<_cfPDFToPDFPageHandle>(new _cfPDFToPDFQPDFPageHandle(orig_pages[iA], iA+1))); - return (ret); -} -// }}} - -std::shared_ptr<_cfPDFToPDFPageHandle> -_cfPDFToPDFQPDFProcessor::new_page(float width, - float height, - pdftopdf_doc_t *doc) // {{{ -{ - if (!pdf) - { - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: No PDF loaded"); - DEBUG_assert(0); - return (std::shared_ptr<_cfPDFToPDFPageHandle>()); - } - return (std::shared_ptr<_cfPDFToPDFQPDFPageHandle>(new _cfPDFToPDFQPDFPageHandle(pdf.get(), width, height))); - // return std::make_shared<_cfPDFToPDFQPDFPageHandle>(pdf.get(), width, height); - // problem: make_shared not friend -} -// }}} - -void -_cfPDFToPDFQPDFProcessor::add_page(std::shared_ptr<_cfPDFToPDFPageHandle> page, - bool front) // {{{ -{ - DEBUG_assert(pdf); - auto qpage = dynamic_cast<_cfPDFToPDFQPDFPageHandle *>(page.get()); - if (qpage) - pdf->addPage(qpage->get(), front); -} -// }}} - -#if 0 -// we remove stuff now probably defunct TODO -pdf->getRoot().removeKey("/PageMode"); -pdf->getRoot().removeKey("/Outlines"); -pdf->getRoot().removeKey("/OpenAction"); -pdf->getRoot().removeKey("/PageLabels"); -#endif - -void -_cfPDFToPDFQPDFProcessor::multiply(int copies, - bool collate) // {{{ -{ - DEBUG_assert(pdf); - DEBUG_assert(copies > 0); - - std::vector pages = pdf->getAllPages(); // need copy - const int len = pages.size(); - - if (collate) - { - for (int iA = 1; iA < copies; iA ++) - for (int iB = 0; iB < len; iB ++) - pdf->addPage(pages[iB].shallowCopy(), false); - } - else - { - for (int iB = 0; iB < len; iB ++) - for (int iA = 1; iA < copies; iA ++) - pdf->addPageAt(pages[iB].shallowCopy(), false, pages[iB]); - } -} -// }}} - -// TODO? elsewhere? -void -_cfPDFToPDFQPDFProcessor::auto_rotate_all(bool dst_lscape, - pdftopdf_rotation_e normal_landscape) // {{{ -{ - DEBUG_assert(pdf); - - const int len = orig_pages.size(); - for (int iA = 0; iA < len; iA ++) - { - QPDFObjectHandle page = orig_pages[iA]; - - pdftopdf_rotation_e src_rot = _cfPDFToPDFGetRotate(page); - - // copy'n'paste from _cfPDFToPDFQPDFPageHandle::get_rect - _cfPDFToPDFPageRect ret = - _cfPDFToPDFGetBoxAsRect(_cfPDFToPDFGetTrimBox(page)); - // ret.translate(-ret.left, -ret.bottom); - ret.rotate_move(src_rot, ret.width, ret.height); - // ret.scale(_cfPDFToPDFGetUserUnit(page)); - - const bool src_lscape = (ret.width > ret.height); - if (src_lscape != dst_lscape) - { - pdftopdf_rotation_e rotation = normal_landscape; - // TODO? other rotation direction, e.g. - // if (src_rot == ROT_0) && (param.orientation == ROT_270) ... etc. - // rotation = ROT_270; - - page.replaceKey("/Rotate", - _cfPDFToPDFMakeRotate(src_rot + rotation)); - } - } -} -// }}} - -// TODO -void -_cfPDFToPDFQPDFProcessor::add_cm(const char *defaulticc, - const char *outputicc) // {{{ -{ - DEBUG_assert(pdf); - - if (_cfPDFToPDFHasOutputIntent(*pdf)) - return; // nothing to do - - QPDFObjectHandle srcicc = _cfPDFToPDFSetDefaultICC(*pdf, defaulticc); - // TODO? rename to putDefaultICC? - _cfPDFToPDFAddDefaultRGB(*pdf, srcicc); - - _cfPDFToPDFAddOutputIntent(*pdf, outputicc); - - hasCM = true; -} -// }}} - -void -_cfPDFToPDFQPDFProcessor::set_comments - (const std::vector &comments) // {{{ -{ - extraheader.clear(); - const int len = comments.size(); - for (int iA = 0; iA < len; iA ++) - { - DEBUG_assert(comments[iA].at(0) == '%'); - extraheader.append(comments[iA]); - extraheader.push_back('\n'); - } -} -// }}} - -void -_cfPDFToPDFQPDFProcessor::emit_file(FILE *f, - pdftopdf_doc_t *doc, - pdftopdf_arg_ownership_e take) // {{{ -{ - if (!pdf) - return; - - QPDFWriter out(*pdf); - switch (take) - { - case CF_PDFTOPDF_WILL_STAY_ALIVE: - out.setOutputFile("temp file", f, false); - break; - case CF_PDFTOPDF_TAKE_OWNERSHIP: - out.setOutputFile("temp file", f, true); - break; - case CF_PDFTOPDF_MUST_DUPLICATE: - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, - "cfFilterPDFToPDF: emit_file with CF_PDFTOPDF_MUST_DUPLICATE is not supported"); - return; - } - if (hasCM) - out.setMinimumPDFVersion("1.4"); - else - out.setMinimumPDFVersion("1.2"); - if (!extraheader.empty()) - out.setExtraHeaderText(extraheader); - out.setPreserveEncryption(false); - out.write(); -} -// }}} - -void -_cfPDFToPDFQPDFProcessor::emit_filename(const char *name, - pdftopdf_doc_t *doc) // {{{ -{ - if (!pdf) - return; - - // special case: name == NULL -> stdout - QPDFWriter out(*pdf, name); - if (hasCM) - out.setMinimumPDFVersion("1.4"); - else - out.setMinimumPDFVersion("1.2"); - if (!extraheader.empty()) - out.setExtraHeaderText(extraheader); - out.setPreserveEncryption(false); - std::vector pages = pdf->getAllPages(); - int len = pages.size(); - if (len) - out.write(); - else - if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPDFToPDF: No pages left, outputting empty file."); -} -// }}} - -// TODO: -// loadPDF(); success? - -bool -_cfPDFToPDFQPDFProcessor::has_acro_form() // {{{ -{ - if (!pdf) - return false; - - QPDFObjectHandle root = pdf->getRoot(); - if (!root.hasKey("/AcroForm")) - return (false); - return (true); -} -// }}} diff --git a/cupsfilters/pdftopdf/qpdf-pdftopdf.cxx b/cupsfilters/pdftopdf/qpdf-pdftopdf.cxx deleted file mode 100644 index 2152d2d10..000000000 --- a/cupsfilters/pdftopdf/qpdf-pdftopdf.cxx +++ /dev/null @@ -1,248 +0,0 @@ -// -// Licensed under Apache License v2.0. See the file "LICENSE" for more -// information. -// - -#include "qpdf-pdftopdf-private.h" -#include "qpdf-tools-private.h" -#include "cupsfilters/debug-internal.h" -#include -#include - - -_cfPDFToPDFPageRect -_cfPDFToPDFGetBoxAsRect(QPDFObjectHandle box) // {{{ -{ - _cfPDFToPDFPageRect ret; - - ret.left = box.getArrayItem(0).getNumericValue(); - ret.bottom = box.getArrayItem(1).getNumericValue(); - ret.right = box.getArrayItem(2).getNumericValue(); - ret.top = box.getArrayItem(3).getNumericValue(); - - ret.width = ret.right - ret.left; - ret.height = ret.top - ret.bottom; - - return (ret); -} -// }}} - -pdftopdf_rotation_e -_cfPDFToPDFGetRotate(QPDFObjectHandle page) // {{{ -{ - if (!page.hasKey("/Rotate")) - return (ROT_0); - double rot = page.getKey("/Rotate").getNumericValue(); - rot = fmod(rot, 360.0); - if (rot < 0) - rot += 360.0; - if (rot == 90.0) // CW - return (ROT_270); // CCW - else if (rot == 180.0) - return (ROT_180); - else if (rot == 270.0) - return (ROT_90); - else if (rot != 0.0) - throw std::runtime_error("Unexpected /Rotate value: " + - QUtil::double_to_string(rot)); - return (ROT_0); -} -// }}} - -double -_cfPDFToPDFGetUserUnit(QPDFObjectHandle page) // {{{ -{ - if (!page.hasKey("/UserUnit")) - return 1.0; - return (page.getKey("/UserUnit").getNumericValue()); -} -// }}} - -QPDFObjectHandle -_cfPDFToPDFMakeRotate(pdftopdf_rotation_e rot) // {{{ -{ - switch (rot) - { - case ROT_0: - return (QPDFObjectHandle::newNull()); - case ROT_90: // CCW - return (QPDFObjectHandle::newInteger(270)); // CW - case ROT_180: - return (QPDFObjectHandle::newInteger(180)); - case ROT_270: - return (QPDFObjectHandle::newInteger(90)); - default: - throw std::invalid_argument("Bad rotation"); - } -} -// }}} - -QPDFObjectHandle -_cfPDFToPDFGetRectAsBox(const _cfPDFToPDFPageRect &rect) // {{{ -{ - return (_cfPDFToPDFMakeBox(rect.left, rect.bottom, rect.right, rect.top)); -} -// }}} - -_cfPDFToPDFMatrix::_cfPDFToPDFMatrix() // {{{ - : ctm{1,0,0,1,0,0} -{ -} -// }}} - -_cfPDFToPDFMatrix::_cfPDFToPDFMatrix(QPDFObjectHandle ar) // {{{ -{ - if (ar.getArrayNItems() != 6) - throw std::runtime_error("Not a ctm matrix"); - for (int iA = 0; iA < 6; iA ++) - ctm[iA] = ar.getArrayItem(iA).getNumericValue(); -} -// }}} - -_cfPDFToPDFMatrix -&_cfPDFToPDFMatrix::rotate(pdftopdf_rotation_e rot) // {{{ -{ - switch (rot) - { - case ROT_0: - break; - case ROT_90: - std::swap(ctm[0], ctm[2]); - std::swap(ctm[1], ctm[3]); - ctm[2] = -ctm[2]; - ctm[3] = -ctm[3]; - break; - case ROT_180: - ctm[0] = -ctm[0]; - ctm[3] = -ctm[3]; - break; - case ROT_270: - std::swap(ctm[0], ctm[2]); - std::swap(ctm[1], ctm[3]); - ctm[0] = -ctm[0]; - ctm[1] = -ctm[1]; - break; - default: - DEBUG_assert(0); - } - return (*this); -} -// }}} - -// TODO: test -_cfPDFToPDFMatrix -&_cfPDFToPDFMatrix::rotate_move(pdftopdf_rotation_e rot, - double width, - double height) // {{{ -{ - rotate(rot); - switch (rot) - { - case ROT_0: - break; - case ROT_90: - translate(width, 0); - break; - case ROT_180: - translate(width, height); - break; - case ROT_270: - translate(0, height); - break; - } - return (*this); -} -// }}} - -_cfPDFToPDFMatrix -&_cfPDFToPDFMatrix::rotate(double rad) // {{{ -{ - _cfPDFToPDFMatrix tmp; - - tmp.ctm[0] = cos(rad); - tmp.ctm[1] = sin(rad); - tmp.ctm[2] = -sin(rad); - tmp.ctm[3] = cos(rad); - - return (*this *= tmp); -} -// }}} - -_cfPDFToPDFMatrix -&_cfPDFToPDFMatrix::translate(double tx, - double ty) // {{{ -{ - ctm[4] += ctm[0] * tx + ctm[2] * ty; - ctm[5] += ctm[1] * tx + ctm[3] * ty; - return (*this); -} -// }}} - -_cfPDFToPDFMatrix -&_cfPDFToPDFMatrix::scale(double sx, - double sy) // {{{ -{ - ctm[0] *= sx; - ctm[1] *= sx; - ctm[2] *= sy; - ctm[3] *= sy; - return (*this); -} -// }}} - -_cfPDFToPDFMatrix -&_cfPDFToPDFMatrix::operator*=(const _cfPDFToPDFMatrix &rhs) // {{{ -{ - double tmp[6]; - - std::copy(ctm, ctm + 6, tmp); - - ctm[0] = tmp[0] * rhs.ctm[0] + tmp[2] * rhs.ctm[1]; - ctm[1] = tmp[1] * rhs.ctm[0] + tmp[3] * rhs.ctm[1]; - - ctm[2] = tmp[0] * rhs.ctm[2] + tmp[2] * rhs.ctm[3]; - ctm[3] = tmp[1] * rhs.ctm[2] + tmp[3] * rhs.ctm[3]; - - ctm[4] = tmp[0] * rhs.ctm[4] + tmp[2] * rhs.ctm[5] + tmp[4]; - ctm[5] = tmp[1] * rhs.ctm[4] + tmp[3] * rhs.ctm[5] + tmp[5]; - - return (*this); -} -// }}} - -QPDFObjectHandle -_cfPDFToPDFMatrix::get() const // {{{ -{ - QPDFObjectHandle ret = QPDFObjectHandle::newArray(); - - ret.appendItem(QPDFObjectHandle::newReal(ctm[0])); - ret.appendItem(QPDFObjectHandle::newReal(ctm[1])); - ret.appendItem(QPDFObjectHandle::newReal(ctm[2])); - ret.appendItem(QPDFObjectHandle::newReal(ctm[3])); - ret.appendItem(QPDFObjectHandle::newReal(ctm[4])); - ret.appendItem(QPDFObjectHandle::newReal(ctm[5])); - - return (ret); -} -// }}} - -std::string -_cfPDFToPDFMatrix::get_string() const // {{{ -{ - std::string ret; - - ret.append(QUtil::double_to_string(ctm[0])); - ret.append(" "); - ret.append(QUtil::double_to_string(ctm[1])); - ret.append(" "); - ret.append(QUtil::double_to_string(ctm[2])); - ret.append(" "); - ret.append(QUtil::double_to_string(ctm[3])); - ret.append(" "); - ret.append(QUtil::double_to_string(ctm[4])); - ret.append(" "); - ret.append(QUtil::double_to_string(ctm[5])); - - return (ret); -} -// }}} diff --git a/cupsfilters/pdftopdf/qpdf-tools-private.h b/cupsfilters/pdftopdf/qpdf-tools-private.h deleted file mode 100644 index 577f2c4f3..000000000 --- a/cupsfilters/pdftopdf/qpdf-tools-private.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// Licensed under Apache License v2.0. See the file "LICENSE" for more -// information. -// - -#ifndef _CUPS_FILTERS_PDFTOPDF_QPDF_TOOLS_H_ -#define _CUPS_FILTERS_PDFTOPDF_QPDF_TOOLS_H_ - -#include -#include -#include - -QPDFObjectHandle _cfPDFToPDFGetMediaBox(QPDFObjectHandle page); -QPDFObjectHandle _cfPDFToPDFGetCropBox(QPDFObjectHandle page); -QPDFObjectHandle _cfPDFToPDFGetBleedBox(QPDFObjectHandle page); -QPDFObjectHandle _cfPDFToPDFGetTrimBox(QPDFObjectHandle page); -QPDFObjectHandle _cfPDFToPDFGetArtBox(QPDFObjectHandle page); - -QPDFObjectHandle _cfPDFToPDFMakePage(QPDF &pdf, const std::map &xobjs, - QPDFObjectHandle mediabox, - const std::string &content); - -QPDFObjectHandle _cfPDFToPDFMakeBox(double x1, double y1, double x2, double y2); - -#endif // !_CUPS_FILTERS_PDFTOPDF_QPDF_TOOLS_H_ diff --git a/cupsfilters/pdftopdf/qpdf-tools.cxx b/cupsfilters/pdftopdf/qpdf-tools.cxx deleted file mode 100644 index d247dd1dd..000000000 --- a/cupsfilters/pdftopdf/qpdf-tools.cxx +++ /dev/null @@ -1,83 +0,0 @@ -// -// Licensed under Apache License v2.0. See the file "LICENSE" for more -// information. -// - -#include "qpdf-tools-private.h" - -QPDFObjectHandle -_cfPDFToPDFGetMediaBox(QPDFObjectHandle page) // {{{ -{ - return (page.getKey("/MediaBox")); -} -// }}} - -QPDFObjectHandle -_cfPDFToPDFGetCropBox(QPDFObjectHandle page) // {{{ -{ - if (page.hasKey("/CropBox")) - return (page.getKey("/CropBox")); - return (page.getKey("/MediaBox")); -} -// }}} - -QPDFObjectHandle -_cfPDFToPDFGetBleedBox(QPDFObjectHandle page) // {{{ -{ - if (page.hasKey("/BleedBox")) - return (page.getKey("/BleedBox")); - return (_cfPDFToPDFGetCropBox(page)); -} -// }}} - -QPDFObjectHandle -_cfPDFToPDFGetTrimBox(QPDFObjectHandle page) // {{{ -{ - if (page.hasKey("/TrimBox")) - return (page.getKey("/TrimBox")); - return (_cfPDFToPDFGetCropBox(page)); -} -// }}} - -QPDFObjectHandle -_cfPDFToPDFGetArtBox(QPDFObjectHandle page) // {{{ -{ - if (page.hasKey("/ArtBox")) - return (page.getKey("/ArtBox")); - return (_cfPDFToPDFGetCropBox(page)); -} -// }}} - -QPDFObjectHandle -_cfPDFToPDFMakePage(QPDF &pdf, - const std::map &xobjs, - QPDFObjectHandle mediabox, - const std::string &content) // {{{ -{ - QPDFObjectHandle ret = QPDFObjectHandle::newDictionary(); - ret.replaceKey("/Type", QPDFObjectHandle::newName("/Page")); - - auto resdict = QPDFObjectHandle::newDictionary(); - resdict.replaceKey("/XObject", QPDFObjectHandle::newDictionary(xobjs)); - ret.replaceKey("/Resources", resdict); - ret.replaceKey("/MediaBox", mediabox); - ret.replaceKey("/Contents", QPDFObjectHandle::newStream(&pdf, content)); - - return (ret); -} -// }}} - -QPDFObjectHandle -_cfPDFToPDFMakeBox(double x1, - double y1, - double x2, - double y2) // {{{ -{ - QPDFObjectHandle ret = QPDFObjectHandle::newArray(); - ret.appendItem(QPDFObjectHandle::newReal(x1)); - ret.appendItem(QPDFObjectHandle::newReal(y1)); - ret.appendItem(QPDFObjectHandle::newReal(x2)); - ret.appendItem(QPDFObjectHandle::newReal(y2)); - return (ret); -} -// }}} diff --git a/cupsfilters/pdftopdf/qpdf-xobject-private.h b/cupsfilters/pdftopdf/qpdf-xobject-private.h deleted file mode 100644 index f318b1e21..000000000 --- a/cupsfilters/pdftopdf/qpdf-xobject-private.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// Licensed under Apache License v2.0. See the file "LICENSE" for more -// information. -// - -#ifndef _CUPS_FILTERS_PDFTOPDF_QPDF_XOBJECT_H_ -#define _CUPS_FILTERS_PDFTOPDF_QPDF_XOBJECT_H_ - -#include - -QPDFObjectHandle _cfPDFToPDFMakeXObject(QPDF *pdf, QPDFObjectHandle page); - -#endif // !_CUPS_FILTERS_PDFTOPDF_QPDF_XOBJECT_H_ diff --git a/cupsfilters/pdftopdf/qpdf-xobject.cxx b/cupsfilters/pdftopdf/qpdf-xobject.cxx deleted file mode 100644 index 508fee7c3..000000000 --- a/cupsfilters/pdftopdf/qpdf-xobject.cxx +++ /dev/null @@ -1,202 +0,0 @@ -// -// Licensed under Apache License v2.0. See the file "LICENSE" for more -// information. -// - -#include "qpdf-xobject-private.h" -//#include -#include -#include -#include -#include -#include "qpdf-tools-private.h" -#include "qpdf-pdftopdf-private.h" - -// TODO: need to remove Struct Parent stuff (or fix) - -// NOTE: use /TrimBox to position content inside Nup cell, -// /BleedBox to clip against - -class CombineFromContents_Provider : public QPDFObjectHandle::StreamDataProvider { -public: - CombineFromContents_Provider(const std::vector &contents); - - void provideStreamData(int objid, int generation, Pipeline* pipeline); -private: - std::vector contents; -}; - -CombineFromContents_Provider::CombineFromContents_Provider(const std::vector &contents) - : contents(contents) -{ -} - -void -CombineFromContents_Provider::provideStreamData(int objid, - int generation, - Pipeline* pipeline) -{ - Pl_Concatenate concat("concat", pipeline); - const int clen = contents.size(); - for (int iA = 0; iA < clen; iA ++) - { - contents[iA].pipeStreamData(&concat, true, false, false); - concat << "\n"; - } - concat.manualFinish(); -} - -// -// To convert a page to an XObject there are several keys to consider: -// -// /Type /Page -> /Type /XObject (/Type optional for XObject) -// -> /Subtype /Form -// -> [/FormType 1] (optional) -// /Parent ? ? R -> remove -// /Resources dict -> copy -// /MediaBox rect [/CropBox /BleedBox /TrimBox /ArtBox] -// -> /BBox (use TrimBox [+ Bleed consideration?], -// with fallback to /MediaBox) -// note that /BBox is in *Form Space*, see /Matrix! -// [/BoxColorInfo dict] (used for guidelines that may be shown by viewer) -// -> ignore/remove -// [/Contents asfd] -> concatenate into stream data of the XObject -// (page is a dict, XObject a stream) -// -// [/Rotate 90] ... must be handled (either use CTM where XObject is -// /used/ -- or set /Matrix) -// [/UserUnit] (PDF 1.6) -> to /Matrix ? -- it MUST be handled. -// -// [/Group dict] -> copy -// [/Thumb stream] -> remove, not needed any more / would have to be -// regenerated (combined) -// [/B] article beads -- ignore for now -// [/Dur] -> remove (transition duration) -// [/Trans] -> remove (transitions) -// [/AA] -> remove (additional-actions) -// -// [/Metadata] what shall we do?? (kill: we can't combine XML) -// [/PieceInfo] -> remove, we can't combine private app data (?) -// [/LastModified date] (opt except /PieceInfo) -> see there -// -// [/PZ] -> remove, can't combine/keep (preferred zoom level) -// [/SeparationInfo] -> remove, no way to keep this (needed for separation) -// -// [/ID] related to web capture -- ignore/kill? -// [/StructParents] (opt except pdf contains "structural content items") -// -> copy (is this correct?) -// -// [/Annots] annotations -- ignore for now -// [/Tabs] tab order for annotations (/R row, /C column, -// /S structure order) -- see /Annots -// -// [/TemplateInstantiated] (reqd, if page was created from named page obj, -// 1.5) -- ? just ignore? -// [/PresSteps] -> remove (sub-page navigation for presentations) -// [no subpage navigation for printing / nup] -// [/VP] viewport rects -- ignore/drop or recalculate into new -// page -// - -QPDFObjectHandle -_cfPDFToPDFMakeXObject(QPDF *pdf, QPDFObjectHandle page) -{ - page.assertPageObject(); - - QPDFObjectHandle ret = QPDFObjectHandle::newStream(pdf); - QPDFObjectHandle dict = ret.getDict(); - - dict.replaceKey("/Type", QPDFObjectHandle::newName("/XObject")); // optional - dict.replaceKey("/Subtype", QPDFObjectHandle::newName("/Form")); // required - // dict.replaceKey("/FormType", QPDFObjectHandle::newInteger(1)); // optional - - QPDFObjectHandle box = _cfPDFToPDFGetTrimBox(page); // already in "form space" - dict.replaceKey("/BBox", box); // reqd - - // [/Matrix .] ... default is [1 0 0 1 0 0]; we incorporate /UserUnit and - // /Rotate here - _cfPDFToPDFMatrix mtx; - if (page.hasKey("/UserUnit")) - mtx.scale(page.getKey("/UserUnit").getNumericValue()); - - // transform, so that bbox is [0 0 w h] (in outer space, but after UserUnit) - pdftopdf_rotation_e rot = _cfPDFToPDFGetRotate(page); - - // calculate rotation effect on [0 0 w h] - _cfPDFToPDFPageRect bbox = _cfPDFToPDFGetBoxAsRect(box), - tmp; - tmp.left = 0; - tmp.bottom = 0; - tmp.right = 0; - tmp.top = 0; - tmp.rotate_move(rot, bbox.width, bbox.height); - // tmp.rotate_move moves the bbox; we must achieve this move with the matrix. - mtx.translate(tmp.left, tmp.bottom); // 1. move origin to end up at - // left,bottom after rotation - - mtx.rotate(rot); // 2. rotate coordinates according to /Rotate - mtx.translate(-bbox.left, -bbox.bottom); // 3. move origin from 0,0 to - // "form space" - - dict.replaceKey("/Matrix", mtx.get()); - - dict.replaceKey("/Resources", page.getKey("/Resources")); - if (page.hasKey("/Group")) - dict.replaceKey("/Group", page.getKey("/Group")); // (transparency); opt, - // copy if there - - // ?? /StructParents ... can basically copy from page, but would need - // fixup in Structure Tree - // FIXME: remove (globally) Tagged spec (/MarkInfo), and Structure Tree - - // Note: [/Name] (reqd. only in 1.0 -- but there we even can't use our - // normal img/patter procedures) - - // none: - // QPDFObjectHandle filter = QPDFObjectHandle::newArray(); - // QPDFObjectHandle decode_parms = QPDFObjectHandle::newArray(); - // null leads to use of "default filters" from qpdf's settings - QPDFObjectHandle filter = QPDFObjectHandle::newNull(); - QPDFObjectHandle decode_parms = QPDFObjectHandle::newNull(); - - std::vector contents = page.getPageContents(); - // (will assertPageObject) - - auto ph = std::shared_ptr(new CombineFromContents_Provider(contents)); - ret.replaceStreamData(ph, filter, decode_parms); - - return (ret); -} - -// -// we will have to fix up the structure tree (e.g. /K in element), when copying -// /StructParents; -// (there is /Pg, which has to point to the containing page, /Stm when it's not -// part of the page's content stream -// i.e. when it is in our XObject!; then there is /StmOwn ...) -// when not copying, we have to remove the structure tree completely -// (also /MarkInfo dict) -// Still this might not be sufficient(?), as there are probably BDC and EMC -// operators in the stream. -// -// -// /XObject /Form has -// [/Type /XObject] -// /Subtype /Form -// [/FormType 1] -// /BBox rect from crop box, or recalculate -// [/Matrix .] ... default is [1 0 0 1 0 0] --- we have to incorporate -// /UserUnit here?! -// [/Resources dict] from page. -// [/Group dict] used for transparency -- can copy from page -// [/Ref dict] not needed; for external reference -// [/Metadata] not, as long we can not combine. -// [/PieceInfo] can copy, but not combine -// [/LastModified date] copy if /PieceInfo there -// [/StructParent] . don't want to be one ... have to read more spec -// [/StructParents] . copy from page! -// [/OPI] no opi version. don't set -// [/OC] is this optional content? NO! not needed. -// [/Name] (only reqd. in 1.0 -- but there we even can't use our -// normal img/patter procedures) -// diff --git a/cupsfilters/pdftoraster.c b/cupsfilters/pdftoraster.c new file mode 100644 index 000000000..4f9db94a5 --- /dev/null +++ b/cupsfilters/pdftoraster.c @@ -0,0 +1,267 @@ +// +// PDF to Raster filter function for libcupsfilters. +// +// Copyright (c) 2025 by Uddhav Phatak +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEMP_PREFIX "/tmp/pdftoraster-XXXXXX" + +typedef struct { + char *input_filename; + char *temp_base; + cf_logfunc_t log; + void *logdata; + cmsHPROFILE dst_profile; +} pdftoraster_doc_t; + +static void +cleanup_temp_files(pdftoraster_doc_t *doc) +{ + if (!doc || !doc->temp_base) return; + + char cmd[2048]; + snprintf(cmd, sizeof(cmd), "rm -rf %s* 2>/dev/null", doc->temp_base); + int rc = system(cmd); + (void)rc; +} + +static int +get_pdf_page_count(const char *filename, + cf_logfunc_t log, + void *ld) +{ + pdfio_file_t *pdf = pdfioFileOpen(filename, NULL, NULL, NULL, NULL); + + int pages = pdfioFileGetNumPages(pdf); + pdfioFileClose(pdf); + return pages; +} + +static int +render_page_to_ppm(pdftoraster_doc_t *doc, + int pagenum, + int resolution) +{ + char temp_ppm[1024]; + snprintf(temp_ppm, sizeof(temp_ppm), "%s-%06d.ppm", doc->temp_base, pagenum); + + const char *args[] = { + "pdftoppm", + "-r", NULL, + "-f", NULL, + "-l", NULL, + "-cropbox", + "-singlefile", + doc->input_filename, + temp_ppm, + NULL + }; + + char res_str[16], page_str[16]; + snprintf(res_str, sizeof(res_str), "%d", resolution); + snprintf(page_str, sizeof(page_str), "%d", pagenum); + + args[2] = res_str; + args[4] = page_str; + args[5] = page_str; + + pid_t pid = fork(); + if (pid == 0) { + execvp(args[0], (char *const *)args); + exit(EXIT_FAILURE); + } + + int status; + waitpid(pid, &status, 0); + return WIFEXITED(status) && WEXITSTATUS(status) == 0 ? 0 : -1; +} + +static int +convert_ppm_to_raster(pdftoraster_doc_t *doc, + const char *ppm_path, + cups_raster_t *raster, + cups_page_header2_t *header) +{ + cf_image_t *img = NULL; + unsigned char *line = NULL; + cmsHTRANSFORM transform = NULL; + cmsHPROFILE src_profile = NULL; + int ret = -1; + + img = cfImageOpen(ppm_path, + CUPS_CSPACE_RGB, // Primary color space + CUPS_CSPACE_W, // Secondary color space + 100, // Saturation + 0, // Hue + NULL); // LUT + + if (!img) + { + doc->log(doc->logdata, CF_LOGLEVEL_ERROR, "Failed to open PPM image"); + goto out; + } + + src_profile = cmsCreate_sRGBProfile(); + if (!doc->dst_profile) { + doc->dst_profile = cmsCreate_sRGBProfile(); + } + + transform = cmsCreateTransform(src_profile, TYPE_RGB_8, + doc->dst_profile, TYPE_CMYK_8, + INTENT_PERCEPTUAL, 0); + + line = malloc(header->cupsBytesPerLine); + if (!line) { + doc->log(doc->logdata, CF_LOGLEVEL_ERROR, "Memory allocation failed"); + goto out; + } + + for (unsigned y = 0; y < header->cupsHeight; y++) { + unsigned char pixels[header->cupsWidth * 3]; + + if (!cfImageGetRow(img, 0, y, header->cupsWidth, pixels)) { + doc->log(doc->logdata, CF_LOGLEVEL_ERROR, "Failed to get image row"); + goto out; + } + + cmsDoTransform(transform, pixels, line, header->cupsWidth); + + if (!cupsRasterWritePixels(raster, line, header->cupsBytesPerLine)) { + doc->log(doc->logdata, CF_LOGLEVEL_ERROR, "Raster write failed"); + goto out; + } + } + + ret = 0; + +out: + free(line); + if (transform) cmsDeleteTransform(transform); + if (src_profile) cmsCloseProfile(src_profile); + if (img) cfImageClose(img); + return ret; +} + +int +cfFilterPDFToRaster(int inputfd, + int outputfd, + int inputseekable, + cf_filter_data_t *data, + void *parameters) +{ + pdftoraster_doc_t doc = {0}; + cups_raster_t *raster = NULL; + cups_page_header2_t header; + int ret = 1; + char temp_input[1024]; + int fd = -1; + + doc.log = data->logfunc; + doc.logdata = data->logdata; + + strncpy(temp_input, TEMP_PREFIX, sizeof(temp_input)); + if ((fd = mkstemp(temp_input)) == -1) { + doc.log(doc.logdata, CF_LOGLEVEL_ERROR, + "Temp file creation failed: %s", strerror(errno)); + goto cleanup; + } + + char buf[8192]; + ssize_t bytes; + while ((bytes = read(inputfd, buf, sizeof(buf))) > 0) { + if (write(fd, buf, bytes) != bytes) { + doc.log(doc.logdata, CF_LOGLEVEL_ERROR, + "Write error: %s", strerror(errno)); + goto cleanup; + } + } + close(fd); + fd = -1; + doc.input_filename = temp_input; + + doc.temp_base = strdup(TEMP_PREFIX); + if (!mkdtemp(doc.temp_base)) { + doc.log(doc.logdata, CF_LOGLEVEL_ERROR, + "Temp directory creation failed: %s", strerror(errno)); + goto cleanup; + } + + int npages = get_pdf_page_count(doc.input_filename, doc.log, doc.logdata); + if (npages < 1) { + doc.log(doc.logdata, CF_LOGLEVEL_ERROR, "Invalid page count"); + goto cleanup; + } + + cf_filter_out_format_t outformat = CF_FILTER_OUT_FORMAT_CUPS_RASTER; + if (data->final_content_type) { + if (strcasestr(data->final_content_type, "pwg")) { + outformat = CF_FILTER_OUT_FORMAT_PWG_RASTER; + } + else if (strcasestr(data->final_content_type, "urf")) { + outformat = CF_FILTER_OUT_FORMAT_APPLE_RASTER; + } + } + + memset(&header, 0, sizeof(header)); + if (!cfRasterPrepareHeader(&header, data, outformat, + (outformat == CF_FILTER_OUT_FORMAT_PWG_RASTER || + outformat == CF_FILTER_OUT_FORMAT_APPLE_RASTER) ? + outformat : CF_FILTER_OUT_FORMAT_CUPS_RASTER, + 0, NULL)) { + doc.log(doc.logdata, CF_LOGLEVEL_ERROR, "Header preparation failed"); + goto cleanup; + } + + raster = cupsRasterOpen(outputfd, + outformat == CF_FILTER_OUT_FORMAT_PWG_RASTER ? CUPS_RASTER_WRITE_PWG : + outformat == CF_FILTER_OUT_FORMAT_APPLE_RASTER ? CUPS_RASTER_WRITE_APPLE : + CUPS_RASTER_WRITE); + + if (!raster) { + doc.log(doc.logdata, CF_LOGLEVEL_ERROR, "Raster output failed"); + goto cleanup; + } + + for (int page = 1; page <= npages; page++) + { + char ppm_path[1024]; + snprintf(ppm_path, sizeof(ppm_path), "%s-%06d.ppm", doc.temp_base, page); + + if (render_page_to_ppm(&doc, page, header.HWResolution[0]) || + convert_ppm_to_raster(&doc, ppm_path, raster, &header)) { + doc.log(doc.logdata, CF_LOGLEVEL_ERROR, + "Page %d conversion failed", page); + goto cleanup; + } + unlink(ppm_path); + } + + ret = 0; + +cleanup: + if (fd != -1) close(fd); + if (raster) cupsRasterClose(raster); + if (doc.input_filename) unlink(doc.input_filename); + cleanup_temp_files(&doc); + free(doc.temp_base); + return ret; +} diff --git a/cupsfilters/pwgtopdf.cxx b/cupsfilters/pwgtopdf.c similarity index 56% rename from cupsfilters/pwgtopdf.cxx rename to cupsfilters/pwgtopdf.c index 06a215651..632a50049 100644 --- a/cupsfilters/pwgtopdf.cxx +++ b/cupsfilters/pwgtopdf.c @@ -5,6 +5,7 @@ // Copyright 2012 by Tobias Hoffmann // Copyright 2014-2022 by Till Kamppeter // Copyright 2017 by Sahil Arora +// Copyright 2024 by Uddhav Phatak // // Licensed under Apache License v2.0. See the file "LICENSE" for more // information. @@ -21,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -30,18 +30,13 @@ #include #include #include +#include #include // ntohl -#include -#include -#include -#include - -#include -#include -#include -#include +#include +#include +#include #ifdef USE_LCMS1 #include @@ -81,7 +76,6 @@ #define PRE_COMPRESS - // Compression method for providing data to PCLm Streams. typedef enum compression_method_e { @@ -102,9 +96,9 @@ typedef unsigned char *(*bit_convert_function)(unsigned char *src, typedef struct pwgtopdf_doc_s // **** Document information **** { - cmsHPROFILE colorProfile = NULL; // ICC Profile to be applied to + cmsHPROFILE colorProfile; // ICC Profile to be applied to // PDF - int cm_disabled = 0; // Flag raised if color + int cm_disabled; // Flag raised if color // management is disabled convert_function conversion_function; // Raster color conversion // function @@ -121,63 +115,174 @@ typedef struct pwgtopdf_doc_s // **** Document information **** // function, can be NULL } pwgtopdf_doc_t; -// PDF color conversion function -typedef void (*pdf_convert_function)(struct pdf_info * info, - pwgtopdf_doc_t *doc); - - -struct pdf_info // **** PDF **** +struct pdf_info { - pdf_info() - : pagecount(0), - width(0), height(0), - line_bytes(0), - bpp(0), bpc(0), - pclm_num_strips(0), - pclm_strip_height_preferred(16), // default strip height - pclm_strip_height(0), - pclm_strip_height_supported(1, 16), - pclm_compression_method_preferred(0), - pclm_source_resolution_supported(0), - pclm_source_resolution_default(""), - pclm_raster_back_side(""), - pclm_strip_data(0), - render_intent(""), - color_space(CUPS_CSPACE_K), - page_width(0), page_height(0), - outformat(CF_FILTER_OUT_FORMAT_PDF) - { - } - - QPDF pdf; - QPDFObjectHandle page; + // PDFio-specific members + pdfio_file_t *pdf; // PDFio file handle + pdfio_obj_t *page; // PDFio page handle + unsigned pagecount; unsigned width; unsigned height; unsigned line_bytes; unsigned bpp; unsigned bpc; - unsigned pclm_num_strips; - unsigned pclm_strip_height_preferred; - std::vector pclm_strip_height; - std::vector pclm_strip_height_supported; - std::vector pclm_compression_method_preferred; - std::vector pclm_source_resolution_supported; - std::string pclm_source_resolution_default; - std::string pclm_raster_back_side; - std::vector< std::shared_ptr > pclm_strip_data; - std::string render_intent; - cups_cspace_t color_space; - std::shared_ptr page_data; - double page_width,page_height; + unsigned pclm_num_strips; + unsigned pclm_strip_height_preferred; + unsigned *pclm_strip_height; // Dynamically allocated array in C + unsigned *pclm_strip_height_supported; // Dynamically allocated array + compression_method_t *pclm_compression_method_preferred; + size_t num_compression_methods; + char **pclm_source_resolution_supported; // Array of dynamically allocated strings + char *pclm_source_resolution_default; // Pointer to dynamically allocated string + char *pclm_raster_back_side; // Pointer to dynamically allocated string + unsigned char **pclm_strip_data; // Array of pointers to raw data (buffers) + char *render_intent; // Pointer to dynamically allocated string + cups_cspace_t color_space; + unsigned char *page_data; // Pointer to raw page data + double page_width, page_height; cf_filter_out_format_t outformat; }; +// PDF color conversion function +typedef void (*pdf_convert_function)(struct pdf_info *info, + pwgtopdf_doc_t *doc); + + +void +init_pdf_info(struct pdf_info *info, + size_t num_methods, + size_t num_strips_supported) +{ + // Initialize primitive types + info->pagecount = 0; + info->width = 0; + info->height = 0; + info->line_bytes = 0; + info->bpp = 0; + info->bpc = 0; + info->pclm_num_strips = 0; + info->pclm_strip_height_preferred = 16; // Default strip height + info->page_width = 0; + info->page_height = 0; + info->outformat = CF_FILTER_OUT_FORMAT_PDF; + + // Allocate memory for pclm_strip_height (for strip height handling) + info->pclm_strip_height = (unsigned *)malloc(num_strips_supported * sizeof(unsigned)); + if (info->pclm_strip_height) + { + for (size_t i = 0; i < num_strips_supported; ++i) + { + info->pclm_strip_height[i] = 0; // Initialize to 0 or a specific value as needed + } + } + + // Allocate memory for pclm_strip_height_supported + info->pclm_strip_height_supported = (unsigned *)malloc(num_strips_supported * sizeof(unsigned)); + if (info->pclm_strip_height_supported) + { + for (size_t i = 0; i < num_strips_supported; ++i) + { + info->pclm_strip_height_supported[i] = 16; // Initialize to default value + } + } + + // Allocate memory for multiple compression methods + info->num_compression_methods = num_methods; + info->pclm_compression_method_preferred = (compression_method_t *)malloc(num_methods * sizeof(compression_method_t)); + if (info->pclm_compression_method_preferred) + { + for (size_t i = 0; i < num_methods; ++i) + { + info->pclm_compression_method_preferred[i] = 0; // Initialize to default or specific compression method + } + } + + info->pclm_source_resolution_default = (char *)malloc(64 * sizeof(char)); + if (info->pclm_source_resolution_default) + { + strcpy(info->pclm_source_resolution_default, ""); // Initialize to empty string + } + + info->pclm_raster_back_side = (char *)malloc(64 * sizeof(char)); + if (info->pclm_raster_back_side) + { + strcpy(info->pclm_raster_back_side, ""); // Initialize to empty string + } + + info->render_intent = (char *)malloc(64 * sizeof(char)); + if (info->render_intent) + { + strcpy(info->render_intent, ""); // Initialize to empty string + } + + info->pclm_source_resolution_supported = NULL; + info->pclm_strip_data = NULL; // Assuming this will be dynamically allocated elsewhere + + info->color_space = CUPS_CSPACE_K; // Default color space + info->page_data = NULL; // Will be allocated when needed + + info->pdf = NULL; // Initialize to NULL, will be set when opening a file + info->page = NULL; // Initialize to NULL, will be set when reading a page +} + +// Freeing the dynamically allocated memory +void free_pdf_info(struct pdf_info *info) +{ + if (info->pclm_strip_height) + { + free(info->pclm_strip_height); + info->pclm_strip_height = NULL; + } + + if (info->pclm_strip_height_supported) + { + free(info->pclm_strip_height_supported); + info->pclm_strip_height_supported = NULL; + } + + if (info->pclm_compression_method_preferred) + { + free(info->pclm_compression_method_preferred); + info->pclm_compression_method_preferred = NULL; + } + + // Free dynamically allocated strings + if (info->pclm_source_resolution_default) + { + free(info->pclm_source_resolution_default); + info->pclm_source_resolution_default = NULL; + } + + if (info->pclm_raster_back_side) + { + free(info->pclm_raster_back_side); + info->pclm_raster_back_side = NULL; + } + + if (info->render_intent) + { + free(info->render_intent); + info->render_intent = NULL; + } + + // Free any other dynamically allocated memory as necessary + if (info->pclm_strip_data) + { + free(info->pclm_strip_data); // Assuming this array will be dynamically allocated elsewhere + info->pclm_strip_data = NULL; + } + + if (info->page_data) + { + free(info->page_data); + info->page_data = NULL; + } +} // // Bit conversion functions // - static unsigned char * invert_bits(unsigned char *src, unsigned char *dst, @@ -192,7 +297,6 @@ invert_bits(unsigned char *src, return (dst); } - static unsigned char * no_bit_conversion(unsigned char *src, unsigned char *dst, @@ -201,7 +305,6 @@ no_bit_conversion(unsigned char *src, return (src); } - // // Color conversion functions // @@ -274,7 +377,6 @@ no_color_conversion(unsigned char *src, return (src); } - // // 'split_strings()' - Split a string to a vector of strings given some // delimiters @@ -283,34 +385,67 @@ no_color_conversion(unsigned char *src, // I - input string to be split // I - string containing delimiters // +// Function to split strings by delimiters -static std::vector -split_strings(std::string const &str, - std::string delimiters = ",") +char** +split_strings(const char *str, + const char *delimiters, + int *size) { - std::vector vec(0); - std::string value = ""; + if (delimiters == NULL || strlen(delimiters) == 0) + delimiters = ","; + + + int capacity = 10; + char **result = malloc(capacity * sizeof(char *)); + + char *value = malloc(strlen(str) + 1); + + int token_count = 0; + int index = 0; bool push_flag = false; - for (size_t i = 0; i < str.size(); i ++) + for (size_t i = 0; i < strlen(str); i++) { - if (push_flag && !(value.empty())) + if (strchr(delimiters, str[i]) != NULL) + { + if (push_flag && index > 0) + { + value[index] = '\0'; + result[token_count] = malloc(strlen(value) + 1); + strcpy(result[token_count], value); + token_count++; + + if (token_count >= capacity) + { + capacity *= 2; + result = realloc(result, capacity * sizeof(char *)); + } + + index = 0; + push_flag = false; + } + } + else { - vec.push_back(value); - push_flag = false; - value.clear(); + value[index++] = str[i]; + push_flag = true; } + } - if (delimiters.find(str[i]) != std::string::npos) - push_flag = true; - else - value += str[i]; + if (push_flag && index > 0) + { + value[index] = '\0'; + result[token_count] = malloc(strlen(value) + 1); + strcpy(result[token_count], value); + token_count++; } - if (!value.empty()) - vec.push_back(value); - return (vec); -} + *size = token_count; + + free(value); + return result; +} // // 'num_digits()' - Calculates the number of digits in an integer @@ -333,7 +468,6 @@ num_digits(int n) return (digits); } - // // 'int_to_fwstring()' - Convert a number to fixed width string by padding // with zeroes @@ -342,64 +476,54 @@ num_digits(int n) // I - width of string required // -static std::string -int_to_fwstring(int n, - int width) +char* +int_to_fwstring(int n, int width) { int num_zeroes = width - num_digits(n); - if (num_zeroes < 0) + if (num_zeroes < 0) num_zeroes = 0; - return (std::string(num_zeroes, '0') + QUtil::int_to_string(n)); -} + int result_length = num_zeroes + num_digits(n) + 1; + char *result = malloc(result_length * sizeof(char)); + + for (int i = 0; i < num_zeroes; i++) + result[i] = '0'; -static int -create_pdf_file(struct pdf_info * info, - const cf_filter_out_format_t &outformat) -{ - try - { - info->pdf.emptyPDF(); - info->outformat = outformat; - } - catch (...) - { - return (1); - } - return (0); + sprintf(result + num_zeroes, "%d", n); + return result; } +static int +create_pdf_file(struct pdf_info *info, + const cf_filter_out_format_t outformat) +{ + if (!info || !info->pdf) + return 1; // Error handling + + pdfio_file_t *temp = pdfioFileCreate(pdfioFileGetName(info->pdf), NULL, NULL, NULL, NULL, NULL); + + info->pdf = temp; + info->outformat = outformat; -static QPDFObjectHandle + return 0; +} + +static pdfio_rect_t make_real_box(double x1, double y1, double x2, double y2) { - QPDFObjectHandle ret = QPDFObjectHandle::newArray(); - ret.appendItem(QPDFObjectHandle::newReal(x1)); - ret.appendItem(QPDFObjectHandle::newReal(y1)); - ret.appendItem(QPDFObjectHandle::newReal(x2)); - ret.appendItem(QPDFObjectHandle::newReal(y2)); - return (ret); -} + pdfio_rect_t ret; + ret.x1 = x1; + ret.y1 = y1; + ret.x2 = x2; + ret.y2 = y2; -static QPDFObjectHandle -make_integer_box(int x1, - int y1, - int x2, - int y2) -{ - QPDFObjectHandle ret = QPDFObjectHandle::newArray(); - ret.appendItem(QPDFObjectHandle::newInteger(x1)); - ret.appendItem(QPDFObjectHandle::newInteger(y1)); - ret.appendItem(QPDFObjectHandle::newInteger(x2)); - ret.appendItem(QPDFObjectHandle::newInteger(y2)); return (ret); } - // // PDF color conversion functons... // @@ -425,7 +549,6 @@ modify_pdf_color(struct pdf_info *info, doc->conversion_function = fn; } - static void convert_pdf_no_conversion(struct pdf_info *info, pwgtopdf_doc_t *doc) @@ -497,31 +620,20 @@ convert_pdf_invert_colors(struct pdf_info *info, doc->bit_function = invert_bits; } - // // Create an '/ICCBased' array and embed a previously // set ICC Profile in the PDF // - -static QPDFObjectHandle -embed_icc_profile(QPDF &pdf, - pwgtopdf_doc_t *doc) +// TODO: HOW IS THIS cmsHPROFILE CALLED +pdfio_obj_t * +embed_icc_profile(pdfio_file_t *pdf, pwgtopdf_doc_t *doc) { - if (doc->colorProfile == NULL) - return (QPDFObjectHandle::newNull()); - - // Return handler - QPDFObjectHandle ret; - // ICCBased array - QPDFObjectHandle array = QPDFObjectHandle::newArray(); - // Profile stream dictionary - QPDFObjectHandle iccstream; - - std::map dict; - std::map streamdict; - std::string n_value = ""; - std::string alternate_cs = ""; - std::shared_ptrph; + pdfio_dict_t *streamdict; + pdfio_obj_t *icc_stream; + char *n_value = NULL; + char *alternate_cs = NULL; + unsigned char *buff; + cmsColorSpaceSignature css; #ifdef USE_LCMS1 size_t profile_size; @@ -529,75 +641,67 @@ embed_icc_profile(QPDF &pdf, unsigned int profile_size; #endif - cmsColorSpaceSignature css = cmsGetColorSpace(doc->colorProfile); + // Determine color space signature + css = cmsGetColorSpace(doc->colorProfile); - // Write color component # for /ICCBased array in stream dictionary + // Determine color component number for /ICCBased array switch (css) { case cmsSigGrayData: - n_value = "1"; - alternate_cs = "/DeviceGray"; - break; + n_value = "1"; + alternate_cs = "/DeviceGray"; + break; case cmsSigRgbData: - n_value = "3"; - alternate_cs = "/DeviceRGB"; - break; + n_value = "3"; + alternate_cs = "/DeviceRGB"; + break; case cmsSigCmykData: - n_value = "4"; - alternate_cs = "/DeviceCMYK"; - break; + n_value = "4"; + alternate_cs = "/DeviceCMYK"; + break; default: - if (doc->logfunc) - doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPWGToPDF: Failed to embed ICC Profile."); - return (QPDFObjectHandle::newNull()); + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPWGToPDF: Failed to embed ICC Profile."); + return NULL; } - streamdict["/Alternate"] = QPDFObjectHandle::newName(alternate_cs); - streamdict["/N"] = QPDFObjectHandle::newName(n_value); - - // Read profile into memory cmsSaveProfileToMem(doc->colorProfile, NULL, &profile_size); - unsigned char *buff = - (unsigned char *)calloc(profile_size, sizeof(unsigned char)); + buff = (unsigned char *)calloc(profile_size, sizeof(unsigned char)); cmsSaveProfileToMem(doc->colorProfile, buff, &profile_size); - // Write ICC profile buffer into PDF - auto bf = new Buffer(buff, profile_size); - ph = std::shared_ptr(bf); - iccstream = QPDFObjectHandle::newStream(&pdf, ph); - iccstream.replaceDict(QPDFObjectHandle::newDictionary(streamdict)); + streamdict = pdfioDictCreate(pdf); + pdfioDictSetName(streamdict, "Alternate", alternate_cs); + pdfioDictSetName(streamdict, "N", n_value); - array.appendItem(QPDFObjectHandle::newName("/ICCBased")); - array.appendItem(iccstream); + icc_stream = pdfioFileCreateObj(pdf, streamdict); - // Return a PDF object reference to an '/ICCBased' array - ret = pdf.makeIndirectObject(array); + if (!icc_stream) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPWGToPDF: Failed to create ICC profile stream."); + free(buff); + return NULL; + } free(buff); - if (doc->logfunc) - doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPWGToPDF: ICC Profile embedded in PDF."); + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPWGToPDF: ICC Profile embedded in PDF."); - return (ret); + return icc_stream; } - -static QPDFObjectHandle -embed_srgb_profile(QPDF &pdf, +pdfio_obj_t* +embed_srgb_profile(pdfio_file_t *pdf, pwgtopdf_doc_t *doc) { - QPDFObjectHandle iccbased_reference; + pdfio_obj_t *iccbased_reference; - // Create an sRGB profile from lcms doc->colorProfile = cmsCreate_sRGBProfile(); - // Embed it into the profile iccbased_reference = embed_icc_profile(pdf, doc); - return (iccbased_reference); + return iccbased_reference; } - // // Calibration function for non-Lab PDF color spaces // Requires white point data, and if available, gamma or matrix numbers. @@ -609,96 +713,91 @@ embed_srgb_profile(QPDF &pdf, // /Matrix ['matrix[0]'...'matrix[n*n]'] // >> // ] -// -static QPDFObjectHandle -get_calibration_array(const char *color_space, - double wp[], - double gamma[], - double matrix[], - double bp[]) -{ - // Check for invalid input - if ((!strcmp("/CalGray", color_space) && matrix != NULL) || - wp == NULL) - return (QPDFObjectHandle()); - - QPDFObjectHandle ret; - std::string csString = color_space; - std::string colorSpaceArrayString = ""; - - char gamma_str[128]; - char bp_str[256]; - char wp_str[256]; - char matrix_str[512]; - - // Convert numbers into string data for /Gamma, /WhitePoint, and/or /Matrix - - // WhitePoint - snprintf(wp_str, sizeof(wp_str), "/WhitePoint [%g %g %g]", - wp[0], wp[1], wp[2]); - - // Gamma - if (!strcmp("/CalGray", color_space) && gamma != NULL) - snprintf(gamma_str, sizeof(gamma_str), "/Gamma %g", - gamma[0]); - else if (!strcmp("/CalRGB", color_space) && gamma != NULL) - snprintf(gamma_str, sizeof(gamma_str), "/Gamma [%g %g %g]", - gamma[0], gamma[1], gamma[2]); - else - gamma_str[0] = '\0'; - - // BlackPoint - if (bp != NULL) - snprintf(bp_str, sizeof(bp_str), "/BlackPoint [%g %g %g]", - bp[0], bp[1], bp[2]); - else - bp_str[0] = '\0'; +pdfio_array_t* +get_calibration_array(pdfio_file_t *pdf, + const char *color_space, + double wp[], + double gamma[], + double matrix[], + double bp[]) +{ + if ((!strcmp("/CalGray", color_space) && matrix != NULL) || wp == NULL) + return NULL; + + pdfio_array_t *calibration_array = pdfioArrayCreate(pdf); + if (!calibration_array) + return NULL; + + pdfioArrayAppendName(calibration_array, color_space); + + pdfio_dict_t *calibration_dict = pdfioDictCreate(pdf); + + if (wp != NULL) + { + pdfio_array_t *white_point_array = pdfioArrayCreate(pdf); + pdfioArrayAppendNumber(white_point_array, wp[0]); + pdfioArrayAppendNumber(white_point_array, wp[1]); + pdfioArrayAppendNumber(white_point_array, wp[2]); + pdfioDictSetArray(calibration_dict, "WhitePoint", white_point_array); + } - // Matrix - if (!strcmp("/CalRGB", color_space) && matrix != NULL) + if (!strcmp("/CalGray", color_space) && gamma != NULL) + pdfioDictSetNumber(calibration_dict, "Gamma", gamma[0]); + + else if (!strcmp("/CalRGB", color_space) && gamma != NULL) { - snprintf(matrix_str, sizeof(matrix_str), - "/Matrix [%g %g %g %g %g %g %g %g %g]", - matrix[0], matrix[1], matrix[2], - matrix[3], matrix[4], matrix[5], - matrix[6], matrix[7], matrix[8]); + pdfio_array_t *gamma_array = pdfioArrayCreate(pdf); + pdfioArrayAppendNumber(gamma_array, gamma[0]); + pdfioArrayAppendNumber(gamma_array, gamma[1]); + pdfioArrayAppendNumber(gamma_array, gamma[2]); + pdfioDictSetArray(calibration_dict, "Gamma", gamma_array); } - else - matrix_str[0] = '\0'; - // Write array string... - colorSpaceArrayString = "[" + csString + " <<" + gamma_str + " " + wp_str + - " " + matrix_str + " " + bp_str + " >>]"; - - ret = QPDFObjectHandle::parse(colorSpaceArrayString); + if (bp != NULL) + { + pdfio_array_t *black_point_array = pdfioArrayCreate(pdf); + pdfioArrayAppendNumber(black_point_array, bp[0]); + pdfioArrayAppendNumber(black_point_array, bp[1]); + pdfioArrayAppendNumber(black_point_array, bp[2]); + pdfioDictSetArray(calibration_dict, "BlackPoint", black_point_array); + } - return (ret); + if (!strcmp("CalRGB", color_space) && matrix != NULL) + { + pdfio_array_t *matrix_array = pdfioArrayCreate(pdf); + for (int i = 0; i < 9; i++) + pdfioArrayAppendNumber(matrix_array, matrix[i]); + pdfioDictSetArray(calibration_dict, "Matrix", matrix_array); + } + + pdfioArrayAppendDict(calibration_array, calibration_dict); + return calibration_array; } -static QPDFObjectHandle -get_cal_rgb_array(double wp[3], +pdfio_array_t* +get_cal_rgb_array(pdfio_file_t *pdf, + double wp[3], double gamma[3], double matrix[9], double bp[3]) { - QPDFObjectHandle ret = get_calibration_array("/CalRGB", wp, gamma, matrix, - bp); + pdfio_array_t *ret = get_calibration_array(pdf, "CalRGB", wp, gamma, matrix, + bp); return (ret); } - -static QPDFObjectHandle -get_cal_gray_array(double wp[3], +pdfio_array_t* +get_cal_gray_array(pdfio_file_t *pdf, + double wp[3], double gamma[1], double bp[3]) { - QPDFObjectHandle ret = get_calibration_array("/CalGray", wp, gamma, 0, bp); + pdfio_array_t *ret = get_calibration_array(pdf, "CalGray", wp, gamma, 0, bp); return (ret); } - // // 'make_pclm_strips()' - Return an std::vector of QPDFObjectHandle, each // containing the stream data of the various strips @@ -715,162 +814,121 @@ get_cal_gray_array(double wp[3], // I - document information // -static std::vector -make_pclm_strips(QPDF &pdf, - unsigned num_strips, - std::vector< std::shared_ptr > &strip_data, - std::vector &compression_methods, - unsigned width, std::vector& strip_height, - cups_cspace_t cs, +pdfio_stream_t** +make_pclm_strips(pdfio_file_t *pdf, + unsigned num_strips, + unsigned char **strip_data, + compression_method_t *compression_methods, + unsigned width, unsigned *strip_height, + cups_cspace_t cs, unsigned bpc, pwgtopdf_doc_t *doc) { - std::vector ret(num_strips); - for (size_t i = 0; i < num_strips; i ++) - ret[i] = QPDFObjectHandle::newStream(&pdf); - - // Strip stream dictionary - std::map dict; - - dict["/Type"] = QPDFObjectHandle::newName("/XObject"); - dict["/Subtype"] = QPDFObjectHandle::newName("/Image"); - dict["/Width"] = QPDFObjectHandle::newInteger(width); - dict["/BitsPerComponent"] = QPDFObjectHandle::newInteger(bpc); - - J_COLOR_SPACE color_space; - unsigned components; - // Write "/ColorSpace" dictionary based on raster input - switch(cs) + pdfio_stream_t **ret = (pdfio_stream_t **)malloc(num_strips * sizeof(pdfio_stream_t *)); + pdfio_dict_t *dict; + const char *color_space_name; + unsigned components = 0; + + switch (cs) { case CUPS_CSPACE_K: case CUPS_CSPACE_SW: - dict["/ColorSpace"] = QPDFObjectHandle::newName("/DeviceGray"); - color_space = JCS_GRAYSCALE; - components = 1; - break; + color_space_name = "DeviceGray"; + components = 1; + break; case CUPS_CSPACE_RGB: case CUPS_CSPACE_SRGB: case CUPS_CSPACE_ADOBERGB: - dict["/ColorSpace"] = QPDFObjectHandle::newName("/DeviceRGB"); - color_space = JCS_RGB; - components = 3; - break; + color_space_name = "DeviceRGB"; + components = 3; + break; default: - if (doc->logfunc) - doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPWGToPDF: Color space not supported."); - return (std::vector(num_strips, QPDFObjectHandle())); - } - - // - // We deliver already compressed content (instead of letting QPDFWriter - // do it) to avoid using excessive memory. For that we first get preferred - // compression method to pre-compress content for strip streams. - // - // Use the compression method with highest priority of the available methods - // __________________ - // Priority | Method - // ------------------ - // 0 | DCT - // 1 | FLATE - // 2 | RLE - // ------------------ - // - - compression_method_t compression = compression_methods.front(); - for (std::vector::iterator it = - compression_methods.begin(); - it != compression_methods.end(); ++it) - compression = compression > *it ? compression : *it; - - // write compressed stream data - for (size_t i = 0; i < num_strips; i ++) + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPWGToPDF: Color space not supported."); + return NULL; + } + + for (size_t i = 0; i < num_strips; i++) { - dict["/Height"] = QPDFObjectHandle::newInteger(strip_height[i]); - ret[i].replaceDict(QPDFObjectHandle::newDictionary(dict)); - Pl_Buffer psink("psink"); + dict = pdfioDictCreate(pdf); + + pdfioDictSetName(dict, "Type", "XObject"); + pdfioDictSetName(dict, "Subtype", "Image"); + pdfioDictSetNumber(dict, "Width", width); + pdfioDictSetNumber(dict, "BitsPerComponent", bpc); + pdfioDictSetName(dict, "ColorSpace", color_space_name); + pdfioDictSetNumber(dict, "Height", strip_height[i]); + + pdfio_obj_t *streamObj = pdfioFileCreateObj(pdf, dict); + ret[i] = pdfioObjCreateStream(streamObj, PDFIO_FILTER_NONE); + + compression_method_t compression = compression_methods[0]; + for (unsigned j = 0; j < num_strips; j++) + compression = compression > compression_methods[j] ? compression : compression_methods[j]; + + if (compression == FLATE_DECODE) { - Pl_Flate pflate("pflate", &psink, Pl_Flate::a_deflate); - pflate.write(strip_data[i]->getBuffer(), strip_data[i]->getSize()); - pflate.finish(); - ret[i].replaceStreamData(std::shared_ptr(psink.getBuffer()), - QPDFObjectHandle::newName("/FlateDecode"), - QPDFObjectHandle::newNull()); + pdfioStreamWrite(ret[i], strip_data[i], strip_height[i] * width * components); + pdfioDictSetName(dict, "Filter", "FlateDecode"); } else if (compression == RLE_DECODE) { - Pl_RunLength prle("prle", &psink, Pl_RunLength::a_encode); - prle.write(strip_data[i]->getBuffer(), strip_data[i]->getSize()); - prle.finish(); - ret[i].replaceStreamData(std::shared_ptr(psink.getBuffer()), - QPDFObjectHandle::newName("/RunLengthDecode"), - QPDFObjectHandle::newNull()); + pdfioStreamWrite(ret[i], strip_data[i], strip_height[i] * width * components); + pdfioDictSetName(dict, "Filter", "RunLengthDecode"); } else if (compression == DCT_DECODE) { - Pl_DCT pdct("pdct", &psink, width, strip_height[i], components, - color_space); - pdct.write(strip_data[i]->getBuffer(), strip_data[i]->getSize()); - pdct.finish(); - ret[i].replaceStreamData(std::shared_ptr(psink.getBuffer()), - QPDFObjectHandle::newName("/DCTDecode"), - QPDFObjectHandle::newNull()); + pdfioStreamWrite(ret[i], strip_data[i], strip_height[i] * width * components); + pdfioDictSetName(dict, "Filter", "DCTDecode"); } } - return (ret); + return ret; } - -static QPDFObjectHandle -make_image(QPDF &pdf, - std::shared_ptr page_data, - unsigned width, - unsigned height, - std::string render_intent, - cups_cspace_t cs, - unsigned bpc, - pwgtopdf_doc_t *doc) +pdfio_obj_t* +make_image(pdfio_file_t *pdf, + unsigned char *page_data, + int data_size, + unsigned width, + unsigned height, + const char *render_intent, + cups_cspace_t cs, + unsigned bpc, + pwgtopdf_doc_t *doc) { - QPDFObjectHandle ret = QPDFObjectHandle::newStream(&pdf); - - QPDFObjectHandle icc_ref; - + pdfio_dict_t *dict = pdfioDictCreate(pdf); + pdfio_obj_t *image_obj; + pdfio_obj_t *icc_ref; int use_blackpoint = 0; - std::map dict; - dict["/Type"] = QPDFObjectHandle::newName("/XObject"); - dict["/Subtype"] = QPDFObjectHandle::newName("/Image"); - dict["/Width"] = QPDFObjectHandle::newInteger(width); - dict["/Height"] = QPDFObjectHandle::newInteger(height); - dict["/BitsPerComponent"] = QPDFObjectHandle::newInteger(bpc); + pdfioDictSetName(dict, "Type", "XObject"); + pdfioDictSetName(dict, "Subtype", "Image"); + pdfioDictSetNumber(dict, "Width", width); + pdfioDictSetNumber(dict, "Height", height); + pdfioDictSetNumber(dict, "BitsPerComponent", bpc); - if (!doc->cm_disabled) + if (!doc->cm_disabled && render_intent) { - // Write rendering intent into the PDF based on raster settings - if (render_intent == "Perceptual") - dict["/Intent"] = QPDFObjectHandle::newName("/Perceptual"); - else if (render_intent == "Absolute") - dict["/Intent"] = QPDFObjectHandle::newName("/AbsoluteColorimetric"); - else if (render_intent == "Relative") - dict["/Intent"] = QPDFObjectHandle::newName("/RelativeColorimetric"); - else if (render_intent == "Saturation") - dict["/Intent"] = QPDFObjectHandle::newName("/Saturation"); - else if (render_intent == "RelativeBpc") + if (strcmp(render_intent, "Perceptual") == 0) + pdfioDictSetName(dict, "Intent", "Perceptual"); + else if (strcmp(render_intent, "Absolute") == 0) + pdfioDictSetName(dict, "Intent", "AbsoluteColorimetric"); + else if (strcmp(render_intent, "Relative") == 0) + pdfioDictSetName(dict, "Intent", "RelativeColorimetric"); + else if (strcmp(render_intent, "Saturation") == 0) + pdfioDictSetName(dict, "Intent", "Saturation"); + else if (strcmp(render_intent, "RelativeBpc") == 0) { - // Enable blackpoint compensation - dict["/Intent"] = QPDFObjectHandle::newName("/RelativeColorimetric"); + pdfioDictSetName(dict, "Intent", "RelativeColorimetric"); use_blackpoint = 1; } } - // Write "/ColorSpace" dictionary based on raster input if (doc->colorProfile != NULL && !doc->cm_disabled) { - icc_ref = embed_icc_profile(pdf, doc); - - if (!icc_ref.isNull()) - dict["/ColorSpace"] = icc_ref; + icc_ref = embed_icc_profile(pdf, doc); + pdfioDictSetObj(dict, "ColorSpace", icc_ref); } else if (!doc->cm_disabled) { @@ -883,7 +941,7 @@ make_image(QPDF &pdf, case CUPS_CSPACE_DEVICE5: case CUPS_CSPACE_DEVICE6: case CUPS_CSPACE_DEVICE7: - case CUPS_CSPACE_DEVICE8: + case CUPS_CSPACE_DEVICE8: case CUPS_CSPACE_DEVICE9: case CUPS_CSPACE_DEVICEA: case CUPS_CSPACE_DEVICEB: @@ -891,64 +949,64 @@ make_image(QPDF &pdf, case CUPS_CSPACE_DEVICED: case CUPS_CSPACE_DEVICEE: case CUPS_CSPACE_DEVICEF: - // For right now, DeviceN will use /DeviceCMYK in the PDF - dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceCMYK"); - break; + pdfioDictSetName(dict, "ColorSpace", "DeviceCMYK"); + break; case CUPS_CSPACE_K: - dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceGray"); - break; + pdfioDictSetName(dict, "ColorSpace", "DeviceGray"); + break; case CUPS_CSPACE_SW: - if (use_blackpoint) - dict["/ColorSpace"]=get_cal_gray_array(cfCmWhitePointSGray(), - cfCmGammaSGray(), - cfCmBlackPointDefault()); - else - dict["/ColorSpace"]=get_cal_gray_array(cfCmWhitePointSGray(), - cfCmGammaSGray(), 0); - break; + if (use_blackpoint) + pdfioDictSetArray(dict, "ColorSpace", get_cal_gray_array(pdf, cfCmWhitePointSGray(), + cfCmGammaSGray(), + cfCmBlackPointDefault())); + + else + pdfioDictSetArray(dict, "ColorSpace", get_cal_gray_array(pdf, cfCmWhitePointSGray(), + cfCmGammaSGray(), 0)); + break; case CUPS_CSPACE_CMYK: - dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceCMYK"); - break; + pdfioDictSetName(dict, "ColorSpace", "DeviceCMYK"); + break; case CUPS_CSPACE_RGB: - dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceRGB"); - break; + pdfioDictSetName(dict, "ColorSpace", "DeviceRGB"); + break; case CUPS_CSPACE_SRGB: - icc_ref = embed_srgb_profile(pdf, doc); - if (!icc_ref.isNull()) - dict["/ColorSpace"]=icc_ref; - else - dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceRGB"); - break; + icc_ref = embed_srgb_profile(pdf, doc); + if(icc_ref != NULL) + pdfioDictSetObj(dict, "ColorSpace", icc_ref); + else + pdfioDictSetName(dict, "ColorSpace", "DeviceRGB"); + break; case CUPS_CSPACE_ADOBERGB: - if (use_blackpoint) - dict["/ColorSpace"]=get_cal_rgb_array(cfCmWhitePointAdobeRGB(), - cfCmGammaAdobeRGB(), - cfCmMatrixAdobeRGB(), - cfCmBlackPointDefault()); - else - dict["/ColorSpace"]=get_cal_rgb_array(cfCmWhitePointAdobeRGB(), - cfCmGammaAdobeRGB(), - cfCmMatrixAdobeRGB(), 0); - break; - default: - if (doc->logfunc) - doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + if (use_blackpoint) + pdfioDictSetArray(dict, "ColorSpace", get_cal_rgb_array(pdf, cfCmWhitePointAdobeRGB(), + cfCmGammaAdobeRGB(), + cfCmMatrixAdobeRGB(), + cfCmBlackPointDefault())); + else + pdfioDictSetArray(dict, "ColorSpace", get_cal_rgb_array(pdf, cfCmWhitePointAdobeRGB(), + cfCmGammaAdobeRGB(), + cfCmMatrixAdobeRGB(), 0)); + break; + default: + if (doc->logfunc) + doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, "cfFilterPWGToPDF: Color space not supported."); - return (QPDFObjectHandle()); + return NULL; } } - else if (doc->cm_disabled) + else if(doc->cm_disabled) { switch(cs) { case CUPS_CSPACE_K: case CUPS_CSPACE_SW: - dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceGray"); + pdfioDictSetName(dict, "ColorSpace", "DeviceGray"); break; case CUPS_CSPACE_RGB: case CUPS_CSPACE_SRGB: case CUPS_CSPACE_ADOBERGB: - dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceRGB"); + pdfioDictSetName(dict, "ColorSpace", "DeviceRGB"); break; case CUPS_CSPACE_DEVICE1: case CUPS_CSPACE_DEVICE2: @@ -966,162 +1024,126 @@ make_image(QPDF &pdf, case CUPS_CSPACE_DEVICEE: case CUPS_CSPACE_DEVICEF: case CUPS_CSPACE_CMYK: - dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceCMYK"); + pdfioDictSetName(dict, "ColorSpace", "DeviceCMYK"); break; default: if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, "cfFilterPWGToPDF: Color space not supported."); - return (QPDFObjectHandle()); + return NULL; } } else - return (QPDFObjectHandle()); + return NULL; - ret.replaceDict(QPDFObjectHandle::newDictionary(dict)); + image_obj = pdfioFileCreateObj(pdf, dict); #ifdef PRE_COMPRESS - // we deliver already compressed content (instead of letting QPDFWriter - // do it), to avoid using excessive memory - Pl_Buffer psink("psink"); - Pl_Flate pflate("pflate", &psink, Pl_Flate::a_deflate); + uLongf compressed_size = compressBound(data_size); + unsigned char *compressed_data = (unsigned char *)malloc(compressed_size); + if (compress(compressed_data, &compressed_size, page_data, data_size) != Z_OK) + { + if (doc->logfunc) + doc->logfunc(doc->logdata, CF_LOGLEVEL_ERROR, + "cfFilterPWGToPDF: Compression failed."); + free(compressed_data); + return NULL; + } - pflate.write(page_data->getBuffer(), page_data->getSize()); - pflate.finish(); + pdfio_stream_t *stream = pdfioObjCreateStream(image_obj, PDFIO_FILTER_NONE); + pdfioStreamWrite(stream, compressed_data, compressed_size); + pdfioStreamClose(stream); - ret.replaceStreamData(std::shared_ptr(psink.getBuffer()), - QPDFObjectHandle::newName("/FlateDecode"), - QPDFObjectHandle::newNull()); + free(compressed_data); #else - ret.replaceStreamData(page_data, QPDFObjectHandle::newNull(), - QPDFObjectHandle::newNull()); + pdfio_stream_t *stream = pdfioStreamCreate(pdf, image_obj); + pdfioStreamWrite(stream, page_data, page_data_size); + pdfioStreamClose(stream); #endif - - return (ret); + return image_obj; } - -static int -finish_page(struct pdf_info *info, - pwgtopdf_doc_t *doc) +static int +finish_page(struct pdf_info *info, + pwgtopdf_doc_t *doc) { + pdfio_obj_t *image_obj; + char content[1024]; + size_t content_length = 0; + if (info->outformat == CF_FILTER_OUT_FORMAT_PDF) { - // Finish previous PDF Page - if (!info->page_data.get()) - return (0); - - QPDFObjectHandle image = make_image(info->pdf, info->page_data, - info->width, info->height, - info->render_intent, - info->color_space, info->bpc, doc); - if (!image.isInitialized()) + if (!info->page_data) + return 0; + + image_obj = make_image(info->pdf, info->page_data, strlen(content), info->width, info->height, + info->render_intent, info->color_space, info->bpc, doc); + if (!image_obj) { - if (doc->logfunc) - doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPWGToPDF: Unable to load image data"); - return (1); + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPWGToPDF: Unable to load image data"); + return 1; } - // add it - info->page.getKey("/Resources").getKey("/XObject").replaceKey("/I",image); + pdfio_dict_t *resources = pdfioDictCreate(info->pdf); + pdfioDictSetObj(resources, "XObject", image_obj); } else if (info->outformat == CF_FILTER_OUT_FORMAT_PCLM) { - // Finish previous PCLm page if (info->pclm_num_strips == 0) - return (0); - - for (size_t i = 0; i < info->pclm_strip_data.size(); i ++) - if (!info->pclm_strip_data[i].get()) - return (0); - - std::vector strips = - make_pclm_strips(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, - doc); - for (size_t i = 0; i < info->pclm_num_strips; i ++) - if (!strips[i].isInitialized()) - { - if (doc->logfunc) - doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPWGToPDF: Unable to load strip data"); - return (1); - } + return 0; - // Add it - for (size_t i = 0; i < info->pclm_num_strips; i ++) - info->page.getKey("/Resources").getKey("/XObject") - .replaceKey("/Image" + - int_to_fwstring(i, num_digits(info->pclm_num_strips - 1)), - strips[i]); + for (size_t i = 0; i < info->pclm_num_strips; i++) + { + if (!info->pclm_strip_data[i]) + return 0; + } } - // Draw it - std::string content; if (info->outformat == CF_FILTER_OUT_FORMAT_PDF) { - content.append(QUtil::double_to_string(info->page_width) + " 0 0 " + - QUtil::double_to_string(info->page_height) + " 0 0 cm\n"); - content.append("/I Do\n"); + content_length += snprintf(content + content_length, sizeof(content) - content_length, + "%.2f 0 0 %.2f 0 0 cm\n", info->page_width, info->page_height); + content_length += snprintf(content + content_length, sizeof(content) - content_length, "/I Do\n"); } else if (info->outformat == CF_FILTER_OUT_FORMAT_PCLM) { - std::string res = info->pclm_source_resolution_default; - - // resolution is in dpi, so remove the last three characters from - // resolution string to get resolution integer - unsigned resolution_integer = std::stoi(res.substr(0, res.size() - 3)); - double d = (double)DEFAULT_PDF_UNIT / resolution_integer; - content.append(QUtil::double_to_string(d) + " 0 0 " + - QUtil::double_to_string(d) + " 0 0 cm\n"); - unsigned yAnchor = info->height; - for (unsigned i = 0; i < info->pclm_num_strips; i ++) + double d = DEFAULT_PDF_UNIT / atoi(info->pclm_source_resolution_default); + content_length += snprintf(content + content_length, sizeof(content) - content_length, + "%.2f 0 0 %.2f 0 0 cm\n", d, d); + + for (unsigned i = 0; i < info->pclm_num_strips; i++) { - yAnchor -= info->pclm_strip_height[i]; - content.append("/P <> BDC q\n"); - content.append(QUtil::int_to_string(info->width) + " 0 0 " + - QUtil::int_to_string(info->pclm_strip_height[i]) + - " 0 " + QUtil::int_to_string(yAnchor) + " cm\n"); - content.append("/Image" + - int_to_fwstring(i, - num_digits(info->pclm_num_strips - 1)) + - " Do Q\n"); - } + unsigned yAnchor = info->height - info->pclm_strip_height[i]; + content_length += snprintf(content + content_length, sizeof(content) - content_length, + "/P <> BDC q\n%.2f 0 0 %.2f 0 %u cm\n/Image%d Do Q\n", + (double)info->width, (double)info->pclm_strip_height[i], yAnchor, i); + } } - QPDFObjectHandle page_contents = info->page.getKey("/Contents"); - if (info->outformat == CF_FILTER_OUT_FORMAT_PDF) - page_contents.replaceStreamData(content, QPDFObjectHandle::newNull(), - QPDFObjectHandle::newNull()); - else if (info->outformat == CF_FILTER_OUT_FORMAT_PCLM) - page_contents.getArrayItem(0).replaceStreamData(content, - QPDFObjectHandle::newNull(), - QPDFObjectHandle::newNull()); + pdfio_stream_t *page_content = pdfioObjCreateStream(image_obj, PDFIO_FILTER_NONE); + pdfioStreamWrite(page_content, content, content_length); + pdfioStreamClose(page_content); - // bookkeeping - info->page_data = std::shared_ptr(); - info->pclm_strip_data.clear(); + info->page_data = NULL; + memset(info->pclm_strip_data, 0, sizeof(info->pclm_strip_data)); - return (0); + return 0; } - // // Perform modifications to PDF if color space conversions are needed // -static int -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, - pwgtopdf_doc_t *doc) +int prepare_pdf_page(struct pdf_info *info, + unsigned width, + unsigned height, + unsigned bpl, + unsigned bpp, + unsigned bpc, + const char *render_intent, + cups_cspace_t color_space, + pwgtopdf_doc_t *doc) { #define IMAGE_CMYK_8 (bpp == 32 && bpc == 8) @@ -1142,28 +1164,30 @@ prepare_pdf_page(struct pdf_info *info, info->line_bytes = bpl; info->bpp = bpp; info->bpc = bpc; - info->render_intent = render_intent; + info->render_intent = strdup(render_intent); info->color_space = color_space; + if (info->outformat == CF_FILTER_OUT_FORMAT_PCLM) { info->pclm_num_strips = (height / info->pclm_strip_height_preferred) + (height % info->pclm_strip_height_preferred ? 1 : 0); - info->pclm_strip_height.resize(info->pclm_num_strips); - info->pclm_strip_data.resize(info->pclm_num_strips); - for (size_t i = 0; i < info->pclm_num_strips; i ++) + + info->pclm_strip_height = (unsigned *)malloc(info->pclm_num_strips * sizeof(unsigned)); + info->pclm_strip_data = (unsigned char **)malloc(info->pclm_num_strips * sizeof(unsigned char *)); + + for (size_t i = 0; i < info->pclm_num_strips; i++) { info->pclm_strip_height[i] = - info->pclm_strip_height_preferred < height ? - info->pclm_strip_height_preferred : height; - height -= info->pclm_strip_height[i]; + info->pclm_strip_height_preferred < height ? + info->pclm_strip_height_preferred : height; + height -= info->pclm_strip_height[i]; } } - // Invert grayscale by default if (color_space == CUPS_CSPACE_K) fn = convert_pdf_invert_colors; - + if (doc->colorProfile != NULL) { css = cmsGetColorSpace(doc->colorProfile); @@ -1175,43 +1199,43 @@ prepare_pdf_page(struct pdf_info *info, // Convert PDF to Grayscale when using a gray profile case cmsSigGrayData: if (color_space == CUPS_CSPACE_CMYK) - fn = convert_pdf_cmyk_8_to_white_8; + fn = convert_pdf_cmyk_8_to_white_8; else if (color_space == CUPS_CSPACE_RGB) - fn = convert_pdf_rgb_8_to_white_8; - else + fn = convert_pdf_rgb_8_to_white_8; + else fn = convert_pdf_invert_colors; - info->color_space = CUPS_CSPACE_K; - break; + info->color_space = CUPS_CSPACE_K; + break; + // Convert PDF to RGB when using an RGB profile case cmsSigRgbData: - if (color_space == CUPS_CSPACE_CMYK) - fn = convert_pdf_cmyk_8_to_rgb_8; - else if (color_space == CUPS_CSPACE_K) - fn = convert_pdf_white_8_to_rgb_8; - info->color_space = CUPS_CSPACE_RGB; - break; - // Convert PDF to CMYK when using an RGB profile + if (color_space == CUPS_CSPACE_CMYK) + fn = convert_pdf_cmyk_8_to_rgb_8; + else if (color_space == CUPS_CSPACE_K) + fn = convert_pdf_white_8_to_rgb_8; + info->color_space = CUPS_CSPACE_RGB; + break; + + // Convert PDF to RGB when using an RGB profile case cmsSigCmykData: if (color_space == CUPS_CSPACE_RGB) fn = convert_pdf_rgb_8_to_cmyk_8; - else if (color_space == CUPS_CSPACE_K) + else if (color_space == CUPS_CSPACE_K) fn = convert_pdf_white_8_to_cmyk_8; info->color_space = CUPS_CSPACE_CMYK; break; + default: - if (doc->logfunc) - doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPWGToPDF: Unable to convert PDF from profile."); + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPWGToPDF: Unable to convert PDF from profile."); doc->colorProfile = NULL; error = 1; } } else if (!doc->cm_disabled) { - // Perform conversion of an image color space switch (color_space) { - // Convert image to CMYK case CUPS_CSPACE_CMYK: if (IMAGE_RGB_8) fn = convert_pdf_rgb_8_to_cmyk_8; @@ -1262,7 +1286,6 @@ prepare_pdf_page(struct pdf_info *info, case CUPS_CSPACE_DEVICED: case CUPS_CSPACE_DEVICEE: case CUPS_CSPACE_DEVICEF: - // No conversion for right now fn = convert_pdf_no_conversion; break; default: @@ -1274,132 +1297,97 @@ prepare_pdf_page(struct pdf_info *info, } } - if (!error) - fn(info, doc); + if (!error) + fn(info, doc); - return (error); + return error; } -static int +static int add_pdf_page(struct pdf_info *info, - int pagen, + int pagen, unsigned width, unsigned height, int bpp, int bpc, int bpl, - std::string render_intent, + const char *render_intent, cups_cspace_t color_space, - unsigned xdpi, + unsigned xdpi, unsigned ydpi, pwgtopdf_doc_t *doc) { - try - { - if (finish_page(info, doc)) // any active - return (1); + if (finish_page(info, doc)) + return 1; - prepare_pdf_page(info, width, height, bpl, bpp, - bpc, render_intent, color_space, doc); + prepare_pdf_page(info, width, height, bpl, bpp, bpc, render_intent, color_space, doc); - if (info->height > (std::numeric_limits::max() / - info->line_bytes)) - { - if (doc->logfunc) - doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPWGToPDF: Page too big"); - return (1); - } - if (info->outformat == CF_FILTER_OUT_FORMAT_PDF) - info->page_data = - std::shared_ptr(new Buffer(info->line_bytes * info->height)); - else if (info->outformat == CF_FILTER_OUT_FORMAT_PCLM) - { - // reserve space for PCLm strips - for (size_t i = 0; i < info->pclm_num_strips; i ++) - info->pclm_strip_data[i] = - std::shared_ptr(new Buffer(info->line_bytes * - info->pclm_strip_height[i])); - } + if (info->height > (UINT_MAX / info->line_bytes)) + { + if (doc->logfunc) doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, + "cfFilterPWGToPDF: Page too big"); + return 1; + } - QPDFObjectHandle page = QPDFObjectHandle::parse( - "<<" - " /Type /Page" - " /Resources <<" - " /XObject << >> " - " >>" - " /MediaBox null " - " /Contents null " - ">>"); - - // Convert to pdf units - info->page_width = ((double)info->width / xdpi) * DEFAULT_PDF_UNIT; - info->page_height = ((double)info->height / ydpi) * DEFAULT_PDF_UNIT; - if (info->outformat == CF_FILTER_OUT_FORMAT_PDF) - { - page.replaceKey("/Contents", QPDFObjectHandle::newStream(&info->pdf)); - // data will be provided later - page.replaceKey("/MediaBox", make_real_box(0, 0, info->page_width, - info->page_height)); - } - else if (info->outformat == CF_FILTER_OUT_FORMAT_PCLM) + if (info->outformat == CF_FILTER_OUT_FORMAT_PDF) + info->page_data = malloc(info->line_bytes * info->height); + + else if (info->outformat == CF_FILTER_OUT_FORMAT_PCLM) + { + for (size_t i = 0; i < info->pclm_num_strips; i++) { - page.replaceKey("/Contents", - QPDFObjectHandle::newArray(std::vector - (1, QPDFObjectHandle::newStream - (&info->pdf)))); - - // box with dimensions rounded off to the nearest integer - page.replaceKey("/MediaBox", - make_integer_box(0, 0, info->page_width + 0.5, - info->page_height + 0.5)); + info->pclm_strip_data[i] = malloc(info->line_bytes * info->pclm_strip_height[i]); } - - info->page = info->pdf.makeIndirectObject(page); // we want to keep a - // reference - info->pdf.addPage(info->page, false); } - catch (std::bad_alloc &ex) + + pdfio_dict_t *page_dict = pdfioDictCreate(info->pdf); + + pdfioDictSetName(page_dict, "Type", "Page"); + pdfioDictSetDict(page_dict, "Resources", pdfioDictCreate(info->pdf)); + pdfioDictSetNull(page_dict, "MediaBox"); + pdfioDictSetNull(page_dict, "Contents"); + + + info->page_width = ((double)info->width / xdpi) * DEFAULT_PDF_UNIT; + info->page_height = ((double)info->height / ydpi) * DEFAULT_PDF_UNIT; + + if (info->outformat == CF_FILTER_OUT_FORMAT_PDF) { - if (doc->logfunc) - doc->logfunc(doc->logdata, CF_LOGLEVEL_DEBUG, - "cfFilterPWGToPDF: Unable to allocate page data"); - return (1); + pdfio_obj_t *null_obj = pdfioFileCreateObj(info->pdf, pdfioDictCreate(info->pdf)); + pdfioDictSetObj(page_dict, "Contents", null_obj); + pdfio_rect_t media_rect = make_real_box(0, 0, info->page_width, info->page_height); + pdfioDictSetRect(page_dict, "MediaBox", &media_rect); } - catch (...) + + else if (info->outformat == CF_FILTER_OUT_FORMAT_PCLM) { - return (1); + pdfio_obj_t *null_obj = pdfioFileCreateObj(info->pdf, pdfioDictCreate(info->pdf)); + pdfioDictSetObj(page_dict, "Contents", null_obj); + + pdfio_rect_t media_rect = make_real_box(0, 0, info->page_width + 0.5, info->page_height + 0.5); + pdfioDictSetRect(page_dict, "MediaBox", &media_rect); } - return (0); -} + pdfio_obj_t *page = pdfioFileCreateObj(info->pdf, page_dict); + info->page = page; // we want to keep a + // reference + return 0; +} static int -close_pdf_file(struct pdf_info * info, +close_pdf_file(struct pdf_info *info, pwgtopdf_doc_t *doc) { - try - { - if (finish_page(info, doc)) // any active - return (1); - QPDFWriter output(info->pdf, NULL); - output.setOutputFile("pdf", doc->outputfp, false); - //output.setMinimumPDFVersion("1.4"); - if (info->outformat == CF_FILTER_OUT_FORMAT_PCLM) - output.setPCLm(true); - output.write(); - } - catch (...) - { - return (1); - } + if (finish_page(info, doc)) + return 1; + + pdfioFileClose(info->pdf); - return (0); + return 0; } - static void pdf_set_line(struct pdf_info * info, unsigned line_n, @@ -1420,15 +1408,14 @@ pdf_set_line(struct pdf_info * info, size_t strip_num = line_n / info->pclm_strip_height_preferred; unsigned line_strip = line_n - strip_num * info->pclm_strip_height_preferred; - memcpy(((info->pclm_strip_data[strip_num])->getBuffer() + + memcpy(((info->pclm_strip_data[strip_num]) + (line_strip*info->line_bytes)), line, info->line_bytes); } else - memcpy((info->page_data->getBuffer() + (line_n * info->line_bytes)), + memcpy((info->page_data + (line_n * info->line_bytes)), line, info->line_bytes); } - static int convert_raster(cups_raster_t *ras, unsigned width, @@ -1482,7 +1469,6 @@ convert_raster(cups_raster_t *ras, return (0); } - static int set_profile(const char *path, pwgtopdf_doc_t *doc) @@ -1537,7 +1523,6 @@ cfFilterPWGToPDF(int inputfd, // I - File descriptor input stream char buf[1024]; const char *kw; - (void)inputseekable; if (parameters) @@ -1655,11 +1640,12 @@ cfFilterPWGToPDF(int inputfd, // I - File descriptor input stream if (log) log(ld, CF_LOGLEVEL_DEBUG, "cfFilterPWGToPDF: Printer PCLm attribute \"%s\"", attr_name); - pdf.pclm_strip_height_supported.clear(); // remove default value = 16 + pdf.pclm_strip_height_supported = NULL; // remove default value = 16 for (i = 0; i < ippGetCount(ipp_attr); i ++) - pdf.pclm_strip_height_supported.push_back(ippGetInteger(ipp_attr, i)); + pdf.pclm_strip_height_supported[i] = ippGetInteger(ipp_attr, i); } + attr_name = (char *)"pclm-raster-back-side"; if ((ipp_attr = ippFindAttribute(printer_attrs, attr_name, IPP_TAG_ZERO)) != NULL) @@ -1678,7 +1664,9 @@ cfFilterPWGToPDF(int inputfd, // I - File descriptor input stream if (log) log(ld, CF_LOGLEVEL_DEBUG, "cfFilterPWGToPDF: Printer PCLm attribute \"%s\" with value \"%s\"", attr_name, buf); - pdf.pclm_source_resolution_supported = split_strings(buf, ","); + int size = (int)sizeof(buf); + + pdf.pclm_source_resolution_supported = split_strings(buf, ",", &size); } attr_name = (char *)"pclm-source-resolution-default"; @@ -1691,7 +1679,7 @@ cfFilterPWGToPDF(int inputfd, // I - File descriptor input stream attr_name, buf); pdf.pclm_source_resolution_default = buf; } - else if (pdf.pclm_source_resolution_supported.size() > 0) + else if (sizeof(pdf.pclm_source_resolution_supported) > 0) { pdf.pclm_source_resolution_default = pdf.pclm_source_resolution_supported[0]; @@ -1714,31 +1702,15 @@ cfFilterPWGToPDF(int inputfd, // I - File descriptor input stream if (log) log(ld, CF_LOGLEVEL_DEBUG, "cfFilterPWGToPDF: Printer PCLm attribute \"%s\" with value \"%s\"", attr_name, buf); - std::vector vec = split_strings(buf, ","); - // get all compression methods supported by the printer - for (std::vector::iterator it = vec.begin(); - it != vec.end(); ++it) - { - std::string compression_method = *it; - for (char& x: compression_method) - x = tolower(x); - if (compression_method == "flate") - pdf.pclm_compression_method_preferred.push_back(FLATE_DECODE); - else if (compression_method == "rle") - pdf.pclm_compression_method_preferred.push_back(RLE_DECODE); - else if (compression_method == "jpeg") - pdf.pclm_compression_method_preferred.push_back(DCT_DECODE); - } } // If the compression methods is none of the above or is erreneous // use FLATE as compression method and show a warning. - if (pdf.pclm_compression_method_preferred.empty()) + if (pdf.pclm_compression_method_preferred == NULL) { if (log) log(ld, CF_LOGLEVEL_WARN, "(pwgtopclm) Unable parse Printer attribute \"%s\". " "Using FLATE for encoding image streams.", attr_name); - pdf.pclm_compression_method_preferred.push_back(FLATE_DECODE); } } diff --git a/cupsfilters/test-filter-cases.txt b/cupsfilters/test-filter-cases.txt index ba045aac4..f45a21ab2 100644 --- a/cupsfilters/test-filter-cases.txt +++ b/cupsfilters/test-filter-cases.txt @@ -1,5 +1,6 @@ -# Input_File Input_Type Output_File Output_Type Make Model Color Duplex Formats Job-Id: random number User: randome name Title: randome title Copies: range between 1 to 20 Options -cupsfilters/test_files/test_file_1pg.pdf application/pdf cupsfilters/test_files/output_files/test_file_op.pdf application/pdf Generic PDF Color 2 1 1 text/plain,application/pdf 13 new-user custom-print 10 sides=two-sided-long-edge media-size=A4 printer-resolution=300dpi -cupsfilters/test_files/test_file_4pg.pdf application/pdf cupsfilters/test_files/output_files/test_file_op.pwg image/pwg-raster Generic PDF Color 2 1 1 image/pwg-raster,application/pdf 13 new-user custom-print 5 sides=two-sided-short-edge media-size=A4 printer-resolution=300dpi -cupsfilters/test_files/bashrc.urf image/urf cupsfilters/test_files/output_files/test_file_op.jpg image/jpeg Canon GX7000 series 2 1 0 image/urf,image/jpeg 13 new-user custom-print 1 sides=two-sided-short-edge media-size=A4 printer-resolution=600dpi -cupsfilters/test_files/test_file_2pg.pdf application/pdf cupsfilters/test_files/output_files/test_file_op.jpg image/jpeg Brother MFC-L6900DW 2 1 0 application/pdf,application/vnd.cups-pdf 13 new-user custom-print 1 sides=two-sided-short-edge media-size=A4 printer-resolution=300dpi \ No newline at end of file +# Columns: Input_File Input_Type Output_File Output_Type Make Model Color Duplex Formats Filter_Chain Job-Id User Title Copies Options +cupsfilters/test_files/test_file_1pg.pdf application/pdf cupsfilters/test_files/output_files/test_file_op.pdf application/pdf Generic PDF Color 2 1 text/plain,application/pdf pdftopdf 13 new-user custom-print 10 sides=two-sided-long-edge media-size=A4 printer-resolution=300dpi +cupsfilters/test_files/test_file_4pg.pdf application/pdf cupsfilters/test_files/output_files/test_file_op.pwg image/pwg-raster Generic PDF Color 2 1 image/pwg-raster,application/pdf pdftopdf,pwgtopwg 13 new-user custom-print 5 sides=two-sided-short-edge media-size=A4 printer-resolution=300dpi +cupsfilters/test_files/bashrc.urf image/urf cupsfilters/test_files/output_files/test_file_urfpdf.pdf application/pdf Canon GX7000 series 2 1 image/urf,application/pdf pwgtopdf 13 new-user custom-print 1 sides=two-sided-short-edge media-size=A4 printer-resolution=600dpi +cupsfilters/test_files/test_file_2pg.pdf application/pdf cupsfilters/test_files/output_files/test_file_op.jpg image/jpeg Brother MFC-L6900DW 2 1 application/pdf,image/jpeg pdftopdf 13 new-user custom-print 1 sides=two-sided-short-edge media-size=A4 printer-resolution=300dpi + diff --git a/cupsfilters/testfilters.c b/cupsfilters/testfilters.c index 9685bb803..c6076011c 100644 --- a/cupsfilters/testfilters.c +++ b/cupsfilters/testfilters.c @@ -12,6 +12,8 @@ * 'remove_white_space()' - Remove white spaces from beginning and end of a string */ +typedef int (*cf_filter_func_t)(int input_fd, int output_fd, int input_seekable, cf_filter_data_t *data, void *parameters); + char* remove_white_space( char* str) @@ -31,6 +33,69 @@ remove_white_space( return str; } +typedef struct { + const char *name; + cf_filter_func_t function; // Correct type + void *(*param_generator)(const char *output_mime); +} FilterMapping; + +void +*ghostscript_param_gen(const char *output_mime) +{ + cf_filter_out_format_t *out = malloc(sizeof(cf_filter_out_format_t)); + if (strcasecmp(output_mime, "application/pdf") == 0) { + *out = CF_FILTER_OUT_FORMAT_PDF; + } else if (strcasecmp(output_mime, "image/pwg-raster") == 0) { + *out = CF_FILTER_OUT_FORMAT_PWG_RASTER; + } else if (strcasecmp(output_mime, "image/urf") == 0) { + *out = CF_FILTER_OUT_FORMAT_APPLE_RASTER; + } else if (strcasecmp(output_mime, "application/PCLm") == 0) { + *out = CF_FILTER_OUT_FORMAT_PCLM; + } else { + free(out); + return NULL; + } + return out; +} + +// Define the filter mappings +FilterMapping filter_mappings[] = { + { "imagetoraster", cfFilterImageToRaster, NULL }, + { "ghostscript", cfFilterGhostscript, ghostscript_param_gen }, + { "rastertopwg", cfFilterRasterToPWG, NULL }, + { "pwgtopdf", cfFilterPWGToPDF, NULL }, + { "pdftopdf", cfFilterPDFToPDF, NULL }, + { "texttopdf", cfFilterTextToPDF, NULL }, +}; + +cups_array_t* +parse_filter_chain(const char *filter_chain_str, + const char *output_mime) +{ + cups_array_t *chain = cupsArrayNew(NULL, NULL); + char *saveptr; + char *filter_name = strtok_r((char *)filter_chain_str, ",", &saveptr); + while (filter_name) + { + filter_name = remove_white_space(filter_name); + for (size_t i = 0; i < sizeof(filter_mappings)/sizeof(filter_mappings[0]); i++) + { + if (strcasecmp(filter_name, filter_mappings[i].name) == 0) + { + cf_filter_filter_in_chain_t *filter = malloc(sizeof(cf_filter_filter_in_chain_t)); + filter->function = filter_mappings[i].function; + filter->name = (char *)filter_mappings[i].name; // Explicit cast here + filter->parameters = filter_mappings[i].param_generator ? filter_mappings[i].param_generator(output_mime) : NULL; + cupsArrayAdd(chain, filter); + break; + } + } + + filter_name = strtok_r(NULL, ",", &saveptr); + } + return chain; +} + /* * 'create_media_size_range()' - Create a ranged media-size value. */ @@ -126,6 +191,8 @@ create_media_size(int width, /* I - x-dimension in 2540ths */ * */ + +/* int // O - Exit status test_wrapper( int num_clargs, // I - Number of command-line args @@ -137,6 +204,21 @@ test_wrapper( char* outputMIME, char* inputFile, char* outputFile) +{ +*/ + +int +test_wrapper( + int num_clargs, + char *clargs[], + void *parameters, + int *JobCanceled, + ipp_t* emulated_ipp, + char* inputMIME, + char* outputMIME, + char* inputFile, + char* outputFile, + cups_array_t *filter_chain) { int inputfd; // Print file descriptor int outputfd; // File Descriptor for Output File @@ -261,7 +343,22 @@ test_wrapper( // Fire up the filter function (output to stdout, file descriptor 1) // - retval = cfFilterUniversal(inputfd, outputfd, inputseekable, &filter_data, parameters); +// retval = cfFilterUniversal(inputfd, outputfd, inputseekable, &filter_data, parameters); + + if (filter_chain && cupsArrayCount(filter_chain) > 0) { + retval = cfFilterChain(inputfd, outputfd, inputseekable, &filter_data, filter_chain); + cf_filter_filter_in_chain_t *filter; + while ((filter = cupsArrayFirst(filter_chain)) != NULL) { + free(filter->parameters); + free(filter); + cupsArrayRemove(filter_chain, filter); + } + cupsArrayDelete(filter_chain); + } else { + retval = cfFilterUniversal(inputfd, outputfd, inputseekable, &filter_data, parameters); + } + + return retval; } @@ -1012,6 +1109,10 @@ run_test( char * test_case, char * currentFile) { + + cups_array_t *filter_chain = NULL; + char *filter_chain_str = NULL; + char* make = (char*) malloc(100 * sizeof(char*)); char* model = (char*) malloc(100 * sizeof(char*)); @@ -1121,6 +1222,14 @@ run_test( } continue; } + else if (globalFlag == 9) + { + filter_chain_str = token; + filter_chain = parse_filter_chain(filter_chain_str, outputContentType); + globalFlag++; + } + + clargs = realloc(clargs, (token_index+1)*sizeof(char*)); char* tmp_token = (char*)malloc(100*sizeof(char*)); @@ -1131,7 +1240,14 @@ run_test( } ipp_t* emulated_ipp = load_legacy_attributes(make, model, ppm, ppm_color, duplex, docformats); - return test_wrapper(token_index, clargs, NULL, &jobCanceled, emulated_ipp, inputContentType, outputContentType, inputFileName, outputFileName); +// return test_wrapper(token_index, clargs, NULL, &jobCanceled, emulated_ipp, inputContentType, outputContentType, inputFileName, outputFileName); + + return test_wrapper(token_index, clargs, NULL, &jobCanceled, emulated_ipp, + inputContentType, outputContentType, + inputFileName, // Fixed variable name + outputFileName, // Fixed variable name + filter_chain); + } int main(int argc, // I - Number of command-line args diff --git a/libcupsfilters.pc.in b/libcupsfilters.pc.in index 2eeef63b2..5cacd69c1 100644 --- a/libcupsfilters.pc.in +++ b/libcupsfilters.pc.in @@ -8,5 +8,5 @@ Description: Library providing everything for filtering/converting print/scan jo Version: @VERSION@ Libs: -L${libdir} -lcupsfilters -Libs.private: @CUPS_LIBS@ @LIBJPEG_LIBS@ @LIBPNG_LIBS@ @LIBTIFF_LIBS@ @LIBQPDF_LIBS@ +Libs.private: @CUPS_LIBS@ @LIBJPEG_LIBS@ @LIBPNG_LIBS@ @LIBTIFF_LIBS@ @LIBPDFIO_LIBS@ Cflags: -I${includedir}/cupsfilters -I${includedir}