diff options
Diffstat (limited to 'pdf-as-pdfbox-2/src/main/java')
8 files changed, 853 insertions, 1044 deletions
diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/PDFBOXObject.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/PDFBOXObject.java index 891ebe39..53b87d86 100644 --- a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/PDFBOXObject.java +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/PDFBOXObject.java @@ -1,14 +1,10 @@ package at.gv.egiz.pdfas.lib.impl.pdfbox2; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; import javax.activation.DataSource; -import org.apache.pdfbox.io.MemoryUsageSetting; import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.font.PDFont; import at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2.PDFAsFontCache; import at.gv.egiz.pdfas.lib.impl.status.OperationStatus; @@ -17,9 +13,7 @@ import at.gv.egiz.pdfas.lib.impl.status.PDFObject; public class PDFBOXObject extends PDFObject { private PDDocument doc; - - private Map<String, PDFont> fontCache = new HashMap<String, PDFont>(); - + private PDFAsFontCache sigBlockFontCache = new PDFAsFontCache(); public PDFAsFontCache getSigBlockFontCache() { @@ -54,11 +48,6 @@ public class PDFBOXObject extends PDFObject { } } - private MemoryUsageSetting getMemoryUsageSettings() { - // TODO: allow fine tuning of memory usage (divided main memory vs file memory) - return MemoryUsageSetting.setupMainMemoryOnly(); - } - public void setOriginalDocument(DataSource originalDocument) throws IOException { this.originalDocument = originalDocument; if(doc != null) { diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/PDFBoxPlaceholderExtractor.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/PDFBoxPlaceholderExtractor.java index 63b006bf..ad874bc0 100644 --- a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/PDFBoxPlaceholderExtractor.java +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/PDFBoxPlaceholderExtractor.java @@ -1,5 +1,7 @@ package at.gv.egiz.pdfas.lib.impl.pdfbox2.placeholder; +import java.io.IOException; + import at.gv.egiz.pdfas.common.exceptions.PDFIOException; import at.gv.egiz.pdfas.common.exceptions.PdfAsException; import at.gv.egiz.pdfas.lib.impl.pdfbox2.PDFBOXObject; @@ -7,9 +9,6 @@ import at.gv.egiz.pdfas.lib.impl.placeholder.PlaceholderExtractor; import at.gv.egiz.pdfas.lib.impl.placeholder.SignaturePlaceholderData; import at.gv.egiz.pdfas.lib.impl.status.PDFObject; -import java.io.IOException; -import java.util.List; - public class PDFBoxPlaceholderExtractor implements PlaceholderExtractor { @@ -18,27 +17,8 @@ public class PDFBoxPlaceholderExtractor implements PlaceholderExtractor { if (doc instanceof PDFBOXObject) { PDFBOXObject object = (PDFBOXObject) doc; try { - SignaturePlaceholderExtractor extractor = new SignaturePlaceholderExtractor(placeholderId, - matchMode, object.getDocument()); - return extractor.extract(object.getDocument(), - placeholderId, matchMode); - } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e2) { - throw new PDFIOException("error.pdf.io.04", e2); - } - - } - throw new PdfAsException("INVALID STATE"); - } - - @Override - public List<SignaturePlaceholderData> extractList(PDFObject doc, String placeholderId, int matchMode) throws PdfAsException { - if (doc instanceof PDFBOXObject) { - PDFBOXObject object = (PDFBOXObject) doc; - try { - SignaturePlaceholderExtractor extractor = new SignaturePlaceholderExtractor(placeholderId, - matchMode, object.getDocument()); - return extractor.extractList(object.getDocument(), - placeholderId, matchMode); + SignaturePlaceholderExtractor extractor = new SignaturePlaceholderExtractor(); + return extractor.extract(object.getDocument(), placeholderId, matchMode); } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e2) { throw new PDFIOException("error.pdf.io.04", e2); } diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/SignatureFieldsAndPlaceHolderExtractor.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/SignatureFieldsAndPlaceHolderExtractor.java index 609f8254..8e5e5d4e 100644 --- a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/SignatureFieldsAndPlaceHolderExtractor.java +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/SignatureFieldsAndPlaceHolderExtractor.java @@ -1,106 +1,58 @@ package at.gv.egiz.pdfas.lib.impl.pdfbox2.placeholder; -import at.gv.egiz.pdfas.lib.impl.placeholder.PlaceholderExtractorConstants; -import at.gv.egiz.pdfas.lib.impl.placeholder.SignaturePlaceholderData; +import java.util.ArrayList; +import java.util.List; + import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDField; import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import at.gv.egiz.pdfas.lib.impl.placeholder.PlaceholderExtractorConstants; +import at.gv.egiz.pdfas.lib.impl.placeholder.SignaturePlaceholderData; public class SignatureFieldsAndPlaceHolderExtractor { - //Search for empty signature fields - public static List<String> findEmptySignatureFields(PDDocument doc) - { - PDSignature signature; - List<PDField> signatureField; - List<String> signatureFieldNames = new ArrayList<>(); - PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm(); - if (acroForm != null) { - signatureField = acroForm.getFields(); - for (PDField pdField : signatureField) { - if(pdField instanceof PDSignatureField && pdField.getPartialName()!=null) - { - signature = ((PDSignatureField) pdField).getSignature(); - if(signature == null) signatureFieldNames.add(pdField.getPartialName()); - } - } - } - return signatureFieldNames; - } - /* - Needed by PDF-OVER - */ - - /** - * Returns the next unused signature placeholder - * @param doc The document to be searched for signature placeholders - * @return The next unused signature placeholder or null in case there is none - */ - public static SignaturePlaceholderData getNextUnusedSignaturePlaceHolder(PDDocument doc) { - try { - String placeholderId = "1"; - int mode = PlaceholderExtractorConstants.PLACEHOLDER_MATCH_MODE_SORTED; - SignaturePlaceholderExtractor signaturePlaceholderExtractor = new SignaturePlaceholderExtractor( placeholderId, - mode, doc); - List<SignaturePlaceholderData> results = signaturePlaceholderExtractor.extractList(doc, placeholderId, - mode); - if (results == null) { - return null; - } - List<String> used = getExistingSignatureLocations(doc); - //return first not used - for(SignaturePlaceholderData result : results) { - if(!used.contains(result.getPlaceholderName())) - return result; - } - return null; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - public static SignaturePlaceholderData getSignaturePlaceHolder(PDDocument doc, String placeholderId, - int mode) { - try { - SignaturePlaceholderExtractor signaturePlaceholderExtractor = new SignaturePlaceholderExtractor( placeholderId, - mode, doc); - return signaturePlaceholderExtractor.extract(doc, placeholderId, mode); - } catch (Exception e) { - e.printStackTrace(); - return null; + // Search for empty signature fields + public static List<String> findEmptySignatureFields(PDDocument doc) { + PDSignature signature; + List<PDField> signatureField; + final List<String> signatureFieldNames = new ArrayList<>(); + final PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm(); + if (acroForm != null) { + signatureField = acroForm.getFields(); + for (final PDField pdField : signatureField) { + if (pdField instanceof PDSignatureField && pdField.getPartialName() != null) { + signature = ((PDSignatureField) pdField).getSignature(); + if (signature == null) { + signatureFieldNames.add(pdField.getPartialName()); + } } + } } - - public static List<SignaturePlaceholderData> getSignaturePlaceHolderList(PDDocument doc, String placeholderId, int mode) { - try { - SignaturePlaceholderExtractor signaturePlaceholderExtractor = new SignaturePlaceholderExtractor( placeholderId, - mode, doc); - return signaturePlaceholderExtractor.extractList(doc, placeholderId, mode); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - public static List<String> getExistingSignatureLocations(PDDocument doc) { - List<String> existingLocations = new ArrayList<>(); - try { - List <PDSignature> pdSignatureList = doc.getSignatureDictionaries(); - if(pdSignatureList.size() != 0) { - for(PDSignature sig : pdSignatureList) { - existingLocations.add(sig.getLocation()); - } - } - } catch (IOException e) { - e.printStackTrace(); - } - return existingLocations; + return signatureFieldNames; + } + /* + * Needed by PDF-OVER + */ + + /** + * Returns the next unused signature placeholder + * + * @param doc The document to be searched for signature placeholders + * @return The next unused signature placeholder or null in case there is none + */ + public static SignaturePlaceholderData getNextUnusedSignaturePlaceHolder(PDDocument doc) { + try { + final String placeholderId = "1"; + final int mode = PlaceholderExtractorConstants.PLACEHOLDER_MATCH_MODE_SORTED; + final SignaturePlaceholderExtractor signaturePlaceholderExtractor = new SignaturePlaceholderExtractor(); + return signaturePlaceholderExtractor.extract(doc, placeholderId, mode); + + } catch (final Exception e) { + e.printStackTrace(); + return null; } + } } diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/SignaturePlaceholderExtractor.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/SignaturePlaceholderExtractor.java index 6df4f0b1..7cff90d6 100644 --- a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/SignaturePlaceholderExtractor.java +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/SignaturePlaceholderExtractor.java @@ -3,19 +3,19 @@ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a * joint initiative of the Federal Chancellery Austria and Graz University of * Technology. - * + * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * http://www.osor.eu/eupl/ - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and * limitations under the Licence. - * + * * This product combines work with different licenses. See the "NOTICE" text * file for details on the various modules and licenses. * The "NOTICE" text file is part of the distribution. Any derivative works @@ -50,34 +50,27 @@ import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.image.BufferedImage; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Hashtable; +import java.util.List; import java.util.Map.Entry; - -import javassist.bytecode.stackmap.TypeData.ClassName; +import java.util.Properties; +import java.util.Set; +import java.util.Vector; +import java.util.stream.Collectors; import org.apache.pdfbox.contentstream.PDFStreamEngine; import org.apache.pdfbox.contentstream.operator.Operator; import org.apache.pdfbox.contentstream.operator.OperatorProcessor; import org.apache.pdfbox.cos.COSBase; -import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; -import org.apache.pdfbox.pdmodel.font.PDFont; -import org.apache.pdfbox.pdmodel.font.PDFontFactory; import org.apache.pdfbox.pdmodel.graphics.PDXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; import org.apache.pdfbox.util.Matrix; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import at.gv.egiz.pdfas.common.exceptions.PDFIOException; -import at.gv.egiz.pdfas.common.exceptions.PdfAsException; -import at.gv.egiz.pdfas.common.exceptions.PlaceholderExtractionException; -import at.gv.egiz.pdfas.lib.impl.placeholder.PlaceholderExtractorConstants; -import at.gv.egiz.pdfas.lib.impl.placeholder.SignaturePlaceholderContext; -import at.gv.egiz.pdfas.lib.impl.placeholder.SignaturePlaceholderData; -import at.knowcenter.wag.egov.egiz.pdf.TablePos; import com.google.zxing.BarcodeFormat; import com.google.zxing.BinaryBitmap; @@ -90,425 +83,432 @@ import com.google.zxing.Result; import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.common.HybridBinarizer; +import at.gv.egiz.pdfas.common.exceptions.PDFIOException; +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +import at.gv.egiz.pdfas.common.exceptions.PlaceholderExtractionException; +import at.gv.egiz.pdfas.lib.impl.placeholder.PlaceholderExtractorConstants; +import at.gv.egiz.pdfas.lib.impl.placeholder.SignaturePlaceholderData; +import at.knowcenter.wag.egov.egiz.pdf.TablePos; +import javassist.bytecode.stackmap.TypeData.ClassName; +import lombok.extern.slf4j.Slf4j; + /** * Extract all relevant information from a placeholder image. * * @author exthex * */ -public class SignaturePlaceholderExtractor extends PDFStreamEngine implements PlaceholderExtractorConstants{ - /** - * The log. - */ - private static Logger logger = LoggerFactory - .getLogger(SignaturePlaceholderExtractor.class); - - private List<SignaturePlaceholderData> placeholders = new ArrayList<>(); - private int currentPage = 0; - private PDDocument doc; - - protected SignaturePlaceholderExtractor(String placeholderId, - int placeholderMatchMode, PDDocument doc) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException { - super(); - - final Properties properties = new Properties(); - properties.load(ClassName.class.getClassLoader().getResourceAsStream("placeholder/pdfbox-reader-2.properties")); - - Set<Entry<Object, Object>> entries = properties.entrySet(); - for(Entry<Object, Object> entry:entries){ - String processorClassName = (String)entry.getValue(); - Class<?> klass = Class.forName( processorClassName ); - org.apache.pdfbox.contentstream.operator.OperatorProcessor processor = - (OperatorProcessor) klass.newInstance(); - - addOperator( processor ); - } - this.doc = doc; - } - - /** - * Search the document for placeholder images and possibly included - * additional info.<br/> - * Searches only for the first placeholder page after page from top. - * - * @return all available info from the first found placeholder. - * @throws PdfAsException - * if the document could not be read. - * @throws PlaceholderExtractionException - * if STRICT matching mode was requested and no suitable - * placeholder could be found. - */ - public SignaturePlaceholderData extract(PDDocument doc, - String placeholderId, int matchMode) throws PdfAsException { - SignaturePlaceholderContext.setSignaturePlaceholderData(null); -// SignaturePlaceholderExtractor extractor; -// try { -// extractor = new SignaturePlaceholderExtractor(placeholderId, -// matchMode, doc); -// } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e2) { -// throw new PDFIOException("error.pdf.io.04", e2); -// } - - int pageNr = 0; - for(PDPage page : doc.getPages()){ - pageNr++; - - try { - setCurrentPage(pageNr); - if(page.getContents() != null && page.getResources() != null && page.getContentStreams() != null) { - processPage(page); //TODO: pdfbox2 - right? - - } - SignaturePlaceholderData ret = matchPlaceholderPage( - placeholders, placeholderId, matchMode); - if (ret != null) { - SignaturePlaceholderContext - .setSignaturePlaceholderData(ret); - return ret; - } - } catch (IOException e1) { - throw new PDFIOException("error.pdf.io.04", e1); - } catch(Throwable e) { - throw new PDFIOException("error.pdf.io.04", e); - } - } - if (placeholders.size() > 0) { - SignaturePlaceholderData ret = matchPlaceholderDocument( - placeholders, placeholderId, matchMode); - SignaturePlaceholderContext.setSignaturePlaceholderData(ret); - return ret; - } - // no placeholders found, apply strict mode if set - if (matchMode == PLACEHOLDER_MATCH_MODE_STRICT) { - throw new PlaceholderExtractionException("error.pdf.stamp.09"); - } - - return null; - } - - public List<SignaturePlaceholderData> extractList(PDDocument doc, - String placeholderId, int matchMode) throws PdfAsException { - SignaturePlaceholderContext.setSignaturePlaceholderData(null); - - int pageNr = 0; - for(PDPage page : doc.getPages()){ - pageNr++; - - try { - setCurrentPage(pageNr); - if(page.getContents() != null && page.getResources() != null && page.getContentStreams() != null) { - processPage(page); //TODO: pdfbox2 - right? - - } - SignaturePlaceholderData ret = matchPlaceholderPage( - placeholders, placeholderId, matchMode); - if (ret != null) { - SignaturePlaceholderContext - .setSignaturePlaceholderData(ret); - return placeholders; - } - } catch (IOException e1) { - throw new PDFIOException("error.pdf.io.04", e1); - } catch(Throwable e) { - throw new PDFIOException("error.pdf.io.04", e); - } - } - if (placeholders.size() > 0) { - SignaturePlaceholderData ret = matchPlaceholderDocument( - placeholders, placeholderId, matchMode); - SignaturePlaceholderContext.setSignaturePlaceholderData(ret); - return placeholders; - } - // no placeholders found, apply strict mode if set - if (matchMode == PLACEHOLDER_MATCH_MODE_STRICT) { - throw new PlaceholderExtractionException("error.pdf.stamp.09"); - } - return null; - } - - private SignaturePlaceholderData matchPlaceholderDocument( - List<SignaturePlaceholderData> placeholders, String placeholderId, - int matchMode) throws PlaceholderExtractionException { - - if (matchMode == PLACEHOLDER_MATCH_MODE_STRICT) - throw new PlaceholderExtractionException("error.pdf.stamp.09"); - - if (placeholders.size() == 0) - return null; - - if (matchMode == PLACEHOLDER_MATCH_MODE_SORTED) { - // sort all placeholders by the id string if all ids are null do nothing - SignaturePlaceholderData currentFirstSpd = null; - for (int i = 0; i < placeholders.size(); i++) { - SignaturePlaceholderData spd = placeholders.get(i); - if (spd.getId() != null) { - if(currentFirstSpd == null) { - currentFirstSpd = spd; - logger.debug("Setting new current ID: {}", - currentFirstSpd.getId()); - } else { - String currentID = currentFirstSpd.getId(); - String testID = spd.getId(); - logger.debug("Testing placeholder current: {} compare to {}", - currentID, testID); - if(testID.compareToIgnoreCase(currentID) < 0) { - currentFirstSpd = spd; - logger.debug("Setting new current ID: {}", - testID); - } - } - } - } - - if(currentFirstSpd != null) { - logger.info("Running Placeholder sorted mode: using id: {}", currentFirstSpd.getId()); - return currentFirstSpd; - } else { - logger.info("Running Placeholder sorted mode: no placeholder with id found, fallback to first placeholder"); - } - } - - for (int i = 0; i < placeholders.size(); i++) { - SignaturePlaceholderData spd = placeholders.get(i); - if (spd.getId() == null) - return spd; - } - - if (matchMode == PLACEHOLDER_MATCH_MODE_LENIENT) - return placeholders.get(0); - - return null; - } - - private SignaturePlaceholderData matchPlaceholderPage( - List<SignaturePlaceholderData> placeholders, String placeholderId, - int matchMode) { - - if(matchMode == PLACEHOLDER_MATCH_MODE_SORTED) - return null; - - if (placeholders.size() == 0) - return null; - for (int i = 0; i < placeholders.size(); i++) { - SignaturePlaceholderData data = placeholders.get(i); - if (placeholderId != null && placeholderId.equals(data.getId())) - return data; - if (placeholderId == null && data.getId() == null) - return data; - } - return null; - } - - - - private void setCurrentPage(int pageNr) { - this.currentPage = pageNr; - } - - @Override - protected void processOperator(Operator operator, List<COSBase> arguments) - throws IOException { - String operation = operator.getName(); - if (operation.equals("Do")) { - COSName objectName = (COSName) arguments.get(0); - PDXObject xobject = (PDXObject) getResources().getXObject(objectName); - if (xobject instanceof PDImageXObject) { - try { - PDImageXObject image = (PDImageXObject) xobject; - SignaturePlaceholderData data = checkImage(image); - if (data != null) { - PDPage page = getCurrentPage(); - Matrix ctm = getGraphicsState() - .getCurrentTransformationMatrix(); - int pageRotation = page.getRotation(); - pageRotation = pageRotation % 360; - double rotationInRadians = Math.toRadians(pageRotation);//(page.findRotation() * Math.PI) / 180; - - AffineTransform rotation = new AffineTransform(); - rotation.setToRotation(rotationInRadians); - AffineTransform rotationInverse = rotation - .createInverse(); - Matrix rotationInverseMatrix = new Matrix(); - rotationInverseMatrix - .setFromAffineTransform(rotationInverse); - Matrix rotationMatrix = new Matrix(); - rotationMatrix.setFromAffineTransform(rotation); - - Matrix unrotatedCTM = ctm - .multiply(rotationInverseMatrix); - - float x = unrotatedCTM.getXPosition(); - float yPos = unrotatedCTM.getYPosition(); - float yScale = unrotatedCTM.getScaleY(); - float y = yPos + yScale; - float w = unrotatedCTM.getScaleX(); - - logger.debug("Page height: {}", page.getCropBox().getHeight()); - logger.debug("Page width: {}", page.getCropBox().getWidth()); - - if(pageRotation == 90) { - y = page.getCropBox().getWidth() - (y * (-1)); - } else if(pageRotation == 180) { - x = page.getCropBox().getWidth() + x; - y = page.getCropBox().getHeight() - (y * (-1)); - } else if(pageRotation == 270) { - x = page.getCropBox().getHeight() + x; - } - - String posString = "p:" + currentPage + ";x:" + x - + ";y:" + y + ";w:" + w; - - logger.debug("Found Placeholder at: {}", posString); - try { - data.setTablePos(new TablePos(posString)); - data.setPlaceholderName(objectName.getName()); - placeholders.add(data); - } catch (PdfAsException e) { - throw new IOException(); - } - } - } catch (NoninvertibleTransformException e) { - throw new IOException(e); - } - } - } else { - super.processOperator(operator, arguments); - } - } - - private Map<String, PDFont> fonts; - - //TODO: pdfbox2 - was override - public Map<String, PDFont> getFonts() { - if (fonts == null) { - // at least an empty map will be returned - // TODO we should return null instead of an empty map - fonts = new HashMap<String, PDFont>(); - if(this.getResources() != null && this.getResources().getCOSObject() != null) { - COSDictionary fontsDictionary = (COSDictionary) this.getResources().getCOSObject().getDictionaryObject(COSName.FONT); - if (fontsDictionary == null) { - // ignore we do not want to set anything, never when creating a signature!!!!! - //fontsDictionary = new COSDictionary(); - //this.getResources().getCOSDictionary().setItem(COSName.FONT, fontsDictionary); +@Slf4j +public class SignaturePlaceholderExtractor extends PDFStreamEngine implements PlaceholderExtractorConstants { + + private final List<SignaturePlaceholderData> placeholders = new ArrayList<>(); + private int currentPage = 0; + + protected SignaturePlaceholderExtractor() throws IOException, ClassNotFoundException, + InstantiationException, IllegalAccessException { + super(); + + final Properties properties = new Properties(); + properties.load(ClassName.class.getClassLoader().getResourceAsStream( + "placeholder/pdfbox-reader-2.properties")); + + final Set<Entry<Object, Object>> entries = properties.entrySet(); + for (final Entry<Object, Object> entry : entries) { + final String processorClassName = (String) entry.getValue(); + final Class<?> klass = Class.forName(processorClassName); + final org.apache.pdfbox.contentstream.operator.OperatorProcessor processor = + (OperatorProcessor) klass.newInstance(); + + addOperator(processor); + + } + } + + /** + * Search the document for placeholder images and possibly included additional + * info.<br/> + * Searches only for the first placeholder page after page from top. + * + * @return available info from the first found placeholder. + * @throws PdfAsException if the document could not be read. + * @throws PlaceholderExtractionException if STRICT matching mode was requested + * and no suitable placeholder could be + * found. + */ + public SignaturePlaceholderData extract(PDDocument doc, + String placeholderId, int matchMode) throws PdfAsException { + + List<String> extistingSignatureNames = existingExistingSignatureNames(doc); + + + int pageNr = 0; + for (final PDPage page : doc.getPages()) { + pageNr++; + + try { + this.currentPage = pageNr; + if (page.getContents() != null && page.getResources() != null && page.getContentStreams() != null) { + processPage(page); // TODO: pdfbox2 - right? + + } + + final SignaturePlaceholderData ret = matchPlaceholderPage( + removeAlreadyUsePlaceholders(placeholders, extistingSignatureNames), placeholderId, matchMode); + if (ret != null) { + return ret; + + } + + } catch (final IOException e1) { + throw new PDFIOException("error.pdf.io.04", e1); + + } catch (final Throwable e) { + throw new PDFIOException("error.pdf.io.04", e); + + } + } + + if (placeholders.size() > 0) { + final SignaturePlaceholderData ret = matchPlaceholderDocument( + removeAlreadyUsePlaceholders(placeholders, extistingSignatureNames), placeholderId, matchMode); + return ret; + + } + // no placeholders found, apply strict mode if set + if (matchMode == PLACEHOLDER_MATCH_MODE_STRICT) { + throw new PlaceholderExtractionException("error.pdf.stamp.09"); + + } + return null; + } + + @Override + protected void processOperator(Operator operator, List<COSBase> arguments) + throws IOException { + final String operation = operator.getName(); + if (operation.equals("Do")) { + final COSName objectName = (COSName) arguments.get(0); + final PDXObject xobject = getResources().getXObject(objectName); + if (xobject instanceof PDImageXObject) { + try { + final PDImageXObject image = (PDImageXObject) xobject; + final SignaturePlaceholderData data = checkImage(image); + if (data != null) { + final PDPage page = getCurrentPage(); + final Matrix ctm = getGraphicsState() + .getCurrentTransformationMatrix(); + int pageRotation = page.getRotation(); + pageRotation = pageRotation % 360; + final double rotationInRadians = Math.toRadians(pageRotation);// (page.findRotation() * Math.PI) / + // 180; + + final AffineTransform rotation = new AffineTransform(); + rotation.setToRotation(rotationInRadians); + final AffineTransform rotationInverse = rotation + .createInverse(); + final Matrix rotationInverseMatrix = new Matrix(); + rotationInverseMatrix + .setFromAffineTransform(rotationInverse); + final Matrix rotationMatrix = new Matrix(); + rotationMatrix.setFromAffineTransform(rotation); + + final Matrix unrotatedCTM = ctm + .multiply(rotationInverseMatrix); + + float x = unrotatedCTM.getXPosition(); + final float yPos = unrotatedCTM.getYPosition(); + final float yScale = unrotatedCTM.getScaleY(); + float y = yPos + yScale; + final float w = unrotatedCTM.getScaleX(); + + log.debug("Page height: {}", page.getCropBox().getHeight()); + log.debug("Page width: {}", page.getCropBox().getWidth()); + + if (pageRotation == 90) { + y = page.getCropBox().getWidth() - y * -1; + } else if (pageRotation == 180) { + x = page.getCropBox().getWidth() + x; + y = page.getCropBox().getHeight() - y * -1; + } else if (pageRotation == 270) { + x = page.getCropBox().getHeight() + x; } - else { - for (COSName fontName : fontsDictionary.keySet()) { - COSBase font = fontsDictionary.getDictionaryObject(fontName); - // data-000174.pdf contains a font that is a COSArray, looks to be an error in the - // PDF, we will just ignore entries that are not dictionaries. - if (font instanceof COSDictionary) { - PDFont newFont = null; - try { - newFont = PDFontFactory.createFont((COSDictionary) font); - } - catch (IOException exception) { - logger.error("error while creating a font", exception); - } - if (newFont != null) { - fonts.put(fontName.getName(), newFont); - } - } - } + + final String posString = "p:" + currentPage + ";x:" + Math.floor(x) + + ";y:" + Math.ceil(y) + ";w:" + Math.ceil(w); + + log.debug("Found Placeholder at: {}", posString); + try { + data.setTablePos(new TablePos(posString)); + data.setPlaceholderName(objectName.getName()); + placeholders.add(data); + + } catch (final PdfAsException e) { + throw new IOException(); + } + } + } catch (final NoninvertibleTransformException e) { + throw new IOException(e); + } + } + } else { + super.processOperator(operator, arguments); + } + } + + private SignaturePlaceholderData matchPlaceholderPage( + List<SignaturePlaceholderData> placeholders, String placeholderId, int matchMode) { + log.debug("Searching requested placeholder:{} with matchMode:{} in single page ... ", placeholderId, matchMode); + + if (placeholders.size() == 0) { + return null; + + } + + // check if find a placeholder with that ID + for (final SignaturePlaceholderData data : placeholders) { + if (placeholderId != null && data.getId() != null + && matchPlaceHolderId(placeholderId, data.getId())) { + return data; + + } + + if (matchMode != PLACEHOLDER_MATCH_MODE_SORTED + && placeholderId == null && data.getId() == null) { + return data; + + } + } + + return null; + } + + private SignaturePlaceholderData matchPlaceholderDocument( + List<SignaturePlaceholderData> placeholders, String placeholderId, + int matchMode) throws PlaceholderExtractionException { + + log.debug("Searching requested placeholder:{} with matchMode:{} on any page ... ", placeholderId, matchMode); + + if (matchMode == PLACEHOLDER_MATCH_MODE_STRICT) { + throw new PlaceholderExtractionException("error.pdf.stamp.09"); + } + + if (placeholders.size() == 0) { + return null; + } + + if (matchMode == PLACEHOLDER_MATCH_MODE_SORTED) { + // sort all placeholders by the id string if all ids are null do nothing + SignaturePlaceholderData currentFirstSpd = null; + for (final SignaturePlaceholderData spd : placeholders) { + if (spd.getId() != null) { + if (currentFirstSpd == null) { + currentFirstSpd = spd; + log.debug("Setting new current ID: {}", + currentFirstSpd.getId()); + } else { + currentFirstSpd = placeHolderIdMatcher(currentFirstSpd, spd); + + } + } + } + + if (currentFirstSpd != null) { + log.info("Running Placeholder sorted mode: using id: {}", currentFirstSpd.getId()); + return currentFirstSpd; + + } else { + log.info( + "Running Placeholder sorted mode: no placeholder with id found, fallback to first placeholder"); + } + } + + for (final SignaturePlaceholderData spd : placeholders) { + if (spd.getId() == null) { + return spd; + } + } + + if (matchMode == PLACEHOLDER_MATCH_MODE_LENIENT) { + return placeholders.get(0); + } + + return null; + } + + private boolean matchPlaceHolderId(String first, String second) { + try { + Integer firstIdInt = Integer.valueOf(first); + Integer secondIdInt = Integer.valueOf(second); + return firstIdInt == secondIdInt; + + } catch (NumberFormatException e) { + log.trace("Can not compare placeholderId's on integer level. Using String compare ... "); + return first.equalsIgnoreCase(second); + + } + + } + + private SignaturePlaceholderData placeHolderIdMatcher(SignaturePlaceholderData currentFirstSpd, + SignaturePlaceholderData spd) { + try { + Integer currentIDInt = Integer.valueOf(currentFirstSpd.getId()); + Integer testIDInt = Integer.valueOf(spd.getId()); + + if (testIDInt < currentIDInt) { + log.debug("Setting new current ID: {}", testIDInt); + return spd; + + } else { + return currentFirstSpd; + + } + } catch (NumberFormatException e) { + log.trace("Can not compare placeholderId's on integer level. Using String compare ... "); + final String currentID = currentFirstSpd.getId(); + final String testID = spd.getId(); + log.debug("Testing placeholder current: {} compare to {}", + currentID, testID); + if (testID.compareToIgnoreCase(currentID) < 0) { + log.debug("Setting new current ID: {}", + testID); + return spd; + + } else { + return currentFirstSpd; + + } + } + } + + private List<String> existingExistingSignatureNames(PDDocument doc) { + final List<String> existingLocations = new ArrayList<>(); + try { + final List<PDSignature> pdSignatureList = doc.getSignatureDictionaries(); + if (pdSignatureList.size() != 0) { + for (final PDSignature sig : pdSignatureList) { + existingLocations.add(sig.getLocation()); + } + } + } catch (final IOException e) { + e.printStackTrace(); + } + return existingLocations; + } + + private List<SignaturePlaceholderData> removeAlreadyUsePlaceholders( + List<SignaturePlaceholderData> placeholders, List<String> existingPlaceholders) { + if (placeholders != null) { + List<SignaturePlaceholderData> result = placeholders.stream() + .filter(el -> !existingPlaceholders.contains(el.getPlaceholderName())) + .collect(Collectors.toList()); + log.debug("Initial found #{} placeholders, but #{} removed because already used.", + placeholders.size(), placeholders.size() - result.size()); + return result; + + } else { + return Collections.emptyList(); + + } + } + + /** + * Checks an image if it is a placeholder for a signature. + * + * @param image + * @return + * @throws IOException + */ + private SignaturePlaceholderData checkImage(PDImageXObject image) + throws IOException { + final BufferedImage bimg = image.getImage(); + if (bimg == null) { + String type = image.getSuffix(); + if (type != null) { + type = type.toUpperCase() + " images"; + } else { + type = "Image type"; + } + log.info("Unable to extract image for QRCode analysis. " + + type + + " not supported. Add additional JAI Image filters to your classpath. Refer to https://jai.dev.java.net. Skipping image."); + return null; + + } + + if (bimg.getHeight() < 10 || bimg.getWidth() < 10) { + log.debug("Image too small for QRCode. Skipping image."); + return null; + } + + final LuminanceSource source = new BufferedImageLuminanceSource(bimg); + final BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); + Result result; + final long before = System.currentTimeMillis(); + try { + final Hashtable<DecodeHintType, Object> hints = new Hashtable<>(); + final Vector<BarcodeFormat> formats = new Vector<>(); + formats.add(BarcodeFormat.QR_CODE); + hints.put(DecodeHintType.POSSIBLE_FORMATS, formats); + result = new MultiFormatReader().decode(bitmap, hints); + + final String text = result.getText(); + String profile = null; + String type = null; + String sigKey = null; + String id = null; + if (text != null) { + if (text.startsWith(QR_PLACEHOLDER_IDENTIFIER)) { + + final String[] data = text.split(";"); + if (data.length > 1) { + for (int i = 1; i < data.length; i++) { + final String kvPair = data[i]; + final String[] kv = kvPair.split("="); + if (kv.length != 2) { + log.debug("Invalid parameter in placeholder data: " + + kvPair); + } else { + if (kv[0] + .equalsIgnoreCase(SignaturePlaceholderData.ID_KEY)) { + id = kv[1]; + } else if (kv[0] + .equalsIgnoreCase(SignaturePlaceholderData.PROFILE_KEY)) { + profile = kv[1]; + } else if (kv[0] + .equalsIgnoreCase(SignaturePlaceholderData.SIG_KEY_KEY)) { + sigKey = kv[1]; + } else if (kv[0] + .equalsIgnoreCase(SignaturePlaceholderData.TYPE_KEY)) { + type = kv[1]; + } + } } + } + return new SignaturePlaceholderData(profile, type, sigKey, + id); + } else { + log.warn("QR-Code found but does not start with \"" + + QR_PLACEHOLDER_IDENTIFIER + + "\". Ignoring QR placeholder."); + } + } + } catch (final ReaderException re) { + if (log.isDebugEnabled()) { + log.debug("Could not decode - not a placeholder. needed: " + + (System.currentTimeMillis() - before)); + } + if (!(re instanceof NotFoundException)) { + if (log.isInfoEnabled()) { + log.info("Failed to decode image", re); } - return fonts; - } - - /** - * Checks an image if it is a placeholder for a signature. - * - * @param image - * @return - * @throws IOException - */ - private SignaturePlaceholderData checkImage(PDImageXObject image) - throws IOException { - BufferedImage bimg = image.getImage(); - if (bimg == null) { - String type = image.getSuffix(); - if (type != null) { - type = type.toUpperCase() + " images"; - } else { - type = "Image type"; - } - logger.info("Unable to extract image for QRCode analysis. " - + type - + " not supported. Add additional JAI Image filters to your classpath. Refer to https://jai.dev.java.net. Skipping image."); - return null; - } - if (bimg.getHeight() < 10 || bimg.getWidth() < 10) { - logger.debug("Image too small for QRCode. Skipping image."); - return null; - } - - LuminanceSource source = new BufferedImageLuminanceSource(bimg); - BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); - Result result; - long before = System.currentTimeMillis(); - try { - Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(); - Vector<BarcodeFormat> formats = new Vector<BarcodeFormat>(); - formats.add(BarcodeFormat.QR_CODE); - hints.put(DecodeHintType.POSSIBLE_FORMATS, formats); - result = new MultiFormatReader().decode(bitmap, hints); - - String text = result.getText(); - String profile = null; - String type = null; - String sigKey = null; - String id = null; - if (text != null) { - if (text.startsWith(QR_PLACEHOLDER_IDENTIFIER)) { - - String[] data = text.split(";"); - if (data.length > 1) { - for (int i = 1; i < data.length; i++) { - String kvPair = data[i]; - String[] kv = kvPair.split("="); - if (kv.length != 2) { - logger.debug("Invalid parameter in placeholder data: " - + kvPair); - } else { - if (kv[0] - .equalsIgnoreCase(SignaturePlaceholderData.ID_KEY)) { - id = kv[1]; - } else if (kv[0] - .equalsIgnoreCase(SignaturePlaceholderData.PROFILE_KEY)) { - profile = kv[1]; - } else if (kv[0] - .equalsIgnoreCase(SignaturePlaceholderData.SIG_KEY_KEY)) { - sigKey = kv[1]; - } else if (kv[0] - .equalsIgnoreCase(SignaturePlaceholderData.TYPE_KEY)) { - type = kv[1]; - } - } - } - } - return new SignaturePlaceholderData(profile, type, sigKey, - id); - } else { - logger.warn("QR-Code found but does not start with \"" - + QR_PLACEHOLDER_IDENTIFIER - + "\". Ignoring QR placeholder."); - } - } - } catch (ReaderException re) { - if (logger.isDebugEnabled()) { - logger.debug("Could not decode - not a placeholder. needed: " - + (System.currentTimeMillis() - before)); - } - if (!(re instanceof NotFoundException)) { - if (logger.isInfoEnabled()) { - logger.info("Failed to decode image", re); - } - } - } catch (ArrayIndexOutOfBoundsException e) { - if (logger.isInfoEnabled()) { - logger.info("Failed to decode image. Probably a zxing bug", e); - } - } - return null; - } + } + } catch (final ArrayIndexOutOfBoundsException e) { + if (log.isInfoEnabled()) { + log.info("Failed to decode image. Probably a zxing bug", e); + } + } + return null; + } } diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/positioning/Positioning.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/positioning/Positioning.java index 13d1ebe6..d1d097aa 100644 --- a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/positioning/Positioning.java +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/positioning/Positioning.java @@ -32,7 +32,9 @@ import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.PDRectangle; import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +import at.gv.egiz.pdfas.common.settings.IProfileConstants; import at.gv.egiz.pdfas.common.settings.ISettings; +import at.gv.egiz.pdfas.common.settings.SignatureProfileSettings; import at.gv.egiz.pdfas.lib.api.IConfigurationConstants; import at.gv.egiz.pdfas.lib.impl.pdfbox2.utils.PdfBoxUtils; import at.gv.egiz.pdfas.lib.impl.stamping.IPDFVisualObject; @@ -64,18 +66,18 @@ public class Positioning { * * @param pos The absolute positioning parameter. If null it is * sought in the profile definition. - * @param signature_type The profile definition of the table to be written. * @param pdfDataSource The pdf. * @param pdf_table The pdf table to be written. * @param settings + * @param signatureProfileSettings Signature-block settings * @return Returns the PositioningInformation. * @throws PdfAsException F.e. */ public static PositioningInstruction determineTablePositioning( - TablePos pos, String signature_type, PDDocument pdfDataSource, - IPDFVisualObject pdf_table, ISettings settings) throws PdfAsException { + TablePos pos, PDDocument pdfDataSource, + IPDFVisualObject pdf_table, ISettings settings, SignatureProfileSettings signatureProfileSettings) throws PdfAsException { return adjustSignatureTableandCalculatePosition(pdfDataSource, - pdf_table, pos, settings); + pdf_table, pos, settings, signatureProfileSettings); } /** @@ -85,12 +87,13 @@ public class Positioning { * @param pdfDataSource The PDF document. * @param pdf_table The PDFPTable to be placed. * @param settings + * @param profilConfig Signature-profile configuration * @return Returns the position where the PDFPTable should be placed. * @throws PdfAsException F.e. */ public static PositioningInstruction adjustSignatureTableandCalculatePosition( final PDDocument pdfDataSource, IPDFVisualObject pdf_table, - TablePos pos, ISettings settings) throws PdfAsException { + TablePos pos, ISettings settings, SignatureProfileSettings profilConfig) throws PdfAsException { PdfBoxUtils.checkPDFPermissions(pdfDataSource); final long numberOfExistingSignatures = getNumberOfExistingSignatures(pdfDataSource); @@ -111,15 +114,9 @@ public class Positioning { } } - make_new_page = checkIfNewPageIsAllowed(make_new_page, numberOfExistingSignatures, settings); - - - if(make_new_page && numberOfExistingSignatures!=0) { - make_new_page = false; - - } - + make_new_page = checkIfNewPageIsAllowed(make_new_page, numberOfExistingSignatures, settings, profilConfig); + final PDPage pdPage = pdfDataSource.getPage(page - 1); PDRectangle cropBox = pdPage.getCropBox(); @@ -216,13 +213,13 @@ public class Positioning { // we do have an empty page or nothing in area above footerline pos_y = page_height - SIGNATURE_MARGIN_VERTICAL; return buildPostitionInfoOnSubpage(pdfDataSource, make_new_page, page, pos_x, pos_y, pos.rotation, - pos.getFooterLine(), table_height, pos, page_height, numberOfExistingSignatures, settings); + pos.getFooterLine(), table_height, pos, page_height, numberOfExistingSignatures, settings, profilConfig); } else { // we do have text take SIGNATURE_MARGIN pos_y = page_height - pre_page_length - SIGNATURE_MARGIN_VERTICAL; return buildPostitionInfoOnSubpage(pdfDataSource, make_new_page, page, pos_x, pos_y, pos.rotation, - pos.getFooterLine(), table_height, pos, page_height, numberOfExistingSignatures, settings); + pos.getFooterLine(), table_height, pos, page_height, numberOfExistingSignatures, settings, profilConfig); } } @@ -245,12 +242,23 @@ public class Positioning { } - private static boolean checkIfNewPageIsAllowed(boolean make_new_page, long numberOfExistingSignatures, ISettings settings) throws PdfAsException { - if(make_new_page && numberOfExistingSignatures!=0) { - log.info("Signature-block would be need a new page, but new pages are not allowed on already signed documents."); - if (isFailOnLessSpaceEnabled(settings)) { - throw new PdfAsException("error.pdf.stamp.12"); + private static boolean isNewPageOnSignedDocumentsEnabled(SignatureProfileSettings profilConfig) { + String value = profilConfig.getValue(IProfileConstants.SIG_NEWPAGE_FORCE); + return Boolean.valueOf(value); + + } + + private static boolean checkIfNewPageIsAllowed(boolean make_new_page, long numberOfExistingSignatures, ISettings settings, + SignatureProfileSettings profilConfig) throws PdfAsException { + if(make_new_page && numberOfExistingSignatures !=0 ) { + log.debug("Signature-block would be need a new page, but new pages are not allowed on already signed documents."); + if (isNewPageOnSignedDocumentsEnabled(profilConfig)) { + log.info("New pages not allowed on already signed documents, but force new page by configuration"); + return make_new_page; + } else if (isFailOnLessSpaceEnabled(settings)) { + throw new PdfAsException("error.pdf.stamp.12"); + } else { log.info("Placing signature-block on last page without free-space checks ... "); return false; @@ -265,10 +273,10 @@ public class Positioning { private static PositioningInstruction buildPostitionInfoOnSubpage(PDDocument pdfDataSource, boolean make_new_page, int page, float pos_x, float pos_y, float rotation, float footer_line, float table_height, TablePos pos, float page_height, - long numberOfExistingSignatures, ISettings settings) throws PdfAsException { + long numberOfExistingSignatures, ISettings settings, SignatureProfileSettings profilConfig) throws PdfAsException { if (pos_y - footer_line <= table_height) { - make_new_page = checkIfNewPageIsAllowed(true, numberOfExistingSignatures, settings); + make_new_page = checkIfNewPageIsAllowed(pos.isPauto(), numberOfExistingSignatures, settings, profilConfig); if (make_new_page) { page++; diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox2/PADESPDFBOXSigner.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox2/PADESPDFBOXSigner.java index 42f16598..36d7dade 100644 --- a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox2/PADESPDFBOXSigner.java +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox2/PADESPDFBOXSigner.java @@ -70,8 +70,6 @@ import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.xmpbox.XMPMetadata; import org.apache.xmpbox.schema.PDFAIdentificationSchema; import org.apache.xmpbox.xml.DomXmpParser; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import at.gv.egiz.pdfas.common.exceptions.PDFASError; import at.gv.egiz.pdfas.common.exceptions.PdfAsException; @@ -83,7 +81,6 @@ import at.gv.egiz.pdfas.lib.api.sign.IPlainSigner; import at.gv.egiz.pdfas.lib.api.sign.SignParameter; import at.gv.egiz.pdfas.lib.impl.ErrorExtractor; import at.gv.egiz.pdfas.lib.impl.SignaturePositionImpl; -import at.gv.egiz.pdfas.lib.impl.configuration.PlaceholderWebConfiguration; import at.gv.egiz.pdfas.lib.impl.configuration.SignatureProfileConfiguration; import at.gv.egiz.pdfas.lib.impl.pdfbox2.PDFBOXObject; import at.gv.egiz.pdfas.lib.impl.pdfbox2.positioning.Positioning; @@ -107,231 +104,123 @@ import at.knowcenter.wag.egov.egiz.pdf.PositioningInstruction; import at.knowcenter.wag.egov.egiz.pdf.TablePos; import at.knowcenter.wag.egov.egiz.table.Table; import iaik.x509.X509Certificate; +import lombok.extern.slf4j.Slf4j; +@Slf4j public class PADESPDFBOXSigner implements IPdfSigner, IConfigurationConstants { - private static final Logger logger = LoggerFactory.getLogger(PADESPDFBOXSigner.class); - private boolean isAdobeSigForm = false; + @Override public void signPDF(PDFObject genericPdfObject, RequestedSignature requestedSignature, PDFASSignatureInterface genericSigner) throws PdfAsException { - PDFAsVisualSignatureProperties properties = null; - List<SignaturePlaceholderData> placeholders; - List<SignaturePlaceholderData> availablePlaceholders; - SignaturePlaceholderData signaturePlaceholderData = null; - - String placeholder_id = ""; - - if (PlaceholderWebConfiguration.getValue(PLACEHOLDER_WEB_ID) != null && !PlaceholderWebConfiguration - .getValue(PLACEHOLDER_WEB_ID).equalsIgnoreCase("")) { - placeholder_id = PlaceholderWebConfiguration.getValue(PLACEHOLDER_WEB_ID); - } - + + boolean isAdobeSigForm = false; + if (!(genericPdfObject instanceof PDFBOXObject)) { - // tODO: - throw new PdfAsException(); + throw new PdfAsException("PDF to signObject is of wrong type: " + genericPdfObject.getClass().getName()); + } - final PDFBOXObject pdfObject = (PDFBOXObject) genericPdfObject; - if (!(genericSigner instanceof PDFASPDFBOXSignatureInterface)) { - // tODO: - throw new PdfAsException(); + throw new PdfAsException("PDF signerObject is of wrong type:" + genericSigner.getClass().getName()); + } - + + final PDFBOXObject pdfObject = (PDFBOXObject) genericPdfObject; final PDFASPDFBOXSignatureInterface signer = (PDFASPDFBOXSignatureInterface) genericSigner; - String pdfaVersion = null; - PDDocument doc = null; SignatureOptions options = new SignatureOptions(); try { - doc = pdfObject.getDocument(); - // if signature already exists dont create new page - final List<PDSignatureField> pdSignatureFieldList = doc.getSignatureFields(); - PDSignature signature; + // sign a PDF with an existing empty signature, as created by the // CreateEmptySignatureForm example. - String sigFieldName = pdfObject.getStatus().getSettings().getValue(SIGNATURE_FIELD_NAME); - signature = findExistingSignature(doc, sigFieldName); + PDSignature signature = findExistingSignature(doc, getSignatureFieldNameConfig(pdfObject)); if (signature == null) { // create signature dictionary signature = new PDSignature(); + } else { isAdobeSigForm = true; + } + // set basic signature parameters signature.setFilter(COSName.getPDFName(signer.getPDFFilter())); signature.setSubFilter(COSName.getPDFName(signer.getPDFSubFilter())); -// SignaturePlaceholderData signaturePlaceholderDataInit = - placeholders = PlaceholderFilter.checkPlaceholderSignatureLocationList(pdfObject.getStatus(), - pdfObject.getStatus().getSettings(), placeholder_id); - -// placeholders = SignaturePlaceholderExtractor.getPlaceholders(); - availablePlaceholders = listAvailablePlaceholders(placeholders, existingSignatureLocations(doc)); - - if (placeholder_id.equalsIgnoreCase("")) { - if (checkAvailablePlaceholders(placeholders, existingSignatureLocations(doc)) != null) { - placeholder_id = checkAvailablePlaceholders(placeholders, existingSignatureLocations(doc)).getId(); - } - } - - if (availablePlaceholders != null) { - signaturePlaceholderData = PlaceholderFilter - .checkPlaceholderSignatureLocation(pdfObject.getStatus(), pdfObject.getStatus().getSettings(), - placeholder_id); - } - - TablePos tablePos = null; - - if (signaturePlaceholderData != null) { - signature.setLocation(signaturePlaceholderData.getPlaceholderName()); - } - - if (signaturePlaceholderData != null) { - // Placeholder found! - placeholders.clear(); - logger.info("Placeholder data found."); - if (signaturePlaceholderData.getProfile() != null) { - logger.debug("Placeholder Profile set to: " + signaturePlaceholderData.getProfile()); - requestedSignature.setSignatureProfileID(signaturePlaceholderData.getProfile()); - } - - tablePos = signaturePlaceholderData.getTablePos(); - if (tablePos != null) { - - final SignatureProfileConfiguration signatureProfileConfiguration = pdfObject.getStatus() - .getSignatureProfileConfiguration(requestedSignature.getSignatureProfileID()); - - final float minWidth = signatureProfileConfiguration.getMinWidth(); - - if (minWidth > 0) { - if (tablePos.getWidth() < minWidth) { - tablePos.width = minWidth; - logger.debug("Correcting placeholder with to minimum width {}", minWidth); - } + signature.setSignDate(Calendar.getInstance()); + log.debug("Signing @ " + signature.getSignDate().getTime().toString()); + + // extract next QR-code placeholder, if exists + SignaturePlaceholderData nextPlaceholderData = PlaceholderFilter.checkPlaceholderSignatureLocation( + pdfObject.getStatus(), pdfObject.getStatus().getSettings(), + pdfObject.getStatus().getSignParamter().getPlaceHolderId()); + + if (nextPlaceholderData != null) { + log.info("Placeholder data found."); + signature.setLocation(nextPlaceholderData.getPlaceholderName()); + + if (nextPlaceholderData.getProfile() != null) { + if (pdfObject.getStatus().getSettings().isValue(IConfigurationConstants.PLACEHOLDER_PROFILE_OVERWRITE, true)) { + log.debug("Placeholder Profile set to: {}", nextPlaceholderData.getProfile()); + requestedSignature.setSignatureProfileID(nextPlaceholderData.getProfile()); + + } else { + log.debug("Placeholder profile over-write is disabled. Using profile from request ... "); + } - logger.debug("Placeholder Position set to: " + tablePos.toString()); - } - } - final SignatureProfileSettings signatureProfileSettings = TableFactory.createProfile( - requestedSignature.getSignatureProfileID(), pdfObject.getStatus().getSettings()); - - // Check if input document is PDF-A conform - if (signatureProfileSettings.isPDFA()) { - final DataSource origDoc = pdfObject.getOriginalDocument(); - final InputStream stream = origDoc.getInputStream(); - // Run PreflightParser for checking conformity// - // runPDFAPreflight(origDoc); + } } + + + final SignatureProfileSettings signatureProfileSettings = + TableFactory.createProfile(requestedSignature.getSignatureProfileID(), pdfObject.getStatus().getSettings()); + + // set signature name final ValueResolver resolver = new ValueResolver(requestedSignature, pdfObject.getStatus()); - - final String signerName = resolver.resolve("SIG_SUBJECT", signatureProfileSettings.getValue( - "SIG_SUBJECT"), - signatureProfileSettings); - + final String signerName = resolver.resolve("SIG_SUBJECT", + signatureProfileSettings.getValue("SIG_SUBJECT"), signatureProfileSettings); signature.setName(signerName); - signature.setSignDate(Calendar.getInstance()); - String signerReason = signatureProfileSettings.getSigningReason(); - - if (signerReason == null) { - signerReason = "PAdES Signature"; - } + // set signature reason + String signerReason = signatureProfileSettings.getSigningReason() != null + ? signatureProfileSettings.getSigningReason() : "PAdES Signature"; signature.setReason(signerReason); - logger.debug("Signing reason: " + signerReason); - - logger.debug("Signing @ " + signer.getSigningDate().getTime().toString()); - // the signing date, needed for valid signature - // signature.setSignDate(signer.getSigningDate()); + log.debug("Signing reason: " + signerReason); signer.setPDSignature(signature); - int signatureSize = 0x1000; - try { - final String reservedSignatureSizeString = signatureProfileSettings.getValue(SIG_RESERVED_SIZE); - if (reservedSignatureSizeString != null) { - signatureSize = Integer.parseInt(reservedSignatureSizeString); - } - logger.debug("Reserving {} bytes for signature", signatureSize); - } catch (final NumberFormatException e) { - logger.warn("Invalid configuration value: {} should be a number using 0x1000", SIG_RESERVED_SIZE); - } - options.setPreferredSignatureSize(signatureSize); - + if (signatureProfileSettings.isPDFA() || signatureProfileSettings.isPDFA3()) { - pdfaVersion = getPDFAVersion(doc); - signatureProfileSettings.setPDFAVersion(pdfaVersion); + signatureProfileSettings.setPDFAVersion(getPDFAVersion(doc)); + } - // Is visible Signature - if (requestedSignature.isVisual()) { - logger.info("Creating visual siganture block"); - - final SignatureProfileConfiguration signatureProfileConfiguration = pdfObject.getStatus() - .getSignatureProfileConfiguration(requestedSignature.getSignatureProfileID()); - - if (tablePos == null) { - // ================================================================ - // PositioningStage (visual) -> find position or use - // fixed - // position - - final String posString = pdfObject.getStatus().getSignParamter().getSignaturePosition(); - - TablePos signaturePos = null; - - final String signaturePosString = signatureProfileConfiguration.getDefaultPositioning(); - - if (signaturePosString != null) { - logger.debug("using signature Positioning: " + signaturePos); - signaturePos = new TablePos(signaturePosString); - } - - logger.debug("using Positioning: " + posString); - - if (posString != null) { - // Merge Signature Position - tablePos = new TablePos(posString, signaturePos); - } else { - // Fallback to signature Position! - tablePos = signaturePos; - } - - if (tablePos == null) { - // Last Fallback default position - tablePos = new TablePos(); - } - } - - // Legacy Modes not supported with pdfbox2 anymore -// boolean legacy32Position = signatureProfileConfiguration.getLegacy32Positioning(); -// boolean legacy40Position = signatureProfileConfiguration.getLegacy40Positioning(); - - // create Table describtion + // prepare basic signature options + options.setPreferredSignatureSize(calculateBlankAreaForSignature(signatureProfileSettings)); + + PDFAsVisualSignatureProperties properties = null; + if (requestedSignature.isVisual()) { + log.info("Creating visual siganture block"); + + final SignatureProfileConfiguration signatureProfileConfiguration = + pdfObject.getStatus().getSignatureProfileConfiguration(requestedSignature.getSignatureProfileID()); + final TablePos tablePos = prepareTablePosition(nextPlaceholderData, signatureProfileConfiguration, + pdfObject.getStatus().getSignParamter().getSignaturePosition()); final Table main = TableFactory.createSigTable(signatureProfileSettings, MAIN, pdfObject.getStatus(), requestedSignature); final IPDFStamper stamper = StamperFactory.createDefaultStamper(pdfObject.getStatus().getSettings()); - final IPDFVisualObject visualObject = stamper.createVisualPDFObject(pdfObject, main); - /* - * PDDocument originalDocument = PDDocument .load(new - * ByteArrayInputStream(pdfObject.getStatus() - * .getPdfObject().getOriginalDocument())); - */ - final PositioningInstruction positioningInstruction = Positioning.determineTablePositioning(tablePos, - "", - doc, visualObject, pdfObject.getStatus().getSettings()); - - logger.debug("Positioning: {}", positioningInstruction.toString()); + doc, visualObject, pdfObject.getStatus().getSettings(), signatureProfileSettings); + log.debug("Positioning: {}", positioningInstruction.toString()); if (!isAdobeSigForm) { if (positioningInstruction.isMakeNewPage()) { @@ -347,11 +236,11 @@ public class PADESPDFBOXSigner implements IPdfSigner, IConfigurationConstants { // handle rotated page final int targetPageNumber = positioningInstruction.getPage(); - logger.debug("Target Page: " + targetPageNumber); + log.debug("Target Page: " + targetPageNumber); final PDPage targetPage = doc.getPages().get(targetPageNumber - 1); final int rot = targetPage.getRotation(); - logger.debug("Page rotation: " + rot); - logger.debug("resulting Sign rotation: " + positioningInstruction.getRotation()); + log.debug("Page rotation: " + rot); + log.debug("resulting Sign rotation: " + positioningInstruction.getRotation()); final SignaturePositionImpl position = new SignaturePositionImpl(); position.setX(positioningInstruction.getX()); @@ -361,44 +250,11 @@ public class PADESPDFBOXSigner implements IPdfSigner, IConfigurationConstants { position.setWidth(visualObject.getWidth()); requestedSignature.setSignaturePosition(position); } - + properties = new PDFAsVisualSignatureProperties(pdfObject.getStatus().getSettings(), pdfObject, (PdfBoxVisualObject) visualObject, positioningInstruction, signatureProfileSettings, false); - properties.buildSignature(); - /* - * ByteArrayOutputStream sigbos = new ByteArrayOutputStream(); - * sigbos.write(StreamUtils.inputStreamToByteArray (properties - * .getVisibleSignature())); sigbos.close(); - */ - - /* - * if (signaturePlaceholderData != null) { - * - * InputStream fis = - * PADESPDFBOXSigner.class.getResourceAsStream("/placeholder/empty.jpg"); - * PDImageXObject img = JPEGFactory.createFromStream(doc, fis); - * - * img.getCOSObject().setNeedToBeUpdated(true); // PDDocumentCatalog root = - * doc.getDocumentCatalog(); // PDPageNode rootPages = root.getPages(); // - * List<PDPage> kids = new ArrayList<PDPage>(); // rootPages.getAllKids(kids); - * int pageNumber = positioningInstruction.getPage(); PDPage page = - * doc.getPages().get(pageNumber - 1); - * - * logger.info("Placeholder name: " + - * signaturePlaceholderData.getPlaceholderName()); COSDictionary - * xobjectsDictionary = (COSDictionary) page.getResources().getCOSObject() - * .getDictionaryObject(COSName.XOBJECT); - * - * - * xobjectsDictionary.setItem(signaturePlaceholderData.getPlaceholderName(), - * img); xobjectsDictionary.setNeedToBeUpdated(true); - * page.getResources().getCOSObject().setNeedToBeUpdated(true); - * logger.info("Placeholder name: " + - * signaturePlaceholderData.getPlaceholderName()); } - */ - if (signatureProfileSettings.isPDFA() || signatureProfileSettings.isPDFA3()) { final PDDocumentCatalog root = doc.getDocumentCatalog(); @@ -418,39 +274,30 @@ public class PADESPDFBOXSigner implements IPdfSigner, IConfigurationConstants { root.setOutputIntents(oi); root.getCOSObject().setNeedToBeUpdated(true); - logger.info("added Output Intent"); + log.info("added Output Intent"); } catch (final Throwable e) { e.printStackTrace(); throw new PdfAsException("Failed to add Output Intent", e); + } finally { IOUtils.closeQuietly(colorProfile); + } } + options.setPage(positioningInstruction.getPage() - 1); options.setVisualSignature(properties.getVisibleSignature()); + } doc.addSignature(signature, signer, options); - - if (sigFieldName == null) { - sigFieldName = "PDF-AS Signatur"; - } - final int count = PdfBoxUtils.countSignatures(doc, sigFieldName); - - sigFieldName = sigFieldName + count; - - final PDAcroForm acroFormm = doc.getDocumentCatalog().getAcroForm(); - - // PDStructureTreeRoot pdstRoot = - // doc.getDocumentCatalog().getStructureTreeRoot(); - // COSDictionary dic = - // doc.getDocumentCatalog().getCOSDictionary(); - // PDStructureElement el = new PDStructureElement("Widget", - // pdstRoot); - + + String sigFieldName = buildNextSignatureFieldName(doc, pdfObject); + // this is not used for Adobe signature fields if (!isAdobeSigForm) { PDSignatureField signatureField = null; + final PDAcroForm acroFormm = doc.getDocumentCatalog().getAcroForm(); if (acroFormm != null) { @SuppressWarnings("unchecked") final List<PDField> fields = acroFormm.getFields(); @@ -473,7 +320,7 @@ public class PADESPDFBOXSigner implements IPdfSigner, IConfigurationConstants { } } } else { - logger.warn("Failed to name Signature Field! [Cannot find Field list in acroForm!]"); + log.warn("Failed to name Signature Field! [Cannot find Field list in acroForm!]"); } if (signatureField != null) { @@ -485,7 +332,8 @@ public class PADESPDFBOXSigner implements IPdfSigner, IConfigurationConstants { signatureField.setAlternateFieldName(sigFieldName); } } else { - logger.warn("Failed to name Signature Field! [Cannot find acroForm!]"); + log.warn("Failed to name Signature Field! [Cannot find acroForm!]"); + } } @@ -493,194 +341,284 @@ public class PADESPDFBOXSigner implements IPdfSigner, IConfigurationConstants { final PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm(); if (acroForm != null) { signatureField = (PDSignatureField) acroForm.getField(sigFieldName); + } - // PDF-UA - logger.info("Adding pdf/ua content."); - try { - final PDDocumentCatalog root = doc.getDocumentCatalog(); - final PDStructureTreeRoot structureTreeRoot = root.getStructureTreeRoot(); - if (structureTreeRoot != null) { - logger.info("Tree Root: {}", structureTreeRoot.toString()); - final List<Object> kids = structureTreeRoot.getKids(); - - if (kids == null) { - logger.info("No kid-elements in structure tree Root, maybe not PDF/UA document"); - } - PDStructureElement docElement = null; - for (final Object k : kids) { - if (k instanceof PDStructureElement) { - docElement = (PDStructureElement) k; - break; + injectPdfUaContent(doc, signatureField, sigFieldName, signatureProfileSettings); + + performTechnicalSignature(doc, pdfObject, signatureProfileSettings); + - } + log.debug("Signature done!"); + + } catch (final IOException e) { + log.warn(MessageResolver.resolveMessage("error.pdf.sig.01"), e); + throw new PdfAsException("error.pdf.sig.01", e); + + } catch (PDFASError e2) { + log.warn(e2.getInfo()); + throw new PdfAsException("error.pdf.sig.01", e2); + + } finally { + if (options != null) { + if (options.getVisualSignature() != null) { + try { + options.getVisualSignature().close(); + options.close(); + } catch (IOException e) { + log.debug("Failed to close VisualSignature!", e); } + } + } + + if (doc != null) { + try { + doc.close(); + // SignaturePlaceholderExtractor.getPlaceholders().clear(); + } catch (final IOException e) { + log.debug("Failed to close COS Doc!", e); + // Ignore + } + } + } + } - final PDStructureElement sigBlock = new PDStructureElement("Form", docElement); + private void performTechnicalSignature(PDDocument doc, PDFBOXObject pdfObject, + SignatureProfileSettings signatureProfileSettings) throws PdfAsException { + try { + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + synchronized (doc) { + doc.saveIncremental(bos); + final byte[] outputDocument = bos.toByteArray(); + pdfObject.setSignedDocument(outputDocument); + } + /* Check if resulting pdf is PDF-A conform */ + if (signatureProfileSettings.isPDFA()) { + runPDFAPreflight(new ByteArrayDataSource(pdfObject.getSignedDocument())); + } - // create object dictionary and add as child element - final COSDictionary objectDic = new COSDictionary(); - objectDic.setName("Type", "OBJR"); + } catch (final IOException e1) { + log.error("Can not save incremental update", e1); - objectDic.setItem("Pg", signatureField.getWidget().getPage()); - objectDic.setItem("Obj", signatureField.getWidget()); + } + + System.gc(); + + } - final List<Object> l = new ArrayList<>(); - l.add(objectDic); - sigBlock.setKids(l); - sigBlock.setPage(signatureField.getWidget().getPage()); + private void injectPdfUaContent(PDDocument doc, PDSignatureField signatureField, String sigFieldName, + SignatureProfileSettings signatureProfileSettings) throws PdfAsException { + try { + log.info("Adding pdf/ua content .... "); + final PDDocumentCatalog root = doc.getDocumentCatalog(); + final PDStructureTreeRoot structureTreeRoot = root.getStructureTreeRoot(); + if (structureTreeRoot != null) { + log.info("Tree Root: {}", structureTreeRoot.toString()); + final List<Object> kids = structureTreeRoot.getKids(); + + if (kids == null) { + log.info("No kid-elements in structure tree Root, maybe not PDF/UA document"); + } - sigBlock.setTitle("Signature Table"); - sigBlock.setParent(docElement); - docElement.appendKid(sigBlock); + PDStructureElement docElement = null; + for (final Object k : kids) { + if (k instanceof PDStructureElement) { + docElement = (PDStructureElement) k; + break; - // Create and add Attribute dictionary to mitigate PAC - // warning - final COSDictionary sigBlockDic = sigBlock.getCOSObject(); - final COSDictionary sub = new COSDictionary(); + } + } - sub.setName("O", "Layout"); - sub.setName("Placement", "Block"); - sigBlockDic.setItem(COSName.A, sub); - sigBlockDic.setNeedToBeUpdated(true); + final PDStructureElement sigBlock = new PDStructureElement("Form", docElement); - // Modify number tree - PDNumberTreeNode ntn = structureTreeRoot.getParentTree(); - if (ntn == null) { - ntn = new PDNumberTreeNode(objectDic, null); - logger.info("No number-tree-node found!"); - } + // create object dictionary and add as child element + final COSDictionary objectDic = new COSDictionary(); + objectDic.setName("Type", "OBJR"); - final COSArray ntnKids = (COSArray) ntn.getCOSObject().getDictionaryObject(COSName.KIDS); - final COSArray ntnNumbers = (COSArray) ntn.getCOSObject().getDictionaryObject(COSName.NUMS); + objectDic.setItem("Pg", signatureField.getWidget().getPage()); + objectDic.setItem("Obj", signatureField.getWidget()); - final int parentTreeNextKey = getParentTreeNextKey(structureTreeRoot); + final List<Object> l = new ArrayList<>(); + l.add(objectDic); + sigBlock.setKids(l); + sigBlock.setPage(signatureField.getWidget().getPage()); - if (ntnNumbers == null && ntnKids != null) {// no number array, so continue with the kids array - // create dictionary with limits and nums array - final COSDictionary pTreeEntry = new COSDictionary(); - final COSArray limitsArray = new COSArray(); - // limits for exact one entry - limitsArray.add(COSInteger.get(parentTreeNextKey)); - limitsArray.add(COSInteger.get(parentTreeNextKey)); + sigBlock.setTitle("Signature Table"); + sigBlock.setParent(docElement); + docElement.appendKid(sigBlock); - final COSArray numsArray = new COSArray(); - numsArray.add(COSInteger.get(parentTreeNextKey)); - numsArray.add(sigBlock); + // Create and add Attribute dictionary to mitigate PAC + // warning + final COSDictionary sigBlockDic = sigBlock.getCOSObject(); + final COSDictionary sub = new COSDictionary(); - pTreeEntry.setItem(COSName.NUMS, numsArray); - pTreeEntry.setItem(COSName.LIMITS, limitsArray); + sub.setName("O", "Layout"); + sub.setName("Placement", "Block"); + sigBlockDic.setItem(COSName.A, sub); + sigBlockDic.setNeedToBeUpdated(true); - final PDNumberTreeNode newKidsElement = new PDNumberTreeNode(pTreeEntry, PDNumberTreeNode.class); + // Modify number tree + PDNumberTreeNode ntn = structureTreeRoot.getParentTree(); + if (ntn == null) { + ntn = new PDNumberTreeNode(objectDic, null); + log.info("No number-tree-node found!"); + } - ntnKids.add(newKidsElement); - ntnKids.setNeedToBeUpdated(true); + final COSArray ntnKids = (COSArray) ntn.getCOSObject().getDictionaryObject(COSName.KIDS); + final COSArray ntnNumbers = (COSArray) ntn.getCOSObject().getDictionaryObject(COSName.NUMS); - } else if (ntnNumbers != null && ntnKids == null) { + final int parentTreeNextKey = getParentTreeNextKey(structureTreeRoot); - final int arrindex = ntnNumbers.size(); + if (ntnNumbers == null && ntnKids != null) {// no number array, so continue with the kids array + // create dictionary with limits and nums array + final COSDictionary pTreeEntry = new COSDictionary(); + final COSArray limitsArray = new COSArray(); + // limits for exact one entry + limitsArray.add(COSInteger.get(parentTreeNextKey)); + limitsArray.add(COSInteger.get(parentTreeNextKey)); - ntnNumbers.add(arrindex, COSInteger.get(parentTreeNextKey)); - ntnNumbers.add(arrindex + 1, sigBlock.getCOSObject()); + final COSArray numsArray = new COSArray(); + numsArray.add(COSInteger.get(parentTreeNextKey)); + numsArray.add(sigBlock); - ntnNumbers.setNeedToBeUpdated(true); + pTreeEntry.setItem(COSName.NUMS, numsArray); + pTreeEntry.setItem(COSName.LIMITS, limitsArray); - structureTreeRoot.setParentTree(ntn); + final PDNumberTreeNode newKidsElement = new PDNumberTreeNode(pTreeEntry, PDNumberTreeNode.class); - } else if (ntnNumbers == null && ntnKids == null) { - // document is not pdfua conform before signature creation - throw new PdfAsException("error.pdf.sig.pdfua.1"); - } else { - // this is not allowed - throw new PdfAsException("error.pdf.sig.pdfua.1"); - } + ntnKids.add(newKidsElement); + ntnKids.setNeedToBeUpdated(true); - // set StructureParent for signature field annotation - signatureField.getWidget().setStructParent(parentTreeNextKey); + } else if (ntnNumbers != null && ntnKids == null) { - // Increase the next Key value in the structure tree root - structureTreeRoot.setParentTreeNextKey(parentTreeNextKey + 1); + final int arrindex = ntnNumbers.size(); - // add the Tabs /S Element for Tabbing through annots - final PDPage p = signatureField.getWidget().getPage(); - p.getCOSObject().setName("Tabs", "S"); - p.getCOSObject().setNeedToBeUpdated(true); + ntnNumbers.add(arrindex, COSInteger.get(parentTreeNextKey)); + ntnNumbers.add(arrindex + 1, sigBlock.getCOSObject()); - // check alternative signature field name - if (signatureField != null) { - if (signatureField.getAlternateFieldName().equals("")) { - signatureField.setAlternateFieldName(sigFieldName); - } - } + ntnNumbers.setNeedToBeUpdated(true); - ntn.getCOSObject().setNeedToBeUpdated(true); - sigBlock.getCOSObject().setNeedToBeUpdated(true); - structureTreeRoot.getCOSObject().setNeedToBeUpdated(true); - objectDic.setNeedToBeUpdated(true); - docElement.getCOSObject().setNeedToBeUpdated(true); - } - } catch (final Throwable e) { - if (signatureProfileSettings.isPDFUA() == true) { - logger.error("Could not create PDF-UA conform document!"); - throw new PdfAsException("error.pdf.sig.pdfua.1", e); + structureTreeRoot.setParentTree(ntn); + + } else if (ntnNumbers == null && ntnKids == null) { + // document is not pdfua conform before signature creation + throw new PdfAsException("error.pdf.sig.pdfua.1"); } else { - logger.info("Could not create PDF-UA conform signature"); + // this is not allowed + throw new PdfAsException("error.pdf.sig.pdfua.1"); } - } - try { - final ByteArrayOutputStream bos = new ByteArrayOutputStream(); - synchronized (doc) { - doc.saveIncremental(bos); - final byte[] outputDocument = bos.toByteArray(); - pdfObject.setSignedDocument(outputDocument); - } - /* Check if resulting pdf is PDF-A conform */ - if (signatureProfileSettings.isPDFA()) { - runPDFAPreflight(new ByteArrayDataSource(pdfObject.getSignedDocument())); + // set StructureParent for signature field annotation + signatureField.getWidget().setStructParent(parentTreeNextKey); + + // Increase the next Key value in the structure tree root + structureTreeRoot.setParentTreeNextKey(parentTreeNextKey + 1); + + // add the Tabs /S Element for Tabbing through annots + final PDPage p = signatureField.getWidget().getPage(); + p.getCOSObject().setName("Tabs", "S"); + p.getCOSObject().setNeedToBeUpdated(true); + + // check alternative signature field name + if (signatureField != null) { + if (signatureField.getAlternateFieldName().equals("")) { + signatureField.setAlternateFieldName(sigFieldName); + } } - } catch (final IOException e1) { - logger.error("Can not save incremental update", e1); + ntn.getCOSObject().setNeedToBeUpdated(true); + sigBlock.getCOSObject().setNeedToBeUpdated(true); + structureTreeRoot.getCOSObject().setNeedToBeUpdated(true); + objectDic.setNeedToBeUpdated(true); + docElement.getCOSObject().setNeedToBeUpdated(true); + } + } catch (final Throwable e) { + if (signatureProfileSettings.isPDFUA()) { + log.error("Could not create PDF-UA conform document!"); + throw new PdfAsException("error.pdf.sig.pdfua.1", e); + + } else { + log.info("Could not create PDF-UA conform signature"); + } + } + + } + private String buildNextSignatureFieldName(PDDocument doc, PDFBOXObject pdfObject) { + String sigFieldName = getSignatureFieldNameConfig(pdfObject); + if (sigFieldName == null) { + sigFieldName = "PDF-AS Signatur"; + + } + return sigFieldName + PdfBoxUtils.countSignatures(doc, sigFieldName); + + } + + private int calculateBlankAreaForSignature(SignatureProfileSettings signatureProfileSettings) { + int signatureSize = 0x1000; + try { + final String reservedSignatureSizeString = signatureProfileSettings.getValue(SIG_RESERVED_SIZE); + if (reservedSignatureSizeString != null) { + signatureSize = Integer.parseInt(reservedSignatureSizeString); } + log.debug("Reserving {} bytes for signature", signatureSize); - System.gc(); - logger.debug("Signature done!"); + } catch (final NumberFormatException e) { + log.warn("Invalid configuration value: {} should be a number using 0x1000", SIG_RESERVED_SIZE); - } catch (final IOException e) { - logger.warn(MessageResolver.resolveMessage("error.pdf.sig.01"), e); - throw new PdfAsException("error.pdf.sig.01", e); + } - } catch (PDFASError e2) { - logger.warn(e2.getInfo()); - throw new PdfAsException("error.pdf.sig.01", e2); - - } finally { - if (options != null) { - if (options.getVisualSignature() != null) { - try { - options.getVisualSignature().close(); - options.close(); - } catch (IOException e) { - logger.debug("Failed to close VisualSignature!", e); - } + return signatureSize; + } + + private TablePos prepareTablePosition(SignaturePlaceholderData nextPlaceholderData, + SignatureProfileConfiguration signatureProfileConfiguration, String profilePosParam) throws PdfAsException { + + + if (nextPlaceholderData != null && nextPlaceholderData.getTablePos() != null) { + final float minWidth = signatureProfileConfiguration.getMinWidth(); + TablePos tablePos = nextPlaceholderData.getTablePos(); + if (minWidth > 0) { + if (tablePos.getWidth() < minWidth) { + tablePos.width = minWidth; + log.debug("Correcting placeholder with to minimum width {}", minWidth); } } + log.debug("Placeholder Position set to: " + tablePos.toString()); + return tablePos; - if (doc != null) { - try { - doc.close(); - // SignaturePlaceholderExtractor.getPlaceholders().clear(); - } catch (final IOException e) { - logger.debug("Failed to close COS Doc!", e); - // Ignore - } + } else { + TablePos signaturePos = null; + final String signaturePosString = signatureProfileConfiguration.getDefaultPositioning(); + + if (signaturePosString != null) { + log.debug("using signature Positioning: " + signaturePosString); + signaturePos = new TablePos(signaturePosString); + + } + + log.debug("using Positioning: " + profilePosParam); + + if (profilePosParam != null) { + // Merge Signature Position + return new TablePos(profilePosParam, signaturePos); + + } else if (signaturePos != null){ + // Fallback to signature Position! + return signaturePos; + + } else { + return new TablePos(); + } } } + private String getSignatureFieldNameConfig(PDFBOXObject pdfObject) { + return pdfObject.getStatus().getSettings().getValue(SIGNATURE_FIELD_NAME); + } + private int getParentTreeNextKey(PDStructureTreeRoot structureTreeRoot) throws IOException { int nextKey = structureTreeRoot.getParentTreeNextKey(); if (nextKey < 0) { @@ -717,31 +655,31 @@ public class PADESPDFBOXSigner implements IPdfSigner, IConfigurationConstants { document.close(); result = document.getResult(); - logger.info("PDF-A Validation Result: " + result.isValid()); + log.info("PDF-A Validation Result: " + result.isValid()); if (result.getErrorsList().size() > 0) { - logger.error("The following validation errors occured for PDF-A validation"); + log.error("The following validation errors occured for PDF-A validation"); } for (final ValidationResult.ValidationError ve : result.getErrorsList()) { - logger.error("\t" + ve.getErrorCode() + ": " + ve.getDetails()); + log.error("\t" + ve.getErrorCode() + ": " + ve.getDetails()); } if (!result.isValid()) { - logger.info("The file is not a valid PDF-A document"); + log.info("The file is not a valid PDF-A document"); } } catch (final SyntaxValidationException e) { - logger.error("The file is syntactically invalid.", e); + log.error("The file is syntactically invalid.", e); throw new PdfAsException("Resulting PDF Document is syntactically invalid."); } catch (final ValidationException e) { - logger.error("The file is not a valid PDF-A document.", e); + log.error("The file is not a valid PDF-A document.", e); } catch (final IOException e) { - logger.error("An IOException (" + e.getMessage() + log.error("An IOException (" + e.getMessage() + ") occurred, while validating the PDF-A conformance", e); throw new PdfAsException("Failed validating PDF Document IOException."); } catch (final RuntimeException e) { - logger.debug("An RuntimeException (" + e.getMessage() + log.debug("An RuntimeException (" + e.getMessage() + ") occurred, while validating the PDF-A conformance", e); throw new PdfAsException("Failed validating PDF Document RuntimeException."); } finally { @@ -819,11 +757,11 @@ public class PADESPDFBOXSigner implements IPdfSigner, IConfigurationConstants { final String signaturePosString = signatureProfileConfiguration.getDefaultPositioning(); PositioningInstruction positioningInstruction; if (signaturePosString != null) { - positioningInstruction = Positioning.determineTablePositioning(new TablePos(signaturePosString), "", - origDoc, visualObject, pdfObject.getStatus().getSettings()); + positioningInstruction = Positioning.determineTablePositioning(new TablePos(signaturePosString), + origDoc, visualObject, pdfObject.getStatus().getSettings(), signatureProfileSettings); } else { - positioningInstruction = Positioning.determineTablePositioning(new TablePos(), "", origDoc, - visualObject, pdfObject.getStatus().getSettings()); + positioningInstruction = Positioning.determineTablePositioning(new TablePos(), origDoc, + visualObject, pdfObject.getStatus().getSettings(), signatureProfileSettings); } origDoc.close(); @@ -877,10 +815,10 @@ public class PADESPDFBOXSigner implements IPdfSigner, IConfigurationConstants { return cutOut; } catch (final PdfAsException e) { - logger.warn("PDF-AS Exception", e); + log.warn("PDF-AS Exception", e); throw ErrorExtractor.searchPdfAsError(e, status); } catch (final Throwable e) { - logger.warn("Unexpected Throwable Exception", e); + log.warn("Unexpected Throwable Exception", e); throw ErrorExtractor.searchPdfAsError(e, status); } } @@ -898,7 +836,7 @@ public class PADESPDFBOXSigner implements IPdfSigner, IConfigurationConstants { if (pdfaIdentificationSchema != null) { final Integer pdfaversion = pdfaIdentificationSchema.getPart(); final String conformance = pdfaIdentificationSchema.getConformance(); - logger.info("Detected PDF/A Version: {} - {}", pdfaversion, conformance); + log.info("Detected PDF/A Version: {} - {}", pdfaversion, conformance); if (pdfaversion != null) { return String.valueOf(pdfaversion); @@ -907,7 +845,7 @@ public class PADESPDFBOXSigner implements IPdfSigner, IConfigurationConstants { } } } catch (final Throwable e) { - logger.warn("Failed to determine PDF/A Version!", e); + log.warn("Failed to determine PDF/A Version!", e); } return null; } @@ -931,75 +869,9 @@ public class PADESPDFBOXSigner implements IPdfSigner, IConfigurationConstants { } } return signature; + } - - private List<String> existingSignatureLocations(PDDocument doc) { - final List<String> existingLocations = new ArrayList<>(); - try { - final List<PDSignature> pdSignatureList = doc.getSignatureDictionaries(); - if (pdSignatureList.size() != 0) { - for (final PDSignature sig : pdSignatureList) { - existingLocations.add(sig.getLocation()); - } - } - } catch (final IOException e) { - e.printStackTrace(); - } - return existingLocations; - } - - // find first placeholder_id - public SignaturePlaceholderData checkAvailablePlaceholders(List<SignaturePlaceholderData> placeholders, - List<String> existingPlaceholders) { - SignaturePlaceholderData result = null; - - if (placeholders != null) { - for (int i = 0; i < placeholders.size(); ++i) { - // take smallest id - if (!existingPlaceholders.contains(placeholders.get(i).getPlaceholderName())) { - final SignaturePlaceholderData spd = placeholders.get(i); - if (spd.getId() != null) { - if (result == null) { - result = spd; - } else { - try { - final int currentID = Integer.parseInt(result.getId()); - final int testID = Integer.parseInt(spd.getId()); - if (testID < currentID) { - result = spd; - } - } catch (final Exception e) { - // fallback to string compare - final String currentID = result.getId(); - final String testID = spd.getId(); - if (testID.compareToIgnoreCase(currentID) < 0) { - result = spd; - } - } - } - } - } - } - } - return result; - } - - // find first placeholder_id - public List<SignaturePlaceholderData> listAvailablePlaceholders(List<SignaturePlaceholderData> placeholders, - List<String> existingPlaceholders) { - final List<SignaturePlaceholderData> result = new ArrayList<>(); - - if (placeholders != null) { - for (int i = 0; i < placeholders.size(); ++i) { - // take smallest id - if (!existingPlaceholders.contains(placeholders.get(i).getPlaceholderName())) { - result.add(placeholders.get(i)); - } - } - } - return result; - } - + static Map<Integer, COSObjectable> getNumberTreeAsMap(PDNumberTreeNode tree) throws IOException { Map<Integer, COSObjectable> numbers = tree.getNumbers(); diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsVisualSignatureBuilder.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsVisualSignatureBuilder.java index d30604d3..a148b3ec 100644 --- a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsVisualSignatureBuilder.java +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsVisualSignatureBuilder.java @@ -30,17 +30,25 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.MessageDigest; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; -import at.gv.egiz.pdfas.common.settings.SignatureProfileSettings; import org.apache.commons.codec.binary.Hex; -import org.apache.commons.io.IOUtils; import org.apache.pdfbox.cos.COSArray; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSName; -import org.apache.pdfbox.pdmodel.*; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDDocumentCatalog; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDStream; +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.apache.pdfbox.pdmodel.font.PDType0Font; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory; import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; @@ -52,12 +60,12 @@ import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleS import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDField; import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; -import org.apache.pdfbox.pdmodel.font.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import at.gv.egiz.pdfas.common.exceptions.PdfAsException; import at.gv.egiz.pdfas.common.settings.ISettings; +import at.gv.egiz.pdfas.common.settings.SignatureProfileSettings; import at.gv.egiz.pdfas.common.utils.ImageUtils; import at.knowcenter.wag.egov.egiz.table.Entry; @@ -260,7 +268,7 @@ public class PDFAsVisualSignatureBuilder extends PDVisibleSigBuilder implements PDPageContentStream stream = new PDPageContentStream(template, getStructure().getPage()); // stream.setFont(PDType1Font.COURIER, 5); - TableDrawUtils.drawTable(getStructure().getPage(), stream, 1, 1, + TableDrawUtils.drawTable(getStructure().getPage(), stream, 0, 1, designer.getWidth(), designer.getHeight(), properties.getMainTable(), template, false, innerFormResources, images, settings, this, properties); diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsVisualSignatureProperties.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsVisualSignatureProperties.java index 34093a9d..623347ff 100644 --- a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsVisualSignatureProperties.java +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsVisualSignatureProperties.java @@ -104,7 +104,7 @@ public class PDFAsVisualSignatureProperties extends PDVisibleSigProperties { logger.debug("ROT {}", rot); logger.debug("COORD X {} Y {}", posx, posy); designer.coordinates(posx, posy); - float[] form_rect = new float[] {0,0, main.getWidth() + 2, main.getHeight() + 2}; + float[] form_rect = new float[] {0,0, main.getWidth() + 1, main.getHeight() + 1}; logger.debug("AP Rect: {} {} {} {}", form_rect[0], form_rect[1], form_rect[2], form_rect[3]); designer.formaterRectangleParams(form_rect); //this.setPdVisibleSignature(designer); |