#include <cstdlib>
#include <cstdio>
-using namespace std;
-
#include "jam.h"
-/*** Lexer ***/
-
-enum token_type {
- TOK_NONE,
- TOK_END,
- TOK_EQUAL,
- TOK_COMMA,
- TOK_OPEN_PAREN,
- TOK_CLOSE_PAREN,
- TOK_OPEN_BRACE,
- TOK_CLOSE_BRACE,
- TOK_IDENT,
- TOK_STRING,
- TOK_NUMBER,
-};
-
-const char *in_pos;
-static token_type this_token = TOK_NONE;
-static token_type buffered_token = TOK_NONE;
-static string token;
-static double token_num;
+#include <qpdf/QPDFWriter.hh>
-static void NONRET parse_error(const char *msg, ...);
+static QPDF in_pdf;
+static QPDF out_pdf;
-static void parse_error(const char *msg, ...)
+string out_context::new_resource(const string type)
{
- va_list args;
- va_start(args, msg);
- fprintf(stderr, "Parse error: ");
- vfprintf(stderr, msg, args);
- fprintf(stderr, "\n");
- va_end(args);
- exit(1);
+ return "/" + type + to_string(++res_cnt);
}
-static token_type get_next_token()
-{
- while (*in_pos == ' ' || *in_pos == '\t' || *in_pos == '\r' || *in_pos == '\n')
- in_pos++;
-
- token = "";
- if (!*in_pos)
- return TOK_END;
+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);
+};
- if (*in_pos >= '0' && *in_pos <= '9' ||
- *in_pos == '-' && in_pos[1] >= '0' && in_pos[1] <= '9')
+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
{
- token += *in_pos++;
- while (*in_pos >= '0' && *in_pos <= '9' || *in_pos == '.')
- token += *in_pos++;
-
- size_t end_pos;
- token_num = stod(token, &end_pos);
- if (end_pos < token.length())
- parse_error("Invalid number %s", token.c_str());
- return TOK_NUMBER;
+ 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;
}
+}
- if (*in_pos >= 'A' && *in_pos <= 'Z' ||
- *in_pos >= 'a' && *in_pos <= 'z')
- {
- while (*in_pos >= 'A' && *in_pos <= 'Z' ||
- *in_pos >= 'a' && *in_pos <= 'z' ||
- *in_pos >= '0' && *in_pos <= '9')
- token += *in_pos++;
- return TOK_IDENT;
- }
+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);
- if (*in_pos == '"')
- {
- in_pos++;
- while (*in_pos != '"')
- {
- if (!*in_pos)
- parse_error("Unterminated string");
- if (*in_pos == '\\')
- {
- in_pos++;
- if (*in_pos == '"')
- parse_error("Unrecognized escape sequence \\%c", *in_pos);
- }
- token += *in_pos++;
- }
- in_pos++;
- return TOK_STRING;
- }
+ pdf_matrix m;
+ m.shift(-media_box.x_min, -media_box.y_min);
+ m.concat(xform);
- uint c = *in_pos++;
- switch (c)
- {
- case '=':
- return TOK_EQUAL;
- case ',':
- return TOK_COMMA;
- case '(':
- return TOK_OPEN_PAREN;
- case ')':
- return TOK_CLOSE_PAREN;
- case '{':
- return TOK_OPEN_BRACE;
- case '}':
- return TOK_CLOSE_BRACE;
- default:
- parse_error("Unrecognized character '%c'", c);
- }
+ out->contents += "q " + m.to_string() + " cm " + xobj_res + " Do Q ";
}
-static token_type next_token()
+static void debug_pages(vector<page *> &pages)
{
- this_token = get_next_token();
- return this_token;
+ 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);
}
-static void return_token()
+vector<page *> run_command_list(list<cmd *> &cmds, vector<page *> &pages)
{
- assert(this_token != TOK_NONE);
- assert(buffered_token == TOK_NONE);
- buffered_token = this_token;
- this_token = TOK_NONE;
-}
+ debug("# Input");
+ debug_pages(pages);
-/*** Parser ***/
+ for (auto c: cmds)
+ {
+ debug("# Executing %s", c->def->name);
+ debug_indent += 4;
+ pages = c->exec->process(pages);
+ debug_indent -= 4;
+ debug_pages(pages);
+ }
-static const arg_def move_args[] = {
- { "x", AT_DIMEN | AT_MANDATORY | AT_POSITIONAL },
- { "y", AT_DIMEN | AT_MANDATORY | AT_POSITIONAL },
- { NULL, 0 }
-};
+ return pages;
+}
-static const cmd_def cmd_table[] = {
- { "move", move_args, NULL },
- { NULL, NULL, NULL }
-};
+static void find_bboxes(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");
-struct unit {
- const char *name;
- double multiplier;
-};
+ for (size_t i=0; i<pages.size(); i++)
+ pages[i]->bbox = bboxes[i];
+}
-#define MM (72/25.4)
+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();
-static const unit units[] = {
- { "mm", MM },
- { "cm", 10*MM },
- { "dm", 100*MM },
- { "m", 1000*MM },
- { "in", 72 },
- { "pt", 1 },
- { NULL, 0 }
-};
+ vector<QPDFObjectHandle> const &in_pages = in_pdf.getAllPages();
+ vector<page *> pages;
-static double parse_dimen(const arg_def *adef)
-{
- token_type t = next_token();
- if (t != TOK_NUMBER)
- parse_error("Paremeter %s must be a dimension", adef->name);
- double tmp = token_num;
-
- t = next_token();
- if (t != TOK_IDENT)
- parse_error("Paremeter %s must have a unit", adef->name);
- for (uint i; units[i].name; i++)
- if (token == units[i].name)
- return tmp * units[i].multiplier;
- parse_error("Unknown unit %s", token.c_str());
-}
+ QPDFObjectHandle page_copy = out_pdf.copyForeignObject(in_pages[0]);
-static cmd_args *parse_args(const cmd_def *cdef)
-{
- cmd_args *args = new cmd_args;
+ int cnt = 0;
+ for (auto inpg: in_pages)
+ pages.push_back(new in_page(inpg, ++cnt));
- const arg_def *adefs = cdef->arg_defs;
- uint num_args = 0;
- while (adefs[num_args].name)
- {
- args->arg.push_back(arg_val());
- args->arg_given.push_back(0);
- num_args++;
- }
+ find_bboxes(pages, in_name);
+
+ pages = run_command_list(cmds, pages);
- token_type t = next_token();
- if (t != TOK_OPEN_PAREN)
+ for (auto pg: pages)
{
- return_token();
- return args;
+ 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);
}
- bool saw_named = false;
- uint next_pos = 0;
- for (;;)
+ // Produce info dictionary
+ QPDFObjectHandle trailer = out_pdf.getTrailer();
+ QPDFObjectHandle info = trailer.getKey("/Info");
+ if (info.isNull())
{
- t = next_token();
- int argi = 0;
- if (t == TOK_IDENT)
- {
- while (adefs[argi].name && token != adefs[argi].name)
- argi++;
- if (!adefs[argi].name)
- parse_error("Command %s has no parameter %s", cdef->name, token.c_str());
- t = next_token();
- if (t != TOK_EQUAL)
- parse_error("Parameter name must be followed by '='");
- saw_named = true;
- }
- else if (saw_named)
- parse_error("Positional parameters must precede named ones");
- else
- {
- while (next_pos < num_args && !(adefs[next_pos].type & AT_POSITIONAL))
- next_pos++;
- if (next_pos >= num_args)
- parse_error("Too many positional arguments for command %s", cdef->name);
- argi = next_pos++;
- }
-
- const arg_def *adef = &adefs[argi];
- switch (adef->type & AT_TYPE_MASK)
- {
- case AT_STRING:
- t = next_token();
- if (t != TOK_STRING)
- parse_error("Paremeter %s must be a string", adef->name);
- args->arg[argi].s = token;
- break;
- case AT_DOUBLE:
- t = next_token();
- if (t != TOK_NUMBER)
- parse_error("Paremeter %s must be a number", adef->name);
- args->arg[argi].d = token_num;
- break;
- case AT_DIMEN:
- args->arg[argi].d = parse_dimen(adef);
- break;
- default:
- abort();
- }
-
- t = next_token();
- if (t == TOK_CLOSE_PAREN)
- break;
- if (t != TOK_COMMA)
- parse_error("Comma expected after parameter %s", adef->name);
+ info = QPDFObjectHandle::newDictionary();
+ trailer.replaceKey("/Info", info);
}
-
- return args;
-}
-
-static cmd *parse_cmd()
-{
- const cmd_def *cdef = cmd_table;
- while (cdef->name && token != cdef->name)
- cdef++;
- if (!cdef->name)
- parse_error("Unknown command %s", token.c_str());
-
- cmd_args *args = parse_args(cdef);
+ 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 parse(list<cmd> *cmds)
+int main(int argc, char **argv)
{
- for (;;)
+ if (argc <= 1 || argc > 1 && !strcmp(argv[1], "--help"))
{
- token_type t = next_token();
- if (t != TOK_IDENT)
- {
- return_token();
- return;
- }
-
- cmd *c = parse_cmd();
- cmds->push_back(c);
+ help();
+ return 0;
}
-}
-/*** Main ***/
-
-int main(int argc, char **argv)
-{
if (argc != 4)
{
- fprintf(stderr, "Usage: pdfjam <commands> <input> <output>\n");
+ fprintf(stderr, "Usage: paperjam <commands> <input> <output>\n");
return 1;
}
- list<cmd> cmds;
- in_pos = argv[1];
- parse(&cmds);
+ debug_mode = 100;
+
+ list<cmd *> cmds;
+ parse(argv[1], cmds);
+
+ try
+ {
+ process(cmds, argv[2], argv[3]);
+ }
+ catch (exception& e)
+ {
+ die("%s", e.what());
+ }
return 0;
}