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)
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
+/*
+ * PaperJam -- Commands
+ *
+ * (c) 2018 Martin Mares <mj@ucw.cz>
+ */
+
#include <cassert>
#include <cstdlib>
#include <cstdio>
+++ /dev/null
-#include <cassert>
-#include <cstdlib>
-#include <cstdio>
-#include <unistd.h>
-#include <sys/wait.h>
-
-#include "jam.h"
-
-vector<BBox> 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<BBox> 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;
-}
+/*
+ * PaperJam -- Common declarations
+ *
+ * (c) 2018 Martin Mares <mj@ucw.cz>
+ */
+
#include <vector>
#include <list>
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;
vector<pipeline_branch *> branches;
};
+/*** Modules ***/
+
// paperjam.cc
-vector<page *> run_command_list(list<cmd *> &cmds, vector<page *> &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<cmd *> &cmds);
-void help();
+void parser_help();
// cmds.cc
extern const cmd_def cmd_table[];
-// gs.cc
+// pdf.cc
+void debug_pages(vector<page *> &pages);
+void process(list<cmd *> &cmds);
+vector<page *> run_command_list(list<cmd *> &cmds, vector<page *> &pages);
vector<BBox> gs_bboxes(const char *in);
+/*
+ * PaperJam -- Main program
+ *
+ * (c) 2018 Martin Mares <mj@ucw.cz>
+ */
+
#include <cassert>
#include <cstdarg>
#include <cstdlib>
#include <cstdio>
+#include <getopt.h>
#include "jam.h"
-#include <qpdf/QPDFWriter.hh>
-
-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<page *> &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<page *> run_command_list(list<cmd *> &cmds, vector<page *> &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<page *> &pages, const char *in_name)
+void die(const char *msg, ...)
{
- vector<BBox> 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; i<pages.size(); i++)
- pages[i]->bbox = 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<cmd *> &cmds, const char *in_name, const char *out_name)
-{
- in_pdf.processFile(in_name);
- in_pdf.pushInheritedAttributesToPage();
- out_pdf.emptyPDF();
-
- vector<QPDFObjectHandle> const &in_pages = in_pdf.getAllPages();
- vector<page *> 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 [<options>] <commands> <in> <out>\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 <commands> <input> <output>\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<cmd *> 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)
{
+/*
+ * PaperJam -- Command parser
+ *
+ * (c) 2018 Martin Mares <mj@ucw.cz>
+ */
+
#include <cassert>
#include <cstdarg>
#include <cstdlib>
instantiate(cmds);
}
-void help()
+void parser_help()
{
for (int i=0; cmd_table[i].name; i++)
{
* (c) 2018 Martin Mares <mj@ucw.cz>
*/
-#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <iconv.h>
-using namespace std;
-
-#include "pdf-tools.h"
+#include "jam.h"
#include <qpdf/QUtil.hh>
#include <qpdf/Pl_Concatenate.hh>
-/*** 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
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);
/*** 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 {
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; }
QPDFObjectHandle page_to_xobject(QPDF *out, QPDFObjectHandle page);
#endif
-
--- /dev/null
+/*
+ * PaperJam -- Low-level handling of PDFs
+ *
+ * (c) 2018 Martin Mares <mj@ucw.cz>
+ */
+
+#include <cassert>
+#include <cstdlib>
+#include <cstdio>
+#include <unistd.h>
+#include <sys/wait.h>
+
+#include "jam.h"
+
+#include <qpdf/QPDFWriter.hh>
+
+static QPDF in_pdf;
+static QPDF out_pdf;
+
+static void do_recalc_bbox(vector<page *> &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<page *> &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<page *> run_command_list(list<cmd *> &cmds, vector<page *> &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<cmd *> &cmds)
+{
+ in_pdf.processFile(in_name);
+ in_pdf.pushInheritedAttributesToPage();
+ out_pdf.emptyPDF();
+
+ vector<QPDFObjectHandle> const &in_pages = in_pdf.getAllPages();
+ vector<page *> 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<BBox> 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<BBox> 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<page *> &pages, const char *in_name)
+{
+ vector<BBox> 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; i<pages.size(); i++)
+ pages[i]->bbox = bboxes[i];
+}