blob: 05ceaa3bccb485e04510cd1aa2f222e4d9935cf1 [file] [log] [blame]
//========================================================================
//
// pdftocairo.cc
//
// Copyright 2003 Glyph & Cog, LLC
//
//========================================================================
//========================================================================
//
// Modified under the Poppler project - http://poppler.freedesktop.org
//
// All changes made under the Poppler project to this file are licensed
// under GPL version 2 or later
//
// Copyright (C) 2007 Ilmari Heikkinen <ilmari.heikkinen@gmail.com>
// Copyright (C) 2008 Richard Airlie <richard.airlie@maglabs.net>
// Copyright (C) 2009 Michael K. Johnson <a1237@danlj.org>
// Copyright (C) 2009 Shen Liang <shenzhuxi@gmail.com>
// Copyright (C) 2009 Stefan Thomas <thomas@eload24.com>
// Copyright (C) 2009, 2010, 2017, 2018 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2010, 2011-2017 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2010, 2014 Hib Eris <hib@hiberis.nl>
// Copyright (C) 2010 Jonathan Liu <net147@gmail.com>
// Copyright (C) 2010 William Bader <williambader@hotmail.com>
// Copyright (C) 2011 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2011, 2015 Carlos Garcia Campos <carlosgc@gnome.org>
// Copyright (C) 2012 Koji Otani <sho@bbr.jp>
// Copyright (C) 2013 Lu Wang <coolwanglu@gmail.com>
// Copyright (C) 2013, 2017 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
// Copyright (C) 2014 Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>
// Copyright (C) 2016 Jason Crain <jason@aquaticape.us>
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================
#include "config.h"
#include <poppler-config.h>
#include <cstdint>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include "parseargs.h"
#include "goo/gmem.h"
#include "goo/gtypes.h"
#include "goo/GooString.h"
#include "goo/ImgWriter.h"
#include "goo/JpegWriter.h"
#include "goo/PNGWriter.h"
#include "goo/TiffWriter.h"
#include "GlobalParams.h"
#include "Object.h"
#include "PDFDoc.h"
#include "PDFDocFactory.h"
#include "CairoOutputDev.h"
#include "Win32Console.h"
#include "numberofcharacters.h"
#ifdef USE_CMS
#include <lcms2.h>
#endif
#include <cairo.h>
#ifdef CAIRO_HAS_PS_SURFACE
#include <cairo-ps.h>
#endif
#ifdef CAIRO_HAS_PDF_SURFACE
#include <cairo-pdf.h>
#endif
#ifdef CAIRO_HAS_SVG_SURFACE
#include <cairo-svg.h>
#endif
#include "pdftocairo-win32.h"
static GBool png = gFalse;
static GBool jpeg = gFalse;
static GBool ps = gFalse;
static GBool eps = gFalse;
static GBool pdf = gFalse;
static GBool printToWin32 = gFalse;
static GBool printdlg = gFalse;
static GBool svg = gFalse;
static GBool tiff = gFalse;
static int firstPage = 1;
static int lastPage = 0;
static GBool printOnlyOdd = gFalse;
static GBool printOnlyEven = gFalse;
static GBool singleFile = gFalse;
static double resolution = 0.0;
static double x_resolution = 150.0;
static double y_resolution = 150.0;
static int scaleTo = 0;
static int x_scaleTo = 0;
static int y_scaleTo = 0;
static int crop_x = 0;
static int crop_y = 0;
static int crop_w = 0;
static int crop_h = 0;
static int sz = 0;
static GBool useCropBox = gFalse;
static GBool mono = gFalse;
static GBool gray = gFalse;
static GBool transp = gFalse;
static GooString antialias;
static GooString icc;
static GBool level2 = gFalse;
static GBool level3 = gFalse;
static GBool origPageSizes = gFalse;
static char paperSize[15] = "";
static int paperWidth = -1;
static int paperHeight = -1;
static GBool noCrop = gFalse;
static GBool expand = gFalse;
static GBool noShrink = gFalse;
static GBool noCenter = gFalse;
static GBool duplex = gFalse;
static char tiffCompressionStr[16] = "";
static char ownerPassword[33] = "";
static char userPassword[33] = "";
static GBool quiet = gFalse;
static GBool printVersion = gFalse;
static GBool printHelp = gFalse;
static GooString jpegOpt;
static int jpegQuality = -1;
static bool jpegProgressive = false;
static GooString printer;
static GooString printOpt;
#ifdef CAIRO_HAS_WIN32_SURFACE
static GBool setupdlg = gFalse;
#endif
static const ArgDesc argDesc[] = {
#ifdef ENABLE_LIBPNG
{"-png", argFlag, &png, 0,
"generate a PNG file"},
#endif
#ifdef ENABLE_LIBJPEG
{"-jpeg", argFlag, &jpeg, 0,
"generate a JPEG file"},
{"-jpegopt", argGooString, &jpegOpt, 0,
"jpeg options, with format <opt1>=<val1>[,<optN>=<valN>]*"},
#endif
#ifdef ENABLE_LIBTIFF
{"-tiff", argFlag, &tiff, 0,
"generate a TIFF file"},
{"-tiffcompression", argString, tiffCompressionStr, sizeof(tiffCompressionStr),
"set TIFF compression: none, packbits, jpeg, lzw, deflate"},
#endif
#ifdef CAIRO_HAS_PS_SURFACE
{"-ps", argFlag, &ps, 0,
"generate PostScript file"},
{"-eps", argFlag, &eps, 0,
"generate Encapsulated PostScript (EPS)"},
#endif
#ifdef CAIRO_HAS_PDF_SURFACE
{"-pdf", argFlag, &pdf, 0,
"generate a PDF file"},
#endif
#ifdef CAIRO_HAS_SVG_SURFACE
{"-svg", argFlag, &svg, 0,
"generate a Scalable Vector Graphics (SVG) file"},
#endif
#ifdef CAIRO_HAS_WIN32_SURFACE
{"-print", argFlag, &printToWin32, 0,
"print to a Windows printer"},
{"-printdlg", argFlag, &printdlg, 0,
"show Windows print dialog and print"},
{"-printer", argGooString, &printer, 0,
"printer name or use default if this option is not specified"},
{"-printopt", argGooString, &printOpt, 0,
"printer options, with format <opt1>=<val1>[,<optN>=<valN>]*"},
{"-setupdlg", argFlag, &setupdlg, 0,
"show printer setup dialog before printing"},
#endif
{"-f", argInt, &firstPage, 0,
"first page to print"},
{"-l", argInt, &lastPage, 0,
"last page to print"},
{"-o", argFlag, &printOnlyOdd, 0,
"print only odd pages"},
{"-e", argFlag, &printOnlyEven, 0,
"print only even pages"},
{"-singlefile", argFlag, &singleFile, 0,
"write only the first page and do not add digits"},
{"-r", argFP, &resolution, 0,
"resolution, in PPI (default is 150)"},
{"-rx", argFP, &x_resolution, 0,
"X resolution, in PPI (default is 150)"},
{"-ry", argFP, &y_resolution, 0,
"Y resolution, in PPI (default is 150)"},
{"-scale-to", argInt, &scaleTo, 0,
"scales each page to fit within scale-to*scale-to pixel box"},
{"-scale-to-x", argInt, &x_scaleTo, 0,
"scales each page horizontally to fit in scale-to-x pixels"},
{"-scale-to-y", argInt, &y_scaleTo, 0,
"scales each page vertically to fit in scale-to-y pixels"},
{"-x", argInt, &crop_x, 0,
"x-coordinate of the crop area top left corner"},
{"-y", argInt, &crop_y, 0,
"y-coordinate of the crop area top left corner"},
{"-W", argInt, &crop_w, 0,
"width of crop area in pixels (default is 0)"},
{"-H", argInt, &crop_h, 0,
"height of crop area in pixels (default is 0)"},
{"-sz", argInt, &sz, 0,
"size of crop square in pixels (sets W and H)"},
{"-cropbox",argFlag, &useCropBox, 0,
"use the crop box rather than media box"},
{"-mono", argFlag, &mono, 0,
"generate a monochrome image file (PNG, JPEG)"},
{"-gray", argFlag, &gray, 0,
"generate a grayscale image file (PNG, JPEG)"},
{"-transp", argFlag, &transp, 0,
"use a transparent background instead of white (PNG)"},
{"-antialias", argGooString, &antialias, 0,
"set cairo antialias option"},
#ifdef USE_CMS
{"-icc", argGooString, &icc, 0,
"ICC color profile to use"},
#endif
{"-level2", argFlag, &level2, 0,
"generate Level 2 PostScript (PS, EPS)"},
{"-level3", argFlag, &level3, 0,
"generate Level 3 PostScript (PS, EPS)"},
{"-origpagesizes",argFlag, &origPageSizes,0,
"conserve original page sizes (PS, PDF, SVG)"},
{"-paper", argString, paperSize, sizeof(paperSize),
"paper size (letter, legal, A4, A3, match)"},
{"-paperw", argInt, &paperWidth, 0,
"paper width, in points"},
{"-paperh", argInt, &paperHeight, 0,
"paper height, in points"},
{"-nocrop", argFlag, &noCrop, 0,
"don't crop pages to CropBox"},
{"-expand", argFlag, &expand, 0,
"expand pages smaller than the paper size"},
{"-noshrink", argFlag, &noShrink, 0,
"don't shrink pages larger than the paper size"},
{"-nocenter", argFlag, &noCenter, 0,
"don't center pages smaller than the paper size"},
{"-duplex", argFlag, &duplex, 0,
"enable duplex printing"},
{"-opw", argString, ownerPassword, sizeof(ownerPassword),
"owner password (for encrypted files)"},
{"-upw", argString, userPassword, sizeof(userPassword),
"user password (for encrypted files)"},
{"-q", argFlag, &quiet, 0,
"don't print any messages or errors"},
{"-v", argFlag, &printVersion, 0,
"print copyright and version info"},
{"-h", argFlag, &printHelp, 0,
"print usage information"},
{"-help", argFlag, &printHelp, 0,
"print usage information"},
{"--help", argFlag, &printHelp, 0,
"print usage information"},
{"-?", argFlag, &printHelp, 0,
"print usage information"},
{}
};
static cairo_surface_t *surface;
static GBool printing;
static FILE *output_file;
static GBool usePDFPageSize;
static cairo_antialias_t antialiasEnum = CAIRO_ANTIALIAS_DEFAULT;
#ifdef USE_CMS
static unsigned char *icc_data;
static int icc_data_size;
static cmsHPROFILE profile;
#endif
struct AntiliasOption
{
const char *name;
cairo_antialias_t value;
};
static const AntiliasOption antialiasOptions[] =
{
{ "default", CAIRO_ANTIALIAS_DEFAULT },
{ "none", CAIRO_ANTIALIAS_NONE },
{ "gray", CAIRO_ANTIALIAS_GRAY },
{ "subpixel", CAIRO_ANTIALIAS_SUBPIXEL },
{ "fast", CAIRO_ANTIALIAS_FAST },
{ "good", CAIRO_ANTIALIAS_GOOD },
{ "best", CAIRO_ANTIALIAS_BEST },
{ nullptr, CAIRO_ANTIALIAS_DEFAULT },
};
static GBool parseAntialiasOption()
{
const AntiliasOption *option = antialiasOptions;
while (option->name) {
if (antialias.cmp(option->name) == 0) {
antialiasEnum = option->value;
return gTrue;
}
option++;
}
fprintf(stderr, "Error: Invalid antialias option \"%s\"\n", antialias.getCString());
fprintf(stderr, "Valid options are:\n");
option = antialiasOptions;
while (option->name) {
fprintf(stderr, " %s\n", option->name);
option++;
}
return gFalse;
}
static GBool parseJpegOptions()
{
//jpegOpt format is: <opt1>=<val1>,<opt2>=<val2>,...
const char *nextOpt = jpegOpt.getCString();
while (nextOpt && *nextOpt)
{
const char *comma = strchr(nextOpt, ',');
GooString opt;
if (comma) {
opt.Set(nextOpt, comma - nextOpt);
nextOpt = comma + 1;
} else {
opt.Set(nextOpt);
nextOpt = nullptr;
}
//here opt is "<optN>=<valN> "
const char *equal = strchr(opt.getCString(), '=');
if (!equal) {
fprintf(stderr, "Unknown jpeg option \"%s\"\n", opt.getCString());
return gFalse;
}
int iequal = equal - opt.getCString();
GooString value(&opt, iequal + 1, opt.getLength() - iequal - 1);
opt.del(iequal, opt.getLength() - iequal);
//here opt is "<optN>" and value is "<valN>"
if (opt.cmp("quality") == 0) {
if (!isInt(value.getCString())) {
fprintf(stderr, "Invalid jpeg quality\n");
return gFalse;
}
jpegQuality = atoi(value.getCString());
if (jpegQuality < 0 || jpegQuality > 100) {
fprintf(stderr, "jpeg quality must be between 0 and 100\n");
return gFalse;
}
} else if (opt.cmp("progressive") == 0) {
jpegProgressive = gFalse;
if (value.cmp("y") == 0) {
jpegProgressive = gTrue;
} else if (value.cmp("n") != 0) {
fprintf(stderr, "jpeg progressive option must be \"y\" or \"n\"\n");
return gFalse;
}
} else {
fprintf(stderr, "Unknown jpeg option \"%s\"\n", opt.getCString());
return gFalse;
}
}
return gTrue;
}
static void writePageImage(GooString *filename)
{
ImgWriter *writer = nullptr;
FILE *file;
int height, width, stride;
unsigned char *data;
if (png) {
#ifdef ENABLE_LIBPNG
if (transp)
writer = new PNGWriter(PNGWriter::RGBA);
else if (gray)
writer = new PNGWriter(PNGWriter::GRAY);
else if (mono)
writer = new PNGWriter(PNGWriter::MONOCHROME);
else
writer = new PNGWriter(PNGWriter::RGB);
#ifdef USE_CMS
if (icc_data) {
cmsUInt8Number profileID[17];
profileID[16] = '\0';
cmsGetHeaderProfileID(profile,profileID);
static_cast<PNGWriter*>(writer)->setICCProfile(reinterpret_cast<char *>(profileID), icc_data, icc_data_size);
} else {
static_cast<PNGWriter*>(writer)->setSRGBProfile();
}
#endif
#endif
} else if (jpeg) {
#ifdef ENABLE_LIBJPEG
if (gray)
writer = new JpegWriter(JpegWriter::GRAY);
else
writer = new JpegWriter(JpegWriter::RGB);
static_cast<JpegWriter*>(writer)->setProgressive(jpegProgressive);
if (jpegQuality >= 0)
static_cast<JpegWriter*>(writer)->setQuality(jpegQuality);
#endif
} else if (tiff) {
#ifdef ENABLE_LIBTIFF
if (transp)
writer = new TiffWriter(TiffWriter::RGBA_PREMULTIPLIED);
else if (gray)
writer = new TiffWriter(TiffWriter::GRAY);
else if (mono)
writer = new TiffWriter(TiffWriter::MONOCHROME);
else
writer = new TiffWriter(TiffWriter::RGB);
static_cast<TiffWriter*>(writer)->setCompressionString(tiffCompressionStr);
#endif
}
if (!writer)
return;
if (filename->cmp("fd://0") == 0)
file = stdout;
else
file = fopen(filename->getCString(), "wb");
if (!file) {
fprintf(stderr, "Error opening output file %s\n", filename->getCString());
exit(2);
}
height = cairo_image_surface_get_height(surface);
width = cairo_image_surface_get_width(surface);
stride = cairo_image_surface_get_stride(surface);
cairo_surface_flush(surface);
data = cairo_image_surface_get_data(surface);
if (!writer->init(file, width, height, x_resolution, y_resolution)) {
fprintf(stderr, "Error writing %s\n", filename->getCString());
exit(2);
}
unsigned char *row = (unsigned char *) gmallocn(width, 4);
for (int y = 0; y < height; y++ ) {
uint32_t *pixel = (uint32_t *) (data + y*stride);
unsigned char *rowp = row;
int bit = 7;
for (int x = 0; x < width; x++, pixel++) {
if (transp) {
if (tiff) {
// RGBA premultipled format
*rowp++ = (*pixel & 0xff0000) >> 16;
*rowp++ = (*pixel & 0x00ff00) >> 8;
*rowp++ = (*pixel & 0x0000ff) >> 0;
*rowp++ = (*pixel & 0xff000000) >> 24;
} else {
// unpremultiply into RGBA format
uint8_t a;
a = (*pixel & 0xff000000) >> 24;
if (a == 0) {
*rowp++ = 0;
*rowp++ = 0;
*rowp++ = 0;
} else {
*rowp++ = (((*pixel & 0xff0000) >> 16) * 255 + a / 2) / a;
*rowp++ = (((*pixel & 0x00ff00) >> 8) * 255 + a / 2) / a;
*rowp++ = (((*pixel & 0x0000ff) >> 0) * 255 + a / 2) / a;
}
*rowp++ = a;
}
} else if (gray || mono) {
// convert to gray
// The PDF Reference specifies the DeviceRGB to DeviceGray conversion as
// gray = 0.3*red + 0.59*green + 0.11*blue
const int r = (*pixel & 0x00ff0000) >> 16;
const int g = (*pixel & 0x0000ff00) >> 8;
const int b = (*pixel & 0x000000ff) >> 0;
// an arbitrary integer approximation of .3*r + .59*g + .11*b
const int grayValue = (r*19661+g*38666+b*7209 + 32829)>>16;
if (mono) {
if (bit == 7)
*rowp = 0;
if (grayValue > 127)
*rowp |= (1 << bit);
bit--;
if (bit < 0) {
bit = 7;
rowp++;
}
} else {
*rowp++ = grayValue;
}
} else {
// copy into RGB format
*rowp++ = (*pixel & 0x00ff0000) >> 16;
*rowp++ = (*pixel & 0x0000ff00) >> 8;
*rowp++ = (*pixel & 0x000000ff) >> 0;
}
}
writer->writeRow(&row);
}
gfree(row);
writer->close();
delete writer;
if (file == stdout) fflush(file);
else fclose(file);
}
static void getCropSize(double page_w, double page_h, double *width, double *height)
{
int w = crop_w;
int h = crop_h;
if (w == 0)
w = (int)ceil(page_w);
if (h == 0)
h = (int)ceil(page_h);
*width = (crop_x + w > page_w ? (int)ceil(page_w - crop_x) : w);
*height = (crop_y + h > page_h ? (int)ceil(page_h - crop_y) : h);
}
static void getOutputSize(double page_w, double page_h, double *width, double *height)
{
if (printing) {
if (usePDFPageSize) {
*width = page_w;
*height = page_h;
} else {
if (page_w > page_h) {
*width = paperHeight;
*height = paperWidth;
} else {
*width = paperWidth;
*height = paperHeight;
}
}
} else {
getCropSize(page_w * (x_resolution / 72.0),
page_h * (y_resolution / 72.0),
width, height);
}
}
static void getFitToPageTransform(double page_w, double page_h,
double paper_w, double paper_h,
cairo_matrix_t *m)
{
double x_scale, y_scale, scale;
x_scale = paper_w / page_w;
y_scale = paper_h / page_h;
if (x_scale < y_scale)
scale = x_scale;
else
scale = y_scale;
if (scale > 1.0 && !expand)
scale = 1.0;
if (scale < 1.0 && noShrink)
scale = 1.0;
cairo_matrix_init_identity (m);
if (!noCenter) {
// centre page
cairo_matrix_translate (m, (paper_w - page_w*scale)/2, (paper_h - page_h*scale)/2);
} else if (!svg) {
// move to PostScript origin
cairo_matrix_translate (m, 0, (paper_h - page_h*scale));
}
cairo_matrix_scale (m, scale, scale);
}
static cairo_status_t writeStream(void *closure, const unsigned char *data, unsigned int length)
{
FILE *file = (FILE *)closure;
if (fwrite(data, length, 1, file) == 1)
return CAIRO_STATUS_SUCCESS;
else
return CAIRO_STATUS_WRITE_ERROR;
}
static void beginDocument(GooString *inputFileName, GooString *outputFileName, double w, double h)
{
if (printing) {
if (printToWin32) {
output_file = nullptr;
} else {
if (outputFileName->cmp("fd://0") == 0)
output_file = stdout;
else
{
output_file = fopen(outputFileName->getCString(), "wb");
if (!output_file) {
fprintf(stderr, "Error opening output file %s\n", outputFileName->getCString());
exit(2);
}
}
}
if (ps || eps) {
#ifdef CAIRO_HAS_PS_SURFACE
surface = cairo_ps_surface_create_for_stream(writeStream, output_file, w, h);
if (level2)
cairo_ps_surface_restrict_to_level (surface, CAIRO_PS_LEVEL_2);
if (eps)
cairo_ps_surface_set_eps (surface, 1);
if (duplex) {
cairo_ps_surface_dsc_comment(surface, "%%Requirements: duplex");
cairo_ps_surface_dsc_begin_setup(surface);
cairo_ps_surface_dsc_comment(surface, "%%IncludeFeature: *Duplex DuplexNoTumble");
}
cairo_ps_surface_dsc_begin_page_setup (surface);
#endif
} else if (pdf) {
#ifdef CAIRO_HAS_PDF_SURFACE
surface = cairo_pdf_surface_create_for_stream(writeStream, output_file, w, h);
#endif
} else if (svg) {
#ifdef CAIRO_HAS_SVG_SURFACE
surface = cairo_svg_surface_create_for_stream(writeStream, output_file, w, h);
cairo_svg_surface_restrict_to_version (surface, CAIRO_SVG_VERSION_1_2);
#endif
}
#ifdef CAIRO_HAS_WIN32_SURFACE
if (printToWin32)
surface = win32BeginDocument(inputFileName, outputFileName);
#endif
}
}
static void beginPage(double *w, double *h)
{
if (printing) {
if (ps || eps) {
#ifdef CAIRO_HAS_PS_SURFACE
if (*w > *h) {
cairo_ps_surface_dsc_comment (surface, "%%PageOrientation: Landscape");
cairo_ps_surface_set_size (surface, *h, *w);
} else {
cairo_ps_surface_dsc_comment (surface, "%%PageOrientation: Portrait");
cairo_ps_surface_set_size (surface, *w, *h);
}
#endif
}
#ifdef CAIRO_HAS_PDF_SURFACE
if (pdf)
cairo_pdf_surface_set_size (surface, *w, *h);
#endif
#ifdef CAIRO_HAS_WIN32_SURFACE
if (printToWin32) {
GBool changePageSize = gTrue;
if (setupdlg && !origPageSizes)
changePageSize = gFalse;
win32BeginPage(w, h, changePageSize, noShrink); // w,h will be changed to actual size used
}
#endif
cairo_surface_set_fallback_resolution (surface, x_resolution, y_resolution);
} else {
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, ceil(*w), ceil(*h));
}
}
static void renderPage(PDFDoc *doc, CairoOutputDev *cairoOut, int pg,
double page_w, double page_h,
double output_w, double output_h)
{
cairo_t *cr;
cairo_status_t status;
cairo_matrix_t m;
cr = cairo_create(surface);
cairoOut->setCairo(cr);
cairoOut->setPrinting(printing);
cairoOut->setAntialias(antialiasEnum);
cairo_save(cr);
if (ps && output_w > output_h) {
// rotate 90 deg for landscape
cairo_translate (cr, 0, output_w);
cairo_matrix_init (&m, 0, -1, 1, 0, 0, 0);
cairo_transform (cr, &m);
}
cairo_translate (cr, -crop_x, -crop_y);
if (printing) {
double cropped_w, cropped_h;
getCropSize(page_w, page_h, &cropped_w, &cropped_h);
getFitToPageTransform(cropped_w, cropped_h, output_w, output_h, &m);
cairo_transform (cr, &m);
cairo_rectangle(cr, crop_x, crop_y, cropped_w, cropped_h);
cairo_clip(cr);
} else {
cairo_scale (cr, x_resolution/72.0, y_resolution/72.0);
}
doc->displayPageSlice(cairoOut,
pg,
72.0, 72.0,
0, /* rotate */
!useCropBox, /* useMediaBox */
gFalse, /* Crop */
printing,
-1, -1, -1, -1);
cairo_restore(cr);
cairoOut->setCairo(nullptr);
// Blend onto white page
if (!printing && !transp) {
cairo_save(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_DEST_OVER);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_paint(cr);
cairo_restore(cr);
}
status = cairo_status(cr);
if (status)
fprintf(stderr, "cairo error: %s\n", cairo_status_to_string(status));
cairo_destroy (cr);
}
static void endPage(GooString *imageFileName)
{
cairo_status_t status;
if (printing) {
cairo_surface_show_page(surface);
#ifdef CAIRO_HAS_WIN32_SURFACE
if (printToWin32)
win32EndPage(imageFileName);
#endif
} else {
writePageImage(imageFileName);
cairo_surface_finish(surface);
status = cairo_surface_status(surface);
if (status)
fprintf(stderr, "cairo error: %s\n", cairo_status_to_string(status));
cairo_surface_destroy(surface);
}
}
static void endDocument()
{
cairo_status_t status;
if (printing) {
cairo_surface_finish(surface);
status = cairo_surface_status(surface);
if (status)
fprintf(stderr, "cairo error: %s\n", cairo_status_to_string(status));
cairo_surface_destroy(surface);
#ifdef CAIRO_HAS_WIN32_SURFACE
if (printToWin32)
win32EndDocument();
#endif
if (output_file)
fclose(output_file);
}
}
static GBool setPSPaperSize(char *size, int &psPaperWidth, int &psPaperHeight) {
if (!strcmp(size, "match")) {
psPaperWidth = psPaperHeight = -1;
} else if (!strcmp(size, "letter")) {
psPaperWidth = 612;
psPaperHeight = 792;
} else if (!strcmp(size, "legal")) {
psPaperWidth = 612;
psPaperHeight = 1008;
} else if (!strcmp(size, "A4")) {
psPaperWidth = 595;
psPaperHeight = 842;
} else if (!strcmp(size, "A3")) {
psPaperWidth = 842;
psPaperHeight = 1190;
} else {
return gFalse;
}
return gTrue;
}
static GooString *getImageFileName(GooString *outputFileName, int numDigits, int page)
{
char buf[10];
GooString *imageName = new GooString(outputFileName);
if (!singleFile) {
snprintf(buf, sizeof(buf), "-%0*d", numDigits, page);
imageName->append(buf);
}
if (outputFileName->cmp("fd://0") != 0) {
if (png)
imageName->append(".png");
else if (jpeg)
imageName->append(".jpg");
else if (tiff)
imageName->append(".tif");
}
return imageName;
}
// If (printing || singleFile) the output file name includes the
// extension. Otherwise it is the file name base.
static GooString *getOutputFileName(GooString *fileName, GooString *outputName)
{
GooString *name;
char *s;
char *p;
if (outputName) {
if (outputName->cmp("-") == 0) {
if (printToWin32 || (!printing && !singleFile)) {
fprintf(stderr, "Error: stdout may only be used with the ps, eps, pdf, svg output options or if -singlefile is used.\n");
exit(99);
}
return new GooString("fd://0");
}
return new GooString(outputName);
}
if (printToWin32)
return nullptr; // No output file means print to printer
if (fileName->cmp("fd://0") == 0) {
fprintf(stderr, "Error: an output filename or '-' must be supplied when the PDF file is stdin.\n");
exit(99);
}
// be careful not to overwrite the input file when the output format is PDF
if (pdf && fileName->cmpN("http://", 7) != 0 && fileName->cmpN("https://", 8) != 0) {
fprintf(stderr, "Error: an output filename or '-' must be supplied when the output format is PDF and input PDF file is a local file.\n");
exit(99);
}
// strip everything up to last '/'
s = fileName->getCString();
p = strrchr(s, '/');
if (p) {
p++;
if (*p == 0) {
fprintf(stderr, "Error: invalid output filename.\n");
exit(99);
}
name = new GooString(p);
} else {
name = new GooString(s);
}
// remove .pdf extension
p = strrchr(name->getCString(), '.');
if (p && strcasecmp(p, ".pdf") == 0) {
GooString *name2 = new GooString(name->getCString(), name->getLength() - 4);
delete name;
name = name2;
}
// append new extension
if (ps)
name->append(".ps");
else if (eps)
name->append(".eps");
else if (pdf)
name->append(".pdf");
else if (svg)
name->append(".svg");
return name;
}
static void checkInvalidPrintOption(GBool option, const char *option_name)
{
if (option) {
fprintf(stderr, "Error: %s may only be used with the -png, -jpeg, or -tiff output options.\n", option_name);
exit(99);
}
}
static void checkInvalidImageOption(GBool option, const char *option_name)
{
if (option) {
fprintf(stderr, "Error: %s may only be used with the -ps, -eps, -pdf, or -svg output options.\n", option_name);
exit(99);
}
}
int main(int argc, char *argv[]) {
PDFDoc *doc;
GooString *fileName = nullptr;
GooString *outputName = nullptr;
GooString *outputFileName = nullptr;
GooString *imageFileName = nullptr;
GooString *ownerPW, *userPW;
CairoOutputDev *cairoOut;
int pg, pg_num_len;
double pg_w, pg_h, tmp, output_w, output_h;
int num_outputs;
// parse args
Win32Console win32Console(&argc, &argv);
if (!parseArgs(argDesc, &argc, argv)) {
printUsage("pdftocairo", nullptr, argDesc);
exit(99);
}
if ( resolution != 0.0 &&
(x_resolution == 150.0 ||
y_resolution == 150.0)) {
x_resolution = resolution;
y_resolution = resolution;
}
if (argc < 2 || argc > 3 || printVersion || printHelp) {
fprintf(stderr, "pdftocairo version %s\n", PACKAGE_VERSION);
fprintf(stderr, "%s\n", popplerCopyright);
fprintf(stderr, "%s\n", xpdfCopyright);
if (!printVersion) {
printUsage("pdftocairo", "<PDF-file> [<output-file>]", argDesc);
}
if (printVersion || printHelp)
exit(0);
else
exit(99);
}
num_outputs = (png ? 1 : 0) +
(jpeg ? 1 : 0) +
(tiff ? 1 : 0) +
(ps ? 1 : 0) +
(eps ? 1 : 0) +
(pdf ? 1 : 0) +
(printToWin32 ? 1 : 0) +
(printdlg ? 1 : 0) +
(svg ? 1 : 0);
if (num_outputs == 0) {
fprintf(stderr, "Error: one of the output format options (-png, -jpeg, -ps, -eps, -pdf, -print, -printdlg, -svg) must be used.\n");
exit(99);
}
if (num_outputs > 1) {
fprintf(stderr, "Error: use only one of the output format options (-png, -jpeg, -ps, -eps, -pdf, -printdlg, -print, -svg).\n");
exit(99);
}
if (png || jpeg || tiff)
printing = gFalse;
else
printing = gTrue;
if (printing) {
checkInvalidPrintOption(mono, "-mono");
checkInvalidPrintOption(gray, "-gray");
checkInvalidPrintOption(transp, "-transp");
checkInvalidPrintOption(icc.getCString()[0], "-icc");
checkInvalidPrintOption(singleFile, "-singlefile");
checkInvalidPrintOption(useCropBox, "-cropbox");
checkInvalidPrintOption(scaleTo != 0, "-scale-to");
checkInvalidPrintOption(x_scaleTo != 0, "-scale-to-x");
checkInvalidPrintOption(y_scaleTo != 0, "-scale-to-y");
} else {
checkInvalidImageOption(level2, "-level2");
checkInvalidImageOption(level3, "-level3");
checkInvalidImageOption(origPageSizes, "-origpagesizes");
checkInvalidImageOption(paperSize[0], "-paper");
checkInvalidImageOption(paperWidth > 0, "-paperw");
checkInvalidImageOption(paperHeight > 0, "-paperh");
checkInvalidImageOption(noCrop, "-nocrop");
checkInvalidImageOption(expand, "-expand");
checkInvalidImageOption(noShrink, "-noshrink");
checkInvalidImageOption(noCenter, "-nocenter");
checkInvalidImageOption(duplex, "-duplex");
}
if (printing)
useCropBox = !noCrop;
if (icc.getCString()[0] && !png) {
fprintf(stderr, "Error: -icc may only be used with png output.\n");
exit(99);
}
if (antialias.getLength() > 0) {
if (!parseAntialiasOption())
exit(99);
}
if (transp && !(png || tiff)) {
fprintf(stderr, "Error: -transp may only be used with png or tiff output.\n");
exit(99);
}
if (mono && gray) {
fprintf(stderr, "Error: -mono and -gray may not be used together.\n");
exit(99);
}
if (mono && !(png || tiff)) {
fprintf(stderr, "Error: -mono may only be used with png or tiff output.\n");
exit(99);
}
if (jpegOpt.getLength() > 0) {
if (!jpeg) {
fprintf(stderr, "Error: -jpegopt may only be used with jpeg output.\n");
exit(99);
}
if (!parseJpegOptions())
exit(99);
}
if (strlen(tiffCompressionStr) > 0 && !tiff) {
fprintf(stderr, "Error: -tiffcompression may only be used with tiff output.\n");
exit(99);
}
if (level2 && level3) {
fprintf(stderr, "Error: use only one of the 'level' options.\n");
exit(99);
}
if (!level2 && !level3)
level3 = gTrue;
if (eps && (origPageSizes || paperSize[0] || paperWidth > 0 || paperHeight > 0)) {
fprintf(stderr, "Error: page size options may not be used with eps output.\n");
exit(99);
}
if ((paperWidth > 0 && paperHeight <= 0) || (paperWidth <= 0 && paperHeight > 0)) {
fprintf(stderr, "Error: both -paperw and -paperh must be specified.\n");
exit(99);
}
if (paperSize[0]) {
if (origPageSizes) {
fprintf(stderr, "Error: -origpagesizes and -paper may not be used together.\n");
exit(99);
}
if (!setPSPaperSize(paperSize, paperWidth, paperHeight)) {
fprintf(stderr, "Invalid paper size\n");
exit(99);
}
}
if (origPageSizes || paperWidth < 0 || paperHeight < 0)
usePDFPageSize = gTrue;
else
usePDFPageSize = gFalse;
if (printdlg)
printToWin32 = gTrue;
globalParams = new GlobalParams();
if (quiet) {
globalParams->setErrQuiet(quiet);
}
// open PDF file
if (ownerPassword[0]) {
ownerPW = new GooString(ownerPassword);
} else {
ownerPW = nullptr;
}
if (userPassword[0]) {
userPW = new GooString(userPassword);
} else {
userPW = nullptr;
}
fileName = new GooString(argv[1]);
if (fileName->cmp("-") == 0) {
delete fileName;
fileName = new GooString("fd://0");
}
if (argc == 3)
outputName = new GooString(argv[2]);
else
outputName = nullptr;
outputFileName = getOutputFileName(fileName, outputName);
#ifdef USE_CMS
icc_data = nullptr;
if (icc.getCString()[0]) {
FILE *file = fopen(icc.getCString(), "rb");
if (!file) {
fprintf(stderr, "Error: unable to open icc profile %s\n", icc.getCString());
exit(4);
}
fseek (file, 0, SEEK_END);
icc_data_size = ftell(file);
fseek (file, 0, SEEK_SET);
icc_data = (unsigned char*)gmalloc(icc_data_size);
if (fread(icc_data, icc_data_size, 1, file) != 1) {
fprintf(stderr, "Error: unable to read icc profile %s\n", icc.getCString());
exit(4);
}
fclose(file);
profile = cmsOpenProfileFromMem(icc_data, icc_data_size);
if (!profile) {
fprintf(stderr, "Error: lcms error opening profile\n");
exit(4);
}
} else {
profile = cmsCreate_sRGBProfile();
}
GfxColorSpace::setDisplayProfile(profile);
#endif
doc = PDFDocFactory().createPDFDoc(*fileName, ownerPW, userPW);
if (!doc->isOk()) {
fprintf(stderr, "Error opening PDF file.\n");
exit(1);
}
#ifdef ENFORCE_PERMISSIONS
// check for print permission
if (printing && !doc->okToPrint()) {
fprintf(stderr, "Printing this document is not allowed.\n");
exit(3);
}
#endif
// get page range
if (firstPage < 1)
firstPage = 1;
if (singleFile && lastPage < 1)
lastPage = firstPage;
if (lastPage < 1 || lastPage > doc->getNumPages())
lastPage = doc->getNumPages();
if (lastPage < firstPage) {
fprintf(stderr,
"Wrong page range given: the first page (%d) can not be after the last page (%d).\n",
firstPage, lastPage);
exit(99);
}
if (eps && firstPage != lastPage) {
fprintf(stderr, "EPS files can only contain one page.\n");
exit(99);
}
if (singleFile && firstPage < lastPage) {
if (!quiet) {
fprintf(stderr,
"Warning: Single file will write only the first of the %d pages.\n",
lastPage + 1 - firstPage);
}
lastPage = firstPage;
}
#ifdef CAIRO_HAS_WIN32_SURFACE
if (printdlg) {
GBool allPages = gFalse;
if (firstPage == 1 && lastPage == doc->getNumPages())
allPages = gTrue;
win32ShowPrintDialog(&expand, &noShrink, &noCenter,
&usePDFPageSize, &allPages,
&firstPage, &lastPage, doc->getNumPages());
if (allPages) {
firstPage = 1;
lastPage = doc->getNumPages();
}
} else if (printToWin32) {
win32SetupPrinter(&printer, &printOpt,
duplex, setupdlg);
}
#endif
// Make sure firstPage is always used so that beginDocument() is called
if ((printOnlyEven && firstPage % 2 == 0) || (printOnlyOdd && firstPage % 2 == 1))
firstPage++;
cairoOut = new CairoOutputDev();
cairoOut->startDoc(doc);
if (sz != 0)
crop_w = crop_h = sz;
pg_num_len = numberOfCharacters(doc->getNumPages());
for (pg = firstPage; pg <= lastPage; ++pg) {
if (printOnlyEven && pg % 2 == 0) continue;
if (printOnlyOdd && pg % 2 == 1) continue;
if (useCropBox) {
pg_w = doc->getPageCropWidth(pg);
pg_h = doc->getPageCropHeight(pg);
} else {
pg_w = doc->getPageMediaWidth(pg);
pg_h = doc->getPageMediaHeight(pg);
}
if (printing && pg == firstPage) {
if (paperWidth < 0 || paperHeight < 0) {
paperWidth = (int)ceil(pg_w);
paperHeight = (int)ceil(pg_h);
}
}
if ((doc->getPageRotate(pg) == 90) || (doc->getPageRotate(pg) == 270)) {
tmp = pg_w;
pg_w = pg_h;
pg_h = tmp;
}
if (scaleTo != 0) {
resolution = (72.0 * scaleTo) / (pg_w > pg_h ? pg_w : pg_h);
x_resolution = y_resolution = resolution;
} else {
if (x_scaleTo > 0) {
x_resolution = (72.0 * x_scaleTo) / pg_w;
if (y_scaleTo == -1)
y_resolution = x_resolution;
}
if (y_scaleTo > 0) {
y_resolution = (72.0 * y_scaleTo) / pg_h;
if (x_scaleTo == -1)
x_resolution = y_resolution;
}
}
if (imageFileName) {
delete imageFileName;
imageFileName = nullptr;
}
if (!printing)
imageFileName = getImageFileName(outputFileName, pg_num_len, pg);
getOutputSize(pg_w, pg_h, &output_w, &output_h);
if (pg == firstPage)
beginDocument(fileName, outputFileName, output_w, output_h);
beginPage(&output_w, &output_h);
renderPage(doc, cairoOut, pg, pg_w, pg_h, output_w, output_h);
endPage(imageFileName);
}
endDocument();
// clean up
delete cairoOut;
delete doc;
delete globalParams;
if (fileName)
delete fileName;
if (outputName)
delete outputName;
if (outputFileName)
delete outputFileName;
if (imageFileName)
delete imageFileName;
if (ownerPW)
delete ownerPW;
if (userPW)
delete userPW;
#ifdef USE_CMS
cmsCloseProfile(profile);
if (icc_data)
gfree(icc_data);
#endif
// check for memory leaks
Object::memCheck(stderr);
gMemReport(stderr);
return 0;
}