Appearance issues with pdf interactive forms using iText - java

I have been following the 'Chapter 8: Filling out interactive forms' iText example (code and pdfs at http://itextpdf.com/examples/iia.php?id=154) and have noticed a number of appearance-related issues which also manifest themselves in the example pdfs.
The example features a list of radio buttons (classic dot within a circle) and a list of checkboxes (crossed box with a red background). So far so good. However the pdf generated after programmatically filling in these fields is visually inconsistent. The radio buttons have all reverted to squares and the selected ('Spanish') has a wingdings (?) cross within it rather than a dot. The fields are all still editable. When I select a different radio button, the previously selected radio button reverts to an empty circle. The newly selected field has changed from an empty square to a dotted circle. This can also be observed with the checkboxes.
Is there any way to fill the fields whilst preserving their original appearances?
Thanks!
EDIT - Here's a simplification of the code I use :
public void buildPdf() throws Exception {
Document document = new Document(PageSize.A4);
File file = new File("/test.pdf");
final PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(file));
document.open();
PdfPCell cell = new PdfPCell();
PdfRadioGroupTable group = new PdfRadioGroupTable("ndna", Arrays.asList("Not Available", "Not Applicable"), writer, Locale.ENGLISH, DEFAULT);
cell.addElement(group);
PdfPTable table = new PdfPTable(1);
table.addCell(cell);
cell = new PdfPCell();
cell.setCellEvent(new ActionEvent(writer, "http://localhost:8080/entrypointpdf"));
table.addCell(cell);
document.add(table);
group.addAnnotations();
document.close();
}
Here's the ActionEvent
class ActionEvent implements PdfPCellEvent {
private PdfWriter writer;
private String submissionUrl;
public ActionEvent(PdfWriter writer, String submissionUrl) {
this.writer = writer;
this.submissionUrl = submissionUrl;
}
#Override
public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) {
PushbuttonField submit = new PushbuttonField(writer, position, "submit") {
#Override
public PdfFormField getField() throws IOException, DocumentException {
PdfFormField field = super.getField();
field.setAction(PdfAction.createSubmitForm(submissionUrl, null, 0));
return field;
}
};
submit.setText("CLICK ME");
submit.setTextColor(BaseColor.BLUE);
submit.setFont(DEFAULT.getCalculatedBaseFont(false));
submit.setVisibility(PushbuttonField.VISIBLE);
try {
writer.addAnnotation(submit.getField());
} catch (Exception ignore) {
}
}
}
Here's the PdfRadioGroupTable
public class PdfRadioGroupTable extends PdfPTable {
private PdfWriter writer;
public PdfFormField radioGroup;
public PdfRadioGroupTable(String name, List<String> choices, PdfWriter writer, Locale locale, Font font) {
super(2);
this.writer = writer;
setWidthPercentage(100);
try {
setTotalWidth(new float[] { 14, 400 });
} catch (DocumentException ignore) {
}
radioGroup = PdfFormField.createRadioButton(writer, true);
radioGroup.setFieldName(name);
PdfPCell cell;
for (String choice : choices) {
cell = new PdfPCell();
cell.setBorder(0);
cell.setPaddingBottom(5);
cell.setUseAscender(true);
cell.setUseDescender(true);
cell.setVerticalAlignment(PdfPCell.ALIGN_CENTER);
cell.setCellEvent(new RadioCellEvent(radioGroup, choice));
addCell(cell);
cell = new PdfPCell();
cell.setBorder(0);
cell.setPaddingBottom(5);
cell.setPaddingLeft(6);
cell.setUseAscender(true);
cell.setUseDescender(true);
cell.setVerticalAlignment(PdfPCell.ALIGN_CENTER);
cell.addElement(new Phrase(choice), font));
addCell(cell);
}
}
class RadioCellEvent implements PdfPCellEvent {
protected PdfFormField radioGroup;
protected String value;
public RadioCellEvent(PdfFormField radioGroup, String value) {
this.radioGroup = radioGroup;
this.value = value;
}
#Override
public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) {
RadioCheckField radio = new RadioCheckField(writer, position, null, value);
radio.setChecked(false);
radio.setBorderColor(GrayColor.GRAYBLACK);
radio.setBackgroundColor(GrayColor.GRAYWHITE);
radio.setCheckType(RadioCheckField.TYPE_CIRCLE);
try {
PdfFormField field = radio.getRadioField();
writer.addAnnotation(field);
radioGroup.addKid(field);
} catch (Exception exception) {
throw new ExceptionConverter(exception);
}
}
}
}
And here's what happens on the server side when one clicks the button in the pdf (in acrobat) :
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "inline; filename=\"your.pdf\"");
try {
FdfReader fdf = new FdfReader(request.getInputStream());
File pdfFile = new File("/test.pdf");
InputStream is = new FileInputStream(pdfFile);
PdfReader reader = new PdfReader(is);
File resultFile = new File(System.currentTimeMillis() + "_" + pdfFile.getName());
FileOutputStream out = new FileOutputStream(resultFile);
PdfStamper stamper = new PdfStamper(reader, out);
AcroFields fields = stamper.getAcroFields();
fields.setFields(fdf);
// EXTRACT VALUES HERE
stamper.setFormFlattening(true);
stamper.close();
reader.close();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write(FilesUtilities.read(resultFile));
OutputStream os = response.getOutputStream();
bos.writeTo(os);
os.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
So basically I generate a pdf with one radiogroup and a button. The button results in the pdf fields being imported and the idea is to send a 'receipt' back to the user in the form of the previously generated pdf containing the same fields with the user selection/values and import status fields (ok, missing, bad data, etc.) (which I have omitted in the above code as that part works fine). My problem is that the radio fields in the returned pdf don't look remotely like the editable ones in the original pdf (they don't even look like radio buttons anymore).
I tried using
fields.setFieldProperty("ndna", "setfflags", PdfFormField.FF_READ_ONLY, null);
and disabling form flattening by this results in the same alternate appearance in the returned pdf.
Your help would be most appreciated as I've been struggling with this for a while now...
Anthony

This seems to be a bug on some versions of iText. I had the same problem with iTextSharp version 5.5.5 and it was solved after I upgraded to version 5.5.9. I used "nuget" to do this. Here's a link: https://www.nuget.org/packages/iTextSharp/5.5.9

Related

Unable to open pdf after creation

I'm writing a program that takes a template PDF with a bunch of blank form fields, makes a copy of it, fills in the forms, then flattens the fields.
One of these templates has a ton of fields so writing a method that fills in all the fields results in an error that says code is too large due to the size limits on methods.
To work around this, I closed the destination file then tried to open it in another method where I could continue to fill in the fields, but this results in an error that says "(The requested operation cannot be performed on a file with a user-mapped section open)"
I ended the first method by closing the PDF, so I'm not sure what the issue is. The program will execute the first method, fill the fields but throws the error when it get to the 2nd method. Sample code below.
public void E2fill(String srcE2, String destE2) throws IOException
{
try
{
PdfDocument pdf2 = new PdfDocument(new PdfReader(destE2), new PdfWriter(dest2E2));
PdfAcroForm form2 = PdfAcroForm.getAcroForm(pdf2, true);
Map<String, PdfFormField> fields2 = form2.getFormFields();
PdfFormField field2;
fields2.get("fieldname1").setValue(stringname1);
//lots more field fills
pdf2.close()
}
catch(Exception x)
{
System.out.println(x.getMessage());
}
}
public void E2fill2(String destE2, String dest2E2) throws IOException
{
try
{
PdfDocument pdf2 = new PdfDocument(new PdfReader(destE2), new PdfWriter(dest2E2));
PdfAcroForm form2 = PdfAcroForm.getAcroForm(pdf2, true);
Map<String, PdfFormField> fields2 = form2.getFormFields();
PdfFormField field2;
fields2.get("fieldname546").setValue(stringname546);
//more field fills
form2.flattenFields();
pdf2.close();
}
catch(Exception x)
{
System.out.println(x.getMessage());
}
}
I suggest you try this:
public void fill(String srcE2, String destE2) {
PdfDocument pdf2 = new PdfDocument(new PdfReader(destE2), new PdfWriter(dest2E2));
PdfAcroForm form2 = PdfAcroForm.getAcroForm(pdf2, true);
Map<String, PdfFormField> fields2 = form2.getFormFields();
E2fill(fields2);
E2fill2(fields2);
form2.flattenFields();
pdf2.close();
}
public void E2fill(Map<String, PdfFormField> fields2) throws IOException
{
fields2.get("fieldname1").setValue(stringname1);
//lots more field fills
}
public void E2fill2(PdfAcroForm form2) throws IOException {
fields2.get("fieldname546").setValue(stringname546);
//more field fills
}

How to use iText get pdf rendered with multiple fonts

I'm using iText to parse html to pdf with both english and chinese characters. I'm using
// for pdf rendering
compile group: 'com.itextpdf', name: 'itextpdf', version: '5.5.13.1'
// for pdf rendering
compile group: 'com.itextpdf.tool', name: 'xmlworker', version: '5.5.13.1'
I've already make it possible to get chinese characters parsing not a problem with dependency
// for chinese font in pdf rendering
compile group: 'com.itextpdf', name: 'itext-asian', version: '5.2.0'
and customized font provider
public class StSongProvider extends XMLWorkerFontProvider {
private static final Logger LOG = LoggerFactory.getLogger(StSongProvider.class);
public StSongProvider() {
super(null, null);
}
#Override
public Font getFont(final String fontName, String encoding, float size, final int style) {
BaseFont bfChinese = null;
try {
bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
} catch (Exception e) {
LOG.error("Not found STSong-Light,maybe com.itextpdf.itext-asian dependency problem");
}
return new Font(bfChinese, size, style);
}
}
and pdf rendring code
public static File html2Pdf(String html, String fileName) {
try {
String path = buildPath(fileName);
// step 1
Document document = new Document(PageSize.A4);
document.setMargins(20, 20, 0, 0);
// step 2
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(path));
// step 3
document.open();
// step 4
InputStream cssInput = null;
XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(html.getBytes(StandardCharsets.UTF_8)), cssInput, new StSongProvider());
// step 5
document.close();
LOG.info("PDF file: {} rendering successfully", path);
return new File(path);
} catch (IOException ex) {
// do something
} catch (DocumentException ex) {
// do something
}
}
But the english characters in the result pdf is not that beautiful without a proper font (all characters are using STSong-Light font). I want to get pdf rendered with chinese characters using STSong-Light and english characters using some fonts that iText supported originally such as Times-Roman.
I found this SO thread makes building a document possible with multiple fonts using FontSelector. But how to make it compatible with pdf creation process? The XMLWorkerHelper.getInstance().parseXHtml api only accept a FontProvider as parameter. Any ideas about this?
The solution is do something on the customized font provider, make it not return only one font, but return font depends on the html cell font-family attribute.
public class StSongProvider extends XMLWorkerFontProvider {
private static final Logger LOG = LoggerFactory.getLogger(StSongProvider.class);
public StSongProvider() {
super(null, null);
}
#Override
public Font getFont(final String fontName, String encoding, float size, final int style) {
BaseFont font = null;
try {
if (StringUtils.equals(fontName, "STSong-Light")) {
font = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
} else {
font = BaseFont.createFont(FontFactory.TIMES_ROMAN, FontFactory.defaultEncoding, true);
}
} catch (Exception e) {
// do something
}
return new Font(font, size, style);
}
}
With above font provider, and set the style="font-family:STSong-Light attribute of a html cell contains chinese characters to format them, and other english characters will be well formatted with TIMES_ROMAN;

Convert HTML to PDF with Header and Footer

We have header and footer String as HTML content type but how to append both on every page.
In below java method we are passing three parameter htmlcontent, HeaderContent, FooterContent and return number of pages created in html, But where do we have to attach the header and footer content?
public static int generatePDF(String strFileName, String htmlContent,String headerHtml,String footerHtml) throws PDFNetException {
PDFDoc doc = new PDFDoc();
HTML2PDF converter = new HTML2PDF();
int nPages = 0;
try {
converter = new HTML2PDF();
doc = new PDFDoc();
converter.insertFromHtmlString(htmlContent);
try {
if (converter.convert(doc)) {
doc.save(strFileName, SDFDoc.e_linearized, null);
nPages = doc.getPageCount();
}
} catch (Exception ex) {
ex.printStackTrace();
}
} catch (Exception e) {
ex.printStackTrace();
} finally {
converter.destroy();
doc.close();
}
return nPages;
}
One option is to post-process the PDF, by using the Stamper class, to add headers/footers.
See the following sample code on how to use Stamper call
https://www.pdftron.com/documentation/samples/#stamper
The HTML2PDF converter appends pages to the PDFDoc object passed in, so you can do the following.
call HTML2PDF.InsertFromURL(url)
call HTML2PDF.Convert(pdfdoc)
run Stamper on pages x-y stamp
and repeat to keep appending pages to pdfdoc.

Itext7 generate pdf with Exception "Pdf indirect object belongs to other PDF document. Copy object to current pdf document."

i want to generate a pdf with itext 7,but some wrong happens to us:
com.itextpdf.kernel.PdfException: Pdf indirect object belongs to other PDF document. Copy object to current pdf document.
at com.itextpdf.kernel.pdf.PdfOutputStream.write(PdfOutputStream.java:195) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfOutputStream.write(PdfOutputStream.java:185) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfOutputStream.write(PdfOutputStream.java:115) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfOutputStream.write(PdfOutputStream.java:187) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfOutputStream.write(PdfOutputStream.java:115) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfOutputStream.write(PdfOutputStream.java:187) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfOutputStream.write(PdfOutputStream.java:115) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfWriter.writeToBody(PdfWriter.java:383) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfWriter.flushObject(PdfWriter.java:289) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfDocument.flushObject(PdfDocument.java:1572) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfObject.flush(PdfObject.java:159) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfObject.flush(PdfObject.java:127) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfObjectWrapper.flush(PdfObjectWrapper.java:94) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfPage.flush(PdfPage.java:495) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfPage.flush(PdfPage.java:454) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfDocument.close(PdfDocument.java:785) ~[kernel-7.0.2.jar:na]
at com.itextpdf.layout.Document.close(Document.java:120) ~[layout-7.0.2.jar:na]
at com.xcz.afbp.thirdparty.service.impl.GeneratePDFService.generatePDF(GeneratePDFService.java:160) ~[classes/:na]
my generate code :
public void generatePDF(CreditQueryData creditQueryData, Map<String, UserCreditContentView> contentViewMap, List<PackageCreditContentView> needRetrievedCreditContentList, File pdfFile, BigDecimal score) throws Exception {
if (!pdfFile.exists()) {
boolean x = pdfFile.createNewFile();
if (!x) {
LOG.error("生成文件出错" + pdfFile.getPath());
return;
}
}
PdfDocument pdf = new PdfDocument(new PdfWriter(new FileOutputStream(pdfFile)));
Document document = new Document(pdf, PageSize.A4);
document.setRenderer(new DocumentRenderer(document));
pdf.addEventHandler(PdfDocumentEvent.END_PAGE, new WatermarkingEventHandler());
try {
//operate code just add tableA tableB tableC...
} catch (Exception e) {
LOG.info();
} finally {
document.close(); //exception throws here
}
}
my only style code in itext7:
private PdfFont bfChinese = null;
will be init in service constructor invoked:
public GeneratePDFService() {
String PdfFontPath = EnvironmentUtils.getClasspathFilePath("font/MSYH.TTF");
try {
bfChinese = PdfFontFactory.createFont(PdfFontPath, "Identity-H", true);
} catch (Exception e) {
e.printStackTrace();
}
}
i have tried set my font to static,but not works.
this is the place throw exception:
private void write(PdfIndirectReference indirectReference) {
if (document != null && !indirectReference.getDocument().equals(document)) {
throw new PdfException(PdfException.PdfIndirectObjectBelongsToOtherPdfDocument);
}
if (indirectReference.getRefersTo() == null) {
write(PdfNull.PDF_NULL);
} else if (indirectReference.getGenNumber() == 0) {
writeInteger(indirectReference.getObjNumber()).
writeBytes(endIndirectWithZeroGenNr);
} else {
writeInteger(indirectReference.getObjNumber()).
writeSpace().
writeInteger(indirectReference.getGenNumber()).
writeBytes(endIndirect);
}
}
it's means i have two different document ,but i do not know when i have create another document.
Thanks in advance for suggestions.
I have experienced the same problem myself (and it took me hours to discover what I was doing wrong). As it turns out, you can use a specific PdfFont instance for only one document. As soon as you use a PdfFont instance it is linked to that document, and you can no longer use it in another document.
For instance:
class ThisGoesWrong {
protected PdfFont font;
public ThisGoesWrong() {
font = PdfFontFactory.createFont(...);
}
public void createPdf() {
...
Paragraph p = new Paragraph("test").setFont(font);
document.add(p);
...
}
}
The class ThisGoesWrong creates a correct PDF the first time you call createPdf(), but it shows the exception you have when you call it a second time.
I discovered that this solves the problem:
class ThisWorksOK {
public ThisWorksOK() {
}
public void createPdf() {
...
PdfFont font = PdfFontFactory.createFont(...);
Paragraph p = new Paragraph("test").setFont(font);
document.add(p);
...
}
}
I don't know if this is a bug (it certainly feels like a bug), so I will create an internal ticket at iText Group.
To improve preformance you should reuse FontProgram:
private FontProgram bfChinese = null;
public GeneratePDFService() {
String PdfFontPath = EnvironmentUtils.getClasspathFilePath("font/MSYH.TTF");
try {
bfChinese = FontProgramFactory.createFont(PdfFontPath);
} catch (Exception e) {
e.printStackTrace();
}
}
And then:
public void createPdf() {
...
PdfFont font = PdfFontFactory.createFont(bfChinese, "Identity-H", true);
Paragraph p = new Paragraph("test").setFont(font);
document.add(p);
...
}
This is how i solved the issue.
Created an DocumentUtil class
Add a static method
public class DocumentUtils {
public static PdfFont setFont() throws Exception {
return PdfFontFactory.createFont(StandardFonts.TIMES_ROMAN);
}
}
Use the font like:
PDfFont font = DocumentUtil.setFont();

how to send an excel file with servlet

im trying to create an excel file on a servlet and send it to the client browser when i did it on a stand alone program the file was created on my computer but when i tried to do it on a servlet it did nothing
servlet:
response.setContentType("text/html;charset=UTF-8");
// PrintWriter out = response.getWriter();
String[] items=request.getParameterValues("lecture");
String course=request.getParameter("course");
int sheets=Integer.parseInt(request.getParameter("sheets"));
List <XlElement> xlElements=getAllElements(items);
ServletOutputStream output=response.getOutputStream();
try
{
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment; filename="+course+".xls");
CreateXl xl=new CreateXl();
xl.createScadualFile(output, xlElements, sheets);
output.println(course);
}
catch (Exception e)
{
System.out.println(e.toString());
throw new ServletException("Exception in Excel Sample Servlet", e);
}
output.close();
createXl class
private List<WritableSheet> xlSheets;
private String[] days={"א","ב","ג","ד","ה"};
private final int numOfClasses=9;
private final int cellHeight= 1020;
private final int cellWidth=15;
public void createScadualFile(ServletOutputStream output, List <XlElement> items,int sheets) throws IOException, WriteException{
xlSheets=new ArrayList<WritableSheet>();
WritableWorkbook workbook = Workbook.createWorkbook(output);
for(int i=0;i<sheets;i++){
WritableSheet sheet = workbook.createSheet("week "+(i+1), i);
xlSheets.add(sheet);
}
for(WritableSheet s: xlSheets){
initSheet(s);
}
for(XlElement e: items){
insertElement(e);
}
workbook.write();
workbook.close();
}
private WritableCellFormat getCellFormat(Colour colour, Pattern pattern) throws WriteException {
WritableFont cellFont = new WritableFont(WritableFont.TIMES, 12);
WritableCellFormat cellFormat = new WritableCellFormat(cellFont);
cellFormat.setBackground(colour, pattern);
cellFormat.setWrap(true);
cellFormat.setVerticalAlignment(jxl.format.VerticalAlignment.TOP);
return cellFormat;
}
private void initSheet(WritableSheet s) throws WriteException{
for(int i=0;i<days.length;i++){
Label l=new Label(i+1,0,days[i],getCellFormat(Colour.GREY_25_PERCENT,Pattern.SOLID));
s.setColumnView(i+1,cellWidth );
s.addCell(l);
}
for(int i=0;i<numOfClasses;i++){
Label l=new Label(0,i+1,Integer.toString(i+1),getCellFormat(Colour.GREY_25_PERCENT,Pattern.SOLID));
s.setRowView(i+1, cellHeight);
s.addCell(l);
}
}
private void insertElement(XlElement e) throws WriteException{
Label l=new Label(e.getCol(),e.getRow(),e.toXlString(), getCellFormat(Colour.RED,Pattern.SOLID));
xlSheets.get(e.getWeek()).mergeCells(e.getCol(), e.getRow(), e.getCol(), e.getRow()+e.getSpan()-1);
xlSheets.get(e.getWeek()).addCell(l);
}
dose anybody know what I am doing wrong?
First, you should only call response.setContentType() once. You want to return an Excel, so take out the one where you are setting the content type to "text/html;charset=UTF-8".
Second, writing text to the output stream after writing the binary file to it will screw it up. Take out the output.println(course);
Third, I really don't think the output.close(); is needed either, so you might try taking that out as well.

Categories