2

I am having some issues with a PDF containing a rotation property created by a Xerox scanner. The below function was originally created to scale the height of an input PDF by the amount given by the variable scaleHeight. This works fine for input documents without rotation.

When testing a document with a 270 degree rotation, I found that the rotation property which would have made the document appear in a portrait orientation was ignored. Instead, the document appeared in a landscape orientation in the output PDF. So I updated the function below to apply the scaling only when there is no rotation, and used another example I found online to try to fix the rotation. This did not work, and resulted in a mirror image of the original document in portrait format.

So now I have two problems: 1. How to properly rotate the document contents. 2. How to scale the rotated contents.

If I can solve item 1, I can simply call the function again (with the rotation property removed) to fix item 2.

Thank you for any and all help, the function is below. The commented out lines referring to the rotationEvent did not help here either.

public String resizePDF (String pdfIn, float x, float y, float scaleHeight) throws Exception {
    String pdfOut = pdfIn.substring(0, pdfIn.length() - 4) + "_resize.pdf";
    PdfReader reader = new PdfReader(pdfIn);
    int rotation = reader.getPageRotation(1);
    com.itextpdf.text.Document doc = new com.itextpdf.text.Document(reader.getPageSizeWithRotation(1), 0, 0, 0, 0);
    PdfWriter writer = PdfWriter.getInstance(doc, new FileOutputStream(pdfOut));
    doc.open();
    PdfContentByte cb = writer.getDirectContent();
    Rotate rotationEvent = new Rotate();
    writer.setPageEvent(rotationEvent);
    for(int i=1; i<=reader.getNumberOfPages(); i++){
        float pageWidth = reader.getPageSizeWithRotation(i).getWidth();
        float pageHeight = reader.getPageSizeWithRotation(i).getHeight();
        doc.newPage();
        PdfImportedPage page = writer.getImportedPage(reader, i);
        if (rotation == 0) {
            cb.addTemplate(page, 1f, 0, 0, scaleHeight, x, y);
            //rotationEvent.setRotation(PdfPage.PORTRAIT);
        } else if (rotation == 90) {
            cb.addTemplate(page, 0, -1f, 1f, 0, 0, pageHeight);
            //rotationEvent.setRotation(PdfPage.LANDSCAPE);
        } else if (rotation == 180) {
            cb.addTemplate(page, 1f, 0, 0, -1f, pageWidth, pageHeight);
            //rotationEvent.setRotation(PdfPage.INVERTEDPORTRAIT);
        } else if (rotation == 270) {
            cb.addTemplate(page, 0, -1f, 1f, 0, 0, pageHeight);
            //cb.addTemplate(page, 0, 1f, -1f, 0, pageWidth, 0);
            //rotationEvent.setRotation(PdfPage.SEASCAPE);
        }
    }
    doc.close();
    return pdfOut;
}
user3854377
  • 63
  • 1
  • 7
  • Your question probably doesn't get an answer, because it is very confusing. It is not clear what you are trying to achieve. Several pages initially have a rotation. That's fine. Now why are you trying to remove that rotation? Also you say "it doesn't work" quite often without any further explanation, but http://lowagie.com/doesntwork – Bruno Lowagie Mar 20 '15 at 08:50
  • For instance: is this similar to what you're trying to do? http://stackoverflow.com/questions/21871027/rotating-in-itextsharp-while-preserving-comment-location-orientation – Bruno Lowagie Mar 20 '15 at 08:52
  • My initial goal was simply to scale the input PDF. When I saw that the output PDF had a different orientation, I realized that the rotation property was causing a problem. This is when I set out to remove the rotation property and actually fix the orientation of the page so that a normal scaling would be possible. The reason it did not work is noted- the output document became a mirror image of the input. – user3854377 Mar 20 '15 at 16:56
  • What you have linked is similar to what I want to do. In my case, I would like to be able to remove the rotation property (since it is apparently not compatible with cb.addTemplate) and physically change the contents of the document to be permanently displayed as portrait. Is there a simpler solution to scale any input document, regardless of whether it has a rotation applied? – user3854377 Mar 20 '15 at 17:01
  • Why not wrap the `PdfImportedPage` into an `Image` (don't worry, you won't use any quality), use the scale methods on the image and add the image to a page for which you define a size that is equal to the scaled size of the image? – Bruno Lowagie Mar 20 '15 at 17:16
  • This could work, however it is more desirable to retain the text. A later point in the workflow involves converting the PDF into a word document. If we were to simply convert each page into an image, it somewhat defeats the purpose of what we have implemented thus far. – user3854377 Mar 20 '15 at 21:23
  • For reference, I referred to the following example for iTextsharp, thinking the java library works similarly: [link](http://stackoverflow.com/questions/3579058/rotating-pdf-in-c-sharp-using-itextsharp) – user3854377 Mar 21 '15 at 00:25
  • Wrapping a page in an image doesn't convert it to a raster image. The iText image class preserves the text. – Bruno Lowagie Mar 21 '15 at 08:24

1 Answers1

2

I don't understand your code, but when I tell people "please throw away your code and start anew", many people feel offended (although it was certainly not my intention to offend them).

I understand that you want to scale the contents of an existing PDF and either keep the rotation or remove it (that part isn't entirely clear).

Hence I have written you an example called ScaleDown that can be used to scale down, keeping the orientation. It's sufficient to remove a single line to remove the orientation.

This example uses a page event (I named it ScaleEvent):

public class ScaleEvent extends PdfPageEventHelper {

    protected float scale = 1;
    protected PdfDictionary pageDict;

    public ScaleEvent(float scale) {
        this.scale = scale;
    }

    public void setPageDict(PdfDictionary pageDict) {
        this.pageDict = pageDict;
    }

    @Override
    public void onStartPage(PdfWriter writer, Document document) {
        writer.addPageDictEntry(PdfName.ROTATE, pageDict.getAsNumber(PdfName.ROTATE));
        writer.addPageDictEntry(PdfName.MEDIABOX, scaleDown(pageDict.getAsArray(PdfName.MEDIABOX), scale));
        writer.addPageDictEntry(PdfName.CROPBOX, scaleDown(pageDict.getAsArray(PdfName.CROPBOX), scale));
    }
}

When you create the event, you pass a value scale that will define the scale factor. I apply the scale to the width and the height, feel free to adapt it if you only want to scale the height.

The information about page size and rotation is stored in the page dictionary. Obviously the ScaleEvent needs the values of the original document, and that why we'll pass a pageDict for every page we copy.

Every time a new page is created, we will copy/replace:

  • the /Rotate value. Remove this line if you want to remove the rotation,
  • the /MediaBox value. This defines the full size of the page.
  • the /CropBox value. This defines the visible size of the page.

As we want to scale the page, we use the following scaleDown() method:

public PdfArray scaleDown(PdfArray original, float scale) {
    if (original == null)
        return null;
    float width = original.getAsNumber(2).floatValue()
            - original.getAsNumber(0).floatValue();
    float height = original.getAsNumber(3).floatValue()
            - original.getAsNumber(1).floatValue();
    return new PdfRectangle(width * scale, height * scale);
}

Suppose that I want to reduce the page width and height to 50% of the original width and height, then I create the event like this:

PdfReader reader = new PdfReader(src);
float scale = 0.5f;
ScaleEvent event = new ScaleEvent(scale);
event.setPageDict(reader.getPageN(1));

I can define a Document with any page size I want as the size will be changed in the ScaleEvent anyway. Obviously, for this to work I need to declare the event to the PdfWriter instance:

Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(dest));
writer.setPageEvent(event);
document.open();

Now it's only a matter of looping over the pages:

int n = reader.getNumberOfPages();
Image page;
for (int p = 1; p <= n; p++) {
    page = Image.getInstance(writer.getImportedPage(reader, p));
    page.setAbsolutePosition(0, 0);
    page.scalePercent(scale * 100);
    document.add(page);
    if (p < n) {
        event.setPageDict(reader.getPageN(p + 1));
    }
    document.newPage();
}
document.close();

I am wrapping the imported page inside an Image because I personally think that the methods available for the Image class are easier to use than defining the parameters of the addTemplate() method. If you want to use addTemplate() instead of Image, feel free to do so; the result will be identical (contrary to what you wrote in a comment, wrapping a page inside an image will not cause any loss of "resolution" as all the text remains available as vector data).

Note that I update the pageDict for every new page.

This code takes the file orientations.pdf measuring 8.26 by 11.69 in and transforms it into the file scaled_down.pdf measuring 4.13 by 5.85 in.

If you want all the pages to be in portrait, just remove the following line:

writer.addPageDictEntry(PdfName.ROTATE, pageDict.getAsNumber(PdfName.ROTATE));
Bruno Lowagie
  • 75,994
  • 9
  • 109
  • 165
  • Thank you for taking the time to write this solution, you were correct that I misunderstood the usage of the Image class. Unfortunately with this solution, the page is oriented correctly, but the content is still rotated sideways and is cut off due to lack of space. I have removed private information to create the following testing PDF: https://drive.google.com/file/d/0B8U8D128iEUbeFJKSnBKRVFJQkE/view?usp=sharing Note: I also tried adding a call to page.setRotation, but this had no effect. – user3854377 Mar 23 '15 at 23:43
  • Finally, I think it is necessary to initialize the new document with some page size: com.itextpdf.text.Document doc = new com.itextpdf.text.Document(reader.getPageSizeWithRotation(1), 0, 0, 0, 0); – user3854377 Mar 23 '15 at 23:44
  • Let me know if it would help to see the corresponding output PDF for this troublesome input document. – user3854377 Mar 24 '15 at 19:56
  • Maybe you can share it. I don't agree that it matters whether or not you initialize the new document with a page size. The size and orientation are changed at the level of the page dictionary in the page event. Whatever size and orientation you define up-front will be overwritten. – Bruno Lowagie Mar 24 '15 at 19:59
  • Here is a sample output document: https://drive.google.com/file/d/0B8U8D128iEUbTXZlbE9WNjJLUUU/view?usp=sharing – user3854377 Mar 24 '15 at 22:31
  • Were you able to reproduce this issue? – user3854377 Mar 26 '15 at 16:43
  • I have just tested my code on your initial file and this was the result: http://itextpdf.org/documents/scaled_down.pdf In other words: my code works. Which version of iText are you using? Because I'm kind of fed up with people wasting my time saying "it doesn't work" when they are in fact not using an official version of iText. – Bruno Lowagie Mar 26 '15 at 17:21
  • Looking at your output file, it is **very obvious** to me that you didn't follow at my example and I really don't appreciate it when I spend time trying to help somebody only to find out that this person doesn't listen to my advice. I am pretty sure that you didn't set the page event: `writer.setPageEvent(event);` – Bruno Lowagie Mar 26 '15 at 17:32
  • I have followed your example, set the page event, and I tested on 5.4.5 and 5.5.5 versions of iText (AGPL binary). The only modifications I made were to scale only the height of the document: https://gist.github.com/anonymous/2ece04be7b44fbdc318e Note that I had to comment out the @Override annotation since eclipse did not think this function exists for this class. – user3854377 Mar 26 '15 at 18:04
  • I see that I was right: you just adapted my example without reading the documentation. One glance is sufficient to see what is wrong. You set the page event *after* opening the document. Page events should be set *before* opening the document. – Bruno Lowagie Mar 26 '15 at 18:27
  • I have reordered these statements to match your example, but still it does not work correctly. https://gist.github.com/anonymous/17789483a9868e6fabde After setting a breakpoint, I realized that the onStartPage function is never called. I am unsure why this happens. – user3854377 Mar 26 '15 at 18:46
  • Thanks for all the help, I finally realized that the problem was a variable declared as the wrong Document class. Changing the definition as follows fixed the issue: public void onStartPage(PdfWriter writer, com.itextpdf.text.Document document) { I am still having an issue with a reflection of the image, but I realized this issue occurs after Aspose converts the PDF to word. – user3854377 Mar 26 '15 at 21:33
  • LInks that work in original document do not work in output document. Any way to maintain all original metadata ? – jkb016 Jun 03 '16 at 21:21
  • A link isn't metadata. A link is an annotation. @jkb016 – Bruno Lowagie Jun 04 '16 at 09:10
  • @BrunoLowagie , ok, but can we keep links to work. I am working with iText7 . – jkb016 Jun 05 '16 at 18:20
  • @jkb016 Don't hijack the comments of another question to post a new one. As documented on many places `PdfImportedPage` only copies the content stream(s) of a page, not the annotations (and a fortiori not the links). You have to scale and copy the annotations separately. – Bruno Lowagie Jun 05 '16 at 18:52
  • @jkb016 The comment in my ScaleDown example already told you this: "This solution is suboptimal as it throws away all interactivity. If you want to keep the interactive elements (annotations, form fields,...), you need to do much more work." Did you try doing more work? If so, what did you try? Don't answer here. Comments aren't meant to post questions. – Bruno Lowagie Jun 05 '16 at 18:56
  • @BrunoLowagie, sorry for adding questions here. I do already have a question specifically for my issue here http://stackoverflow.com/questions/37623853/itext7-scaling-rotated-document-with-links?noredirect=1#comment62736794_37623853 – jkb016 Jun 05 '16 at 19:11