> objectStore =
@@ -111,6 +113,7 @@ public class Library {
// handles signature validation and signing.
private final SignatureHandler signatureHandler;
+ private final SignatureManager signatureManager;
// signature permissions
private Permissions permissions;
@@ -123,7 +126,6 @@ public class Library {
private boolean isLinearTraversal;
private final ImagePool imagePool;
-
/**
* Creates a new instance of a Library.
*/
@@ -132,6 +134,7 @@ public Library() {
// set Catalog memory Manager and cache manager.
imagePool = new ImagePool();
signatureHandler = new SignatureHandler();
+ signatureManager = new SignatureManager();
}
/**
@@ -518,6 +521,10 @@ public boolean isValidEntry(DictionaryEntries dictionaryEntries, Name key) {
return o != null && (!(o instanceof Reference) || isValidEntry((Reference) o));
}
+ public int getOffset(Reference reference) throws CrossReferenceStateException, ObjectStateException, IOException {
+ return crossReferenceRoot.getObjectOffset(objectLoader, reference, null);
+ }
+
/**
* Tests if there exists a cross-reference entry for this reference.
*
@@ -898,6 +905,10 @@ public SignatureHandler getSignatureHandler() {
return signatureHandler;
}
+ public SignatureManager getSignatureDictionaries() {
+ return signatureManager;
+ }
+
/**
* Set a documents permissions for a given certificate of signature, optional.
* The permission should also be used with the encryption permissions if present
diff --git a/core/core-awt/src/main/java/org/icepdf/core/util/PropertyConstants.java b/core/core-awt/src/main/java/org/icepdf/core/util/PropertyConstants.java
index c7925e87f..487f86e51 100644
--- a/core/core-awt/src/main/java/org/icepdf/core/util/PropertyConstants.java
+++ b/core/core-awt/src/main/java/org/icepdf/core/util/PropertyConstants.java
@@ -73,5 +73,4 @@ public class PropertyConstants {
ANNOTATION_SUMMARY_BOX_FONT_SIZE_CHANGE = "annotationSummaryBoxFontSizeChange";
-
}
diff --git a/core/core-awt/src/main/java/org/icepdf/core/util/SignatureManager.java b/core/core-awt/src/main/java/org/icepdf/core/util/SignatureManager.java
new file mode 100644
index 000000000..91457ba46
--- /dev/null
+++ b/core/core-awt/src/main/java/org/icepdf/core/util/SignatureManager.java
@@ -0,0 +1,98 @@
+package org.icepdf.core.util;
+
+import org.icepdf.core.pobjects.PObject;
+import org.icepdf.core.pobjects.StateManager;
+import org.icepdf.core.pobjects.acroform.InteractiveForm;
+import org.icepdf.core.pobjects.acroform.SignatureDictionary;
+import org.icepdf.core.pobjects.acroform.SignatureReferenceDictionary;
+import org.icepdf.core.pobjects.annotations.SignatureWidgetAnnotation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * SignatureManager is used to manage the signature dictionaries associated with a document. A users can create
+ * more than one Signature annotation but, they must be linked to the same SignatureDictionary. When a document is
+ * written to disk, only one signature dictionary can be used to sign the document.
+ *
+ * This class also does basic validation to make sure there is only one dictionary marked as the
+ * /DocMDP or "certifier" distinction.
+ */
+public class SignatureManager {
+
+ private SignatureDictionary currentSignatureDictionary;
+ private final ArrayList signatureWidgetAnnotations = new ArrayList<>();
+
+ public void addSignature(SignatureDictionary signatureDictionary, SignatureWidgetAnnotation signatureAnnotation) {
+ // if not the same dictionary then we need to apply it to all the existing signature widgets and clean up
+ // the old dictionary.
+ if (currentSignatureDictionary != null && !currentSignatureDictionary.equals(signatureDictionary)) {
+ for (SignatureWidgetAnnotation signatureWidgetAnnotation : signatureWidgetAnnotations) {
+ signatureWidgetAnnotation.setSignatureDictionary(signatureDictionary);
+ }
+ // remove the old signature dictionary
+ StateManager stateManager = signatureAnnotation.getLibrary().getStateManager();
+ stateManager.removeChange(new PObject(currentSignatureDictionary,
+ currentSignatureDictionary.getPObjectReference()));
+ }
+
+ currentSignatureDictionary = signatureDictionary;
+ signatureAnnotation.setSignatureDictionary(currentSignatureDictionary);
+
+ // add the new signature widget to the list
+ if (!signatureWidgetAnnotations.contains(signatureAnnotation)) {
+ signatureWidgetAnnotations.add(signatureAnnotation);
+ }
+ }
+
+ /**
+ * Clears the current signature dictionary and references to associated SignatureWidgetAnnotation.
+ * This should be done after the document has been signed or if the signature process is cancelled.
+ */
+ public void clearSignatures() {
+ currentSignatureDictionary = null;
+ signatureWidgetAnnotations.clear();
+ }
+
+ /**
+ * Returns the signature dictionaries associated with the document edits and will be used to sign the document.
+ *
+ * @return current signature dictionary for signing or null if not set.
+ */
+ public SignatureDictionary getCurrentSignatureDictionary() {
+ return currentSignatureDictionary;
+ }
+
+ /**
+ * Check if a signature dictionary has been set. If a signature dictionary has been set, then the current
+ * signature dictionary should be used to sign the document and a new one should not be created
+ *
+ * @return true if a signature dictionary has been set, otherwise false.
+ */
+ public boolean hasSignatureDictionary() {
+ return currentSignatureDictionary != null;
+ }
+
+ /**
+ * Checks to see if a certifier signature already exists in the document.
+ *
+ * @param library document library
+ * @return true if there is already a certifier signature, otherwise false.
+ */
+ public boolean hasExistingCertifier(Library library) {
+ InteractiveForm interactiveForm = library.getCatalog().getInteractiveForm();
+ if (interactiveForm != null) {
+ ArrayList signatureWidgets = interactiveForm.getSignatureFields();
+ for (SignatureWidgetAnnotation signatureWidget : signatureWidgets) {
+ List signatureReferenceDictionary =
+ signatureWidget.getSignatureDictionary().getReferences();
+ for (SignatureReferenceDictionary reference : signatureReferenceDictionary) {
+ if (reference.getTransformMethod() == SignatureReferenceDictionary.TransformMethods.DocMDP) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/core/core-awt/src/main/java/org/icepdf/core/util/parser/content/AbstractContentParser.java b/core/core-awt/src/main/java/org/icepdf/core/util/parser/content/AbstractContentParser.java
index e9b27b7a8..8c00e560b 100644
--- a/core/core-awt/src/main/java/org/icepdf/core/util/parser/content/AbstractContentParser.java
+++ b/core/core-awt/src/main/java/org/icepdf/core/util/parser/content/AbstractContentParser.java
@@ -648,7 +648,7 @@ else if (viewParse) {
// create an ImageReference for future decoding
ImageReference imageReference = ImageReferenceFactory.getImageReference(
- imageStream, resources, graphicState,
+ imageStream, xobjectName, resources, graphicState,
imageIndex.get(), page);
imageIndex.incrementAndGet();
diff --git a/core/core-awt/src/main/java/org/icepdf/core/util/parser/content/ContentParser.java b/core/core-awt/src/main/java/org/icepdf/core/util/parser/content/ContentParser.java
index 82b5cc9b5..5167aaf2c 100644
--- a/core/core-awt/src/main/java/org/icepdf/core/util/parser/content/ContentParser.java
+++ b/core/core-awt/src/main/java/org/icepdf/core/util/parser/content/ContentParser.java
@@ -1052,14 +1052,14 @@ private void parseInlineImage(Lexer lexer, Shapes shapes, Page page) throws IOEx
// create the image stream
imageStream = new ImageStream(library, iih, data);
imageStreamReference = ImageReferenceFactory.getImageReference(
- imageStream, resources, graphicState, imageIndex.get(), page);
+ imageStream, null, resources, graphicState, imageIndex.get(), page);
inlineImageCache.put(tmpKey, imageStreamReference);
}
} else {
// create the image stream
imageStream = new ImageStream(library, iih, data);
imageStreamReference = ImageReferenceFactory.getImageReference(
- imageStream, resources, graphicState, imageIndex.get(), page);
+ imageStream, null, resources, graphicState, imageIndex.get(), page);
}
// experimental display
// ImageUtility.displayImage(imageStreamReference.getImage(), "BI");
diff --git a/core/core-awt/src/main/java/org/icepdf/core/util/parser/object/ObjectLoader.java b/core/core-awt/src/main/java/org/icepdf/core/util/parser/object/ObjectLoader.java
index c3f52bbe7..410509a2c 100644
--- a/core/core-awt/src/main/java/org/icepdf/core/util/parser/object/ObjectLoader.java
+++ b/core/core-awt/src/main/java/org/icepdf/core/util/parser/object/ObjectLoader.java
@@ -47,4 +47,20 @@ public synchronized PObject loadObject(CrossReference crossReference, Reference
}
return null;
}
+
+ public synchronized int getObjectOffset(CrossReference crossReference, Reference reference)
+ throws ObjectStateException, CrossReferenceStateException, IOException {
+
+ CrossReferenceEntry entry = crossReference.getEntry(reference);
+
+ if (entry instanceof CrossReferenceUsedEntry) {
+ CrossReferenceUsedEntry crossReferenceEntry = (CrossReferenceUsedEntry) entry;
+ // parse the object
+ int offset = crossReferenceEntry.getFilePositionOfObject();
+ return offset;
+ } else if (entry instanceof CrossReferenceCompressedEntry) {
+ throw new IllegalStateException("The cross reference compressed entry is not supported.");
+ }
+ return -1;
+ }
}
\ No newline at end of file
diff --git a/core/core-awt/src/main/java/org/icepdf/core/util/updater/DocumentBuilder.java b/core/core-awt/src/main/java/org/icepdf/core/util/updater/DocumentBuilder.java
index 073731c9a..d3cd301a1 100644
--- a/core/core-awt/src/main/java/org/icepdf/core/util/updater/DocumentBuilder.java
+++ b/core/core-awt/src/main/java/org/icepdf/core/util/updater/DocumentBuilder.java
@@ -5,9 +5,6 @@
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
-import java.nio.channels.Channels;
-import java.nio.channels.WritableByteChannel;
-import java.util.logging.Level;
import java.util.logging.Logger;
/**
@@ -28,29 +25,21 @@ public long createDocument(
OutputStream out,
long documentLength) throws IOException, InterruptedException {
- try (WritableByteChannel channel = Channels.newChannel(out)) {
- if (writeMode == WriteMode.FULL_UPDATE) {
- // kick of a full rewrite of the document, replacing any updates objects with new data
- long newLength = new FullUpdater().writeDocument(
- document,
- out);
- return newLength;
- } else if (writeMode == WriteMode.INCREMENT_UPDATE) {
- // copy original file data
- channel.write(documentByteBuffer);
- // append the data from the incremental updater
- long appendedLength = new IncrementalUpdater().appendIncrementalUpdate(
- document,
- out,
- documentLength);
- channel.close();
- return documentLength + appendedLength;
- }
- } catch (IOException | InterruptedException e) {
- logger.log(Level.FINE, "Error writing PDF output stream.", e);
- throw e;
+ long length = -1;
+ if (writeMode == WriteMode.FULL_UPDATE) {
+ // kick of a full rewrite of the document, replacing any updates objects with new data
+ length = new FullUpdater().writeDocument(
+ document,
+ out);
+ } else if (writeMode == WriteMode.INCREMENT_UPDATE) {
+ // append the data from the incremental updater
+ long appendedLength = new IncrementalUpdater().appendIncrementalUpdate(
+ document,
+ documentByteBuffer,
+ out,
+ documentLength);
+ length = documentLength + appendedLength;
}
-
- return 0;
+ return length;
}
}
diff --git a/core/core-awt/src/main/java/org/icepdf/core/util/updater/FullUpdater.java b/core/core-awt/src/main/java/org/icepdf/core/util/updater/FullUpdater.java
index b1c264e7f..176bcc10f 100644
--- a/core/core-awt/src/main/java/org/icepdf/core/util/updater/FullUpdater.java
+++ b/core/core-awt/src/main/java/org/icepdf/core/util/updater/FullUpdater.java
@@ -3,20 +3,25 @@
import org.icepdf.core.exceptions.PDFSecurityException;
import org.icepdf.core.io.CountingOutputStream;
import org.icepdf.core.pobjects.*;
+import org.icepdf.core.pobjects.acroform.signature.DocumentSigner;
import org.icepdf.core.pobjects.graphics.images.references.ImageReference;
import org.icepdf.core.pobjects.security.SecurityManager;
import org.icepdf.core.pobjects.structure.CrossReferenceRoot;
import org.icepdf.core.util.Defs;
import org.icepdf.core.util.Library;
+import org.icepdf.core.util.SignatureManager;
import org.icepdf.core.util.redaction.Redactor;
import org.icepdf.core.util.updater.writeables.BaseWriter;
+import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
/**
* Writes a document stream in its entirety. The document's root object is used ot traverse the page tree
@@ -27,6 +32,9 @@
*/
public class FullUpdater {
+ private static final Logger logger =
+ Logger.getLogger(FullUpdater.class.toString());
+
/**
* Write the xrefTable in a compressed format by default. Can be disabled if to aid in debugging or to
* support old PDF versions.
@@ -49,9 +57,9 @@ public static void setCompressXrefTable(boolean compressXrefTable) {
* Write a new document inserting and updating modified objects to the specified output stream.
*
* @param document The Document that is being saved
- * @param outputStream OutputStream to write the incremental update to
+ * @param outputStream OutputStream to write the full document to
* @return The number of bytes written generating the new document
- * @throws java.io.IOException error writing stream.
+ * @throws java.io.IOException error writing stream.
* @throws InterruptedException
*/
public long writeDocument(
@@ -59,23 +67,53 @@ public long writeDocument(
throws IOException, InterruptedException {
// create a tmp file and write the changed document
- Path tmpFile = Files.createTempFile(null, null);
- OutputStream tmpOutputStream = new FileOutputStream(tmpFile.toFile());
- writeDocument(document, tmpOutputStream, false);
+ Path tmpFilePath = Files.createTempFile(null, null);
+ Path tmpRedactionFilePath = Files.createTempFile(null, null);
+ OutputStream tmpOutputStream = new FileOutputStream(tmpFilePath.toFile());
+ OutputStream tmpRedactionOutputStream = null;
+ long bytesWritten = writeDocument(document, tmpOutputStream, false);
tmpOutputStream.close();
// open the copy and burn the redactions to the specified outputStream
+ if (stateManager.hasRedactions()) {
+ Document tmpDocument = new Document();
+ tmpRedactionOutputStream = new FileOutputStream(tmpRedactionFilePath.toFile());
+ try {
+ tmpDocument.setFile(tmpFilePath.toString());
+ bytesWritten = writeDocument(tmpDocument, tmpRedactionOutputStream, true);
+ } catch (PDFSecurityException e) {
+ throw new RuntimeException(e);
+ } finally {
+ // clean up
+ tmpDocument.dispose();
+ tmpRedactionOutputStream.close();
+ }
+ }
+ Path currentPath;
+ if (tmpRedactionOutputStream != null) {
+ currentPath = tmpRedactionFilePath;
+ } else {
+ currentPath = tmpFilePath;
+ }
+ // apply any signatures
Document tmpDocument = new Document();
- long bytesWritten;
try {
- tmpDocument.setFile(tmpFile.toString());
- bytesWritten = writeDocument(tmpDocument, outputStream, true);
- } catch (PDFSecurityException e) {
+ SignatureManager signatureManager = library.getSignatureDictionaries();
+ if (signatureManager.hasSignatureDictionary()) {
+ tmpDocument.setFile(currentPath.toString());
+ File tempFile = currentPath.toFile();
+ DocumentSigner.signDocument(tmpDocument, tempFile,
+ signatureManager.getCurrentSignatureDictionary());
+ }
+ Files.copy(currentPath, outputStream);
+ } catch (Exception e) {
+ logger.log(Level.FINE, "Failed to sign document.", e);
throw new RuntimeException(e);
} finally {
- // clean up
+ // clean of the tmp files
tmpDocument.dispose();
- Files.delete(tmpFile);
+ Files.delete(tmpFilePath);
+ Files.delete(tmpRedactionFilePath);
}
return bytesWritten;
}
diff --git a/core/core-awt/src/main/java/org/icepdf/core/util/updater/IncrementalUpdater.java b/core/core-awt/src/main/java/org/icepdf/core/util/updater/IncrementalUpdater.java
index 891c5b711..baf7ffa0f 100644
--- a/core/core-awt/src/main/java/org/icepdf/core/util/updater/IncrementalUpdater.java
+++ b/core/core-awt/src/main/java/org/icepdf/core/util/updater/IncrementalUpdater.java
@@ -5,37 +5,68 @@
import org.icepdf.core.pobjects.PObject;
import org.icepdf.core.pobjects.PTrailer;
import org.icepdf.core.pobjects.StateManager;
+import org.icepdf.core.pobjects.acroform.signature.DocumentSigner;
import org.icepdf.core.pobjects.security.SecurityManager;
import org.icepdf.core.pobjects.structure.CrossReferenceRoot;
+import org.icepdf.core.util.Library;
+import org.icepdf.core.util.SignatureManager;
import org.icepdf.core.util.updater.writeables.BaseWriter;
+import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.WritableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.Iterator;
+import java.util.logging.Level;
+import java.util.logging.Logger;
public class IncrementalUpdater {
+ private static final Logger logger =
+ Logger.getLogger(IncrementalUpdater.class.toString());
+
/**
* Appends modified objects to the specified output stream.
*
* @param document The Document that is being saved
+ * @param documentByteBuffer ByteBuffer of the original document
* @param outputStream OutputStream to write the incremental update to
* @param documentLength start of appender bytes, can be zero if storing the bytes to another source.
* @return The number of bytes written in the incremental update
* @throws java.io.IOException error writing stream.
*/
public long appendIncrementalUpdate(
- Document document, OutputStream outputStream, long documentLength)
+ Document document, ByteBuffer documentByteBuffer, OutputStream outputStream, long documentLength)
throws IOException {
+ Library library = document.getCatalog().getLibrary();
+ SignatureManager signatureManager = library.getSignatureDictionaries();
StateManager stateManager = document.getStateManager();
CrossReferenceRoot crossReferenceRoot = stateManager.getCrossReferenceRoot();
- if (stateManager.isNoChange()) {
+ if (stateManager.isNoChange() && !signatureManager.hasSignatureDictionary()) {
return 0L;
}
+ // create a temp document so that it can sign it after the incremental update
+ Path tmpFilePath = Files.createTempFile(null, null);
+ File tempFile = tmpFilePath.toFile();
+ OutputStream newDocumentOutputStream = new FileOutputStream(tempFile);
+ try {
+ // copy original file data
+ WritableByteChannel channel = Channels.newChannel(newDocumentOutputStream);
+ channel.write(documentByteBuffer);
+ } catch (IOException e) {
+ logger.log(Level.FINE, "Error writing PDF output stream during incremental write.", e);
+ throw e;
+ }
+
SecurityManager securityManager = document.getSecurityManager();
- CountingOutputStream output = new CountingOutputStream(outputStream);
+ CountingOutputStream output = new CountingOutputStream(newDocumentOutputStream);
BaseWriter writer = new BaseWriter(crossReferenceRoot, securityManager, output, documentLength);
writer.initializeWriters();
@@ -49,7 +80,7 @@ public long appendIncrementalUpdate(
}
}
- // todo may need updating as I don't think it handles hybrid mode
+ // todo, may need updating as I don't think it handles hybrid mode properly
PTrailer trailer = crossReferenceRoot.getTrailerDictionary();
if (trailer.isCompressedXref()) {
writer.writeIncrementalCompressedXrefTable();
@@ -59,6 +90,29 @@ public long appendIncrementalUpdate(
}
output.close();
+ // sign the document using the first signature, this could be reworked to handle more signatures, like
+ // certification followed by other approvals. But for now it will be assumed this is done as seperate steps
+ Document tmpDocument = new Document();
+ try {
+ if (signatureManager.hasSignatureDictionary()) {
+ // open new incrementally updated tmp file
+ tmpDocument.setFile(tempFile.toString());
+ // size of new file, this won't change as SignatureDictionary has padding to account for content and
+ // offsets
+ DocumentSigner.signDocument(tmpDocument, tempFile,
+ signatureManager.getCurrentSignatureDictionary());
+ }
+ } catch (Exception e) {
+ logger.log(Level.FINE, "Failed to sign document.", e);
+ throw new RuntimeException(e);
+ } finally {
+ tmpDocument.dispose();
+ }
+
+ // copy the temp file to the outputStream and cleanup.
+ Files.copy(tmpFilePath, outputStream);
+ Files.delete(tmpFilePath);
+
return writer.getBytesWritten();
}
}
diff --git a/core/core-awt/src/main/java/org/icepdf/core/util/updater/modifiers/AnnotationRemovalModifier.java b/core/core-awt/src/main/java/org/icepdf/core/util/updater/modifiers/AnnotationRemovalModifier.java
index d97174e5b..d3ba178c7 100644
--- a/core/core-awt/src/main/java/org/icepdf/core/util/updater/modifiers/AnnotationRemovalModifier.java
+++ b/core/core-awt/src/main/java/org/icepdf/core/util/updater/modifiers/AnnotationRemovalModifier.java
@@ -1,6 +1,7 @@
package org.icepdf.core.util.updater.modifiers;
import org.icepdf.core.pobjects.*;
+import org.icepdf.core.pobjects.acroform.SignatureDictionary;
import org.icepdf.core.pobjects.annotations.*;
import org.icepdf.core.util.Library;
@@ -37,7 +38,7 @@ public void modify(Annotation annot) {
Stream nAp = annot.getAppearanceStream();
if (nAp != null) {
nAp.setDeleted(true);
- // find the xObjects font resources.
+ // clean up resources.
Object tmp = library.getObject(nAp.getEntries(), RESOURCES_KEY);
if (tmp instanceof Resources) {
Resources resources = (Resources) tmp;
@@ -48,7 +49,26 @@ public void modify(Annotation annot) {
font.setDeleted(true);
stateManager.addDeletion(font.getPObjectReference());
}
+ DictionaryEntries xObject = resources.getXObjects();
+ if (xObject != null) {
+ for (Object key : xObject.keySet()) {
+ Object obj = xObject.get(key);
+ if (obj instanceof Reference) {
+ stateManager.addDeletion((Reference) obj);
+ }
+ }
+ }
+ }
+ }
+ // check for /V key which is a reference to a signature dictionary
+ // todo new annotation base method to encapsulate the cleanup
+ if (annot instanceof SignatureWidgetAnnotation) {
+ SignatureWidgetAnnotation signatureWidgetAnnotation = (SignatureWidgetAnnotation) annot;
+ Object v = signatureWidgetAnnotation.getEntries().get(SignatureDictionary.V_KEY);
+ if (v instanceof Reference) {
+ stateManager.addDeletion((Reference) v);
}
+ library.getSignatureDictionaries().clearSignatures();
}
// check to see if this is an existing annotations, if the annotations
diff --git a/core/core-awt/src/main/java/org/icepdf/core/util/updater/writeables/BaseWriter.java b/core/core-awt/src/main/java/org/icepdf/core/util/updater/writeables/BaseWriter.java
index 17881dee6..136102251 100644
--- a/core/core-awt/src/main/java/org/icepdf/core/util/updater/writeables/BaseWriter.java
+++ b/core/core-awt/src/main/java/org/icepdf/core/util/updater/writeables/BaseWriter.java
@@ -63,8 +63,7 @@ public BaseWriter() {
}
public BaseWriter(CrossReferenceRoot crossReferenceRoot, SecurityManager securityManager,
- CountingOutputStream output,
- long startingPosition) {
+ CountingOutputStream output, long startingPosition) {
this.output = output;
this.crossReferenceRoot = crossReferenceRoot;
this.securityManager = securityManager;
@@ -153,7 +152,7 @@ public void writeHeader(Header header) throws IOException {
headerWriter.write(header, output);
}
- protected void writeValue(PObject pObject, CountingOutputStream output) throws IOException {
+ public void writeValue(PObject pObject, CountingOutputStream output) throws IOException {
Object val = pObject.getObject();
if (val == null) {
output.write(NULL);
@@ -255,16 +254,12 @@ protected byte[] encryptStream(Stream stream, byte[] outputData) throws IOExcept
Library library = stream.getLibrary();
if (stream.getEntries().get(Stream.DECODEPARAM_KEY) != null) {
// needed to check for a custom crypt filter
- decodeParams = library.getDictionary(stream.getEntries(),
- Stream.DECODEPARAM_KEY);
+ decodeParams = library.getDictionary(stream.getEntries(), Stream.DECODEPARAM_KEY);
} else {
decodeParams = new DictionaryEntries();
}
- InputStream decryptedStream = securityManager.encryptInputStream(
- stream.getPObjectReference(),
- securityManager.getDecryptionKey(),
- decodeParams,
- new ByteArrayInputStream(outputData), true);
+ InputStream decryptedStream = securityManager.encryptInputStream(stream.getPObjectReference(),
+ securityManager.getDecryptionKey(), decodeParams, new ByteArrayInputStream(outputData), true);
ByteArrayOutputStream out = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[16384];
diff --git a/core/core-awt/src/main/java/org/icepdf/core/util/updater/writeables/image/PredictorEncoder.java b/core/core-awt/src/main/java/org/icepdf/core/util/updater/writeables/image/PredictorEncoder.java
index ffa1564cb..f69bf7067 100644
--- a/core/core-awt/src/main/java/org/icepdf/core/util/updater/writeables/image/PredictorEncoder.java
+++ b/core/core-awt/src/main/java/org/icepdf/core/util/updater/writeables/image/PredictorEncoder.java
@@ -77,6 +77,7 @@ class PredictorEncoder implements ImageEncoder {
PredictorEncoder(ImageStream imageStream) {
this.imageStream = imageStream;
BufferedImage image = imageStream.getDecodedImage();
+
// The raw count of components per pixel including optional alpha
this.componentsPerPixel = image.getColorModel().getNumComponents();
int transferType = image.getRaster().getTransferType();
@@ -123,7 +124,7 @@ class PredictorEncoder implements ImageEncoder {
/**
* Tries to compress the image using a predictor.
*
- * @return the image or null if it is not possible to encoded the image (e.g. not supported
+ * @return the image or null if it is not possible to encode the image (e.g. not supported
* raster format etc.)
*/
public ImageStream encode() throws IOException {
diff --git a/examples/signatures/src/main/java/org/icepdf/examples/signatures/Pkcs11SignatureCreation.java b/examples/signatures/src/main/java/org/icepdf/examples/signatures/Pkcs11SignatureCreation.java
new file mode 100644
index 000000000..180492c90
--- /dev/null
+++ b/examples/signatures/src/main/java/org/icepdf/examples/signatures/Pkcs11SignatureCreation.java
@@ -0,0 +1,180 @@
+package org.icepdf.examples.signatures;
+
+import org.icepdf.core.pobjects.Document;
+import org.icepdf.core.pobjects.PDate;
+import org.icepdf.core.pobjects.acroform.FieldDictionaryFactory;
+import org.icepdf.core.pobjects.acroform.InteractiveForm;
+import org.icepdf.core.pobjects.acroform.SignatureDictionary;
+import org.icepdf.core.pobjects.acroform.signature.SignatureValidator;
+import org.icepdf.core.pobjects.acroform.signature.appearance.SignatureType;
+import org.icepdf.core.pobjects.acroform.signature.handlers.Pkcs11SignerHandler;
+import org.icepdf.core.pobjects.acroform.signature.handlers.SimplePasswordCallbackHandler;
+import org.icepdf.core.pobjects.acroform.signature.utils.SignatureUtilities;
+import org.icepdf.core.pobjects.annotations.AnnotationFactory;
+import org.icepdf.core.pobjects.annotations.SignatureWidgetAnnotation;
+import org.icepdf.core.util.Library;
+import org.icepdf.core.util.SignatureManager;
+import org.icepdf.core.util.updater.WriteMode;
+import org.icepdf.ri.common.views.annotations.signing.BasicSignatureAppearanceCallback;
+import org.icepdf.ri.common.views.annotations.signing.SignatureAppearanceModelImpl;
+import org.icepdf.ri.util.FontPropertiesManager;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.math.BigInteger;
+import java.nio.file.Path;
+import java.util.Locale;
+
+/**
+ * The Pkcs11SignatureCreation class is an example of how to sign a document with a digital signatures
+ * using PKCS#11 provider. More information on the pkcs11 configuration file can be found here,
+ * https://docs.oracle.com/en/java/javase/11/security/pkcs11-reference-guide1.html
+ *
+ * @since 6.3
+ */
+public class Pkcs11SignatureCreation {
+
+ static {
+ // read/store the font cache.
+ FontPropertiesManager.getInstance().loadOrReadSystemFonts();
+ }
+
+ public static void main(String[] args) {
+ // Get a file from the command line to open
+ String filePath = args[0];
+ String providerConfig = args[1];
+ BigInteger certSerial = convertHexStringToBigInteger(args[2]);
+ String password = args[3];
+ Path path = Path.of(filePath);
+ // start the capture
+ new Pkcs11SignatureCreation().signDocument(path, providerConfig, certSerial, password);
+ }
+
+ private static BigInteger convertHexStringToBigInteger(String hexStr) {
+ hexStr = hexStr.replace(":", "");
+ return new BigInteger(hexStr, 16);
+ }
+
+ public void signDocument(Path filePath, String providerConfig, BigInteger certSerial, String password) {
+ try {
+
+ Pkcs11SignerHandler pkcs11SignerHandler = new Pkcs11SignerHandler(
+ providerConfig,
+ certSerial,
+ new SimplePasswordCallbackHandler(password));
+
+ Document document = new Document();
+ document.setFile(filePath.toFile().getPath());
+ Library library = document.getCatalog().getLibrary();
+ SignatureManager signatureManager = library.getSignatureDictionaries();
+
+ // Creat signature annotation
+ SignatureWidgetAnnotation signatureAnnotation = (SignatureWidgetAnnotation)
+ AnnotationFactory.buildWidgetAnnotation(
+ document.getPageTree().getLibrary(),
+ FieldDictionaryFactory.TYPE_SIGNATURE,
+ new Rectangle(100, 250, 300, 150));
+ document.getPageTree().getPage(0).addAnnotation(signatureAnnotation, true);
+
+ // Add the signatureWidget to catalog
+ InteractiveForm interactiveForm = document.getCatalog().getOrCreateInteractiveForm();
+ interactiveForm.addField(signatureAnnotation);
+
+ // update dictionary
+ SignatureDictionary signatureDictionary = SignatureDictionary.getInstance(signatureAnnotation,
+ SignatureType.SIGNER);
+ signatureDictionary.setSignerHandler(pkcs11SignerHandler);
+ signatureDictionary.setName("Tester McTest");
+ signatureDictionary.setLocation("Springfield");
+ signatureDictionary.setReason("Make sure stuff didn't change");
+ signatureDictionary.setDate("D:20240423082733+02'00'");
+ signatureManager.addSignature(signatureDictionary, signatureAnnotation);
+
+ // assign cert metadata to dictionary
+ SignatureUtilities.updateSignatureDictionary(signatureDictionary, pkcs11SignerHandler.getCertificate());
+
+ // build basic appearance
+ SignatureAppearanceModelImpl signatureAppearanceModel = new SignatureAppearanceModelImpl(library);
+ signatureAppearanceModel.setLocale(Locale.ENGLISH);
+ signatureAppearanceModel.setName(signatureDictionary.getName());
+ signatureAppearanceModel.setContact(signatureDictionary.getContactInfo());
+ signatureAppearanceModel.setLocation(signatureDictionary.getLocation());
+ signatureAppearanceModel.setSignatureType(signatureDictionary.getReason().equals("Approval") ?
+ SignatureType.SIGNER : SignatureType.CERTIFIER);
+ signatureAppearanceModel.setSignatureImageVisible(false);
+
+ BasicSignatureAppearanceCallback signatureAppearance = new BasicSignatureAppearanceCallback();
+ signatureAppearance.setSignatureAppearanceModel(signatureAppearanceModel);
+ signatureAnnotation.setAppearanceCallback(signatureAppearance);
+ signatureAnnotation.resetAppearanceStream(new AffineTransform());
+
+ String absolutePath = filePath.toFile().getPath();
+ String signedFileName = absolutePath.replace(".pdf", "_signed.pdf");
+
+ File out = new File(signedFileName);
+ try (BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(out), 8192)) {
+ document.saveToOutputStream(stream, WriteMode.INCREMENT_UPDATE);
+ }
+
+ // open the signed document
+ Document modifiedDocument = new Document();
+ modifiedDocument.setFile(out.getAbsolutePath());
+
+ } catch (Exception e) {
+ // make sure we have no io errors.
+ e.printStackTrace();
+ }
+ }
+
+ private static void printSignatureSummary(SignatureWidgetAnnotation signatureWidgetAnnotation) {
+ SignatureDictionary signatureDictionary = signatureWidgetAnnotation.getSignatureDictionary();
+ String signingTime = new PDate(signatureWidgetAnnotation.getLibrary().getSecurityManager(),
+ signatureDictionary.getDate()).toString();
+ System.out.println("General Info:");
+ System.out.println(" Signing time: " + signingTime);
+ System.out.println(" Reason: " + signatureDictionary.getReason());
+ System.out.println(" Location: " + signatureDictionary.getLocation());
+ }
+
+ /**
+ * Print out some summary data of the validator results.
+ *
+ * @param signatureValidator validator to show properties data.
+ */
+ private static void printValidationSummary(SignatureValidator signatureValidator) {
+ System.out.println("Singer Info:");
+ if (signatureValidator.isCertificateChainTrusted()) {
+ System.out.println(" Path validation checks were successful");
+ } else {
+ System.out.println(" Path validation checks were unsuccessful");
+ }
+ if (!signatureValidator.isCertificateChainTrusted() || signatureValidator.isRevocation()) {
+ System.out.println(" Revocation checking was not performed");
+ } else {
+ System.out.println(" Signer's certificate is valid and has not been revoked");
+ }
+ System.out.println("Validity Summary:");
+ if (!signatureValidator.isSignedDataModified() && !signatureValidator.isDocumentDataModified()) {
+ System.out.println(" Document has not been modified since it was signed");
+ } else if (!signatureValidator.isSignedDataModified() && signatureValidator.isDocumentDataModified() && signatureValidator.isSignaturesCoverDocumentLength()) {
+ System.out.println(" This version of the document is unaltered but subsequent changes have been made");
+ } else if (!signatureValidator.isSignaturesCoverDocumentLength()) {
+ System.out.println(" Document has been altered or corrupted sing it was singed");
+ }
+ if (!signatureValidator.isCertificateDateValid()) {
+ System.out.println(" Signers certificate has expired");
+ }
+ if (signatureValidator.isEmbeddedTimeStamp()) {
+ System.out.println(" Signature included an embedded timestamp but it could not be validated");
+ } else {
+ System.out.println(" Signing time is from the clock on this signer's computer");
+ }
+ if (signatureValidator.isSelfSigned()) {
+ System.out.println(" Document is self signed");
+ }
+ System.out.println();
+ }
+}
diff --git a/examples/signatures/src/main/java/org/icepdf/examples/signatures/Pkcs12SignatureCreation.java b/examples/signatures/src/main/java/org/icepdf/examples/signatures/Pkcs12SignatureCreation.java
new file mode 100644
index 000000000..03b870320
--- /dev/null
+++ b/examples/signatures/src/main/java/org/icepdf/examples/signatures/Pkcs12SignatureCreation.java
@@ -0,0 +1,172 @@
+package org.icepdf.examples.signatures;
+
+import org.icepdf.core.pobjects.Document;
+import org.icepdf.core.pobjects.PDate;
+import org.icepdf.core.pobjects.acroform.FieldDictionaryFactory;
+import org.icepdf.core.pobjects.acroform.InteractiveForm;
+import org.icepdf.core.pobjects.acroform.SignatureDictionary;
+import org.icepdf.core.pobjects.acroform.signature.SignatureValidator;
+import org.icepdf.core.pobjects.acroform.signature.appearance.SignatureType;
+import org.icepdf.core.pobjects.acroform.signature.handlers.Pkcs12SignerHandler;
+import org.icepdf.core.pobjects.acroform.signature.handlers.SimplePasswordCallbackHandler;
+import org.icepdf.core.pobjects.acroform.signature.utils.SignatureUtilities;
+import org.icepdf.core.pobjects.annotations.AnnotationFactory;
+import org.icepdf.core.pobjects.annotations.SignatureWidgetAnnotation;
+import org.icepdf.core.util.Library;
+import org.icepdf.core.util.SignatureManager;
+import org.icepdf.core.util.updater.WriteMode;
+import org.icepdf.ri.common.views.annotations.signing.BasicSignatureAppearanceCallback;
+import org.icepdf.ri.common.views.annotations.signing.SignatureAppearanceModelImpl;
+import org.icepdf.ri.util.FontPropertiesManager;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.nio.file.Path;
+import java.util.Locale;
+
+/**
+ * The Pkcs12SignatureCreation class is an example of how to sign a document with a digital signatures
+ * using PKCS#12 provider.
+ *
+ * @since 6.3
+ */
+public class Pkcs12SignatureCreation {
+
+ static {
+ // read/store the font cache.
+ FontPropertiesManager.getInstance().loadOrReadSystemFonts();
+ }
+
+ public static void main(String[] args) {
+ // Get a file from the command line to open
+ String filePath = args[0];
+ // path to keystore file, keystore-keypair.pfx
+ String keyStorePath = args[1];
+ // cert alias
+ String alias = args[2];
+ // keystore password
+ String password = args[3];
+ Path path = Path.of(filePath);
+ // start the capture
+ new Pkcs12SignatureCreation().signDocument(path, keyStorePath, alias, password);
+ }
+
+ public void signDocument(Path filePath, String keyStorePath, String certAlias, String password) {
+ try {
+
+ Pkcs12SignerHandler pkcs12SignerHandler = new Pkcs12SignerHandler(
+ new File(keyStorePath),
+ certAlias,
+ new SimplePasswordCallbackHandler(password));
+
+ Document document = new Document();
+ document.setFile(filePath.toFile().getPath());
+ Library library = document.getCatalog().getLibrary();
+ SignatureManager signatureManager = library.getSignatureDictionaries();
+
+ // Creat signature annotation
+ SignatureWidgetAnnotation signatureAnnotation = (SignatureWidgetAnnotation)
+ AnnotationFactory.buildWidgetAnnotation(
+ document.getPageTree().getLibrary(),
+ FieldDictionaryFactory.TYPE_SIGNATURE,
+ new Rectangle(100, 250, 100, 50));
+ document.getPageTree().getPage(0).addAnnotation(signatureAnnotation, true);
+
+ // Add the signatureWidget to catalog
+ InteractiveForm interactiveForm = document.getCatalog().getOrCreateInteractiveForm();
+ interactiveForm.addField(signatureAnnotation);
+
+ // update dictionary
+ SignatureDictionary signatureDictionary = SignatureDictionary.getInstance(signatureAnnotation,
+ SignatureType.SIGNER);
+ signatureDictionary.setSignerHandler(pkcs12SignerHandler);
+ signatureDictionary.setName("Tester McTest");
+ signatureDictionary.setLocation("Springfield");
+ signatureDictionary.setReason("Make sure stuff didn't change");
+ signatureDictionary.setDate("D:20240423082733+02'00'");
+ signatureManager.addSignature(signatureDictionary, signatureAnnotation);
+
+ // assign cert metadata to dictionary
+ SignatureUtilities.updateSignatureDictionary(signatureDictionary, pkcs12SignerHandler.getCertificate());
+
+ // build basic appearance
+ SignatureAppearanceModelImpl signatureAppearanceModel = new SignatureAppearanceModelImpl(library);
+ signatureAppearanceModel.setLocale(Locale.ENGLISH);
+ signatureAppearanceModel.setName(signatureDictionary.getName());
+ signatureAppearanceModel.setContact(signatureDictionary.getContactInfo());
+ signatureAppearanceModel.setLocation(signatureDictionary.getLocation());
+ signatureAppearanceModel.setSignatureType(signatureDictionary.getReason().equals("Approval") ?
+ SignatureType.SIGNER : SignatureType.CERTIFIER);
+ signatureAppearanceModel.setSignatureImageVisible(false);
+
+ BasicSignatureAppearanceCallback signatureAppearance = new BasicSignatureAppearanceCallback();
+ signatureAppearance.setSignatureAppearanceModel(signatureAppearanceModel);
+ signatureAnnotation.setAppearanceCallback(signatureAppearance);
+ signatureAnnotation.resetAppearanceStream(new AffineTransform());
+
+ String absolutePath = filePath.toFile().getPath();
+ String signedFileName = absolutePath.replace(".pdf", "_signed.pdf");
+
+ File out = new File(signedFileName);
+ try (BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(out), 8192)) {
+ document.saveToOutputStream(stream, WriteMode.INCREMENT_UPDATE);
+ }
+ document.dispose();
+ } catch (Exception e) {
+ // make sure we have no io errors.
+ e.printStackTrace();
+ }
+ }
+
+ private static void printSignatureSummary(SignatureWidgetAnnotation signatureWidgetAnnotation) {
+ SignatureDictionary signatureDictionary = signatureWidgetAnnotation.getSignatureDictionary();
+ String signingTime = new PDate(signatureWidgetAnnotation.getLibrary().getSecurityManager(),
+ signatureDictionary.getDate()).toString();
+ System.out.println("General Info:");
+ System.out.println(" Signing time: " + signingTime);
+ System.out.println(" Reason: " + signatureDictionary.getReason());
+ System.out.println(" Location: " + signatureDictionary.getLocation());
+ }
+
+ /**
+ * Print out some summary data of the validator results.
+ *
+ * @param signatureValidator validator to show properties data.
+ */
+ private static void printValidationSummary(SignatureValidator signatureValidator) {
+ System.out.println("Singer Info:");
+ if (signatureValidator.isCertificateChainTrusted()) {
+ System.out.println(" Path validation checks were successful");
+ } else {
+ System.out.println(" Path validation checks were unsuccessful");
+ }
+ if (!signatureValidator.isCertificateChainTrusted() || signatureValidator.isRevocation()) {
+ System.out.println(" Revocation checking was not performed");
+ } else {
+ System.out.println(" Signer's certificate is valid and has not been revoked");
+ }
+ System.out.println("Validity Summary:");
+ if (!signatureValidator.isSignedDataModified() && !signatureValidator.isDocumentDataModified()) {
+ System.out.println(" Document has not been modified since it was signed");
+ } else if (!signatureValidator.isSignedDataModified() && signatureValidator.isDocumentDataModified() && signatureValidator.isSignaturesCoverDocumentLength()) {
+ System.out.println(" This version of the document is unaltered but subsequent changes have been made");
+ } else if (!signatureValidator.isSignaturesCoverDocumentLength()) {
+ System.out.println(" Document has been altered or corrupted sing it was singed");
+ }
+ if (!signatureValidator.isCertificateDateValid()) {
+ System.out.println(" Signers certificate has expired");
+ }
+ if (signatureValidator.isEmbeddedTimeStamp()) {
+ System.out.println(" Signature included an embedded timestamp but it could not be validated");
+ } else {
+ System.out.println(" Signing time is from the clock on this signer's computer");
+ }
+ if (signatureValidator.isSelfSigned()) {
+ System.out.println(" Document is self signed");
+ }
+ System.out.println();
+ }
+}
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/MyAnnotationCallback.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/MyAnnotationCallback.java
index 3d2742d1a..88440b75f 100644
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/MyAnnotationCallback.java
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/MyAnnotationCallback.java
@@ -15,24 +15,27 @@
*/
package org.icepdf.ri.common;
-import org.icepdf.core.pobjects.*;
+import org.icepdf.core.pobjects.Document;
+import org.icepdf.core.pobjects.Name;
+import org.icepdf.core.pobjects.Page;
+import org.icepdf.core.pobjects.PageTree;
+import org.icepdf.core.pobjects.acroform.InteractiveForm;
import org.icepdf.core.pobjects.actions.*;
+import org.icepdf.core.pobjects.annotations.AbstractWidgetAnnotation;
import org.icepdf.core.pobjects.annotations.Annotation;
import org.icepdf.core.pobjects.annotations.LinkAnnotation;
import org.icepdf.core.pobjects.annotations.MarkupAnnotation;
import org.icepdf.ri.common.views.*;
-import org.icepdf.ri.common.views.annotations.AbstractAnnotationComponent;
import org.icepdf.ri.common.views.annotations.MarkupAnnotationComponent;
-import org.icepdf.ri.common.views.annotations.PopupAnnotationComponent;
import org.icepdf.ri.util.BareBonesBrowserLaunch;
import java.io.File;
-import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class represents a basic implementation of the AnnotationCallback
+ *
* @since 2.6
*/
public class MyAnnotationCallback implements AnnotationCallback {
@@ -233,6 +236,12 @@ public void removeAnnotation(PageViewComponent pageComponent,
}
}
}
+ // corner case for acroform widget annotations, todo creat a formal api for clean up.
+ if (annotationComponent.getAnnotation() instanceof AbstractWidgetAnnotation) {
+ InteractiveForm interactiveForm =
+ documentViewController.getDocument().getCatalog().getOrCreateInteractiveForm();
+ interactiveForm.removeField((AbstractWidgetAnnotation) annotationComponent.getAnnotation());
+ }
}
}
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java
index ff6d09970..1167f7a72 100644
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java
@@ -224,6 +224,8 @@ public class SwingController extends ComponentAdapter implements org.icepdf.ri.c
private JButton deleteAllAnnotationsButton;
private AnnotationColorToggleButton highlightAnnotationToolButton;
private JToggleButton redactionAnnotationToolButton;
+
+ private JToggleButton signatureAnnotationToolButton;
private JToggleButton linkAnnotationToolButton;
private AnnotationColorToggleButton strikeOutAnnotationToolButton;
private AnnotationColorToggleButton underlineAnnotationToolButton;
@@ -1284,6 +1286,11 @@ public void setRedactionAnnotationToolButton(JToggleButton btn) {
btn.addItemListener(this);
}
+ public void setSignatureAnnotationToolButton(JToggleButton btn) {
+ signatureAnnotationToolButton = btn;
+ btn.addItemListener(this);
+ }
+
/**
* Called by SwingViewerBuilder, so that Controller can setup event handling
*
@@ -1729,6 +1736,7 @@ protected void reflectStateInComponents() {
setEnabled(deleteAllAnnotationsButton, opened && canModify && !pdfCollection && !IS_READONLY);
setEnabled(highlightAnnotationToolButton, opened && canModify && !pdfCollection && !IS_READONLY);
setEnabled(redactionAnnotationToolButton, opened && canModify && !pdfCollection && !IS_READONLY);
+ setEnabled(signatureAnnotationToolButton, opened && canModify && !pdfCollection && !IS_READONLY);
setEnabled(strikeOutAnnotationToolButton, opened && canModify && !pdfCollection && !IS_READONLY);
setEnabled(underlineAnnotationToolButton, opened && canModify && !pdfCollection && !IS_READONLY);
setEnabled(lineAnnotationToolButton, opened && canModify && !pdfCollection && !IS_READONLY);
@@ -1754,7 +1762,7 @@ protected void reflectStateInComponents() {
setEnabled(freeTextAnnotationPropertiesToolButton, opened && canModify && !pdfCollection && !IS_READONLY);
setEnabled(annotationPrivacyComboBox, opened && !pdfCollection && !IS_READONLY);
setEnabled(textAnnotationPropertiesToolButton, opened && canModify && !pdfCollection && !IS_READONLY);
- setEnabled(formHighlightButton, opened && !pdfCollection && hasForms());
+ setEnabled(formHighlightButton, opened && !pdfCollection);
setEnabled(quickSearchToolBar, opened && !pdfCollection);
setEnabled(facingPageViewContinuousButton, opened && !pdfCollection);
setEnabled(singlePageViewContinuousButton, opened && !pdfCollection);
@@ -2006,6 +2014,11 @@ public void setDisplayTool(final int argToolName) {
documentViewController.setToolMode(DocumentViewModelImpl.DISPLAY_TOOL_LINK_ANNOTATION);
documentViewController.setViewCursor(DocumentViewController.CURSOR_CROSSHAIR);
setCursorOnComponents(DocumentViewController.CURSOR_DEFAULT);
+ } else if (argToolName == DocumentViewModelImpl.DISPLAY_TOOL_SIGNATURE_ANNOTATION) {
+ actualToolMayHaveChanged =
+ documentViewController.setToolMode(DocumentViewModelImpl.DISPLAY_TOOL_SIGNATURE_ANNOTATION);
+ documentViewController.setViewCursor(DocumentViewController.CURSOR_CROSSHAIR);
+ setCursorOnComponents(DocumentViewController.CURSOR_DEFAULT);
} else if (argToolName == DocumentViewModelImpl.DISPLAY_TOOL_REDACTION_ANNOTATION) {
actualToolMayHaveChanged =
documentViewController.setToolMode(DocumentViewModelImpl.DISPLAY_TOOL_REDACTION_ANNOTATION);
@@ -2109,7 +2122,7 @@ private void setCursorOnComponents(final int cursorType) {
}
/**
- * Sets the state of the "Tools" buttons. This ensure that correct button
+ * Sets the state of the "Tools" buttons. This ensures that correct button
* is depressed when the state of the Document class specifies it.
*/
private void reflectToolInToolButtons() {
@@ -2123,6 +2136,8 @@ private void reflectToolInToolButtons() {
documentViewController.isToolModeSelected(DocumentViewModelImpl.DISPLAY_TOOL_HIGHLIGHT_ANNOTATION));
reflectSelectionInButton(redactionAnnotationToolButton,
documentViewController.isToolModeSelected(DocumentViewModelImpl.DISPLAY_TOOL_REDACTION_ANNOTATION));
+ reflectSelectionInButton(signatureAnnotationToolButton,
+ documentViewController.isToolModeSelected(DocumentViewModelImpl.DISPLAY_TOOL_SIGNATURE_ANNOTATION));
reflectSelectionInButton(underlineAnnotationToolButton,
documentViewController.isToolModeSelected(DocumentViewModelImpl.DISPLAY_TOOL_UNDERLINE_ANNOTATION));
reflectSelectionInButton(strikeOutAnnotationToolButton,
@@ -2131,6 +2146,8 @@ private void reflectToolInToolButtons() {
documentViewController.isToolModeSelected(DocumentViewModelImpl.DISPLAY_TOOL_LINE_ANNOTATION));
reflectSelectionInButton(linkAnnotationToolButton,
documentViewController.isToolModeSelected(DocumentViewModelImpl.DISPLAY_TOOL_LINK_ANNOTATION));
+ reflectSelectionInButton(signatureAnnotationToolButton,
+ documentViewController.isToolModeSelected(DocumentViewModelImpl.DISPLAY_TOOL_SIGNATURE_ANNOTATION));
reflectSelectionInButton(lineArrowAnnotationToolButton,
documentViewController.isToolModeSelected(DocumentViewModelImpl.DISPLAY_TOOL_LINE_ARROW_ANNOTATION));
reflectSelectionInButton(squareAnnotationToolButton,
@@ -3237,6 +3254,7 @@ public void dispose() {
selectToolButton = null;
highlightAnnotationToolButton = null;
redactionAnnotationToolButton = null;
+ signatureAnnotationToolButton = null;
strikeOutAnnotationToolButton = null;
underlineAnnotationToolButton = null;
lineAnnotationToolButton = null;
@@ -3516,25 +3534,7 @@ protected void saveFileChecks(SaveMode saveMode, String originalFileName, File f
// but that could cause problems with slow network links too,
// and would complicate the incremental update code, so we're
// harmonising on this approach.
- try (final FileOutputStream fileOutputStream = new FileOutputStream(file);
- final BufferedOutputStream buf = new BufferedOutputStream(fileOutputStream, 8192)) {
-
- // We want 'save as' or 'save a copy to always occur
- if (saveMode == SaveMode.EXPORT) {
- // save as copy
- document.writeToOutputStream(buf, WriteMode.FULL_UPDATE);
- } else {
- // save as will append changes.
- document.saveToOutputStream(buf);
- }
- document.getStateManager().setChangesSnapshot();
- } catch (MalformedURLException e) {
- logger.log(Level.WARNING, "Malformed URL Exception ", e);
- } catch (IOException e) {
- logger.log(Level.WARNING, "IO Exception ", e);
- } catch (Exception e) {
- logger.log(Level.WARNING, "Failed to append document changes", e);
- }
+ writeDocument(saveMode, file);
// save the default directory
ViewModel.setDefaultFile(file);
}
@@ -3551,6 +3551,26 @@ protected void saveFileChecks(SaveMode saveMode, String originalFileName, File f
}
}
+ private void writeDocument(SaveMode saveMode, File file) {
+ try (final FileOutputStream fileOutputStream = new FileOutputStream(file);
+ final BufferedOutputStream buf = new BufferedOutputStream(fileOutputStream, 8192)) {
+ if (saveMode == SaveMode.EXPORT) {
+ // save as copy
+ document.writeToOutputStream(buf, WriteMode.FULL_UPDATE);
+ } else {
+ // save as will append changes.
+ document.writeToOutputStream(buf, WriteMode.INCREMENT_UPDATE);
+ }
+ document.getStateManager().setChangesSnapshot();
+ } catch (MalformedURLException e) {
+ logger.log(Level.WARNING, "Malformed URL Exception ", e);
+ } catch (IOException e) {
+ logger.log(Level.WARNING, "IO Exception ", e);
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "Failed to append document changes", e);
+ }
+ }
+
/**
* Generates a file name based on the original file name but appends "-new".
* If new file extension exists a ".pdf" is automatically added.
@@ -5040,6 +5060,11 @@ else if (source == selectToolButton) {
tool = DocumentViewModelImpl.DISPLAY_TOOL_REDACTION_ANNOTATION;
setDocumentToolMode(DocumentViewModelImpl.DISPLAY_TOOL_REDACTION_ANNOTATION);
}
+ } else if (source == signatureAnnotationToolButton) {
+ if (e.getStateChange() == ItemEvent.SELECTED) {
+ tool = DocumentViewModelImpl.DISPLAY_TOOL_SIGNATURE_ANNOTATION;
+ setDocumentToolMode(DocumentViewModelImpl.DISPLAY_TOOL_SIGNATURE_ANNOTATION);
+ }
} else if (checkAnnotationButton(source, strikeOutAnnotationToolButton,
strikeOutAnnotationPropertiesToolButton)) {
if (e.getStateChange() == ItemEvent.SELECTED) {
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingViewBuilder.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingViewBuilder.java
index a42fade0c..20866618e 100644
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingViewBuilder.java
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingViewBuilder.java
@@ -1667,6 +1667,10 @@ public JToolBar buildAnnotationToolBar() {
ViewerPropertiesManager.PROPERTY_SHOW_TOOLBAR_ANNOTATION_REDACTION)) {
addToToolBar(toolbar, buildRedactionAnnotationToolButton(iconSize));
}
+ if (propertiesManager.checkAndStoreBooleanProperty(
+ ViewerPropertiesManager.PROPERTY_SHOW_TOOLBAR_ANNOTATION_SIGNATURE)) {
+ addToToolBar(toolbar, buildSignatureAnnotationToolButton(iconSize));
+ }
if (SystemProperties.PRIVATE_PROPERTY_ENABLED && propertiesManager.checkAndStoreBooleanProperty(
ViewerPropertiesManager.PROPERTY_SHOW_TOOLBAR_ANNOTATION_PERMISSION)) {
addToToolBar(toolbar, buildAnnotationPermissionCombBox());
@@ -1830,6 +1834,16 @@ public JToggleButton buildRedactionAnnotationToolButton(final Images.IconSize im
return btn;
}
+ public JToggleButton buildSignatureAnnotationToolButton(final Images.IconSize imageSize) {
+ JToggleButton btn = makeToolbarToggleButton(
+ messageBundle.getString("viewer.toolbar.tool.signature.label"),
+ messageBundle.getString("viewer.toolbar.tool.signature.tooltip"),
+ "signature_annot", imageSize, buttonFont);
+ if (viewerController != null && btn != null)
+ viewerController.setSignatureAnnotationToolButton(btn);
+ return btn;
+ }
+
public AbstractButton buildStrikeOutAnnotationToolButton(final Images.IconSize imageSize) {
AnnotationColorToggleButton btn = makeAnnotationToggleButton(
messageBundle.getString("viewer.toolbar.tool.strikeOut.label"),
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/preferences/PreferencesDialog.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/preferences/PreferencesDialog.java
index 06a6a31e0..45c74f48f 100644
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/preferences/PreferencesDialog.java
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/preferences/PreferencesDialog.java
@@ -68,7 +68,7 @@ public PreferencesDialog(Frame frame, SwingController controller,
constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.BOTH;
constraints.weightx = 1.0;
- constraints.weighty = 0;
+ constraints.weighty = 1.0;
constraints.insets = new Insets(5, 5, 5, 5);
constraints.anchor = GridBagConstraints.NORTH;
addGB(layoutPanel, propertiesTabbedPane, 0, 0, 1, 1);
@@ -101,6 +101,13 @@ protected ImagingPreferencesPanel createImagingPreferencesPanel(SwingController
}
+ protected SigningPreferencesPanel createSigningPreferencesPanel(SwingController controller,
+ ViewerPropertiesManager propertiesManager,
+ ResourceBundle messageBundle) {
+ return new SigningPreferencesPanel(controller, propertiesManager, messageBundle);
+
+ }
+
protected FontsPreferencesPanel createFontsPreferencesPanel(SwingController controller,
ViewerPropertiesManager propertiesManager,
ResourceBundle messageBundle) {
@@ -147,6 +154,13 @@ protected JTabbedPane createTabbedPane(SwingController controller, ResourceBundl
messageBundle.getString("viewer.dialog.viewerPreferences.section.imaging.title"),
createImagingPreferencesPanel(controller, propertiesManager, messageBundle));
}
+ // build the signing preferences tab
+ if (propertiesManager.checkAndStoreBooleanProperty(
+ ViewerPropertiesManager.PROPERTY_SHOW_PREFERENCES_SIGNING)) {
+ propertiesTabbedPane.addTab(
+ messageBundle.getString("viewer.dialog.viewerPreferences.section.signatures.title"),
+ createSigningPreferencesPanel(controller, propertiesManager, messageBundle));
+ }
// build the fonts preferences tab
if (propertiesManager.checkAndStoreBooleanProperty(
ViewerPropertiesManager.PROPERTY_SHOW_PREFERENCES_FONTS)) {
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/preferences/SigningPreferencesPanel.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/preferences/SigningPreferencesPanel.java
new file mode 100644
index 000000000..1c617fdcb
--- /dev/null
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/preferences/SigningPreferencesPanel.java
@@ -0,0 +1,201 @@
+package org.icepdf.ri.common.preferences;
+
+import org.icepdf.ri.common.SwingController;
+import org.icepdf.ri.util.ViewerPropertiesManager;
+
+import javax.swing.*;
+import javax.swing.border.EtchedBorder;
+import javax.swing.border.TitledBorder;
+import java.awt.*;
+import java.util.ResourceBundle;
+import java.util.prefs.Preferences;
+
+/**
+ * Contains singing setting for the viewer reference implementation. Allows users to pick the default signing handler.
+ *
+ * - Pkcs11SigningHandlerEnables smart card Pkcs11 keystore capabilities
+ * - Pkcs12SigningHandlerEnables standard Pkcs12 keystore capabilities
+ *
+ *
+ * @since 7.3
+ */
+public class SigningPreferencesPanel extends JPanel {
+
+ public static final String PKCS_11_TYPE = "PKCS#11";
+ public static final String PKCS_12_TYPE = "PKCS#12";
+
+ private static final short PKCS11 = 0;
+ private static final short PKCS12 = 1;
+
+ // layouts constraint
+ private final GridBagConstraints constraints;
+ private final JComboBox keystoreTypeComboBox;
+ private final JLabel pkcsPathLabel;
+ private final JTextField pkcsPathTextField;
+
+ private final ResourceBundle messageBundle;
+ private final Preferences preferences;
+
+ public SigningPreferencesPanel(SwingController controller, ViewerPropertiesManager propertiesManager,
+ ResourceBundle messageBundle) {
+
+ super(new GridBagLayout());
+ setAlignmentY(JPanel.TOP_ALIGNMENT);
+
+ preferences = propertiesManager.getPreferences();
+ this.messageBundle = messageBundle;
+
+ KeystoreTypeItem[] pkcsTypeItems =
+ new KeystoreTypeItem[]{
+ new KeystoreTypeItem(messageBundle.getString(
+ "viewer.dialog.viewerPreferences.section.signatures.pkcs.11.label"),
+ PKCS_11_TYPE),
+ new KeystoreTypeItem(messageBundle.getString(
+ "viewer.dialog.viewerPreferences.section.signatures.pkcs.12.label"),
+ PKCS_12_TYPE),
+ };
+
+ keystoreTypeComboBox = new JComboBox<>(pkcsTypeItems);
+ keystoreTypeComboBox.setSelectedItem(new KeystoreTypeItem("",
+ preferences.get(ViewerPropertiesManager.PROPERTY_PKCS_KEYSTORE_TYPE, pkcsTypeItems[PKCS12].value)));
+
+ // setup default state
+ pkcsPathLabel = new JLabel();
+ pkcsPathTextField = new JTextField();
+ updatePkcsPaths();
+ JButton pkcsPathBrowseButton = new JButton(messageBundle.getString(
+ "viewer.dialog.viewerPreferences.section.signatures.pkcs.keystore.path.browse.label"));
+ pkcsPathBrowseButton.addActionListener(e -> showBrowseDialog());
+
+ pkcsPathTextField.addActionListener(e -> savePkcsPaths(keystoreTypeComboBox));
+
+ keystoreTypeComboBox.addActionListener(e -> {
+ updatePkcsPaths();
+ });
+
+ JPanel imagingPreferencesPanel = new JPanel(new GridBagLayout());
+ imagingPreferencesPanel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+ imagingPreferencesPanel.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.LOWERED),
+ messageBundle.getString("viewer.dialog.viewerPreferences.section.signatures.pkcs.border.label"),
+ TitledBorder.LEFT,
+ TitledBorder.DEFAULT_POSITION));
+
+ constraints = new GridBagConstraints();
+ constraints.fill = GridBagConstraints.NONE;
+ constraints.weightx = 1;
+ constraints.weighty = 0;
+ constraints.anchor = GridBagConstraints.WEST;
+ constraints.insets = new Insets(5, 5, 5, 5);
+
+ addGB(imagingPreferencesPanel, new JLabel(messageBundle.getString(
+ "viewer.dialog.viewerPreferences.section.signatures.pkcs.label")),
+ 0, 0, 1, 1);
+
+ constraints.anchor = GridBagConstraints.EAST;
+ addGB(imagingPreferencesPanel, keystoreTypeComboBox, 1, 0, 2, 1);
+
+ constraints.anchor = GridBagConstraints.WEST;
+ addGB(imagingPreferencesPanel, pkcsPathLabel, 0, 1, 1, 1);
+ constraints.anchor = GridBagConstraints.EAST;
+ constraints.weightx = 1.0;
+ constraints.fill = GridBagConstraints.BOTH;
+ addGB(imagingPreferencesPanel, pkcsPathTextField, 1, 1, 1, 1);
+ addGB(imagingPreferencesPanel, pkcsPathBrowseButton, 2, 1, 1, 1);
+
+ constraints.anchor = GridBagConstraints.NORTHWEST;
+ constraints.fill = GridBagConstraints.BOTH;
+ addGB(this, imagingPreferencesPanel, 0, 0, 1, 1);
+ // little spacer
+ constraints.weighty = 1.0;
+ addGB(this, new Label(" "), 0, 1, 1, 1);
+ }
+
+ private void updatePkcsPaths() {
+ // update which config/keystore input to show.
+ if (keystoreTypeComboBox.getSelectedIndex() == PKCS11) {
+ pkcsPathLabel.setText(messageBundle.getString(
+ "viewer.dialog.viewerPreferences.section.signatures.pkcs.11.config.path.label"));
+ pkcsPathTextField.setText(preferences.get(ViewerPropertiesManager.PROPERTY_PKCS11_PROVIDER_CONFIG_PATH,
+ ""));
+ } else if (keystoreTypeComboBox.getSelectedIndex() == PKCS12) {
+ pkcsPathLabel.setText(messageBundle.getString(
+ "viewer.dialog.viewerPreferences.section.signatures.pkcs.12.keystore.path.label"));
+ pkcsPathTextField.setText(preferences.get(ViewerPropertiesManager.PROPERTY_PKCS12_PROVIDER_KEYSTORE_PATH,
+ ""));
+ }
+ KeystoreTypeItem selectedItem = (KeystoreTypeItem) keystoreTypeComboBox.getSelectedItem();
+ if (selectedItem != null) {
+ preferences.put(ViewerPropertiesManager.PROPERTY_PKCS_KEYSTORE_TYPE, selectedItem.getValue());
+ }
+ }
+
+ private void savePkcsPaths(JComboBox cb) {
+ // update which config/keystore input to show.
+ if (cb.getSelectedIndex() == PKCS11) {
+ preferences.put(ViewerPropertiesManager.PROPERTY_PKCS11_PROVIDER_CONFIG_PATH, pkcsPathTextField.getText());
+ } else if (cb.getSelectedIndex() == PKCS12) {
+ preferences.put(ViewerPropertiesManager.PROPERTY_PKCS12_PROVIDER_KEYSTORE_PATH,
+ pkcsPathTextField.getText());
+ }
+ }
+
+ private void showBrowseDialog() {
+ String pkcsPath = pkcsPathTextField.getText();
+ JFileChooser fileChooser = new JFileChooser(pkcsPath);
+ fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+ fileChooser.setMultiSelectionEnabled(false);
+ fileChooser.setDialogTitle(messageBundle.getString(
+ "viewer.dialog.viewerPreferences.section.signatures.pkcs.keystore.path.selection.title"));
+ final int responseValue = fileChooser.showDialog(this, messageBundle.getString(
+ "viewer.dialog.viewerPreferences.section.signatures.pkcs.keystore.path.accept.label"));
+ if (responseValue == JFileChooser.APPROVE_OPTION) {
+ pkcsPathTextField.setText(fileChooser.getSelectedFile().getAbsolutePath());
+ savePkcsPaths(keystoreTypeComboBox);
+ updatePkcsPaths();
+ }
+
+ }
+
+ private void addGB(JPanel layout, Component component,
+ int x, int y,
+ int rowSpan, int colSpan) {
+ constraints.gridx = x;
+ constraints.gridy = y;
+ constraints.gridwidth = rowSpan;
+ constraints.gridheight = colSpan;
+ layout.add(component, constraints);
+ }
+
+ static class KeystoreTypeItem {
+ final String label;
+ final String value;
+
+ public KeystoreTypeItem(String label, String value) {
+ this.label = label;
+ this.value = value;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return label;
+ }
+
+ @Override
+ public boolean equals(Object keystoreTypeItem) {
+ if (keystoreTypeItem instanceof KeystoreTypeItem) {
+ return value.equals(((KeystoreTypeItem) keystoreTypeItem).getValue());
+ } else {
+ return value.equals(keystoreTypeItem);
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/tools/SignatureAnnotationHandler.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/tools/SignatureAnnotationHandler.java
new file mode 100644
index 000000000..0a9d801c2
--- /dev/null
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/tools/SignatureAnnotationHandler.java
@@ -0,0 +1,132 @@
+package org.icepdf.ri.common.tools;
+
+import org.icepdf.core.pobjects.acroform.FieldDictionaryFactory;
+import org.icepdf.core.pobjects.acroform.InteractiveForm;
+import org.icepdf.core.pobjects.annotations.AnnotationFactory;
+import org.icepdf.core.pobjects.annotations.SignatureWidgetAnnotation;
+import org.icepdf.ri.common.ViewModel;
+import org.icepdf.ri.common.views.AbstractPageViewComponent;
+import org.icepdf.ri.common.views.DocumentViewController;
+import org.icepdf.ri.common.views.annotations.AbstractAnnotationComponent;
+import org.icepdf.ri.common.views.annotations.AnnotationComponentFactory;
+import org.icepdf.ri.util.ViewerPropertiesManager;
+
+import javax.swing.event.MouseInputListener;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.util.logging.Logger;
+
+/**
+ * Creates a placeholder for a digital signature. Placeholder can be used to add signatures fields to a document,
+ * signers can then add there certificate to the signature on a user by user basis.
+ */
+public class SignatureAnnotationHandler extends SelectionBoxHandler
+ implements ToolHandler, MouseInputListener {
+
+ private static final Logger logger = Logger.getLogger(SignatureAnnotationHandler.class.toString());
+
+ public SignatureAnnotationHandler(DocumentViewController documentViewController,
+ AbstractPageViewComponent pageViewComponent) {
+ super(documentViewController, pageViewComponent);
+ selectionBoxColour = Color.GRAY;
+ }
+
+ public void mouseClicked(MouseEvent e) {
+ if (pageViewComponent != null) {
+ pageViewComponent.requestFocus();
+ }
+ }
+
+ public void mousePressed(MouseEvent e) {
+ // annotation selection box.
+ int x = e.getX();
+ int y = e.getY();
+ currentRect = new Rectangle(x, y, 0, 0);
+ updateDrawableRect(pageViewComponent.getWidth(),
+ pageViewComponent.getHeight());
+ pageViewComponent.repaint();
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ updateSelectionSize(e.getX(), e.getY(), pageViewComponent);
+
+ // check the bounds on rectToDraw to try and avoid creating
+ // an annotation that is very small.
+ if (rectToDraw.getWidth() < 15 || rectToDraw.getHeight() < 15) {
+ rectToDraw.setSize(new Dimension(15, 15));
+ }
+
+ Rectangle tBbox = convertToPageSpace(rectToDraw).getBounds();
+
+ // create annotations types that are rectangle based;
+ // which is actually just link annotations
+ SignatureWidgetAnnotation annotation = (SignatureWidgetAnnotation) AnnotationFactory.buildWidgetAnnotation(
+ documentViewController.getDocument().getPageTree().getLibrary(),
+ FieldDictionaryFactory.TYPE_SIGNATURE,
+ tBbox);
+ // setup widget highlighting
+ ViewModel viewModel = documentViewController.getParentController().getViewModel();
+ annotation.setEnableHighlightedWidget(viewModel.isWidgetAnnotationHighlight());
+
+ // Add the signatureWidget to catalog
+ InteractiveForm interactiveForm =
+ documentViewController.getDocument().getCatalog().getOrCreateInteractiveForm();
+ interactiveForm.addField(annotation);
+
+ // create the annotation object.
+ AbstractAnnotationComponent comp =
+ AnnotationComponentFactory.buildAnnotationComponent(
+ annotation, documentViewController, pageViewComponent);
+ comp.setBounds(rectToDraw);
+ comp.refreshAnnotationRect();
+
+ // add them to the container, using absolute positioning.
+ documentViewController.addNewAnnotation(comp);
+
+ // set the annotation tool to the given tool
+ documentViewController.getParentController().setDocumentToolMode(
+ preferences.getInt(ViewerPropertiesManager.PROPERTY_ANNOTATION_SIGNATURE_SELECTION_TYPE, 0));
+
+ // clear the rectangle
+ clearRectangle(pageViewComponent);
+
+ }
+
+ protected void checkAndApplyPreferences() {
+
+ }
+
+ public void mouseEntered(MouseEvent e) {
+
+ }
+
+ public void mouseExited(MouseEvent e) {
+
+ }
+
+ public void mouseDragged(MouseEvent e) {
+ updateSelectionSize(e.getX(), e.getY(), pageViewComponent);
+ }
+
+ public void mouseMoved(MouseEvent e) {
+
+ }
+
+ public void installTool() {
+
+ }
+
+ public void uninstallTool() {
+
+ }
+
+ @Override
+ public void setSelectionRectangle(Point cursorLocation, Rectangle selection) {
+
+ }
+
+ public void paintTool(Graphics g) {
+ paintSelectionBox(g, rectToDraw);
+ }
+
+}
\ No newline at end of file
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/annotation/properties/FontWidgetUtilities.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/annotation/properties/FontWidgetUtilities.java
new file mode 100644
index 000000000..38e4a6a38
--- /dev/null
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/annotation/properties/FontWidgetUtilities.java
@@ -0,0 +1,51 @@
+package org.icepdf.ri.common.utility.annotation.properties;
+
+import java.util.ResourceBundle;
+
+public class FontWidgetUtilities {
+ public static ValueLabelItem[] generateFontNameList(ResourceBundle messageBundle) {
+ return new ValueLabelItem[]{
+ new ValueLabelItem("Helvetica",
+ messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.helvetica")),
+ new ValueLabelItem("Helvetica-Oblique",
+ messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.helveticaOblique")),
+ new ValueLabelItem("Helvetica-Bold",
+ messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.helveticaBold")),
+ new ValueLabelItem("Helvetica-BoldOblique",
+ messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name" +
+ ".HelveticaBoldOblique")),
+ new ValueLabelItem("Times-Italic",
+ messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.timesItalic")),
+ new ValueLabelItem("Times-Bold",
+ messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.timesBold")),
+ new ValueLabelItem("Times-BoldItalic",
+ messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.timesBoldItalic")),
+ new ValueLabelItem("Times-Roman",
+ messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.timesRoman")),
+ new ValueLabelItem("Courier",
+ messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.courier")),
+ new ValueLabelItem("Courier-Oblique",
+ messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.courierOblique")),
+ new ValueLabelItem("Courier-BoldOblique",
+ messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.courierBoldOblique")),
+ new ValueLabelItem("Courier-Bold",
+ messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.courierBold"))};
+ }
+
+ public static ValueLabelItem[] generateFontSizeNameList(ResourceBundle messageBundle) {
+ return new ValueLabelItem[]{
+ new ValueLabelItem(6, messageBundle.getString("viewer.common.number.six")),
+ new ValueLabelItem(8, messageBundle.getString("viewer.common.number.eight")),
+ new ValueLabelItem(9, messageBundle.getString("viewer.common.number.nine")),
+ new ValueLabelItem(10, messageBundle.getString("viewer.common.number.ten")),
+ new ValueLabelItem(11, messageBundle.getString("viewer.common.number.eleven")),
+ new ValueLabelItem(12, messageBundle.getString("viewer.common.number.twelve")),
+ new ValueLabelItem(14, messageBundle.getString("viewer.common.number.fourteen")),
+ new ValueLabelItem(16, messageBundle.getString("viewer.common.number.sixteen")),
+ new ValueLabelItem(18, messageBundle.getString("viewer.common.number.eighteen")),
+ new ValueLabelItem(20, messageBundle.getString("viewer.common.number.twenty")),
+ new ValueLabelItem(24, messageBundle.getString("viewer.common.number.twentyFour")),
+ new ValueLabelItem(36, messageBundle.getString("viewer.common.number.thirtySix")),
+ new ValueLabelItem(48, messageBundle.getString("viewer.common.number.fortyEight"))};
+ }
+}
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/annotation/properties/FreeTextAnnotationPanel.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/annotation/properties/FreeTextAnnotationPanel.java
index 6843f3d8c..fc3503ed6 100644
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/annotation/properties/FreeTextAnnotationPanel.java
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/annotation/properties/FreeTextAnnotationPanel.java
@@ -33,7 +33,6 @@
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
-import java.util.ResourceBundle;
/**
* FreeTextAnnotationPanel is a configuration panel for changing the properties
@@ -262,62 +261,17 @@ public void stateChanged(ChangeEvent e) {
alphaSliderChange(e, freeTextAnnotation, ViewerPropertiesManager.PROPERTY_ANNOTATION_FREE_TEXT_OPACITY);
}
- public static ValueLabelItem[] generateFontNameList(ResourceBundle messageBundle) {
- return new ValueLabelItem[]{
- new ValueLabelItem("Helvetica",
- messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.helvetica")),
- new ValueLabelItem("Helvetica-Oblique",
- messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.helveticaOblique")),
- new ValueLabelItem("Helvetica-Bold",
- messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.helveticaBold")),
- new ValueLabelItem("Helvetica-BoldOblique",
- messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.HelveticaBoldOblique")),
- new ValueLabelItem("Times-Italic",
- messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.timesItalic")),
- new ValueLabelItem("Times-Bold",
- messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.timesBold")),
- new ValueLabelItem("Times-BoldItalic",
- messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.timesBoldItalic")),
- new ValueLabelItem("Times-Roman",
- messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.timesRoman")),
- new ValueLabelItem("Courier",
- messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.courier")),
- new ValueLabelItem("Courier-Oblique",
- messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.courierOblique")),
- new ValueLabelItem("Courier-BoldOblique",
- messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.courierBoldOblique")),
- new ValueLabelItem("Courier-Bold",
- messageBundle.getString("viewer.utilityPane.annotation.freeText.font.name.courierBold"))};
- }
-
- public static ValueLabelItem[] generateFontSizeNameList(ResourceBundle messageBundle) {
- return new ValueLabelItem[]{
- new ValueLabelItem(6, messageBundle.getString("viewer.common.number.six")),
- new ValueLabelItem(8, messageBundle.getString("viewer.common.number.eight")),
- new ValueLabelItem(9, messageBundle.getString("viewer.common.number.nine")),
- new ValueLabelItem(10, messageBundle.getString("viewer.common.number.ten")),
- new ValueLabelItem(11, messageBundle.getString("viewer.common.number.eleven")),
- new ValueLabelItem(12, messageBundle.getString("viewer.common.number.twelve")),
- new ValueLabelItem(14, messageBundle.getString("viewer.common.number.fourteen")),
- new ValueLabelItem(16, messageBundle.getString("viewer.common.number.sixteen")),
- new ValueLabelItem(18, messageBundle.getString("viewer.common.number.eighteen")),
- new ValueLabelItem(20, messageBundle.getString("viewer.common.number.twenty")),
- new ValueLabelItem(24, messageBundle.getString("viewer.common.number.twentyFour")),
- new ValueLabelItem(36, messageBundle.getString("viewer.common.number.thirtySix")),
- new ValueLabelItem(48, messageBundle.getString("viewer.common.number.fortyEight"))};
- }
-
private void createGUI() {
// font styles - core java font names and respective labels. All Java JRE should have these fonts, these
// fonts also have huge number of glyphs support many different languages.
if (FONT_NAMES_LIST == null) {
- FONT_NAMES_LIST = generateFontNameList(messageBundle);
+ FONT_NAMES_LIST = FontWidgetUtilities.generateFontNameList(messageBundle);
}
// Font size.
if (FONT_SIZES_LIST == null) {
- FONT_SIZES_LIST = generateFontSizeNameList(messageBundle);
+ FONT_SIZES_LIST = FontWidgetUtilities.generateFontSizeNameList(messageBundle);
}
// Create and setup an Appearance panel
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/signatures/SignatureTreeNode.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/signatures/SignatureTreeNode.java
index b9967bb58..d28865c2f 100644
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/signatures/SignatureTreeNode.java
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/signatures/SignatureTreeNode.java
@@ -22,6 +22,7 @@
import org.icepdf.core.pobjects.acroform.SignatureFieldDictionary;
import org.icepdf.core.pobjects.acroform.signature.SignatureValidator;
import org.icepdf.core.pobjects.acroform.signature.exceptions.SignatureIntegrityException;
+import org.icepdf.core.pobjects.acroform.signature.utils.SignatureUtilities;
import org.icepdf.core.pobjects.annotations.SignatureWidgetAnnotation;
import org.icepdf.ri.images.IconPack;
import org.icepdf.ri.images.Images;
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/signatures/SignatureUtilities.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/signatures/SignatureUtilities.java
deleted file mode 100644
index 1285528b1..000000000
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/signatures/SignatureUtilities.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2006-2019 ICEsoft Technologies Canada Corp.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the
- * License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an "AS
- * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language
- * governing permissions and limitations under the License.
- */
-package org.icepdf.ri.common.utility.signatures;
-
-import org.bouncycastle.asn1.ASN1ObjectIdentifier;
-import org.bouncycastle.asn1.x500.RDN;
-import org.bouncycastle.asn1.x500.X500Name;
-
-/**
- * Utility of commonly used signature related algorithms.
- */
-public class SignatureUtilities {
-
- /**
- * Parse out a known data element from an X500Name.
- *
- * @param rdName name to parse value from.
- * @param commonCode BCStyle name .
- * @return BCStyle name value, null if the BCStyle name was not found.
- */
- public static String parseRelativeDistinguishedName(X500Name rdName, ASN1ObjectIdentifier commonCode) {
- RDN[] rdns = rdName.getRDNs(commonCode);
- if (rdns != null && rdns.length > 0 && rdns[0].getFirst() != null) {
- return rdns[0].getFirst().getValue().toString();
- }
- return null;
- }
-}
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/signatures/VerifyAllSignaturesTask.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/signatures/VerifyAllSignaturesTask.java
index d2e78b11a..d0df14894 100644
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/signatures/VerifyAllSignaturesTask.java
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/signatures/VerifyAllSignaturesTask.java
@@ -64,6 +64,7 @@ protected Void doInBackground() {
taskStatusMessage = messageFormat.format(new Object[]{i + 1, signatures.size()});
SignatureWidgetAnnotation signatureWidgetAnnotation = signatures.get(i);
+ signatureWidgetAnnotation.init();
SignatureDictionary signatureDictionary = signatureWidgetAnnotation.getSignatureDictionary();
if (signatureDictionary.getEntries().size() > 0) {
try {
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewController.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewController.java
index 9eba22d37..101210c06 100644
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewController.java
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewController.java
@@ -18,13 +18,12 @@
import org.icepdf.core.SecurityCallback;
import org.icepdf.core.pobjects.Destination;
import org.icepdf.core.pobjects.Document;
-import org.icepdf.ri.common.views.annotations.AbstractAnnotationComponent;
+import org.icepdf.core.pobjects.acroform.signature.appearance.SignatureAppearanceCallback;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyListener;
import java.util.Collection;
-import java.util.Set;
/**
@@ -186,6 +185,8 @@ public interface DocumentViewController {
void setAnnotationCallback(AnnotationCallback annotationCallback);
+ void setSignatureAppearanceCallback(SignatureAppearanceCallback signatureAppearanceCallback);
+
void setSecurityCallback(SecurityCallback securityCallback);
void addNewAnnotation(AnnotationComponent annotationComponent);
@@ -210,6 +211,8 @@ public interface DocumentViewController {
AnnotationCallback getAnnotationCallback();
+ SignatureAppearanceCallback getSignatureAppearanceCallback();
+
SecurityCallback getSecurityCallback();
DocumentViewModel getDocumentViewModel();
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewControllerImpl.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewControllerImpl.java
index 40ff86652..fa3a3f742 100644
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewControllerImpl.java
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewControllerImpl.java
@@ -19,6 +19,7 @@
import org.icepdf.core.Memento;
import org.icepdf.core.SecurityCallback;
import org.icepdf.core.pobjects.*;
+import org.icepdf.core.pobjects.acroform.signature.appearance.SignatureAppearanceCallback;
import org.icepdf.core.search.DocumentSearchController;
import org.icepdf.core.util.Library;
import org.icepdf.core.util.PropertyConstants;
@@ -116,6 +117,7 @@ public class DocumentViewControllerImpl
protected final SwingController viewerController;
protected AnnotationCallback annotationCallback;
+ protected SignatureAppearanceCallback signatureAppearanceCallback;
protected SecurityCallback securityCallback;
protected final PropertyChangeSupport changes = new PropertyChangeSupport(this);
@@ -226,7 +228,7 @@ public JViewport getViewPort() {
}
/**
- * Set an annotation callback.
+ * Set a SignatureAppearanceCallback callback. Allows setting up a custom signature appearance stream
*
* @param annotationCallback annotation callback associated with this document
* view.
@@ -352,6 +354,25 @@ public AnnotationCallback getAnnotationCallback() {
return annotationCallback;
}
+
+ /**
+ * Gets the SignatureAppearanceCallback used to generate a signature annotation's appearance stream
+ *
+ * @return assigned callback
+ */
+ public SignatureAppearanceCallback getSignatureAppearanceCallback() {
+ return signatureAppearanceCallback;
+ }
+
+ /**
+ * Set a SignatureAppearanceCallback callback. Allows setting up a custom signature appearance stream
+ *
+ * @return annotation callback associated with this document.
+ */
+ public void setSignatureAppearanceCallback(SignatureAppearanceCallback signatureAppearanceCallback) {
+ this.signatureAppearanceCallback = signatureAppearanceCallback;
+ }
+
/**
* Gets the security callback.
*
@@ -899,8 +920,7 @@ public boolean setToolMode(final int viewToolMode) {
if (documentView != null) documentView.setToolMode(viewToolMode);
// notify the page components of the tool change.
- List pageComponents =
- documentViewModel.getPageComponents();
+ List pageComponents = documentViewModel.getPageComponents();
for (AbstractPageViewComponent page : pageComponents) {
((PageViewComponentImpl) page).setToolMode(viewToolMode);
}
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewModel.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewModel.java
index 16035fdf0..216ab3fc1 100644
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewModel.java
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewModel.java
@@ -116,6 +116,8 @@ public interface DocumentViewModel {
int DISPLAY_TOOL_REDACTION_ANNOTATION = 19;
+ int DISPLAY_TOOL_SIGNATURE_ANNOTATION = 20;
+
/**
* Display tool constant for setting no tools
*/
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/PageViewComponentImpl.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/PageViewComponentImpl.java
index f27a3bef3..35df9018d 100644
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/PageViewComponentImpl.java
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/PageViewComponentImpl.java
@@ -132,43 +132,39 @@ public void setToolMode(final int viewToolMode) {
this);
break;
case DocumentViewModel.DISPLAY_TOOL_SELECTION:
- // no handler is needed for selection as it is handle by
- // each annotation.
+ // no handler is needed for selection as it is handled by each annotation.
currentToolHandler = new AnnotationSelectionHandler(
documentViewController, this);
documentViewController.clearSelectedText();
break;
case DocumentViewModel.DISPLAY_TOOL_LINK_ANNOTATION:
- // handler is responsible for the initial creation of the annotation
currentToolHandler = new LinkAnnotationHandler(
documentViewController, this);
documentViewController.clearSelectedText();
break;
case DocumentViewModel.DISPLAY_TOOL_HIGHLIGHT_ANNOTATION:
- // handler is responsible for the initial creation of the annotation
currentToolHandler = new HighLightAnnotationHandler(
documentViewController, this);
- ((HighLightAnnotationHandler) currentToolHandler).createMarkupAnnotation(null);
documentViewController.clearSelectedText();
break;
case DocumentViewModel.DISPLAY_TOOL_REDACTION_ANNOTATION:
- // handler is responsible for the initial creation of the annotation
currentToolHandler = new RedactionAnnotationHandler(
documentViewController, this);
- ((RedactionAnnotationHandler) currentToolHandler).createMarkupAnnotation(null);
documentViewController.clearSelectedText();
break;
-
+ case DocumentViewModel.DISPLAY_TOOL_SIGNATURE_ANNOTATION:
+ currentToolHandler = new SignatureAnnotationHandler(
+ documentViewController, this);
+ documentViewController.clearSelectedText();
+ break;
case DocumentViewModel.DISPLAY_TOOL_STRIKEOUT_ANNOTATION:
currentToolHandler = new StrikeOutAnnotationHandler(
documentViewController, this);
- ((StrikeOutAnnotationHandler) currentToolHandler).createMarkupAnnotation(null);
documentViewController.clearSelectedText();
break;
case DocumentViewModel.DISPLAY_TOOL_UNDERLINE_ANNOTATION:
currentToolHandler = new UnderLineAnnotationHandler(
documentViewController, this);
- ((UnderLineAnnotationHandler) currentToolHandler).createMarkupAnnotation(null);
documentViewController.clearSelectedText();
break;
case DocumentViewModel.DISPLAY_TOOL_LINE_ANNOTATION:
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/LinkAnnotationComponent.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/LinkAnnotationComponent.java
index bfa39d1cd..681ba2277 100644
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/LinkAnnotationComponent.java
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/LinkAnnotationComponent.java
@@ -75,10 +75,10 @@ private boolean isAnnotationEditable() {
}
public void paintComponent(Graphics g) {
- // sniff out tool bar state to set correct annotation border
+ // sniff out toolbar state to set correct annotation border
isEditable = isAnnotationEditable();
- // check for the annotation editing mode and draw the link effect so it's easier to see.
+ // check for the annotation editing mode and draw the link effect, so it's easier to see.
if (documentViewController.getParentController().getViewModel().isAnnotationEditingMode()) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/acroform/SignatureComponent.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/acroform/SignatureComponent.java
index 35abe5e59..56bc785c8 100644
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/acroform/SignatureComponent.java
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/acroform/SignatureComponent.java
@@ -21,18 +21,21 @@
import org.icepdf.core.pobjects.acroform.signature.SignatureValidator;
import org.icepdf.core.pobjects.acroform.signature.exceptions.SignatureIntegrityException;
import org.icepdf.core.pobjects.annotations.SignatureWidgetAnnotation;
+import org.icepdf.core.util.Library;
import org.icepdf.ri.common.views.AbstractPageViewComponent;
import org.icepdf.ri.common.views.Controller;
import org.icepdf.ri.common.views.DocumentViewController;
import org.icepdf.ri.common.views.annotations.AbstractAnnotationComponent;
import org.icepdf.ri.common.views.annotations.signatures.CertificatePropertiesDialog;
import org.icepdf.ri.common.views.annotations.signatures.SignaturePropertiesDialog;
+import org.icepdf.ri.common.views.annotations.signing.SignatureCreationDialog;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
+import java.util.logging.Level;
import java.util.logging.Logger;
/**
@@ -46,7 +49,10 @@ public class SignatureComponent extends AbstractAnnotationComponent {
+
+ protected static final Logger logger =
+ Logger.getLogger(BasicSignatureAppearanceCallback.class.toString());
+
+ protected SignatureAppearanceModelImpl signatureAppearanceModel;
+
+
+ @Override
+ public void setSignatureAppearanceModel(SignatureAppearanceModelImpl signatureAppearanceModel) {
+ this.signatureAppearanceModel = signatureAppearanceModel;
+ }
+
+ @Override
+ public void removeAppearanceStream(SignatureWidgetAnnotation signatureWidgetAnnotation,
+ AffineTransform pageSpace, boolean isNew) {
+ if (signatureAppearanceModel == null) {
+ throw new IllegalStateException("SignatureAppearanceModel must be set before calling this method.");
+ }
+ Library library = signatureWidgetAnnotation.getLibrary();
+ SignatureManager signatureManager = library.getSignatureDictionaries();
+ signatureWidgetAnnotation.setSignatureDictionary(new SignatureDictionary(library, new DictionaryEntries()));
+ signatureManager.clearSignatures();
+ StateManager stateManager = library.getStateManager();
+ stateManager.removeChange(new PObject(null, signatureAppearanceModel.getImageXObjectReference()));
+
+ Name currentAppearance = signatureWidgetAnnotation.getCurrentAppearance();
+ HashMap appearances = signatureWidgetAnnotation.getAppearances();
+ Appearance appearance = appearances.get(currentAppearance);
+ AppearanceState appearanceState = appearance.getSelectedAppearanceState();
+ Shapes shapes = ContentWriterUtils.createAppearanceShapes(appearanceState, 0, 0);
+ byte[] postScript = PostScriptEncoder.generatePostScript(shapes.getShapes());
+ Rectangle2D bbox = appearanceState.getBbox();
+ AffineTransform matrix = appearanceState.getMatrix();
+ Form xObject = signatureWidgetAnnotation.updateAppearanceStream(shapes, bbox, matrix, postScript, isNew);
+ xObject.getEntries().remove(RESOURCES_KEY);
+ }
+
+ @Override
+ public void createAppearanceStream(SignatureWidgetAnnotation signatureWidgetAnnotation,
+ AffineTransform pageSpace, boolean isNew) {
+ if (signatureAppearanceModel == null) {
+ throw new IllegalStateException("SignatureAppearanceModel must be set before calling this method.");
+ }
+ SignatureDictionary signatureDictionary = signatureWidgetAnnotation.getSignatureDictionary();
+ Name currentAppearance = signatureWidgetAnnotation.getCurrentAppearance();
+ HashMap appearances = signatureWidgetAnnotation.getAppearances();
+ Appearance appearance = appearances.get(currentAppearance);
+ AppearanceState appearanceState = appearance.getSelectedAppearanceState();
+
+ Shapes shapes = ContentWriterUtils.createAppearanceShapes(appearanceState, 0, 0);
+
+ if (!signatureAppearanceModel.isSignatureVisible() || !signatureAppearanceModel.isSelectedCertificate()) {
+ return;
+ }
+
+ // create the new font to draw with
+ FontFile fontFile = ContentWriterUtils.createFont(signatureAppearanceModel.getFontName());
+ fontFile = fontFile.deriveFont(signatureAppearanceModel.getFontSize());
+
+ ResourceBundle messageBundle = signatureAppearanceModel.getMessageBundle();
+
+ Library library = signatureDictionary.getLibrary();
+
+ // reasons
+ MessageFormat reasonFormatter = new MessageFormat(messageBundle.getString(
+ "viewer.annotation.signature.handler.properties.reason.label"));
+ String reasonTranslated;
+ if (signatureAppearanceModel.getSignatureType() == SignatureType.CERTIFIER) {
+ reasonTranslated = messageBundle.getString(
+ "viewer.annotation.signature.handler.properties.reason.certification.label");
+ } else {
+ reasonTranslated = messageBundle.getString(
+ "viewer.annotation.signature.handler.properties.reason.approval.label");
+ }
+ String reason = reasonFormatter.format(new Object[]{reasonTranslated});
+ // contact info
+ MessageFormat contactFormatter = new MessageFormat(messageBundle.getString(
+ "viewer.annotation.signature.handler.properties.contact.label"));
+ String contactInfo = contactFormatter.format(new Object[]{signatureAppearanceModel.getContact()});
+ // common name
+ MessageFormat signerFormatter = new MessageFormat(messageBundle.getString(
+ "viewer.annotation.signature.handler.properties.signer.label"));
+ String commonName = signerFormatter.format(new Object[]{signatureAppearanceModel.getName()});
+ // location
+ MessageFormat locationFormatter = new MessageFormat(messageBundle.getString(
+ "viewer.annotation.signature.handler.properties.location.label"));
+ String location = locationFormatter.format(new Object[]{signatureAppearanceModel.getLocation()});
+
+ Rectangle2D bbox = appearanceState.getBbox();
+
+ // create new image stream for the signature image
+ BufferedImage signatureImage = signatureAppearanceModel.getSignatureImage();
+ Name imageName = signatureAppearanceModel.getImageXObjectName();
+ Reference imageReference = signatureAppearanceModel.getImageXObjectReference();
+ ImageStream imageStream = null;
+ if (signatureAppearanceModel.isSignatureImageVisible() && signatureImage != null) {
+ imageStream = ContentWriterUtils.addImageToShapes(library, imageName, imageReference, signatureImage,
+ shapes, bbox, signatureAppearanceModel.getImageScale());
+ signatureAppearanceModel.setImageXObjectReference(imageStream.getPObjectReference());
+ }
+
+ if (signatureAppearanceModel.isSignatureTextVisible()) {
+ float offsetY = 0;
+ int lineSpacing = signatureAppearanceModel.getFontSize();
+ int fontSize = signatureAppearanceModel.getFontSize();
+
+ String[] signatureText = {reason, contactInfo, commonName, location};
+ int leftMargin = calculateLeftMargin(bbox, signatureText);
+ int padding = 3;
+ float groupSpacing = calculateTextSpacing(bbox, signatureText, fontSize, padding);
+ AffineTransform centeringTransform = calculatePaddingTransform(leftMargin, padding);
+
+ Point2D.Float lastOffset;
+ float advanceY = (float) bbox.getMinY() + offsetY;
+ shapes.add(new TransformDrawCmd(centeringTransform));
+ for (String text : signatureText) {
+ lastOffset = ContentWriterUtils.addTextSpritesToShapes(fontFile, 0, advanceY,
+ shapes,
+ fontSize,
+ lineSpacing,
+ signatureAppearanceModel.getFontColor(),
+ text);
+ advanceY = lastOffset.y + groupSpacing;
+ }
+ }
+
+ // finalized appearance stream and generated postscript
+ StateManager stateManager = library.getStateManager();
+ AffineTransform matrix = appearanceState.getMatrix();
+
+ byte[] postScript = PostScriptEncoder.generatePostScript(shapes.getShapes());
+ Form xObject = signatureWidgetAnnotation.updateAppearanceStream(shapes, bbox, matrix, postScript, isNew);
+ xObject.addFontResource(ContentWriterUtils.createDefaultFontDictionary(signatureAppearanceModel.getFontName()));
+ if (signatureAppearanceModel.isSignatureImageVisible() && imageStream != null) {
+ xObject.addImageResource(imageName, imageStream);
+ }
+ try {
+ xObject.init();
+ // the image make it more difficult to use the shapes array, so we generated
+ // from the postscript array to get a proper shapes
+ appearanceState.setShapes(xObject.getShapes());
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ ContentWriterUtils.setAppearance(signatureWidgetAnnotation, xObject, appearanceState, stateManager, isNew);
+
+ }
+
+ private float calculateTextSpacing(Rectangle2D bbox, String[] text, int fontSize, int padding) {
+ float textHeight = text.length * fontSize;
+ float bboxHeight = (float) bbox.getHeight() - (padding * 2);
+ if (textHeight > bboxHeight) {
+ return 0;
+ } else {
+ return (bboxHeight - textHeight) / (text.length - 1);
+ }
+ }
+
+ private AffineTransform calculatePaddingTransform(int leftMargin, int padding) {
+ // this is a little fuzzy but should work for most cases to center text in the middle of the bbox0;
+ return new AffineTransform(
+ 1, 0, 0,
+ 1, leftMargin,
+ padding);
+ }
+
+ private int calculateLeftMargin(Rectangle2D bbox, String[] text) {
+ Font font = new Font(signatureAppearanceModel.getFontName(), Font.PLAIN,
+ signatureAppearanceModel.getFontSize());
+ FontRenderContext fontRenderContext = new FontRenderContext(new AffineTransform(), true, true);
+ int maxWidth = 0;
+
+ for (String s : text) {
+ GlyphVector glyphVector = font.createGlyphVector(fontRenderContext, s);
+ Rectangle2D rect = glyphVector.getOutline().getBounds2D();
+ if (rect.getWidth() > maxWidth) {
+ maxWidth = (int) rect.getWidth();
+ }
+ }
+ return (int) bbox.getWidth() - maxWidth;
+ }
+
+}
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/signing/CertificateTableModel.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/signing/CertificateTableModel.java
new file mode 100644
index 000000000..53b91c91c
--- /dev/null
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/signing/CertificateTableModel.java
@@ -0,0 +1,103 @@
+package org.icepdf.ri.common.views.annotations.signing;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.icepdf.core.pobjects.acroform.signature.handlers.SignerHandler;
+import org.icepdf.core.pobjects.acroform.signature.utils.SignatureUtilities;
+
+import javax.security.auth.x500.X500Principal;
+import javax.swing.table.AbstractTableModel;
+import java.security.KeyStoreException;
+import java.security.cert.X509Certificate;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+public class CertificateTableModel extends AbstractTableModel {
+
+ private String[] columnNames;
+ private String[][] data = new String[][]{};
+ private ArrayList certs;
+ private ArrayList aliases;
+ private static SimpleDateFormat validityDateFormat = new SimpleDateFormat("dd/MM/yyyy");
+
+ public CertificateTableModel(SignerHandler signerHandler, Enumeration aliases,
+ ResourceBundle messageBundle) throws KeyStoreException {
+ columnNames = new String[]{
+ messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.certificate.table.name.label"),
+ messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.certificate.table.author.label"),
+ messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.certificate.table.validity.label"),
+ messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.certificate.table.description.label")};
+
+ // build data from aliases in keystore.
+ List rows = new ArrayList<>();
+ certs = new ArrayList<>();
+ this.aliases = Collections.list(aliases);
+ for (String alias : this.aliases) {
+ X509Certificate cert = signerHandler.getCertificate(alias);
+ certs.add(cert);
+ rows.add(createCertSummaryData(cert));
+ }
+ data = new String[rows.size()][columnNames.length];
+ rows.toArray(data);
+
+ }
+
+ private static String[] createCertSummaryData(X509Certificate certificate) {
+ X500Principal principal = certificate.getSubjectX500Principal();
+ X500Name x500name = new X500Name(principal.getName());
+ // Set up dictionary using certificate values.
+ // https://javadoc.io/static/org.bouncycastle/bcprov-jdk15on/1.70/org/bouncycastle/asn1/x500/style/BCStyle.html
+ if (x500name.getRDNs() != null) {
+ String commonName = SignatureUtilities.parseRelativeDistinguishedName(x500name, BCStyle.CN);
+ String email = SignatureUtilities.parseRelativeDistinguishedName(x500name, BCStyle.EmailAddress);
+ String validity = validityDateFormat.format(certificate.getNotAfter());
+ String description = SignatureUtilities.parseRelativeDistinguishedName(x500name, BCStyle.DESCRIPTION);
+ return new String[]{commonName, email, validity, description,};
+ } else {
+ throw new IllegalStateException("Certificate has no DRNs data");
+ }
+ }
+
+ @Override
+ public int getRowCount() {
+ return data.length;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return columnNames.length;
+ }
+
+ @Override
+ public String getColumnName(int col) {
+ return columnNames[col];
+ }
+
+ public String getAliasAt(int row) {
+ if (row < 0 || row >= getRowCount()) {
+ return null;
+ }
+ return aliases.get(row);
+ }
+
+ public X509Certificate getCertificateAt(int row) {
+ if (row < 0 || row >= getRowCount()) {
+ return null;
+ }
+ return certs.get(row);
+ }
+
+ @Override
+ public Object getValueAt(int row, int col) {
+ return data[row][col];
+ }
+
+ @Override
+ public boolean isCellEditable(int row, int col) {
+ return false;
+ }
+}
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/signing/PasswordDialogCallbackHandler.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/signing/PasswordDialogCallbackHandler.java
new file mode 100644
index 000000000..b44bd53ce
--- /dev/null
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/signing/PasswordDialogCallbackHandler.java
@@ -0,0 +1,96 @@
+package org.icepdf.ri.common.views.annotations.signing;
+
+import org.icepdf.core.pobjects.acroform.signature.handlers.PasswordCallbackHandler;
+
+import javax.security.auth.callback.*;
+import javax.swing.*;
+import java.io.IOException;
+import java.util.ResourceBundle;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static javax.swing.JOptionPane.CLOSED_OPTION;
+import static javax.swing.JOptionPane.OK_OPTION;
+import static org.icepdf.ri.common.preferences.SigningPreferencesPanel.PKCS_11_TYPE;
+
+/**
+ * PasswordDialogCallbackHandler handles requesting passwords or pins when accessing a users keystore. The password
+ * is used to open the keystore as well as retrieve the private key used when signing a document.
+ *
+ * @since 7.3
+ */
+public class PasswordDialogCallbackHandler extends PasswordCallbackHandler {
+
+ private static final Logger logger = Logger.getLogger(PasswordDialogCallbackHandler.class.getName());
+
+ private JDialog parentComponent;
+ private ResourceBundle messageBundle;
+ private String dialogType;
+
+ public PasswordDialogCallbackHandler(JDialog parentDialog, ResourceBundle messageBundle) {
+ super("");
+ this.parentComponent = parentDialog;
+ this.messageBundle = messageBundle;
+ }
+
+ public void setType(String dialogType) {
+ this.dialogType = dialogType;
+ }
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (Callback callback : callbacks) {
+ if (callback instanceof PasswordCallback) {
+ PasswordCallback pc = (PasswordCallback) callback;
+// pc.setPassword("changeit".toCharArray());
+// password = "changeit";
+ JPanel panel = new JPanel();
+ String[] options = new String[]{
+ messageBundle.getString("viewer.button.ok.label"),
+ messageBundle.getString("viewer.button.cancel.label")};
+ String dialogTitle;
+ // slightly different verbiage for pkcs11 or pks12.
+ if (dialogType.equals(PKCS_11_TYPE)) {
+ dialogTitle = messageBundle.getString(
+ "viewer.annotation.signature.creation.keystore.pkcs11.dialog.title");
+ JLabel label = new JLabel(messageBundle.getString(
+ "viewer.annotation.signature.creation.keystore.pkcs11.dialog.label"));
+ panel.add(label);
+ } else {
+ dialogTitle = messageBundle.getString(
+ "viewer.annotation.signature.creation.keystore.pkcs12.dialog.title");
+ JLabel label = new JLabel(messageBundle.getString(
+ "viewer.annotation.signature.creation.keystore.pkcs12.dialog.label"));
+ panel.add(label);
+ }
+ JPasswordField pass = new JPasswordField(15);
+ panel.add(pass);
+ int option = JOptionPane.showOptionDialog(parentComponent, panel,
+ dialogTitle,
+ JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE,
+ null, options, options[0]);
+ if (option == OK_OPTION) {
+ char[] password = pass.getPassword();
+ this.password = password;
+ pc.setPassword(password);
+ } else if (option == CLOSED_OPTION) {
+ System.out.println("closed");
+ }
+
+ } else if (callback instanceof TextOutputCallback) {
+ TextOutputCallback tc = (TextOutputCallback) callback;
+ logger.log(Level.WARNING,
+ "TextOutputCallback type {0} message: {1}",
+ new Object[]{tc.getMessageType(), tc.getMessage()});
+ throw new UnsupportedCallbackException(callback);
+ } else if (callback instanceof NameCallback) {
+ throw new UnsupportedCallbackException(callback);
+ } else {
+ logger.log(Level.WARNING,
+ "Unknown callback type {0}",
+ callback.getClass().getName());
+ throw new UnsupportedCallbackException(callback);
+ }
+ }
+ }
+}
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/signing/PkcsSignerFactory.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/signing/PkcsSignerFactory.java
new file mode 100644
index 000000000..1467ef3ef
--- /dev/null
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/signing/PkcsSignerFactory.java
@@ -0,0 +1,47 @@
+package org.icepdf.ri.common.views.annotations.signing;
+
+import org.icepdf.core.pobjects.acroform.signature.handlers.Pkcs11SignerHandler;
+import org.icepdf.core.pobjects.acroform.signature.handlers.Pkcs12SignerHandler;
+import org.icepdf.core.pobjects.acroform.signature.handlers.SignerHandler;
+import org.icepdf.ri.util.ViewerPropertiesManager;
+
+import java.io.File;
+import java.util.prefs.Preferences;
+
+import static org.icepdf.ri.common.preferences.SigningPreferencesPanel.PKCS_11_TYPE;
+import static org.icepdf.ri.common.preferences.SigningPreferencesPanel.PKCS_12_TYPE;
+
+/**
+ * Factory class for creating a SignerHandler instance based on the keystore type.
+ *
+ * @since 7.3
+ */
+public class PkcsSignerFactory {
+
+ private PkcsSignerFactory() {
+ }
+
+ /**
+ * Factory method for creating a SignerHandler instance based on the keystore type. Two instance types are supported
+ * PKCS12 and PKCS11.
+ *
+ * @param passwordDialogCallbackHandler
+ * @return SignerHandler instance based on the keystore type. Null if the keystore type is not supported.
+ */
+ public static SignerHandler getInstance(PasswordDialogCallbackHandler passwordDialogCallbackHandler) {
+ ViewerPropertiesManager propertiesManager = ViewerPropertiesManager.getInstance();
+ Preferences preferences = propertiesManager.getPreferences();
+ String keyStoreType = preferences.get(ViewerPropertiesManager.PROPERTY_PKCS_KEYSTORE_TYPE, "");
+ if (keyStoreType.equals(PKCS_12_TYPE)) {
+ passwordDialogCallbackHandler.setType(PKCS_12_TYPE);
+ String keyStorePath = preferences.get(ViewerPropertiesManager.PROPERTY_PKCS12_PROVIDER_KEYSTORE_PATH, "");
+ File keystoreFile = new File(keyStorePath);
+ return new Pkcs12SignerHandler(keystoreFile, null, passwordDialogCallbackHandler);
+ } else if (keyStoreType.equals(PKCS_11_TYPE)) {
+ passwordDialogCallbackHandler.setType(PKCS_11_TYPE);
+ String configPath = preferences.get(ViewerPropertiesManager.PROPERTY_PKCS11_PROVIDER_CONFIG_PATH, "");
+ return new Pkcs11SignerHandler(configPath, null, passwordDialogCallbackHandler);
+ }
+ return null;
+ }
+}
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/signing/SignatureAppearanceModelImpl.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/signing/SignatureAppearanceModelImpl.java
new file mode 100644
index 000000000..71ec35e70
--- /dev/null
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/signing/SignatureAppearanceModelImpl.java
@@ -0,0 +1,189 @@
+package org.icepdf.ri.common.views.annotations.signing;
+
+import org.icepdf.core.pobjects.Name;
+import org.icepdf.core.pobjects.Reference;
+import org.icepdf.core.pobjects.acroform.signature.appearance.SignatureAppearanceModel;
+import org.icepdf.core.pobjects.acroform.signature.appearance.SignatureType;
+import org.icepdf.core.pobjects.acroform.signature.utils.SignatureUtilities;
+import org.icepdf.core.util.Library;
+import org.icepdf.ri.util.ViewerPropertiesManager;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.Locale;
+import java.util.ResourceBundle;
+import java.util.prefs.Preferences;
+
+
+/**
+ * Signature appearance state allows the signature builder to dialog/ui and a SignatureAppearanceCallback
+ * implementation to
+ * share a common model. When any of the model property values are changed a
+ * SIGNATURE_ANNOTATION_APPEARANCE_PROPERTY_CHANGE
+ * even is fired. The intent is that any properties chane can trigger the SignatureAppearanceCallback to rebuild
+ * the signatures appearance stream.
+ */
+public class SignatureAppearanceModelImpl implements SignatureAppearanceModel {
+
+ private BufferedImage signatureImage;
+ private Name imageXObjectName;
+ private Reference imageXObjectReference;
+
+ private Color fontColor = Color.BLACK;
+
+ private SignatureType signatureType;
+ private boolean signatureVisible = true;
+ private boolean isSelectedCertificate = true;
+ private String location;
+ private String contact;
+ private String name;
+
+ private ResourceBundle messageBundle;
+ private Locale locale;
+ private final Preferences preferences;
+
+
+ public SignatureAppearanceModelImpl(Library library) {
+ imageXObjectName = new Name("sig_img_" + library.getStateManager().getNextImageNumber());
+ preferences = ViewerPropertiesManager.getInstance().getPreferences();
+ }
+
+ public Locale getLocale() {
+ return locale;
+ }
+
+ public void setLocale(Locale locale) {
+ this.locale = locale;
+ messageBundle = ResourceBundle.getBundle(ViewerPropertiesManager.DEFAULT_MESSAGE_BUNDLE, locale);
+ }
+
+ public Name getImageXObjectName() {
+ return imageXObjectName;
+ }
+
+ public Reference getImageXObjectReference() {
+ return imageXObjectReference;
+ }
+
+ public void setImageXObjectReference(Reference imageXObjectReference) {
+ this.imageXObjectReference = imageXObjectReference;
+ }
+
+ public SignatureType getSignatureType() {
+ return signatureType;
+ }
+
+ public void setSignatureType(SignatureType signatureType) {
+ this.signatureType = signatureType;
+ }
+
+ public boolean isSignatureVisible() {
+ return signatureVisible;
+ }
+
+ public boolean isSelectedCertificate() {
+ return isSelectedCertificate;
+ }
+
+ public void setSelectedCertificate(boolean selectedCertificate) {
+ isSelectedCertificate = selectedCertificate;
+ }
+
+ public void setSignatureVisible(boolean signatureVisible) {
+ this.signatureVisible = signatureVisible;
+ }
+
+ public BufferedImage getSignatureImage() {
+ return signatureImage;
+ }
+
+ public void setSignatureImage(BufferedImage image) {
+ this.signatureImage = image;
+ }
+
+ public String getFontName() {
+ return preferences.get(ViewerPropertiesManager.PROPERTY_SIGNATURE_FONT_NAME, "Helvetica");
+ }
+
+ public void setFontName(String fontName) {
+ preferences.put(ViewerPropertiesManager.PROPERTY_SIGNATURE_FONT_NAME, fontName);
+ }
+
+ public int getFontSize() {
+ return preferences.getInt(ViewerPropertiesManager.PROPERTY_SIGNATURE_FONT_SIZE, 6);
+ }
+
+ public void setFontSize(int fontSize) {
+ preferences.putInt(ViewerPropertiesManager.PROPERTY_SIGNATURE_FONT_SIZE, fontSize);
+ }
+
+ public boolean isSignatureTextVisible() {
+ return preferences.getBoolean(ViewerPropertiesManager.PROPERTY_SIGNATURE_SHOW_TEXT, true);
+ }
+
+ public void setSignatureTextVisible(boolean signatureTextVisible) {
+ preferences.putBoolean(ViewerPropertiesManager.PROPERTY_SIGNATURE_SHOW_TEXT, signatureTextVisible);
+ }
+
+ public boolean isSignatureImageVisible() {
+ return preferences.getBoolean(ViewerPropertiesManager.PROPERTY_SIGNATURE_SHOW_IMAGE, true);
+ }
+
+ public void setSignatureImageVisible(boolean signatureImageVisible) {
+ preferences.putBoolean(ViewerPropertiesManager.PROPERTY_SIGNATURE_SHOW_IMAGE, signatureImageVisible);
+ }
+
+ public int getImageScale() {
+ return preferences.getInt(ViewerPropertiesManager.PROPERTY_SIGNATURE_IMAGE_SCALE, 100);
+ }
+
+ public void setImageScale(int imageScale) {
+ preferences.putInt(ViewerPropertiesManager.PROPERTY_SIGNATURE_IMAGE_SCALE, imageScale);
+ }
+
+ public void setSignatureImagePath(String imagePath) {
+ preferences.put(ViewerPropertiesManager.PROPERTY_SIGNATURE_IMAGE_PATH, imagePath);
+ signatureImage = SignatureUtilities.loadSignatureImage(imagePath);
+ }
+
+ public String getSignatureImagePath() {
+ return preferences.get(ViewerPropertiesManager.PROPERTY_SIGNATURE_IMAGE_PATH, "");
+ }
+
+ public Color getFontColor() {
+ return fontColor;
+ }
+
+ public void setFontColor(Color fontColor) {
+ this.fontColor = fontColor;
+ }
+
+ public String getLocation() {
+ return location;
+ }
+
+ public void setLocation(String location) {
+ this.location = location;
+ }
+
+ public String getContact() {
+ return contact;
+ }
+
+ public void setContact(String contact) {
+ this.contact = contact;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public ResourceBundle getMessageBundle() {
+ return messageBundle;
+ }
+
+}
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/signing/SignatureCreationDialog.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/signing/SignatureCreationDialog.java
new file mode 100644
index 000000000..e64183122
--- /dev/null
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/signing/SignatureCreationDialog.java
@@ -0,0 +1,650 @@
+package org.icepdf.ri.common.views.annotations.signing;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.icepdf.core.pobjects.acroform.SignatureDictionary;
+import org.icepdf.core.pobjects.acroform.signature.appearance.SignatureAppearanceCallback;
+import org.icepdf.core.pobjects.acroform.signature.appearance.SignatureType;
+import org.icepdf.core.pobjects.acroform.signature.handlers.SignerHandler;
+import org.icepdf.core.pobjects.acroform.signature.utils.SignatureUtilities;
+import org.icepdf.core.pobjects.annotations.SignatureWidgetAnnotation;
+import org.icepdf.core.util.Library;
+import org.icepdf.core.util.SignatureManager;
+import org.icepdf.ri.common.EscapeJDialog;
+import org.icepdf.ri.common.utility.annotation.properties.FontWidgetUtilities;
+import org.icepdf.ri.common.utility.annotation.properties.ValueLabelItem;
+import org.icepdf.ri.common.views.Controller;
+import org.icepdf.ri.common.views.annotations.acroform.SignatureComponent;
+
+import javax.security.auth.x500.X500Principal;
+import javax.swing.*;
+import javax.swing.border.EtchedBorder;
+import javax.swing.border.TitledBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.geom.AffineTransform;
+import java.security.KeyStoreException;
+import java.security.cert.X509Certificate;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ * The SignatureCreationDialog allows users to select an available signing certificate and customize various setting
+ * associated with signing a document.
+ */
+public class SignatureCreationDialog extends EscapeJDialog implements ActionListener, ListSelectionListener,
+ ItemListener, FocusListener, ChangeListener {
+
+ private static final Logger logger =
+ Logger.getLogger(SignatureCreationDialog.class.toString());
+
+ private static final Locale[] supportedLocales = {
+ new Locale("da"),
+ new Locale("de"),
+ new Locale("en"),
+ new Locale("es"),
+ new Locale("fi"),
+ new Locale("fr"),
+ new Locale("it"),
+ new Locale("nl"),
+ new Locale("no"),
+ new Locale("pt"),
+ new Locale("sv"),
+ };
+
+ private GridBagConstraints constraints;
+
+ private JTable certificateTable;
+ private JRadioButton signerRadioButton;
+ private JRadioButton certifyRadioButton;
+ private JCheckBox signerVisibilityCheckBox;
+ private JTextField locationTextField;
+ private JTextField nameTextField;
+ private JTextField contactTextField;
+
+ private JComboBox fontNameBox;
+ private JComboBox fontSizeBox;
+ private JCheckBox showTextCheckBox;
+ private JCheckBox showSignatureCheckBox;
+ private JTextField imagePathTextField;
+ private JButton imagePathBrowseButton;
+ private JSlider imageScaleSlider;
+
+ private JComboBox languagesComboBox;
+ private JButton signButton;
+ private JButton closeButton;
+
+ private SignerHandler signerHandler;
+
+ private final SignatureAppearanceCallback signatureAppearanceCallback;
+ private final SignatureAppearanceModelImpl signatureAppearanceModel;
+
+ protected static ResourceBundle messageBundle;
+ protected final SignatureComponent signatureWidgetComponent;
+ protected final SignatureWidgetAnnotation signatureWidgetAnnotation;
+
+ public SignatureCreationDialog(Controller controller, ResourceBundle messageBundle,
+ SignatureComponent signatureComponent) throws KeyStoreException {
+ super(controller.getViewerFrame(), true);
+ SignatureCreationDialog.messageBundle = messageBundle;
+ this.signatureWidgetComponent = signatureComponent;
+ this.signatureWidgetAnnotation = signatureComponent.getAnnotation();
+
+ signatureAppearanceCallback = controller.getDocumentViewController().getSignatureAppearanceCallback();
+ signatureAppearanceModel = new SignatureAppearanceModelImpl(signatureComponent.getAnnotation().getLibrary());
+ signatureAppearanceModel.setSelectedCertificate(false);
+ signatureAppearanceCallback.setSignatureAppearanceModel(signatureAppearanceModel);
+ signatureWidgetAnnotation.setAppearanceCallback(signatureAppearanceCallback);
+
+ this.addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) {
+ cancelOrCloseSignatureCleanup();
+ }
+ });
+
+ buildUI();
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent actionEvent) {
+
+ Object source = actionEvent.getSource();
+ if (source == null) return;
+
+ if (source == signButton) {
+ Library library = signatureWidgetAnnotation.getLibrary();
+ SignatureManager signatureManager = library.getSignatureDictionaries();
+
+ // set up signer dictionary as the primary certification signer.
+ SignatureDictionary signatureDictionary;
+ if (signerRadioButton.isSelected()) {
+ signatureDictionary = SignatureDictionary.getInstance(signatureWidgetAnnotation, SignatureType.SIGNER);
+ } else {
+ if (signatureManager.hasExistingCertifier(library)) {
+ JOptionPane.showMessageDialog(this,
+ messageBundle.getString("viewer.annotation.signature.creation.dialog.certify.error.msg"),
+ messageBundle.getString("viewer.annotation.signature.creation.dialog.certify.error.title"),
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ signatureDictionary = SignatureDictionary.getInstance(signatureWidgetAnnotation,
+ SignatureType.CERTIFIER);
+ }
+ signatureDictionary.setSignerHandler(signerHandler);
+ signatureManager.addSignature(signatureDictionary, signatureWidgetAnnotation);
+
+ // assign original values from cert
+ signatureDictionary.setName(nameTextField.getText());
+ signatureDictionary.setContactInfo(contactTextField.getText());
+ signatureDictionary.setLocation(locationTextField.getText());
+ signatureDictionary.setReason(signerRadioButton.isSelected() ?
+ SignatureType.SIGNER.toString().toLowerCase() :
+ SignatureType.CERTIFIER.toString().toLowerCase());
+ buildAppearanceStream();
+
+ setVisible(false);
+ dispose();
+ } else if (source == closeButton) {
+ // clean anything we set up and just leave the signature with an empty dictionary
+ cancelOrCloseSignatureCleanup();
+ setVisible(false);
+ dispose();
+ } else if (source == imagePathTextField) {
+ setSignatureImage();
+ buildAppearanceStream();
+ } else if (source == signerRadioButton) {
+ signatureAppearanceModel.setSignatureType(SignatureType.SIGNER);
+ buildAppearanceStream();
+ } else if (source == certifyRadioButton) {
+ signatureAppearanceModel.setSignatureType(SignatureType.CERTIFIER);
+ buildAppearanceStream();
+ } else if (source == signerVisibilityCheckBox) {
+ signatureAppearanceModel.setSignatureVisible(signerVisibilityCheckBox.isSelected());
+ buildAppearanceStream();
+ } else if (source == showTextCheckBox) {
+ signatureAppearanceModel.setSignatureTextVisible(showTextCheckBox.isSelected());
+ buildAppearanceStream();
+ } else if (source == showSignatureCheckBox) {
+ signatureAppearanceModel.setSignatureImageVisible(showSignatureCheckBox.isSelected());
+ buildAppearanceStream();
+ } else if (source == imagePathBrowseButton) {
+ String imagePath = signatureAppearanceModel.getSignatureImagePath();
+ JFileChooser fileChooser = new JFileChooser(imagePath);
+ fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+ fileChooser.setMultiSelectionEnabled(false);
+ fileChooser.setDialogTitle(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.signature.selection.title"));
+ final int responseValue = fileChooser.showDialog(this, messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.signature.selection.accept.label"));
+ if (responseValue == JFileChooser.APPROVE_OPTION) {
+ imagePathTextField.setText(fileChooser.getSelectedFile().getAbsolutePath());
+ setSignatureImage();
+ buildAppearanceStream();
+ }
+ }
+ }
+
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getStateChange() != ItemEvent.SELECTED) {
+ return;
+ }
+ if (e.getSource() == fontSizeBox) {
+ ValueLabelItem item = (ValueLabelItem) fontSizeBox.getSelectedItem();
+ if (item != null) {
+ int fontSize = (int) item.getValue();
+ signatureAppearanceModel.setFontSize(fontSize);
+ buildAppearanceStream();
+ }
+ } else if (e.getSource() == fontNameBox) {
+ ValueLabelItem item = (ValueLabelItem) fontNameBox.getSelectedItem();
+ if (item != null) {
+ String fontName = item.getValue().toString();
+ signatureAppearanceModel.setFontName(fontName);
+ buildAppearanceStream();
+ }
+ } else if (e.getSource() == languagesComboBox) {
+ signatureAppearanceModel.setLocale((Locale) languagesComboBox.getSelectedItem());
+ buildAppearanceStream();
+ }
+ }
+
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ JSlider source = (JSlider) e.getSource();
+ if (!source.getValueIsAdjusting()) {
+ int scale = source.getValue();
+ signatureAppearanceModel.setImageScale(scale);
+ buildAppearanceStream();
+ }
+ }
+
+ @Override
+ public void valueChanged(ListSelectionEvent event) {
+ if (event.getValueIsAdjusting()) {
+ return;
+ }
+ int row = certificateTable.convertRowIndexToModel(certificateTable.getSelectedRow());
+ CertificateTableModel model = (CertificateTableModel) certificateTable.getModel();
+ signerHandler.setCertAlias(model.getAliasAt(row));
+ setSelectedCertificate(model.getCertificateAt(row));
+ buildAppearanceStream();
+ }
+
+ @Override
+ public void focusGained(FocusEvent focusEvent) {
+
+ }
+
+ @Override
+ public void focusLost(FocusEvent focusEvent) {
+ Object source = focusEvent.getSource();
+ boolean changed = false;
+ if (source == locationTextField) {
+ signatureAppearanceModel.setLocation(locationTextField.getText());
+ changed = true;
+ } else if (source == contactTextField) {
+ signatureAppearanceModel.setContact(contactTextField.getText());
+ changed = true;
+ } else if (source == nameTextField) {
+ signatureAppearanceModel.setName(nameTextField.getText());
+ changed = true;
+ } else if (source == imagePathTextField) {
+ setSignatureImage();
+ changed = true;
+ }
+ if (changed) {
+ buildAppearanceStream();
+ }
+ }
+
+ private void cancelOrCloseSignatureCleanup() {
+ signatureAppearanceCallback.removeAppearanceStream(signatureWidgetAnnotation, new AffineTransform(), true);
+ signatureWidgetAnnotation.setAppearanceCallback(null);
+ }
+
+ private void updateModelAppearanceState() {
+ signatureAppearanceModel.setLocation(locationTextField.getText());
+ signatureAppearanceModel.setContact(contactTextField.getText());
+ signatureAppearanceModel.setName(nameTextField.getText());
+ signatureAppearanceModel.setSignatureType(signerRadioButton.isSelected() ?
+ SignatureType.SIGNER : SignatureType.CERTIFIER);
+ signatureAppearanceModel.setFontName(Objects.requireNonNull(fontNameBox.getSelectedItem()).toString());
+ signatureAppearanceModel.setFontSize((int) ((ValueLabelItem) Objects.requireNonNull(fontSizeBox.getSelectedItem())).getValue());
+ signatureAppearanceModel.setSignatureImagePath(imagePathTextField.getText());
+ signatureAppearanceModel.setImageScale(imageScaleSlider.getValue());
+ setSignatureImage();
+ }
+
+
+ private void setSignatureImage() {
+ signatureAppearanceModel.setSignatureImagePath(imagePathTextField.getText());
+ }
+
+ private void buildAppearanceStream() {
+
+ signatureWidgetAnnotation.resetAppearanceStream(new AffineTransform());
+ signatureWidgetComponent.repaint();
+ }
+
+ private void setSelectedCertificate(X509Certificate certificate) {
+ signatureAppearanceModel.setSelectedCertificate(certificate != null);
+ if (certificate == null) {
+ // clear metadata
+ nameTextField.setText("");
+ contactTextField.setText("");
+ locationTextField.setText("");
+ enableInputComponents(false);
+ signerHandler.setCertAlias(null);
+ } else {
+ // pull out metadata from cert.
+ X500Principal principal = certificate.getSubjectX500Principal();
+ X500Name x500name = new X500Name(principal.getName());
+
+ enableInputComponents(true);
+
+ if (x500name.getRDNs() != null) {
+ nameTextField.setText(SignatureUtilities.parseRelativeDistinguishedName(x500name, BCStyle.CN));
+ contactTextField.setText(SignatureUtilities.parseRelativeDistinguishedName(x500name,
+ BCStyle.EmailAddress));
+ String address = SignatureUtilities.parseRelativeDistinguishedName(x500name, BCStyle.POSTAL_ADDRESS);
+ if (address != null) {
+ locationTextField.setText(SignatureUtilities.parseRelativeDistinguishedName(x500name,
+ BCStyle.POSTAL_ADDRESS));
+ } else {
+ String state = SignatureUtilities.parseRelativeDistinguishedName(x500name, BCStyle.ST);
+ if (state != null) {
+ locationTextField.setText(SignatureUtilities.parseRelativeDistinguishedName(x500name,
+ BCStyle.ST));
+ }
+ }
+
+ ArrayList location = new ArrayList<>(2);
+ String state = SignatureUtilities.parseRelativeDistinguishedName(x500name, BCStyle.ST);
+ if (state != null) {
+ location.add(state);
+ }
+ String country = SignatureUtilities.parseRelativeDistinguishedName(x500name, BCStyle.C);
+ if (country != null) {
+ location.add(country);
+ }
+ if (!location.isEmpty()) {
+ locationTextField.setText(String.join(", ", location));
+ }
+ }
+ }
+ updateModelAppearanceState();
+ }
+
+ private void buildUI() throws KeyStoreException {
+
+ // need to build keystore right up front, so we can build out the JTable to show certs in the keychain
+ PasswordDialogCallbackHandler passwordDialogCallbackHandler =
+ new PasswordDialogCallbackHandler(this, messageBundle);
+ signerHandler = PkcsSignerFactory.getInstance(passwordDialogCallbackHandler);
+
+ this.setTitle(messageBundle.getString("viewer.annotation.signature.creation.dialog.title"));
+
+ JPanel certificateSelectionPanel = buildCertificateSelectionPanel();
+ JPanel signatureBuilderPanel = buildSignatureBuilderPanel();
+ JPanel signatureControlPanel = buildSignatureControlPanel();
+
+ JTabbedPane signatureTabbedPane = new JTabbedPane();
+ signatureTabbedPane.addTab(
+ messageBundle.getString("viewer.annotation.signature.creation.dialog.certificate.tab.title"),
+ certificateSelectionPanel);
+ signatureTabbedPane.addTab(
+ messageBundle.getString("viewer.annotation.signature.creation.dialog.signature.tab.title"),
+ signatureBuilderPanel);
+
+ enableInputComponents(false);
+
+ JPanel contentPane = new JPanel(new BorderLayout());
+ contentPane.add(signatureTabbedPane, BorderLayout.CENTER);
+ contentPane.add(signatureControlPanel, BorderLayout.SOUTH);
+
+ // pack it up and go.
+ getContentPane().add(contentPane);
+ pack();
+ setLocationRelativeTo(getOwner());
+ setResizable(true);
+ }
+
+ private JPanel buildSignatureBuilderPanel() {
+ JPanel appearancePanel = new JPanel(new GridBagLayout());
+ appearancePanel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+
+ constraints = new GridBagConstraints();
+ constraints.fill = GridBagConstraints.HORIZONTAL;
+ constraints.weightx = 1.0;
+ constraints.weighty = 0;
+ constraints.insets = new Insets(2, 10, 2, 10);
+
+ JPanel visibilityPanel = new JPanel(new GridBagLayout());
+ visibilityPanel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+ visibilityPanel.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.LOWERED),
+ messageBundle.getString("viewer.annotation.signature.creation.dialog.signature.appearance.title")));
+ // font name setup
+ String fontName = signatureAppearanceModel.getFontName();
+ ValueLabelItem[] fontNameItems = FontWidgetUtilities.generateFontNameList(messageBundle);
+ fontNameBox = new JComboBox<>(fontNameItems);
+ fontNameBox.setSelectedItem(Arrays.stream(fontNameItems).filter(t -> t.getValue() == fontName).findAny().orElse(fontNameItems[0]));
+ fontNameBox.addItemListener(this);
+
+ // font size setup
+ int fontSize = signatureAppearanceModel.getFontSize();
+ ValueLabelItem[] fontSizeItems = FontWidgetUtilities.generateFontSizeNameList(messageBundle);
+ fontSizeBox = new JComboBox<>(fontSizeItems);
+ fontSizeBox.setSelectedItem(Arrays.stream(fontSizeItems).filter(t -> (int) t.getValue() == fontSize).findAny().orElse(fontSizeItems[0]));
+ fontSizeBox.addItemListener(this);
+
+ // show text on signature appearance stream
+ boolean showText = signatureAppearanceModel.isSignatureTextVisible();
+ showTextCheckBox = new JCheckBox(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.signature.appearance.showText.label"), showText);
+ showTextCheckBox.addActionListener(this);
+
+ // show image on signature appearance stream
+ boolean showImage = signatureAppearanceModel.isSignatureImageVisible();
+ showSignatureCheckBox = new JCheckBox(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.signature.appearance.showSignature.label"), showImage);
+ showSignatureCheckBox.addActionListener(this);
+
+ // image path
+ JLabel imagePathLabel = new JLabel(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.signature.imagePath.label"));
+ imagePathTextField = new JTextField();
+ String imagePath = signatureAppearanceModel.getSignatureImagePath();
+
+ imagePathTextField.setText(imagePath);
+ if (!imagePath.isEmpty()) {
+ signatureAppearanceModel.setSignatureImagePath(imagePath);
+ }
+ imagePathTextField.addFocusListener(this);
+ imagePathBrowseButton = new JButton(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.signature.selection.browse.label"));
+ imagePathBrowseButton.addActionListener(this);
+
+ // image scale
+ int imageScale = signatureAppearanceModel.getImageScale();
+ JLabel imageScaleLabel = new JLabel(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.signature.imageScale.label"));
+ imageScaleSlider = new JSlider(JSlider.HORIZONTAL, 0, 300, imageScale);
+ imageScaleSlider.setMajorTickSpacing(50);
+ imageScaleSlider.setPaintLabels(true);
+
+ imageScaleSlider.setPaintTicks(true);
+ imageScaleSlider.addChangeListener(this);
+
+ // font name and size
+ addGB(visibilityPanel, new JLabel(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.signature.appearance.font.label")),
+ 0, 0, 1, 1);
+ addGB(visibilityPanel, fontNameBox, 1, 0, 1, 1);
+ addGB(visibilityPanel, new JLabel(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.signature.appearance.fontSize.label")),
+ 2, 0, 1, 1);
+ addGB(visibilityPanel, fontSizeBox, 3, 0, 1, 1);
+ addGB(visibilityPanel, showTextCheckBox, 0, 1, 1, 2);
+ addGB(visibilityPanel, showSignatureCheckBox, 2, 1, 1, 2);
+
+ JPanel signaturePanel = new JPanel(new GridBagLayout());
+ signaturePanel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+ signaturePanel.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.LOWERED),
+ messageBundle.getString("viewer.annotation.signature.creation.dialog.signature.canvas.title")));
+ // image path input
+ addGB(signaturePanel, imagePathLabel, 0, 0, 1, 1);
+ addGB(signaturePanel, imagePathTextField, 1, 0, 1, 1);
+ addGB(signaturePanel, imagePathBrowseButton, 2, 0, 1, 1);
+ addGB(signaturePanel, imageScaleLabel, 0, 1, 1, 1);
+ addGB(signaturePanel, imageScaleSlider, 1, 1, 1, 2);
+
+ constraints.insets = new Insets(2, 10, 2, 10);
+ addGB(appearancePanel, visibilityPanel, 0, 0, 1, 1);
+ addGB(appearancePanel, signaturePanel, 0, 1, 1, 1);
+ constraints.weighty = 1.0;
+ addGB(appearancePanel, new Label(" "), 0, 9, 1, 1);
+
+ return appearancePanel;
+ }
+
+ private JPanel buildSignatureControlPanel() {
+ JPanel controlPanel = new JPanel(new GridBagLayout());
+ controlPanel.setAlignmentY(JPanel.BOTTOM_ALIGNMENT);
+
+ // close buttons.
+ closeButton = new JButton(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.close.button.label"));
+ closeButton.setMnemonic(messageBundle.getString("viewer.button.cancel.mnemonic").charAt(0));
+ closeButton.addActionListener(this);
+ signButton = new JButton(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.sign.button.label"));
+ signButton.addActionListener(this);
+
+ constraints = new GridBagConstraints();
+ constraints.fill = GridBagConstraints.NONE;
+ constraints.weightx = 1.0;
+ constraints.weighty = 0;
+ constraints.insets = new Insets(5, 10, 5, 10);
+
+ // close and sign input
+ constraints.anchor = GridBagConstraints.WEST;
+ addGB(controlPanel, closeButton, 0, 0, 1, 1);
+
+ constraints.anchor = GridBagConstraints.EAST;
+ addGB(controlPanel, signButton, 1, 0, 1, 1);
+ return controlPanel;
+ }
+
+ private JPanel buildCertificateSelectionPanel() throws KeyStoreException {
+ JPanel certificateSelectionPanel = new JPanel(new GridBagLayout());
+ certificateSelectionPanel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+ this.setLayout(new BorderLayout());
+ add(certificateSelectionPanel, BorderLayout.NORTH);
+
+ // keystore certificate table
+ if (signerHandler == null) {
+ throw new IllegalStateException("Signer handler is null");
+ }
+ Enumeration aliases = signerHandler.buildKeyStore().aliases();
+ CertificateTableModel certificateTableModel = new CertificateTableModel(signerHandler, aliases, messageBundle);
+ certificateTable = new JTable(certificateTableModel);
+ certificateTable.getSelectionModel().addListSelectionListener(this);
+ certificateTable.setPreferredScrollableViewportSize(new Dimension(600, 100));
+ certificateTable.setFillsViewportHeight(true);
+
+ // certificate type selection
+ signerRadioButton = new JRadioButton(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.certificate.type.signer.label"));
+ signerRadioButton.setSelected(true);
+ signerRadioButton.addActionListener(this);
+ signerRadioButton.setActionCommand(SignatureType.SIGNER.toString());
+ certifyRadioButton = new JRadioButton(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.certificate.type.certify.label"));
+ certifyRadioButton.addActionListener(this);
+ signerRadioButton.setActionCommand(SignatureType.CERTIFIER.toString());
+ ButtonGroup certificateTypeButtonGroup = new ButtonGroup();
+ certificateTypeButtonGroup.add(signerRadioButton);
+ certificateTypeButtonGroup.add(certifyRadioButton);
+
+ // signature visibility
+ signerVisibilityCheckBox = new JCheckBox(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.certificate.visibility.certify.label"));
+ signerVisibilityCheckBox.setSelected(true);
+ signerVisibilityCheckBox.addActionListener(this);
+
+ // location and date
+ locationTextField = new JTextField();
+ locationTextField.addFocusListener(this);
+ JTextField dateTextField = new JTextField();
+ dateTextField.setEnabled(false);
+ DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+ String today = df.format(new Date());
+ dateTextField.setText(today);
+ // name
+ nameTextField = new JTextField();
+ nameTextField.addFocusListener(this);
+ // contact
+ contactTextField = new JTextField();
+ contactTextField.addFocusListener(this);
+
+ // todo very much needed -> Timestamp service
+
+ // language
+ languagesComboBox = new JComboBox<>(supportedLocales);
+ // be nice to do this and take into account country too.
+ Locale defaultLocal = new Locale(Locale.getDefault().getLanguage());
+ languagesComboBox.setSelectedItem(new Locale(Locale.getDefault().getLanguage()));
+ languagesComboBox.addItemListener(this);
+ signatureAppearanceModel.setLocale(defaultLocal);
+
+
+ constraints = new GridBagConstraints();
+ constraints.fill = GridBagConstraints.HORIZONTAL;
+ constraints.weightx = 1.0;
+ constraints.weighty = 0;
+ constraints.insets = new Insets(2, 10, 2, 10);
+
+ // cert selection label
+ addGB(certificateSelectionPanel, new JLabel(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.certificate.selection.label")),
+ 0, 0, 1, 3);
+
+ // cert table
+ addGB(certificateSelectionPanel, new JScrollPane(certificateTable), 0, 1, 1, 4);
+
+ // type of signature.
+ addGB(certificateSelectionPanel, new JLabel(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.certificate.type.description.label")),
+ 0, 2, 1, 1);
+ addGB(certificateSelectionPanel, signerRadioButton, 1, 2, 1, 1);
+ addGB(certificateSelectionPanel, certifyRadioButton, 2, 2, 1, 1);
+ // signature visibility
+ addGB(certificateSelectionPanel, signerVisibilityCheckBox, 3, 2, 1, 1);
+
+ // location and Date
+ addGB(certificateSelectionPanel, new JLabel(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.certificate.location.date.label")),
+ 0, 3, 1, 1);
+ addGB(certificateSelectionPanel, locationTextField, 1, 3, 1, 1);
+ addGB(certificateSelectionPanel, dateTextField, 2, 3, 1, 1);
+
+ // name
+ addGB(certificateSelectionPanel, new JLabel(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.certificate.name.label")),
+ 0, 5, 1, 1);
+ addGB(certificateSelectionPanel, nameTextField, 1, 5, 1, 3);
+
+ // contact
+ addGB(certificateSelectionPanel, new JLabel(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.certificate.contact.label")),
+ 0, 6, 1, 1);
+ addGB(certificateSelectionPanel, contactTextField, 1, 6, 1, 3);
+
+ // language selection
+ constraints.anchor = GridBagConstraints.LINE_START;
+ addGB(certificateSelectionPanel, new JLabel(messageBundle.getString(
+ "viewer.annotation.signature.creation.dialog.certificate.i18n.label")),
+ 0, 8, 1, 1);
+ addGB(certificateSelectionPanel, languagesComboBox, 1, 8, 1, 1);
+
+ constraints.weighty = 1.0;
+ addGB(certificateSelectionPanel, new Label(" "), 0, 9, 1, 1);
+
+ return certificateSelectionPanel;
+ }
+
+ private void enableInputComponents(boolean enable) {
+ nameTextField.setEnabled(enable);
+ contactTextField.setEnabled(enable);
+ locationTextField.setEnabled(enable);
+ signerRadioButton.setEnabled(enable);
+ certifyRadioButton.setEnabled(enable);
+ signerVisibilityCheckBox.setEnabled(enable);
+ languagesComboBox.setEnabled(enable);
+
+ fontNameBox.setEnabled(enable);
+ fontSizeBox.setEnabled(enable);
+ showTextCheckBox.setEnabled(enable);
+ showSignatureCheckBox.setEnabled(enable);
+ imagePathTextField.setEnabled(enable);
+
+ signButton.setEnabled(enable);
+ }
+
+ private void addGB(JPanel layout, Component component,
+ int x, int y,
+ int rowSpan, int colSpan) {
+ constraints.gridx = x;
+ constraints.gridy = y;
+ constraints.gridwidth = colSpan;
+ constraints.gridheight = rowSpan;
+ layout.add(component, constraints);
+ }
+}
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryPanel.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryPanel.java
index babad83b3..24a5cf45a 100644
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryPanel.java
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryPanel.java
@@ -20,7 +20,7 @@
import org.icepdf.core.pobjects.annotations.MarkupAnnotation;
import org.icepdf.core.util.PropertyConstants;
import org.icepdf.ri.common.MutableDocument;
-import org.icepdf.ri.common.utility.annotation.properties.FreeTextAnnotationPanel;
+import org.icepdf.ri.common.utility.annotation.properties.FontWidgetUtilities;
import org.icepdf.ri.common.utility.annotation.properties.ValueLabelItem;
import org.icepdf.ri.common.views.Controller;
import org.icepdf.ri.common.views.DocumentViewControllerImpl;
@@ -200,7 +200,7 @@ protected void buildStatusToolBarPanel() {
ViewerPropertiesManager propertiesManager = controller.getPropertiesManager();
- fontSizeBox = new JComboBox<>(FreeTextAnnotationPanel.generateFontSizeNameList(messageBundle));
+ fontSizeBox = new JComboBox<>(FontWidgetUtilities.generateFontSizeNameList(messageBundle));
applySelectedValue(fontSizeBox, propertiesManager.checkAndStoreIntProperty(
ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_FONT_SIZE, new JLabel().getFont().getSize()));
fontSizeBox.addItemListener(this);
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/ViewerPropertiesManager.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/ViewerPropertiesManager.java
index 09a3e0c82..ff205cfe6 100644
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/ViewerPropertiesManager.java
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/ViewerPropertiesManager.java
@@ -89,6 +89,18 @@ public final class ViewerPropertiesManager {
public static final String PROPERTY_PAGE_VIEW_BACKGROUND_COLOR = "org.icepdf.core.views.background.color";
// image reference type.
public static final String PROPERTY_IMAGING_REFERENCE_TYPE = "org.icepdf.core.imageReference";
+ // signature creation keystore type handler.
+ public static final String PROPERTY_PKCS_KEYSTORE_TYPE = "org.icepdf.core.signatures.keystore.type";
+ public static final String PROPERTY_PKCS11_PROVIDER_CONFIG_PATH = "org.icepdf.core.signatures.keystore.pkcs11" +
+ ".config.path";
+ public static final String PROPERTY_PKCS12_PROVIDER_KEYSTORE_PATH = "org.icepdf.core.signatures.keystore.pkcs12" +
+ ".config.path";
+ public static final String PROPERTY_SIGNATURE_IMAGE_PATH = "org.icepdf.core.signatures.image.path";
+ public static final String PROPERTY_SIGNATURE_SHOW_TEXT = "org.icepdf.core.signatures.show.txt";
+ public static final String PROPERTY_SIGNATURE_SHOW_IMAGE = "org.icepdf.core.signatures.show.image";
+ public static final String PROPERTY_SIGNATURE_IMAGE_SCALE = "org.icepdf.core.signatures.show.imageScale";
+ public static final String PROPERTY_SIGNATURE_FONT_NAME = "org.icepdf.core.signatures.font.name";
+ public static final String PROPERTY_SIGNATURE_FONT_SIZE = "org.icepdf.core.signatures.font.size";
// advanced threading properties
public static final String PROPERTY_IMAGE_PROXY_ENABLED = "org.icepdf.core.imageProxy";
public static final String PROPERTY_IMAGE_PROXY_THREAD_COUNT = "org.icepdf.core.library.imageThreadPoolSize";
@@ -138,6 +150,7 @@ public final class ViewerPropertiesManager {
public static final String PROPERTY_SHOW_PREFERENCES_GENERAL = "application.preferences.show.general";
public static final String PROPERTY_SHOW_PREFERENCES_ANNOTATIONS = "application.preferences.show.annotations";
public static final String PROPERTY_SHOW_PREFERENCES_IMAGING = "application.preferences.show.imaging";
+ public static final String PROPERTY_SHOW_PREFERENCES_SIGNING = "application.preferences.show.signing";
public static final String PROPERTY_SHOW_PREFERENCES_FONTS = "application.preferences.show.fonts";
public static final String PROPERTY_SHOW_PREFERENCES_ADVANCED = "application.preferences.show.advanced";
public static final String PROPERTY_SHOW_PREFERENCES_EXIMPORT = "application.preferences.show.eximport";
@@ -161,6 +174,8 @@ public final class ViewerPropertiesManager {
".selection.type";
public static final String PROPERTY_ANNOTATION_LINE_SELECTION_TYPE = "application.annotation.line.selection.type";
public static final String PROPERTY_ANNOTATION_LINK_SELECTION_TYPE = "application.annotation.link.selection.type";
+ public static final String PROPERTY_ANNOTATION_SIGNATURE_SELECTION_TYPE = "application.annotation.signature" +
+ ".selection.type";
public static final String PROPERTY_ANNOTATION_SQUARE_SELECTION_TYPE = "application.annotation.rectangle.selection.type";
public static final String PROPERTY_ANNOTATION_CIRCLE_SELECTION_TYPE = "application.annotation.circle.selection.type";
public static final String PROPERTY_ANNOTATION_INK_SELECTION_TYPE = "application.annotation.ink.selection.type";
@@ -172,6 +187,7 @@ public final class ViewerPropertiesManager {
ViewerPropertiesManager.PROPERTY_ANNOTATION_INK_SELECTION_TYPE,
ViewerPropertiesManager.PROPERTY_ANNOTATION_LINE_SELECTION_TYPE,
ViewerPropertiesManager.PROPERTY_ANNOTATION_LINK_SELECTION_TYPE,
+ ViewerPropertiesManager.PROPERTY_ANNOTATION_SIGNATURE_SELECTION_TYPE,
ViewerPropertiesManager.PROPERTY_ANNOTATION_SQUARE_SELECTION_TYPE,
ViewerPropertiesManager.PROPERTY_ANNOTATION_TEXT_SELECTION_TYPE,
ViewerPropertiesManager.PROPERTY_ANNOTATION_FREE_TEXT_SELECTION_TYPE
@@ -195,6 +211,8 @@ public final class ViewerPropertiesManager {
public static final String PROPERTY_SHOW_TOOLBAR_ANNOTATION_HIGHLIGHT = "application.toolbar.annotation.highlight.enabled";
public static final String PROPERTY_SHOW_TOOLBAR_ANNOTATION_REDACTION = "application.toolbar.annotation.redaction" +
".enabled";
+ public static final String PROPERTY_SHOW_TOOLBAR_ANNOTATION_SIGNATURE = "application.toolbar.annotation.signature" +
+ ".enabled";
public static final String PROPERTY_SHOW_TOOLBAR_ANNOTATION_UNDERLINE = "application.toolbar.annotation.underline.enabled";
public static final String PROPERTY_SHOW_TOOLBAR_ANNOTATION_STRIKE_OUT = "application.toolbar.annotation.strikeout.enabled";
public static final String PROPERTY_SHOW_TOOLBAR_ANNOTATION_LINE = "application.toolbar.annotation.line.enabled";
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/Launcher.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/Launcher.java
index f7cd973ca..3ac602a66 100644
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/Launcher.java
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/Launcher.java
@@ -29,6 +29,9 @@
import java.util.logging.Logger;
import java.util.prefs.Preferences;
+import static org.icepdf.ri.common.preferences.SigningPreferencesPanel.PKCS_11_TYPE;
+import static org.icepdf.ri.common.preferences.SigningPreferencesPanel.PKCS_12_TYPE;
+
/**
* Launches the Viewer Application. The following parameters can be used
* to optionally load a PDF document at startup.
@@ -49,6 +52,16 @@
* URL. Use the following syntax:
* -loadurl http://www.examplesite.com/file.pdf
*
+ *
+ * | -pkcs11path filename |
+ * Specifies the location of the PKCS#11 provider configuration file to use when signing a document:
+ * -pkcs11path /home/user/myProvider.cfg |
+ *
+ *
+ * | -pkcs12path filename |
+ * Specifies the location of the PKCS#12 keystore file that will be used when signing a document:
+ * -pkcs12path /home/user/certificate.pfx" |
+ *
*
*/
public class Launcher {
@@ -67,6 +80,8 @@ public static void main(String[] argv) {
String contentURL = "";
String contentFile = "";
+ String pkcs11ConfigPath = "";
+ String pkcs12KeystorePath = "";
String printer = null;
// parse command line arguments
for (int i = 0; i < argv.length; i++) {
@@ -82,6 +97,12 @@ public static void main(String[] argv) {
case "-loadurl":
contentURL = argv[++i].trim();
break;
+ case "-pkcs11path":
+ pkcs11ConfigPath = argv[++i].trim();
+ break;
+ case "-pkcs12path":
+ pkcs12KeystorePath = argv[++i].trim();
+ break;
case "-print":
printer = argv[++i].trim();
break;
@@ -95,13 +116,13 @@ public static void main(String[] argv) {
ResourceBundle messageBundle = ResourceBundle.getBundle(
ViewerPropertiesManager.DEFAULT_MESSAGE_BUNDLE);
- // Quit if there where any problems parsing the command line arguments
+ // Quit if there were any problems parsing the command line arguments
if (brokenUsage) {
System.out.println(messageBundle.getString("viewer.commandLin.error"));
System.exit(1);
}
// start the viewer
- run(contentFile, contentURL, printer, messageBundle);
+ run(contentFile, contentURL, printer, pkcs11ConfigPath, pkcs12KeystorePath, messageBundle);
}
/**
@@ -112,11 +133,15 @@ public static void main(String[] argv) {
* @param contentURL URL of a file which will be loaded at runtime, can be
* null.
* @param printer The name of the printer to use, can be null
+ * @param pkcs11ConfigPath set path of a PKCS#11 config file to that will be used by the singer UI.
+ * @param pkcs12KeystorePath set path if a PKCS#12 keystore file that will be used by the signer UI
* @param messageBundle messageBundle to pull strings from
*/
private static void run(String contentFile,
String contentURL,
String printer,
+ String pkcs11ConfigPath,
+ String pkcs12KeystorePath,
ResourceBundle messageBundle) {
// initiate the properties manager.
@@ -133,6 +158,17 @@ private static void run(String contentFile,
ViewModel.setDefaultURL(propertiesManager.getPreferences().get(
ViewerPropertiesManager.PROPERTY_DEFAULT_URL, null));
+ // override any current PKCS paths with command line overrides.
+ if (pkcs12KeystorePath != null && !pkcs12KeystorePath.isEmpty()) {
+ propertiesManager.getPreferences().put(ViewerPropertiesManager.PROPERTY_PKCS12_PROVIDER_KEYSTORE_PATH,
+ pkcs12KeystorePath);
+ propertiesManager.getPreferences().put(ViewerPropertiesManager.PROPERTY_PKCS_KEYSTORE_TYPE, PKCS_12_TYPE);
+ } else if (pkcs11ConfigPath != null && !pkcs11ConfigPath.isEmpty()) {
+ propertiesManager.getPreferences().put(ViewerPropertiesManager.PROPERTY_PKCS11_PROVIDER_CONFIG_PATH,
+ pkcs11ConfigPath);
+ propertiesManager.getPreferences().put(ViewerPropertiesManager.PROPERTY_PKCS_KEYSTORE_TYPE, PKCS_11_TYPE);
+ }
+
// application instance
WindowManager windowManager = WindowManager.createInstance(propertiesManager, messageBundle);
if (contentFile != null && !contentFile.isEmpty()) {
diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/WindowManager.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/WindowManager.java
index 8d7a90252..1c8b21285 100644
--- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/WindowManager.java
+++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/WindowManager.java
@@ -22,6 +22,7 @@
import org.icepdf.ri.common.views.Controller;
import org.icepdf.ri.common.views.DocumentViewController;
import org.icepdf.ri.common.views.DocumentViewControllerImpl;
+import org.icepdf.ri.common.views.annotations.signing.BasicSignatureAppearanceCallback;
import org.icepdf.ri.util.ViewerPropertiesManager;
import javax.swing.*;
@@ -152,6 +153,8 @@ protected Controller commonWindowCreation(boolean isVisible) {
// add interactive mouse link annotation support
controller.getDocumentViewController().setAnnotationCallback(
new MyAnnotationCallback(controller.getDocumentViewController()));
+ // add custom signature appearance stream callback
+ controller.getDocumentViewController().setSignatureAppearanceCallback(new BasicSignatureAppearanceCallback());
controllers.add(controller);
// guild a new swing viewer with remembered view settings.
diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_a_24.png b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_a_24.png
new file mode 100644
index 000000000..c58cb27fb
Binary files /dev/null and b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_a_24.png differ
diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_a_32.png b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_a_32.png
new file mode 100644
index 000000000..74d954e64
Binary files /dev/null and b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_a_32.png differ
diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_i_24.png b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_i_24.png
new file mode 100644
index 000000000..336283729
Binary files /dev/null and b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_i_24.png differ
diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_i_32.png b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_i_32.png
new file mode 100644
index 000000000..cb43e0e41
Binary files /dev/null and b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_i_32.png differ
diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_r_24.png b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_r_24.png
new file mode 100644
index 000000000..0f65ba53a
Binary files /dev/null and b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_r_24.png differ
diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_r_32.png b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_r_32.png
new file mode 100644
index 000000000..0795c4d1c
Binary files /dev/null and b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/signature_annot_r_32.png differ
diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle.properties
index 3a0ae1a9d..6a7072093 100644
--- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle.properties
+++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle.properties
@@ -117,6 +117,8 @@ viewer.toolbar.tool.highlight.label=Highlight
viewer.toolbar.tool.highlight.tooltip=Highlight Annotation Tool
viewer.toolbar.tool.redaction.label=Redaction
viewer.toolbar.tool.redaction.tooltip=Redaction Annotation Tool
+viewer.toolbar.tool.signature.label=Signature
+viewer.toolbar.tool.signature.tooltip=Signature Annotation Tool
viewer.toolbar.tool.strikeOut.label=Strike Out
viewer.toolbar.tool.strikeOut.tooltip=Strike Out Annotation Tool
viewer.toolbar.tool.underline.label=Underline
@@ -284,6 +286,7 @@ viewer.dialog.viewerPreferences.title=Viewer Preferences
viewer.dialog.viewerPreferences.section.general.title=General
viewer.dialog.viewerPreferences.section.annotations.title=Annotations
viewer.dialog.viewerPreferences.section.imaging.title=Imaging
+viewer.dialog.viewerPreferences.section.signatures.title=Signatures
viewer.dialog.viewerPreferences.section.fonts.title=Fonts
viewer.dialog.viewerPreferences.section.advanced.title=Advanced
viewer.dialog.viewerPreferences.section.eximport.title=Export/Import
@@ -319,6 +322,16 @@ viewer.dialog.viewerPreferences.section.imaging.imageReference.scaled.label=Scal
viewer.dialog.viewerPreferences.section.imaging.imageReference.mipMap.label=MIP Map
viewer.dialog.viewerPreferences.section.imaging.imageReference.smothScaled.label=Smooth scaled
viewer.dialog.viewerPreferences.section.imaging.imageReference.blurred.label=Blurred
+## Signatures preferences dialog
+viewer.dialog.viewerPreferences.section.signatures.pkcs.border.label=PKCS Settings
+viewer.dialog.viewerPreferences.section.signatures.pkcs.label=PKCS Format:
+viewer.dialog.viewerPreferences.section.signatures.pkcs.11.label=PKCS#11
+viewer.dialog.viewerPreferences.section.signatures.pkcs.12.label=PKCS#12
+viewer.dialog.viewerPreferences.section.signatures.pkcs.11.config.path.label=PKCS#11 Config File Path:
+viewer.dialog.viewerPreferences.section.signatures.pkcs.12.keystore.path.label=PKCS#12 File Path:
+viewer.dialog.viewerPreferences.section.signatures.pkcs.keystore.path.selection.title=Select a keystore file
+viewer.dialog.viewerPreferences.section.signatures.pkcs.keystore.path.accept.label=Select
+viewer.dialog.viewerPreferences.section.signatures.pkcs.keystore.path.browse.label=Browse
## fonts preferences dialog
viewer.dialog.viewerPreferences.section.fonts.fontCache.border.label=Font Cache
viewer.dialog.viewerPreferences.section.fonts.fontCache.label=Reset Font Cache:
@@ -425,6 +438,10 @@ viewer.dialog.redaction.unburned.title=Unburned Redaction Annotation Detected
viewer.dialog.redaction.unburned.msgs=\
The PDF document contains Redaction Annotations that have not yet been burned into the document. \
\nWould you like to export the document instead of saving?
+## Signature "multiple" signatures warning messages
+viewer.dialog.signature.creation.title=Signature Creation Warning
+viewer.dialog.signature.creation.msgs=\
+ The PDF document already contains a signature, document must be saved before adding another.
## Export Text Dialog
viewer.dialog.exportText.title=Export Document Text
viewer.dialog.exportText.progress.msg=Extracting PDF Text
@@ -868,6 +885,8 @@ viewer.annotation.popup.addAnnotation.strikeout.label=Add Strikeout
viewer.annotation.popup.color.change.label=Change color...
viewer.annotation.popup.text.extract.label=Copy highlighted text
## Signature component
+viewer.annotation.signature.menu.addSignature.label=Add Signature
+viewer.annotation.signature.menu.deleteSignature.label=Delete Signature
viewer.annotation.signature.menu.validateSignature.label=Validate Signature
viewer.annotation.signature.menu.showCertificates.label=Show Certificate Properties
viewer.annotation.signature.menu.signatureProperties.label=Show Signature Properties
@@ -912,6 +931,60 @@ viewer.annotation.signature.properties.dialog.certificateExpired.failure=- Signe
viewer.annotation.signature.properties.dialog.showCertificates.label=Signer's Certificate...
viewer.annotation.signature.properties.dialog.validity.title=Validity Summary
viewer.annotation.signature.properties.dialog.signerInfo.title=Signer Info
+# keystore password callback dialog
+viewer.annotation.signature.creation.keystore.pkcs11.dialog.title=Keystore Pin
+viewer.annotation.signature.creation.keystore.pkcs11.dialog.label=Pin:
+viewer.annotation.signature.creation.keystore.pkcs12.dialog.title=Keystore Password
+viewer.annotation.signature.creation.keystore.pkcs12.dialog.label=Password:
+# Signature annotation creation dialog
+viewer.annotation.signature.creation.dialog.title=Signature Creation
+viewer.annotation.signature.creation.dialog.certificate.tab.title=Certificate
+viewer.annotation.signature.creation.dialog.certificate.selection.label=Please choose the desired certificate
+viewer.annotation.signature.creation.dialog.certificate.table.name.label=Name
+viewer.annotation.signature.creation.dialog.certificate.table.author.label=Email
+viewer.annotation.signature.creation.dialog.certificate.table.validity.label=Validity
+viewer.annotation.signature.creation.dialog.certificate.table.description.label=Description
+viewer.annotation.signature.creation.dialog.certificate.type.description.label=Type of Signature:
+viewer.annotation.signature.creation.dialog.certificate.type.signer.label=Approval (multiple signatures)
+viewer.annotation.signature.creation.dialog.certificate.type.certify.label=Certification
+viewer.annotation.signature.creation.dialog.certificate.visibility.certify.label=Signature/certificate visible
+viewer.annotation.signature.creation.dialog.certificate.location.date.label=Place of signature/Date:
+viewer.annotation.signature.creation.dialog.certificate.name.label=Name:
+viewer.annotation.signature.creation.dialog.certificate.contact.label=Contact:
+viewer.annotation.signature.creation.dialog.certificate.reason.label=Reason:
+viewer.annotation.signature.creation.dialog.certificate.timestamp.label=Insert Timestamp Token (TSA):
+viewer.annotation.signature.creation.dialog.certificate.i18n.label=Language:
+viewer.annotation.signature.creation.dialog.signature.tab.title=Signature
+viewer.annotation.signature.creation.dialog.signature.appearance.title=Appearance
+viewer.annotation.signature.creation.dialog.signature.appearance.font.label=Font:
+viewer.annotation.signature.creation.dialog.signature.appearance.fontSize.label=Size:
+viewer.annotation.signature.creation.dialog.signature.appearance.showText.label=Show Text
+viewer.annotation.signature.creation.dialog.signature.appearance.showSignature.label=Show Signature
+viewer.annotation.signature.creation.dialog.signature.canvas.title=Signature
+viewer.annotation.signature.creation.dialog.signature.imagePath.label=Signature Image:
+viewer.annotation.signature.creation.dialog.signature.selection.title=Select a signature image
+viewer.annotation.signature.creation.dialog.signature.selection.accept.label=Select
+viewer.annotation.signature.creation.dialog.signature.selection.browse.label=Browse
+viewer.annotation.signature.creation.dialog.signature.imageScale.label=Scale:
+viewer.annotation.signature.creation.dialog.close.button.label=Close
+viewer.annotation.signature.creation.dialog.sign.button.label=Add Signature
+# multiple certifier error dialog
+viewer.annotation.signature.creation.dialog.certify.error.title=Signature Error
+viewer.annotation.signature.creation.dialog.certify.error.msg=Certifier signature already exists
+# keystore authentication error dialog
+viewer.annotation.signature.authentication.failure.dialog.certify.error.title=Authentication Error
+viewer.annotation.signature.authentication.failure.dialog.certify.error.msg=Keystore authentication failed
+# keystore not configured error dialog
+viewer.annotation.signature.keystore.failure.dialog.certify.error.title=Authentication Error
+viewer.annotation.signature.keystore.failure.dialog.certify.error.msg=\
+ Keystore has not been configured, please configure keystore in the preferences dialog.
+# Signature annotation creation handler
+viewer.annotation.signature.handler.properties.reason.label=Reason: {0}
+viewer.annotation.signature.handler.properties.reason.approval.label=Approval
+viewer.annotation.signature.handler.properties.reason.certification.label=Certification
+viewer.annotation.signature.handler.properties.contact.label=Contact: {0}
+viewer.annotation.signature.handler.properties.signer.label=Digitally signed by {0}
+viewer.annotation.signature.handler.properties.location.label={0}
## Common Button Labels
viewer.button.ok.label=Ok
viewer.button.ok.mnemonic=O
diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/viewer/res/ICEpdfDefault.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/viewer/res/ICEpdfDefault.properties
index bb7eef051..f2c5daad4 100644
--- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/viewer/res/ICEpdfDefault.properties
+++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/viewer/res/ICEpdfDefault.properties
@@ -36,6 +36,7 @@ document.fixedfont.size = 11
application.toolbar.annotation.selection.enabled=true
application.toolbar.annotation.highlight.enabled=true
application.toolbar.annotation.redaction.enabled=true
+application.toolbar.annotation.signature.enabled=true
application.toolbar.annotation.underline.enabled=false
application.toolbar.annotation.strikeout.enabled=false
application.toolbar.annotation.line.enabled=false
@@ -47,6 +48,9 @@ application.toolbar.annotation.ink.enabled=false
application.toolbar.annotation.freetext.enabled=false
application.toolbar.annotation.text.enabled=true
application.toolbar.annotation.permission.enabled=false
+application.toolbar.annotation.delete.enabled=false
+application.toolbar.bookmark.toolbar.enabled=false
+application.toolbar.annotation.preview.enabled=false
# annotations that default selection tool after creation
application.annotation.highlight.selection.type=0
application.annotation.line.selection.type=0
@@ -56,6 +60,7 @@ application.annotation.circle.selection.type=0
application.annotation.ink.selection.type=0
application.annotation.freetext.selection.type=0
application.annotation.text.selection.type=0
+application.annotation.signature.selection.type=0
# markup annotation (comments)utility pane settings
application.viewer.utility.annotation.sort.column=PAGE
application.viewer.utility.annotation.filter.type.column=ALL
diff --git a/viewer/viewer-awt/src/test/java/org/icepdf/core/pobjects/acroform/SigningTests.java b/viewer/viewer-awt/src/test/java/org/icepdf/core/pobjects/acroform/SigningTests.java
new file mode 100644
index 000000000..9a487ced5
--- /dev/null
+++ b/viewer/viewer-awt/src/test/java/org/icepdf/core/pobjects/acroform/SigningTests.java
@@ -0,0 +1,127 @@
+package org.icepdf.core.pobjects.acroform;
+
+import org.icepdf.core.pobjects.Document;
+import org.icepdf.core.pobjects.PDate;
+import org.icepdf.core.pobjects.acroform.signature.appearance.SignatureType;
+import org.icepdf.core.pobjects.acroform.signature.handlers.Pkcs12SignerHandler;
+import org.icepdf.core.pobjects.acroform.signature.handlers.SimplePasswordCallbackHandler;
+import org.icepdf.core.pobjects.acroform.signature.utils.SignatureUtilities;
+import org.icepdf.core.pobjects.annotations.AnnotationFactory;
+import org.icepdf.core.pobjects.annotations.SignatureWidgetAnnotation;
+import org.icepdf.core.util.Library;
+import org.icepdf.core.util.SignatureManager;
+import org.icepdf.core.util.updater.WriteMode;
+import org.icepdf.ri.common.views.annotations.signing.BasicSignatureAppearanceCallback;
+import org.icepdf.ri.common.views.annotations.signing.SignatureAppearanceModelImpl;
+import org.icepdf.ri.util.FontPropertiesManager;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.util.Date;
+import java.util.Locale;
+
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class SigningTests {
+
+ @BeforeAll
+ public static void init() {
+ FontPropertiesManager.getInstance().loadOrReadSystemFonts();
+ }
+
+ @DisplayName("signatures - should create signed document")
+ @Test
+ public void testXrefTableFullUpdate() {
+
+ try {
+ String keystorePath = "/home/pcorless/dev/cert-test/openssl-keypair/certificate.pfx";
+ String password = "changeit";
+ String certAlias = "senderKeyPair";
+
+ Pkcs12SignerHandler pkcs12SignerHandler = new Pkcs12SignerHandler(new File(keystorePath), certAlias,
+ new SimplePasswordCallbackHandler(password));
+
+ Document document = new Document();
+ InputStream fileUrl = SigningTests.class.getResourceAsStream("/signing/test_print.pdf");
+ document.setInputStream(fileUrl, "test_print.pdf");
+ Library library = document.getCatalog().getLibrary();
+ SignatureManager signatureManager = library.getSignatureDictionaries();
+
+ // Create signature annotation
+ SignatureWidgetAnnotation signatureAnnotation =
+ (SignatureWidgetAnnotation) AnnotationFactory.buildWidgetAnnotation(
+ document.getPageTree().getLibrary(),
+ FieldDictionaryFactory.TYPE_SIGNATURE,
+ new Rectangle(100, 250, 375, 150));
+ document.getPageTree().getPage(0).addAnnotation(signatureAnnotation, true);
+
+ // Add the signatureWidget to catalog
+ InteractiveForm interactiveForm = document.getCatalog().getOrCreateInteractiveForm();
+ interactiveForm.addField(signatureAnnotation);
+
+ // set up signer dictionary as the primary certification signer.
+ SignatureDictionary signatureDictionary =
+ SignatureDictionary.getInstance(signatureAnnotation, SignatureType.CERTIFIER);
+ signatureDictionary.setSignerHandler(pkcs12SignerHandler);
+ signatureDictionary.setReason("Approval"); // Approval or certification but technically can be anything
+ signatureDictionary.setDate(PDate.formatDateTime(new Date()));
+ signatureManager.addSignature(signatureDictionary, signatureAnnotation);
+
+ // assign cert metadata to dictionary
+ SignatureUtilities.updateSignatureDictionary(signatureDictionary, pkcs12SignerHandler.getCertificate());
+
+ // build basic appearance
+ SignatureAppearanceModelImpl signatureAppearanceModel = new SignatureAppearanceModelImpl(library);
+ signatureAppearanceModel.setLocale(Locale.ENGLISH);
+ signatureAppearanceModel.setName(signatureDictionary.getName());
+ signatureAppearanceModel.setContact(signatureDictionary.getContactInfo());
+ signatureAppearanceModel.setLocation(signatureDictionary.getLocation());
+ signatureAppearanceModel.setSignatureType(signatureDictionary.getReason().equals("Approval") ?
+ SignatureType.SIGNER : SignatureType.CERTIFIER);
+ signatureAppearanceModel.setSignatureImage(createTestSignatureBufferedImage());
+
+ BasicSignatureAppearanceCallback signatureAppearance = new BasicSignatureAppearanceCallback();
+ signatureAppearance.setSignatureAppearanceModel(signatureAppearanceModel);
+ signatureAnnotation.setAppearanceCallback(signatureAppearance);
+ signatureAnnotation.resetAppearanceStream(new AffineTransform());
+
+ // Most common workflow is to add just one signature as we do here
+ File out = new File("./src/test/out/SigningTest_signed_document.pdf");
+ try (BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(out), 8192)) {
+ document.saveToOutputStream(stream, WriteMode.INCREMENT_UPDATE);
+ }
+ // open the signed document
+ Document modifiedDocument = new Document();
+ modifiedDocument.setFile(out.getAbsolutePath());
+
+ } catch (Exception e) {
+ // make sure we have no io errors.
+ e.printStackTrace();
+ fail("should not be any exceptions");
+ }
+ }
+
+ private BufferedImage createTestSignatureBufferedImage() {
+ BufferedImage image = new BufferedImage(150, 50, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D imageGraphics = image.createGraphics();
+ imageGraphics.setStroke(new BasicStroke(2));
+ imageGraphics.setColor(new Color(255, 255, 255));
+ imageGraphics.fillRect(0, 0, 150, 50);
+ imageGraphics.setColor(Color.BLUE);
+ imageGraphics.fillRect(0, 0, 100, 25);
+ imageGraphics.setColor(Color.RED);
+ imageGraphics.drawRect(0, 0, 100, 25);
+ imageGraphics.dispose();
+ return image;
+ }
+
+
+}
diff --git a/viewer/viewer-awt/src/test/resources/signing/test_print.pdf b/viewer/viewer-awt/src/test/resources/signing/test_print.pdf
new file mode 100644
index 000000000..d6d9bc33c
Binary files /dev/null and b/viewer/viewer-awt/src/test/resources/signing/test_print.pdf differ