]> mj.ucw.cz Git - paperjam.git/commitdiff
Partial nup
authorMartin Mares <mj@ucw.cz>
Wed, 4 Apr 2018 20:49:59 +0000 (22:49 +0200)
committerMartin Mares <mj@ucw.cz>
Wed, 4 Apr 2018 20:49:59 +0000 (22:49 +0200)
TODO
cmds.cc
jam.h

diff --git a/TODO b/TODO
index c958ad4daa0f9a434f85c36a707fb88e58c08a2f..611462bf0c184967f45ecc3a876feafaf202b106 100644 (file)
--- a/TODO
+++ b/TODO
@@ -64,13 +64,13 @@ duplex
 #      - paper size + margins
 #      - scale + margins
 nup(hnum, vnum)
-       by=row/column   # Filling order (default: row)
+       by=rows/cols    # Filling order (default: rows)
+       by=tile         # Tile with copies of a single page
        paper / w / h   # Specify paper size, default=copy from 1st image
        + fit options (*margin, pos)
        crop            # Crop to image
        rotate=1        # Override rotation decision
        scale=1         # Override scaling factor
-       tile            # Tile with copies of a single page
        hspace / vspace / space # Distance between tiles
        + cropmarks?
 
diff --git a/cmds.cc b/cmds.cc
index b12595e8b5822743da0a3362a80e9a1e7632f734..7157a05ec67cdfd344debde9e9ff6b4be8a3f844 100644 (file)
--- a/cmds.cc
+++ b/cmds.cc
@@ -191,6 +191,10 @@ public:
       t = c->arg("t" + sx)->as_double(v);
       b = c->arg("b" + sx)->as_double(v);
     }
+  bool may_shrink(BBox *bb)
+    {
+      return (bb->width() > l+r && bb->height() > t+b);
+    }
   void shrink_box(BBox *bb)
     {
       bb->x_min += l;
@@ -839,6 +843,285 @@ static const arg_def book_args[] = {
   { NULL,      0 }
 };
 
+/*** nup ***/
+
+struct nup_state {
+  int rows, cols;
+  double tile_w, tile_h;
+  double paper_w, paper_h;
+  double fill_factor;
+  double scale;
+};
+
+class nup_cmd : public cmd_exec {
+  // Parameters
+  int grid_n, grid_m, num_tiles;
+  enum {
+    BY_ROWS,
+    BY_COLS,
+    BY_TILE,
+  } fill_by;
+  bool crop;
+  int rotate;
+  double scale;
+  paper_spec paper;
+  margin_spec marg;
+  pos_spec pos;
+  double hspace, vspace;
+
+  // Processing state
+  page *process_single(vector<page *> &in);
+  void find_config(vector<page *> &in, BBox *page_boxes);
+  void try_config(nup_state &st);
+  nup_state best;
+  bool found_solution;
+
+public:
+  nup_cmd(cmd *c) : paper(c), marg(c, "margin", "margin"), pos(c)
+    {
+      grid_n = c->arg("n")->as_int(0);
+      grid_m = c->arg("m")->as_int(0);
+      if (grid_n > 0 && grid_m > 0)
+       num_tiles = grid_n * grid_m;
+      else if (grid_n > 0 && !grid_m)
+       num_tiles = grid_n;
+      else
+       die("Grid size must be at least 1x1");
+
+      const string by = c->arg("by")->as_string("rows");
+      if (by == "rows" || by == "row" || by == "r")
+       fill_by = BY_ROWS;
+      else if (by == "cols" || by == "cols" || by == "c")
+       fill_by = BY_COLS;
+      else if (by == "tile" || by == "t")
+       fill_by = BY_TILE;
+      else
+       die("Parameter \"by\" must be rows/cols/tile");
+
+      crop = c->arg("crop")->as_int(0);
+      rotate = c->arg("rotate")->as_int(-1);
+      scale = c->arg("scale")->as_double(0);
+
+      double space = c->arg("space")->as_double(0);
+      hspace = c->arg("hspace")->as_double(space);
+      vspace = c->arg("vspace")->as_double(space);
+
+      if (!is_zero(scale) && (!is_zero(paper.w) || rotate >= 0))
+       die("When nup is used with explicit scaling, paper size nor rotation may be given");
+      if (!is_zero(scale) && !grid_m)
+       die("When nup is used with explicit scaling, both grid sizes must be given");
+    }
+
+  vector<page *> process(vector<page *> &pages) override;
+};
+
+vector<page *> nup_cmd::process(vector<page *> &pages)
+{
+  vector<page *> out;
+  int i = 0;
+
+  while (i < (int) pages.size())
+    {
+      vector<page *> in;
+      if (fill_by == BY_TILE)
+       {
+         for (int j=0; j<num_tiles; j++)
+           in.push_back(pages[i]);
+         i++;
+       }
+      else
+       {
+         for (int j=0; j<num_tiles; j++)
+           {
+             if (i < (int) pages.size())
+               in.push_back(pages[i]);
+             else
+               in.push_back(new empty_page(in[0]->width, in[0]->height));
+             i++;
+           }
+       }
+      out.push_back(process_single(in));
+    }
+
+  return out;
+}
+
+void nup_cmd::try_config(nup_state &st)
+{
+  BBox window(st.paper_w, st.paper_h);
+  if (!marg.may_shrink(&window))
+    return;
+  marg.shrink_box(&window);
+  window.x_max -= (st.cols - 1) * hspace;
+  window.y_max -= (st.rows - 1) * vspace;
+  if (window.width() < 0 || window.height() < 0)
+    return;
+
+  BBox image(st.cols * st.tile_w, st.rows * st.tile_h);
+  st.scale = scale_to_fit(image, window);
+  st.fill_factor = (st.scale*image.width() * st.scale*image.height()) / (st.paper_w * st.paper_h);
+
+  debug("Try: %dx%d on %.3f x %.3f => scale %.3f, fill %.6f",
+    st.cols, st.rows,
+    st.paper_w, st.paper_h,
+    st.scale, st.fill_factor);
+
+  if (!found_solution || best.fill_factor < st.fill_factor)
+    {
+      found_solution = true;
+      best = st;
+    }
+}
+
+void nup_cmd::find_config(vector<page *> &in, BBox *page_boxes)
+{
+  nup_state st;
+
+  // Determine tile size
+  st.tile_w = st.tile_h = 0;
+  for (int i=0; i<num_tiles; i++)
+    {
+      if (crop)
+       page_boxes[i] = in[i]->bbox;
+      else
+       page_boxes[i] = BBox(in[i]->width, in[i]->height);
+      st.tile_w = max(st.tile_w, page_boxes[i].width());
+      st.tile_h = max(st.tile_h, page_boxes[i].height());
+    }
+  debug("NUP: %d tiles of size %.3f x %.3f", num_tiles, st.tile_w, st.tile_h);
+
+  // Try all possible configurations of tiles
+  found_solution = false;
+  if (!is_zero(scale))
+    {
+      // If explicit scaling is requested, we choose page size ourselves,
+      // so there is just one configuration.
+      st.rows = grid_n;
+      st.cols = grid_m;
+      st.paper_w = marg.l + st.cols*st.tile_w + (st.cols-1)*hspace + marg.r;
+      st.paper_h = marg.t + st.rows*st.tile_h + (st.rows-1)*vspace + marg.b;
+      try_config(st);
+    }
+  else
+    {
+      // Page size is fixed (either given or copied from the source pages),
+      // but we can have freedom to rotate and/or to choose grid size.
+      for (int rot=0; rot<=1; rot++)
+       {
+         if (rotate >= 0 && rot != rotate)
+           continue;
+
+         // Establish paper size
+         if (!is_zero(paper.w))
+           {
+             st.paper_w = paper.w;
+             st.paper_h = paper.h;
+           }
+         else
+           {
+             st.paper_w = in[0]->width;
+             st.paper_h = in[0]->height;
+           }
+         if (rot)
+           swap(st.paper_w, st.paper_h);
+
+         // Try grid sizes
+         if (grid_m)
+           {
+             st.rows = grid_n;
+             st.cols = grid_m;
+             try_config(st);
+           }
+         else
+           {
+             for (int r=1; r<=grid_n; r++)
+               if (!(grid_n % r))
+                 {
+                   st.rows = r;
+                   st.cols = grid_n / r;
+                   try_config(st);
+                 }
+           }
+       }
+    }
+
+  if (!found_solution)
+    die("Nup did not find a feasible solution");
+}
+
+class nup_page : public page {
+public:
+  vector<page *> orig_pages;
+  vector<pdf_matrix> xforms;
+  void render(out_context *out, pdf_matrix xform) override;
+  nup_page(nup_state &st) : page(st.paper_w, st.paper_h) { }
+};
+
+void nup_page::render(out_context *out, pdf_matrix parent_xform)
+{
+  for (int i=0; i < (int) orig_pages.size(); i++)
+    orig_pages[i]->render(out, xforms[i] * parent_xform);
+}
+
+page *nup_cmd::process_single(vector<page *> &in)
+{
+  BBox page_boxes[num_tiles];
+  find_config(in, page_boxes);
+
+  // Construct transform from paper to window with tiles
+  BBox paper_box(best.paper_w, best.paper_h);
+  marg.shrink_box(&paper_box);
+  BBox tile_box(best.cols * best.scale * best.tile_w + (best.cols-1) * hspace,
+                best.rows * best.scale * best.tile_h + (best.rows-1) * vspace);
+  pdf_matrix place_xform = pos.place(tile_box, paper_box);
+
+  nup_page *p = new nup_page(best);
+  p->bbox = tile_box;
+  p->bbox.transform(place_xform);
+
+  for (int i=0; i<num_tiles; i++)
+    {
+      int r, c;
+      if (fill_by == BY_ROWS || fill_by == BY_TILE)
+       {
+         r = i / best.cols;
+         c = i % best.cols;
+       }
+      else
+       {
+         c = i / best.rows;
+         r = i % best.rows;
+       }
+
+      pdf_matrix m;
+      m.shift(-page_boxes[i].x_min, -page_boxes[i].y_min);
+      m.scale(best.scale);
+      m.shift(c * (best.scale*best.tile_w + hspace), (best.rows-1-r) * (best.scale*best.tile_h + vspace));
+
+      p->orig_pages.push_back(in[i]);
+      p->xforms.push_back(m * place_xform);
+    }
+
+  return p;
+}
+
+static const arg_def nup_args[] = {
+  { "n",       AT_INT | AT_POSITIONAL | AT_MANDATORY },
+  { "m",       AT_INT | AT_POSITIONAL },
+  { "by",      AT_STRING },
+  { "crop",    AT_SWITCH },
+  { "rotate",  AT_SWITCH },
+  { "scale",   AT_DOUBLE },
+  PAPER_ARGS,
+  MARGIN_ARGS1_NAMED("margin"),
+  MARGIN_ARGS2("margin"),
+  POS_ARGS,
+  { "space",   AT_DIMEN },
+  { "hspace",  AT_DIMEN },
+  { "vspace",  AT_DIMEN },
+  { NULL,      0 }
+};
+
 /*** Command table ***/
 
 template<typename T> cmd_exec *ctor(cmd *c) { return new T(c); }
@@ -861,5 +1144,6 @@ const cmd_def cmd_table[] = {
   { "margins", margins_args,   0,      &ctor<margins_cmd>      },
   { "add-blank",add_blank_args,        0,      &ctor<add_blank_cmd>    },
   { "book",    book_args,      0,      &ctor<book_cmd>         },
+  { "nup",     nup_args,       0,      &ctor<nup_cmd>          },
   { NULL,      NULL,           0,      NULL    }
 };
diff --git a/jam.h b/jam.h
index 31510d8c2b5f3469d3e6ab831919df33b92d231a..0653c2506904286e94449c5ed8c64ab8857f69d2 100644 (file)
--- a/jam.h
+++ b/jam.h
@@ -19,7 +19,7 @@ typedef unsigned int uint;
 
 static inline bool is_zero(double z)
 {
-  return fabs(z) < 0.001;
+  return fabs(z) < 1e-6;
 }
 
 static inline bool is_equal(double x, double y)