/*
* PaperJam -- Low-level handling of PDFs
*
- * (c) 2018 Martin Mares <mj@ucw.cz>
+ * (c) 2018--2022 Martin Mares <mj@ucw.cz>
*/
#include <cassert>
#include <cstdlib>
#include <cstdio>
+#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
public:
BBox media_box;
void render(out_context *out, pdf_matrix xform);
+ void debug_dump() { debug("Input page %d", index); }
in_page(QPDFObjectHandle inpg, int idx);
+ int get_rotate();
};
in_page::in_page(QPDFObjectHandle inpg, int idx)
{
// 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);
out->contents += "q " + m.to_string() + " cm " + xobj_res + " Do Q ";
}
+int in_page::get_rotate()
+{
+ QPDFObjectHandle rotate = pdf_page.getKey("/Rotate");
+ if (rotate.isNull())
+ return 0;
+ else if (rotate.isInteger())
+ {
+ long long deg = rotate.getIntValue();
+ if (deg < 0 || deg >= 360 || deg % 90)
+ {
+ warn("Page #%d: /Rotate must be 0, 90, 180 or 270", index);
+ return 0;
+ }
+ else
+ return deg;
+ }
+ else
+ {
+ warn("Page #%d: /Rotate is not an integer", index);
+ return 0;
+ }
+}
+
void debug_pages(vector<page *> &pages)
{
- if (!debug_mode)
+ if (!debug_level)
return;
for (auto pg: pages)
- debug("Page #%d: media[%.3f %.3f] image[%.3f %.3f %.3f %.3f]",
- pg->index,
- pg->width, pg->height,
- pg->image_box.x_min, pg->image_box.y_min, pg->image_box.x_max, pg->image_box.y_max);
+ {
+ debug("Page #%d: media[%.3f %.3f] image[%.3f %.3f %.3f %.3f][%.3f %.3f]",
+ pg->index,
+ pg->width, pg->height,
+ pg->image_box.x_min, pg->image_box.y_min, pg->image_box.x_max, pg->image_box.y_max,
+ pg->image_box.width(), pg->image_box.height());
+ if (debug_level > 2)
+ {
+ debug_indent += 4;
+ pg->debug_dump();
+ debug_indent -= 4;
+ }
+ }
+}
+
+static vector<page *> apply_input_xforms(vector<page *> in_pages)
+{
+ vector<page *> out_pages;
+
+ for (auto pg: in_pages)
+ {
+ in_page * in_pg = dynamic_cast<in_page *>(pg);
+ if (in_pg)
+ {
+ int deg = in_pg->get_rotate();
+ if (deg)
+ pg = new xform_page(pg, "/Rotate", pdf_rotation_matrix(deg, pg->width, pg->height));
+ }
+ out_pages.push_back(pg);
+ }
+
+ return out_pages;
}
vector<page *> run_command_list(list<cmd *> &cmds, vector<page *> &pages)
{
debug("# Executing %s", c->def->name);
debug_indent += 4;
- pages = c->exec->process(pages);
+ try
+ {
+ pages = c->exec->process(pages);
+ }
+ catch (exception &e)
+ {
+ die("Error in %s: %s", c->def->name, e.what());
+ }
debug_indent -= 4;
debug_pages(pages);
}
return pages;
}
+static void make_info_dict()
+{
+ // Create info dictionary if it did not exist yet
+ QPDFObjectHandle trailer = out_pdf.getTrailer();
+ QPDFObjectHandle info = trailer.getKey("/Info");
+ if (info.isNull())
+ {
+ info = QPDFObjectHandle::newDictionary();
+ trailer.replaceKey("/Info", info);
+ }
+ else
+ assert(info.isDictionary());
+
+ info.replaceKey("/Producer", unicode_string("PaperJam"));
+
+ // Copy entries from the source file's info dictionary
+ QPDFObjectHandle orig_trailer = in_pdf.getTrailer();
+ QPDFObjectHandle orig_info = orig_trailer.getKey("/Info");
+ if (!orig_info.isNull())
+ {
+ const string to_copy[] = { "/Title", "/Author", "/Subject", "/Keywords", "/Creator", "/CreationDate" };
+ for (string key: to_copy)
+ info.replaceOrRemoveKey(key, orig_info.getKey(key));
+ }
+}
+
void process(list<cmd *> &cmds)
{
+ debug("### Reading input");
in_pdf.processFile(in_name);
in_pdf.pushInheritedAttributesToPage();
out_pdf.emptyPDF();
if (recalc_bbox)
do_recalc_bbox(pages, in_name);
+ if (!no_auto_transforms)
+ {
+ debug("### Applying input transforms");
+ pages = apply_input_xforms(pages);
+ }
+
+ debug("### Running commands");
pages = run_command_list(cmds, pages);
+ debug("### Writing output");
+ int out_page = 0;
for (auto pg: pages)
{
+ ++out_page;
+ if (debug_level > 1)
+ {
+ debug("Page #%d", out_page);
+ debug_indent += 4;
+ pg->debug_dump();
+ debug_indent -= 4;
+ }
+
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);
// 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);
}
// Produce info dictionary
- QPDFObjectHandle trailer = out_pdf.getTrailer();
- QPDFObjectHandle info = trailer.getKey("/Info");
- if (info.isNull())
- {
- info = QPDFObjectHandle::newDictionary();
- trailer.replaceKey("/Info", info);
- }
- else
- assert(info.isDictionary());
- // FIXME: More meta-data
- info.replaceKey("/Producer", unicode_string("PaperJam"));
+ make_info_dict();
// Write the output file
QPDFWriter writer(out_pdf, out_name);
writer.write();
+ debug("### Done");
}
/*** Re-calculation of bboxes ***/
for (size_t i=0; i<pages.size(); i++)
pages[i]->image_box = bboxes[i];
}
+
+// Transformed page
+
+xform_page::xform_page(page *p, const char *desc, pdf_matrix xf)
+{
+ orig_page = p;
+ index = p->index;
+ description = desc;
+ 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::debug_dump()
+{
+ debug("Transform (%s): [%s]", description, xform.to_string().c_str());
+ orig_page->debug_dump();
+}
+
+void xform_page::render(out_context *out, pdf_matrix parent_xform)
+{
+ orig_page->render(out, xform * parent_xform);
+}