I am attempting to create documents that are highly-variable in length, and make them something that can be table-driven. I am trying to use iTextSharp tables exclusively to create the documents, including footer verbiage. In the code below Iām trying to pad the very last cell to the exact amount equal to the remaining space, so that my footer will be placed exactly at the bottom of the document, tight against the bottom margin. This works fine when I try to use document margins of 1, 2, or 3 inches, but when I use 5 for example, my footer begins to wrap. I think this may simply be a case of bad math. Does anyone have any suggestions to correct this (outside of using Page Events)?
(*Note: there are 2 helper methods used in creating tables and measuring the height of a paragraph, also included in addition to the main code from a button click)
(*Note: each table has SplitLate set to false, to make the content flow from page to page evenly); all tables have a specific width, and the width is locked
//declare path for the PDF file to be created
string strpath = #"C:\CROauto\Junk\MSE.pdf";
//instantiate a new PDF document
//(regular portrait format, with half-inch margins all around)
var doc = new Document(PageSize.LETTER,
iTextSharp.text.Utilities.InchesToPoints(1f),
iTextSharp.text.Utilities.InchesToPoints(1f),
iTextSharp.text.Utilities.InchesToPoints(5f),
iTextSharp.text.Utilities.InchesToPoints(.5f));
//create a PDF table
//(2 column, with no border)
PdfPTable mas_tbl = clsPDF.CreateTable(2, false, cell_padding_bottom: 0f, total_width: doc.PageSize.Width - doc.LeftMargin - doc.RightMargin);
//get base font types
BaseFont font_tb = FontFactory.GetFont(FontFactory.TIMES_BOLD).BaseFont;
BaseFont font_t = FontFactory.GetFont(FontFactory.TIMES).BaseFont;
//create regular Font objects, with varying sizes
iTextSharp.text.Font f10 = new iTextSharp.text.Font(font_t, 10);
iTextSharp.text.Font f8 = new iTextSharp.text.Font(font_t, 8);
//create regular Font objects, bold, with varying sizes
iTextSharp.text.Font fb10 = new iTextSharp.text.Font(font_tb, 10);
iTextSharp.text.Font fb8 = new iTextSharp.text.Font(font_tb, 8);
//get image
iTextSharp.text.Image da_img = iTextSharp.text.Image.GetInstance(#"C:\CROauto\Junk\img.gif");
//scale the image to a good size
da_img.ScaleAbsolute(iTextSharp.text.Utilities.InchesToPoints(.75f),
iTextSharp.text.Utilities.InchesToPoints(.75f));
//create new PDF cell to hold image
PdfPCell icell = new PdfPCell();
icell.PaddingBottom = 0f;
//make sure the "image" cell has no border
icell.Border = iTextSharp.text.Rectangle.NO_BORDER;
//add the image to the PDF cell
icell.AddElement(da_img);
//add the image cell to the table
mas_tbl.AddCell(icell);
//work with the return address
PdfPCell ra = new PdfPCell();
ra.Border = iTextSharp.text.Rectangle.NO_BORDER;
ra.PaddingBottom = 5f;
PdfPTable tblra = clsPDF.CreateTable(1, false);
tblra.HorizontalAlignment = Element.ALIGN_RIGHT;
Chunk c = new Chunk("Help Me Please", fb8);
string rtnadd = "\r\n123 iTextSharp Rd\r\nHelpMe, ST 12345\r\n\r\nstackoverflow.com";
Phrase pra = new Phrase(rtnadd, f8);
Paragraph p = new Paragraph();
p.SetLeading(1f, 1.1f);
p.Add(c);
p.Add(pra);
tblra.TotalWidth = clsPDF.GetLongestWidth(p) + ra.PaddingLeft + ra.PaddingRight + 2;
ra.AddElement(p);
tblra.AddCell(ra);
PdfPCell dummy = new PdfPCell();
dummy.PaddingBottom = 0f;
dummy.Border = iTextSharp.text.Rectangle.NO_BORDER;
dummy.AddElement(tblra);
mas_tbl.AddCell(dummy);
//create "content" table
PdfPTable t2 = clsPDF.CreateTable(1, false, Element.ALIGN_JUSTIFIED, cell_padding_bottom: 0f);
//create FileStream for the file
using (FileStream fs = new FileStream(strpath, FileMode.Create))
{
//get an instance of a PdfWriter, attached to the FileStream
PdfWriter.GetInstance(doc, fs);
//open the document
doc.Open();
string tmp = "";
for (int i = 0; i < 80; i++)
{
tmp += "The brown fox jumped over the lazy dog a whole bunch of times." + i.ToString();
}
Phrase p2 = new Phrase(tmp, f10);
t2.AddCell(p2);
p2 = new Phrase("Another paragraph", f10);
t2.AddCell(p2);
tmp = "";
PdfPTable t3 = clsPDF.CreateTable(1, false, cell_padding_bottom: 0f);
for (int i = 0; i < 150; i++)
{
tmp += "The lazy dog didn't like that very much." + i.ToString();
}
t3.AddCell(new Phrase(tmp, f10));
t2.AddCell(t3);
t2.AddCell(new Phrase("I SURE HOPE THIS WORKED", f10));
PdfPCell c2 = new PdfPCell();
c2.PaddingBottom = 0f;
c2.Border = iTextSharp.text.Rectangle.NO_BORDER;
c2.Colspan = mas_tbl.NumberOfColumns;
c2.AddElement(t2);
mas_tbl.AddCell(c2);
//work with adding a footer
//FOOTER MSE: ADD ENOUGH PADDING TO PUSH THE FOOTER TO THE BOTTOM OF THE PAGE
Paragraph fp = new Paragraph(new Phrase("Line 1 of footer\r\nLine 2 of footer\r\nhere's more of my footer text:\r\nthis project was SOOOO much fun\r\nand stuff", fb8));
//get the height of the footer
float footer_height = clsPDF.GetTotalHeightOfParagraph(fp);
Console.WriteLine("Footer height {0}", footer_height.ToString());
//get the total amount of "writeable" space per page
//(taking top and bottom margins into consideration)
float avail = doc.PageSize.Height - (doc.TopMargin + doc.BottomMargin);
//declare a variable to assist in calculating
//the total amount of "writeable" room remaining
//on the last page;
//start with with the current height of the master table
//(will do math below to calculate just what it's using
// on the last page)
float mas_tbl_last_page_height = mas_tbl.TotalHeight;
//the purpose of this loop is to start determining
//how much writeable space is left on the last page;
//this loop will subtract the "available" value from
//the total height of the master table until what's
//left is the amount of space the master table is
//using on the last page of the document only
while (mas_tbl_last_page_height > avail)
{
mas_tbl_last_page_height -= avail;
}
//to truly calculate the amount of writeable space
//remaining, subtract the amount of space that the
//master table is utilizing on the last page of
//the document, from the total amount of writeable
//space per page
float room_remaining = avail - mas_tbl_last_page_height;
//declare variable for the padding amount
//that will be used above the footer
float pad_amt = 0f;
if (room_remaining > (footer_height * 2))
{
//pad to push down
pad_amt = room_remaining - (footer_height * 2);
}
else
{
//don't use a pad
//(just let the table wrap normally)
pad_amt = 0f;
}
//declare the footer cell, and set all of it's values
PdfPCell ftcell = new PdfPCell();
ftcell.HorizontalAlignment = Element.ALIGN_JUSTIFIED;
//(use column span that is equal to the number of
// columns in the master table)
ftcell.Colspan = mas_tbl.NumberOfColumns;
ftcell.Border = iTextSharp.text.Rectangle.NO_BORDER;
ftcell.PaddingTop = pad_amt;
ftcell.PaddingBottom = 0f;
ftcell.AddElement(fp);
//add the footer cell to the master table
mas_tbl.AddCell(ftcell);
//add the master table to the document, which should contain everything
doc.Add(mas_tbl);
//close the document
doc.Close();
//HELPER METHODS
internal static PdfPTable CreateTable(int column_count,
bool include_border,
int h_align = Element.ALIGN_LEFT,
int v_align = Element.ALIGN_TOP,
float leading_multiplier = 1.1f,
float total_width = 468f,
float cell_padding_bottom = 5f,
float cell_padding_top = 0)
{
////////////////////////////////////////////////////////////////////////////////////
PdfPTable t = new PdfPTable(column_count);
//this line will keep the inner tables,
//from splitting off onto a 2nd page
//(making them only do it on wrapping)
//*NOTE: this needs to be tested thoroughly
// to make sure that rows aren't dropped!!!
t.SplitLate = false;
//used if you're adding paragraphs directly to table cells,
//instead of adding new PdfPCell objects
if (include_border == false)
{
t.DefaultCell.Border = Rectangle.NO_BORDER;
}
t.DefaultCell.PaddingLeft = 0f;
t.DefaultCell.PaddingRight = 0f;
t.DefaultCell.PaddingTop = cell_padding_top;
t.DefaultCell.PaddingBottom = cell_padding_bottom;
t.DefaultCell.HorizontalAlignment = h_align;
t.DefaultCell.VerticalAlignment = h_align;
t.TotalWidth = total_width;
t.LockedWidth = true;
t.DefaultCell.SetLeading(0, leading_multiplier);
return t;
}
internal static float GetLongestWidth(Paragraph p)
{
List<float> f = new List<float>();
foreach (Chunk c in p.Chunks)
{
string[] strarray = c.Content.Split(new string[] { "\r\n" }, System.StringSplitOptions.None);
for (int i = 0; i < strarray.Length; i++)
{
Chunk tc = new Chunk(strarray[i], c.Font);
Console.WriteLine(tc.Content + ", width: {0}", tc.GetWidthPoint().ToString());
f.Add(tc.GetWidthPoint());
}
}
return f.Max();
}
internal static float GetTotalHeightOfParagraph(Paragraph p)
{
PdfPTable t = clsPDF.CreateTable(1, false, cell_padding_bottom: 0f);
t.DefaultCell.PaddingBottom = 0f;
t.DefaultCell.PaddingTop = 0f;
t.AddCell(p);
return t.TotalHeight;
}
Solved it by using absolute positioning. I calculate the position that the footer should be in, by adding the footer height to the value of the bottom margin. To alleviate concerns about overwriting existing content, I calculate the amount of available space remaining on the last page; if it's not enough for my footer, I add a new page and post my content at the beginning of the next page.
byte[] content;
using (MemoryStream output = new MemoryStream())
{
PdfReader pdf_rdr = new PdfReader(strpath);
PdfStamper stamper = new PdfStamper(pdf_rdr, output);
PdfContentByte pcb = stamper.GetOverContent(pdf_rdr.NumberOfPages);
PdfPTable ftbl = clsPDF.CreateTable(1, false, cell_padding_bottom: 0f);
Paragraph fp = new Paragraph(new Phrase("Line 1 of footer\r\nLine 2 of footer\r\nhere's more of my footer text:\r\nthis project was SOOOO much fun\r\nand stuff", fb8));
//get the height of the footer
float footer_height = clsPDF.GetTotalHeightOfParagraph(fp);
Console.WriteLine("Footer height {0}", footer_height.ToString());
//get the total amount of "writeable" space per page
//(taking top and bottom margins into consideration)
float avail = doc.PageSize.Height - (doc.TopMargin + doc.BottomMargin);
//declare a variable to assist in calculating
//the total amount of "writeable" room remaining
//on the last page;
//start with with the current height of the master table
//(will do math below to calculate just what it's using
// on the last page)
float mas_tbl_last_page_height = mas_tbl.TotalHeight;
mas_tbl_last_page_height = mas_tbl_last_page_height % avail;
avail = avail - mas_tbl_last_page_height;
Console.WriteLine(clsPDF.GetTotalHeightOfParagraph(fp).ToString());
//float ft_top = avail - mas_tbl_last_page_height - clsPDF.GetTotalHeightOfParagraph(fp) - doc.BottomMargin;
float ft_top = doc.BottomMargin + clsPDF.GetTotalHeightOfParagraph(fp);
ftbl.AddCell(fp);
if (avail < clsPDF.GetTotalHeightOfParagraph(fp))
{
stamper.InsertPage(pdf_rdr.NumberOfPages + 1, pdf_rdr.GetPageSize(1));
pcb = stamper.GetOverContent(pdf_rdr.NumberOfPages);
ft_top = doc.PageSize.Height - doc.TopMargin;
}
ftbl.WriteSelectedRows(0, -1, doc.LeftMargin, ft_top, pcb);
// Set the flattening flag to true, as the editing is done
stamper.FormFlattening = true;
// close the pdf stamper
stamper.Close();
//close the PDF reader
pdf_rdr.Close();
content = output.ToArray();
}
//write the content to a PDF file
using (FileStream fs = File.Create(strpath))
{
fs.Write(content, 0, (int)content.Length);
fs.Flush();
}
Related
PDF Page Example
The PDF is composed by serveral paragraphs added to the document, and there is a long table which need to set started from top of one page(not the first page) which means a paragraph will be splitted to two parts.
If the table is short(length not exceed the page height), it can be done gracefully by handling Page Event of start page, just added the table to the canvas with the fixed position and size, and set the document's top margin.
PdfDocumentEvent docEvent = (PdfDocumentEvent)currentEvent;
PdfDocument pdfDoc = docEvent.GetDocument();
PdfPage page = docEvent.GetPage();
var currentPageNumber = pdfDoc.GetPageNumber(page);
var addingTablesOfPage = addingTables.Where(t => t.PageNumber == currentPageNumber && t.Prepared).ToList();
if (addingTablesOfPage != null && addingTablesOfPage.Count > 0)
{
PdfCanvas canvas = new PdfCanvas(page.NewContentStreamBefore(), page.GetResources(), pdfDoc);
PageSize pageSize = pdfDoc.GetDefaultPageSize();
var pageHeight = pageSize.GetHeight() - Constants.DocumentMargins[0] - Constants.DocumentMargins[2];
var pageWidth = pageSize.GetWidth() - Constants.DocumentMargins[1] - Constants.DocumentMargins[3];
var totalHeight = 0f;
var alignRight = false;
// add tables to the top of page
foreach (var table in addingTablesOfPage.OrderBy(t => t.Type).ToList())
{
var tableWidth = 0f;
float coordX = 0;
tableWidth = pageWidth;
coordX = pageSize.GetX() + doc.GetLeftMargin();
totalHeight += table.TableHeight;
float coordY = pageSize.GetTop() - Constants.DocumentMargins[0] - totalHeight;
Rectangle rect = new Rectangle(coordX, coordY, tableWidth, table.TableHeight);
var tableCanvas = new Canvas(canvas, rect);
tableCanvas.Add(table.Table);
tableCanvas.Close();
}
float topMargin = Constants.DocumentMargins[0] + totalHeight;
doc.SetTopMargin(topMargin);
}
else
{
doc.SetTopMargin(Constants.DocumentMargins[0]);
}
But here the table is too long which will be splitted to multi pages. As far as I known, Canvas class is primarily aimed at cases when you need to add elements to a specific predefined area on a page / XObject and it is not aimed at overflowing your content to next areas. So how can I achieve the behavior?
Thank you!
The description of what you are trying to achieve is quite vague (not clear if it's paragraph wrapping around table, or if it's a table in between paragraph; not clear if the table is bound to any text in paragraph or not etc etc), but my answer should guide you to the right direction.
I will show you how to add the paragraph part that fits till the end of the current page to the document, then add your table spanning across more than one page, and then add the leftover part of your paragraph.
First off, we need a custom renderer for our paragraph that will only render the part that currently fits in the page and will remember the rest to be added later.
private static class CustomParagraphRenderer extends ParagraphRenderer {
public CustomParagraphRenderer leftover;
private CustomParagraphRenderer toDraw;
public CustomParagraphRenderer(Paragraph modelElement) {
super(modelElement);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
LayoutResult result = super.layout(layoutContext);
if (result.getStatus() == LayoutResult.PARTIAL) {
// Expected result here is that paragraph splits across pages
leftover = (CustomParagraphRenderer) result.getOverflowRenderer();
toDraw = (CustomParagraphRenderer) result.getSplitRenderer();
return new LayoutResult(LayoutResult.FULL, result.getSplitRenderer().getOccupiedArea(), null, null);
}
return result;
}
#Override
public void draw(DrawContext drawContext) {
if (toDraw != null) {
toDraw.draw(drawContext);
} else {
super.draw(drawContext);
}
}
#Override
public IRenderer getNextRenderer() {
return new CustomParagraphRenderer((Paragraph) modelElement);
}
}
Now, we are adding some placeholder rectangle to the document just to occupy some space for the testing purposes, then we add our paragraph with our custom renderer (note that the expectation is that the paragraph will be rendered as much as possible on the current page, and the leftover part will be remembered), then we add our table spanning across multiple pages, and finally, we add our leftover paragraph part.
PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outFileName));
Document document = new Document(pdfDocument);
document.setFontSize(20);
Div emptyArea = new Div().setHeight(400).setBackgroundColor(ColorConstants.RED);
document.add(emptyArea);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 500; i++) {
sb.append(i).append(" ");
}
Paragraph p = new Paragraph(sb.toString());
p.setNextRenderer(new CustomParagraphRenderer(p).setParent(document.getRenderer()));
CustomParagraphRenderer paragraphRenderer = (CustomParagraphRenderer) p.createRendererSubTree();
// Add first part of the paragraph
document.getRenderer().addChild(paragraphRenderer);
Table table = new Table(3);
for (int i = 0; i < 25; i++) {
for (int j = 0; j < 3; j++) {
table.addCell(new Cell().add(new Paragraph("Cell (" + i + ", " + j + ")")));
}
}
// Add big table
document.add(table);
// Add leftover paragraph part
document.getRenderer().addChild(paragraphRenderer.leftover.setParent(document.getRenderer()));
document.close();
Result looks as follows. Note that I deliberately added consequent numbers as paragraph content so that it's clear that it's a continuous paragraph that is interrupted with a table:
I'm trying to align an image to the bottom-right of a cell.
I'm basically creating a table with two cells for each row. The cell contains text and an image, which I want to be aligned to the right bottom of the cells. Please refer to image
This is my code
PdfPTable table = new PdfPTable(2);
table.TotalWidth = 400f;
table.LockedWidth = true;
float[] widths = new float[] { 1.5f, 1.5f };
table.SetWidths(widths);
table.HorizontalAlignment = 0;
table.SpacingBefore = 50f;
table.SpacingAfter = 30f;
iTextSharp.text.Image logoImage = iTextSharp.text.Image.GetInstance(HttpContext.Current.Server.MapPath("~/Images/MyImage.png"));
logoImage.ScaleAbsolute(40, 40);
logoImage.Alignment = iTextSharp.text.Image.ALIGN_BOTTOM;
logoImage.Alignment = iTextSharp.text.Image.RIGHT_ALIGN;
foreach (EmployeeModel employee in employees)
{
PdfPCell cell = new PdfPCell();
cell.FixedHeight = 140f;
cell.PaddingLeft = 30f;
cell.PaddingRight = 10f;
cell.PaddingTop = 20f;
cell.PaddingBottom = 5f;
Paragraph p = new Paragraph(GetLabelCellText(Employee), NormalFont);
p.Alignment = Element.ALIGN_LEFT;
p.Alignment = Element.ALIGN_TOP;
cell.AddElement(p);
cell.AddElement(logoImage);
table.AddCell(cell);
}
How do I place the image at the bottom right of each cell (without affecting the position of the text of course).
The question is a bit ambiguous because you create a Table with two columns, but add cells without verifying the employees collection has an even number of elements, which will throw if not....
Assuming you really do want both the text and the image in a single cell, probably the simplest way to get the layout you want is to implement IPdfPCellEvent:
public class BottomRightImage : IPdfPCellEvent
{
public Image Image { get; set; }
public void CellLayout(
PdfPCell cell,
Rectangle position,
PdfContentByte[] canvases)
{
if (Image == null) throw new InvalidOperationException("image is null");
PdfContentByte canvas = canvases[PdfPTable.TEXTCANVAS];
Image.SetAbsolutePosition(
position.Right - Image.ScaledWidth - cell.PaddingRight,
position.Bottom + cell.PaddingBottom
);
canvas.AddImage(Image);
}
}
Then set the CellEvent property on the PdfPCell. Here's a simple working example:
using (var stream = new MemoryStream())
{
using (var document = new Document())
{
PdfWriter.GetInstance(document, stream);
document.Open();
var table = new PdfPTable(2)
{
HorizontalAlignment = Element.ALIGN_LEFT,
TotalWidth = 400f,
LockedWidth = true
};
var image = Image.GetInstance(imagePath);
image.ScaleAbsolute(40, 40);
var cellEvent = new BottomRightImage() { Image = image };
var testString =
#"first name: {0}
last name: {0}
ID no: {0}";
for (int i = 0; i < 2; ++i)
{
var cell = new PdfPCell()
{
FixedHeight = 140f,
PaddingLeft = 30f,
PaddingRight = 10f,
PaddingTop = 20f,
PaddingBottom = 5f
};
cell.CellEvent = cellEvent;
var p = new Paragraph(string.Format(testString, i))
{
Alignment = Element.ALIGN_TOP | Element.ALIGN_LEFT
};
cell.AddElement(p);
table.AddCell(cell);
}
document.Add(table);
}
File.WriteAllBytes(outputFile, stream.ToArray());
}
And PDF output:
An all-black square image is used to show the PdfPCell padding is taken into account.
My requirement is to print the details in landscape,not the page rotation.i need content printed in landscape.
Document doc = new Document(iTextSharp.text.PageSize.A4, (float)MarginLeft, (float)MarginRight, (float)MarginTop, (float)MarginBottom);
//210 mm width * 297 mm height
PaperWidthAvailable = iTextSharp.text.Utilities.MillimetersToPoints(210f) - ((float)MarginLeft + (float)MarginRight);
PaperHeightAvailable = iTextSharp.text.Utilities.MillimetersToPoints(297f) - ((float)MarginTop + (float)MarginBottom);
wdtOFcell = (float)BarcodeWidth + (float)BarcodeSpaceHorizontal;
colNo = (int)Math.Floor(PaperWidthAvailable / wdtOFcell);
TableWidth = wdtOFcell * colNo;
htOFcell = (float)BarcodeHeight + (float)BarcodeSpaceVertical;
PdfWriter writer = PdfWriter.GetInstance(doc, memStream);
doc.Open();
int noOfColumns = colNo;
// int additionalRow = imageBarcodeLists.Count % noOfColumns;
int i = 1;
PdfPTable table = new PdfPTable(noOfColumns);
table.DefaultCell.Border = iTextSharp.text.Rectangle.NO_BORDER;
table.HorizontalAlignment = 0;
table.TotalWidth = TableWidth;
table.LockedWidth = true;
float[] widths = new float[colNo];
for (int j = 0; j < colNo; j++)
{
widths[j] = wdtOFcell;
}
table.SetWidths(widths);
iTextSharp.text.Image itextBarcodeImage = null;
foreach (System.Drawing.Image barcodeImage in imageBarcodeLists)
{
var imageCompressor = new ImageCompressionUtility();
System.Drawing.Image barcodeImages = imageCompressor.TrimImageWhiteSpacesFromImage(barcodeImage);
itextBarcodeImage = iTextSharp.text.Image.GetInstance(barcodeImages, BaseColor.BLUE);
itextBarcodeImage.ScaleAbsolute((float)BarcodeWidth, (float)BarcodeHeight);
PdfPCell cells = new PdfPCell(itextBarcodeImage);
cells.Border = iTextSharp.text.Rectangle.NO_BORDER;
cells.PaddingTop = 0f;
cells.PaddingRight = 0f;
cells.PaddingBottom = 0f;
cells.PaddingLeft = 0f;
cells.UseAscender = true;
cells.FixedHeight = htOFcell;
cells.BackgroundColor = BaseColor.WHITE;
cells.Border = iTextSharp.text.Rectangle.NO_BORDER;
table.AddCell(cells);
i++;
}
doc.Add(table);
doc.Close();
The constant iTextSharp.text.PageSize.A4 is actually a Rectangle that is created like this:
public static readonly Rectangle A4 = new Rectangle(595,842);
If you want to rotate the page, you can use the Rotate() method as explained in my answer to this question: How to print custom page size as portrait in itextsharp
However, based on your comment "it rotates only the paper not the content written", you may be looking for a page size that is created like this:
Rectangle myA4 = new Rectangle(842,595);
Document doc = new Document(myA4);
If that doesn't work, please take a look at my answer to the question iText - Rotate page content while creating PDF
In that answer, I introduce a page rotation using a page event:
public class MyPdfPageEvent : iTextSharp.text.pdf.PdfPageEventHelper
{
public override void OnEndPage(PdfWriter writer, Document document)
{
writer.AddPageDictEntry(PdfName.ROTATE, PdfPage.SEASCAPE);
}
}
If none of the above has the effect you desire, you should improve your question (or it will be closed as "unclear what is asked").
I want to draw a schedule, based on a table (including borders), where some of the cells are (partly) filled with a color. This color is based on PdfTemplate which is added as an image to the cell.
I've create a test project for it, and attached placed the output at: PDF output
In the example, I expect it to draw the filled line, everytime 2 blocks larger. As you can see, it is a bit more than the 2 blocks.
The template I create, should be the exact the size of the table cells. So I don't understand, why it keeps getting bigger every iteration.
Any ideas how to fix this?
public Test()
{
int cellWidth = 23;
int usernameCellWith = 86;
float[] scheduleTableColumnWidths = new float[33];
scheduleTableColumnWidths[0] = usernameCellWith;
for (int i = 1; i <= 32; i++)
scheduleTableColumnWidths[i] = cellWidth;
using (MemoryStream stream = new MemoryStream())
{
Rectangle rect = PageSize.GetRectangle("A4");
rect = new Rectangle(rect.Height, rect.Width, 90);
using (Document document = new Document(rect, 10f, 10f, 10f, 10f))
{
PdfWriter writer = PdfWriter.GetInstance(document, stream);
if (!document.IsOpen())
{
document.NewPage();
document.Open();
}
PdfPTable containerTable = new PdfPTable(1);
containerTable.DefaultCell.Border = Rectangle.NO_BORDER;
containerTable.WidthPercentage = 100;
PdfPTable scheduleTable = new PdfPTable(scheduleTableColumnWidths);
for (int iRow = 0; iRow <= 23; iRow++)
{
PdfPCell usernameCell = new PdfPCell(new Phrase("user "+ iRow));
usernameCell.FixedHeight = cellWidth;
scheduleTable.AddCell(usernameCell);
for (int iColumn = 1; iColumn <= 32; iColumn++)
{
PdfPCell cell = new PdfPCell();
cell.FixedHeight = cellWidth;
if (iColumn == 1 && iRow % 2 == 0)
{
float width = (float)(iRow + 1) * cellWidth;
PdfTemplate template = writer.DirectContent.CreateTemplate(width, cellWidth);
template.SetColorFill(new BaseColor(System.Drawing.ColorTranslator.FromHtml("#255C8A")));
template.Rectangle(0, 0, width, cellWidth);
template.Fill();
cell = new PdfPCell(Image.GetInstance(template));
}
scheduleTable.AddCell(cell);
}
scheduleTable.CompleteRow();
}
containerTable.AddCell(scheduleTable);
containerTable.CompleteRow();
document.Add(containerTable);
document.CloseDocument();
}
}
}
Do you absolutely need to use a PdfTemplate? Unless you've just posted a simplified version of your code it is screaming to use a colspan instead and then iText will just calculate everything for you.
Below is a modified version of what you posted. I moved some of your "magic numbers" to variables just to help my own mental process. They might not be named ideally but they do the job. Also, when working in a double for loop I recommend always looping over rows and columns and then performing logic. Your code loops over rows, does some logic for a "first cell" and then loops over a subset of the columns and performs more logic. Although absolutely 100% valid I personally find it confusing, especially when having to jump between 1 and 0 as a starting value.
Also, also, I very strongly recommend avoiding CompleteRow(). It exists to solve a certain problem but it also literally means "my logic above might be wrong so iText, could you just fudge the numbers for me?"
I commented most of what I changed so hopefully this is helpful.
public void Test() {
int cellWidth = 23;
int usernameCellWith = 86;
int maxNumberOfColumns = 32;
int maxNumberOfRows = 24;
float[] scheduleTableColumnWidths = new float[maxNumberOfColumns];
scheduleTableColumnWidths[0] = usernameCellWith;
for (int i = 1; i < maxNumberOfColumns; i++)
scheduleTableColumnWidths[i] = cellWidth;
using (MemoryStream stream = new MemoryStream()) {
iTextSharp.text.Rectangle rect = PageSize.GetRectangle("A4");
rect = new iTextSharp.text.Rectangle(rect.Height, rect.Width, 90);
using (Document document = new Document(rect, 10f, 10f, 10f, 10f)) {
PdfWriter writer = PdfWriter.GetInstance(document, stream);
//Unless this code is just a sample, a new document is never open and NewPage() is implied so these four lines could just be document.Open();
if (!document.IsOpen()) {
document.NewPage();
document.Open();
}
PdfPTable containerTable = new PdfPTable(1);
containerTable.DefaultCell.Border = iTextSharp.text.Rectangle.NO_BORDER;
containerTable.WidthPercentage = 100;
PdfPTable scheduleTable = new PdfPTable(scheduleTableColumnWidths);
//Loop through each row
for (int iRow = 0; iRow < maxNumberOfRows; iRow++) {
//Loop through each column
for (int iColumn = 0; iColumn < maxNumberOfColumns; iColumn++) {
//Placeholder variable that will be instantiated within the if statements below
PdfPCell cell;
//On the first column (of every row)
if (0 == iColumn) {
cell = new PdfPCell(new Phrase("user " + iRow));
//On the second column of every other row starting with the first (zeroth) row
} else if ((iColumn == 1) && (iRow % 2 == 0)) {
cell = new PdfPCell();
//Have the cell span based on the current row number
cell.Colspan = iRow + 1;
//Set the color
cell.BackgroundColor = new BaseColor(System.Drawing.ColorTranslator.FromHtml("#255C8A"));
//Tell the inner for loop that we've accounted for some cells already
iColumn += iRow;
//Every other cell
} else {
cell = new PdfPCell();
}
//This is weird
cell.FixedHeight = cellWidth;
//Regardless of the above, add a cell
scheduleTable.AddCell(cell);
}
}
containerTable.AddCell(scheduleTable);
document.Add(containerTable);
document.CloseDocument();
}
}
}
I am pretty new in iTextSharp and I have the following situation: I am creating a PDF that contains an header and a footer (for the header and footer creation I am using a class that extends PdfPageEventHelper and I have override the OnStartPage() and the OnEndPage() method, it work fine).
Now my problem is that I have to insert as Page X of Y into my footer. Where X**is the **current page number and Y is the total page number. The value of Y is not fixed (because the length of my PDF is not known in advance because it depends depends on the length of the content and can differ from PDF to PDF). How can I handle this situation? (inserting the correct Y value in each footer?)
Searching online I have find this tutorial (that is for Java iText but maybe it is not so different from iTextSharp version): http://itextpdf.com/examples/iia.php?id=104
In this example it create a PDF and its header contains something like Page X of Y.
I am trying to understand this example (and translate it for iTextSharp) but I have some doubts about how it work (and if it is a real solution for my problem).
From what I can understand it perform the following operations:
Into the class that extends PdfPageEventHelper it is declared a PdfTemplate object
PdfTemplate total;
I think that maybe it handles the total pages number that are into my PDF document, but reading the official documentation I have not many information about what exactly do this class: http://api.itextpdf.com/itext/com/itextpdf/text/pdf/PdfTemplate.html
I am trying to do something similar into my class that extends PdfPageEventHelper but I can't do it.
This is my not working code:
public class PdfHeaderFooter : PdfPageEventHelper
{
// The template with the total number of pages:
PdfTemplate total;
private static int numPagina = 1;
public Image CellImage;
private string folderImages;
private string _sourceId;
public PdfHeaderFooter(string _folderImages, string sourceId)
{
folderImages = _folderImages;
_sourceId = sourceId;
}
/* Creates the PdfTemplate that will hold the total number of pages.
* Write on top of document
* */
public override void OnOpenDocument(PdfWriter writer, Document document)
{
base.OnOpenDocument(writer, document);
// (Nobili) Page Number:
total = writer.DirectContent.CreateTemplate(30, 16);
//PdfPTable tabFot = new PdfPTable(new float[] { 1F });
PdfPTable tabFot = new PdfPTable(2);
tabFot.WidthPercentage = 98;
tabFot.SpacingAfter = 10F;
PdfPCell cell;
//tabFot.TotalWidth = 300F;
cell = new PdfPCell(new Phrase("Header"));
tabFot.AddCell(cell);
tabFot.WriteSelectedRows(0, -1, 150, document.Top, writer.DirectContent);
}
// write on end of each page
public override void OnEndPage(PdfWriter writer, Document document)
{
base.OnEndPage(writer, document);
int pageN = writer.PageNumber;
String text = "Page " + pageN + " of ";
//PdfPTable tabFoot = new PdfPTable(new float[] { 1F });
PdfPTable tabFoot = new PdfPTable(3);
tabFoot.TotalWidth = document.Right - document.Left;
tabFoot.DefaultCell.Border = PdfPCell.NO_BORDER;
tabFoot.DefaultCell.CellEvent = new RoundedBorder();
PdfPTable innerTable = new PdfPTable(2);
innerTable.SetWidths(new int[] { 247, 246 });
innerTable.TotalWidth = document.Right - document.Left;
innerTable.DefaultCell.Border = PdfPCell.NO_BORDER;
PdfPCell innerCellLeft = new PdfPCell(new Phrase("Early Warning - Bollettino")) { Border = PdfPCell.NO_BORDER, Padding = 5, MinimumHeight = 20, HorizontalAlignment = Element.ALIGN_LEFT };
//PdfPCell innerCellRight = new PdfPCell(new Phrase("Pag. " + numPagina + "/5")) { Border = PdfPCell.NO_BORDER, Padding = 5, MinimumHeight = 20, HorizontalAlignment = Element.ALIGN_RIGHT };
PdfPCell innerCellCenter = new PdfPCell(new Phrase(text)) { Border = PdfPCell.NO_BORDER, Padding = 5, MinimumHeight = 20, HorizontalAlignment = Element.ALIGN_RIGHT };
PdfPCell innerCellRight = new PdfPCell(Image.GetInstance(total)) { Border = PdfPCell.NO_BORDER, Padding = 5, MinimumHeight = 20, HorizontalAlignment = Element.ALIGN_RIGHT };
innerTable.AddCell(innerCellLeft);
innerTable.AddCell(innerCellRight);
tabFoot.AddCell(innerTable);
tabFoot.WriteSelectedRows(0, -1, document.Left, document.Bottom, writer.DirectContent);
numPagina++;
}
// write on start of each page
public override void OnStartPage(PdfWriter writer, Document document)
{
base.OnStartPage(writer, document);
PdfPTable tabHead = new PdfPTable(3);
tabHead.SetWidths(new int[] { 165, 205, 125 });
//tabHead.TotalWidth = 460F;
tabHead.TotalWidth = document.Right - document.Left; // TotalWidth = 495
tabHead.WidthPercentage = 98;
PdfPCell cell1 = new PdfPCell(iTextSharp.text.Image.GetInstance(folderImages + "logoEarlyWarning.png"), true) { Border = PdfPCell.BOTTOM_BORDER };
tabHead.AddCell(cell1);
//tabHead.AddCell(new PdfPCell(new Phrase("CELL 1:")) { Border = PdfPCell.BOTTOM_BORDER, Padding = 5, MinimumHeight = 50, PaddingTop = 15, });
tabHead.AddCell(new PdfPCell(new Phrase("CELL 2:")) { Border = PdfPCell.BOTTOM_BORDER, Padding = 5, MinimumHeight = 50, PaddingTop = 15 });
if(_sourceId == "NVD")
{
iTextSharp.text.Image logo = iTextSharp.text.Image.GetInstance(folderImages + "nvdLogo.png");
logo.ScalePercent(48f);
//PdfPCell cell3 = new PdfPCell(iTextSharp.text.Image.GetInstance(folderImages + "nvdLogo.png"), true) { Border = PdfPCell.BOTTOM_BORDER, PaddingBottom = 25 };
PdfPCell cell3 = new PdfPCell(logo) { Border = PdfPCell.BOTTOM_BORDER, PaddingLeft = 50 };
tabHead.AddCell(cell3);
}
else if(_sourceId == "DeepSight")
{
PdfPCell cell3 = new PdfPCell(iTextSharp.text.Image.GetInstance(folderImages + "DSLogo.jpg"), true) { Border = PdfPCell.BOTTOM_BORDER };
tabHead.AddCell(cell3);
}
//tabHead.AddCell(new PdfPCell(new Phrase("CELL 3:")) { Border = PdfPCell.BOTTOM_BORDER, Padding = 5, MinimumHeight = 50, PaddingTop = 15 });
tabHead.WriteSelectedRows(0, -1, document.Left, document.Top, writer.DirectContent);
}
//write on close of document
public override void OnCloseDocument(PdfWriter writer, Document document)
{
base.OnCloseDocument(writer, document);
}
}
}
I want put in the footer something like the Page X of Y as shown in the tutorial
I tryied to do the following thing:
1) I declared the PdfTemplate total; object as field of my class and I initialize it into my OnOpenDocument() method
total = writer.DirectContent.CreateTemplate(30, 16);
2) Into my OnEndPage() method I put:
String text = "Page " + pageN + " of ";
and I created the following table to show the Page X of Y
PdfPCell innerCellLeft = new PdfPCell(new Phrase("Early Warning - Bollettino")) { Border = PdfPCell.NO_BORDER, Padding = 5, MinimumHeight = 20, HorizontalAlignment = Element.ALIGN_LEFT };
PdfPCell innerCellCenter = new PdfPCell(new Phrase(text)) { Border = PdfPCell.NO_BORDER, Padding = 5, MinimumHeight = 20, HorizontalAlignment = Element.ALIGN_RIGHT };
PdfPCell innerCellRight = new PdfPCell(Image.GetInstance(total)) { Border = PdfPCell.NO_BORDER, Padding = 5, MinimumHeight = 20, HorizontalAlignment = Element.ALIGN_RIGHT };
But it don't work and throw me an exception
What could be the problem in my code?
You are copy/pasting code without reading the documentation that comes with the examples. Moreover you are completely ignoring the C# version of the examples from my book. I have paid good money to a C# developer to port these examples. It feels as if that money was thrown away. You can find the example you tried to port yourself here: http://tinyurl.com/itextsharpIIA2C05
Error #1: you are adding content in the OnStartPage() method. That is forbidden! This is documented on many places. See for instance this answer copied from page 150 of the official documentation:
FAQ Why is it not advised to add content in the onStartPage() method? You'll remember from section 5.2.4 that iText ignores newPage() calls when the current page is empty. This method is executed ā or ignored ā when you call it explicitly from your code, but it's also invoked implicitly from within iText on multiple occasions. It's important that it's ignored for empty pages; otherwise you'd end up with plenty of unwanted new pages that are unintentionally left blank. If you add content in an onStartPage() method, there's always a risk of having unwanted pages. Consider it more safe to reserve the onEndPage() method for adding content.
Error #2: you are adding content in the OnOpenDocument() method. Why would that make sense?
Error #3: you create the total object because you want to create a place holder that can be added to each page. Once you know the total number of pages, you want to fill out that number on that place holder. However: I don't see you doing this anywhere. The appropriate place to do this, is obviously in the OnCloseDocument() even. This event is triggered right before the document is closed, so at that moment, the total number of pages is known. This is, as Sherlock Holmes would say, elementary, my dear!