From 367bdc00abaaad2d13b77705d8f1ba493e371c63 Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Mon, 2 Apr 2018 12:29:28 +0200 Subject: [PATCH] Shuffling code around --- Makefile | 4 +- cmds.cc | 6 ++ gs.cc | 62 -------------- jam.h | 31 +++++-- paperjam.cc | 236 ++++++++++++++++++--------------------------------- parse.cc | 8 +- pdf-tools.cc | 63 ++------------ pdf-tools.h | 23 +---- pdf.cc | 230 +++++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 365 insertions(+), 298 deletions(-) delete mode 100644 gs.cc create mode 100644 pdf.cc diff --git a/Makefile b/Makefile index bb74b75..9344b44 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ CXXFLAGS=-O2 -Wall -Wextra -Wno-parentheses -std=gnu++11 -g all: paperjam -paperjam: paperjam.o pdf-tools.o parse.o cmds.o gs.o +paperjam: paperjam.o pdf-tools.o parse.o cmds.o pdf.o $(LD) -o $@ $^ $(LDLIBS) paperjam: LDLIBS += -lqpdf paperjam: LD=$(CXX) @@ -11,4 +11,4 @@ paperjam.o: jam.h pdf-tools.h pdf-tools.o: jam.h pdf-tools.h parse.o: jam.h pdf-tools.h cmds.o: jam.h pdf-tools.h -gs.o: jam.h pdf-tools.h +pdf.o: jam.h pdf-tools.h diff --git a/cmds.cc b/cmds.cc index 71f9c29..9ca7637 100644 --- a/cmds.cc +++ b/cmds.cc @@ -1,3 +1,9 @@ +/* + * PaperJam -- Commands + * + * (c) 2018 Martin Mares + */ + #include #include #include diff --git a/gs.cc b/gs.cc deleted file mode 100644 index b52448a..0000000 --- a/gs.cc +++ /dev/null @@ -1,62 +0,0 @@ -#include -#include -#include -#include -#include - -#include "jam.h" - -vector gs_bboxes(const char *in) -{ - int pipes[2]; - if (pipe(pipes) < 0) - die("Cannot create pipe: %m"); - - pid_t pid = fork(); - if (pid < 0) - die("Cannot fork: %m"); - - if (!pid) - { - close(pipes[0]); - dup2(pipes[1], 1); - dup2(pipes[1], 2); - close(pipes[1]); - execlp("gs", "gs", "-sDEVICE=bbox", "-dSAFER", "-dBATCH", "-dNOPAUSE", "-q", in, NULL); - die("Cannot execute gs: %m"); - } - - close(pipes[1]); - FILE *f = fdopen(pipes[0], "r"); - if (!f) - die("fdopen failed: %m"); - - char line[1024]; - vector bboxes; - while (fgets(line, sizeof(line), f)) - { - char *eol = strchr(line, '\n'); - if (!eol) - die("Ghostscript produced too long lines"); - *eol = 0; - - if (!strncmp(line, "%%HiResBoundingBox: ", 20)) - { - double x1, y1, x2, y2; - if (sscanf(line+20, "%lf%lf%lf%lf", &x1, &y1, &x2, &y2) != 4) - die("Cannot parse Ghostscript output: %s", line); - bboxes.push_back(BBox(x1, y1, x2, y2)); - } - else if (line[0] != '%') - fprintf(stderr, "%s\n", line); - } - fclose(f); - - int stat; - if (waitpid(pid, &stat, 0) < 0) - die("wait failed: %m"); - if (!WIFEXITED(stat) || WEXITSTATUS(stat)) - die("Ghostscript failed"); - - return bboxes; -} diff --git a/jam.h b/jam.h index 7fd316a..ca5dd9c 100644 --- a/jam.h +++ b/jam.h @@ -1,14 +1,23 @@ +/* + * PaperJam -- Common declarations + * + * (c) 2018 Martin Mares + */ + #include #include using namespace std; -#include "pdf-tools.h" - typedef unsigned int uint; #define NONRET __attribute__((noreturn)) #define UNUSED __attribute__((unused)) +#define FORMAT_CHECK(x,y,z) __attribute__((format(x,y,z))) + +#include "pdf-tools.h" + +/*** Representation of commands ***/ struct pipeline; struct cmd; @@ -99,19 +108,31 @@ struct pipeline { vector branches; }; +/*** Modules ***/ + // paperjam.cc -vector run_command_list(list &cmds, vector &pages); +extern const char *in_name, *out_name; +extern bool recalc_bbox; +extern int debug_mode; +extern int debug_indent; + +void debug(const char *msg, ...) FORMAT_CHECK(printf, 1, 2); +void warn(const char *msg, ...) FORMAT_CHECK(printf, 1, 2); +void die(const char *msg, ...) FORMAT_CHECK(printf, 1, 2) NONRET; // parse.cc void parse(const char *in, list &cmds); -void help(); +void parser_help(); // cmds.cc extern const cmd_def cmd_table[]; -// gs.cc +// pdf.cc +void debug_pages(vector &pages); +void process(list &cmds); +vector run_command_list(list &cmds, vector &pages); vector gs_bboxes(const char *in); diff --git a/paperjam.cc b/paperjam.cc index c8e1071..c8e0a4f 100644 --- a/paperjam.cc +++ b/paperjam.cc @@ -1,189 +1,121 @@ +/* + * PaperJam -- Main program + * + * (c) 2018 Martin Mares + */ + #include #include #include #include +#include #include "jam.h" -#include - -static QPDF in_pdf; -static QPDF out_pdf; - -string out_context::new_resource(const string type) -{ - return "/" + type + to_string(++res_cnt); -} +/*** Options ***/ -class in_page : public page { - QPDFObjectHandle pdf_page; - QPDFObjectHandle xobject; -public: - BBox media_box; - void render(out_context *out, pdf_matrix xform); - in_page(QPDFObjectHandle inpg, int idx); -}; +const char *in_name; +const char *out_name; +bool recalc_bbox; +int debug_mode; +int debug_indent; -in_page::in_page(QPDFObjectHandle inpg, int idx) -{ - pdf_page = inpg; - xobject = QPDFObjectHandle::newNull(); - index = idx; - - media_box = BBox(inpg.getKey("/MediaBox")); - width = media_box.width(); - height = media_box.height(); - - QPDFObjectHandle art_box = inpg.getKey("/ArtBox"); - if (art_box.isNull()) - art_box = inpg.getKey("/CropBox"); - if (art_box.isNull()) - bbox = BBox(width, height); - else - { - bbox = BBox(art_box); - bbox.x_min -= media_box.x_min; - bbox.x_max -= media_box.x_min; - bbox.y_min -= media_box.y_min; - bbox.y_max -= media_box.y_min; - } -} +/*** Messages ***/ -void in_page::render(out_context *out, pdf_matrix xform) -{ - // Convert page to xobject - if (xobject.isNull()) - xobject = out_pdf.makeIndirectObject( page_to_xobject(&out_pdf, out_pdf.copyForeignObject(pdf_page)) ); - string xobj_res = out->new_resource("XO"); - out->xobjects.replaceKey(xobj_res, xobject); - - pdf_matrix m; - m.shift(-media_box.x_min, -media_box.y_min); - m.concat(xform); - - out->contents += "q " + m.to_string() + " cm " + xobj_res + " Do Q "; -} - -static void debug_pages(vector &pages) +void debug(const char *msg, ...) { if (!debug_mode) return; - - for (auto pg: pages) - debug("Page #%d: media[%.3f %.3f] bbox[%.3f %.3f %.3f %.3f]", - pg->index, - pg->width, pg->height, - pg->bbox.x_min, pg->bbox.y_min, pg->bbox.x_max, pg->bbox.y_max); + va_list args; + va_start(args, msg); + fprintf(stderr, "%*s", debug_indent, ""); + vfprintf(stderr, msg, args); + fputc('\n', stderr); + va_end(args); } -vector run_command_list(list &cmds, vector &pages) +void warn(const char *msg, ...) { - debug("# Input"); - debug_pages(pages); - - for (auto c: cmds) - { - debug("# Executing %s", c->def->name); - debug_indent += 4; - pages = c->exec->process(pages); - debug_indent -= 4; - debug_pages(pages); - } - - return pages; + va_list args; + va_start(args, msg); + fprintf(stderr, "Warning: "); + vfprintf(stderr, msg, args); + fputc('\n', stderr); + va_end(args); } -static void find_bboxes(vector &pages, const char *in_name) +void die(const char *msg, ...) { - vector bboxes = gs_bboxes(in_name); - if (pages.size() != bboxes.size()) - die("Ghostscript failed to produce the right number of bboxes"); - - for (size_t i=0; ibbox = bboxes[i]; + va_list args; + va_start(args, msg); + vfprintf(stderr, msg, args); + fputc('\n', stderr); + va_end(args); + exit(1); } -static void process(list &cmds, const char *in_name, const char *out_name) -{ - in_pdf.processFile(in_name); - in_pdf.pushInheritedAttributesToPage(); - out_pdf.emptyPDF(); - - vector const &in_pages = in_pdf.getAllPages(); - vector pages; - - QPDFObjectHandle page_copy = out_pdf.copyForeignObject(in_pages[0]); +/*** Arguments ***/ - int cnt = 0; - for (auto inpg: in_pages) - pages.push_back(new in_page(inpg, ++cnt)); - - find_bboxes(pages, in_name); +enum opt { + OPT_HELP = 256, + OPT_VERSION, +}; - pages = run_command_list(cmds, pages); +static const struct option long_opts[] = { + { "debug", 0, 0, 'd' }, + { "help", 0, 0, OPT_HELP }, + { "version", 0, 0, OPT_VERSION }, + { 0, 0, 0, 0 } +}; - for (auto pg: pages) - { - out_context out; - out.resources = QPDFObjectHandle::newDictionary(); - out.resources.replaceKey("/ProcSet", QPDFObjectHandle::parse("[/PDF]")); - out.xobjects = QPDFObjectHandle::newDictionary(); - out.resources.replaceKey("/XObject", out.xobjects); - pg->render(&out, pdf_matrix()); - - QPDFObjectHandle contents = QPDFObjectHandle::newStream(&out_pdf, out.contents); - - // Create the page object - QPDFObjectHandle out_page = out_pdf.makeIndirectObject(QPDFObjectHandle::newDictionary()); - out_page.replaceKey("/Type", QPDFObjectHandle::newName("/Page")); - out_page.replaceKey("/MediaBox", BBox(pg->width, pg->height).to_array()); - // FIXME: - // out_page.replaceKey("/CropBox", pg->bbox.to_array()); - out_page.replaceKey("/Contents", contents); - out_page.replaceKey("/Resources", out.resources); - out_pdf.addPage(out_page, false); - } +static const char short_opts[] = "bd"; - // Produce info dictionary - QPDFObjectHandle trailer = out_pdf.getTrailer(); - QPDFObjectHandle info = trailer.getKey("/Info"); - if (info.isNull()) - { - info = QPDFObjectHandle::newDictionary(); - trailer.replaceKey("/Info", info); - } - else - assert(info.isDictionary()); - // FIXME: More meta-data - info.replaceKey("/Producer", unicode_string("PaperJam")); - - // Write the output file - QPDFWriter writer(out_pdf, out_name); - writer.write(); +static void usage() +{ + printf("Usage: paperjam [] \n\ +\n\ +Options:\n\ +-b, --bbox Recalculate bounding boxes\n\ +-d, --debug Show debugging messages\n\ +\n\ +Commands: (FIXME)\n\ +"); + parser_help(); } int main(int argc, char **argv) { - if (argc <= 1 || argc > 1 && !strcmp(argv[1], "--help")) - { - help(); - return 0; - } - - if (argc != 4) - { - fprintf(stderr, "Usage: paperjam \n"); - return 1; - } - - debug_mode = 100; + int c; + while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0) + switch (c) + { + case 'b': + recalc_bbox = 1; + break; + case 'd': + debug_mode++; + break; + case OPT_VERSION: + printf("This is paperjam with no version yet.\n"); // FIXME + return 0; + case OPT_HELP: + usage(); + return 0; + default: + exit(1); + } + + if (optind + 3 != argc) + die("Exactly three positional parameters should be given"); list cmds; - parse(argv[1], cmds); + parse(argv[optind], cmds); + in_name = argv[optind+1]; + out_name = argv[optind+2]; try { - process(cmds, argv[2], argv[3]); + process(cmds); } catch (exception& e) { diff --git a/parse.cc b/parse.cc index d3d92cd..48a32c7 100644 --- a/parse.cc +++ b/parse.cc @@ -1,3 +1,9 @@ +/* + * PaperJam -- Command parser + * + * (c) 2018 Martin Mares + */ + #include #include #include @@ -483,7 +489,7 @@ void parse(const char *in, list &cmds) instantiate(cmds); } -void help() +void parser_help() { for (int i=0; cmd_table[i].name; i++) { diff --git a/pdf-tools.cc b/pdf-tools.cc index c170d8a..136fe99 100644 --- a/pdf-tools.cc +++ b/pdf-tools.cc @@ -4,69 +4,16 @@ * (c) 2018 Martin Mares */ -#include #include #include #include -using namespace std; - -#include "pdf-tools.h" +#include "jam.h" #include #include -/*** Messages ***/ - -int debug_mode; -int debug_indent; - -void debug(const char *msg, ...) -{ - if (!debug_mode) - return; - va_list args; - va_start(args, msg); - fprintf(stderr, "%*s", debug_indent, ""); - vfprintf(stderr, msg, args); - fputc('\n', stderr); - va_end(args); -} - -void warn(const char *msg, ...) -{ - va_list args; - va_start(args, msg); - fprintf(stderr, "WARNING: "); - vfprintf(stderr, msg, args); - fputc('\n', stderr); - va_end(args); -} - -void die(const char *msg, ...) -{ - va_list args; - va_start(args, msg); - fprintf(stderr, "ERROR: "); - vfprintf(stderr, msg, args); - fputc('\n', stderr); - va_end(args); - exit(1); -} - -void bad(const char *msg, ...) -{ - va_list args; - va_start(args, msg); - char buf[1024]; - vsnprintf(buf, sizeof(buf), msg, args); - va_end(args); - - printf("error: %s\n", buf); - die("BAD: %s", buf); -} - /*** Transformation matrices ***/ // Construct string representation of a transformation matrix @@ -119,6 +66,14 @@ bool BBox::parse(QPDFObjectHandle h) return true; } +BBox::BBox(QPDFObjectHandle box) { + if (!parse(box)) { + warn("Invalid bounding box, falling back to A4"); + x_min = 0, x_max = A4_WIDTH; + y_min = 0, y_max = A4_HEIGHT; + } +} + void BBox::transform(pdf_matrix &m) { m.apply(&x_min, &y_min); diff --git a/pdf-tools.h b/pdf-tools.h index 9563ec0..18aac24 100644 --- a/pdf-tools.h +++ b/pdf-tools.h @@ -14,24 +14,10 @@ /*** Basic macros and constants ***/ -#define UNUSED __attribute__((unused)) -#define FORMAT_CHECK(x,y,z) __attribute__((format(x,y,z))) -#define NONRET __attribute__((noreturn)) - #define A4_WIDTH 595 #define A4_HEIGHT 842 static const double mm = 72/25.4; -/*** Messages ***/ - -void debug(const char *msg, ...) FORMAT_CHECK(printf, 1, 2); -void warn(const char *msg, ...) FORMAT_CHECK(printf, 1, 2); -void die(const char *msg, ...) FORMAT_CHECK(printf, 1, 2) NONRET; -void bad(const char *msg, ...) FORMAT_CHECK(printf, 1, 2) NONRET; - -extern int debug_mode; -extern int debug_indent; - /*** Transformation matrices ***/ struct pdf_matrix { @@ -137,13 +123,7 @@ struct BBox { x_min = 0, x_max = x; y_min = 0, y_max = y; } - BBox(QPDFObjectHandle box) { - if (!parse(box)) { - warn("Invalid bounding box, falling back to A4"); - x_min = 0, x_max = A4_WIDTH; - y_min = 0, y_max = A4_HEIGHT; - } - } + BBox(QPDFObjectHandle box); QPDFObjectHandle to_array(); string to_rect(); double width() { return x_max - x_min; } @@ -159,4 +139,3 @@ QPDFObjectHandle unicode_string(std::string s); QPDFObjectHandle page_to_xobject(QPDF *out, QPDFObjectHandle page); #endif - diff --git a/pdf.cc b/pdf.cc new file mode 100644 index 0000000..daf68c2 --- /dev/null +++ b/pdf.cc @@ -0,0 +1,230 @@ +/* + * PaperJam -- Low-level handling of PDFs + * + * (c) 2018 Martin Mares + */ + +#include +#include +#include +#include +#include + +#include "jam.h" + +#include + +static QPDF in_pdf; +static QPDF out_pdf; + +static void do_recalc_bbox(vector &pages, const char *in_name); + +string out_context::new_resource(const string type) +{ + return "/" + type + to_string(++res_cnt); +} + +class in_page : public page { + QPDFObjectHandle pdf_page; + QPDFObjectHandle xobject; +public: + BBox media_box; + void render(out_context *out, pdf_matrix xform); + in_page(QPDFObjectHandle inpg, int idx); +}; + +in_page::in_page(QPDFObjectHandle inpg, int idx) +{ + pdf_page = inpg; + xobject = QPDFObjectHandle::newNull(); + index = idx; + + media_box = BBox(inpg.getKey("/MediaBox")); + width = media_box.width(); + height = media_box.height(); + + QPDFObjectHandle art_box = inpg.getKey("/ArtBox"); + if (art_box.isNull()) + art_box = inpg.getKey("/CropBox"); + if (art_box.isNull()) + bbox = BBox(width, height); + else + { + bbox = BBox(art_box); + bbox.x_min -= media_box.x_min; + bbox.x_max -= media_box.x_min; + bbox.y_min -= media_box.y_min; + bbox.y_max -= media_box.y_min; + } +} + +void in_page::render(out_context *out, pdf_matrix xform) +{ + // Convert page to xobject + if (xobject.isNull()) + xobject = out_pdf.makeIndirectObject( page_to_xobject(&out_pdf, out_pdf.copyForeignObject(pdf_page)) ); + string xobj_res = out->new_resource("XO"); + out->xobjects.replaceKey(xobj_res, xobject); + + pdf_matrix m; + m.shift(-media_box.x_min, -media_box.y_min); + m.concat(xform); + + out->contents += "q " + m.to_string() + " cm " + xobj_res + " Do Q "; +} + +void debug_pages(vector &pages) +{ + if (!debug_mode) + return; + + for (auto pg: pages) + debug("Page #%d: media[%.3f %.3f] bbox[%.3f %.3f %.3f %.3f]", + pg->index, + pg->width, pg->height, + pg->bbox.x_min, pg->bbox.y_min, pg->bbox.x_max, pg->bbox.y_max); +} + +vector run_command_list(list &cmds, vector &pages) +{ + debug("# Input"); + debug_pages(pages); + + for (auto c: cmds) + { + debug("# Executing %s", c->def->name); + debug_indent += 4; + pages = c->exec->process(pages); + debug_indent -= 4; + debug_pages(pages); + } + + return pages; +} + +void process(list &cmds) +{ + in_pdf.processFile(in_name); + in_pdf.pushInheritedAttributesToPage(); + out_pdf.emptyPDF(); + + vector const &in_pages = in_pdf.getAllPages(); + vector pages; + + QPDFObjectHandle page_copy = out_pdf.copyForeignObject(in_pages[0]); + + int cnt = 0; + for (auto inpg: in_pages) + pages.push_back(new in_page(inpg, ++cnt)); + + if (recalc_bbox) + do_recalc_bbox(pages, in_name); + + pages = run_command_list(cmds, pages); + + for (auto pg: pages) + { + out_context out; + out.resources = QPDFObjectHandle::newDictionary(); + out.resources.replaceKey("/ProcSet", QPDFObjectHandle::parse("[/PDF]")); + out.xobjects = QPDFObjectHandle::newDictionary(); + out.resources.replaceKey("/XObject", out.xobjects); + pg->render(&out, pdf_matrix()); + + QPDFObjectHandle contents = QPDFObjectHandle::newStream(&out_pdf, out.contents); + + // Create the page object + QPDFObjectHandle out_page = out_pdf.makeIndirectObject(QPDFObjectHandle::newDictionary()); + out_page.replaceKey("/Type", QPDFObjectHandle::newName("/Page")); + out_page.replaceKey("/MediaBox", BBox(pg->width, pg->height).to_array()); + // FIXME: + // out_page.replaceKey("/CropBox", pg->bbox.to_array()); + out_page.replaceKey("/Contents", contents); + out_page.replaceKey("/Resources", out.resources); + out_pdf.addPage(out_page, false); + } + + // Produce info dictionary + QPDFObjectHandle trailer = out_pdf.getTrailer(); + QPDFObjectHandle info = trailer.getKey("/Info"); + if (info.isNull()) + { + info = QPDFObjectHandle::newDictionary(); + trailer.replaceKey("/Info", info); + } + else + assert(info.isDictionary()); + // FIXME: More meta-data + info.replaceKey("/Producer", unicode_string("PaperJam")); + + // Write the output file + QPDFWriter writer(out_pdf, out_name); + writer.write(); +} + +/*** Re-calculation of bboxes ***/ + +vector gs_bboxes(const char *in) +{ + int pipes[2]; + if (pipe(pipes) < 0) + die("Cannot create pipe: %m"); + + pid_t pid = fork(); + if (pid < 0) + die("Cannot fork: %m"); + + if (!pid) + { + close(pipes[0]); + dup2(pipes[1], 1); + dup2(pipes[1], 2); + close(pipes[1]); + execlp("gs", "gs", "-sDEVICE=bbox", "-dSAFER", "-dBATCH", "-dNOPAUSE", "-q", in, NULL); + die("Cannot execute gs: %m"); + } + + close(pipes[1]); + FILE *f = fdopen(pipes[0], "r"); + if (!f) + die("fdopen failed: %m"); + + char line[1024]; + vector bboxes; + while (fgets(line, sizeof(line), f)) + { + char *eol = strchr(line, '\n'); + if (!eol) + die("Ghostscript produced too long lines"); + *eol = 0; + + if (!strncmp(line, "%%HiResBoundingBox: ", 20)) + { + double x1, y1, x2, y2; + if (sscanf(line+20, "%lf%lf%lf%lf", &x1, &y1, &x2, &y2) != 4) + die("Cannot parse Ghostscript output: %s", line); + bboxes.push_back(BBox(x1, y1, x2, y2)); + } + else if (line[0] != '%') + fprintf(stderr, "%s\n", line); + } + fclose(f); + + int stat; + if (waitpid(pid, &stat, 0) < 0) + die("wait failed: %m"); + if (!WIFEXITED(stat) || WEXITSTATUS(stat)) + die("Ghostscript failed"); + + return bboxes; +} + +static void do_recalc_bbox(vector &pages, const char *in_name) +{ + vector bboxes = gs_bboxes(in_name); + if (pages.size() != bboxes.size()) + die("Ghostscript failed to produce the right number of bboxes"); + + for (size_t i=0; ibbox = bboxes[i]; +} -- 2.39.2