From 9cb6374b5599ebc58bbc719f64666bdae4049ac9 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Sun, 3 Nov 2013 19:31:55 +1030 Subject: [PATCH 02/11] test program for PDFWriter class --- test/.gitignore | 1 + test/CMakeLists.txt | 7 +- test/Makefile.am | 12 +- test/pdftopdf.cc | 505 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 523 insertions(+), 2 deletions(-) create mode 100644 test/pdftopdf.cc diff --git a/test/.gitignore b/test/.gitignore index 16c7c50..fac43a2 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -12,4 +12,5 @@ gtk-test pdf_inspector perf-test pdf-fullrewrite +pdftopdf *~ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a89a4cf..e49fc8e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -49,4 +49,9 @@ set (pdf_fullrewrite_SRCS add_executable(pdf-fullrewrite ${pdf_fullrewrite_SRCS}) target_link_libraries(pdf-fullrewrite poppler) - +set (pdftopdf_SRCS + pdftopdf.cc + ../utils/parseargs.cc +) +add_executable(pdftopdf ${pdftopdf_SRCS}) +target_link_libraries(pdftopdf poppler) diff --git a/test/Makefile.am b/test/Makefile.am index af5bcf2..88a7770 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -33,6 +33,9 @@ endif pdf_fullrewrite = \ pdf-fullrewrite +pdftopdf = \ + pdftopdf + INCLUDES = \ -I$(top_srcdir) \ -I$(top_srcdir)/poppler \ @@ -41,7 +44,7 @@ INCLUDES = \ $(cairo_includes) \ $(GTK_TEST_CFLAGS) -noinst_PROGRAMS = $(pdf_inspector) $(perf_test) $(pdf_fullrewrite) $(gtk_test) +noinst_PROGRAMS = $(pdf_inspector) $(perf_test) $(pdf_fullrewrite) $(gtk_test) $(pdftopdf) AM_LDFLAGS = @auto_import_flags@ @@ -84,6 +87,13 @@ pdf_fullrewrite_SOURCES = \ pdf_fullrewrite_LDADD = \ $(top_builddir)/poppler/libpoppler.la +pdftopdf_SOURCES = \ + pdftopdf.cc \ + ../utils/parseargs.cc + +pdftopdf_LDADD = \ + $(top_builddir)/poppler/libpoppler.la + EXTRA_DIST = \ pdf-operators.c \ pdf-inspector.ui diff --git a/test/pdftopdf.cc b/test/pdftopdf.cc new file mode 100644 index 0000000..7fae678 --- /dev/null +++ b/test/pdftopdf.cc @@ -0,0 +1,505 @@ +//======================================================================== +// +// pdftopdf.cc +// +// +// This file is licensed under the GPLv2 or later +// +// Copyright (C) 2013 Adrian Johnson +// +// 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 +#include +#include +#include "GlobalParams.h" +#include "Error.h" +#include "Object.h" +#include "PDFDoc.h" +#include "PDFWriter.h" +#include "XRef.h" +#include "goo/GooString.h" +#include "goo/gstrtod.h" +#include "utils/parseargs.h" + +static GooString pageRanges; +static int copies = 1; +static GBool collate = gFalse; +static GBool reverse = gFalse; +static GBool odd = gFalse; +static GBool even = gFalse; +static GBool duplex = gFalse; +static GooString paperSizes; +static GooString margins; +static int nup = 1; +static GooString order; +static GBool portrait = gFalse; +static GBool landscape = gFalse; +static GBool shrink = gFalse; +static GBool fit = gFalse; +static GBool nocenter = gFalse; +static double scale = 100.0; + +static char ownerPassword[33] = ""; +static char userPassword[33] = ""; +static GBool quiet = gFalse; +static GBool printVersion = gFalse; +static GBool printHelp = gFalse; + + + +static const ArgDesc argDesc[] = { + {"-pages", argGooString, &pageRanges, 0, + "Comma separated page ranges. eg \"1,3-5,8,14-\". Default is all pages. "}, + {"-copies", argInt, &copies, 0, + "Number of copies. Default is 1."}, + {"-collate", argFlag, &collate, 0, + "Collate pages when number of copies > 1. "}, + {"-reverse", argFlag, &reverse, 0, + "reverse page order"}, + {"-odd", argFlag, &odd, 0, + "output odd pages only"}, + {"-even", argFlag, &even, 0, + "output even pages only"}, + {"-duplex", argFlag, &duplex, 0, + "enable duplex (will insert blank pages where required when number of copies > 1)"}, + {"-paper", argGooString, &paperSizes, 0, + "comma separated list of paper sizes and margins. eg a4:10,letter:12:12:6:6,850x1140\n" + "\tEach page is in the format \"size\" or \"size:margin\" or \"size:top:bottom:left:right\" where size is either\n\t\"widthxheight\" in points or a builtin size eg \"a4\" or \"letter\". margin is a single value in points to use\n\tas the margin on all sides. top,bottom,left, and right are margins for each side in points."}, + {"-margins", argGooString, &margins, 0, + "the margins to use for any paper sizes that don't have the margins specified in the -paper option.\n\tFormat is either a single value (in points) to use for all sides or \"top:bottom:left:right\"."}, + {"-nup", argInt, &nup, 0, + "number up: output multiple pages per sheet. Suported values are 1,2,4,6,9,16"}, + {"-order", argGooString, &order, 0, + "order to layout pages when outputting number up. This is specified as a string of the form AB_CD.\n\teg lr_tb means left to right then top to bottom. bt_rl means bottom to top then right to left."}, + {"-portrait", argFlag, &portrait, 0, + "use portrait orientation for all output pages"}, + {"-landscape", argFlag, &landscape, 0, + "use landscape orientation for all output pages"}, + {"-shrink", argFlag, &shrink, 0, + "shrink oversized pages to fit output paper size"}, + {"-fit", argFlag, &fit, 0, + "shrink or expand pages to fit output paper size"}, + {"-nocenter", argFlag, &nocenter, 0, + "do not center pages on output paper"}, + {"-scale", argFP, &scale, 0, + "Percentage to scale pages. Default 100%."}, + + {"-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"}, + {NULL} +}; + +struct Media { + const char *name; + int width; + int height; +}; + +static const Media standardMediaSizes[] = +{ + { "A0", 2384, 3371 }, + { "A1", 1685, 2384 }, + { "A2", 1190, 1684 }, + { "A3", 842, 1190 }, + { "A4", 595, 842 }, + { "A5", 420, 595 }, + { "B4", 729, 1032 }, + { "B5", 516, 729 }, + { "Letter", 612, 792 }, + { "Tabloid", 792, 1224 }, + { "Ledger", 1224, 792 }, + { "Legal", 612, 1008 }, + { "Statement", 396, 612 }, + { "Executive", 540, 720 }, + { "Folio", 612, 936 }, + { "Quarto", 610, 780 }, + { NULL, 0, 0 } +}; + +static char *getPaperSize(char *s, double *width, double *height) { + char *p, *end; + const Media *media = standardMediaSizes; + + // search standard sizes + while (media->name) { + if (strncasecmp(s, media->name, strlen(media->name)) == 0) { + *width = media->width; + *height = media->height; + return s + strlen(media->name); + } + media++; + } + + // check if name is of the form widthxheight + *width = gstrtod(s, &end); + if (end == s) + return s; + + p = end; + if (*p != 'x' && *p != 'X') + return s; + + p++; + *height = gstrtod(p, &end); + if (end == p) + return s; + + return end; +} + +// return string from s up to next ',' for use in error messages +char *extractField(char *s) { + static char buf[1000]; + char *p; + + strncpy(buf, s, sizeof(buf)); + buf[sizeof(buf)-1] = 0; + + if ((p = strchr(buf, ','))) + *p = 0; + + return buf; +} + +static void setPaperSizes(PDFWriter *writer) { + char *s = paperSizes.getCString(); + char *p; + char *end; + double width, height; + double top, bottom, left, right; + double defaultTop = 0, defaultBottom = 0, defaultLeft = 0, defaultRight = 0; + GBool err; + + // get default margins + p = margins.getCString(); + if (strlen(p) > 0) { + err = gFalse; + defaultTop = gstrtod(p, &end); + if (end == p) + err = gTrue; + p = end; + if (*p == 0) { + // only one number specified. use this for all margins + defaultBottom = defaultLeft = defaultRight = defaultTop; + } else if (*p != ':') { + err = gTrue; + } else { + p++; + defaultBottom = gstrtod(p, &end); + if (end == p || *end != ':') + err = gTrue; + p = end + 1; + defaultLeft = gstrtod(p, &end); + if (end == p || *end != ':') + err = gTrue; + p = end + 1; + defaultRight = gstrtod(p, &end); + if (end == p || *end != 0) + err = gTrue; + } + if (err) { + fprintf(stderr, "Error: invalid margins in -margins: %s\n", margins.getCString()); + exit(99); + } + } + + // get paper sizes + s = paperSizes.getCString(); + while (*s) { + p = s; + end = getPaperSize(p, &width, &height); + if (end == p) { + fprintf(stderr, "Error: invalid paper size %s\n", extractField(s)); + exit(99); + } + + if (*end == ',' || *end == 0) { + // margins not specified. use default margins + writer->addPaperSize(width, height, defaultTop, defaultBottom, defaultLeft, defaultRight); + s = end; + if (*s == ',') + s++; + continue; + } + + if (*end != ':') { + fprintf(stderr, "Error: invalid paper size %s\n", s); + exit(99); + } + + p = end + 1; + err = gFalse; + top = gstrtod(p, &end); + if (end == p) + err = gTrue; + p = end; + if (*p == 0 || *p == ',') { + // only one number specified. use this for all margins + bottom = left = right = top; + } else if (*p != ':') { + err = gTrue; + } else { + p++; + bottom = gstrtod(p, &end); + if (end == p || *end != ':') + err = gTrue; + p = end + 1; + left = gstrtod(p, &end); + if (end == p || *end != ':') + err = gTrue; + p = end + 1; + right = gstrtod(p, &end); + if (end == p) + err = gTrue; + p = end; + } + if (*p && *p != ',') + err = gTrue; + + if (err) { + fprintf(stderr, "Error: invalid margins in paper size %s\n", extractField(s)); + exit(99); + } else { + writer->addPaperSize(width, height, top, bottom, left, right); + } + s = p; + if (*s == ',') + s++; + } +} + +static void setOrder(PDFWriter *writer) { + PDFWriter::NumberUpLayout layout; + GooString *s = order.copy(); + + if (s->getLength() == 0) + return; + + s->lowerCase(); + if (s->cmp("lr_tb") == 0) { + layout = PDFWriter::LEFT_TO_RIGHT_TOP_TO_BOTTOM; + } else if (s->cmp("lr_bt") == 0) { + layout = PDFWriter::LEFT_TO_RIGHT_BOTTOM_TO_TOP; + } else if (s->cmp("rl_tb") == 0) { + layout = PDFWriter::RIGHT_TO_LEFT_TOP_TO_BOTTOM; + } else if (s->cmp("rl_bt") == 0) { + layout = PDFWriter::RIGHT_TO_LEFT_BOTTOM_TO_TOP; + } else if (s->cmp("tb_lr") == 0) { + layout = PDFWriter::TOP_TO_BOTTOM_LEFT_TO_RIGHT; + } else if (s->cmp("tb_rl") == 0) { + layout = PDFWriter::TOP_TO_BOTTOM_RIGHT_TO_LEFT; + } else if (s->cmp("bt_lr") == 0) { + layout = PDFWriter::BOTTOM_TO_TOP_LEFT_TO_RIGHT; + } else if (s->cmp("bt_rl") == 0) { + layout = PDFWriter::BOTTOM_TO_TOP_RIGHT_TO_LEFT; + } else { + fprintf(stderr, "Error: invalid -order option: %s\n", order.getCString()); + exit(99); + } + writer->setNumberUpLayout(layout); +} + +// parse the page range at p. last = -1 means last page in document +static char *getNextPageRange(char *s, int *first, int *last) { + char *p, *end; + GBool err = gFalse; + + if (*s == 0) { + *first = 1; + *last = -1; + return NULL; + } + + p = s; + if (isdigit(*p)) { + *first = (int)strtol(p, &end, 10); + if (end == p) + err = gTrue; + p = end; + } else if (*p == '-') { + *first = 1; + } else { + err = gTrue; + } + + if (!err && *p == 0) { + *last = *first; + return NULL; + } + + if (!err && p[0] == ',' && p[1] != 0) { + last = first; + return p + 1; + } + + if (*p != '-') + err = gTrue; + p++; + + if (!isdigit(*p)) + err = gTrue; + + *last = (int)strtol(p, &end, 10); + if (end == p) + err = gTrue; + p = end; + + if (!err && *p == 0) + return NULL; + + if (!err && p[0] == ',' && p[1] != 0) + return p + 1; + + fprintf(stderr, "Error: invalid page range: %s\n", extractField(s)); + exit(99); +} + +int main (int argc, char *argv[]) +{ + PDFDoc *doc = NULL; + GooString *inputName = NULL; + GooString *outputName = NULL; + GooString *ownerPW = NULL; + GooString *userPW = NULL; + PDFWriter *writer; + char *p; + int first = 1, last = 1; + int res = 0; + + // parse args + if (!parseArgs(argDesc, &argc, argv)) + exit(99); + + if (argc != 3 || printVersion || printHelp) { + fprintf(stderr, "pdftopdf version %s\n", PACKAGE_VERSION); + fprintf(stderr, "%s\n", popplerCopyright); + fprintf(stderr, "%s\n", xpdfCopyright); + if (!printVersion) { + printUsage("pdftopdf", " ", argDesc); + } + if (printVersion || printHelp) + exit(0); + else + exit(99); + + } + + if (odd && even) { + fprintf(stderr, "Error: use only one of the options -odd and -even\n"); + exit(99); + } + + if (nup != 1 && nup != 2 && nup != 4 && nup != 6 && nup != 9 && nup != 16) { + fprintf(stderr, "Error: valid values for -nup are 1, 2, 4, 6, 9, and 16\n"); + exit(99); + } + + if (portrait && landscape) { + fprintf(stderr, "Error: use only one of the options -portrait and -landscape\n"); + exit(99); + } + + if (shrink && fit) { + fprintf(stderr, "Error: use only one of the options -shrink and -fit\n"); + exit(99); + } + + if (scale < 0.001 || scale > 100000) { + fprintf(stderr, "Error: scale must be between 0.001 and 100000\n"); + exit(99); + } + + inputName = new GooString(argv[1]); + outputName = new GooString(argv[2]); + + if (ownerPassword[0]) { + ownerPW = new GooString(ownerPassword); + } + if (userPassword[0]) { + userPW = new GooString(userPassword); + } + + // load input document + globalParams = new GlobalParams(); + doc = new PDFDoc(inputName, ownerPW, userPW); + if (!doc->isOk()) { + fprintf(stderr, "Error loading input document\n"); + res = 1; + goto done; + } + + writer = new PDFWriter(doc); + + // set output options + setPaperSizes(writer); + setOrder(writer); + writer->setNumCopies(copies); + writer->setCollate(collate); + writer->setReverse(reverse); + writer->setDuplex(duplex); + if (odd) + writer->setPageSet(PDFWriter::ODD); + else if (even) + writer->setPageSet(PDFWriter::EVEN); + else + writer->setPageSet(PDFWriter::ALL); + writer->setNumberUp(nup); + writer->setScale(scale/100.0); + if (portrait) { + writer->setOrientation(PDFWriter::PORTRAIT); + writer->setAutoRotate(gFalse); + } else if (landscape) { + writer->setOrientation(PDFWriter::LANDSCAPE); + writer->setAutoRotate(gFalse); + } else { + writer->setAutoRotate(gTrue); + } + if (shrink) + writer->setResize(PDFWriter::SHRINK); + else if (fit) + writer->setResize(PDFWriter::FIT); + else + writer->setResize(PDFWriter::NONE); + writer->setCenter(!nocenter); + + p = pageRanges.getCString(); + while (p) { + p = getNextPageRange(p, &first, &last); + if (last == -1) + last = doc->getNumPages(); + for (int i = first; i <= last; i++) { + writer->addPage(i); + } + } + writer->writeFile(outputName); + delete writer; + +done: + delete doc; + delete globalParams; + delete userPW; + delete ownerPW; + return res; +} + -- 1.8.3.2