Itextsharp: Adjust 2 Elements on Exactly One Page

Itextsharp: Adjust 2 elements on exactly one page

Let me give you a couple of things that might help you and then I'll give you a full working example that you should be able to customize.

The first thing is that the PdfPTable has a special method called WriteSelectedRows() that allows you to draw a table at an exact x,y coordinate. It has six overloads but the most commonly used one is probably:

PdfPTable.WriteSelectedRows(int rowStart,int rowEnd, float xPos, float yPos, PdfContentByte canvas)

To place a table with the upper left corner positioned at 400,400 you would call:

t.WriteSelectedRows(0, t.Rows.Count, 400, 400, writer.DirectContent);

Before calling this method you are required to set the table's width using SetTotalWidth() first:

//Set these to your absolute column width(s), whatever they are.
t.SetTotalWidth(new float[] { 200, 300 });

The second thing is that the height of the table isn't known until the entire table is rendered. This means that you can't know exactly where to place a table so that it truly is at the bottom. The solution to this is to render the table to a temporary document first and then calculate the height. Below is a method that I use to do this:

    public static float CalculatePdfPTableHeight(PdfPTable table)
{
using (MemoryStream ms = new MemoryStream())
{
using (Document doc = new Document(PageSize.TABLOID))
{
using (PdfWriter w = PdfWriter.GetInstance(doc, ms))
{
doc.Open();

table.WriteSelectedRows(0, table.Rows.Count, 0, 0, w.DirectContent);

doc.Close();
return table.TotalHeight;
}
}
}
}

This can be called like this:

        PdfPTable t = new PdfPTable(2);
//In order to use WriteSelectedRows you need to set the width of the table
t.SetTotalWidth(new float[] { 200, 300 });
t.AddCell("Hello");
t.AddCell("World");
t.AddCell("Test");
t.AddCell("Test");

float tableHeight = CalculatePdfPTableHeight(t);

So with all of that here's a full working WinForms example targetting iTextSharp 5.1.1.0 (I know you said 5.1.2 but this should work just the same). This sample looks for all JPEGs in a folder on the desktop called "Test". It then adds them to an 8.5"x11" PDF. Then on the last page of the PDF, or if there's only 1 JPEG to start with on the only page, it expands the height of the PDF by however tall the table that we're adding is and then places the table at the bottom left corner. See the comments in the code itself for further explanation.

using System;
using System.Text;
using System.Windows.Forms;
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.IO;

namespace Full_Profile1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

public static float CalculatePdfPTableHeight(PdfPTable table)
{
//Create a temporary PDF to calculate the height
using (MemoryStream ms = new MemoryStream())
{
using (Document doc = new Document(PageSize.TABLOID))
{
using (PdfWriter w = PdfWriter.GetInstance(doc, ms))
{
doc.Open();

table.WriteSelectedRows(0, table.Rows.Count, 0, 0, w.DirectContent);

doc.Close();
return table.TotalHeight;
}
}
}
}
private void Form1_Load(object sender, EventArgs e)
{
//Create our table
PdfPTable t = new PdfPTable(2);
//In order to use WriteSelectedRows you need to set the width of the table
t.SetTotalWidth(new float[] { 200, 300 });
t.AddCell("Hello");
t.AddCell("World");
t.AddCell("Test");
t.AddCell("Test");

//Calculate true height of the table so we can position it at the document's bottom
float tableHeight = CalculatePdfPTableHeight(t);

//Folder that we are working in
string workingFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Test");

//PDF that we are creating
string outputFile = Path.Combine(workingFolder, "Output.pdf");

//Get an array of all JPEGs in the folder
String[] AllImages = Directory.GetFiles(workingFolder, "*.jpg", SearchOption.TopDirectoryOnly);

//Standard iTextSharp PDF init
using (FileStream fs = new FileStream(outputFile, FileMode.Create, FileAccess.Write, FileShare.None))
{
using (Document document = new Document(PageSize.LETTER))
{
using (PdfWriter writer = PdfWriter.GetInstance(document, fs))
{
//Open our document for writing
document.Open();

//We do not want any margins in the document probably
document.SetMargins(0, 0, 0, 0);

//Declare here, init in loop below
iTextSharp.text.Image pageImage;

//Loop through each image
for (int i = 0; i < AllImages.Length; i++)
{
//If we only have one image or we are on the second to last one
if ((AllImages.Length == 1) | (i == (AllImages.Length - 1)))
{
//Increase the size of the page by the height of the table
document.SetPageSize(new iTextSharp.text.Rectangle(0, 0, document.PageSize.Width, document.PageSize.Height + tableHeight));
}

//Add a new page to the PDF
document.NewPage();

//Create our image instance
pageImage = iTextSharp.text.Image.GetInstance(AllImages[i]);
pageImage.ScaleToFit(document.PageSize.Width, document.PageSize.Height);
pageImage.Alignment = iTextSharp.text.Image.ALIGN_TOP | iTextSharp.text.Image.ALIGN_CENTER;
document.Add(pageImage);

//If we only have one image or we are on the second to last one
if ((AllImages.Length == 1) | (i == (AllImages.Length - 1)))
{
//Draw the table to the bottom left corner of the document
t.WriteSelectedRows(0, t.Rows.Count, 0, tableHeight, writer.DirectContent);
}

}

//Close document for writing
document.Close();
}
}
}

this.Close();
}
}
}

EDIT

Below is an edit based on your comments. I'm only posting the contents of the for loop which is the only part that changed. When calling ScaleToFit you just need to take tableHeight into account.

                    //Loop through each image
for (int i = 0; i < AllImages.Length; i++)
{
//Add a new page to the PDF
document.NewPage();

//Create our image instance
pageImage = iTextSharp.text.Image.GetInstance(AllImages[i]);

//If we only have one image or we are on the second to last one
if ((AllImages.Length == 1) | (i == (AllImages.Length - 1)))
{
//Scale based on the height of document minus the table height
pageImage.ScaleToFit(document.PageSize.Width, document.PageSize.Height - tableHeight);
}
else
{
//Scale normally
pageImage.ScaleToFit(document.PageSize.Width, document.PageSize.Height);
}

pageImage.Alignment = iTextSharp.text.Image.ALIGN_TOP | iTextSharp.text.Image.ALIGN_CENTER;
document.Add(pageImage);

//If we only have one image or we are on the second to last one
if ((AllImages.Length == 1) | (i == (AllImages.Length - 1)))
{
//Draw the table to the bottom left corner of the document
t.WriteSelectedRows(0, t.Rows.Count, 0, tableHeight, writer.DirectContent);
}

}

iTextSharp : How to add 2 sentence in 1 line and adjust width of cells from datagridview?

[Solution]

In C# , to create an inline text with a tabbing is done as per below :

Paragraph c1 = new Paragraph("SUBMITTED BY: "+ "\n\n" + "\n", head3);
c1 = new Paragraph();
c1.Add(new Chunk("SUBMITTED BY : ", head3));
c1.Add(Chunk.TABBING);
c1.Add(Chunk.TABBING);
c1.Add(Chunk.TABBING);
c1.Add(Chunk.TABBING);
c1.Add(new Chunk("ACKNOWLEDGED/TAKEN BY : ", head3));

The Chunk.TABBING will create a space in between the text. As per picture below :
Sample Image

While to adjust cell width size , depending on how you convert your DataGridView into a PDF or such , here's the code :

PdfPTable pdfTable = new PdfPTable(dataGridView1.ColumnCount);
float[] widths1 = new float[] { 5f, 30f, 30f, 5f, 15f, 30f, 30f };
pdfTable.SetWidths(widths1);

foreach (DataGridViewColumn column in dataGridView1.Columns)
{
iTextSharp.text.Font header1 = FontFactory.GetFont("Arial", 10, iTextSharp.text.Font.NORMAL);
PdfPCell cell = new PdfPCell(new Phrase(column.HeaderText, header1)) { BorderWidth = 1, VerticalAlignment = Element.ALIGN_MIDDLE, HorizontalAlignment = Element.ALIGN_CENTER };
//cell.BackgroundColor = new iTextSharp.text.Color(240, 240, 240);
pdfTable.AddCell(cell);
}

I created my pdftable with the columcount i have in DataGridView.
With this , i manually adjust the size of each columns width using float array. Picture above is already using this method.

How can add space\margin between two elements in iTextSharp\iText?

You have a couple of different options. You could set the SpacingAfter on your paragraph:

titolo.SpacingAfter = 20;

You could also set the SpacingBefore on the table:

table.SpacingBefore = 20;

Or you could just add some returns to your paragraph:

iTextSharp.text.Paragraph titolo = new iTextSharp.text.Paragraph("Hello World\n\n");

Insert Element between two elements with iTextSharp

As you say that you know how to make a table and add it to a pdf file and that the Blue and the Red one always have a fixed height, let's assume

  • you have already created the table for the red part in a separate PDF,
  • you have determined the y coordinate at which to split the input document to insert the red part, and
  • you also have determined between which y coordinates in the separate PDF the table to insert is located.

Thus, the problem is reduced to

  • "cutting" the input document page into three stripes,

    • the stripe above the splitting y coordinate containing the blue box,
    • the stripe below the splitting y coordinate containing as much of the green box as will still fit on that page after insertion of the red box, and
    • the stripe at the bottom of the page containing the remainder of the green box that will overflow to a new second page,
  • "cutting" a stripe from the separate PDF containing the red box to insert, and
  • "pasting" these stripes together in the desired new order.

For this you can use a tool like this:

public class CutAndPasteTool
{
readonly Document document;
readonly PdfWriter pdfWriter;
readonly Dictionary<string, PdfTemplate> templates = new Dictionary<string, PdfTemplate>();

public CutAndPasteTool(Rectangle pageSize, Stream os)
{
document = new Document(pageSize);
pdfWriter = PdfWriter.GetInstance(document, os);
document.Open();
}

public ICutter CreateCutter(PdfReader pdfReader, int pageNumber)
{
return new SimpleCutter(pdfReader, pageNumber, pdfWriter, templates);
}

public void Paste(string name, float x, float y)
{
pdfWriter.DirectContent.AddTemplate(templates[name], x, y);
}

public void NewPage()
{
document.NewPage();
}

public void Close()
{
document.Close();
}

class SimpleCutter : ICutter
{
readonly PdfImportedPage importedPage;
readonly Dictionary<string, PdfTemplate> templates;

internal SimpleCutter(PdfReader pdfReader, int pageNumber, PdfWriter pdfWriter, Dictionary<string, PdfTemplate> templates)
{
this.importedPage = pdfWriter.GetImportedPage(pdfReader, pageNumber);
this.templates = templates;
}

public void Cut(string name, Rectangle rectangle)
{
PdfTemplate template = importedPage.CreateTemplate(rectangle.Width, rectangle.Height);
template.AddTemplate(importedPage, importedPage.BoundingBox.Left - rectangle.Left, importedPage.BoundingBox.Bottom - rectangle.Bottom);
templates.Add(name, template);
}
}
}

public interface ICutter
{
void Cut(string name, Rectangle rectangle);
}

Using this tool you can cut the stripes and paste them like this:

Rectangle pageSize = PageSize.A4; // The page size of the result file
int doc1page = 1; // The number of the page in the input PDF
float doc1split = 600; // The splitting y coordinate in the input PDF
int doc2page = 1; // The number of the page in the separate PDF
float doc2from = 700; // The top y coordinate of the table in the separate PDF
float doc2to = 600; // The bottom y coordinate of the table in the separate PDF

using (PdfReader reader1 = new PdfReader(...)) // The input PDF file
using (PdfReader reader2 = new PdfReader(...)) // The separate PDF file
using (Stream os = new FileStream(..., FileMode.Create)) // The stream to write the result to
{
Rectangle doc1box = reader1.GetPageSize(doc1page);
Rectangle doc1upper = new Rectangle(doc1box);
doc1upper.Bottom = doc1split;
Rectangle doc1lower = new Rectangle(doc1box);
doc1lower.Top = doc1split;

Rectangle doc2box = reader2.GetPageSize(doc2page);
Rectangle doc2inset = new Rectangle(doc2box);
doc2inset.Top = doc2from;
doc2inset.Bottom = doc2to;

float doc1lowerRemainHeight = pageSize.Height - doc1upper.Height - doc2inset.Height;
float doc1lowerOverflowHeight = doc1lower.Height - doc1lowerRemainHeight;

Rectangle doc1lowerRemain = new Rectangle(doc1lower);
doc1lowerRemain.Bottom = doc1lowerRemain.Top - doc1lowerRemainHeight;
Rectangle doc1lowerOverflow = new Rectangle(doc1lower);
doc1lowerOverflow.Top = doc1lowerRemain.Bottom;

CutAndPasteTool tool = new CutAndPasteTool(pageSize, os);

ICutter cutterDoc1 = tool.CreateCutter(reader1, doc1page);
cutterDoc1.Cut("Top1", doc1upper);
cutterDoc1.Cut("Bottom1Remain", doc1lowerRemain);
cutterDoc1.Cut("Bottom1Overflow", doc1lowerOverflow);
ICutter cutterDoc2 = tool.CreateCutter(reader2, doc2page);
cutterDoc2.Cut("Inset", doc2inset);

float y = pageSize.Top;
tool.Paste("Top1", 0, y-= doc1upper.Height);
tool.Paste("Inset", 0, y -= doc2inset.Height);
tool.Paste("Bottom1Remain", 0, y -= doc1lowerRemain.Height);

tool.NewPage();

y = pageSize.Top;
tool.Paste("Bottom1Overflow", 0, y -= doc1lowerOverflow.Height);

tool.Close();
}

Beware: This tool only works for content in the page content streams or content streams referenced from there, in particular not for content in annotations or XFA forms.

In case of annotations one can extend the solution to associate each annotation with one of those stripes (which might be difficult for border cases) and copy stripes and associated annotations together.

In case of XFA form tables this approach won't work unless you flatten the form first.

Furthermore, there now is some hidden text which nonetheless can be copied and pasted elsewhere. If that is not acceptable, one can replace the SimpleCutter by a more advanced ICutter implementation which removes (and not only hides) content outside of stripes, e.g. using the iText PdfCleanUpProcessor.

C# - itextSharp subtotal on every page

Please take a look at the SubTotal example.

First we create a class to keep track of the subtotal and the total:

class Totals {
double subtotal = 0;
double total = 0;
}

Then we implement the PdfPCellEvent interface that we will use on column 5:

class SubTotalEvent implements PdfPCellEvent {

Double price;
Totals totals;

public SubTotalEvent(Totals totals, double price) {
this.totals = totals;
this.price = price;
}

public SubTotalEvent(Totals totals) {
this.totals = totals;
price = null;
}

@Override
public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) {
if (price == null) {
PdfContentByte canvas = canvases[PdfPTable.TEXTCANVAS];
ColumnText.showTextAligned(canvas, Element.ALIGN_LEFT,
new Phrase(String.valueOf(totals.subtotal)),
position.getLeft() + 2, position.getBottom() + 2, 0);
totals.subtotal = 0;
return;
}
totals.subtotal += price;
totals.total += price;
}

}

We have defined two constructors: one for a normal cell containing a price. One for a footer cell containing a subtotal (in this case we don't pass a price).

This is how we use this cell event:

public void createPdf(String dest) throws IOException, DocumentException {

Totals totals = new Totals();

Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream(dest));
document.open();
PdfPTable table = new PdfPTable(5);
table.setWidths(new int[]{1, 1, 1, 3, 3});
// header
table.addCell("Pos");
table.addCell("Menge");
table.addCell("Text");
table.addCell("Einzerpreis");
table.addCell("Summe");
// footer
PdfPCell cell = new PdfPCell(new Phrase("Subtotal"));
cell.setColspan(4);
table.addCell(cell);
cell = new PdfPCell();
cell.setCellEvent(new SubTotalEvent(totals));
table.addCell(cell);
// definitions
table.setHeaderRows(2);
table.setFooterRows(1);
// table body
for(int r = 0; r < 50; ){
table.addCell(String.valueOf(++r));
table.addCell("1");
table.addCell("text");
table.addCell("10.0");
cell = new PdfPCell(new Phrase("10.0"));
cell.setCellEvent(new SubTotalEvent(totals, 10));
table.addCell(cell);
}
document.add(table);
// extra footer
table = new PdfPTable(5);
table.setWidths(new int[]{1, 1, 1, 3, 3});
cell = new PdfPCell(new Phrase("Grand total"));
cell.setColspan(4);
table.addCell(cell);
table.addCell(String.valueOf(totals.total));
document.add(table);
document.close();
}

The result looks like this:

Sample Image

On the first page, we have 46 body rows each for an item with price 10. In the footer, we have a subtotal of 460. On the second page, we have 4 body rows each for an item with price 10. In the footer, we have a subtotal of 40. We added an extra table to mimic an extra footer for the Grand Total: 500.

Using itextsharp check if adding element will create new page

Thanks to mkl found the solution.

I put both paragraphs in 2 row table, set the KeepTogether property of the table to true and hid the borders of the table.

Document pdf = new Document();
var previousParagraph = new Paragraph();
/* fill content of previousParagraph*/
var signHere = new Paragraph();
/* fill content of signHere*/

var signatureTable = new PdfPTable(1)
{
KeepTogether = true,
WidthPercentage = 100f
};
signatureTable.DefaultCell.Border = Rectangle.NO_BORDER;

signatureTable.AddCell(previousParagraph);
signatureTable.AddCell(signHere);

pdf.Add(signatureTable);

Resizing IText pdf so the text fits on one page

Essentially you need to first draw on a very long page, longer than any plausible input to your use case, and then cut off the lower, empty parts.

This means that you have to start by setting your pageHeight value accordingly. A safe value is

float pageHeight = 14400f;

The result you get now is a PDF document with one extremely long page, more than 5m in length.

This page has to be shortened to match the contents. This can be done in a second pass like this (I use the pageSize rectangle you have already defined; INTERMEDIATE points to the PDF you generated in the first pass):

PdfReader reader = new PdfReader(INTERMEDIATE);
PdfStamper stamper = new PdfStamper(reader, FINAL_RESULT);

PdfReaderContentParser parser = new PdfReaderContentParser(reader);
TextMarginFinder finder = parser.processContent(1, new TextMarginFinder());

PdfDictionary page = reader.getPageN(1);
page.put(PdfName.CROPBOX, new PdfArray(new float[]{pageSize.getLeft(), finder.getLly(), pageSize.getRight(), pageSize.getTop()}));
stamper.markUsed(page);

stamper.close();
reader.close();

Depending on your document data you can keep the intermediary PDF in memory by using a ByteArrayOutputStream for your PdfWriter, retrieving the intermdeiate PDF as byte[] after document.close(), and feeding that byte[] into the PdfReader in the second step.



Related Topics



Leave a reply



Submit