2

I am using following code to shrink every pages (Top and bottom) of existing pdf using iText library.

Code working fine. But now if i process result pdf, i get 0 value for rotation of every page, while old pdf has other rotation too(i.e. 90deg).

I want to keep rotation as it is but unable to do it.

Code i am using As below to shrink pages

public void shrinkPDFPages() throws Exception {
        PdfReader reader = new PdfReader("D:/testpdfs/test.pdf");

        Document doc = new Document();
        PdfWriter writer = PdfWriter.getInstance(doc, new FileOutputStream(
                "D://testpdfs/result.pdf"));
        doc.open();
        PdfContentByte cb = writer.getDirectContent();
        for (int i = 1; i <= reader.getNumberOfPages(); i++) {

            PdfImportedPage page = writer.getImportedPage(reader, i);
            float pageHeight = reader.getPageSizeWithRotation(i).getHeight();
            float pageWidth = reader.getPageSizeWithRotation(i).getWidth();
            int rotation = reader.getPageRotation(i);

            Rectangle pageRectangle = reader.getPageSizeWithRotation(i);
            Rectangle PageRect = null;

            System.out.println(rotation);

            switch (rotation) {
            case 0:
                PageRect = new Rectangle(pageRectangle.getWidth(), pageRectangle
                        .getHeight());
                doc.setPageSize(PageRect);
                doc.newPage();
                AffineTransform af = new AffineTransform();
                af.scale(1, 0.84f);
                af.translate(1, 50);

                cb.addTemplate(page, af);
                break;
            case 90:
                PageRect = new Rectangle(pageRectangle.getWidth(), pageRectangle
                        .getHeight());
                doc.setPageSize(PageRect);
                doc.newPage();

                cb.addTemplate(page, 0, -1f, 0.84f, 0, 50, pageHeight);
                break;
            case 270:
                PageRect = new Rectangle(pageRectangle.getWidth(), pageRectangle
                        .getHeight());
                doc.setPageSize(PageRect);
                doc.newPage();
                cb.addTemplate(page, 0, 1f, -0.84f, 0, pageWidth - 50, 0);
                break;

            case 180:
                PageRect = new Rectangle(pageRectangle.getWidth(), pageRectangle
                        .getHeight());
                doc.setPageSize(PageRect);
                doc.newPage();
                cb.addTemplate(page, -1f, 0, 0, -0.84f, pageWidth,
                        pageHeight - 50);
                break;
            default:
                break;

            }
        }
        doc.close();
    }

What should i do? so rotation remains as it is.

One more problem i am fetching is, unable to preserve internal hyper links.

Actual pdf page:

enter image description here

After Shrink(Scale Down Content):

enter image description here

Butani Vijay
  • 4,181
  • 2
  • 29
  • 61
  • You copy the document pages into a new document with its own page definitions. If you want to *keep* page definitions, you have to copy them or even better use a PdfStamper instead – mkl Aug 18 '14 at 06:04
  • can i Shrink/scale pages if i use PdfStamper? – Butani Vijay Aug 18 '14 at 06:20
  • 1
    Shrinking admittedly is difficult. So creating new pages with the appropriate rotation is the way to go for you. – mkl Aug 18 '14 at 06:28
  • can you provide some sample if possible? i dont know how to do it. – Butani Vijay Aug 18 '14 at 06:30
  • 1
    Do I correctly understand your edit, you want to squeeze vertically but leave the dimensions horizontally? In that case you have to replace the first percentage after `String.format("\nq %s 0 0 %s %s %s cm\nq\n", ...` by `1` and set `offsetX` to `0`. – mkl Sep 08 '14 at 10:12
  • Hi Mkl can you explain This ..String.format("\nq %s 0 0 %s %s %s cm\nq\n", – Butani Vijay Sep 09 '14 at 06:01
  • My comment essentially amounts to Bruno's latest edit of his answer. – mkl Sep 09 '14 at 07:00
  • In this six parameter which one is used for rotation? – Butani Vijay Sep 09 '14 at 07:21
  • *which one is used for rotation* - the first four. A pure rotation (by an angle q counter clockwise) transformation matrix looks like `[cos q sin q -sin q cos q 0 0]`. For details cf. section 8.3.3 *Common Transformations* of the [PDF specification](http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/PDF32000_2008.pdf). – mkl Sep 09 '14 at 08:39
  • q used in this string is used for rotation? – Butani Vijay Sep 09 '14 at 09:53
  • `q` in `[cos q sin q -sin q cos q 0 0]` represents a rotation angle, but og course you have to calculate the `sin q` and `cos q` values before writing that expression to a content stream. The `q` in `"\nq %s 0 0 %s %s %s cm\nq\n"` is something else entirely, its a save-graphics-state operator. – mkl Sep 09 '14 at 10:47

1 Answers1

2

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, DocumentException {
    PdfReader reader = new PdfReader(src);
    PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
    int n = reader.getNumberOfPages();
    PdfDictionary page;
    PdfArray crop;
    PdfArray media;
    for (int p = 1; p <= n; p++) {
        page = reader.getPageN(p);
        media = page.getAsArray(PdfName.CROPBOX);
        if (media == null) {
            media = page.getAsArray(PdfName.MEDIABOX);
        }
        crop = new PdfArray();
        crop.add(new PdfNumber(0));
        crop.add(new PdfNumber(0));
        crop.add(new PdfNumber(media.getAsNumber(2).floatValue() / 2));
        crop.add(new PdfNumber(media.getAsNumber(3).floatValue() / 2));
        page.put(PdfName.MEDIABOX, crop);
        page.put(PdfName.CROPBOX, crop);
        stamper.getUnderContent(p).setLiteral("\nq 0.5 0 0 0.5 0 0 cm\nq\n");
        stamper.getOverContent(p).setLiteral("\nQ\nQ\n");
    }
    stamper.close();
    reader.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 setLiteral() method to fool iText.

Based on your feedback in the comments section, it appears that you do not understand the mechanics explained in my example. Hence I have made a second example, named ShrinkPdf2:

public void manipulatePdf(String src, String dest) throws IOException, DocumentException {
    PdfReader reader = new PdfReader(src);
    PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
    int n = reader.getNumberOfPages();
    float percentage = 0.8f;
    for (int p = 1; p <= n; p++) {
        float offsetX = (reader.getPageSize(p).getWidth() * (1 - percentage)) / 2;
        float offsetY = (reader.getPageSize(p).getHeight() * (1 - percentage)) / 2;
        stamper.getUnderContent(p).setLiteral(
                String.format("\nq %s 0 0 %s %s %s cm\nq\n", percentage, percentage, offsetX, offsetY));
        stamper.getOverContent(p).setLiteral("\nQ\nQ\n");
    }
    stamper.close();
    reader.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 hardcoded 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.

Bruno Lowagie
  • 75,994
  • 9
  • 109
  • 165
  • Thanks Bruno Lowagie. Above code scale whole page actually i want to make room for adding header and footer means by scaling(shrink) page. – Butani Vijay Sep 08 '14 at 09:14
  • i want to page size as it is ex.. if the page size is **A4** then output page should be **A4** – Butani Vijay Sep 08 '14 at 09:27
  • That's a different question! Maybe you don't even need to *shrink* the content, you can just change the page size to add some more space on top and at the bottom of each page. That code is much easier than my example. If you **do** need to shrink the content, then you can still use my example, you only need to apply some Math. – Bruno Lowagie Sep 08 '14 at 09:40
  • I've added an extra example to prove my point that your comment wasn't really relevant: just apply the correct Math and you'll get the result you need. – Bruno Lowagie Sep 08 '14 at 09:56
  • Thanks!!This is what i want. i would like to know more about setLiteral. which values in example use for top,left,right and bottom margin? – Butani Vijay Sep 08 '14 at 11:33
  • The `setLiteral()` method is considered being a *dangerous* method because it allows you to insert literal PDF syntax into your PDF. In this case, the most important PDF operator is `cm` preceded by 6 operands that are elements of a matrix described in section 4.2.5.1 of the book "The ABC of PDF". See http://itextpdf.com/learn – Bruno Lowagie Sep 08 '14 at 11:53
  • I've added an extra line that shows how to adapt the operands of the `cm` operator so that scaling only happens in one direction. – Bruno Lowagie Sep 08 '14 at 12:01
  • I have tested your code it working fine but when i have pages with different rotation value i got different result. – Butani Vijay Sep 09 '14 at 04:31
  • Yes, I've explained the mechanism and I've explained the Math. Now it's up to you to apply the mechanism and to do the Math. For instance: I also mentioned that I'm making the assumption that the lower-left corner of the page has coordinates 0,0. You'll also have to check for that and adapt the Math. – Bruno Lowagie Sep 09 '14 at 05:25
  • I am unable to understood this->String.format("\nq 1 0 0 %s 0 %s cm\nq\n", percentage, offsetY) – Butani Vijay Sep 09 '14 at 05:32
  • Seems like you overlooked my comment that refers to the 6 operands that are elements of a matrix described in section 4.2.5.1 of the book "The ABC of PDF". See itextpdf.com/learn I am NOT going to copy the contents of that section to StackOverflow. That would be overkill. – Bruno Lowagie Sep 09 '14 at 05:49
  • @BrunoLowagie Can you suggest how could we restore back to original size of the content considering the 2 example solution being implemented initially with 80% shrinkage. I mean do we have to change any calculations to the shrink percentage and Offset values, if so please post that code too... – Bhanu Chandra Jun 01 '20 at 15:50