X-Git-Url: http://mj.ucw.cz/gitweb/?a=blobdiff_plain;f=cmds.cc;h=7d0f9b659d934323bd02ef1b336c1811990867b6;hb=f9f510737accd2022b293eb32dba8a097b88791d;hp=b6c8eed7cdb355bd1f4f793a8e2be268bcb52b3a;hpb=932f7160f80f9c34fcf1dcc41cb2b05b78e54956;p=paperjam.git diff --git a/cmds.cc b/cmds.cc index b6c8eed..7d0f9b6 100644 --- a/cmds.cc +++ b/cmds.cc @@ -20,7 +20,7 @@ public: }; static const arg_def no_args[] = { - { NULL, 0 } + { NULL, 0, NULL } }; /*** Generic routines ***/ @@ -32,6 +32,11 @@ class xform_page : public page { pdf_matrix xform; public: void render(out_context *out, pdf_matrix xform) override; + void debug_dump() override + { + debug("Transform [%s]", xform.to_string().c_str()); + orig_page->debug_dump(); + } xform_page(page *p, pdf_matrix xf); }; @@ -86,13 +91,13 @@ public: return; } if (aw->given() != ah->given() || aname->given() == aw->given()) - die("Either paper format name or width and height must be given"); + err("Either paper format name or width and height must be given"); if (aname->given()) { const char *name = aname->as_string("").c_str(); const paper *pap = paperinfo(name); if (!pap) - die("No paper called %s is known", name); + err("No paper called \"%s\" is known", name); w = paperpswidth(pap); h = paperpsheight(pap); } @@ -105,9 +110,9 @@ public: }; #define PAPER_ARGS \ - { "paper", AT_STRING | AT_POSITIONAL }, \ - { "w", AT_DIMEN }, \ - { "h", AT_DIMEN } + { "paper", AT_STRING | AT_POSITIONAL, "Paper format name (e.g., a4)" }, \ + { "w", AT_DIMEN, "Paper width" }, \ + { "h", AT_DIMEN, "Paper height" } // Position specification @@ -118,7 +123,7 @@ public: pos_spec(string s) { if (s.size() != 2) - die("Value of pos must have two characters"); + err("Value of pos must have two characters"); if (s[0] == 't') v = 1; else if (s[0] == 'c') @@ -126,7 +131,7 @@ public: else if (s[0] == 'b') v = -1; else - die("First character of pos must be t/c/b"); + err("First character of pos must be t/c/b"); if (s[1] == 'l') h = -1; else if (s[1] == 'c') @@ -134,7 +139,7 @@ public: else if (s[1] == 'r') h = 1; else - die("Second character of pos must be l/c/r"); + err("Second character of pos must be l/c/r"); } pos_spec(cmd *c) : pos_spec(c->arg("pos")->as_string("cc")) { } pdf_matrix place(BBox &inner, BBox &outer) @@ -173,7 +178,7 @@ public: }; #define POS_ARGS \ - { "pos", AT_STRING } + { "pos", AT_STRING, "Position on the page: (t|c|b)(l|c|r)" } // Margins @@ -199,43 +204,47 @@ public: { bb->x_min += l; bb->x_max -= r; - bb->y_min += b; - bb->y_max -= t; + bb->y_min += t; + bb->y_max -= b; if (bb->x_min >= bb->x_max || bb->y_min >= bb->y_max) - die("Margins cannot be larger than the whole page"); + err("Margins cannot be larger than the whole page"); } void expand_box(BBox *bb) { bb->x_min -= l; bb->x_max += r; - bb->y_min -= b; - bb->y_max += t; + bb->y_min -= t; + bb->y_max += b; } }; -#define MARGIN_ARGS1_NAMED(name) \ - { name, AT_DIMEN } +#define MARGIN_ARGS1_NAMED(name) \ + { name, AT_DIMEN, "Size of all margins (default: 0)" } -#define MARGIN_ARGS1_POSNL(name) \ - { name, AT_DIMEN | AT_POSITIONAL } +#define MARGIN_ARGS1_POSNL(name) \ + { name, AT_DIMEN | AT_POSITIONAL, "Size of all margins (default: 0)" } -#define MARGIN_ARGS2(sx) \ - { "h" sx, AT_DIMEN }, \ - { "v" sx, AT_DIMEN }, \ - { "l" sx, AT_DIMEN }, \ - { "r" sx, AT_DIMEN }, \ - { "t" sx, AT_DIMEN }, \ - { "b" sx, AT_DIMEN } +#define MARGIN_ARGS2(sx) \ + { "h" sx, AT_DIMEN, "Size of horizontal margins" }, \ + { "v" sx, AT_DIMEN, "Size of vertical margins" }, \ + { "l" sx, AT_DIMEN, "Size of left margin" }, \ + { "r" sx, AT_DIMEN, "Size of right margin" }, \ + { "t" sx, AT_DIMEN, "Size of top margin" }, \ + { "b" sx, AT_DIMEN, "Size of bottom margin" } // Colors class color_spec { - double rgb[3]; public: + double rgb[3]; + color_spec() + { + rgb[0] = rgb[1] = rgb[2] = 0; + } color_spec(string s) { if (s.length() != 6) - die("Invalid color specification \"%s\": expecting 6 hex digits", s.c_str()); + err("Invalid color specification \"%s\": expecting 6 hex digits", s.c_str()); for (int i=0; i<3; i++) { int x = 0; @@ -261,6 +270,7 @@ public: // Cropmarks class cropmark_spec { +public: enum mark_type { MARK_NONE, MARK_BOX, @@ -272,10 +282,15 @@ class cropmark_spec { double pen_width; double arm_length; double offset; - string crop_cross(double x, double y, uint mask); - QPDFObjectHandle egstate; color_spec color; -public: + cropmark_spec() + { + type = MARK_NONE; + pen_width = 0.2; + arm_length = 5*mm; + offset = 0; + egstate = QPDFObjectHandle::newNull(); + } cropmark_spec(cmd *c, const string prefix="", const string def_type="cross") : color(c->arg(prefix + "color")->as_string("000000")) { string t = c->arg(prefix + "mark")->as_string(def_type); @@ -292,7 +307,7 @@ public: else if (t == "bg") type = MARK_BG; else - die("Invalid cropmark type %s", t.c_str()); + err("Invalid cropmark type %s", t.c_str()); pen_width = c->arg(prefix + "pen")->as_double(0.2); arm_length = c->arg(prefix + "len")->as_double(5*mm); @@ -301,6 +316,9 @@ public: } bool is_bg() { return (type == MARK_BG); } string pdf_stream(out_context *out, BBox &box, pdf_matrix &xform); +private: + string crop_cross(double x, double y, uint mask); + QPDFObjectHandle egstate; }; string cropmark_spec::crop_cross(double x, double y, uint mask) @@ -347,11 +365,7 @@ string cropmark_spec::pdf_stream(out_context *out, BBox &box, pdf_matrix &xform) out->egstates.replaceKey(egs_res, egstate); s += egs_res + " gs "; - BBox b = box; - b.x_min -= offset; - b.x_max += offset; - b.y_min -= offset; - b.y_max += offset; + BBox b = box.enlarged(offset); switch (type) { @@ -387,12 +401,12 @@ string cropmark_spec::pdf_stream(out_context *out, BBox &box, pdf_matrix &xform) return s; } -#define CROPMARK_ARGS(px) \ - { px "mark", AT_STRING }, \ - { px "pen", AT_DIMEN }, \ - { px "len", AT_DIMEN }, \ - { px "offset",AT_DIMEN }, \ - { px "color", AT_STRING } +#define CROPMARK_ARGS(px) \ + { px "mark", AT_STRING, "Cropmark style: box/cross/in/out/bg" }, \ + { px "pen", AT_DIMEN, "Cropmark pen width (default: 0.2pt)" }, \ + { px "len", AT_DIMEN, "Cropmark arm length (default: 5mm)" }, \ + { px "offset",AT_DIMEN, "Cropmark offset outside the box (default: 0)" }, \ + { px "color", AT_STRING, "Cropmark color (RRGGBB, default: 000000)" } // Scaling preserving aspect ratio @@ -425,9 +439,9 @@ public: }; static const arg_def move_args[] = { - { "x", AT_DIMEN | AT_MANDATORY | AT_POSITIONAL }, - { "y", AT_DIMEN | AT_MANDATORY | AT_POSITIONAL }, - { NULL, 0 } + { "x", AT_DIMEN | AT_MANDATORY | AT_POSITIONAL, "Move right by this distance" }, + { "y", AT_DIMEN | AT_MANDATORY | AT_POSITIONAL, "Move up by this distance" }, + { NULL, 0, NULL } }; /*** scale ***/ @@ -449,9 +463,9 @@ public: }; static const arg_def scale_args[] = { - { "x", AT_DOUBLE | AT_MANDATORY | AT_POSITIONAL }, - { "y", AT_DOUBLE | AT_POSITIONAL }, - { NULL, 0 } + { "x", AT_DOUBLE | AT_MANDATORY | AT_POSITIONAL, "Scale horizontally by this fraction" }, + { "y", AT_DOUBLE | AT_POSITIONAL, "Scale vertically by this fraction (default: x)" }, + { NULL, 0, NULL } }; /*** rotate ***/ @@ -461,11 +475,11 @@ class rotate_cmd : public cmd_exec_simple { public: rotate_cmd(cmd *c) { - deg = c->arg("deg")->as_int(0) % 360; + deg = c->arg("angle")->as_int(0) % 360; if (deg < 0) deg += 360; if (deg % 90) - die("Rotate requires a multiple of 90 degrees"); + err("The angle must be a multiple of 90 degrees"); } page *process_page(page *p) override { @@ -494,8 +508,8 @@ public: }; static const arg_def rotate_args[] = { - { "angle", AT_INT | AT_MANDATORY | AT_POSITIONAL }, - { NULL, 0 } + { "angle", AT_INT | AT_MANDATORY | AT_POSITIONAL, "Rotate clockwise by this angle" }, + { NULL, 0, NULL } }; /*** flip ***/ @@ -509,7 +523,7 @@ public: horizontal = c->arg("h")->as_int(0); vertical = c->arg("v")->as_int(0); if (!horizontal && !vertical) - die("Flip has no direction specified"); + err("No direction specified"); } page *process_page(page *p) override { @@ -529,9 +543,9 @@ public: }; static const arg_def flip_args[] = { - { "h", AT_SWITCH }, - { "v", AT_SWITCH }, - { NULL, 0 } + { "h", AT_SWITCH, "Flip horizontally" }, + { "v", AT_SWITCH, "Flip vertically" }, + { NULL, 0, NULL } }; /*** select ***/ @@ -552,7 +566,7 @@ static int validate_page_index(vector &pages, int idx) return idx - 1; if (idx <= -1 && idx >= (int) -pages.size()) return idx + pages.size(); - die("Page index %d out of range", idx); + err("Page index %d out of range", idx); } vector select_cmd::process(vector &pages) @@ -566,7 +580,7 @@ vector select_cmd::process(vector &pages) int f = validate_page_index(pages, ps.from); int t = validate_page_index(pages, ps.to); int step = (f <= t) ? 1 : -1; - for (int i=f; f<=t; f += step) + for (int i=f; i<=t; i += step) selected.push_back(pages[i]); } auto processed = run_command_list(pb->commands, selected); @@ -636,7 +650,7 @@ public: { n = c->arg("n")->as_int(0); if (n <= 0) - die("Modulo must have n > 0"); + err("Modulo must have n > 0"); half = c->arg("half")->as_int(0); pipe = c->pipe; } @@ -669,7 +683,7 @@ vector modulo_cmd::process(vector &pages) else if (i < 0 && i >= -n) j = (tuples-1-tuple)*n + (-i) - 1; else - die("Modulo: invalid index %d", i); + err("Invalid index %d", i); if (j < (int) pages.size()) tmp.push_back(pages[j]); else @@ -690,40 +704,53 @@ vector modulo_cmd::process(vector &pages) } static const arg_def modulo_args[] = { - { "n", AT_INT | AT_MANDATORY | AT_POSITIONAL }, - { "half", AT_SWITCH }, - { NULL, 0 } + { "n", AT_INT | AT_MANDATORY | AT_POSITIONAL, "Number of pages in a single tuple" }, + { "half", AT_SWITCH, "Process only the first half of n-tuples" }, + { NULL, 0, NULL } }; -/*** draw-bbox ***/ +/*** debug ***/ -class draw_bbox_page : public page { +class debug_page : public page { page *orig_page; + cropmark_spec *page_cm, *image_cm; public: - void render(out_context *out, pdf_matrix xform) override; - draw_bbox_page(page *p) : page(p) { orig_page = p; } + 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) { } + void render(out_context *out, pdf_matrix xform) override + { + orig_page->render(out, xform); + BBox page_bbox = BBox(0, 0, width, height); + out->contents += page_cm->pdf_stream(out, page_bbox, xform); + out->contents += image_cm->pdf_stream(out, image_box, xform); + } + void debug_dump() override + { + debug("Draw debugging boxes"); + orig_page->debug_dump(); + } }; -class draw_bbox_cmd : public cmd_exec_simple { +class debug_cmd : public cmd_exec_simple { + cropmark_spec page_cmarks; + cropmark_spec image_cmarks; public: - draw_bbox_cmd(cmd *c UNUSED) { } + debug_cmd(cmd *c UNUSED) + { + page_cmarks.type = cropmark_spec::MARK_BOX; + page_cmarks.color.rgb[0] = 1; + page_cmarks.color.rgb[1] = 0; + page_cmarks.color.rgb[2] = 0; + image_cmarks.type = cropmark_spec::MARK_BOX; + image_cmarks.color.rgb[0] = 0; + image_cmarks.color.rgb[1] = 1; + image_cmarks.color.rgb[2] = 0; + } page *process_page(page *p) override { - return new draw_bbox_page(p); + return new debug_page(p, &page_cmarks, &image_cmarks); } }; -void draw_bbox_page::render(out_context *out, pdf_matrix xform) -{ - orig_page->render(out, xform); - out->contents += - "q " + - xform.to_string() + " cm " + - "0 1 0 RG " + - image_box.to_rect() + " re S " + - "Q "; -} - /*** merge ***/ class merge_cmd : public cmd_exec { @@ -751,7 +778,7 @@ public: else { if (!is_equal(width, p->width) || !is_equal(height, p->height)) - die("All pages participating in a merge must have the same dimensions"); + err("All pages must have the same dimensions"); image_box.join(p->image_box); } } @@ -761,6 +788,14 @@ public: for (auto p: orig_pages) p->render(out, xform); } + void debug_dump() override + { + debug("Merge pages"); + debug_indent += 4; + for (auto p: orig_pages) + p->debug_dump(); + debug_indent -= 4; + } }; vector merge_cmd::process(vector &pages) @@ -792,7 +827,7 @@ public: static const arg_def paper_args[] = { PAPER_ARGS, POS_ARGS, - { NULL, 0 } + { NULL, 0, NULL } }; /*** scaleto ***/ @@ -820,7 +855,7 @@ public: static const arg_def scaleto_args[] = { PAPER_ARGS, POS_ARGS, - { NULL, 0 } + { NULL, 0, NULL } }; /*** fit ***/ @@ -867,7 +902,7 @@ static const arg_def fit_args[] = { POS_ARGS, MARGIN_ARGS1_NAMED("margin"), MARGIN_ARGS2("margin"), - { NULL, 0 } + { NULL, 0, NULL } }; /*** expand ***/ @@ -884,7 +919,7 @@ public: q->width = p->width + marg.l + marg.r; q->height = p->height + marg.t + marg.b; if (q->width < 0.001 || q->height < 0.001) - die("Expansion must result in positive page dimensions"); + err("Expansion must result in positive page dimensions"); return q; } }; @@ -892,7 +927,7 @@ public: static const arg_def expand_args[] = { MARGIN_ARGS1_POSNL("by"), MARGIN_ARGS2(""), - { NULL, 0 } + { NULL, 0, NULL } }; /*** margins ***/ @@ -903,14 +938,10 @@ public: margins_cmd(cmd *c) : marg(c, "size", "") { } page *process_page(page *p) override { - pdf_matrix xf; - xf.shift(-p->image_box.x_min, -p->image_box.y_min); - xf.shift(marg.l, marg.b); - page *q = new xform_page(p, xf); - q->width = p->image_box.width() + marg.l + marg.r; - q->height = p->image_box.height() + marg.t + marg.b; - if (q->width < 0.001 || q->height < 0.001) - die("Margins must result in positive page dimensions"); + page *q = new xform_page(p, pdf_matrix()); + q->image_box = BBox(marg.l, marg.t, p->width - marg.r, p->height - marg.b); + if (q->image_box.width() < 0.001 || q->image_box.height() < 0.001) + err("Margins must result in positive image dimensions"); return q; } }; @@ -918,7 +949,7 @@ public: static const arg_def margins_args[] = { MARGIN_ARGS1_POSNL("size"), MARGIN_ARGS2(""), - { NULL, 0 } + { NULL, 0, NULL } }; /*** add-blank ***/ @@ -954,9 +985,9 @@ vector add_blank_cmd::process(vector &pages) } static const arg_def add_blank_args[] = { - { "n", AT_INT | AT_POSITIONAL }, + { "n", AT_INT | AT_POSITIONAL, "Number of blank pages to add (default: 1)" }, PAPER_ARGS, - { NULL, 0 } + { NULL, 0, NULL } }; /*** book ***/ @@ -968,7 +999,7 @@ public: { n = c->arg("n")->as_int(0); if (n % 4) - die("Number of pages per signature must be divisible by 4"); + err("Number of pages per signature must be divisible by 4"); } vector process(vector &pages) override; }; @@ -1001,8 +1032,8 @@ vector book_cmd::process(vector &pages) } static const arg_def book_args[] = { - { "n", AT_INT | AT_POSITIONAL }, - { NULL, 0 } + { "n", AT_INT | AT_POSITIONAL, "Number of pages in a single booklet" }, + { NULL, 0, NULL } }; /*** nup ***/ @@ -1052,7 +1083,7 @@ public: else if (grid_n > 0 && !grid_m) num_tiles = grid_n; else - die("Grid size must be at least 1x1"); + err("Grid size must be at least 1x1"); const string by = c->arg("by")->as_string("rows"); if (by == "rows" || by == "row" || by == "r") @@ -1062,7 +1093,7 @@ public: else if (by == "tile" || by == "t") fill_by = BY_TILE; else - die("Parameter \"by\" must be rows/cols/tile"); + err("Argument \"by\" must be rows/cols/tile"); crop = c->arg("crop")->as_int(0); mixed = c->arg("mixed")->as_int(0); @@ -1074,9 +1105,9 @@ public: vspace = c->arg("vspace")->as_double(space); if (!is_zero(scale) && (!is_zero(paper.w) || rotate >= 0)) - die("When nup is used with explicit scaling, paper size nor rotation may be given"); + err("When used with explicit scaling, paper size nor rotation may be given"); if (!is_zero(scale) && !grid_m) - die("When nup is used with explicit scaling, both grid sizes must be given"); + err("When used with explicit scaling, both grid sizes must be given"); } vector process(vector &pages) override; @@ -1145,10 +1176,11 @@ void nup_cmd::try_config(nup_state &st) st.scale = scale_to_fit(image, window); st.fill_factor = (st.scale*image.width() * st.scale*image.height()) / (st.paper_w * st.paper_h); - debug("Try: %dx%d on %.3f x %.3f => scale %.3f, fill %.6f", - st.cols, st.rows, - st.paper_w, st.paper_h, - st.scale, st.fill_factor); + if (debug_level > 1) + debug("Try: %dx%d on %.3f x %.3f => scale %.3f, fill %.6f", + st.cols, st.rows, + st.paper_w, st.paper_h, + st.scale, st.fill_factor); if (!found_solution || best.fill_factor < st.fill_factor) { @@ -1233,8 +1265,11 @@ void nup_cmd::find_config(vector &in, BBox *page_boxes) } if (!found_solution) - die("Nup did not find a feasible solution"); - debug("Best: %dx%d on %.3f x %.3f", best.cols, best.rows, best.paper_w, best.paper_h); + err("No feasible solution found"); + debug("Best: %dx%d on %.3f x %.3f => scale %.3f, fill %.6f", + best.cols, best.rows, + best.paper_w, best.paper_h, + best.scale, best.fill_factor); debug_indent -= 4; } @@ -1246,6 +1281,14 @@ public: cropmark_spec *cmarks; void render(out_context *out, pdf_matrix xform) override; nup_page(nup_state &st) : page(st.paper_w, st.paper_h) { } + void debug_dump() override + { + debug("N-up printing"); + debug_indent += 4; + for (auto p: orig_pages) + p->debug_dump(); + debug_indent -= 4; + } }; void nup_page::render(out_context *out, pdf_matrix parent_xform) @@ -1313,23 +1356,23 @@ page *nup_cmd::process_single(vector &in) } static const arg_def nup_args[] = { - { "n", AT_INT | AT_POSITIONAL | AT_MANDATORY }, - { "m", AT_INT | AT_POSITIONAL }, - { "by", AT_STRING }, - { "crop", AT_SWITCH }, - { "mixed", AT_SWITCH }, - { "rotate", AT_SWITCH }, - { "scale", AT_DOUBLE }, + { "n", AT_INT | AT_POSITIONAL | AT_MANDATORY, "Number of tiles on a page" }, + { "m", AT_INT | AT_POSITIONAL, "If both n and m are given, produce n x m tiles on a page" }, + { "by", AT_STRING, "Tile filling order: rows/cols/tile (default: rows)" }, + { "crop", AT_SWITCH, "Crop input pages to their image box" }, + { "mixed", AT_SWITCH, "Allow input pages of mixed sizes" }, + { "rotate", AT_SWITCH, "Force (non-)rotation" }, + { "scale", AT_DOUBLE, "Force specific scaling factor" }, PAPER_ARGS, MARGIN_ARGS1_NAMED("margin"), MARGIN_ARGS2("margin"), POS_ARGS, CROPMARK_ARGS("c"), - { "tpos", AT_STRING }, - { "space", AT_DIMEN }, - { "hspace", AT_DIMEN }, - { "vspace", AT_DIMEN }, - { NULL, 0 } + { "tpos", AT_STRING, "Position of images inside tiles (default: tl)" }, + { "space", AT_DIMEN, "Space between tiles (default: 0)" }, + { "hspace", AT_DIMEN, "Horizontal space between tiles (default: space)" }, + { "vspace", AT_DIMEN, "Vertical space between tiles (default: space)" }, + { NULL, 0, NULL } }; /*** cropmarks ***/ @@ -1343,6 +1386,11 @@ public: orig_page->render(out, xform); out->contents += cm->pdf_stream(out, image_box, xform); } + void debug_dump() override + { + debug("Add cropmarks"); + orig_page->debug_dump(); + } cropmarks_page(page *p, cropmark_spec *cms) : page(p), orig_page(p), cm(cms) { } }; @@ -1352,14 +1400,149 @@ class cropmarks_cmd : public cmd_exec_simple { { return new cropmarks_page(p, &cm); } - public: cropmarks_cmd(cmd *c) : cm(c) { } }; static const arg_def cropmarks_args[] = { CROPMARK_ARGS(""), - { NULL, 0 } + { NULL, 0, NULL } +}; + +/*** clip ***/ + +class clip_page : public page { + page *orig_page; + BBox clip_to; +public: + void render(out_context *out, pdf_matrix xform) override + { + out->contents += "q " + clip_to.transformed(xform).to_rect() + " re W n "; + orig_page->render(out, xform); + out->contents += "Q "; + } + void debug_dump() override + { + debug("Clip [%.3f %.3f %.3f %.3f]", clip_to.x_min, clip_to.y_min, clip_to.x_max, clip_to.y_max); + orig_page->debug_dump(); + } + clip_page(page *p, BBox &to) : page(p), orig_page(p), clip_to(to) { } +}; + +class clip_cmd : public cmd_exec_simple { + double bleed; + page *process_page(page *p) override + { + BBox to = p->image_box.enlarged(bleed); + return new clip_page(p, to); + } +public: + clip_cmd(cmd *c) + { + bleed = c->arg("bleed")->as_double(0); + } +}; + +static const arg_def clip_args[] = { + { "bleed", AT_DIMEN, "Allow bleeding of image outside its box" }, + { NULL, 0, NULL } +}; + +/*** common ***/ + +class common_cmd : public cmd_exec { + vector process(vector &pages) override + { + if (!pages.size()) + return pages; + + const page *first = pages[0]; + BBox pbox(first->width, first->height); + BBox ibox = first->image_box; + for (auto p: pages) + { + BBox pg(p->width, p->height); + pbox.join(pg); + ibox.join(p->image_box); + } + + vector out; + for (auto p: pages) + { + page *q = new xform_page(p, pdf_matrix()); + q->width = pbox.width(); + q->height = pbox.height(); + q->image_box = ibox; + out.push_back(q); + } + + return out; + } +public: + common_cmd(cmd *c UNUSED) { } +}; + +/*** slice ***/ + +class slice_cmd : public cmd_exec { + paper_spec paper; + pos_spec pos; + margin_spec margin; + double bleed; +public: + slice_cmd(cmd *c) : paper(c), pos(c), margin(c, "margin", "margin") + { + if (is_zero(paper.w) || is_zero(paper.h)) + err("Paper format must be given"); + bleed = c->arg("bleed")->as_double(0); + } + vector process(vector &pages) override; +}; + +vector slice_cmd::process(vector &pages) +{ + vector out; + + for (auto p: pages) + { + double pw = paper.w - margin.l - margin.r; + double ph = paper.h - margin.t - margin.b; + if (pw < 0 || ph < 0) + err("Margins larger than paper"); + + int cols = (int) ceil((p->image_box.width() - 2*mm) / pw); + int rows = (int) ceil((p->image_box.height() - 2*mm) / ph); + BBox big_box(cols*pw, rows*ph); + pdf_matrix placement = pos.place(p->image_box, big_box); + + debug("Slicing [%.3f,%.3f] to %dx%d [%.3f,%.3f]", p->image_box.width(), p->image_box.height(), rows, cols, pw, ph); + for (int r=0; rwidth = paper.w; + q->height = paper.h; + BBox slice_box = BBox(margin.l, margin.t, paper.w - margin.r, paper.h - margin.b); + q->image_box = p->image_box.transformed(xf); + q->image_box.intersect(slice_box); + BBox bleeding_slice = slice_box.enlarged(bleed); + out.push_back(new clip_page(q, bleeding_slice)); + } + } + + return out; +} + +static const arg_def slice_args[] = { + PAPER_ARGS, + POS_ARGS, + MARGIN_ARGS1_NAMED("margin"), + MARGIN_ARGS2("margin"), + { "bleed", AT_DIMEN, "Allow bleeding of image outside its box" }, + { NULL, 0, NULL } }; /*** Command table ***/ @@ -1367,24 +1550,50 @@ static const arg_def cropmarks_args[] = { template cmd_exec *ctor(cmd *c) { return new T(c); } const cmd_def cmd_table[] = { - { "null", no_args, 0, &ctor }, - { "move", move_args, 0, &ctor }, - { "scale", scale_args, 0, &ctor }, - { "rotate", rotate_args, 0, &ctor }, - { "flip", flip_args, 0, &ctor }, - { "select", no_args, 1, &ctor }, - { "apply", no_args, 1, &ctor }, - { "modulo", modulo_args, 1, &ctor }, - { "draw-bbox",no_args, 0, &ctor }, - { "merge", no_args, 0, &ctor }, - { "paper", paper_args, 0, &ctor }, - { "scaleto", scaleto_args, 0, &ctor }, - { "fit", fit_args, 0, &ctor }, - { "expand", expand_args, 0, &ctor }, - { "margins", margins_args, 0, &ctor }, - { "add-blank",add_blank_args, 0, &ctor }, - { "book", book_args, 0, &ctor }, - { "nup", nup_args, 0, &ctor }, - { "cropmarks",cropmarks_args, 0, &ctor }, - { NULL, NULL, 0, NULL } + { "add-blank",add_blank_args, 0, &ctor, + "Add blank page(s) after each page" }, + { "apply", no_args, 1, &ctor, + "Apply commands to selected pages" }, + { "book", book_args, 0, &ctor, + "Prepare booklets for book binding" }, + { "clip", clip_args, 0, &ctor, + "Suppress page contents drawn outside the image box" }, + { "common", no_args, 0, &ctor, + "Use a common page size and image box for all pages" }, + { "cropmarks",cropmarks_args, 0, &ctor, + "Draw cropping marks around the image box" }, + { "debug", no_args, 0, &ctor, + "Draw debugging information on the page)" }, + { "expand", expand_args, 0, &ctor, + "Expand paper around the image" }, + { "fit", fit_args, 0, &ctor, + "Fit image to a given paper" }, + { "flip", flip_args, 0, &ctor, + "Flip page horizontally and/or vertically" }, + { "margins", margins_args, 0, &ctor, + "Define image box by dimensions of margins around it" }, + { "merge", no_args, 0, &ctor, + "Merge all pages to one by placing them one over another" }, + { "modulo", modulo_args, 1, &ctor, + "Act on n-tuples of pages" }, + { "move", move_args, 0, &ctor, + "Shift contents on the page" }, + { "null", no_args, 0, &ctor, + "Do nothing" }, + { "nup", nup_args, 0, &ctor, + "Combine multiple pages to one (n-up printing)" }, + { "paper", paper_args, 0, &ctor, + "Place image on a given paper" }, + { "rotate", rotate_args, 0, &ctor, + "Rotate the page by multiples of 90 degrees" }, + { "scale", scale_args, 0, &ctor, + "Scale the page by a given factor" }, + { "scaleto", scaleto_args, 0, &ctor, + "Scale the page to a given size" }, + { "select", no_args, 1, &ctor, + "Select a subset of pages" }, + { "slice", slice_args, 0, &ctor, + "Slice to smaller pages" }, + { NULL, NULL, 0, NULL, + NULL, } };