Rotate PDF around its center using PDFBox in java - 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.
For the above image, i am getting following output
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
Please suggest me some options. Thanks in advance.

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:
Actually rotating around the center
Scaling the content down to fit after rotation
Pulling the crop box along
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.

Related

IText 7.2.3, How to rotate a stamp or annotation in a pdf using Itext and java

Using iText, exactly how are objects like rectangles and stamp annotations rotated?
I've seen some examples on how to rotate annotations using current transformation matrix and using appearance matrix. You can see my implementation in the below code.
I've tried with different values for appearance matrix, it doesn't seem to be solving or fitting the image as I've expected.
My use-case is I have X, Y, width, height and rotation angle(+or-45, +or- 90 etc.,) of the location I need to fit in the image.
I'm able to rotate the image but I would like the image to fit in the dimensions I've given.
For example check this DOCUMENT , it is generated with -45 degree with rectangle height and width are 400, 250. The rotation seems ok but the image didn't fit in the dimensions I've given.
I would like to rotate the image based on the (X,Y) as axis and would really be glad if someone helped out.
public static void addStampAnnotation(File file) throws Exception
{
PdfReader reader1 = new PdfReader(file);
ByteArrayOutputStream mos = new ByteArrayOutputStream();
PdfDocument docc = new PdfDocument(reader1, new PdfWriter(mos),new StampingProperties().useAppendMode());
String filePath = "rectangle.png";
ImageData img = ImageDataFactory.create(filePath);
// img.setRotation(90);
Rectangle rect=null;
float w = img.getWidth();
float h = img.getHeight();
rect = new Rectangle(0,0, 250,400);
PdfFormXObject xObj = new PdfFormXObject(new Rectangle(w,h));
PdfCanvas canvas = new PdfCanvas(xObj, docc);
canvas.addImageAt(img,0,0,false);
// canvas.addImageWithTransformationMatrix(img, w, 0, 0, h, 0, 0);
xObj.put(PdfName.Matrix, new PdfArray(new int[]{0, 1, -1, 0, 0, 0}));
PdfStampAnnotation stamp = new PdfStampAnnotation(rect);
stamp.setNormalAppearance(xObj.getPdfObject());
int flag = PdfAnnotation.PRINT ;
stamp.setFlags(flag);
docc.getPage(1).addAnnotation(stamp);
docc.close();
file = new File("Test_Doc.pdf");
FileOutputStream fout1 = new FileOutputStream(file);
fout1.write(mos.toByteArray());
fout1.close();
}
The image used is here IMAGE
Thanks in advance !

How to scale a PDAnnotation?

I have a pdf that has been passed to me with a signature (annotation) and I have to scale the pdf, but when I scale the pdf the annotation does not scale, and it comes out out out of shape. Any suggestions? I'll show you the code of the method and the pdf before and after
PDF BEFORE
PDF AFTER
Method
public static byte[] adjustPages(byte[] content) {
try (PDDocument pdf = PDDocument.load(content)) {
float letterWidth = PDRectangle.LETTER.getWidth();
float letterHeight = PDRectangle.LETTER.getHeight();
PDPageTree tree = pdf.getDocumentCatalog().getPages();
for (PDPage page : tree) {
if (page.getMediaBox().getWidth() > letterWidth || page.getMediaBox().getHeight() > letterHeight) {
float fWidth = letterWidth / page.getMediaBox().getWidth();
float fHeight = letterHeight / page.getMediaBox().getHeight();
float factor = Math.min(fWidth, fHeight);
PDPageContentStream contentStream = new PDPageContentStream(pdf, page, PDPageContentStream.AppendMode.PREPEND, false);
contentStream.transform(Matrix.getScaleInstance(factor, factor));
contentStream.close();
page.setMediaBox(PDRectangle.LETTER);
}
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
pdf.save(baos);
return baos.toByteArray();
}catch (IOException ioe) {
return content;
}
}
As already said in a comment, for each page you should not only scale the content stream but also all other coordinates on it, in particular the rectangles of each annotation thereon. In response you asked for an example.
You can extend your inner if block to
if (page.getMediaBox().getWidth() > letterWidth || page.getMediaBox().getHeight() > letterHeight) {
float fWidth = letterWidth / page.getMediaBox().getWidth();
float fHeight = letterHeight / page.getMediaBox().getHeight();
float factor = Math.min(fWidth, fHeight);
PDPageContentStream contentStream = new PDPageContentStream(pdf, page, PDPageContentStream.AppendMode.PREPEND, false);
contentStream.transform(Matrix.getScaleInstance(factor, factor));
contentStream.close();
page.setMediaBox(PDRectangle.LETTER);
for (PDAnnotation pdAnnotation : page.getAnnotations()) {
PDRectangle rectangle = pdAnnotation.getRectangle();
PDRectangle scaled = new PDRectangle(factor * rectangle.getLowerLeftX(), factor * rectangle.getLowerLeftY(),
factor * rectangle.getWidth(), factor * rectangle.getHeight());
pdAnnotation.setRectangle(scaled);
}
}
(ScalePageWithAnnots test testForPereZix)
This will scale the rectangular area on the page into which the respective annotation is drawn.
Beware, though: Depending on the exact type of annotations in your documents, you'll have to scale other properties of the annotations, too.
On one hand there are a number of annotations with some extra coordinate information, e.g. the QuadPoints of link, markup, and redaction annotations.
On the other hand there are annotation properties relevant for a PDF viewer to create an annotation appearance if it does not have an accompanying appearance stream, e.g. border widths of annotations in general and DA font sizes for widgets of text fields.

Need help on applying drop shadow effects to a pdf box components in java

Work Story: Need to convert fabric js generated one page design json code to pdf file using java.
Issue: When i try to apply drop shadow to components like rectangle, circle or text i could not find any supported classes from PDFBox Library.
I am trying to explore how to apply drop shadow styles after creating components using pdfbox API.
Below is the code snippet for creating a rectangle. Need help after creating the component for applying drop shadow effects.
PDDocument doc = new PDDocument();
PDImageXObject pdImage = PDImageXObject.createFromFile("imagepath",doc);
PDPage page = new PDPage();
doc.addPage(page);
PDPageContentStream contentStream = new PDPageContentStream(doc, page);
int left = -60;
int top = 96;
int width = 471;
int height = 365;
contentStream.setStrokingColor(1, 0, 0);
contentStream.setLineWidth(4);
int imageOriginalWidth = 633;
int imageOriginalHeight = 422;
float scaleX = 0.99f;
float scaleY = 0.99f;
float imageWidth = imageOriginalWidth*scaleX;
float imageHeight = imageOriginalHeight*scaleY;
float imageY = page.getMediaBox().getHeight() - (top + imageHeight-58);
float imageX = -104;
contentStream.addRect(left, page.getMediaBox().getHeight() - top - height,
width, height);
contentStream.clip();
contentStream.drawImage(pdImage, imageX, imageY, imageWidth, imageHeight);
contentStream.close();
doc.save(new File(RESULT_FOLDER, "dummy.pdf"));
doc.close();
Below image is expected output of drop shadow applied rectangle:
You can add a drop shadow by first drawing a dark rectangle with transparency at a slight offset right before drawing your content. In your case e.g. like this:
...
float imageWidth = imageOriginalWidth*scaleX;
float imageHeight = imageOriginalHeight*scaleY;
float imageY = page.getMediaBox().getHeight() - (top + imageHeight-58);
float imageX = -104;
// vvv--- code added for shadow
PDExtendedGraphicsState extGS = new PDExtendedGraphicsState();
extGS.setNonStrokingAlphaConstant(.2f);
contentStream.saveGraphicsState();
contentStream.setGraphicsStateParameters(extGS);
contentStream.setNonStrokingColor(Color.BLACK);
contentStream.addRect(left + 5, page.getMediaBox().getHeight() - top - height - 5, width, height);
contentStream.fill();
contentStream.restoreGraphicsState();
// ^^^--- code added for shadow
contentStream.addRect(left, page.getMediaBox().getHeight() - top - height, width, height);
contentStream.clip();
contentStream.drawImage(pdImage, imageX, imageY, imageWidth, imageHeight);
contentStream.close();
...
(AddDropShadow test testRectangleWithDropShadow)
You might want to play around with different values for the alpha value (.2f), the color (BLACK), and the offsets (5 each) I used. Also I took the clip path as area to apply a shadow to. If your image does not fully fill that area, you might want to play around with the coordinates here.

Pdfbox : Draw image in rotated page

I have a simple A4 pdf document with a property /Rotate 90 : The original version of my pdf is landscape but printed portrait.
I am trying to draw a small image at the bottom left of the portait document.
Here is my code so far :
File file = new File("rotated90.pdf");
try (final PDDocument doc = PDDocument.load(file)) {
PDPage page = doc.getPage(0);
PDImageXObject image = PDImageXObject.createFromFile("image.jpg", doc);
PDPageContentStream contents = new PDPageContentStream(doc, page, PDPageContentStream.AppendMode.APPEND, false, true);
contents.drawImage(image, 0, 0);
contents.close();
doc.save(new File("newpdf.pdf"));
}
Here is the end result : As you can see the image was placed at the top left (which was the 0,0 coordinate before rotation) and was not rotated.
I tried playing with drawImage(PDImageXObject image, Matrix matrix) without success.
Here is the orignal document pdf with 90° rotation
Here's a solution for a page that is rotated 90°:
PDPageContentStream cs = new PDPageContentStream(doc, page, PDPageContentStream.AppendMode.APPEND, true, true);
PDImageXObject image = ....
cs.saveGraphicsState();
cs.transform(Matrix.getRotateInstance(Math.toRadians(90), page.getCropBox().getWidth() + page.getCropBox().getLowerLeftX(), 0));
cs.drawImage(image, 0, 0);
cs.restoreGraphicsState();
cs.close();
If it is only the image, then you don't need the save/restore.
Solution for a page that is rotated 270°:
cs.transform(Matrix.getRotateInstance(Math.toRadians(270), 0, page.getCropBox().getHeight() + page.getCropBox().getLowerLeftY()));
For 180°:
cs.transform(Matrix.getRotateInstance(Math.toRadians(180), page.getCropBox().getWidth() + page.getCropBox().getLowerLeftX(), page.getCropBox().getHeight() + page.getCropBox().getLowerLeftY()));

2D barcode generation issue in Java

I am generating barcodes using iText API, everything looks good when it is linear barcodes when it is 2D barcodes then barcodes are placed into pdf document as images, hence reducing the quality of the barcode on low resolution printers and unable to scan the barcode. Below is the code
BarcodePDF417 pdf417 = new BarcodePDF417();
String text = "BarcodePDF417 barcode";
pdf417.setText(text);
Image img = pdf417.getImage();
document.add(img);
Now i am look for an alternative to draw barcode and i found palceBarcode method which might favor to the requirement.
I have seen the below code in BarcodePDF417 class in itext source and could not able to find out the way how to use it
public void placeBarcode(PdfContentByte cb, BaseColor foreground, float moduleHeight, float moduleWidth) {
paintCode();
int stride = (bitColumns + 7) / 8;
cb.setColorFill(foreground);
for (int k = 0; k < codeRows; ++k) {
int p = k * stride;
for (int j = 0; j < bitColumns; ++j) {
int b = outBits[p + j / 8] & 0xff;
b <<= j % 8;
if ((b & 0x80) != 0) {
cb.rectangle(j * moduleWidth, (codeRows - k - 1) * moduleHeight, moduleWidth, moduleHeight);
}
}
}
cb.fill();
}
Can anyone suggest the way to use the above method?
I have written code like below but getting dark page as a whole.
Rectangle pageSize = new Rectangle(w * 72, h * 72);
Document doc = new Document(pageSize, 1f, 1f, 1f, 1f);
PdfWriter writer = PdfWriter.getInstance(doc, getOutputStream());
doc.open();
PdfContentByte cb = writer.getDirectContent();
BarcodePDF417 pf = new BarcodePDF417();
pf.setText("BarcodePDF417 barcode");
pf.getImage();
Rectangle rc = pf.getBarcodeSize();
pf.placeBarcode(cb, BaseColor.BLACK, rc.getHeight(), rc.getWidth());
doc.close();
Please take a look at the BarcodePlacement example. In this example, we create three PDF417 barcodes:
PdfContentByte cb = writer.getDirectContent();
Image img = createBarcode(cb, "This is a 2D barcode", 1, 1);
document.add(new Paragraph(
String.format("This barcode measures %s by %s user units",
img.getScaledWidth(), img.getScaledHeight())));
document.add(img);
img = createBarcode(cb, "This is NOT a raster image", 3, 3);
document.add(new Paragraph(
String.format("This barcode measures %s by %s user units",
img.getScaledWidth(), img.getScaledHeight())));
document.add(img);
img = createBarcode(cb, "This is vector data drawn on a PDF page", 1, 3);
document.add(new Paragraph(
String.format("This barcode measures %s by %s user units",
img.getScaledWidth(), img.getScaledHeight())));
The result looks like this on the outside:
One particular barcode looks like this on the inside:
I'm adding this inside view to show that the 2D barcode is not added as a raster image (as was the case with the initial approach you've tried). It is a vector image consisting of a series of small rectangles. You can check this for yourself by taking a look at the barcode_placement.pdf file.
Please don't be confused because I use an Image object. If you look at the createBarcode() method, you can see that the Image is, in fact, a vector image:
public Image createBarcode(PdfContentByte cb, String text,
float mh, float mw) throws BadElementException {
BarcodePDF417 pf = new BarcodePDF417();
pf.setText("BarcodePDF417 barcode");
Rectangle size = pf.getBarcodeSize();
PdfTemplate template = cb.createTemplate(
mw * size.getWidth(), mh * size.getHeight());
pf.placeBarcode(template, BaseColor.BLACK, mh, mw);
return Image.getInstance(template);
}
The height and the width passed to the placeBarcode() method, define the height and the width of the small rectangles that are drawn. If you look at the inside view, you can see for instance:
0 21 3 1 re
This is a rectangle with x = 0, y = 21, width 3 and height 1.
When you ask the barcode for its size, you get the number of rectangles that will be drawn. Hence the dimensions of the barcode is:
Rectangle size = pf.getBarcodeSize();
float width = mw * size.getWidth();
float height = mh * size.getHeight();
Your assumption that size is a size in user units is only correct if mw and mh are equal to 1.
I use these values to create a PdfTemplate instance and I draw the barcode to this Form XObject. Most of the times, it's easier to work with the Image class than working with PdfTemplate, so I wrap the PdfTemplate inside an Image.
I can then add this Image to the document just like any other image. The main difference with ordinary images, is that this image is a vector image.

Categories