Scale image to fill multiple pages with iText - itext

I am trying to scale an image with iText (on a new PDF document) in order to make it fill the width of the page without streching, so that it could take several pages.
I've found a lot of solutions but they were pretty complicated and I don't really like coding like that. The best solution I've found till now (from another question on SO) is using PdfTable but it always uses a single page, scaling the image.
// Load image from external storage
Image image = Image.getInstance(path + "/img.png");
// Calculate ratio
float width = PageSize.A4.getWidth();
float heightRatio = image.getHeight() * width / image.getWidth();
Document document = new Document();
document.open();
PdfPTable table = new PdfPTable(1);
table.setWidthPercentage(100);
PdfPCell c = new PdfPCell(image, true);
c.setBorder(PdfPCell.NO_BORDER);
c.setPadding(0);
// Set image dimensions
c.getImage().scaleToFit(width, heightRatio);
table.addCell(c);
document.add(table);
// Write PDF file
document.close();
Any suggestions?

Ok I finally decided to go the way I didn't want to go, since it seems to be the only way: adding the same image to every page and setting the proper vertical offset to each one. The offset gets calculated as the number of pages left to draw + the gap the remains blank. For each step I decrement the number of pages until there's nothing left to draw.
// Open new PDF file
Document document = new Document();
PdfWriter pdfWriter = PdfWriter.getInstance(document, new FileOutputStream(getSharedDirPath() + File.separator + "file.pdf"));
document.open();
PdfContentByte content = pdfWriter.getDirectContent();
// Load image from external folder
Image image = Image.getInstance(path + "/img.png");
image.scaleAbsolute(PageSize.A4);
image.setAbsolutePosition(0, 0);
float width = PageSize.A4.getWidth();
float heightRatio = image.getHeight() * width / image.getWidth();
int nPages = (int) (heightRatio / PageSize.A4.getHeight());
float difference = heightRatio % PageSize.A4.getHeight();
while (nPages >= 0) {
document.newPage();
content.addImage(image, width, 0, 0, heightRatio, 0, -((--nPages * PageSize.A4.getHeight()) + difference));
}
// Write PDF file
document.close();
Honestly I don't like this solution, I thought it was possible to auto-adjust dimensions as I do in a text editor, but after all it was not very difficult.....it just took me three days to figure out how the whole PDF thing worked.

Related

ITextSharp - Draw Rectangle top left corner

I'm trying to draw a rectangle to the very top left of a page using ITextSharp (5.5.13). I want to draw in the page margins. However, the rectangle is around 25 pixels too low. How can I draw the rectangle in the top left corner?
Below is how I'm adding the rectangle to the page:
using (PdfReader reader = new PdfReader(inputPdf.FullName))
using (PdfStamper stamper = new PdfStamper(reader, new FileStream(outputPdf.FullName, FileMode.Create)))
{
PdfContentByte contentByte = stamper.GetOverContent(1);
PdfDocument doc = contentByte.PdfDocument;
float X = 0.0f;
float Y = 0.0f;
float Height = Utilities.InchesToPoints(0.50f);
float Width = Utilities.InchesToPoints(0.50f);
float llx = (doc.Left - doc.LeftMargin) + X;
float lly = (doc.Top - doc.TopMargin) - (Height + Y);
float urx = (doc.Left - doc.LeftMargin) + Width + X;
float ury = (doc.Top - doc.TopMargin) - Y;
Rectangle rectangle = new Rectangle(llx, lly, urx, ury)
{
BackgroundColor = BaseColor.BLACK
};
contentByte.Rectangle(rectangle);
}
Below are the debug values for each aforementioned variable:
Whenever you use a PdfStamper, the PdfDocument you can retrieve from its parts does not contain sensible information, it merely is a dummy object.
Thus, don't try to determine the page size from that PdfDocument doc, instead use the appropriate methods or properties of your PdfReader reader, e.g.
/** Gets the crop box without taking rotation into account. This
* is the value of the /CropBox key. The crop box is the part
* of the document to be displayed or printed. It usually is the same
* as the media box but may be smaller. If the page doesn't have a crop
* box the page size will be returned.
* #param index the page number. The first page is 1
* #return the crop box
*/
virtual public Rectangle GetCropBox(int index)

How to create a multi page TOC using itext?

I need to create TOC in a PDF. It can be of 1 page or multi pages depending on the number of pages in PDF. I have learnt that PdfStamper, PdfAction, PdfAnnotaion can be used to achieve this.
I am currently merging more than one document and creating both bookmarks and TOC for all the documents in JAVA. I have got rid of bookmarks but got stuck on multi-pages TOC.
Also, please explain this line of your code - link = new PdfAnnotation(copy, 36, ct.getYLine(), 559, y, action);... What I have understood is you are passing the dimensions of a rectangle on the page on which it will be going after clicking the link (, 36, ct.getYLine(), 559, y, ).. And thus I am facing an issue of not going to correct position of the page on clicking the link, in the case if page size is different from US Letter Portrait.
Here is the snippet -
int tocPages = 1;
Document tocDocument = new Document();
String tocFilename ="toc-filename";
Phrase tocPhrase =
new Phrase("Table of Contents", new Font(Font.FontFamily.HELVETICA, 20, Font.BOLD, BaseColor.BLACK));
PdfWriter writer = PdfWriter.getInstance(tocDocument, new FileOutputStream(tocFilename));
tocDocument.open();
tocDocument.add(new Paragraph(tocPhrase));
PdfReader reader = new PdfReader(tocFilename);
page = copy.getImportedPage(reader, tocPages);
stamp = copy.createPageStamp(page);
float y = 770;
ColumnText ct = new ColumnText(stamp.getOverContent());
ct.setSimpleColumn(36, 36, 559, y);
for (Map.Entry<Integer, String> entry : toc.entrySet()) {
if (y <= 20) {
copy.addPage(page);
copy.newPage(); //(tried with writer.newPage() and tocDocument.newPage(), not working )
page = copy.getImportedPage(reader, ++tocPages);
}
p = new Paragraph(entry.getValue());
p.add(new Chunk(new DottedLineSeparator()));
p.add(String.valueOf(entry.getKey() + 1));
ct.addElement(p);
ct.go();
action = PdfAction.gotoLocalPage("p" + entry.getKey(), false);
link = new PdfAnnotation(copy, 36, ct.getYLine(), 559, y, action);
stamp.addAnnotation(link);
y = ct.getYLine();
}
ct.go();
stamp.alterContents();
copy.addPage(page);
tocDocument.close();
reader.close();
com.itextpdf.text.exceptions.InvalidPdfException: PDF header signature not found.
at com.itextpdf.text.pdf.PRTokeniser.getHeaderOffset(PRTokeniser.java:227)
at com.itextpdf.text.pdf.PdfReader.getOffsetTokeniser(PdfReader.java:442)
at com.itextpdf.text.pdf.PdfReader.(PdfReader.java:176)
at com.itextpdf.text.pdf.PdfReader.(PdfReader.java:219)
at com.itextpdf.text.pdf.PdfReader.(PdfReader.java:207)
at com.itextpdf.text.pdf.PdfReader.(PdfReader.java:197)
Excetion at line - PdfReader reader = new PdfReader(tocFilename);
You have adapted your question so that it became a question about this snippet from my answer to Create Index File(TOC) for merged pdf using itext library in java
Paragraph p;
PdfAction action;
PdfAnnotation link;
float y = 770;
ColumnText ct = new ColumnText(stamp.getOverContent());
ct.setSimpleColumn(36, 36, 559, y);
for (Map.Entry<Integer, String> entry : toc.entrySet()) {
p = new Paragraph(entry.getValue());
p.add(new Chunk(new DottedLineSeparator()));
p.add(String.valueOf(entry.getKey()));
ct.addElement(p);
ct.go();
action = PdfAction.gotoLocalPage("p" + entry.getKey(), false);
link = new PdfAnnotation(copy, 36, ct.getYLine(), 559, y, action);
stamp.addAnnotation(link);
y = ct.getYLine();
}
ct.go();
Your question was reduced to: please explain this line of your code:
link = new PdfAnnotation(copy, 36, ct.getYLine(), 559, y, action);
I have pasted the complete snippet, because this line can not be explained out of context,
In the snippet, we create a link annotation using the PdfAnnotation class. A link annotation is a area somewhere on a page, that triggers an action when clicked.
Which action is triggered in this case? That's what the action object is about, in this case, it jumps to a local page that is defined by a named destination. This is very similar to HTML where you have <a name="dest" /> and Jump to a specific destination on the current page.
When creating a PdfAnotation, you always need a PdfWriter instance. In this case, we are merging documents using a PdfCopy instance named copy. As PdfCopy extends PdfWriter, we can pass the copy instance as a parameter.
Finally, we define the clickable area. This is always a rectangle that is defined using two coordinates: the coordinate of the lower-left corner and the coordinate of the upper-right corner.
In the above snippet, we add paragraphs using ColumnText. ColumnText allows you to get information about the current y position after adding content. For instance, when we do this:
ct.addElement(p);
ct.go();
We can do this to get the current Y-coordinate:
float y = ct.getYLine();
In our code snippet, we keep track of the previous y value (the Y position before we add p) and we use the current value of ct.getYLine() to get the current y position.
This way, I can define the coordinate of the lower-left corner like this:
float llx = 36;
float lly = ct.getYLine();
And the coordinate of the upper-right corner like this:
float urx = 559;
float ury = y;
These are the values that you can see when we construct the link annotation.
I have hardcode the x values. They are based on the fact that I am creating a document with pages of size A4 and margins of half an inch. An A-4 page is 595 user units wide. To the left, I have a margin of 36 user units; to the right I also have a margin of 36 user units, which I have to subtract from the width of the page: 595 - 36 = 559.
If you have a page of which the format is LETTER, you need to adapt these values. However: it's better to calculate them based on the actual value of the MediaBox / CropBox of the existing page. This way, your code keeps working when you're accidentally confronted with documents that have a different page size.
You can read more about the MediaBox and the CropBox in my answer to this question: How to get dimensions of each page of a pdf file

iText - How to stamp image on existing PDF and create an anchor

I have an existing document, onto which I would like to stamp an image at an absolute position.
I am able to do this, but I would also like to make this image clickable: when a user clicks
on the image I would like the PDF to go to the last page of the document.
Here is my code:
PdfReader readerOriginalDoc = new PdfReader("src/main/resources/test.pdf");
PdfStamper stamper = new PdfStamper(readerOriginalDoc,new FileOutputStream("NewStamper.pdf"));
PdfContentByte content = stamper.getOverContent(1);
Image image = Image.getInstance("src/main/resources/images.jpg");
image.scaleAbsolute(50, 20);
image.setAbsolutePosition(100, 100);
image.setAnnotation(new Annotation(0, 0, 0, 0, 3));
content.addImage(image);
stamper.close();
Any idea how to do this ?
You are using a technique that only works when creating documents from scratch.
Please take a look at the AddImageLink example to find out how to add an image and a link to make that image clickable to an existing document:
public void manipulatePdf(String src, String dest) throws IOException, DocumentException {
PdfReader reader = new PdfReader(src);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
Image img = Image.getInstance(IMG);
float x = 10;
float y = 650;
float w = img.getScaledWidth();
float h = img.getScaledHeight();
img.setAbsolutePosition(x, y);
stamper.getOverContent(1).addImage(img);
Rectangle linkLocation = new Rectangle(x, y, x + w, y + h);
PdfDestination destination = new PdfDestination(PdfDestination.FIT);
PdfAnnotation link = PdfAnnotation.createLink(stamper.getWriter(),
linkLocation, PdfAnnotation.HIGHLIGHT_INVERT,
reader.getNumberOfPages(), destination);
link.setBorder(new PdfBorderArray(0, 0, 0));
stamper.addAnnotation(link, 1);
stamper.close();
}
You already have the part about adding the image right. Note that I create parameters for the position of the image as well as its dimensions:
float x = 10;
float y = 650;
float w = img.getScaledWidth();
float h = img.getScaledHeight();
I use these values to create a Rectangle object:
Rectangle linkLocation = new Rectangle(x, y, x + w, y + h);
This is the location for the link annotation we are creating with the PdfAnnotation class. You need to add this annotation separately using the addAnnotation() method.
You can take a look at the result here: link_image.pdf
If you click on the i icon, you jump to the last page of the document.

How to scale a page less then 1 percent?

I want to scale a full pdf-page and insert into an new document. This works fine. But i have problems with the scale-factor when the factor is less than 1%. for example with a factor of 0,5%, no scale is happening. With a factor from 0,7%, the document (rectagle) has the correct size, but the scaled page is bigger then the calculated new size. Is there a way to scale a page continuously? It looks like, that in such cases with very small scale factors, the scaling works only in stages. Or maybe is there a problem with the internal matrix-calculation?
float scale = 1.005f; //100,5%
var newWidth = originWidth*scale;
var newHeight = originHeight*scale;
PdfReader reader = new PdfReader(inputfile);
var newMediaBox = new Rectangle(newWidth, newHeight);
Document doc = new Document(newMediaBox);
PdfWriter writer = PdfWriter.GetInstance(doc, new FileStream(outputfile,FileMode.Create));
doc.Open();
PdfContentByte cb = writer.DirectContent;
PdfImportedPage page = writer.GetImportedPage(reader, 1); //page #1
cb.AddTemplate(page, scale, 0, 0, scale, 0, 0);
doc.Close();
The other way: Can i calculate or read out the new size after the scaling?
Thanks.
iTextSharp by default will use two decimal places rounded when adding the template. You can change this by setting the HIGH_PRECISION static variable of the ByteBuffer class to true which will give you six decimal places. I do not know if this will affect your overall application performance but I'm fairly confident that it won't.
iTextSharp.text.pdf.ByteBuffer.HIGH_PRECISION = true;

How to calculate the correct image size in out pdf using itextsharp?

I' am trying to add an image to a pdf using itextsharp, regardless of the image size it always appears to be mapped to a different greater size inside the pdf ?
The image I add is 624x500 pixel (DPI:72):
alt text http://www.freeimagehosting.net/uploads/727711dc70.png
And here is a screen of the output pdf:
alt text http://www.freeimagehosting.net/uploads/313d49044d.png
And here is how I created the document:
Document document = new Document();
System.IO.MemoryStream stream = new MemoryStream();
PdfWriter writer = PdfWriter.GetInstance(document, stream);
document.Open();
System.Drawing.Image pngImage = System.Drawing.Image.FromFile("test.png");
Image pdfImage = Image.GetInstance(pngImage, System.Drawing.Imaging.ImageFormat.Png);
document.Add(pdfImage);
document.Close();
byte[] buffer = stream.GetBuffer();
FileStream fs = new FileStream("test.pdf", FileMode.Create);
fs.Write(buffer, 0, buffer.Length);
fs.Close();
Any idea on how to calculate the correct size ?
I alreay tried ScaleAbsolute and the image still renders with incorrect dimensions.
I forget to mention that I' am using itextsharp 5.0.2.
It turned out that PDF DPI = 110, which means 110 pixels per inch, and since itextsharp uses points as measurment unit then :
n pixels = n/110 inches.
n inches = n * 72 points.
Having a helper method to convert pixels to points is all I needed:
public static float PixelsToPoints(float value,int dpi)
{
return value / dpi * 72;
}
By using the above formula and passing a dpi value of 110 it worked perfectly:
Note: Since you can create pdf documents in any size you want, this may lead to incorrect scaling when printing out your documents. To overcome this issue all you need to do is to have the correct aspect ratio between width and height [approximately 1:1.4142] (see : Paper Size - The international standard: ISO 216 ).
Multiply the image's height and width by 72 and divide them by the dpi(ppi): points = pixels * 72 / dpi.