diff options
author | Thomas Lenz <thomas.lenz@iaik.tugraz.at> | 2023-01-12 12:30:11 +0000 |
---|---|---|
committer | Thomas Lenz <thomas.lenz@iaik.tugraz.at> | 2023-01-12 12:30:11 +0000 |
commit | 6fada4e066d79f55491ceafa84eb4a436b519913 (patch) | |
tree | e0c57f79dc1a57d3f6c4e3af1d982b750bad96f9 /pdf-as-pdfbox-2 | |
parent | e78fccac558a93f18bae96ddb6c8e131afaf946d (diff) | |
parent | ab328e055e01b12a91c8ccee4ac0cbea3e0fb282 (diff) | |
download | pdf-as-4-6fada4e066d79f55491ceafa84eb4a436b519913.tar.gz pdf-as-4-6fada4e066d79f55491ceafa84eb4a436b519913.tar.bz2 pdf-as-4-6fada4e066d79f55491ceafa84eb4a436b519913.zip |
Merge branch 'feature/issue_73' into 'development'
feat(signatureblock): optimize processing for signed documents with less space for signature block
See merge request egiz/pdf-as-4!9
Diffstat (limited to 'pdf-as-pdfbox-2')
2 files changed, 1195 insertions, 1178 deletions
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 52a865b1..13d1ebe6 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 @@ -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 @@ -26,323 +26,336 @@ package at.gv.egiz.pdfas.lib.impl.pdfbox2.positioning; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; -import org.apache.pdfbox.pdmodel.PDPageTree; import org.apache.pdfbox.pdmodel.common.PDRectangle; -import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; -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.lib.api.IConfigurationConstants; import at.gv.egiz.pdfas.lib.impl.pdfbox2.utils.PdfBoxUtils; import at.gv.egiz.pdfas.lib.impl.stamping.IPDFVisualObject; import at.knowcenter.wag.egov.egiz.pdf.PositioningInstruction; import at.knowcenter.wag.egov.egiz.pdf.TablePos; import at.knowcenter.wag.egov.egiz.pdfbox2.pdf.PDFUtilities; +import lombok.extern.slf4j.Slf4j; /** * Created with IntelliJ IDEA. User: afitzek Date: 8/29/13 Time: 4:30 PM To * change this template use File | Settings | File Templates. */ +@Slf4j public class Positioning { - private static final Logger logger = LoggerFactory - .getLogger(Positioning.class); - - /** - * The left/right margin. - */ - public static final float SIGNATURE_MARGIN_HORIZONTAL = 50f; - - /** - * The top/bottom margin. - */ - public static final float SIGNATURE_MARGIN_VERTICAL = 20f; - - /** - * Evalutates absolute positioning and prepares the PositioningInstruction - * for placing the table. - * - * @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 - * @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 { - return adjustSignatureTableandCalculatePosition(pdfDataSource, - pdf_table, pos, settings); - } - - private static PDRectangle rotateBox(PDRectangle cropBox, int rotation) { - if (rotation != 0) { - Point2D upSrc = new Point2D.Float(); - - upSrc.setLocation(cropBox.getUpperRightX(), - cropBox.getUpperRightY()); - - Point2D llSrc = new Point2D.Float(); - llSrc.setLocation(cropBox.getLowerLeftX(), cropBox.getLowerLeftY()); - AffineTransform transform = new AffineTransform(); - transform.setToIdentity(); - if (rotation % 360 != 0) { - transform.setToRotation(Math.toRadians(rotation * -1), llSrc.getX(), - llSrc.getY()); - } - Point2D upDst = new Point2D.Float(); - transform.transform(upSrc, upDst); - - Point2D llDst = new Point2D.Float(); - transform.transform(llSrc, llDst); - - float y1 = (float) upDst.getY(); - float y2 = (float) llDst.getY(); - - if(y1 > y2) { - float t = y1; - y1 = y2; - y2 = t; - } - - if(y1 < 0) { - y2 = y2 + -1 * y1; - y1 = 0; - } - - float x1 = (float) upDst.getX(); - float x2 = (float) llDst.getX(); - - if(x1 > x2) { - float t = x1; - x1 = x2; - x2 = t; - } - - if(x1 < 0) { - x2 = x2 + -1 * x1; - x1 = 0; - } - - cropBox.setUpperRightX(x2); - cropBox.setUpperRightY(y2); - cropBox.setLowerLeftY(y1); - cropBox.setLowerLeftX(x1); - } - return cropBox; - } - - /** - * Sets the width of the table according to the layout of the document and - * calculates the y position where the PDFPTable should be placed. - * - * @param pdfDataSource - * The PDF document. - * @param pdf_table - * The PDFPTable to be placed. - * @param settings - * @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 { - List<PDSignatureField> pdSignatureFieldList; - PdfBoxUtils.checkPDFPermissions(pdfDataSource); - int counter = 0; - - try { - //count signature fields with signatures - pdSignatureFieldList = pdfDataSource.getSignatureFields(); - for (PDSignatureField signatureField : pdSignatureFieldList) - { - if(signatureField.getSignature()!=null){ - counter++; - } - } - } catch (IOException e) { - e.printStackTrace(); - } - // get pages of currentdocument - int doc_pages = pdfDataSource.getNumberOfPages(); - int page = doc_pages; - boolean make_new_page = pos.isNewPage(); - - //we cannot add new page if a document is already signed - - - if (!(pos.isNewPage() || pos.isPauto())) { - // we should posit signaturtable on this page - page = pos.getPage(); - // System.out.println("XXXXPAGE="+page+" doc_pages="+doc_pages); - if (page > doc_pages ) { - make_new_page = true; - page = doc_pages; - } - } - - if(make_new_page && counter!=0) { - make_new_page = false; - } - - PDPage pdPage = pdfDataSource.getPage(page-1); - - PDRectangle cropBox = pdPage.getCropBox(); - - // fallback to MediaBox if Cropbox not available! - - if (cropBox == null) { - cropBox = pdPage.getCropBox(); - } - - if (cropBox == null) { - cropBox = pdPage.getCropBox(); - } - - // getPagedimensions - // Rectangle psize = reader.getPageSizeWithRotation(page); - // int page_rotation = reader.getPageRotation(page); - - // Integer rotation = pdPage.getRotation(); - // int page_rotation = rotation.intValue(); - - int rotation = pdPage.getRotation(); - - logger.debug("Original CropBox: " + cropBox.toString()); - - cropBox = rotateBox(cropBox, rotation); - - logger.debug("Rotated CropBox: " + cropBox.toString()); - - float page_width = cropBox.getWidth(); - float page_height = cropBox.getHeight(); - - logger.debug("CropBox width: " + page_width); - logger.debug("CropBox heigth: " + page_height); - - // now we can calculate x-position - float pre_pos_x = SIGNATURE_MARGIN_HORIZONTAL; - if (!pos.isXauto()) { - // we do have absolute x - pre_pos_x = pos.getPosX(); - } - // calculate width - // center - float pre_width = page_width - 2 * pre_pos_x; - if (!pos.isWauto()) { - // we do have absolute width - pre_width = pos.getWidth(); - if (pos.isXauto()) { // center x - pre_pos_x = (page_width - pre_width) / 2; - } - } - final float pos_x = pre_pos_x; - final float width = pre_width; - // Signatur table dimensions are complete - pdf_table.setWidth(width); - pdf_table.fixWidth(); - // pdf_table.setTotalWidth(width); - // pdf_table.setLockedWidth(true); - - final float table_height = pdf_table.getHeight(); - // now check pos_y - float pos_y = pos.getPosY(); - - // in case an absolute y position is already given OR - // if the table is related to an invisible signature - // there is no need for further calculations - // (fixed adding new page in case of invisible signatures) - if (!pos.isYauto() || table_height == 0) { - // we do have y-position too --> all parameters but page ok - if (make_new_page) { - page++; - } - return new PositioningInstruction(make_new_page, page, pos_x, - pos_y, pos.rotation); - } - // pos_y is auto - if (make_new_page) { - // ignore footer in new page - page++; - pos_y = page_height - SIGNATURE_MARGIN_VERTICAL; - return new PositioningInstruction(make_new_page, page, pos_x, - pos_y, pos.rotation); - } - // up to here no checks have to be made if Tablesize and Pagesize are - // fit - // Now we have to getfreespace in page and reguard footerline - float footer_line = pos.getFooterLine(); - -// float pre_page_length = PDFUtilities.calculatePageLength(pdfDataSource, -// page - 1, page_height - footer_line, /* page_rotation, */ -// legacy32, legacy40); - - float pre_page_length = Float.NEGATIVE_INFINITY; - try { - pre_page_length = PDFUtilities.getMaxYPosition(pdfDataSource, page-1, pdf_table, SIGNATURE_MARGIN_VERTICAL, footer_line, settings); - //pre_page_length = PDFUtilities.getFreeTablePosition(pdfDataSource, page-1, pdf_table,SIGNATURE_MARGIN_VERTICAL); - } catch (IOException e) { - logger.warn("Could not determine page length, using -INFINITY"); - } - - if (pre_page_length == Float.NEGATIVE_INFINITY){ - // we do have an empty page or nothing in area above footerline - pre_page_length = page_height; - // no text --> SIGNATURE_BORDER - pos_y = page_height - SIGNATURE_MARGIN_VERTICAL; - if (pos_y - footer_line <= table_height) { - if(counter!=0) - make_new_page = false; - else{ - make_new_page = true; - page++; - } - if (!pos.isPauto()) { - // we have to correct pagenumber - page = pdfDataSource.getNumberOfPages(); - } - // no text --> SIGNATURE_BORDER - pos_y = page_height - SIGNATURE_MARGIN_VERTICAL; - } - return new PositioningInstruction(make_new_page, page, pos_x, - pos_y, pos.rotation); - } - final float page_length = pre_page_length; - // we do have text take SIGNATURE_MARGIN - pos_y = page_height - page_length - SIGNATURE_MARGIN_VERTICAL; - if (pos_y - footer_line <= table_height) { - if(counter!=0){ - make_new_page = false; - } - else{ - make_new_page = true; - page++; - } - if (!pos.isPauto()) { - // we have to correct pagenumber in case of absolute page and - // not enough - // space - page = pdfDataSource.getNumberOfPages(); - } - // no text --> SIGNATURE_BORDER - pos_y = page_height - SIGNATURE_MARGIN_VERTICAL; - } - return new PositioningInstruction(make_new_page, page, pos_x, pos_y, - pos.rotation); - } + /** + * The left/right margin. + */ + public static final float SIGNATURE_MARGIN_HORIZONTAL = 50f; + + /** + * The top/bottom margin. + */ + public static final float SIGNATURE_MARGIN_VERTICAL = 20f; + + /** + * Evalutates absolute positioning and prepares the PositioningInstruction for + * placing the table. + * + * @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 + * @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 { + return adjustSignatureTableandCalculatePosition(pdfDataSource, + pdf_table, pos, settings); + } + + /** + * Sets the width of the table according to the layout of the document and + * calculates the y position where the PDFPTable should be placed. + * + * @param pdfDataSource The PDF document. + * @param pdf_table The PDFPTable to be placed. + * @param settings + * @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 { + PdfBoxUtils.checkPDFPermissions(pdfDataSource); + final long numberOfExistingSignatures = getNumberOfExistingSignatures(pdfDataSource); + + // get pages of currentdocument + final int doc_pages = pdfDataSource.getNumberOfPages(); + int page = doc_pages; + boolean make_new_page = pos.isNewPage(); + + + if (!(pos.isNewPage() || pos.isPauto())) { + // we should posit signaturtable on this page + page = pos.getPage(); + if (page > doc_pages) { + log.debug("Document is shorter than requested page for signature block. Adding new page ..."); + make_new_page = true; + page = doc_pages; + + } + } + + make_new_page = checkIfNewPageIsAllowed(make_new_page, numberOfExistingSignatures, settings); + + + if(make_new_page && numberOfExistingSignatures!=0) { + make_new_page = false; + + } + + + final PDPage pdPage = pdfDataSource.getPage(page - 1); + + PDRectangle cropBox = pdPage.getCropBox(); + + // fallback to MediaBox if Cropbox not available! + + if (cropBox == null) { + cropBox = pdPage.getCropBox(); + } + + if (cropBox == null) { + cropBox = pdPage.getCropBox(); + } + + // getPagedimensions + // Rectangle psize = reader.getPageSizeWithRotation(page); + // int page_rotation = reader.getPageRotation(page); + + // Integer rotation = pdPage.getRotation(); + // int page_rotation = rotation.intValue(); + + final int rotation = pdPage.getRotation(); + + log.debug("Original CropBox: " + cropBox.toString()); + + cropBox = rotateBox(cropBox, rotation); + + log.debug("Rotated CropBox: " + cropBox.toString()); + + final float page_width = cropBox.getWidth(); + final float page_height = cropBox.getHeight(); + + log.debug("CropBox width: " + page_width); + log.debug("CropBox heigth: " + page_height); + + // now we can calculate x-position + float pre_pos_x = SIGNATURE_MARGIN_HORIZONTAL; + if (!pos.isXauto()) { + // we do have absolute x + pre_pos_x = pos.getPosX(); + } + // calculate width + // center + float pre_width = page_width - 2 * pre_pos_x; + if (!pos.isWauto()) { + // we do have absolute width + pre_width = pos.getWidth(); + if (pos.isXauto()) { // center x + pre_pos_x = (page_width - pre_width) / 2; + + } + } + + final float pos_x = pre_pos_x; + final float width = pre_width; + + // Signatur table dimensions are complete + pdf_table.setWidth(width); + pdf_table.fixWidth(); + + final float table_height = pdf_table.getHeight(); + // now check pos_y + float pos_y = pos.getPosY(); + + // in case an absolute y position is already given OR + // if the table is related to an invisible signature + // there is no need for further calculations + // (fixed adding new page in case of invisible signatures) + if (!pos.isYauto() || table_height == 0) { + // we do have y-position too --> all parameters but page ok + if (make_new_page) { + page++; + + } + return new PositioningInstruction(make_new_page, page, pos_x, pos_y, pos.rotation); + + } + + // pos_y is auto + if (make_new_page) { + // ignore footer in new page + page++; + pos_y = page_height - SIGNATURE_MARGIN_VERTICAL; + return new PositioningInstruction(make_new_page, page, pos_x, pos_y, pos.rotation); + + } + + // up to here no checks have to be made if Tablesize and Pagesize are + // fit + // Now we have to getfreespace in page and reguard footerline + float pre_page_length = calculatePrePageLength(pdfDataSource, page, pdf_table, pos.getFooterLine(), settings); + + if (pre_page_length == Float.NEGATIVE_INFINITY) { + // 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); + + } 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); + + } + } + + private static float calculatePrePageLength(PDDocument pdfDataSource, int page, IPDFVisualObject pdf_table, float footer_line, ISettings settings) { + try { + return PDFUtilities.getMaxYPosition(pdfDataSource, page - 1, pdf_table, + SIGNATURE_MARGIN_VERTICAL, footer_line, settings); + + } catch (final IOException e) { + log.warn("Could not determine page length, using -INFINITY"); + return Float.NEGATIVE_INFINITY; + + } + } + + private static boolean isFailOnLessSpaceEnabled(ISettings settings) { + String value = settings.getValue(IConfigurationConstants.SIG_BLOCK_LESS_SPACE_STOPPING_WITH_ERROR); + return Boolean.valueOf(value); + + } + + 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"); + + } else { + log.info("Placing signature-block on last page without free-space checks ... "); + return false; + + } + + } else { + return make_new_page; + + } + } + + 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 { + if (pos_y - footer_line <= table_height) { + + make_new_page = checkIfNewPageIsAllowed(true, numberOfExistingSignatures, settings); + if (make_new_page) { + page++; + + } + + if (!pos.isPauto()) { + // we have to correct pagenumber + page = pdfDataSource.getNumberOfPages(); + + } + // no text --> SIGNATURE_BORDER + pos_y = page_height - SIGNATURE_MARGIN_VERTICAL; + + } + + return new PositioningInstruction(make_new_page, page, pos_x, pos_y, pos.rotation); + + } + + private static long getNumberOfExistingSignatures(PDDocument pdfDataSource) { + try { + return pdfDataSource.getSignatureFields().stream() + .filter(el -> el.getSignature() != null) + .count(); + + } catch (final IOException e) { + log.warn("Can not extract existing Signatures from PDF. Use it as 0", e); + return 0; + + } + } + + private static PDRectangle rotateBox(PDRectangle cropBox, int rotation) { + if (rotation != 0) { + final Point2D upSrc = new Point2D.Float(); + + upSrc.setLocation(cropBox.getUpperRightX(), + cropBox.getUpperRightY()); + + final Point2D llSrc = new Point2D.Float(); + llSrc.setLocation(cropBox.getLowerLeftX(), cropBox.getLowerLeftY()); + final AffineTransform transform = new AffineTransform(); + transform.setToIdentity(); + if (rotation % 360 != 0) { + transform.setToRotation(Math.toRadians(rotation * -1), llSrc.getX(), + llSrc.getY()); + } + final Point2D upDst = new Point2D.Float(); + transform.transform(upSrc, upDst); + + final Point2D llDst = new Point2D.Float(); + transform.transform(llSrc, llDst); + + float y1 = (float) upDst.getY(); + float y2 = (float) llDst.getY(); + + if (y1 > y2) { + final float t = y1; + y1 = y2; + y2 = t; + } + + if (y1 < 0) { + y2 = y2 + -1 * y1; + y1 = 0; + } + + float x1 = (float) upDst.getX(); + float x2 = (float) llDst.getX(); + + if (x1 > x2) { + final float t = x1; + x1 = x2; + x2 = t; + } + + if (x1 < 0) { + x2 = x2 + -1 * x1; + x1 = 0; + } + + cropBox.setUpperRightX(x2); + cropBox.setUpperRightY(y2); + cropBox.setLowerLeftY(y1); + cropBox.setLowerLeftX(x1); + } + return cropBox; + } + } 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 df2a42bb..d780aeec 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 @@ -23,6 +23,58 @@ ******************************************************************************/ package at.gv.egiz.pdfas.lib.impl.signing.pdfbox2; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.activation.DataSource; + +import org.apache.commons.io.IOUtils; +import org.apache.pdfbox.cos.COSArray; +import org.apache.pdfbox.cos.COSBase; +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSDocument; +import org.apache.pdfbox.cos.COSInteger; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.cos.COSString; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDDocumentCatalog; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDResources; +import org.apache.pdfbox.pdmodel.common.COSObjectable; +import org.apache.pdfbox.pdmodel.common.PDMetadata; +import org.apache.pdfbox.pdmodel.common.PDNumberTreeNode; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot; +import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions; +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.preflight.PreflightDocument; +import org.apache.pdfbox.preflight.ValidationResult; +import org.apache.pdfbox.preflight.exception.SyntaxValidationException; +import org.apache.pdfbox.preflight.exception.ValidationException; +import org.apache.pdfbox.preflight.parser.PreflightParser; +import org.apache.pdfbox.rendering.ImageType; +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; import at.gv.egiz.pdfas.common.messages.MessageResolver; @@ -36,7 +88,6 @@ 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.placeholder.SignaturePlaceholderExtractor; import at.gv.egiz.pdfas.lib.impl.pdfbox2.positioning.Positioning; import at.gv.egiz.pdfas.lib.impl.pdfbox2.utils.PdfBoxUtils; import at.gv.egiz.pdfas.lib.impl.placeholder.PlaceholderFilter; @@ -58,955 +109,908 @@ 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 org.apache.commons.io.IOUtils; -import org.apache.pdfbox.cos.*; -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.PDDocumentCatalog; -import org.apache.pdfbox.pdmodel.PDPage; -import org.apache.pdfbox.pdmodel.PDResources; -import org.apache.pdfbox.pdmodel.common.COSObjectable; -import org.apache.pdfbox.pdmodel.common.PDMetadata; -import org.apache.pdfbox.pdmodel.common.PDNumberTreeNode; -import org.apache.pdfbox.pdmodel.common.PDRectangle; -import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement; -import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot; -import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent; -import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; -import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions; -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.preflight.PreflightDocument; -import org.apache.pdfbox.preflight.ValidationResult; -import org.apache.pdfbox.preflight.exception.SyntaxValidationException; -import org.apache.pdfbox.preflight.exception.ValidationException; -import org.apache.pdfbox.preflight.parser.PreflightParser; -import org.apache.pdfbox.rendering.ImageType; -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 javax.activation.DataSource; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; public class PADESPDFBOXSigner implements IPdfSigner, IConfigurationConstants { - private static final Logger logger = LoggerFactory.getLogger(PADESPDFBOXSigner.class); - private boolean isAdobeSigForm = false; + private static final Logger logger = LoggerFactory.getLogger(PADESPDFBOXSigner.class); + private boolean isAdobeSigForm = false; - public void signPDF(PDFObject genericPdfObject, RequestedSignature requestedSignature, - PDFASSignatureInterface genericSigner) throws PdfAsException { + @Override + public void signPDF(PDFObject genericPdfObject, RequestedSignature requestedSignature, + PDFASSignatureInterface genericSigner) throws PdfAsException { - PDFAsVisualSignatureProperties properties = null; - List<SignaturePlaceholderData> placeholders; - List<SignaturePlaceholderData> availablePlaceholders; - SignaturePlaceholderData signaturePlaceholderData = null; + 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); - } + String placeholder_id = ""; + if (PlaceholderWebConfiguration.getValue(PLACEHOLDER_WEB_ID) != null && !PlaceholderWebConfiguration + .getValue(PLACEHOLDER_WEB_ID).equalsIgnoreCase("")) { + placeholder_id = PlaceholderWebConfiguration.getValue(PLACEHOLDER_WEB_ID); + } - if (!(genericPdfObject instanceof PDFBOXObject)) { - // tODO: - throw new PdfAsException(); - } + if (!(genericPdfObject instanceof PDFBOXObject)) { + // tODO: + throw new PdfAsException(); + } - PDFBOXObject pdfObject = (PDFBOXObject) genericPdfObject; + final PDFBOXObject pdfObject = (PDFBOXObject) genericPdfObject; - if (!(genericSigner instanceof PDFASPDFBOXSignatureInterface)) { - // tODO: - throw new PdfAsException(); - } + if (!(genericSigner instanceof PDFASPDFBOXSignatureInterface)) { + // tODO: + throw new PdfAsException(); + } - PDFASPDFBOXSignatureInterface signer = (PDFASPDFBOXSignatureInterface) genericSigner; + final PDFASPDFBOXSignatureInterface signer = (PDFASPDFBOXSignatureInterface) genericSigner; + + String pdfaVersion = null; + + PDDocument doc = null; + final SignatureOptions options = new SignatureOptions(); + COSDocument visualSignatureDocumentGuard = null; + 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); + if (signature == null) { + // create signature dictionary + signature = new PDSignature(); + } else { + isAdobeSigForm = true; + } + + signature.setFilter(COSName.getPDFName(signer.getPDFFilter())); + signature.setSubFilter(COSName.getPDFName(signer.getPDFSubFilter())); +// SignaturePlaceholderData signaturePlaceholderDataInit = + placeholders = PlaceholderFilter.checkPlaceholderSignatureLocationList(pdfObject.getStatus(), + pdfObject.getStatus().getSettings(), placeholder_id); - String pdfaVersion = null; +// placeholders = SignaturePlaceholderExtractor.getPlaceholders(); + availablePlaceholders = listAvailablePlaceholders(placeholders, existingSignatureLocations(doc)); - PDDocument doc = null; - SignatureOptions options = new SignatureOptions(); - COSDocument visualSignatureDocumentGuard = null; - try { + 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()); + } - doc = pdfObject.getDocument(); - //if signature already exists dont create new page - List<PDSignatureField> pdSignatureFieldList = doc.getSignatureFields(); - PDSignature signature; + tablePos = signaturePlaceholderData.getTablePos(); + if (tablePos != null) { - // 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); - if (signature == null) { - // create signature dictionary - signature = new PDSignature(); - } - else { - isAdobeSigForm = true; - } + final SignatureProfileConfiguration signatureProfileConfiguration = pdfObject.getStatus() + .getSignatureProfileConfiguration(requestedSignature.getSignatureProfileID()); - signature.setFilter(COSName.getPDFName(signer.getPDFFilter())); - signature.setSubFilter(COSName.getPDFName(signer.getPDFSubFilter())); -// SignaturePlaceholderData signaturePlaceholderDataInit = - placeholders = PlaceholderFilter.checkPlaceholderSignatureLocationList(pdfObject.getStatus(), - pdfObject.getStatus().getSettings(), placeholder_id); + final float minWidth = signatureProfileConfiguration.getMinWidth(); -// 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) { - - SignatureProfileConfiguration signatureProfileConfiguration = pdfObject.getStatus() - .getSignatureProfileConfiguration(requestedSignature.getSignatureProfileID()); - - float minWidth = signatureProfileConfiguration.getMinWidth(); - - if(minWidth > 0) { - if (tablePos.getWidth() < minWidth) { - tablePos.width = minWidth; - logger.debug("Correcting placeholder with to minimum width {}", minWidth); - } - } - logger.debug("Placeholder Position set to: " + tablePos.toString()); - } - } - SignatureProfileSettings signatureProfileSettings = TableFactory - .createProfile(requestedSignature.getSignatureProfileID(), pdfObject.getStatus().getSettings()); - - //Check if input document is PDF-A conform - if (signatureProfileSettings.isPDFA()) { - DataSource origDoc = pdfObject.getOriginalDocument(); - InputStream stream = origDoc.getInputStream(); - //Run PreflightParser for checking conformity// - //runPDFAPreflight(origDoc); + if (minWidth > 0) { + if (tablePos.getWidth() < minWidth) { + tablePos.width = minWidth; + logger.debug("Correcting placeholder with to minimum width {}", minWidth); } - ValueResolver resolver = new ValueResolver(requestedSignature, pdfObject.getStatus()); - - 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"; - } - - 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()); - - signer.setPDSignature(signature); - - int signatureSize = 0x1000; - try { - String reservedSignatureSizeString = signatureProfileSettings.getValue(SIG_RESERVED_SIZE); - if (reservedSignatureSizeString != null) { - signatureSize = Integer.parseInt(reservedSignatureSizeString); - } - logger.debug("Reserving {} bytes for signature", signatureSize); - } catch (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); - } - - // Is visible Signature - if (requestedSignature.isVisual()) { - logger.info("Creating visual siganture block"); - - SignatureProfileConfiguration signatureProfileConfiguration = pdfObject.getStatus() - .getSignatureProfileConfiguration(requestedSignature.getSignatureProfileID()); + } + 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 ValueResolver resolver = new ValueResolver(requestedSignature, pdfObject.getStatus()); + + 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"; + } + + 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()); + + 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); + } + + // 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(); + } + } - if (tablePos == null) { - // ================================================================ - // PositioningStage (visual) -> find position or use - // fixed - // position + // Legacy Modes not supported with pdfbox2 anymore +// boolean legacy32Position = signatureProfileConfiguration.getLegacy32Positioning(); +// boolean legacy40Position = signatureProfileConfiguration.getLegacy40Positioning(); - String posString = pdfObject.getStatus().getSignParamter().getSignaturePosition(); + // create Table describtion + + 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()); + + if (!isAdobeSigForm) { + if (positioningInstruction.isMakeNewPage()) { + final int last = doc.getNumberOfPages() - 1; + final PDDocumentCatalog root = doc.getDocumentCatalog(); + final PDPage lastPage = root.getPages().get(last); + root.getPages().getCOSObject().setNeedToBeUpdated(true); + final PDPage p = new PDPage(lastPage.getMediaBox()); + p.setResources(new PDResources()); + p.setRotation(lastPage.getRotation()); + doc.addPage(p); + } + + // handle rotated page + final int targetPageNumber = positioningInstruction.getPage(); + logger.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()); + + final SignaturePositionImpl position = new SignaturePositionImpl(); + position.setX(positioningInstruction.getX()); + position.setY(positioningInstruction.getY()); + position.setPage(positioningInstruction.getPage()); + position.setHeight(visualObject.getHeight()); + position.setWidth(visualObject.getWidth()); + requestedSignature.setSignaturePosition(position); + } - TablePos signaturePos = null; + properties = new PDFAsVisualSignatureProperties(pdfObject.getStatus().getSettings(), pdfObject, + (PdfBoxVisualObject) visualObject, positioningInstruction, signatureProfileSettings); + + 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(); + final COSBase base = root.getCOSObject().getItem(COSName.OUTPUT_INTENTS); + + InputStream colorProfile = null; + // colorProfile = this.getClass().getResourceAsStream("/icm/sRGB.icm"); + colorProfile = this.getClass().getResourceAsStream("/icm/sRGB Color Space Profile.icm"); + // Set output intents for PDF/A conformity// + try { + final PDOutputIntent intent = new PDOutputIntent(doc, colorProfile); + + intent.setInfo("sRGB IEC61966-2.1"); + intent.setOutputCondition("sRGB IEC61966-2.1"); + intent.setOutputConditionIdentifier("sRGB IEC61966-2.1"); + intent.setRegistryName("http://www.color.org"); + final List<PDOutputIntent> oi = new ArrayList<>(); + oi.add(intent); + root.setOutputIntents(oi); + root.getCOSObject().setNeedToBeUpdated(true); + + logger.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()); + } - String signaturePosString = signatureProfileConfiguration.getDefaultPositioning(); + visualSignatureDocumentGuard = options.getVisualSignature(); - if (signaturePosString != null) { - logger.debug("using signature Positioning: " + signaturePos); - signaturePos = new TablePos(signaturePosString); - } + doc.addSignature(signature, signer, options); - logger.debug("using Positioning: " + posString); + if (sigFieldName == null) { + sigFieldName = "PDF-AS Signatur"; + } + final int count = PdfBoxUtils.countSignatures(doc, sigFieldName); - if (posString != null) { - // Merge Signature Position - tablePos = new TablePos(posString, signaturePos); - } else { - // Fallback to signature Position! - tablePos = signaturePos; - } + sigFieldName = sigFieldName + count; - if (tablePos == null) { - // Last Fallback default position - tablePos = new TablePos(); - } - } + final PDAcroForm acroFormm = doc.getDocumentCatalog().getAcroForm(); - //Legacy Modes not supported with pdfbox2 anymore -// boolean legacy32Position = signatureProfileConfiguration.getLegacy32Positioning(); -// boolean legacy40Position = signatureProfileConfiguration.getLegacy40Positioning(); + // PDStructureTreeRoot pdstRoot = + // doc.getDocumentCatalog().getStructureTreeRoot(); + // COSDictionary dic = + // doc.getDocumentCatalog().getCOSDictionary(); + // PDStructureElement el = new PDStructureElement("Widget", + // pdstRoot); - // create Table describtion - - - Table main = TableFactory.createSigTable(signatureProfileSettings, MAIN, pdfObject.getStatus(), - requestedSignature); - - IPDFStamper stamper = StamperFactory.createDefaultStamper(pdfObject.getStatus().getSettings()); - - IPDFVisualObject visualObject = stamper.createVisualPDFObject(pdfObject, main); - - /* - * PDDocument originalDocument = PDDocument .load(new - * ByteArrayInputStream(pdfObject.getStatus() - * .getPdfObject().getOriginalDocument())); - */ - - PositioningInstruction positioningInstruction = Positioning.determineTablePositioning(tablePos, "", - doc, visualObject, pdfObject.getStatus().getSettings()); - - logger.debug("Positioning: {}", positioningInstruction.toString()); - - if(!isAdobeSigForm) { - if (positioningInstruction.isMakeNewPage()) { - int last = doc.getNumberOfPages() - 1; - PDDocumentCatalog root = doc.getDocumentCatalog(); - PDPage lastPage = root.getPages().get(last); - root.getPages().getCOSObject().setNeedToBeUpdated(true); - PDPage p = new PDPage(lastPage.getMediaBox()); - p.setResources(new PDResources()); - p.setRotation(lastPage.getRotation()); - doc.addPage(p); - } - - // handle rotated page - int targetPageNumber = positioningInstruction.getPage(); - logger.debug("Target Page: " + targetPageNumber); - PDPage targetPage = doc.getPages().get(targetPageNumber - 1); - int rot = targetPage.getRotation(); - logger.debug("Page rotation: " + rot); - logger.debug("resulting Sign rotation: " + positioningInstruction.getRotation()); - - SignaturePositionImpl position = new SignaturePositionImpl(); - position.setX(positioningInstruction.getX()); - position.setY(positioningInstruction.getY()); - position.setPage(positioningInstruction.getPage()); - position.setHeight(visualObject.getHeight()); - position.setWidth(visualObject.getWidth()); - requestedSignature.setSignaturePosition(position); - } - - properties = new PDFAsVisualSignatureProperties(pdfObject.getStatus().getSettings(), pdfObject, - (PdfBoxVisualObject) visualObject, positioningInstruction, signatureProfileSettings); - - 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()) { - PDDocumentCatalog root = doc.getDocumentCatalog(); - COSBase base = root.getCOSObject().getItem(COSName.OUTPUT_INTENTS); - - InputStream colorProfile = null; - //colorProfile = this.getClass().getResourceAsStream("/icm/sRGB.icm"); - colorProfile = this.getClass().getResourceAsStream("/icm/sRGB Color Space Profile.icm"); - //Set output intents for PDF/A conformity// - try { - PDOutputIntent intent = new PDOutputIntent(doc, colorProfile); - - intent.setInfo("sRGB IEC61966-2.1"); - intent.setOutputCondition("sRGB IEC61966-2.1"); - intent.setOutputConditionIdentifier("sRGB IEC61966-2.1"); - intent.setRegistryName("http://www.color.org"); - List<PDOutputIntent> oi = new ArrayList<PDOutputIntent>(); - oi.add(intent); - root.setOutputIntents(oi); - root.getCOSObject().setNeedToBeUpdated(true); - - logger.info("added Output Intent"); - } catch (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()); - } + // this is not used for Adobe signature fields + if (!isAdobeSigForm) { + PDSignatureField signatureField = null; + if (acroFormm != null) { + @SuppressWarnings("unchecked") + final List<PDField> fields = acroFormm.getFields(); - visualSignatureDocumentGuard = options.getVisualSignature(); - - doc.addSignature(signature, signer, options); - - if (sigFieldName == null) { - sigFieldName = "PDF-AS Signatur"; - } - int count = PdfBoxUtils.countSignatures(doc, sigFieldName); - - sigFieldName = sigFieldName + count; - - PDAcroForm acroFormm = doc.getDocumentCatalog().getAcroForm(); - - // PDStructureTreeRoot pdstRoot = - // doc.getDocumentCatalog().getStructureTreeRoot(); - // COSDictionary dic = - // doc.getDocumentCatalog().getCOSDictionary(); - // PDStructureElement el = new PDStructureElement("Widget", - // pdstRoot); - - //this is not used for Adobe signature fields - if(!isAdobeSigForm) { - PDSignatureField signatureField = null; - if (acroFormm != null) { - @SuppressWarnings("unchecked") - List<PDField> fields = acroFormm.getFields(); - - if (fields != null) { - for (PDField pdField : fields) { - if (pdField != null) { - if (pdField instanceof PDSignatureField) { - PDSignatureField tmpSigField = (PDSignatureField) pdField; - - if (tmpSigField.getSignature() != null - && tmpSigField.getSignature().getCOSObject() != null) { - if (tmpSigField.getSignature().getCOSObject() - .equals(signature.getCOSObject())) { - signatureField = (PDSignatureField) pdField; - - } - } - } - } - } - } else { - logger.warn("Failed to name Signature Field! [Cannot find Field list in acroForm!]"); - } - - if (signatureField != null) { - signatureField.setPartialName(sigFieldName); - } - if (properties != null) { - signatureField.setAlternateFieldName(properties.getAlternativeTableCaption()); - } else { - signatureField.setAlternateFieldName(sigFieldName); - } - } else { - logger.warn("Failed to name Signature Field! [Cannot find acroForm!]"); - } - } - - PDSignatureField signatureField = null; - PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm(); - if (acroForm != null) { - signatureField = (PDSignatureField) acroForm.getField(sigFieldName); - } - - // PDF-UA - logger.info("Adding pdf/ua content."); - try { - PDDocumentCatalog root = doc.getDocumentCatalog(); - PDStructureTreeRoot structureTreeRoot = root.getStructureTreeRoot(); - if (structureTreeRoot != null) { - logger.info("Tree Root: {}", structureTreeRoot.toString()); - 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 (Object k : kids) { - if (k instanceof PDStructureElement) { - docElement = (PDStructureElement) k; - break; - - } - } - - PDStructureElement sigBlock = new PDStructureElement("Form", docElement); - - // create object dictionary and add as child element - COSDictionary objectDic = new COSDictionary(); - objectDic.setName("Type", "OBJR"); - - objectDic.setItem("Pg", signatureField.getWidget().getPage()); - objectDic.setItem("Obj", signatureField.getWidget()); - - List<Object> l = new ArrayList<Object>(); - l.add(objectDic); - sigBlock.setKids(l); - sigBlock.setPage(signatureField.getWidget().getPage()); - - - sigBlock.setTitle("Signature Table"); - sigBlock.setParent(docElement); - docElement.appendKid(sigBlock); - - // Create and add Attribute dictionary to mitigate PAC - // warning - COSDictionary sigBlockDic = (COSDictionary) sigBlock.getCOSObject(); - COSDictionary sub = new COSDictionary(); - - sub.setName("O", "Layout"); - sub.setName("Placement", "Block"); - sigBlockDic.setItem(COSName.A, sub); - sigBlockDic.setNeedToBeUpdated(true); - - // Modify number tree - PDNumberTreeNode ntn = structureTreeRoot.getParentTree(); - if (ntn == null) { - ntn = new PDNumberTreeNode(objectDic, null); - logger.info("No number-tree-node found!"); - } - - COSArray ntnKids = (COSArray) ntn.getCOSObject().getDictionaryObject(COSName.KIDS); - COSArray ntnNumbers = (COSArray) ntn.getCOSObject().getDictionaryObject(COSName.NUMS); - - int parentTreeNextKey = getParentTreeNextKey(structureTreeRoot); - - if(ntnNumbers == null && ntnKids != null){//no number array, so continue with the kids array - //create dictionary with limits and nums array - COSDictionary pTreeEntry = new COSDictionary(); - COSArray limitsArray = new COSArray(); - //limits for exact one entry - limitsArray.add(COSInteger.get(parentTreeNextKey)); - limitsArray.add(COSInteger.get(parentTreeNextKey)); - - COSArray numsArray = new COSArray(); - numsArray.add(COSInteger.get(parentTreeNextKey)); - numsArray.add(sigBlock); - - pTreeEntry.setItem(COSName.NUMS, numsArray); - pTreeEntry.setItem(COSName.LIMITS, limitsArray); - - PDNumberTreeNode newKidsElement = new PDNumberTreeNode(pTreeEntry, PDNumberTreeNode.class); - - ntnKids.add(newKidsElement); - ntnKids.setNeedToBeUpdated(true); - - - }else if(ntnNumbers != null && ntnKids == null){ - - int arrindex = ntnNumbers.size(); - - ntnNumbers.add(arrindex, COSInteger.get(parentTreeNextKey)); - ntnNumbers.add(arrindex + 1, sigBlock.getCOSObject()); - - ntnNumbers.setNeedToBeUpdated(true); - - 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{ - //this is not allowed - throw new PdfAsException("error.pdf.sig.pdfua.1"); - } - - // 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 - 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); - } - - ntn.getCOSObject().setNeedToBeUpdated(true); - sigBlock.getCOSObject().setNeedToBeUpdated(true); - structureTreeRoot.getCOSObject().setNeedToBeUpdated(true); - objectDic.setNeedToBeUpdated(true); - docElement.getCOSObject().setNeedToBeUpdated(true); - } - } catch (Throwable e) { - if (signatureProfileSettings.isPDFUA() == true) { - logger.error("Could not create PDF-UA conform document!"); - throw new PdfAsException("error.pdf.sig.pdfua.1", e); - } else { - logger.info("Could not create PDF-UA conform signature"); - } - } - - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - synchronized (doc) { - doc.saveIncremental(bos); - byte[] outputDocument = bos.toByteArray(); - pdfObject.setSignedDocument(outputDocument); - } - /* Check if resulting pdf is PDF-A conform */ - if (signatureProfileSettings.isPDFA()) { - runPDFAPreflight(new ByteArrayDataSource(pdfObject.getSignedDocument())); - } + if (fields != null) { + for (final PDField pdField : fields) { + if (pdField != null) { + if (pdField instanceof PDSignatureField) { + final PDSignatureField tmpSigField = (PDSignatureField) pdField; - } catch (IOException e1) { - e1.printStackTrace(); - } + if (tmpSigField.getSignature() != null + && tmpSigField.getSignature().getCOSObject() != null) { + if (tmpSigField.getSignature().getCOSObject() + .equals(signature.getCOSObject())) { + signatureField = (PDSignatureField) pdField; - finally { - if (options != null) { - if (options.getVisualSignature() != null) { - options.getVisualSignature().close(); } + } } + } } + } else { + logger.warn("Failed to name Signature Field! [Cannot find Field list in acroForm!]"); + } + + if (signatureField != null) { + signatureField.setPartialName(sigFieldName); + } + if (properties != null) { + signatureField.setAlternateFieldName(properties.getAlternativeTableCaption()); + } else { + signatureField.setAlternateFieldName(sigFieldName); + } + } else { + logger.warn("Failed to name Signature Field! [Cannot find acroForm!]"); + } + } + + PDSignatureField signatureField = null; + 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; - System.gc(); - } catch (IOException e) { - logger.warn(MessageResolver.resolveMessage("error.pdf.sig.01"), e); - throw new PdfAsException("error.pdf.sig.01", e); - } finally { - if (doc != null) { - try { - doc.close(); - //SignaturePlaceholderExtractor.getPlaceholders().clear(); - } catch (IOException e) { - logger.debug("Failed to close COS Doc!", e); - // Ignore - } - } - logger.debug("Signature done!"); - } - } - - private int getParentTreeNextKey(PDStructureTreeRoot structureTreeRoot) throws IOException { - int nextKey = structureTreeRoot.getParentTreeNextKey(); - if (nextKey < 0) { - Map<Integer, COSObjectable> destNumberTreeAsMap = getNumberTreeAsMap(structureTreeRoot.getParentTree()); - if (destNumberTreeAsMap.isEmpty()) { - nextKey = 0; - - } else { - nextKey = Collections.max(destNumberTreeAsMap.keySet()) + 1; - - } - } - - return nextKey; - } - - /** - * Check via PreFlightParser if PDF-Document is a valid PDFA1 - * @param signedDocument: signed Document - * @throws PdfAsException - */ - private void runPDFAPreflight(final DataSource signedDocument) throws PdfAsException { - PreflightDocument document = null; - ValidationResult result = null; - try { - PreflightParser parser = new PreflightParser(signedDocument); - // - // parser.parse(Format.PDF_A1B); - parser.parse(); - document = parser.getPreflightDocument(); - document.validate(); - - document.close(); - result = document.getResult(); - logger.info("PDF-A Validation Result: " + result.isValid()); - - if (result.getErrorsList().size() > 0) { - logger.error("The following validation errors occured for PDF-A validation"); } + } - for (ValidationResult.ValidationError ve : result.getErrorsList()) { - logger.error("\t" + ve.getErrorCode() + ": " + ve.getDetails()); - } + final PDStructureElement sigBlock = new PDStructureElement("Form", docElement); - if (!result.isValid()) { - logger.info("The file is not a valid PDF-A document"); - } + // create object dictionary and add as child element + final COSDictionary objectDic = new COSDictionary(); + objectDic.setName("Type", "OBJR"); - } catch (SyntaxValidationException e) { - logger.error("The file is syntactically invalid.", e); - throw new PdfAsException("Resulting PDF Document is syntactically invalid."); - } catch (ValidationException e) { - logger.error("The file is not a valid PDF-A document.", e); - } catch (IOException e) { - logger.error("An IOException (" + e.getMessage() - + ") occurred, while validating the PDF-A conformance", e); - throw new PdfAsException("Failed validating PDF Document IOException."); - } catch (RuntimeException e) { - logger.debug("An RuntimeException (" + e.getMessage() - + ") occurred, while validating the PDF-A conformance", e); - throw new PdfAsException("Failed validating PDF Document RuntimeException."); - } finally { - if (document != null) { - IOUtils.closeQuietly(document); - } - } - } + objectDic.setItem("Pg", signatureField.getWidget().getPage()); + objectDic.setItem("Obj", signatureField.getWidget()); - @Override - public PDFObject buildPDFObject(OperationStatus operationStatus) { - return new PDFBOXObject(operationStatus); - } - - @Override - public PDFASSignatureInterface buildSignaturInterface(IPlainSigner signer, SignParameter parameters, - RequestedSignature requestedSignature) { - return new PdfboxSignerWrapper(signer, parameters, requestedSignature); - } + final List<Object> l = new ArrayList<>(); + l.add(objectDic); + sigBlock.setKids(l); + sigBlock.setPage(signatureField.getWidget().getPage()); - @Override - public PDFASSignatureExtractor buildBlindSignaturInterface(X509Certificate certificate, String filter, String subfilter, Calendar date) { - return new SignatureDataExtractor(certificate, filter, subfilter, date); - } + sigBlock.setTitle("Signature Table"); + sigBlock.setParent(docElement); + docElement.appendKid(sigBlock); - @Override - public void checkPDFPermissions(PDFObject genericPdfObject) throws PdfAsException { - if (!(genericPdfObject instanceof PDFBOXObject)) { - // tODO: - throw new PdfAsException(); - } + // Create and add Attribute dictionary to mitigate PAC + // warning + final COSDictionary sigBlockDic = sigBlock.getCOSObject(); + final COSDictionary sub = new COSDictionary(); - PDFBOXObject pdfObject = (PDFBOXObject) genericPdfObject; - PdfBoxUtils.checkPDFPermissions(pdfObject.getDocument()); - } + sub.setName("O", "Layout"); + sub.setName("Placement", "Block"); + sigBlockDic.setItem(COSName.A, sub); + sigBlockDic.setNeedToBeUpdated(true); - @Override - public byte[] rewritePlainSignature(byte[] plainSignature) { - String signature = new COSString(plainSignature).toHexString(); - byte[] pdfSignature = signature.getBytes(); - return pdfSignature; - } + // Modify number tree + PDNumberTreeNode ntn = structureTreeRoot.getParentTree(); + if (ntn == null) { + ntn = new PDNumberTreeNode(objectDic, null); + logger.info("No number-tree-node found!"); + } - @Override - public Image generateVisibleSignaturePreview(SignParameter parameter, java.security.cert.X509Certificate cert, - int resolution, OperationStatus status, RequestedSignature requestedSignature) throws PDFASError { - try { + final COSArray ntnKids = (COSArray) ntn.getCOSObject().getDictionaryObject(COSName.KIDS); + final COSArray ntnNumbers = (COSArray) ntn.getCOSObject().getDictionaryObject(COSName.NUMS); - PDFBOXObject pdfObject = (PDFBOXObject) status.getPdfObject(); - PDDocument origDoc = new PDDocument(); + final int parentTreeNextKey = getParentTreeNextKey(structureTreeRoot); - origDoc.addPage(new PDPage(PDRectangle.A4)); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - origDoc.save(baos); - baos.close(); + 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)); - pdfObject.setOriginalDocument(new ByteArrayDataSource(baos.toByteArray())); + final COSArray numsArray = new COSArray(); + numsArray.add(COSInteger.get(parentTreeNextKey)); + numsArray.add(sigBlock); - SignatureProfileSettings signatureProfileSettings = TableFactory - .createProfile(requestedSignature.getSignatureProfileID(), pdfObject.getStatus().getSettings()); + pTreeEntry.setItem(COSName.NUMS, numsArray); + pTreeEntry.setItem(COSName.LIMITS, limitsArray); - // create Table describtion - Table main = TableFactory.createSigTable(signatureProfileSettings, MAIN, pdfObject.getStatus(), - requestedSignature); + final PDNumberTreeNode newKidsElement = new PDNumberTreeNode(pTreeEntry, PDNumberTreeNode.class); - IPDFStamper stamper = StamperFactory.createDefaultStamper(pdfObject.getStatus().getSettings()); + ntnKids.add(newKidsElement); + ntnKids.setNeedToBeUpdated(true); - IPDFVisualObject visualObject = stamper.createVisualPDFObject(pdfObject, main); + } else if (ntnNumbers != null && ntnKids == null) { - SignatureProfileConfiguration signatureProfileConfiguration = pdfObject.getStatus() - .getSignatureProfileConfiguration(requestedSignature.getSignatureProfileID()); + final int arrindex = ntnNumbers.size(); - String signaturePosString = signatureProfileConfiguration.getDefaultPositioning(); - PositioningInstruction positioningInstruction; - if (signaturePosString != null) { - positioningInstruction = Positioning.determineTablePositioning(new TablePos(signaturePosString), "", - origDoc, visualObject, pdfObject.getStatus().getSettings()); - } else { - positioningInstruction = Positioning.determineTablePositioning(new TablePos(), "", origDoc, - visualObject, pdfObject.getStatus().getSettings()); - } + ntnNumbers.add(arrindex, COSInteger.get(parentTreeNextKey)); + ntnNumbers.add(arrindex + 1, sigBlock.getCOSObject()); - origDoc.close(); + ntnNumbers.setNeedToBeUpdated(true); - SignaturePositionImpl position = new SignaturePositionImpl(); - position.setX(positioningInstruction.getX()); - position.setY(positioningInstruction.getY()); - position.setPage(positioningInstruction.getPage()); - position.setHeight(visualObject.getHeight()); - position.setWidth(visualObject.getWidth()); + structureTreeRoot.setParentTree(ntn); - requestedSignature.setSignaturePosition(position); + } 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"); + } - PDFAsVisualSignatureProperties properties = new PDFAsVisualSignatureProperties( - pdfObject.getStatus().getSettings(), pdfObject, (PdfBoxVisualObject) visualObject, - positioningInstruction, signatureProfileSettings); + // set StructureParent for signature field annotation + signatureField.getWidget().setStructParent(parentTreeNextKey); - properties.buildSignature(); - PDDocument visualDoc; - synchronized (PDDocument.class) { - visualDoc = PDDocument.load(properties.getVisibleSignature()); - } - // PDPageable pageable = new PDPageable(visualDoc); + // Increase the next Key value in the structure tree root + structureTreeRoot.setParentTreeNextKey(parentTreeNextKey + 1); - PDPage firstPage = visualDoc.getDocumentCatalog().getPages().get(0); + // add the Tabs /S Element for Tabbing through annots + final PDPage p = signatureField.getWidget().getPage(); + p.getCOSObject().setName("Tabs", "S"); + p.getCOSObject().setNeedToBeUpdated(true); - float stdRes = 72; - float targetRes = resolution; - float factor = targetRes / stdRes; + // check alternative signature field name + if (signatureField != null) { + if (signatureField.getAlternateFieldName().equals("")) { + signatureField.setAlternateFieldName(sigFieldName); + } + } + 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); + } else { + logger.info("Could not create PDF-UA conform signature"); + } + } + + 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())); + } - int targetPageNumber = 0;//TODO: is this always the case - PDFRenderer pdfRenderer = new PDFRenderer(visualDoc); - BufferedImage outputImage = pdfRenderer.renderImageWithDPI(targetPageNumber, targetRes, ImageType.ARGB); + } catch (final IOException e1) { + e1.printStackTrace(); + } - //BufferedImage outputImage = firstPage.convertToImage(BufferedImage.TYPE_4BYTE_ABGR, (int) targetRes); + finally { + if (options != null) { + if (options.getVisualSignature() != null) { + options.getVisualSignature().close(); + } + } + } + + System.gc(); + logger.debug("Signature done!"); + + } catch (final IOException e) { + logger.warn(MessageResolver.resolveMessage("error.pdf.sig.01"), e); + throw new PdfAsException("error.pdf.sig.01", e); + + } finally { + if (doc != null) { + try { + doc.close(); + // SignaturePlaceholderExtractor.getPlaceholders().clear(); + } catch (final IOException e) { + logger.debug("Failed to close COS Doc!", e); + // Ignore + } + } + } + } - visualDoc.close(); - pdfRenderer = null; + private int getParentTreeNextKey(PDStructureTreeRoot structureTreeRoot) throws IOException { + int nextKey = structureTreeRoot.getParentTreeNextKey(); + if (nextKey < 0) { + final Map<Integer, COSObjectable> destNumberTreeAsMap = getNumberTreeAsMap(structureTreeRoot + .getParentTree()); + if (destNumberTreeAsMap.isEmpty()) { + nextKey = 0; - BufferedImage cutOut = new BufferedImage((int) (position.getWidth() * factor), - (int) (position.getHeight() * factor), BufferedImage.TYPE_4BYTE_ABGR); + } else { + nextKey = Collections.max(destNumberTreeAsMap.keySet()) + 1; - Graphics2D graphics = (Graphics2D) cutOut.getGraphics(); + } + } - graphics.drawImage(outputImage, 0, 0, cutOut.getWidth(), cutOut.getHeight(), (int) (1 * factor), - (int) (outputImage.getHeight() - ((position.getHeight() + 1) * factor)), - (int) ((1 + position.getWidth()) * factor), (int) (outputImage.getHeight() - - ((position.getHeight() + 1) * factor) + (position.getHeight() * factor)), - null); - return cutOut; - } catch (PdfAsException e) { - logger.warn("PDF-AS Exception", e); - throw ErrorExtractor.searchPdfAsError(e, status); - } catch (Throwable e) { - logger.warn("Unexpected Throwable Exception", e); - throw ErrorExtractor.searchPdfAsError(e, status); - } + return nextKey; + } + + /** + * Check via PreFlightParser if PDF-Document is a valid PDFA1 + * + * @param signedDocument: signed Document + * @throws PdfAsException + */ + private void runPDFAPreflight(final DataSource signedDocument) throws PdfAsException { + PreflightDocument document = null; + ValidationResult result = null; + try { + final PreflightParser parser = new PreflightParser(signedDocument); + // + // parser.parse(Format.PDF_A1B); + parser.parse(); + document = parser.getPreflightDocument(); + document.validate(); + + document.close(); + result = document.getResult(); + logger.info("PDF-A Validation Result: " + result.isValid()); + + if (result.getErrorsList().size() > 0) { + logger.error("The following validation errors occured for PDF-A validation"); + } + + for (final ValidationResult.ValidationError ve : result.getErrorsList()) { + logger.error("\t" + ve.getErrorCode() + ": " + ve.getDetails()); + } + + if (!result.isValid()) { + logger.info("The file is not a valid PDF-A document"); + } + + } catch (final SyntaxValidationException e) { + logger.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); + } catch (final IOException e) { + logger.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() + + ") occurred, while validating the PDF-A conformance", e); + throw new PdfAsException("Failed validating PDF Document RuntimeException."); + } finally { + if (document != null) { + IOUtils.closeQuietly(document); + } + } + } + + @Override + public PDFObject buildPDFObject(OperationStatus operationStatus) { + return new PDFBOXObject(operationStatus); + } + + @Override + public PDFASSignatureInterface buildSignaturInterface(IPlainSigner signer, SignParameter parameters, + RequestedSignature requestedSignature) { + return new PdfboxSignerWrapper(signer, parameters, requestedSignature); + } + + @Override + public PDFASSignatureExtractor buildBlindSignaturInterface(X509Certificate certificate, String filter, + String subfilter, Calendar date) { + return new SignatureDataExtractor(certificate, filter, subfilter, date); + } + + @Override + public void checkPDFPermissions(PDFObject genericPdfObject) throws PdfAsException { + if (!(genericPdfObject instanceof PDFBOXObject)) { + // tODO: + throw new PdfAsException(); } - private String getPDFAVersion(PDDocument doc) { - try { - PDDocumentCatalog cat = doc.getDocumentCatalog(); - PDMetadata metadata = cat.getMetadata(); - - if (metadata != null) { - DomXmpParser xmpParser = new DomXmpParser(); - XMPMetadata xmpMetadata = xmpParser.parse(metadata.exportXMPMetadata()); - if (xmpMetadata != null) { - PDFAIdentificationSchema pdfaIdentificationSchema = xmpMetadata.getPDFIdentificationSchema(); - if (pdfaIdentificationSchema != null) { - Integer pdfaversion = pdfaIdentificationSchema.getPart(); - String conformance = pdfaIdentificationSchema.getConformance(); - logger.info("Detected PDF/A Version: {} - {}", pdfaversion, conformance); - - if (pdfaversion != null) { - return String.valueOf(pdfaversion); - } - } - } + final PDFBOXObject pdfObject = (PDFBOXObject) genericPdfObject; + PdfBoxUtils.checkPDFPermissions(pdfObject.getDocument()); + } + + @Override + public byte[] rewritePlainSignature(byte[] plainSignature) { + final String signature = new COSString(plainSignature).toHexString(); + final byte[] pdfSignature = signature.getBytes(); + return pdfSignature; + } + + @Override + public Image generateVisibleSignaturePreview(SignParameter parameter, + java.security.cert.X509Certificate cert, + int resolution, OperationStatus status, RequestedSignature requestedSignature) throws PDFASError { + try { + + final PDFBOXObject pdfObject = (PDFBOXObject) status.getPdfObject(); + final PDDocument origDoc = new PDDocument(); + + origDoc.addPage(new PDPage(PDRectangle.A4)); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + origDoc.save(baos); + baos.close(); + + pdfObject.setOriginalDocument(new ByteArrayDataSource(baos.toByteArray())); + + final SignatureProfileSettings signatureProfileSettings = TableFactory + .createProfile(requestedSignature.getSignatureProfileID(), pdfObject.getStatus().getSettings()); + + // create Table describtion + 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); + + final SignatureProfileConfiguration signatureProfileConfiguration = pdfObject.getStatus() + .getSignatureProfileConfiguration(requestedSignature.getSignatureProfileID()); + + final String signaturePosString = signatureProfileConfiguration.getDefaultPositioning(); + PositioningInstruction positioningInstruction; + if (signaturePosString != null) { + positioningInstruction = Positioning.determineTablePositioning(new TablePos(signaturePosString), "", + origDoc, visualObject, pdfObject.getStatus().getSettings()); + } else { + positioningInstruction = Positioning.determineTablePositioning(new TablePos(), "", origDoc, + visualObject, pdfObject.getStatus().getSettings()); + } + + origDoc.close(); + + final SignaturePositionImpl position = new SignaturePositionImpl(); + position.setX(positioningInstruction.getX()); + position.setY(positioningInstruction.getY()); + position.setPage(positioningInstruction.getPage()); + position.setHeight(visualObject.getHeight()); + position.setWidth(visualObject.getWidth()); + + requestedSignature.setSignaturePosition(position); + + final PDFAsVisualSignatureProperties properties = new PDFAsVisualSignatureProperties( + pdfObject.getStatus().getSettings(), pdfObject, (PdfBoxVisualObject) visualObject, + positioningInstruction, signatureProfileSettings); + + properties.buildSignature(); + PDDocument visualDoc; + synchronized (PDDocument.class) { + visualDoc = PDDocument.load(properties.getVisibleSignature()); + } + // PDPageable pageable = new PDPageable(visualDoc); + + final PDPage firstPage = visualDoc.getDocumentCatalog().getPages().get(0); + + final float stdRes = 72; + final float targetRes = resolution; + final float factor = targetRes / stdRes; + + final int targetPageNumber = 0;// TODO: is this always the case + PDFRenderer pdfRenderer = new PDFRenderer(visualDoc); + final BufferedImage outputImage = pdfRenderer.renderImageWithDPI(targetPageNumber, targetRes, + ImageType.ARGB); + + visualDoc.close(); + pdfRenderer = null; + + final BufferedImage cutOut = new BufferedImage((int) (position.getWidth() * factor), + (int) (position.getHeight() * factor), BufferedImage.TYPE_4BYTE_ABGR); + + final Graphics2D graphics = (Graphics2D) cutOut.getGraphics(); + + graphics.drawImage(outputImage, 0, 0, cutOut.getWidth(), cutOut.getHeight(), (int) (1 * factor), + (int) (outputImage.getHeight() - (position.getHeight() + 1) * factor), + (int) ((1 + position.getWidth()) * factor), (int) (outputImage.getHeight() + - (position.getHeight() + 1) * factor + position.getHeight() * factor), + null); + return cutOut; + } catch (final PdfAsException e) { + logger.warn("PDF-AS Exception", e); + throw ErrorExtractor.searchPdfAsError(e, status); + } catch (final Throwable e) { + logger.warn("Unexpected Throwable Exception", e); + throw ErrorExtractor.searchPdfAsError(e, status); + } + } + + private String getPDFAVersion(PDDocument doc) { + try { + final PDDocumentCatalog cat = doc.getDocumentCatalog(); + final PDMetadata metadata = cat.getMetadata(); + + if (metadata != null) { + final DomXmpParser xmpParser = new DomXmpParser(); + final XMPMetadata xmpMetadata = xmpParser.parse(metadata.exportXMPMetadata()); + if (xmpMetadata != null) { + final PDFAIdentificationSchema pdfaIdentificationSchema = xmpMetadata.getPDFIdentificationSchema(); + if (pdfaIdentificationSchema != null) { + final Integer pdfaversion = pdfaIdentificationSchema.getPart(); + final String conformance = pdfaIdentificationSchema.getConformance(); + logger.info("Detected PDF/A Version: {} - {}", pdfaversion, conformance); + + if (pdfaversion != null) { + return String.valueOf(pdfaversion); } - } catch (Throwable e) { - logger.warn("Failed to determine PDF/A Version!", e); + } } - return null; + } + } catch (final Throwable e) { + logger.warn("Failed to determine PDF/A Version!", e); } - - // Find an existing signature. - private PDSignature findExistingSignature(PDDocument doc, String sigFieldName) { - PDSignature signature = null; - PDSignatureField signatureField; - PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm(); - if (acroForm != null) { - signatureField = (PDSignatureField) acroForm.getField(sigFieldName); - if (signatureField != null) { - // retrieve signature dictionary - signature = signatureField.getSignature(); - if (signature == null) { - signature = new PDSignature(); - signatureField.getCOSObject().setItem(COSName.V, signature); - } - else { - throw new IllegalStateException("The signature field " + sigFieldName + " is already signed."); - } - } - } - return signature; - } - - private List<String> existingSignatureLocations(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; - } - - //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())) { - SignaturePlaceholderData spd = placeholders.get(i); - if (spd.getId() != null) { - if(result == null) { - result = spd; - } else { - try{ - int currentID = Integer.parseInt(result.getId()); - int testID = Integer.parseInt(spd.getId()); - if(testID < currentID) { - result = spd; - } - }catch(Exception e){ - //fallback to string compare - String currentID = result.getId(); - 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) { - 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(); - if (numbers == null) - { - numbers = new LinkedHashMap<>(); + return null; + } + + // Find an existing signature. + private PDSignature findExistingSignature(PDDocument doc, String sigFieldName) { + PDSignature signature = null; + PDSignatureField signatureField; + final PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm(); + if (acroForm != null) { + signatureField = (PDSignatureField) acroForm.getField(sigFieldName); + if (signatureField != null) { + // retrieve signature dictionary + signature = signatureField.getSignature(); + if (signature == null) { + signature = new PDSignature(); + signatureField.getCOSObject().setItem(COSName.V, signature); + } else { + throw new IllegalStateException("The signature field " + sigFieldName + " is already signed."); } - else - { - // must copy because the map is read only - numbers = new LinkedHashMap<>(numbers); + } + } + 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()); } - List<PDNumberTreeNode> kids = tree.getKids(); - if (kids != null) - { - for (PDNumberTreeNode kid : kids) - { - numbers.putAll(getNumberTreeAsMap(kid)); + } + } 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 numbers; + } } - -} + 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(); + if (numbers == null) { + numbers = new LinkedHashMap<>(); + } else { + // must copy because the map is read only + numbers = new LinkedHashMap<>(numbers); + } + final List<PDNumberTreeNode> kids = tree.getKids(); + if (kids != null) { + for (final PDNumberTreeNode kid : kids) { + numbers.putAll(getNumberTreeAsMap(kid)); + } + } + return numbers; + } + +}
\ No newline at end of file |