From 265b545fbd3629941da2247f513bd517756aefac Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Tue, 3 Apr 2018 22:36:37 +0200 Subject: [PATCH] Implemented paper, scaleto, fit --- Makefile | 2 +- cmds.cc | 270 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- jam.h | 11 +++ 3 files changed, 280 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 9344b44..7552ec0 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ all: paperjam paperjam: paperjam.o pdf-tools.o parse.o cmds.o pdf.o $(LD) -o $@ $^ $(LDLIBS) -paperjam: LDLIBS += -lqpdf +paperjam: LDLIBS += -lqpdf -lpaper paperjam: LD=$(CXX) paperjam.o: jam.h pdf-tools.h diff --git a/cmds.cc b/cmds.cc index 4212ddf..3dcf02b 100644 --- a/cmds.cc +++ b/cmds.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include "jam.h" @@ -22,7 +23,9 @@ static const arg_def no_args[] = { { NULL, 0 } }; -/*** Generic transformed page ***/ +/*** Generic routines ***/ + +// Transformed page class xform_page : public page { page *orig_page; @@ -52,6 +55,8 @@ 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 { virtual page *process_page(page *p) = 0; vector process(vector &pages) override; @@ -65,6 +70,166 @@ vector cmd_exec_simple::process(vector &pages) return out; } +// Paper specifications + +class paper_spec { +public: + double w, h; + paper_spec(cmd *c, bool maybe=true) + { + arg_val *aname = c->arg("paper"); + arg_val *aw = c->arg("w"); + arg_val *ah = c->arg("h"); + if (!aname->given() && !aw->given() && !ah->given() && maybe) + { + w = h = 0; + return; + } + if (aw->given() != ah->given() || aname->given() == aw->given()) + die("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); + w = paperpswidth(pap); + h = paperpsheight(pap); + } + else + { + w = aw->as_double(0); + h = ah->as_double(0); + } + } +}; + +#define PAPER_ARGS \ + { "paper", AT_STRING | AT_POSITIONAL }, \ + { "w", AT_DIMEN }, \ + { "h", AT_DIMEN } + +// Position specification + +class pos_spec { +public: + int h, v; + pos_spec() { v = h = 0; } + pos_spec(string s) + { + if (s.size() != 2) + die("Value of pos must have two characters"); + if (s[0] == 't') + v = 1; + else if (s[0] == 'c') + v = 0; + else if (s[0] == 'b') + v = -1; + else + die("First character of pos must be t/c/b"); + if (s[1] == 'l') + h = -1; + else if (s[1] == 'c') + h = 0; + else if (s[1] == 'r') + h = 1; + else + die("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) + { + pdf_matrix m; + m.shift(-inner.x_min, -inner.y_min); + switch (h) + { + case -1: + break; + case 0: + m.shift((outer.width() - inner.width()) / 2, 0); + break; + case 1: + m.shift(outer.width() - inner.width(), 0); + break; + default: + abort(); + } + switch (v) + { + case -1: + break; + case 0: + m.shift(0, (outer.height() - inner.height()) / 2); + break; + case 1: + m.shift(0, outer.height() - inner.height()); + break; + default: + abort(); + } + m.shift(outer.x_min, outer.y_min); + return m; + } +}; + +#define POS_ARGS \ + { "pos", AT_STRING } + +// Margins + +class margin_spec { +public: + double l, r, t, b; + margin_spec(cmd *c, string basic, string sx) + { + double m, h, v; + m = c->arg(basic)->as_double(0); + h = c->arg("h" + sx)->as_double(m); + v = c->arg("v" + sx)->as_double(m); + l = c->arg("l" + sx)->as_double(h); + r = c->arg("r" + sx)->as_double(h); + t = c->arg("t" + sx)->as_double(v); + b = c->arg("b" + sx)->as_double(v); + } + void shrink_box(BBox *bb) + { + bb->x_min += l; + bb->x_max -= r; + bb->y_min += b; + bb->y_max -= t; + if (bb->x_min >= bb->x_max || bb->y_min >= bb->y_max) + die("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; + } +}; + +#define MARGIN_ARGS(basic, sx) \ + { basic, AT_DIMEN }, \ + { "h" sx, AT_DIMEN }, \ + { "v" sx, AT_DIMEN }, \ + { "l" sx, AT_DIMEN }, \ + { "r" sx, AT_DIMEN }, \ + { "t" sx, AT_DIMEN }, \ + { "b" sx, AT_DIMEN } + +// Scaling preserving aspect ratio + +double scale_to_fit(BBox &from, BBox &to) +{ + double fw = from.width(), fh = from.height(); + double tw = to.width(), th = to.height(); + if (is_zero(fw) || is_zero(fh) || is_zero(tw) || is_zero(th)) + return 1; + else + return min(tw/fw, th/fh); +} + /*** move ***/ class move_cmd : public cmd_exec_simple { @@ -410,7 +575,7 @@ public: } else { - if (abs(width-p->width) > 0.001 || abs(height-p->height) > 0.001) + if (!is_equal(width, p->width) || !is_equal(height, p->height)) die("All pages participating in a merge must have the same dimensions"); bbox.join(p->bbox); } @@ -431,6 +596,104 @@ vector merge_cmd::process(vector &pages) return out; } +/*** paper ***/ + +class paper_cmd : public cmd_exec_simple { + paper_spec paper; + pos_spec pos; +public: + paper_cmd(cmd *c) : paper(c), pos(c) { } + page *process_page(page *p) override + { + BBox paper_box = BBox(paper.w, paper.h); + pdf_matrix xf = pos.place(p->bbox, paper_box); + page *q = new xform_page(p, xf); + q->width = paper.w; + q->height = paper.h; + return q; + } +}; + +static const arg_def paper_args[] = { + PAPER_ARGS, + POS_ARGS, + { NULL, 0 } +}; + +/*** scaleto ***/ + +class scaleto_cmd : public cmd_exec_simple { + paper_spec paper; + pos_spec pos; +public: + scaleto_cmd(cmd *c) : paper(c), pos(c) { } + page *process_page(page *p) override + { + BBox orig_box = BBox(p->width, p->height); + BBox paper_box = BBox(paper.w, paper.h); + pdf_matrix xf; + 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); + q->width = paper.w; + q->height = paper.h; + return q; + } +}; + +static const arg_def scaleto_args[] = { + PAPER_ARGS, + POS_ARGS, + { NULL, 0 } +}; + +/*** fit ***/ + +class fit_cmd : public cmd_exec_simple { + paper_spec paper; + pos_spec pos; + margin_spec marg; +public: + fit_cmd(cmd *c) : paper(c, true), pos(c), marg(c, "margin", "margin") { } + page *process_page(page *p) override + { + pdf_matrix xf; + page *q; + + if (!is_zero(paper.w) && !is_zero(paper.h)) + { + // Paper given: scale image to fit paper + BBox orig_box = p->bbox; + BBox paper_box = BBox(paper.w, paper.h); + marg.shrink_box(&paper_box); + 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->width = paper.w; + q->height = paper.h; + } + else + { + // No paper given: adjust paper to fit image + xf.shift(-p->bbox.x_min, -p->bbox.y_min); + xf.shift(marg.l, marg.b); + q = new xform_page(p, xf); + q->width = p->bbox.width() + marg.l + marg.r; + q->height = p->bbox.height() + marg.t + marg.b; + } + return q; + } +}; + +static const arg_def fit_args[] = { + PAPER_ARGS, + POS_ARGS, + MARGIN_ARGS("margin", "margin"), + { NULL, 0 } +}; + /*** Command table ***/ template cmd_exec *ctor(cmd *c) { return new T(c); } @@ -446,5 +709,8 @@ const cmd_def cmd_table[] = { { "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 }, { NULL, NULL, 0, NULL } }; diff --git a/jam.h b/jam.h index 0c52b3b..31510d8 100644 --- a/jam.h +++ b/jam.h @@ -7,6 +7,7 @@ #include #include #include +#include using namespace std; @@ -16,6 +17,16 @@ typedef unsigned int uint; #define UNUSED __attribute__((unused)) #define FORMAT_CHECK(x,y,z) __attribute__((format(x,y,z))) +static inline bool is_zero(double z) +{ + return fabs(z) < 0.001; +} + +static inline bool is_equal(double x, double y) +{ + return is_zero(x-y); +} + #include "pdf-tools.h" /*** Representation of commands ***/ -- 2.39.2