iText PDF using .getSplitRenderer for Table renderer - itext7

In iText PDF 7, I am using the .layout method of the Table renderer to determine whether a table will break across a page.
However, when I add the .getSplitRenderer (returned from the layout result object) as a child of the Documents's renderer, I get this error: "java.lang.IndexOutOfBoundsException".
I'm using iText PDF version 7.1.7 in its Java incarnation. The last three entries in the stacktrace are:
java.util.ArrayList$SubList.rangeCheck(ArrayList.java:1225)
java.util.ArrayList$SubList.get(ArrayList.java:1042)
com.itextpdf.layout.renderer.TableBorders.processAllBordersAndEmptyRows(TableBorders.java:139)
Here is a bare-bones version of the code that triggers the error:
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PdfWriter pdfWriter = new PdfWriter(outputStream);
PdfDoc pdfDoc = new PdfDocument(pdfWriter);
PageSize pageSize = new PageSize(612, 792);
Document doc = new Document(pdfDoc, pageSize);
Table table = new Table([50, 50, 50]);
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 3; j++) {
Cell cell = new Cell();
cell.setHeight(100);
table.addCell(cell);
}
}
LayoutContext context = new LayoutContext(doc.getRenderer().getCurrentArea().clone());
TableRenderer tableRenderer = (TableRenderer)table.createRendererSubTree();
LayoutResult result = tableRenderer.setParent(doc.getRenderer()).layout(context);
if (result.getStatus() == result.PARTIAL) {
tableRenderer = (TableRenderer) result.getSplitRenderer();
doc.getRenderer().addChild(tableRenderer); // this is where the error occurs
}

When you add a child to the DocumentRenderer it will layout and draw it automatically. It is not possible to layout a renderer several times in most cases (although what can be improved here is the exception type and message).
If you want to draw the part that fits immediately you can use the following line:
tableRenderer.draw(new DrawContext(pdfDocument, new PdfCanvas(pdfDocument.getPage(doc.getRenderer().getCurrentArea().getPageNumber()))));
Complete if expression:
if (result.getStatus() == LayoutResult.PARTIAL) {
tableRenderer = (TableRenderer) result.getSplitRenderer();
tableRenderer.draw(new DrawContext(pdfDocument, new PdfCanvas(pdfDocument.getPage(doc.getRenderer().getCurrentArea().getPageNumber()))));
}
It might have some drawbacks in complex cases though, so if you are dealing with complex layout or tagged documents I would recommend using binary search to determine the amount of content that still fits and add that content as an element to Document instance still.
An approach that is between those two is adding the table completely and then removing the extra pages from PdfDocument. In this case keep in mind that you will have to recreate the DocumentRenderer because it does not keep track of low level events like page removal from PdfDocument.

Related

iText7 flips text - bug?

Consider a 1-page PDF document named in.pdf that looks like this:
We execute the following code in C#:
using (var reader = new PdfReader(#"C:\<path>\in.pdf"))
using (var writer = new PdfWriter(#"C:\<path>\out.pdf"))
using (var pdfDoc = new PdfDocument(reader, writer))
{
var page = pdfDoc.GetFirstPage();
var pdfCanvas = new PdfCanvas(page.NewContentStreamBefore(), page.GetResources(), pdfDoc);
using (var canvas = new Canvas(pdfCanvas, new Rectangle(400, 300)))
{
canvas.Add(new Paragraph("new text\r\nnew text")
.SetFontColor(new DeviceRgb(255, 0, 0))
.SetFontSize(50));
}
pdfCanvas.Release();
}
We get new text appearing under the original text, as expected:
Now we execute the same code with one change - we create PdfCanvas as follows:
var pdfCanvas = new PdfCanvas(page);
Result is still as expected - new text is above the original text:
Finally, we execute the same code with PdfCanvas created as follows:
var pdfCanvas = new PdfCanvas(page.NewContentStreamAfter(), page.GetResources(), pdfDoc);
New text is above the original text as expected, but it's flipped!
Is this a bug? Getting the same flipped output creating PdfCanvas like this:
var pdfCanvas = new PdfCanvas(page.GetFirstContentStream(), page.GetResources(), pdfDoc);
//OR
var pdfCanvas = new PdfCanvas(page.GetLastContentStream(), page.GetResources(), pdfDoc);
Using nuget package itext7 v7.1.15.
Is this a bug?
No. It's matching your code and a feature of the PdfCanvas(PdfPage page) constructor.
Each PDF content stream contains a sequence of instructions that change the current graphics state and/or draw something. If the original creator of the respective content stream did not take care to clean up the graphics state, instructions you add to it or to a following content stream of the same page are subject to those graphics state changes.
In your case the original page content stream apparently at the end has a graphics state with the current transformation matrix set to a reflection. Depending on how you add your new content, your additions also are subject to that reflection transformation:
var pdfCanvas = new PdfCanvas(page.NewContentStreamBefore(), page.GetResources(), pdfDoc);
Here you add a new content stream before all existing content streams. Thus, your additions to pdfCanvas are not subject to any graphics state changes and you get upright text.
var pdfCanvas = new PdfCanvas(page);
The constructor you use here, PdfCanvas(PdfPage page), calls another constructor, PdfCanvas(PdfPage page, bool wrapOldContent), which for wrapOldContent == true wraps the existing content in an envelope of a save-graphics-state / restore-graphics-state instruction pair, to restore the original graphics state after the existing content. PdfCanvas(PdfPage page) uses a true value for wrapOldContent under certain conditions, in particular if you are manipulating an existing PDF and the page in question already has a non-empty content stream.
In your case, therefore, the original content is wrapped in such an envelope and the graphics state is restored thereafter. So your additions are not subject to any graphics state changes and you get upright text.
var pdfCanvas = new PdfCanvas(page.NewContentStreamAfter(), page.GetResources(), pdfDoc);
var pdfCanvas = new PdfCanvas(page.GetFirstContentStream(), page.GetResources(), pdfDoc);
var pdfCanvas = new PdfCanvas(page.GetLastContentStream(), page.GetResources(), pdfDoc);
The PdfCanvas constructors you use here do not add any such envelope. Thus, your additions are subject to graphics state changes in the original content and you get mirrored text.

Issue scaling the first page of a PDF using iText7 for .NET

I'm trying to scale the first page of a PDF using iText7 for .NET. The rest of the pages should remain untouched.
The method below works if the PDF contains one page, but if there's multiple pages, the first (supposed to be scaled) page is blank, while the remaining pages is added correctly.
What am I missing here?
public byte[] ScaleFirstPagePdf(byte[] pdf)
{
using (var inputStream = new MemoryStream(pdf))
using (var outputStream = new MemoryStream(pdf))
using (var srcPdf = new PdfDocument(new PdfReader(inputStream)))
using (var destPdf = new PdfDocument(new PdfWriter(outputStream)))
{
for (int pageNum = 1; pageNum <= srcPdf.GetNumberOfPages(); pageNum++)
{
var srcPage = srcPdf.GetPage(pageNum);
var srcPageSize = srcPage.GetPageSizeWithRotation();
if (pageNum == 1)
{
var destPage = destPdf.AddNewPage(new PageSize(srcPageSize));
var canvas = new PdfCanvas(destPage);
var transformMatrix = AffineTransform.GetScaleInstance(0.5f, 0.5f);
canvas.ConcatMatrix(transformMatrix);
var pageCopy = srcPage.CopyAsFormXObject(destPdf);
canvas.AddXObject(pageCopy, 0, 0);
}
else
{
destPdf.AddPage(srcPage.CopyTo(destPdf));
}
}
destPdf.Close();
srcPdf.Close();
return outputStream.ToArray();
}
}
I couldn't reproduce the blank page issue with this code, but definitely the files that are generated in this way can be problematic.
The issue is that you are sharing a byte buffer between two memory streams - one used for reading and another one for writing, simultaneously.
Simply using another buffer or relying on the default MemoryStream implementation solved the issue for me, and should do so for you as well because there doesn't seem to be anything suspicious about your code apart from the problem I mentioned.
Here is how you should create the output stream:
using (var inputStream = new MemoryStream(pdf))
using (var outputStream = new MemoryStream())
If you still experience issues even after this tweak then the problem is definitely file-specific and I doubt you could get any help without sharing the file.

Mixing page-level elements and column text

Trying to tap into iText7 potential, that is in there, but hidden from the developer. I want my text in columns to flow around images that will be placed on page-level (fixed position). Either I'm doing something wrong, or there is a bug in the code. Here is what I have so far:
open Document for writing
set document renderer to custom ColumnDocumentRenderer:
Class<RootRenderer> cls = RootRenderer.class;
final Field f = cls.getDeclaredField("floatRendererAreas");
f.setAccessible(true);
ColumnDocumentRenderer cdr = new ColumnDocumentRenderer(document, new Rectangle[]{
new Rectangle(36f, 36f, 243.5f, 770f),
new Rectangle(315.5f, 36f, 243.5f, 770f)
}) {
#Override
protected LayoutArea updateCurrentArea(LayoutResult overflowResult) {
LayoutArea lr = super.updateCurrentArea(overflowResult);
try {
List<Rectangle> list = (List<Rectangle>) f.get(this);
list.add(new Rectangle(130f, 230f, 350f, 200f));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
return lr;
}
};
document.setRenderer(cdr);
This custom renderer adds the rectangle in which I will later place an image to floatRendererAreas field, so that the renderers will know which area to avoid.
add my content:
Div div = new Div();
div.setBackgroundColor(ColorConstants.BLUE);
div.setMargins(0f, 0f, 0f, 0f);
div.setPaddings(0f, 0f, 0f, 0f);
div.setFixedPosition(130f, 230f, 350f);
div.setHeight(200f);
document.add(div);
for (int i = 0; i < 6; i++) {
Paragraph p = new Paragraph();
p.add("Lorem ipsum...");
document.add(p);
}
unfortunately I get this:
edit: the layouting code is aware of the forbidden area, it just detects it too late, so up to one line is rendered in a wrong way. With y set to 260:
Probably it looks that the top of the text line is not affected, whereas it should check whether bottom of the text line is not affected as well.
What am I doing wrong? Is there another method to do it? Why is this not part of public/protected API? It could be extremely useful when creating documents.

How to add multiple Textfields in single or multiple pages in a Loop

I am Using Itext 5 maven and I want to add multiple textfields in multiple pdf pages. like page 1 need 3 fields, page 2 need 4 fields etc.
I have write the below code
public byte[] setupDocument(EditPdfDTO editPdfDTOList, MultipartFile attachment)
{
WritePDF obj = new WritePDF();
Document document = null;
PdfWriter writer = null;
PdfImportedPage page = null;
PdfReader reader = null;
try
{
// Create output PDF
document = new Document(PageSize.A4);
document.setMargins(0, 0, 0, 0);
writer = PdfWriter.getInstance(document,
new FileOutputStream("D:/test.pdf"));
document.open();
PdfContentByte cb = writer.getDirectContent();
// Load existing PDF
reader = new PdfReader(attachment.getBytes());
int totalPages = reader.getNumberOfPages();
for (int i = 0; i < totalPages; i++)
{
page = writer.getImportedPage(reader, i + 1);
document.newPage();
cb.addTemplate(page, 0, 0);
for (int j = 0; j < editPdfDTOList.getPdf().size(); j++)
{
if (i + 1 == editPdfDTOList.getPdf().get(j).getPageNo())
{
BaseFont baseFont = null;
try
{
baseFont = BaseFont.createFont();
}
catch (DocumentException | IOException e1)
{
e1.printStackTrace();
}
int a, b;
a = editPdfDTOList.getPdf().get(j).getxCoordinate();
b = editPdfDTOList.getPdf().get(j).getyCoordinate();
String str = editPdfDTOList.getPdf().get(j).getTextContent();
Rectangle linkLocation =
new Rectangle(a, b + baseFont.getDescentPoint(str, 10),
a + 10 + baseFont.getWidthPoint(str, 10),
b + baseFont.getAscentPoint(str, 10) + 10);
TextField field =
new TextField(writer, linkLocation, "user1" + j+UUID.randomUUID());
field.setFontSize(10);
field.setOptions(TextField.MULTILINE | TextField.READ_ONLY);
field.setTextColor(BaseColor.RED);
field.setText(str);
field.setBorderWidth(1);
cb = writer.getDirectContent();
try
{
cb.addAnnotation(field.getTextField(),false);
}
catch (IOException | DocumentException e)
{
e.printStackTrace();
}
}
}
}
}
catch (DocumentException | IOException e)
{
e.printStackTrace();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
document.close();
}
return null;
}
this code is able to add only one Textfield on every expected but not to add 2 or many textfields in a single page.
there is no issue of multiple try--catch block.
The appropriate classes to use
First of, you say you "want to add multiple textfields in multiple pdf pages". When implementing tasks like this, i.e. tasks that take a single document and want to somehow manipulate it while keeping it structurally more or less as before, one should usually work with a PdfReader/PdfStamper couple. This allows you to concentrate on the manipulation and provides a copy of the original PDF with all its properties to work on.
Adding multiple fields to a page of an existing PDF
Adding multiple fields to a single existing page is trivial, e.g.:
PdfReader pdfReader = new PdfReader(resource);
PdfStamper pdfStamper = new PdfStamper(pdfReader, output);
TextField field1 = new TextField(pdfStamper.getWriter(),
new Rectangle(100, 800, 200, 820), "Field1");
field1.setBorderColor(BaseColor.CYAN);
field1.setBorderStyle(PdfBorderDictionary.STYLE_DASHED);
field1.setBorderWidth(BaseField.BORDER_WIDTH_MEDIUM);
field1.setText("Field 1");
pdfStamper.addAnnotation(field1.getTextField(), 1);
TextField field2 = new TextField(pdfStamper.getWriter(),
new Rectangle(300, 800, 400, 820), "Field2");
field2.setBorderColor(BaseColor.RED);
field2.setBorderStyle(PdfBorderDictionary.STYLE_INSET);
field2.setBorderWidth(BaseField.BORDER_WIDTH_THIN);
field2.setText("Field 2");
pdfStamper.addAnnotation(field2.getTextField(), 1);
pdfStamper.close();
(AddField test testAddMultipleFields)
Applied to my example document
the code generates
Thus, there is no conceptual problem adding multiple text fields to the same document page, it works in a very natural manner.
In your case I would switch to using a PdfReader/PdfStamper couple. If some issue still remain, I would inspect your data. Probably they simply contain only a single field dataset per page. Or two textfields have the same coordinates and, therefore, look like one. Or some text fields have off-screen coordinates. Or... Or... Or...
The original answer
Originally the code in the question looked differently. This original answer focused on issues of that code.
You claim your code
is able to add only one Textfield on every expected but not to add 2 or many textfields in a single page
I doubt that because
you have two distinct objects writing to the same file "D:/TemplateFilePDf/" + attachment.getOriginalFilename() concurrently, the PdfWriter writer and the PdfStamper stamper. If you get something sensible as a result of your code, then only by pure luck; and
additionally stamper is instantiated for a null instance of PdfReader. This actually will cause a NullPointerException in the constructor which will keep your textfield adding code from being executed at all.
Thus, either the code you shared is considerably different from the code you run or your test runs actually all throw that NullPointerException and you probably find the outputs of a former, less broken version of your code which happens to have added only a single text field.
After fixing those two issues, some questions still remain (e.g. what is the intention of that cb.fill()? That instruction is only allowed directly after a path definition, the path whose inner area to fill, but I don't see you defining any path).
Furthermore, you access your editPdfDTOList for a lot of relevant values but we don't know those values. Thus, we cannot run your code to try and reproduce the issue. Probably you create only a single textfield because that object contains only values for a single textfield...

Javamail multiparts not ordered correctly

I have an application that attempts to dynamically build a Javamail message, assembling Mime body parts that are available at mail time. Each image is to have a 'GPC' image at the top, followed by some HTML text(body), a closing constructed by HTML, a closing 'Brand' image, and final part of the closing, also in HTML. File attachments may or may not be included. An error disclaimer(HTML) may precede the first image if applicable.
The disclaimer, body, closing, & attachment(s) are body parts, while the images are nested multiparts, consisting of HTML containers holding CIDs, and the image body parts themselves. When the email is sent, it appears correct in Gmail & Lotus Notes, while in Outlook(Hotmail) the GPC image is overwritten at the top by the Brand image. The order of the body parts is preserved, with the exception of the images, which always seem to revert to the top.
I cannot figure this out. It seems to me that the 'parent' multipart should be subtype 'Mixed' as the order of the body parts is important, and they are relatively unrelated, whereas the nested multiparts are 'Related' as one body part references the other. My code:
public MimeMultipart email_content_builder() throws MessagingException {
MimeMultipart mp = new MimeMultipart();
MimeBodyPart txt_part = new MimeBodyPart(), att_part, err_part, gpc_img_location_part, gpc_img_part, brand_img_location_part, brand_img_part, closing_pt1_part, closing_pt2_part;
DataSource att_source, gpc_img_src, brand_img_src;
String gpc_img_container_html, brand_img_container_html;
//Insert error disclaimer, if applicable:
if (!error_disclaimer.isEmpty()) {
err_part = new MimeBodyPart();
err_part.setText(error_disclaimer, "ISO-8859-1", "html");
mp.addBodyPart(err_part);
}
//Insert GPC logo image, if applicable:
if (GPC_logo.length > 0) {
MimeMultipart gpc_imagery_mp = new MimeMultipart("related");
MimeBodyPart gpc_imagery_part = new MimeBodyPart();
//When resizing the GPC image, the height should be roughly 23% of the width; use this factor: .2331
gpc_img_container_html = "<html><body><img src='cid:gpc_logo' height=\"23px\" width=\"100px\"></body></html>";
gpc_img_location_part = new MimeBodyPart();
gpc_img_location_part.setContent(gpc_img_container_html, "text/html");
gpc_imagery_mp.addBodyPart(gpc_img_location_part);
gpc_img_part = new MimeBodyPart();
gpc_img_src = new ByteArrayDataSource(GPC_logo, "image/jpeg");
gpc_img_part.setDataHandler(new DataHandler(gpc_img_src));
gpc_img_part.setHeader("Content-ID", "<gpc_logo>");
gpc_img_part.setDisposition(MimeBodyPart.INLINE);
gpc_imagery_mp.addBodyPart(gpc_img_part);
gpc_imagery_part.setContent(gpc_imagery_mp);
mp.addBodyPart(gpc_imagery_part);
}
//Insert main body of email:
txt_part.setText(body, "ISO-8859-1", "html");
mp.addBodyPart(txt_part);
//Insert the first part of the closing, if applicable:
if (!Closing_Part1.isEmpty()) {
closing_pt1_part = new MimeBodyPart();
closing_pt1_part.setText(Closing_Part1, "ISO-8859-1", "html");
mp.addBodyPart(closing_pt1_part);
}
//Insert brand logo image, if applicable:
if (brand_logo.length > 0) {
MimeMultipart brand_imagery_mp = new MimeMultipart("related");
MimeBodyPart brand_imagery_part = new MimeBodyPart();
//When resizing the brand image, the height should be roughly 43% of the width; use this factor: .4294
brand_img_container_html = "<html><body><img src='cid:brand_logo' height=\"64px\" width=\"150px\"></body></html>";
brand_img_location_part = new MimeBodyPart();
brand_img_location_part.setContent(brand_img_container_html, "text/html");
brand_imagery_mp.addBodyPart(brand_img_location_part);
brand_img_part = new MimeBodyPart();
brand_img_src = new ByteArrayDataSource(brand_logo, "image/jpeg");
brand_img_part.setDataHandler(new DataHandler(brand_img_src));
brand_img_part.setHeader("Content-ID", "<brand_logo>");
brand_img_part.setDisposition(MimeBodyPart.INLINE);
brand_imagery_mp.addBodyPart(brand_img_part);
brand_imagery_part.setContent(brand_imagery_mp);
mp.addBodyPart(brand_imagery_part);
}
//Insert the second part of the closing, if applicable:
if (!Closing_Part2.isEmpty()) {
closing_pt2_part = new MimeBodyPart();
closing_pt2_part.setText(Closing_Part2, "ISO-8859-1", "html");
mp.addBodyPart(closing_pt2_part);
}
//Insert attachments, if applicable:
if (attachments != null) {
for (int j = 0; j < attachments.size(); j++) {
att_part = new MimeBodyPart();
att_source = new FileDataSource((attachments.get(j)).getPath());
att_part.setDataHandler(new DataHandler(att_source));
att_part.setFileName((attachments.get(j)).getPath());
mp.addBodyPart(att_part);
}
}
return mp;
}
You have very limited control over how different mail readers will display the multiple body parts in your message. You best bet is to put all of the non-attachment content into a single text/html body part, using html to format the message. You can include images in your message using multipart/related, but it's often simpler to reference images on the web.

Resources