I'm trying to add interactive fields to an existing PDF, i use a PdfReader and a PdfStamper to do this, and when i open it on my tablet, everything is ok. However, when I want to open it on my computer, there are no fields.
This is a sample of my code, I have more than one field for each page but i only printed two fields here:
public class SelfNoteFragment extends Fragment {
private PdfStamper pdfStamper;
class MyCellField implements PdfPCellEvent {
protected String fieldname;
protected int page;
public MyCellField(String fieldname, int page) {
this.fieldname = fieldname;
this.page = page;
}
public void cellLayout(PdfPCell cell, Rectangle rectangle, PdfContentByte[] canvases) {
final PdfWriter writer = canvases[0].getPdfWriter();
final TextField textField = new TextField(writer, rectangle, fieldname);
try {
final PdfFormField field = textField.getTextField();
pdfStamper.addAnnotation(field, page);
} catch (final IOException ioe) {
throw new ExceptionConverter(ioe);
} catch (final DocumentException de) {
throw new ExceptionConverter(de);
}
}
}
class CheckboxCellEvent implements PdfPCellEvent {
protected String name;
protected boolean check;
protected int page;
public CheckboxCellEvent(String name, boolean check, int page) {
this.check = check;
this.name = name;
this.page = page;
}
public void cellLayout(PdfPCell cell, Rectangle position,
PdfContentByte[] canvases) {
PdfWriter writer = canvases[0].getPdfWriter();
float x = position.getLeft();
float y = position.getBottom();
Rectangle rect = new Rectangle(x-5, y-5, x+5, y+5);
RadioCheckField checkbox = new RadioCheckField(
writer, rect, name, "Yes");
checkbox.setCheckType(RadioCheckField.TYPE_CROSS);
checkbox.setChecked(check);
try {
pdfStamper.addAnnotation(checkbox.getCheckField(), page);
} catch (Exception e) {
throw new ExceptionConverter(e);
}
}
}
private void createPdf(int idPrevision) throws FileNotFoundException, DocumentException {
try {
PdfReader pdfReader = new PdfReader("/storage/emulated/0/Documents/fiche_chantier2.pdf");
//Create time stamp
Date date = new Date() ;
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(date);
File pdfFolder = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOCUMENTS), "Prevision_");
if (!pdfFolder.exists()) {
pdfFolder.mkdir();
}
File myFile = new File(pdfFolder + timeStamp + ".pdf");
OutputStream output = new FileOutputStream(myFile);
this.pdfStamper = new PdfStamper(pdfReader, output);
PdfContentByte canvas1;
PdfContentByte canvas2;
canvas1 = pdfStamper.getOverContent(1);
canvas2 = pdfStamper.getOverContent(2);
PdfPCell cellFillFieldPage1 = new PdfPCell();
cellFillFieldPage1.setCellEvent(new MyCellField("", 1));
cellFillFieldPage1.setFixedHeight(15);
cellFillFieldPage1.setBorder(Rectangle.NO_BORDER);
cellFillFieldPage1.setVerticalAlignment(Element.ALIGN_MIDDLE);
PdfPCell cellCheckBoxPage2 = new PdfPCell();
cellCheckBoxPage2.setCellEvent(new CheckboxCellEvent("", false, 2));
cellCheckBoxPage2.setBorder(Rectangle.NO_BORDER);
// ************** PAGE 1 ************** //
// SET TABLE
PdfPTable tableSection1Page1 = new PdfPTable(1);
tableSection1Page1.setTotalWidth(136);
tableSection1Page1.setWidthPercentage(100.0f);
tableSection1Page1.setLockedWidth(true);
// ADD CELLS TO TABLE
tableSection1Page1.addCell(cellFillFieldPage1);
// PRINT TABLES
tableSection1Page1.writeSelectedRows(0, -1, 165, 730, canvas1);
// ************ PAGE 2 ************ //
// SET TABLES
PdfPTable tableSection1Page2 = new PdfPTable(1);
tableSection1Page2.setTotalWidth(10);
tableSection1Page2.setWidthPercentage(100.0f);
tableSection1Page2.setLockedWidth(true);
// ADD CELLS TO TABLE
tableSection1Page2.addCell(cellCheckBoxPage2);
// PRINT TABLES
tableSection1Page2.writeSelectedRows(0, -1, 182, 736, canvas2);
// I tried this, but it didn't change anything
pdfStamper.setFormFlattening(false);
pdfStamper.close();
pdfReader.close();
} catch (IOException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
}
}
}
Do you have an idea why my created PDF is good on my Tablet and not on my Computer (same when i want to send it by mail)? If so, do you know how to manage this problem?
Thank you,
Corentin
There actually are two issues in your code,
you create the fields with empty names:
cellFillFieldPage1.setCellEvent(new MyCellField("", 1));
...
cellCheckBoxPage2.setCellEvent(new CheckboxCellEvent("", false, 2));
First of all fields need to have a name. And furthermore, even if the empty name was an allowed choice for a name, two fields with the same name are considered to be two representations of the same abstract field which hardly works out if one is a text field and the other a checkbox field.
As soon as you name those fields with non-empty, different names, you'll see that you have fields on Adobe Reader DC/Win: You can tab through them with the TAB key and change their values. Unfortunately, though, they are borderless and, therefore, hard to spot if empty. This is because
you don't set border colors for your fields. iText, consequently, creates the form field appearance streams without borders.
You can set the border color like this:
...
final TextField textField = new TextField(writer, rectangle, fieldname);
textField.setBorderColor(BaseColor.BLACK);
...
RadioCheckField checkbox = new RadioCheckField(writer, rect, name, "Yes");
checkbox.setBorderColor(BaseColor.BLACK);
...
Having done so, you get the fields with black borders...
If you wonder why different viewers (even by the same company with the same name) behave different here...
Some PDF viewers recognize early that they couldn't properly handle fields with empty names and, therefore, don't even show them while other viewers recognize that later when trying to save or post the form, or don't recognize it at all, producing broken outputs.
PDF viewers are allowed to ignore appearance streams of form fields (actually of annotations in general) and create their own appearances; thus, in the case at hand they may ignore the borderless appearance generated by iText and draw one with a visible border.
Related
I want to add the reply to the comment I entered for the annotation. But the code I have written produce no reply but only annotation and the comment. What am I missing? I am sharing my code. Also I want to add the creation date for the annotation, how should I do that?
Thanks in advance.
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
// Loading an existing document
File file = new File("ABC.pdf");
PDDocument document = PDDocument.load(file);
System.out.println("PDF loaded.");
PDPage page = document.getPage(0);
List<PDAnnotation> annotations = page.getAnnotations();
PDColor color = new PDColor(new float[] {255f, 0, 0}, PDDeviceRGB.INSTANCE);
PDBorderStyleDictionary thickness = new PDBorderStyleDictionary();
thickness.setWidth((float)2);
PDAnnotationSquareCircle rectangle = new PDAnnotationSquareCircle(PDAnnotationSquareCircle.SUB_TYPE_SQUARE);
rectangle.setColor(color);
rectangle.setBorderStyle(thickness);
PDRectangle points = new PDRectangle();
points.setLowerLeftX((float) 100);
points.setLowerLeftY((float) 100);
points.setUpperRightX((float) 300);
points.setUpperRightY((float) 300);
rectangle.setContents("Rectangle");
rectangle.setRectangle(points);
rectangle.getCOSObject().setString(COSName.T, "XYZ");
PDAnnotationMarkup reply = new PDAnnotationMarkup();
reply.getCOSObject().setName(COSName.SUBTYPE, PDAnnotationMarkup.RT_REPLY);
reply.setContents("Hello 2");
reply.setReplyType("R");
reply.setInReplyTo(rectangle);
annotations.add(rectangle);
System.out.println("Rectangle is added.");
// Save the file
document.save(file);
// Close the document
document.close();
}
When running this code with the PdfDocument not having a read source, it works properly. When I try reading from a premade pdf it stops creating the form/widgets, but still adds the paragraph as expected. There is no error given. Does anyone understand why this is happening?
Here is the code I'm running:
public class HelloWorld {
public static final String DEST = "sampleOutput.pdf";
public static final String SRC = "sample.pdf";
public static void main(String args[]) throws IOException {
File file = new File(DEST);
new HelloWorld().createPdf(SRC, DEST);
}
public void createPdf(String src, String dest) throws IOException {
//Initialize PDF reader and writer
PdfReader reader = new PdfReader(src);
PdfWriter writer = new PdfWriter(dest);
//Initialize PDF document
PdfDocument pdf = new PdfDocument(writer); //if i do (reader, writer) the widget isn't added to the first page anymore.
// Initialize document
Document document = new Document(pdf);
HelloWorld.addAcroForm(pdf, document);
//Close document
document.close();
}
public static PdfAcroForm addAcroForm(PdfDocument pdf, Document doc) throws IOException {
Paragraph title = new Paragraph("Test Form")
.setTextAlignment(TextAlignment.CENTER)
.setFontSize(16);
doc.add(title);
doc.add(new Paragraph("Full name:").setFontSize(12));
//Add acroform
PdfAcroForm form = PdfAcroForm.getAcroForm(doc.getPdfDocument(), true);
//Create text field
PdfTextFormField nameField = PdfFormField.createText(doc.getPdfDocument(),
new Rectangle(99, 753, 425, 15), "name", "");
form.addField(nameField);
return form;
}
}
I adapted your code like this:
public static PdfAcroForm addAcroForm(PdfDocument pdf, Document doc) throws IOException {
Paragraph title = new Paragraph("Test Form")
.setTextAlignment(TextAlignment.CENTER)
.setFontSize(16);
doc.add(title);
doc.add(new Paragraph("Full name:").setFontSize(12));
PdfAcroForm form = PdfAcroForm.getAcroForm(pdf, true);
PdfTextFormField nameField = PdfFormField.createText(pdf,
new Rectangle(99, 525, 425, 15), "name", "");
form.addField(nameField, pdf.getPage(1));
return form;
}
You'll notice two changes:
I change the Y offset of the field (525 instead of 753). Now the field is added inside the visible area of the page. In your code, the field was added, but it wasn't visible.
I defined to which page the fields needs to be added by adding pdf.getPage(1) as second parameter for the addField() method.
I want to create PDF table from HTML string. I can create that table, but instead of Text, I'm getting question marks. Here is my code:
public class ExportReportsToPdf implements StreamSource {
private static final long serialVersionUID = 1L;
private ByteArrayOutputStream byteArrayOutputStream;
public static final String FILE_LOC = "C:/Users/KiKo/CasesWorkspace/case/Export.pdf";
private static final String CSS = ""
+ "table {text-align:center; margin-top:20px; border-collapse:collapse; border-spacing:0; border-width:1px;}"
+ "th {font-size:14px; font-weight:normal; padding:10px; border-style:solid; overflow:hidden; word-break:normal;}"
+ "td {padding:10px; border-style:solid; overflow:hidden; word-break:normal;}"
+ "table-header {font-weight:bold; background-color:#EAEAEA; color:#000000;}";
public void createReportPdf(String tableHtml, Integer type) throws IOException, DocumentException {
// step 1
Document document = new Document(PageSize.A4, 20, 20, 50, 20);
// step 2
PdfWriter.getInstance(document, new FileOutputStream(FILE_LOC));
// step 3
byteArrayOutputStream = new ByteArrayOutputStream();
PdfWriter writer = PdfWriter.getInstance(document, byteArrayOutputStream);
if (type != null) {
writer.setPageEvent(new Watermark());
}
// step 4
document.open();
// step 5
document.add(getTable(tableHtml));
// step 6
document.close();
}
private PdfPTable getTable(String tableHtml) throws IOException {
// CSS
CSSResolver cssResolver = new StyleAttrCSSResolver();
CssFile cssFile = XMLWorkerHelper.getCSS(new ByteArrayInputStream(CSS.getBytes()));
cssResolver.addCss(cssFile);
// HTML
HtmlPipelineContext htmlContext = new HtmlPipelineContext(null);
htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
// Pipelines
ElementList elements = new ElementList();
ElementHandlerPipeline pdf = new ElementHandlerPipeline(elements, null);
HtmlPipeline html = new HtmlPipeline(htmlContext, pdf);
CssResolverPipeline css = new CssResolverPipeline(cssResolver, html);
// XML Worker
XMLWorker worker = new XMLWorker(css, true);
XMLParser parser = new XMLParser(worker);
InputStream inputStream = new byteArrayInputStream(tableHtml.getBytes());
parser.parse(inputStream);
return (PdfPTable) elements.get(0);
}
private static class Watermark extends PdfPageEventHelper {
#Override
public void onEndPage(PdfWriter writer, Document document) {
try {
URL url = Thread.currentThread().getContextClassLoader().getResource("/images/memotemp.jpg");
Image background = Image.getInstance(url);
float width = document.getPageSize().getWidth();
float height = document.getPageSize().getHeight();
writer.getDirectContentUnder().addImage(background, width, 0, 0, height, 0, 0);
} catch (DocumentException | IOException e) {
e.printStackTrace();
}
}
}
#Override
public InputStream getStream() {
return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
}
}
This code is working, and I'm getting this:
I've try to add UTF-8,
InputStream inputStream = new byteArrayInputStream(tableHtml.getBytes("UTF-8"));
but than I'm getting this:
I want to get something like this:
I think the problem is with the encoding, but I don't know how to solve this bug. Any suggestions...?
To get bytes from a (Unicode) String in some encoding, specify it,
otherwise the default system encoding is used.
tableHtml.getBytes(StandardCharsets.UTF_8)
In your case however "Windows-1251" seems a better match as the PDF does not seem to use UTF-8.
Maybe the original tableHTML String was read with the wrong encoding. Might check that, if it came from file or database.
You need to tell iText what encoding to use by creating an instance of the BaseFont class. Then in your document.add(getTable(tableHtml)); you can add a call to the font. Example at http://itextpdf.com/examples/iia.php?id=199.
I can't tell how you create a table but the class PdfPTable has a method addCell(PdfCell) and one constructor for PdfCell takes a Phrase. The Phrase can be constructed with a String and a Font. The font class takes a BaseFont as a constructor argument.
If you look around the Javadoc for iText you will see various classes take a Font as a constructor argument.
I have to generate a pdf file depending on some input .Each time the code runs , the input length may vary , so how can I add pages to the document dynamically depending on my input content .
public class pdfproject
{
static int lineno=768;
public static void main (String[] args) throws Exception
{
PDDocument doc= new PDDocument();
PDPage page = new PDPage();
doc.addPage(page);
PDPageContentStream cos = new PDPageContentStream(doc, page);
for(int i=0;i<2000;i++)
{
renderText("hello"+i,cos,60);
}
cos.close();
doc.save("test.pdf");
doc.close();
}
static void renderText(String Info,PDPageContentStream cos,int marginwidth) throws Exception
{
lineno-=12;
System.out.print("lineno="+lineno);
PDFont fontPlain = PDType1Font.HELVETICA;
cos.beginText();
cos.setFont(fontPlain, 10);
cos.moveTextPositionByAmount(marginwidth,lineno);
cos.drawString(Info);
cos.endText();
}
}
How do i ensure that the content is rendered on the next page by adding a new page dynamically when there is no space on the current page ?
Pdfbox does not include any automatic layouting support. Thus, you have to keep track of how full a page is, and you have to close the current page, create a new one, reset fill indicators, etc
This obviously should not be done in static members in some project class but instead in some dedicated class and its instance members. E.g.
public class PdfRenderingSimple implements AutoCloseable
{
//
// rendering
//
public void renderText(String Info, int marginwidth) throws IOException
{
if (content == null || textRenderingLineY < 12)
newPage();
textRenderingLineY-=12;
System.out.print("lineno=" + textRenderingLineY);
PDFont fontPlain = PDType1Font.HELVETICA;
content.beginText();
content.setFont(fontPlain, 10);
content.moveTextPositionByAmount(marginwidth, textRenderingLineY);
content.drawString(Info);
content.endText();
}
//
// constructor
//
public PdfRenderingSimple(PDDocument doc)
{
this.doc = doc;
}
//
// AutoCloseable implementation
//
/**
* Closes the current page
*/
#Override
public void close() throws IOException
{
if (content != null)
{
content.close();
content = null;
}
}
//
// helper methods
//
void newPage() throws IOException
{
close();
PDPage page = new PDPage();
doc.addPage(page);
content = new PDPageContentStream(doc, page);
content.setNonStrokingColor(Color.BLACK);
textRenderingLineY = 768;
}
//
// members
//
final PDDocument doc;
private PDPageContentStream content = null;
private int textRenderingLineY = 0;
}
(PdfRenderingSimple.java)
You can use it like this
PDDocument doc = new PDDocument();
PdfRenderingSimple renderer = new PdfRenderingSimple(doc);
for (int i = 0; i < 2000; i++)
{
renderer.renderText("hello" + i, 60);
}
renderer.close();
doc.save(new File("renderSimple.pdf"));
doc.close();
(RenderSimple.java)
For more specialized rendering support you will implement improved rendering classes, e.g. PdfRenderingEndorsementAlternative.java from this answer.
I am creating a PDF and writing the stream in response. Before writing in the stream, I want to add a background image as watermark in all the pages so that PDF document flushed through response is the final one with watermark.
Hi this is my code sample. Any help would be much appriciated
private static String generatePDF(HttpServletRequest request, HttpServletResponse response, String fileName) throws Exception
{
Document document = null;
PdfWriter writer = null;
FileOutputStream fos = null;
try
{
fos = new FileOutputStream(fileName);
Document document = new Document(PageSize.A4);
writer = PdfWriter.getInstance(document, fos);
document.open();
/**
* Adding tables and cells and other stuff required
**/
return pdfFileName;
} catch (Exception e) {
FileUtil.deleteFile(fileName);
throw e
} finally {
if (document != null) {
document.close();
}
fos.flush();
}
}
I now would like to add a background image using the below code and write the output PDF to the same stream
PdfReader sourcePDFReader = null;
try
{
sourcePDFReader = new PdfReader(sourcePdfFileName);
int noOfPages = sourcePDFReader.getNumberOfPages();
PdfStamper stamp = new PdfStamper(sourcePDFReader, new FileOutputStream(destPdfFileName));
int i = 0;
Image templateImage = Image.getInstance(templateImageFile);
templateImage.setAbsolutePosition(0, 0);
PdfContentByte tempalteBytes;
while (i < noOfPages) {
i++;
tempalteBytes = stamp.getUnderContent(i);
tempalteBytes.addImage(templateImage);
}
stamp.close();
return destPdfFileName;
} catch (Exception ex) {
LOGGER.log(Level.INFO, "Error when applying tempalte image as watermark");
} finally {
if (sourcePDFReader != null) {
sourcePDFReader.close();
}
}
I solved this using Bruno's first (recommended) approach.
1) Create a page event helper with an onEndPage event:
class PDFBackground extends PdfPageEventHelper {
#Override
void onEndPage(PdfWriter writer, Document document) {
Image background = Image.getInstance("myimage.png");
// This scales the image to the page,
// use the image's width & height if you don't want to scale.
float width = document.getPageSize().getWidth();
float height = document.getPageSize().getHeight();
writer.getDirectContentUnder()
.addImage(background, width, 0, 0, height, 0, 0);
}
}
2) When creating your writer, register your page event helper:
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(file));
writer.setPageEvent(new PDFBackground());
I have solved this with Bruno's second option. Here is the code.
public static String addBackgroundImageToPDF(ByteArrayOutputStream bos, String destPdfFileName, String templateImageFile)
{
PdfReader sourcePDFReader = null;
try
{
sourcePDFReader = new PdfReader(bos.toByteArray());
int noOfPages = sourcePDFReader.getNumberOfPages();
PdfStamper stamp = new PdfStamper(sourcePDFReader, new FileOutputStream(destPdfFileName));
int i = 0;
Image templateImage = Image.getInstance(templateImageFile);
templateImage.setAbsolutePosition(0, 0);
PdfContentByte tempalteBytes;
while (i < noOfPages)
{
i++;
tempalteBytes = stamp.getUnderContent(i);
tempalteBytes.addImage(templateImage);
}
stamp.close();
return destPdfFileName;
}
catch (Exception ex)
{
LOGGER.log(Level.INFO, "Error when applying template image as watermark");
}
finally
{
if (sourcePDFReader != null)
{
sourcePDFReader.close();
}
}
}
You can choose between two options:
Use the background image in a page event (to the 'under' content in the onEndPage() method)/
Create the first PDF in memory, then add the background image in a second pass using the code you posted.
I prefer option 1.