@@ -30,6 +30,7 @@ This file is part of the iText (R) project.
3030import com .itextpdf .forms .fields .PdfFormCreator ;
3131import com .itextpdf .forms .fields .PdfSignatureFormField ;
3232import com .itextpdf .forms .fields .SignatureFormFieldBuilder ;
33+ import com .itextpdf .forms .form .element .SignatureFieldAppearance ;
3334import com .itextpdf .io .image .ImageDataFactory ;
3435import com .itextpdf .commons .utils .MessageFormatUtil ;
3536import 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