From 58b1c1cb5c6edf24a420842cff177445e669f519 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Sun, 3 Nov 2013 19:15:28 +1030 Subject: [PATCH 1/8] Add PDFWriter class for writing a PDF file with printing options Print options include: - page selection - page scaling - n-up - multiple copies - printing to selected paper sizes --- CMakeLists.txt | 2 + poppler/GfxState.cc | 23 ++ poppler/GfxState.h | 5 + poppler/Makefile.am | 2 + poppler/PDFDoc.cc | 7 +- poppler/PDFDoc.h | 2 +- poppler/PDFWriter.cc | 779 +++++++++++++++++++++++++++++++++++++++++++++++++++ poppler/PDFWriter.h | 148 ++++++++++ poppler/Stream.cc | 16 ++ poppler/Stream.h | 7 + 10 files changed, 987 insertions(+), 4 deletions(-) create mode 100644 poppler/PDFWriter.cc create mode 100644 poppler/PDFWriter.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6814d01..0daef37 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -316,6 +316,7 @@ set(poppler_SRCS poppler/PDFDoc.cc poppler/PDFDocEncoding.cc poppler/PDFDocFactory.cc + poppler/PDFWriter.cc poppler/PopplerCache.cc poppler/ProfileData.cc poppler/PreScanOutputDev.cc @@ -476,6 +477,7 @@ if(ENABLE_XPDF_HEADERS) poppler/PDFDocBuilder.h poppler/PDFDocEncoding.h poppler/PDFDocFactory.h + poppler/PDFWriter.h poppler/PopplerCache.h poppler/ProfileData.h poppler/PreScanOutputDev.h diff --git a/poppler/GfxState.cc b/poppler/GfxState.cc index 8a53ee4..24a71ce 100644 --- a/poppler/GfxState.cc +++ b/poppler/GfxState.cc @@ -78,6 +78,22 @@ GBool Matrix::invertTo(Matrix *other) const return gTrue; } +void Matrix::translate(double tx, double ty) +{ + double x0 = tx*m[0] + ty*m[1] + m[4]; + double y0 = tx*m[2] + ty*m[3] + m[5]; + m[4] = x0; + m[5] = y0; +} + +void Matrix::scale(double sx, double sy) +{ + m[0] *= sx; + m[1] *= sx; + m[2] *= sy; + m[3] *= sy; +} + void Matrix::transform(double x, double y, double *tx, double *ty) const { double temp_x, temp_y; @@ -89,6 +105,13 @@ void Matrix::transform(double x, double y, double *tx, double *ty) const *ty = temp_y; } +GBool Matrix::isIdentity() +{ + return (m[0] == 1 && m[1] == 0 && + m[2] == 0 && m[3] == 1 && + m[4] == 0 && m[5] == 0); +} + // Matrix norm, taken from _cairo_matrix_transformed_circle_major_axis double Matrix::norm() const { diff --git a/poppler/GfxState.h b/poppler/GfxState.h index 106b2c0..2116436 100644 --- a/poppler/GfxState.h +++ b/poppler/GfxState.h @@ -58,8 +58,13 @@ class Matrix { public: double m[6]; + void init(double xx, double yx, double xy, double yy, double x0, double y0) { + m[0] = xx; m[1] = yx; m[2] = xy; m[3] = yy; m[4] = x0; m[5] = y0; } GBool invertTo(Matrix *other) const; + void translate(double tx, double ty); + void scale(double sx, double sy); void transform(double x, double y, double *tx, double *ty) const; + GBool isIdentity(); double determinant() const { return m[0] * m[3] - m[1] * m[2]; } double norm() const; }; diff --git a/poppler/Makefile.am b/poppler/Makefile.am index 9f90c9d..6386c0e 100644 --- a/poppler/Makefile.am +++ b/poppler/Makefile.am @@ -207,6 +207,7 @@ poppler_include_HEADERS = \ PDFDocBuilder.h \ PDFDocEncoding.h \ PDFDocFactory.h \ + PDFWriter.h \ PopplerCache.h \ ProfileData.h \ PreScanOutputDev.h \ @@ -285,6 +286,7 @@ libpoppler_la_SOURCES = \ Page.cc \ PageTransition.cc \ Parser.cc \ + PDFWriter.cc \ PDFDoc.cc \ PDFDocEncoding.cc \ PDFDocFactory.cc \ diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc index c78d5ca..835dbb4 100644 --- a/poppler/PDFDoc.cc +++ b/poppler/PDFDoc.cc @@ -1548,7 +1548,7 @@ void PDFDoc::replacePageDict(int pageNo, int rotate, page.free(); } -void PDFDoc::markPageObjects(Dict *pageDict, XRef *xRef, XRef *countRef, Guint numOffset) +void PDFDoc::markPageObjects(Dict *pageDict, XRef *xRef, XRef *countRef, Guint numOffset, GBool markContent) { pageDict->remove("Names"); pageDict->remove("OpenAction"); @@ -1559,8 +1559,9 @@ void PDFDoc::markPageObjects(Dict *pageDict, XRef *xRef, XRef *countRef, Guint n const char *key = pageDict->getKey(n); Object value; pageDict->getValNF(n, &value); if (strcmp(key, "Parent") != 0 && - strcmp(key, "Pages") != 0 && - strcmp(key, "Root") != 0) { + strcmp(key, "Pages") != 0 && + strcmp(key, "Root") != 0 && + !(markContent == gFalse && strcmp(key, "Content") == 0)) { markObject(&value, xRef, countRef, numOffset); } value.free(); diff --git a/poppler/PDFDoc.h b/poppler/PDFDoc.h index 48189bc..2d62d02 100644 --- a/poppler/PDFDoc.h +++ b/poppler/PDFDoc.h @@ -247,7 +247,7 @@ public: // rewrite pageDict with MediaBox, CropBox and new page CTM void replacePageDict(int pageNo, int rotate, PDFRectangle *mediaBox, PDFRectangle *cropBox, Object *pageCTM); - void markPageObjects(Dict *pageDict, XRef *xRef, XRef *countRef, Guint numOffset); + void markPageObjects(Dict *pageDict, XRef *xRef, XRef *countRef, Guint numOffset, GBool markContent = gTrue); // write all objects used by pageDict to outStr Guint writePageObjects(OutStream *outStr, XRef *xRef, Guint numOffset, GBool combine = gFalse); static void writeObject (Object *obj, OutStream* outStr, XRef *xref, Guint numOffset, Guchar *fileKey, diff --git a/poppler/PDFWriter.cc b/poppler/PDFWriter.cc new file mode 100644 index 0000000..13e67cc --- /dev/null +++ b/poppler/PDFWriter.cc @@ -0,0 +1,779 @@ +//======================================================================== +// +// PDFWriter.cc +// +// This file is licensed under the GPLv2 or later +// +// Copyright (C) 2013 Adrian Johnson +// +// To see a description of the changes please see the Changelog file that +// came with your tarball or type make ChangeLog if you are building from git +// +//======================================================================== + +#include "PDFWriter.h" +#include + +PDFWriter::PDFWriter(PDFDoc *docA) +{ + doc = docA; + copies = 1; + collate = gTrue; + reverse = gFalse; + pageSet = ALL; + numberUp = 1; + numberUpOrder = LR_TB; + pageScale = 1.0; + resize = NONE; + orientation = PORTRAIT; + autoRotate = gTrue; + nextObject = 0; +} + +PDFWriter::~PDFWriter() +{ +} + +void PDFWriter::addPaperSize(double width, double height, + double topMargin, double bottomMargin, + double leftMargin, double rightMargin) +{ + PaperSize paperSize; + if (height > width) { + paperSize.width = width; + paperSize.height = height; + paperSize.top = topMargin; + paperSize.bottom = bottomMargin; + paperSize.left = leftMargin; + paperSize.right = rightMargin; + } else { + paperSize.width = height; + paperSize.height = width; + paperSize.top = rightMargin; + paperSize.bottom = leftMargin; + paperSize.left = topMargin; + paperSize.right = bottomMargin; + } + paperSizes.push_back(paperSize); +} + +void PDFWriter::addPage(int page) +{ + pages.push_back(page); +} + +struct PageScale { + int paper; + double scale; + bool operator<(const PageScale &b) const { return scale < b.scale; } +}; + +// Return the size and margins of the closest matching paper for the specified page +// The width/height and margins will take into account the page orientation based on +// the orientation and autoRotate parameters +void PDFWriter::getPaperSize(Page *page, PDFRectangle *mediaSize, PDFRectangle *margins) +{ + double width = page->getMediaWidth(); + double height = page->getMediaHeight(); + + Orientation orient = orientation; + if (autoRotate) { + if (height > width) + orient = PORTRAIT; + else + orient = LANDSCAPE; + } + mediaSize->x1 = 0; + mediaSize->y1 = 0; + + if (paperSizes.size() == 0) { + // No paper sizes specified. Use PDF page size as the paper size. + mediaSize->x2 = width; + mediaSize->y2 = height; + margins->x1 = 0; + margins->x2 = width; + margins->y1 = 0; + margins->y2 = height; + return; + } + + width *= pageScale; + height *= pageScale; + + if (width > height) + std::swap(width, height); + + // find smallest paper size that will fit this page + std::priority_queue queue; + for (int i = 0; i < (int)paperSizes.size(); i++) { + double scale = std::max(width/paperSizes[i].width, + height/paperSizes[i].height); + PageScale ps; + ps.paper = i; + ps.scale = scale; + queue.push(ps); + } + + // find paper with largest scale <= 1.0. if not found the last paper + // on the queue is the largest size available. + PageScale ps; + while (!queue.empty()) { + ps = queue.top(); + if (ps.scale <= 1.0) + break; + queue.pop(); + } + + if (orient == PORTRAIT) { + mediaSize->x2 = paperSizes[ps.paper].width; + mediaSize->y2 = paperSizes[ps.paper].height; + margins->x1 = paperSizes[ps.paper].left; + margins->x2 = mediaSize->x2 - paperSizes[ps.paper].right; + margins->y1 = paperSizes[ps.paper].bottom; + margins->y2 = mediaSize->y2 - paperSizes[ps.paper].top; + } else { + mediaSize->x2 = paperSizes[ps.paper].height; + mediaSize->y2 = paperSizes[ps.paper].width; + margins->x1 = paperSizes[ps.paper].bottom; + margins->x2 = mediaSize->y2 - paperSizes[ps.paper].top; + margins->y1 = paperSizes[ps.paper].left; + margins->y2 = mediaSize->x2 - paperSizes[ps.paper].right; + } +} + +// Get the paper size and margins for Nup printing. This will be the first +// entry in paperSizes (there should only be one). If paperSizes is empty +// use A4 with no margins. +void PDFWriter::getNupPaperSize(PDFRectangle *mediaSize, PDFRectangle *margins) +{ + const double A4Width = 595; + const double A4Height = 842; + + Orientation orient = orientation; + if (nupRotateSheet()) + orient = (orient == PORTRAIT) ? LANDSCAPE : PORTRAIT; + + mediaSize->x1 = 0; + mediaSize->y1 = 0; + + if (paperSizes.size() == 0) { + // No paper sizes specified. Use A4. + if (orient == PORTRAIT) { + mediaSize->x2 = A4Width; + mediaSize->y2 = A4Height; + margins->x1 = 0; + margins->x2 = A4Width; + margins->y1 = 0; + margins->y2 = A4Height; + } else { + mediaSize->x2 = A4Height; + mediaSize->y2 = A4Width; + margins->x1 = 0; + margins->x2 = A4Height; + margins->y1 = 0; + margins->y2 = A4Width; + } + } else { + if (orient == PORTRAIT) { + mediaSize->x2 = paperSizes[0].width; + mediaSize->y2 = paperSizes[0].height; + margins->x1 = paperSizes[0].left; + margins->x2 = mediaSize->x2 - paperSizes[0].right; + margins->y1 = paperSizes[0].bottom; + margins->y2 = mediaSize->y2 - paperSizes[0].top; + } else { + mediaSize->x2 = paperSizes[0].height; + mediaSize->y2 = paperSizes[0].width; + margins->x1 = paperSizes[0].bottom; + margins->x2 = mediaSize->y2 - paperSizes[0].top; + margins->y1 = paperSizes[0].left; + margins->y2 = mediaSize->x2 - paperSizes[0].right; + } + } +} + +// return the ctm that implements the scale, resize, and center parameters +void PDFWriter::getPageCTM(Page *page, PDFRectangle *mediaSize, PDFRectangle *margins, Matrix *ctm) +{ + double width = page->getMediaWidth(); + double height = page->getMediaHeight(); + double paperWidth = mediaSize->x2; + double paperHeight = mediaSize->y2; + double left = margins->x1; + double bottom = margins->y1; + double right = paperWidth - margins->x2; + double top = paperHeight - margins->y2; + + ctm->init(1, 0, 0, 1, 0, 0); + if (resize == NONE) { + if (center) + ctm->translate((paperWidth - width * pageScale) / 2, (paperHeight - height * pageScale) / 2); + ctm->scale(pageScale, pageScale); + } else { + double x_scale = (paperWidth - left - right) / (width * pageScale); + double y_scale = (paperHeight - top - bottom) / (height * pageScale); + double scale = std::min(x_scale, y_scale); + + if (resize == FIT || scale < 1.0) + scale = pageScale * scale; + else + scale = pageScale; + + if (center) { + double left_right_sides, top_bottom_sides; + + ctm->translate((paperWidth - scale * width) / 2, (paperHeight - scale * height) / 2); + + /* Ensure document page is within the margins. The + * scale guarantees the document will fit in the + * margins so we just need to check each side and + * if it overhangs the margin, translate it to the + * margin. */ + left_right_sides = (paperWidth - width*scale)/2; + top_bottom_sides = (paperHeight - height*scale)/2; + if (left_right_sides < left) + ctm->translate(left - left_right_sides, 0); + + if (left_right_sides < right) + ctm->translate(-(right - left_right_sides), 0); + + if (top_bottom_sides < top) + ctm->translate(0, top - top_bottom_sides); + + if (top_bottom_sides < bottom) + ctm->translate(0, -(bottom - top_bottom_sides)); + } else { + ctm->translate(left, top); + } + ctm->scale(scale, scale); + } +} + +// Does the output paper need to be rotated 90 degrees for this nup number? +GBool PDFWriter::nupRotateSheet() +{ + switch (numberUp) { + default: + case 1: + return gFalse; + case 2: + return gTrue; + case 4: + return gFalse; + case 6: + return gTrue; + case 9: + return gFalse; + case 16: + return gFalse; + } +} + +// return the ctm for drawing a nup page +void PDFWriter::getNupCTM(int nupPage, PDFRectangle *mediaSize, PDFRectangle *margins, Matrix *ctm) +{ + int rows, cols; + double sheetWidth = margins->x2 - margins->x1; + double sheetHeight = margins->y2 - margins->y1; + double pageWidth = mediaSize->x2; + double pageHeight = mediaSize->y2; + double w, h; + int x, y; + + if (nupRotateSheet()) + std::swap(pageWidth, pageHeight); + + switch (numberUp) { + default: + case 1: + rows = 1; cols = 1; break; + case 2: + rows = 1; cols = 2; break; + case 4: + rows = 2; cols = 2; break; + case 6: + rows = 2; cols = 3; break; + case 9: + rows = 3; cols = 3; break; + case 16: + rows = 4; cols = 4; break; + } + if (orientation == LANDSCAPE) + std::swap(rows, cols); + + switch (numberUpOrder) { + case LR_TB: + x = nupPage % cols; + y = (numberUp - nupPage - 1) / cols; + break; + case LR_BT: + x = nupPage % cols; + y = nupPage / cols; + break; + case RL_TB: + x = (numberUp - nupPage - 1) % cols; + y = (numberUp - nupPage - 1) / cols; + break; + case RL_BT: + x = (numberUp - nupPage - 1) % cols; + y = nupPage / cols; + break; + case TB_LR: + x = nupPage /cols; + y = (numberUp - nupPage - 1) % cols; + break; + case TB_RL: + x = (numberUp - nupPage - 1) /cols; + y = (numberUp - nupPage - 1) % cols; + break; + case BT_LR: + x = nupPage /cols; + y = nupPage % cols; + break; + case BT_RL: + x = (numberUp - nupPage - 1) /cols; + y = nupPage % cols; + break; + } + + w = sheetWidth / cols; + h = sheetHeight / rows; + double scale = std::min(w/pageWidth, h/pageHeight); + ctm->init(1, 0, 0, 1, 0, 0); + ctm->translate(margins->x1, margins->y1); + ctm->translate(x * w, y * h); + ctm->scale(scale, scale); +} + +void PDFWriter::writeObject(Object *obj) +{ + PDFDoc::writeObject(obj, outputStr, yRef, 0, + NULL, cryptRC4, 0, + 0, 0); +} + +Ref PDFWriter::createRef() +{ + assert (nextObject > 0); + Ref ref; + ref.num = nextObject++; + ref.gen = 0; + return ref; +} + +void PDFWriter::beginIndirectObject(Ref *ref) +{ + Goffset offset = outputStr->getPos(); + yRef->add(ref->num, ref->gen, offset, gTrue); + outputStr->printf("%d %d obj\n", ref->num, ref->gen); +} + +// Write page object (for the n-up == 1 case). A stream containing the +// new CTM (if required) is prepended to the Contents and an updated +// MediaBox and Parent is written. The other *Box keys are removed as +// the content may be resized. +// Return the media size +void PDFWriter::writePageObject(int pageNum, int copy, PDFRectangle *mediaSize) +{ + PDFRectangle margins; + Matrix ctm; + Object ctmObj; + Ref ctmRef; + static const char *pageKeysToExclude[] = { + "Type", + "Parent", + "MediaBox", + "CropBox", + "ArtBox", + "BleedBox", + "TrimBox", + "Contents", + NULL + }; + + Page *page = doc->getCatalog()->getPage(pageNum); + getPaperSize(page, mediaSize, &margins); + getPageCTM(page, mediaSize, &margins, &ctm); + GBool ctmRequired = !ctm.isIdentity(); + if (ctmRequired) { + GooString *s = GooString::format("{0:.10g} {1:.10g} {2:.10g} {3:.10g} {4:.10g} {5:.10g} cm", + ctm.m[0], ctm.m[1], ctm.m[2], ctm.m[3], ctm.m[4], ctm.m[5]); + ctmRef = createRef(); + beginIndirectObject(&ctmRef); + outputStr->printf("<< /Length %d >>\n", s->getLength()); + outputStr->printf("stream\n"); + outputStr->printf("%s\n", s->getCString()); + outputStr->printf("endstream\n"); + outputStr->printf("endobj\n"); + delete s; + } + + Ref oldPageRef = page->getRef(); + Object pageObj; + doc->getXRef()->fetch(oldPageRef.num, oldPageRef.gen, &pageObj); + Dict *pageDict = pageObj.getDict(); + Ref pageRef = createRef(); + pageRefs[copy].push_back(pageRef); + beginIndirectObject(&pageRef); + outputStr->printf("<< /Type /Page\n"); + outputStr->printf("/Parent %d %d R\n", parentRef.num, parentRef.gen); + outputStr->format("/MediaBox [ 0 0 {0:.10g} {1:.10g} ]\n", mediaSize->x2, mediaSize->y2); + outputStr->printf("/Contents [ "); + if (ctmRequired) { + outputStr->printf("%d %d R ", ctmRef.num, ctmRef.gen); + } + Object contentsObj; + pageDict->lookupNF("Contents", &contentsObj); + if (contentsObj.isArray()) { + for (int i = 0; i < contentsObj.arrayGetLength(); i++) { + Object obj2; + contentsObj.arrayGetNF(i, &obj2); + writeObject(&obj2); + obj2.free(); + } + } else { + writeObject(&contentsObj); + } + outputStr->printf(" ]\n"); + + for (int i = 0; i < pageDict->getLength(); i++) { + GooString keyName(pageDict->getKey(i)); + GBool outputKey = gTrue; + const char **excludeKey = pageKeysToExclude; + while (*excludeKey) { + if (keyName.cmp(*excludeKey) == 0) { + outputKey = gFalse; + break; + } + excludeKey++; + } + if (outputKey) { + outputStr->printf("/%s ", keyName.getCString()); + Object obj1; + writeObject(pageDict->getValNF(i, &obj1)); + outputStr->printf("\n"); + obj1.free(); + } + } + outputStr->printf(">>\n"); + outputStr->printf("endobj\n"); +} + +void PDFWriter::writeStream(Stream* str) +{ + str->reset(); + int c; + while ((c = str->getChar()) != EOF) + outputStr->put(c); +} + +// Convert a Page object to an XObject (for the n-up > 1 case). The +// XObject BBox is copied from the Page MediaBox. The XObject Matrix +// incorporates the Page Rotate value. The XObject Resources and +// Group values are copied from the Page. As XObjects cannot split the +// content into multiple streams, all the Page content streams are +// concatenated together into a single XObject stream. +void PDFWriter::writeXObject(int pageNum) +{ + Page *page = doc->getCatalog()->getPage(pageNum); + Ref *refPage = doc->getCatalog()->getPageRef(pageNum); + Object pageObj; + doc->getXRef()->fetch(refPage->num, refPage->gen, &pageObj); + Dict *pageDict = pageObj.getDict(); + Object obj; + + Ref xobjectRef = createRef(); + Ref lengthRef = createRef(); + xobjectRefs.push_back(xobjectRef); + beginIndirectObject(&xobjectRef); + outputStr->printf("<< /Type /XObject\n"); + outputStr->printf("/Subtype /Form\n"); + + pageDict->lookup("MediaBox", &obj); + outputStr->printf("/BBox "); + writeObject(&obj); + outputStr->printf("\n"); + + pageDict->lookup("Resources", &obj); + if (!obj.isNull()) { + outputStr->printf("/Resources "); + writeObject(&obj); + outputStr->printf("\n"); + } + + pageDict->lookup("Group", &obj); + if (!obj.isNull()) { + outputStr->printf("/Group "); + writeObject(&obj); + outputStr->printf("\n"); + } + + if (page->getRotate() != 0) { + Matrix mat; + switch (page->getRotate()) { + case 0: + default: + mat.init(1, 0, 0, 1, 0, 0); + break; + case 90: + mat.init(0, -1, 1, 0, 0, page->getMediaWidth()); + break; + case 180: + mat.init(-1, 0, 0, -1, page->getMediaWidth(), page->getMediaHeight()); + break; + case 270: + mat.init(0, 1, -1, 0, page->getMediaHeight(), -page->getMediaWidth()); + } + outputStr->format("/Matrix [ {0:.10g} {1:.10g} {2:.10g} {3:.10g} {4:.10g} {5:.10g} ]\n", + mat.m[0], mat.m[1], mat.m[2], mat.m[3], mat.m[4], mat.m[5]); + } + + outputStr->printf("/Length %d %d R\n", lengthRef.num, lengthRef.gen); + outputStr->printf(">>\n"); + + // concatenate content streams into a single stream + outputStr->printf("stream\n"); + Goffset streamStart = outputStr->getPos(); + page->getContents(&obj); + if (obj.isArray()) { + for (int i = 0; i < obj.arrayGetLength(); ++i) { + Object obj2; + obj.arrayGet(i, &obj2); + if (!obj2.isStream()) { + error(errSyntaxError, -1, "Weird page contents"); + obj2.free(); + break; + } + Stream *str = obj2.getStream(); + writeStream(str); + obj2.free(); + } + } else if (obj.isStream()) { + Stream *str = obj.getStream(); + writeStream(str); + } else { + error(errSyntaxError, -1, "Weird page contents"); + } + Goffset length = outputStr->getPos() - streamStart; + outputStr->printf("\nendstream\n"); + outputStr->printf("endobj\n"); + + beginIndirectObject(&lengthRef); + outputStr->printf("%lld\n", (long long)length); + outputStr->printf("endobj\n"); +} + +// Write a Page object and content stream to draw the XObjects in xobjectRefs. +// Return the media size +void PDFWriter::writeSheetPageObject(int copy, PDFRectangle *mediaSize) +{ + PDFRectangle margins; + Matrix ctm; + + Ref pageRef = createRef(); + pageRefs[copy].push_back(pageRef); + Ref resourcesRef = createRef(); + Ref contentRef = createRef(); + getNupPaperSize(mediaSize, &margins); + + beginIndirectObject(&pageRef); + outputStr->printf("<< /Type /Page\n"); + outputStr->printf("/Parent %d %d R\n", parentRef.num, parentRef.gen); + outputStr->format("/MediaBox [ 0 0 {0:.10g} {1:.10g} ]\n", mediaSize->x2, mediaSize->y2); + outputStr->printf("/Resources %d %d R\n", resourcesRef.num, resourcesRef.gen); + outputStr->printf("/Contents %d %d R\n", contentRef.num, contentRef.gen); + outputStr->printf(">>\n"); + outputStr->printf("endobj\n"); + + beginIndirectObject(&resourcesRef); + outputStr->printf("<< /XObject <<\n"); + for (int i = 0; i < (int)xobjectRefs.size(); i++) + outputStr->printf("/x%d %d %d R\n", i, xobjectRefs[i].num, xobjectRefs[i].gen); + outputStr->printf(">> >>\n"); + outputStr->printf("endobj\n"); + + GooString content; + for (int i = 0; i < (int)xobjectRefs.size(); i++) { + getNupCTM(i, mediaSize, &margins, &ctm); + content.appendf("q {0:.10g} {1:.10g} {2:.10g} {3:.10g} {4:.10g} {5:.10g} cm /x{6:d} Do Q\n", + ctm.m[0], ctm.m[1], ctm.m[2], ctm.m[3], ctm.m[4], ctm.m[5], i); + } + + beginIndirectObject(&contentRef); + outputStr->printf("<< /Length %d >>\n", content.getLength()); + outputStr->printf("stream\n"); + outputStr->printf("%s", content.getCString()); + outputStr->printf("\nendstream\n"); + outputStr->printf("endobj\n"); +} + +void PDFWriter::writeBlankPage(int copy, PDFRectangle *mediaSize) +{ + Ref pageRef = createRef(); + pageRefs[copy].push_back(pageRef); + beginIndirectObject(&pageRef); + outputStr->printf("<< /Type /Page\n"); + outputStr->printf("/Parent %d %d R\n", parentRef.num, parentRef.gen); + outputStr->format("/MediaBox [ 0 0 {0:.10g} {1:.10g} ]\n", mediaSize->x2, mediaSize->y2); + outputStr->printf(">>\n"); + outputStr->printf("endobj\n"); +} + +void PDFWriter::writePageTree() +{ + beginIndirectObject(&catalogRef); + outputStr->printf("<< /Type /Catalog\n"); + outputStr->printf("/Pages %d %d R\n", parentRef.num, parentRef.gen); + outputStr->printf(">>\n"); + outputStr->printf("endobj\n"); + + beginIndirectObject(&parentRef); + outputStr->printf("<< /Type /Pages\n"); + outputStr->printf("/Kids ["); + if (collate) { + for (int cp = 0; cp < copies; cp++) { + if (reverse) { + for (int pg = (int)pageRefs[0].size() - 1; pg >= 0; pg--) + outputStr->printf(" %d %d R", pageRefs[cp][pg].num, pageRefs[cp][pg].gen); + } else { + for (int pg = 0; pg < (int)pageRefs[0].size(); pg++) + outputStr->printf(" %d %d R", pageRefs[cp][pg].num, pageRefs[cp][pg].gen); + } + } + } else { + if (duplex) { + assert(pageRefs[0].size() % 2 == 0); + if (reverse) { + for (int pg = (int)pageRefs[0].size() - 2; pg >= 0; pg -= 2) { + for (int cp = 0; cp < copies; cp++) { + outputStr->printf(" %d %d R", pageRefs[cp][pg].num, pageRefs[cp][pg].gen); + outputStr->printf(" %d %d R", pageRefs[cp][pg+1].num, pageRefs[cp][pg+1].gen); + } + } + } else { + for (int pg = 0; pg < (int)pageRefs[0].size(); pg += 2) + for (int cp = 0; cp < copies; cp++) { + outputStr->printf(" %d %d R", pageRefs[cp][pg].num, pageRefs[cp][pg].gen); + outputStr->printf(" %d %d R", pageRefs[cp][pg+1].num, pageRefs[cp][pg+1].gen); + } + } + } else { + if (reverse) { + for (int pg = (int)pageRefs[0].size() - 1; pg >= 0; pg--) { + for (int cp = 0; cp < copies; cp++) + outputStr->printf(" %d %d R", pageRefs[cp][pg].num, pageRefs[cp][pg].gen); + } + } else { + for (int pg = 0; pg < (int)pageRefs[0].size(); pg++) + for (int cp = 0; cp < copies; cp++) + outputStr->printf(" %d %d R", pageRefs[cp][pg].num, pageRefs[cp][pg].gen); + } + } + } + outputStr->printf(" ]\n"); + outputStr->printf("/Count %zd >>\n", pageRefs[0].size() * copies); + outputStr->printf("endobj\n"); +} + +void PDFWriter::writeFile(FILE *f) +{ + PDFRectangle mediaSize; + int outputPageNum; + int nupPageNum; // 0..numberUp-1 + int pageNum; + Object pageObj; + + // header + objectsCount = 0; + outputStr = new FileOutStream(f,0); + yRef = new XRef(); + countRef = new XRef(); + yRef->add(0, 65535, 0, gFalse); + PDFDoc::writeHeader(outputStr, doc->getPDFMajorVersion(), doc->getPDFMinorVersion()); + + // Mark all page objects. when printing n-up the content streams + // are not marked as they will be included in the XObjects created for + // each page. + outputPageNum = 1; + nupPageNum = 0; + for (int i = 0; i < (int)pages.size(); i++) { + pageNum = pages[i]; + if (pageSet == ALL || + (pageSet == ODD && outputPageNum % 2 == 1) || + (pageSet == EVEN && outputPageNum % 2 == 0)) { + Object pageObj; + Ref *refPage = doc->getCatalog()->getPageRef(pageNum); + doc->getXRef()->fetch(refPage->num, refPage->gen, &pageObj); + Dict *pageDict = pageObj.getDict(); + doc->markPageObjects(pageDict, yRef, countRef, 0, numberUp > 1 ? gFalse : gTrue); + } + nupPageNum++; + if (nupPageNum == numberUp) { + nupPageNum = 0; + outputPageNum++; + } + } + // write marked page objects + doc->writePageObjects(outputStr, yRef, 0, gTrue); + nextObject = yRef->getNumObjects() + 1; + + // Write a Page object for each page (nup == 1) or + // Xobject for each page (nup > 1) + a Page object for each sheet. + // Note: acroread will not display all pages when more than one page uses + // the same page object. So when printing multiple copies output a new Page object + // for each copy of the page. + parentRef = createRef(); + catalogRef = createRef(); + for (int i = 0; i < copies; i++) { + std::vector refs; + pageRefs.push_back(refs); + } + outputPageNum = 1; + nupPageNum = 0; + for (int i = 0; i < (int)pages.size(); i++) { + pageNum = pages[i]; + if (pageSet == ALL || + (pageSet == ODD && outputPageNum % 2 == 1) || + (pageSet == EVEN && outputPageNum % 2 == 0)) { + if (numberUp == 1) { + for (int cp = 0; cp < copies; cp++) + writePageObject(pageNum, cp, &mediaSize); + } else { + writeXObject(pageNum); + if (nupPageNum == numberUp - 1 || i == (int)pages.size() - 1) { + for (int cp = 0; cp < copies; cp++) + writeSheetPageObject(cp, &mediaSize); + xobjectRefs.clear(); + } + } + } + nupPageNum++; + if (nupPageNum == numberUp) { + nupPageNum = 0; + outputPageNum++; + } + } + // when printing multiple copies in duplex: if there are an odd + // number of pages, add a blank page the same size as the last page + // to ensure the first page of each copy starts on a new sheet. + if (duplex && copies > 1 && (pageRefs[0].size() % 2 == 1)) { + for (int cp = 0; cp < copies; cp++) + writeBlankPage(cp, &mediaSize); + } + + // catalog and page tree + writePageTree(); + + // trailer + Goffset uxrefOffset = outputStr->getPos(); + Dict *trailerDict = PDFDoc::createTrailerDict(objectsCount, gFalse, 0, &catalogRef, yRef, + doc->getFileName()->getCString(), + outputStr->getPos()); + PDFDoc::writeXRefTableTrailer(trailerDict, yRef, gFalse /* do not write unnecessary entries */, + uxrefOffset, outputStr, yRef); + delete trailerDict; + delete yRef; + delete countRef; + + outputStr->close(); +} diff --git a/poppler/PDFWriter.h b/poppler/PDFWriter.h new file mode 100644 index 0000000..6799ab3 --- /dev/null +++ b/poppler/PDFWriter.h @@ -0,0 +1,148 @@ +//======================================================================== +// +// PDFWriter.h +// +// This file is licensed under the GPLv2 or later +// +// Copyright (C) 2013 Adrian Johnson +// +// To see a description of the changes please see the Changelog file that +// came with your tarball or type make ChangeLog if you are building from git +// +//======================================================================== + +#ifndef PDFWRITER_H +#define PDFWRITER_H + +#include "config.h" +#include "poppler-config.h" +#include "GfxState.h" +#include "PDFDoc.h" +#include + +class PDFWriter { +public: + + enum PageSet { ALL, ODD, EVEN }; + + // order to layout multiple pages on a sheet. + // LR_TB = Left to Right then Top to Bottom + // LR_BT = Left to Right then Bottom to Top etc + enum NumberUpOrder { LR_TB, LR_BT, RL_TB, RL_BT, TB_LR, TB_RL, BT_LR, BT_RL }; + enum Orientation { PORTRAIT, LANDSCAPE, REVERSE_PORTRAIT, REVERSE_LANDSCAPE }; + enum Resize { NONE, SHRINK, FIT }; + + PDFWriter(PDFDoc *docA); + ~PDFWriter(); + + // print options + + // Number of copies + void setNumCopies(int copiesA) { copies = copiesA; } + + // If true, the pages of each copy are collated eg 1, 2, 3, 1, 2, 3 otherwise 1, 1, 2, 2, 3, 3. + void setCollate(GBool collateA) { collate = collateA; } + + // Print pages in reverse order. + void setReverse(GBool reverseA) { reverse = reverseA; } + + // Enable duplex. Ensures blank pages are inserted where required + // when printing multiple copies. + void setDuplex(GBool duplexA) { duplex = duplexA; } + + // Print odd, even or all pages. + void setPageSet(PageSet pageSetA) { pageSet = pageSetA; } + + // print multiple pages per sheet. Valid values are 1, 2, 4, 6, 9, or 16. + void setNumberUp(int numberUpA) { numberUp = numberUpA; } + + // Order to layout pages on sheet when printing multiple pages per sheet; + void setNumberUpOrdering(NumberUpOrder order) { numberUpOrder = order; } + + // Add paper size (including margins of printable area). + // If one paper size is added, all pages will be printed to this + // paper size. If more than one paper size is added, each page will + // be printed to the closest matching paper size. + // If printing multiple pages per sheet, only one paper size should be specified. + void addPaperSize(double width, double height, + double topMargin, double bottomMargin, double leftMargin, double rightMargin); + + // Scale all pages by this value + void setScale(double scaleA) { pageScale = scaleA; } + + // Orientation of output pages. Ignored if numberUp = 1 and autoRotate = true + void setOrientation(Orientation orientationA) { orientation = orientationA; }; + + // Resize options. + void setResize(Resize resizeA) { resize = resizeA; } + + // If true set output page orientation to match PDF page orientation + void setAutoRotate(GBool autoRotateA) { autoRotate = autoRotateA; } + + // If true center page within margins of paper + void setCenter(GBool centerA) { center = centerA; } + + void addPage(int page); + + void writeFile(FILE *f); + +private: + int getNextPage(); + void getPaperSize(Page *page, PDFRectangle *mediaSize, PDFRectangle *margins); + void getPageCTM(Page *page, PDFRectangle *mediaSize, PDFRectangle *margins, Matrix *ctm); + GBool nupRotateSheet(); + void getNupPaperSize(PDFRectangle *mediaSize, PDFRectangle *margins); + void getNupCTM(int nupPage, PDFRectangle *mediaSize, PDFRectangle *margins, Matrix *ctm); + void createCTMStream(Matrix *ctm, Object *obj); + + Ref createRef(); + void beginIndirectObject(Ref *ref); + void writeObject(Object *obj); + void writePageObject(int pageNum, int copy, PDFRectangle *mediaSize); + void writeStream(Stream* str); + void writeXObject(int pageNum); + void writeSheetPageObject(int copy, PDFRectangle *mediaSize); + void writeBlankPage(int copy, PDFRectangle *mediaSize); + void writePageTree(); + + struct PaperSize { + double width; + double height; + double top; + double bottom; + double left; + double right; + }; + + PDFDoc *doc; + int nextObject; + int objectsCount; + Ref catalogRef; + Ref parentRef; + Ref blankPageRef; + std::vector< std::vector > pageRefs; + std::vector xobjectRefs; + std::vector ctmObjects; + std::vector ctmStrings; + XRef *yRef, *countRef; + OutStream *outputStr; + + // print params + int copies; + GBool collate; + GBool reverse; + GBool duplex; + std::vector pages; + std::vector paperSizes; + PageSet pageSet; + int numberUp; + NumberUpOrder numberUpOrder; + double pageScale; + Resize resize; + Orientation orientation; + GBool autoRotate; + GBool center; +}; + +#endif + diff --git a/poppler/Stream.cc b/poppler/Stream.cc index 41cb8c1..167e4df 100644 --- a/poppler/Stream.cc +++ b/poppler/Stream.cc @@ -368,6 +368,17 @@ OutStream::~OutStream () { } +void OutStream::format(const char *format, ...) +{ + va_list argptr; + va_start (argptr, format); + GooString *s = GooString::formatv(format, argptr); + write((Guchar*)s->getCString(), s->getLength()); + delete s; + va_end (argptr); +} + + //------------------------------------------------------------------------ // FileOutStream //------------------------------------------------------------------------ @@ -392,6 +403,11 @@ Goffset FileOutStream::getPos () return Gftell(f); } +void FileOutStream::write (const Guchar *data, long length) +{ + fwrite(data, length, 1, f); +} + void FileOutStream::put (char c) { fputc(c,f); diff --git a/poppler/Stream.h b/poppler/Stream.h index 00b2925..c659e2a 100644 --- a/poppler/Stream.h +++ b/poppler/Stream.h @@ -263,11 +263,16 @@ public: // Return position in stream virtual Goffset getPos() = 0; + virtual void write (const Guchar *data, long length) = 0; + // Put a char in the stream virtual void put (char c) = 0; virtual void printf (const char *format, ...) GCC_PRINTF_FORMAT(2,3) = 0; + // GooString formatting + virtual void format (const char *format, ...); + private: int ref; // reference count @@ -286,6 +291,8 @@ public: virtual Goffset getPos(); + virtual void write (const Guchar *data, long length); + virtual void put (char c); virtual void printf (const char *format, ...); -- 1.8.3.2