From 58b1c1cb5c6edf24a420842cff177445e669f519 Mon Sep 17 00:00:00 2001
From: Adrian Johnson <ajohnson@redneon.com>
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 <ajohnson@redneon.com>
+//
+// 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 <queue>
+
+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<PageScale> 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<Ref> 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 <ajohnson@redneon.com>
+//
+// 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 <vector>
+
+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<Ref> > pageRefs;
+  std::vector<Ref> xobjectRefs;
+  std::vector<Object> ctmObjects;
+  std::vector<char *> ctmStrings;
+  XRef *yRef, *countRef;
+  OutStream *outputStr;
+
+  // print params
+  int copies;
+  GBool collate;
+  GBool reverse;
+  GBool duplex;
+  std::vector<int> pages;
+  std::vector<PaperSize> 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

