How to add new lines on a splitted page - itext

I'm using (in Java)
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
I'm printing a header, a logo, a picture and a table on each page. Follow is the code ( a little "pseudo" ):
Document document = new Document( PageSize.A4 );
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream( pdfFullPath ) );
writer.setPageEvent( new HeaderAndFooter() );
for ( some condition for each page ) {
document.add( theLogoInLeftTop );
document.add( new Paragraph("\n\n\n\n\n") ); <<<-- Will give room for the heder printed by the `setPageEvent`
Image thumb = Image.getInstance( theImage );
float scaler = ((document.getPageSize().getWidth() - document.leftMargin()
- document.rightMargin() ) / thumb.getWidth()) * 100;
thumb.scalePercent(scaler);
thumb.setBorder( Image.BOX );
thumb.setBorderWidth(1);
document.add( thumb );
PdfPTable table = new PdfPTable( new float[] { 1, 3 } );
table.setTotalWidth( document.getPageSize().getWidth() - document.leftMargin() - document.rightMargin() );
table.setLockedWidth(true);
// Too much rows will cause a page break and will print
// the rows over the header.
for( some or few rows ) {
table.addCell( someLeftText );
table.addCell( someRightText );
}
document.add(table);
document.newPage();
}
document.close();
writer.close();
When I have few rows all is fine and all content is printed in one page. But when I have a lot of rows a new page is started ( not my document.newPage() ) and the rows contents are printed over my header and logo ( they don't respect the document.add( new Paragraph("\n\n\n\n\n") ); at begining of each page ).
How can I do a document.add( new Paragraph("\n\n\n\n\n") ); when a new page is started by "content overflow" so I can save the header space ?
The logo is not being printed too.
This is my header creator (Just to illustrate):
public class HeaderAndFooter extends PdfPageEventHelper {
#Override
public void onEndPage(PdfWriter writer, Document document) {
// Printing the headers using
// ColumnText.showTextAligned
}
}

Solved:
After removing all the \n's I added the new margins at the document creation.
No need of document.setMargins()
float left = 30;
float right = 30;
float top = 85;
float bottom = 20;
Document document = new Document( PageSize.A4, left, right, top, bottom );

Related

Is there any way to vertically-align iText (iTextSharp) text using ColumnText?

Is there any way to vertically-align text using ColumnText? I am using iTextSharp
Chunk c = new Chunk(text, this.detailFont);
Phrase myText = new Phrase(c);
ct.SetSimpleColumn(myText, llx, lly, urx, ury, lineheight, Element.ALIGN_BOTTOM);
ct.Go();
My text appears at the top of the box I specify. I would like it to align with the bottom of the box.
as it is as it should be
|---------------| |---------------|
| some text | ---> | |
| | | some text |
|---------------| |---------------|
The answer of birwin is just not working. The ColumnText alignment is accept only horizontal alignments. And this is working till ColumnText text is in Text mode. When you call the ColumnText.AddElement the ColumnText switches to CompositMode and the Alignment has no effect.
If you really want to Vertical Alignment in ColumnText then you have to create a content which has aligned content and must add to ColumnText. Only the Table and Cell the correct object where vertical alignment has any effect.
So first we have to create a table, with one column and one cell. And set the size of the cell to same as column text size.
And second, this table must be added to ColumntText
Here is my code:
// create doc
var pdfDoc = new Document( PageSize.A4 );
var pdfWriter = PdfWriter.GetInstance( pdfDoc, new FileStream( Path.Combine( Path.GetTempPath(), "test.pdf" ), FileMode.Create ) );
pdfDoc.Open();
var canvas = pdfWriter.DirectContent;
canvas.SaveState();
canvas.Rectangle( 100, 100, 100, 100 );
canvas.SetRgbColorFill( 255, 128, 128 );
canvas.Fill();
canvas.RestoreState();
// create font
BaseFont bfTimes = BaseFont.CreateFont( BaseFont.TIMES_ROMAN, BaseFont.CP1252, false );
Font times = new Font( bfTimes, (float)10, Font.ITALIC, new BaseColor( (int)232434 ) );
Phrase myText = new Phrase( new Chunk( "Hello World", times ) );
ColumnText ct = new ColumnText( canvas );
/* wrong birwin code :
ct.SetSimpleColumn( myText, 100, 100, 200, 200, ct.Leading, Element.ALIGN_BOTTOM );
*/
// new working code: crreate a table
PdfPTable table = new PdfPTable( 1 );
table.SetWidths( new[] { 1 } );
table.WidthPercentage = 100;
table.AddCell( new PdfPCell( myText )
{
HorizontalAlignment = Element.ALIGN_RIGHT,
VerticalAlignment = Element.ALIGN_BOTTOM,
FixedHeight = 100
} );
ct.SetSimpleColumn( 100, 100, 200, 200 );
ct.AddElement( table );
ct.Go();
pdfDoc.Close();
The Solution:
Chunk c = new Chunk(text, this.detailFont);
Phrase myText = new Phrase(c);
ct.SetSimpleColumn(myText, llx, lly, urx, ury, lineheight, Element.ALIGN_BOTTOM);
ct.Go(true);
if (ct.LinesWritten == 1)
{
ury = ury - (this.rowheight);
}
ct.SetSimpleColumn(myText, llx, lly, urx, ury, lineheight, Element.ALIGN_BOTTOM);
ct.Go();
ct.Go(true) -- Apparently the "true" here indicates the ColumnText is created in "Simulation" mode. I was not aware of this mode before comments to my question were posted.
if (ct.LinesWritten == 1) checks the number of lines that were written. If only one line was written, I shrink the size of the upper-right-y corner of my rectangle.
ct.SetSimpleColumn(myText, llx, lly, urx, ury, lineheight, Element.ALIGN_BOTTOM); when I call this line the second time, the ury (upper-right-hand corner) location has been changed and the box is smaller.
ct.Go() - The second time I call this, I don't pass the "true" param so the second call is not simulation mode and writes the data to the document.

Strange display of a table of radio buttons

I am creating a form, in which there is a table of buttons. The following shows the idea:
Good OK Bad
Item1 [button] [button] [button]
Item2 [button] [button] [button]
Each row is a button group.
My program is able to work for a small table like the above. However, when rows or columns are more than one page to hold, the display is weird. This is my program:
PdfPTable table = new PdfPTable(colHeaders.size() + 1 );
table.setWidthPercentage(100);
PdfPCell cell;
table.setSpacingBefore(10);
table.setSpacingAfter(0);
table.setKeepTogether(false); //tested "true" different display error
//upper-left corner empty cell
cell = new CustomPdfPCell();
cell.setBackgroundColor(BaseColor.LIGHT_GRAY);
table.addCell(cell);
//header row
for (TableOfChoicesHeader c: colHeaders) {
String text = c.getText();
cell = new PdfPCell(new Phrase(text));
cell.setBackgroundColor(BaseColor.LIGHT_GRAY);
table.addCell(cell);
}
List<PdfFormField> radioGroups= new ArrayList<PdfFormField>();
//for each row
for (TableOfChoicesHeader r: rowHeaders) {
//row header
String rowText = r.getText();
cell = new PdfPCell(new Phrase(rowText));
cell.setBackgroundColor(BaseColor.LIGHT_GRAY);
table.addCell(cell);
//for the current row row
String fieldName = "question" + q.getId() +"_" + r.getId().toString();
PdfFormField radiogroup = PdfFormField.createRadioButton(writer, true);
radiogroup.setFieldName(fieldName);
radioGroups.add(radiogroup);
for (TableOfChoicesHeader c: colHeaders) {
cell = new PdfPCell();
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setPadding(5);
cell.setCellEvent(new CheckboxCellEvent(fieldName, r.getId() + "_" + c.getId()));
table.addCell(cell);
}
}
document.add(table);
for (PdfFormField g: radioGroups) {
writer.addAnnotation(g);
}
Here are the screenshots when table.setKeepTogether(false). The display is a little different but still strange if the value is true.
Page 1
Page 2
Update
By following Samuel's approach, I am able to see radio buttons displayed on the first page. However, the buttons on the first page got displayed on the second page. See the screenshots.
Update 2
I got it working by adding the following in MyCellField :
writer.addAnnotation(radiogroup);
instead of
PdfFormField field = radio.getRadioField();
writer.addAnnotation(field);
I cannot explain why. Thank Samuel and Bruno for their help!
Looking at your code again, is there any reason why you add the annotations for the radiobuttons all in one go at the end?.
Have you tried adding the annotation whenever you've added your row of cells? Something like this:
for (TableOfChoicesHeader r: rowHeaders) {
//row header
String rowText = r.getText();
cell = new PdfPCell(new Phrase(rowText));
cell.setBackgroundColor(BaseColor.LIGHT_GRAY);
table.addCell(cell);
//for the current row row
String fieldName = "question" + q.getId() +"_" + r.getId().toString();
PdfFormField radiogroup = PdfFormField.createRadioButton(writer, true);
radiogroup.setFieldName(fieldName);
radioGroups.add(radiogroup);
for (TableOfChoicesHeader c: colHeaders) {
cell = new PdfPCell();
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setPadding(5);
cell.setCellEvent(new CheckboxCellEvent(fieldName, r.getId() + "_" + c.getId()));
table.addCell(cell);
}
writer.addAnnotation(radiogroup)
}
EDIT:
As Bruno said, the examples for distributing a radio-group over multiple pages are RadioGroupMultiPage1 and RadioGroupMultiPage2. The examples for distributing a radio group across a table is found here: Distributing radio buttons of a radio field across multiple PdfCells.
EDIT 2, Electric Bogaloo:
Wipped up a quick example based on your code and the examples Bruno pointed out:
I basicly took your code frm the question above(with some stub-implementations for your custom data-classes), and used the MyCellField PdfCellEvent extension found in RadioGroupMultiPage2:
public MyCellField(PdfWriter writer, PdfFormField radiogroup, String value) {
this.writer = writer;
this.radiogroup = radiogroup;
this.value = value;
}
public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) {
try {
RadioCheckField radio = new RadioCheckField(writer, position, null, value);
PdfFormField field = radio.getRadioField();
writer.addAnnotation(field);
radiogroup.addKid(field);
} catch (IOException ex) {
// do nothing
} catch (DocumentException ex) {
// do nothing
}
}

Mixing text and images causes incorrect vertical positioning

Using iTextSharp version 5.5.8 (same bug existed in 5.5.7), there's an unpleasant bug when you add images to Chapters and Sections - the images and the section headings start out OK but quickly become offset relative to each other.
The PDF generated from the following code starts out correctly, it says "Section 1" and below it is the image. The next section ("Section 2") has a little of the image overlapping the section text, the next section is even worse, etc. I think it's the text that's mal-positioned, not the image.
Is this a known iTextSharp bug?
static Document m_doc = null;
static BaseFont m_helvetica = null;
static Font m_font = null;
static PdfWriter m_writer = null;
static Image m_image = null;
static void Main(string[] args)
{
m_doc = new Document(PageSize.LETTER);
m_helvetica = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
m_font = new Font(m_helvetica, 10.0f);
m_writer = PdfWriter.GetInstance(m_doc, new FileStream("Output.pdf", FileMode.Create));
m_writer.StrictImageSequence = true;
m_doc.Open();
m_doc.Add(new Chunk("Created by iTextSharp version " + new iTextSharp.text.Version().GetVersion, m_font));
Chapter chapter = new Chapter("Chapter 1", 1);
chapter.TriggerNewPage = false;
if (m_image == null)
{
m_image = Image.GetInstance(new Uri("https://pbs.twimg.com/profile_images/2002307628/Captura_de_pantalla_2012-03-17_a_la_s__22.14.48.png"));
m_image.ScaleAbsolute(100, 100);
}
for (int i = 0; i < 5; i++)
{
Section section = chapter.AddSection(18, "Section " + (i + 1));
section.Add(new Chunk(" ", m_font));
section.Add(m_image);
}
m_doc.Add(chapter);
m_doc.Close();
}
From the documentation for the Java version:
A Section is a part of a Document containing other Sections, Paragraphs, List and/or Tables.
Further looking at the Add() method in the C# source we see:
Adds a Paragraph, List, Table or another Section
Basically, instead of a Chunk use a Paragraph. So instead of this
section.Add(new Chunk(" ", m_font));
Use this:
section.Add(new Paragraph(new Chunk(" ", m_font)));
Or even just this:
section.Add(new Paragraph(" ", m_font));

iTextSharp does not render header/footer when using element generated by XmlWorkerHelper

I am trying to add header/footer to a PDF whose content is otherwise generated by XMLWorkerHelper. Not sure if it's a placement issue but I can't see the header/footer.
This is in an ASP.NET MVC app using iTextSharp and XmlWorker packages ver 5.4.4 from Nuget.
Code to generate PDF is as follows:
private byte[] ParseXmlToPdf(string html)
{
XhtmlToListHelper xhtmlHelper = new XhtmlToListHelper();
Document document = new Document(PageSize.A4, 30, 30, 90, 90);
MemoryStream msOutput = new MemoryStream();
PdfWriter writer = PdfWriter.GetInstance(document, msOutput);
writer.PageEvent = new TextSharpPageEventHelper();
document.Open();
var htmlContext = new HtmlPipelineContext(null);
htmlContext.SetTagFactory(Tags.GetHtmlTagProcessorFactory());
var cssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(false);
cssResolver.AddCssFile(HttpContext.Server.MapPath("~/Content/themes/mytheme.css"), true);
var pipeline = new CssResolverPipeline(cssResolver, new HtmlPipeline(htmlContext, new PdfWriterPipeline(document, writer)));
var worker = new XMLWorker(pipeline, true);
var parser = new XMLParser();
parser.AddListener(worker);
using (TextReader sr = new StringReader(html))
{
parser.Parse(sr);
}
//string text = "Some Random Text";
//for (int k = 0; k < 8; ++k)
//{
// text += " " + text;
// Paragraph p = new Paragraph(text);
// p.SpacingBefore = 8f;
// document.Add(p);
//}
worker.Close();
document.Close();
return msOutput.ToArray();
}
Now instead of using these three lines
using (TextReader sr = new StringReader(html))
{
parser.Parse(sr);
}
if I comment them out and uncomment the code to add a random Paragraph of text (commented in above sample), I see the header/footer along with the random text.
What am I doing wrong?
The EventHandler is as follows:
public class TextSharpPageEventHelper : PdfPageEventHelper
{
public Image ImageHeader { get; set; }
public override void OnEndPage(PdfWriter writer, Document document)
{
float cellHeight = document.TopMargin;
Rectangle page = document.PageSize;
PdfPTable head = new PdfPTable(2);
head.TotalWidth = page.Width;
PdfPCell c = new PdfPCell(ImageHeader, true);
c.HorizontalAlignment = Element.ALIGN_RIGHT;
c.FixedHeight = cellHeight;
c.Border = PdfPCell.NO_BORDER;
head.AddCell(c);
c = new PdfPCell(new Phrase(
DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + " GMT",
new Font(Font.FontFamily.COURIER, 8)
));
c.Border = PdfPCell.TOP_BORDER | PdfPCell.RIGHT_BORDER | PdfPCell.BOTTOM_BORDER | PdfPCell.LEFT_BORDER;
c.VerticalAlignment = Element.ALIGN_BOTTOM;
c.FixedHeight = cellHeight;
head.AddCell(c);
head.WriteSelectedRows(
0, -1, // first/last row; -1 flags all write all rows
0, // left offset
// ** bottom** yPos of the table
page.Height - cellHeight + head.TotalHeight,
writer.DirectContent
);
}
}
Feeling angry and stupid for having lost more than two hours of my life to Windows 8.1! Apparently the built in PDF Reader app isn't competent enough to show 'Headers'/'Footers'. Installed Foxit Reader and opened the output and it shows the Headers allright!
I guess I should try with Adobe Reader too!!!
UPDATES:
I tried opening the generated PDF in Adobe Acrobat reader and finally saw some errors. This made me look at the HTML that I was sending to the XMLWorkerHelper more closely. The structure of the HTML had <div>s inside <table>s and that was tripping up the generated PDF. I cleaned up the HTML to ensure <table> inside <table> and thereafter the PDF came out clean in all readers.
Long story short, the code above is fine, but if you are testing for PDF's correctness, have Adobe Acrobat Reader handy at a minimum. Other readers behave unpredictably as in either not reporting errors or incorrectly rendering the PDF.

iText - Strange column- / page changing behaviour with ColumnText

I am quite new to iText and trying to accomplish the following:
read a list of text files from local hd
arrange the texts of the files in a 2-column layout pdf file
add a consecutively numbered index before each text
I started with the MovieColumns1 example (http://itextpdf.com/examples/iia.php?id=64) and ended up with the following code:
final float[][] COLUMNS_COORDS = { { 36, 36, 296, 806 }, { 299, 36, 559, 806 } };
Document document = new Document(PageSize.A4);
PdfWriter writer = PdfWriter.getInstance(document, resultFile);
document.open();
ColumnText ct = new ColumnText(writer.getDirectContent());
ct.setSimpleColumn(COLUMNS_COORDS[0][0], COLUMNS_COORDS[0][1],
COLUMNS_COORDS[0][2], COLUMNS_COORDS[0][3]);
File textDir = new File("c:/Users/raddatz/Desktop/123/texts/");
File[] files = textDir.listFiles();
int i = 1;
int column = 0;
for (File file : files) {
String text = FileUtils.readFileToString(file, "UTF-8");
float yLine = ct.getYLine();
System.out.println("adding '" + file.getName() + "'");
PdfPCell theText = new PdfPCell(new Phrase(text, new Font(Font.HELVETICA, 10)));
theText.setBorder(Rectangle.NO_BORDER);
theText.setPaddingBottom(10);
PdfPCell runningNumber = new PdfPCell(new Phrase(new DecimalFormat("00").format(i++), new Font(
Font.HELVETICA, 14, Font.BOLDITALIC,
new Color(0.7f, 0.7f, 0.7f))));
runningNumber.setBorder(Rectangle.NO_BORDER);
runningNumber.setPaddingBottom(10);
PdfPTable table = new PdfPTable(2);
table.setWidths(new int[] { 12, 100 });
table.addCell(runningNumber);
table.addCell(theText);
ct.addElement(table);
int status = ct.go(true);
if (ColumnText.hasMoreText(status)) {
column = Math.abs(column - 1);
if (column == 0) {
document.newPage();
System.out.println("inserting new page with size :" + document.getPageSize());
}
ct.setSimpleColumn(
COLUMNS_COORDS[column][0], COLUMNS_COORDS[column][1],
COLUMNS_COORDS[column][2], COLUMNS_COORDS[column][3]);
yLine = COLUMNS_COORDS[column][3];
System.out.println("correcting yLine to: " + yLine);
} else {
ct.addElement(table);
}
ct.setYLine(yLine);
System.out.println("before adding: " + ct.getYLine());
status = ct.go(false);
System.out.println("after adding: " + ct.getYLine());
System.out.println("--------------------------------");
}
document.close();
Here you can see the result:
http://d.pr/f/NEmx
Looking at the first page of the resulting PDF I assumed everything was working out fine.
But on second page you can see the problem(s):
text #31 is not displayed completely (first line + index is cut / not in visible area)
text #46 is not displayed completely (first three lines + index is cut / not in visible area)
On page 3 everything seems to be ok again. I am really lost here.
-- UPDATE (2013-03-14) --
I have analyzed the contents of the PDF now. The problem is not that content is show in non-visible areas but that the content is not present in the pdf at all. The missing part of the content is exactly the one which would have fit in the previous column / page. So it seems like ColumnText.go(true) is manipulating the object passed by addElement() before. Can someone confirm this? If so: what can I do about it?
-- end UPDATE (2013-03-14) --
Looking forward to your reply
regards,
sven
Solved! As soon as ColumnText indicates a table will not fit the current column I reinitialize ct with a new instance of ColumnText and add the table again.
In other words:
Each instance of ColumnText is exactly dealing one column of my document.
if (ColumnText.hasMoreText(status) || mediaPoolSwitch) {
column = Math.abs(column - 1);
if (mediaPoolSwitch) {
currentMediaPool = mediapool;
column = 0;
}
if (column == 0) {
document.newPage();
writeTitle(writer.getDirectContent(), mediapool.getName());
}
ct = new ColumnText(writer.getDirectContent());
ct.addElement(table);
ct.setSimpleColumn(
COLUMNS_COORDS[column][0], COLUMNS_COORDS[column][1],
COLUMNS_COORDS[column][2], COLUMNS_COORDS[column][3]);
yLine = COLUMNS_COORDS[column][3];
LOG.debug("correcting yLine to: " + yLine);
} else {
ct.addElement(table);
}
ct.setYLine(yLine);
ct.go();