]> mj.ucw.cz Git - paperjam.git/blob - cmds.cc
Implemented paper, scaleto, fit
[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_ARGS(basic, sx)          \
213   { basic,      AT_DIMEN },             \
214   { "h" sx,     AT_DIMEN },             \
215   { "v" sx,     AT_DIMEN },             \
216   { "l" sx,     AT_DIMEN },             \
217   { "r" sx,     AT_DIMEN },             \
218   { "t" sx,     AT_DIMEN },             \
219   { "b" sx,     AT_DIMEN }
220
221 // Scaling preserving aspect ratio
222
223 double scale_to_fit(BBox &from, BBox &to)
224 {
225   double fw = from.width(), fh = from.height();
226   double tw = to.width(), th = to.height();
227   if (is_zero(fw) || is_zero(fh) || is_zero(tw) || is_zero(th))
228     return 1;
229   else
230     return min(tw/fw, th/fh);
231 }
232
233 /*** move ***/
234
235 class move_cmd : public cmd_exec_simple {
236   double x, y;
237 public:
238   move_cmd(cmd *c)
239     {
240       x = c->arg("x")->as_double(0);
241       y = c->arg("y")->as_double(0);
242     }
243   page *process_page(page *p) override
244     {
245       pdf_matrix m;
246       m.shift(x, y);
247       return new xform_page(p, m);
248     }
249 };
250
251 static const arg_def move_args[] = {
252   { "x",        AT_DIMEN | AT_MANDATORY | AT_POSITIONAL },
253   { "y",        AT_DIMEN | AT_MANDATORY | AT_POSITIONAL },
254   { NULL,       0 }
255 };
256
257 /*** scale ***/
258
259 class scale_cmd : public cmd_exec_simple {
260   double x_factor, y_factor;
261 public:
262   scale_cmd(cmd *c)
263     {
264       x_factor = c->arg("x")->as_double(1);
265       y_factor = c->arg("y")->as_double(x_factor);
266     }
267   page *process_page(page *p) override
268     {
269       pdf_matrix m;
270       m.scale(x_factor, y_factor);
271       return new xform_page(p, m);
272     }
273 };
274
275 static const arg_def scale_args[] = {
276   { "x",        AT_DOUBLE | AT_MANDATORY | AT_POSITIONAL },
277   { "y",        AT_DOUBLE | AT_POSITIONAL },
278   { NULL,       0 }
279 };
280
281 /*** rotate ***/
282
283 class rotate_cmd : public cmd_exec_simple {
284   int deg;
285 public:
286   rotate_cmd(cmd *c)
287     {
288       deg = c->arg("deg")->as_int(0) % 360;
289       if (deg < 0)
290         deg += 360;
291       if (deg % 90)
292         die("Rotate requires a multiple of 90 degrees");
293     }
294   page *process_page(page *p) override
295     {
296       pdf_matrix m;
297       switch (deg)
298         {
299         case 0:
300           break;
301         case 90:
302           m.rotate_deg(-90);
303           m.shift(0, p->width);
304           break;
305         case 180:
306           m.rotate_deg(180);
307           m.shift(p->width, p->height);
308           break;
309         case 270:
310           m.rotate_deg(90);
311           m.shift(p->height, 0);
312           break;
313         default:
314           abort();
315         }
316       return new xform_page(p, m);
317     }
318 };
319
320 static const arg_def rotate_args[] = {
321   { "angle",    AT_INT | AT_MANDATORY | AT_POSITIONAL },
322   { NULL,       0 }
323 };
324
325 /*** flip ***/
326
327 class flip_cmd : public cmd_exec_simple {
328   bool horizontal;
329   bool vertical;
330 public:
331   flip_cmd(cmd *c)
332     {
333       horizontal = c->arg("h")->as_int(0);
334       vertical = c->arg("v")->as_int(0);
335       if (!horizontal && !vertical)
336         die("Flip has no direction specified");
337     }
338   page *process_page(page *p) override
339     {
340       pdf_matrix m;
341       if (vertical)
342         {
343           m.scale(1, -1);
344           m.shift(0, p->height);
345         }
346       if (horizontal)
347         {
348           m.scale(-1, 1);
349           m.shift(p->width, 0);
350         }
351       return new xform_page(p, m);
352     }
353 };
354
355 static const arg_def flip_args[] = {
356   { "h",        AT_SWITCH },
357   { "v",        AT_SWITCH },
358   { NULL,       0 }
359 };
360
361 /*** select ***/
362
363 class select_cmd : public cmd_exec {
364   pipeline *pipe;
365 public:
366   select_cmd(cmd *c)
367     {
368       pipe = c->pipe;
369     }
370   vector<page *> process(vector<page *> &pages);
371 };
372
373 static int validate_page_index(vector<page *> &pages, int idx)
374 {
375   if (idx >= 1 && idx <= (int) pages.size())
376     return idx - 1;
377   if (idx <= -1 && idx >= (int) -pages.size())
378     return idx + pages.size();
379   die("Page index %d out of range", idx);
380 }
381
382 vector<page *> select_cmd::process(vector<page *> &pages)
383 {
384   vector<page *> out;
385   for (auto pb: pipe->branches)
386     {
387       vector<page *> selected;
388       for (auto ps: pb->selectors)
389         {
390           int f = validate_page_index(pages, ps.from);
391           int t = validate_page_index(pages, ps.to);
392           int step = (f <= t) ? 1 : -1;
393           for (int i=f; f<=t; f += step)
394             selected.push_back(pages[i]);
395         }
396       auto processed = run_command_list(pb->commands, selected);
397       for (auto p: processed)
398         out.push_back(p);
399     }
400   return out;
401 }
402
403 /*** apply ***/
404
405 class apply_cmd : public cmd_exec {
406   pipeline *pipe;
407 public:
408   apply_cmd(cmd *c)
409     {
410       pipe = c->pipe;
411     }
412   vector<page *> process(vector<page *> &pages);
413 };
414
415 static pipeline_branch *find_branch(pipeline *pipe, vector <page *> &pages, int idx)
416 {
417   for (auto pb: pipe->branches)
418     for (auto ps: pb->selectors)
419       {
420         int f = validate_page_index(pages, ps.from);
421         int t = validate_page_index(pages, ps.to);
422         if (f <= idx && idx <= t || t <= idx && idx <= f)
423           return pb;
424       }
425   return NULL;
426 }
427
428 vector<page *> apply_cmd::process(vector<page *> &pages)
429 {
430   vector<page *> out;
431
432   int cnt = 0;
433   for (auto p: pages)
434     {
435       pipeline_branch *pb = find_branch(pipe, pages, cnt);
436       if (pb)
437         {
438           vector<page *> tmp;
439           tmp.push_back(p);
440           auto processed = run_command_list(pb->commands, tmp);
441           for (auto q: processed)
442             out.push_back(q);
443         }
444       else
445         out.push_back(p);
446       cnt++;
447     }
448
449   return out;
450 }
451
452 /*** modulo ***/
453
454 class modulo_cmd : public cmd_exec {
455   pipeline *pipe;
456   int n;
457 public:
458   modulo_cmd(cmd *c)
459     {
460       n = c->arg("n")->as_int(0);
461       if (n <= 0)
462         die("Modulo must have n > 0");
463       pipe = c->pipe;
464     }
465   vector<page *> process(vector<page *> &pages);
466 };
467
468 vector<page *> modulo_cmd::process(vector<page *> &pages)
469 {
470   vector<page *> out;
471   int tuples = ((int) pages.size() + n - 1) / n;
472
473   for (int tuple=0; tuple < tuples; tuple++)
474     {
475       debug("# Tuple %d", tuple);
476       debug_indent += 4;
477       for (auto pb: pipe->branches)
478         {
479           vector<page *> tmp;
480           for (auto ps: pb->selectors)
481             {
482               int f = ps.from;
483               int t = ps.to;
484               int step = (f <= t) ? 1 : -1;
485               for (int i=f; i<=t; i += step)
486                 {
487                   int j;
488                   if (i > 0 && i <= n)
489                     j = tuple*n + i - 1;
490                   else if (i < 0 && i >= -n)
491                     j = (tuples-1-tuple)*n + (-i) - 1;
492                   else
493                     die("Modulo: invalid index %d", i);
494                   if (j < (int) pages.size())
495                     tmp.push_back(pages[j]);
496                   else
497                     {
498                       page *ref_page = pages[tuple*n];
499                       tmp.push_back(new empty_page(ref_page->width, ref_page->height));
500                     }
501                 }
502             }
503           auto processed = run_command_list(pb->commands, tmp);
504           for (auto q: processed)
505             out.push_back(q);
506         }
507       debug_indent -= 4;
508     }
509
510   return out;
511 }
512
513 static const arg_def modulo_args[] = {
514   { "n",        AT_INT | AT_MANDATORY | AT_POSITIONAL },
515   { NULL,       0 }
516 };
517
518 /*** draw_bbox ***/
519
520 class draw_bbox_cmd : public cmd_exec {
521 public:
522   draw_bbox_cmd(cmd *c UNUSED) { }
523   vector<page *> process(vector<page *> &pages);
524 };
525
526 class draw_bbox_page : public page {
527   page *orig_page;
528 public:
529   void render(out_context *out, pdf_matrix xform) override;
530   draw_bbox_page(page *p) : page(p) { orig_page = p; }
531 };
532
533 void draw_bbox_page::render(out_context *out, pdf_matrix xform)
534 {
535   orig_page->render(out, xform);
536   out->contents +=
537      "q " +
538      xform.to_string() + " cm " +
539      "0 1 0 RG " +
540      bbox.to_rect() + " re S " +
541      "Q ";
542 }
543
544 vector<page *> draw_bbox_cmd::process(vector<page *> &pages)
545 {
546   vector<page *> out;
547   for (auto p: pages)
548     out.push_back(new draw_bbox_page(p));
549   return out;
550 }
551
552 /*** merge ***/
553
554 class merge_cmd : public cmd_exec {
555 public:
556   merge_cmd(cmd *c UNUSED) { }
557   vector<page *> process(vector<page *> &pages);
558 };
559
560 class merge_page : public page {
561   vector<page *> orig_pages;
562 public:
563   merge_page(vector<page *> &orig) : page(0, 0)
564     {
565       orig_pages = orig;
566       bool first = true;
567       for (auto p: orig)
568         {
569           if (first)
570             {
571               width = p->width;
572               height = p->height;
573               bbox = p->bbox;
574               first = false;
575             }
576           else
577             {
578               if (!is_equal(width, p->width) || !is_equal(height, p->height))
579                 die("All pages participating in a merge must have the same dimensions");
580               bbox.join(p->bbox);
581             }
582         }
583     }
584   void render(out_context *out, pdf_matrix xform) override
585     {
586       for (auto p: orig_pages)
587         p->render(out, xform);
588     }
589 };
590
591 vector<page *> merge_cmd::process(vector<page *> &pages)
592 {
593   vector<page *> out;
594   if (pages.size())
595     out.push_back(new merge_page(pages));
596   return out;
597 }
598
599 /*** paper ***/
600
601 class paper_cmd : public cmd_exec_simple {
602   paper_spec paper;
603   pos_spec pos;
604 public:
605   paper_cmd(cmd *c) : paper(c), pos(c) { }
606   page *process_page(page *p) override
607     {
608       BBox paper_box = BBox(paper.w, paper.h);
609       pdf_matrix xf = pos.place(p->bbox, paper_box);
610       page *q = new xform_page(p, xf);
611       q->width = paper.w;
612       q->height = paper.h;
613       return q;
614     }
615 };
616
617 static const arg_def paper_args[] = {
618   PAPER_ARGS,
619   POS_ARGS,
620   { NULL,       0 }
621 };
622
623 /*** scaleto ***/
624
625 class scaleto_cmd : public cmd_exec_simple {
626   paper_spec paper;
627   pos_spec pos;
628 public:
629   scaleto_cmd(cmd *c) : paper(c), pos(c) { }
630   page *process_page(page *p) override
631     {
632       BBox orig_box = BBox(p->width, p->height);
633       BBox paper_box = BBox(paper.w, paper.h);
634       pdf_matrix xf;
635       xf.scale(scale_to_fit(orig_box, paper_box));
636       orig_box.transform(xf);
637       xf.concat(pos.place(orig_box, paper_box));
638       page *q = new xform_page(p, xf);
639       q->width = paper.w;
640       q->height = paper.h;
641       return q;
642     }
643 };
644
645 static const arg_def scaleto_args[] = {
646   PAPER_ARGS,
647   POS_ARGS,
648   { NULL,       0 }
649 };
650
651 /*** fit ***/
652
653 class fit_cmd : public cmd_exec_simple {
654   paper_spec paper;
655   pos_spec pos;
656   margin_spec marg;
657 public:
658   fit_cmd(cmd *c) : paper(c, true), pos(c), marg(c, "margin", "margin") { }
659   page *process_page(page *p) override
660     {
661       pdf_matrix xf;
662       page *q;
663
664       if (!is_zero(paper.w) && !is_zero(paper.h))
665         {
666           // Paper given: scale image to fit paper
667           BBox orig_box = p->bbox;
668           BBox paper_box = BBox(paper.w, paper.h);
669           marg.shrink_box(&paper_box);
670           xf.scale(scale_to_fit(orig_box, paper_box));
671           orig_box.transform(xf);
672           xf.concat(pos.place(orig_box, paper_box));
673           q = new xform_page(p, xf);
674           q->width = paper.w;
675           q->height = paper.h;
676         }
677       else
678         {
679           // No paper given: adjust paper to fit image
680           xf.shift(-p->bbox.x_min, -p->bbox.y_min);
681           xf.shift(marg.l, marg.b);
682           q = new xform_page(p, xf);
683           q->width = p->bbox.width() + marg.l + marg.r;
684           q->height = p->bbox.height() + marg.t + marg.b;
685         }
686       return q;
687     }
688 };
689
690 static const arg_def fit_args[] = {
691   PAPER_ARGS,
692   POS_ARGS,
693   MARGIN_ARGS("margin", "margin"),
694   { NULL,       0 }
695 };
696
697 /*** Command table ***/
698
699 template<typename T> cmd_exec *ctor(cmd *c) { return new T(c); }
700
701 const cmd_def cmd_table[] = {
702   { "null",     no_args,        0,      &ctor<null_cmd>         },
703   { "move",     move_args,      0,      &ctor<move_cmd>         },
704   { "scale",    scale_args,     0,      &ctor<scale_cmd>        },
705   { "rotate",   rotate_args,    0,      &ctor<rotate_cmd>       },
706   { "flip",     flip_args,      0,      &ctor<flip_cmd>         },
707   { "select",   no_args,        1,      &ctor<select_cmd>       },
708   { "apply",    no_args,        1,      &ctor<apply_cmd>        },
709   { "modulo",   modulo_args,    1,      &ctor<modulo_cmd>       },
710   { "draw_bbox",no_args,        0,      &ctor<draw_bbox_cmd>    },
711   { "merge",    no_args,        0,      &ctor<merge_cmd>        },
712   { "paper",    paper_args,     0,      &ctor<paper_cmd>        },
713   { "scaleto",  scaleto_args,   0,      &ctor<scaleto_cmd>      },
714   { "fit",      fit_args,       0,      &ctor<fit_cmd>          },
715   { NULL,       NULL,           0,      NULL    }
716 };