]> mj.ucw.cz Git - paperjam.git/blob - cmds.cc
Modulo: "half" switch
[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   bbox = p->bbox;
50   bbox.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   void shrink_box(BBox *bb)
195     {
196       bb->x_min += l;
197       bb->x_max -= r;
198       bb->y_min += b;
199       bb->y_max -= t;
200       if (bb->x_min >= bb->x_max || bb->y_min >= bb->y_max)
201         die("Margins cannot be larger than the whole page");
202     }
203   void expand_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     }
210 };
211
212 #define MARGIN_ARGS1_NAMED(name)        \
213   { name,       AT_DIMEN }
214
215 #define MARGIN_ARGS1_POSNL(name)        \
216   { name,       AT_DIMEN | AT_POSITIONAL }
217
218 #define MARGIN_ARGS2(sx)                \
219   { "h" sx,     AT_DIMEN },             \
220   { "v" sx,     AT_DIMEN },             \
221   { "l" sx,     AT_DIMEN },             \
222   { "r" sx,     AT_DIMEN },             \
223   { "t" sx,     AT_DIMEN },             \
224   { "b" sx,     AT_DIMEN }
225
226 // Scaling preserving aspect ratio
227
228 double scale_to_fit(BBox &from, BBox &to)
229 {
230   double fw = from.width(), fh = from.height();
231   double tw = to.width(), th = to.height();
232   if (is_zero(fw) || is_zero(fh) || is_zero(tw) || is_zero(th))
233     return 1;
234   else
235     return min(tw/fw, th/fh);
236 }
237
238 /*** move ***/
239
240 class move_cmd : public cmd_exec_simple {
241   double x, y;
242 public:
243   move_cmd(cmd *c)
244     {
245       x = c->arg("x")->as_double(0);
246       y = c->arg("y")->as_double(0);
247     }
248   page *process_page(page *p) override
249     {
250       pdf_matrix m;
251       m.shift(x, y);
252       return new xform_page(p, m);
253     }
254 };
255
256 static const arg_def move_args[] = {
257   { "x",        AT_DIMEN | AT_MANDATORY | AT_POSITIONAL },
258   { "y",        AT_DIMEN | AT_MANDATORY | AT_POSITIONAL },
259   { NULL,       0 }
260 };
261
262 /*** scale ***/
263
264 class scale_cmd : public cmd_exec_simple {
265   double x_factor, y_factor;
266 public:
267   scale_cmd(cmd *c)
268     {
269       x_factor = c->arg("x")->as_double(1);
270       y_factor = c->arg("y")->as_double(x_factor);
271     }
272   page *process_page(page *p) override
273     {
274       pdf_matrix m;
275       m.scale(x_factor, y_factor);
276       return new xform_page(p, m);
277     }
278 };
279
280 static const arg_def scale_args[] = {
281   { "x",        AT_DOUBLE | AT_MANDATORY | AT_POSITIONAL },
282   { "y",        AT_DOUBLE | AT_POSITIONAL },
283   { NULL,       0 }
284 };
285
286 /*** rotate ***/
287
288 class rotate_cmd : public cmd_exec_simple {
289   int deg;
290 public:
291   rotate_cmd(cmd *c)
292     {
293       deg = c->arg("deg")->as_int(0) % 360;
294       if (deg < 0)
295         deg += 360;
296       if (deg % 90)
297         die("Rotate requires a multiple of 90 degrees");
298     }
299   page *process_page(page *p) override
300     {
301       pdf_matrix m;
302       switch (deg)
303         {
304         case 0:
305           break;
306         case 90:
307           m.rotate_deg(-90);
308           m.shift(0, p->width);
309           break;
310         case 180:
311           m.rotate_deg(180);
312           m.shift(p->width, p->height);
313           break;
314         case 270:
315           m.rotate_deg(90);
316           m.shift(p->height, 0);
317           break;
318         default:
319           abort();
320         }
321       return new xform_page(p, m);
322     }
323 };
324
325 static const arg_def rotate_args[] = {
326   { "angle",    AT_INT | AT_MANDATORY | AT_POSITIONAL },
327   { NULL,       0 }
328 };
329
330 /*** flip ***/
331
332 class flip_cmd : public cmd_exec_simple {
333   bool horizontal;
334   bool vertical;
335 public:
336   flip_cmd(cmd *c)
337     {
338       horizontal = c->arg("h")->as_int(0);
339       vertical = c->arg("v")->as_int(0);
340       if (!horizontal && !vertical)
341         die("Flip has no direction specified");
342     }
343   page *process_page(page *p) override
344     {
345       pdf_matrix m;
346       if (vertical)
347         {
348           m.scale(1, -1);
349           m.shift(0, p->height);
350         }
351       if (horizontal)
352         {
353           m.scale(-1, 1);
354           m.shift(p->width, 0);
355         }
356       return new xform_page(p, m);
357     }
358 };
359
360 static const arg_def flip_args[] = {
361   { "h",        AT_SWITCH },
362   { "v",        AT_SWITCH },
363   { NULL,       0 }
364 };
365
366 /*** select ***/
367
368 class select_cmd : public cmd_exec {
369   pipeline *pipe;
370 public:
371   select_cmd(cmd *c)
372     {
373       pipe = c->pipe;
374     }
375   vector<page *> process(vector<page *> &pages) override;
376 };
377
378 static int validate_page_index(vector<page *> &pages, int idx)
379 {
380   if (idx >= 1 && idx <= (int) pages.size())
381     return idx - 1;
382   if (idx <= -1 && idx >= (int) -pages.size())
383     return idx + pages.size();
384   die("Page index %d out of range", idx);
385 }
386
387 vector<page *> select_cmd::process(vector<page *> &pages)
388 {
389   vector<page *> out;
390   for (auto pb: pipe->branches)
391     {
392       vector<page *> selected;
393       for (auto ps: pb->selectors)
394         {
395           int f = validate_page_index(pages, ps.from);
396           int t = validate_page_index(pages, ps.to);
397           int step = (f <= t) ? 1 : -1;
398           for (int i=f; f<=t; f += step)
399             selected.push_back(pages[i]);
400         }
401       auto processed = run_command_list(pb->commands, selected);
402       for (auto p: processed)
403         out.push_back(p);
404     }
405   return out;
406 }
407
408 /*** apply ***/
409
410 class apply_cmd : public cmd_exec {
411   pipeline *pipe;
412 public:
413   apply_cmd(cmd *c)
414     {
415       pipe = c->pipe;
416     }
417   vector<page *> process(vector<page *> &pages) override;
418 };
419
420 static pipeline_branch *find_branch(pipeline *pipe, vector <page *> &pages, int idx)
421 {
422   for (auto pb: pipe->branches)
423     for (auto ps: pb->selectors)
424       {
425         int f = validate_page_index(pages, ps.from);
426         int t = validate_page_index(pages, ps.to);
427         if (f <= idx && idx <= t || t <= idx && idx <= f)
428           return pb;
429       }
430   return NULL;
431 }
432
433 vector<page *> apply_cmd::process(vector<page *> &pages)
434 {
435   vector<page *> out;
436
437   int cnt = 0;
438   for (auto p: pages)
439     {
440       pipeline_branch *pb = find_branch(pipe, pages, cnt);
441       if (pb)
442         {
443           vector<page *> tmp;
444           tmp.push_back(p);
445           auto processed = run_command_list(pb->commands, tmp);
446           for (auto q: processed)
447             out.push_back(q);
448         }
449       else
450         out.push_back(p);
451       cnt++;
452     }
453
454   return out;
455 }
456
457 /*** modulo ***/
458
459 class modulo_cmd : public cmd_exec {
460   pipeline *pipe;
461   int n;
462   bool half;
463 public:
464   modulo_cmd(cmd *c)
465     {
466       n = c->arg("n")->as_int(0);
467       if (n <= 0)
468         die("Modulo must have n > 0");
469       half = c->arg("half")->as_int(0);
470       pipe = c->pipe;
471     }
472   vector<page *> process(vector<page *> &pages) override;
473 };
474
475 vector<page *> modulo_cmd::process(vector<page *> &pages)
476 {
477   vector<page *> out;
478   int tuples = ((int) pages.size() + n - 1) / n;
479   int use_tuples = half ? tuples/2 : tuples;
480
481   for (int tuple=0; tuple < use_tuples; tuple++)
482     {
483       debug("# Tuple %d", tuple);
484       debug_indent += 4;
485       for (auto pb: pipe->branches)
486         {
487           vector<page *> tmp;
488           for (auto ps: pb->selectors)
489             {
490               int f = ps.from;
491               int t = ps.to;
492               int step = (f <= t) ? 1 : -1;
493               for (int i=f; i<=t; i += step)
494                 {
495                   int j;
496                   if (i > 0 && i <= n)
497                     j = tuple*n + i - 1;
498                   else if (i < 0 && i >= -n)
499                     j = (tuples-1-tuple)*n + (-i) - 1;
500                   else
501                     die("Modulo: invalid index %d", i);
502                   if (j < (int) pages.size())
503                     tmp.push_back(pages[j]);
504                   else
505                     {
506                       page *ref_page = pages[tuple*n];
507                       tmp.push_back(new empty_page(ref_page->width, ref_page->height));
508                     }
509                 }
510             }
511           auto processed = run_command_list(pb->commands, tmp);
512           for (auto q: processed)
513             out.push_back(q);
514         }
515       debug_indent -= 4;
516     }
517
518   return out;
519 }
520
521 static const arg_def modulo_args[] = {
522   { "n",        AT_INT | AT_MANDATORY | AT_POSITIONAL },
523   { "half",     AT_SWITCH },
524   { NULL,       0 }
525 };
526
527 /*** draw-bbox ***/
528
529 class draw_bbox_cmd : public cmd_exec {
530 public:
531   draw_bbox_cmd(cmd *c UNUSED) { }
532   vector<page *> process(vector<page *> &pages) override;
533 };
534
535 class draw_bbox_page : public page {
536   page *orig_page;
537 public:
538   void render(out_context *out, pdf_matrix xform) override;
539   draw_bbox_page(page *p) : page(p) { orig_page = p; }
540 };
541
542 void draw_bbox_page::render(out_context *out, pdf_matrix xform)
543 {
544   orig_page->render(out, xform);
545   out->contents +=
546      "q " +
547      xform.to_string() + " cm " +
548      "0 1 0 RG " +
549      bbox.to_rect() + " re S " +
550      "Q ";
551 }
552
553 vector<page *> draw_bbox_cmd::process(vector<page *> &pages)
554 {
555   vector<page *> out;
556   for (auto p: pages)
557     out.push_back(new draw_bbox_page(p));
558   return out;
559 }
560
561 /*** merge ***/
562
563 class merge_cmd : public cmd_exec {
564 public:
565   merge_cmd(cmd *c UNUSED) { }
566   vector<page *> process(vector<page *> &pages) override;
567 };
568
569 class merge_page : public page {
570   vector<page *> orig_pages;
571 public:
572   merge_page(vector<page *> &orig) : page(0, 0)
573     {
574       orig_pages = orig;
575       bool first = true;
576       for (auto p: orig)
577         {
578           if (first)
579             {
580               width = p->width;
581               height = p->height;
582               bbox = p->bbox;
583               first = false;
584             }
585           else
586             {
587               if (!is_equal(width, p->width) || !is_equal(height, p->height))
588                 die("All pages participating in a merge must have the same dimensions");
589               bbox.join(p->bbox);
590             }
591         }
592     }
593   void render(out_context *out, pdf_matrix xform) override
594     {
595       for (auto p: orig_pages)
596         p->render(out, xform);
597     }
598 };
599
600 vector<page *> merge_cmd::process(vector<page *> &pages)
601 {
602   vector<page *> out;
603   if (pages.size())
604     out.push_back(new merge_page(pages));
605   return out;
606 }
607
608 /*** paper ***/
609
610 class paper_cmd : public cmd_exec_simple {
611   paper_spec paper;
612   pos_spec pos;
613 public:
614   paper_cmd(cmd *c) : paper(c), pos(c) { }
615   page *process_page(page *p) override
616     {
617       BBox paper_box = BBox(paper.w, paper.h);
618       pdf_matrix xf = pos.place(p->bbox, paper_box);
619       page *q = new xform_page(p, xf);
620       q->width = paper.w;
621       q->height = paper.h;
622       return q;
623     }
624 };
625
626 static const arg_def paper_args[] = {
627   PAPER_ARGS,
628   POS_ARGS,
629   { NULL,       0 }
630 };
631
632 /*** scaleto ***/
633
634 class scaleto_cmd : public cmd_exec_simple {
635   paper_spec paper;
636   pos_spec pos;
637 public:
638   scaleto_cmd(cmd *c) : paper(c), pos(c) { }
639   page *process_page(page *p) override
640     {
641       BBox orig_box = BBox(p->width, p->height);
642       BBox paper_box = BBox(paper.w, paper.h);
643       pdf_matrix xf;
644       xf.scale(scale_to_fit(orig_box, paper_box));
645       orig_box.transform(xf);
646       xf.concat(pos.place(orig_box, paper_box));
647       page *q = new xform_page(p, xf);
648       q->width = paper.w;
649       q->height = paper.h;
650       return q;
651     }
652 };
653
654 static const arg_def scaleto_args[] = {
655   PAPER_ARGS,
656   POS_ARGS,
657   { NULL,       0 }
658 };
659
660 /*** fit ***/
661
662 class fit_cmd : public cmd_exec_simple {
663   paper_spec paper;
664   pos_spec pos;
665   margin_spec marg;
666 public:
667   fit_cmd(cmd *c) : paper(c, true), pos(c), marg(c, "margin", "margin") { }
668   page *process_page(page *p) override
669     {
670       pdf_matrix xf;
671       page *q;
672
673       if (!is_zero(paper.w) && !is_zero(paper.h))
674         {
675           // Paper given: scale image to fit paper
676           BBox orig_box = p->bbox;
677           BBox paper_box = BBox(paper.w, paper.h);
678           marg.shrink_box(&paper_box);
679           xf.scale(scale_to_fit(orig_box, paper_box));
680           orig_box.transform(xf);
681           xf.concat(pos.place(orig_box, paper_box));
682           q = new xform_page(p, xf);
683           q->width = paper.w;
684           q->height = paper.h;
685         }
686       else
687         {
688           // No paper given: adjust paper to fit image
689           xf.shift(-p->bbox.x_min, -p->bbox.y_min);
690           xf.shift(marg.l, marg.b);
691           q = new xform_page(p, xf);
692           q->width = p->bbox.width() + marg.l + marg.r;
693           q->height = p->bbox.height() + marg.t + marg.b;
694         }
695       return q;
696     }
697 };
698
699 static const arg_def fit_args[] = {
700   PAPER_ARGS,
701   POS_ARGS,
702   MARGIN_ARGS1_NAMED("margin"),
703   MARGIN_ARGS2("margin"),
704   { NULL,       0 }
705 };
706
707 /*** expand ***/
708
709 class expand_cmd : public cmd_exec_simple {
710   margin_spec marg;
711 public:
712   expand_cmd(cmd *c) : marg(c, "by", "") { }
713   page *process_page(page *p) override
714     {
715       pdf_matrix xf;
716       xf.shift(marg.l, marg.b);
717       page *q = new xform_page(p, xf);
718       q->width = p->width + marg.l + marg.r;
719       q->height = p->height + marg.t + marg.b;
720       if (q->width < 0.001 || q->height < 0.001)
721         die("Expansion must result in positive page dimensions");
722       return q;
723     }
724 };
725
726 static const arg_def expand_args[] = {
727   MARGIN_ARGS1_POSNL("by"),
728   MARGIN_ARGS2(""),
729   { NULL,       0 }
730 };
731
732 /*** margins ***/
733
734 class margins_cmd : public cmd_exec_simple {
735   margin_spec marg;
736 public:
737   margins_cmd(cmd *c) : marg(c, "size", "") { }
738   page *process_page(page *p) override
739     {
740       pdf_matrix xf;
741       xf.shift(-p->bbox.x_min, -p->bbox.y_min);
742       xf.shift(marg.l, marg.b);
743       page *q = new xform_page(p, xf);
744       q->width = p->bbox.width() + marg.l + marg.r;
745       q->height = p->bbox.height() + marg.t + marg.b;
746       if (q->width < 0.001 || q->height < 0.001)
747         die("Margins must result in positive page dimensions");
748       return q;
749     }
750 };
751
752 static const arg_def margins_args[] = {
753   MARGIN_ARGS1_POSNL("size"),
754   MARGIN_ARGS2(""),
755   { NULL,       0 }
756 };
757
758 /*** add-blank ***/
759
760 class add_blank_cmd : public cmd_exec {
761   int n;
762   paper_spec paper;
763 public:
764   add_blank_cmd(cmd *c) : paper(c, true)
765     {
766       n = c->arg("n")->as_int(1);
767     }
768   vector<page *> process(vector<page *> &pages) override;
769 };
770
771 vector<page *> add_blank_cmd::process(vector<page *> &pages)
772 {
773   vector<page *> out;
774
775   for (auto p: pages)
776     {
777       out.push_back(p);
778       for (int i=0; i<n; i++)
779         {
780           double w = paper.w, h = paper.h;
781           if (is_zero(w) || is_zero(h))
782             w = p->width, h = p->height;
783           out.push_back(new empty_page(w, h));
784         }
785     }
786
787   return out;
788 }
789
790 static const arg_def add_blank_args[] = {
791   { "n",        AT_INT | AT_POSITIONAL },
792   PAPER_ARGS,
793   { NULL,       0 }
794 };
795
796 /*** book ***/
797
798 class book_cmd : public cmd_exec {
799   int n;
800 public:
801   book_cmd(cmd *c)
802     {
803       n = c->arg("n")->as_int(0);
804       if (n % 4)
805         die("Number of pages per signature must be divisible by 4");
806     }
807   vector<page *> process(vector<page *> &pages) override;
808 };
809
810 vector<page *> book_cmd::process(vector<page *> &pages)
811 {
812   vector<page *> in, out;
813
814   in = pages;
815   while (in.size() % 4)
816     in.push_back(new empty_page(in[0]->width, in[0]->height));
817
818   int i = 0;
819   while (i < (int) in.size())
820     {
821       int sig = in.size() - i;
822       if (n)
823         sig = min(sig, n);
824       for (int j=0; j<sig/2; j+=2)
825         {
826           out.push_back(in[i + sig-1-j]);
827           out.push_back(in[i + j]);
828           out.push_back(in[i + j+1]);
829           out.push_back(in[i + sig-2-j]);
830         }
831       i += sig;
832     }
833
834   return out;
835 }
836
837 static const arg_def book_args[] = {
838   { "n",        AT_INT | AT_POSITIONAL },
839   { NULL,       0 }
840 };
841
842 /*** Command table ***/
843
844 template<typename T> cmd_exec *ctor(cmd *c) { return new T(c); }
845
846 const cmd_def cmd_table[] = {
847   { "null",     no_args,        0,      &ctor<null_cmd>         },
848   { "move",     move_args,      0,      &ctor<move_cmd>         },
849   { "scale",    scale_args,     0,      &ctor<scale_cmd>        },
850   { "rotate",   rotate_args,    0,      &ctor<rotate_cmd>       },
851   { "flip",     flip_args,      0,      &ctor<flip_cmd>         },
852   { "select",   no_args,        1,      &ctor<select_cmd>       },
853   { "apply",    no_args,        1,      &ctor<apply_cmd>        },
854   { "modulo",   modulo_args,    1,      &ctor<modulo_cmd>       },
855   { "draw-bbox",no_args,        0,      &ctor<draw_bbox_cmd>    },
856   { "merge",    no_args,        0,      &ctor<merge_cmd>        },
857   { "paper",    paper_args,     0,      &ctor<paper_cmd>        },
858   { "scaleto",  scaleto_args,   0,      &ctor<scaleto_cmd>      },
859   { "fit",      fit_args,       0,      &ctor<fit_cmd>          },
860   { "expand",   expand_args,    0,      &ctor<expand_cmd>       },
861   { "margins",  margins_args,   0,      &ctor<margins_cmd>      },
862   { "add-blank",add_blank_args, 0,      &ctor<add_blank_cmd>    },
863   { "book",     book_args,      0,      &ctor<book_cmd>         },
864   { NULL,       NULL,           0,      NULL    }
865 };