From: Martin Mares Date: Fri, 6 Apr 2018 19:08:19 +0000 (+0200) Subject: Cropmarks X-Git-Tag: v0.1~16 X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=8af87b82854c47567506a6250cb8cd7d19e4b2f4;p=paperjam.git Cropmarks --- diff --git a/TODO b/TODO index a1ce839..b43e598 100644 --- a/TODO +++ b/TODO @@ -2,6 +2,8 @@ - What if an input page specifies /Rotate? - Better error messages from instantiation - "-f" switch +- Help +- Cropmarks: settable colors | # Position bbox on a new paper | paper("a4") @@ -35,7 +37,7 @@ cropmarks mark=in # In-pointing half-crosses pen=1pt # Line width len=5mm # Cross arm length - dist=5mm # Distance from border + offset=5mm # Distance from border | book | signature= @@ -72,7 +74,7 @@ duplex | rotate=1 # Override rotation decision | scale=1 # Override scaling factor | hspace / vspace / space # Distance between tiles - + cropmarks? + cmark, cpen, clen, coffset # Cropmark parameters # Send pages to multiple pipes and merge their results mix { ..., ..., ...} diff --git a/cmds.cc b/cmds.cc index aa68e2e..67b3736 100644 --- a/cmds.cc +++ b/cmds.cc @@ -227,6 +227,130 @@ public: { "t" sx, AT_DIMEN }, \ { "b" sx, AT_DIMEN } +// Cropmarks + +class cropmark_spec { + enum mark_type { + MARK_NONE, + MARK_BOX, + MARK_CROSS, + MARK_OUT, + MARK_IN, + } type; + double pen_width; + double arm_length; + double offset; + string crop_cross(double x, double y, uint mask); + QPDFObjectHandle egstate; +public: + cropmark_spec(cmd *c, string prefix="", string def_type="cross") + { + string t = c->arg(prefix + "mark")->as_string(def_type); + if (t == "none") + type = MARK_NONE; + else if (t == "box") + type = MARK_BOX; + else if (t == "cross") + type = MARK_CROSS; + else if (t == "in") + type = MARK_IN; + else if (t == "out") + type = MARK_OUT; + else + die("Invalid cropmark type %s", t.c_str()); + + pen_width = c->arg(prefix + "pen")->as_double(1); + arm_length = c->arg(prefix + "len")->as_double(5*mm); + offset = c->arg(prefix + "offset")->as_double(0); + egstate = QPDFObjectHandle::newNull(); + } + string pdf_stream(out_context *out, BBox &box, pdf_matrix &xform); +}; + +string cropmark_spec::crop_cross(double x, double y, uint mask) +{ + string s = ""; + + for (uint i=0; i<4; i++) + if (mask & (1U << i)) + { + double x2 = x, y2 = y; + switch (i) + { + case 3: x2 -= arm_length; break; + case 2: x2 += arm_length; break; + case 1: y2 += arm_length; break; + case 0: y2 -= arm_length; break; + } + s += pdf_coord(x) + " " + pdf_coord(y) + " m " + pdf_coord(x2) + " " + pdf_coord(y2) + " l S "; + } + + return s; +} + +string cropmark_spec::pdf_stream(out_context *out, BBox &box, pdf_matrix &xform) +{ + string s = "q "; + s += "0 0 0 RG "; + s += xform.to_string() + " cm "; + + if (egstate.isNull()) + { + auto egs = QPDFObjectHandle::newDictionary(); + egs.replaceKey("/Type", QPDFObjectHandle::newName("/ExtGState")); + egs.replaceKey("/LW", QPDFObjectHandle::newReal(pen_width, 1)); + egs.replaceKey("/LC", QPDFObjectHandle::newInteger(2)); + egs.replaceKey("/LJ", QPDFObjectHandle::newInteger(0)); + egstate = out->pdf->makeIndirectObject(egs); + } + + string egs_res = out->new_resource("GS"); + 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; + + switch (type) + { + case MARK_NONE: + break; + case MARK_BOX: + s += b.to_rect() + " re S "; + break; + case MARK_CROSS: + s += crop_cross(b.x_min, b.y_min, 0b1111); + s += crop_cross(b.x_max, b.y_min, 0b1111); + s += crop_cross(b.x_max, b.y_max, 0b1111); + s += crop_cross(b.x_min, b.y_max, 0b1111); + break; + case MARK_IN: + s += crop_cross(b.x_min, b.y_min, 0b0110); + s += crop_cross(b.x_max, b.y_min, 0b1010); + s += crop_cross(b.x_max, b.y_max, 0b1001); + s += crop_cross(b.x_min, b.y_max, 0b0101); + break; + case MARK_OUT: + s += crop_cross(b.x_min, b.y_min, 0b1001); + s += crop_cross(b.x_max, b.y_min, 0b0101); + s += crop_cross(b.x_max, b.y_max, 0b0110); + s += crop_cross(b.x_min, b.y_max, 0b1010); + break; + } + + s += "Q "; + return s; +} + +#define CROPMARK_ARGS(px) \ + { px "mark", AT_STRING }, \ + { px "pen", AT_DIMEN }, \ + { px "len", AT_DIMEN }, \ + { px "offset",AT_DIMEN } + // Scaling preserving aspect ratio double scale_to_fit(BBox &from, BBox &to) @@ -530,12 +654,6 @@ static const arg_def modulo_args[] = { /*** draw-bbox ***/ -class draw_bbox_cmd : public cmd_exec { -public: - draw_bbox_cmd(cmd *c UNUSED) { } - vector process(vector &pages) override; -}; - class draw_bbox_page : public page { page *orig_page; public: @@ -543,6 +661,15 @@ public: draw_bbox_page(page *p) : page(p) { orig_page = p; } }; +class draw_bbox_cmd : public cmd_exec_simple { +public: + draw_bbox_cmd(cmd *c UNUSED) { } + page *process_page(page *p) override + { + return new draw_bbox_page(p); + } +}; + void draw_bbox_page::render(out_context *out, pdf_matrix xform) { orig_page->render(out, xform); @@ -554,14 +681,6 @@ void draw_bbox_page::render(out_context *out, pdf_matrix xform) "Q "; } -vector draw_bbox_cmd::process(vector &pages) -{ - vector out; - for (auto p: pages) - out.push_back(new draw_bbox_page(p)); - return out; -} - /*** merge ***/ class merge_cmd : public cmd_exec { @@ -1158,6 +1277,36 @@ static const arg_def nup_args[] = { { NULL, 0 } }; +/*** cropmarks ***/ + +class cropmarks_page : public page { + page *orig_page; + cropmark_spec *cm; +public: + void render(out_context *out, pdf_matrix xform) override + { + orig_page->render(out, xform); + out->contents += cm->pdf_stream(out, image_box, xform); + } + cropmarks_page(page *p, cropmark_spec *cms) : page(p), orig_page(p), cm(cms) { } +}; + +class cropmarks_cmd : public cmd_exec_simple { + cropmark_spec cm; + page *process_page(page *p) override + { + return new cropmarks_page(p, &cm); + } + +public: + cropmarks_cmd(cmd *c) : cm(c) { } +}; + +static const arg_def cropmarks_args[] = { + CROPMARK_ARGS(""), + { NULL, 0 } +}; + /*** Command table ***/ template cmd_exec *ctor(cmd *c) { return new T(c); } @@ -1181,5 +1330,6 @@ const cmd_def cmd_table[] = { { "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 } }; diff --git a/jam.h b/jam.h index e7e2099..10f547a 100644 --- a/jam.h +++ b/jam.h @@ -62,8 +62,10 @@ public: extern arg_val null_arg; struct out_context { + QPDF *pdf; QPDFObjectHandle resources; QPDFObjectHandle xobjects; + QPDFObjectHandle egstates; string contents; int res_cnt; string new_resource(const string type); @@ -71,7 +73,7 @@ struct out_context { }; struct page { - int index; + int index; // Position in the source PDF, 0 for synthesized pages double width; // Physical dimensions of media double height; BBox image_box; // Bounds useful contents diff --git a/paperjam.cc b/paperjam.cc index 284cf5f..5aa4c61 100644 --- a/paperjam.cc +++ b/paperjam.cc @@ -79,7 +79,7 @@ Options:\n\ -b, --bbox Recalculate bounding boxes\n\ -d, --debug Show debugging messages\n\ \n\ -Commands: (FIXME)\n\ +Commands:\n\ "); parser_help(); } diff --git a/pdf-tools.cc b/pdf-tools.cc index 29e79bf..2077969 100644 --- a/pdf-tools.cc +++ b/pdf-tools.cc @@ -22,9 +22,7 @@ string pdf_matrix::to_string() { for (int i=0; i<6; i++) { if (i) s += " "; - char buf[16]; - snprintf(buf, sizeof(buf), "%.3f", m[i]); - s += buf; + s += pdf_coord(m[i], 0); } return s; } @@ -43,9 +41,11 @@ QPDFObjectHandle BBox::to_array() string BBox::to_rect() { - char buf[64]; - snprintf(buf, sizeof(buf), "%.1f %.1f %.1f %.1f", x_min, y_min, width(), height()); - return string(buf); + return + pdf_coord(x_min) + " " + + pdf_coord(y_min) + " " + + pdf_coord(width()) + " " + + pdf_coord(height()); } bool BBox::parse(QPDFObjectHandle h) @@ -193,3 +193,12 @@ QPDFObjectHandle page_to_xobject(QPDF *out, QPDFObjectHandle page) xo_stream.replaceStreamData(ph, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull()); return xo_stream; } + +/*** Formatting of coordinates ***/ + +string pdf_coord(double x, uint digits) +{ + char buf[16]; + snprintf(buf, sizeof(buf), "%.*f", digits, x); + return buf; +} diff --git a/pdf-tools.h b/pdf-tools.h index 132062d..844d4b5 100644 --- a/pdf-tools.h +++ b/pdf-tools.h @@ -138,5 +138,6 @@ private: QPDFObjectHandle unicode_string(std::string s); QPDFObjectHandle page_to_xobject(QPDF *out, QPDFObjectHandle page); +string pdf_coord(double x, uint digits=1); #endif diff --git a/pdf.cc b/pdf.cc index cf281f5..0c9b27b 100644 --- a/pdf.cc +++ b/pdf.cc @@ -62,7 +62,7 @@ void in_page::render(out_context *out, pdf_matrix xform) { // Convert page to xobject if (xobject.isNull()) - xobject = out_pdf.makeIndirectObject( page_to_xobject(&out_pdf, out_pdf.copyForeignObject(pdf_page)) ); + xobject = out->pdf->makeIndirectObject( page_to_xobject(out->pdf, out->pdf->copyForeignObject(pdf_page)) ); string xobj_res = out->new_resource("XO"); out->xobjects.replaceKey(xobj_res, xobject); @@ -125,10 +125,11 @@ void process(list &cmds) for (auto pg: pages) { out_context out; + out.pdf = &out_pdf; out.resources = QPDFObjectHandle::newDictionary(); out.resources.replaceKey("/ProcSet", QPDFObjectHandle::parse("[/PDF]")); out.xobjects = QPDFObjectHandle::newDictionary(); - out.resources.replaceKey("/XObject", out.xobjects); + out.egstates = QPDFObjectHandle::newDictionary(); pg->render(&out, pdf_matrix()); QPDFObjectHandle contents = QPDFObjectHandle::newStream(&out_pdf, out.contents); @@ -140,6 +141,10 @@ void process(list &cmds) // FIXME: // out_page.replaceKey("/CropBox", pg->image_box.to_array()); out_page.replaceKey("/Contents", contents); + if (!out.xobjects.getKeys().empty()) + out.resources.replaceKey("/XObject", out.xobjects); + if (!out.egstates.getKeys().empty()) + out.resources.replaceKey("/ExtGState", out.egstates); out_page.replaceKey("/Resources", out.resources); out_pdf.addPage(out_page, false); }