/**
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* @brief Convert PWG Raster to a PDF/PCLm file
* @file rastertopdf.cpp
* @author Neil 'Superna' Armstrong (C) 2010
* @author Tobias Hoffmann (c) 2012
* @author Till Kamppeter (c) 2014
* @author Sahil Arora (c) 2017
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include // ntohl
#include
#include
#include
#include
#include
#include
#ifdef QPDF_HAVE_PCLM
#include
#include
#endif
#ifdef USE_LCMS1
#include
#define cmsColorSpaceSignature icColorSpaceSignature
#define cmsSetLogErrorHandler cmsSetErrorHandler
#define cmsSigXYZData icSigXYZData
#define cmsSigLuvData icSigLuvData
#define cmsSigLabData icSigLabData
#define cmsSigYCbCrData icSigYCbCrData
#define cmsSigYxyData icSigYxyData
#define cmsSigRgbData icSigRgbData
#define cmsSigHsvData icSigHsvData
#define cmsSigHlsData icSigHlsData
#define cmsSigCmyData icSigCmyData
#define cmsSig3colorData icSig3colorData
#define cmsSigGrayData icSigGrayData
#define cmsSigCmykData icSigCmykData
#define cmsSig4colorData icSig4colorData
#define cmsSig2colorData icSig2colorData
#define cmsSig5colorData icSig5colorData
#define cmsSig6colorData icSig6colorData
#define cmsSig7colorData icSig7colorData
#define cmsSig8colorData icSig8colorData
#define cmsSig9colorData icSig9colorData
#define cmsSig10colorData icSig10colorData
#define cmsSig11colorData icSig11colorData
#define cmsSig12colorData icSig12colorData
#define cmsSig13colorData icSig13colorData
#define cmsSig14colorData icSig14colorData
#define cmsSig15colorData icSig15colorData
#define cmsSaveProfileToMem _cmsSaveProfileToMem
#else
#include
#endif
#define DEFAULT_PDF_UNIT 72 // 1/72 inch
#define PROGRAM "rastertopdf"
#define dprintf(format, ...) fprintf(stderr, "DEBUG2: (" PROGRAM ") " format, __VA_ARGS__)
#define iprintf(format, ...) fprintf(stderr, "INFO: (" PROGRAM ") " format, __VA_ARGS__)
typedef enum {
OUTPUT_FORMAT_PDF,
OUTPUT_FORMAT_PCLM
} OutFormatType;
// Compression method for providing data to PCLm Streams.
typedef enum {
DCT_DECODE = 0,
FLATE_DECODE,
RLE_DECODE
} CompressionMethod;
// Color conversion function
typedef unsigned char *(*convertFunction)(unsigned char *src,
unsigned char *dst, unsigned int pixels);
// Bit conversion function
typedef unsigned char *(*bitFunction)(unsigned char *src,
unsigned char *dst, unsigned int pixels);
// PDF color conversion function
typedef void (*pdfConvertFunction)(struct pdf_info * info);
cmsHPROFILE colorProfile = NULL; // ICC Profile to be applied to PDF
int cm_disabled = 0; // Flag rasied if color management is disabled
cm_calibration_t cm_calibrate; // Status of CUPS color management ("on" or "off")
convertFunction conversion_function; // Raster color conversion function
bitFunction bit_function; // Raster bit function
#ifdef USE_LCMS1
static int lcmsErrorHandler(int ErrorCode, const char *ErrorText)
{
fprintf(stderr, "ERROR: %s\n",ErrorText);
return 1;
}
#else
static void lcmsErrorHandler(cmsContext contextId, cmsUInt32Number ErrorCode,
const char *ErrorText)
{
fprintf(stderr, "ERROR: %s\n",ErrorText);
}
#endif
// Bit conversion functions
unsigned char *invertBits(unsigned char *src, unsigned char *dst, unsigned int pixels)
{
unsigned int i;
// Invert black to grayscale...
for (i = pixels, dst = src; i > 0; i --, dst ++)
*dst = ~*dst;
return dst;
}
unsigned char *noBitConversion(unsigned char *src, unsigned char *dst, unsigned int pixels)
{
return src;
}
// Color conversion functions
unsigned char *rgbToCmyk(unsigned char *src, unsigned char *dst, unsigned int pixels)
{
cupsImageRGBToCMYK(src,dst,pixels);
return dst;
}
unsigned char *whiteToCmyk(unsigned char *src, unsigned char *dst, unsigned int pixels)
{
cupsImageWhiteToCMYK(src,dst,pixels);
return dst;
}
unsigned char *cmykToRgb(unsigned char *src, unsigned char *dst, unsigned int pixels)
{
cupsImageCMYKToRGB(src,dst,pixels);
return dst;
}
unsigned char *whiteToRgb(unsigned char *src, unsigned char *dst, unsigned int pixels)
{
cupsImageWhiteToRGB(src,dst,pixels);
return dst;
}
unsigned char *rgbToWhite(unsigned char *src, unsigned char *dst, unsigned int pixels)
{
cupsImageRGBToWhite(src,dst,pixels);
return dst;
}
unsigned char *cmykToWhite(unsigned char *src, unsigned char *dst, unsigned int pixels)
{
cupsImageCMYKToWhite(src,dst,pixels);
return dst;
}
unsigned char *noColorConversion(unsigned char *src,
unsigned char *dst, unsigned int pixels)
{
return src;
}
/**
* 'split_strings()' - Split a string to a vector of strings given some delimiters
* O - std::vector of std::string after splitting
* I - input string to be split
* I - string containing delimiters
*/
static std::vector
split_strings(std::string const &str, std::string delimiters = ",")
{
std::vector vec(0);
std::string value = "";
bool push_flag = false;
for (size_t i = 0; i < str.size(); i ++)
{
if (push_flag && !(value.empty()))
{
vec.push_back(value);
push_flag = false;
value.clear();
}
if (delimiters.find(str[i]) != std::string::npos)
push_flag = true;
else
value += str[i];
}
if (!value.empty())
vec.push_back(value);
return vec;
}
/**
* 'num_digits()' - Calculates the number of digits in an integer
* O - number of digits in the input integer
* I - the integer whose digits needs to be calculated
*/
int num_digits(int n)
{
if (n == 0) return 1;
int digits = 0;
while (n)
{
++digits;
n /= 10;
}
return digits;
}
/**
* 'int_to_fwstring()' - Convert a number to fixed width string by padding with zeroes
* O - converted string
* I - the integee which needs to be converted to string
* I - width of string required
*/
std::string int_to_fwstring(int n, int width)
{
int num_zeroes = width - num_digits(n);
if (num_zeroes < 0)
num_zeroes = 0;
return std::string(num_zeroes, '0') + QUtil::int_to_string(n);
}
void die(const char * str)
{
fprintf(stderr, "ERROR: (" PROGRAM ") %s\n", str);
exit(1);
}
//------------- 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(OUTPUT_FORMAT_PDF)
{
}
QPDF pdf;
QPDFObjectHandle page;
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< PointerHolder > pclm_strip_data;
std::string render_intent;
cups_cspace_t color_space;
PointerHolder page_data;
double page_width,page_height;
OutFormatType outformat;
};
int create_pdf_file(struct pdf_info * info, const OutFormatType & outformat)
{
try {
info->pdf.emptyPDF();
info->outformat = outformat;
} catch (...) {
return 1;
}
return 0;
}
QPDFObjectHandle makeRealBox(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;
}
QPDFObjectHandle makeIntegerBox(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...
void modify_pdf_color(struct pdf_info * info, int bpp, int bpc, convertFunction fn)
{
unsigned old_bpp = info->bpp;
unsigned old_bpc = info->bpc;
double old_ncolor = old_bpp/old_bpc;
unsigned old_line_bytes = info->line_bytes;
double new_ncolor = (bpp/bpc);
info->line_bytes = (unsigned)old_line_bytes*(new_ncolor/old_ncolor);
info->bpp = bpp;
info->bpc = bpc;
conversion_function = fn;
return;
}
void convertPdf_NoConversion(struct pdf_info * info)
{
conversion_function = noColorConversion;
bit_function = noBitConversion;
}
void convertPdf_Cmyk8ToWhite8(struct pdf_info * info)
{
modify_pdf_color(info, 8, 8, cmykToWhite);
bit_function = noBitConversion;
}
void convertPdf_Rgb8ToWhite8(struct pdf_info * info)
{
modify_pdf_color(info, 8, 8, rgbToWhite);
bit_function = noBitConversion;
}
void convertPdf_Cmyk8ToRgb8(struct pdf_info * info)
{
modify_pdf_color(info, 24, 8, cmykToRgb);
bit_function = noBitConversion;
}
void convertPdf_White8ToRgb8(struct pdf_info * info)
{
modify_pdf_color(info, 24, 8, whiteToRgb);
bit_function = invertBits;
}
void convertPdf_Rgb8ToCmyk8(struct pdf_info * info)
{
modify_pdf_color(info, 32, 8, rgbToCmyk);
bit_function = noBitConversion;
}
void convertPdf_White8ToCmyk8(struct pdf_info * info)
{
modify_pdf_color(info, 32, 8, whiteToCmyk);
bit_function = invertBits;
}
void convertPdf_InvertColors(struct pdf_info * info)
{
conversion_function = noColorConversion;
bit_function = invertBits;
}
#define PRE_COMPRESS
// Create an '/ICCBased' array and embed a previously
// set ICC Profile in the PDF
QPDFObjectHandle embedIccProfile(QPDF &pdf)
{
if (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 = "";
PointerHolderph;
#ifdef USE_LCMS1
size_t profile_size;
#else
unsigned int profile_size;
#endif
cmsColorSpaceSignature css = cmsGetColorSpace(colorProfile);
// Write color component # for /ICCBased array in stream dictionary
switch(css){
case cmsSigGrayData:
n_value = "1";
alternate_cs = "/DeviceGray";
break;
case cmsSigRgbData:
n_value = "3";
alternate_cs = "/DeviceRGB";
break;
case cmsSigCmykData:
n_value = "4";
alternate_cs = "/DeviceCMYK";
break;
default:
fputs("DEBUG: Failed to embed ICC Profile.\n", stderr);
return QPDFObjectHandle::newNull();
}
streamdict["/Alternate"]=QPDFObjectHandle::newName(alternate_cs);
streamdict["/N"]=QPDFObjectHandle::newName(n_value);
// Read profile into memory
cmsSaveProfileToMem(colorProfile, NULL, &profile_size);
unsigned char *buff =
(unsigned char *)calloc(profile_size, sizeof(unsigned char));
cmsSaveProfileToMem(colorProfile, buff, &profile_size);
// Write ICC profile buffer into PDF
ph = new Buffer(buff, profile_size);
iccstream = QPDFObjectHandle::newStream(&pdf, ph);
iccstream.replaceDict(QPDFObjectHandle::newDictionary(streamdict));
array.appendItem(QPDFObjectHandle::newName("/ICCBased"));
array.appendItem(iccstream);
// Return a PDF object reference to an '/ICCBased' array
ret = pdf.makeIndirectObject(array);
free(buff);
fputs("DEBUG: ICC Profile embedded in PDF.\n", stderr);
return ret;
}
QPDFObjectHandle embedSrgbProfile(QPDF &pdf)
{
QPDFObjectHandle iccbased_reference;
// Create an sRGB profile from lcms
colorProfile = cmsCreate_sRGBProfile();
// Embed it into the profile
iccbased_reference = embedIccProfile(pdf);
return iccbased_reference;
}
/*
Calibration function for non-Lab PDF color spaces
Requires white point data, and if available, gamma or matrix numbers.
Output:
[/'color_space'
<< /Gamma ['gamma[0]'...'gamma[n]']
/WhitePoint ['wp[0]' 'wp[1]' 'wp[2]']
/Matrix ['matrix[0]'...'matrix[n*n]']
>>
]
*/
QPDFObjectHandle getCalibrationArray(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';
// Matrix
if (!strcmp("/CalRGB", color_space) && matrix != 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]);
} else
matrix_str[0] = '\0';
// Write array string...
colorSpaceArrayString = "[" + csString + " <<"
+ gamma_str + " " + wp_str + " " + matrix_str + " " + bp_str
+ " >>]";
ret = QPDFObjectHandle::parse(colorSpaceArrayString);
return ret;
}
QPDFObjectHandle getCalRGBArray(double wp[3], double gamma[3], double matrix[9], double bp[3])
{
QPDFObjectHandle ret = getCalibrationArray("/CalRGB", wp, gamma, matrix, bp);
return ret;
}
QPDFObjectHandle getCalGrayArray(double wp[3], double gamma[1], double bp[3])
{
QPDFObjectHandle ret = getCalibrationArray("/CalGray", wp, gamma, 0, bp);
return ret;
}
#ifdef QPDF_HAVE_PCLM
/**
* 'makePclmStrips()' - return an std::vector of QPDFObjectHandle, each containing the
* stream data of the various strips which make up a PCLm page.
* O - std::vector of QPDFObjectHandle
* I - QPDF object
* I - number of strips per page
* I - std::vector of PointerHolder containing data for each strip
* I - strip width
* I - strip height
* I - color space
* I - bits per component
*/
std::vector
makePclmStrips(QPDF &pdf, unsigned num_strips,
std::vector< PointerHolder > &strip_data,
std::vector &compression_methods,
unsigned width, std::vector& strip_height, cups_cspace_t cs, unsigned bpc)
{
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) {
case CUPS_CSPACE_K:
case CUPS_CSPACE_SW:
dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceGray");
color_space = JCS_GRAYSCALE;
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;
default:
fputs("DEBUG: Color space not supported.\n", stderr);
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
// ------------------
CompressionMethod 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 ++)
{
dict["/Height"]=QPDFObjectHandle::newInteger(strip_height[i]);
ret[i].replaceDict(QPDFObjectHandle::newDictionary(dict));
Pl_Buffer psink("psink");
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(PointerHolder(psink.getBuffer()),
QPDFObjectHandle::newName("/FlateDecode"),QPDFObjectHandle::newNull());
}
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(PointerHolder(psink.getBuffer()),
QPDFObjectHandle::newName("/RunLengthDecode"),QPDFObjectHandle::newNull());
}
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(PointerHolder(psink.getBuffer()),
QPDFObjectHandle::newName("/DCTDecode"),QPDFObjectHandle::newNull());
}
}
return ret;
}
#endif
QPDFObjectHandle makeImage(QPDF &pdf, PointerHolder page_data, unsigned width,
unsigned height, std::string render_intent, cups_cspace_t cs, unsigned bpc)
{
QPDFObjectHandle ret = QPDFObjectHandle::newStream(&pdf);
QPDFObjectHandle 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);
if (!cm_disabled) {
// 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") {
/* Enable blackpoint compensation */
dict["/Intent"]=QPDFObjectHandle::newName("/RelativeColorimetric");
use_blackpoint = 1;
}
}
/* Write "/ColorSpace" dictionary based on raster input */
if (colorProfile != NULL && !cm_disabled) {
icc_ref = embedIccProfile(pdf);
if (!icc_ref.isNull())
dict["/ColorSpace"]=icc_ref;
} else if (!cm_disabled) {
switch (cs) {
case CUPS_CSPACE_DEVICE1:
case CUPS_CSPACE_DEVICE2:
case CUPS_CSPACE_DEVICE3:
case CUPS_CSPACE_DEVICE4:
case CUPS_CSPACE_DEVICE5:
case CUPS_CSPACE_DEVICE6:
case CUPS_CSPACE_DEVICE7:
case CUPS_CSPACE_DEVICE8:
case CUPS_CSPACE_DEVICE9:
case CUPS_CSPACE_DEVICEA:
case CUPS_CSPACE_DEVICEB:
case CUPS_CSPACE_DEVICEC:
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;
case CUPS_CSPACE_K:
dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceGray");
break;
case CUPS_CSPACE_SW:
if (use_blackpoint)
dict["/ColorSpace"]=getCalGrayArray(cmWhitePointSGray(), cmGammaSGray(),
cmBlackPointDefault());
else
dict["/ColorSpace"]=getCalGrayArray(cmWhitePointSGray(), cmGammaSGray(), 0);
break;
case CUPS_CSPACE_CMYK:
dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceCMYK");
break;
case CUPS_CSPACE_RGB:
dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceRGB");
break;
case CUPS_CSPACE_SRGB:
icc_ref = embedSrgbProfile(pdf);
if (!icc_ref.isNull())
dict["/ColorSpace"]=icc_ref;
else
dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceRGB");
break;
case CUPS_CSPACE_ADOBERGB:
if (use_blackpoint)
dict["/ColorSpace"]=getCalRGBArray(cmWhitePointAdobeRgb(), cmGammaAdobeRgb(),
cmMatrixAdobeRgb(), cmBlackPointDefault());
else
dict["/ColorSpace"]=getCalRGBArray(cmWhitePointAdobeRgb(),
cmGammaAdobeRgb(), cmMatrixAdobeRgb(), 0);
break;
default:
fputs("DEBUG: Color space not supported.\n", stderr);
return QPDFObjectHandle();
}
} else if (cm_disabled) {
switch(cs) {
case CUPS_CSPACE_K:
case CUPS_CSPACE_SW:
dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceGray");
break;
case CUPS_CSPACE_RGB:
case CUPS_CSPACE_SRGB:
case CUPS_CSPACE_ADOBERGB:
dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceRGB");
break;
case CUPS_CSPACE_DEVICE1:
case CUPS_CSPACE_DEVICE2:
case CUPS_CSPACE_DEVICE3:
case CUPS_CSPACE_DEVICE4:
case CUPS_CSPACE_DEVICE5:
case CUPS_CSPACE_DEVICE6:
case CUPS_CSPACE_DEVICE7:
case CUPS_CSPACE_DEVICE8:
case CUPS_CSPACE_DEVICE9:
case CUPS_CSPACE_DEVICEA:
case CUPS_CSPACE_DEVICEB:
case CUPS_CSPACE_DEVICEC:
case CUPS_CSPACE_DEVICED:
case CUPS_CSPACE_DEVICEE:
case CUPS_CSPACE_DEVICEF:
case CUPS_CSPACE_CMYK:
dict["/ColorSpace"]=QPDFObjectHandle::newName("/DeviceCMYK");
break;
default:
fputs("DEBUG: Color space not supported.\n", stderr);
return QPDFObjectHandle();
}
} else
return QPDFObjectHandle();
ret.replaceDict(QPDFObjectHandle::newDictionary(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);
pflate.write(page_data->getBuffer(),page_data->getSize());
pflate.finish();
ret.replaceStreamData(PointerHolder(psink.getBuffer()),
QPDFObjectHandle::newName("/FlateDecode"),QPDFObjectHandle::newNull());
#else
ret.replaceStreamData(page_data,QPDFObjectHandle::newNull(),QPDFObjectHandle::newNull());
#endif
return ret;
}
void finish_page(struct pdf_info * info)
{
if (info->outformat == OUTPUT_FORMAT_PDF)
{
// Finish previous PDF Page
if(!info->page_data.getPointer())
return;
QPDFObjectHandle image = makeImage(info->pdf, info->page_data, info->width, info->height, info->render_intent, info->color_space, info->bpc);
if(!image.isInitialized()) die("Unable to load image data");
// add it
info->page.getKey("/Resources").getKey("/XObject").replaceKey("/I",image);
}
#ifdef QPDF_HAVE_PCLM
else if (info->outformat == OUTPUT_FORMAT_PCLM)
{
// Finish previous PCLm page
if (info->pclm_num_strips == 0)
return;
for (size_t i = 0; i < info->pclm_strip_data.size(); i ++)
if(!info->pclm_strip_data[i].getPointer())
return;
std::vector strips = makePclmStrips(info->pdf, info->pclm_num_strips, info->pclm_strip_data, info->pclm_compression_method_preferred, info->width, info->pclm_strip_height, info->color_space, info->bpc);
for (size_t i = 0; i < info->pclm_num_strips; i ++)
if(!strips[i].isInitialized()) die("Unable to load strip data");
// 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]);
}
#endif
// draw it
std::string content;
if (info->outformat == OUTPUT_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");
}
#ifdef QPDF_HAVE_PCLM
else if (info->outformat == OUTPUT_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 ++)
{
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");
}
}
#endif
QPDFObjectHandle page_contents = info->page.getKey("/Contents");
if (info->outformat == OUTPUT_FORMAT_PDF)
page_contents.replaceStreamData(content, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
#ifdef QPDF_HAVE_PCLM
else if (info->outformat == OUTPUT_FORMAT_PCLM)
page_contents.getArrayItem(0).replaceStreamData(content, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
#endif
// bookkeeping
info->page_data = PointerHolder();
#ifdef QPDF_HAVE_PCLM
info->pclm_strip_data.clear();
#endif
}
/* Perform modifications to PDF if color space conversions are needed */
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)
{
#define IMAGE_CMYK_8 (bpp == 32 && bpc == 8)
#define IMAGE_CMYK_16 (bpp == 64 && bpc == 16)
#define IMAGE_RGB_8 (bpp == 24 && bpc == 8)
#define IMAGE_RGB_16 (bpp == 48 && bpc == 16)
#define IMAGE_WHITE_1 (bpp == 1 && bpc == 1)
#define IMAGE_WHITE_8 (bpp == 8 && bpc == 8)
#define IMAGE_WHITE_16 (bpp == 16 && bpc == 16)
int error = 0;
pdfConvertFunction fn = convertPdf_NoConversion;
cmsColorSpaceSignature css;
/* Register available raster information into the PDF */
info->width = width;
info->height = height;
info->line_bytes = bpl;
info->bpp = bpp;
info->bpc = bpc;
info->render_intent = render_intent;
info->color_space = color_space;
if (info->outformat == OUTPUT_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[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 = convertPdf_InvertColors;
if (colorProfile != NULL) {
css = cmsGetColorSpace(colorProfile);
// Convert image and PDF color space to an embedded ICC Profile color space
switch(css) {
// Convert PDF to Grayscale when using a gray profile
case cmsSigGrayData:
if (color_space == CUPS_CSPACE_CMYK)
fn = convertPdf_Cmyk8ToWhite8;
else if (color_space == CUPS_CSPACE_RGB)
fn = convertPdf_Rgb8ToWhite8;
else
fn = convertPdf_InvertColors;
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 = convertPdf_Cmyk8ToRgb8;
else if (color_space == CUPS_CSPACE_K)
fn = convertPdf_White8ToRgb8;
info->color_space = CUPS_CSPACE_RGB;
break;
// Convert PDF to CMYK when using an RGB profile
case cmsSigCmykData:
if (color_space == CUPS_CSPACE_RGB)
fn = convertPdf_Rgb8ToCmyk8;
else if (color_space == CUPS_CSPACE_K)
fn = convertPdf_White8ToCmyk8;
info->color_space = CUPS_CSPACE_CMYK;
break;
default:
fputs("DEBUG: Unable to convert PDF from profile.\n", stderr);
colorProfile = NULL;
error = 1;
}
// Perform conversion of an image color space
} else if (!cm_disabled) {
switch (color_space) {
// Convert image to CMYK
case CUPS_CSPACE_CMYK:
if (IMAGE_RGB_8)
fn = convertPdf_Rgb8ToCmyk8;
else if (IMAGE_RGB_16)
fn = convertPdf_NoConversion;
else if (IMAGE_WHITE_8)
fn = convertPdf_White8ToCmyk8;
else if (IMAGE_WHITE_16)
fn = convertPdf_NoConversion;
break;
// Convert image to RGB
case CUPS_CSPACE_ADOBERGB:
case CUPS_CSPACE_RGB:
case CUPS_CSPACE_SRGB:
if (IMAGE_CMYK_8)
fn = convertPdf_Cmyk8ToRgb8;
else if (IMAGE_CMYK_16)
fn = convertPdf_NoConversion;
else if (IMAGE_WHITE_8)
fn = convertPdf_White8ToRgb8;
else if (IMAGE_WHITE_16)
fn = convertPdf_NoConversion;
break;
// Convert image to Grayscale
case CUPS_CSPACE_SW:
case CUPS_CSPACE_K:
if (IMAGE_CMYK_8)
fn = convertPdf_Cmyk8ToWhite8;
else if (IMAGE_CMYK_16)
fn = convertPdf_NoConversion;
else if (IMAGE_RGB_8)
fn = convertPdf_Rgb8ToWhite8;
else if (IMAGE_RGB_16)
fn = convertPdf_NoConversion;
break;
case CUPS_CSPACE_DEVICE1:
case CUPS_CSPACE_DEVICE2:
case CUPS_CSPACE_DEVICE3:
case CUPS_CSPACE_DEVICE4:
case CUPS_CSPACE_DEVICE5:
case CUPS_CSPACE_DEVICE6:
case CUPS_CSPACE_DEVICE7:
case CUPS_CSPACE_DEVICE8:
case CUPS_CSPACE_DEVICE9:
case CUPS_CSPACE_DEVICEA:
case CUPS_CSPACE_DEVICEB:
case CUPS_CSPACE_DEVICEC:
case CUPS_CSPACE_DEVICED:
case CUPS_CSPACE_DEVICEE:
case CUPS_CSPACE_DEVICEF:
// No conversion for right now
fn = convertPdf_NoConversion;
break;
default:
fputs("DEBUG: Color space not supported.\n", stderr);
error = 1;
break;
}
}
if (!error)
fn(info);
return error;
}
int add_pdf_page(struct pdf_info * info, int pagen, unsigned width,
unsigned height, int bpp, int bpc, int bpl, std::string render_intent,
cups_cspace_t color_space, unsigned xdpi, unsigned ydpi)
{
try {
finish_page(info); // any active
prepare_pdf_page(info, width, height, bpl, bpp,
bpc, render_intent, color_space);
if (info->height > (std::numeric_limits::max() / info->line_bytes)) {
die("Page too big");
}
if (info->outformat == OUTPUT_FORMAT_PDF)
info->page_data = PointerHolder(new Buffer(info->line_bytes*info->height));
else if (info->outformat == OUTPUT_FORMAT_PCLM)
{
// reserve space for PCLm strips
for (size_t i = 0; i < info->pclm_num_strips; i ++)
info->pclm_strip_data[i] = PointerHolder(new Buffer(info->line_bytes*info->pclm_strip_height[i]));
}
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 == OUTPUT_FORMAT_PDF)
{
page.replaceKey("/Contents",QPDFObjectHandle::newStream(&info->pdf)); // data will be provided later
page.replaceKey("/MediaBox",makeRealBox(0,0,info->page_width,info->page_height));
}
else if (info->outformat == OUTPUT_FORMAT_PCLM)
{
page.replaceKey("/Contents",
QPDFObjectHandle::newArray(std::vector(1, QPDFObjectHandle::newStream(&info->pdf))));
// box with dimensions rounded off to the nearest integer
page.replaceKey("/MediaBox",makeIntegerBox(0,0,info->page_width + 0.5,info->page_height + 0.5));
}
info->page = info->pdf.makeIndirectObject(page); // we want to keep a reference
info->pdf.addPage(info->page, false);
} catch (std::bad_alloc &ex) {
die("Unable to allocate page data");
} catch (...) {
return 1;
}
return 0;
}
int close_pdf_file(struct pdf_info * info)
{
try {
finish_page(info); // any active
QPDFWriter output(info->pdf,NULL);
// output.setMinimumPDFVersion("1.4");
#ifdef QPDF_HAVE_PCLM
if (info->outformat == OUTPUT_FORMAT_PCLM)
output.setPCLm(true);
#endif
output.write();
} catch (...) {
return 1;
}
return 0;
}
void pdf_set_line(struct pdf_info * info, unsigned line_n, unsigned char *line)
{
//dprintf("pdf_set_line(%d)\n", line_n);
if(line_n > info->height)
{
dprintf("Bad line %d\n", line_n);
return;
}
switch(info->outformat)
{
case OUTPUT_FORMAT_PDF:
memcpy((info->page_data->getBuffer()+(line_n*info->line_bytes)), line, info->line_bytes);
break;
case OUTPUT_FORMAT_PCLM:
// copy line data into appropriate pclm strip
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() + (line_strip*info->line_bytes)),
line, info->line_bytes);
break;
}
}
int convert_raster(cups_raster_t *ras, unsigned width, unsigned height,
int bpp, int bpl, struct pdf_info * info)
{
// We should be at raster start
int i;
unsigned cur_line = 0;
unsigned char *PixelBuffer, *ptr = NULL, *buff;
PixelBuffer = (unsigned char *)malloc(bpl);
buff = (unsigned char *)malloc(info->line_bytes);
do
{
// Read raster data...
cupsRasterReadPixels(ras, PixelBuffer, bpl);
#if !ARCH_IS_BIG_ENDIAN
if (info->bpc == 16)
{
// Swap byte pairs for endianess (cupsRasterReadPixels() switches
// from Big Endian back to the system's Endian)
for (i = bpl, ptr = PixelBuffer; i > 0; i -= 2, ptr += 2)
{
unsigned char swap = *ptr;
*ptr = *(ptr + 1);
*(ptr + 1) = swap;
}
}
#endif /* !ARCH_IS_BIG_ENDIAN */
// perform bit operations if necessary
bit_function(PixelBuffer, ptr, bpl);
// write lines and color convert when necessary
pdf_set_line(info, cur_line, conversion_function(PixelBuffer, buff, width));
++cur_line;
}
while(cur_line < height);
free(buff);
free(PixelBuffer);
return 0;
}
int setProfile(const char * path)
{
if (path != NULL)
colorProfile = cmsOpenProfileFromFile(path,"r");
if (colorProfile != NULL) {
fputs("DEBUG: Load profile successful.\n", stderr);
return 0;
}
else {
fputs("DEBUG: Unable to load profile.\n", stderr);
return 1;
}
}
/* Obtain a source profile name using color qualifiers from raster file */
const char * getIPPColorProfileName(const char * media_type, cups_cspace_t cs, unsigned dpi)
{
std::string mediaType = "";
std::string resolution = "";
std::string colorModel = "";
std::string iccProfile = "";
// ColorModel
switch (cs) {
case CUPS_CSPACE_RGB:
colorModel = "rgb";
break;
case CUPS_CSPACE_SRGB:
colorModel = "srgb";
break;
case CUPS_CSPACE_ADOBERGB:
colorModel = "adobergb";
break;
case CUPS_CSPACE_K:
colorModel = "gray";
break;
case CUPS_CSPACE_CMYK:
colorModel = "cmyk";
break;
default:
colorModel = "";
break;
}
if (media_type != NULL)
mediaType = media_type;
if (dpi > 0)
resolution = dpi;
// Requires color space and media type qualifiers
if (resolution != "" || colorModel != "")
return 0;
// profile-uri reference: "http://www.server.com/colorModel-Resolution-mediaType.icc
if (mediaType != "")
iccProfile = colorModel + "-" + resolution + ".icc";
else
iccProfile = colorModel + "-" + resolution + "-" + mediaType + ".icc";
return strdup(iccProfile.c_str());
}
int main(int argc, char **argv)
{
char *outformat_env = NULL;
OutFormatType outformat; /* Output format */
int fd, Page, empty = 1;
struct pdf_info pdf;
FILE * input = NULL;
cups_raster_t *ras; /* Raster stream for printing */
cups_page_header2_t header; /* Page header from file */
ppd_file_t *ppd; /* PPD file */
ppd_attr_t *attr; /* PPD attribute */
int num_options; /* Number of options */
const char* profile_name; /* IPP Profile Name */
cups_option_t *options; /* Options */
// Make sure status messages are not buffered...
setbuf(stderr, NULL);
cmsSetLogErrorHandler(lcmsErrorHandler);
if (argc < 6 || argc > 7)
{
fprintf(stderr, "Usage: %s