]> mj.ucw.cz Git - paperjam.git/blob - cmds.cc
Added debugging dump of page operations
[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,      NULL }
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   void debug_dump() override
36     {
37       debug("Transform [%s]", xform.to_string().c_str());
38       orig_page->debug_dump();
39     }
40   xform_page(page *p, pdf_matrix xf);
41 };
42
43 xform_page::xform_page(page *p, pdf_matrix xf)
44 {
45   orig_page = p;
46   index = p->index;
47   xform = xf;
48
49   BBox media(p->width, p->height);
50   media.transform(xf);
51   width = media.width();
52   height = media.height();
53
54   image_box = p->image_box;
55   image_box.transform(xf);
56 }
57
58 void xform_page::render(out_context *out, pdf_matrix parent_xform)
59 {
60   orig_page->render(out, xform * parent_xform);
61 }
62
63 // Commands acting on individual pages
64
65 class cmd_exec_simple : public cmd_exec {
66   virtual page *process_page(page *p) = 0;
67   vector<page *> process(vector<page *> &pages) override;
68 };
69
70 vector<page *> cmd_exec_simple::process(vector<page *> &pages)
71 {
72   vector<page *> out;
73   for (auto p: pages)
74     out.push_back(process_page(p));
75   return out;
76 }
77
78 // Paper specifications
79
80 class paper_spec {
81 public:
82   double w, h;
83   paper_spec(cmd *c, bool maybe=true)
84     {
85       arg_val *aname = c->arg("paper");
86       arg_val *aw = c->arg("w");
87       arg_val *ah = c->arg("h");
88       if (!aname->given() && !aw->given() && !ah->given() && maybe)
89         {
90           w = h = 0;
91           return;
92         }
93       if (aw->given() != ah->given() || aname->given() == aw->given())
94         err("Either paper format name or width and height must be given");
95       if (aname->given())
96         {
97           const char *name = aname->as_string("").c_str();
98           const paper *pap = paperinfo(name);
99           if (!pap)
100             err("No paper called \"%s\" is known", name);
101           w = paperpswidth(pap);
102           h = paperpsheight(pap);
103         }
104       else
105         {
106           w = aw->as_double(0);
107           h = ah->as_double(0);
108         }
109     }
110 };
111
112 #define PAPER_ARGS \
113   { "paper",    AT_STRING | AT_POSITIONAL,              "Paper format name (e.g., a4)" },       \
114   { "w",        AT_DIMEN,                               "Paper width" },                        \
115   { "h",        AT_DIMEN,                               "Paper height" }
116
117 // Position specification
118
119 class pos_spec {
120 public:
121   int h, v;
122   pos_spec() { v = h = 0; }
123   pos_spec(string s)
124     {
125       if (s.size() != 2)
126         err("Value of pos must have two characters");
127       if (s[0] == 't')
128         v = 1;
129       else if (s[0] == 'c')
130         v = 0;
131       else if (s[0] == 'b')
132         v = -1;
133       else
134         err("First character of pos must be t/c/b");
135       if (s[1] == 'l')
136         h = -1;
137       else if (s[1] == 'c')
138         h = 0;
139       else if (s[1] == 'r')
140         h = 1;
141       else
142         err("Second character of pos must be l/c/r");
143     }
144   pos_spec(cmd *c) : pos_spec(c->arg("pos")->as_string("cc")) { }
145   pdf_matrix place(BBox &inner, BBox &outer)
146     {
147       pdf_matrix m;
148       m.shift(-inner.x_min, -inner.y_min);
149       switch (h)
150         {
151         case -1:
152           break;
153         case 0:
154           m.shift((outer.width() - inner.width()) / 2, 0);
155           break;
156         case 1:
157           m.shift(outer.width() - inner.width(), 0);
158           break;
159         default:
160           abort();
161         }
162       switch (v)
163         {
164         case -1:
165           break;
166         case 0:
167           m.shift(0, (outer.height() - inner.height()) / 2);
168           break;
169         case 1:
170           m.shift(0, outer.height() - inner.height());
171           break;
172         default:
173           abort();
174         }
175       m.shift(outer.x_min, outer.y_min);
176       return m;
177     }
178 };
179
180 #define POS_ARGS \
181   { "pos",      AT_STRING,                      "Position on the page: (t|c|b)(l|c|r)" }
182
183 // Margins
184
185 class margin_spec {
186 public:
187   double l, r, t, b;
188   margin_spec(cmd *c, string basic, string sx)
189     {
190       double m, h, v;
191       m = c->arg(basic)->as_double(0);
192       h = c->arg("h" + sx)->as_double(m);
193       v = c->arg("v" + sx)->as_double(m);
194       l = c->arg("l" + sx)->as_double(h);
195       r = c->arg("r" + sx)->as_double(h);
196       t = c->arg("t" + sx)->as_double(v);
197       b = c->arg("b" + sx)->as_double(v);
198     }
199   bool may_shrink(BBox *bb)
200     {
201       return (bb->width() > l+r && bb->height() > t+b);
202     }
203   void shrink_box(BBox *bb)
204     {
205       bb->x_min += l;
206       bb->x_max -= r;
207       bb->y_min += b;
208       bb->y_max -= t;
209       if (bb->x_min >= bb->x_max || bb->y_min >= bb->y_max)
210         err("Margins cannot be larger than the whole page");
211     }
212   void expand_box(BBox *bb)
213     {
214       bb->x_min -= l;
215       bb->x_max += r;
216       bb->y_min -= b;
217       bb->y_max += t;
218     }
219 };
220
221 #define MARGIN_ARGS1_NAMED(name)                                                        \
222   { name,       AT_DIMEN,                       "Size of all margins (default: 0)" }
223
224 #define MARGIN_ARGS1_POSNL(name)                                                        \
225   { name,       AT_DIMEN | AT_POSITIONAL,       "Size of all margins (default: 0)" }
226
227 #define MARGIN_ARGS2(sx)                                                                \
228   { "h" sx,     AT_DIMEN,                       "Size of horizontal margins" },         \
229   { "v" sx,     AT_DIMEN,                       "Size of vertical margins" },           \
230   { "l" sx,     AT_DIMEN,                       "Size of left margin" },                \
231   { "r" sx,     AT_DIMEN,                       "Size of right margin" },               \
232   { "t" sx,     AT_DIMEN,                       "Size of top margin" },                 \
233   { "b" sx,     AT_DIMEN,                       "Size of bottom margin" }
234
235 // Colors
236
237 class color_spec {
238 public:
239   double rgb[3];
240   color_spec()
241     {
242       rgb[0] = rgb[1] = rgb[2] = 0;
243     }
244   color_spec(string s)
245     {
246       if (s.length() != 6)
247         err("Invalid color specification \"%s\": expecting 6 hex digits", s.c_str());
248       for (int i=0; i<3; i++)
249         {
250           int x = 0;
251           for (int j=0; j<2; j++)
252             {
253               char c = s[2*i+j];
254               if (c >= '0' && c <= '9')
255                 x = (x << 4) | (c - '0');
256               else if (c >= 'a' && c <= 'f')
257                 x = (x << 4) | (c - 'a' + 10);
258               else if (c >= 'A' && c <= 'F')
259                 x = (x << 4) | (c - 'A' + 10);
260             }
261           rgb[i] = x / 255.;
262         }
263     }
264   string to_string()
265     {
266       return pdf_coord(rgb[0]) + " " + pdf_coord(rgb[1]) + " " + pdf_coord(rgb[2]);
267     }
268 };
269
270 // Cropmarks
271
272 class cropmark_spec {
273 public:
274   enum mark_type {
275     MARK_NONE,
276     MARK_BOX,
277     MARK_CROSS,
278     MARK_OUT,
279     MARK_IN,
280     MARK_BG,
281   } type;
282   double pen_width;
283   double arm_length;
284   double offset;
285   color_spec color;
286   cropmark_spec()
287     {
288       type = MARK_NONE;
289       pen_width = 0.2;
290       arm_length = 5*mm;
291       offset = 0;
292       egstate = QPDFObjectHandle::newNull();
293     }
294   cropmark_spec(cmd *c, const string prefix="", const string def_type="cross") : color(c->arg(prefix + "color")->as_string("000000"))
295     {
296       string t = c->arg(prefix + "mark")->as_string(def_type);
297       if (t == "none")
298         type = MARK_NONE;
299       else if (t == "box")
300         type = MARK_BOX;
301       else if (t == "cross")
302         type = MARK_CROSS;
303       else if (t == "in")
304         type = MARK_IN;
305       else if (t == "out")
306         type = MARK_OUT;
307       else if (t == "bg")
308         type = MARK_BG;
309       else
310         err("Invalid cropmark type %s", t.c_str());
311
312       pen_width = c->arg(prefix + "pen")->as_double(0.2);
313       arm_length = c->arg(prefix + "len")->as_double(5*mm);
314       offset = c->arg(prefix + "offset")->as_double(0);
315       egstate = QPDFObjectHandle::newNull();
316     }
317   bool is_bg() { return (type == MARK_BG); }
318   string pdf_stream(out_context *out, BBox &box, pdf_matrix &xform);
319 private:
320   string crop_cross(double x, double y, uint mask);
321   QPDFObjectHandle egstate;
322 };
323
324 string cropmark_spec::crop_cross(double x, double y, uint mask)
325 {
326   string s = "";
327
328   for (uint i=0; i<4; i++)
329     if (mask & (1U << i))
330       {
331         double x2 = x, y2 = y;
332         switch (i)
333           {
334           case 3: x2 -= arm_length; break;
335           case 2: x2 += arm_length; break;
336           case 1: y2 += arm_length; break;
337           case 0: y2 -= arm_length; break;
338           }
339         s += pdf_coord(x) + " " + pdf_coord(y) + " m " + pdf_coord(x2) + " " + pdf_coord(y2) + " l S ";
340       }
341
342   return s;
343 }
344
345 string cropmark_spec::pdf_stream(out_context *out, BBox &box, pdf_matrix &xform)
346 {
347   if (type == MARK_NONE)
348     return "";
349
350   string s = "q ";
351   s += color.to_string() + (type == MARK_BG ? " rg " : " RG ");
352   s += xform.to_string() + " cm ";
353
354   if (egstate.isNull())
355     {
356       auto egs = QPDFObjectHandle::newDictionary();
357       egs.replaceKey("/Type", QPDFObjectHandle::newName("/ExtGState"));
358       egs.replaceKey("/LW", QPDFObjectHandle::newReal(pen_width, 1));
359       egs.replaceKey("/LC", QPDFObjectHandle::newInteger(2));
360       egs.replaceKey("/LJ", QPDFObjectHandle::newInteger(0));
361       egstate = out->pdf->makeIndirectObject(egs);
362     }
363
364   string egs_res = out->new_resource("GS");
365   out->egstates.replaceKey(egs_res, egstate);
366   s += egs_res + " gs ";
367
368   BBox b = box.enlarged(offset);
369
370   switch (type)
371     {
372     case MARK_NONE:
373       break;
374     case MARK_BOX:
375        s += b.to_rect() + " re S ";
376        break;
377     case MARK_CROSS:
378        s += crop_cross(b.x_min, b.y_min, 0b1111);
379        s += crop_cross(b.x_max, b.y_min, 0b1111);
380        s += crop_cross(b.x_max, b.y_max, 0b1111);
381        s += crop_cross(b.x_min, b.y_max, 0b1111);
382        break;
383     case MARK_IN:
384        s += crop_cross(b.x_min, b.y_min, 0b0110);
385        s += crop_cross(b.x_max, b.y_min, 0b1010);
386        s += crop_cross(b.x_max, b.y_max, 0b1001);
387        s += crop_cross(b.x_min, b.y_max, 0b0101);
388        break;
389     case MARK_OUT:
390        s += crop_cross(b.x_min, b.y_min, 0b1001);
391        s += crop_cross(b.x_max, b.y_min, 0b0101);
392        s += crop_cross(b.x_max, b.y_max, 0b0110);
393        s += crop_cross(b.x_min, b.y_max, 0b1010);
394        break;
395     case MARK_BG:
396        s += b.to_rect() + " re f ";
397        break;
398     }
399
400   s += "Q ";
401   return s;
402 }
403
404 #define CROPMARK_ARGS(px)                                                                       \
405   { px "mark",  AT_STRING,              "Cropmark style: box/cross/in/out/bg" },                \
406   { px "pen",   AT_DIMEN,               "Cropmark pen width (default: 0.2pt)" },                \
407   { px "len",   AT_DIMEN,               "Cropmark arm length (default: 5mm)" },                 \
408   { px "offset",AT_DIMEN,               "Cropmark offset outside the box (default: 0)" },       \
409   { px "color", AT_STRING,              "Cropmark color (RRGGBB, default: 000000)" }
410
411 // Scaling preserving aspect ratio
412
413 double scale_to_fit(BBox &from, BBox &to)
414 {
415   double fw = from.width(), fh = from.height();
416   double tw = to.width(), th = to.height();
417   if (is_zero(fw) || is_zero(fh) || is_zero(tw) || is_zero(th))
418     return 1;
419   else
420     return min(tw/fw, th/fh);
421 }
422
423 /*** move ***/
424
425 class move_cmd : public cmd_exec_simple {
426   double x, y;
427 public:
428   move_cmd(cmd *c)
429     {
430       x = c->arg("x")->as_double(0);
431       y = c->arg("y")->as_double(0);
432     }
433   page *process_page(page *p) override
434     {
435       pdf_matrix m;
436       m.shift(x, y);
437       return new xform_page(p, m);
438     }
439 };
440
441 static const arg_def move_args[] = {
442   { "x",        AT_DIMEN | AT_MANDATORY | AT_POSITIONAL,        "Move right by this distance" },
443   { "y",        AT_DIMEN | AT_MANDATORY | AT_POSITIONAL,        "Move up by this distance" },
444   { NULL,       0,                              NULL }
445 };
446
447 /*** scale ***/
448
449 class scale_cmd : public cmd_exec_simple {
450   double x_factor, y_factor;
451 public:
452   scale_cmd(cmd *c)
453     {
454       x_factor = c->arg("x")->as_double(1);
455       y_factor = c->arg("y")->as_double(x_factor);
456     }
457   page *process_page(page *p) override
458     {
459       pdf_matrix m;
460       m.scale(x_factor, y_factor);
461       return new xform_page(p, m);
462     }
463 };
464
465 static const arg_def scale_args[] = {
466   { "x",        AT_DOUBLE | AT_MANDATORY | AT_POSITIONAL,       "Scale horizontally by this fraction" },
467   { "y",        AT_DOUBLE | AT_POSITIONAL,                      "Scale vertically by this fraction (default: x)" },
468   { NULL,       0,                                              NULL }
469 };
470
471 /*** rotate ***/
472
473 class rotate_cmd : public cmd_exec_simple {
474   int deg;
475 public:
476   rotate_cmd(cmd *c)
477     {
478       deg = c->arg("angle")->as_int(0) % 360;
479       if (deg < 0)
480         deg += 360;
481       if (deg % 90)
482         err("The angle must be a multiple of 90 degrees");
483     }
484   page *process_page(page *p) override
485     {
486       pdf_matrix m;
487       switch (deg)
488         {
489         case 0:
490           break;
491         case 90:
492           m.rotate_deg(-90);
493           m.shift(0, p->width);
494           break;
495         case 180:
496           m.rotate_deg(180);
497           m.shift(p->width, p->height);
498           break;
499         case 270:
500           m.rotate_deg(90);
501           m.shift(p->height, 0);
502           break;
503         default:
504           abort();
505         }
506       return new xform_page(p, m);
507     }
508 };
509
510 static const arg_def rotate_args[] = {
511   { "angle",    AT_INT | AT_MANDATORY | AT_POSITIONAL,  "Rotate clockwise by this angle" },
512   { NULL,       0,                                      NULL }
513 };
514
515 /*** flip ***/
516
517 class flip_cmd : public cmd_exec_simple {
518   bool horizontal;
519   bool vertical;
520 public:
521   flip_cmd(cmd *c)
522     {
523       horizontal = c->arg("h")->as_int(0);
524       vertical = c->arg("v")->as_int(0);
525       if (!horizontal && !vertical)
526         err("No direction specified");
527     }
528   page *process_page(page *p) override
529     {
530       pdf_matrix m;
531       if (vertical)
532         {
533           m.scale(1, -1);
534           m.shift(0, p->height);
535         }
536       if (horizontal)
537         {
538           m.scale(-1, 1);
539           m.shift(p->width, 0);
540         }
541       return new xform_page(p, m);
542     }
543 };
544
545 static const arg_def flip_args[] = {
546   { "h",        AT_SWITCH,                              "Flip horizontally" },
547   { "v",        AT_SWITCH,                              "Flip vertically" },
548   { NULL,       0,                                      NULL }
549 };
550
551 /*** select ***/
552
553 class select_cmd : public cmd_exec {
554   pipeline *pipe;
555 public:
556   select_cmd(cmd *c)
557     {
558       pipe = c->pipe;
559     }
560   vector<page *> process(vector<page *> &pages) override;
561 };
562
563 static int validate_page_index(vector<page *> &pages, int idx)
564 {
565   if (idx >= 1 && idx <= (int) pages.size())
566     return idx - 1;
567   if (idx <= -1 && idx >= (int) -pages.size())
568     return idx + pages.size();
569   err("Page index %d out of range", idx);
570 }
571
572 vector<page *> select_cmd::process(vector<page *> &pages)
573 {
574   vector<page *> out;
575   for (auto pb: pipe->branches)
576     {
577       vector<page *> selected;
578       for (auto ps: pb->selectors)
579         {
580           int f = validate_page_index(pages, ps.from);
581           int t = validate_page_index(pages, ps.to);
582           int step = (f <= t) ? 1 : -1;
583           for (int i=f; i<=t; i += step)
584             selected.push_back(pages[i]);
585         }
586       auto processed = run_command_list(pb->commands, selected);
587       for (auto p: processed)
588         out.push_back(p);
589     }
590   return out;
591 }
592
593 /*** apply ***/
594
595 class apply_cmd : public cmd_exec {
596   pipeline *pipe;
597 public:
598   apply_cmd(cmd *c)
599     {
600       pipe = c->pipe;
601     }
602   vector<page *> process(vector<page *> &pages) override;
603 };
604
605 static pipeline_branch *find_branch(pipeline *pipe, vector <page *> &pages, int idx)
606 {
607   for (auto pb: pipe->branches)
608     for (auto ps: pb->selectors)
609       {
610         int f = validate_page_index(pages, ps.from);
611         int t = validate_page_index(pages, ps.to);
612         if (f <= idx && idx <= t || t <= idx && idx <= f)
613           return pb;
614       }
615   return NULL;
616 }
617
618 vector<page *> apply_cmd::process(vector<page *> &pages)
619 {
620   vector<page *> out;
621
622   int cnt = 0;
623   for (auto p: pages)
624     {
625       pipeline_branch *pb = find_branch(pipe, pages, cnt);
626       if (pb)
627         {
628           vector<page *> tmp;
629           tmp.push_back(p);
630           auto processed = run_command_list(pb->commands, tmp);
631           for (auto q: processed)
632             out.push_back(q);
633         }
634       else
635         out.push_back(p);
636       cnt++;
637     }
638
639   return out;
640 }
641
642 /*** modulo ***/
643
644 class modulo_cmd : public cmd_exec {
645   pipeline *pipe;
646   int n;
647   bool half;
648 public:
649   modulo_cmd(cmd *c)
650     {
651       n = c->arg("n")->as_int(0);
652       if (n <= 0)
653         err("Modulo must have n > 0");
654       half = c->arg("half")->as_int(0);
655       pipe = c->pipe;
656     }
657   vector<page *> process(vector<page *> &pages) override;
658 };
659
660 vector<page *> modulo_cmd::process(vector<page *> &pages)
661 {
662   vector<page *> out;
663   int tuples = ((int) pages.size() + n - 1) / n;
664   int use_tuples = half ? tuples/2 : tuples;
665
666   for (int tuple=0; tuple < use_tuples; tuple++)
667     {
668       debug("# Tuple %d", tuple);
669       debug_indent += 4;
670       for (auto pb: pipe->branches)
671         {
672           vector<page *> tmp;
673           for (auto ps: pb->selectors)
674             {
675               int f = ps.from;
676               int t = ps.to;
677               int step = (f <= t) ? 1 : -1;
678               for (int i=f; i<=t; i += step)
679                 {
680                   int j;
681                   if (i > 0 && i <= n)
682                     j = tuple*n + i - 1;
683                   else if (i < 0 && i >= -n)
684                     j = (tuples-1-tuple)*n + (-i) - 1;
685                   else
686                     err("Invalid index %d", i);
687                   if (j < (int) pages.size())
688                     tmp.push_back(pages[j]);
689                   else
690                     {
691                       page *ref_page = pages[tuple*n];
692                       tmp.push_back(new empty_page(ref_page->width, ref_page->height));
693                     }
694                 }
695             }
696           auto processed = run_command_list(pb->commands, tmp);
697           for (auto q: processed)
698             out.push_back(q);
699         }
700       debug_indent -= 4;
701     }
702
703   return out;
704 }
705
706 static const arg_def modulo_args[] = {
707   { "n",        AT_INT | AT_MANDATORY | AT_POSITIONAL,          "Number of pages in a single tuple" },
708   { "half",     AT_SWITCH,                                      "Process only the first half of n-tuples" },
709   { NULL,       0,                                              NULL }
710 };
711
712 /*** debug ***/
713
714 class debug_page : public page {
715   page *orig_page;
716   cropmark_spec *page_cm, *image_cm;
717 public:
718   debug_page(page *p, cropmark_spec *page_cms, cropmark_spec *image_cms) : page(p), orig_page(p), page_cm(page_cms), image_cm(image_cms) { }
719   void render(out_context *out, pdf_matrix xform) override
720     {
721       orig_page->render(out, xform);
722       BBox page_bbox = BBox(0, 0, width, height);
723       out->contents += page_cm->pdf_stream(out, page_bbox, xform);
724       out->contents += image_cm->pdf_stream(out, image_box, xform);
725     }
726   void debug_dump() override
727     {
728       debug("Draw debugging boxes");
729       orig_page->debug_dump();
730     }
731 };
732
733 class debug_cmd : public cmd_exec_simple {
734   cropmark_spec page_cmarks;
735   cropmark_spec image_cmarks;
736 public:
737   debug_cmd(cmd *c UNUSED)
738     {
739       page_cmarks.type = cropmark_spec::MARK_BOX;
740       page_cmarks.color.rgb[0] = 1;
741       page_cmarks.color.rgb[1] = 0;
742       page_cmarks.color.rgb[2] = 0;
743       image_cmarks.type = cropmark_spec::MARK_BOX;
744       image_cmarks.color.rgb[0] = 0;
745       image_cmarks.color.rgb[1] = 1;
746       image_cmarks.color.rgb[2] = 0;
747     }
748   page *process_page(page *p) override
749     {
750       return new debug_page(p, &page_cmarks, &image_cmarks);
751     }
752 };
753
754 /*** merge ***/
755
756 class merge_cmd : public cmd_exec {
757 public:
758   merge_cmd(cmd *c UNUSED) { }
759   vector<page *> process(vector<page *> &pages) override;
760 };
761
762 class merge_page : public page {
763   vector<page *> orig_pages;
764 public:
765   merge_page(vector<page *> &orig) : page(0, 0)
766     {
767       orig_pages = orig;
768       bool first = true;
769       for (auto p: orig)
770         {
771           if (first)
772             {
773               width = p->width;
774               height = p->height;
775               image_box = p->image_box;
776               first = false;
777             }
778           else
779             {
780               if (!is_equal(width, p->width) || !is_equal(height, p->height))
781                 err("All pages must have the same dimensions");
782               image_box.join(p->image_box);
783             }
784         }
785     }
786   void render(out_context *out, pdf_matrix xform) override
787     {
788       for (auto p: orig_pages)
789         p->render(out, xform);
790     }
791   void debug_dump() override
792     {
793       debug("Merge pages");
794       debug_indent += 4;
795       for (auto p: orig_pages)
796         p->debug_dump();
797       debug_indent -= 4;
798     }
799 };
800
801 vector<page *> merge_cmd::process(vector<page *> &pages)
802 {
803   vector<page *> out;
804   if (pages.size())
805     out.push_back(new merge_page(pages));
806   return out;
807 }
808
809 /*** paper ***/
810
811 class paper_cmd : public cmd_exec_simple {
812   paper_spec paper;
813   pos_spec pos;
814 public:
815   paper_cmd(cmd *c) : paper(c), pos(c) { }
816   page *process_page(page *p) override
817     {
818       BBox paper_box = BBox(paper.w, paper.h);
819       pdf_matrix xf = pos.place(p->image_box, paper_box);
820       page *q = new xform_page(p, xf);
821       q->width = paper.w;
822       q->height = paper.h;
823       return q;
824     }
825 };
826
827 static const arg_def paper_args[] = {
828   PAPER_ARGS,
829   POS_ARGS,
830   { NULL,       0,                              NULL }
831 };
832
833 /*** scaleto ***/
834
835 class scaleto_cmd : public cmd_exec_simple {
836   paper_spec paper;
837   pos_spec pos;
838 public:
839   scaleto_cmd(cmd *c) : paper(c), pos(c) { }
840   page *process_page(page *p) override
841     {
842       BBox orig_box = BBox(p->width, p->height);
843       BBox paper_box = BBox(paper.w, paper.h);
844       pdf_matrix xf;
845       xf.scale(scale_to_fit(orig_box, paper_box));
846       orig_box.transform(xf);
847       xf.concat(pos.place(orig_box, paper_box));
848       page *q = new xform_page(p, xf);
849       q->width = paper.w;
850       q->height = paper.h;
851       return q;
852     }
853 };
854
855 static const arg_def scaleto_args[] = {
856   PAPER_ARGS,
857   POS_ARGS,
858   { NULL,       0,                              NULL }
859 };
860
861 /*** fit ***/
862
863 class fit_cmd : public cmd_exec_simple {
864   paper_spec paper;
865   pos_spec pos;
866   margin_spec marg;
867 public:
868   fit_cmd(cmd *c) : paper(c, true), pos(c), marg(c, "margin", "margin") { }
869   page *process_page(page *p) override
870     {
871       pdf_matrix xf;
872       page *q;
873
874       if (!is_zero(paper.w) && !is_zero(paper.h))
875         {
876           // Paper given: scale image to fit paper
877           BBox orig_box = p->image_box;
878           BBox paper_box = BBox(paper.w, paper.h);
879           marg.shrink_box(&paper_box);
880           xf.scale(scale_to_fit(orig_box, paper_box));
881           orig_box.transform(xf);
882           xf.concat(pos.place(orig_box, paper_box));
883           q = new xform_page(p, xf);
884           q->width = paper.w;
885           q->height = paper.h;
886         }
887       else
888         {
889           // No paper given: adjust paper to fit image
890           xf.shift(-p->image_box.x_min, -p->image_box.y_min);
891           xf.shift(marg.l, marg.b);
892           q = new xform_page(p, xf);
893           q->width = p->image_box.width() + marg.l + marg.r;
894           q->height = p->image_box.height() + marg.t + marg.b;
895         }
896       return q;
897     }
898 };
899
900 static const arg_def fit_args[] = {
901   PAPER_ARGS,
902   POS_ARGS,
903   MARGIN_ARGS1_NAMED("margin"),
904   MARGIN_ARGS2("margin"),
905   { NULL,       0,                              NULL }
906 };
907
908 /*** expand ***/
909
910 class expand_cmd : public cmd_exec_simple {
911   margin_spec marg;
912 public:
913   expand_cmd(cmd *c) : marg(c, "by", "") { }
914   page *process_page(page *p) override
915     {
916       pdf_matrix xf;
917       xf.shift(marg.l, marg.b);
918       page *q = new xform_page(p, xf);
919       q->width = p->width + marg.l + marg.r;
920       q->height = p->height + marg.t + marg.b;
921       if (q->width < 0.001 || q->height < 0.001)
922         err("Expansion must result in positive page dimensions");
923       return q;
924     }
925 };
926
927 static const arg_def expand_args[] = {
928   MARGIN_ARGS1_POSNL("by"),
929   MARGIN_ARGS2(""),
930   { NULL,       0,                              NULL }
931 };
932
933 /*** margins ***/
934
935 class margins_cmd : public cmd_exec_simple {
936   margin_spec marg;
937 public:
938   margins_cmd(cmd *c) : marg(c, "size", "") { }
939   page *process_page(page *p) override
940     {
941       page *q = new xform_page(p, pdf_matrix());
942       q->image_box = BBox(marg.l, marg.t, p->width - marg.r, p->height - marg.b);
943       if (q->image_box.width() < 0.001 || q->image_box.height() < 0.001)
944         err("Margins must result in positive image dimensions");
945       return q;
946     }
947 };
948
949 static const arg_def margins_args[] = {
950   MARGIN_ARGS1_POSNL("size"),
951   MARGIN_ARGS2(""),
952   { NULL,       0,                              NULL }
953 };
954
955 /*** add-blank ***/
956
957 class add_blank_cmd : public cmd_exec {
958   int n;
959   paper_spec paper;
960 public:
961   add_blank_cmd(cmd *c) : paper(c, true)
962     {
963       n = c->arg("n")->as_int(1);
964     }
965   vector<page *> process(vector<page *> &pages) override;
966 };
967
968 vector<page *> add_blank_cmd::process(vector<page *> &pages)
969 {
970   vector<page *> out;
971
972   for (auto p: pages)
973     {
974       out.push_back(p);
975       for (int i=0; i<n; i++)
976         {
977           double w = paper.w, h = paper.h;
978           if (is_zero(w) || is_zero(h))
979             w = p->width, h = p->height;
980           out.push_back(new empty_page(w, h));
981         }
982     }
983
984   return out;
985 }
986
987 static const arg_def add_blank_args[] = {
988   { "n",        AT_INT | AT_POSITIONAL,         "Number of blank pages to add (default: 1)" },
989   PAPER_ARGS,
990   { NULL,       0,                              NULL }
991 };
992
993 /*** book ***/
994
995 class book_cmd : public cmd_exec {
996   int n;
997 public:
998   book_cmd(cmd *c)
999     {
1000       n = c->arg("n")->as_int(0);
1001       if (n % 4)
1002         err("Number of pages per signature must be divisible by 4");
1003     }
1004   vector<page *> process(vector<page *> &pages) override;
1005 };
1006
1007 vector<page *> book_cmd::process(vector<page *> &pages)
1008 {
1009   vector<page *> in, out;
1010
1011   in = pages;
1012   while (in.size() % 4)
1013     in.push_back(new empty_page(in[0]->width, in[0]->height));
1014
1015   int i = 0;
1016   while (i < (int) in.size())
1017     {
1018       int sig = in.size() - i;
1019       if (n)
1020         sig = min(sig, n);
1021       for (int j=0; j<sig/2; j+=2)
1022         {
1023           out.push_back(in[i + sig-1-j]);
1024           out.push_back(in[i + j]);
1025           out.push_back(in[i + j+1]);
1026           out.push_back(in[i + sig-2-j]);
1027         }
1028       i += sig;
1029     }
1030
1031   return out;
1032 }
1033
1034 static const arg_def book_args[] = {
1035   { "n",        AT_INT | AT_POSITIONAL,         "Number of pages in a single booklet" },
1036   { NULL,       0,                              NULL }
1037 };
1038
1039 /*** nup ***/
1040
1041 struct nup_state {
1042   int rows, cols;
1043   double tile_w, tile_h;
1044   double paper_w, paper_h;
1045   double fill_factor;
1046   double scale;
1047 };
1048
1049 class nup_cmd : public cmd_exec {
1050   // Parameters
1051   int grid_n, grid_m, num_tiles;
1052   enum {
1053     BY_ROWS,
1054     BY_COLS,
1055     BY_TILE,
1056   } fill_by;
1057   bool crop;
1058   bool mixed;
1059   int rotate;
1060   double scale;
1061   paper_spec paper;
1062   margin_spec marg;
1063   pos_spec pos;
1064   pos_spec tpos;
1065   double hspace, vspace;
1066   cropmark_spec cmarks;
1067
1068   // Processing state
1069   page *process_single(vector<page *> &in);
1070   void find_config(vector<page *> &in, BBox *page_boxes);
1071   void try_config(nup_state &st);
1072   nup_state best;
1073   bool found_solution;
1074   BBox common_page_box;
1075
1076 public:
1077   nup_cmd(cmd *c) : paper(c), marg(c, "margin", "margin"), pos(c), tpos(c->arg("tpos")->as_string("tl")), cmarks(c, "c", "none")
1078     {
1079       grid_n = c->arg("n")->as_int(0);
1080       grid_m = c->arg("m")->as_int(0);
1081       if (grid_n > 0 && grid_m > 0)
1082         num_tiles = grid_n * grid_m;
1083       else if (grid_n > 0 && !grid_m)
1084         num_tiles = grid_n;
1085       else
1086         err("Grid size must be at least 1x1");
1087
1088       const string by = c->arg("by")->as_string("rows");
1089       if (by == "rows" || by == "row" || by == "r")
1090         fill_by = BY_ROWS;
1091       else if (by == "cols" || by == "cols" || by == "c")
1092         fill_by = BY_COLS;
1093       else if (by == "tile" || by == "t")
1094         fill_by = BY_TILE;
1095       else
1096         err("Argument \"by\" must be rows/cols/tile");
1097
1098       crop = c->arg("crop")->as_int(0);
1099       mixed = c->arg("mixed")->as_int(0);
1100       rotate = c->arg("rotate")->as_int(-1);
1101       scale = c->arg("scale")->as_double(0);
1102
1103       double space = c->arg("space")->as_double(0);
1104       hspace = c->arg("hspace")->as_double(space);
1105       vspace = c->arg("vspace")->as_double(space);
1106
1107       if (!is_zero(scale) && (!is_zero(paper.w) || rotate >= 0))
1108         err("When used with explicit scaling, paper size nor rotation may be given");
1109       if (!is_zero(scale) && !grid_m)
1110         err("When used with explicit scaling, both grid sizes must be given");
1111     }
1112
1113   vector<page *> process(vector<page *> &pages) override;
1114 };
1115
1116 vector<page *> nup_cmd::process(vector<page *> &pages)
1117 {
1118   vector<page *> out;
1119
1120   // Unless mixed is given, find the common page size
1121   if (!mixed)
1122     {
1123       for (int i=0; i < (int) pages.size(); i++)
1124         {
1125           page *p = pages[i];
1126           BBox pb = crop ? p->image_box : BBox(p->width, p->height);
1127           if (!i)
1128             common_page_box = pb;
1129           else
1130             common_page_box.join(pb);
1131         }
1132       debug("NUP: Common page box [%.3f %.3f]-[%.3f %.3f]",
1133         common_page_box.x_min, common_page_box.y_min, common_page_box.x_max, common_page_box.y_max);
1134     }
1135
1136   // Process one tile set after another
1137   int i = 0;
1138   while (i < (int) pages.size())
1139     {
1140       vector<page *> in;
1141       if (fill_by == BY_TILE)
1142         {
1143           for (int j=0; j<num_tiles; j++)
1144             in.push_back(pages[i]);
1145           i++;
1146         }
1147       else
1148         {
1149           for (int j=0; j<num_tiles; j++)
1150             {
1151               if (i < (int) pages.size())
1152                 in.push_back(pages[i]);
1153               else
1154                 in.push_back(new empty_page(in[0]->width, in[0]->height));
1155               i++;
1156             }
1157         }
1158       out.push_back(process_single(in));
1159     }
1160
1161   return out;
1162 }
1163
1164 void nup_cmd::try_config(nup_state &st)
1165 {
1166   BBox window(st.paper_w, st.paper_h);
1167   if (!marg.may_shrink(&window))
1168     return;
1169   marg.shrink_box(&window);
1170   window.x_max -= (st.cols - 1) * hspace;
1171   window.y_max -= (st.rows - 1) * vspace;
1172   if (window.width() < 0 || window.height() < 0)
1173     return;
1174
1175   BBox image(st.cols * st.tile_w, st.rows * st.tile_h);
1176   st.scale = scale_to_fit(image, window);
1177   st.fill_factor = (st.scale*image.width() * st.scale*image.height()) / (st.paper_w * st.paper_h);
1178
1179   if (debug_level > 1)
1180     debug("Try: %dx%d on %.3f x %.3f => scale %.3f, fill %.6f",
1181           st.cols, st.rows,
1182           st.paper_w, st.paper_h,
1183           st.scale, st.fill_factor);
1184
1185   if (!found_solution || best.fill_factor < st.fill_factor)
1186     {
1187       found_solution = true;
1188       best = st;
1189     }
1190 }
1191
1192 void nup_cmd::find_config(vector<page *> &in, BBox *page_boxes)
1193 {
1194   nup_state st;
1195
1196   // Determine tile size
1197   st.tile_w = st.tile_h = 0;
1198   for (int i=0; i<num_tiles; i++)
1199     {
1200       if (!mixed)
1201         page_boxes[i] = common_page_box;
1202       else if (crop)
1203         page_boxes[i] = in[i]->image_box;
1204       else
1205         page_boxes[i] = BBox(in[i]->width, in[i]->height);
1206       st.tile_w = max(st.tile_w, page_boxes[i].width());
1207       st.tile_h = max(st.tile_h, page_boxes[i].height());
1208     }
1209   debug("NUP: %d tiles of size [%.3f,%.3f]", num_tiles, st.tile_w, st.tile_h);
1210   debug_indent += 4;
1211
1212   // Try all possible configurations of tiles
1213   found_solution = false;
1214   if (!is_zero(scale))
1215     {
1216       // If explicit scaling is requested, we choose page size ourselves,
1217       // so there is just one configuration.
1218       st.rows = grid_n;
1219       st.cols = grid_m;
1220       st.paper_w = marg.l + st.cols*st.tile_w + (st.cols-1)*hspace + marg.r;
1221       st.paper_h = marg.t + st.rows*st.tile_h + (st.rows-1)*vspace + marg.b;
1222       try_config(st);
1223     }
1224   else
1225     {
1226       // Page size is fixed (either given or copied from the source pages),
1227       // but we can have freedom to rotate and/or to choose grid size.
1228       for (int rot=0; rot<=1; rot++)
1229         {
1230           if (rotate >= 0 && rot != rotate)
1231             continue;
1232
1233           // Establish paper size
1234           if (!is_zero(paper.w))
1235             {
1236               st.paper_w = paper.w;
1237               st.paper_h = paper.h;
1238             }
1239           else
1240             {
1241               st.paper_w = in[0]->width;
1242               st.paper_h = in[0]->height;
1243             }
1244           if (rot)
1245             swap(st.paper_w, st.paper_h);
1246
1247           // Try grid sizes
1248           if (grid_m)
1249             {
1250               st.rows = grid_n;
1251               st.cols = grid_m;
1252               try_config(st);
1253             }
1254           else
1255             {
1256               for (int r=1; r<=grid_n; r++)
1257                 if (!(grid_n % r))
1258                   {
1259                     st.rows = r;
1260                     st.cols = grid_n / r;
1261                     try_config(st);
1262                   }
1263             }
1264         }
1265     }
1266
1267   if (!found_solution)
1268     err("No feasible solution found");
1269   debug("Best: %dx%d on %.3f x %.3f => scale %.3f, fill %.6f",
1270         best.cols, best.rows,
1271         best.paper_w, best.paper_h,
1272         best.scale, best.fill_factor);
1273   debug_indent -= 4;
1274 }
1275
1276 class nup_page : public page {
1277 public:
1278   vector<page *> orig_pages;
1279   vector<pdf_matrix> xforms;
1280   vector<BBox> tile_boxes;
1281   cropmark_spec *cmarks;
1282   void render(out_context *out, pdf_matrix xform) override;
1283   nup_page(nup_state &st) : page(st.paper_w, st.paper_h) { }
1284   void debug_dump() override
1285     {
1286       debug("N-up printing");
1287       debug_indent += 4;
1288       for (auto p: orig_pages)
1289         p->debug_dump();
1290       debug_indent -= 4;
1291     }
1292 };
1293
1294 void nup_page::render(out_context *out, pdf_matrix parent_xform)
1295 {
1296   for (int i=0; i < (int) orig_pages.size(); i++)
1297     {
1298       if (cmarks->is_bg())
1299         out->contents += cmarks->pdf_stream(out, tile_boxes[i], parent_xform);
1300       orig_pages[i]->render(out, xforms[i] * parent_xform);
1301       if (!cmarks->is_bg())
1302         out->contents += cmarks->pdf_stream(out, tile_boxes[i], parent_xform);
1303     }
1304 }
1305
1306 page *nup_cmd::process_single(vector<page *> &in)
1307 {
1308   BBox page_boxes[num_tiles];
1309   find_config(in, page_boxes);
1310   double tw = best.scale * best.tile_w;
1311   double th = best.scale * best.tile_h;
1312
1313   // Construct transform from paper to grid of tiles
1314   BBox paper_box(best.paper_w, best.paper_h);
1315   marg.shrink_box(&paper_box);
1316   BBox grid_box(best.cols * tw + (best.cols-1) * hspace,
1317                 best.rows * th + (best.rows-1) * vspace);
1318   pdf_matrix place_xform = pos.place(grid_box, paper_box);
1319
1320   nup_page *p = new nup_page(best);
1321   p->image_box = grid_box;
1322   p->image_box.transform(place_xform);
1323
1324   for (int i=0; i<num_tiles; i++)
1325     {
1326       int r, c;
1327       if (fill_by == BY_ROWS || fill_by == BY_TILE)
1328         {
1329           r = i / best.cols;
1330           c = i % best.cols;
1331         }
1332       else
1333         {
1334           c = i / best.rows;
1335           r = i % best.rows;
1336         }
1337
1338       pdf_matrix m;
1339       BBox &page_box = page_boxes[i];
1340       m.shift(-page_box.x_min, -page_box.y_min);
1341       m.scale(best.scale);
1342       page_box.transform(m);
1343
1344       double x = c * (tw + hspace);
1345       double y = (best.rows-1-r) * (th + vspace);
1346       BBox tile_box = BBox(x, y, x+tw, y+th);
1347       m.concat(tpos.place(page_box, tile_box));
1348
1349       p->orig_pages.push_back(in[i]);
1350       p->xforms.push_back(m * place_xform);
1351       p->tile_boxes.push_back(tile_box.transformed(place_xform));
1352       p->cmarks = &cmarks;
1353     }
1354
1355   return p;
1356 }
1357
1358 static const arg_def nup_args[] = {
1359   { "n",        AT_INT | AT_POSITIONAL | AT_MANDATORY,  "Number of tiles on a page" },
1360   { "m",        AT_INT | AT_POSITIONAL,                 "If both n and m are given, produce n x m tiles on a page" },
1361   { "by",       AT_STRING,                              "Tile filling order: rows/cols/tile (default: rows)" },
1362   { "crop",     AT_SWITCH,                              "Crop input pages to their image box" },
1363   { "mixed",    AT_SWITCH,                              "Allow input pages of mixed sizes" },
1364   { "rotate",   AT_SWITCH,                              "Force (non-)rotation" },
1365   { "scale",    AT_DOUBLE,                              "Force specific scaling factor" },
1366   PAPER_ARGS,
1367   MARGIN_ARGS1_NAMED("margin"),
1368   MARGIN_ARGS2("margin"),
1369   POS_ARGS,
1370   CROPMARK_ARGS("c"),
1371   { "tpos",     AT_STRING,                              "Position of images inside tiles (default: tl)" },
1372   { "space",    AT_DIMEN,                               "Space between tiles (default: 0)" },
1373   { "hspace",   AT_DIMEN,                               "Horizontal space between tiles (default: space)" },
1374   { "vspace",   AT_DIMEN,                               "Vertical space between tiles (default: space)" },
1375   { NULL,       0,                                      NULL }
1376 };
1377
1378 /*** cropmarks ***/
1379
1380 class cropmarks_page : public page {
1381   page *orig_page;
1382   cropmark_spec *cm;
1383 public:
1384   void render(out_context *out, pdf_matrix xform) override
1385     {
1386       orig_page->render(out, xform);
1387       out->contents += cm->pdf_stream(out, image_box, xform);
1388     }
1389   void debug_dump() override
1390     {
1391       debug("Add cropmarks");
1392       orig_page->debug_dump();
1393     }
1394   cropmarks_page(page *p, cropmark_spec *cms) : page(p), orig_page(p), cm(cms) { }
1395 };
1396
1397 class cropmarks_cmd : public cmd_exec_simple {
1398   cropmark_spec cm;
1399   page *process_page(page *p) override
1400     {
1401       return new cropmarks_page(p, &cm);
1402     }
1403 public:
1404   cropmarks_cmd(cmd *c) : cm(c) { }
1405 };
1406
1407 static const arg_def cropmarks_args[] = {
1408   CROPMARK_ARGS(""),
1409   { NULL,       0,                              NULL }
1410 };
1411
1412 /*** clip ***/
1413
1414 class clip_page : public page {
1415   page *orig_page;
1416   BBox clip_to;
1417 public:
1418   void render(out_context *out, pdf_matrix xform) override
1419     {
1420       out->contents += "q " + clip_to.transformed(xform).to_rect() + " re W n ";
1421       orig_page->render(out, xform);
1422       out->contents += "Q ";
1423     }
1424   void debug_dump() override
1425     {
1426       debug("Clip [%.3f %.3f %.3f %.3f]", clip_to.x_min, clip_to.y_min, clip_to.x_max, clip_to.y_max);
1427       orig_page->debug_dump();
1428     }
1429   clip_page(page *p, BBox &to) : page(p), orig_page(p), clip_to(to) { }
1430 };
1431
1432 class clip_cmd : public cmd_exec_simple {
1433   double bleed;
1434   page *process_page(page *p) override
1435     {
1436       BBox to = p->image_box.enlarged(bleed);
1437       return new clip_page(p, to);
1438     }
1439 public:
1440   clip_cmd(cmd *c)
1441     {
1442       bleed = c->arg("bleed")->as_double(0);
1443     }
1444 };
1445
1446 static const arg_def clip_args[] = {
1447   { "bleed",    AT_DIMEN,                       "Allow bleeding of image outside its box" },
1448   { NULL,       0,                              NULL }
1449 };
1450
1451 /*** common ***/
1452
1453 class common_cmd : public cmd_exec {
1454   vector<page *> process(vector<page *> &pages) override
1455     {
1456       if (!pages.size())
1457         return pages;
1458
1459       const page *first = pages[0];
1460       BBox pbox(first->width, first->height);
1461       BBox ibox = first->image_box;
1462       for (auto p: pages)
1463         {
1464           BBox pg(p->width, p->height);
1465           pbox.join(pg);
1466           ibox.join(p->image_box);
1467         }
1468
1469       vector<page *> out;
1470       for (auto p: pages)
1471         {
1472           page *q = new xform_page(p, pdf_matrix());
1473           q->width = pbox.width();
1474           q->height = pbox.height();
1475           q->image_box = ibox;
1476           out.push_back(q);
1477         }
1478
1479       return out;
1480     }
1481 public:
1482   common_cmd(cmd *c UNUSED) { }
1483 };
1484
1485 /*** slice ***/
1486
1487 class slice_cmd : public cmd_exec {
1488   paper_spec paper;
1489   pos_spec pos;
1490   margin_spec margin;
1491   double bleed;
1492 public:
1493   slice_cmd(cmd *c) : paper(c), pos(c), margin(c, "margin", "margin")
1494     {
1495       if (is_zero(paper.w) || is_zero(paper.h))
1496         err("Paper format must be given");
1497       bleed = c->arg("bleed")->as_double(0);
1498     }
1499   vector<page *> process(vector<page *> &pages) override;
1500 };
1501
1502 vector<page *> slice_cmd::process(vector<page *> &pages)
1503 {
1504   vector<page *> out;
1505
1506   for (auto p: pages)
1507     {
1508       double pw = paper.w - margin.l - margin.r;
1509       double ph = paper.h - margin.t - margin.b;
1510       if (pw < 0 || ph < 0)
1511         err("Margins larger than paper");
1512
1513       int cols = (int) ceil((p->image_box.width() - 2*mm) / pw);
1514       int rows = (int) ceil((p->image_box.height() - 2*mm) / ph);
1515       BBox big_box(cols*pw, rows*ph);
1516       pdf_matrix placement = pos.place(p->image_box, big_box);
1517
1518       debug("Slicing [%.3f,%.3f] to %dx%d [%.3f,%.3f]", p->image_box.width(), p->image_box.height(), rows, cols, pw, ph);
1519       for (int r=0; r<rows; r++)
1520         for (int c=0; c<cols; c++)
1521           {
1522             pdf_matrix xf = placement;
1523             xf.shift(-c*pw, -(rows-1-r)*ph);
1524             xf.shift(margin.l, margin.t);
1525             page *q = new xform_page(p, xf);
1526             q->width = paper.w;
1527             q->height = paper.h;
1528             BBox slice_box = BBox(margin.l, margin.t, paper.w - margin.r, paper.h - margin.b);
1529             q->image_box = p->image_box.transformed(xf);
1530             q->image_box.intersect(slice_box);
1531             BBox bleeding_slice = slice_box.enlarged(bleed);
1532             out.push_back(new clip_page(q, bleeding_slice));
1533           }
1534     }
1535
1536   return out;
1537 }
1538
1539 static const arg_def slice_args[] = {
1540   PAPER_ARGS,
1541   POS_ARGS,
1542   MARGIN_ARGS1_NAMED("margin"),
1543   MARGIN_ARGS2("margin"),
1544   { "bleed",    AT_DIMEN,                       "Allow bleeding of image outside its box" },
1545   { NULL,       0,                              NULL }
1546 };
1547
1548 /*** Command table ***/
1549
1550 template<typename T> cmd_exec *ctor(cmd *c) { return new T(c); }
1551
1552 const cmd_def cmd_table[] = {
1553   { "add-blank",add_blank_args, 0,      &ctor<add_blank_cmd>,
1554                 "Add blank page(s) after each page" },
1555   { "apply",    no_args,        1,      &ctor<apply_cmd>,
1556                 "Apply commands to selected pages" },
1557   { "book",     book_args,      0,      &ctor<book_cmd>,
1558                 "Prepare booklets for book binding" },
1559   { "clip",     clip_args,      0,      &ctor<clip_cmd>,
1560                 "Suppress page contents drawn outside the image box" },
1561   { "common",   no_args,        0,      &ctor<common_cmd>,
1562                 "Use a common page size and image box for all pages" },
1563   { "cropmarks",cropmarks_args, 0,      &ctor<cropmarks_cmd>,
1564                 "Draw cropping marks around the image box" },
1565   { "debug",    no_args,        0,      &ctor<debug_cmd>,
1566                 "Draw debugging information on the page)" },
1567   { "expand",   expand_args,    0,      &ctor<expand_cmd>,
1568                 "Expand paper around the image" },
1569   { "fit",      fit_args,       0,      &ctor<fit_cmd>,
1570                 "Fit image to a given paper" },
1571   { "flip",     flip_args,      0,      &ctor<flip_cmd>,
1572                 "Flip page horizontally and/or vertically" },
1573   { "margins",  margins_args,   0,      &ctor<margins_cmd>,
1574                 "Define image box by dimensions of margins around it" },
1575   { "merge",    no_args,        0,      &ctor<merge_cmd>,
1576                 "Merge all pages to one by placing them one over another" },
1577   { "modulo",   modulo_args,    1,      &ctor<modulo_cmd>,
1578                 "Act on n-tuples of pages" },
1579   { "move",     move_args,      0,      &ctor<move_cmd>,
1580                 "Shift contents on the page" },
1581   { "null",     no_args,        0,      &ctor<null_cmd>,
1582                 "Do nothing" },
1583   { "nup",      nup_args,       0,      &ctor<nup_cmd>,
1584                 "Combine multiple pages to one (n-up printing)" },
1585   { "paper",    paper_args,     0,      &ctor<paper_cmd>,
1586                 "Place image on a given paper" },
1587   { "rotate",   rotate_args,    0,      &ctor<rotate_cmd>,
1588                 "Rotate the page by multiples of 90 degrees" },
1589   { "scale",    scale_args,     0,      &ctor<scale_cmd>,
1590                 "Scale the page by a given factor" },
1591   { "scaleto",  scaleto_args,   0,      &ctor<scaleto_cmd>,
1592                 "Scale the page to a given size" },
1593   { "select",   no_args,        1,      &ctor<select_cmd>,
1594                 "Select a subset of pages" },
1595   { "slice",    slice_args,     0,      &ctor<slice_cmd>,
1596                 "Slice to smaller pages" },
1597   { NULL,       NULL,           0,      NULL,
1598                 NULL, }
1599 };