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();
49 image_box = p->image_box;
50 image_box.transform(xf);
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 err("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 err("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 err("Value of pos must have two characters");
124 else if (s[0] == 'c')
126 else if (s[0] == 'b')
129 err("First character of pos must be t/c/b");
132 else if (s[1] == 'c')
134 else if (s[1] == 'r')
137 err("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 err("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 }, \
237 rgb[0] = rgb[1] = rgb[2] = 0;
242 err("Invalid color specification \"%s\": expecting 6 hex digits", s.c_str());
243 for (int i=0; i<3; i++)
246 for (int j=0; j<2; j++)
249 if (c >= '0' && c <= '9')
250 x = (x << 4) | (c - '0');
251 else if (c >= 'a' && c <= 'f')
252 x = (x << 4) | (c - 'a' + 10);
253 else if (c >= 'A' && c <= 'F')
254 x = (x << 4) | (c - 'A' + 10);
261 return pdf_coord(rgb[0]) + " " + pdf_coord(rgb[1]) + " " + pdf_coord(rgb[2]);
267 class cropmark_spec {
287 egstate = QPDFObjectHandle::newNull();
289 cropmark_spec(cmd *c, const string prefix="", const string def_type="cross") : color(c->arg(prefix + "color")->as_string("000000"))
291 string t = c->arg(prefix + "mark")->as_string(def_type);
296 else if (t == "cross")
305 err("Invalid cropmark type %s", t.c_str());
307 pen_width = c->arg(prefix + "pen")->as_double(0.2);
308 arm_length = c->arg(prefix + "len")->as_double(5*mm);
309 offset = c->arg(prefix + "offset")->as_double(0);
310 egstate = QPDFObjectHandle::newNull();
312 bool is_bg() { return (type == MARK_BG); }
313 string pdf_stream(out_context *out, BBox &box, pdf_matrix &xform);
315 string crop_cross(double x, double y, uint mask);
316 QPDFObjectHandle egstate;
319 string cropmark_spec::crop_cross(double x, double y, uint mask)
323 for (uint i=0; i<4; i++)
324 if (mask & (1U << i))
326 double x2 = x, y2 = y;
329 case 3: x2 -= arm_length; break;
330 case 2: x2 += arm_length; break;
331 case 1: y2 += arm_length; break;
332 case 0: y2 -= arm_length; break;
334 s += pdf_coord(x) + " " + pdf_coord(y) + " m " + pdf_coord(x2) + " " + pdf_coord(y2) + " l S ";
340 string cropmark_spec::pdf_stream(out_context *out, BBox &box, pdf_matrix &xform)
342 if (type == MARK_NONE)
346 s += color.to_string() + (type == MARK_BG ? " rg " : " RG ");
347 s += xform.to_string() + " cm ";
349 if (egstate.isNull())
351 auto egs = QPDFObjectHandle::newDictionary();
352 egs.replaceKey("/Type", QPDFObjectHandle::newName("/ExtGState"));
353 egs.replaceKey("/LW", QPDFObjectHandle::newReal(pen_width, 1));
354 egs.replaceKey("/LC", QPDFObjectHandle::newInteger(2));
355 egs.replaceKey("/LJ", QPDFObjectHandle::newInteger(0));
356 egstate = out->pdf->makeIndirectObject(egs);
359 string egs_res = out->new_resource("GS");
360 out->egstates.replaceKey(egs_res, egstate);
361 s += egs_res + " gs ";
363 BBox b = box.enlarged(offset);
370 s += b.to_rect() + " re S ";
373 s += crop_cross(b.x_min, b.y_min, 0b1111);
374 s += crop_cross(b.x_max, b.y_min, 0b1111);
375 s += crop_cross(b.x_max, b.y_max, 0b1111);
376 s += crop_cross(b.x_min, b.y_max, 0b1111);
379 s += crop_cross(b.x_min, b.y_min, 0b0110);
380 s += crop_cross(b.x_max, b.y_min, 0b1010);
381 s += crop_cross(b.x_max, b.y_max, 0b1001);
382 s += crop_cross(b.x_min, b.y_max, 0b0101);
385 s += crop_cross(b.x_min, b.y_min, 0b1001);
386 s += crop_cross(b.x_max, b.y_min, 0b0101);
387 s += crop_cross(b.x_max, b.y_max, 0b0110);
388 s += crop_cross(b.x_min, b.y_max, 0b1010);
391 s += b.to_rect() + " re f ";
399 #define CROPMARK_ARGS(px) \
400 { px "mark", AT_STRING }, \
401 { px "pen", AT_DIMEN }, \
402 { px "len", AT_DIMEN }, \
403 { px "offset",AT_DIMEN }, \
404 { px "color", AT_STRING }
406 // Scaling preserving aspect ratio
408 double scale_to_fit(BBox &from, BBox &to)
410 double fw = from.width(), fh = from.height();
411 double tw = to.width(), th = to.height();
412 if (is_zero(fw) || is_zero(fh) || is_zero(tw) || is_zero(th))
415 return min(tw/fw, th/fh);
420 class move_cmd : public cmd_exec_simple {
425 x = c->arg("x")->as_double(0);
426 y = c->arg("y")->as_double(0);
428 page *process_page(page *p) override
432 return new xform_page(p, m);
436 static const arg_def move_args[] = {
437 { "x", AT_DIMEN | AT_MANDATORY | AT_POSITIONAL },
438 { "y", AT_DIMEN | AT_MANDATORY | AT_POSITIONAL },
444 class scale_cmd : public cmd_exec_simple {
445 double x_factor, y_factor;
449 x_factor = c->arg("x")->as_double(1);
450 y_factor = c->arg("y")->as_double(x_factor);
452 page *process_page(page *p) override
455 m.scale(x_factor, y_factor);
456 return new xform_page(p, m);
460 static const arg_def scale_args[] = {
461 { "x", AT_DOUBLE | AT_MANDATORY | AT_POSITIONAL },
462 { "y", AT_DOUBLE | AT_POSITIONAL },
468 class rotate_cmd : public cmd_exec_simple {
473 deg = c->arg("deg")->as_int(0) % 360;
477 err("The angle must be a multiple of 90 degrees");
479 page *process_page(page *p) override
488 m.shift(0, p->width);
492 m.shift(p->width, p->height);
496 m.shift(p->height, 0);
501 return new xform_page(p, m);
505 static const arg_def rotate_args[] = {
506 { "angle", AT_INT | AT_MANDATORY | AT_POSITIONAL },
512 class flip_cmd : public cmd_exec_simple {
518 horizontal = c->arg("h")->as_int(0);
519 vertical = c->arg("v")->as_int(0);
520 if (!horizontal && !vertical)
521 err("No direction specified");
523 page *process_page(page *p) override
529 m.shift(0, p->height);
534 m.shift(p->width, 0);
536 return new xform_page(p, m);
540 static const arg_def flip_args[] = {
548 class select_cmd : public cmd_exec {
555 vector<page *> process(vector<page *> &pages) override;
558 static int validate_page_index(vector<page *> &pages, int idx)
560 if (idx >= 1 && idx <= (int) pages.size())
562 if (idx <= -1 && idx >= (int) -pages.size())
563 return idx + pages.size();
564 err("Page index %d out of range", idx);
567 vector<page *> select_cmd::process(vector<page *> &pages)
570 for (auto pb: pipe->branches)
572 vector<page *> selected;
573 for (auto ps: pb->selectors)
575 int f = validate_page_index(pages, ps.from);
576 int t = validate_page_index(pages, ps.to);
577 int step = (f <= t) ? 1 : -1;
578 for (int i=f; f<=t; f += step)
579 selected.push_back(pages[i]);
581 auto processed = run_command_list(pb->commands, selected);
582 for (auto p: processed)
590 class apply_cmd : public cmd_exec {
597 vector<page *> process(vector<page *> &pages) override;
600 static pipeline_branch *find_branch(pipeline *pipe, vector <page *> &pages, int idx)
602 for (auto pb: pipe->branches)
603 for (auto ps: pb->selectors)
605 int f = validate_page_index(pages, ps.from);
606 int t = validate_page_index(pages, ps.to);
607 if (f <= idx && idx <= t || t <= idx && idx <= f)
613 vector<page *> apply_cmd::process(vector<page *> &pages)
620 pipeline_branch *pb = find_branch(pipe, pages, cnt);
625 auto processed = run_command_list(pb->commands, tmp);
626 for (auto q: processed)
639 class modulo_cmd : public cmd_exec {
646 n = c->arg("n")->as_int(0);
648 err("Modulo must have n > 0");
649 half = c->arg("half")->as_int(0);
652 vector<page *> process(vector<page *> &pages) override;
655 vector<page *> modulo_cmd::process(vector<page *> &pages)
658 int tuples = ((int) pages.size() + n - 1) / n;
659 int use_tuples = half ? tuples/2 : tuples;
661 for (int tuple=0; tuple < use_tuples; tuple++)
663 debug("# Tuple %d", tuple);
665 for (auto pb: pipe->branches)
668 for (auto ps: pb->selectors)
672 int step = (f <= t) ? 1 : -1;
673 for (int i=f; i<=t; i += step)
678 else if (i < 0 && i >= -n)
679 j = (tuples-1-tuple)*n + (-i) - 1;
681 err("Invalid index %d", i);
682 if (j < (int) pages.size())
683 tmp.push_back(pages[j]);
686 page *ref_page = pages[tuple*n];
687 tmp.push_back(new empty_page(ref_page->width, ref_page->height));
691 auto processed = run_command_list(pb->commands, tmp);
692 for (auto q: processed)
701 static const arg_def modulo_args[] = {
702 { "n", AT_INT | AT_MANDATORY | AT_POSITIONAL },
703 { "half", AT_SWITCH },
709 class debug_page : public page {
711 cropmark_spec *page_cm, *image_cm;
713 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) { }
714 void render(out_context *out, pdf_matrix xform) override
716 orig_page->render(out, xform);
717 BBox page_bbox = BBox(0, 0, width, height);
718 out->contents += page_cm->pdf_stream(out, page_bbox, xform);
719 out->contents += image_cm->pdf_stream(out, image_box, xform);
723 class debug_cmd : public cmd_exec_simple {
724 cropmark_spec page_cmarks;
725 cropmark_spec image_cmarks;
727 debug_cmd(cmd *c UNUSED)
729 page_cmarks.type = cropmark_spec::MARK_BOX;
730 page_cmarks.color.rgb[0] = 1;
731 page_cmarks.color.rgb[1] = 0;
732 page_cmarks.color.rgb[2] = 0;
733 image_cmarks.type = cropmark_spec::MARK_BOX;
734 image_cmarks.color.rgb[0] = 0;
735 image_cmarks.color.rgb[1] = 1;
736 image_cmarks.color.rgb[2] = 0;
738 page *process_page(page *p) override
740 return new debug_page(p, &page_cmarks, &image_cmarks);
746 class merge_cmd : public cmd_exec {
748 merge_cmd(cmd *c UNUSED) { }
749 vector<page *> process(vector<page *> &pages) override;
752 class merge_page : public page {
753 vector<page *> orig_pages;
755 merge_page(vector<page *> &orig) : page(0, 0)
765 image_box = p->image_box;
770 if (!is_equal(width, p->width) || !is_equal(height, p->height))
771 err("All pages must have the same dimensions");
772 image_box.join(p->image_box);
776 void render(out_context *out, pdf_matrix xform) override
778 for (auto p: orig_pages)
779 p->render(out, xform);
783 vector<page *> merge_cmd::process(vector<page *> &pages)
787 out.push_back(new merge_page(pages));
793 class paper_cmd : public cmd_exec_simple {
797 paper_cmd(cmd *c) : paper(c), pos(c) { }
798 page *process_page(page *p) override
800 BBox paper_box = BBox(paper.w, paper.h);
801 pdf_matrix xf = pos.place(p->image_box, paper_box);
802 page *q = new xform_page(p, xf);
809 static const arg_def paper_args[] = {
817 class scaleto_cmd : public cmd_exec_simple {
821 scaleto_cmd(cmd *c) : paper(c), pos(c) { }
822 page *process_page(page *p) override
824 BBox orig_box = BBox(p->width, p->height);
825 BBox paper_box = BBox(paper.w, paper.h);
827 xf.scale(scale_to_fit(orig_box, paper_box));
828 orig_box.transform(xf);
829 xf.concat(pos.place(orig_box, paper_box));
830 page *q = new xform_page(p, xf);
837 static const arg_def scaleto_args[] = {
845 class fit_cmd : public cmd_exec_simple {
850 fit_cmd(cmd *c) : paper(c, true), pos(c), marg(c, "margin", "margin") { }
851 page *process_page(page *p) override
856 if (!is_zero(paper.w) && !is_zero(paper.h))
858 // Paper given: scale image to fit paper
859 BBox orig_box = p->image_box;
860 BBox paper_box = BBox(paper.w, paper.h);
861 marg.shrink_box(&paper_box);
862 xf.scale(scale_to_fit(orig_box, paper_box));
863 orig_box.transform(xf);
864 xf.concat(pos.place(orig_box, paper_box));
865 q = new xform_page(p, xf);
871 // No paper given: adjust paper to fit image
872 xf.shift(-p->image_box.x_min, -p->image_box.y_min);
873 xf.shift(marg.l, marg.b);
874 q = new xform_page(p, xf);
875 q->width = p->image_box.width() + marg.l + marg.r;
876 q->height = p->image_box.height() + marg.t + marg.b;
882 static const arg_def fit_args[] = {
885 MARGIN_ARGS1_NAMED("margin"),
886 MARGIN_ARGS2("margin"),
892 class expand_cmd : public cmd_exec_simple {
895 expand_cmd(cmd *c) : marg(c, "by", "") { }
896 page *process_page(page *p) override
899 xf.shift(marg.l, marg.b);
900 page *q = new xform_page(p, xf);
901 q->width = p->width + marg.l + marg.r;
902 q->height = p->height + marg.t + marg.b;
903 if (q->width < 0.001 || q->height < 0.001)
904 err("Expansion must result in positive page dimensions");
909 static const arg_def expand_args[] = {
910 MARGIN_ARGS1_POSNL("by"),
917 class margins_cmd : public cmd_exec_simple {
920 margins_cmd(cmd *c) : marg(c, "size", "") { }
921 page *process_page(page *p) override
924 xf.shift(-p->image_box.x_min, -p->image_box.y_min);
925 xf.shift(marg.l, marg.b);
926 page *q = new xform_page(p, xf);
927 q->width = p->image_box.width() + marg.l + marg.r;
928 q->height = p->image_box.height() + marg.t + marg.b;
929 if (q->width < 0.001 || q->height < 0.001)
930 err("Margins must result in positive page dimensions");
935 static const arg_def margins_args[] = {
936 MARGIN_ARGS1_POSNL("size"),
943 class add_blank_cmd : public cmd_exec {
947 add_blank_cmd(cmd *c) : paper(c, true)
949 n = c->arg("n")->as_int(1);
951 vector<page *> process(vector<page *> &pages) override;
954 vector<page *> add_blank_cmd::process(vector<page *> &pages)
961 for (int i=0; i<n; i++)
963 double w = paper.w, h = paper.h;
964 if (is_zero(w) || is_zero(h))
965 w = p->width, h = p->height;
966 out.push_back(new empty_page(w, h));
973 static const arg_def add_blank_args[] = {
974 { "n", AT_INT | AT_POSITIONAL },
981 class book_cmd : public cmd_exec {
986 n = c->arg("n")->as_int(0);
988 err("Number of pages per signature must be divisible by 4");
990 vector<page *> process(vector<page *> &pages) override;
993 vector<page *> book_cmd::process(vector<page *> &pages)
995 vector<page *> in, out;
998 while (in.size() % 4)
999 in.push_back(new empty_page(in[0]->width, in[0]->height));
1002 while (i < (int) in.size())
1004 int sig = in.size() - i;
1007 for (int j=0; j<sig/2; j+=2)
1009 out.push_back(in[i + sig-1-j]);
1010 out.push_back(in[i + j]);
1011 out.push_back(in[i + j+1]);
1012 out.push_back(in[i + sig-2-j]);
1020 static const arg_def book_args[] = {
1021 { "n", AT_INT | AT_POSITIONAL },
1029 double tile_w, tile_h;
1030 double paper_w, paper_h;
1035 class nup_cmd : public cmd_exec {
1037 int grid_n, grid_m, num_tiles;
1051 double hspace, vspace;
1052 cropmark_spec cmarks;
1055 page *process_single(vector<page *> &in);
1056 void find_config(vector<page *> &in, BBox *page_boxes);
1057 void try_config(nup_state &st);
1059 bool found_solution;
1060 BBox common_page_box;
1063 nup_cmd(cmd *c) : paper(c), marg(c, "margin", "margin"), pos(c), tpos(c->arg("tpos")->as_string("tl")), cmarks(c, "c", "none")
1065 grid_n = c->arg("n")->as_int(0);
1066 grid_m = c->arg("m")->as_int(0);
1067 if (grid_n > 0 && grid_m > 0)
1068 num_tiles = grid_n * grid_m;
1069 else if (grid_n > 0 && !grid_m)
1072 err("Grid size must be at least 1x1");
1074 const string by = c->arg("by")->as_string("rows");
1075 if (by == "rows" || by == "row" || by == "r")
1077 else if (by == "cols" || by == "cols" || by == "c")
1079 else if (by == "tile" || by == "t")
1082 err("Parameter \"by\" must be rows/cols/tile");
1084 crop = c->arg("crop")->as_int(0);
1085 mixed = c->arg("mixed")->as_int(0);
1086 rotate = c->arg("rotate")->as_int(-1);
1087 scale = c->arg("scale")->as_double(0);
1089 double space = c->arg("space")->as_double(0);
1090 hspace = c->arg("hspace")->as_double(space);
1091 vspace = c->arg("vspace")->as_double(space);
1093 if (!is_zero(scale) && (!is_zero(paper.w) || rotate >= 0))
1094 err("When used with explicit scaling, paper size nor rotation may be given");
1095 if (!is_zero(scale) && !grid_m)
1096 err("When used with explicit scaling, both grid sizes must be given");
1099 vector<page *> process(vector<page *> &pages) override;
1102 vector<page *> nup_cmd::process(vector<page *> &pages)
1106 // Unless mixed is given, find the common page size
1109 for (int i=0; i < (int) pages.size(); i++)
1112 BBox pb = crop ? p->image_box : BBox(p->width, p->height);
1114 common_page_box = pb;
1116 common_page_box.join(pb);
1118 debug("NUP: Common page box [%.3f %.3f]-[%.3f %.3f]",
1119 common_page_box.x_min, common_page_box.y_min, common_page_box.x_max, common_page_box.y_max);
1122 // Process one tile set after another
1124 while (i < (int) pages.size())
1127 if (fill_by == BY_TILE)
1129 for (int j=0; j<num_tiles; j++)
1130 in.push_back(pages[i]);
1135 for (int j=0; j<num_tiles; j++)
1137 if (i < (int) pages.size())
1138 in.push_back(pages[i]);
1140 in.push_back(new empty_page(in[0]->width, in[0]->height));
1144 out.push_back(process_single(in));
1150 void nup_cmd::try_config(nup_state &st)
1152 BBox window(st.paper_w, st.paper_h);
1153 if (!marg.may_shrink(&window))
1155 marg.shrink_box(&window);
1156 window.x_max -= (st.cols - 1) * hspace;
1157 window.y_max -= (st.rows - 1) * vspace;
1158 if (window.width() < 0 || window.height() < 0)
1161 BBox image(st.cols * st.tile_w, st.rows * st.tile_h);
1162 st.scale = scale_to_fit(image, window);
1163 st.fill_factor = (st.scale*image.width() * st.scale*image.height()) / (st.paper_w * st.paper_h);
1165 debug("Try: %dx%d on %.3f x %.3f => scale %.3f, fill %.6f",
1167 st.paper_w, st.paper_h,
1168 st.scale, st.fill_factor);
1170 if (!found_solution || best.fill_factor < st.fill_factor)
1172 found_solution = true;
1177 void nup_cmd::find_config(vector<page *> &in, BBox *page_boxes)
1181 // Determine tile size
1182 st.tile_w = st.tile_h = 0;
1183 for (int i=0; i<num_tiles; i++)
1186 page_boxes[i] = common_page_box;
1188 page_boxes[i] = in[i]->image_box;
1190 page_boxes[i] = BBox(in[i]->width, in[i]->height);
1191 st.tile_w = max(st.tile_w, page_boxes[i].width());
1192 st.tile_h = max(st.tile_h, page_boxes[i].height());
1194 debug("NUP: %d tiles of size [%.3f,%.3f]", num_tiles, st.tile_w, st.tile_h);
1197 // Try all possible configurations of tiles
1198 found_solution = false;
1199 if (!is_zero(scale))
1201 // If explicit scaling is requested, we choose page size ourselves,
1202 // so there is just one configuration.
1205 st.paper_w = marg.l + st.cols*st.tile_w + (st.cols-1)*hspace + marg.r;
1206 st.paper_h = marg.t + st.rows*st.tile_h + (st.rows-1)*vspace + marg.b;
1211 // Page size is fixed (either given or copied from the source pages),
1212 // but we can have freedom to rotate and/or to choose grid size.
1213 for (int rot=0; rot<=1; rot++)
1215 if (rotate >= 0 && rot != rotate)
1218 // Establish paper size
1219 if (!is_zero(paper.w))
1221 st.paper_w = paper.w;
1222 st.paper_h = paper.h;
1226 st.paper_w = in[0]->width;
1227 st.paper_h = in[0]->height;
1230 swap(st.paper_w, st.paper_h);
1241 for (int r=1; r<=grid_n; r++)
1245 st.cols = grid_n / r;
1252 if (!found_solution)
1253 err("No feasible solution found");
1254 debug("Best: %dx%d on %.3f x %.3f", best.cols, best.rows, best.paper_w, best.paper_h);
1258 class nup_page : public page {
1260 vector<page *> orig_pages;
1261 vector<pdf_matrix> xforms;
1262 vector<BBox> tile_boxes;
1263 cropmark_spec *cmarks;
1264 void render(out_context *out, pdf_matrix xform) override;
1265 nup_page(nup_state &st) : page(st.paper_w, st.paper_h) { }
1268 void nup_page::render(out_context *out, pdf_matrix parent_xform)
1270 for (int i=0; i < (int) orig_pages.size(); i++)
1272 if (cmarks->is_bg())
1273 out->contents += cmarks->pdf_stream(out, tile_boxes[i], parent_xform);
1274 orig_pages[i]->render(out, xforms[i] * parent_xform);
1275 if (!cmarks->is_bg())
1276 out->contents += cmarks->pdf_stream(out, tile_boxes[i], parent_xform);
1280 page *nup_cmd::process_single(vector<page *> &in)
1282 BBox page_boxes[num_tiles];
1283 find_config(in, page_boxes);
1284 double tw = best.scale * best.tile_w;
1285 double th = best.scale * best.tile_h;
1287 // Construct transform from paper to grid of tiles
1288 BBox paper_box(best.paper_w, best.paper_h);
1289 marg.shrink_box(&paper_box);
1290 BBox grid_box(best.cols * tw + (best.cols-1) * hspace,
1291 best.rows * th + (best.rows-1) * vspace);
1292 pdf_matrix place_xform = pos.place(grid_box, paper_box);
1294 nup_page *p = new nup_page(best);
1295 p->image_box = grid_box;
1296 p->image_box.transform(place_xform);
1298 for (int i=0; i<num_tiles; i++)
1301 if (fill_by == BY_ROWS || fill_by == BY_TILE)
1313 BBox &page_box = page_boxes[i];
1314 m.shift(-page_box.x_min, -page_box.y_min);
1315 m.scale(best.scale);
1316 page_box.transform(m);
1318 double x = c * (tw + hspace);
1319 double y = (best.rows-1-r) * (th + vspace);
1320 BBox tile_box = BBox(x, y, x+tw, y+th);
1321 m.concat(tpos.place(page_box, tile_box));
1323 p->orig_pages.push_back(in[i]);
1324 p->xforms.push_back(m * place_xform);
1325 p->tile_boxes.push_back(tile_box.transformed(place_xform));
1326 p->cmarks = &cmarks;
1332 static const arg_def nup_args[] = {
1333 { "n", AT_INT | AT_POSITIONAL | AT_MANDATORY },
1334 { "m", AT_INT | AT_POSITIONAL },
1335 { "by", AT_STRING },
1336 { "crop", AT_SWITCH },
1337 { "mixed", AT_SWITCH },
1338 { "rotate", AT_SWITCH },
1339 { "scale", AT_DOUBLE },
1341 MARGIN_ARGS1_NAMED("margin"),
1342 MARGIN_ARGS2("margin"),
1345 { "tpos", AT_STRING },
1346 { "space", AT_DIMEN },
1347 { "hspace", AT_DIMEN },
1348 { "vspace", AT_DIMEN },
1354 class cropmarks_page : public page {
1358 void render(out_context *out, pdf_matrix xform) override
1360 orig_page->render(out, xform);
1361 out->contents += cm->pdf_stream(out, image_box, xform);
1363 cropmarks_page(page *p, cropmark_spec *cms) : page(p), orig_page(p), cm(cms) { }
1366 class cropmarks_cmd : public cmd_exec_simple {
1368 page *process_page(page *p) override
1370 return new cropmarks_page(p, &cm);
1373 cropmarks_cmd(cmd *c) : cm(c) { }
1376 static const arg_def cropmarks_args[] = {
1383 class clip_page : public page {
1387 void render(out_context *out, pdf_matrix xform) override
1389 out->contents += "q " + clip_to.transformed(xform).to_rect() + " re W n ";
1390 orig_page->render(out, xform);
1391 out->contents += "Q ";
1393 clip_page(page *p, BBox &to) : page(p), orig_page(p), clip_to(to) { }
1396 class clip_cmd : public cmd_exec_simple {
1398 page *process_page(page *p) override
1400 BBox to = p->image_box.enlarged(bleed);
1401 return new clip_page(p, to);
1406 bleed = c->arg("bleed")->as_double(0);
1410 static const arg_def clip_args[] = {
1411 { "bleed", AT_DIMEN },
1415 /*** Command table ***/
1417 template<typename T> cmd_exec *ctor(cmd *c) { return new T(c); }
1419 const cmd_def cmd_table[] = {
1420 { "add-blank",add_blank_args, 0, &ctor<add_blank_cmd> },
1421 { "apply", no_args, 1, &ctor<apply_cmd> },
1422 { "book", book_args, 0, &ctor<book_cmd> },
1423 { "clip", clip_args, 0, &ctor<clip_cmd> },
1424 { "cropmarks",cropmarks_args, 0, &ctor<cropmarks_cmd> },
1425 { "debug", no_args, 0, &ctor<debug_cmd> },
1426 { "expand", expand_args, 0, &ctor<expand_cmd> },
1427 { "fit", fit_args, 0, &ctor<fit_cmd> },
1428 { "flip", flip_args, 0, &ctor<flip_cmd> },
1429 { "margins", margins_args, 0, &ctor<margins_cmd> },
1430 { "merge", no_args, 0, &ctor<merge_cmd> },
1431 { "modulo", modulo_args, 1, &ctor<modulo_cmd> },
1432 { "move", move_args, 0, &ctor<move_cmd> },
1433 { "null", no_args, 0, &ctor<null_cmd> },
1434 { "nup", nup_args, 0, &ctor<nup_cmd> },
1435 { "paper", paper_args, 0, &ctor<paper_cmd> },
1436 { "rotate", rotate_args, 0, &ctor<rotate_cmd> },
1437 { "scale", scale_args, 0, &ctor<scale_cmd> },
1438 { "scaleto", scaleto_args, 0, &ctor<scaleto_cmd> },
1439 { "select", no_args, 1, &ctor<select_cmd> },
1440 { NULL, NULL, 0, NULL }