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 bool may_shrink(BBox *bb)
196 return (bb->width() > l+r && bb->height() > t+b);
198 void shrink_box(BBox *bb)
204 if (bb->x_min >= bb->x_max || bb->y_min >= bb->y_max)
205 die("Margins cannot be larger than the whole page");
207 void expand_box(BBox *bb)
216 #define MARGIN_ARGS1_NAMED(name) \
219 #define MARGIN_ARGS1_POSNL(name) \
220 { name, AT_DIMEN | AT_POSITIONAL }
222 #define MARGIN_ARGS2(sx) \
223 { "h" sx, AT_DIMEN }, \
224 { "v" sx, AT_DIMEN }, \
225 { "l" sx, AT_DIMEN }, \
226 { "r" sx, AT_DIMEN }, \
227 { "t" sx, AT_DIMEN }, \
230 // Scaling preserving aspect ratio
232 double scale_to_fit(BBox &from, BBox &to)
234 double fw = from.width(), fh = from.height();
235 double tw = to.width(), th = to.height();
236 if (is_zero(fw) || is_zero(fh) || is_zero(tw) || is_zero(th))
239 return min(tw/fw, th/fh);
244 class move_cmd : public cmd_exec_simple {
249 x = c->arg("x")->as_double(0);
250 y = c->arg("y")->as_double(0);
252 page *process_page(page *p) override
256 return new xform_page(p, m);
260 static const arg_def move_args[] = {
261 { "x", AT_DIMEN | AT_MANDATORY | AT_POSITIONAL },
262 { "y", AT_DIMEN | AT_MANDATORY | AT_POSITIONAL },
268 class scale_cmd : public cmd_exec_simple {
269 double x_factor, y_factor;
273 x_factor = c->arg("x")->as_double(1);
274 y_factor = c->arg("y")->as_double(x_factor);
276 page *process_page(page *p) override
279 m.scale(x_factor, y_factor);
280 return new xform_page(p, m);
284 static const arg_def scale_args[] = {
285 { "x", AT_DOUBLE | AT_MANDATORY | AT_POSITIONAL },
286 { "y", AT_DOUBLE | AT_POSITIONAL },
292 class rotate_cmd : public cmd_exec_simple {
297 deg = c->arg("deg")->as_int(0) % 360;
301 die("Rotate requires a multiple of 90 degrees");
303 page *process_page(page *p) override
312 m.shift(0, p->width);
316 m.shift(p->width, p->height);
320 m.shift(p->height, 0);
325 return new xform_page(p, m);
329 static const arg_def rotate_args[] = {
330 { "angle", AT_INT | AT_MANDATORY | AT_POSITIONAL },
336 class flip_cmd : public cmd_exec_simple {
342 horizontal = c->arg("h")->as_int(0);
343 vertical = c->arg("v")->as_int(0);
344 if (!horizontal && !vertical)
345 die("Flip has no direction specified");
347 page *process_page(page *p) override
353 m.shift(0, p->height);
358 m.shift(p->width, 0);
360 return new xform_page(p, m);
364 static const arg_def flip_args[] = {
372 class select_cmd : public cmd_exec {
379 vector<page *> process(vector<page *> &pages) override;
382 static int validate_page_index(vector<page *> &pages, int idx)
384 if (idx >= 1 && idx <= (int) pages.size())
386 if (idx <= -1 && idx >= (int) -pages.size())
387 return idx + pages.size();
388 die("Page index %d out of range", idx);
391 vector<page *> select_cmd::process(vector<page *> &pages)
394 for (auto pb: pipe->branches)
396 vector<page *> selected;
397 for (auto ps: pb->selectors)
399 int f = validate_page_index(pages, ps.from);
400 int t = validate_page_index(pages, ps.to);
401 int step = (f <= t) ? 1 : -1;
402 for (int i=f; f<=t; f += step)
403 selected.push_back(pages[i]);
405 auto processed = run_command_list(pb->commands, selected);
406 for (auto p: processed)
414 class apply_cmd : public cmd_exec {
421 vector<page *> process(vector<page *> &pages) override;
424 static pipeline_branch *find_branch(pipeline *pipe, vector <page *> &pages, int idx)
426 for (auto pb: pipe->branches)
427 for (auto ps: pb->selectors)
429 int f = validate_page_index(pages, ps.from);
430 int t = validate_page_index(pages, ps.to);
431 if (f <= idx && idx <= t || t <= idx && idx <= f)
437 vector<page *> apply_cmd::process(vector<page *> &pages)
444 pipeline_branch *pb = find_branch(pipe, pages, cnt);
449 auto processed = run_command_list(pb->commands, tmp);
450 for (auto q: processed)
463 class modulo_cmd : public cmd_exec {
470 n = c->arg("n")->as_int(0);
472 die("Modulo must have n > 0");
473 half = c->arg("half")->as_int(0);
476 vector<page *> process(vector<page *> &pages) override;
479 vector<page *> modulo_cmd::process(vector<page *> &pages)
482 int tuples = ((int) pages.size() + n - 1) / n;
483 int use_tuples = half ? tuples/2 : tuples;
485 for (int tuple=0; tuple < use_tuples; tuple++)
487 debug("# Tuple %d", tuple);
489 for (auto pb: pipe->branches)
492 for (auto ps: pb->selectors)
496 int step = (f <= t) ? 1 : -1;
497 for (int i=f; i<=t; i += step)
502 else if (i < 0 && i >= -n)
503 j = (tuples-1-tuple)*n + (-i) - 1;
505 die("Modulo: invalid index %d", i);
506 if (j < (int) pages.size())
507 tmp.push_back(pages[j]);
510 page *ref_page = pages[tuple*n];
511 tmp.push_back(new empty_page(ref_page->width, ref_page->height));
515 auto processed = run_command_list(pb->commands, tmp);
516 for (auto q: processed)
525 static const arg_def modulo_args[] = {
526 { "n", AT_INT | AT_MANDATORY | AT_POSITIONAL },
527 { "half", AT_SWITCH },
533 class draw_bbox_cmd : public cmd_exec {
535 draw_bbox_cmd(cmd *c UNUSED) { }
536 vector<page *> process(vector<page *> &pages) override;
539 class draw_bbox_page : public page {
542 void render(out_context *out, pdf_matrix xform) override;
543 draw_bbox_page(page *p) : page(p) { orig_page = p; }
546 void draw_bbox_page::render(out_context *out, pdf_matrix xform)
548 orig_page->render(out, xform);
551 xform.to_string() + " cm " +
553 bbox.to_rect() + " re S " +
557 vector<page *> draw_bbox_cmd::process(vector<page *> &pages)
561 out.push_back(new draw_bbox_page(p));
567 class merge_cmd : public cmd_exec {
569 merge_cmd(cmd *c UNUSED) { }
570 vector<page *> process(vector<page *> &pages) override;
573 class merge_page : public page {
574 vector<page *> orig_pages;
576 merge_page(vector<page *> &orig) : page(0, 0)
591 if (!is_equal(width, p->width) || !is_equal(height, p->height))
592 die("All pages participating in a merge must have the same dimensions");
597 void render(out_context *out, pdf_matrix xform) override
599 for (auto p: orig_pages)
600 p->render(out, xform);
604 vector<page *> merge_cmd::process(vector<page *> &pages)
608 out.push_back(new merge_page(pages));
614 class paper_cmd : public cmd_exec_simple {
618 paper_cmd(cmd *c) : paper(c), pos(c) { }
619 page *process_page(page *p) override
621 BBox paper_box = BBox(paper.w, paper.h);
622 pdf_matrix xf = pos.place(p->bbox, paper_box);
623 page *q = new xform_page(p, xf);
630 static const arg_def paper_args[] = {
638 class scaleto_cmd : public cmd_exec_simple {
642 scaleto_cmd(cmd *c) : paper(c), pos(c) { }
643 page *process_page(page *p) override
645 BBox orig_box = BBox(p->width, p->height);
646 BBox paper_box = BBox(paper.w, paper.h);
648 xf.scale(scale_to_fit(orig_box, paper_box));
649 orig_box.transform(xf);
650 xf.concat(pos.place(orig_box, paper_box));
651 page *q = new xform_page(p, xf);
658 static const arg_def scaleto_args[] = {
666 class fit_cmd : public cmd_exec_simple {
671 fit_cmd(cmd *c) : paper(c, true), pos(c), marg(c, "margin", "margin") { }
672 page *process_page(page *p) override
677 if (!is_zero(paper.w) && !is_zero(paper.h))
679 // Paper given: scale image to fit paper
680 BBox orig_box = p->bbox;
681 BBox paper_box = BBox(paper.w, paper.h);
682 marg.shrink_box(&paper_box);
683 xf.scale(scale_to_fit(orig_box, paper_box));
684 orig_box.transform(xf);
685 xf.concat(pos.place(orig_box, paper_box));
686 q = new xform_page(p, xf);
692 // No paper given: adjust paper to fit image
693 xf.shift(-p->bbox.x_min, -p->bbox.y_min);
694 xf.shift(marg.l, marg.b);
695 q = new xform_page(p, xf);
696 q->width = p->bbox.width() + marg.l + marg.r;
697 q->height = p->bbox.height() + marg.t + marg.b;
703 static const arg_def fit_args[] = {
706 MARGIN_ARGS1_NAMED("margin"),
707 MARGIN_ARGS2("margin"),
713 class expand_cmd : public cmd_exec_simple {
716 expand_cmd(cmd *c) : marg(c, "by", "") { }
717 page *process_page(page *p) override
720 xf.shift(marg.l, marg.b);
721 page *q = new xform_page(p, xf);
722 q->width = p->width + marg.l + marg.r;
723 q->height = p->height + marg.t + marg.b;
724 if (q->width < 0.001 || q->height < 0.001)
725 die("Expansion must result in positive page dimensions");
730 static const arg_def expand_args[] = {
731 MARGIN_ARGS1_POSNL("by"),
738 class margins_cmd : public cmd_exec_simple {
741 margins_cmd(cmd *c) : marg(c, "size", "") { }
742 page *process_page(page *p) override
745 xf.shift(-p->bbox.x_min, -p->bbox.y_min);
746 xf.shift(marg.l, marg.b);
747 page *q = new xform_page(p, xf);
748 q->width = p->bbox.width() + marg.l + marg.r;
749 q->height = p->bbox.height() + marg.t + marg.b;
750 if (q->width < 0.001 || q->height < 0.001)
751 die("Margins must result in positive page dimensions");
756 static const arg_def margins_args[] = {
757 MARGIN_ARGS1_POSNL("size"),
764 class add_blank_cmd : public cmd_exec {
768 add_blank_cmd(cmd *c) : paper(c, true)
770 n = c->arg("n")->as_int(1);
772 vector<page *> process(vector<page *> &pages) override;
775 vector<page *> add_blank_cmd::process(vector<page *> &pages)
782 for (int i=0; i<n; i++)
784 double w = paper.w, h = paper.h;
785 if (is_zero(w) || is_zero(h))
786 w = p->width, h = p->height;
787 out.push_back(new empty_page(w, h));
794 static const arg_def add_blank_args[] = {
795 { "n", AT_INT | AT_POSITIONAL },
802 class book_cmd : public cmd_exec {
807 n = c->arg("n")->as_int(0);
809 die("Number of pages per signature must be divisible by 4");
811 vector<page *> process(vector<page *> &pages) override;
814 vector<page *> book_cmd::process(vector<page *> &pages)
816 vector<page *> in, out;
819 while (in.size() % 4)
820 in.push_back(new empty_page(in[0]->width, in[0]->height));
823 while (i < (int) in.size())
825 int sig = in.size() - i;
828 for (int j=0; j<sig/2; j+=2)
830 out.push_back(in[i + sig-1-j]);
831 out.push_back(in[i + j]);
832 out.push_back(in[i + j+1]);
833 out.push_back(in[i + sig-2-j]);
841 static const arg_def book_args[] = {
842 { "n", AT_INT | AT_POSITIONAL },
850 double tile_w, tile_h;
851 double paper_w, paper_h;
856 class nup_cmd : public cmd_exec {
858 int grid_n, grid_m, num_tiles;
871 double hspace, vspace;
874 page *process_single(vector<page *> &in);
875 void find_config(vector<page *> &in, BBox *page_boxes);
876 void try_config(nup_state &st);
881 nup_cmd(cmd *c) : paper(c), marg(c, "margin", "margin"), pos(c), tpos(c->arg("tpos")->as_string("tl"))
883 grid_n = c->arg("n")->as_int(0);
884 grid_m = c->arg("m")->as_int(0);
885 if (grid_n > 0 && grid_m > 0)
886 num_tiles = grid_n * grid_m;
887 else if (grid_n > 0 && !grid_m)
890 die("Grid size must be at least 1x1");
892 const string by = c->arg("by")->as_string("rows");
893 if (by == "rows" || by == "row" || by == "r")
895 else if (by == "cols" || by == "cols" || by == "c")
897 else if (by == "tile" || by == "t")
900 die("Parameter \"by\" must be rows/cols/tile");
902 crop = c->arg("crop")->as_int(0);
903 rotate = c->arg("rotate")->as_int(-1);
904 scale = c->arg("scale")->as_double(0);
906 double space = c->arg("space")->as_double(0);
907 hspace = c->arg("hspace")->as_double(space);
908 vspace = c->arg("vspace")->as_double(space);
910 if (!is_zero(scale) && (!is_zero(paper.w) || rotate >= 0))
911 die("When nup is used with explicit scaling, paper size nor rotation may be given");
912 if (!is_zero(scale) && !grid_m)
913 die("When nup is used with explicit scaling, both grid sizes must be given");
916 vector<page *> process(vector<page *> &pages) override;
919 vector<page *> nup_cmd::process(vector<page *> &pages)
924 while (i < (int) pages.size())
927 if (fill_by == BY_TILE)
929 for (int j=0; j<num_tiles; j++)
930 in.push_back(pages[i]);
935 for (int j=0; j<num_tiles; j++)
937 if (i < (int) pages.size())
938 in.push_back(pages[i]);
940 in.push_back(new empty_page(in[0]->width, in[0]->height));
944 out.push_back(process_single(in));
950 void nup_cmd::try_config(nup_state &st)
952 BBox window(st.paper_w, st.paper_h);
953 if (!marg.may_shrink(&window))
955 marg.shrink_box(&window);
956 window.x_max -= (st.cols - 1) * hspace;
957 window.y_max -= (st.rows - 1) * vspace;
958 if (window.width() < 0 || window.height() < 0)
961 BBox image(st.cols * st.tile_w, st.rows * st.tile_h);
962 st.scale = scale_to_fit(image, window);
963 st.fill_factor = (st.scale*image.width() * st.scale*image.height()) / (st.paper_w * st.paper_h);
965 debug("Try: %dx%d on %.3f x %.3f => scale %.3f, fill %.6f",
967 st.paper_w, st.paper_h,
968 st.scale, st.fill_factor);
970 if (!found_solution || best.fill_factor < st.fill_factor)
972 found_solution = true;
977 void nup_cmd::find_config(vector<page *> &in, BBox *page_boxes)
981 // Determine tile size
982 st.tile_w = st.tile_h = 0;
983 for (int i=0; i<num_tiles; i++)
986 page_boxes[i] = in[i]->bbox;
988 page_boxes[i] = BBox(in[i]->width, in[i]->height);
989 st.tile_w = max(st.tile_w, page_boxes[i].width());
990 st.tile_h = max(st.tile_h, page_boxes[i].height());
992 debug("NUP: %d tiles of size [%.3f,%.3f]", num_tiles, st.tile_w, st.tile_h);
995 // Try all possible configurations of tiles
996 found_solution = false;
999 // If explicit scaling is requested, we choose page size ourselves,
1000 // so there is just one configuration.
1003 st.paper_w = marg.l + st.cols*st.tile_w + (st.cols-1)*hspace + marg.r;
1004 st.paper_h = marg.t + st.rows*st.tile_h + (st.rows-1)*vspace + marg.b;
1009 // Page size is fixed (either given or copied from the source pages),
1010 // but we can have freedom to rotate and/or to choose grid size.
1011 for (int rot=0; rot<=1; rot++)
1013 if (rotate >= 0 && rot != rotate)
1016 // Establish paper size
1017 if (!is_zero(paper.w))
1019 st.paper_w = paper.w;
1020 st.paper_h = paper.h;
1024 st.paper_w = in[0]->width;
1025 st.paper_h = in[0]->height;
1028 swap(st.paper_w, st.paper_h);
1039 for (int r=1; r<=grid_n; r++)
1043 st.cols = grid_n / r;
1050 if (!found_solution)
1051 die("Nup did not find a feasible solution");
1052 debug("Best: %dx%d on %.3f x %.3f", best.cols, best.rows, best.paper_w, best.paper_h);
1056 class nup_page : public page {
1058 vector<page *> orig_pages;
1059 vector<pdf_matrix> xforms;
1060 void render(out_context *out, pdf_matrix xform) override;
1061 nup_page(nup_state &st) : page(st.paper_w, st.paper_h) { }
1064 void nup_page::render(out_context *out, pdf_matrix parent_xform)
1066 for (int i=0; i < (int) orig_pages.size(); i++)
1067 orig_pages[i]->render(out, xforms[i] * parent_xform);
1070 page *nup_cmd::process_single(vector<page *> &in)
1072 BBox page_boxes[num_tiles];
1073 find_config(in, page_boxes);
1074 double tw = best.scale * best.tile_w;
1075 double th = best.scale * best.tile_h;
1077 // Construct transform from paper to grid of tiles
1078 BBox paper_box(best.paper_w, best.paper_h);
1079 marg.shrink_box(&paper_box);
1080 BBox grid_box(best.cols * tw + (best.cols-1) * hspace,
1081 best.rows * th + (best.rows-1) * vspace);
1082 pdf_matrix place_xform = pos.place(grid_box, paper_box);
1084 nup_page *p = new nup_page(best);
1086 p->bbox.transform(place_xform);
1088 for (int i=0; i<num_tiles; i++)
1091 if (fill_by == BY_ROWS || fill_by == BY_TILE)
1103 BBox &page_box = page_boxes[i];
1104 m.shift(-page_box.x_min, -page_box.y_min);
1105 m.scale(best.scale);
1106 page_box.transform(m);
1108 double x = c * (tw + hspace);
1109 double y = (best.rows-1-r) * (th + vspace);
1110 BBox tile_box = BBox(x, y, x+tw, y+th);
1111 m.concat(tpos.place(page_box, tile_box));
1113 p->orig_pages.push_back(in[i]);
1114 p->xforms.push_back(m * place_xform);
1120 static const arg_def nup_args[] = {
1121 { "n", AT_INT | AT_POSITIONAL | AT_MANDATORY },
1122 { "m", AT_INT | AT_POSITIONAL },
1123 { "by", AT_STRING },
1124 { "crop", AT_SWITCH },
1125 { "rotate", AT_SWITCH },
1126 { "scale", AT_DOUBLE },
1128 MARGIN_ARGS1_NAMED("margin"),
1129 MARGIN_ARGS2("margin"),
1131 { "tpos", AT_STRING },
1132 { "space", AT_DIMEN },
1133 { "hspace", AT_DIMEN },
1134 { "vspace", AT_DIMEN },
1138 /*** Command table ***/
1140 template<typename T> cmd_exec *ctor(cmd *c) { return new T(c); }
1142 const cmd_def cmd_table[] = {
1143 { "null", no_args, 0, &ctor<null_cmd> },
1144 { "move", move_args, 0, &ctor<move_cmd> },
1145 { "scale", scale_args, 0, &ctor<scale_cmd> },
1146 { "rotate", rotate_args, 0, &ctor<rotate_cmd> },
1147 { "flip", flip_args, 0, &ctor<flip_cmd> },
1148 { "select", no_args, 1, &ctor<select_cmd> },
1149 { "apply", no_args, 1, &ctor<apply_cmd> },
1150 { "modulo", modulo_args, 1, &ctor<modulo_cmd> },
1151 { "draw-bbox",no_args, 0, &ctor<draw_bbox_cmd> },
1152 { "merge", no_args, 0, &ctor<merge_cmd> },
1153 { "paper", paper_args, 0, &ctor<paper_cmd> },
1154 { "scaleto", scaleto_args, 0, &ctor<scaleto_cmd> },
1155 { "fit", fit_args, 0, &ctor<fit_cmd> },
1156 { "expand", expand_args, 0, &ctor<expand_cmd> },
1157 { "margins", margins_args, 0, &ctor<margins_cmd> },
1158 { "add-blank",add_blank_args, 0, &ctor<add_blank_cmd> },
1159 { "book", book_args, 0, &ctor<book_cmd> },
1160 { "nup", nup_args, 0, &ctor<nup_cmd> },
1161 { NULL, NULL, 0, NULL }