]> mj.ucw.cz Git - paperjam.git/blob - cmds.cc
page->bbox renamed to page->image_box
[paperjam.git] / cmds.cc
1 /*
2  *      PaperJam -- Commands
3  *
4  *      (c) 2018 Martin Mares <mj@ucw.cz>
5  */
6
7 #include <cassert>
8 #include <cstdlib>
9 #include <cstdio>
10 #include <paper.h>
11
12 #include "jam.h"
13
14 /*** null ***/
15
16 class null_cmd : public cmd_exec {
17 public:
18   null_cmd(cmd *c UNUSED) { }
19   vector<page *> process(vector<page *> &pages) override { return pages; }
20 };
21
22 static const arg_def no_args[] = {
23   { NULL,       0 }
24 };
25
26 /*** Generic routines ***/
27
28 // Transformed page
29
30 class xform_page : public page {
31   page *orig_page;
32   pdf_matrix xform;
33 public:
34   void render(out_context *out, pdf_matrix xform) override;
35   xform_page(page *p, pdf_matrix xf);
36 };
37
38 xform_page::xform_page(page *p, pdf_matrix xf)
39 {
40   orig_page = p;
41   index = p->index;
42   xform = xf;
43
44   BBox media(p->width, p->height);
45   media.transform(xf);
46   width = media.width();
47   height = media.height();
48
49   image_box = p->image_box;
50   image_box.transform(xf);
51 }
52
53 void xform_page::render(out_context *out, pdf_matrix parent_xform)
54 {
55   orig_page->render(out, xform * parent_xform);
56 }
57
58 // Commands acting on individual pages
59
60 class cmd_exec_simple : public cmd_exec {
61   virtual page *process_page(page *p) = 0;
62   vector<page *> process(vector<page *> &pages) override;
63 };
64
65 vector<page *> cmd_exec_simple::process(vector<page *> &pages)
66 {
67   vector<page *> out;
68   for (auto p: pages)
69     out.push_back(process_page(p));
70   return out;
71 }
72
73 // Paper specifications
74
75 class paper_spec {
76 public:
77   double w, h;
78   paper_spec(cmd *c, bool maybe=true)
79     {
80       arg_val *aname = c->arg("paper");
81       arg_val *aw = c->arg("w");
82       arg_val *ah = c->arg("h");
83       if (!aname->given() && !aw->given() && !ah->given() && maybe)
84         {
85           w = h = 0;
86           return;
87         }
88       if (aw->given() != ah->given() || aname->given() == aw->given())
89         die("Either paper format name or width and height must be given");
90       if (aname->given())
91         {
92           const char *name = aname->as_string("").c_str();
93           const paper *pap = paperinfo(name);
94           if (!pap)
95             die("No paper called %s is known", name);
96           w = paperpswidth(pap);
97           h = paperpsheight(pap);
98         }
99       else
100         {
101           w = aw->as_double(0);
102           h = ah->as_double(0);
103         }
104     }
105 };
106
107 #define PAPER_ARGS \
108   { "paper",    AT_STRING | AT_POSITIONAL },    \
109   { "w",        AT_DIMEN },                     \
110   { "h",        AT_DIMEN }
111
112 // Position specification
113
114 class pos_spec {
115 public:
116   int h, v;
117   pos_spec() { v = h = 0; }
118   pos_spec(string s)
119     {
120       if (s.size() != 2)
121         die("Value of pos must have two characters");
122       if (s[0] == 't')
123         v = 1;
124       else if (s[0] == 'c')
125         v = 0;
126       else if (s[0] == 'b')
127         v = -1;
128       else
129         die("First character of pos must be t/c/b");
130       if (s[1] == 'l')
131         h = -1;
132       else if (s[1] == 'c')
133         h = 0;
134       else if (s[1] == 'r')
135         h = 1;
136       else
137         die("Second character of pos must be l/c/r");
138     }
139   pos_spec(cmd *c) : pos_spec(c->arg("pos")->as_string("cc")) { }
140   pdf_matrix place(BBox &inner, BBox &outer)
141     {
142       pdf_matrix m;
143       m.shift(-inner.x_min, -inner.y_min);
144       switch (h)
145         {
146         case -1:
147           break;
148         case 0:
149           m.shift((outer.width() - inner.width()) / 2, 0);
150           break;
151         case 1:
152           m.shift(outer.width() - inner.width(), 0);
153           break;
154         default:
155           abort();
156         }
157       switch (v)
158         {
159         case -1:
160           break;
161         case 0:
162           m.shift(0, (outer.height() - inner.height()) / 2);
163           break;
164         case 1:
165           m.shift(0, outer.height() - inner.height());
166           break;
167         default:
168           abort();
169         }
170       m.shift(outer.x_min, outer.y_min);
171       return m;
172     }
173 };
174
175 #define POS_ARGS \
176   { "pos",      AT_STRING }
177
178 // Margins
179
180 class margin_spec {
181 public:
182   double l, r, t, b;
183   margin_spec(cmd *c, string basic, string sx)
184     {
185       double m, h, v;
186       m = c->arg(basic)->as_double(0);
187       h = c->arg("h" + sx)->as_double(m);
188       v = c->arg("v" + sx)->as_double(m);
189       l = c->arg("l" + sx)->as_double(h);
190       r = c->arg("r" + sx)->as_double(h);
191       t = c->arg("t" + sx)->as_double(v);
192       b = c->arg("b" + sx)->as_double(v);
193     }
194   bool may_shrink(BBox *bb)
195     {
196       return (bb->width() > l+r && bb->height() > t+b);
197     }
198   void shrink_box(BBox *bb)
199     {
200       bb->x_min += l;
201       bb->x_max -= r;
202       bb->y_min += b;
203       bb->y_max -= t;
204       if (bb->x_min >= bb->x_max || bb->y_min >= bb->y_max)
205         die("Margins cannot be larger than the whole page");
206     }
207   void expand_box(BBox *bb)
208     {
209       bb->x_min -= l;
210       bb->x_max += r;
211       bb->y_min -= b;
212       bb->y_max += t;
213     }
214 };
215
216 #define MARGIN_ARGS1_NAMED(name)        \
217   { name,       AT_DIMEN }
218
219 #define MARGIN_ARGS1_POSNL(name)        \
220   { name,       AT_DIMEN | AT_POSITIONAL }
221
222 #define MARGIN_ARGS2(sx)                \
223   { "h" sx,     AT_DIMEN },             \
224   { "v" sx,     AT_DIMEN },             \
225   { "l" sx,     AT_DIMEN },             \
226   { "r" sx,     AT_DIMEN },             \
227   { "t" sx,     AT_DIMEN },             \
228   { "b" sx,     AT_DIMEN }
229
230 // Scaling preserving aspect ratio
231
232 double scale_to_fit(BBox &from, BBox &to)
233 {
234   double fw = from.width(), fh = from.height();
235   double tw = to.width(), th = to.height();
236   if (is_zero(fw) || is_zero(fh) || is_zero(tw) || is_zero(th))
237     return 1;
238   else
239     return min(tw/fw, th/fh);
240 }
241
242 /*** move ***/
243
244 class move_cmd : public cmd_exec_simple {
245   double x, y;
246 public:
247   move_cmd(cmd *c)
248     {
249       x = c->arg("x")->as_double(0);
250       y = c->arg("y")->as_double(0);
251     }
252   page *process_page(page *p) override
253     {
254       pdf_matrix m;
255       m.shift(x, y);
256       return new xform_page(p, m);
257     }
258 };
259
260 static const arg_def move_args[] = {
261   { "x",        AT_DIMEN | AT_MANDATORY | AT_POSITIONAL },
262   { "y",        AT_DIMEN | AT_MANDATORY | AT_POSITIONAL },
263   { NULL,       0 }
264 };
265
266 /*** scale ***/
267
268 class scale_cmd : public cmd_exec_simple {
269   double x_factor, y_factor;
270 public:
271   scale_cmd(cmd *c)
272     {
273       x_factor = c->arg("x")->as_double(1);
274       y_factor = c->arg("y")->as_double(x_factor);
275     }
276   page *process_page(page *p) override
277     {
278       pdf_matrix m;
279       m.scale(x_factor, y_factor);
280       return new xform_page(p, m);
281     }
282 };
283
284 static const arg_def scale_args[] = {
285   { "x",        AT_DOUBLE | AT_MANDATORY | AT_POSITIONAL },
286   { "y",        AT_DOUBLE | AT_POSITIONAL },
287   { NULL,       0 }
288 };
289
290 /*** rotate ***/
291
292 class rotate_cmd : public cmd_exec_simple {
293   int deg;
294 public:
295   rotate_cmd(cmd *c)
296     {
297       deg = c->arg("deg")->as_int(0) % 360;
298       if (deg < 0)
299         deg += 360;
300       if (deg % 90)
301         die("Rotate requires a multiple of 90 degrees");
302     }
303   page *process_page(page *p) override
304     {
305       pdf_matrix m;
306       switch (deg)
307         {
308         case 0:
309           break;
310         case 90:
311           m.rotate_deg(-90);
312           m.shift(0, p->width);
313           break;
314         case 180:
315           m.rotate_deg(180);
316           m.shift(p->width, p->height);
317           break;
318         case 270:
319           m.rotate_deg(90);
320           m.shift(p->height, 0);
321           break;
322         default:
323           abort();
324         }
325       return new xform_page(p, m);
326     }
327 };
328
329 static const arg_def rotate_args[] = {
330   { "angle",    AT_INT | AT_MANDATORY | AT_POSITIONAL },
331   { NULL,       0 }
332 };
333
334 /*** flip ***/
335
336 class flip_cmd : public cmd_exec_simple {
337   bool horizontal;
338   bool vertical;
339 public:
340   flip_cmd(cmd *c)
341     {
342       horizontal = c->arg("h")->as_int(0);
343       vertical = c->arg("v")->as_int(0);
344       if (!horizontal && !vertical)
345         die("Flip has no direction specified");
346     }
347   page *process_page(page *p) override
348     {
349       pdf_matrix m;
350       if (vertical)
351         {
352           m.scale(1, -1);
353           m.shift(0, p->height);
354         }
355       if (horizontal)
356         {
357           m.scale(-1, 1);
358           m.shift(p->width, 0);
359         }
360       return new xform_page(p, m);
361     }
362 };
363
364 static const arg_def flip_args[] = {
365   { "h",        AT_SWITCH },
366   { "v",        AT_SWITCH },
367   { NULL,       0 }
368 };
369
370 /*** select ***/
371
372 class select_cmd : public cmd_exec {
373   pipeline *pipe;
374 public:
375   select_cmd(cmd *c)
376     {
377       pipe = c->pipe;
378     }
379   vector<page *> process(vector<page *> &pages) override;
380 };
381
382 static int validate_page_index(vector<page *> &pages, int idx)
383 {
384   if (idx >= 1 && idx <= (int) pages.size())
385     return idx - 1;
386   if (idx <= -1 && idx >= (int) -pages.size())
387     return idx + pages.size();
388   die("Page index %d out of range", idx);
389 }
390
391 vector<page *> select_cmd::process(vector<page *> &pages)
392 {
393   vector<page *> out;
394   for (auto pb: pipe->branches)
395     {
396       vector<page *> selected;
397       for (auto ps: pb->selectors)
398         {
399           int f = validate_page_index(pages, ps.from);
400           int t = validate_page_index(pages, ps.to);
401           int step = (f <= t) ? 1 : -1;
402           for (int i=f; f<=t; f += step)
403             selected.push_back(pages[i]);
404         }
405       auto processed = run_command_list(pb->commands, selected);
406       for (auto p: processed)
407         out.push_back(p);
408     }
409   return out;
410 }
411
412 /*** apply ***/
413
414 class apply_cmd : public cmd_exec {
415   pipeline *pipe;
416 public:
417   apply_cmd(cmd *c)
418     {
419       pipe = c->pipe;
420     }
421   vector<page *> process(vector<page *> &pages) override;
422 };
423
424 static pipeline_branch *find_branch(pipeline *pipe, vector <page *> &pages, int idx)
425 {
426   for (auto pb: pipe->branches)
427     for (auto ps: pb->selectors)
428       {
429         int f = validate_page_index(pages, ps.from);
430         int t = validate_page_index(pages, ps.to);
431         if (f <= idx && idx <= t || t <= idx && idx <= f)
432           return pb;
433       }
434   return NULL;
435 }
436
437 vector<page *> apply_cmd::process(vector<page *> &pages)
438 {
439   vector<page *> out;
440
441   int cnt = 0;
442   for (auto p: pages)
443     {
444       pipeline_branch *pb = find_branch(pipe, pages, cnt);
445       if (pb)
446         {
447           vector<page *> tmp;
448           tmp.push_back(p);
449           auto processed = run_command_list(pb->commands, tmp);
450           for (auto q: processed)
451             out.push_back(q);
452         }
453       else
454         out.push_back(p);
455       cnt++;
456     }
457
458   return out;
459 }
460
461 /*** modulo ***/
462
463 class modulo_cmd : public cmd_exec {
464   pipeline *pipe;
465   int n;
466   bool half;
467 public:
468   modulo_cmd(cmd *c)
469     {
470       n = c->arg("n")->as_int(0);
471       if (n <= 0)
472         die("Modulo must have n > 0");
473       half = c->arg("half")->as_int(0);
474       pipe = c->pipe;
475     }
476   vector<page *> process(vector<page *> &pages) override;
477 };
478
479 vector<page *> modulo_cmd::process(vector<page *> &pages)
480 {
481   vector<page *> out;
482   int tuples = ((int) pages.size() + n - 1) / n;
483   int use_tuples = half ? tuples/2 : tuples;
484
485   for (int tuple=0; tuple < use_tuples; tuple++)
486     {
487       debug("# Tuple %d", tuple);
488       debug_indent += 4;
489       for (auto pb: pipe->branches)
490         {
491           vector<page *> tmp;
492           for (auto ps: pb->selectors)
493             {
494               int f = ps.from;
495               int t = ps.to;
496               int step = (f <= t) ? 1 : -1;
497               for (int i=f; i<=t; i += step)
498                 {
499                   int j;
500                   if (i > 0 && i <= n)
501                     j = tuple*n + i - 1;
502                   else if (i < 0 && i >= -n)
503                     j = (tuples-1-tuple)*n + (-i) - 1;
504                   else
505                     die("Modulo: invalid index %d", i);
506                   if (j < (int) pages.size())
507                     tmp.push_back(pages[j]);
508                   else
509                     {
510                       page *ref_page = pages[tuple*n];
511                       tmp.push_back(new empty_page(ref_page->width, ref_page->height));
512                     }
513                 }
514             }
515           auto processed = run_command_list(pb->commands, tmp);
516           for (auto q: processed)
517             out.push_back(q);
518         }
519       debug_indent -= 4;
520     }
521
522   return out;
523 }
524
525 static const arg_def modulo_args[] = {
526   { "n",        AT_INT | AT_MANDATORY | AT_POSITIONAL },
527   { "half",     AT_SWITCH },
528   { NULL,       0 }
529 };
530
531 /*** draw-bbox ***/
532
533 class draw_bbox_cmd : public cmd_exec {
534 public:
535   draw_bbox_cmd(cmd *c UNUSED) { }
536   vector<page *> process(vector<page *> &pages) override;
537 };
538
539 class draw_bbox_page : public page {
540   page *orig_page;
541 public:
542   void render(out_context *out, pdf_matrix xform) override;
543   draw_bbox_page(page *p) : page(p) { orig_page = p; }
544 };
545
546 void draw_bbox_page::render(out_context *out, pdf_matrix xform)
547 {
548   orig_page->render(out, xform);
549   out->contents +=
550      "q " +
551      xform.to_string() + " cm " +
552      "0 1 0 RG " +
553      image_box.to_rect() + " re S " +
554      "Q ";
555 }
556
557 vector<page *> draw_bbox_cmd::process(vector<page *> &pages)
558 {
559   vector<page *> out;
560   for (auto p: pages)
561     out.push_back(new draw_bbox_page(p));
562   return out;
563 }
564
565 /*** merge ***/
566
567 class merge_cmd : public cmd_exec {
568 public:
569   merge_cmd(cmd *c UNUSED) { }
570   vector<page *> process(vector<page *> &pages) override;
571 };
572
573 class merge_page : public page {
574   vector<page *> orig_pages;
575 public:
576   merge_page(vector<page *> &orig) : page(0, 0)
577     {
578       orig_pages = orig;
579       bool first = true;
580       for (auto p: orig)
581         {
582           if (first)
583             {
584               width = p->width;
585               height = p->height;
586               image_box = p->image_box;
587               first = false;
588             }
589           else
590             {
591               if (!is_equal(width, p->width) || !is_equal(height, p->height))
592                 die("All pages participating in a merge must have the same dimensions");
593               image_box.join(p->image_box);
594             }
595         }
596     }
597   void render(out_context *out, pdf_matrix xform) override
598     {
599       for (auto p: orig_pages)
600         p->render(out, xform);
601     }
602 };
603
604 vector<page *> merge_cmd::process(vector<page *> &pages)
605 {
606   vector<page *> out;
607   if (pages.size())
608     out.push_back(new merge_page(pages));
609   return out;
610 }
611
612 /*** paper ***/
613
614 class paper_cmd : public cmd_exec_simple {
615   paper_spec paper;
616   pos_spec pos;
617 public:
618   paper_cmd(cmd *c) : paper(c), pos(c) { }
619   page *process_page(page *p) override
620     {
621       BBox paper_box = BBox(paper.w, paper.h);
622       pdf_matrix xf = pos.place(p->image_box, paper_box);
623       page *q = new xform_page(p, xf);
624       q->width = paper.w;
625       q->height = paper.h;
626       return q;
627     }
628 };
629
630 static const arg_def paper_args[] = {
631   PAPER_ARGS,
632   POS_ARGS,
633   { NULL,       0 }
634 };
635
636 /*** scaleto ***/
637
638 class scaleto_cmd : public cmd_exec_simple {
639   paper_spec paper;
640   pos_spec pos;
641 public:
642   scaleto_cmd(cmd *c) : paper(c), pos(c) { }
643   page *process_page(page *p) override
644     {
645       BBox orig_box = BBox(p->width, p->height);
646       BBox paper_box = BBox(paper.w, paper.h);
647       pdf_matrix xf;
648       xf.scale(scale_to_fit(orig_box, paper_box));
649       orig_box.transform(xf);
650       xf.concat(pos.place(orig_box, paper_box));
651       page *q = new xform_page(p, xf);
652       q->width = paper.w;
653       q->height = paper.h;
654       return q;
655     }
656 };
657
658 static const arg_def scaleto_args[] = {
659   PAPER_ARGS,
660   POS_ARGS,
661   { NULL,       0 }
662 };
663
664 /*** fit ***/
665
666 class fit_cmd : public cmd_exec_simple {
667   paper_spec paper;
668   pos_spec pos;
669   margin_spec marg;
670 public:
671   fit_cmd(cmd *c) : paper(c, true), pos(c), marg(c, "margin", "margin") { }
672   page *process_page(page *p) override
673     {
674       pdf_matrix xf;
675       page *q;
676
677       if (!is_zero(paper.w) && !is_zero(paper.h))
678         {
679           // Paper given: scale image to fit paper
680           BBox orig_box = p->image_box;
681           BBox paper_box = BBox(paper.w, paper.h);
682           marg.shrink_box(&paper_box);
683           xf.scale(scale_to_fit(orig_box, paper_box));
684           orig_box.transform(xf);
685           xf.concat(pos.place(orig_box, paper_box));
686           q = new xform_page(p, xf);
687           q->width = paper.w;
688           q->height = paper.h;
689         }
690       else
691         {
692           // No paper given: adjust paper to fit image
693           xf.shift(-p->image_box.x_min, -p->image_box.y_min);
694           xf.shift(marg.l, marg.b);
695           q = new xform_page(p, xf);
696           q->width = p->image_box.width() + marg.l + marg.r;
697           q->height = p->image_box.height() + marg.t + marg.b;
698         }
699       return q;
700     }
701 };
702
703 static const arg_def fit_args[] = {
704   PAPER_ARGS,
705   POS_ARGS,
706   MARGIN_ARGS1_NAMED("margin"),
707   MARGIN_ARGS2("margin"),
708   { NULL,       0 }
709 };
710
711 /*** expand ***/
712
713 class expand_cmd : public cmd_exec_simple {
714   margin_spec marg;
715 public:
716   expand_cmd(cmd *c) : marg(c, "by", "") { }
717   page *process_page(page *p) override
718     {
719       pdf_matrix xf;
720       xf.shift(marg.l, marg.b);
721       page *q = new xform_page(p, xf);
722       q->width = p->width + marg.l + marg.r;
723       q->height = p->height + marg.t + marg.b;
724       if (q->width < 0.001 || q->height < 0.001)
725         die("Expansion must result in positive page dimensions");
726       return q;
727     }
728 };
729
730 static const arg_def expand_args[] = {
731   MARGIN_ARGS1_POSNL("by"),
732   MARGIN_ARGS2(""),
733   { NULL,       0 }
734 };
735
736 /*** margins ***/
737
738 class margins_cmd : public cmd_exec_simple {
739   margin_spec marg;
740 public:
741   margins_cmd(cmd *c) : marg(c, "size", "") { }
742   page *process_page(page *p) override
743     {
744       pdf_matrix xf;
745       xf.shift(-p->image_box.x_min, -p->image_box.y_min);
746       xf.shift(marg.l, marg.b);
747       page *q = new xform_page(p, xf);
748       q->width = p->image_box.width() + marg.l + marg.r;
749       q->height = p->image_box.height() + marg.t + marg.b;
750       if (q->width < 0.001 || q->height < 0.001)
751         die("Margins must result in positive page dimensions");
752       return q;
753     }
754 };
755
756 static const arg_def margins_args[] = {
757   MARGIN_ARGS1_POSNL("size"),
758   MARGIN_ARGS2(""),
759   { NULL,       0 }
760 };
761
762 /*** add-blank ***/
763
764 class add_blank_cmd : public cmd_exec {
765   int n;
766   paper_spec paper;
767 public:
768   add_blank_cmd(cmd *c) : paper(c, true)
769     {
770       n = c->arg("n")->as_int(1);
771     }
772   vector<page *> process(vector<page *> &pages) override;
773 };
774
775 vector<page *> add_blank_cmd::process(vector<page *> &pages)
776 {
777   vector<page *> out;
778
779   for (auto p: pages)
780     {
781       out.push_back(p);
782       for (int i=0; i<n; i++)
783         {
784           double w = paper.w, h = paper.h;
785           if (is_zero(w) || is_zero(h))
786             w = p->width, h = p->height;
787           out.push_back(new empty_page(w, h));
788         }
789     }
790
791   return out;
792 }
793
794 static const arg_def add_blank_args[] = {
795   { "n",        AT_INT | AT_POSITIONAL },
796   PAPER_ARGS,
797   { NULL,       0 }
798 };
799
800 /*** book ***/
801
802 class book_cmd : public cmd_exec {
803   int n;
804 public:
805   book_cmd(cmd *c)
806     {
807       n = c->arg("n")->as_int(0);
808       if (n % 4)
809         die("Number of pages per signature must be divisible by 4");
810     }
811   vector<page *> process(vector<page *> &pages) override;
812 };
813
814 vector<page *> book_cmd::process(vector<page *> &pages)
815 {
816   vector<page *> in, out;
817
818   in = pages;
819   while (in.size() % 4)
820     in.push_back(new empty_page(in[0]->width, in[0]->height));
821
822   int i = 0;
823   while (i < (int) in.size())
824     {
825       int sig = in.size() - i;
826       if (n)
827         sig = min(sig, n);
828       for (int j=0; j<sig/2; j+=2)
829         {
830           out.push_back(in[i + sig-1-j]);
831           out.push_back(in[i + j]);
832           out.push_back(in[i + j+1]);
833           out.push_back(in[i + sig-2-j]);
834         }
835       i += sig;
836     }
837
838   return out;
839 }
840
841 static const arg_def book_args[] = {
842   { "n",        AT_INT | AT_POSITIONAL },
843   { NULL,       0 }
844 };
845
846 /*** nup ***/
847
848 struct nup_state {
849   int rows, cols;
850   double tile_w, tile_h;
851   double paper_w, paper_h;
852   double fill_factor;
853   double scale;
854 };
855
856 class nup_cmd : public cmd_exec {
857   // Parameters
858   int grid_n, grid_m, num_tiles;
859   enum {
860     BY_ROWS,
861     BY_COLS,
862     BY_TILE,
863   } fill_by;
864   bool crop;
865   int rotate;
866   double scale;
867   paper_spec paper;
868   margin_spec marg;
869   pos_spec pos;
870   pos_spec tpos;
871   double hspace, vspace;
872
873   // Processing state
874   page *process_single(vector<page *> &in);
875   void find_config(vector<page *> &in, BBox *page_boxes);
876   void try_config(nup_state &st);
877   nup_state best;
878   bool found_solution;
879
880 public:
881   nup_cmd(cmd *c) : paper(c), marg(c, "margin", "margin"), pos(c), tpos(c->arg("tpos")->as_string("tl"))
882     {
883       grid_n = c->arg("n")->as_int(0);
884       grid_m = c->arg("m")->as_int(0);
885       if (grid_n > 0 && grid_m > 0)
886         num_tiles = grid_n * grid_m;
887       else if (grid_n > 0 && !grid_m)
888         num_tiles = grid_n;
889       else
890         die("Grid size must be at least 1x1");
891
892       const string by = c->arg("by")->as_string("rows");
893       if (by == "rows" || by == "row" || by == "r")
894         fill_by = BY_ROWS;
895       else if (by == "cols" || by == "cols" || by == "c")
896         fill_by = BY_COLS;
897       else if (by == "tile" || by == "t")
898         fill_by = BY_TILE;
899       else
900         die("Parameter \"by\" must be rows/cols/tile");
901
902       crop = c->arg("crop")->as_int(0);
903       rotate = c->arg("rotate")->as_int(-1);
904       scale = c->arg("scale")->as_double(0);
905
906       double space = c->arg("space")->as_double(0);
907       hspace = c->arg("hspace")->as_double(space);
908       vspace = c->arg("vspace")->as_double(space);
909
910       if (!is_zero(scale) && (!is_zero(paper.w) || rotate >= 0))
911         die("When nup is used with explicit scaling, paper size nor rotation may be given");
912       if (!is_zero(scale) && !grid_m)
913         die("When nup is used with explicit scaling, both grid sizes must be given");
914     }
915
916   vector<page *> process(vector<page *> &pages) override;
917 };
918
919 vector<page *> nup_cmd::process(vector<page *> &pages)
920 {
921   vector<page *> out;
922   int i = 0;
923
924   while (i < (int) pages.size())
925     {
926       vector<page *> in;
927       if (fill_by == BY_TILE)
928         {
929           for (int j=0; j<num_tiles; j++)
930             in.push_back(pages[i]);
931           i++;
932         }
933       else
934         {
935           for (int j=0; j<num_tiles; j++)
936             {
937               if (i < (int) pages.size())
938                 in.push_back(pages[i]);
939               else
940                 in.push_back(new empty_page(in[0]->width, in[0]->height));
941               i++;
942             }
943         }
944       out.push_back(process_single(in));
945     }
946
947   return out;
948 }
949
950 void nup_cmd::try_config(nup_state &st)
951 {
952   BBox window(st.paper_w, st.paper_h);
953   if (!marg.may_shrink(&window))
954     return;
955   marg.shrink_box(&window);
956   window.x_max -= (st.cols - 1) * hspace;
957   window.y_max -= (st.rows - 1) * vspace;
958   if (window.width() < 0 || window.height() < 0)
959     return;
960
961   BBox image(st.cols * st.tile_w, st.rows * st.tile_h);
962   st.scale = scale_to_fit(image, window);
963   st.fill_factor = (st.scale*image.width() * st.scale*image.height()) / (st.paper_w * st.paper_h);
964
965   debug("Try: %dx%d on %.3f x %.3f => scale %.3f, fill %.6f",
966     st.cols, st.rows,
967     st.paper_w, st.paper_h,
968     st.scale, st.fill_factor);
969
970   if (!found_solution || best.fill_factor < st.fill_factor)
971     {
972       found_solution = true;
973       best = st;
974     }
975 }
976
977 void nup_cmd::find_config(vector<page *> &in, BBox *page_boxes)
978 {
979   nup_state st;
980
981   // Determine tile size
982   st.tile_w = st.tile_h = 0;
983   for (int i=0; i<num_tiles; i++)
984     {
985       if (crop)
986         page_boxes[i] = in[i]->image_box;
987       else
988         page_boxes[i] = BBox(in[i]->width, in[i]->height);
989       st.tile_w = max(st.tile_w, page_boxes[i].width());
990       st.tile_h = max(st.tile_h, page_boxes[i].height());
991     }
992   debug("NUP: %d tiles of size [%.3f,%.3f]", num_tiles, st.tile_w, st.tile_h);
993   debug_indent += 4;
994
995   // Try all possible configurations of tiles
996   found_solution = false;
997   if (!is_zero(scale))
998     {
999       // If explicit scaling is requested, we choose page size ourselves,
1000       // so there is just one configuration.
1001       st.rows = grid_n;
1002       st.cols = grid_m;
1003       st.paper_w = marg.l + st.cols*st.tile_w + (st.cols-1)*hspace + marg.r;
1004       st.paper_h = marg.t + st.rows*st.tile_h + (st.rows-1)*vspace + marg.b;
1005       try_config(st);
1006     }
1007   else
1008     {
1009       // Page size is fixed (either given or copied from the source pages),
1010       // but we can have freedom to rotate and/or to choose grid size.
1011       for (int rot=0; rot<=1; rot++)
1012         {
1013           if (rotate >= 0 && rot != rotate)
1014             continue;
1015
1016           // Establish paper size
1017           if (!is_zero(paper.w))
1018             {
1019               st.paper_w = paper.w;
1020               st.paper_h = paper.h;
1021             }
1022           else
1023             {
1024               st.paper_w = in[0]->width;
1025               st.paper_h = in[0]->height;
1026             }
1027           if (rot)
1028             swap(st.paper_w, st.paper_h);
1029
1030           // Try grid sizes
1031           if (grid_m)
1032             {
1033               st.rows = grid_n;
1034               st.cols = grid_m;
1035               try_config(st);
1036             }
1037           else
1038             {
1039               for (int r=1; r<=grid_n; r++)
1040                 if (!(grid_n % r))
1041                   {
1042                     st.rows = r;
1043                     st.cols = grid_n / r;
1044                     try_config(st);
1045                   }
1046             }
1047         }
1048     }
1049
1050   if (!found_solution)
1051     die("Nup did not find a feasible solution");
1052   debug("Best: %dx%d on %.3f x %.3f", best.cols, best.rows, best.paper_w, best.paper_h);
1053   debug_indent -= 4;
1054 }
1055
1056 class nup_page : public page {
1057 public:
1058   vector<page *> orig_pages;
1059   vector<pdf_matrix> xforms;
1060   void render(out_context *out, pdf_matrix xform) override;
1061   nup_page(nup_state &st) : page(st.paper_w, st.paper_h) { }
1062 };
1063
1064 void nup_page::render(out_context *out, pdf_matrix parent_xform)
1065 {
1066   for (int i=0; i < (int) orig_pages.size(); i++)
1067     orig_pages[i]->render(out, xforms[i] * parent_xform);
1068 }
1069
1070 page *nup_cmd::process_single(vector<page *> &in)
1071 {
1072   BBox page_boxes[num_tiles];
1073   find_config(in, page_boxes);
1074   double tw = best.scale * best.tile_w;
1075   double th = best.scale * best.tile_h;
1076
1077   // Construct transform from paper to grid of tiles
1078   BBox paper_box(best.paper_w, best.paper_h);
1079   marg.shrink_box(&paper_box);
1080   BBox grid_box(best.cols * tw + (best.cols-1) * hspace,
1081                 best.rows * th + (best.rows-1) * vspace);
1082   pdf_matrix place_xform = pos.place(grid_box, paper_box);
1083
1084   nup_page *p = new nup_page(best);
1085   p->image_box = grid_box;
1086   p->image_box.transform(place_xform);
1087
1088   for (int i=0; i<num_tiles; i++)
1089     {
1090       int r, c;
1091       if (fill_by == BY_ROWS || fill_by == BY_TILE)
1092         {
1093           r = i / best.cols;
1094           c = i % best.cols;
1095         }
1096       else
1097         {
1098           c = i / best.rows;
1099           r = i % best.rows;
1100         }
1101
1102       pdf_matrix m;
1103       BBox &page_box = page_boxes[i];
1104       m.shift(-page_box.x_min, -page_box.y_min);
1105       m.scale(best.scale);
1106       page_box.transform(m);
1107
1108       double x = c * (tw + hspace);
1109       double y = (best.rows-1-r) * (th + vspace);
1110       BBox tile_box = BBox(x, y, x+tw, y+th);
1111       m.concat(tpos.place(page_box, tile_box));
1112
1113       p->orig_pages.push_back(in[i]);
1114       p->xforms.push_back(m * place_xform);
1115     }
1116
1117   return p;
1118 }
1119
1120 static const arg_def nup_args[] = {
1121   { "n",        AT_INT | AT_POSITIONAL | AT_MANDATORY },
1122   { "m",        AT_INT | AT_POSITIONAL },
1123   { "by",       AT_STRING },
1124   { "crop",     AT_SWITCH },
1125   { "rotate",   AT_SWITCH },
1126   { "scale",    AT_DOUBLE },
1127   PAPER_ARGS,
1128   MARGIN_ARGS1_NAMED("margin"),
1129   MARGIN_ARGS2("margin"),
1130   POS_ARGS,
1131   { "tpos",     AT_STRING },
1132   { "space",    AT_DIMEN },
1133   { "hspace",   AT_DIMEN },
1134   { "vspace",   AT_DIMEN },
1135   { NULL,       0 }
1136 };
1137
1138 /*** Command table ***/
1139
1140 template<typename T> cmd_exec *ctor(cmd *c) { return new T(c); }
1141
1142 const cmd_def cmd_table[] = {
1143   { "null",     no_args,        0,      &ctor<null_cmd>         },
1144   { "move",     move_args,      0,      &ctor<move_cmd>         },
1145   { "scale",    scale_args,     0,      &ctor<scale_cmd>        },
1146   { "rotate",   rotate_args,    0,      &ctor<rotate_cmd>       },
1147   { "flip",     flip_args,      0,      &ctor<flip_cmd>         },
1148   { "select",   no_args,        1,      &ctor<select_cmd>       },
1149   { "apply",    no_args,        1,      &ctor<apply_cmd>        },
1150   { "modulo",   modulo_args,    1,      &ctor<modulo_cmd>       },
1151   { "draw-bbox",no_args,        0,      &ctor<draw_bbox_cmd>    },
1152   { "merge",    no_args,        0,      &ctor<merge_cmd>        },
1153   { "paper",    paper_args,     0,      &ctor<paper_cmd>        },
1154   { "scaleto",  scaleto_args,   0,      &ctor<scaleto_cmd>      },
1155   { "fit",      fit_args,       0,      &ctor<fit_cmd>          },
1156   { "expand",   expand_args,    0,      &ctor<expand_cmd>       },
1157   { "margins",  margins_args,   0,      &ctor<margins_cmd>      },
1158   { "add-blank",add_blank_args, 0,      &ctor<add_blank_cmd>    },
1159   { "book",     book_args,      0,      &ctor<book_cmd>         },
1160   { "nup",      nup_args,       0,      &ctor<nup_cmd>          },
1161   { NULL,       NULL,           0,      NULL    }
1162 };