From e0ac5d79c01e458eeb5eb4233f8a0360db878911 Mon Sep 17 00:00:00 2001 From: Thomas <> Date: Thu, 12 Jan 2023 12:58:24 +0100 Subject: feat(signatureblock): optimize processing for signed documents with less space for signature block Issue #73 add configuration property to stop signing process if document has less space for new signature block and new page is not allowed because document is already signed --- .../lib/impl/pdfbox2/positioning/Positioning.java | 619 +++++++++++---------- 1 file changed, 316 insertions(+), 303 deletions(-) (limited to 'pdf-as-pdfbox-2') 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 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; + } + } -- cgit v1.2.3