How to shrink pages in an existing PDF?

Tags: page sizecrop boxscale contentiText 7

I am using PdfWriter, PdfImportedPage and the addTemplate() method to shrink pages. However, when I do so, I lose the rotation of the pages and I lose all interactive features. Is there another way I can shrink pages?

Posted on StackOverflow on Aug 18, 2014 by Butani Vijay

Scaling a PDF to make pages larger is easy. This is shown in the ScaleRotate example. It's only a matter of changing the default version of the user unit. By default, this unit is 1, meaning that 1 user unit equals to 1 point. You can increase this value to 75,000. Larger values of the user unit, will result in larger pages.

Unfortunately, the user unit can never be smaller than 1, so you can't use this technique to shrink a page. If you want to shrink a page, you need to introduce a transformation (resulting in a new CTM).

This is shown in the ShrinkPdf example. In this example, I take a PDF named hero.pdf that measures 8.26 by 11.69 inch, and we shrink it by 50%, resulting in a PDF named hero_shrink.pdf that measures 4.13 by 5.85 inch.

To achieve this, we need a dirty hack:

public void manipulatePdf(String src, String dest) throws IOException {
    PdfDocument pdfDoc = new PdfDocument(new PdfReader(src), new PdfWriter(dest));
    int n = pdfDoc.getNumberOfPages();
    PdfPage page;
    Rectangle crop;
    Rectangle media;
    for (int p = 1; p <= n; p++) {
        page = pdfDoc.getPage(p);
        media = page.getCropBox();
        if (media == null) {
            media = page.getMediaBox();
        }
        crop = new Rectangle(0, 0, media.getWidth() / 2, media.getHeight() / 2);
        page.setMediaBox(crop);
        page.setCropBox(crop);
        new PdfCanvas(pdfDoc.getPage(p).newContentStreamBefore(),
                new PdfResources(), pdfDoc).writeLiteral("\nq 0.5 0 0 0.5 0 0 cm\nq\n");
        new PdfCanvas(pdfDoc.getPage(p).newContentStreamAfter(),
                new PdfResources(), pdfDoc).writeLiteral("\nQ\nQ\n");
    }
    pdfDoc.close();
}

We loop over every page and we take the crop box of each page. If there is no crop box, we take the media box. These boxes are stored as arrays of 4 numbers. In this example, I assume that the first two numbers are zero and I divide the next two values by 2 to shrink the page to 50% (if the first two values are not zero, you'll need a more elaborate formula).

Once I have the new array, I change the media box and the crop box to this array, and I introduce a CTM that scales all content down to 50%. I need to use the writeLiteral() method to fool iText.

Thanks Bruno Lowagie. Above code scale whole page actually I want to make room for adding header and footer means by scaling(shrink) page as shown in this screenshot:

Screen shot

I have made a second example, named ShrinkPdf2 that allows you to introduce a different scaling percentage:

public void manipulatePdf(String src, String dest) throws IOException {
    PdfDocument pdfDoc = new PdfDocument(new PdfReader(src), new PdfWriter(dest));
    int n = pdfDoc.getNumberOfPages();
    float percentage = 0.8f;
    for (int p = 1; p <= n; p++) {
        float offsetX = (pdfDoc.getPage(p).getPageSize().getWidth() * (1 - percentage)) / 2;
        float offsetY = (pdfDoc.getPage(p).getPageSize().getHeight() * (1 - percentage)) / 2;
        new PdfCanvas(pdfDoc.getPage(p).newContentStreamBefore(), new PdfResources(), pdfDoc).writeLiteral(
                String.format(Locale.ENGLISH, "\nq %s 0 0 %s %s %s cm\nq\n",
                        percentage, percentage, offsetX, offsetY));
        new PdfCanvas(pdfDoc.getPage(p).newContentStreamAfter(),
                new PdfResources(), pdfDoc).writeLiteral("\nQ\nQ\n");
    }
    pdfDoc.close();
}

In this example, I don't change the page size (no changes to the media or crop box), I only shrink the content (in this case to 80%) and I center the shrunken content on the page, leaving bigger margins to the top, bottom, left and right.

As you can see, this is only a matter of applying the correct Math. This second example is even more simple than the first, so I introduced some extra complexity: now you can easily change the percentage (in the first example I hard coded 50%. in this case I introduced the percentage variable and I defined it as 80%).

Note that I apply the scaling in the X direction as well as in the Y direction to preserve the aspect ratio. Looking at your screen shots, it looks like you only want to shrink the content in the Y direction. For instance like this:

String.format("\nq 1 0 0 %s 0 %s cm\nq\n", percentage, offsetY)

Feel free to adapt the code to meet your need, but... the result will be ugly: all text will look funny and if you apply it to photos of people standing up vertically, you'll make them look fat.

Click this link if you want to see how to answer this question in iText 5.