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()));
Now I used pdfCanvas to graph a rectangle, code like below:
PdfPage page = pdf.getFirstPage();
PdfCanvas canvas = new PdfCanvas(page, true);
float x = 35;
float y = 480;
canvas.rectangle(x, y, 30, 30).stroke();
In fact, I want to add the rectangle into a table
Table table = new Table(2);
table.setWidth(261.5f);
iText 5, the canvas can be set to an image object and add to table. But in iText 7 the function doesn't work. How can i draw a picture to a table in iText7?
You can create a PdfCanvas from a standalone XObject, transform that XObject into an Image and then add the Image to the Table:
Rectangle boundingBox = new Rectangle(20,470,50,50);
PdfFormXObject xObject = new PdfFormXObject(boundingBox);
xObject.makeIndirect(pdfDoc);//Make sure the XObject gets added to the document
PdfCanvas canvas = new PdfCanvas(xObject, pdfDoc);//Create a canvas from the XObject
canvas.setStrokeColor(Color.BLUE).setLineWidth(3f).rectangle(35, 480, 30, 30).stroke();
Image rect = new Image(xObject);
table.addCell(rect);
There is a comprehensive tutorial on the developers corner of the iText website, explaining (through various examples) how to add an image to a table.
Check out http://developers.itextpdf.com/examples/tables/clone-adding-images-table
I did split PDF to JPEG images using PDFBox version 2.0.2. At first, I did just coding as sample like thatÖ
BufferedImage image = pdfRenderer.renderImageWithDPI(pageCounter, 300, ImageType.RGB);
And now, I want to convert this image to PDF, but the image DPI is so large.
I really want to reduce dpi. So I tried this, but it also didn't work:
PDImageXObject pdImageXObject = JPEGFactory.createFromImage(doc, bimg, 0.5f, 100);
How can I reduce DPI?
This is my source code:
InputStream in = new FileInputStream(imagePath);
BufferedImage bimg = ImageIO.read(in);
float width = bimg.getWidth() ;
float height = bimg.getHeight();
PDPage page = new PDPage(new PDRectangle(width, height));
doc.addPage(page);
//PDStream stream = new PDStream(doc, in);
PDImageXObject pdImageXObject = JPEGFactory.createFromImage(doc, bimg, 0.5f, 10);
PDPageContentStream contentStream = new PDPageContentStream(doc, page);
contentStream.drawImage(pdImageXObject, 0, 0);
contentStream.close();
}
} finally {
System.out.println("ddd");
doc.save(pdfPath);
doc.close();
}
}
There are 2 problems:
1) page size. It is not done in pixels but in page units. 1 unit = 1/72 inch. So your rectangle would be calculated like this:
PDPage page = new PDPage(new PDRectangle(width / 300 * 72, height / 300 * 72));
2) scale image. At 300dpi it must be scaled by 72/300 because the 1:1 is 72 dpi.
float scale = 72 / 300;
contentStream.drawImage(pdImage, 0, 0, pdImage.getWidth()*scale, pdImage.getHeight()*scale);
Btw using JPEGFactory is not a good idea, because some quality will be lost. Use LosslessFactory instead.
About your use of the dpi parameter of JPEGFactory - that is just metadata. It doesn't scale anything.
If you really want to "reduce dpi", then render the PDF at 72 dpi instead of 300, then you don't need the scaling when creating the new PDF.
I had a similar problem and I solved it using the following:
PDPage page = new PDPage(PDRectangle.A4);
PDImageXObject pdImage = PDImageXObject.createFromFile(imgFile, doc);
PDPageContentStream contents = new PDPageContentStream(doc, page, false, false);
contents.drawImage(pdImage, 0, 0, PDRectangle.A4.getWidth(), PDRectangle.A4.getHeight());
I am trying to use Java with PDFBox to draw some text to a PDF file, and set a background color for the text. I know how to draw text and draw filled rectangles, but when I try to draw text in the same position as a rectangle, the text is never shown. Example:
//draw rectangle
content.setNonStrokingColor(200, 200, 200); //gray background
content.fillRect(cursorX, cursorY, 100, 50);
//draw text
content.setNonStrokingColor(0, 0, 0); //black text
content.beginText();
content.setFont(family, fontPt);
content.moveTextPositionByAmount(cursorX, cursorY);
content.drawString("Test Data");
content.endText();
The text never shows up. It is always covered by the rectangle. Any ideas for how to make the text draw on top of the rectangle?
EDIT: As Mkl mentioned in answer, the code I provided actually works. My problem ended up being that the code was in a loop, drawing the background for each line, but the background was drawing over the previous line, and not the current line, overwriting previous text. I just needed to alter the order of events in my looping. Should this question be deleted? It seems unlikely that anyone else would find it useful.
The code you show works.
I made it runnable like this:
PDDocument document = new PDDocument();
PDPage page = new PDPage();
document.addPage(page);
PDPageContentStream content = new PDPageContentStream(document, page);
PDFont font = PDType1Font.HELVETICA_BOLD;
int cursorX = 70;
int cursorY = 500;
//draw rectangle
content.setNonStrokingColor(200, 200, 200); //gray background
content.fillRect(cursorX, cursorY, 100, 50);
//draw text
content.setNonStrokingColor(0, 0, 0); //black text
content.beginText();
content.setFont(font, 12);
content.moveTextPositionByAmount(cursorX, cursorY);
content.drawString("Test Data");
content.endText();
content.close();
document.save(new File("textOnBackground.pdf"));
document.close();
(DrawOnBackground.java)
And the result looks like this:
Thus, the cause for your issue lies beyond the code you provided.
PS: I use PDFBox 1.8.10.
I would like to add a link in my overlay text. I've read that using Anchor will only work for documents made from scratch but not for existing pdfs. My code is adding an overlay text to every page. My goal is to make a portion of that text clickable. I don't know how to make a link annotation that is part of a phrase.
Here's my code:
int n = reader.getNumberOfPages();
// step 4: we add content
PdfImportedPage page;
PdfCopy.PageStamp stamp;
for (int j = 0; j < n; )
{
++j;
page = writer.getImportedPage(reader, j);
if (i == 1) {
stamp = writer.createPageStamp(page);
Rectangle mediabox = reader.getPageSize(j);
Rectangle crop = new Rectangle(mediabox);
writer.setCropBoxSize(crop);
// add overlay text
Paragraph p = new Paragraph();
p.setAlignment(Element.ALIGN_CENTER);
FONT_URL_OVERLAY.setColor(0, 191, 255);
// get current user
EPerson loggedin = context.getCurrentUser();
String eperson = null;
if (loggedin != null)
{
eperson = loggedin.getFullName();
}
else eperson = "Anonymous";
Phrase downloaded = new Phrase();
Chunk site = new Chunk("My Website",FONT_URL_OVERLAY);
site.setAction(new PdfAction("http://www.mywebsite.com"));
downloaded.add(new Chunk("Downloaded by [" + eperson + "] from ", FONT_OVERLAY));
downloaded.add(site);
downloaded.add(new Chunk(" on ", FONT_OVERLAY));
downloaded.add(new Chunk(new SimpleDateFormat("MMMM d, yyyy").format(new Date()), FONT_OVERLAY));
downloaded.add(new Chunk(" at ", FONT_OVERLAY));
downloaded.add(new Chunk(new SimpleDateFormat("h:mm a z").format(new Date()), FONT_OVERLAY));
p.add(downloaded);
ColumnText.showTextAligned(stamp.getOverContent(), Element.ALIGN_CENTER, p,
crop.getLeft(10), crop.getHeight() / 2 + crop.getBottom(), 90);
stamp.alterContents();
}
writer.addPage(page);
}
So my overlay would looked like this:
Downloaded by [Anonymous] from My Website on February 17, 2015 at 1:20 AM CST
How can I convert My Website to a link annotation? Searching here in SO, I found this post, but I don't know how to apply adding link annotation to a portion of my overlay text.
Thanks in advance.
EDIT: How to add a rotated overlay text with link annotations to existing pdf?
Thanks to Bruno Lowagie for going out of his way in answering my question. Although I originally asked how to add link annotations in an overlay text to existing pdfs, he also catered my questions in the comments section of his answer about setting the coordinates properly if the overlay text were rotated.
You are using ColumnText.showAligned() which is sufficient to add a line of text without any special features, but if you want the anchor to work, you need to use ColumnText differently.
This is shown in the AddLinkAnnotation2 example:
public void manipulatePdf(String src, String dest) throws IOException, DocumentException {
PdfReader reader = new PdfReader(src);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
PdfContentByte canvas = stamper.getOverContent(1);
Font bold = new Font(FontFamily.HELVETICA, 12, Font.BOLD);
Chunk chunk = new Chunk("The Best iText Questions on StackOverflow", bold);
chunk.setAnchor("http://pages.itextpdf.com/ebook-stackoverflow-questions.html");
Phrase p = new Phrase("Download ");
p.add(chunk);
p.add(" and discover more than 200 questions and answers.");
ColumnText ct = new ColumnText(canvas);
ct.setSimpleColumn(36, 700, 559, 750);
ct.addText(p);
ct.go();
stamper.close();
reader.close();
}
In this case, we define a rectangle for a ColumnText object, we add the Phrase to the column, and we go().
If you check the result, link_annotation2.pdf, you'll notice that you can click the words in bold.
There are no plans to support this in ColumnText.showTextAligned(). That is a convenience method that can be used as a short-cut for the handful of lines shown above, but there are some known limitations: lines are not wrapped, interactivity is ignored,...
Update 1: in the comment section, you asked an additional question about rotation the content and the link.
Rotating the content isn't difficult. There's even more than one way to do that. Rotating the link isn't trivial, as a link is a type of annotation, and annotations aren't part of the content.
Let's first take a look at AddLinkAnnotation3:
public void manipulatePdf(String src, String dest) throws IOException, DocumentException {
PdfReader reader = new PdfReader(src);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
AffineTransform transform = AffineTransform.getRotateInstance(Math.PI / 6);
stamper.getWriter().setPageEvent(new AddAnnotation(stamper, transform));
PdfContentByte canvas = stamper.getOverContent(1);
Font bold = new Font(FontFamily.HELVETICA, 12, Font.BOLD);
Chunk chunk = new Chunk("The Best iText Questions on StackOverflow", bold);
chunk.setGenericTag("http://pages.itextpdf.com/ebook-stackoverflow-questions.html");
Phrase p = new Phrase("Download ");
p.add(chunk);
p.add(" and discover more than 200 questions and answers.");
canvas.saveState();
canvas.concatCTM(transform);
ColumnText ct = new ColumnText(canvas);
ct.setSimpleColumn(300, 0, 800, 400);
ct.addText(p);
ct.go();
canvas.restoreState();
stamper.close();
reader.close();
}
In this example, we define a tranformation of 30 degrees (Math.PI / 6):
AffineTransform transform = AffineTransform.getRotateInstance(Math.PI / 6);
We use this transformation when rendering the column:
canvas.saveState();
canvas.concatCTM(transform);
// render column
canvas.restoreState();
This rotates the content, but we didn't add any annotation yet. Instead, we define a page event:
stamper.getWriter().setPageEvent(new AddAnnotation(stamper, transform));
and we introduced a generic tag:
chunk.setGenericTag("http://pages.itextpdf.com/ebook-stackoverflow-questions.html");
To add the annotation, we use some magic in the page event implementation:
public class AddAnnotation extends PdfPageEventHelper {
protected PdfStamper stamper;
protected AffineTransform transform;
public AddAnnotation(PdfStamper stamper, AffineTransform transform) {
this.stamper = stamper;
this.transform = transform;
}
#Override
public void onGenericTag(PdfWriter writer, Document document, Rectangle rect, String text) {
float[] pts = {rect.getLeft(), rect.getBottom(), rect.getRight(), rect.getTop()};
transform.transform(pts, 0, pts, 0, 2);
float[] dstPts = {pts[0], pts[1], pts[2], pts[3]};
rect = new Rectangle(dstPts[0], dstPts[1], dstPts[2], dstPts[3]);
PdfAnnotation annot = PdfAnnotation.createLink(writer, rect, PdfAnnotation.HIGHLIGHT_INVERT, new PdfAction(text));
stamper.addAnnotation(annot, 1);
}
}
We create an annotation, but before we do so, we perform a transformation on the rectangle. This makes sure that the text fits the rectangle with the text that needs to be clickable, but... this may not be what you expect:
You may have wanted the rectangle to be rotated, and that's possible, but it's more math. For instance: you could create a polygon that is a better fit: ITextShape Clickable Polygon or path
Fortunately, you don't need an angle of 30 degrees, you want to rotate the text with an angle of 90 degrees. In that case, you don't have the strange effect shown in the above screen shot.
Take a look at AddLinkAnnotation4
public class AddAnnotation extends PdfPageEventHelper {
protected PdfStamper stamper;
protected AffineTransform transform;
public AddAnnotation(PdfStamper stamper, AffineTransform transform) {
this.stamper = stamper;
this.transform = transform;
}
#Override
public void onGenericTag(PdfWriter writer, Document document, Rectangle rect, String text) {
float[] pts = {rect.getLeft(), rect.getBottom(), rect.getRight(), rect.getTop()};
transform.transform(pts, 0, pts, 0, 2);
float[] dstPts = {pts[0], pts[1], pts[2], pts[3]};
rect = new Rectangle(dstPts[0], dstPts[1], dstPts[2], dstPts[3]);
PdfAnnotation annot = PdfAnnotation.createLink(writer, rect, PdfAnnotation.HIGHLIGHT_INVERT, new PdfAction(text));
annot.setBorder(new PdfBorderArray(0, 0, 0));
stamper.addAnnotation(annot, 1);
}
}
As you can see, I've added a single line to remove the border (the border is there by default unless you redefine the PdfBorderArray).
The rest of the code is also almost identical. We now define an angle of Math.PI / 2 (90 degrees).
public void manipulatePdf(String src, String dest) throws IOException, DocumentException {
PdfReader reader = new PdfReader(src);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
AffineTransform transform = AffineTransform.getRotateInstance(Math.PI / 2);
stamper.getWriter().setPageEvent(new AddAnnotation(stamper, transform));
PdfContentByte canvas = stamper.getOverContent(1);
Font bold = new Font(FontFamily.HELVETICA, 12, Font.BOLD);
Chunk chunk = new Chunk("The Best iText Questions on StackOverflow", bold);
chunk.setGenericTag("http://pages.itextpdf.com/ebook-stackoverflow-questions.html");
Phrase p = new Phrase("Download ");
p.add(chunk);
p.add(" and discover more than 200 questions and answers.");
canvas.saveState();
canvas.concatCTM(transform);
ColumnText ct = new ColumnText(canvas);
ct.setSimpleColumn(36, -559, 806, -36);
ct.addText(p);
ct.go();
canvas.restoreState();
stamper.close();
reader.close();
}
Note that the lower left corner of the page is the pivot point, hence we need to adapt the coordinates where we add the column, otherwise you'll rotate all the content outside the visible area of the page.
Update 2:
In yet another comment, you are asking about the coordinates you need to use when adding text in a rotated coordinate system.
I made this drawing:
In the top part, you add the word MIDDLE in the middle of a page, but that's not where it will appear: you are rotating everything by 90 degrees, hence the word MIDDLE will rotate outside your page (into the hatched area). The word will be in the PDF, but you'll never see it.
If you look at my code, you see that I use these coordinates:
ct.setSimpleColumn(36, -559, 806, -36);
This is outside the visible area (it's below the actual page dimensions), but as I rotate everything with 90 degrees, it rotates into the visible area.
If you look at my drawing, you can see that the page with coordinates (0, 0), (0, -595), (842, -598) and (842, 0) rotates by 90 degrees and thus gets the coincides with a page with coordinates (0, 0), (595, 0), (595, 842) and (0, 842). That's the type of Math we all learned in high school ;-)
You were adding text at position crop.getLeft(10), crop.getHeight() / 2 + crop.getBottom(). If you know that the text will be rotated by 90 degrees, you should use crop.getHeight() / 2 + crop.getBottom(), -crop.getLeft().
The best way to understand why, is to make a drawing.