Example (Java)
The whole project may be fetched as a zip or tape archive.
The code in the listing ViewThenSignAPdfWithPad may be used to view
and then to sign a PDF document with a StepOver signature pad such as the duraSign Pad Brilliance.
It uses code from the listing Common below. A resulting signed document is attached here.
ViewThenSignAPdfWithPad.java
package com.so.net.example; import static com.so.net.DeviceApi.*; import com.so.net.SignApi; import static com.so.net.SignApi.Behaviour.*; import static com.so.net.SignApi.Pdf.*; import static com.so.net.example.Common.*; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; public class ViewThenSignAPdfWithPad { public static void main(String[] args) throws Exception { // This requires // .NET5 with hostfxr, // libgdiplus on Linux and MacOS, // the PDF service, // and the native SignAPI. SignApi.load(); // To connect to a pad on the remote computer "ThinClient" // Communication.useTCP(java.net.InetAddress.getByName("ThinClient"), 8888); // Search for signature device. final List<Device.Name> devices = enumerateDevices(Filter.StepOver); if (devices.isEmpty()) { System.out.println("Exit because no device was found."); return; } // Select the first device. Device.Name firstDevice = devices.get(0); setDevice(firstDevice); // See https://www.stepoverinfo.net/confluence/display/NETDEVAPI/Driver+XML+certificate setTheDriverXmlCertificate(Paths.get("DriverXmlCertificate.xml")); // Loads a PDF document from the file system. byte[] theDocument = Files.readAllBytes(Paths.get("TheDocument.pdf")); loadPdf(theDocument); // View the document on the pad SignApi.viewDocument(0, Zoom.Level.FitWidth, true); // wait until the start-signing-button is clicked // with a timeout of 10 minutes. waitForStartSignButton(600); // Performs an example signature with hardcoded // signature field position, signer info and signing behaviour. exampleSign(); // If the device shall no longer show the document. // setCustomerLogoMode(); // Download and save the signed document. byte[] signedDocument = downloadPdf(); Files.write(Paths.get("SignedDocument.pdf"), signedDocument); // Frees unmanaged resources. close(); } }
The code in the listing ViewAPdfWithPad displays document pages and prints button clicks.
ViewAPdfWithPad
package com.so.net.example; import static com.so.net.DeviceApi.*; import com.so.net.SignApi; import static com.so.net.SignApi.Pdf.*; import static com.so.net.example.Common.*; import static java.lang.Math.*; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; import java.util.Optional; public class ViewAPdfWithPad { public static void main(String[] args) throws Exception { // This requires // .NET5 with hostfxr, // libgdiplus on Linux and MacOS, // the PDF service, // and the native SignAPI. SignApi.load(); final List<Device.Name> devices = enumerateDevices(Filter.StepOver); if (devices.isEmpty()) { System.out.println("Exit because no device was found."); return; } // Select the first device. Device.Name firstDevice = devices.get(0); setDevice(firstDevice); // Loads a PDF document from the file system. byte[] theDocument = Files.readAllBytes(Paths.get("TheDocument.pdf")); loadPdf(theDocument); // View the document on the pad // wait and print button clicks // and stop if the start signature button was clicked // or stop if no button is clicked for more than 10 minutes. showAndEnableAllButtonsInTheDocViewMode(); printButtonClicksInDocViewMode(600, 0); // Frees unmanaged resources. close(); } static void printButtonClicksInDocViewMode(int timeout, int page) { int numberOfPages = getNumberOfPages().orElse(0); final int pageInRange = max(0, min(numberOfPages - 1, page)); renderPage(pageInRange, 96) .ifPresent(pageImage -> DocumentViewing.start(pageImage, 1 + pageInRange, numberOfPages)); Optional<Button.Kind> buttonKind; try { buttonKind = waitForButton(timeout); } catch (InterruptedException ex) { return; } if (buttonKind.isPresent()) { Button.Kind kind = buttonKind.get(); printButton(kind); int nextPage = pageInRange; if (Button.Kind.Next == kind) { nextPage++; } else if (Button.Kind.Prev == kind) { nextPage--; } else if (Button.Kind.StartSignature == kind) { System.out.println("Good bye."); return; } printButtonClicksInDocViewMode(timeout, nextPage); } } static void printButton(Button.Kind buttonKind) { System.out.println("Button " + buttonKind.toString() + " was clicked."); } }
Common functions.
Common.java with exampleSign.
package com.so.net.example; import com.so.net.DeviceApi; import com.so.net.DeviceApi.*; import com.so.net.SignApi; import static com.so.net.SignApi.Pdf.*; import java.awt.Color; import java.util.Optional; import static com.so.net.SignApi.*; import com.so.net.SignApi.Behaviour; import static java.lang.Math.*; import java.nio.file.Path; public class Common { static class ClickedButton { Optional<Button.Kind> kind; } static Optional<Button.Kind> waitForButton(int timeoutSeconds) throws InterruptedException { ClickedButton obj = new ClickedButton(); DeviceApi.Button.setCallback(e -> { obj.kind = Optional.of(e.kind); notify(obj); }); wait(obj, timeoutSeconds); return obj.kind; } static void waitForStartSignButton(int timeoutSeconds) throws InterruptedException { Object obj = new Object(); DeviceApi.Button.setCallback(e -> { if (DeviceApi.Button.Kind.StartSignature == e.kind) { notify(obj); } }); wait(obj, timeoutSeconds); } static void wait(Object obj, int timeoutSeconds) throws InterruptedException { synchronized (obj) { obj.wait(timeoutSeconds * 1000); } } static void notify(Object obj) { synchronized (obj) { obj.notifyAll(); } } static boolean exampleSign() { Behaviour behaviour = Behaviour.Builder.getDefault() .signInDocument(Behaviour.DocSign.Rectangle.Builder // The rectangle should be at least 40mm wide and 30mm high. // and place the signature rectangle on the device display // with a distance of 10mm to the lower and the left display edge. .fromBottomLeft(10, 10, 40, 30) // The document shall be shown on the pad while signing .withColor(Color.LIGHT_GRAY) // The rectangle shall be highlighted by a red border .withBorderColor(Color.RED) // which shall be 5 dots wide. .withBorderWidth(5)) .build().get(); // Choose a field name, field position and signature info. FieldName fieldName = FieldName.of(null); // Choose the position of the new signature field. int page = 0; FieldPosition pageDim = getPageDimensions().get(page); // The default length unit is inch/72. double width = 4 * 72; //4 inches wide double height = 2 * 72; //2 inches high double row0 = max(pageDim.getHeight() / 2 - height / 2, 0); double column0 = max(pageDim.getWidth() / 2 - width / 2, 0); double row1 = min(row0 + height / 2, pageDim.getHeight()); double column1 = min(column0 + width / 2, pageDim.getWidth()); FieldPosition fieldPosition = FieldPosition.of(row0, column0, row1, column1, page); // Adds a signature field with the given field name and position. addSignatureField(fieldName, fieldPosition); // This queries for the signature field just added. SignatureField signatureField = getSignatureField(fieldName).get(); // A signature field can have these entries. // They are not mandatory. Pass an empty string or null to skip a field. final String name = "Signer's name."; final String reason = "Reason for signing."; final String location = "The location."; final String contactInfo = "The contact info."; SignatureInfo signatureInfo = SignatureInfo.of(name, reason, location, contactInfo); // Signs this signature field with the selected pad. return sign(signatureField, signatureInfo, behaviour); } static Optional<SignApi.Pdf.SignatureField> getSignatureField(SignApi.Pdf.FieldName fieldName) { return getAcroFormFields().stream() .filter(field -> fieldName.equals(field.getFieldName())) .filter(field -> field instanceof SignApi.Pdf.SignatureField) .map(field -> (SignApi.Pdf.SignatureField) field) .findFirst(); } static void setTheDriverXmlCertificate(Path certPath) { boolean fileExists = certPath.toFile().exists(); if (!fileExists) { System.out.println(certPath.toAbsolutePath().toString() + " does not exist."); } boolean success = DeviceApi.DriverXmlCertificate.set(certPath); if (!success) { System.out.println("Failed to set the driver XML certificate."); } } static void showAndEnableAllButtonsInTheDocViewMode() { // The device can show document pages in the DocView mode. DeviceApi.Button.Bar.get(DeviceApi.Button.Bar.Mode.DocView) .ifPresent(bar -> { bar.configs.forEach(Common::showAndEnable); bar.set(); }); } static void showAndEnable(Button.Config buttonConfig) { buttonConfig.setEnabled(true); buttonConfig.setVisible(true); } }
The POM of the example project.
Example pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.so</groupId> <artifactId>net-example</artifactId> <version>0.0.2</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <repositories> <!-- https://www.stepoverinfo.net/confluence/display/NETSIGNAPI/Install+with+Maven --> <repository> <id>nexus-stepover-maven-public</id> <url>https://nexus.stepover.de:8043/repository/maven-public/</url> </repository> </repositories> <dependencies> <dependency> <groupId>com.so</groupId> <artifactId>net</artifactId> <version>1.0.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <mainClass>com.so.net.example.ViewThenSignAPdfWithPad</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build> </project>