Set AcroField Text Size to Auto - itext

Using itextsharp, I'm attempting to set the font size of my form's text fields to auto.
I'm currently doing something like this:
Object d = 0.0;
PdfReader reader = new PdfReader(path);
byte [] pdf;
using (var ms = new MemoryStream())
{
PdfStamper stamper = new PdfStamper(reader, ms);
AcroFields fields = stamper.AcroFields;
foreach (var f in fields.Fields.Keys)
{
fields.SetFieldProperty(f, "textsize", d, null);
}
}
But I'm getting the following error:
System.InvalidCastException: Specified cast is not valid.
at iTextSharp.text.pdf.AcroFields.SetFieldProperty(String field, String name, Object value, Int32[] inst)
How can I fix this?

Using 0f instead of d in the call SetFieldProperty let me change the font size to auto.

Related

Field validation in editable pdf while filling data

I have editable pdf with fields name, roll number , DOB and city, I want to validate the fields like, for example when I enter the value in roll number field, if name is empty then it should alert "name is empty" and similar to roll number and DOB and city.
DOB - Need to validate mm/dd/yyyy format.
I am able to get fields and values using below iTextSharp code, but how can validate and and show alert to user.
iTextSharp.text.pdf.PdfReader reader = new iTextSharp.text.pdf.PdfReader(filename);
using (MemoryStream outputStream = new MemoryStream())
{
//PdfStamper stamper = new PdfStamper(reader, );
using (PdfStamper stamper = new PdfStamper(reader, outputStream, '\0', true))
{
PdfWriter writer = stamper.Writer;
AcroFields acroFields = reader.AcroFields;
AcroFields.Item dateField = acroFields.GetFieldItem("D");
iTextSharp.text.pdf.PdfAction pdfAction = iTextSharp.text.pdf.PdfAction.JavaScript("app.alert('hello');", writer);
iTextSharp.text.pdf.PdfDictionary widgetRefDict = (iTextSharp.text.pdf.PdfDictionary)iTextSharp.text.pdf.PdfReader.GetPdfObject(dateField.GetWidgetRef(0));
iTextSharp.text.pdf.PdfDictionary actionDict = widgetRefDict.GetAsDict(iTextSharp.text.pdf.PdfName.AA);
if (actionDict == null)
{
actionDict = new iTextSharp.text.pdf.PdfDictionary();
// add the newly created action dict
widgetRefDict.Put(iTextSharp.text.pdf.PdfName.AA, actionDict);
}
actionDict.Put(iTextSharp.text.pdf.PdfName.V, pdfAction);
stamper.Close();
reader.Close();
}
byte[] content = outputStream.ToArray();
// Write out PDF from memory stream.
using (FileStream fs = File.Create("d:\\Page1-output.pdf"))
{
fs.Write(content, 0, (int)content.Length);
}
}

iText7: com.itextpdf.kernel.PdfException: Dictionary doesn't have supported font data

I try to generate a toc(table of content) for my pdf, and I want to get some strings which look like chapter title in xxx.pdf using ITextExtractionStrategy. But I got com.itextpdf.kernel.PdfException when I am running a test.
Here is my code:
#org.junit.Test
public void test() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfDocument pdfDoc = new PdfDocument(new PdfReader("src/test/resources/template/xxx.pdf"),
new PdfWriter(baos));
pdfDoc.addNewPage(1);
Document document = new Document(pdfDoc);
// when add this code, throw com.itextpdf.kernel.PdfException: Dictionary doesn't have supported font data.
Paragraph title = new Paragraph(new Text("index"))
.setTextAlignment(TextAlignment.CENTER);
document.add(title);
SimpleTextExtractionStrategy extractionStrategy = new SimpleTextExtractionStrategy();
for (int i = 1; i < pdfDoc.getNumberOfPages(); i++) {
PdfPage page = pdfDoc.getPage(i);
PdfCanvasProcessor parser = new PdfCanvasProcessor(extractionStrategy);
parser.processPageContent(page);
}
...
document.close();
pdfDoc.close();
new FileOutputStream("./yyy.pdf").write(baos.toByteArray());
}
Here is the output:
com.itextpdf.kernel.PdfException: Dictionary doesn't have supported font data.
at com.itextpdf.kernel.font.PdfFontFactory.createFont(PdfFontFactory.java:123)
at com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor.getFont(PdfCanvasProcessor.java:490)
at com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor$SetTextFontOperator.invoke(PdfCanvasProcessor.java:811)
at com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor.invokeOperator(PdfCanvasProcessor.java:454)
at com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor.processContent(PdfCanvasProcessor.java:282)
at com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor.processPageContent(PdfCanvasProcessor.java:303)
at com.example.pdf.util.Test.test(Test.java:138)
Whenever you add content to a PdfDocument like you do here
Document document = new Document(pdfDoc);
Paragraph title = new Paragraph(new Text("index"))
.setTextAlignment(TextAlignment.CENTER);
document.add(title);
you have to be aware that this content is not already stored in its final form; for example fonts used are not yet properly subset'ed. The final form is generated when you're closing the document.
Text extraction on the other hand requires the content to extract to be in its final form.
Thus, you should not apply text extraction to a document you're working on. In particular, don't apply text extraction to a page you've changed the content of.
If you need to extract text from the documents you create yourself, close your document first, open a new document from the output, and extract from that new document.

iText7 Merge pdf annotations on a new pdf document

I have multiple copies of a .pdf document that are commented by different users. I would like to merge all these comments into a new pdf "merged".
I wrote this sub inside a class called document with properties "path" and "directory".
Public Sub MergeComments(ByVal pdfDocuments As String())
Dim oSavePath As String = Directory & "\" & FileName & "_Merged.pdf"
Dim oPDFdocument As New iText.Kernel.Pdf.PdfDocument(New PdfReader(Path),
New PdfWriter(New IO.FileStream(oSavePath, IO.FileMode.Create)))
For Each oFile As String In pdfDocuments
Dim oSecundairyPDFdocument As New iText.Kernel.Pdf.PdfDocument(New PdfReader(oFile))
Dim oAnnotations As New PDFannotations
For i As Integer = 1 To oSecundairyPDFdocument.GetNumberOfPages
Dim pdfPage As PdfPage = oSecundairyPDFdocument.GetPage(i)
For Each oAnnotation As Annot.PdfAnnotation In pdfPage.GetAnnotations()
oPDFdocument.GetPage(i).AddAnnotation(oAnnotation)
Next
Next
Next
oPDFdocument.Close()
End Sub
This code results in an exception that I am failing to solve.
iText.Kernel.PdfException: 'Pdf indirect object belongs to other PDF document. Copy object to current pdf document.'
What do I need to change in order to perform this task? Or am I completely off with my code block?
You need to explicitly copy the underlying PDF object to the destination document. After that you will be easily able to add that object to the list of page annotations.
Instead of adding the annotation directly:
oPDFdocument.GetPage(i).AddAnnotation(oAnnotation)
Copy the object to the destination document first, wrap it into PdfAnnotation class with makeAnnotation method and then add it as usual. Code is in Java but you will easily be able to convert it into VB:
PdfObject annotObject = oAnnotation.getPdfObject().copyTo(pdfDocument);
pdfDocument.getPage(i).addAnnotation(PdfAnnotation.makeAnnotation(annotObject));
Here is a working Java code, with annotations copied from one document to other using the copyTo method.
PdfReader reader = new PdfReader(new
RandomAccessSourceFactory().createBestSource(sourceFileName), null);
PdfDocument document = new PdfDocument(reader);
PdfReader toMergeReader = new PdfReader(new RandomAccessSourceFactory().createBestSource(targetFileName), null);
PdfDocument toMergeDocument = new PdfDocument(toMergeReader);
PdfWriter writer = new PdfWriter(targetFileName + "_MergedVersion.pdf");
PdfDocument writeDocument = new PdfDocument(writer);
int pageCount = toMergeDocument.getNumberOfPages();
for (int i = 1; i <= pageCount; i++) {
PdfPage page = document.getPage(i);
writeDocument.addPage(page.copyTo(writeDocument));
PdfPage pdfPage = toMergeDocument.getPage(i);
List<PdfAnnotation> pageAnnots = pdfPage.getAnnotations();
if (pageAnnots != null) {
for (PdfAnnotation pdfAnnotation : pageAnnots) {
PdfObject annotObject = pdfAnnotation.getPdfObject().copyTo(writeDocument);
writeDocument.getPage(i).addAnnotation(PdfAnnotation.makeAnnotation(annotObject));
}
}
}
reader.close();
toMergeReader.close();
toMergeDocument.close();
document.close();
writeDocument.close();
writer.close();

How can I get ByteRange with iText7?

As the picture show , I need to get byte array from ByteRange to do some verify , they are 0 to 840 and 960 to 1200.
I found the similar question : In Itext 7, how to get the range stream to sign a pdf?
iText in its own verification code needs to do the same thing. It does so in its SignatureUtil class. Thus, one can simply borrow from that code, e.g. like this:
try ( PdfReader pdfReader = new PdfReader(SOURCE_PDF);
PdfDocument pdfDocument = new PdfDocument(pdfReader);) {
SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
for (String name : signatureUtil.getSignatureNames()) {
PdfSignature signature = signatureUtil.getSignature(name);
PdfArray b = signature.getByteRange();
RandomAccessFileOrArray rf = pdfReader.getSafeFile();
try ( InputStream rg = new RASInputStream(new RandomAccessSourceFactory().createRanged(rf.createSourceView(), SignatureUtil.asLongArray(b)));
OutputStream result = TARGET_STREAM_FOR_name_BYTES) {
byte[] buf = new byte[8192];
int rd;
while ((rd = rg.read(buf, 0, buf.length)) > 0) {
result.write(buf, 0, rd);
}
}
}
}
(RetrieveSignedRanges test testExtractSignedBytes)
If you want the byte range as a byte[] in memory, you can use a ByteArrayOutputStream as TARGET_STREAM_FOR_name_BYTES and retrieve the resulting byte array from it.

Font Size of acroFields of pdf is coming 0 using iText

I am using this code to get font size of AcroField of PDF.
But font size for a AcroField named "first name last name" is coming out to be 0 (Although its actual font is 32.3).
The font size of other fields are coming accurate.Kindly help me to get exact font size.
My code is...
final AcroFields.Item item = acroFields.getFieldItem(fieldName);
ArrayList list =null;
if(item!=null)
list = item.merged;
if (list != null)
{
for (final Iterator it1 = list.iterator(); it1.hasNext();)
{
final PdfDictionary itemDict = (PdfDictionary) it1.next();
final PdfObject da = itemDict.get(PdfName.DA);
System.out.println(da.toString()); //font size is printing out to be 0;
}
}
New Code is
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import com.lowagie.text.pdf.AcroFields;
import com.lowagie.text.pdf.PRStream;
import com.lowagie.text.pdf.PdfDictionary;
import com.lowagie.text.pdf.PdfEncodings;
import com.lowagie.text.pdf.PdfName;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfStamper;
import com.lowagie.text.pdf.PdfStream;
public class MyTest {
public static void main(String[] args) {
String pdfName = "Crunch-Business_card_NRW_edits.pdf";
PdfStamper stamper = null;
FileOutputStream fout = null;
try{
PdfReader reader = new PdfReader(pdfName);
fout = new FileOutputStream("output.pdf");
stamper = new PdfStamper(reader, fout);
AcroFields acroFields = stamper.getAcroFields();
Map fieldMap = acroFields.getFields();
Set keys = fieldMap.keySet();
for (Iterator it = keys.iterator(); it.hasNext();)
{
String fieldName = (String) it.next();
acroFields.setField(fieldName,acroFields.getField(fieldName));
final AcroFields.Item item = acroFields.getFieldItem(fieldName);
final ArrayList list = item.merged;
if (list != null) {
for (final Iterator it1 = list.iterator(); it1.hasNext();) {
final PdfDictionary itemDict = (PdfDictionary) it1.next();
PdfDictionary appearanceDict = itemDict.getAsDict(PdfName.AP);
PdfStream normalAppearance = appearanceDict.getAsStream(PdfName.N);
System.out.println("normalAppearance======"+normalAppearance);// normalAppearance is coming null.
byte[] streamBytes = PdfReader.getStreamBytes((PRStream) normalAppearance);
System.out.println(PdfEncodings.convertToString(streamBytes, null));
}
}
}
stamper.setFreeTextFlattening(false);
stamper.setFormFlattening(false);
stamper.close();
}
catch(Exception e){
e.printStackTrace();
}
}
}
The link for the PDF is http://www.mediafire.com/view/?tpjql3ipn3xqpbo.
Thanks in Advance.
In essence:
0 means 'auto-size': You have to calculate a size which fits.
In detail:
Let's look at the field definition in the document:
52 0 obj
<<
/Ff 41943042
/F 4
/Type/Annot
/RV(<?xml version="1.0"?>
<body xfa:APIVersion="Acroform:2.7.0.0" xfa:spec="2.1"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
<p dir="ltr"
style="margin-top:0pt;margin-bottom:0pt;text-valign:middle;
font-family:'Alternate Gothic LT';font-size:30pt;
color:#ffffff">first name last name</p>
</body>)
/Subtype/Widget
/DV(first name last name)
/T(name)
/V(first name last name)
/DS(font: 'Alternate Gothic LT',sans-serif 12.0pt; text-align:left; color:#FFFFFF )
/AP<</N 7 0 R>>
/P 21 0 R
/MK<<>>
/FT/Tx
/Rect[36.8297 87.7383 250.89 129.353]
/DA(/AlternateGothicLT-No3 0 Tf 1 1 1 rg)
>>
endobj
And at the content of the appearance stream in 7 0:
q Q /Tx
BMC
q
0 0 214.06 41.61 re W n
q
BT
1 0 0 1 2 7.14 Tm
/AlternateGothicLT-No3 32.31 Tf
1 1 1 rg
(first name last name)Tj
0 g
ET
Q
Q
EMC
So just like you read via iText, the DA (default appearance) string sets the font at size 0.
According to the PDF specification ISO 32000-1, page 435, this means:
The default appearance string (DA) contains any graphics state or text state operators needed to establish the graphics state parameters, such as text size and colour, for displaying the field’s variable text. Only operators that are allowed within text objects shall occur in this string (see Figure 9). At a minimum, the string shall include a Tf (text font) operator along with its two operands, font and size. The specified font value shall match a resource name in the Font entry of the default resource dictionary (referenced from the DR entry of the interactive form dictionary; see Table 218). A zero value for size means that the font shall be auto-sized: its size shall be computed as a function of the height of the annotation rectangle.
So the size has to be calculated to fill the space available but not more.
In the appearance stream you see that the last creator of an appearance considered 32.31 pt to do the job.
EDIT
You can extract the bytes of the normal appearance stream like this:
PdfDictionary appearanceDict = itemDict.getAsDict(PdfName.AP);
PdfStream normalAppearance = appearanceDict.getAsStream(PdfName.N);
byte[] streamBytes = PdfReader.getStreamBytes((PRStream) normalAppearance);
System.out.println(PdfEncodings.convertToString(streamBytes, null));
I have tried solution provided by #mkl and got the expected result.
This is the solution as explained by #mkl.
PdfDictionary appearanceDict = itemDict.getAsDict(PdfName.AP);
PdfStream normalAppearance = appearanceDict.getAsStream(PdfName.N);
byte[] streamBytes = PdfReader.getStreamBytes((PRStream) normalAppearance);
System.out.println(PdfEncodings.convertToString(streamBytes, null));