From: Martin Mares Date: Wed, 4 Apr 2018 20:49:59 +0000 (+0200) Subject: Partial nup X-Git-Tag: v0.1~21 X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=8c3a7baee6b265d8c73df1d709793f032880dbb6;p=paperjam.git Partial nup --- diff --git a/TODO b/TODO index c958ad4..611462b 100644 --- a/TODO +++ b/TODO @@ -64,13 +64,13 @@ duplex # - paper size + margins # - scale + margins nup(hnum, vnum) - by=row/column # Filling order (default: row) + by=rows/cols # Filling order (default: rows) + by=tile # Tile with copies of a single page paper / w / h # Specify paper size, default=copy from 1st image + fit options (*margin, pos) crop # Crop to image rotate=1 # Override rotation decision scale=1 # Override scaling factor - tile # Tile with copies of a single page hspace / vspace / space # Distance between tiles + cropmarks? diff --git a/cmds.cc b/cmds.cc index b12595e..7157a05 100644 --- a/cmds.cc +++ b/cmds.cc @@ -191,6 +191,10 @@ public: t = c->arg("t" + sx)->as_double(v); b = c->arg("b" + sx)->as_double(v); } + bool may_shrink(BBox *bb) + { + return (bb->width() > l+r && bb->height() > t+b); + } void shrink_box(BBox *bb) { bb->x_min += l; @@ -839,6 +843,285 @@ static const arg_def book_args[] = { { NULL, 0 } }; +/*** nup ***/ + +struct nup_state { + int rows, cols; + double tile_w, tile_h; + double paper_w, paper_h; + double fill_factor; + double scale; +}; + +class nup_cmd : public cmd_exec { + // Parameters + int grid_n, grid_m, num_tiles; + enum { + BY_ROWS, + BY_COLS, + BY_TILE, + } fill_by; + bool crop; + int rotate; + double scale; + paper_spec paper; + margin_spec marg; + pos_spec pos; + double hspace, vspace; + + // Processing state + page *process_single(vector &in); + void find_config(vector &in, BBox *page_boxes); + void try_config(nup_state &st); + nup_state best; + bool found_solution; + +public: + nup_cmd(cmd *c) : paper(c), marg(c, "margin", "margin"), pos(c) + { + grid_n = c->arg("n")->as_int(0); + grid_m = c->arg("m")->as_int(0); + if (grid_n > 0 && grid_m > 0) + num_tiles = grid_n * grid_m; + else if (grid_n > 0 && !grid_m) + num_tiles = grid_n; + else + die("Grid size must be at least 1x1"); + + const string by = c->arg("by")->as_string("rows"); + if (by == "rows" || by == "row" || by == "r") + fill_by = BY_ROWS; + else if (by == "cols" || by == "cols" || by == "c") + fill_by = BY_COLS; + else if (by == "tile" || by == "t") + fill_by = BY_TILE; + else + die("Parameter \"by\" must be rows/cols/tile"); + + crop = c->arg("crop")->as_int(0); + rotate = c->arg("rotate")->as_int(-1); + scale = c->arg("scale")->as_double(0); + + double space = c->arg("space")->as_double(0); + hspace = c->arg("hspace")->as_double(space); + 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"); + if (!is_zero(scale) && !grid_m) + die("When nup is used with explicit scaling, both grid sizes must be given"); + } + + vector process(vector &pages) override; +}; + +vector nup_cmd::process(vector &pages) +{ + vector out; + int i = 0; + + while (i < (int) pages.size()) + { + vector in; + if (fill_by == BY_TILE) + { + for (int j=0; jwidth, in[0]->height)); + i++; + } + } + out.push_back(process_single(in)); + } + + return out; +} + +void nup_cmd::try_config(nup_state &st) +{ + BBox window(st.paper_w, st.paper_h); + if (!marg.may_shrink(&window)) + return; + marg.shrink_box(&window); + window.x_max -= (st.cols - 1) * hspace; + window.y_max -= (st.rows - 1) * vspace; + if (window.width() < 0 || window.height() < 0) + return; + + BBox image(st.cols * st.tile_w, st.rows * st.tile_h); + 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 (!found_solution || best.fill_factor < st.fill_factor) + { + found_solution = true; + best = st; + } +} + +void nup_cmd::find_config(vector &in, BBox *page_boxes) +{ + nup_state st; + + // Determine tile size + st.tile_w = st.tile_h = 0; + for (int i=0; ibbox; + else + page_boxes[i] = BBox(in[i]->width, in[i]->height); + st.tile_w = max(st.tile_w, page_boxes[i].width()); + st.tile_h = max(st.tile_h, page_boxes[i].height()); + } + debug("NUP: %d tiles of size %.3f x %.3f", num_tiles, st.tile_w, st.tile_h); + + // Try all possible configurations of tiles + found_solution = false; + if (!is_zero(scale)) + { + // If explicit scaling is requested, we choose page size ourselves, + // so there is just one configuration. + st.rows = grid_n; + st.cols = grid_m; + st.paper_w = marg.l + st.cols*st.tile_w + (st.cols-1)*hspace + marg.r; + st.paper_h = marg.t + st.rows*st.tile_h + (st.rows-1)*vspace + marg.b; + try_config(st); + } + else + { + // Page size is fixed (either given or copied from the source pages), + // but we can have freedom to rotate and/or to choose grid size. + for (int rot=0; rot<=1; rot++) + { + if (rotate >= 0 && rot != rotate) + continue; + + // Establish paper size + if (!is_zero(paper.w)) + { + st.paper_w = paper.w; + st.paper_h = paper.h; + } + else + { + st.paper_w = in[0]->width; + st.paper_h = in[0]->height; + } + if (rot) + swap(st.paper_w, st.paper_h); + + // Try grid sizes + if (grid_m) + { + st.rows = grid_n; + st.cols = grid_m; + try_config(st); + } + else + { + for (int r=1; r<=grid_n; r++) + if (!(grid_n % r)) + { + st.rows = r; + st.cols = grid_n / r; + try_config(st); + } + } + } + } + + if (!found_solution) + die("Nup did not find a feasible solution"); +} + +class nup_page : public page { +public: + vector orig_pages; + vector xforms; + void render(out_context *out, pdf_matrix xform) override; + nup_page(nup_state &st) : page(st.paper_w, st.paper_h) { } +}; + +void nup_page::render(out_context *out, pdf_matrix parent_xform) +{ + for (int i=0; i < (int) orig_pages.size(); i++) + orig_pages[i]->render(out, xforms[i] * parent_xform); +} + +page *nup_cmd::process_single(vector &in) +{ + BBox page_boxes[num_tiles]; + find_config(in, page_boxes); + + // Construct transform from paper to window with tiles + BBox paper_box(best.paper_w, best.paper_h); + marg.shrink_box(&paper_box); + BBox tile_box(best.cols * best.scale * best.tile_w + (best.cols-1) * hspace, + best.rows * best.scale * best.tile_h + (best.rows-1) * vspace); + pdf_matrix place_xform = pos.place(tile_box, paper_box); + + nup_page *p = new nup_page(best); + p->bbox = tile_box; + p->bbox.transform(place_xform); + + for (int i=0; iorig_pages.push_back(in[i]); + p->xforms.push_back(m * place_xform); + } + + return p; +} + +static const arg_def nup_args[] = { + { "n", AT_INT | AT_POSITIONAL | AT_MANDATORY }, + { "m", AT_INT | AT_POSITIONAL }, + { "by", AT_STRING }, + { "crop", AT_SWITCH }, + { "rotate", AT_SWITCH }, + { "scale", AT_DOUBLE }, + PAPER_ARGS, + MARGIN_ARGS1_NAMED("margin"), + MARGIN_ARGS2("margin"), + POS_ARGS, + { "space", AT_DIMEN }, + { "hspace", AT_DIMEN }, + { "vspace", AT_DIMEN }, + { NULL, 0 } +}; + /*** Command table ***/ template cmd_exec *ctor(cmd *c) { return new T(c); } @@ -861,5 +1144,6 @@ const cmd_def cmd_table[] = { { "margins", margins_args, 0, &ctor }, { "add-blank",add_blank_args, 0, &ctor }, { "book", book_args, 0, &ctor }, + { "nup", nup_args, 0, &ctor }, { NULL, NULL, 0, NULL } }; diff --git a/jam.h b/jam.h index 31510d8..0653c25 100644 --- a/jam.h +++ b/jam.h @@ -19,7 +19,7 @@ typedef unsigned int uint; static inline bool is_zero(double z) { - return fabs(z) < 0.001; + return fabs(z) < 1e-6; } static inline bool is_equal(double x, double y)