]> mj.ucw.cz Git - paperjam.git/blobdiff - paperjam.cc
Flip and switch parameters
[paperjam.git] / paperjam.cc
index 6ff37d1757ab8a4fe120b41a8f1d184c3e819500..c8e107181d7ad97b1b1bdb195c20e45405a1b6eb 100644 (file)
 #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;
 }