Skip to content

Commit f2b70c5

Browse files
committed
Fix issue with setters from PdfSignatureFormField
DEVSIX-7969
1 parent 4733e37 commit f2b70c5

File tree

8 files changed

+200
-26
lines changed

8 files changed

+200
-26
lines changed

sign/src/main/java/com/itextpdf/signatures/PdfSignatureAppearance.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ public class PdfSignatureAppearance {
188188
* Indicates if we need to reuse the existing appearance as layer 0.
189189
*/
190190
private boolean reuseAppearance = false;
191+
// Option for backward compatibility.
192+
private boolean reuseAppearanceSet = false;
193+
191194

192195
/**
193196
* Creates a PdfSignatureAppearance.
@@ -546,6 +549,7 @@ public PdfSignatureAppearance setSignatureGraphic(ImageData signatureGraphic) {
546549
@Deprecated
547550
public PdfSignatureAppearance setReuseAppearance(boolean reuseAppearance) {
548551
this.reuseAppearance = reuseAppearance;
552+
this.reuseAppearanceSet = true;
549553
return this;
550554
}
551555

@@ -868,6 +872,16 @@ boolean isReuseAppearance() {
868872
return reuseAppearance;
869873
}
870874

875+
/**
876+
* Checks if reuseAppearance value was set using {@link this#setReuseAppearance(boolean)}.
877+
* Used for backward compatibility.
878+
*
879+
* @return boolean value.
880+
*/
881+
boolean isReuseAppearanceSet() {
882+
return reuseAppearanceSet;
883+
}
884+
871885
/**
872886
* Gets the background layer that is present when creating the signature field if it was set.
873887
*

sign/src/main/java/com/itextpdf/signatures/PdfSigner.java

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,11 @@ public enum CryptoStandard {
208208
*/
209209
protected boolean closed = false;
210210

211+
/**
212+
* AcroForm for the PdfDocument.
213+
*/
214+
private final PdfAcroForm acroForm;
215+
211216
/**
212217
* Creates a PdfSigner instance. Uses a {@link java.io.ByteArrayOutputStream} instead of a temporary file.
213218
*
@@ -241,6 +246,7 @@ public PdfSigner(PdfReader reader, OutputStream outputStream, String path, Stamp
241246
this.tempFile = FileUtil.createTempFile(path);
242247
document = initDocument(reader, new PdfWriter(FileUtil.getFileOutputStream(tempFile)), localProps);
243248
}
249+
acroForm = PdfFormCreator.getAcroForm(document, true);
244250

245251
originalOS = outputStream;
246252
fieldName = getNewSigFieldName();
@@ -255,6 +261,7 @@ public PdfSigner(PdfReader reader, OutputStream outputStream, String path, Stamp
255261
this.tempFile = tempFile;
256262
}
257263
this.document = document;
264+
this.acroForm = PdfFormCreator.getAcroForm(document, true);
258265
this.originalOS = outputStream;
259266
this.fieldName = getNewSigFieldName();
260267
this.appearance = new PdfSignatureAppearance(document, new Rectangle(0, 0), 1);
@@ -385,7 +392,6 @@ public void setSignatureEvent(ISignatureEvent signatureEvent) {
385392
* @return A new signature field name.
386393
*/
387394
public String getNewSigFieldName() {
388-
PdfAcroForm acroForm = PdfFormCreator.getAcroForm(document, true);
389395
String name = "Signature";
390396
int step = 1;
391397

@@ -404,8 +410,6 @@ public String getNewSigFieldName() {
404410
*/
405411
public void setFieldName(String fieldName) {
406412
if (fieldName != null) {
407-
PdfAcroForm acroForm = PdfFormCreator.getAcroForm(document, true);
408-
409413
PdfFormField field = acroForm.getField(fieldName);
410414
if (field != null) {
411415
if (!PdfName.Sig.equals(field.getFormType())) {
@@ -431,7 +435,7 @@ public void setFieldName(String fieldName) {
431435
}
432436
}
433437

434-
appearance.setFieldName(fieldName);
438+
this.appearance.setFieldName(fieldName);
435439
this.fieldName = fieldName;
436440
}
437441
}
@@ -622,6 +626,31 @@ public PdfSigner setLocation(String location) {
622626
return this;
623627
}
624628

629+
/**
630+
* Gets the signature field to be signed. The field can already be presented in the document. If the field is
631+
* not presented in the document, it will be created.
632+
*
633+
* <p>
634+
* This field instance is expected to be used for setting appearance related properties such as
635+
* {@link PdfSignatureFormField#setReuseAppearance}, {@link PdfSignatureFormField#setBackgroundLayer} and
636+
* {@link PdfSignatureFormField#setSignatureAppearanceLayer}.
637+
*
638+
* @return the {@link PdfSignatureFormField} instance.
639+
*/
640+
public PdfSignatureFormField getSignatureField() {
641+
PdfFormField field = acroForm.getField(fieldName);
642+
if (field == null) {
643+
PdfSignatureFormField sigField = new SignatureFormFieldBuilder(document, fieldName)
644+
.setWidgetRectangle(getPageRect()).createSignature();
645+
acroForm.addField(sigField);
646+
return sigField;
647+
}
648+
if (field instanceof PdfSignatureFormField) {
649+
return (PdfSignatureFormField) field;
650+
}
651+
return null;
652+
}
653+
625654
/**
626655
* Signs the document using the detached mode, CMS or CAdES equivalent.
627656
* <br><br>
@@ -1036,7 +1065,6 @@ protected void preClose(Map<PdfName, Integer> exclusionSizes) throws IOException
10361065
throw new PdfException(SignExceptionMessageConstant.DOCUMENT_ALREADY_PRE_CLOSED);
10371066
}
10381067
preClosed = true;
1039-
PdfAcroForm acroForm = PdfFormCreator.getAcroForm(document, true);
10401068
SignatureUtil sgnUtil = new SignatureUtil(document);
10411069
String name = getFieldName();
10421070
boolean fieldExist = sgnUtil.doesSignatureFieldExist(name);
@@ -1149,7 +1177,6 @@ protected void preClose(Map<PdfName, Integer> exclusionSizes) throws IOException
11491177
*/
11501178
protected PdfSigFieldLock populateExistingSignatureFormField(PdfAcroForm acroForm) throws IOException {
11511179
PdfSignatureFormField sigField = (PdfSignatureFormField) acroForm.getField(fieldName);
1152-
sigField.put(PdfName.V, cryptoDictionary.getPdfObject());
11531180

11541181
PdfSigFieldLock sigFieldLock = sigField.getSigFieldLockDictionary();
11551182

@@ -1172,9 +1199,15 @@ protected PdfSigFieldLock populateExistingSignatureFormField(PdfAcroForm acroFor
11721199
sigField.put(PdfName.F, new PdfNumber(flags));
11731200

11741201
sigField.disableFieldRegeneration();
1175-
sigField.setReuseAppearance(appearance.isReuseAppearance())
1176-
.setSignatureAppearanceLayer(appearance.getSignatureAppearanceLayer())
1177-
.setBackgroundLayer(appearance.getBackgroundLayer());
1202+
if (appearance.isReuseAppearanceSet()) {
1203+
sigField.setReuseAppearance(appearance.isReuseAppearance());
1204+
}
1205+
if (appearance.getSignatureAppearanceLayer() != null) {
1206+
sigField.setSignatureAppearanceLayer(appearance.getSignatureAppearanceLayer());
1207+
}
1208+
if (appearance.getBackgroundLayer() != null) {
1209+
sigField.setBackgroundLayer(appearance.getBackgroundLayer());
1210+
}
11781211
sigField.getFirstFormAnnotation().setFormFieldElement(appearance.getSignatureAppearance());
11791212
sigField.enableFieldRegeneration();
11801213

@@ -1382,24 +1415,21 @@ protected boolean documentContainsCertificationOrApprovalSignatures() {
13821415
urSignature = catalogPerms.getAsDictionary(PdfName.UR3);
13831416
}
13841417

1385-
PdfAcroForm acroForm = PdfFormCreator.getAcroForm(document, false);
1386-
if (acroForm != null) {
1387-
for (Map.Entry<String, PdfFormField> entry : acroForm.getAllFormFields().entrySet()) {
1388-
PdfDictionary fieldDict = entry.getValue().getPdfObject();
1389-
if (!PdfName.Sig.equals(fieldDict.get(PdfName.FT)))
1390-
continue;
1391-
PdfDictionary sigDict = fieldDict.getAsDictionary(PdfName.V);
1392-
if (sigDict == null)
1393-
continue;
1394-
PdfSignature pdfSignature = new PdfSignature(sigDict);
1395-
if (pdfSignature.getContents() == null || pdfSignature.getByteRange() == null) {
1396-
continue;
1397-
}
1418+
for (Map.Entry<String, PdfFormField> entry : acroForm.getAllFormFields().entrySet()) {
1419+
PdfDictionary fieldDict = entry.getValue().getPdfObject();
1420+
if (!PdfName.Sig.equals(fieldDict.get(PdfName.FT)))
1421+
continue;
1422+
PdfDictionary sigDict = fieldDict.getAsDictionary(PdfName.V);
1423+
if (sigDict == null)
1424+
continue;
1425+
PdfSignature pdfSignature = new PdfSignature(sigDict);
1426+
if (pdfSignature.getContents() == null || pdfSignature.getByteRange() == null) {
1427+
continue;
1428+
}
13981429

1399-
if (!pdfSignature.getType().equals(PdfName.DocTimeStamp) && sigDict != urSignature) {
1400-
containsCertificationOrApprovalSignature = true;
1401-
break;
1402-
}
1430+
if (!pdfSignature.getType().equals(PdfName.DocTimeStamp) && sigDict != urSignature) {
1431+
containsCertificationOrApprovalSignature = true;
1432+
break;
14031433
}
14041434
}
14051435
return containsCertificationOrApprovalSignature;

sign/src/test/java/com/itextpdf/signatures/sign/PdfSignatureAppearanceTest.java

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ This file is part of the iText (R) project.
3030
import com.itextpdf.forms.fields.PdfFormCreator;
3131
import com.itextpdf.forms.fields.PdfSignatureFormField;
3232
import com.itextpdf.forms.fields.SignatureFormFieldBuilder;
33+
import com.itextpdf.forms.form.element.SignatureFieldAppearance;
3334
import com.itextpdf.io.image.ImageDataFactory;
3435
import com.itextpdf.commons.utils.MessageFormatUtil;
3536
import com.itextpdf.kernel.colors.ColorConstants;
@@ -575,6 +576,47 @@ public void signExistedSignatureFieldTest() throws IOException, GeneralSecurityE
575576
compareSignatureAppearances(dest, SOURCE_FOLDER + "cmp_" + fileName);
576577
}
577578

579+
@Test
580+
public void reuseAppearanceTest() throws GeneralSecurityException,
581+
IOException, InterruptedException {
582+
// Field is not merged with widget and has /P key
583+
String src = SOURCE_FOLDER + "emptyFieldNotMerged.pdf";
584+
String fileName = "reuseAppearance.pdf";
585+
testReuseAppearance(src, fileName, false, true, false);
586+
}
587+
588+
@Test
589+
public void reuseAppearanceDeprecatedTest() throws GeneralSecurityException,
590+
IOException, InterruptedException {
591+
// Field is not merged with widget and has /P key
592+
String src = SOURCE_FOLDER + "emptyFieldNotMerged.pdf";
593+
String fileName = "reuseAppearanceDeprecated.pdf";
594+
testReuseAppearance(src, fileName, true, false, true);
595+
}
596+
597+
@Test
598+
public void reuseAppearanceCompatibilityTest() throws GeneralSecurityException,
599+
IOException, InterruptedException {
600+
// Field is not merged with widget and has /P key
601+
String src = SOURCE_FOLDER + "emptyFieldNotMerged.pdf";
602+
String fileName = "reuseAppearanceCompatibility.pdf";
603+
testReuseAppearance(src, fileName, true, true, false);
604+
}
605+
606+
@Test
607+
public void fieldLayersTest() throws IOException, GeneralSecurityException {
608+
String src = SOURCE_FOLDER + "noSignatureField.pdf";
609+
String fileName = "fieldLayersTest.pdf";
610+
testLayers(src, fileName, false);
611+
}
612+
613+
@Test
614+
public void deprecatedLayersTest() throws IOException, GeneralSecurityException {
615+
String src = SOURCE_FOLDER + "noSignatureField.pdf";
616+
String fileName = "deprecatedLayersTest.pdf";
617+
testLayers(src, fileName, true);
618+
}
619+
578620
private static void compareSignatureAppearances(String outPdf, String cmpPdf) throws IOException {
579621
ITextTest.printOutCmpPdfNameAndDir(outPdf, cmpPdf);
580622
try (PdfDocument outDoc = new PdfDocument(new PdfReader(outPdf))) {
@@ -590,6 +632,94 @@ private static void compareSignatureAppearances(String outPdf, String cmpPdf) th
590632
}
591633
}
592634

635+
private void testReuseAppearance(String src, String fileName, boolean useDeprecated, boolean fieldReuseAp,
636+
boolean deprecatedReuseAp) throws IOException, GeneralSecurityException, InterruptedException {
637+
String cmp = SOURCE_FOLDER + "cmp_" + fileName;
638+
String dest = DESTINATION_FOLDER + fileName;
639+
String fieldName = "Signature1";
640+
641+
PdfSigner signer = new PdfSigner(new PdfReader(src), new FileOutputStream(dest), new StampingProperties());
642+
signer.setFieldName(fieldName);
643+
signer.getSignatureField().setReuseAppearance(fieldReuseAp);
644+
if (useDeprecated) {
645+
signer.getSignatureAppearance().setReuseAppearance(deprecatedReuseAp);
646+
}
647+
648+
signer.setReason("Test 1").setLocation("TestCity")
649+
.setSignatureAppearance(new SignatureFieldAppearance(fieldName)
650+
.setContent("New appearance").setFontColor(ColorConstants.GREEN));
651+
652+
IExternalSignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, FACTORY.getProviderName());
653+
signer.signDetached(new BouncyCastleDigest(), pks, chain, null, null, null, 0, PdfSigner.CryptoStandard.CADES);
654+
655+
Assert.assertNull(new CompareTool().compareVisually(dest, cmp, DESTINATION_FOLDER, "diff_"));
656+
}
657+
658+
private void testLayers(String src, String fileName, boolean useDeprecated)
659+
throws IOException, GeneralSecurityException {
660+
String dest = DESTINATION_FOLDER + fileName;
661+
String fieldName = "Signature1";
662+
663+
PdfSigner signer = new PdfSigner(new PdfReader(src), new FileOutputStream(dest), new StampingProperties());
664+
signer.setFieldName(fieldName);
665+
signer.setPageRect(new Rectangle(250, 500, 100, 100)).setReason("Test 1").setLocation("TestCity")
666+
.setSignatureAppearance(new SignatureFieldAppearance(fieldName));
667+
668+
PdfFormXObject layer0 = new PdfFormXObject(new Rectangle(0, 0, 100, 100));
669+
// Draw pink rectangle with blue border
670+
new PdfCanvas(layer0, signer.getDocument())
671+
.saveState()
672+
.setFillColor(ColorConstants.PINK)
673+
.setStrokeColor(ColorConstants.BLUE)
674+
.rectangle(0, 0, 100, 100)
675+
.fillStroke()
676+
.restoreState();
677+
678+
PdfFormXObject layer2 = new PdfFormXObject(new Rectangle(0, 0, 100, 100));
679+
// Draw yellow circle with gray border
680+
new PdfCanvas(layer2, signer.getDocument())
681+
.saveState()
682+
.setFillColor(ColorConstants.YELLOW)
683+
.setStrokeColor(ColorConstants.DARK_GRAY)
684+
.circle(50, 50, 50)
685+
.fillStroke()
686+
.restoreState();
687+
688+
signer.getSignatureField().setBackgroundLayer(layer0).setSignatureAppearanceLayer(layer2);
689+
690+
if (useDeprecated) {
691+
// Creating the appearance
692+
PdfSignatureAppearance appearance = signer.getSignatureAppearance();
693+
694+
PdfFormXObject deprecatedLayer0 = appearance.getLayer0();
695+
// Draw yellow rectangle with gray border
696+
new PdfCanvas(deprecatedLayer0, signer.getDocument())
697+
.saveState()
698+
.setFillColor(ColorConstants.YELLOW)
699+
.setStrokeColor(ColorConstants.DARK_GRAY)
700+
.rectangle(0, 0, 100, 100)
701+
.fillStroke()
702+
.restoreState();
703+
704+
PdfFormXObject deprecatedLayer2 = appearance.getLayer2();
705+
// Draw pink circle with blue border
706+
new PdfCanvas(deprecatedLayer2, signer.getDocument())
707+
.saveState()
708+
.setFillColor(ColorConstants.PINK)
709+
.setStrokeColor(ColorConstants.BLUE)
710+
.circle(50, 50, 50)
711+
.fillStroke()
712+
.restoreState();
713+
}
714+
715+
// Signing
716+
IExternalSignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256,
717+
FACTORY.getProviderName());
718+
signer.signDetached(new BouncyCastleDigest(), pks, chain, null, null, null, 0, PdfSigner.CryptoStandard.CADES);
719+
720+
compareSignatureAppearances(dest, SOURCE_FOLDER + "cmp_" + fileName);
721+
}
722+
593723
private void testSignatureOnRotatedPage(int pageNum, PdfSignatureAppearance.RenderingMode renderingMode,
594724
StringBuilder assertionResults) throws IOException, GeneralSecurityException, InterruptedException {
595725
String fileName = "signaturesOnRotatedPages" + pageNum + "_mode_" + renderingMode.name() + ".pdf";

0 commit comments

Comments
 (0)