Search code examples
javapdfpdfbox

How do I rotate the contents of a PDF page to an arbitrary angle?


I need to rotate the contents of a PDF page by an arbitrary angle and the PDPage.setRotation(int) command is restricted to multiples of 90 degrees. The contents of the page are vector and text and I need to be able to zoom in on the contents later, which means that I cannot convert the page to an image because of the loss of resolution.


Solution

  • In comments it already has been indicated that to draw some content, e.g. an existing regular portrait or landscape page, at an arbitrary angle onto a new regular portrait or landscape page, one can use the mechanism presented in this answer.

    As the code presented there

    1. requires the PDFBox development 2.0.0-SNAPSHOT version and
    2. makes use of form xobjects which in the context at hand is not necessary,

    though, here a quick&dirty solution working for the current regular release 1.8.8 without introducing form xobjects.

    This method

    void transformPage(PDDocument document, PDPage page, AffineTransform at) throws IOException, COSVisitorException
    {
        PDRectangle cropBox = page.findCropBox();
        float xOffset = (cropBox.getUpperRightX() + cropBox.getLowerLeftX()) / 2f;
        float yOffset = (cropBox.getUpperRightY() + cropBox.getLowerLeftY()) / 2f;
        AffineTransform transform = AffineTransform.getTranslateInstance(xOffset, yOffset);
        transform.concatenate(at);
        transform.concatenate(AffineTransform.getTranslateInstance(-xOffset, -yOffset));
    
        PDPageContentStream stream = new PDPageContentStream(document, page, true, false);
        stream.concatenate2CTM(transform);
        stream.close();
    
        COSBase contents = page.getCOSDictionary().getDictionaryObject(COSName.CONTENTS);
        if (contents instanceof COSStreamArray)
        {
            COSStreamArray contentsArray = (COSStreamArray) contents;
            COSArray newArray = new COSArray();
            newArray.add(contentsArray.get(contentsArray.getStreamCount() - 1));
    
            for (int i = 0; i < contentsArray.getStreamCount() - 1; i++)
            {
                newArray.add(contentsArray.get(i));
            }
    
            COSStreamArray newStreamArray = new COSStreamArray(newArray);
            page.getCOSDictionary().setItem(COSName.CONTENTS, newStreamArray);
        }
    }
    

    applies the given transformation to the given page. To make the use case at hands (to rotate the contents of a PDF page) easier, the transformation is enveloped in translations moving the origin of the coordinate system to the center of the page for the transformation.

    The method can be used like this

    try ( InputStream sourceStream = getClass().getResourceAsStream("13.pdf") )
    {
        final PDDocument document = PDDocument.load(sourceStream);
        final AffineTransform transform = AffineTransform.getRotateInstance(Math.PI / 4);
    
        List<PDPage> pages = document.getDocumentCatalog().getAllPages();
    
        for (PDPage page: pages)
        {
            transformPage(document, page, transform);
        }
    
        document.save("13-transformedPages.pdf");
    }
    

    to rotate the pages of a document counterclockwise by 45° (PI/4, mathematically positive rotation direction).