Search code examples
javaprintingpdfboxscaling

PDFBOX 2.0: How can I scale large page sizes to letter?


I am using pdfbox 2.0.21 to (physically) print PDF files that may have mixed page sizes, including nonstandard sizes such as 8.7"x11.3" which would print on legal size or larger sheets without scaling. The pdfs are created by many different employees in different ways (Acrobat DC, saving from Word, Print-to-pdf, generated by our document system...) and sent to my team to print and mail. The volume does not allow us to inspect them individually, ultimately we point our app at a folder and it will print everything there. I would prefer to create a PrinterJob or PDFPageable that automatically scaled all pages as needed, but can't get it to work.

public class PDPrn {
  public static void main(String[] args) {
    try (PDDocument pdf = PDDocument.load(new File("foo.pdf"))) {
      PrinterJob job = PrinterJob.getPrinterJob();
      Paper paper = new Paper();
      Paper.setSize(612, 792); // letter size
      paper.setImageableArea(36, 36, paper.getWidth() - 72, paper.getHeight() - 72); // 0.5in margins
      PageFormat format = new PageFormat();
      format.setPaper(paper);
      PDFPageable pageable = new PDFPageable(pdf);

      //doesn't scale down pages
      //printer will ask for legal and cutoff bottom when forced to use letter
      pageable.append(new PDFPrintable(pdf, Scaling.SCALE_TO_FIT), format);

      job.setPageable(pageable);
      try {
        job.print();
      } catch (PrinterException pe) {
      }
      pdf.close();
    } catch (IOException ioe) {
    }
  }
}

Failing that I tried walking each page and directly scaling down anything larger than letter, but that moves the content around on the page in baffling ways.

public class PDPrn {
  public static void main(String[] args) {
    try (PDDocument pdf = PDDocument.load(new File("foo.pdf"))) {
      PrinterJob job = PrinterJob.getPrinterJob();
      PDFPageable pageable = new PDFPageable(pdf);
      PDPageTree tree = pdf.getDocumentCatalog().getPages();
      Iterator<PDPage> iterator = tree.iterator();
      while (iterator.hasNext()) {
        PDPage page = iterator.next();
        if (page.getMediaBox().getWidth() !=612 || page.getMediaBox().getHeight() != 792) {
          PDPageContentStream contentStream = new PDPageContentStream(pdf, page,
            PDPageContentStream.AppendMode.PREPEND, false);

          //these are the wrong scaling factors but they do shrink oversized pages
          //however the shrunk content is moved far down the page and runs off the bottom
          //printer still asks for legal and cuts off bottom when forced to use letter
          //edit: these floats should probably be smaller
          //to account for margins, but the result is still
          //offset significantly below the upper left
          contentStream.transform(Matrix.getScaleInstance(612f / page.getMediaBox().getWidth(),
            792f / page.getMediaBox().getHeight()));

          //round figures, a large positive Y moves the content upward
          //but not cleanly to the upper left
          //whereas a large negative Y moves the content down and cuts off the bottom
          //but does print on letter without the printer complaining about size
          contentStream.transform(Matrix.getTransformInstance(0f, 250f);
          contentStream.close();
        }
      }
      job.setPageable(pageable);
      try {
        job.print();
      } catch (PrinterException pe) {
      }
      pdf.close();
    } catch (IOException ioe) {
    }
  }
}

Using an attribute set with various combinations of MediaName, MediaSizeName, and MediaTray constants also resulted in no reduction, the printer stops and asks for legal paper, and cuts off the bottom when forced to letter.

How can I use pdfbox 2.0 to correctly scale down individual oversized pages to letter size as they are being printed?

Edit: By setting the page's MediaBox to PDRectangle.LETTER, I'm able to ensure that the printer will not ask for legal paper and that the origin of the content stream in the lower left corner is visible. Now it's just a matter of using the right math to get the scaling factors and perhaps affecting the margins, with ImageableArea or CropBox?

public class PDPrn {
  public static void main(String[] args) {
    try (PDDocument pdf = PDDocument.load(new File("foo.pdf"))) {
      PrinterJob job = PrinterJob.getPrinterJob();
      PDFPageable pageable = new PDFPageable(pdf);
      PDPageTree tree = pdf.getDocumentCatalog().getPages();
      Iterator<PDPage> iterator = tree.iterator();
      while (iterator.hasNext()) {
        PDPage page = iterator.next();
        if (page.getMediaBox().getWidth() !=612 || page.getMediaBox().getHeight() != 792) {
          PDPageContentStream contentStream = new PDPageContentStream(pdf, page,
            PDPageContentStream.AppendMode.PREPEND, false);

          //these are the wrong scaling factors but they do shrink oversized pages
          contentStream.transform(Matrix.getScaleInstance(0.5f,0.5f));
          page.setMediaBox(PDRectangle.LETTER); // make the page letter size with shrunk content origin in lower left
          contentStream.close();
        }
      }
      job.setPageable(pageable);
      try {
        job.print();
      } catch (PrinterException pe) {
      }
      pdf.close();
    } catch (IOException ioe) {
    }
  }
}

Solution

  • Setting a new MediaBox size is indeed enough to push the scaled content stream inside the smaller page. Comparing the dividends of source width/height and target width/height allows me to find a suitable scaling factor to apply to both sides, maintaining aspect ratio. The resulting margins from the pages themselves look acceptable for our use so I'm not worrying about fine control now, just setting the paper to 0 margin and letting it go. SHRINK_TO_FIT in the PDFPrintable constructor doesn't seem to be doing anything, I'm not sure what circumstances it has an effect under.

    public class PDPrn {
      public static void main(String[] args) {
        try (PDDocument pdf = PDDocument.load(new File("foo.pdf"))) {
          PrinterJob job = PrinterJob.getPrinterJob();
          PDPageTree tree = pdf.getDocumentCatalog().getPages();
          Iterator<PDPage> iterator = tree.iterator();
          while (iterator.hasNext()) {
            PDPage page = iterator.next();
            if (page.getMediaBox().getWidth() > 612 || page.getMediaBox().getHeight() > 792) {
              float fWidth = 612f / page.getMediaBox().getWidth();
              float fHeight = 792f / page.getMediaBox().getHeight();
              float factor = 0f;
              if (fWidth > fHeight) {
                factor = fHeight;
              } else {
                factor = fWidth;
              }
              PDPageContentStream contentStream = new PDPageContentStream(pdf, page,
                PDPageContentStream.AppendMode.PREPEND, false);
              contentStream.transform(Matrix.getScaleInstance(factor, factor));
              contentStream.close();
              page.setMediaBox(PDRectangle.LETTER);
            }
          }
          Paper paper = new Paper();
          paper.setSize(612, 792);
          paper.setImageableArea(0, 0, 612, 792);
          PageFormat pageFormat = new PageFormat();
          pageFormat.setPaper(paper);
          Book book = new Book();
          book.append(new PDFPrintable(pdf, Scaling.SHRINK_TO_FIT), pageFormat, pdf.getNumberOfPages());
          job.setPageable(book);
          try {
            job.print();
          } catch (PrinterException pe) {
          }
          pdf.close();
        } catch (IOException ioe) {
        }
      }
    }