Add Object onStartPage itextPdf except the last Page - java

I am adding a rectangle on top of my page for all pages but I do not want the rectangle on the last page. Here is my code:
#Override
public void onStartPage(PdfWriter writer, Document output) {
Font bold = new Font(Font.FontFamily.HELVETICA, 16, Font.BOLD);
bold.setStyle(Font.UNDERLINE);
bold.setColor(new BaseColor(171, 75, 15));
PdfContentByte cb = writer.getDirectContent();
// Bottom left coordinates x & y, followed by width, height and radius of corners.
cb.roundRectangle(100f, 1180f, 400f, 100f, 5f);//I dont want this on the ;ast page
cb.stroke();
try {
output.add(new Paragraph("STATEMENT OF ACCOUNT", bold));
output.add(new Paragraph(new Phrase(new Chunk(" "))));
output.add(new Paragraph(new Phrase(new Chunk(" "))));
output.add(new Paragraph(new Phrase(new Chunk(" "))));
output.add(new Paragraph(new Phrase(new Chunk(" "))));
Image logo = Image.getInstance(imagepath);
logo.setAbsolutePosition(780, 1230);
logo.scaleAbsolute(200, 180);
writer.getDirectContent().addImage(logo);
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
Is there a way to either skip or remove this rectangle from the last Page of the document?

First of all iText developers often have stressed that in onStartPage one MUST NOT add content to the PDF. The reason is that under certain circumstances unused pages are created and onStartPage is called for them but they then are dropped. If you add content to them in onStartPage, though, they are not dropped but remain in your document.
Thus, always use onEndPage to add any content to a page.
In your use case there is yet another reason for using onEndPage: Usually it only becomes clear that a given page is the last page when the last bit of content has been added to the document. This usually occurs after onStartPage has been called for the page but before onEndPage has.
Thus, after you've added the last bit of regular page content to the document, you can simply set a flag in the page event listener that the current page is the final document page. Now the following onEndPage call knows it processes the final page and can add content differently.
So the page event listener would look like this
class MyPageEventListener extends PdfPageEventHelper {
public boolean lastPage = false;
#Override
public void onEndPage(PdfWriter writer, Document output) {
if (!lastPage) {
[add extra content for page before the last one]
} else {
[add extra content for last page]
}
}
...
}
and be used like this
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, TARGET);
MyPageEventListener pageEventListener = new MyPageEventListener();
writer.setPageEvent(pageEventListener);
document.open();
[add all regular content to the document]
pageEventListener.lastPage = true;
document.close();

Related

IText 7 How To Add Div or Paragraph in Header Without Overlapping Page Content?

I am facing the following problem for which i haven't found any solution yet. I am implementing a platform for a medical laboratory. They want for every incident to write the report to the system and then generate and print it from the system. I am using itext 7 to accomplish this. However i am facing the following problem.
They have a very strange template. On the first page in the beginning they want to print a specific table, while in the beginning of every other page they want to print something else. So i need to know when pages change in order to print in the beginning of the page the corresponding table.
After reading various sources i ended up creating the first page normally and then adding a header event handler that checks the page number and gets executed always except page 1.
public class VariableHeaderEventHandler implements IEventHandler {
#Override
public void handleEvent(Event event) {
System.out.println("THIS IS ME: HEADER EVENT HANDLER STARTED.....");
PdfDocumentEvent documentEvent = (PdfDocumentEvent) event;
PdfDocument pdfDoc = documentEvent.getDocument();
PdfPage page = documentEvent.getPage();
Rectangle pageSize = page.getPageSize();
int pageNumber = pdfDoc.getPageNumber(page);
if (pageNumber == 1) return; //Do nothing in the first page...
System.out.println("Page size: " + pageSize.getHeight());
Rectangle rectangle = new Rectangle(pageSize.getLeft() + 30, pageSize.getHeight()-234, pageSize.getWidth() - 60, 200);
PdfCanvas pdfCanvas = new PdfCanvas(page.newContentStreamBefore(), page.getResources(), pdfDoc);
pdfCanvas.rectangle(rectangle);
pdfCanvas.setFontAndSize(FontsAndStyles.getRegularFont(), 10);
Canvas canvas = new Canvas(pdfCanvas, pdfDoc, rectangle);
Div header = new Div();
Paragraph paragraph = new Paragraph();
Text text = new Text("Διαγνωστικό Εργαστήριο Ιστοπαθολογίας και Μοριακής Παθολογοανατομικής").addStyle(FontsAndStyles.getBoldStyle());
paragraph.add(text);
paragraph.add(new Text("\n"));
text = new Text("Μοριακή Διάγνωση σε Συνεργασία με").addStyle(FontsAndStyles.getBoldStyle());
paragraph.add(text);
paragraph.add(new Text("\n"));
text = new Text("Γκούρβας Βίκτωρας, M.D., Ph.D.").addStyle(FontsAndStyles.getBoldStyle());
paragraph.add(text);
paragraph.add(new Text("\n"));
text = new Text("Τσιμισκή 33, Τ.Κ. 54624, ΘΕΣΣΑΛΟΝΙΚΗ").addStyle(FontsAndStyles.getNormalStyle());
paragraph.add(text);
paragraph.add(new Text("\n"));
text = new Text("Τήλ/Φάξ: 2311292924 Κιν.: 6932104909 e-mail: vgourvas#gmail.com").addStyle(FontsAndStyles.getNormalStyle());
paragraph.add(text);
header.add(paragraph);
// =============Horizontal Line BOLD============
SolidLine solidLine = new SolidLine((float) 1.5);
header.add(new LineSeparator(solidLine));
// ========Horizontal Line BOLD End==========
text = new Text("ΠΑΘΟΛΟΓΟΑΝΑΤΟΜΙΚΗ ΕΞΕΤΑΣΗ").addStyle(FontsAndStyles.getBoldStyle());
paragraph = new Paragraph().add(text);
header.add(paragraph);
header.setTextAlignment(TextAlignment.CENTER);
canvas.add(header);
canvas.close();
}
However the problem i am facing now is that header overlaps content and i can't figure out how to set different margins per page. For example form page 2 and beyond i would like different topMargin.
Has anyone faced these problems before and have found a working solution? Am I implementing correct? Is there a better way of accomplishing the same result?
Thanks in advance,
Toutoudakis Michail
You should create your own custom document renderer and decrease the area which would be used to place content for each page except for the first one.
Please look at the snippet below and updateCurrentArea method in particular.
class CustomDocumentRenderer extends DocumentRenderer {
public CustomDocumentRenderer(Document document) {
super(document);
}
#Override
public IRenderer getNextRenderer() {
return new CustomDocumentRenderer(this.document);
}
#Override
protected LayoutArea updateCurrentArea(LayoutResult overflowResult) {
LayoutArea area = super.updateCurrentArea(overflowResult);
if (currentPageNumber > 1) {
area.setBBox(area.getBBox().decreaseHeight(200));
}
return area;
}
}
Then just set the renderer on your document:
Document doc = new Document(pdfDoc);
doc.setRenderer(new CustomDocumentRenderer(doc));
The resultant pdf which I get for your document looks as follows:
There is another solution however. Once you've added at least one element to your document, you can change the default document's margins. The change will be applied on all pages created afterwards (and in your case these are pages 2, 3, ...)
doc.add(new Paragraph("At least one element should be added. Otherwise the first page wouldn't be created and changing of the default margins would affect it."));
doc.setMargins(200, 36, 36, 36);
// now you can be sure that all the next pages would have new margins

How to add header or footer using itextpdf only after first page in java?

I have written a java program to download a pdf , but I want to add header to that pdf document only after 1st page , I have tried some code.
class MyFooter extends PdfPageEventHelper {
Font ffont = new Font(Font.FontFamily.UNDEFINED, 10, Font.BOLD);
private void addHeader(PdfWriter writer,Document document){
PdfContentByte cb = writer.getDirectContent();
Phrase header = new Phrase("Customer Id : ", ffont);
ColumnText.showTextAligned(cb, Element.ALIGN_CENTER,
header,
(document.right() - document.left()) / 2 +
document.leftMargin(),
document.top() - 10, 0);
}
private void addFooter(PdfWriter writer,Document document){
PdfContentByte cb = writer.getDirectContent();
Phrase footer = new Phrase("Page "+writer.getPageNumber(),
ffont);
ColumnText.showTextAligned(cb, Element.ALIGN_CENTER,
footer,
(document.right() - document.left()) / 2 +
document.leftMargin(),
document.bottom() - 10, 0);
}
public void onEndPage(PdfWriter writer, Document document) {
try{
addHeader(writer,document);
addFooter(writer,document);
}catch(Exception e){
e.printStackTrace();
}
}
}
The footer function is working properly , but I want the header function to print only after 1st page, so I tried using
if(writer.getPageNumber() > 1)
but this condition is not working
Try
if (document.getPageNumber() > 1)
The page number in the writer is the page number of the PDF and will be there after writing the PDF.
Better to try this method
document.resetHeader();
after the execution of the first-page logic.

Use different Table Header on first page

I'm using iText and create a dynamic table which has a a reoccurring header in the method createTabularHeader:
PdfPTable table = new PdfPTable(6);
// fill it with some basic information
table.setHeaderRows(1);
Yet on the first page I would like to display different information. (but the table structure/size remains the same)
Due to the dynamic content which is obtained in a different method I can't say when a new page starts.
I tried with the most primitive variant - just adding a white rectangle over the text and insert the different text. As it's just on the first page all I have to do is creating that rectangle between both methods.
But the white rectangle doesn't have any opacity and can' cover anything.
Yet by trying around I found the method writer.getDirectContent().setColorStroke(BaseColor.WHITE); which set the text to white. Later I just set the BaseColor of my cells manually to black. But the even though the new text is applied after the calling of my createTabularHeader-method its layer is under the layer of the original text and the letters are covering the new text partly.
Using the answer to How to insert invisible text into a PDF? brought me to the idea of using myPdfContentByte.setTextRenderMode(PdfContentByte.TEXT_RENDER_MODE_INVISIBLE); was not so helpful as it resets only on the 2nd page regardless what I do and the regular text on the first page stays invisible.
I'm unable to find a proper solution... How can the table-header be modified only on the first page?
The solution is not really nice, but works... and as some sort of bonus I want to add how you can modify the indentions on the first page.
public void createPdf() {
document = new Document();
try {
PdfWriter writer = PDFHead.getWriter(document);
//If it's a letter we have a different indention on the top
if (letterPDF) {
document.setMargins(36, 36, 100, 36);
} else {
document.setMargins(36, 36, 36, 36);
}
document.open();
document.add(createTabularContent());
document.close();
} catch (DocumentException | FileNotFoundException ex) {
try {
document = new Document();
PdfWriter.getInstance(document, new FileOutputStream(FILENAME));
document.open();
document.add(new Phrase(ex.getLocalizedMessage()));
document.close();
Logger.getLogger(Etikette.class.getName()).log(Level.SEVERE, null, ex);
} catch (FileNotFoundException | DocumentException ex1) {
Logger.getLogger(Etikette.class.getName()).log(Level.SEVERE, null, ex1);
}
}
}
The PDFHead is used to create a regular header (the one which appears on every page, not only on pages with the table):
public static PdfWriter getWriter(Document document) throws FileNotFoundException, DocumentException {
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(filename));
HeaderFooter event = new HeaderFooter("Ing. Mario J. Schwaiger", type + " " + DDMMYYYY.format(new java.util.Date()), 835, isLetterPDF(), customerNumber);
writer.setBoxSize("art", new Rectangle(36, 54, 559, 788));
writer.setPageEvent(event);
return writer;
}
And in that HeaderFooter-Event I use the fact the function is called after the PDF is basically created (for the page number for instance):
#Override
public void onEndPage(PdfWriter writer, Document document) {
if (isLetter) {
//That's only for the first page, apparently 1 is too late
//I'm open for improvements but that works fine for me
if (writer.getPageNumber() == 0) {
//If it's a letter we use the different margins
document.setMargins(36, 36, 100, 36);
}
if (writer.getPageNumber() == 1) {
PdfContentByte canvas = writer.getDirectContent();
float llx = 460;
float lly = 742;
float urx = 36;
float ury = 607;
//As I add the rectangle in the event here it's
//drawn over the table-header. Seems the tableheader
//is rendered afterwards
Rectangle rect1 = new Rectangle(llx, lly, urx, ury);
rect1.setBackgroundColor(BaseColor.WHITE);
rect1.setBorder(Rectangle.NO_BORDER);
rect1.setBorderWidth(1);
canvas.rectangle(rect1);
ColumnText ct = new ColumnText(canvas);
ct.setSimpleColumn(rect1);
PdfPTable minitable = new PdfPTable(1);
PdfPCell cell = PDFKopf.getKundenCol(PDFHeader.getCustomer(customerNumber));
cell.setBorder(Rectangle.NO_BORDER);
minitable.addCell(cell);
//A single cell is not accepted as an "Element"
//But a table including only a single cell is
ct.addElement(minitable);
try {
ct.go();
} catch (DocumentException ex) {
Logger.getLogger(HeaderFooter.class.getName()).log(Level.SEVERE, null, ex);
}
//In any other case we reset the margins back to normal
//This could be solved in a more intelligent way, feel free
} else {
document.setMargins(36, 36, 36, 36);
}
}
//The regular header of any page...
PdfPTable table = new PdfPTable(4);
try {
table.setWidths(new int[]{16, 16, 16, 2});
table.setWidthPercentage(100);
table.setTotalWidth(527);
table.setLockedWidth(true);
table.getDefaultCell().setFixedHeight(20);
table.getDefaultCell().setBorder(Rectangle.BOTTOM);
table.addCell(header);
PdfPCell cell;
cell = new PdfPCell(new Phrase(mittelteil));
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setBorder(Rectangle.BOTTOM);
table.addCell(cell);
table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_RIGHT);
table.addCell(String.format("Page %d of ", writer.getPageNumber()));
cell = new PdfPCell(Image.getInstance(total));
cell.setBorder(Rectangle.BOTTOM);
table.addCell(cell);
table.writeSelectedRows(0, -1, 34, y, writer.getDirectContent());
} catch (DocumentException de) {
throw new ExceptionConverter(de);
}
}

iText add Watermark to selected pages

I need to add a watermark to every page that has certain text, such as "PROCEDURE DELETED".
Based on Bruno Lowagie's suggestion in Adding watermark directly to the stream
So far have the PdfWatermark Class with:
protected Phrase watermark = new Phrase("DELETED", new Font(FontFamily.HELVETICA, 60, Font.NORMAL, BaseColor.PINK));
ArrayList<Integer> arrPages = new ArrayList<Integer>();
boolean pdfChecked = false;
#Override
public void onEndPage(PdfWriter writer, Document document) {
if(pdfChecked == false) {
detectPages(writer, document);
pdfChecked = true;
}
int pageNum = writer.getPageNumber();
if(arrPages.contains(pageNum)) {
PdfContentByte canvas = writer.getDirectContentUnder();
ColumnText.showTextAligned(canvas, Element.ALIGN_CENTER, watermark, 298, 421, 45);
}
}
And this works fine if I add, say, the number 3 to the arrPages ArrayList in my custom detectPages method - it shows the desired watermark on page 3.
What I am having trouble with is how to search through the document for the text string, which I have access to here, only from the PdfWriter writer or the com.itextpdf.text.Document document sent to onEndPage method.
Here is what I have tried, unsuccessfully:
private void detectPages(PdfWriter writer, Document document) {
try {
//arrPages.add(3);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
PdfWriter.getInstance(document, byteArrayOutputStream);
//following code no work
PdfReader reader = new PdfReader(writer.getDirectContent().getPdfDocument());
PdfContentByte canvas = writer.getDirectContent();
PdfImportedPage page;
for (int i = 0; i < reader.getNumberOfPages(); ) {
page = writer.getImportedPage(reader, ++i);
canvas.addTemplate(page, 1f, 0, 0.4f, 0.4f, 72, 50 * i);
canvas.beginText();
canvas.showTextAligned(Element.ALIGN_CENTER,
//search String
String.valueOf((char)(181 + i)), 496, 150 + 50 * i, 0);
//if(detected) arrPages.add(i);
canvas.endText();
}
Am I on the right track with this as a solution or do I need to back out?
Can anyone supply the missing link needed to scan the doc and pick out "PROCEDURE DELETED" pages?
EDIT: I am using iText 5.0.4 - cannot upgrade to 5.5.X at this time, but could probably upgrade to latest version below that.
EDIT2: More information: This is approach to adding text to the document (doc):
String processed = processText(template);
List<Element> objects = HTMLWorker.parseToListLog(new StringReader(processed),
styles, interfaceProps, errors);
for (Element elem : objects) {
doc.add(elem);
}
That is called in an addText method I control. The template is simply html from a database LOB. The processText checks the html for custom markers contained by curlies as in ${replaceMe}.
This seems to be the place to identify the "PROCEDURE DELETED" string during generation of the document, but I don't see the path to Chunk.setGenericTags().
EDIT3: Table difficulties
List<Element> objects = HTMLWorker.parseToListLog(new StringReader(processed),
styles, interfaceProps, errors);
for (Element elem : objects) {
//Code below no work
if (elem instanceof PdfPTable) {
PdfPTable table = (PdfPTable) elem;
ArrayList<Chunk> chks = table.getChunks();
for(Chunk chk : chks){
if(chk.toString().contains("TEST DELETED")) {
chk.setGenericTag("delete_tag");
}
}
}
doc.add(elem);
}
Commenters mlk and Bruno suggested to detect the "PROCEDURE DELETED" keywords at the time they are added to the doc. However, since the keywords are necessarily inside a table, they have to be detected through PdfPTable rather than the simpler Element.
I could not do it with the code above. Any suggestions exactly how to find text inside a table cell and do a string comparison on it?
EDIT4: Based on some experimentation, I would like to make some assertions and please show me the way through them:
Using Chunk.setGenericTag() is required to trigger the handler onGenericTag
For some reason (PdfPTable) table.getChunks() does not return chunks, at least that my system picks up. This is counterintuitive and possibly there is a setup, version, or code bug causing this behavior.
Therefore, a selection text string inside a table cannot be used to trigger a watermark.
Thanks to all the help from #mkl and #Bruno, I finally got a workable solution. Anybody interested who can give a more elegant approach is welcome - there is certainly room.
Two classes were in play in all the snippets given in the original question. Below is partial code that embodies the working solution.
public class PdfExporter
//a custom class to build content
//create some content to add to pdf
String text = "<div>Lots of important content here</div>";
//assume the section is known to be deleted
if(section_deleted) {
//add a special marker to the text, in white-on-white
text = "<span style=\"color:white;\">.!^DEL^?.</span>" + text + "</span>";
}
//based on some indicator, we know we want to force a new page for a new section
boolean ensureNewPage = true;
//use an instance of PdfDocHandler to add the content
pdfDocHandler.addText(text, ensureNewPage);
public class PdfDocHandler extends PdfPageEventHelper
private boolean isDEL;
public boolean addText(String text, boolean ensureNewPage)
if (ensureNewPage) {
//turn isDEL off: a forced pagebreak indicates a new section
isDEL = false;
}
//attempt to find the special DELETE marker in first chunk
//NOTE: this can be done several ways
ArrayList<Chunk> chks = elem.getChunks();
if(chks.size()>0) {
if(chks.get(0).getContent().contains(".!^DEL^?.")) {
//special delete marker found in text
isDEL = true;
}
}
//doc is an iText Document
doc.add(elem);
public void onEndPage(PdfWriter writer, Document document) {
if(isDEL) {
//set the watermark
Phrase watermark = new Phrase("DELETED", new Font(Font.FontFamily.HELVETICA, 60, Font.NORMAL, BaseColor.PINK));
PdfContentByte canvas = writer.getDirectContentUnder();
ColumnText.showTextAligned(canvas, Element.ALIGN_CENTER, watermark, 298, 421, 45);
}
}

iTextPDF: changing the Table Alignment dynamically

I want to dynamically align the iText PdfTable.
How to set the x and y position based alignment in iTextPDF.
PdfPCell cell;
cell = new PdfPCell(testTable);
cell.setFixedHeight(44f);
cell.setColspan(3);
cell.setBorder(0);
table.addCell(cell);
table1.addCell(table);
please look in to this example...
public static void Main() {
// step 1: creation of a document-object
Document document = new Document();
try {
// step 2:
// we create a writer that listens to the document
// and directs a PDF-stream to a file
PdfWriter writer = PdfWriter.getInstance(document, new FileStream("Chap1002.pdf", FileMode.Create));
// step 3: we open the document
document.Open();
// step 4: we grab the ContentByte and do some stuff with it
PdfContentByte cb = writer.DirectContent;
// we tell the ContentByte we're ready to draw text
cb.beginText();
// we draw some text on a certain position
cb.setTextMatrix(100, 400);
cb.showText("Text at position 100,400.");
// we tell the contentByte, we've finished drawing text
cb.endText();
}
catch(DocumentException de) {
Console.Error.WriteLine(de.Message);
}
catch(IOException ioe) {
Console.Error.WriteLine(ioe.Message);
}
// step 5: we close the document
document.Close();
}
}
Please take a look at the C# port of the examples of chapter 4 of my book: http://tinyurl.com/itextsharpIIA2C04
You can add the table to a ColumnText object and add the column at an absolute position:
ColumnText column = new ColumnText(writer.DirectContent);
column.AddElement(table);
column.SetSimpleColumn(llx, lly, urx, ury);
column.Go();
In this snippet llx, lly and urx, ury are the coordinates of the lower-left corner and the upper-right corner of the column on the page (see the ColumnTable example).
In the PdfCalendar example, another method is used:
table.WriteSelectedRows(0, -1, x, y, writer.DirectContent);
The first parameters define which rows need to be drawn (0 to -1 means all rows), x and y define the absolute position.

Categories

Resources