2 * PaperJam -- Low-level handling of PDFs
4 * (c) 2018 Martin Mares <mj@ucw.cz>
15 #include <qpdf/QPDFWriter.hh>
20 static void do_recalc_bbox(vector<page *> &pages, const char *in_name);
22 string out_context::new_resource(const string type)
24 return "/" + type + to_string(++res_cnt);
27 class in_page : public page {
28 QPDFObjectHandle pdf_page;
29 QPDFObjectHandle xobject;
32 void render(out_context *out, pdf_matrix xform);
33 in_page(QPDFObjectHandle inpg, int idx);
36 in_page::in_page(QPDFObjectHandle inpg, int idx)
39 xobject = QPDFObjectHandle::newNull();
42 media_box = BBox(inpg.getKey("/MediaBox"));
43 width = media_box.width();
44 height = media_box.height();
46 QPDFObjectHandle art_box = inpg.getKey("/ArtBox");
48 art_box = inpg.getKey("/CropBox");
50 image_box = BBox(width, height);
53 image_box = BBox(art_box);
54 image_box.x_min -= media_box.x_min;
55 image_box.x_max -= media_box.x_min;
56 image_box.y_min -= media_box.y_min;
57 image_box.y_max -= media_box.y_min;
61 void in_page::render(out_context *out, pdf_matrix xform)
63 // Convert page to xobject
65 xobject = out->pdf->makeIndirectObject( page_to_xobject(out->pdf, out->pdf->copyForeignObject(pdf_page)) );
66 string xobj_res = out->new_resource("XO");
67 out->xobjects.replaceKey(xobj_res, xobject);
70 m.shift(-media_box.x_min, -media_box.y_min);
73 out->contents += "q " + m.to_string() + " cm " + xobj_res + " Do Q ";
76 void debug_pages(vector<page *> &pages)
82 debug("Page #%d: media[%.3f %.3f] image[%.3f %.3f %.3f %.3f]",
84 pg->width, pg->height,
85 pg->image_box.x_min, pg->image_box.y_min, pg->image_box.x_max, pg->image_box.y_max);
88 vector<page *> run_command_list(list<cmd *> &cmds, vector<page *> &pages)
95 debug("# Executing %s", c->def->name);
99 pages = c->exec->process(pages);
103 die("Error in %s: %s", c->def->name, e.what());
112 static void make_info_dict()
114 // Create info dictionary if it did not exist yet
115 QPDFObjectHandle trailer = out_pdf.getTrailer();
116 QPDFObjectHandle info = trailer.getKey("/Info");
119 info = QPDFObjectHandle::newDictionary();
120 trailer.replaceKey("/Info", info);
123 assert(info.isDictionary());
125 info.replaceKey("/Producer", unicode_string("PaperJam"));
127 // Copy entries from the source file's info dictionary
128 QPDFObjectHandle orig_trailer = in_pdf.getTrailer();
129 QPDFObjectHandle orig_info = orig_trailer.getKey("/Info");
130 if (!orig_info.isNull())
132 const string to_copy[] = { "/Title", "/Author", "/Subject", "/Keywords", "/Creator", "/CreationDate" };
133 for (string key: to_copy)
134 info.replaceOrRemoveKey(key, orig_info.getKey(key));
138 void process(list<cmd *> &cmds)
140 debug("### Reading input");
141 in_pdf.processFile(in_name);
142 in_pdf.pushInheritedAttributesToPage();
145 vector<QPDFObjectHandle> const &in_pages = in_pdf.getAllPages();
146 vector<page *> pages;
148 QPDFObjectHandle page_copy = out_pdf.copyForeignObject(in_pages[0]);
151 for (auto inpg: in_pages)
152 pages.push_back(new in_page(inpg, ++cnt));
155 do_recalc_bbox(pages, in_name);
157 debug("### Running commands");
158 pages = run_command_list(cmds, pages);
160 debug("### Writing output");
165 out.resources = QPDFObjectHandle::newDictionary();
166 out.resources.replaceKey("/ProcSet", QPDFObjectHandle::parse("[/PDF]"));
167 out.xobjects = QPDFObjectHandle::newDictionary();
168 out.egstates = QPDFObjectHandle::newDictionary();
169 pg->render(&out, pdf_matrix());
171 QPDFObjectHandle contents = QPDFObjectHandle::newStream(&out_pdf, out.contents);
173 // Create the page object
174 QPDFObjectHandle out_page = out_pdf.makeIndirectObject(QPDFObjectHandle::newDictionary());
175 out_page.replaceKey("/Type", QPDFObjectHandle::newName("/Page"));
176 out_page.replaceKey("/MediaBox", BBox(pg->width, pg->height).to_array());
178 // out_page.replaceKey("/CropBox", pg->image_box.to_array());
179 out_page.replaceKey("/Contents", contents);
180 if (!out.xobjects.getKeys().empty())
181 out.resources.replaceKey("/XObject", out.xobjects);
182 if (!out.egstates.getKeys().empty())
183 out.resources.replaceKey("/ExtGState", out.egstates);
184 out_page.replaceKey("/Resources", out.resources);
185 out_pdf.addPage(out_page, false);
188 // Produce info dictionary
191 // Write the output file
192 QPDFWriter writer(out_pdf, out_name);
197 /*** Re-calculation of bboxes ***/
199 vector<BBox> gs_bboxes(const char *in)
203 die("Cannot create pipe: %m");
207 die("Cannot fork: %m");
215 execlp("gs", "gs", "-sDEVICE=bbox", "-dSAFER", "-dBATCH", "-dNOPAUSE", "-q", in, NULL);
216 die("Cannot execute gs: %m");
220 FILE *f = fdopen(pipes[0], "r");
222 die("fdopen failed: %m");
226 while (fgets(line, sizeof(line), f))
228 char *eol = strchr(line, '\n');
230 die("Ghostscript produced too long lines");
233 if (!strncmp(line, "%%HiResBoundingBox: ", 20))
235 double x1, y1, x2, y2;
236 if (sscanf(line+20, "%lf%lf%lf%lf", &x1, &y1, &x2, &y2) != 4)
237 die("Cannot parse Ghostscript output: %s", line);
238 bboxes.push_back(BBox(x1, y1, x2, y2));
240 else if (line[0] != '%')
241 fprintf(stderr, "%s\n", line);
246 if (waitpid(pid, &stat, 0) < 0)
247 die("wait failed: %m");
248 if (!WIFEXITED(stat) || WEXITSTATUS(stat))
249 die("Ghostscript failed");
254 static void do_recalc_bbox(vector<page *> &pages, const char *in_name)
256 debug("Calling Ghostscript to re-calculate bounding boxes");
257 vector<BBox> bboxes = gs_bboxes(in_name);
258 if (pages.size() != bboxes.size())
259 die("Ghostscript failed to produce the right number of bboxes");
261 for (size_t i=0; i<pages.size(); i++)
262 pages[i]->image_box = bboxes[i];