4 * (c) 2018 Martin Mares <mj@ucw.cz>
16 class null_cmd : public cmd_exec {
18 null_cmd(cmd *c UNUSED) { }
19 vector<page *> process(vector<page *> &pages) override { return pages; }
22 static const arg_def no_args[] = {
26 /*** Generic routines ***/
30 class xform_page : public page {
34 void render(out_context *out, pdf_matrix xform) override;
35 xform_page(page *p, pdf_matrix xf);
38 xform_page::xform_page(page *p, pdf_matrix xf)
44 BBox media(p->width, p->height);
46 width = media.width();
47 height = media.height();
53 void xform_page::render(out_context *out, pdf_matrix parent_xform)
55 orig_page->render(out, xform * parent_xform);
58 // Commands acting on individual pages
60 class cmd_exec_simple : public cmd_exec {
61 virtual page *process_page(page *p) = 0;
62 vector<page *> process(vector<page *> &pages) override;
65 vector<page *> cmd_exec_simple::process(vector<page *> &pages)
69 out.push_back(process_page(p));
73 // Paper specifications
78 paper_spec(cmd *c, bool maybe=true)
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)
88 if (aw->given() != ah->given() || aname->given() == aw->given())
89 die("Either paper format name or width and height must be given");
92 const char *name = aname->as_string("").c_str();
93 const paper *pap = paperinfo(name);
95 die("No paper called %s is known", name);
96 w = paperpswidth(pap);
97 h = paperpsheight(pap);
101 w = aw->as_double(0);
102 h = ah->as_double(0);
108 { "paper", AT_STRING | AT_POSITIONAL }, \
112 // Position specification
117 pos_spec() { v = h = 0; }
121 die("Value of pos must have two characters");
124 else if (s[0] == 'c')
126 else if (s[0] == 'b')
129 die("First character of pos must be t/c/b");
132 else if (s[1] == 'c')
134 else if (s[1] == 'r')
137 die("Second character of pos must be l/c/r");
139 pos_spec(cmd *c) : pos_spec(c->arg("pos")->as_string("cc")) { }
140 pdf_matrix place(BBox &inner, BBox &outer)
143 m.shift(-inner.x_min, -inner.y_min);
149 m.shift((outer.width() - inner.width()) / 2, 0);
152 m.shift(outer.width() - inner.width(), 0);
162 m.shift(0, (outer.height() - inner.height()) / 2);
165 m.shift(0, outer.height() - inner.height());
170 m.shift(outer.x_min, outer.y_min);
183 margin_spec(cmd *c, string basic, string sx)
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);
194 void shrink_box(BBox *bb)
200 if (bb->x_min >= bb->x_max || bb->y_min >= bb->y_max)
201 die("Margins cannot be larger than the whole page");
203 void expand_box(BBox *bb)
212 #define MARGIN_ARGS1_NAMED(name) \
215 #define MARGIN_ARGS1_POSNL(name) \
216 { name, AT_DIMEN | AT_POSITIONAL }
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 }, \
226 // Scaling preserving aspect ratio
228 double scale_to_fit(BBox &from, BBox &to)
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))
235 return min(tw/fw, th/fh);
240 class move_cmd : public cmd_exec_simple {
245 x = c->arg("x")->as_double(0);
246 y = c->arg("y")->as_double(0);
248 page *process_page(page *p) override
252 return new xform_page(p, m);
256 static const arg_def move_args[] = {
257 { "x", AT_DIMEN | AT_MANDATORY | AT_POSITIONAL },
258 { "y", AT_DIMEN | AT_MANDATORY | AT_POSITIONAL },
264 class scale_cmd : public cmd_exec_simple {
265 double x_factor, y_factor;
269 x_factor = c->arg("x")->as_double(1);
270 y_factor = c->arg("y")->as_double(x_factor);
272 page *process_page(page *p) override
275 m.scale(x_factor, y_factor);
276 return new xform_page(p, m);
280 static const arg_def scale_args[] = {
281 { "x", AT_DOUBLE | AT_MANDATORY | AT_POSITIONAL },
282 { "y", AT_DOUBLE | AT_POSITIONAL },
288 class rotate_cmd : public cmd_exec_simple {
293 deg = c->arg("deg")->as_int(0) % 360;
297 die("Rotate requires a multiple of 90 degrees");
299 page *process_page(page *p) override
308 m.shift(0, p->width);
312 m.shift(p->width, p->height);
316 m.shift(p->height, 0);
321 return new xform_page(p, m);
325 static const arg_def rotate_args[] = {
326 { "angle", AT_INT | AT_MANDATORY | AT_POSITIONAL },
332 class flip_cmd : public cmd_exec_simple {
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");
343 page *process_page(page *p) override
349 m.shift(0, p->height);
354 m.shift(p->width, 0);
356 return new xform_page(p, m);
360 static const arg_def flip_args[] = {
368 class select_cmd : public cmd_exec {
375 vector<page *> process(vector<page *> &pages) override;
378 static int validate_page_index(vector<page *> &pages, int idx)
380 if (idx >= 1 && idx <= (int) pages.size())
382 if (idx <= -1 && idx >= (int) -pages.size())
383 return idx + pages.size();
384 die("Page index %d out of range", idx);
387 vector<page *> select_cmd::process(vector<page *> &pages)
390 for (auto pb: pipe->branches)
392 vector<page *> selected;
393 for (auto ps: pb->selectors)
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]);
401 auto processed = run_command_list(pb->commands, selected);
402 for (auto p: processed)
410 class apply_cmd : public cmd_exec {
417 vector<page *> process(vector<page *> &pages) override;
420 static pipeline_branch *find_branch(pipeline *pipe, vector <page *> &pages, int idx)
422 for (auto pb: pipe->branches)
423 for (auto ps: pb->selectors)
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)
433 vector<page *> apply_cmd::process(vector<page *> &pages)
440 pipeline_branch *pb = find_branch(pipe, pages, cnt);
445 auto processed = run_command_list(pb->commands, tmp);
446 for (auto q: processed)
459 class modulo_cmd : public cmd_exec {
466 n = c->arg("n")->as_int(0);
468 die("Modulo must have n > 0");
469 half = c->arg("half")->as_int(0);
472 vector<page *> process(vector<page *> &pages) override;
475 vector<page *> modulo_cmd::process(vector<page *> &pages)
478 int tuples = ((int) pages.size() + n - 1) / n;
479 int use_tuples = half ? tuples/2 : tuples;
481 for (int tuple=0; tuple < use_tuples; tuple++)
483 debug("# Tuple %d", tuple);
485 for (auto pb: pipe->branches)
488 for (auto ps: pb->selectors)
492 int step = (f <= t) ? 1 : -1;
493 for (int i=f; i<=t; i += step)
498 else if (i < 0 && i >= -n)
499 j = (tuples-1-tuple)*n + (-i) - 1;
501 die("Modulo: invalid index %d", i);
502 if (j < (int) pages.size())
503 tmp.push_back(pages[j]);
506 page *ref_page = pages[tuple*n];
507 tmp.push_back(new empty_page(ref_page->width, ref_page->height));
511 auto processed = run_command_list(pb->commands, tmp);
512 for (auto q: processed)
521 static const arg_def modulo_args[] = {
522 { "n", AT_INT | AT_MANDATORY | AT_POSITIONAL },
523 { "half", AT_SWITCH },
529 class draw_bbox_cmd : public cmd_exec {
531 draw_bbox_cmd(cmd *c UNUSED) { }
532 vector<page *> process(vector<page *> &pages) override;
535 class draw_bbox_page : public page {
538 void render(out_context *out, pdf_matrix xform) override;
539 draw_bbox_page(page *p) : page(p) { orig_page = p; }
542 void draw_bbox_page::render(out_context *out, pdf_matrix xform)
544 orig_page->render(out, xform);
547 xform.to_string() + " cm " +
549 bbox.to_rect() + " re S " +
553 vector<page *> draw_bbox_cmd::process(vector<page *> &pages)
557 out.push_back(new draw_bbox_page(p));
563 class merge_cmd : public cmd_exec {
565 merge_cmd(cmd *c UNUSED) { }
566 vector<page *> process(vector<page *> &pages) override;
569 class merge_page : public page {
570 vector<page *> orig_pages;
572 merge_page(vector<page *> &orig) : page(0, 0)
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");
593 void render(out_context *out, pdf_matrix xform) override
595 for (auto p: orig_pages)
596 p->render(out, xform);
600 vector<page *> merge_cmd::process(vector<page *> &pages)
604 out.push_back(new merge_page(pages));
610 class paper_cmd : public cmd_exec_simple {
614 paper_cmd(cmd *c) : paper(c), pos(c) { }
615 page *process_page(page *p) override
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);
626 static const arg_def paper_args[] = {
634 class scaleto_cmd : public cmd_exec_simple {
638 scaleto_cmd(cmd *c) : paper(c), pos(c) { }
639 page *process_page(page *p) override
641 BBox orig_box = BBox(p->width, p->height);
642 BBox paper_box = BBox(paper.w, paper.h);
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);
654 static const arg_def scaleto_args[] = {
662 class fit_cmd : public cmd_exec_simple {
667 fit_cmd(cmd *c) : paper(c, true), pos(c), marg(c, "margin", "margin") { }
668 page *process_page(page *p) override
673 if (!is_zero(paper.w) && !is_zero(paper.h))
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);
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;
699 static const arg_def fit_args[] = {
702 MARGIN_ARGS1_NAMED("margin"),
703 MARGIN_ARGS2("margin"),
709 class expand_cmd : public cmd_exec_simple {
712 expand_cmd(cmd *c) : marg(c, "by", "") { }
713 page *process_page(page *p) override
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");
726 static const arg_def expand_args[] = {
727 MARGIN_ARGS1_POSNL("by"),
734 class margins_cmd : public cmd_exec_simple {
737 margins_cmd(cmd *c) : marg(c, "size", "") { }
738 page *process_page(page *p) override
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");
752 static const arg_def margins_args[] = {
753 MARGIN_ARGS1_POSNL("size"),
760 class add_blank_cmd : public cmd_exec {
764 add_blank_cmd(cmd *c) : paper(c, true)
766 n = c->arg("n")->as_int(1);
768 vector<page *> process(vector<page *> &pages) override;
771 vector<page *> add_blank_cmd::process(vector<page *> &pages)
778 for (int i=0; i<n; i++)
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));
790 static const arg_def add_blank_args[] = {
791 { "n", AT_INT | AT_POSITIONAL },
798 class book_cmd : public cmd_exec {
803 n = c->arg("n")->as_int(0);
805 die("Number of pages per signature must be divisible by 4");
807 vector<page *> process(vector<page *> &pages) override;
810 vector<page *> book_cmd::process(vector<page *> &pages)
812 vector<page *> in, out;
815 while (in.size() % 4)
816 in.push_back(new empty_page(in[0]->width, in[0]->height));
819 while (i < (int) in.size())
821 int sig = in.size() - i;
824 for (int j=0; j<sig/2; j+=2)
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]);
837 static const arg_def book_args[] = {
838 { "n", AT_INT | AT_POSITIONAL },
842 /*** Command table ***/
844 template<typename T> cmd_exec *ctor(cmd *c) { return new T(c); }
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 }