itextpdf-writer

Copying annotations with PdfWriter instead of PdfCopy


I need to copy annotations using PdfWriter instead of PdfCopy because at the time of the copy I need to resize/rotate the page. Can anyone tell me how to do this?


Solution

  • You think you need to use a plain PdfWriter instead of a PdfCopy for copying PDFs because you need to resize/rotate the page and iText in Action, 2nd Ed, says doing so is not possible with the PdfCopy class. Thus, you look for a way to copy annotations in such a context.

    What you should look for instead is a way to rotate or resize pages and at the same time use PdfCopy nonetheless!

    While it is true that the PdfCopy class itself does not allow resizing or rotating pages, you can manipulate a PDF loaded into a PdfReader and resize and/or rotate its pages before using the PdfCopy class. If you then copy the pages from this manipulated PdfReader into a PdfCopy, you get a result with resized or rotated pages (due to the manipulated PdfReader) and all the annotations present (due to the use of a PdfCopy).

    E.g. you can resize all the pages in a PdfReader like this:

    void resize(PdfReader pdfReader, float width, float height) {
        for (int i = 1; i <= pdfReader.getNumberOfPages(); i++) {
            boolean switched = pdfReader.getPageRotation(i) % 180 != 0;
            float widthHere = switched ? height : width;
            float heightHere = switched ? width : height;
    
            Rectangle cropBox = pdfReader.getCropBox(i);
            float halfWidthGain = (widthHere - cropBox.getWidth()) / 2;
            float halfHeightGain = (heightHere - cropBox.getHeight()) / 2;
            Rectangle newCropBox = new Rectangle(cropBox.getLeft() - halfWidthGain, cropBox.getBottom() - halfHeightGain,
                    cropBox.getRight() + halfWidthGain, cropBox.getTop() + halfHeightGain);
    
            Rectangle mediaBox = pdfReader.getPageSize(i);
            Rectangle newMediaBox = new Rectangle(Math.min(newCropBox.getLeft(), mediaBox.getLeft()),
                    Math.min(newCropBox.getBottom(), mediaBox.getBottom()),
                    Math.max(newCropBox.getRight(), mediaBox.getRight()),
                    Math.max(newCropBox.getTop(), mediaBox.getTop()));
    
            PdfDictionary pageDictionary = pdfReader.getPageN(i);
            pageDictionary.put(PdfName.MEDIABOX, new PdfArray(new float[] {newMediaBox.getLeft(), newMediaBox.getBottom(),
                    newMediaBox.getRight(), newMediaBox.getTop()}));
            pageDictionary.put(PdfName.CROPBOX, new PdfArray(new float[] {newCropBox.getLeft(), newCropBox.getBottom(),
                    newCropBox.getRight(), newCropBox.getTop()}));
        }
    }
    

    (CopyWithResizeRotate helper method)

    and you can rotate all the pages in a PdfReader like this:

    void rotate(PdfReader pdfReader) {
        for (int i = 1; i <= pdfReader.getNumberOfPages(); i++) {
            int rotation = pdfReader.getPageRotation(i);
            int newRotation = rotation + 90 % 360;
    
            PdfDictionary pageDictionary = pdfReader.getPageN(i);
            if (newRotation == 0)
                pageDictionary.remove(PdfName.ROTATE);
            else
                pageDictionary.put(PdfName.ROTATE, new PdfNumber(newRotation));
        }
    }
    

    (CopyWithResizeRotate helper method)

    Using these helpers, you can e.g. create a PDF from the rotated and/or resized pages of some source PDF and copy them like this:

    byte[] wildPdf = RETRIEVE_SOURCE_PDF;
    
    PdfReader pdfReaderOriginal = new PdfReader(wildPdf);
    PdfReader pdfReaderRotate = new PdfReader(wildPdf);
    rotate(pdfReaderRotate);
    PdfReader pdfReaderResize = new PdfReader(wildPdf);
    resize(pdfReaderResize, PageSize.LETTER.getWidth(), PageSize.LETTER.getHeight());
    PdfReader pdfReaderRotateResize = new PdfReader(wildPdf);
    rotate(pdfReaderRotateResize);
    resize(pdfReaderRotateResize, PageSize.LETTER.getWidth(), PageSize.LETTER.getHeight());
    
    try (   OutputStream os = new FileOutputStream(new File(RESULT_FOLDER, "wild-rotated-resized.pdf"))) {
        Document document = new Document();
        PdfCopy pdfCopy = new PdfCopy(document, os);
        document.open();
        pdfCopy.addDocument(pdfReaderOriginal);
        pdfCopy.addDocument(pdfReaderRotate);
        pdfCopy.addDocument(pdfReaderResize);
        pdfCopy.addDocument(pdfReaderRotateResize);
        document.close();
    }
    

    (CopyWithResizeRotate test method testRotateResizeAndCopy)

    The result can look as follows, the first row the original pages (#1 A4, #2 HALFLETTER, #3 A5, #4 A5 rotated, #5 500x700), the second row the rotated ones, the third row the resized ones (to LETTER), and the fourth row the rotated and resized ones (to LETTER). The Adobe Reader thumbnails unfortunately are not at all to scale:

    screen shot

    Manipulating a single PDF only

    If you actually only want to resize/rotate pages of a single input PDF, you should not use a PdfCopy instance but instead a PdfStamper:

    PdfReader pdfReader = new PdfReader(SOURCE);
    [...manipulate properties of the pdfReader like above...]
    new PdfStamper(pdfReader, TARGET_STREAM).close();
    

    The advantage here is that not only page-level data but also document-level data of the original document are retained.

    Special annotations

    There is one type of annotations which will behave in an unexpected manner with the code above: annotations with the NoRotate flag set. Such annotations will behave like this when their host page is rotated:

    screenshot from specification

    (ISO 32000-2 section 12.5.3 — Annotation flags)