Radiogroup in a table split due a page break, is rendered wrong
Describe the bug
Given I have a table, where radio buttons of the same radio-group are put in different rows.
When this table is split, because of a page break, having the row with radio button 1 still on page 1, and the row with radio button 2 on page 2,
then the radio button 2 will be drawn correctly on page 2, but radio button 1 will not be drawn on page 1, but on page 2, and in the position, as if it was drawn on page 1. (See generated PDF from code bellow)
To Reproduce
This can be reproduced with this code:
mport com.lowagie.text.Chunk;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.ExceptionConverter;
import com.lowagie.text.PageSize;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.GrayColor;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfFormField;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPCellEvent;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
import com.lowagie.text.pdf.RadioCheckField;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
public class RadioButtonPageBreak {
public static void main(String... args) throws FileNotFoundException, DocumentException {
Document document = new Document(PageSize.A4);
FileOutputStream outputStream = new FileOutputStream("RadioButtonPageBreak.pdf");
PdfWriter writer = PdfWriter.getInstance(document, outputStream);
document.open();
almostFillThePage(document);
addTableWithRadioButtons(document, writer);
document.close();
}
private static void almostFillThePage(Document document) throws DocumentException {
document.add(new Paragraph(
"This example shows: A table with a radio group. Each radio button is in a different row. If the"
+ " table is split due a page break, then the radio button on page 1will be drawn in page 2,"
+ " but in the corresponding page position, as if it was drawn in page 1."));
for (int i = 0; i < 36; i++) {
document.add(Chunk.NEWLINE);
}
}
private static void addTableWithRadioButtons(Document document, PdfWriter writer) throws DocumentException {
PdfPTable table = new PdfPTable(3);
// Add some rows before the radio buttons, so we get the break in the desired position
addRow(table, "Column 1", "Column 2", "Column 3");
addRow(table, "Radio buttons", "coming", "soon");
// Radio button rows
PdfFormField radioGroup = PdfFormField.createRadioButton(writer, true);
radioGroup.setFieldName("radio");
String[] values = {"Selection One", "Selection Two"};
for (String value : values) {
PdfPCell radioCell = new PdfPCell();
radioCell.setCellEvent(new MyCellEvent(radioGroup, value, writer));
table.addCell(radioCell);
table.addCell(value);
table.completeRow();
}
// Add table and radioGroup
document.add(table);
writer.addAnnotation(radioGroup); // Adding radioGroup after adding the table
}
private static void addRow(PdfPTable table, String... cells) {
Arrays.stream(cells).forEach(table::addCell);
table.completeRow();
}
private static class MyCellEvent implements PdfPCellEvent {
private final PdfFormField radioGroup;
private final String value;
private final PdfWriter writer;
public MyCellEvent(PdfFormField radioGroup, String value, PdfWriter writer) {
this.radioGroup = radioGroup;
this.value = value;
this.writer = writer;
}
@Override
public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) {
RadioCheckField radio = new RadioCheckField(writer, position, null, value);
radio.setBorderColor(GrayColor.GRAYBLACK);
radio.setCheckType(RadioCheckField.TYPE_CIRCLE);
try {
radioGroup.addKid(radio.getRadioField());
} catch (final IOException | DocumentException e) {
throw new ExceptionConverter(e);
}
}
}
}
Expected behavior
Radio button 1 should be rendered on page 1 in the last row of the table.
@asturio you can change your code like this. The cause is that when new page come, it will clear PdfAnnotationsImp in PdfWriter. when we call addAnnotation, it only that the current page can add annotation. And the /Kids can not effect the button only if two button have same parent. Otherwise this issue also happens when the table is to big. we add annotation in the first page, and table has 3 page. Then annotation will show in the third page.
package com.lowagie.text.pdf;
import com.lowagie.text.Chunk;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.ExceptionConverter;
import com.lowagie.text.PageSize;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Rectangle;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
public class RadioButtonPageBreak {
public static void main(String... args) throws FileNotFoundException, DocumentException {
Document document = new Document(PageSize.A4);
Document.compress = false;
FileOutputStream outputStream = new FileOutputStream("RadioButtonPageBreak2.pdf");
PdfWriter writer = PdfWriter.getInstance(document, outputStream);
document.open();
almostFillThePage(document);
addTableWithRadioButtons(document, writer);
document.close();
}
private static void almostFillThePage(Document document) throws DocumentException {
document.add(new Paragraph(
"This example shows: A table with a radio group. Each radio button is in a different row. If the"
+ " table is split due a page break, then the radio button on page 1will be drawn in page 2,"
+ " but in the corresponding page position, as if it was drawn in page 1."));
for (int i = 0; i < 36; i++) {
document.add(Chunk.NEWLINE);
}
}
private static void addTableWithRadioButtons(Document document, PdfWriter writer) throws DocumentException {
PdfPTable table = new PdfPTable(3);
// Add some rows before the radio buttons, so we get the break in the desired position
addRow(table, "Column 1", "Column 2", "Column 3");
addRow(table, "Radio buttons", "coming", "soon");
// Radio button rows
PdfFormField radioGroup = PdfFormField.createRadioButton(writer, true);
radioGroup.setFieldName("radio");
String[] values = {"Selection One", "Selection Two"};
for (String value : values) {
PdfPCell radioCell = new PdfPCell();
radioCell.setCellEvent(new MyCellEvent(radioGroup, value, writer));
table.addCell(radioCell);
table.addCell(value);
table.completeRow();
}
table.completeRow();
document.add(table);
}
private static void addRow(PdfPTable table, String... cells) {
Arrays.stream(cells).forEach(table::addCell);
table.completeRow();
}
private static class MyCellEvent implements PdfPCellEvent {
private final PdfFormField radioGroup;
private final String value;
private final PdfWriter writer;
private static int count = 1;
public MyCellEvent(PdfFormField radioGroup, String value, PdfWriter writer) {
this.radioGroup = radioGroup;
this.value = value;
this.writer = writer;
}
@Override
public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) {
RadioCheckField radio = new RadioCheckField(writer, position, null, value);
radio.setBorderColor(GrayColor.GRAYBLACK);
radio.setCheckType(RadioCheckField.TYPE_CIRCLE);
try {
radioGroup.addKid(radio.getRadioField());
if (count == 1) {
count = 2;
writer.addAnnotation(radioGroup);
}else{
int len = radioGroup.kids.size();
writer.addAnnotation(radioGroup.kids.get(len-1));
}
} catch (final IOException | DocumentException e) {
throw new ExceptionConverter(e);
}
}
}
}
@andreasrosdal It also has a better solution like this which can keep completeness of /Kids of radioGroup.
package com.lowagie.text.pdf;
import com.lowagie.text.Chunk;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.ExceptionConverter;
import com.lowagie.text.PageSize;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Rectangle;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
public class RadioButtonPageBreak {
public static void main(String... args) throws FileNotFoundException, DocumentException, IOException {
Document document = new Document(PageSize.A4);
Document.compress = false;
FileOutputStream outputStream = new FileOutputStream("RadioButtonPageBreak2.pdf");
PdfWriter writer = PdfWriter.getInstance(document, outputStream);
document.open();
almostFillThePage(document);
addTableWithRadioButtons(document, writer);
document.close();
}
private static void almostFillThePage(Document document) throws DocumentException {
document.add(new Paragraph(
"This example shows: A table with a radio group. Each radio button is in a different row. If the"
+ " table is split due a page break, then the radio button on page 1will be drawn in page 2,"
+ " but in the corresponding page position, as if it was drawn in page 1."));
for (int i = 0; i < 36; i++) {
document.add(Chunk.NEWLINE);
}
}
private static void addTableWithRadioButtons(Document document, PdfWriter writer) throws DocumentException, IOException {
PdfPTable table = new PdfPTable(3);
// Add some rows before the radio buttons, so we get the break in the desired position
addRow(table, "Column 1", "Column 2", "Column 3");
addRow(table, "Radio buttons", "coming", "soon");
// Radio button rows
PdfFormField radioGroup = PdfFormField.createRadioButton(writer, true);
radioGroup.setFieldName("radio");
String[] values = {"Selection One", "Selection Two"};
for (String value : values) {
PdfPCell radioCell = new PdfPCell();
radioCell.setCellEvent(new MyCellEvent(radioGroup, value, writer));
table.addCell(radioCell);
table.addCell(value);
table.completeRow();
}
table.completeRow();
document.add(table);
radioGroup.setUsed();
writer.addToBody(radioGroup, radioGroup.reference.number);
}
private static void addRow(PdfPTable table, String... cells) {
Arrays.stream(cells).forEach(table::addCell);
table.completeRow();
}
private static class MyCellEvent implements PdfPCellEvent {
private final PdfFormField radioGroup;
private final String value;
private final PdfWriter writer;
private static PdfIndirectReference indirectReference;
public MyCellEvent(PdfFormField radioGroup, String value, PdfWriter writer) {
this.radioGroup = radioGroup;
this.value = value;
this.writer = writer;
}
@Override
public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) {
RadioCheckField radio = new RadioCheckField(writer, position, null, value);
radio.setBorderColor(GrayColor.GRAYBLACK);
radio.setCheckType(RadioCheckField.TYPE_CIRCLE);
if(indirectReference == null) {
indirectReference = writer.getPdfIndirectReference();
radioGroup.reference = indirectReference;
}
try {
PdfFormField radioField = radio.getRadioField();
writer.addAnnotation(radioField);
radioGroup.addKid(radioField);
} catch (final IOException | DocumentException e) {
throw new ExceptionConverter(e);
}
}
}
}