Total page number with itextpdf - itext

I am using itextpdf-5.5.6 for publish my data.
I'm trying to set page numbers in PdfPCell of my pdf using following format : page_num/total_page_num
For this I use PdfTemplate object filling inside total page number before close document.
It warks, but PdfTemplate exceeds PdfPCell border.
Is it possible that all content of cell stay inside of cell properly ?
PdfTemplate pageNumTemplate;
#Test
public void quick_test() throws FileNotFoundException, DocumentException {
String filename = "C:\\test.pdf";
File file = new File(filename);
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(filename));
PageNumberEvent pageNumberEvent = new PageNumberEvent();
writer.setPageEvent(pageNumberEvent);
document.open();
for(int i = 0; i < 10000; i++){
document.add(new Paragraph("This is my paragraph"));
document.newPage();
}
document.close();
}
public class PageNumberEvent extends PdfPageEventHelper {
#Override
public void onEndPage(PdfWriter writer, Document document) {
if(pageNumTemplate == null){
pageNumTemplate = writer.getDirectContent().createTemplate(0.1f, 0.1f);
}
PdfPTable table = new PdfPTable(1);
table.setSpacingBefore(50);
Paragraph para = new Paragraph();
Chunk pageNum = new Chunk(writer.getPageNumber() + "/");
para.add(pageNum);
Image totalPageNumImg = null;
try {
totalPageNumImg = Image.getInstance(pageNumTemplate);
} catch (BadElementException e) {
e.printStackTrace();
}
Chunk totalPageNumImgChunk = new Chunk(totalPageNumImg, 0, -1, true);
para.add(totalPageNumImgChunk);
para.setIndentationLeft(370);
PdfPCell cell = new PdfPCell(para);
cell.addElement(para);
table.addCell(cell);
try {
document.add(table);
} catch (DocumentException e) {
e.printStackTrace();
}
}
#Override
public void onCloseDocument(PdfWriter writer,Document document) {
String totalPageNumString = String.valueOf(writer.getPageNumber() - 1);
float widthPoint = totalPageNumString.length() * 10;
float heightPoint = totalPageNumString.length() * 20;
Phrase totalPageNumPhrase = new Phrase(totalPageNumString);
Rectangle templRect = pageNumTemplate.getBoundingBox();
Rectangle rectangle = new Rectangle(templRect.getLeft(), templRect.getBottom(), widthPoint, heightPoint + 2);
pageNumTemplate.setBoundingBox(rectangle);
ColumnText.showTextAligned(pageNumTemplate, Element.ALIGN_LEFT, totalPageNumPhrase, 0, 1, 0);
}
}

Early, in the first onEndPage call, you create a minute template (0.1x0.1) to start with
pageNumTemplate = writer.getDirectContent().createTemplate(0.1f, 0.1f);
which fits into your cell. At the end though, in onCloseDocument, after everything has been layout'ed to a fixed position, you resize this template which makes it grow to the left:
Rectangle templRect = pageNumTemplate.getBoundingBox();
Rectangle rectangle = new Rectangle(templRect.getLeft(), templRect.getBottom(), widthPoint, heightPoint + 2);
pageNumTemplate.setBoundingBox(rectangle);
If you want your template to remain in your table cell, you have to initialize it with a width which most likely will be enough to hold the number of pages, e.g.
pageNumTemplate = writer.getDirectContent().createTemplate(30f, 0.1f);
Here you may have to play around a bit...
As the OP indicated in a comment, not only keeping all content of cell stay inside of cell is required but the content also is expected to be right aligned in the cell.
This second requirement obviously cannot be fulfilled by the OP's code which draws the number of pages into the template using ALIGN_LEFT. Furthermore it does not mix and match with resizing the template on the right size because it has already been positioned on the pages.
To fulfill it, therefore, the OP should
horizontally initialize the template with a size large enough for any expected number of pages (onEndPage);
fill the table cell with right alignment (instead of some pseudo right alignment by means of setIndentationLeft) (onEndPage);
leave the horizontal size of the template as is (onCloseDocument); and
show the number of pages right aligned to the right template border (onCloseDocument).

Related

Itext - Adding image in header - Empy Chunk

I'm trying to add an image to a PdfPageEventHelper class, as i saw in the original documentation, but when trying to add the new Chunk with the image to a Phrase, it's always empty...
This is my header and footer class
class PDFHeaderFooter extends PdfPageEventHelper {
public PDFHeaderFooter() throws BadElementException, MalformedURLException, IOException {
super();
}
Image image = Image.getInstance(imagesDir + "logo.png");
Phrase header = new Phrase(new Chunk(image, 0, 0, true));
int pagenumber;
public void onChapter(PdfWriter writer, Document document, float paragraphPosition, Paragraph title) {
pagenumber = 1;
}
public void onStartPage(PdfWriter writer, Document document) {
pagenumber++;
}
public void onEndPage(PdfWriter writer, Document document) {
Rectangle rect = writer.getBoxSize("art");
logger.debug(header.getContent());
ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_RIGHT, header, rect.getRight(),
rect.getTop(), 0);
ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_RIGHT,
new Phrase(String.format("pag. %d", pagenumber), fontSize9), (rect.getLeft() + rect.getRight()) / 2,
rect.getBottom() - 18, 0);
}
}
If i try to add the image in the document body, there is no problem at all, it's just when trying to add it the support class... is there another way?
thanks
The cause of the issue is that ColumnText.showTextAligned uses a ColumnText instance with a column rectangle only three units high.
The ColumnText code is designed to output one line of text at its preferred height (usually exceeding the 3 units) anyways in such a case. Most likely this is a hack to make ColumnText.showTextAligned work for a single line of text.
Unfortunately the ColumnText code at the same time scales down images in chunks to that 3 unit available column height, making them hardly more than dirt specks, or alternatively even drops them.
For phrases containing image chunks, therefore, one should not use the ColumnText.showTextAligned convenience methods but instead explicitly use a ColumnText instance with a sensible column rectangle height, e.g. replace
ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_RIGHT, header, rect.getRight(),
rect.getTop()-30, 0);
by
ColumnText ct = new ColumnText(writer.getDirectContent());
ct.setSimpleColumn(header, rect.getLeft(), rect.getTop(30), rect.getRight(), rect.getTop(), 2, Element.ALIGN_RIGHT);
try {
ct.go();
} catch (DocumentException e) {
throw new ExceptionConverter(e);
}
(AddHeaderImage page event listener method onEndPage)
It's not so much less convenient, after all, and one has more influence on the layout'ing details...

iTextSharp IExtRenderListener and boundingbox [duplicate]

I have a pdf which comprises of some data, followed by some whitespace. I don't know how large the data is, but I'd like to trim off the whitespace following the data
PdfReader reader = new PdfReader(PDFLOCATION);
Rectangle rect = new Rectangle(700, 2000);
Document document = new Document(rect);
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(SAVELCATION));
document.open();
int n = reader.getNumberOfPages();
PdfImportedPage page;
for (int i = 1; i <= n; i++) {
document.newPage();
page = writer.getImportedPage(reader, i);
Image instance = Image.getInstance(page);
document.add(instance);
}
document.close();
Is there a way to clip/trim the whitespace for each page in the new document?
This PDF contains vector graphics.
I'm usung iTextPDF, but can switch to any Java library (mavenized, Apache license preferred)
As no actual solution has been posted, here some pointers from the accompanying itext-questions mailing list thread:
As you want to merely trim pages, this is not a case of PdfWriter + getImportedPage usage but instead of PdfStamper usage. Your main code using a PdfStamper might look like this:
PdfReader reader = new PdfReader(resourceStream);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream("target/test-outputs/test-trimmed-stamper.pdf"));
// Go through all pages
int n = reader.getNumberOfPages();
for (int i = 1; i <= n; i++)
{
Rectangle pageSize = reader.getPageSize(i);
Rectangle rect = getOutputPageSize(pageSize, reader, i);
PdfDictionary page = reader.getPageN(i);
page.put(PdfName.CROPBOX, new PdfArray(new float[]{rect.getLeft(), rect.getBottom(), rect.getRight(), rect.getTop()}));
stamper.markUsed(page);
}
stamper.close();
As you see I also added another argument to your getOutputPageSize method to-be. It is the page number. The amount of white space to trim might differ on different pages after all.
If the source document did not contain vector graphics, you could simply use the iText parser package classes. There even already is a TextMarginFinder based on them. In this case the getOutputPageSize method (with the additional page parameter) could look like this:
private Rectangle getOutputPageSize(Rectangle pageSize, PdfReader reader, int page) throws IOException
{
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
TextMarginFinder finder = parser.processContent(page, new TextMarginFinder());
Rectangle result = new Rectangle(finder.getLlx(), finder.getLly(), finder.getUrx(), finder.getUry());
System.out.printf("Text/bitmap boundary: %f,%f to %f, %f\n", finder.getLlx(), finder.getLly(), finder.getUrx(), finder.getUry());
return result;
}
Using this method with your file test.pdf results in:
As you see the code trims according to text (and bitmap image) content on the page.
To find the bounding box respecting vector graphics, too, you essentially have to do the same but you have to extend the parser framework used here to inform its listeners (the TextMarginFinder essentially is a listener to drawing events sent from the parser framework) about vector graphics operations, too. This is non-trivial, especially if you don't know PDF syntax by heart yet.
If your PDFs to trim are not too generic but can be forced to include some text or bitmap graphics in relevant positions, though, you could use the sample code above (probably with minor changes) anyways.
E.g. if your PDFs always start with text on top and end with text at the bottom, you could change getOutputPageSize to create the result rectangle like this:
Rectangle result = new Rectangle(pageSize.getLeft(), finder.getLly(), pageSize.getRight(), finder.getUry());
This only trims top and bottom empty space:
Depending on your input data pool and requirements this might suffice.
Or you can use some other heuristics depending on your knowledge on the input data. If you know something about the positioning of text (e.g. the heading to always be centered and some other text to always start at the left), you can easily extend the TextMarginFinder to take advantage of this knowledge.
Recent (April 2015, iText 5.5.6-SNAPSHOT) improvements
The current development version, 5.5.6-SNAPSHOT, extends the parser package to also include vector graphics parsing. This allows for an extension of iText's original TextMarginFinder class implementing the new ExtRenderListener methods like this:
#Override
public void modifyPath(PathConstructionRenderInfo renderInfo)
{
List<Vector> points = new ArrayList<Vector>();
if (renderInfo.getOperation() == PathConstructionRenderInfo.RECT)
{
float x = renderInfo.getSegmentData().get(0);
float y = renderInfo.getSegmentData().get(1);
float w = renderInfo.getSegmentData().get(2);
float h = renderInfo.getSegmentData().get(3);
points.add(new Vector(x, y, 1));
points.add(new Vector(x+w, y, 1));
points.add(new Vector(x, y+h, 1));
points.add(new Vector(x+w, y+h, 1));
}
else if (renderInfo.getSegmentData() != null)
{
for (int i = 0; i < renderInfo.getSegmentData().size()-1; i+=2)
{
points.add(new Vector(renderInfo.getSegmentData().get(i), renderInfo.getSegmentData().get(i+1), 1));
}
}
for (Vector point: points)
{
point = point.cross(renderInfo.getCtm());
Rectangle2D.Float pointRectangle = new Rectangle2D.Float(point.get(Vector.I1), point.get(Vector.I2), 0, 0);
if (currentPathRectangle == null)
currentPathRectangle = pointRectangle;
else
currentPathRectangle.add(pointRectangle);
}
}
#Override
public Path renderPath(PathPaintingRenderInfo renderInfo)
{
if (renderInfo.getOperation() != PathPaintingRenderInfo.NO_OP)
{
if (textRectangle == null)
textRectangle = currentPathRectangle;
else
textRectangle.add(currentPathRectangle);
}
currentPathRectangle = null;
return null;
}
#Override
public void clipPath(int rule)
{
}
(Full source: MarginFinder.java)
Using this class to trim the white space results in
which is pretty much what one would hope for.
Beware: The implementation above is far from optimal. It is not even correct as it includes all curve control points which is too much. Furthermore it ignores stuff like line width or wedge types. It actually merely is a proof-of-concept.
All test code is in TestTrimPdfPage.java.

adding a textbox to the right corner of the existing pdf using ITextSharp in C#

I tiied to add a TextBox to the right corner of the existing pdf using c#, but im unable to get it done. I have wrote the following code,but it is not helping in solving the problem, can any body please suggest me
using (MemoryStream stream = new MemoryStream())
{
PdfReader reader = new PdfReader(bytes);
PdfReader.unethicalreading = true;
Paragraph p = new Paragraph();
Document doc = new Document();
using (PdfStamper stamper = new PdfStamper(reader, stream))
{
PdfContentByte canvas = stamper.GetOverContent(1);
iTextSharp.text.Rectangle size = reader.GetPageSizeWithRotation(1);
//PdfContentByte cb = null;
//PdfImportedPage page;
int pages = reader.NumberOfPages;
for (int i = 1; i <= pages; i++)
{
var size1 = reader.GetPageSize(i);
w = size1.Width;
h = size1.Height;
stamper.FormFlattening = true;
TextField tf = new TextField(stamper.Writer, new iTextSharp.text.Rectangle(0, 0, 300, 100), displaytext);
//Change the orientation of the text
tf.Rotation = 0;
stamper.AddAnnotation(tf.GetTextField(), i);
}
}
bytes = stream.ToArray();
}
File.WriteAllBytes(str, bytes);
As the OP clarified in comments to the question, he wants
to add the text as a page content in the right bottom corner of the page and
the page content previously existing there to be removed.
A simple implementation of this would include
first covering the existing page content with a filled rectangle and
then writing text there.
These tasks can be achieved with these helper methods:
void EmptyTextBoxSimple(PdfStamper stamper, int pageNumber, Rectangle boxArea, BaseColor fillColor)
{
PdfContentByte canvas = stamper.GetOverContent(pageNumber);
canvas.SaveState();
canvas.SetColorFill(fillColor);
canvas.Rectangle(boxArea.Left, boxArea.Bottom, boxArea.Width, boxArea.Height);
canvas.Fill();
canvas.RestoreState();
}
and
ColumnText GenerateTextBox(PdfStamper stamper, int pageNumber, Rectangle boxArea)
{
PdfContentByte canvas = stamper.GetOverContent(pageNumber);
ColumnText columnText = new ColumnText(canvas);
columnText.SetSimpleColumn(boxArea);
return columnText;
}
E.g. like this:
using (PdfReader reader = new PdfReader(source))
using (PdfStamper stamper = new PdfStamper(reader, new FileStream(dest, FileMode.Create)))
{
Rectangle cropBox = reader.GetCropBox(1);
Rectangle bottomRight = new Rectangle(cropBox.GetRight(216), cropBox.Bottom, cropBox.Right, cropBox.GetBottom(146));
EmptyTextBoxSimple(stamper, 1, bottomRight, BaseColor.WHITE);
ColumnText columnText = GenerateTextBox(stamper, 1, bottomRight);
columnText.AddText(new Phrase("Some test text to draw into a text box in the lower right corner of the first page"));
columnText.Go();
}
For this source page
the sample code generates this
Addendum
In a comment the OP indicated
it is working for all files but for some pdf files it is displaying in the middle
Eventually he supplied a sample file for which the issue occurs. And indeed, with this file the issue could be reproduced.
The cause for the issue is that the pages in the sample file use page rotation, something that iText (only) partially allows users to ignore. In particular iText automatically rotates text to be upright after rotation and transforms coordinates, but when retrieving the cropbox of a page, one still has to apply rotation before making use of it coordinates. Thus, a more complete example would be like this:
using (PdfReader reader = new PdfReader(source))
using (PdfStamper stamper = new PdfStamper(reader, new FileStream(dest, FileMode.Create)))
{
Rectangle cropBox = reader.GetCropBox(1);
int rotation = reader.GetPageRotation(1);
while (rotation > 0)
{
cropBox = cropBox.Rotate();
rotation -= 90;
}
Rectangle bottomRight = new Rectangle(cropBox.GetRight(216), cropBox.Bottom, cropBox.Right, cropBox.GetBottom(146));
EmptyTextBoxSimple(stamper, 1, bottomRight, BaseColor.WHITE);
ColumnText columnText = GenerateTextBox(stamper, 1, bottomRight);
columnText.AddText(new Phrase("Some test text to draw into a text box in the lower right corner of the first page"));
columnText.Go();
}

How to decide the right coordinates in the middle of a PDF page?

I am new to iText and I looked at its many examples. The thing I have hard time to figure it out is the rectangle. On the page
http://developers.itextpdf.com/examples/form-examples-itext5/multiline-fields
there are many examples with hard-coded values for Rectangle objects. For example:
Rectangle rect = new Rectangle(36, 770, 144, 806);
My problem is that I create one Paragraph and I would like to add a fillable text input box (multi-lines) beneath it. How do I know the exact values for creating a Rectangle object that can be nicely put just after the paragraph. The size of the text of a Paragraph can change. So I cannot assume any hard-coded value.
In iText 5, iText keeps track of the coordinates of the content when using document.add(). You could take control yourself by adding content at absolute positions (e.g. by using ColumnText), but that's hard, because then you have to keep track of many things yourself (for instance: you have to introduce page breaks yourself when the content reaches the bottom of the page).
If you leave the control of the coordinates to iText, you can get access to these coordinates by using page events.
Take a look at the example below, where we keep track of the start and the end of a Paragraph in the onParagraph() and onParagraphEnd() method. This code sample is not easy to understand, but it's the only way to get the coordinates of a Paragraph in iText 5 for instance if we want to draw a rectangle around a block of text. As you can read at the bottom of that page, iText 7 makes it much easier to meet this requirement.
If you stick to iText 5, it's much easier to use generic tags to define locations. See the GenericFields example, where we use empty Chunks that result in fields. If you want to see a screen shot of the result, see Add PdfPCell to Paragraph
In your case, I'd create a Paragraph containing a Chunk that spans different lines, and I'd add the field in the onGenericTag() method of a page event.
Suppose that we have the following text file: jekyll_hyde.txt
How do we convert it to a PDF that looks like this:
Note the blue border that is added to the titles, and the page number at the bottom of each page. In iText 5, these elements are added using page events:
class MyPageEvents extends PdfPageEventHelper {
protected float startpos = -1;
protected boolean title = true;
public void setTitle(boolean title) {
this.title = title;
}
#Override
public void onEndPage(PdfWriter writer, Document document) {
Rectangle pagesize = document.getPageSize();
ColumnText.showTextAligned(
writer.getDirectContent(),
Element.ALIGN_CENTER,
new Phrase(String.valueOf(writer.getPageNumber())),
(pagesize.getLeft() + pagesize.getRight()) / 2,
pagesize.getBottom() + 15,
0);
if (startpos != -1)
onParagraphEnd(writer, document,
pagesize.getBottom(document.bottomMargin()));
startpos = pagesize.getTop(document.topMargin());
}
#Override
public void onParagraph(PdfWriter writer, Document document,
float paragraphPosition) {
startpos = paragraphPosition;
}
#Override
public void onParagraphEnd(PdfWriter writer, Document document,
float paragraphPosition) {
if (!title) return;
PdfContentByte canvas = writer.getDirectContentUnder();
Rectangle pagesize = document.getPageSize();
canvas.saveState();
canvas.setColorStroke(BaseColor.BLUE);
canvas.rectangle(
pagesize.getLeft(document.leftMargin()),
paragraphPosition - 3,
pagesize.getWidth() - document.leftMargin() - document.rightMargin(),
startpos - paragraphPosition);
canvas.stroke();
canvas.restoreState();
}
}
We can use the following code to convert a text file to a PDF and introduce the page event to the PdfWriter:
public void createPdf(String dest)
throws DocumentException, IOException {
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(dest));
MyPageEvents events = new MyPageEvents();
writer.setPageEvent(events);
document.open();
BufferedReader br = new BufferedReader(new FileReader(TEXT));
String line;
Paragraph p;
Font normal = new Font(FontFamily.TIMES_ROMAN, 12);
Font bold = new Font(FontFamily.TIMES_ROMAN, 12, Font.BOLD);
boolean title = true;
while ((line = br.readLine()) != null) {
p = new Paragraph(line, title ? bold : normal);
p.setAlignment(Element.ALIGN_JUSTIFIED);
events.setTitle(title);
document.add(p);
title = line.isEmpty();
}
document.close();
}
Source: developers.itextpdf.com

Page Header overlaps with Table

In itext, I have a table in my pdf. When the table gets full for a page the entries continues on the next page but it overlaps with my pdf page header. How do I avoid that?
Please take a look at the TableHeader example.
In this example, I create a document with some "Hello World" content:
public void createPdf(String filename) throws IOException, DocumentException {
TableHeader.HeaderTable event = new TableHeader.HeaderTable();
// step 1
Document document = new Document(PageSize.A4, 36, 36, 20 + event.getTableHeight(), 36);
// step 2
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(filename));
writer.setPageEvent(event);
// step 3
document.open();
// step 4
for (int i = 0; i < 50; i++)
document.add(new Paragraph("Hello World!"));
document.newPage();
document.add(new Paragraph("Hello World!"));
document.newPage();
document.add(new Paragraph("Hello World!"));
// step 5
document.close();
}
As you can see, I also define a TableHeader event. I use this event as a page event, but I also need this event when I define the Document. I use the following value as top margin:
20 + event.getTableHeight()
What does this mean? Let's take a look at the implementation of this event:
public class HeaderTable extends PdfPageEventHelper {
protected PdfPTable table;
protected float tableHeight;
public HeaderTable() {
table = new PdfPTable(1);
table.setTotalWidth(523);
table.setLockedWidth(true);
table.addCell("Header row 1");
table.addCell("Header row 2");
table.addCell("Header row 3");
tableHeight = table.getTotalHeight();
}
public float getTableHeight() {
return tableHeight;
}
public void onEndPage(PdfWriter writer, Document document) {
table.writeSelectedRows(0, -1,
document.left(),
document.top() + ((document.topMargin() + tableHeight) / 2),
writer.getDirectContent());
}
}
When I create the event, a PdfPTable is constructed. I store this table as a member variable, along with the height of this table: tableHeight.
I use this tableHeight when I define the top margin, so that I am 100% sure that the table will fit the margin. I add an additional 20 user units because I don't want the table to stick to the top border of the page:
20 + event.getTableHeight()
When I add the table in the onEndPage() method, I use the following coordinates:
x = document.left()
y = document.top() + ((document.topMargin() + tableHeight) / 2),
The value of document.top() is the value of the top of the page minus the top margin. I add some extra space, more specifically the difference of the top margin and the table height divided by two, added to the table height:
tableHeight + ((document.topMargin() - tableHeight) / 2)
This formula can be simplified to:
((document.topMargin() + tableHeight) / 2)
As you can see, all of this is simple Math, the kind of Math you are taught in primary school.
The resulting PDF looks like this:
This proves that your allegation that "it doesn't work" is wrong. Please understand that it is not polite to say "it doesn't work" after people have explained in great detail how to do something. By showing that it does work, people reading this could interpret your allegation as a lie (and that is bad for your karma).