sagar sagar - 19 days ago 12
Java Question

Rotate PDF around its center using PDFBox in java

PDDocument document = PDDocument.load(new File(input));
PDPage page = document.getDocumentCatalog().getPages().get(0);
PDPageContentStream cs = new PDPageContentStream(document, page,PDPageContentStream.AppendMode.PREPEND, false, false);
cs.transform(Matrix.getRotateInstance(Math.toRadians(45), 0, 0));


I am using the above code to rotate the PDF.

enter image description here

For the above image, i am getting following output

enter image description here

From that code, the content of the page has been moving out of the frame and the rotation is not happening around its center. But i want to get the output as

enter image description here

Please suggest me some options. Thanks in advance.

mkl mkl
Answer

There are two major ways to rotate the page content and make it appear in a viewer as if the rotation happened around the middle of the visible page: Either one actually does rotate around the middle of it by concatenating the rotation with translations or one moves the crop box so that the page area center follow the rotation.

Actually rotating around the center

To do this we envelop the rotation between two translations, the first one moves the origin of the coordinate system to the page center and the second one moves it back again.

PDDocument document = PDDocument.load(resource);
PDPage page = document.getDocumentCatalog().getPages().get(0);
PDPageContentStream cs = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.PREPEND, false, false); 
PDRectangle cropBox = page.getCropBox();
float tx = (cropBox.getLowerLeftX() + cropBox.getUpperRightX()) / 2;
float ty = (cropBox.getLowerLeftY() + cropBox.getUpperRightY()) / 2;
cs.transform(Matrix.getTranslateInstance(tx, ty));
cs.transform(Matrix.getRotateInstance(Math.toRadians(45), 0, 0));
cs.transform(Matrix.getTranslateInstance(-tx, -ty));
cs.close();

(RotatePageContent test testRotateCenter)

Obviously you can multiply the matrices and only add a single transformation to the PDF.

Pulling the crop box along

To do this we calculate the move of the page center and move the boxes accordingly.

PDDocument document = PDDocument.load(resource);
PDPage page = document.getDocumentCatalog().getPages().get(0);
PDPageContentStream cs = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.PREPEND, false, false);
Matrix matrix = Matrix.getRotateInstance(Math.toRadians(45), 0, 0);
cs.transform(matrix);
cs.close();

PDRectangle cropBox = page.getCropBox();
float cx = (cropBox.getLowerLeftX() + cropBox.getUpperRightX()) / 2;
float cy = (cropBox.getLowerLeftY() + cropBox.getUpperRightY()) / 2;
Point2D.Float newC = matrix.transformPoint(cx, cy);
float tx = (float)newC.getX() - cx;
float ty = (float)newC.getY() - cy;
page.setCropBox(new PDRectangle(cropBox.getLowerLeftX() + tx, cropBox.getLowerLeftY() + ty, cropBox.getWidth(), cropBox.getHeight()));
PDRectangle mediaBox = page.getMediaBox();
page.setMediaBox(new PDRectangle(mediaBox.getLowerLeftX() + tx, mediaBox.getLowerLeftY() + ty, mediaBox.getWidth(), mediaBox.getHeight()));

(RotatePageContent test testRotateMoveBox)

Scaling the content down to fit after rotation

If one wants to scale down the rotated content to make it all fit, one can do this as an easy extension of the first variant:

PDDocument document = PDDocument.load(resource);
PDPage page = document.getDocumentCatalog().getPages().get(0);
PDPageContentStream cs = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.PREPEND, false, false);

Matrix matrix = Matrix.getRotateInstance(Math.toRadians(45), 0, 0);
PDRectangle cropBox = page.getCropBox();
float tx = (cropBox.getLowerLeftX() + cropBox.getUpperRightX()) / 2;
float ty = (cropBox.getLowerLeftY() + cropBox.getUpperRightY()) / 2;

Rectangle rectangle = cropBox.transform(matrix).getBounds();
float scale = Math.min(cropBox.getWidth() / (float)rectangle.getWidth(), cropBox.getHeight() / (float)rectangle.getHeight());

cs.transform(Matrix.getTranslateInstance(tx, ty));
cs.transform(matrix);
cs.transform(Matrix.getScaleInstance(scale, scale));
cs.transform(Matrix.getTranslateInstance(-tx, -ty));
cs.close();

(RotatePageContent test testRotateCenterScale)

Changing the crop box to make all former page area remain visible

If one wants instead to change the crop box to make everything fit without scaling, one can do this as an easy extension of the second variant:

PDDocument document = PDDocument.load(resource);
PDPage page = document.getDocumentCatalog().getPages().get(0);
PDPageContentStream cs = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.PREPEND, false, false);
Matrix matrix = Matrix.getRotateInstance(Math.toRadians(45), 0, 0);
cs.transform(matrix);
cs.close();

PDRectangle cropBox = page.getCropBox();
Rectangle rectangle = cropBox.transform(matrix).getBounds();
PDRectangle newBox = new PDRectangle((float)rectangle.getX(), (float)rectangle.getY(), (float)rectangle.getWidth(), (float)rectangle.getHeight());
page.setCropBox(newBox);
page.setMediaBox(newBox);

(RotatePageContent test testRotateExpandBox)

Sample results

The following image shows an output for each of the methods above:

Screenshot

  1. Actually rotating around the center
  2. Scaling the content down to fit after rotation
  3. Pulling the crop box along
  4. Changing the crop box to make all former page area remain visible

Image 4 is not at the same scale as the others, it should show larger.