/*
* PaperJam -- Commands
*
- * (c) 2018 Martin Mares <mj@ucw.cz>
+ * (c) 2018--2022 Martin Mares <mj@ucw.cz>
*/
#include <cassert>
/*** Generic routines ***/
-// Transformed page
-
-class xform_page : public page {
- page *orig_page;
- pdf_matrix xform;
-public:
- void render(out_context *out, pdf_matrix xform) override;
- xform_page(page *p, pdf_matrix xf);
-};
-
-xform_page::xform_page(page *p, pdf_matrix xf)
-{
- orig_page = p;
- index = p->index;
- xform = xf;
-
- BBox media(p->width, p->height);
- media.transform(xf);
- width = media.width();
- height = media.height();
-
- image_box = p->image_box;
- image_box.transform(xf);
-}
-
-void xform_page::render(out_context *out, pdf_matrix parent_xform)
-{
- orig_page->render(out, xform * parent_xform);
-}
-
// Commands acting on individual pages
class cmd_exec_simple : public cmd_exec {
{
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)
err("Margins cannot be larger than the whole page");
}
{
bb->x_min -= l;
bb->x_max += r;
- bb->y_min -= b;
- bb->y_max += t;
+ bb->y_min -= t;
+ bb->y_max += b;
}
};
{
pdf_matrix m;
m.shift(x, y);
- return new xform_page(p, m);
+ return new xform_page(p, "move", m);
}
};
{
pdf_matrix m;
m.scale(x_factor, y_factor);
- return new xform_page(p, m);
+ return new xform_page(p, "scale", m);
}
};
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)
}
page *process_page(page *p) override
{
- pdf_matrix m;
- switch (deg)
- {
- case 0:
- break;
- case 90:
- m.rotate_deg(-90);
- m.shift(0, p->width);
- break;
- case 180:
- m.rotate_deg(180);
- m.shift(p->width, p->height);
- break;
- case 270:
- m.rotate_deg(90);
- m.shift(p->height, 0);
- break;
- default:
- abort();
- }
- return new xform_page(p, m);
+ return new xform_page(p, "rotate", pdf_rotation_matrix(deg, p->width, p->height));
}
};
m.scale(-1, 1);
m.shift(p->width, 0);
}
- return new xform_page(p, m);
+ return new xform_page(p, "flip", m);
}
};
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 + step; i += step)
selected.push_back(pages[i]);
}
auto processed = run_command_list(pb->commands, selected);
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 debug_cmd : public cmd_exec_simple {
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<page *> merge_cmd::process(vector<page *> &pages)
{
BBox paper_box = BBox(paper.w, paper.h);
pdf_matrix xf = pos.place(p->image_box, paper_box);
- page *q = new xform_page(p, xf);
+ page *q = new xform_page(p, "paper", xf);
q->width = paper.w;
q->height = paper.h;
return q;
xf.scale(scale_to_fit(orig_box, paper_box));
orig_box.transform(xf);
xf.concat(pos.place(orig_box, paper_box));
- page *q = new xform_page(p, xf);
+ page *q = new xform_page(p, "scaleto", xf);
q->width = paper.w;
q->height = paper.h;
return q;
xf.scale(scale_to_fit(orig_box, paper_box));
orig_box.transform(xf);
xf.concat(pos.place(orig_box, paper_box));
- q = new xform_page(p, xf);
+ q = new xform_page(p, "fit", xf);
q->width = paper.w;
q->height = paper.h;
}
// No paper given: adjust paper to fit image
xf.shift(-p->image_box.x_min, -p->image_box.y_min);
xf.shift(marg.l, marg.b);
- q = new xform_page(p, xf);
+ q = new xform_page(p, "fit", xf);
q->width = p->image_box.width() + marg.l + marg.r;
q->height = p->image_box.height() + marg.t + marg.b;
}
{
pdf_matrix xf;
xf.shift(marg.l, marg.b);
- page *q = new xform_page(p, xf);
+ page *q = new xform_page(p, "expand", xf);
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)
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)
- err("Margins must result in positive page dimensions");
+ page *q = new xform_page(p, "margins", 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;
}
};
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)
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) { }
};
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) { }
};
{ NULL, 0, NULL }
};
+/*** common ***/
+
+class common_cmd : public cmd_exec {
+ vector<page *> process(vector<page *> &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<page *> out;
+ for (auto p: pages)
+ {
+ page *q = new xform_page(p, "common", 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<page *> process(vector<page *> &pages) override;
+};
+
+vector<page *> slice_cmd::process(vector<page *> &pages)
+{
+ vector<page *> 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; r<rows; r++)
+ for (int c=0; c<cols; c++)
+ {
+ pdf_matrix xf = placement;
+ xf.shift(-c*pw, -(rows-1-r)*ph);
+ xf.shift(margin.l, margin.t);
+ page *q = new xform_page(p, "slice", xf);
+ q->width = 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 ***/
template<typename T> cmd_exec *ctor(cmd *c) { return new T(c); }
"Prepare booklets for book binding" },
{ "clip", clip_args, 0, &ctor<clip_cmd>,
"Suppress page contents drawn outside the image box" },
+ { "common", no_args, 0, &ctor<common_cmd>,
+ "Use a common page size and image box for all pages" },
{ "cropmarks",cropmarks_args, 0, &ctor<cropmarks_cmd>,
"Draw cropping marks around the image box" },
{ "debug", no_args, 0, &ctor<debug_cmd>,
"Scale the page to a given size" },
{ "select", no_args, 1, &ctor<select_cmd>,
"Select a subset of pages" },
+ { "slice", slice_args, 0, &ctor<slice_cmd>,
+ "Slice to smaller pages" },
{ NULL, NULL, 0, NULL,
NULL, }
};