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.
Related
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.
I'm using PDFBox 1.7.0 (I do not have a choice for the version due to old version in production server). I am trying to add an image to an existing PDF which has already a logo.
When I add the new image, the old one disappears like it is replaced.
// Use for convert mm to dots
// ... 72 dots per inch
static final int DEFAULT_USER_SPACE_UNIT_DPI = 72;
// ... mm -> inch -> dots
static final float MM_TO_UNITS = 1 / (10 * 2.54f) * DEFAULT_USER_SPACE_UNIT_DPI;
/**
* Add a given image to a specific page of a PDF
* #param document PDF document to manipulate
* #param input image inputStream
* #param pdfpage page number to target
* #param x image position (en mm)
* #param y image position (en mm)
* #param width max width of the image (mm)
* #param height max height of the image (en mm)
* #param opacity opacity level of the image (fraction)
*/
void addImageToPage (PDDocument document, InputStream input, int pdfpage, int x, int y, int width, int height, float opacity) throws IOException {
if (input != null) {
// Convert inputstream to usable BufferedImage
BufferedImage tmp_image = ImageIO.read (input);
// User TYPE_4BYTE_ABGR to fix PDFBox issue with transparent PNG
BufferedImage image = new BufferedImage (tmp_image.getWidth(), tmp_image.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
// Prepare the image
image.createGraphics().drawRenderedImage (tmp_image, null);
PDXObjectImage ximage = new PDPixelMap (document, image);
// Resize the image
int iWidth = ximage.getWidth();
int iHeight = ximage.getHeight();
if (width / height > iWidth / iHeight) {
ximage.setWidth (Math.round (width * MM_TO_UNITS));
ximage.setHeight (Math.round ((iHeight * width / iWidth) * MM_TO_UNITS));
} else {
ximage.setWidth (Math.round ((iWidth * height / iHeight) * MM_TO_UNITS));
ximage.setHeight (Math.round (height * MM_TO_UNITS));
}
// Retrieve the page to update
PDPage page = (PDPage)document.getDocumentCatalog().getAllPages().get (pdfpage);
PDResources resources = page.findResources();
// Get graphics states
Map graphicsStates = resources.getGraphicsStates();
if (graphicsStates == null) {
graphicsStates = new HashMap();
}
// Set graphics states configurations
PDExtendedGraphicsState extendedGraphicsState = new PDExtendedGraphicsState();
// Set the opacity of the image
extendedGraphicsState.setNonStrokingAlphaConstant (opacity);
graphicsStates.put ("TransparentState", extendedGraphicsState);
// Restore graphics states
resources.setGraphicsStates (graphicsStates);
// Retrieve the content stream
PDPageContentStream contentStream = new PDPageContentStream (document, page, true, true);
// Activate transparency options
contentStream.appendRawCommands ("/TransparentState gs\n");
contentStream.endMarkedContentSequence();
// Insert image
contentStream.drawImage (
ximage,
(float) x * MM_TO_UNITS,
(float) y * MM_TO_UNITS
);
// close the stream
contentStream.close();
}
}
I expected to have the new image within the page, but the existing image inside the page has disappeared instead of the new one.
Example of used PDF : http://www.mediafire.com/folder/g6p7c2b5ob1c7/PDFBox_issue
There are several bugs in 1.7... one I mentioned in a comment (turns out it doesn't affect you), the other one is that the resources does some caching but isn't managed properly… long story short, you need to save and restore your xobject resources like this:
Map<String, PDXObject> xObjectsMap = page.getResources().getXObjects(); // save xobjects
…
PDXObjectImage ximage = new PDPixelMap (document, image);
String imgName = page.getResources().addXObject(ximage, "Im");
cs.drawImage(ximage, 0, 0); // bug happens here, old xobjects gets lost
xObjectsMap.put(imgName, ximage);
page.getResources().setXObjects(xObjectsMap); // restore xobjects
This is really just a workaround… there may be more bad surprises coming. You should not use old versions. They no longer spark joy. You should thank them for their service and then let them go without guilt.
Ok. I have given up trying to use PDFbox 1.7 for this part of the development. It requirement to many fixes to implements few things. It is not really maintainable for the future works. Thanks to everyone for the hints and helps.
I'm using iText for stamping a watermark (text: "SuperEasy You Done") on PDF files as described in How to watermark PDFs using text or images? (TransparentWatermark2.java). See project source code on GitHub.
Now an example of the PDF I'm getting is this one (the rest of the document is omitted):
As you can see the watermark is centered and horizontal.
I'd like to keep it centered in the middle of the page, but rotate it "45" degrees, so it rotates anticlockwise. Something like this:
This is the code for stamping the watermark on a given byte array (pdf documents only for me right now)
/**
* Returns the same document with the watermark stamped on it.
* #param documentBytes Byte array of the pdf which is going to be returned with the watermark
* #return byte[] with the same byte array provided but now with the watermark stamped on it.
* #throws IOException If any IO exception occurs while adding the watermark
* #throws DocumentException If any DocumentException exception occurs while adding the watermark
*/
private byte[] getDocumentWithWaterMark(byte[] documentBytes) throws IOException, DocumentException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// pdf
PdfReader reader = new PdfReader(documentBytes);
int n = reader.getNumberOfPages();
PdfStamper stamper = new PdfStamper(reader, outputStream);
// text watermark
Font font = new Font(Font.HELVETICA, 60);
Phrase phrase = new Phrase("SuperEasy You Done", font);
// transparency
PdfGState gs1 = new PdfGState();
gs1.setFillOpacity(0.06f);
// properties
PdfContentByte over;
Rectangle pagesize;
float x, y;
// loop over every page (in case more than one page)
for (int i = 1; i <= n; i++) {
pagesize = reader.getPageSizeWithRotation(i);
x = (pagesize.getLeft() + pagesize.getRight()) / 2;
y = (pagesize.getTop() + pagesize.getBottom()) / 2;
over = stamper.getOverContent(i);
over.saveState();
over.setGState(gs1);
// add text
ColumnText.showTextAligned(over, Element.ALIGN_CENTER, phrase, x, y, 0);
over.restoreState();
}
stamper.close();
reader.close();
return outputStream.toByteArray();
}
PS: I read this, but it didn't help:
http://itext.2136553.n4.nabble.com/rotate-a-watermark-td2155042.html
You just need to specify the desired rotation angle as the 6th parameter in this line:
ColumnText.showTextAligned(over, Element.ALIGN_CENTER, phrase, x, y, 0); // rotate 0 grades in this case
If the specified value is positive ( > 0) the rotation is anticlockwise, otherwise (< 0) the rotation is clockwise.
In this particular case, for rotating the watermark 45 degrees anticlockwise you just need to write the previous line like this:
ColumnText.showTextAligned(over, Element.ALIGN_CENTER, phrase, x, y, 45f); // 45f means rotate the watermark 45 degrees anticlockwise
By applying this same principle we can achieve any rotation in any direction.
The whole documentation is here: https://itextpdf.com/en/resources/api-documentation under the links for version 5 and version 7.
I am working with Apache PDFBox 2.0.8. My Objective is to convert a PDF into Image and enlarge the canvas and put the contents in the center so that i can put some header and footer in the remaining space.
My issue is that the canvas is getting enlarged but the contents are not getting centered, they are stick to the bottom.
public class PDFRescale {
public static void main(String[] args) {
try {
String pdfFilename = "/MuhimbiPOC/Templates/Source_doc_withheaderfooter.pdf";
PDDocument document = PDDocument.load(new File(pdfFilename));
PDFRenderer pdfRenderer = new PDFRenderer(document);
PDPage pge = new PDPage();
PDFRescale ps = new PDFRescale();
int pageCounter = 0;
for (PDPage page : document.getPages())
{
final PDRectangle mediaBox = pge.getMediaBox();
mediaBox.setUpperRightX((float) (mediaBox.getUpperRightX()));
mediaBox.setUpperRightY((float) (mediaBox.getUpperRightY() * 1.5));
mediaBox.setLowerLeftY((float) (mediaBox.getLowerLeftY() * 1.5));
// note that the page number parameter is zero based
page.setMediaBox(mediaBox);
BufferedImage bim = pdfRenderer.renderImageWithDPI(pageCounter, 140, ImageType.RGB);
// suffix in filename will be used as the file format
ImageIOUtil.writeImage(bim, pdfFilename + "-" + (pageCounter++) + ".png", 140);
}
System.out.println("Task Completed ... ");
document.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
My issue is that the canvas is getting enlarged but the contents are not getting centered, they are stick to the bottom.
That is your issue for PDF pages whose mediaBox.getLowerLeftY() is 0. While this is very common, it is not required. If you had worked with a more generic selection of PDFs, you'd have seen that your issue is that the former contents eventually can be anywhere, even off-screen!
The cause is that you do
mediaBox.setUpperRightY((float) (mediaBox.getUpperRightY() * 1.5));
mediaBox.setLowerLeftY((float) (mediaBox.getLowerLeftY() * 1.5));
This would only work if the origin was somewhere on the horizontal mid-screen axis.
Instead use something like
mediaBox.setUpperRightY(mediaBox.getUpperRightY() + mediaBox.getHeight() * 0.5f);
mediaBox.setLowerLeftY(mediaBox.getLowerLeftY() - mediaBox.getHeight() * 0.5f);
Another issue of your code: you only set the MediaBox and ignore the CropBox. pdfRenderer.renderImageWithDPI on the other hand uses the CropBox. Only for PDF pages without explicit CropBox your code enlarges the page area. For a generic solution you should also adapt the CropBox.
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.