]> mj.ucw.cz Git - paperjam.git/blobdiff - pdf.cc
TODO: a4r paper
[paperjam.git] / pdf.cc
diff --git a/pdf.cc b/pdf.cc
index cf281f50dcfcb6191e608cce842521bb10bbc7e5..9f8dc12bcd5e94c6d620f6d81733e81c9182ea93 100644 (file)
--- a/pdf.cc
+++ b/pdf.cc
@@ -1,12 +1,13 @@
 /*
  *     PaperJam -- Low-level handling of PDFs
  *
- *     (c) 2018 Martin Mares <mj@ucw.cz>
+ *     (c) 2018--2022 Martin Mares <mj@ucw.cz>
  */
 
 #include <cassert>
 #include <cstdlib>
 #include <cstdio>
+#include <cstring>
 #include <unistd.h>
 #include <sys/wait.h>
 
@@ -30,7 +31,9 @@ class in_page : public page {
 public:
   BBox media_box;
   void render(out_context *out, pdf_matrix xform);
+  void debug_dump() { debug("Input page %d", index); }
   in_page(QPDFObjectHandle inpg, int idx);
+  int get_rotate();
 };
 
 in_page::in_page(QPDFObjectHandle inpg, int idx)
@@ -62,7 +65,7 @@ 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)) );
+    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);
 
@@ -73,16 +76,67 @@ void in_page::render(out_context *out, pdf_matrix xform)
   out->contents += "q " + m.to_string() + " cm " + xobj_res + " Do Q ";
 }
 
+int in_page::get_rotate()
+{
+  QPDFObjectHandle rotate = pdf_page.getKey("/Rotate");
+  if (rotate.isNull())
+    return 0;
+  else if (rotate.isInteger())
+    {
+      long long deg = rotate.getIntValue();
+      if (deg < 0 || deg >= 360 || deg % 90)
+       {
+         warn("Page #%d: /Rotate must be 0, 90, 180 or 270", index);
+         return 0;
+       }
+      else
+       return deg;
+    }
+  else
+    {
+      warn("Page #%d: /Rotate is not an integer", index);
+      return 0;
+    }
+}
+
 void debug_pages(vector<page *> &pages)
 {
-  if (!debug_mode)
+  if (!debug_level)
     return;
 
   for (auto pg: pages)
-    debug("Page #%d: media[%.3f %.3f] image[%.3f %.3f %.3f %.3f]",
-      pg->index,
-      pg->width, pg->height,
-      pg->image_box.x_min, pg->image_box.y_min, pg->image_box.x_max, pg->image_box.y_max);
+    {
+      debug("Page #%d: media[%.3f %.3f] image[%.3f %.3f %.3f %.3f][%.3f %.3f]",
+       pg->index,
+       pg->width, pg->height,
+       pg->image_box.x_min, pg->image_box.y_min, pg->image_box.x_max, pg->image_box.y_max,
+       pg->image_box.width(), pg->image_box.height());
+      if (debug_level > 2)
+       {
+         debug_indent += 4;
+         pg->debug_dump();
+         debug_indent -= 4;
+       }
+    }
+}
+
+static vector<page *> apply_input_xforms(vector<page *> in_pages)
+{
+  vector<page *> out_pages;
+
+  for (auto pg: in_pages)
+    {
+      in_page * in_pg = dynamic_cast<in_page *>(pg);
+      if (in_pg)
+       {
+         int deg = in_pg->get_rotate();
+         if (deg)
+           pg = new xform_page(pg, "/Rotate", pdf_rotation_matrix(deg, pg->width, pg->height));
+       }
+      out_pages.push_back(pg);
+    }
+
+  return out_pages;
 }
 
 vector<page *> run_command_list(list<cmd *> &cmds, vector<page *> &pages)
@@ -94,7 +148,14 @@ vector<page *> run_command_list(list<cmd *> &cmds, vector<page *> &pages)
     {
       debug("# Executing %s", c->def->name);
       debug_indent += 4;
-      pages = c->exec->process(pages);
+      try
+       {
+         pages = c->exec->process(pages);
+       }
+      catch (exception &e)
+       {
+         die("Error in %s: %s", c->def->name, e.what());
+       }
       debug_indent -= 4;
       debug_pages(pages);
     }
@@ -102,8 +163,35 @@ vector<page *> run_command_list(list<cmd *> &cmds, vector<page *> &pages)
   return pages;
 }
 
+static void make_info_dict()
+{
+  // Create info dictionary if it did not exist yet
+  QPDFObjectHandle trailer = out_pdf.getTrailer();
+  QPDFObjectHandle info = trailer.getKey("/Info");
+  if (info.isNull())
+    {
+      info = QPDFObjectHandle::newDictionary();
+      trailer.replaceKey("/Info", info);
+    }
+  else
+    assert(info.isDictionary());
+
+  info.replaceKey("/Producer", unicode_string("PaperJam"));
+
+  // Copy entries from the source file's info dictionary
+  QPDFObjectHandle orig_trailer = in_pdf.getTrailer();
+  QPDFObjectHandle orig_info = orig_trailer.getKey("/Info");
+  if (!orig_info.isNull())
+    {
+      const string to_copy[] = { "/Title", "/Author", "/Subject", "/Keywords", "/Creator", "/CreationDate" };
+      for (string key: to_copy)
+       info.replaceOrRemoveKey(key, orig_info.getKey(key));
+    }
+}
+
 void process(list<cmd *> &cmds)
 {
+  debug("### Reading input");
   in_pdf.processFile(in_name);
   in_pdf.pushInheritedAttributesToPage();
   out_pdf.emptyPDF();
@@ -120,15 +208,34 @@ void process(list<cmd *> &cmds)
   if (recalc_bbox)
     do_recalc_bbox(pages, in_name);
 
+  if (!no_auto_transforms)
+    {
+      debug("### Applying input transforms");
+      pages = apply_input_xforms(pages);
+    }
+
+  debug("### Running commands");
   pages = run_command_list(cmds, pages);
 
+  debug("### Writing output");
+  int out_page = 0;
   for (auto pg: pages)
     {
+      ++out_page;
+      if (debug_level > 1)
+       {
+         debug("Page #%d", out_page);
+         debug_indent += 4;
+         pg->debug_dump();
+         debug_indent -= 4;
+       }
+
       out_context out;
+      out.pdf = &out_pdf;
       out.resources = QPDFObjectHandle::newDictionary();
       out.resources.replaceKey("/ProcSet", QPDFObjectHandle::parse("[/PDF]"));
       out.xobjects = QPDFObjectHandle::newDictionary();
-      out.resources.replaceKey("/XObject", out.xobjects);
+      out.egstates = QPDFObjectHandle::newDictionary();
       pg->render(&out, pdf_matrix());
 
       QPDFObjectHandle contents = QPDFObjectHandle::newStream(&out_pdf, out.contents);
@@ -140,26 +247,21 @@ void process(list<cmd *> &cmds)
       // FIXME:
       // out_page.replaceKey("/CropBox", pg->image_box.to_array());
       out_page.replaceKey("/Contents", contents);
+      if (!out.xobjects.getKeys().empty())
+       out.resources.replaceKey("/XObject", out.xobjects);
+      if (!out.egstates.getKeys().empty())
+       out.resources.replaceKey("/ExtGState", out.egstates);
       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"));
+  make_info_dict();
 
   // Write the output file
   QPDFWriter writer(out_pdf, out_name);
   writer.write();
+  debug("### Done");
 }
 
 /*** Re-calculation of bboxes ***/
@@ -229,3 +331,32 @@ static void do_recalc_bbox(vector<page *> &pages, const char *in_name)
   for (size_t i=0; i<pages.size(); i++)
     pages[i]->image_box = bboxes[i];
 }
+
+// Transformed page
+
+xform_page::xform_page(page *p, const char *desc, pdf_matrix xf)
+{
+  orig_page = p;
+  index = p->index;
+  description = desc;
+  xform = xf;
+
+  BBox media(p->width, p->height);
+  media.transform(xf);
+  width = media.width();
+  height = media.height();
+
+  image_box = p->image_box;
+  image_box.transform(xf);
+}
+
+void xform_page::debug_dump()
+{
+  debug("Transform (%s): [%s]", description, xform.to_string().c_str());
+  orig_page->debug_dump();
+}
+
+void xform_page::render(out_context *out, pdf_matrix parent_xform)
+{
+  orig_page->render(out, xform * parent_xform);
+}