diff options
author | Christian Maierhofer <cmaierhofer@iaik.tugraz.at> | 2016-06-08 08:12:21 +0200 |
---|---|---|
committer | Andreas Fitzek <andreas.fitzek@iaik.tugraz.at> | 2016-08-17 16:45:49 +0200 |
commit | c4e41301d0746ce57044a3aa41375cff3a9f2b5e (patch) | |
tree | bdf13d0a0d7fae97215a84286e31ff894bacc979 /pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib | |
parent | a7945fff6ca14d35ac75039712736dddc6d48e90 (diff) | |
download | pdf-as-4-c4e41301d0746ce57044a3aa41375cff3a9f2b5e.tar.gz pdf-as-4-c4e41301d0746ce57044a3aa41375cff3a9f2b5e.tar.bz2 pdf-as-4-c4e41301d0746ce57044a3aa41375cff3a9f2b5e.zip |
initial pdfbox-2 commit
Diffstat (limited to 'pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib')
25 files changed, 5514 insertions, 0 deletions
diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/PDFBOXBackend.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/PDFBOXBackend.java new file mode 100644 index 00000000..1a9424a6 --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/PDFBOXBackend.java @@ -0,0 +1,53 @@ +package at.gv.egiz.pdfas.lib.impl.pdfbox2; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.pdfas.lib.backend.PDFASBackend; +import at.gv.egiz.pdfas.lib.impl.pdfbox2.placeholder.PDFBoxPlaceholderExtractor; +import at.gv.egiz.pdfas.lib.impl.placeholder.PlaceholderExtractor; +import at.gv.egiz.pdfas.lib.impl.signing.IPdfSigner; +import at.gv.egiz.pdfas.lib.impl.signing.pdfbox2.PADESPDFBOXSigner; +import at.gv.egiz.pdfas.lib.impl.verify.VerifyBackend; +import at.gv.egiz.pdfas.lib.impl.verify.pdfbox2.PDFBOXVerifier; + +public class PDFBOXBackend implements PDFASBackend { + + private static final String NAME = "PDFBOX_2_BACKEND"; + + private static final Logger logger = LoggerFactory + .getLogger(PDFBOXBackend.class); + + static { + logger.info(" ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); + logger.info(" + PDFBOX Backend created"); + logger.info(" + PDFBOX Version used: " + org.apache.pdfbox.util.Version.getVersion()); + logger.info(" ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); + } + + @Override + public String getName() { + return NAME; + } + + @Override + public boolean usedAsDefault() { + return true; + } + + @Override + public IPdfSigner getPdfSigner() { + return new PADESPDFBOXSigner(); + } + + @Override + public PlaceholderExtractor getPlaceholderExtractor() { + return new PDFBoxPlaceholderExtractor(); + } + + @Override + public VerifyBackend getVerifier() { + return new PDFBOXVerifier(); + } + +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/PDFBOXObject.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/PDFBOXObject.java new file mode 100644 index 00000000..0eee4cd3 --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/PDFBOXObject.java @@ -0,0 +1,70 @@ +package at.gv.egiz.pdfas.lib.impl.pdfbox2; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.activation.DataSource; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.font.PDFont; + +import at.gv.egiz.pdfas.lib.impl.status.OperationStatus; +import at.gv.egiz.pdfas.lib.impl.status.PDFObject; + +public class PDFBOXObject extends PDFObject { + + private PDDocument doc; + + private Map<String, PDFont> fontCache = new HashMap<String, PDFont>(); + + public PDFBOXObject(OperationStatus operationStatus) { + super(operationStatus); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + if(doc != null) { + doc.close(); + } + } + + public void close() { + if(doc != null) { + try { + doc.close(); + //System.gc(); + } catch(Throwable e) { + // ignore! + } + doc = null; + } + } + + public void setOriginalDocument(DataSource originalDocument) throws IOException { + this.originalDocument = originalDocument; + if(doc != null) { + doc.close(); + } + synchronized(PDDocument.class) { + this.doc = PDDocument.load(this.originalDocument.getInputStream()); + } + if(this.doc != null) { + this.doc.getDocument().setWarnMissingClose(false); + } + } + + public PDDocument getDocument() { + return this.doc; + } + + @Override + public String getPDFVersion() { + return String.valueOf(getDocument().getDocument().getVersion()); + } + + public Map<String, PDFont> getFontCache() { + return fontCache; + } +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/PDFBoxPlaceholderExtractor.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/PDFBoxPlaceholderExtractor.java new file mode 100644 index 00000000..730a6581 --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/PDFBoxPlaceholderExtractor.java @@ -0,0 +1,24 @@ +package at.gv.egiz.pdfas.lib.impl.pdfbox2.placeholder; + +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +import at.gv.egiz.pdfas.lib.impl.pdfbox2.PDFBOXObject; +import at.gv.egiz.pdfas.lib.impl.placeholder.PlaceholderExtractor; +import at.gv.egiz.pdfas.lib.impl.placeholder.SignaturePlaceholderData; +import at.gv.egiz.pdfas.lib.impl.status.PDFObject; + +public class PDFBoxPlaceholderExtractor implements PlaceholderExtractor { + + @Override + public SignaturePlaceholderData extract(PDFObject doc, + String placeholderId, int matchMode) throws PdfAsException { + + if (doc instanceof PDFBOXObject) { + PDFBOXObject object = (PDFBOXObject) doc; + return SignaturePlaceholderExtractor.extract(object.getDocument(), + placeholderId, matchMode); + } + + throw new PdfAsException("INVALID STATE"); + } + +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/SignaturePlaceholderExtractor.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/SignaturePlaceholderExtractor.java new file mode 100644 index 00000000..39d66c3c --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/placeholder/SignaturePlaceholderExtractor.java @@ -0,0 +1,474 @@ +/******************************************************************************* + * <copyright> Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria </copyright> + * 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 + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +/** + * <copyright> Copyright 2006 by Know-Center, Graz, Austria </copyright> + * 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 + * that you distribute must include a readable copy of the "NOTICE" text file. + */ +package at.gv.egiz.pdfas.lib.impl.pdfbox2.placeholder; + +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import org.apache.pdfbox.contentstream.PDContentStream; +import org.apache.pdfbox.contentstream.PDFStreamEngine; +import org.apache.pdfbox.contentstream.operator.Operator; +import org.apache.pdfbox.cos.COSBase; +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.common.PDStream; +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.apache.pdfbox.pdmodel.font.PDFontFactory; +import org.apache.pdfbox.pdmodel.graphics.PDXObject; +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.apache.pdfbox.util.Matrix; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.pdfas.common.exceptions.PDFIOException; +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +import at.gv.egiz.pdfas.common.exceptions.PlaceholderExtractionException; +import at.gv.egiz.pdfas.lib.impl.placeholder.PlaceholderExtractorConstants; +import at.gv.egiz.pdfas.lib.impl.placeholder.SignaturePlaceholderContext; +import at.gv.egiz.pdfas.lib.impl.placeholder.SignaturePlaceholderData; +import at.knowcenter.wag.egov.egiz.pdf.TablePos; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.DecodeHintType; +import com.google.zxing.LuminanceSource; +import com.google.zxing.MultiFormatReader; +import com.google.zxing.NotFoundException; +import com.google.zxing.ReaderException; +import com.google.zxing.Result; +import com.google.zxing.client.j2se.BufferedImageLuminanceSource; +import com.google.zxing.common.HybridBinarizer; + +/** + * Extract all relevant information from a placeholder image. + * + * @author exthex + * + */ +public class SignaturePlaceholderExtractor extends PDFStreamEngine implements PlaceholderExtractorConstants{ + /** + * The log. + */ + private static Logger logger = LoggerFactory + .getLogger(SignaturePlaceholderExtractor.class); + + private List<SignaturePlaceholderData> placeholders = new Vector<SignaturePlaceholderData>(); + private int currentPage = 0; + private PDDocument doc; + + private SignaturePlaceholderExtractor(String placeholderId, + int placeholderMatchMode, PDDocument doc) throws IOException { + super(); + //super(ResourceLoader.loadProperties( //TODO: pdfbox2 - properties need to be set + // "placeholder/pdfbox-reader.properties", true)); + this.doc = doc; + } + + /** + * Search the document for placeholder images and possibly included + * additional info.<br/> + * Searches only for the first placeholder page after page from top. + * + * @param inputStream + * @return all available info from the first found placeholder. + * @throws PDFDocumentException + * if the document could not be read. + * @throws PlaceholderExtractionException + * if STRICT matching mode was requested and no suitable + * placeholder could be found. + */ + public static SignaturePlaceholderData extract(PDDocument doc, + String placeholderId, int matchMode) throws PdfAsException { + SignaturePlaceholderContext.setSignaturePlaceholderData(null); + + SignaturePlaceholderExtractor extractor; + try { + extractor = new SignaturePlaceholderExtractor(placeholderId, + matchMode, doc); + } catch (IOException e2) { + throw new PDFIOException("error.pdf.io.04", e2); + } + + int pageNr = 0; + for(PDPage page : doc.getPages()){ + pageNr++; + + try { + extractor.setCurrentPage(pageNr); + if(page.getContents() != null && page.getResources() != null && page.getContentStreams() != null) { + extractor.processPage(page); //TODO: pdfbox2 - right? + + } + SignaturePlaceholderData ret = matchPlaceholderPage( + extractor.placeholders, placeholderId, matchMode); + if (ret != null) { + SignaturePlaceholderContext + .setSignaturePlaceholderData(ret); + return ret; + } + } catch (IOException e1) { + throw new PDFIOException("error.pdf.io.04", e1); + } catch(Throwable e) { + throw new PDFIOException("error.pdf.io.04", e); + } + } + if (extractor.placeholders.size() > 0) { + SignaturePlaceholderData ret = matchPlaceholderDocument( + extractor.placeholders, placeholderId, matchMode); + SignaturePlaceholderContext.setSignaturePlaceholderData(ret); + return ret; + } + // no placeholders found, apply strict mode if set + if (matchMode == PLACEHOLDER_MATCH_MODE_STRICT) { + throw new PlaceholderExtractionException("error.pdf.stamp.09"); + } + + return null; + } + + private static SignaturePlaceholderData matchPlaceholderDocument( + List<SignaturePlaceholderData> placeholders, String placeholderId, + int matchMode) throws PlaceholderExtractionException { + + if (matchMode == PLACEHOLDER_MATCH_MODE_STRICT) + throw new PlaceholderExtractionException("error.pdf.stamp.09"); + + if (placeholders.size() == 0) + return null; + + if (matchMode == PLACEHOLDER_MATCH_MODE_SORTED) { + // sort all placeholders by the id string if all ids are null do nothing + SignaturePlaceholderData currentFirstSpd = null; + for (int i = 0; i < placeholders.size(); i++) { + SignaturePlaceholderData spd = placeholders.get(i); + if (spd.getId() != null) { + if(currentFirstSpd == null) { + currentFirstSpd = spd; + logger.debug("Setting new current ID: {}", + currentFirstSpd.getId()); + } else { + String currentID = currentFirstSpd.getId(); + String testID = spd.getId(); + logger.debug("Testing placeholder current: {} compare to {}", + currentID, testID); + if(testID.compareToIgnoreCase(currentID) < 0) { + currentFirstSpd = spd; + logger.debug("Setting new current ID: {}", + testID); + } + } + } + } + + if(currentFirstSpd != null) { + logger.info("Running Placeholder sorted mode: using id: {}", currentFirstSpd.getId()); + return currentFirstSpd; + } else { + logger.info("Running Placeholder sorted mode: no placeholder with id found, fallback to first placeholder"); + } + } + + for (int i = 0; i < placeholders.size(); i++) { + SignaturePlaceholderData spd = placeholders.get(i); + if (spd.getId() == null) + return spd; + } + + if (matchMode == PLACEHOLDER_MATCH_MODE_LENIENT) + return placeholders.get(0); + + return null; + } + + private static SignaturePlaceholderData matchPlaceholderPage( + List<SignaturePlaceholderData> placeholders, String placeholderId, + int matchMode) { + + if(matchMode == PLACEHOLDER_MATCH_MODE_SORTED) + return null; + + if (placeholders.size() == 0) + return null; + for (int i = 0; i < placeholders.size(); i++) { + SignaturePlaceholderData data = placeholders.get(i); + if (placeholderId != null && placeholderId.equals(data.getId())) + return data; + if (placeholderId == null && data.getId() == null) + return data; + } + return null; + } + + private void setCurrentPage(int pageNr) { + this.currentPage = pageNr; + } + + @Override + protected void processOperator(Operator operator, List<COSBase> arguments) + throws IOException { + String operation = operator.getName(); + if (operation.equals("Do")) { + COSName objectName = (COSName) arguments.get(0); + PDXObject xobject = (PDXObject) getResources().getXObject(objectName); + if (xobject instanceof PDImageXObject) { + try { + PDImageXObject image = (PDImageXObject) xobject; + SignaturePlaceholderData data = checkImage(image); + if (data != null) { + PDPage page = getCurrentPage(); + Matrix ctm = getGraphicsState() + .getCurrentTransformationMatrix(); + int pageRotation = page.getRotation(); + pageRotation = pageRotation % 360; + double rotationInRadians = Math.toRadians(pageRotation);//(page.findRotation() * Math.PI) / 180; + + AffineTransform rotation = new AffineTransform(); + rotation.setToRotation(rotationInRadians); + AffineTransform rotationInverse = rotation + .createInverse(); + Matrix rotationInverseMatrix = new Matrix(); + rotationInverseMatrix + .setFromAffineTransform(rotationInverse); + Matrix rotationMatrix = new Matrix(); + rotationMatrix.setFromAffineTransform(rotation); + + Matrix unrotatedCTM = ctm + .multiply(rotationInverseMatrix); + + float x = unrotatedCTM.getXPosition(); + float yPos = unrotatedCTM.getYPosition(); + float yScale = unrotatedCTM.getScaleY(); + float y = yPos + yScale; + float w = unrotatedCTM.getScaleX();; + + logger.debug("Page height: {}", page.getCropBox().getHeight()); + logger.debug("Page width: {}", page.getCropBox().getWidth()); + + if(pageRotation == 90) { + y = page.getCropBox().getWidth() - (y * (-1)); + } else if(pageRotation == 180) { + x = page.getCropBox().getWidth() + x; + y = page.getCropBox().getHeight() - (y * (-1)); + } else if(pageRotation == 270) { + x = page.getCropBox().getHeight() + x; + } + + String posString = "p:" + currentPage + ";x:" + x + + ";y:" + y + ";w:" + w; + + logger.debug("Found Placeholder at: {}", posString); + try { + data.setTablePos(new TablePos(posString)); + data.setPlaceholderName(objectName.getName()); + placeholders.add(data); + } catch (PdfAsException e) { + throw new IOException(); + } + } + } catch (NoninvertibleTransformException e) { + throw new IOException(e); + } + } + } else { + super.processOperator(operator, arguments); + } + } + + private Map<String, PDFont> fonts; + + //TODO: pdfbox2 - was override + public Map<String, PDFont> getFonts() { + if (fonts == null) + { + // at least an empty map will be returned + // TODO we should return null instead of an empty map + fonts = new HashMap<String, PDFont>(); + if(this.getResources() != null && this.getResources().getCOSObject() != null) { + COSDictionary fontsDictionary = (COSDictionary) this.getResources().getCOSObject().getDictionaryObject(COSName.FONT); + if (fontsDictionary == null) + { + // ignore we do not want to set anything, never when creating a signature!!!!! + //fontsDictionary = new COSDictionary(); + //this.getResources().getCOSDictionary().setItem(COSName.FONT, fontsDictionary); + } + else + { + for (COSName fontName : fontsDictionary.keySet()) + { + COSBase font = fontsDictionary.getDictionaryObject(fontName); + // data-000174.pdf contains a font that is a COSArray, looks to be an error in the + // PDF, we will just ignore entries that are not dictionaries. + if (font instanceof COSDictionary) + { + PDFont newFont = null; + try + { + newFont = PDFontFactory.createFont((COSDictionary) font); + } + catch (IOException exception) + { + logger.error("error while creating a font", exception); + } + if (newFont != null) + { + fonts.put(fontName.getName(), newFont); + } + } + } + } + } + } + return fonts; + } + + /** + * Checks an image if it is a placeholder for a signature. + * + * @param image + * @return + * @throws IOException + */ + private SignaturePlaceholderData checkImage(PDImageXObject image) + throws IOException { + BufferedImage bimg = image.getImage(); + if (bimg == null) { + String type = image.getSuffix(); + if (type != null) { + type = type.toUpperCase() + " images"; + } else { + type = "Image type"; + } + logger.info("Unable to extract image for QRCode analysis. " + + type + + " not supported. Add additional JAI Image filters to your classpath. Refer to https://jai.dev.java.net. Skipping image."); + return null; + } + if (bimg.getHeight() < 10 || bimg.getWidth() < 10) { + logger.debug("Image too small for QRCode. Skipping image."); + return null; + } + + LuminanceSource source = new BufferedImageLuminanceSource(bimg); + BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); + Result result; + long before = System.currentTimeMillis(); + try { + Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(); + Vector<BarcodeFormat> formats = new Vector<BarcodeFormat>(); + formats.add(BarcodeFormat.QR_CODE); + hints.put(DecodeHintType.POSSIBLE_FORMATS, formats); + result = new MultiFormatReader().decode(bitmap, hints); + + String text = result.getText(); + String profile = null; + String type = null; + String sigKey = null; + String id = null; + if (text != null) { + if (text.startsWith(QR_PLACEHOLDER_IDENTIFIER)) { + String[] data = text.split(";"); + if (data.length > 1) { + for (int i = 1; i < data.length; i++) { + String kvPair = data[i]; + String[] kv = kvPair.split("="); + if (kv.length != 2) { + logger.debug("Invalid parameter in placeholder data: " + + kvPair); + } else { + if (kv[0] + .equalsIgnoreCase(SignaturePlaceholderData.ID_KEY)) { + id = kv[1]; + } else if (kv[0] + .equalsIgnoreCase(SignaturePlaceholderData.PROFILE_KEY)) { + profile = kv[1]; + } else if (kv[0] + .equalsIgnoreCase(SignaturePlaceholderData.SIG_KEY_KEY)) { + sigKey = kv[1]; + } else if (kv[0] + .equalsIgnoreCase(SignaturePlaceholderData.TYPE_KEY)) { + type = kv[1]; + } + } + } + } + return new SignaturePlaceholderData(profile, type, sigKey, + id); + } else { + logger.warn("QR-Code found but does not start with \"" + + QR_PLACEHOLDER_IDENTIFIER + + "\". Ignoring QR placeholder."); + } + } + } catch (ReaderException re) { + if (logger.isDebugEnabled()) { + logger.debug("Could not decode - not a placeholder. needed: " + + (System.currentTimeMillis() - before)); + } + if (!(re instanceof NotFoundException)) { + if (logger.isInfoEnabled()) { + logger.info("Failed to decode image", re); + } + } + } catch (ArrayIndexOutOfBoundsException e) { + if (logger.isInfoEnabled()) { + logger.info("Failed to decode image. Probably a zxing bug", e); + } + } + return null; + } + +} 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 new file mode 100644 index 00000000..429fe518 --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/positioning/Positioning.java @@ -0,0 +1,308 @@ +/******************************************************************************* + * <copyright> Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria </copyright> + * 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 + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +package at.gv.egiz.pdfas.lib.impl.pdfbox2.positioning; + +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +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; + +/** + * Created with IntelliJ IDEA. User: afitzek Date: 8/29/13 Time: 4:30 PM To + * change this template use File | Settings | File Templates. + */ +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. + * @return Returns the PositioningInformation. + * @throws PdfAsException + * F.e. + */ + public static PositioningInstruction determineTablePositioning( + TablePos pos, String signature_type, PDDocument pdfDataSource, + IPDFVisualObject pdf_table, boolean legacy32, boolean legacy40) throws PdfAsException { + return adjustSignatureTableandCalculatePosition(pdfDataSource, + pdf_table, pos, legacy32, legacy40); + } + + 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. + * @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, boolean legacy32, boolean legacy40) throws PdfAsException { + + PdfBoxUtils.checkPDFPermissions(pdfDataSource); + // get pages of currentdocument + + 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(); + // System.out.println("XXXXPAGE="+page+" doc_pages="+doc_pages); + if (page > doc_pages) { + make_new_page = true; + page = doc_pages; + // throw new PDFDocumentException(227, "Page number is to big(=" + // + page+ + // ") cannot be parsed."); + } + } + + 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); + + 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) { + make_new_page = true; + if (!pos.isPauto()) { + // we have to correct pagenumber + page = pdfDataSource.getNumberOfPages(); + } + page++; + // 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) { + make_new_page = true; + if (!pos.isPauto()) { + // we have to correct pagenumber in case of absolute page and + // not enough + // space + page = pdfDataSource.getNumberOfPages(); + } + page++; + // no text --> SIGNATURE_BORDER + pos_y = page_height - SIGNATURE_MARGIN_VERTICAL; + } + return new PositioningInstruction(make_new_page, page, pos_x, pos_y, + pos.rotation); + + } + +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/utils/PdfBoxUtils.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/utils/PdfBoxUtils.java new file mode 100644 index 00000000..85073568 --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/pdfbox2/utils/PdfBoxUtils.java @@ -0,0 +1,73 @@ +package at.gv.egiz.pdfas.lib.impl.pdfbox2.utils; + +import org.apache.pdfbox.cos.COSArray; +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.encryption.AccessPermission; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.pdfas.common.exceptions.PdfAsValidationException; + +public class PdfBoxUtils { + private static final Logger logger = LoggerFactory + .getLogger(PdfBoxUtils.class); + + public static void checkPDFPermissions(PDDocument doc) + throws PdfAsValidationException { + + AccessPermission accessPermission = doc.getCurrentAccessPermission(); + if (doc.isEncrypted()) { + throw new PdfAsValidationException("error.pdf.sig.12", null); + } + + if (!accessPermission.isOwnerPermission()) { + throw new PdfAsValidationException("error.pdf.sig.12", null); + } + + } + + public static int countSignatures(PDDocument doc, String sigName) { + int count = 0; + COSDictionary trailer = doc.getDocument().getTrailer(); + COSDictionary root = (COSDictionary) trailer + .getDictionaryObject(COSName.ROOT); + COSDictionary acroForm = (COSDictionary) root + .getDictionaryObject(COSName.ACRO_FORM); + COSArray fields = (COSArray) acroForm + .getDictionaryObject(COSName.FIELDS); + for (int i = 0; i < fields.size(); i++) { + COSDictionary field = (COSDictionary) fields.getObject(i); + String type = field.getNameAsString("FT"); + if ("Sig".equals(type)) { + String name = field.getString(COSName.T); + if (name != null) { + logger.debug("Found Sig: " + name); + try { + if (name.startsWith(sigName)) { + String numberString = name.replace(sigName, ""); + + logger.debug("Found Number: " + numberString); + + int SigIDX = Integer.parseInt(numberString); + if (SigIDX > count) { + count = SigIDX; + } + } + } catch (Throwable e) { + logger.info("Found a different Signature, we do not need to count this."); + } + } + } + + } + + count++; + + logger.debug("Returning sig number: " + count); + + return count; + } + +} 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 new file mode 100644 index 00000000..d105174b --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox2/PADESPDFBOXSigner.java @@ -0,0 +1,796 @@ +/******************************************************************************* + * <copyright> Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria </copyright> + * 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 + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +package at.gv.egiz.pdfas.lib.impl.signing.pdfbox2; + +import iaik.x509.X509Certificate; + +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +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.pdfwriter.COSWriter; +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.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.graphics.image.PDImageXObject; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface; +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.rendering.ImageType; +import org.apache.pdfbox.rendering.PDFRenderer; +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; +import at.gv.egiz.pdfas.common.settings.SignatureProfileSettings; +import at.gv.egiz.pdfas.common.utils.StreamUtils; +import at.gv.egiz.pdfas.common.utils.TempFileHelper; +import at.gv.egiz.pdfas.lib.api.ByteArrayDataSource; +import at.gv.egiz.pdfas.lib.api.IConfigurationConstants; +import at.gv.egiz.pdfas.lib.api.sign.IPlainSigner; +import at.gv.egiz.pdfas.lib.api.sign.SignParameter; +import at.gv.egiz.pdfas.lib.impl.ErrorExtractor; +import at.gv.egiz.pdfas.lib.impl.SignaturePositionImpl; +import at.gv.egiz.pdfas.lib.impl.configuration.SignatureProfileConfiguration; +import at.gv.egiz.pdfas.lib.impl.pdfbox2.PDFBOXObject; +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; +import at.gv.egiz.pdfas.lib.impl.placeholder.SignaturePlaceholderData; +import at.gv.egiz.pdfas.lib.impl.signing.IPdfSigner; +import at.gv.egiz.pdfas.lib.impl.signing.PDFASSignatureExtractor; +import at.gv.egiz.pdfas.lib.impl.signing.PDFASSignatureInterface; +import at.gv.egiz.pdfas.lib.impl.stamping.IPDFStamper; +import at.gv.egiz.pdfas.lib.impl.stamping.IPDFVisualObject; +import at.gv.egiz.pdfas.lib.impl.stamping.TableFactory; +import at.gv.egiz.pdfas.lib.impl.stamping.ValueResolver; +import at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2.PDFAsVisualSignatureProperties; +import at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2.PdfBoxVisualObject; +import at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2.StamperFactory; +import at.gv.egiz.pdfas.lib.impl.status.OperationStatus; +import at.gv.egiz.pdfas.lib.impl.status.PDFObject; +import at.gv.egiz.pdfas.lib.impl.status.RequestedSignature; +import at.knowcenter.wag.egov.egiz.pdf.PositioningInstruction; +import at.knowcenter.wag.egov.egiz.pdf.TablePos; +import at.knowcenter.wag.egov.egiz.table.Table; + +public class PADESPDFBOXSigner implements IPdfSigner, IConfigurationConstants { + + private static final Logger logger = LoggerFactory.getLogger(PADESPDFBOXSigner.class); + + public void signPDF(PDFObject genericPdfObject, RequestedSignature requestedSignature, + PDFASSignatureInterface genericSigner) throws PdfAsException { + //String fisTmpFile = null; + + PDFAsVisualSignatureProperties properties = null; + + if (!(genericPdfObject instanceof PDFBOXObject)) { + // tODO: + throw new PdfAsException(); + } + + PDFBOXObject pdfObject = (PDFBOXObject) genericPdfObject; + + if (!(genericSigner instanceof PDFASPDFBOXSignatureInterface)) { + // tODO: + throw new PdfAsException(); + } + + PDFASPDFBOXSignatureInterface signer = (PDFASPDFBOXSignatureInterface) genericSigner; + + PDDocument doc = null; + SignatureOptions options = new SignatureOptions(); + COSDocument visualSignatureDocumentGuard = null; + try { + + + doc = pdfObject.getDocument(); + + SignaturePlaceholderData signaturePlaceholderData = PlaceholderFilter + .checkPlaceholderSignature(pdfObject.getStatus(), pdfObject.getStatus().getSettings()); + + TablePos tablePos = null; + + if (signaturePlaceholderData != null) { + // Placeholder found! + 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()); + } + } + + PDSignature signature = new PDSignature(); + signature.setFilter(COSName.getPDFName(signer.getPDFFilter())); // default + // filter + signature.setSubFilter(COSName.getPDFName(signer.getPDFSubFilter())); + + SignatureProfileSettings signatureProfileSettings = TableFactory + .createProfile(requestedSignature.getSignatureProfileID(), pdfObject.getStatus().getSettings()); + + 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); + + // Is visible Signature + if (requestedSignature.isVisual()) { + logger.info("Creating visual siganture block"); + + SignatureProfileConfiguration signatureProfileConfiguration = pdfObject.getStatus() + .getSignatureProfileConfiguration(requestedSignature.getSignatureProfileID()); + + if (tablePos == null) { + // ================================================================ + // PositioningStage (visual) -> find position or use + // fixed + // position + + String posString = pdfObject.getStatus().getSignParamter().getSignaturePosition(); + + TablePos signaturePos = null; + + 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(); + } + } + + boolean legacy32Position = signatureProfileConfiguration.getLegacy32Positioning(); + boolean legacy40Position = signatureProfileConfiguration.getLegacy40Positioning(); + + // 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, legacy32Position, legacy40Position); + + logger.debug("Positioning: {}", positioningInstruction.toString()); + + 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); + // positioningInstruction.setRotation(positioningInstruction.getRotation() + // + 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) { + // Placeholder found! + // replace placeholder + + PDImageXObject img = PDImageXObject.createFromFile("/placeholder/empty.jpg", doc); + + 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()) { + PDDocumentCatalog root = doc.getDocumentCatalog(); + COSBase base = root.getCOSObject().getItem(COSName.OUTPUT_INTENTS); + if (base == null) { + InputStream colorProfile = null; + try { + colorProfile = PDDocumentCatalog.class + .getResourceAsStream("/icm/sRGB Color Space Profile.icm"); + + try { + PDOutputIntent oi = new PDOutputIntent(doc, colorProfile); + oi.setInfo("sRGB IEC61966-2.1"); + oi.setOutputCondition("sRGB IEC61966-2.1"); + oi.setOutputConditionIdentifier("sRGB IEC61966-2.1"); + oi.setRegistryName("http://www.color.org"); + + root.addOutputIntent(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()); + + options.setVisualSignature(properties.getVisibleSignature()); + } + + visualSignatureDocumentGuard = options.getVisualSignature(); + + doc.addSignature(signature, signer, options); + + String sigFieldName = signatureProfileSettings.getSignFieldValue(); + + 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); + + 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!]"); + } + + // PDF-UA + logger.info("Adding pdf/ua content."); + try { + if(true) + throw new Exception("skip"); + 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(); + int parentTreeNextKey = structureTreeRoot.getParentTreeNextKey(); + 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); + + 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"); + } + } + + if (requestedSignature.isVisual()) { + + // if(requestedSignature.getSignaturePosition().) + /* + * PDAcroForm acroForm = + * doc.getDocumentCatalog().getAcroForm(); if (acroForm != + * null) { + * + * @SuppressWarnings("unchecked") List<PDField> fields = + * acroForm.getFields(); PDSignatureField signatureField = + * null; + * + * if (fields != null) { for (PDField pdField : fields) { if + * (pdField instanceof PDSignatureField) { if + * (((PDSignatureField) pdField).getSignature() + * .getDictionary() .equals(signature.getDictionary())) { + * signatureField = (PDSignatureField) pdField; } } } } else + * { logger.warn( + * "Failed to apply rotation! [Cannot find Field list in acroForm!]" + * ); } + * + * if (signatureField != null) { if + * (signatureField.getWidget() != null) { if + * (signatureField.getWidget() + * .getAppearanceCharacteristics() == null) { + * PDAppearanceCharacteristicsDictionary dict = new + * PDAppearanceCharacteristicsDictionary( new + * COSDictionary()); signatureField.getWidget() + * .setAppearanceCharacteristics(dict); } + * + * if (signatureField.getWidget() + * .getAppearanceCharacteristics() != null) { + * signatureField.getWidget() + * .getAppearanceCharacteristics() .setRotation(90); } } } + * else { logger.warn( + * "Failed to apply rotation! [Cannot find signature Field!]" + * ); } } else { logger.warn( + * "Failed to apply rotation! [Cannot find acroForm!]" ); } + */ + } + + + + + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + synchronized (doc) { + this.saveIncremental(bos, doc, pdfObject.getOriginalDocument().getInputStream(), signer); + pdfObject.setSignedDocument(bos.toByteArray()); + } + + } finally { + if (options != null) { + if (options.getVisualSignature() != null) { + options.getVisualSignature().close(); + } + } + } + + + 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(); + } catch (IOException e) { + logger.debug("Failed to close COS Doc!", e); + // Ignore + } + } + + logger.debug("Signature done!"); + + } + } + + public void saveIncremental(OutputStream outStream, PDDocument doc, InputStream inStream, SignatureInterface signer) throws IOException{ + COSWriter writer = null; + try + { + writer = new COSWriter(outStream, inStream); + writer.write(doc, signer); + writer.close(); + } + finally + { + if (writer != null) + { + writer.close(); + } + } + } + + @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(); + } + + PDFBOXObject pdfObject = (PDFBOXObject) genericPdfObject; + PdfBoxUtils.checkPDFPermissions(pdfObject.getDocument()); + } + + @Override + public byte[] rewritePlainSignature(byte[] plainSignature) { + String signature = new COSString(plainSignature).toHexString(); + 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 { + PDFBOXObject pdfObject = (PDFBOXObject) status.getPdfObject(); + + PDDocument origDoc = new PDDocument(); + origDoc.addPage(new PDPage(PDRectangle.A4)); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + origDoc.save(baos); + baos.close(); + + pdfObject.setOriginalDocument(new ByteArrayDataSource(baos.toByteArray())); + + SignatureProfileSettings signatureProfileSettings = TableFactory + .createProfile(requestedSignature.getSignatureProfileID(), pdfObject.getStatus().getSettings()); + + // 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); + + SignatureProfileConfiguration signatureProfileConfiguration = pdfObject.getStatus() + .getSignatureProfileConfiguration(requestedSignature.getSignatureProfileID()); + + String signaturePosString = signatureProfileConfiguration.getDefaultPositioning(); + PositioningInstruction positioningInstruction = null; + if (signaturePosString != null) { + positioningInstruction = Positioning.determineTablePositioning(new TablePos(signaturePosString), "", + origDoc, visualObject, false, false); + } else { + positioningInstruction = Positioning.determineTablePositioning(new TablePos(), "", origDoc, + visualObject, false, false); + } + + origDoc.close(); + + 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); + + 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); + + PDPage firstPage = visualDoc.getDocumentCatalog().getPages().get(0); + + float stdRes = 72; + float targetRes = resolution; + float factor = targetRes / stdRes; + + + int targetPageNumber = 0;//TODO: is this always the case + PDFRenderer pdfRenderer = new PDFRenderer(visualDoc); + BufferedImage outputImage = pdfRenderer.renderImageWithDPI(targetPageNumber, targetRes, ImageType.ARGB); + + //BufferedImage outputImage = firstPage.convertToImage(BufferedImage.TYPE_4BYTE_ABGR, (int) targetRes); + + BufferedImage cutOut = new BufferedImage((int) (position.getWidth() * factor), + (int) (position.getHeight() * factor), BufferedImage.TYPE_4BYTE_ABGR); + + 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); + } + } +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox2/PDFASPDFBOXExtractorInterface.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox2/PDFASPDFBOXExtractorInterface.java new file mode 100644 index 00000000..c99e7c59 --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox2/PDFASPDFBOXExtractorInterface.java @@ -0,0 +1,7 @@ +package at.gv.egiz.pdfas.lib.impl.signing.pdfbox2; + +import at.gv.egiz.pdfas.lib.impl.signing.PDFASSignatureExtractor; + +public interface PDFASPDFBOXExtractorInterface extends PDFASSignatureExtractor, PDFASPDFBOXSignatureInterface { + +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox2/PDFASPDFBOXSignatureInterface.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox2/PDFASPDFBOXSignatureInterface.java new file mode 100644 index 00000000..cc260ece --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox2/PDFASPDFBOXSignatureInterface.java @@ -0,0 +1,10 @@ +package at.gv.egiz.pdfas.lib.impl.signing.pdfbox2; + +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface; + +import at.gv.egiz.pdfas.lib.impl.signing.PDFASSignatureInterface; + +public interface PDFASPDFBOXSignatureInterface extends PDFASSignatureInterface, SignatureInterface { + public void setPDSignature(PDSignature signature); +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox2/PdfboxSignerWrapper.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox2/PdfboxSignerWrapper.java new file mode 100644 index 00000000..7aaf1510 --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox2/PdfboxSignerWrapper.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * <copyright> Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria </copyright> + * 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 + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +package at.gv.egiz.pdfas.lib.impl.signing.pdfbox2; + +import java.io.IOException; +import java.io.InputStream; +import java.security.SignatureException; +import java.util.Calendar; + +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +import at.gv.egiz.pdfas.common.exceptions.PdfAsWrappedIOException; +import at.gv.egiz.pdfas.common.utils.PDFUtils; +import at.gv.egiz.pdfas.common.utils.StreamUtils; +import at.gv.egiz.pdfas.lib.api.sign.IPlainSigner; +import at.gv.egiz.pdfas.lib.api.sign.SignParameter; +import at.gv.egiz.pdfas.lib.impl.status.RequestedSignature; + +public class PdfboxSignerWrapper implements PDFASPDFBOXSignatureInterface { + + private static final Logger logger = LoggerFactory + .getLogger(PdfboxSignerWrapper.class); + + private IPlainSigner signer; + private PDSignature signature; + private int[] byteRange; + private Calendar date; + private SignParameter parameters; + private RequestedSignature requestedSignature; + + public PdfboxSignerWrapper(IPlainSigner signer, SignParameter parameters, RequestedSignature requestedSignature) { + this.signer = signer; + this.date = Calendar.getInstance(); + this.parameters = parameters; + this.requestedSignature = requestedSignature; + } + + public byte[] sign(InputStream inputStream) throws IOException { + byte[] data = StreamUtils.inputStreamToByteArray(inputStream); + byteRange = PDFUtils.extractSignatureByteRange(data); + int[] byteRange2 = signature.getByteRange(); + logger.debug("Byte Range 2: " + byteRange2); + try { + logger.debug("Signing with Pdfbox Wrapper"); + byte[] signature = signer.sign(data, byteRange, this.parameters, this.requestedSignature); + + return signature; + } catch (PdfAsException e) { + throw new PdfAsWrappedIOException(e); + } + } + + public int[] getByteRange() { + return byteRange; + } + + public String getPDFSubFilter() { + return this.signer.getPDFSubFilter(); + } + + public String getPDFFilter() { + return this.signer.getPDFFilter(); + } + + public void setPDSignature(PDSignature signature) { + this.signature = signature; + } + + public Calendar getSigningDate() { + return this.date; + } +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox2/SignatureDataExtractor.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox2/SignatureDataExtractor.java new file mode 100644 index 00000000..78e48e5e --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox2/SignatureDataExtractor.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * <copyright> Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria </copyright> + * 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 + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +package at.gv.egiz.pdfas.lib.impl.signing.pdfbox2; + +import iaik.x509.X509Certificate; + +import java.io.IOException; +import java.io.InputStream; +import java.security.SignatureException; +import java.util.Calendar; + +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; + +import at.gv.egiz.pdfas.common.utils.StreamUtils; + +public class SignatureDataExtractor implements PDFASPDFBOXExtractorInterface { + + protected X509Certificate certificate; + protected byte[] signatureData; + + protected String pdfSubFilter; + protected String pdfFilter; + protected PDSignature signature; + protected int[] byteRange; + protected Calendar date; + + public SignatureDataExtractor(X509Certificate certificate, + String filter, String subfilter, Calendar date) { + this.certificate = certificate; + this.pdfFilter = filter; + this.pdfSubFilter = subfilter; + this.date = date; + } + + public X509Certificate getCertificate() { + return certificate; + } + + public String getPDFSubFilter() { + return this.pdfSubFilter; + } + + public String getPDFFilter() { + return this.pdfFilter; + } + + public byte[] getSignatureData() { + return this.signatureData; + } + + public byte[] sign(InputStream content) throws IOException { + signatureData = StreamUtils.inputStreamToByteArray(content); + byteRange = this.signature.getByteRange(); + return new byte[] { 0 }; + } + + public void setPDSignature(PDSignature signature) { + this.signature = signature; + } + + public int[] getByteRange() { + return byteRange; + } + + public Calendar getSigningDate() { + return this.date; + } + + + +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/FontInfoCache.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/FontInfoCache.java new file mode 100644 index 00000000..c208820e --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/FontInfoCache.java @@ -0,0 +1,8 @@ +package at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2; + +public class FontInfoCache { + String filename; + String fontName; + String fontFamily; + String fontPath; +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/IDGenerator.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/IDGenerator.java new file mode 100644 index 00000000..ef898d74 --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/IDGenerator.java @@ -0,0 +1,5 @@ +package at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2; + +public interface IDGenerator { + public String createHashedId(String value); +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/ImageObject.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/ImageObject.java new file mode 100644 index 00000000..16af5cfb --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/ImageObject.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * <copyright> Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria </copyright> + * 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 + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +package at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2; + +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; + + +public class ImageObject { + private PDImageXObject image; + private float width; + private float height; + + public ImageObject(PDImageXObject image, float width, float height) { + this.image = image; + this.width = width; + this.height = height; + } + + public PDImageXObject getImage() { + return image; + } + + public void setImage(PDImageXObject image) { + this.image = image; + } + + public float getWidth() { + return width; + } + + public void setWidth(float width) { + this.width = width; + } + + public float getHeight() { + return height; + } + + public void setHeight(float height) { + this.height = height; + } + +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsTemplateCreator.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsTemplateCreator.java new file mode 100644 index 00000000..b8c15119 --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsTemplateCreator.java @@ -0,0 +1,166 @@ +/******************************************************************************* + * <copyright> Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria </copyright> + * 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 + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +package at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDResources; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.common.PDStream; +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDFTemplateCreator; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDFTemplateStructure; +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; + +public class PDFAsTemplateCreator extends PDFTemplateCreator { + + PDFAsVisualSignatureBuilder pdfBuilder; + private static final Logger logger = LoggerFactory.getLogger(PDFAsTemplateCreator.class); + + public PDFAsTemplateCreator(PDFAsVisualSignatureBuilder bookBuilder) { + super(bookBuilder); + this.pdfBuilder = bookBuilder; + } + + + public InputStream buildPDF(PDFAsVisualSignatureDesigner properties, PDDocument originalDocument) + throws IOException, PdfAsException { + logger.debug("pdf building has been started"); + PDFTemplateStructure pdfStructure = pdfBuilder.getStructure(); + + // we create array of [Text, ImageB, ImageC, ImageI] + this.pdfBuilder.createProcSetArray(); + + //create page + this.pdfBuilder.createPage(properties); + PDPage page = pdfStructure.getPage(); + + //create template + this.pdfBuilder.createTemplate(page); + PDDocument template = pdfStructure.getTemplate(); + + //create /AcroForm + this.pdfBuilder.createAcroForm(template); + PDAcroForm acroForm = pdfStructure.getAcroForm(); + + // AcroForm contains singature fields + this.pdfBuilder.createSignatureField(acroForm); + PDSignatureField pdSignatureField = pdfStructure.getSignatureField(); + + // create signature + this.pdfBuilder.createSignature(pdSignatureField, page, properties.getSignatureFieldName()); + + // that is /AcroForm/DR entry + this.pdfBuilder.createAcroFormDictionary(acroForm, pdSignatureField); + + // create AffineTransform + this.pdfBuilder.createAffineTransform(properties.getAffineTransformParams()); + //AffineTransform transform = pdfStructure.getAffineTransform(); + + // rectangle, formatter, image. /AcroForm/DR/XObject contains that form + this.pdfBuilder.createSignatureRectangle(pdSignatureField, properties, properties.getRotation() + properties.getPageRotation()); + this.pdfBuilder.createFormaterRectangle(properties.getFormaterRectangleParams()); + PDRectangle formater = pdfStructure.getFormatterRectangle(); + + //this.pdfBuilder.createSignatureImage(template, properties.getImageStream()); + + // create form stream, form and resource. + this.pdfBuilder.createHolderFormStream(template); + PDStream holderFormStream = pdfStructure.getHolderFormStream(); + this.pdfBuilder.createHolderFormResources(); + PDResources holderFormResources = pdfStructure.getHolderFormResources(); + this.pdfBuilder.createHolderForm(holderFormResources, holderFormStream, formater); + + // that is /AP entry the appearance dictionary. + this.pdfBuilder.createAppearanceDictionary(pdfStructure.getHolderForm(), pdSignatureField, + properties.getRotation() + properties.getPageRotation()); + + // inner formstream, form and resource (hlder form containts inner form) + this.pdfBuilder.createInnerFormStreamPdfAs(template, originalDocument); + this.pdfBuilder.createInnerFormResource(); + PDResources innerFormResource = pdfStructure.getInnerFormResources(); + this.pdfBuilder.createInnerForm(innerFormResource, pdfStructure.getInnerFormStream(), formater); + PDFormXObject innerForm = pdfStructure.getInnerForm(); + + // inner form must be in the holder form as we wrote + this.pdfBuilder.insertInnerFormToHolerResources(innerForm, holderFormResources); + + // Image form is in this structure: /AcroForm/DR/FRM0/Resources/XObject/n0 + //this.pdfBuilder.createImageFormStream(template); + //PDStream imageFormStream = pdfStructure.getImageFormStream(); + //this.pdfBuilder.createImageFormResources(); + //PDResources imageFormResources = pdfStructure.getImageFormResources(); + //this.pdfBuilder.createImageForm(imageFormResources, innerFormResource, imageFormStream, formater, transform, + // pdfStructure.getJpedImage()); + + // now inject procSetArray + /*this.pdfBuilder.injectProcSetArray(innerForm, page, innerFormResource, imageFormResources, holderFormResources, + pdfStructure.getProcSet());*/ + this.pdfBuilder.injectProcSetArray(innerForm, page, innerFormResource, null, holderFormResources, + pdfStructure.getProcSet()); + + + /*String imgFormName = pdfStructure.getImageFormName(); + String imgName = pdfStructure.getImageName();*/ + String innerFormName = pdfStructure.getInnerFormName().getName(); + + // now create Streams of AP + /*this.pdfBuilder.injectAppearanceStreams(holderFormStream, imageFormStream, imageFormStream, imgFormName, + imgName, innerFormName, properties);*/ + this.pdfBuilder.injectAppearanceStreams(holderFormStream, null, null, null, + null, innerFormName, properties); + this.pdfBuilder.createVisualSignature(template); + this.pdfBuilder.createWidgetDictionary(pdSignatureField, holderFormResources); + + ByteArrayInputStream in = null; + + //COSDocument doc = pdfStructure.getVisualSignature(); + //doc. + //in = pdfStructure.getTemplateAppearanceStream(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + template.save(baos); + baos.close(); + in = new ByteArrayInputStream(baos.toByteArray()); + + + logger.debug("stream returning started, size= " + in.available()); + + // we must close the document + this.pdfBuilder.closeTemplate(template); + + // return result of the stream + return in; + } +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsVisualSignatureBuilder.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsVisualSignatureBuilder.java new file mode 100644 index 00000000..30487ead --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsVisualSignatureBuilder.java @@ -0,0 +1,659 @@ +/******************************************************************************* + * <copyright> Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria </copyright> + * 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 + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +package at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2; + +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.io.IOUtils; +import org.apache.pdfbox.cos.COSArray; +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdfparser.PDFParser; +import org.apache.pdfbox.pdfparser.PDFStreamParser; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.PDResources; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.common.PDStream; +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; +import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory; +import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSigBuilder; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +import at.gv.egiz.pdfas.common.settings.ISettings; +import at.gv.egiz.pdfas.common.utils.ImageUtils; +import at.knowcenter.wag.egov.egiz.table.Entry; + +public class PDFAsVisualSignatureBuilder extends PDVisibleSigBuilder implements + IDGenerator { + + private static final Logger logger = LoggerFactory + .getLogger(PDFAsVisualSignatureBuilder.class); + + private PDFAsVisualSignatureProperties properties; + private PDFAsVisualSignatureDesigner designer; + private ISettings settings; + private PDResources innerFormResources; + private Map<String, ImageObject> images = new HashMap<String, ImageObject>(); + + public PDFAsVisualSignatureBuilder( + PDFAsVisualSignatureProperties properties, ISettings settings, + PDFAsVisualSignatureDesigner designer) { + this.properties = properties; + this.settings = settings; + this.designer = designer; + } + + @Override + public void createProcSetArray() { + COSArray procSetArr = new COSArray(); + procSetArr.add(COSName.getPDFName("PDF")); + procSetArr.add(COSName.getPDFName("Text")); + procSetArr.add(COSName.getPDFName("ImageC")); + procSetArr.add(COSName.getPDFName("ImageB")); + procSetArr.add(COSName.getPDFName("ImageI")); + getStructure().setProcSet(procSetArr); + logger.debug("ProcSet array has been created"); + } + + public void createMyPage(PDFAsVisualSignatureDesigner properties) { + PDPage page = properties.getSignaturePage(); + if (page == null) { + page = new PDPage(); + page.setMediaBox(new PDRectangle(properties.getPageWidth(), + properties.getPageHeight())); + + } + getStructure().setPage(page); + logger.info("PDF page has been created"); + } + + @Override + public void createTemplate(PDPage page) throws IOException { + PDDocument template = new PDDocument(); + + template.addPage(page); + getStructure().setTemplate(template); + } + + public String createHashedId(String value) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + md.reset(); + return Hex.encodeHexString(md.digest(value.getBytes("UTF-8"))); + } catch (Throwable e) { + logger.warn("Failed to generate ID for Image using value", e); + return value; + } + } + + private void readTableResources(PDFBoxTable table, PDDocument template) + throws PdfAsException, IOException { + + float[] colsSizes = table.getColsRelativeWith(); + int max_cols = table.getColCount(); + float padding = table.getPadding(); + if (colsSizes == null) { + colsSizes = new float[max_cols]; + // set the column ratio for all columns to 1 + for (int cols_idx = 0; cols_idx < colsSizes.length; cols_idx++) { + colsSizes[cols_idx] = 1; + } + } + + logger.debug("TOTAL Width: " + table.getWidth()); + + float total = 0; + + for (int cols_idx = 0; cols_idx < colsSizes.length; cols_idx++) { + total += colsSizes[cols_idx]; + } + + for (int cols_idx = 0; cols_idx < colsSizes.length; cols_idx++) { + colsSizes[cols_idx] = (colsSizes[cols_idx] / total) + * table.getWidth(); + } + + for (int cols_idx = 0; cols_idx < colsSizes.length; cols_idx++) { + logger.debug("Col: " + cols_idx + " : " + colsSizes[cols_idx]); + } + + /* + * if(!addedFonts.contains(table.getFont().getFont(null))) { PDFont font + * = table.getFont().getFont(template); addedFonts.add(font); + * innerFormResources.addFont(font); } + * + * if(!addedFonts.contains(table.getValueFont().getFont(null))) { PDFont + * font = table.getValueFont().getFont(template); addedFonts.add(font); + * innerFormResources.addFont(font); } + */ + + for (int i = 0; i < table.getRowCount(); i++) { + ArrayList<Entry> row = table.getRow(i); + for (int j = 0; j < row.size(); j++) { + Entry cell = (Entry) row.get(j); + if (cell.getType() == Entry.TYPE_IMAGE) { + String img_value = (String) cell.getValue(); + String img_ref = createHashedId(img_value); + if (!images.containsKey(img_ref)) { + BufferedImage img = ImageUtils.getImage(img_value, + settings); + + float width = colsSizes[j]; + float height = table.getRowHeights()[i] + padding * 2; + + float iwidth = (int) Math.floor((double) width); + iwidth -= 2 * padding; + + float iheight = (int) Math.floor((double) height); + iheight -= 2 * padding; + + float origWidth = (float) img.getWidth(); + float origHeight = (float) img.getHeight(); + + if (table.style != null) { + if (table.style.getImageScaleToFit() != null) { + iwidth = table.style.getImageScaleToFit() + .getWidth(); + iheight = table.style.getImageScaleToFit() + .getHeight(); + } + } + + float wfactor = iwidth / origWidth; + float hfactor = iheight / origHeight; + float scaleFactor = wfactor; + if (hfactor < wfactor) { + scaleFactor = hfactor; + } + + iwidth = (float) Math + .floor((double) (scaleFactor * origWidth)); + iheight = (float) Math + .floor((double) (scaleFactor * origHeight)); + + logger.debug("Scaling image to: " + iwidth + " x " + + iheight); + + if (this.designer.properties + .getSignatureProfileSettings().isPDFA()) { + img = ImageUtils.removeAlphaChannel(img); + } else { + if (img.getAlphaRaster() == null + && img.getColorModel().hasAlpha()) { + img = ImageUtils.removeAlphaChannel(img); + } + } + // img = ImageUtils.convertRGBAToIndexed(img); + + PDImageXObject pdImage = LosslessFactory.createFromImage(template, img); + + + ImageObject image = new ImageObject(pdImage, iwidth, + iheight); + images.put(img_ref, image); + innerFormResources.add(pdImage, "Im"); + } + } else if (cell.getType() == Entry.TYPE_TABLE) { + PDFBoxTable tbl_value = (PDFBoxTable) cell.getValue(); + readTableResources(tbl_value, template); + } + } + } + } + + public void createInnerFormStreamPdfAs(PDDocument template, PDDocument origDoc) + throws PdfAsException { + try { + + // Hint we have to create all PDXObjectImages before creating the + // PDPageContentStream + // only PDFbox developers know why ... + // if (getStructure().getPage().getResources() != null) { + // innerFormResources = getStructure().getPage().getResources(); + // } else { + innerFormResources = new PDResources(); + getStructure().getPage().setResources(innerFormResources); + // } + readTableResources(properties.getMainTable(), template); + + PDPageContentStream stream = new PDPageContentStream(template, + getStructure().getPage()); + // stream.setFont(PDType1Font.COURIER, 5); + TableDrawUtils.drawTable(getStructure().getPage(), stream, 1, 1, + designer.getWidth(), designer.getHeight(), + properties.getMainTable(), template, false, + innerFormResources, images, settings, this, properties); + stream.close(); + PDStream innterFormStream = new PDStream(template,getStructure().getPage().getContents()); + + getStructure().setInnterFormStream(innterFormStream); + logger.debug("Strean of another form (inner form - it would be inside holder form) has been created"); + + } catch (Throwable e) { + logger.warn("Failed to create visual signature block", e); + throw new PdfAsException("Failed to create visual signature block", + e); + } + } + + @Override + public void injectProcSetArray(PDFormXObject innerForm, PDPage page, + PDResources innerFormResources, PDResources imageFormResources, + PDResources holderFormResources, COSArray procSet) { + innerForm.getResources().getCOSObject() + .setItem(COSName.PROC_SET, procSet); // + page.getCOSObject().setItem(COSName.PROC_SET, procSet); + innerFormResources.getCOSObject() + .setItem(COSName.PROC_SET, procSet); + /* + * imageFormResources.getCOSDictionary() .setItem(COSName.PROC_SET, + * procSet); + */ + holderFormResources.getCOSObject().setItem(COSName.PROC_SET, + procSet); + logger.debug("inserted ProcSet to PDF"); + } + + public void injectAppearanceStreams(PDStream holderFormStream, + PDStream innterFormStream, PDStream imageFormStream, + String imageObjectName, String imageName, String innerFormName, + PDFAsVisualSignatureDesigner properties) throws IOException { + + // 100 means that document width is 100% via the rectangle. if rectangle + // is 500px, images 100% is 500px. + // String imgFormComment = "q "+imageWidthSize+ " 0 0 50 0 0 cm /" + + // imageName + " Do Q\n" + builder.toString(); + /* + * String imgFormComment = "q " + 100 + " 0 0 50 0 0 cm /" + imageName + + * " Do Q\n"; + */ + double m00 = getStructure().getAffineTransform().getScaleX(); + double m10 = getStructure().getAffineTransform().getShearY(); + double m01 = getStructure().getAffineTransform().getShearX(); + double m11 = getStructure().getAffineTransform().getScaleY(); + double m02 = getStructure().getAffineTransform().getTranslateX(); + double m12 = getStructure().getAffineTransform().getTranslateY(); + + String holderFormComment = "q " + m00 + " " + m10 + " " + m01 + " " + + m11 + " " + m02 + " " + m12 + " cm /" + innerFormName + + " Do Q"; + + logger.debug("Holder Form Stream: " + holderFormComment); + + // String innerFormComment = "q 1 0 0 1 0 0 cm /" + imageObjectName + + // " Do Q\n"; + String innerFormComment = IOUtils.toString(getStructure().getInnerFormStream().toByteArray()); + + // .getInputStreamAsString();//TODO: pdfbox2 - get the string from the stream + + // logger.debug("Inner Form Stream: " + innerFormComment); + + // appendRawCommands(getStructure().getInnterFormStream().createOutputStream(), + // getStructure().getInnterFormStream().getInputStreamAsString()); + + appendRawCommands(getStructure().getHolderFormStream() + .createOutputStream(), holderFormComment.trim().replace("\n", "").replace("\r", "")); + appendRawCommands(getStructure().getInnerFormStream() + .createOutputStream(), innerFormComment/*.trim().replace("\n", "").replace("\r", "")*/); + // appendRawCommands(getStructure().getImageFormStream().createOutputStream(), + // imgFormComment); + logger.debug("Injected apereance stream to pdf"); + + } + + public void createPage(PDFAsVisualSignatureDesigner properties) { + PDPage page = new PDPage(); + page.setMediaBox(new PDRectangle(properties.getPageWidth(), properties + .getPageHeight())); + page.setRotation(properties.getPageRotation()); + getStructure().setPage(page); + logger.debug("PDF page has been created"); + } + + public void createAcroForm(PDDocument template) { + PDAcroForm theAcroForm = new PDAcroForm(template); + template.getDocumentCatalog().setAcroForm(theAcroForm); + getStructure().setAcroForm(theAcroForm); + logger.debug("Acro form page has been created"); + } + + public void createSignatureField(PDAcroForm acroForm) throws IOException { + PDSignatureField sf = new PDSignatureField(acroForm); + getStructure().setSignatureField(sf); + logger.debug("Signature field has been created"); + } + + public void createSignature(PDSignatureField pdSignatureField, PDPage page, + String signatureName) throws IOException { + PDSignature pdSignature = new PDSignature(); + pdSignatureField.setSignature(pdSignature); + pdSignatureField.getWidget().setPage(page); + page.getAnnotations().add(pdSignatureField.getWidget()); + pdSignature.setName(signatureName); + pdSignature.setByteRange(new int[] { 0, 0, 0, 0 }); + pdSignature.setContents(new byte[4096]); + getStructure().setPdSignature(pdSignature); + logger.debug("PDSignatur has been created"); + } + + public void createAcroFormDictionary(PDAcroForm acroForm, + PDSignatureField signatureField) throws IOException { + @SuppressWarnings("unchecked") + List<PDField> acroFormFields = acroForm.getFields(); + COSDictionary acroFormDict = acroForm.getCOSObject(); + acroFormDict.setDirect(true); + acroFormDict.setInt(COSName.SIG_FLAGS, 3); + acroFormFields.add(signatureField); + acroFormDict.setString(COSName.DA, "/sylfaen 0 Tf 0 g"); + getStructure().setAcroFormFields(acroFormFields); + getStructure().setAcroFormDictionary(acroFormDict); + logger.debug("AcroForm dictionary has been created"); + } + + public void createSignatureRectangle(PDSignatureField signatureField, + PDFAsVisualSignatureDesigner properties, float degrees) + throws IOException { + + PDRectangle rect = new PDRectangle(); + + Point2D upSrc = new Point2D.Float(); + upSrc.setLocation(properties.getxAxis() + properties.getWidth(), + properties.getPageHeight() - properties.getyAxis()); + + Point2D llSrc = new Point2D.Float(); + llSrc.setLocation(properties.getxAxis(), properties.getPageHeight() + - properties.getyAxis() - properties.getHeight()); + + rect.setUpperRightX((float) upSrc.getX()); + rect.setUpperRightY((float) upSrc.getY()); + rect.setLowerLeftY((float) llSrc.getY()); + rect.setLowerLeftX((float) llSrc.getX()); + logger.debug("orig rectangle of signature has been created: {}", + rect.toString()); + + AffineTransform transform = new AffineTransform(); + transform.setToIdentity(); + if (degrees % 360 != 0) { + transform.setToRotation(Math.toRadians(degrees), llSrc.getX(), + llSrc.getY()); + } + + Point2D upDst = new Point2D.Float(); + transform.transform(upSrc, upDst); + + Point2D llDst = new Point2D.Float(); + transform.transform(llSrc, llDst); + + float xPos = properties.getxAxis(); + float yPos = properties.getPageHeight() - properties.getyAxis(); + logger.debug("POS {} x {}", xPos, yPos); + logger.debug("SIZE {} x {}", properties.getWidth(), + properties.getHeight()); + // translate according to page! rotation + int pageRotation = properties.getPageRotation(); + AffineTransform translate = new AffineTransform(); + switch (pageRotation) { + case 90: + translate.setToTranslation( + properties.getPageHeight() + - (properties.getPageHeight() - properties + .getyAxis()) - properties.getxAxis() + + properties.getHeight(), + properties.getxAxis() + + properties.getHeight() + - (properties.getPageHeight() - properties + .getyAxis())); + break; + case 180: + // translate.setToTranslation(properties.getPageWidth() - + // properties.getxAxis() - properties.getxAxis(), + // properties.getPageHeight() - properties.getyAxis() + + // properties.getHeight()); + translate.setToTranslation( + properties.getPageWidth() - 2 * xPos, + properties.getPageHeight() - 2 + * (yPos - properties.getHeight())); + break; + case 270: + translate.setToTranslation(-properties.getHeight() + yPos - xPos, + properties.getPageWidth() - (yPos - properties.getHeight()) + - xPos); + break; + } + + translate.transform(upDst, upDst); + translate.transform(llDst, llDst); + + rect.setUpperRightX((float) upDst.getX()); + rect.setUpperRightY((float) upDst.getY()); + rect.setLowerLeftY((float) llDst.getY()); + rect.setLowerLeftX((float) llDst.getX()); + logger.debug("rectangle of signature has been created: {}", + rect.toString()); + signatureField.getWidget().setRectangle(rect); + getStructure().setSignatureRectangle(rect); + logger.debug("rectangle of signature has been created"); + } + + public void createAffineTransform(float[] params) { + AffineTransform transform = new AffineTransform(params[0], params[1], + params[2], params[3], params[4], params[5]); + // transform.rotate(90); + getStructure().setAffineTransform(transform); + logger.debug("Matrix has been added"); + } + + public void createSignatureImage(PDDocument template, + InputStream inputStream) throws IOException { + PDImageXObject img = JPEGFactory.createFromStream(template, inputStream); + getStructure().setImage(img); + logger.debug("Visible Signature Image has been created"); + // pdfStructure.setTemplate(template); + inputStream.close(); + + } + + public void createFormaterRectangle(float[] params) { + + PDRectangle formrect = new PDRectangle(); + float[] translated = new float[4]; + getStructure().getAffineTransform().transform(params, 0, translated, 0, + 2); + + formrect.setUpperRightX(translated[0]); + formrect.setUpperRightY(translated[1]); + formrect.setLowerLeftX(translated[2]); + formrect.setLowerLeftY(translated[3]); + + getStructure().setFormatterRectangle(formrect); + logger.debug("Formater rectangle has been created"); + + } + + public void createHolderFormStream(PDDocument template) { + PDStream holderForm = new PDStream(template); + getStructure().setHolderFormStream(holderForm); + logger.debug("Holder form Stream has been created"); + } + + public void createHolderFormResources() { + PDResources holderFormResources = new PDResources(); + getStructure().setHolderFormResources(holderFormResources); + logger.debug("Holder form resources have been created"); + + } + + public void createHolderForm(PDResources holderFormResources, + PDStream holderFormStream, PDRectangle formrect) { + + PDFormXObject holderForm = new PDFormXObject(holderFormStream); + holderForm.setResources(holderFormResources); + holderForm.setBBox(formrect); + holderForm.setFormType(1); + getStructure().setHolderForm(holderForm); + logger.debug("Holder form has been created"); + + } + + public void createAppearanceDictionary(PDFormXObject holderForml, + PDSignatureField signatureField, float degrees) throws IOException { + + PDAppearanceDictionary appearance = new PDAppearanceDictionary(); + appearance.getCOSObject().setDirect(true); + + PDAppearanceStream appearanceStream = new PDAppearanceStream( + holderForml.getCOSStream()); + AffineTransform transform = new AffineTransform(); + transform.setToIdentity(); + transform.rotate(Math.toRadians(degrees)); + appearanceStream.setMatrix(transform); + appearance.setNormalAppearance(appearanceStream); + signatureField.getWidget().setAppearance(appearance); + + getStructure().setAppearanceDictionary(appearance); + logger.debug("PDF appereance Dictionary has been created"); + + } + + public void createInnerFormResource() { + getStructure().setInnerFormResources(innerFormResources); + logger.debug("Resources of another form (inner form - it would be inside holder form) have been created"); + } + + public void createInnerForm(PDResources innerFormResources, + PDStream innerFormStream, PDRectangle formrect) { + PDFormXObject innerForm = new PDFormXObject(innerFormStream); + innerForm.setResources(innerFormResources); + innerForm.setBBox(formrect); + innerForm.setFormType(1); + getStructure().setInnerForm(innerForm); + logger.debug("Another form (inner form - it would be inside holder form) have been created"); + + } + + public void insertInnerFormToHolerResources(PDFormXObject innerForm, + PDResources holderFormResources) { + COSName name = holderFormResources.add(innerForm, "FRM");//TODO: pdfbox2 - is this right? + getStructure().setInnerFormName(name); + logger.debug("Alerady inserted inner form inside holder form"); + } + + public void createImageFormStream(PDDocument template) { + PDStream imageFormStream = new PDStream(template); + getStructure().setImageFormStream(imageFormStream); + logger.debug("Created image form Stream"); + } + + public void createImageFormResources() { + PDResources imageFormResources = new PDResources(); + getStructure().setImageFormResources(imageFormResources); + logger.debug("Created image form Resources"); + } + + public void createImageForm(PDResources imageFormResources, + PDResources innerFormResource, PDStream imageFormStream, + PDRectangle formrect, AffineTransform affineTransform, PDFormXObject img) + throws IOException { + + /* + * if you need text on the visible signature + * + * PDFont font = PDTrueTypeFont.loadTTF(this.pdfStructure.getTemplate(), + * new File("D:\\arial.ttf")); font.setFontEncoding(new + * WinAnsiEncoding()); + * + * Map<String, PDFont> fonts = new HashMap<String, PDFont>(); + * fonts.put("arial", font); + */ + PDFormXObject imageForm = new PDFormXObject(imageFormStream); + imageForm.setBBox(formrect); + imageForm.setMatrix(affineTransform); + imageForm.setResources(imageFormResources); + imageForm.setFormType(1); + /* + * imageForm.getResources().addFont(font); + * imageForm.getResources().setFonts(fonts); + */ + + imageFormResources.getCOSObject().setDirect(true); + COSName imageFormName = innerFormResource.add(imageForm, "n");//TODO: pdfbox2 - is this right? + COSName imageName = imageFormResources.add(img, "img"); + this.getStructure().setImageForm(imageForm); + this.getStructure().setImageFormName(imageFormName); + this.getStructure().setImageName(imageName); + logger.debug("Created image form"); + } + + public void appendRawCommands(OutputStream os, String commands) + throws IOException { + os.write(commands.getBytes("UTF-8")); + os.close(); + } + + public void createVisualSignature(PDDocument template) { + this.getStructure().setVisualSignature(template.getDocument()); + logger.debug("Visible signature has been created"); + + } + + public void createWidgetDictionary(PDSignatureField signatureField, + PDResources holderFormResources) throws IOException { + + COSDictionary widgetDict = signatureField.getWidgets().get(0).getCOSObject();//TODO: pdfbox2 - is this right was getWidget before? + widgetDict.setNeedToBeUpdated(true); + widgetDict.setItem(COSName.DR, holderFormResources.getCOSObject()); + + getStructure().setWidgetDictionary(widgetDict); + logger.debug("WidgetDictionary has been crated"); + } + + public void closeTemplate(PDDocument template) throws IOException { + template.close(); + this.getStructure().getTemplate().close(); + } + +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsVisualSignatureDesigner.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsVisualSignatureDesigner.java new file mode 100644 index 00000000..33450b56 --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsVisualSignatureDesigner.java @@ -0,0 +1,472 @@ +/******************************************************************************* + * <copyright> Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria </copyright> + * 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 + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +package at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import javax.imageio.ImageIO; + +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; + +public class PDFAsVisualSignatureDesigner { + +// private static final Logger logger = LoggerFactory.getLogger(PDFAsVisualSignatureDesigner.class); + + private Float sigImgWidth; + private Float sigImgHeight; + private float xAxis; + private float yAxis; + private float pageHeight; + private float pageWidth; + private InputStream imgageStream; + private String signatureFieldName = "sig"; // default + private float[] formaterRectangleParams = { 0, 0, 100, 50 }; // default + //private float[] AffineTransformParams = { 0, 1, -1, 0, 0, 0 }; // default + private float[] AffineTransformParams = { 1, 0, 0, 1, 0, 0 }; // default + private float imageSizeInPercents; + private PDDocument document = null; + private int page = 0; + private int pageRotation = 0; + private boolean newpage = false; + PDFAsVisualSignatureProperties properties; + + /** + * + * @param doc + * - Already created PDDocument of your PDF document + * @param imageStream + * @param page + * @throws IOException + * - If we can't read, flush, or can't close stream + */ + public PDFAsVisualSignatureDesigner(PDDocument doc, int page, + PDFAsVisualSignatureProperties properties, boolean newpage) throws IOException { + this.properties = properties; + calculatePageSize(doc, page, newpage); + document = doc; + this.page = page; + this.newpage = newpage; + } + + /** + * Each page of document can be different sizes. + * + * @param document + * @param page + */ + private void calculatePageSize(PDDocument document, int page, boolean newpage) { + + if (page < 1) { + throw new IllegalArgumentException("First page of pdf is 1, not " + + page); + } + + PDPageTree pages = document.getDocumentCatalog().getPages(); + if(newpage) { + PDPage lastPage = (PDPage) pages.get(pages.getCount()-1); + PDRectangle mediaBox = lastPage.getMediaBox(); + pageRotation = lastPage.getRotation() % 360; + if(pageRotation == 90 || pageRotation == 270) { + this.pageHeight(mediaBox.getWidth()); + this.pageWidth = mediaBox.getHeight(); + } else { + this.pageHeight(mediaBox.getHeight()); + this.pageWidth = mediaBox.getWidth(); + } + } else { + PDPage firstPage = (PDPage) pages.get(page - 1); + PDRectangle mediaBox = firstPage.getMediaBox(); + pageRotation = firstPage.getRotation() % 360; + if(pageRotation == 90 || pageRotation == 270) { + this.pageHeight(mediaBox.getWidth()); + this.pageWidth = mediaBox.getHeight(); + } else { + this.pageHeight(mediaBox.getHeight()); + this.pageWidth = mediaBox.getWidth(); + } + } + float x = this.pageWidth; + float y = 0; + this.pageWidth = this.pageWidth + y; + float tPercent = (100 * y / (x + y)); + this.imageSizeInPercents = 100 - tPercent; + } + + /** + * + * @param path + * of image location + * @return image Stream + * @throws IOException + */ + public PDFAsVisualSignatureDesigner signatureImage(String path) + throws IOException { + InputStream fin = new FileInputStream(path); + return signatureImageStream(fin); + } + + /** + * zoom signature image with some percent. + * + * @param percent + * - x % increase image with x percent. + * @return Visible Signature Configuration Object + */ + public PDFAsVisualSignatureDesigner zoom(float percent) { + sigImgHeight = sigImgHeight + (sigImgHeight * percent) / 100; + sigImgWidth = sigImgWidth + (sigImgWidth * percent) / 100; + return this; + } + + /** + * + * @param xAxis + * - x coordinate + * @param yAxis + * - y coordinate + * @return Visible Signature Configuration Object + */ + public PDFAsVisualSignatureDesigner coordinates(float x, float y) { + xAxis(x); + yAxis(y); + return this; + } + + /** + * + * @return xAxis - gets x coordinates + */ + public float getxAxis() { + return xAxis; + } + + /** + * + * @param xAxis + * - x coordinate + * @return Visible Signature Configuration Object + */ + public PDFAsVisualSignatureDesigner xAxis(float xAxis) { + this.xAxis = xAxis; + return this; + } + + /** + * + * @return yAxis + */ + public float getyAxis() { + return yAxis; + } + + /** + * + * @param yAxis + * @return Visible Signature Configuration Object + */ + public PDFAsVisualSignatureDesigner yAxis(float yAxis) { + this.yAxis = yAxis; + return this; + } + + /** + * + * @return signature image width + */ + public float getWidth() { + return this.properties.getMainTable().getWidth(); + } + + /** + * + * @param sets + * signature image width + * @return Visible Signature Configuration Object + */ + public PDFAsVisualSignatureDesigner width(float signatureImgWidth) { + this.sigImgWidth = signatureImgWidth; + return this; + } + + /** + * + * @return signature image height + */ + public float getHeight() { + return this.properties.getMainTable().getHeight(); + } + + /** + * + * @param set + * signature image Height + * @return Visible Signature Configuration Object + */ + public PDFAsVisualSignatureDesigner height(float signatureImgHeight) { + this.sigImgHeight = signatureImgHeight; + return this; + } + + /** + * + * @return template height + */ + protected float getTemplateHeight() { + return getPageHeight(); + } + + /** + * + * @param templateHeight + * @return Visible Signature Configuration Object + */ + private PDFAsVisualSignatureDesigner pageHeight(float templateHeight) { + this.pageHeight = templateHeight; + return this; + } + + /** + * + * @return signature field name + */ + public String getSignatureFieldName() { + return signatureFieldName; + } + + /** + * + * @param signatureFieldName + * @return Visible Signature Configuration Object + */ + public PDFAsVisualSignatureDesigner signatureFieldName( + String signatureFieldName) { + this.signatureFieldName = signatureFieldName; + return this; + } + + /** + * + * @return image Stream + */ + public InputStream getImageStream() { + return imgageStream; + } + + /** + * + * @param imgageStream + * - stream of your visible signature image + * @return Visible Signature Configuration Object + * @throws IOException + * - If we can't read, flush, or close stream of image + */ + private PDFAsVisualSignatureDesigner signatureImageStream( + InputStream imageStream) throws IOException { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len; + while ((len = imageStream.read(buffer)) > -1) { + baos.write(buffer, 0, len); + } + baos.flush(); + baos.close(); + + byte[] byteArray = baos.toByteArray(); + byte[] byteArraySecond = byteArray.clone(); + + InputStream inputForBufferedImage = new ByteArrayInputStream(byteArray); + InputStream revertInputStream = new ByteArrayInputStream( + byteArraySecond); + + if (sigImgHeight == null || sigImgWidth == null) { + calcualteImageSize(inputForBufferedImage); + } + + this.imgageStream = revertInputStream; + + return this; + } + + /** + * calculates image width and height. sported formats: all + * + * @param fis + * - input stream of image + * @throws IOException + * - if can't read input stream + */ + private void calcualteImageSize(InputStream fis) throws IOException { + + BufferedImage bimg = ImageIO.read(fis); + int width = bimg.getWidth(); + int height = bimg.getHeight(); + + sigImgHeight = (float) height; + sigImgWidth = (float) width; + + } + + /** + * + * @return Affine Transform parameters of for PDF Matrix + */ + public float[] getAffineTransformParams() { + return AffineTransformParams; + } + + /** + * + * @param affineTransformParams + * @return Visible Signature Configuration Object + */ + public PDFAsVisualSignatureDesigner affineTransformParams( + float[] affineTransformParams) { + AffineTransformParams = affineTransformParams; + return this; + } + + /** + * + * @return formatter PDRectanle parameters + */ + public float[] getFormaterRectangleParams() { + return formaterRectangleParams; + } + + /** + * sets formatter PDRectangle; + * + * @param formaterRectangleParams + * @return Visible Signature Configuration Object + */ + public PDFAsVisualSignatureDesigner formaterRectangleParams( + float[] formaterRectangleParams) { + this.formaterRectangleParams = formaterRectangleParams; + return this; + } + + /** + * + * @return page width + */ + public float getPageWidth() { + return pageWidth; + } + + public PDPage getSignaturePage() { + if (page < 1) { + throw new IllegalArgumentException("First page of pdf is 1, not " + + page); + } + PDPage pdPage = null; + PDPageTree pages = document.getDocumentCatalog().getPages(); + if(newpage) { + pdPage = new PDPage(); + } else { + pdPage = (PDPage) pages.get(page - 1); + } + + return pdPage; + } + + /** + * + * @param sets + * pageWidth + * @return Visible Signature Configuration Object + */ + public PDFAsVisualSignatureDesigner pageWidth(float pageWidth) { + this.pageWidth = pageWidth; + return this; + } + + /** + * + * @return page height + */ + public float getPageHeight() { + return pageHeight; + } + + /** + * + * @return page height + */ + public int getPageRotation() { + return pageRotation; + } + + /** + * get image size in percents + * + * @return + */ + public float getImageSizeInPercents() { + return imageSizeInPercents; + } + + /** + * + * @param imageSizeInPercents + */ + public void imageSizeInPercents(float imageSizeInPercents) { + this.imageSizeInPercents = imageSizeInPercents; + } + + /** + * returns visible signature text + * + * @return + */ + public String getSignatureText() { + throw new UnsupportedOperationException( + "That method is not yet implemented"); + } + + /** + * + * @param signatureText + * - adds the text on visible signature + * @return + */ + public PDFAsVisualSignatureDesigner signatureText(String signatureText) { + throw new UnsupportedOperationException( + "That method is not yet implemented"); + } + + public float getRotation() { + return this.properties.getRotation(); + } + +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsVisualSignatureProperties.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsVisualSignatureProperties.java new file mode 100644 index 00000000..db96767a --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFAsVisualSignatureProperties.java @@ -0,0 +1,149 @@ +/******************************************************************************* + * <copyright> Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria </copyright> + * 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 + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +package at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2; + +import java.io.IOException; +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.interactive.digitalsignature.visible.PDVisibleSigProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +import at.gv.egiz.pdfas.common.exceptions.PdfAsWrappedIOException; +import at.gv.egiz.pdfas.common.settings.ISettings; +import at.gv.egiz.pdfas.common.settings.SignatureProfileSettings; +import at.gv.egiz.pdfas.lib.impl.pdfbox2.PDFBOXObject; +import at.knowcenter.wag.egov.egiz.pdf.PositioningInstruction; + +public class PDFAsVisualSignatureProperties extends PDVisibleSigProperties { + + private static final Logger logger = LoggerFactory.getLogger(PDFAsVisualSignatureProperties.class); + + private ISettings settings; + + private PDFBoxTable main; + + private PDFAsVisualSignatureDesigner designer; + + private float rotationAngle = 0; + + private PDDocument origDoc; + + private SignatureProfileSettings signatureProfileSettings; + + private String alternativeTableCaption=""; + + public PDFAsVisualSignatureProperties(ISettings settings, PDFBOXObject object, + PdfBoxVisualObject visObj, PositioningInstruction pos, SignatureProfileSettings signatureProfileSettings) { + this.settings = settings; + this.signatureProfileSettings = signatureProfileSettings; + try { + main = visObj.getTable(); + } catch (Throwable e) { + e.printStackTrace(); + } + this.rotationAngle = pos.getRotation(); + try { + origDoc = object.getDocument(); + + designer = new PDFAsVisualSignatureDesigner(origDoc, pos.getPage(), this, pos.isMakeNewPage()); + PDPageTree pages = origDoc.getDocumentCatalog().getPages(); + PDPage page = null; + if(pos.isMakeNewPage()) { + page = (PDPage) pages.get(pages.getCount()-1); + } else { + page = (PDPage) pages.get(pos.getPage() - 1); + } + logger.debug("PAGE width {} HEIGHT {}", designer.getPageWidth(), designer.getPageHeight()); + logger.debug("POS X {} Y {}", pos.getX(), pos.getY()); + int rot = page.getRotation(); + float posy = designer.getPageHeight() - pos.getY(); + float posx = pos.getX(); + /*switch (rot) { + case 90: // CW + posx = designer.getPageHeight() - pos.getY(); + posy = designer.getPageWidth() - main.getWidth(); + break; + case 180: + posy = pos.getY(); + posx = designer.getPageWidth() - pos.getX(); + break; + case 270: // CCW + posx = pos.getY(); + posy = designer.getPageWidth() - pos.getX(); + break; + }*/ + logger.debug("ROT {}", rot); + logger.debug("COORD X {} Y {}", posx, posy); + designer.coordinates(posx, posy); + float[] form_rect = new float[] {0,0, main.getWidth() + 2, main.getHeight() + 2}; + logger.debug("AP Rect: {} {} {} {}", form_rect[0], form_rect[1], form_rect[2], form_rect[3]); + designer.formaterRectangleParams(form_rect); + //this.setPdVisibleSignature(designer); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + @Override + public void buildSignature() throws IOException { + PDFAsVisualSignatureBuilder builder = new PDFAsVisualSignatureBuilder(this, this.settings, designer); + PDFAsTemplateCreator creator = new PDFAsTemplateCreator(builder); + try { + setVisibleSignature(creator.buildPDF(designer, this.origDoc)); + } catch (PdfAsException e) { + throw new PdfAsWrappedIOException(e); + } + } + + public PDFBoxTable getMainTable() { + return main; + } + + + public float getRotation() { + return this.rotationAngle; + } + + public PDFAsVisualSignatureDesigner getDesigner() { + return designer; + } + + public SignatureProfileSettings getSignatureProfileSettings() { + return signatureProfileSettings; + } + + public String getAlternativeTableCaption() { + return alternativeTableCaption; + } + + public void setAlternativeTableCaption(String alternativeTableCaption) { + this.alternativeTableCaption = alternativeTableCaption; + } + +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFBoxFont.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFBoxFont.java new file mode 100644 index 00000000..8b46d56a --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFBoxFont.java @@ -0,0 +1,299 @@ +/******************************************************************************* + * <copyright> Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria </copyright> + * 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 + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +package at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.fontbox.ttf.NameRecord; +import org.apache.fontbox.ttf.NamingTable; +import org.apache.fontbox.ttf.TTFParser; +import org.apache.fontbox.ttf.TrueTypeFont; +import org.apache.pdfbox.cos.COSBase; +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.cos.COSObject; +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.apache.pdfbox.pdmodel.font.PDTrueTypeFont; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.pdfas.common.settings.ISettings; +import at.gv.egiz.pdfas.lib.impl.pdfbox2.PDFBOXObject; + +public class PDFBoxFont { + + private static final Logger logger = LoggerFactory + .getLogger(PDFBoxFont.class); + + private static final String HELVETICA = "HELVETICA"; + private static final String COURIER = "COURIER"; + private static final String TIMES_ROMAN = "TIMES_ROMAN"; + private static final String BOLD = "BOLD"; + private static final String NORMAL = "NORMAL"; + private static final String ITALIC = "ITALIC"; + private static final String SEP = ":"; + + public static PDFont defaultFont = PDType1Font.HELVETICA; + public static float defaultFontSize = 8; + + private static Map<String, PDFont> fontStyleMap = new HashMap<String, PDFont>(); + + private static Map<String, FontInfoCache> fontInfoCache = new HashMap<String, FontInfoCache>(); + + static { + fontStyleMap.put(HELVETICA + SEP + NORMAL, PDType1Font.HELVETICA); + fontStyleMap.put(HELVETICA + SEP + BOLD, PDType1Font.HELVETICA_BOLD); + + fontStyleMap.put(COURIER + SEP + NORMAL, PDType1Font.COURIER); + fontStyleMap.put(COURIER + SEP + BOLD, PDType1Font.COURIER_BOLD); + + fontStyleMap.put(TIMES_ROMAN + SEP + NORMAL, PDType1Font.TIMES_ROMAN); + fontStyleMap.put(TIMES_ROMAN + SEP + BOLD, PDType1Font.TIMES_BOLD); + fontStyleMap.put(TIMES_ROMAN + SEP + ITALIC, PDType1Font.TIMES_ITALIC); + } + + public static void showBuildinFonts() { + Iterator<String> it = fontStyleMap.keySet().iterator(); + logger.info("Available Fonts:"); + while (it.hasNext()) { + logger.info(it.next()); + } + } + + PDFont font; + PDFont cachedfont = null; + float fontSize; + String fontDesc; + String ttfFontDesc; + ISettings settings; + + private FontInfoCache getFontInfo(String pathName) { + synchronized (fontInfoCache) { + + if (fontInfoCache.containsKey(pathName)) { + return fontInfoCache.get(pathName); + } else { + try { + String fontNameToLoad = null; + String fontFamilyToLoad = null; + InputStream ttfData = new FileInputStream(pathName); + try { + TrueTypeFont ttf = null; + TTFParser parser = new TTFParser(); + ttf = parser.parse(ttfData); + NamingTable naming = ttf.getNaming(); + List<NameRecord> records = naming.getNameRecords(); + for (int i = 0; i < records.size(); i++) { + NameRecord nr = records.get(i); + if (nr.getNameId() == NameRecord.NAME_POSTSCRIPT_NAME) { + fontNameToLoad = nr.getString(); + } else if (nr.getNameId() == NameRecord.NAME_FONT_FAMILY_NAME) { + fontFamilyToLoad = nr.getString(); + } + } + } finally { + ttfData.close(); + } + FontInfoCache fontInfo = new FontInfoCache(); + fontInfo.filename = pathName; + fontInfo.fontFamily = fontFamilyToLoad; + fontInfo.fontName = fontNameToLoad; + fontInfo.fontPath = pathName; + fontInfoCache.put(pathName, fontInfo); + return fontInfo; + } catch (Throwable e) { + logger.warn("Failed to generate FontInfo from file: {}", pathName); + } + return null; + } + } + } + + private PDFont findCachedFont(PDFBOXObject pdfObject, FontInfoCache fontInfo) { + try { + if(pdfObject.getFontCache().containsKey(fontInfo.fontPath)) { + return pdfObject.getFontCache().get(fontInfo.fontPath); + } + + List<COSObject> cosObjects = pdfObject.getDocument().getDocument().getObjectsByType( + COSName.FONT); + + //COSName cosFontName = COSName.getPDFName(fontInfo.fontName); + //COSName cosFontFamily = COSName.getPDFName(fontInfo.fontFamily); + + Iterator<COSObject> cosObjectIt = cosObjects.iterator(); + + while (cosObjectIt.hasNext()) { + COSObject cosObject = cosObjectIt.next(); + COSDictionary baseObject = (COSDictionary) cosObject + .getObject(); + if (baseObject instanceof COSDictionary) { + COSDictionary fontDictionary = (COSDictionary) baseObject; + COSBase subType = cosObject.getItem(COSName.SUBTYPE); + COSDictionary fontDescriptor = (COSDictionary)cosObject.getDictionaryObject(COSName.FONT_DESC); + String fontName = fontDescriptor.getNameAsString(COSName.FONT_NAME); + String fontFamily = fontDescriptor.getNameAsString(COSName.FONT_FAMILY); + logger.debug("Checking Font {} - {}", fontFamily, fontName); + if (COSName.TRUE_TYPE.equals(subType)) { + if (fontInfo.fontName != null && fontInfo.fontName.equals(fontName) && + fontInfo.fontFamily != null && fontInfo.fontFamily.equals(fontFamily)) { + // Found it! :) + logger.info("Found Font {}", fontInfo.fontName); + return new PDTrueTypeFont(fontDictionary); + } else { + logger.debug("Font not found: {} is {}", + fontInfo.fontName, fontName); + } + } else { + logger.debug("Font not a TTF"); + } + } else { + logger.debug("Font not a COSDictionary"); + } + } + } catch (Throwable e) { + logger.info("Failed to find existing TTF fonts!", e); + } + return null; + } + + private PDFont generateTTF(String fonttype, PDFBOXObject pdfObject) + throws IOException { + /*boolean cacheNow = true; + if (pdfObject == null) { + if (this.doc == null) { + this.doc = new PDDocument(); + } + doc = this.doc; + } else { + cacheNow = true; + }*/ + ttfFontDesc = fonttype; + String fontName = fonttype.replaceFirst("TTF:", ""); + String fontPath = this.settings.getWorkingDirectory() + File.separator + + "fonts" + File.separator + fontName; + + logger.debug("Font from: \"" + fontPath + "\"."); + + if(fontStyleMap.containsKey(fontPath)) { + return fontStyleMap.get(fontPath); + } + + FontInfoCache fontInfo = getFontInfo(fontPath); + + if(fontInfo != null) { + + PDFont font = findCachedFont(pdfObject, fontInfo); + + if (font != null) { + return font; + } + } + + logger.debug("Instantiating font."); + + //if (cacheNow) { + cachedfont = PDTrueTypeFont.loadTTF(pdfObject.getDocument(), new File(fontPath)); + + fontStyleMap.put(fontPath, cachedfont); + return cachedfont; + //} else { + // return PDTrueTypeFont.loadTTF(doc, fontPath); + //} + + } + + private PDFont generateFont(String fonttype, String fontder, + PDFBOXObject pdfObject) throws IOException { + if (fonttype.startsWith("TTF:")) { + // Load TTF Font + return generateTTF(fonttype, pdfObject); + } else { + if (fontder == null) { + fontder = NORMAL; + } + + String fontDesc = fonttype + SEP + fontder; + PDFont font = fontStyleMap.get(fontDesc); + if (font == null) { + showBuildinFonts(); + throw new IOException("Invalid font descriptor"); + } + return font; + } + } + + private void setFont(String desc, PDFBOXObject pdfObject) + throws IOException { + String[] fontArr = desc.split(","); + + if (fontArr.length == 3) { + font = generateFont(fontArr[0], fontArr[2], pdfObject); + fontSize = Float.parseFloat(fontArr[1]); + } else if (fontArr.length == 2 && fontArr[0].startsWith("TTF:")) { + font = generateFont(fontArr[0], null, pdfObject); + fontSize = Float.parseFloat(fontArr[1]); + } else { + logger.warn( + "Using default font because: {} is not a valid font descriptor.", + desc); + this.font = defaultFont; + this.fontSize = defaultFontSize; + } + + } + + public PDFBoxFont(String fontDesc, ISettings settings, + PDFBOXObject pdfObject) throws IOException { + this.settings = settings; + this.fontDesc = fontDesc; + logger.debug("Creating Font: " + fontDesc); + this.setFont(fontDesc, pdfObject); + } + + public PDFont getFont(/*PDFBOXObject pdfObject*/) throws IOException { + if (cachedfont != null) { + return cachedfont; + } + return font; + /* + if (font instanceof PDTrueTypeFont && pdfObject != null) { + return generateTTF(ttfFontDesc, pdfObject); + } else { + return font; + }*/ + } + + public float getFontSize() { + return fontSize; + } +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFBoxTable.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFBoxTable.java new file mode 100644 index 00000000..415e1665 --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PDFBoxTable.java @@ -0,0 +1,722 @@ +/******************************************************************************* + * <copyright> Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria </copyright> + * 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 + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +package at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2; + +import java.awt.Color; +import java.awt.Dimension; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +import at.gv.egiz.pdfas.common.exceptions.PdfAsWrappedIOException; +import at.gv.egiz.pdfas.common.settings.ISettings; +import at.gv.egiz.pdfas.common.utils.ImageUtils; +import at.gv.egiz.pdfas.common.utils.StringUtils; +import at.gv.egiz.pdfas.lib.impl.pdfbox2.PDFBOXObject; +import at.knowcenter.wag.egov.egiz.table.Entry; +import at.knowcenter.wag.egov.egiz.table.Style; +import at.knowcenter.wag.egov.egiz.table.Table; + +public class PDFBoxTable { + + private static final Logger logger = LoggerFactory + .getLogger(PDFBoxTable.class); + + Table table; + Style style; + PDFBoxFont font; + PDFBoxFont valueFont; + ISettings settings; + + float padding; + int positionX = 0; + int positionY = 0; + float tableWidth; + float tableHeight; + Color bgColor; + + boolean[] addPadding; + float[] rowHeights; + float[] colWidths; + + PDDocument originalDoc; + + PDFBOXObject pdfBoxObject; + + private void normalizeContent(Table abstractTable) throws PdfAsException { + try { + int rows = abstractTable.getRows().size(); + for (int i = 0; i < rows; i++) { + ArrayList<Entry> row = this.table.getRows().get(i); + for (int j = 0; j < row.size(); j++) { + Entry cell = (Entry) row.get(j); + + switch (cell.getType()) { + case Entry.TYPE_CAPTION: + case Entry.TYPE_VALUE: + String value = (String) cell.getValue(); + cell.setValue(StringUtils + .convertStringToPDFFormat(value)); + break; + } + } + } + } catch (UnsupportedEncodingException e) { + throw new PdfAsException("Unsupported Encoding", e); + } + } + + private void initializeStyle(Table abstractTable, PDFBoxTable parent, + PDFBOXObject pdfBoxObject) throws IOException { + this.table = abstractTable; + try { + normalizeContent(abstractTable); + } catch (PdfAsException e) { + throw new PdfAsWrappedIOException(e); + } + + if (parent != null) { + style = Style.doInherit(abstractTable.getStyle(), parent.style); + } else { + style = abstractTable.getStyle(); + } + + if (style == null) { + throw new IOException("Failed to determine Table style, for table " + + abstractTable.getName()); + } + + String fontString = style.getFont(); + + String vfontString = style.getValueFont(); + + if (parent != null && style == parent.style) { + font = parent.getFont(); + + valueFont = parent.getValueFont(); + } else { + if (fontString == null && parent != null && parent.style != null) { + fontString = parent.style.getFont(); + } else if (fontString == null) { + throw new IOException( + "Failed to determine Table font style, for table " + + abstractTable.getName()); + } + + font = new PDFBoxFont(fontString, settings, pdfBoxObject); + + if (vfontString == null && parent != null && parent.style != null) { + vfontString = parent.style.getValueFont(); + } else if (fontString == null) { + throw new IOException( + "Failed to determine value Table font style, for table " + + abstractTable.getName()); + } + + valueFont = new PDFBoxFont(vfontString, settings, pdfBoxObject); + } + padding = style.getPadding(); + + bgColor = style.getBgColor(); + } + + public PDFBoxTable(Table abstractTable, PDFBoxTable parent, float fixSize, + ISettings settings, PDFBOXObject pdfBoxObject) throws IOException, + PdfAsException { + this.settings = settings; + this.pdfBoxObject = pdfBoxObject; + this.originalDoc = pdfBoxObject.getDocument(); + initializeStyle(abstractTable, parent, pdfBoxObject); + float[] relativSizes = abstractTable.getColsRelativeWith(); + if (relativSizes != null) { + colWidths = new float[relativSizes.length]; + float totalrel = 0; + + for (int i = 0; i < relativSizes.length; i++) { + totalrel += relativSizes[i]; + } + + float unit = (fixSize / totalrel); + + for (int i = 0; i < relativSizes.length; i++) { + + colWidths[i] = unit * relativSizes[i]; + } + } else { + colWidths = new float[abstractTable.getMaxCols()]; + float totalrel = abstractTable.getMaxCols(); + float unit = (fixSize / totalrel); + for (int i = 0; i < colWidths.length; i++) { + + colWidths[i] = unit; + } + } + calculateHeightsBasedOnWidths(); + + logger.debug("Generating Table with fixed With {} got width {}", + fixSize, getWidth()); + } + + public PDFBoxTable(Table abstractTable, PDFBoxTable parent, + ISettings settings, PDFBOXObject pdfBoxObject) throws IOException, + PdfAsException { + this.settings = settings; + this.pdfBoxObject = pdfBoxObject; + this.originalDoc = pdfBoxObject.getDocument(); + initializeStyle(abstractTable, parent, pdfBoxObject); + this.calculateWidthHeight(); + } + + private void calculateHeightsBasedOnWidths() throws IOException, + PdfAsException { + int rows = this.table.getRows().size(); + rowHeights = new float[rows]; + addPadding = new boolean[rows]; + + for (int i = 0; i < rows; i++) { + rowHeights[i] = 0; + } + + for (int i = 0; i < rows; i++) { + ArrayList<Entry> row = this.table.getRows().get(i); + for (int j = 0; j < row.size(); j++) { + Entry cell = (Entry) row.get(j); + + float colWidth = 0;// colWidths[j]; + + int colsleft = cell.getColSpan(); + + if (j + colsleft > colWidths.length) { + throw new IOException( + "Configuration is wrong. Cannot determine column width!"); + } + + for (int k = 0; k < colsleft; k++) { + colWidth = colWidth + colWidths[j + k]; + } + + float cellheight = getCellHeight(cell, colWidth); + + if (rowHeights[i] < cellheight) { + rowHeights[i] = cellheight; + } + + logger.debug("ROW: {} COL: {} Width: {} Height: {}", i, j, + colWidth, cellheight); + + int span = cell.getColSpan() - 1; + j += span; + } + } + + calcTotals(); + } + + private void calculateWidthHeight() throws IOException, PdfAsException { + int cols = this.table.getMaxCols(); + colWidths = new float[cols]; + + for (int i = 0; i < cols; i++) { + colWidths[i] = 0; + } + + int rows = this.table.getRows().size(); + rowHeights = new float[rows]; + + for (int i = 0; i < rows; i++) { + rowHeights[i] = 0; + } + + for (int i = 0; i < rows; i++) { + ArrayList<Entry> row = this.table.getRows().get(i); + for (int j = 0; j < row.size(); j++) { + Entry cell = (Entry) row.get(j); + float cellWidth = getCellWidth(cell); + + if (colWidths[j] < cellWidth) { + colWidths[j] = cellWidth; + } + + float cellheight = getCellHeight(cell); + + if (rowHeights[i] < cellheight) { + rowHeights[i] = cellheight; + } + + logger.debug("ROW: {} COL: {} Width: {} Height: {}", i, j, + cellWidth, cellheight); + + int span = cell.getColSpan() - 1; + j += span; + } + } + + calcTotals(); + } + + private void calcTotals() { + + this.tableHeight = 0; + + for (int i = 0; i < rowHeights.length; i++) { + this.tableHeight += rowHeights[i]; + } + + // Post Process heights for inner Tables ... + for (int i = 0; i < rowHeights.length; i++) { + ArrayList<Entry> row = this.table.getRows().get(i); + for (int j = 0; j < row.size(); j++) { + Entry cell = (Entry) row.get(j); + if (cell.getType() == Entry.TYPE_TABLE) { + PDFBoxTable tbl = (PDFBoxTable) cell.getValue(); + if (rowHeights[i] != tbl.getHeight()) { + tbl.setHeight(rowHeights[i]); + } + } + } + } + + this.tableWidth = 0; + + for (int i = 0; i < colWidths.length; i++) { + this.tableWidth += colWidths[i]; + } + } + + private float getCellWidth(Entry cell) throws IOException, PdfAsException { + boolean isValue = true; + switch (cell.getType()) { + case Entry.TYPE_CAPTION: + isValue = false; + case Entry.TYPE_VALUE: + PDFont c = null; + float fontSize; + String string = (String) cell.getValue(); + if (isValue) { + c = valueFont.getFont();//null + fontSize = valueFont.getFontSize(); + } else { + c = font.getFont();//null + fontSize = font.getFontSize(); + } + if (string == null) { + string = ""; + cell.setValue(string); + } + if (string.contains("\n")) { + float maxWidth = 0; + String[] lines = string.split("\n"); + for (int i = 0; i < lines.length; i++) { + float w = c.getStringWidth(lines[i]) / 1000 * fontSize; + if (maxWidth < w) { + maxWidth = w; + } + } + return maxWidth; + } else { + return c.getStringWidth(string) / 1000 * fontSize; + } + case Entry.TYPE_IMAGE: + if (style != null && style.getImageScaleToFit() != null) { + return style.getImageScaleToFit().getWidth(); + } + return 80.f; + case Entry.TYPE_TABLE: + PDFBoxTable pdfBoxTable = null; + if (cell.getValue() instanceof Table) { + pdfBoxTable = new PDFBoxTable((Table) cell.getValue(), this, + this.settings, pdfBoxObject); + cell.setValue(pdfBoxTable); + } else if (cell.getValue() instanceof PDFBoxTable) { + pdfBoxTable = (PDFBoxTable) cell.getValue(); + } else { + throw new IOException("Failed to build PDFBox Table"); + } + return pdfBoxTable.getWidth(); + default: + logger.warn("Invalid Cell Entry Type: " + cell.getType()); + } + return 0; + } + + private String concatLines(String[] lines) { + String v = ""; + for (int i = 0; i < lines.length; i++) { + v += lines[i]; + if (i + 1 < lines.length) { + v += "\n"; + } + } + return v; + } + + private String[] breakString(String value, float maxwidth, PDFont font, + float fontSize) throws IOException { + String[] words = value.split(" "); + List<String> lines = new ArrayList<String>(); + String cLineValue = ""; + for (int i = 0; i < words.length; i++) { + String word = words[i]; + String[] lineBreaks = word.split("\n"); + if (lineBreaks.length > 1) { + for (int j = 0; j < lineBreaks.length; j++) { + String subword = lineBreaks[j]; + // if (cLine + subword.length() > maxline) { + if (j == 0 && word.startsWith("\n")) { + lines.add(cLineValue.trim()); + cLineValue = ""; + } else if (j != 0) { + lines.add(cLineValue.trim()); + cLineValue = ""; + } + // } + String tmpLine = cLineValue + subword; + float size = font.getStringWidth(tmpLine) / 1000.0f + * fontSize; + if (size > maxwidth && cLineValue.length() != 0) { + lines.add(cLineValue.trim()); + cLineValue = ""; + } + cLineValue += subword + " "; + } + } else { + String tmpLine = cLineValue + word; + float size = font.getStringWidth(tmpLine) / 1000.0f * fontSize; + if (size > maxwidth && cLineValue.length() != 0) { + lines.add(cLineValue.trim()); + cLineValue = ""; + } + cLineValue += word + " "; + } + } + lines.add(cLineValue.trim()); + return lines.toArray(new String[0]); + } + + private String[] breakString(String value, int maxline) { + String[] words = value.split(" "); + List<String> lines = new ArrayList<String>(); + int cLine = 0; + String cLineValue = ""; + for (int i = 0; i < words.length; i++) { + String word = words[i]; + String[] lineBreaks = word.split("\n"); + if (lineBreaks.length > 1) { + for (int j = 0; j < lineBreaks.length; j++) { + String subword = lineBreaks[j]; + // if (cLine + subword.length() > maxline) { + lines.add(cLineValue.trim()); + cLineValue = ""; + cLine = 0; + // } + cLineValue += subword + " "; + cLine += subword.length(); + } + } else { + if (cLine + word.length() > maxline && cLineValue.length() != 0) { + lines.add(cLineValue.trim()); + cLineValue = ""; + cLine = 0; + } + cLineValue += word + " "; + cLine += word.length(); + } + } + lines.add(cLineValue.trim()); + return lines.toArray(new String[0]); + } + + // private String[] breakString(String value, PDFont f, float maxwidth) + // throws IOException { + // String[] words = value.split(" "); + // List<String> lines = new ArrayList<String>(); + // int cLine = 0; + // String cLineValue = ""; + // for (int i = 0; i < words.length; i++) { + // String word = words[i]; + // String[] lineBreaks = word.split("\n"); + // if (lineBreaks.length > 1) { + // for (int j = 0; j < lineBreaks.length; j++) { + // String subword = lineBreaks[j]; + // // if (cLine + subword.length() > maxline) { + // lines.add(cLineValue.trim()); + // cLineValue = ""; + // cLine = 0; + // // } + // cLineValue += subword + " "; + // cLine += subword.length(); + // } + // } else { + // if (f.getStringWidth(cLineValue + word) > maxwidth && cLineValue.length() + // != 0) { + // lines.add(cLineValue.trim()); + // cLineValue = ""; + // cLine = 0; + // } + // cLineValue += word + " "; + // cLine += word.length(); + // } + // } + // lines.add(cLineValue.trim()); + // return lines.toArray(new String[0]); + // } + + private float[] getStringHeights(String[] lines, PDFont c, float fontSize) { + float[] heights = new float[lines.length]; + for (int i = 0; i < lines.length; i++) { + float maxLineHeight = 0; + try { + byte[] linebytes = StringUtils.applyWinAnsiEncoding(lines[i]); + for (int j = 0; j < linebytes.length; j++) { + float he = c.getHeight(linebytes[j]) / 1000 + * fontSize; + if (he > maxLineHeight) { + maxLineHeight = he; + } + } + } catch (UnsupportedEncodingException e) { + logger.warn("failed to determine String height", e); + maxLineHeight = c.getFontDescriptor().getCapHeight() / 1000 + * fontSize; + } catch (IOException e) { + logger.warn("failed to determine String height", e); + maxLineHeight = c.getFontDescriptor().getCapHeight() / 1000 + * fontSize; + } + + heights[i] = maxLineHeight; + } + + return heights; + } + + private float getCellHeight(Entry cell, float width) throws IOException, + PdfAsException { + boolean isValue = true; + switch (cell.getType()) { + case Entry.TYPE_CAPTION: + isValue = false; + case Entry.TYPE_VALUE: + PDFont c = null; + float fontSize; + String string = (String) cell.getValue(); + if (isValue) { + c = valueFont.getFont();//null + fontSize = valueFont.getFontSize(); + } else { + c = font.getFont();//null + fontSize = font.getFontSize(); + } + + String[] lines = breakString(string, (width - padding * 2.0f), c, + fontSize); + cell.setValue(concatLines(lines)); + float[] heights = getStringHeights(lines, c, fontSize); + return fontSize * heights.length + padding * 2; + case Entry.TYPE_IMAGE: + String imageFile = (String) cell.getValue(); + if (style != null && style.getImageScaleToFit() != null) { + // if (style.getImageScaleToFit().getHeight() < width) { + return style.getImageScaleToFit().getHeight() + padding * 2; + // } + } + Dimension dim = ImageUtils.getImageDimensions(imageFile, settings); + float wfactor = (float) ((width - padding * 2.0f) / dim.getWidth()); + float scaleFactor = wfactor; + float iheight = (float) Math + .floor((double) (scaleFactor * dim.getHeight())); + //if (dim.getHeight() > 80.0f) { + // return width + padding * 2; + //} + return (float) iheight + padding * 2; + case Entry.TYPE_TABLE: + PDFBoxTable pdfBoxTable = null; + if (cell.getValue() instanceof Table) { + pdfBoxTable = new PDFBoxTable((Table) cell.getValue(), this, + width, this.settings, this.pdfBoxObject); + cell.setValue(pdfBoxTable); + } else if (cell.getValue() instanceof PDFBoxTable) { + // recreate here beacuse of fixed width! + pdfBoxTable = (PDFBoxTable) cell.getValue(); + pdfBoxTable = new PDFBoxTable(pdfBoxTable.table, this, width, + this.settings, this.pdfBoxObject); + cell.setValue(pdfBoxTable); + } else { + throw new IOException("Failed to build PDFBox Table"); + } + return pdfBoxTable.getHeight(); + default: + logger.warn("Invalid Cell Entry Type: " + cell.getType()); + } + return 0; + } + + private float getCellHeight(Entry cell) throws IOException, PdfAsException { + boolean isValue = true; + switch (cell.getType()) { + case Entry.TYPE_CAPTION: + isValue = false; + case Entry.TYPE_VALUE: + PDFont c = null; + float fontSize; + String string = (String) cell.getValue(); + if (isValue) { + c = valueFont.getFont();//null + fontSize = valueFont.getFontSize(); + } else { + c = font.getFont();//null + fontSize = font.getFontSize(); + } + + if (string.contains("\n")) { + String[] lines = string.split("\n"); + + return fontSize * lines.length + padding * 2; + } else { + return fontSize + padding * 2; + } + case Entry.TYPE_IMAGE: + String imageFile = (String) cell.getValue(); + if (style != null && style.getImageScaleToFit() != null) { + return style.getImageScaleToFit().getHeight() + padding * 2; + } + Dimension dim = ImageUtils.getImageDimensions(imageFile, settings); + if (dim.getHeight() > 80.0f) { + return 80.0f + padding * 2; + } + return (float) dim.getHeight() + padding * 2; + + case Entry.TYPE_TABLE: + PDFBoxTable pdfBoxTable = null; + if (cell.getValue() instanceof Table) { + pdfBoxTable = new PDFBoxTable((Table) cell.getValue(), this, + this.settings, pdfBoxObject); + cell.setValue(pdfBoxTable); + } else if (cell.getValue() instanceof PDFBoxTable) { + pdfBoxTable = (PDFBoxTable) cell.getValue(); + } else { + throw new IOException("Failed to build PDFBox Table"); + } + return pdfBoxTable.getHeight(); + default: + logger.warn("Invalid Cell Entry Type: " + cell.getType()); + } + return 0; + } + + public int getX() { + return this.positionX; + } + + public int getY() { + return this.positionY; + } + + public float getWidth() { + return tableWidth; + } + + public float getHeight() { + return tableHeight; + } + + public void setHeight(float height) { + float diff = height - this.getHeight(); + if (diff > 0) { + this.rowHeights[rowHeights.length - 1] += diff; + calcTotals(); + } else { + logger.warn("Table cannot be this small!"); + } + } + + public float[] getRowHeights() { + return rowHeights; + } + + public int getRowCount() { + return this.table.getRows().size(); + } + + public int getColCount() { + return this.table.getMaxCols();// .getColsRelativeWith().length; + } + + public float[] getColsRelativeWith() { + return this.table.getColsRelativeWith(); + } + + public float getPadding() { + return this.padding; + } + + public void dumpTable() { + logger.debug("====================================================================="); + logger.debug("Information about: " + this.table.getName()); + logger.debug("\tDimensions: {} x {} (W x H)", this.tableWidth, + this.tableHeight); + logger.debug("\tPadding: {}", padding); + logger.debug("\t================================"); + logger.debug("\tRow Heights:"); + for (int i = 0; i < rowHeights.length; i++) { + logger.debug("\t[{}] : {}", i, this.rowHeights[i]); + } + logger.debug("\t================================"); + logger.debug("\tCol Widths:"); + for (int i = 0; i < colWidths.length; i++) { + logger.debug("\t[{}] : {}", i, this.colWidths[i]); + } + logger.debug("\t================================"); + logger.debug("\tTable:"); + logger.debug("\t" + this.table.toString()); + logger.debug("====================================================================="); + } + + public Table getOrigTable() { + return this.table; + } + + public ArrayList<Entry> getRow(int i) { + return this.table.getRows().get(i); + } + + public PDFBoxFont getFont() { + return font; + } + + public PDFBoxFont getValueFont() { + return valueFont; + } + + public Color getBGColor() { + return this.bgColor; + } +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PdfBoxStamper.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PdfBoxStamper.java new file mode 100644 index 00000000..f89d53c5 --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PdfBoxStamper.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * <copyright> Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria </copyright> + * 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 + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +package at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2; + +import java.io.IOException; + +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +import at.gv.egiz.pdfas.common.exceptions.PdfAsWrappedIOException; +import at.gv.egiz.pdfas.common.settings.ISettings; +import at.gv.egiz.pdfas.lib.impl.pdfbox2.PDFBOXObject; +import at.gv.egiz.pdfas.lib.impl.stamping.IPDFStamper; +import at.gv.egiz.pdfas.lib.impl.stamping.IPDFVisualObject; +import at.gv.egiz.pdfas.lib.impl.status.PDFObject; +import at.knowcenter.wag.egov.egiz.pdf.PositioningInstruction; +import at.knowcenter.wag.egov.egiz.table.Table; + +public class PdfBoxStamper implements IPDFStamper { + +// private static final Logger logger = LoggerFactory.getLogger(PdfBoxStamper.class); + +// private PDFTemplateBuilder pdfBuilder; + + public PdfBoxStamper() { +// this.pdfBuilder = new PDVisibleSigBuilder(); + } + + public IPDFVisualObject createVisualPDFObject(PDFObject pdf, Table table) throws IOException { + try { + PDFBOXObject pdfboxObject = (PDFBOXObject)pdf; + return new PdfBoxVisualObject(table, pdf.getStatus().getSettings(), pdfboxObject); + } catch (PdfAsException e) { + throw new PdfAsWrappedIOException(e); + } + } + + public byte[] writeVisualObject(IPDFVisualObject visualObject, + PositioningInstruction positioningInstruction, byte[] pdfData, + String placeholderName) throws PdfAsException { + return null; + } + + public void setSettings(ISettings settings) { + // not needed currently + } + +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PdfBoxVisualObject.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PdfBoxVisualObject.java new file mode 100644 index 00000000..3a4c3957 --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/PdfBoxVisualObject.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * <copyright> Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria </copyright> + * 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 + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +package at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2; + +import java.io.IOException; + +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.impl.pdfbox2.PDFBOXObject; +import at.gv.egiz.pdfas.lib.impl.stamping.IPDFVisualObject; +import at.knowcenter.wag.egov.egiz.table.Table; + +public class PdfBoxVisualObject implements IPDFVisualObject { + + private static final Logger logger = LoggerFactory + .getLogger(PdfBoxVisualObject.class); + + private Table abstractTable; + private PDFBoxTable table; + private float width; + private float x; + private float y; + private int page; + private ISettings settings; + private PDFBOXObject pdfBoxObject; + + public PdfBoxVisualObject(Table table, ISettings settings, PDFBOXObject pdfBoxObject) + throws IOException, PdfAsException { + this.abstractTable = table; + this.pdfBoxObject = pdfBoxObject; + this.table = new PDFBoxTable(table, null, settings, pdfBoxObject); + this.settings = settings; + } + + public void setWidth(float width) { + this.width = width; + } + + public void fixWidth() { + try { + table = new PDFBoxTable(abstractTable, null, this.width, settings, this.pdfBoxObject); + } catch (IOException e) { + logger.warn("Failed to fix width of Table!", e); + } catch (PdfAsException e) { + logger.warn("Failed to fix width of Table!", e); + } + } + + public float getHeight() { + return table.getHeight(); + } + + public float getWidth() { + return table.getWidth(); + } + + public void setXPos(float x) { + this.x = x; + } + + public void setYPos(float y) { + this.y = y; + } + + public float getX() { + return x; + } + + public float getY() { + return y; + } + + public int getPage() { + return page; + } + + public void setPage(int page) { + this.page = page; + } + + public PDFBoxTable getTable() { + return this.table; + } +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/StamperFactory.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/StamperFactory.java new file mode 100644 index 00000000..90561740 --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/StamperFactory.java @@ -0,0 +1,25 @@ +package at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2; + +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +import at.gv.egiz.pdfas.common.settings.ISettings; +import at.gv.egiz.pdfas.lib.impl.stamping.IPDFStamper; + +public class StamperFactory { + + //public static final String DEFAULT_STAMPER_CLASS = "at.gv.egiz.pdfas.stmp.itext.ITextStamper"; + public static final String DEFAULT_STAMPER_CLASS = "at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2.PdfBoxStamper"; + + public static IPDFStamper createDefaultStamper(ISettings settings) throws PdfAsException { + try { + Class<?> cls = Class.forName(DEFAULT_STAMPER_CLASS); + Object st = cls.newInstance(); + if (!(st instanceof IPDFStamper)) + throw new ClassCastException(); + IPDFStamper stamper = (IPDFStamper) st; + stamper.setSettings(settings); + return stamper; + } catch (Throwable e) { + throw new PdfAsException("error.pdf.stamp.10", e); + } + } +}
\ No newline at end of file diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/TableDrawUtils.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/TableDrawUtils.java new file mode 100644 index 00000000..aa2a397d --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/stamping/pdfbox2/TableDrawUtils.java @@ -0,0 +1,609 @@ +/******************************************************************************* + * <copyright> Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria </copyright> + * 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 + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +package at.gv.egiz.pdfas.lib.impl.stamping.pdfbox2; + +import java.awt.Color; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Map; + +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.PDResources; +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +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.knowcenter.wag.egov.egiz.table.Entry; +import at.knowcenter.wag.egov.egiz.table.Style; + +public class TableDrawUtils { + + private static final Logger logger = LoggerFactory + .getLogger(TableDrawUtils.class); + + public static final String TABLE_DEBUG = "debug.table"; + + public static void drawTable(PDPage page, + PDPageContentStream contentStream, float x, float y, float width, + float height, PDFBoxTable abstractTable, PDDocument doc, + boolean subtable, PDResources formResources, + Map<String, ImageObject> images, ISettings settings, IDGenerator generator, PDFAsVisualSignatureProperties properties) + throws PdfAsException { + + logger.debug("Drawing Table: X {} Y {} WIDTH {} HEIGHT {} \n{}", x, y, + width, height, abstractTable.getOrigTable().toString()); + + abstractTable.getOrigTable().setWidth(width); + + drawTableBackground(page, contentStream, x, y, width, height, + abstractTable, settings); + + drawBorder(page, contentStream, x, y, width, height, abstractTable, + doc, subtable, settings); +//append strings + drawContent(page, contentStream, x, y, width, height, abstractTable, + doc, subtable, formResources, images, settings, generator, properties); + } + + public static void drawContent(PDPage page, + PDPageContentStream contentStream, float x, float y, float width, + float height, PDFBoxTable abstractTable, PDDocument doc, + boolean subtable, PDResources formResources, + Map<String, ImageObject> images, ISettings settings, IDGenerator generator, PDFAsVisualSignatureProperties properties) + throws PdfAsException { + + float contentx = x; + float contenty = y + height; + float padding = abstractTable.getPadding(); + float[] colsSizes = getColSizes(abstractTable); + StringBuilder alternateTableCaption = new StringBuilder(); + for (int i = 0; i < abstractTable.getRowCount(); i++) { + ArrayList<Entry> row = abstractTable.getRow(i); + for (int j = 0; j < row.size(); j++) { + Entry cell = (Entry) row.get(j); + + // Cell only contains default values so table style is the primary style + Style inherit_style = Style.doInherit(abstractTable.style, cell.getStyle()); + cell.setStyle(inherit_style); + + float colWidth = 0;//colWidths[j]; + + int colsleft = cell.getColSpan(); + + if (j + colsleft > colsSizes.length) { + throw new PdfAsException( + "Configuration is wrong. Cannot determine column width!"); + } + + for (int k = 0; k < colsleft; k++) { + colWidth = colWidth + colsSizes[j + k]; + } + + drawDebugPadding(contentStream, contentx, contenty, padding, + colWidth, abstractTable.getRowHeights()[i], settings); + + switch (cell.getType()) { + case Entry.TYPE_CAPTION: + drawCaption(page, contentStream, contentx, contenty, + colWidth, abstractTable.getRowHeights()[i], + padding, abstractTable, doc, cell, formResources, settings); + addToAlternateTableCaption(cell, alternateTableCaption); + break; + case Entry.TYPE_VALUE: + drawValue(page, contentStream, contentx, contenty, + colWidth, abstractTable.getRowHeights()[i], + padding, abstractTable, doc, cell, formResources, settings); + addToAlternateTableCaption(cell, alternateTableCaption); + break; + case Entry.TYPE_IMAGE: + drawImage(page, contentStream, contentx, contenty, + colWidth, abstractTable.getRowHeights()[i], + padding, abstractTable, doc, cell, formResources, + images, settings, generator); + break; + case Entry.TYPE_TABLE: + + PDFBoxTable tbl_value = (PDFBoxTable) cell.getValue(); + + Style inherit_styletab = Style.doInherit( + abstractTable.style, cell.getStyle()); + tbl_value.table.setStyle(inherit_styletab); + + drawTable(page, contentStream, contentx, contenty + - abstractTable.getRowHeights()[i], colWidth, + abstractTable.getRowHeights()[i], tbl_value, doc, + true, formResources, images, settings, generator,properties); + break; + default: + logger.warn("Unknown Cell entry type: " + cell.getType()); + break; + } + + // Move content pointer + contentx += colWidth; + + int span = cell.getColSpan() - 1; + j += span; + } + + // Move content pointer + contenty -= abstractTable.getRowHeights()[i]; + contentx = x; + } + properties.setAlternativeTableCaption(alternateTableCaption.toString()); + } + + private static void drawString(PDPage page, + PDPageContentStream contentStream, float contentx, float contenty, + float width, float height, float padding, + PDFBoxTable abstractTable, PDDocument doc, Entry cell, + float fontSize, float textHeight, String valign, String halign, + String[] tlines, PDFont textFont, PDResources formResources, + ISettings settings) throws PdfAsException { + try { + float ty = contenty - padding; + float tx = contentx + padding; + float innerHeight = height - (2 * padding); + float innerWidth = width - (2 * padding); + if (Style.BOTTOM.equals(valign)) { + float bottom_offset = innerHeight - textHeight; + ty -= bottom_offset; + } else if (Style.MIDDLE.equals(valign)) { + float bottom_offset = innerHeight - textHeight; + bottom_offset = bottom_offset / 2.0f; + ty -= bottom_offset; + } + + // calculate the max with of the text content + float maxWidth = 0; + for (int k = 0; k < tlines.length; k++) { + float lineWidth; + // if (textFont instanceof PDType1Font) { + lineWidth = textFont.getStringWidth(tlines[k]) / 1000.0f + * fontSize; + /* + * } else { float fwidth = textFont + * .getStringWidth("abcdefghijklmnopqrstuvwxyz ") / 1000.0f * + * fontSize; fwidth = fwidth / (float) + * "abcdefghijklmnopqrstuvwxyz" .length(); lineWidth = + * tlines[k].length() * fwidth; } + */ + if (maxWidth < lineWidth) { + maxWidth = lineWidth; + } + } + + if (Style.CENTER.equals(halign)) { + float offset = innerWidth - maxWidth; + if (offset > 0) { + offset = offset / 2.0f; + tx += offset; + } + } else if (Style.RIGHT.equals(halign)) { + float offset = innerWidth - maxWidth; + if (offset > 0) { + tx += offset; + } + } + float ascent = textFont.getFontDescriptor().getAscent(); + float descent = textFont.getFontDescriptor().getDescent(); + + ascent = ascent / 1000.0f * fontSize; + descent = descent / 1000.0f * fontSize; + + //ty = ty + (descent * (-1)); + + logger.debug("Text tx {} ty {} maxWidth {} textHeight {}", tx, ty, + maxWidth, textHeight); + logger.debug("Text ASCENT {} DESCENT {}", ascent, descent); + + logger.debug("Text TRANSFORMED ASCENT {} DESCENT {}", ascent, descent); + + drawDebugLineString(contentStream, tx, ty, maxWidth, textHeight, descent, settings); + + contentStream.beginText(); + + + if (formResources.getFont(COSName.getPDFName(textFont.getName())) != null) { + String fontID = getFontID(textFont, formResources); + logger.debug("Using Font: " + fontID); + contentStream.appendRawCommands("/" + fontID + " " + fontSize + + " Tf\n"); + } else { + contentStream.setFont(textFont, fontSize); + } + + logger.debug("Writing: " + tx + " : " + (ty - fontSize + (descent * (-1))) + " as " + + cell.getType()); + contentStream.moveTextPositionByAmount(tx, (ty - fontSize + (descent * (-1)))); + + contentStream.appendRawCommands(fontSize + " TL\n"); + for (int k = 0; k < tlines.length; k++) { + contentStream.drawString(tlines[k]); + if (k < tlines.length - 1) { + contentStream.appendRawCommands("T*\n"); + } + } + + contentStream.endText(); + + } catch (IOException e) { + logger.warn("IO Exception", e); + throw new PdfAsException("Error", e); + } + } + + public static void drawCaption(PDPage page, + PDPageContentStream contentStream, float contentx, float contenty, + float width, float height, float padding, + PDFBoxTable abstractTable, PDDocument doc, Entry cell, + PDResources formResources, ISettings settings) + throws PdfAsException { + + logger.debug("Drawing Caption @ X: {} Y: {}", contentx, contenty); + + try { + float fontSize = PDFBoxFont.defaultFontSize; + PDFont textFont = PDFBoxFont.defaultFont; + + textFont = abstractTable.getFont().getFont();//doc); + fontSize = abstractTable.getFont().getFontSize(); + + // get the cell Text + String text = (String) cell.getValue(); + String[] tlines = text.split("\n"); + float textHeight = fontSize * tlines.length; + + Style cellStyle = cell.getStyle(); + String valign = cellStyle.getVAlign(); + String halign = cellStyle.getHAlign(); + + drawString(page, contentStream, contentx, contenty, width, height, + padding, abstractTable, doc, cell, fontSize, textHeight, + valign, halign, tlines, textFont, formResources, settings); + } catch (IOException e) { + logger.warn("IO Exception", e); + throw new PdfAsException("Error", e); + } + } + + public static void drawValue(PDPage page, + PDPageContentStream contentStream, float contentx, float contenty, + float width, float height, float padding, + PDFBoxTable abstractTable, PDDocument doc, Entry cell, + PDResources formResources, ISettings settings) + throws PdfAsException { + + logger.debug("Drawing Value @ X: {} Y: {}", contentx, contenty); + + try { + float fontSize = PDFBoxFont.defaultFontSize; + PDFont textFont = PDFBoxFont.defaultFont; + + textFont = abstractTable.getValueFont().getFont();//doc); + fontSize = abstractTable.getValueFont().getFontSize(); + + // get the cell Text + String text = (String) cell.getValue(); + String[] tlines = text.split("\n"); + float textHeight = fontSize * tlines.length; + + Style cellStyle = cell.getStyle(); + String valign = cellStyle.getValueVAlign(); + String halign = cellStyle.getValueHAlign(); + + drawString(page, contentStream, contentx, contenty, width, height, + padding, abstractTable, doc, cell, fontSize, textHeight, + valign, halign, tlines, textFont, formResources, settings); + } catch (IOException e) { + logger.warn("IO Exception", e); + throw new PdfAsException("Error", e); + } + } + + public static void drawImage(PDPage page, + PDPageContentStream contentStream, float contentx, float contenty, + float width, float height, float padding, + PDFBoxTable abstractTable, PDDocument doc, Entry cell, + PDResources formResources, Map<String, ImageObject> images, + ISettings settings, IDGenerator generator) throws PdfAsException { + try { + float innerHeight = height; + float innerWidth = width; + + String img_ref = generator.createHashedId((String) cell.getValue()); + if (!images.containsKey(img_ref)) { + logger.warn("Image not prepared! : " + img_ref); + throw new PdfAsException("Image not prepared! : " + img_ref); + } + ImageObject image = images.get(img_ref); + PDImageXObject pdImage = image.getImage(); + + + float imgx = contentx; + float hoffset = innerWidth - image.getWidth(); + if (cell.getStyle().getImageHAlign() != null + && cell.getStyle().getImageHAlign().equals(Style.LEFT)) { + hoffset = hoffset / 2.0f; + imgx += hoffset; + } else if (cell.getStyle().getImageHAlign() != null + && cell.getStyle().getImageHAlign().equals(Style.RIGHT)) { + imgx += hoffset; + } else { + hoffset = hoffset / 2.0f; + imgx += hoffset; + } + + float imgy = contenty; + float voffset = innerHeight - image.getHeight(); + if (cell.getStyle().getImageVAlign() != null + && cell.getStyle().getImageVAlign().equals(Style.MIDDLE)) { + voffset = voffset / 2.0f; + imgy -= voffset; + } else if (cell.getStyle().getImageVAlign() != null + && cell.getStyle().getImageVAlign().equals(Style.BOTTOM)) { + imgy -= voffset; + } + + drawDebugLine(contentStream, imgx, imgy, image.getWidth(), + image.getHeight(), settings); + + // logger.debug("Image: " + imgx + " : " + (imgy - + // image.getHeight())); + contentStream.drawXObject(pdImage, imgx, imgy - image.getHeight(), + image.getWidth(), image.getHeight()); + } catch (IOException e) { + logger.warn("IO Exception", e); + throw new PdfAsException("Error", e); + } + + } + + public static float[] getColSizes(PDFBoxTable abstractTable) { + float[] origcolsSizes = abstractTable.getColsRelativeWith(); + int max_cols = abstractTable.getColCount(); + float[] colsSizes = new float[max_cols]; + if (origcolsSizes == null) { + // set the column ratio for all columns to 1 + for (int cols_idx = 0; cols_idx < colsSizes.length; cols_idx++) { + colsSizes[cols_idx] = 1; + } + } else { + // set the column ratio for all columns to 1 + for (int cols_idx = 0; cols_idx < colsSizes.length; cols_idx++) { + colsSizes[cols_idx] = origcolsSizes[cols_idx]; + } + } + + // adapt + float total = 0; + + for (int cols_idx = 0; cols_idx < colsSizes.length; cols_idx++) { + total += colsSizes[cols_idx]; + } + + for (int cols_idx = 0; cols_idx < colsSizes.length; cols_idx++) { + colsSizes[cols_idx] = (colsSizes[cols_idx] / total) + * abstractTable.getWidth(); + } + + float sum = 0; + + for (int cols_idx = 0; cols_idx < colsSizes.length; cols_idx++) { + sum += colsSizes[cols_idx]; + } + + logger.debug("Table Col Sizes SUM {} Table Width {}", sum, + abstractTable.getWidth()); + logger.debug("Table Table Height {}", abstractTable.getHeight()); + + return colsSizes; + } + + public static void drawBorder(PDPage page, + PDPageContentStream contentStream, float x, float y, float width, + float height, PDFBoxTable abstractTable, PDDocument doc, + boolean subtable, ISettings settings) throws PdfAsException { + try { + + logger.debug("Drawing Table borders for " + + abstractTable.getOrigTable().getName()); + + final int rows = abstractTable.getRowCount(); + float border = abstractTable.style.getBorder(); + float[] colsSizes = getColSizes(abstractTable); + + if (border > 0) { + contentStream.setLineWidth(border); + + float x_from = x; + float x_to = x + width; + float y_from = y + height; + float y_to = y + height; + + // draw first line + logger.debug("ROW LINE: {} {} {} {}", x_from, y_from, x_to, + y_from); + contentStream.drawLine(x, y_from, x_to, y_from); + + // Draw all row borders + for (int i = 0; i < rows; i++) { + y_from -= abstractTable.getRowHeights()[i]; + + // Draw row border! + logger.debug("ROW LINE: {} {} {} {}", x_from, y_from, x_to, + y_from); + contentStream.drawLine(x, y_from, x_to, y_from); + + } + + // reset y for "line feed" + y_from = y + height; + y_to = y_from - abstractTable.getRowHeights()[0]; + + // Draw all column borders + for (int i = 0; i < rows; i++) { + ArrayList<Entry> row = abstractTable.getRow(i); + + // reset x for "line feed" + x_from = x; + + // draw first line + logger.debug("COL LINE: {} {} {} {}", x_from, y_from, + x_from, y_to); + + contentStream.drawLine(x_from, y_from, x_from, y_to); + + for (int j = 0; j < row.size(); j++) { + Entry cell = (Entry) row.get(j); + + for (int k = 0; k < cell.getColSpan(); k++) { + if (k + j < colsSizes.length) { + x_from += colsSizes[k + j]; + } + } + logger.debug("COL LINE: {} {} {} {}", x_from, y_from, + x_from, y_to); + contentStream.drawLine(x_from, y_from, x_from, y_to); + } + + if (i + 1 < rows) { + y_from = y_to; + y_to = y_from - abstractTable.getRowHeights()[i + 1]; + } + } + + } + } catch (Throwable e) { + logger.warn("drawing table borders", e); + throw new PdfAsException("drawing table borders", e); + } + } + + public static void drawTableBackground(PDPage page, + PDPageContentStream contentStream, float x, float y, float width, + float height, PDFBoxTable abstractTable, ISettings settings) + throws PdfAsException { + try { + if (abstractTable.getBGColor() != null) { + contentStream.setNonStrokingColor(abstractTable.getBGColor()); + contentStream.fillRect(x, y, abstractTable.getWidth(), + abstractTable.getHeight()); + contentStream.setNonStrokingColor(Color.BLACK); + } + } catch (Throwable e) { + logger.warn("drawing table borders", e); + throw new PdfAsException("drawing table borders", e); + } + } + + private static void drawDebugLine(PDPageContentStream contentStream, + float x, float y, float width, float height, ISettings settings) { + if ("true".equals(settings.getValue(TABLE_DEBUG))) { + try { + contentStream.setStrokingColor(Color.RED); + contentStream.drawLine(x, y, x + width, y); + contentStream.setStrokingColor(Color.BLUE); + contentStream.drawLine(x, y, x, y - height); + contentStream.setStrokingColor(Color.GREEN); + contentStream.drawLine(x + width, y, x + width, y - height); + contentStream.setStrokingColor(Color.ORANGE); + contentStream.drawLine(x, y - height, x + width, y - height); + + contentStream.setStrokingColor(Color.BLACK); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + private static void drawDebugLineString(PDPageContentStream contentStream, + float x, float y, float width, float height, float descent, ISettings settings) { + if ("true".equals(settings.getValue(TABLE_DEBUG))) { + try { + contentStream.setStrokingColor(Color.RED); + contentStream.drawLine(x, y, x + width, y); + contentStream.setStrokingColor(Color.BLUE); + contentStream.drawLine(x, y, x, y - height); + contentStream.setStrokingColor(Color.GREEN); + contentStream.drawLine(x + width, y, x + width, y - height); + contentStream.setStrokingColor(Color.ORANGE); + contentStream.drawLine(x, y - height, x + width, y - height); + contentStream.setStrokingColor(Color.MAGENTA); + contentStream.drawLine(x, y + (descent * (-1)) - height, x + width, y + (descent * (-1)) - height); + + contentStream.setStrokingColor(Color.BLACK); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + private static void drawDebugPadding(PDPageContentStream contentStream, + float x, float y, float padding, float width, float height, + ISettings settings) { + if ("true".equals(settings.getValue(TABLE_DEBUG))) { + try { + contentStream.setStrokingColor(Color.RED); + contentStream.drawLine(x, y, x + padding, y - padding); + contentStream.drawLine(x + width, y, x + width - padding, y + - padding); + contentStream.drawLine(x + width, y - height, x + width + - padding, y - height + padding); + contentStream.drawLine(x, y - height, x + padding, y - height + + padding); + contentStream.setStrokingColor(Color.BLACK); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + private static String getFontID(PDFont font, PDResources resources) { +// Iterator<java.util.Map.Entry<String, PDFont>> it = resources.getfon +// .entrySet().iterator(); + Iterator<COSName> it = resources.getFontNames().iterator(); + while (it.hasNext()) { + COSName entry = it.next(); + if (entry.getName().equals(font.getName())) { + return entry.getName(); + } + } + return ""; + } + + private static void addToAlternateTableCaption(Entry cell, StringBuilder alternateTableCaption){ + alternateTableCaption.append(cell.getValue()); + alternateTableCaption.append("\n");//better for screen reader + } + + +} diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/pdfbox2/PDFBOXVerifier.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/pdfbox2/PDFBOXVerifier.java new file mode 100644 index 00000000..270e9e28 --- /dev/null +++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/pdfbox2/PDFBOXVerifier.java @@ -0,0 +1,158 @@ +package at.gv.egiz.pdfas.lib.impl.verify.pdfbox2; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +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.COSName; +import org.apache.pdfbox.cos.COSString; +import org.apache.pdfbox.pdmodel.PDDocument; +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.settings.ISettings; +import at.gv.egiz.pdfas.lib.api.verify.VerifyParameter; +import at.gv.egiz.pdfas.lib.api.verify.VerifyResult; +import at.gv.egiz.pdfas.lib.impl.ErrorExtractor; +import at.gv.egiz.pdfas.lib.impl.verify.IVerifier; +import at.gv.egiz.pdfas.lib.impl.verify.IVerifyFilter; +import at.gv.egiz.pdfas.lib.impl.verify.VerifierDispatcher; +import at.gv.egiz.pdfas.lib.impl.verify.VerifyBackend; + +public class PDFBOXVerifier implements VerifyBackend { + + private static final Logger logger = LoggerFactory.getLogger(PDFBOXVerifier.class); + + @Override + public List<VerifyResult> verify(VerifyParameter parameter) throws PDFASError { + int signatureToVerify = parameter.getWhichSignature(); + int currentSignature = 0; + PDDocument doc = null; + try { + List<VerifyResult> result = new ArrayList<VerifyResult>(); + ISettings settings = (ISettings) parameter.getConfiguration(); + VerifierDispatcher verifier = new VerifierDispatcher(settings); + doc = PDDocument.load(parameter.getDataSource().getInputStream()); + + COSDictionary trailer = doc.getDocument().getTrailer(); + if (trailer == null) { + // No signatures ... + return result; + } + COSDictionary root = (COSDictionary) trailer.getDictionaryObject(COSName.ROOT); + if (root == null) { + // No signatures ... + return result; + } + COSDictionary acroForm = (COSDictionary) root.getDictionaryObject(COSName.ACRO_FORM); + if (acroForm == null) { + // No signatures ... + return result; + } + COSArray fields = (COSArray) acroForm.getDictionaryObject(COSName.FIELDS); + if (fields == null) { + // No signatures ... + return result; + } + + int lastSig = -1; + for (int i = 0; i < fields.size(); i++) { + COSDictionary field = (COSDictionary) fields.getObject(i); + String type = field.getNameAsString("FT"); + if ("Sig".equals(type)) { + lastSig = i; + } + } + + byte[] inputData = IOUtils.toByteArray(parameter.getDataSource().getInputStream()); + + for (int i = 0; i < fields.size(); i++) { + COSDictionary field = (COSDictionary) fields.getObject(i); + String type = field.getNameAsString("FT"); + if ("Sig".equals(type)) { + boolean verifyThis = true; + + if (signatureToVerify >= 0) { + // verify only specific siganture! + verifyThis = signatureToVerify == currentSignature; + } + + if (signatureToVerify == -2) { + verifyThis = i == lastSig; + } + + if (verifyThis) { + logger.trace("Found Signature: "); + COSBase base = field.getDictionaryObject("V"); + COSDictionary dict = (COSDictionary) base; + + logger.debug("Signer: " + dict.getNameAsString("Name")); + logger.debug("SubFilter: " + dict.getNameAsString("SubFilter")); + logger.debug("Filter: " + dict.getNameAsString("Filter")); + logger.debug("Modified: " + dict.getNameAsString("M")); + COSArray byteRange = (COSArray) dict.getDictionaryObject("ByteRange"); + + StringBuilder sb = new StringBuilder(); + int[] bytes = new int[byteRange.size()]; + for (int j = 0; j < byteRange.size(); j++) { + bytes[j] = byteRange.getInt(j); + sb.append(" " + bytes[j]); + } + + logger.debug("ByteRange" + sb.toString()); + + COSString content = (COSString) dict.getDictionaryObject("Contents"); + + ByteArrayOutputStream contentData = new ByteArrayOutputStream(); + for (int j = 0; j < bytes.length; j = j + 2) { + int offset = bytes[j]; + int length = bytes[j + 1]; + + contentData.write(inputData, offset, length); + } + contentData.close(); + + IVerifyFilter verifyFilter = verifier.getVerifier(dict.getNameAsString("Filter"), + dict.getNameAsString("SubFilter")); + + IVerifier lvlVerifier = verifier.getVerifierByLevel(parameter.getSignatureVerificationLevel()); + synchronized (lvlVerifier) { + lvlVerifier.setConfiguration(parameter.getConfiguration()); + if (verifyFilter != null) { + List<VerifyResult> results = verifyFilter.verify(contentData.toByteArray(), + content.getBytes(), parameter.getVerificationTime(), bytes, lvlVerifier); + if (results != null && !results.isEmpty()) { + result.addAll(results); + } + } + } + } + currentSignature++; + } + } + return result; + } catch (IOException e) { + logger.warn("Failed to verify document", e); + throw ErrorExtractor.searchPdfAsError(e, null); + } catch (PdfAsException e) { + logger.warn("Failed to verify document", e); + throw ErrorExtractor.searchPdfAsError(e, null); + } finally { + if (doc != null) { + try { + doc.close(); + } catch (IOException e) { + logger.info("Failed to close doc"); + } + } + } + } + +} |