]> mj.ucw.cz Git - paperjam.git/commitdiff
Cropmarks
authorMartin Mares <mj@ucw.cz>
Fri, 6 Apr 2018 19:08:19 +0000 (21:08 +0200)
committerMartin Mares <mj@ucw.cz>
Fri, 6 Apr 2018 19:08:19 +0000 (21:08 +0200)
TODO
cmds.cc
jam.h
paperjam.cc
pdf-tools.cc
pdf-tools.h
pdf.cc

diff --git a/TODO b/TODO
index a1ce8399ec5615685ca20e6277cacfd3ce1f896b..b43e5987a53b71b2cef835a85c40bbbb6a735267 100644 (file)
--- a/TODO
+++ b/TODO
@@ -2,6 +2,8 @@
 - What if an input page specifies /Rotate?
 - Better error messages from instantiation
 - "-f" switch
+- Help
+- Cropmarks: settable colors
 
 | # Position bbox on a new paper
 | paper("a4")
@@ -35,7 +37,7 @@ cropmarks
        mark=in         # In-pointing half-crosses
        pen=1pt         # Line width
        len=5mm         # Cross arm length
-       dist=5mm        # Distance from border
+       offset=5mm      # Distance from border
 
 | book
 |      signature=<n>
@@ -72,7 +74,7 @@ duplex
 |      rotate=1        # Override rotation decision
 |      scale=1         # Override scaling factor
 |      hspace / vspace / space # Distance between tiles
-       + cropmarks?
+       cmark, cpen, clen, coffset      # Cropmark parameters
 
 # Send pages to multiple pipes and merge their results
 mix { ..., ..., ...}
diff --git a/cmds.cc b/cmds.cc
index aa68e2e97bd38e9cd679c2db36fd5fed24c0c9b9..67b37366d0495760c8c123fc86f0aabdb11082f0 100644 (file)
--- a/cmds.cc
+++ b/cmds.cc
@@ -227,6 +227,130 @@ public:
   { "t" sx,    AT_DIMEN },             \
   { "b" sx,    AT_DIMEN }
 
+// Cropmarks
+
+class cropmark_spec {
+  enum mark_type {
+    MARK_NONE,
+    MARK_BOX,
+    MARK_CROSS,
+    MARK_OUT,
+    MARK_IN,
+  } type;
+  double pen_width;
+  double arm_length;
+  double offset;
+  string crop_cross(double x, double y, uint mask);
+  QPDFObjectHandle egstate;
+public:
+  cropmark_spec(cmd *c, string prefix="", string def_type="cross")
+    {
+      string t = c->arg(prefix + "mark")->as_string(def_type);
+      if (t == "none")
+       type = MARK_NONE;
+      else if (t == "box")
+       type = MARK_BOX;
+      else if (t == "cross")
+       type = MARK_CROSS;
+      else if (t == "in")
+       type = MARK_IN;
+      else if (t == "out")
+       type = MARK_OUT;
+      else
+       die("Invalid cropmark type %s", t.c_str());
+
+      pen_width = c->arg(prefix + "pen")->as_double(1);
+      arm_length = c->arg(prefix + "len")->as_double(5*mm);
+      offset = c->arg(prefix + "offset")->as_double(0);
+      egstate = QPDFObjectHandle::newNull();
+    }
+  string pdf_stream(out_context *out, BBox &box, pdf_matrix &xform);
+};
+
+string cropmark_spec::crop_cross(double x, double y, uint mask)
+{
+  string s = "";
+
+  for (uint i=0; i<4; i++)
+    if (mask & (1U << i))
+      {
+       double x2 = x, y2 = y;
+       switch (i)
+         {
+         case 3: x2 -= arm_length; break;
+         case 2: x2 += arm_length; break;
+         case 1: y2 += arm_length; break;
+         case 0: y2 -= arm_length; break;
+         }
+       s += pdf_coord(x) + " " + pdf_coord(y) + " m " + pdf_coord(x2) + " " + pdf_coord(y2) + " l S ";
+      }
+
+  return s;
+}
+
+string cropmark_spec::pdf_stream(out_context *out, BBox &box, pdf_matrix &xform)
+{
+  string s = "q ";
+  s += "0 0 0 RG ";
+  s += xform.to_string() + " cm ";
+
+  if (egstate.isNull())
+    {
+      auto egs = QPDFObjectHandle::newDictionary();
+      egs.replaceKey("/Type", QPDFObjectHandle::newName("/ExtGState"));
+      egs.replaceKey("/LW", QPDFObjectHandle::newReal(pen_width, 1));
+      egs.replaceKey("/LC", QPDFObjectHandle::newInteger(2));
+      egs.replaceKey("/LJ", QPDFObjectHandle::newInteger(0));
+      egstate = out->pdf->makeIndirectObject(egs);
+    }
+
+  string egs_res = out->new_resource("GS");
+  out->egstates.replaceKey(egs_res, egstate);
+  s += egs_res + " gs ";
+
+  BBox b = box;
+  b.x_min -= offset;
+  b.x_max += offset;
+  b.y_min -= offset;
+  b.y_max += offset;
+
+  switch (type)
+    {
+    case MARK_NONE:
+      break;
+    case MARK_BOX:
+       s += b.to_rect() + " re S ";
+       break;
+    case MARK_CROSS:
+       s += crop_cross(b.x_min, b.y_min, 0b1111);
+       s += crop_cross(b.x_max, b.y_min, 0b1111);
+       s += crop_cross(b.x_max, b.y_max, 0b1111);
+       s += crop_cross(b.x_min, b.y_max, 0b1111);
+       break;
+    case MARK_IN:
+       s += crop_cross(b.x_min, b.y_min, 0b0110);
+       s += crop_cross(b.x_max, b.y_min, 0b1010);
+       s += crop_cross(b.x_max, b.y_max, 0b1001);
+       s += crop_cross(b.x_min, b.y_max, 0b0101);
+       break;
+    case MARK_OUT:
+       s += crop_cross(b.x_min, b.y_min, 0b1001);
+       s += crop_cross(b.x_max, b.y_min, 0b0101);
+       s += crop_cross(b.x_max, b.y_max, 0b0110);
+       s += crop_cross(b.x_min, b.y_max, 0b1010);
+       break;
+    }
+
+  s += "Q ";
+  return s;
+}
+
+#define CROPMARK_ARGS(px)              \
+  { px "mark", AT_STRING },            \
+  { px "pen",  AT_DIMEN },             \
+  { px "len",  AT_DIMEN },             \
+  { px "offset",AT_DIMEN }
+
 // Scaling preserving aspect ratio
 
 double scale_to_fit(BBox &from, BBox &to)
@@ -530,12 +654,6 @@ static const arg_def modulo_args[] = {
 
 /*** draw-bbox ***/
 
-class draw_bbox_cmd : public cmd_exec {
-public:
-  draw_bbox_cmd(cmd *c UNUSED) { }
-  vector<page *> process(vector<page *> &pages) override;
-};
-
 class draw_bbox_page : public page {
   page *orig_page;
 public:
@@ -543,6 +661,15 @@ public:
   draw_bbox_page(page *p) : page(p) { orig_page = p; }
 };
 
+class draw_bbox_cmd : public cmd_exec_simple {
+public:
+  draw_bbox_cmd(cmd *c UNUSED) { }
+  page *process_page(page *p) override
+    {
+      return new draw_bbox_page(p);
+    }
+};
+
 void draw_bbox_page::render(out_context *out, pdf_matrix xform)
 {
   orig_page->render(out, xform);
@@ -554,14 +681,6 @@ void draw_bbox_page::render(out_context *out, pdf_matrix xform)
      "Q ";
 }
 
-vector<page *> draw_bbox_cmd::process(vector<page *> &pages)
-{
-  vector<page *> out;
-  for (auto p: pages)
-    out.push_back(new draw_bbox_page(p));
-  return out;
-}
-
 /*** merge ***/
 
 class merge_cmd : public cmd_exec {
@@ -1158,6 +1277,36 @@ static const arg_def nup_args[] = {
   { NULL,      0 }
 };
 
+/*** cropmarks ***/
+
+class cropmarks_page : public page {
+  page *orig_page;
+  cropmark_spec *cm;
+public:
+  void render(out_context *out, pdf_matrix xform) override
+    {
+      orig_page->render(out, xform);
+      out->contents += cm->pdf_stream(out, image_box, xform);
+    }
+  cropmarks_page(page *p, cropmark_spec *cms) : page(p), orig_page(p), cm(cms) { }
+};
+
+class cropmarks_cmd : public cmd_exec_simple {
+  cropmark_spec cm;
+  page *process_page(page *p) override
+    {
+      return new cropmarks_page(p, &cm);
+    }
+
+public:
+  cropmarks_cmd(cmd *c) : cm(c) { }
+};
+
+static const arg_def cropmarks_args[] = {
+  CROPMARK_ARGS(""),
+  { NULL,      0 }
+};
+
 /*** Command table ***/
 
 template<typename T> cmd_exec *ctor(cmd *c) { return new T(c); }
@@ -1181,5 +1330,6 @@ const cmd_def cmd_table[] = {
   { "add-blank",add_blank_args,        0,      &ctor<add_blank_cmd>    },
   { "book",    book_args,      0,      &ctor<book_cmd>         },
   { "nup",     nup_args,       0,      &ctor<nup_cmd>          },
+  { "cropmarks",cropmarks_args,        0,      &ctor<cropmarks_cmd>    },
   { NULL,      NULL,           0,      NULL    }
 };
diff --git a/jam.h b/jam.h
index e7e2099c7fb9ac70aeb9f7c70e81174c5b604165..10f547ac55ad7dfe9c58575204a04e9818446328 100644 (file)
--- a/jam.h
+++ b/jam.h
@@ -62,8 +62,10 @@ public:
 extern arg_val null_arg;
 
 struct out_context {
+  QPDF *pdf;
   QPDFObjectHandle resources;
   QPDFObjectHandle xobjects;
+  QPDFObjectHandle egstates;
   string contents;
   int res_cnt;
   string new_resource(const string type);
@@ -71,7 +73,7 @@ struct out_context {
 };
 
 struct page {
-  int index;
+  int index;           // Position in the source PDF, 0 for synthesized pages
   double width;                // Physical dimensions of media
   double height;
   BBox image_box;      // Bounds useful contents
index 284cf5fe0dc26e1bf9d2753d686d0e6923b14405..5aa4c6139fd76814a3d6297ced7a339ebd1201eb 100644 (file)
@@ -79,7 +79,7 @@ Options:\n\
 -b, --bbox             Recalculate bounding boxes\n\
 -d, --debug            Show debugging messages\n\
 \n\
-Commands: (FIXME)\n\
+Commands:\n\
 ");
   parser_help();
 }
index 29e79bff9f835e4ca10eb595883f7cca09eaee6c..2077969cdac6d5ab10006dda5b1b8b28db55a589 100644 (file)
@@ -22,9 +22,7 @@ string pdf_matrix::to_string() {
        for (int i=0; i<6; i++) {
                if (i)
                        s += " ";
-               char buf[16];
-               snprintf(buf, sizeof(buf), "%.3f", m[i]);
-               s += buf;
+               s += pdf_coord(m[i], 0);
        }
        return s;
 }
@@ -43,9 +41,11 @@ QPDFObjectHandle BBox::to_array()
 
 string BBox::to_rect()
 {
-       char buf[64];
-       snprintf(buf, sizeof(buf), "%.1f %.1f %.1f %.1f", x_min, y_min, width(), height());
-       return string(buf);
+       return
+               pdf_coord(x_min) + " " +
+               pdf_coord(y_min) + " " +
+               pdf_coord(width()) + " " +
+               pdf_coord(height());
 }
 
 bool BBox::parse(QPDFObjectHandle h)
@@ -193,3 +193,12 @@ QPDFObjectHandle page_to_xobject(QPDF *out, QPDFObjectHandle page)
        xo_stream.replaceStreamData(ph, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
        return xo_stream;
 }
+
+/*** Formatting of coordinates ***/
+
+string pdf_coord(double x, uint digits)
+{
+  char buf[16];
+  snprintf(buf, sizeof(buf), "%.*f", digits, x);
+  return buf;
+}
index 132062d16135d2293bd7b6581ab6d3ec6c1a386f..844d4b5b4aa26d9ce8602260ebff2ccaa1300bea 100644 (file)
@@ -138,5 +138,6 @@ private:
 
 QPDFObjectHandle unicode_string(std::string s);
 QPDFObjectHandle page_to_xobject(QPDF *out, QPDFObjectHandle page);
+string pdf_coord(double x, uint digits=1);
 
 #endif
diff --git a/pdf.cc b/pdf.cc
index cf281f50dcfcb6191e608cce842521bb10bbc7e5..0c9b27b491be88b3b248d4901b692e2820bfd2a9 100644 (file)
--- a/pdf.cc
+++ b/pdf.cc
@@ -62,7 +62,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);
 
@@ -125,10 +125,11 @@ void process(list<cmd *> &cmds)
   for (auto pg: pages)
     {
       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,6 +141,10 @@ 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);
     }