How can you eliminate white-space in multiple columns using iTextSharp? - itext

I'd like to add a Paragraph of text to pages in 2 columns. I understand that MultiColumnText has been eliminated. I know I can create column 1, write to it, and if there is more text create column 2 and write to it. If there is still more text, go to the next page and repeat.
However I always end up with either:
a long chunk of text orphaned in the left column.
a full left column and partially used right column.
How can I format my content in 2 columns while reducing white space, such as compressing the columns so I end with 2 full columns of equal length?
Thanks!

You should add your column twice, once in simulation mode and once for real.
I have adapted the ColumnTextParagraphs example to show what is meant by simulation mode. Take a look at the ColumnTextParagraphs2 example:
We add the column in simulation mode to obtain the total height needed for the column. This is done in the following method:
public float getNecessaryHeight(ColumnText ct) throws DocumentException {
ct.setSimpleColumn(new Rectangle(0, 0, COLUMN_WIDTH, -500000));
ct.go(true);
return -ct.getYLine();
}
We use this height when we add the left and right column:
Rectangle left;
float top = COLUMNS[0].getTop();
float middle = (COLUMNS[0].getLeft() + COLUMNS[1].getRight()) / 2;
float columnheight;
int status = ColumnText.START_COLUMN;
while (ColumnText.hasMoreText(status)) {
if (checkHeight(height)) {
columnheight = COLUMNS[0].getHeight();
left = COLUMNS[0];
}
else {
columnheight = (height / 2) + ERROR_MARGIN;
left = new Rectangle(
COLUMNS[0].getLeft(),
COLUMNS[0].getTop() - columnheight,
COLUMNS[0].getRight(),
COLUMNS[0].getTop()
);
}
// left half
ct.setSimpleColumn(left);
ct.go();
height -= COLUMNS[0].getTop() - ct.getYLine();
// separator
canvas.moveTo(middle, top - columnheight);
canvas.lineTo(middle, top);
canvas.stroke();
// right half
ct.setSimpleColumn(COLUMNS[1]);
status = ct.go();
height -= COLUMNS[1].getTop() - ct.getYLine();
// new page
document.newPage();
}
This is how we check the height:
public boolean checkHeight(float height) {
height -= COLUMNS[0].getHeight() + COLUMNS[1].getHeight() + ERROR_MARGIN;
return height > 0;
}
As you can see, we add the full columns as long as the height of both columns is smaller than the remaining height. When columns are added, we adjust the remaining height. As soon as the height is lower than the height of to columns, we adapt the height of the first column.
Note that we work with an ERROR_MARGIN because dividing by two often leads to a situation where the second column has one line more than the first column. It is better when it's the other way around.
This is the full example at your request:
/**
* Example written by Bruno Lowagie in answer to:
* http://stackoverflow.com/questions/29378407/how-can-you-eliminate-white-space-in-multiple-columns-using-itextsharp
*/
package sandbox.objects;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfWriter;
public class ColumnTextParagraphs2 {
public static final String DEST = "results/objects/column_paragraphs2.pdf";
public static final String TEXT = "This is some long paragraph that will be added over and over again to prove a point.";
public static final float COLUMN_WIDTH = 254;
public static final float ERROR_MARGIN = 16;
public static final Rectangle[] COLUMNS = {
new Rectangle(36, 36, 36 + COLUMN_WIDTH, 806),
new Rectangle(305, 36, 305 + COLUMN_WIDTH, 806)
};
public static void main(String[] args) throws IOException, DocumentException {
File file = new File(DEST);
file.getParentFile().mkdirs();
new ColumnTextParagraphs2().createPdf(DEST);
}
public void createPdf(String dest) throws IOException, DocumentException {
// step 1
Document document = new Document();
// step 2
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(dest));
// step 3
document.open();
// step 4
PdfContentByte canvas = writer.getDirectContent();
ColumnText ct = new ColumnText(canvas);
addContent(ct);
float height = getNecessaryHeight(ct);
addContent(ct);
Rectangle left;
float top = COLUMNS[0].getTop();
float middle = (COLUMNS[0].getLeft() + COLUMNS[1].getRight()) / 2;
float columnheight;
int status = ColumnText.START_COLUMN;
while (ColumnText.hasMoreText(status)) {
if (checkHeight(height)) {
columnheight = COLUMNS[0].getHeight();
left = COLUMNS[0];
}
else {
columnheight = (height / 2) + ERROR_MARGIN;
left = new Rectangle(
COLUMNS[0].getLeft(),
COLUMNS[0].getTop() - columnheight,
COLUMNS[0].getRight(),
COLUMNS[0].getTop()
);
}
// left half
ct.setSimpleColumn(left);
ct.go();
height -= COLUMNS[0].getTop() - ct.getYLine();
// separator
canvas.moveTo(middle, top - columnheight);
canvas.lineTo(middle, top);
canvas.stroke();
// right half
ct.setSimpleColumn(COLUMNS[1]);
status = ct.go();
height -= COLUMNS[1].getTop() - ct.getYLine();
// new page
document.newPage();
}
// step 5
document.close();
}
public void addContent(ColumnText ct) {
for (int i = 0; i < 35; i++) {
ct.addElement(new Paragraph(String.format("Paragraph %s: %s", i, TEXT)));
}
}
public float getNecessaryHeight(ColumnText ct) throws DocumentException {
ct.setSimpleColumn(new Rectangle(0, 0, COLUMN_WIDTH, -500000));
ct.go(true);
return -ct.getYLine();
}
public boolean checkHeight(float height) {
height -= COLUMNS[0].getHeight() + COLUMNS[1].getHeight() + ERROR_MARGIN;
return height > 0;
}
}

Related

iTextSharp Footer Background Color

I am trying to set the background color of my footer. I can't seem to find any code that can assist in doing this. Please see my code for the OnEndPage event.
I have tried cb.SetColorFill(BaseColor.LIGHT_GRAY);, but that does not work :/
Which property or method is used to add a background color to the footer?
public override void OnEndPage(iTextSharp.text.pdf.PdfWriter writer, iTextSharp.text.Document document)
{
base.OnEndPage(writer, document);
iTextSharp.text.Font baseFontNormal = new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, 12f, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLACK);
iTextSharp.text.Font baseFontBig = new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, 12f, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
var headerImagePath = System.Web.HttpContext.Current.Server.MapPath("~/Content/misc/proactive-reg-form-header.jpg");
var headerImage = Image.GetInstance(headerImagePath);
if (headerImage.Height > headerImage.Width)
{
//Maximum height is 800 pixels.
float percentage = 0.0f;
percentage = 700 / headerImage.Height;
headerImage.ScalePercent(percentage * 100);
}
else
{
//Maximum width is 600 pixels.
float percentage = 0.0f;
percentage = 572 / headerImage.Width;
headerImage.ScalePercent(percentage * 100);
}
//Create PdfTable object
PdfPTable pdfTab = new PdfPTable(1);
//We will have to create separate cells to include image logo and 2 separate strings
//Row 1
//PdfPCell pdfCell1 = new PdfPCell();
PdfPCell pdfCell2 = new PdfPCell(headerImage);
//PdfPCell pdfCell3 = new PdfPCell();
String text = "Page " + writer.PageNumber + " of ";
//Add paging to footer
{
cb.BeginText();
cb.SetFontAndSize(bf, 10);
cb.SetTextMatrix(document.PageSize.GetRight(100), document.PageSize.GetBottom(30));
cb.ShowText(text);
cb.EndText();
float len = bf.GetWidthPoint(text, 10);
cb.AddTemplate(footerTemplate, document.PageSize.GetRight(100) + len, document.PageSize.GetBottom(30));
//add image to footer
writer.DirectContent.AddImage(footerImage);
}
pdfCell2.HorizontalAlignment = Element.ALIGN_CENTER;
pdfCell2.VerticalAlignment = Element.ALIGN_BOTTOM;
pdfCell2.Border = 0;
pdfTab.AddCell(pdfCell2);
pdfTab.TotalWidth = 100f;
pdfTab.WidthPercentage = 100f;
//call WriteSelectedRows of PdfTable. This writes rows from PdfWriter in PdfTable
//first param is start row. -1 indicates there is no end row and all the rows to be included to write
//Third and fourth param is x and y position to start writing
pdfTab.WriteSelectedRows(0, -1, 10, document.PageSize.Height - 10, writer.DirectContent);
//set pdfContent value
if (document.PageNumber != 1)
{
//Move the pointer and draw line to separate header section from rest of page
cb.MoveTo(10, document.PageSize.Height - 60);
cb.LineTo((document.PageSize.Width - 10), document.PageSize.Height - 60);
cb.Stroke();
}
}
Got it!
So I amended the following code:
//Add paging to footer
{
cb.BeginText();
cb.SetFontAndSize(bf, 10);
cb.SetTextMatrix(document.PageSize.GetRight(100), document.PageSize.GetBottom(30));
cb.ShowText(text);
cb.EndText();
float len = bf.GetWidthPoint(text, 10);
cb.AddTemplate(footerTemplate, document.PageSize.GetRight(100) + len, document.PageSize.GetBottom(30));
//add image to footer
writer.DirectContent.AddImage(footerImage);
}
with
//Add paging to footer
{
cb.BeginText();
cb.SetFontAndSize(bf, 10);
cb.SetTextMatrix(document.PageSize.GetRight(100), document.PageSize.GetBottom(30));
cb.ShowText(text);
cb.EndText();
float len = bf.GetWidthPoint(text, 10);
cb.AddTemplate(footerTemplate, document.PageSize.GetRight(100) + len, document.PageSize.GetBottom(30));
//this part adds the background color
cb.SetColorFill(BaseColor.LIGHT_GRAY);
cb.Rectangle(0, 0, document.PageSize.Width, 50);
cb.FillStroke();
//add image to footer
writer.DirectContent.AddImage(footerImage);
}

How to specify a row background in table with iText5

When creating a PDF table with iText5, it is possible to create a table background by implementing a PdfPTableEvent and a cell background by implementing a PdfPCellEvent.
But what about a row background? How can I create that?
The reason is because I want to create a calendar table like in the image below:
Actually, you have included the answer in your question: you have to use a table event. Take a look at the RowBackground example. It contains a table event RowBackgroundEvent that allows you to create a table event to draw the background of a single row.
public class RowBackgroundEvent implements PdfPTableEvent {
// the row number of the row that needs a background
protected int row;
// creates a background event for a specific row
public RowBackgroundEvent(int row) {
this.row = row;
}
/**
* Draws the background of a row.
*/
#Override
public void tableLayout(PdfPTable table, float[][] widths, float[] heights,
int headerRows, int rowStart, PdfContentByte[] canvases) {
float llx = widths[row][0];
float lly = heights[row];
float urx = widths[row][widths[row].length - 1];
float ury = heights[row - 1];
float h = ury - lly;
PdfContentByte canvas = canvases[PdfPTable.BASECANVAS];
canvas.saveState();
canvas.arc(llx - h / 2, lly, llx + h / 2, ury, 90, 180);
canvas.lineTo(urx, lly);
canvas.arc(urx - h / 2, lly, urx + h / 2, ury, 270, 180);
canvas.lineTo(llx, ury);
canvas.setColorFill(BaseColor.LIGHT_GRAY);
canvas.fill();
canvas.restoreState();
}
}
This is how this event is used:
public void createPdf(String filename) throws SQLException, DocumentException, IOException {
// step 1
Document document = new Document(PageSize.A4.rotate());
// step 2
PdfWriter.getInstance(document, new FileOutputStream(filename));
// step 3
document.open();
// step 4
PdfPTableEvent event = new RowBackgroundEvent(3);
PdfPTable table = new PdfPTable(7);
table.setTableEvent(event);
table.getDefaultCell().setBorder(Rectangle.NO_BORDER);
for (int i = 0; i < 10; i++) {
for (int j = 1; j < 8; j++) {
table.addCell(String.valueOf(j));
}
}
document.add(table);
// step 5
document.close();
}
As you can see, we want a background for the third row. The result looks like this:
You can tweak the llx, lly, urx, and ury value if you want to adapt the size of the background bar.
If you can draw the background of a single row, you can extend the code to draw the background of more than one row.

How to programmatically add header and footer to an existing form-based PDF using iText?

I need to programmatically add header and footer to an existing form-based PDF using iText. The existing PDF comes from user and it contains no space for header and footer. So the solution is to create a new PDF by concatenating the contents of the existing PDF with the header and footer. However, this approach only works for PDF containing no form. For interactive PDF that contains AcroForm or XFA Form, it fails as follows: (1) AcroForm gets flattened in the new PDF. (2) XFA Form doesn't import at all - the new PDF shows "Please wait...If this message is not eventually replaced by proper contents of the document, your PDF viewer may not be able to display this type of document...".
Here's my code:
import java.awt.Color;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfWriter;
import com.lowagie.text.Font;
import com.lowagie.text.pdf.PdfGState;
import com.lowagie.text.pdf.PdfStamper;
import com.lowagie.text.FontFactory;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.Phrase;
import com.lowagie.text.pdf.ColumnText;
import com.lowagie.text.pdf.PdfImportedPage;
public class PdfFormCopyTest {
private static final String ACRO_FORM_PDF = "AcroForm.pdf";
private static final String XFA_FORM_PDF = "XfaForm.pdf";
private static final String NO_FORM_PDF = "NoForm.pdf";
private static final String ACRO_FORM_PDF_NEW = "AcroForm-new.pdf";
private static final String XFA_FORM_PDF_NEW = "XfaForm-new.pdf";
private static final String NO_FORM_PDF_NEW = "NoForm-new.pdf";
private static final float MARGIN_LEFT = 36.0f;
private static final float MARGIN_RIGHT = 36.0f;
private static final float MARGIN_BOTTOM = 56.0f;
private static final float MARGIN_TOP = 36.0f;
private static final float FONT_SIZE = 10.0f;
private static final float MIN_LINE_HEIGHT = FONT_SIZE * 1.5f;
/**
* #param args
*/
public static void main(String[] args) {
try {
createPdfFromAcroFormBasedPdf();
createPdfFromXfaFormBasedPdf();
createPdfFromFormlessPdf();
}
catch (Exception error) {
System.out.println(error.getMessage());
}
}
private static void createPdfFromAcroFormBasedPdf() throws IOException, DocumentException {
System.out.println("Creating new PDF from an existing PDF containing AcroForm.....");
PdfReader reader = new PdfReader(ACRO_FORM_PDF);
createNewPdfWithHeaderFooter(reader, ACRO_FORM_PDF_NEW);
System.out.println("Success");
}
private static void createPdfFromXfaFormBasedPdf() throws IOException, DocumentException {
System.out.println("Creating new PDF from an existing PDF containing XfaForm......");
PdfReader reader = new PdfReader(XFA_FORM_PDF);
createNewPdfWithHeaderFooter(reader, XFA_FORM_PDF_NEW);
System.out.println("Success");
}
private static void createPdfFromFormlessPdf() throws IOException, DocumentException {
System.out.println("Creating new PDF from an existing PDF containing no form......");
PdfReader reader = new PdfReader(NO_FORM_PDF);
createNewPdfWithHeaderFooter(reader, NO_FORM_PDF_NEW);
System.out.println("Success");
}
/**
* Creates a new PDF which contains header and footer from the specified input PdfReader object
* and saves the result as the specified output file.
* #param reader A PdfReader for the existing PDF.
* #param outputFileName Name of the PDF file which contains header and footer.
* #throws IOException
* #throws DocumentException
*/
private static void createNewPdfWithHeaderFooter(PdfReader reader, String outputFileName)
throws IOException, DocumentException {
String footer = getFooter();
String header = getHeader();
List<Float> footerHeights = computeHeights(footer, reader, Font.NORMAL);
List<Float> headerHeights = computeHeights(header, reader, Font.BOLD);
InputStream resizedPdfStream = createPdfWithHeaderFooterSpace(reader, footerHeights, headerHeights);
PdfStamper stamper = null;
try {
FileOutputStream fos = new FileOutputStream(outputFileName);
PdfReader newReader = new PdfReader(resizedPdfStream);
stamper = new PdfStamper(newReader, fos);
int numberOfPages = stamper.getReader().getNumberOfPages();
for (int pageNumber = 1; pageNumber <= numberOfPages; pageNumber++) {
Rectangle rect = stamper.getReader().getPageSize(pageNumber);
PdfContentByte pageContent = stamper.getOverContent(pageNumber);
pageContent.saveState();
pageContent.setGState(new PdfGState());
renderHeaderFooter(rect, pageContent, header, footer);
pageContent.restoreState();
}
}
finally {
if (stamper != null) {
stamper.close();
}
}
}
/**
* Computes the height of the specified content for each page
* in the specified PdfReader with the specified font weight.
* #param content The string content for which the height of each page is computed.
* #param reader A PdfReader containing the existing PDF.
* #param fontWeight The font weight.
* #return A list of float representing the height of each page.
* #throws IOException
* #throws DocumentException
*/
private static List<Float> computeHeights(String content, PdfReader reader, int fontWeight)
throws IOException, DocumentException {
List<Float> contentHeights = new ArrayList<Float>();
int numberOfPages = reader.getNumberOfPages();
for (int pageNumber = 1; pageNumber <= numberOfPages; pageNumber++) {
Rectangle pageSize = reader.getPageSize(pageNumber);
float height = computeWrappedTextHeight(content, pageSize.getWidth(), fontWeight);
contentHeights.add(pageNumber - 1, height);
}
return contentHeights;
}
/**
* Creates a new PDF with place holder for header and footer from the specified parameters.
* #param reader The PdfReader storing the contents of the PDF to be created.
* #param footerHeights The footer height for each page.
* #param headerHeights The header height for each page.
* #return An InputStream representing the new PDF.
* #throws IOException
* #throws DocumentException
*/
private static InputStream createPdfWithHeaderFooterSpace(PdfReader reader,
List<Float> footerHeights, List<Float> headerHeights) throws IOException, DocumentException {
ByteArrayOutputStream baos = null;
Document newDocument = null;
try {
baos = new ByteArrayOutputStream();
newDocument = new Document();
PdfWriter newPdfWriter = PdfWriter.getInstance(newDocument, baos);
PdfContentByte newPdfCanvas = null;
int numberOfPages = reader.getNumberOfPages();
for (int pageNumber = 1; pageNumber <= numberOfPages; pageNumber++) {
Rectangle oldPageSize = reader.getPageSize(pageNumber);
float oldPageWidth = oldPageSize.getWidth();
float oldPageHeight = oldPageSize.getHeight();
float footerHeight = footerHeights.get(pageNumber - 1);
float headerHeight = headerHeights.get(pageNumber - 1);
float newPageHeight = calculateNewPageHeight(oldPageHeight, headerHeight, footerHeight);
float newPageWidth = calculateNewPageWidth(oldPageWidth);
Rectangle newPageSize = new Rectangle(0, 0, newPageWidth, newPageHeight);
newDocument.setPageSize(newPageSize);
if (!newDocument.isOpen()) {
newDocument.open();
newPdfCanvas = newPdfWriter.getDirectContent();
}
float xFactor = 1.0f;
float yFactor = 1.0f;
float xOffset = MARGIN_LEFT;
float yOffset = MARGIN_BOTTOM + footerHeight;
PdfImportedPage importedPage = newPdfWriter.getImportedPage(reader, pageNumber);
newPdfCanvas.addTemplate(importedPage, xFactor, 0, 0, yFactor, xOffset, yOffset);
newDocument.newPage();
}
}
finally {
if (newDocument != null && newDocument.isOpen()) {
newDocument.close();
}
}
return new ByteArrayInputStream(baos.toByteArray());
}
/**
* Computes the height of the specified string content which must
* wrap at the specified maximum line width with the specified font weight.
* #param content The string content for which the height is computed.
* #param maxLineWidth The maximum line width at which the content must wrap.
* #param fontWeight The font weight.
* #return The height of the specified content which wraps at
* the specified maximum line width with the specified font weight.
*/
private static float computeWrappedTextHeight(String content, float maxLineWidth, int fontWeight) {
float totalHeight = 0.0f;
Font font = FontFactory.getFont(BaseFont.HELVETICA, FONT_SIZE);
font.setStyle(fontWeight);
BaseFont baseFont = font.getCalculatedBaseFont(true);
String lineText = "";
int currentWordStart = -1;
float lineHeight;
for (int charIndex = 0; charIndex < content.length(); charIndex++) {
String currentChar = content.substring(charIndex, charIndex + 1);
lineText = lineText + currentChar;
boolean isCurrentCharWordSeparator = isWordSeperator(currentChar);
float lineWidth = computeLineWidth(lineText, baseFont);
if (charIndex == 0 || (!isCurrentCharWordSeparator && currentWordStart < 0)) {
currentWordStart = charIndex;
}
if (lineWidth > maxLineWidth || currentChar.equals("\n")) {
// Start a new line.
if (isCurrentCharWordSeparator) {
// The current character is a word separator - break the line at the current character.
lineHeight = computeLineHeight(lineText, baseFont);
// Reset line text.
if (currentChar.equals("\n")) {
lineText = "";
}
else {
lineText = currentChar;
}
}
else {
// The current character is in the middle of a word - break the line at the previous word separator.
int lineEnd = lineText.length() - (charIndex - currentWordStart) - 1;
if (lineEnd > 0) {
String currentWordExcludedLineText = lineText.substring(0, lineEnd);
lineHeight = computeLineHeight(currentWordExcludedLineText, baseFont);
charIndex = currentWordStart; // New line starts at the beginning of the current word.
lineText = "";
}
else {
lineHeight = computeLineHeight(lineText, baseFont);
lineText = currentChar;
}
}
totalHeight = totalHeight + lineHeight;
}
// If it is at a new word break, reset the current word starting index so that
// the next iteration can set it at the beginning of the next word.
if (charIndex > 0 && isCurrentCharWordSeparator && currentWordStart >= 0) {
currentWordStart = -1;
}
}
lineHeight = computeLineHeight(lineText, baseFont);
totalHeight = totalHeight + lineHeight;
return totalHeight;
}
/**
* Determines if the specified string is a word separator.
* #param c The string to test.
* #return true if the specified string is a word separator; false othewise.
*/
private static boolean isWordSeperator(String c) {
return (c.equals("\n") || c.equals("\t") || c.equals(" "));
}
/**
* Computes the line width of the specified line text with the specified base font.
* #param lineText The line text.
* #param baseFont A BaseFont object representing the base font of the line.
* #return A float representing the width of the line.
*/
private static float computeLineWidth(String lineText, BaseFont baseFont) {
return baseFont.getWidthPoint(lineText, FONT_SIZE);
}
/**
* Computes the line height with the specified parameters.
* #param lineText The line text.
* #param baseFont A BaseFont object representing the base font of the line.
* #return A float value representing the height of the line.
*/
private static float computeLineHeight(String lineText, BaseFont baseFont) {
float lineHeight = baseFont.getAscentPoint(lineText, FONT_SIZE) - baseFont.getDescentPoint(lineText, FONT_SIZE);
if (lineHeight < MIN_LINE_HEIGHT) {
lineHeight = MIN_LINE_HEIGHT;
}
return lineHeight;
}
/**
* Renders the header and footer to the specified Rectangle with the specified page content, header and footer.
* #param rect A Rectangle to render the header and footer.
* #param pageContent A PdfContentByte representing the content of the page.
* #param header The page header.
* #param footer The page footer.
* #throws DocumentException
* #throws IOException
*/
private static void renderHeaderFooter(Rectangle rect, PdfContentByte pageContent, String header, String footer)
throws DocumentException, IOException {
float margin = 36.0f;
int sides = 2;
float footerHeight = (float)Math.ceil(computeWrappedTextHeight(footer, rect.getWidth() - margin * sides, Font.NORMAL));
float headerHeight = (float)Math.ceil(computeWrappedTextHeight(footer, rect.getWidth() - margin * sides, Font.BOLD));
if (headerHeight < MIN_LINE_HEIGHT) {
headerHeight = MIN_LINE_HEIGHT;
}
// Render header.
Font headerFont = getDefaultFont();
headerFont.setStyle(Font.BOLD);
Phrase headerPhrase = new Phrase(header, headerFont);
ColumnText headerRenderer = new ColumnText(pageContent);
headerRenderer.setSimpleColumn(headerPhrase, margin, rect.getHeight() - headerHeight - margin + 4,
rect.getWidth() - margin, rect.getHeight() - margin + 4, MIN_LINE_HEIGHT, Element.ALIGN_RIGHT);
headerRenderer.go();
// Render footer.
Phrase footerPhrase = new Phrase(footer, getDefaultFont());
ColumnText footerRender = new ColumnText(pageContent);
footerRender.setSimpleColumn(footerPhrase, margin, margin, rect.getWidth() - margin, footerHeight + margin, MIN_LINE_HEIGHT, Element.ALIGN_CENTER);
footerRender.go();
}
/**
* Calculates the height of the new page with the specified parameters.
* #param oldPageHeight The height of the old page.
* #param headerHeight The height of header.
* #param footerHeight The height of footer.
* #return The height of the new page.
*/
private static float calculateNewPageHeight(float oldPageHeight, float headerHeight, float footerHeight) {
return oldPageHeight + MARGIN_TOP + headerHeight + footerHeight + MARGIN_BOTTOM;
}
/**
* Calculates the width of the new page with the specified width of old page.
* #param oldPageWidth The width of the old page.
* #return The width of the new page.
*/
private static float calculateNewPageWidth(float oldPageWidth) {
return oldPageWidth + MARGIN_LEFT + MARGIN_RIGHT;
}
private static String getHeader() {
return "This is dynamically added header.";
}
private static String getFooter() {
StringBuilder footerBuilder = new StringBuilder();
footerBuilder.append("This is the dynamically added footer.");
footerBuilder.append("\n\n");
footerBuilder.append("Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. ");
footerBuilder.append("Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. ");
footerBuilder.append("Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. ");
footerBuilder.append("Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. ");
footerBuilder.append("Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. ");
footerBuilder.append("Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. ");
footerBuilder.append("\n\n");
footerBuilder.append("Footer paragraph 2 content. Footer paragraph 2 content. Footer paragraph 2 content. Footer paragraph 2 content. ");
footerBuilder.append("Footer paragraph 2 content. Footer paragraph 2 content. Footer paragraph 2 content. Footer paragraph 2 content. ");
footerBuilder.append("Footer paragraph 2 content. Footer paragraph 2 content. Footer paragraph 2 content. Footer paragraph 2 content. ");
footerBuilder.append("Footer paragraph 2 content. Footer paragraph 2 content. Footer paragraph 2 content. Footer paragraph 2 content. ");
footerBuilder.append("\n\n");
footerBuilder.append("Footer paragraph 3 content. Footer paragraph 3 content. Footer paragraph 3 content. Footer paragraph 3 content.");
return footerBuilder.toString();
}
private static Font getDefaultFont() {
return FontFactory.getFont(BaseFont.HELVETICA, FONT_SIZE, Color.BLACK);
}
}
I need to programmatically add header and footer to an existing form-based PDF using iText. The existing PDF comes from user and it contains no space for header and footer. So the solution is to create a new PDF by concatenating the contents of the existing PDF with the header and footer.
No, this is not a good solution. You had better use a PdfStamper, change the sizes of the existing pages, and add headers and footers in the new page area. In particular as you use a PdfStamper already now for the final step.
#Mark Storer in this old answer shows how to manipulate the bottom of the MediaBox. Likewise you can also change its top. And as Mark remarks in his answer, you may also have to change the CropBox.
However, this approach only works for PDF containing no form. For interactive PDF that contains AcroForm or XFA Form, it fails as follows: (1) AcroForm gets flattened in the new PDF.
With your code the AcroForm form elements should not get flattened (i.e. their appearances should not get added to the static PDF content) but they should get lost. Sometimes, though, border lines or other indications of form field boundaries actually are already part of the static content. This might be the case for you.
The reason is that your code uses PdfWriter.getImportedPage, a method that only takes the page content stream but no interactive features like AcroForm form field widget annotations.
(2) XFA Form doesn't import at all - the new PDF shows "Please wait...If this message is not eventually replaced by proper contents of the document, your PDF viewer may not be able to display this type of document...".
XFA forms are a document type of its own which merely use PDF files as transport medium. Your PdfWriter.getImportedPage does not even see the XFA data in the document and only copies a page that your XFA PDF document shows on PDF viewers without XFA support.
In case of XFA forms the PDF page objects usually have no part in what eventually is displayed. Instead the PDF transports an XFA XML. Thus, all your changes to any existing PDF pages remain unseen. You have to extract that XFA XML, manipulate it, and store it again.
iText only has limited support for XFA, and the ancient version you appear to use has none at all.

itextsharp change margins no document.SetPageSize

I added the function below to change the margins for the page
at every page change.
I found the forum a method that sets the size of the page:
document.SetPageSize (New Rectangle (36.0F, 36.0F, 52.0F, PageFooter.TotalHeight))
But I do not want to change the size of the page, but those margins.
thanks
public override void OnEndPage(PdfWriter writer, Document document)
{
try
{
DataSet dsReport = new DataSet();
foreach (DataSet obj in report.arrayDs)
{
dsReport = obj;
break;
}
Single topMargin = 0;
if (document.PageNumber != 1)
{
if (report.repeatHead) //ripete l'intestazione del report su tutte le pagine di stampa
{
repeatHead(writer, document);
topMargin = 60;
}
else
{
if (document.PageNumber == 2) //ripete l'intestazione del report solo sulla second pagina dopo la copertina
{
repeatHead(writer, document);
topMargin = 60;
}
else
{
topMargin = Convert.ToSingle(dsReport.Tables["REPORT_STYLE"].Rows[0]["topMargin"]) * 10;
}
}
document.SetMargins(Convert.ToSingle(dsReport.Tables["REPORT_STYLE"].Rows[0]["leftMargin"]) * 10,
Convert.ToSingle(dsReport.Tables["REPORT_STYLE"].Rows[0]["rightMargin"]) * 10,
topMargin,
Convert.ToSingle(dsReport.Tables["REPORT_STYLE"].Rows[0]["bottomMargin"]) * 10);
}
}
catch
{ throw; }
}
Based on your clarification in the comments, you want the top margin of the first page to be 60 and the top margin of the second page to be 0.
This is shown in the following screen shot:
The Java code to achieve this looks like this:
public void createPdf(String dest) throws IOException, DocumentException {
float left = 30;
float right = 30;
float top = 60;
float bottom = 0;
Document document = new Document(PageSize.A4, left, right, top, bottom);
PdfWriter.getInstance(document, new FileOutputStream(dest));
document.open();
document.setMargins(left, right, 0, bottom);
for (int i = 0; i < 60; i++) {
document.add(new Paragraph("This is a test"));
}
document.close();
}
If you want to port this to C#, you need to change some lower cases into upper cases. You already knew most of the methods, for instance: I see document.SetMargins(...) in your page event.

How can I combine multiple PDF files excluding page breaks using iTextSharp?

I wonder if anyone has done this with iTextSharp, but I would like to combine multiple PDF files into one but leave the page breaks out. For example, I would like to create 4 PDF files containing 3 lines of text each, so I want the resulting file to have all 12 lines in 1 page. Is this possible?
As the OP also tagged this question with [iText] and I am more at home with Java than .Net, here an answer for iText/Java. It should be easy to translate to iTextSharp/C#.
The original question
I would like to combine multiple PDF files into one but leave the page breaks out. For example, I would like to create 4 PDF files containing 3 lines of text each, so I want the resulting file to have all 12 lines in 1 page.
For PDF files as indicated in that example you can use this simple utility class:
public class PdfDenseMergeTool
{
public PdfDenseMergeTool(Rectangle size, float top, float bottom, float gap)
{
this.pageSize = size;
this.topMargin = top;
this.bottomMargin = bottom;
this.gap = gap;
}
public void merge(OutputStream outputStream, Iterable<PdfReader> inputs) throws DocumentException, IOException
{
try
{
openDocument(outputStream);
for (PdfReader reader: inputs)
{
merge(reader);
}
}
finally
{
closeDocument();
}
}
void openDocument(OutputStream outputStream) throws DocumentException
{
final Document document = new Document(pageSize, 36, 36, topMargin, bottomMargin);
final PdfWriter writer = PdfWriter.getInstance(document, outputStream);
document.open();
this.document = document;
this.writer = writer;
newPage();
}
void closeDocument()
{
try
{
document.close();
}
finally
{
this.document = null;
this.writer = null;
this.yPosition = 0;
}
}
void newPage()
{
document.newPage();
yPosition = pageSize.getTop(topMargin);
}
void merge(PdfReader reader) throws IOException
{
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
for (int page = 1; page <= reader.getNumberOfPages(); page++)
{
merge(reader, parser, page);
}
}
void merge(PdfReader reader, PdfReaderContentParser parser, int page) throws IOException
{
TextMarginFinder finder = parser.processContent(page, new TextMarginFinder());
Rectangle pageSizeToImport = reader.getPageSize(page);
float heightToImport = finder.getHeight();
float maxHeight = pageSize.getHeight() - topMargin - bottomMargin;
if (heightToImport > maxHeight)
{
throw new IllegalArgumentException(String.format("Page %s content too large; height: %s, limit: %s.", page, heightToImport, maxHeight));
}
if (heightToImport > yPosition - pageSize.getBottom(bottomMargin))
{
newPage();
}
else if (!writer.isPageEmpty())
{
heightToImport += gap;
}
yPosition -= heightToImport;
PdfImportedPage importedPage = writer.getImportedPage(reader, page);
writer.getDirectContent().addTemplate(importedPage, 0, yPosition - (finder.getLly() - pageSizeToImport.getBottom()));
}
Document document = null;
PdfWriter writer = null;
float yPosition = 0;
final Rectangle pageSize;
final float topMargin;
final float bottomMargin;
final float gap;
}
If you have a list of PdfReader instances inputs, you can merge them like this into an OutputStream output:
PdfDenseMergeTool tool = new PdfDenseMergeTool(PageSize.A4, 18, 18, 5);
tool.merge(output, inputs);
This creates a merged document using an A4 page size, a top and bottom margin of 18/72" each and a gap between contents of different PDF pages of 5/72".
The comments
The iText TextMarginFinder (used in the PdfDenseMergeTool above) only considers text. If other content types also are to be considered, this class has to be extended somewhat.
Each PDF has just a few lines, perhaps a table or an image, but I want the end result in one page.
If the tables contain decorations reaching above or below the text content (e.g. lines or colored backgrounds), you should use a larger gap value. Unfortunately the parsing framework used by the TextMarginFinder does not forward vector graphics commands to the finder.
If the images are bitmap images, the TextMarginFinder should be extended by implementing its renderImage method to take the image area into account, too.
Also, some of the PDFs may contain fields, so I'd like to keep those fields in the resulting combined PDF as well.
If AcroForm fields are also to be considered, you have to
extend the rectangle represented by the TextMarginFinder to also include the visualization rectangles of the widget annotations, and
extend the PdfDenseMergeTool.merge(PdfReader, PdfReaderContentParser, int) method to also copy those widget annotations.
Update
I wrote above
Unfortunately the parsing framework used by the TextMarginFinder does not forward vector graphics commands to the finder.
Meanwhile (in version 5.5.6) that parsing framework has been extended to also forward vector graphics commands.
If you replace the line
TextMarginFinder finder = parser.processContent(page, new TextMarginFinder());
by
MarginFinder finder = parser.processContent(page, new MarginFinder());
using the MarginFinder class presented at the bottom of this answer, all content is considered, not merely text.
For those of you who want the above code in C#, here you go.
using System;
using System.Collections.Generic;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.parser;
namespace Test.WebService.Support {
public class PDFMerge {
private Rectangle PageSize;
private float TopMargin;
private float BottomMargin;
private float Gap;
private Document Document = null;
private PdfWriter Writer = null;
private float YPosition = 0;
public PDFMerge(Rectangle size, float top, float bottom, float gap) {
this.PageSize = size;
this.TopMargin = top;
this.BottomMargin = bottom;
this.Gap = gap;
} // PDFMerge
public void Merge(MemoryStream outputStream, List<PdfReader> inputs) {
try {
this.OpenDocument(outputStream);
foreach (PdfReader reader in inputs) {
this.Merge(reader);
}
} finally {
this.CloseDocument();
}
} // Merge
private void Merge(PdfReader reader) {
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
for (int p = 1; p <= reader.NumberOfPages; p++) {
this.Merge(reader, parser, p);
}
} // Merge
private void Merge(PdfReader reader, PdfReaderContentParser parser, int pageIndex) {
TextMarginFinder Finder = parser.ProcessContent(pageIndex, new TextMarginFinder());
Rectangle PageSizeToImport = reader.GetPageSize(pageIndex);
float HeightToImport = Finder.GetHeight();
float MaxHeight = PageSize.Height - TopMargin - BottomMargin;
if (HeightToImport > MaxHeight) {
throw new ArgumentException(string.Format("Page {0} content too large; height: {1}, limit: {2}.", pageIndex, HeightToImport, MaxHeight));
}
if (HeightToImport > YPosition - PageSize.GetBottom(BottomMargin)) {
this.NewPage();
} else if (!Writer.PageEmpty) {
HeightToImport += Gap;
}
YPosition -= HeightToImport;
PdfImportedPage ImportedPage = Writer.GetImportedPage(reader, pageIndex);
Writer.DirectContent.AddTemplate(ImportedPage, 0, YPosition - (Finder.GetLly() - PageSizeToImport.Bottom));
} // Merge
private void OpenDocument(MemoryStream outputStream) {
Document Document = new Document(PageSize, 36, 36, this.TopMargin, BottomMargin);
PdfWriter Writer = PdfWriter.GetInstance(Document, outputStream);
Document.Open();
this.Document = Document;
this.Writer = Writer;
this.NewPage();
} // OpenDocument
private void CloseDocument() {
try {
Document.Close();
} finally {
this.Document = null;
this.Writer = null;
this.YPosition = 0;
}
} // CloseDocument
private void NewPage() {
Document.NewPage();
YPosition = PageSize.GetTop(TopMargin);
} // NewPage
}
}