From 535a04fa05f739ec16dd81666e3b0f82dfbd442d Mon Sep 17 00:00:00 2001 From: tknall Date: Wed, 9 Jan 2013 15:41:29 +0000 Subject: pdf-as-lib maven project files moved to pdf-as-lib git-svn-id: https://joinup.ec.europa.eu/svn/pdf-as/pdf-as/trunk@926 7b5415b0-85f9-ee4d-85bd-d5d0c3b42d1c --- .../wag/egov/egiz/pdf/AbsoluteTextSignature.java | 956 +++++++++ .../wag/egov/egiz/pdf/ActualTablePos.java | 42 + .../wag/egov/egiz/pdf/AdobeSignatureHelper.java | 272 +++ .../wag/egov/egiz/pdf/BinaryBlockInfo.java | 61 + .../wag/egov/egiz/pdf/BinarySignature.java | 2145 ++++++++++++++++++++ .../wag/egov/egiz/pdf/BinarySignatureHolder.java | 185 ++ .../at/knowcenter/wag/egov/egiz/pdf/EGIZDate.java | 284 +++ .../egiz/pdf/IncrementalUpdateInformation.java | 252 +++ .../wag/egov/egiz/pdf/NoSignatureHolder.java | 84 + .../wag/egov/egiz/pdf/ObjectExtractor.java | 233 +++ .../at/knowcenter/wag/egov/egiz/pdf/PDFPage.java | 377 ++++ .../wag/egov/egiz/pdf/PDFSignatureCreation.java | 176 ++ .../wag/egov/egiz/pdf/PDFSignatureObject.java | 56 + .../wag/egov/egiz/pdf/PDFSignatureObjectIText.java | 618 ++++++ .../knowcenter/wag/egov/egiz/pdf/PDFUtilities.java | 148 ++ .../knowcenter/wag/egov/egiz/pdf/Placeholder.java | 572 ++++++ .../java/at/knowcenter/wag/egov/egiz/pdf/Pos.java | 70 + .../wag/egov/egiz/pdf/PositioningInstruction.java | 139 ++ .../knowcenter/wag/egov/egiz/pdf/ReplaceInfo.java | 93 + .../wag/egov/egiz/pdf/SignatureHolder.java | 76 + .../knowcenter/wag/egov/egiz/pdf/SplitStrings.java | 177 ++ .../knowcenter/wag/egov/egiz/pdf/StringInfo.java | 106 + .../wag/egov/egiz/pdf/StructContentHelper.java | 716 +++++++ .../at/knowcenter/wag/egov/egiz/pdf/TablePos.java | 262 +++ .../wag/egov/egiz/pdf/TextualSignature.java | 282 +++ .../wag/egov/egiz/pdf/TextualSignatureHolder.java | 153 ++ .../at/knowcenter/wag/egov/egiz/pdf/Utils.java | 124 ++ 27 files changed, 8659 insertions(+) create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/AbsoluteTextSignature.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ActualTablePos.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/AdobeSignatureHelper.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinaryBlockInfo.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignature.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignatureHolder.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/EGIZDate.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/IncrementalUpdateInformation.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/NoSignatureHolder.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ObjectExtractor.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFPage.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureCreation.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObject.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObjectIText.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFUtilities.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Placeholder.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Pos.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PositioningInstruction.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ReplaceInfo.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/SignatureHolder.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/SplitStrings.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/StringInfo.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/StructContentHelper.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TablePos.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignature.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignatureHolder.java create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Utils.java (limited to 'pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf') diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/AbsoluteTextSignature.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/AbsoluteTextSignature.java new file mode 100644 index 0000000..fd59d34 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/AbsoluteTextSignature.java @@ -0,0 +1,956 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: AbsoluteTextSignature.java,v 1.1 2006/10/31 08:08:33 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Vector; + +import org.apache.log4j.Logger; + +import at.knowcenter.wag.egov.egiz.PdfAS; +import at.knowcenter.wag.egov.egiz.cfg.ConfigLogger; +import at.knowcenter.wag.egov.egiz.exceptions.SignatureException; +import at.knowcenter.wag.egov.egiz.exceptions.SignatureTypesException; +import at.knowcenter.wag.egov.egiz.framework.FoundBlock; +import at.knowcenter.wag.egov.egiz.framework.FoundKey; +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; +import at.knowcenter.wag.egov.egiz.sig.SignatureTypeDefinition; +import at.knowcenter.wag.egov.egiz.sig.SignatureTypes; + +/** + * Contains methods and helpers that implement the absolute text signature. + * + * @author wprinz + */ +public class AbsoluteTextSignature +{ + + /** + * The logger definition. + */ + private static final Logger logger = ConfigLogger.getLogger(AbsoluteTextSignature.class); + + /** + * Returns a List of SignatureTypeDefinitions that can be extracted from text. + * + *

+ * These SignatureTypeDefinitions are all text extractable, which means that they define all required fields as visible. + *

+ *

+ * This method filters out all SignatureTypeDefinitions that are not text extractable. + *

+ * + * @return Returns a List of SignatureTypeDefinitions that can be extracted from text. + * @throws SignatureTypesException F.e. + */ + public static List getSignatureTypesForTextAnalysis() throws SignatureTypesException + { + SignatureTypes sig_types = SignatureTypes.getInstance(); + List allSignatureTypes = sig_types.getSignatureTypeDefinitions(); + + List textSignatureTypes = new ArrayList(allSignatureTypes.size()); + Iterator it = allSignatureTypes.iterator(); + while (it.hasNext()) + { + SignatureTypeDefinition std = (SignatureTypeDefinition) it.next(); + if (!std.isTextExtractable()) + { + logger.debug("The profile " + std.getType() + " is not text extractable and is thereby not used for text analysis."); + continue; + } + textSignatureTypes.add(std); + } + + return textSignatureTypes; + } + + /** + * Extracts all signature holders from a given text. + * + *

+ * First the latest signature holder is extracted. Then the latest signature + * holder in the rest text, which is the second latest one, is extracted. Then + * the third latest signature holder is extracted and so forth until no more + * signature holders are found. + *

+ * + * @param text + * The text. + * @return Returns the List of extracted signature holders ordered by their + * date ascendingly (the lowest, earliest date first, the latest, + * newest date last). An empty list is returned if no signature + * holders were found. + * @throws SignatureException + * F.e. + * @throws SignatureTypesException + * F.e. + */ + public static List extractSignatureHoldersFromText(String text) throws SignatureException, SignatureTypesException + { + List holders = new ArrayList(); + String current_text = text; + for (;;) + { + TextualSignatureHolder signature_holder = extractLatestBlock(current_text); + if (signature_holder == null) + { + break; + } + holders.add(0, signature_holder); + current_text = signature_holder.getSignedText(); + } + return holders; + } + + /** + * Extracts the latest signature block from the given text and creates a + * SignatureHolder object that can be verified. + * + * @param text + * The text. + * @return Returns the SignatureObject extracted from the text, or null, if no + * latest block was found. + * @throws SignatureException + * F.e. + * @throws SignatureTypesException + * F.e. + */ + public static TextualSignatureHolder extractLatestBlock(String text) throws SignatureException, SignatureTypesException + { + FoundBlock latest_block = findLatestBlock(text); + if (latest_block == null) + { + return null; + } + String reconstructed_text = cutOutBlock(text, latest_block); + SignatureObject so = createSignatureObjectFromFoundBlock(text, latest_block); + TextualSignatureHolder tsh = new TextualSignatureHolder(reconstructed_text, so); + return tsh; + } + + /** + * Finds the latest signature block for a given text. + * + *

+ * The latest block is the one with the highest, most recent date. Usually + * this block will be extracted (cut out) of the text which will result in the + * originally signed text of this signature to be verified using the cut out + * data. + *

+ * + * @param text + * The text to be analyzed. + * @return Returns the latest found block or null, if there was none. + * @throws SignatureException + * F.e. + * @throws SignatureTypesException + * F.e. + */ + public static FoundBlock findLatestBlock(String text) throws SignatureException, SignatureTypesException + { + List signatureTypes_ = getSignatureTypesForTextAnalysis(); + + List found_potential_candidates = new ArrayList(); + + for (int i = 0; i < signatureTypes_.size(); i++) + { + SignatureTypeDefinition block_type = (SignatureTypeDefinition) signatureTypes_.get(i); + List found_candidates_for_type = findPotentialSignaturesForProfile(text, block_type); + + found_potential_candidates.addAll(found_candidates_for_type); + } + + if (found_potential_candidates.isEmpty()) + { + logger.debug("no candidates found at all"); + return null; + } + + List found_candidates = new ArrayList(); + logger.debug("checking block integrity"); + for (int i = 0; i < found_potential_candidates.size(); i++) + { + FoundBlock found_block = (FoundBlock) found_potential_candidates.get(i); + String date_value = getDateValue(text, found_block); + try + { + EGIZDate date = EGIZDate.parseFromString(date_value); + + logger.debug("found_block = " + date + " - " + found_block); + + checkBlockIntegrity(text, found_block); + found_candidates.add(found_block); + } + catch (Exception e) + { + logger.debug("Exception while checking the integrity of the found block " + found_block + ". Ignoring this block.", e); + } + } + + sortFoundBlocksByDate(text, found_candidates); + if (logger.isDebugEnabled()) + { + logger.debug("sorted blocks:"); + for (int i = 0; i < found_candidates.size(); i++) + { + FoundBlock found_block = (FoundBlock) found_candidates.get(i); + + String date_value = getDateValue(text, found_block); + EGIZDate date = EGIZDate.parseFromString(date_value); + + logger.debug(" #" + i + ": " + date + " - " + found_block); + } + } + + List latest_blocks = filterLastDateEqualBlocks(text, found_candidates); + if (logger.isDebugEnabled()) + { + logger.debug("latest blocks:"); + for (int i = 0; i < latest_blocks.size(); i++) + { + FoundBlock found_block = (FoundBlock) latest_blocks.get(i); + + String date_value = getDateValue(text, found_block); + EGIZDate date = EGIZDate.parseFromString(date_value); + + logger.debug(" #" + i + ": " + date + " - " + found_block); + } + } + + // The semantic equality check has been outdated by the + // advanced choosing algorithm. + // boolean semantic_equality = + // PdfAS.checkForSemanticEquality(latest_blocks); + // logger.debug("semantic_equality = " + semantic_equality); + // if (!semantic_equality) + // { + // throw new SignatureException(314, "The latest blocks weren't semantically + // equal."); + // } + + FoundBlock latest_block = chooseMostPossibleBlock(latest_blocks); + + logger.debug("latest block = " + latest_block); + return latest_block; + } + + /** + * Finds the List of potential blocks within the given text for the given + * profile. + * + * @param text + * The text, in which potential block are to be sought. + * @param block_type + * The profile for which the text is to be sought. + * @return Returns the List of potential FoundBlocks or an empty List if none + * could be found. + */ + public static List findPotentialSignaturesForProfile(String text, + SignatureTypeDefinition block_type) + { + logger.debug("find potential signatures for " + block_type.getType()); + List found_blocks = new ArrayList(); + + final boolean old_style = false; + + Vector keys = block_type.getRevertSortedKeys(); + Vector captions = block_type.getRevertSortedCaptions(); + + String last_key = (String) keys.get(0); + logger.debug("last_key = " + last_key); + String last_caption = (String) captions.get(0); + logger.debug("last_caption = " + last_caption); + String current_last_caption= last_caption; + List found_last_captions = findIndicesWithStartingNL(text, last_caption); + if (last_key.equals(SignatureTypes.SIG_ID)) + { + logger.debug("Last key is SIG_ID, so it may not be present. Searching for the previous to last key."); + String prevlast_key = (String) keys.get(1); + String prevlast_caption = (String) captions.get(1); + current_last_caption = prevlast_caption; + List found_prevlast_captions = findIndicesWithStartingNL(text, prevlast_caption); + if (!found_prevlast_captions.isEmpty()) + { + found_last_captions.addAll(found_prevlast_captions); + } + } + if (logger.isDebugEnabled()) + { + logger.debug("found " + found_last_captions.size() + " last captions."); + for (int i = 0; i < found_last_captions.size(); i++) + { + logger.debug(" found last caption at index " + found_last_captions.get(i)); + } + } + + for (int lci = 0; lci < found_last_captions.size(); lci++) + { + int last_caption_index = ((Integer) found_last_captions.get(lci)).intValue(); + logger.debug("resolving signature block from last caption index " + last_caption_index); + int potential_block_end = findEndOfValue(text, last_caption_index); + if (potential_block_end == (last_caption_index + current_last_caption.length()+1)) + { + potential_block_end = findEndOfValue(text, potential_block_end); + } + + // FIXME: complete HOTFIX + /* + int extendedValueEnd = potential_block_end; + String cv; + do { + extendedValueEnd = findEndOfValue(text, extendedValueEnd); + cv = text.substring(last_caption_index + current_last_caption.length()+1, extendedValueEnd); + } while (extendedValueEnd < text.length()); + */ + + logger.debug("potential_block_end = " + potential_block_end); + List found_keys = PdfAS.findBlockInText(text.substring(0, potential_block_end), block_type, old_style); // findRestKeys(text, + // keys, + // captions, + // last_caption_index); + + if (found_keys == null) + { + logger.debug("Not all other captions could be found for the last_caption_index " + last_caption_index + " ==> discarding this index."); + + continue; + } + + // sort found keys ascendingly + PdfAS.sortFoundKeysAscendingly(found_keys); + + boolean reverse_check_ok = reverseCheckFoundKeys(text, found_keys); + if (!reverse_check_ok) + { + logger.debug("The reverse check ruled this list of found keys out ==> they are discarded."); + + continue; + } + + logger.debug("The reverse check proved this list of found keys out ==> adding them as potential candidates."); + + FoundBlock found_block = new FoundBlock(); + found_block.std = block_type; + found_block.found_keys = found_keys; + found_block.end_index = potential_block_end;//findEndOfValue(text, last_caption_index); + found_blocks.add(found_block); + } + + logger.debug("found " + found_blocks.size() + " potential signatures for " + block_type.getType()); + return found_blocks; + } + + /** + * Finds all indices of the given subtext (starting at a new line) within a + * given text. + * + *

+ * This is usually used to find the indices of the last captions. + *

+ * + * @param text + * The text to be searched. + * @param subtext + * The subtext to be sought. + * @return Returns the List of found indices. + */ + public static List findIndicesWithStartingNL(String text, String subtext) + { + List found_indices = new ArrayList(); + + // // for some reason "^" + subtext doesn't work as a pattern + // String pattern = "\n" + subtext; + // Pattern p = Pattern.compile(pattern); + // Matcher m = p.matcher(text); + // + // while (m.find()) + // { + // int found_index = m.start() + 1; // +1 removes the newline + // found_indices.add(new Integer(found_index)); + // } + + int search_from_index = 0; + for (;;) + { + int found_index = text.indexOf("\n" + subtext, search_from_index); + if (found_index < 0) + { + break; + } + found_index += 1; // The +1 compensates the "\n" + found_indices.add(new Integer(found_index)); + search_from_index = found_index + subtext.length(); + } + return found_indices; + } + + /** + * Finds the other keys/captions according to their order starting from the + * last_caption index upwards. + * + * @param text + * The text. + * @param keys + * The list of keys. + * @param captions + * The list of captions. + * @param last_caption_index + * The index of the last caption. + * @return Returns the List of found keys, if all keys could be found, or null + * if not all keys could be found. + */ + public static List findRestKeys(String text, List keys, List captions, + int last_caption_index) + { + List found_keys = new ArrayList(); + + FoundKey last_caption_found_key = new FoundKey((String) keys.get(0), (String) captions.get(0), last_caption_index); + found_keys.add(last_caption_found_key); + + String rest_text = text.substring(0, last_caption_index); + + for (int i = 1; i < captions.size(); i++) + { + String sought_caption = (String) captions.get(i); + int index = rest_text.lastIndexOf(sought_caption); + + if (index < 0) + { + return null; + } + FoundKey found_key = new FoundKey((String) keys.get(i), (String) captions.get(i), index); + found_keys.add(0, found_key); + + rest_text = rest_text.substring(0, index); + } + + return found_keys; + } + + /** + * Performs a reverse (top to bottom) search for the found keys and checks + * that these indices are the same as those that were found during the regular + * (bottom up) search. + *

+ * If a reverse check proves that the found keys are not at the same positions + * as during regular search, this list of found keys should be discarded. + *

+ * + * @param text + * The text. + * @param found_keys + * The found keys to be reversely checked. + * @return Returns true, if all (also the non required) captions could be + * found at the same indices as during regular search, false + * otherwise. + */ + public static boolean reverseCheckFoundKeys(String text, List found_keys) + { + int search_from_index = ((FoundKey) found_keys.get(0)).start_index; + for (int i = 0; i < found_keys.size(); i++) + { + FoundKey found_key = (FoundKey) found_keys.get(i); + + int reverse_found_index = text.indexOf(found_key.caption, search_from_index); + + // dferbas fix #331 ?? + + if (reverse_found_index < 0) + { + throw new RuntimeException("The caption " + found_key.caption + " wasn't found in the text during reverse checking - there is something wrong."); + } + + if (reverse_found_index != found_key.start_index) + { + logger.debug("The index for caption " + found_key.caption + " wasn't proved during reverse checking."); + return false; + } + search_from_index = found_key.start_index + found_key.caption.length(); + } + return true; + } + + /** + * Finds the end of the value in the text. + * + *

+ * This simply scans for a '\n' from a given start index. The line up to and + * inclusive the '\n' is considered to be the value. + *

+ *

+ * Note that this method does NOT find the accurate value, if the value goes + * over multiple lines! This may bear a serious problem. Usually this method + * is only used to finding the end of the last value in a found block, because + * mid- values are exactly determined by their start index and the start of + * the next caption. Nevertheless, if the last value spans over multiple + * lines, this method will not retrieve it completely. + *

+ * + * @param text + * The text. + * @param start_index + * The start index from where the end of the value is sought. + * @return Returns the end index of the value, which is the index of the first + * character not belonging to the value anymore (the character after + * the '\n'). + */ + public static int findEndOfValue(String text, int start_index) + { + int newline_index = text.indexOf('\n', start_index); + if (newline_index < 0) + { + return text.length(); + } + return newline_index + 1; + } + + /** + * Checks the integrity of a found block. + * + *

+ * This is an assertive function. + *

+ * + * @param text + * The text. + * @param found_block + * The found block. + */ + public static void checkBlockIntegrity(String text, FoundBlock found_block) + { + for (int i = 0; i < found_block.found_keys.size() - 1; i++) + { + FoundKey this_key = (FoundKey) found_block.found_keys.get(i); + FoundKey next_key = (FoundKey) found_block.found_keys.get(i + 1); + + int this_end_index = findEndOfValue(text, this_key.start_index); + if (this_end_index != next_key.start_index) + { + logger.debug("multi line value: " + this_key); + // throw new RuntimeException("The end index of found key " + this_key + + // " doesn't match the start index of found key " + next_key); + } + } + + FoundKey last_key = (FoundKey) found_block.found_keys.get(found_block.found_keys.size() - 1); + int end_of_block = findEndOfValue(text, last_key.start_index); + if (end_of_block == (last_key.start_index+last_key.caption.length()+1)) + { + end_of_block = findEndOfValue(text,end_of_block); + } + if (end_of_block != found_block.end_index) + { + throw new RuntimeException("The end index of last key " + last_key + " doesn't match the end index of the block " + found_block); + } + } + + /** + * Cuts out the given found block from the text. + * + * @param text + * The text. + * @param block + * The found block. + * @return Returns the rest text without the block. + */ + public static String cutOutBlock(String text, FoundBlock block) + { + int block_start_index = ((FoundKey) block.found_keys.get(0)).getStartIndex(); + int block_end_index = block.end_index; + + if (block_start_index == 0 && block_end_index == text.length()) + { + // the block is the whole text - the rest text is empty. + // This may happen if a (no-text) empty document contains a binary signature. + // Then the "signed text" of the binary signature is empty. + return ""; + } + + if (block_end_index == text.length()) + { + // if the block is at the end of the text, remove the "\n" before the + // block as well. + String pre = text.substring(0, block_start_index - 1); + return pre; + } + + String pre = text.substring(0, block_start_index); + String post = text.substring(block_end_index); + + String rest_text = pre + post; + return rest_text; + } + + /** + * Returns the value of the date field as String. + * + * @param text + * The text. + * @param block + * The found block. + * @return Returns the date value. + */ + public static String getDateValue(String text, FoundBlock block) + { + FoundKey date_key = block.getDateFoundKey(); + int date_value_start_index = date_key.start_index + date_key.caption.length(); + int date_value_end_index = findEndOfValue(text, date_value_start_index); + if (date_value_end_index == (date_value_start_index+1)) + { + date_value_end_index = findEndOfValue(text, date_value_end_index); + } + String date_value = text.substring(date_value_start_index, date_value_end_index).trim(); + logger.debug("DateString="+date_value); + return date_value; + } + + /** + * Creates a SignatureObject from a found block by extracting the + * corresponding values. + * + * @param text + * The text. + * @param found_block + * The found block. + * @return Returns the created SignatureObject. + * @throws SignatureTypesException + * F.e. + * @throws SignatureException + * F.e. + */ + public static SignatureObject createSignatureObjectFromFoundBlock( + String text, FoundBlock found_block) throws SignatureTypesException, SignatureException + { + SignatureObject signatureObject = new SignatureObject(); + + signatureObject.setSigType(found_block.std.getType()); + signatureObject.initByType(); + + int end_index = found_block.end_index; + for (int i = found_block.found_keys.size() - 1; i >= 0; i--) + { + FoundKey cur_key = (FoundKey) found_block.found_keys.get(i); + int start_index = cur_key.getStartIndex() + cur_key.caption.length(); + + String value = text.substring(start_index, end_index); + + signatureObject.setSigValueCaption(cur_key.getKey(), value, cur_key.caption); + + end_index = cur_key.getStartIndex(); + } + + return signatureObject; + + } + + /** + * Parses the EGIZDate from a found block and the given text. + * + * @param text + * The text. + * @param found_block + * The found block. + * @return Returns the parsed EGIZDate. + */ + public static EGIZDate getDateFromFoundBlock(String text, + FoundBlock found_block) + { + String date_value = getDateValue(text, found_block); + EGIZDate date = EGIZDate.parseFromString(date_value); + return date; + } + + /** + * Sorts the List of found blocks by date. + * + * @param text + * The text. + * @param found_blocks + * The List of found blocks. + */ + public static void sortFoundBlocksByDate(final String text, List found_blocks) + { + Collections.sort(found_blocks, new Comparator() + { + public int compare(Object arg0, Object arg1) + { + FoundBlock fb0 = (FoundBlock) arg0; + FoundBlock fb1 = (FoundBlock) arg1; + + EGIZDate date0 = getDateFromFoundBlock(text, fb0); + EGIZDate date1 = getDateFromFoundBlock(text, fb1); + return date0.compareTo(date1); + } + }); + } + + /** + * Given a List of FoundBlock objects, this method returns the last blocks of + * this list that have the same date. + * + *

+ * Usually a date sorted list (earliest first, latest last) will be provided + * to this method. Then the last date equal blocks are returned, which are the + * last blocks. + *

+ * + * @param text + * The text to retrieve the values of the fields from. + * @param found_blocks + * The List of FoundBlock objects. + * @return Returns the List of the last date equal blocks. + */ + public static List filterLastDateEqualBlocks(String text, List found_blocks) + { + List latest_blocks = new ArrayList(); + latest_blocks.add(found_blocks.get(found_blocks.size() - 1)); + for (int i = found_blocks.size() - 2; i >= 0; i--) + { + FoundBlock this_block = (FoundBlock) found_blocks.get(i); + FoundBlock succ_block = (FoundBlock) found_blocks.get(i + 1); + + EGIZDate this_date = getDateFromFoundBlock(text, this_block); + EGIZDate succ_date = getDateFromFoundBlock(text, succ_block); + + if (!this_date.equals(succ_date)) + { + break; + } + latest_blocks.add(0, this_block); + } + + return latest_blocks; + } + /** + * Chooses the most possible (best choice) block of the list of blocks. + * + *

+ * The strategy to find the most possible block is to choose the very one + * block with the maximum number of captions. This block has extracted most + * information from the text. + *

+ *

+ * If there are still multiple blocks with the same number of cations, the + * blocks are compared caption-wise. The block with all captions being longer + * or equal to all other blocks' captions wins. + *

+ * + * @param found_blocks + * The List of semantically equal blocks. + * @return Returns the best choice FoundBlock. + * @throws SignatureException + */ + public static FoundBlock chooseMostPossibleBlock(List found_blocks) throws SignatureException + { + // int largest_block_index = 0; + // FoundBlock largest_block = (FoundBlock) found_blocks.get(0); + // + // for (int i = 1; i < found_blocks.size(); i++) + // { + // FoundBlock current_block = (FoundBlock) found_blocks.get(i); + // + // if (current_block.found_keys.size() > largest_block.found_keys.size()) + // { + // largest_block = current_block; + // largest_block_index = i; + // } + // } + + List vertically_largest = filterVerticallyLargestBlocks(found_blocks); + if (logger.isDebugEnabled()) + { + logger.debug("vertically largest blocks:"); + for (int i = 0; i < vertically_largest.size(); i++) + { + FoundBlock found_block = (FoundBlock) vertically_largest.get(i); + logger.debug(" #" + i + ": " + found_block); + } + } + + List horizontally_largest = filterHorizontallyLargestBlocks(vertically_largest); + if (logger.isDebugEnabled()) + { + logger.debug("horizontally largest blocks:"); + for (int i = 0; i < horizontally_largest.size(); i++) + { + FoundBlock found_block = (FoundBlock) horizontally_largest.get(i); + logger.debug(" #" + i + ": " + found_block); + } + } + FoundBlock largest_block = (FoundBlock) horizontally_largest.get(0); + logger.debug("Chose largest block: " + largest_block); + return largest_block; + } + + /** + * Filters out all blocks but the vertically largest ones. + * + *

+ * A vertically largest block has the most found keys. + *

+ * + * @param found_blocks + * The List of FoundBlock objects to be filtered. + * @return Returns the List of the vertically largest FoundBlock objects. + */ + public static List filterVerticallyLargestBlocks(List found_blocks) + { + // determine the size of the largest block(s) + int largest_size = Integer.MIN_VALUE; + for (int i = 0; i < found_blocks.size(); i++) + { + FoundBlock fb = (FoundBlock) found_blocks.get(i); + final int current_size = fb.found_keys.size(); + if (current_size > largest_size) + { + largest_size = current_size; + } + } + + // keep all blocks that have the largest_size + List largest_blocks = new ArrayList(); + for (int i = 0; i < found_blocks.size(); i++) + { + FoundBlock fb = (FoundBlock) found_blocks.get(i); + if (fb.found_keys.size() < largest_size) + { + continue; + } + largest_blocks.add(fb); + } + return largest_blocks; + } + + /** + * Filters out all blocks but the horizonally largest ones. + * + *

+ * A vertically largest block has the most found keys. + *

+ * + * @param found_blocks + * The List of FoundBlock objects to be filtered. All of these + * FoundBlock objects must have the same number of found keys. + * @return Returns the List of the horizontally largest FoundBlock objects. + * @throws SignatureException + */ + public static List filterHorizontallyLargestBlocks(List found_blocks) throws SignatureException + { + List horizontally_largest = new ArrayList(); + FoundBlock largest_block = (FoundBlock) found_blocks.get(0); + horizontally_largest.add(largest_block); + + for (int i = 1; i < found_blocks.size(); i++) + { + FoundBlock fb = (FoundBlock) found_blocks.get(i); + + if (isHorizontallyEqual(fb, largest_block)) + { + horizontally_largest.add(fb); + continue; + } + + if (isHorizontallyLarger(fb, largest_block)) + { + horizontally_largest = new ArrayList(); + largest_block = fb; + horizontally_largest.add(largest_block); + } + else + { + if (!isHorizontallyLarger(largest_block, fb)) + { + // The block is neither equal nor larger nor lower. + // We cannot exactly determine which one to use. + throw new SignatureException(315, "The blocks are neither larger nor lower nor equal. Cannot decide which one to pick. fb = " + fb + ", largest_block = " + largest_block); + } + } + + } + + return horizontally_largest; + } + + protected static boolean isHorizontallyEqual(FoundBlock fb0, FoundBlock fb1) + { + final int num_keys = fb0.found_keys.size(); + if (num_keys != fb1.found_keys.size()) + { + throw new IllegalArgumentException("Cannot compare FoundBlock keys: fb0 doesn't have the same number of keys as fb1. " + fb0.found_keys.size() + " vs. " + fb1.found_keys.size()); + } + + for (int i = 0; i < num_keys; i++) + { + FoundKey fk0 = (FoundKey) fb0.found_keys.get(i); + FoundKey fk1 = (FoundKey) fb1.found_keys.get(i); + + if (fk0.caption.length() != fk1.caption.length()) + { + return false; + } + } + + return true; + } + + protected static boolean isHorizontallyLarger(FoundBlock fb0, FoundBlock fb1) + { + final int num_keys = fb0.found_keys.size(); + if (num_keys != fb1.found_keys.size()) + { + throw new IllegalArgumentException("Cannot compare FoundBlock keys: fb0 doesn't have the same number of keys as fb1. " + fb0.found_keys.size() + " vs. " + fb1.found_keys.size()); + } + + boolean larger = false; + + for (int i = 0; i < num_keys; i++) + { + FoundKey fk0 = (FoundKey) fb0.found_keys.get(i); + FoundKey fk1 = (FoundKey) fb1.found_keys.get(i); + + if (fk0.caption.length() == fk1.caption.length()) + { + continue; + } + + if (fk0.caption.length() > fk1.caption.length()) + { + larger = true; + continue; + } + + // if (fk0.caption.length() < fk1.caption.length()) + return false; + } + + return larger; + } + +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ActualTablePos.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ActualTablePos.java new file mode 100644 index 0000000..6c0e56c --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ActualTablePos.java @@ -0,0 +1,42 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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.knowcenter.wag.egov.egiz.pdf; + +/** + * The actual table position where the signature was placed after signation. + * @author wprinz + */ +public class ActualTablePos +{ + public int page; + + public float x; + + public float y; + + public float width; + + public float height; + +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/AdobeSignatureHelper.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/AdobeSignatureHelper.java new file mode 100644 index 0000000..e337e71 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/AdobeSignatureHelper.java @@ -0,0 +1,272 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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.knowcenter.wag.egov.egiz.pdf; + +import java.util.HashMap; +import java.util.Iterator; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; + +import at.gv.egiz.pdfas.exceptions.ErrorCode; +import at.gv.egiz.pdfas.framework.signator.SignatorInformation; +import at.gv.egiz.pdfas.utils.OgnlUtil; +import at.knowcenter.wag.egov.egiz.cfg.SettingsReader; +import at.knowcenter.wag.egov.egiz.exceptions.PresentableException; +import at.knowcenter.wag.egov.egiz.exceptions.SettingsException; +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; + +import com.lowagie.text.Rectangle; +import com.lowagie.text.pdf.AcroFields; +import com.lowagie.text.pdf.PdfDictionary; +import com.lowagie.text.pdf.PdfFormField; +import com.lowagie.text.pdf.PdfName; +import com.lowagie.text.pdf.PdfNumber; +import com.lowagie.text.pdf.PdfObject; +import com.lowagie.text.pdf.PdfSignature; +import com.lowagie.text.pdf.PdfSignatureAppearance; +import com.lowagie.text.pdf.PdfStamper; +import com.lowagie.text.pdf.PdfString; + +/** + * Helper class for creating adobe signature attributes. + * + * @author dferbas + * + */ +public class AdobeSignatureHelper { + private static final String ADOBE_SIGN_FIELDNAME_KEY = "adobeSignFieldValue"; + + private static final String ADOBE_SIGN_REASONNAME_KEY = "adobeSignReasonValue"; + + private static final String ADOBE_SIG_ENABLED_KEY = "adobeSignEnabled"; + + private static Logger logger = Logger.getLogger(AdobeSignatureHelper.class); + + public static final String ADOBE_SIG_FILTER = "Adobe.PDF-AS"; + + public static final String ADOBE_SIG_TEXT_KEY = "adobeSignText"; + + private static final String ADOBE_VERIFY_URL_KEY = "verifyURL"; + + /** + * Writes Adobe-pdf signature entry with itext + * + * @param stamper + * @param si + * @param so + * @param atp + * @throws PresentableException + */ + public static void createAdobeSignatureField(PdfStamper stamper, SignatorInformation si, + SignatureObject so, ActualTablePos atp, StructContentHelper structHelper) throws PresentableException { + + try { + logger.debug("Creating adobe signature field."); + PdfSignatureAppearance sap = stamper.getSignatureAppearance(); + + String profileId = so.getSignatureTypeDefinition().getType(); + String fieldName = getAdobeFieldName(profileId); + // find field num + /* + int nexSigNum = 1; + String finalFieldName = fieldName + " #" + nexSigNum; + + while (stamper.getAcroFields().getField(finalFieldName) != null) { + nexSigNum++; + finalFieldName = fieldName + " #" + nexSigNum; + } + */ + + AcroFields af = stamper.getAcroFields(); + Iterator signatureNamesIt = af.getSignatureNames().iterator(); + PdfName referenceFilterName = new PdfName(ADOBE_SIG_FILTER); + int nextSigNum = 1; + while (signatureNamesIt.hasNext()) { + PdfDictionary dictionary = (PdfDictionary) af.getSignatureDictionary((String) signatureNamesIt.next()); + PdfObject filterName = dictionary.get(PdfName.FILTER); + if (filterName != null && filterName.isName()) { + PdfName name = (PdfName) filterName; + if (referenceFilterName.equals(name)) { + nextSigNum++; + } + } + } + String finalFieldName = fieldName + " #" + nextSigNum; + + sap.setCrypto(null, null, null, null); + // supress overlay text for visible signatures + sap.setLayer2Text(""); + sap.setLayer4Text(""); + + // the following line marks the sig block as adobe sig + // sap.setVisibleSignature(createRectangleFromTablePos(iui.actualTablePos), + // iui.actualTablePos.page, "PDF-AS-Signatur"); + sap.setVisibleSignature(new Rectangle(0, 0, 0, 0), atp.page, finalFieldName); + String subfilter = "unknown"; + if (so != null && so.getKZ() != null) { + subfilter = so.getKZ().toString(); + } else if (si != null) { + subfilter = si.getSignSignatureObject().kz; + } + PdfSignature sig = new PdfSignature(new PdfName(ADOBE_SIG_FILTER), new PdfName(subfilter)); + // the following fields are not shown by the reader, because its is no + // Standard filter + // sig.setLocation("location is not visible"); + // sig.setReason("reason is not visible"); + + // contact field is used to embed signature verification url for adobe handler + String verifyURL = getVerifyUrl(profileId); + if (!StringUtils.isEmpty(verifyURL)) { + sig.setContact(getVerifyUrl(profileId)); + } else { + logger.debug("No verify URL set -> verify URL is not embedded."); + } + // sig.setDate(new PdfDate()); + + String reason = getAdobeReasonName(profileId); + if (!StringUtils.isEmpty(reason)) { + sig.setReason(reason); + } + + + /* disabled in order to align adobe signature appearance for textual signatures with binary signatures + if (si != null) { + XMLGregorianCalendar c = DatatypeFactory.newInstance().newXMLGregorianCalendar( + si.getSignSignatureObject().date); + sig.setDate(new PdfDate(c.toGregorianCalendar())); + } + */ + + sig.setName(getAdobeSignText(profileId, si)); + sap.setCryptoDictionary(sig); + sap.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED); + + // content element is mandatory but empty + HashMap exc = new HashMap(); + exc.put(PdfName.CONTENTS, new Integer(2)); + + PdfNumber parentNum = structHelper.buildAdobeSigStructParent(); + if (parentNum != null) { + PdfFormField sigField = PdfFormField.createSignature(stamper.getWriter()); + sap.setSigFormField(sigField); + sigField.put(PdfName.STRUCTPARENT, parentNum); + structHelper.buildAdobeSigStruct(sigField, finalFieldName); + } + sap.preClose(exc); // *2+2 + + PdfDictionary dic = new PdfDictionary(); + dic.put(PdfName.CONTENTS, new PdfString((String) null).setHexWriting(true)); + sap.close(dic); + } catch (Exception ex) { + logger.error("error", ex); + throw new PresentableException(ErrorCode.CANNOT_WRITE_PDF, + "Error creating adobe signature attribute", ex); + } + } + + + /** + * Returns if adobe signature is enabled for the passed signature profile. + * Config key: {@value #ADOBE_SIG_ENABLED_KEY} + * @param sigProfile + * @return + */ + public static boolean isAdobeSignatureFieldEnabled(String sigProfile) { + return "true".equalsIgnoreCase( + getDefaultableConfigProperty(sigProfile, ADOBE_SIG_ENABLED_KEY, "false")); + } + + protected static Rectangle createRectangleFromTablePos(ActualTablePos pos) { + return new Rectangle(pos.x, pos.y, pos.x + pos.width, pos.y - pos.height); + } + + private static String getAdobeFieldName(String sigProfile) { + return getDefaultableConfigProperty(sigProfile, ADOBE_SIGN_FIELDNAME_KEY, "PDF-AS Signatur"); + } + + private static String getAdobeReasonName(String sigProfile) { + return getDefaultableConfigProperty(sigProfile, ADOBE_SIGN_REASONNAME_KEY, "Informationen zur Prüfung finden Sie unter http://www.signaturpruefung.gv.at"); + } + + private static String getVerifyUrl(String sigProfile) { + return getDefaultableConfigProperty(sigProfile, ADOBE_VERIFY_URL_KEY, "http://www.signaturpruefung.gv.at"); + } + + public static String getDefaultableConfigProperty(String sigProfile, String propName, String defaultValue) { + String confVal; + try { + confVal = SettingsReader.getInstance().getSetting( + "sig_obj." + sigProfile + "." + propName, + "default." + propName, + defaultValue); + } catch (SettingsException e) { + logger.warn("error reading " + propName + " from config. Using default: " + defaultValue, e); + return defaultValue; + } + return confVal; + } + + /** + * Evaluate name for adobe signature field. Get from config. Evaluate ognl if + * ok. + * + * @param sigProfile + * @param si + * @return + */ + private static String getAdobeSignText(String sigProfile, SignatorInformation si) { + String defaultName = "PDF-AS"; + try { + logger.debug("reading adobe sig name for profile: " + sigProfile); + String propKey = ADOBE_SIG_TEXT_KEY + ".textual"; + if (si == null) { + propKey = propKey.replaceAll("textual", "binary"); + } + String adobeStr = getDefaultableConfigProperty(sigProfile, propKey, defaultName); + + HashMap ognlCtx = new HashMap(); + OgnlUtil ognl = new OgnlUtil(ognlCtx); + if (ognl.containsExpression(adobeStr)) { + if (si == null) { + logger + .error(ADOBE_SIG_TEXT_KEY + + " ognl expressions not allowed for binary signatures (SignatorInformation not available)"); + return defaultName; + } + ognlCtx.put("si", si); + ognlCtx.put("sso", si.getSignSignatureObject()); + String res = ognl.compileMessage(adobeStr); + return res; + } else { + return adobeStr; + } + + } catch (Exception ex) { + logger.warn("error creating adobe sign text, using default '" + defaultName + "'", ex); + return defaultName; + } + } + +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinaryBlockInfo.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinaryBlockInfo.java new file mode 100644 index 0000000..2087712 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinaryBlockInfo.java @@ -0,0 +1,61 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: BinaryBlockInfo.java,v 1.1 2006/08/25 17:10:08 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.util.List; + +/** + * Helper class that holds information about a binary signature block. + * + * @author wprinz + */ +public class BinaryBlockInfo +{ + /** + * The signed size, in bytes. + * + *

+ * This includes the block itself. + *

+ */ + public int signed_size = -1; + + /** + * The List of ReplaceInfo objects that specify the replaced strings. + */ + public List replaces = null; + +// /** +// * The start of the /ODS number in the PDF. +// */ +// public int ods_start = -1; +// +// /** +// * The start of the \replaces array in the PDF. +// */ +// public int array_start = -1; + +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignature.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignature.java new file mode 100644 index 0000000..ece9525 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignature.java @@ -0,0 +1,2145 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: BinarySignature.java,v 1.4 2006/10/11 07:57:58 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import at.gv.egiz.pdfas.api.timestamp.TimeStamper; +import at.gv.egiz.pdfas.exceptions.ErrorCode; +import at.gv.egiz.pdfas.exceptions.pdf.CaptionNotFoundException; +import at.gv.egiz.pdfas.exceptions.pdf.KZSettingNotFoundException; +import at.gv.egiz.pdfas.framework.input.PdfDataSource; +import at.gv.egiz.pdfas.framework.output.DataSink; +import at.gv.egiz.pdfas.framework.signator.SignatorInformation; +import at.gv.egiz.pdfas.placeholder.SignaturePlaceholderContext; +import at.gv.egiz.pdfas.placeholder.SignaturePlaceholderData; +import at.gv.egiz.pdfas.utils.PDFASUtils; +import at.knowcenter.wag.egov.egiz.cfg.SettingsReader; +import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; +import at.knowcenter.wag.egov.egiz.exceptions.PlaceholderException; +import at.knowcenter.wag.egov.egiz.exceptions.PresentableException; +import at.knowcenter.wag.egov.egiz.exceptions.SettingNotFoundException; +import at.knowcenter.wag.egov.egiz.exceptions.SettingsException; +import at.knowcenter.wag.egov.egiz.sig.SignatureFieldDefinition; +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; +import at.knowcenter.wag.egov.egiz.sig.SignatureTypeDefinition; +import at.knowcenter.wag.egov.egiz.sig.SignatureTypes; +import at.knowcenter.wag.egov.egiz.tools.CodingHelper; +import at.knowcenter.wag.exactparser.ByteArrayUtils; + +import com.lowagie.text.BadElementException; +import com.lowagie.text.Document; +import com.lowagie.text.DocumentException; +import com.lowagie.text.Image; +import com.lowagie.text.Rectangle; +import com.lowagie.text.pdf.BadPdfFormatException; +import com.lowagie.text.pdf.PRStream; +import com.lowagie.text.pdf.PdfArray; +import com.lowagie.text.pdf.PdfContentByte; +import com.lowagie.text.pdf.PdfDictionary; +import com.lowagie.text.pdf.PdfImage; +import com.lowagie.text.pdf.PdfIndirectObject; +import com.lowagie.text.pdf.PdfIndirectReference; +import com.lowagie.text.pdf.PdfName; +import com.lowagie.text.pdf.PdfNumber; +import com.lowagie.text.pdf.PdfObject; +import com.lowagie.text.pdf.PdfPTable; +import com.lowagie.text.pdf.PdfReader; +import com.lowagie.text.pdf.PdfStamper; +import com.lowagie.text.pdf.PdfStamperImp; +import com.lowagie.text.pdf.PdfString; +import com.lowagie.text.pdf.PdfTemplate; + +/** + * Contains various extension functions to digitally sign documents. + * + *

+ * These functions are used to replace parts of the original Egiz plain text + * signature mechanism. + *

+ * + * @author wprinz + */ +public abstract class BinarySignature +{ +//23.11.2010 changed by exthex - added replacePlaceholder(PdfStamper stamper, int pageNr, String placeholderName) method + + protected static Log logger = LogFactory.getLog(BinarySignature.class); + /** + * The tolerance area of the line break algorithm. + * + * @see Placeholder#replacePlaceholderWithTolerance(byte[], List, byte[], int) + */ + public static final int LINE_BREAK_TOLERANCE = 10; + + /** + * The number of bytes left out for the certificate placeholder. + */ + public static final int CERTIFICATE_PLACEHOLDER_LENGTH = 10000; + + /** + * The number of bytes left out for the timestamp placeholder. + */ + public static final int TIMESTAMP_PLACEHOLDER_LENGTH = 5000; + + /** + * The placeholder character used to fill out Strings in the layout process. + */ + public static final byte LAYOUT_PLACEHOLDER = 'w'; + + /** + * This placeholder is used to fill out holes between the byte ranges before + * the document is signed. + */ + public static final byte SIGN_PLACEHOLDER = 0; + + /** + * The nil brev used to define an unrecognized value. + */ + public static final byte[] BREV_NIL = { 'n', 'i', 'l' }; + + /** + * The date brev. + */ + public static final byte[] BREV_DAT = { 'd', 'a', 't' }; + + /** + * The issure brev. + */ + public static final byte[] BREV_ISS = { 'i', 's', 's' }; + + /** + * The serial number brev. + */ + public static final byte[] BREV_SNR = { 's', 'n', 'r' }; + + /** + * The value brev. + */ + public static final byte[] BREV_VAL = { 'v', 'a', 'l' }; + + /** + * The SIG_ID brev. + */ + public static final byte[] BREV_SID = { 's', 'i', 'd' }; + + /** + * The SIG_ALG brev. + */ + public static final byte[] BREV_ALG = { 'a', 'l', 'g' }; + + /** + * No explicit encoding. + */ + public static final byte[] ENCODING_NIL = { 'n', 'i', 'l' }; + + /** + * PDF WinAnsiEncoding. + */ + public static final byte[] ENCODING_WIN = { 'w', 'i', 'n' }; + + /** + * URL encoding. + */ + public static final byte[] ENCODING_URL = { 'u', 'r', 'l' }; + + /** + * The PDFName of the Egiz Dictionary. + * + *

+ * Used to locate and identify the Egiz Dictionary in the document. + *

+ */ + public static final PdfName EGIZ_DICT_NAME = new PdfName("EGIZSigDict"); + + /** + * The PDFName of the Original Document Size (ODS) field in an Egiz + * Dictionary. + * + *

+ * The ODS must be a positive integral number. + *

+ */ + public static final PdfName EGIZ_ODS_NAME = new PdfName("ODS"); + + /** + * The PDFName of the Kennzeichnung attribute. + */ + public static final PdfName EGIZ_KZ_NAME = new PdfName("ID"); + + /** + * The PDFName of the /replaces field in an Egiz Dictionary. + */ + public static final PdfName EGIZ_REPLACES_NAME = new PdfName("replaces"); + + /** + * The PDFName of the /encodings field in an Egiz Dictionary. + */ + public static final PdfName EGIZ_ENCODINGS_NAME = new PdfName("encodings"); + + /** + * The PDFName of the byte ranges array. + */ + public static final PdfName EGIZ_BYTERANGES_NAME = new PdfName("ByteRange"); + + /** + * The PdfName of the certificate array. + */ + public static final PdfName EGIZ_CERTIFICATE_NAME = new PdfName("Cert"); + + /** + * The PdfName of the Timestamp + */ + public static final PdfName EGIZ_TIMESTAMP_NAME = new PdfName("TimeStamp"); + + /** + * The PdfName of the data array that contains various egiz-dict data. + */ + public static final PdfName EGIZ_DATA_NAME = new PdfName("Data"); + + /** + * The PDFName of the Signature XObject field in an Egiz Dictionary. + * + *

+ * This must be an indirect reference to the XObject containing the Signature + * table. + *

+ */ + public static final PdfName EGIZ_XOBJ_NAME = new PdfName("SigXObject"); + + /** + * The number placeholder that is used to give numbers a fixed length. + */ + protected static final PdfNumber NUMBER_PLACEHOLDER = new PdfNumber(999999999); + + /** + * Extracts the signature text only. + * + *

+ * The signature text is the text of the Signature XObject. + *

+ * + * @param egiz_dict + * The Egiz Dictionary. + * + * @return Returns the signature text. + */ + public static String extractSignatureTextOnly(PdfDictionary egiz_dict) throws IOException + { + PdfIndirectReference xobj_ir = (PdfIndirectReference) egiz_dict.get(EGIZ_XOBJ_NAME); + PRStream temp_stream = (PRStream) PdfReader.getPdfObject(xobj_ir); + + byte[] stream_bytes = PdfReader.getStreamBytes(temp_stream); + + return Utils.extractPureTextFromContentStream(stream_bytes); + } + + /** + * Retrieves the size of the original document from the Egiz Dictionary. + * + * @param egiz_dict + * The Egiz Dictionary. + * @return Returns the size (in bytes) of the original document. + */ + public static int getOriginalDocumentSizeFromEgizDict(PdfDictionary egiz_dict) + { + PdfObject ods_obj = egiz_dict.get(EGIZ_ODS_NAME); + PdfNumber ods_number = (PdfNumber) PdfReader.getPdfObject(ods_obj); + + return ods_number.intValue(); + } + + /** + * Retrieves the previous Egiz dictionary from the given one, if a previous + * dictionary exists. + * + * @param egiz_dict + * The Egiz Dictionary. + * @return Returns the previous Egiz Dictionary, or null if there is none. + */ + public static PdfDictionary getPreviousFromEgizDict(PdfDictionary egiz_dict) + { + PdfObject prev_obj = egiz_dict.get(PdfName.PREV); + PdfDictionary previous_dict = (PdfDictionary) PdfReader.getPdfObject(prev_obj); + return previous_dict; + } + + /** + * Retrieves the Egiz Dictionary from the document if present. + * + * @param reader + * The reader to retrieve the dictionary from. + * @return Returns the Egiz Dictionary, if present, or returns null, if no + * egiz dictionary was found. + */ + public static PdfDictionary getEgizDictFromReader(PdfReader reader) + { + PdfIndirectReference dict_ir = getEgizDictIndRefFromReader(reader); + if (dict_ir == null) + { + return null; + } + + PdfDictionary egiz_dict = (PdfDictionary) PdfReader.getPdfObject(dict_ir); + + return egiz_dict; + } + + /** + * Retrieves the Egiz Dictionary's indirect reference from the reader. + * + * @param reader + * The reader. + * @return Returns the indirect reference of the Egiz Dictionary, or null, if + * none exists. + */ + public static PdfIndirectReference getEgizDictIndRefFromReader(PdfReader reader) + { + PdfDictionary catalog = reader.getCatalog(); + PdfIndirectReference dict_ir = (PdfIndirectReference) catalog.get(EGIZ_DICT_NAME); + return dict_ir; + } + + /** + * Retrieves the chain of Egiz Dictionaries from the reader. + * + *

+ * The first element in the List will be the top most (oldest) Egiz + * Dictionary. The last element in the List will be the bottom most (latest) + * Egiz Dictionary. If the list is empty, no dictionary could be found at all, + * which means that the document is not digitally signed. + *

+ * + * @param reader + * The reader. + * @return Returns the List of PdfDictionaries from the document. + */ + public static List getEgizDictChainFromReader(PdfReader reader) + { + List dicts = new ArrayList(); + + PdfDictionary current_dict = getEgizDictFromReader(reader); + if (current_dict != null) + { + dicts.add(0, current_dict); + + while ((current_dict = getPreviousFromEgizDict(current_dict)) != null) + { + dicts.add(0, current_dict); + } + } + + return dicts; + } + + /** + * Builds a digest of the given data. + * + * @param data + * The data to be digested. + * @param length + * The length of the data portion that should be used for digesting. + * This allows to build the digest only over parts of the data. + * @return Returns the created digest. + * @throws PDFDocumentException + * Forwarded exception. + */ + public static byte[] buildDigest(final byte[] data, final int length) throws PDFDocumentException + { + MessageDigest sha_512 = null; + try + { + sha_512 = MessageDigest.getInstance("SHA-512"); + } + catch (NoSuchAlgorithmException e) + { + e.printStackTrace(); + throw new PDFDocumentException(202, "Digest algorithm not supported - NoSuchAlgorithmException", e); + } + + sha_512.reset(); + sha_512.update(data, 0, length); + byte[] digest = sha_512.digest(); + + return digest; + } + + /** + * Retrieves the signable text from the given document. + * + * @param data + * The data. + * @param ods + * The original document size. + * @return Returns the signable text. + */ + public static String retrieveSignableTextFromData(final byte[] data, final int ods) + { + // byte[] digest = buildDigest(data, ods); + String raw_text = CodingHelper.encodeBase64(data);// digest); // data); + + return raw_text; + } + + /** + * Fills the holes in the byte ranges with the SIGN_PLACEHOLDER. + * + * @param data + * The given byte ranged data. + * @param byte_ranges + * The byte ranges. + * @return Returns the filled text. + */ + public static byte[] prepareDataToSign(final byte[] data, final List byte_ranges) + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Iterator it = byte_ranges.iterator(); + int last_end = 0; + while (it.hasNext()) + { + StringInfo si = (StringInfo) it.next(); + + for (int i = last_end; i < si.string_start; i++) + { + baos.write(SIGN_PLACEHOLDER); + } + + baos.write(data, si.string_start, si.string_length); + + last_end = si.string_start + si.string_length; + } + byte[] data_to_sign = baos.toByteArray(); + + return data_to_sign; + } + + // TODO old code - remove + // /** + // * Extracts the binary 'text' of a document. + // * + // *

+ // * If the document contains an Egiz Dictionary, which means that it is + // already + // * signed, the binary text is the Base64 coded string of the original + // document + // * followed by the Ascii representation of the signature block. + // *

+ // *

+ // * If the document does not contain an Egiz Dictionary, which means that it + // is + // * unsigned, only the binary Base64 coded original document is returned as + // * binary text. + // *

+ // *

+ // * This function is intented for being used instead of the "text extraction" + // * mechanism used in the plain text Egiz project. + // *

+ // * + // * @param doc + // * The file. + // * @return Returns the binary text of the document. + // * @throws PDFDocumentException + // * Forwarded exception. + // */ + // public static String extractTextBinary(File doc) throws + // PDFDocumentException + // { + // try + // { + // FileInputStream fis = new FileInputStream(doc); + // return extractTextBinary(fis); + // } + // catch (FileNotFoundException e) + // { + // throw new PDFDocumentException(202, e); + // } + // } + // + // /** + // * Extracts the text binary. + // * + // * @param is + // * @return Returns the binary text. + // * @throws PDFDocumentException + // */ + // public static String extractTextBinary(InputStream is) throws + // PDFDocumentException + // { + // try + // { + // // for some stupid reason this produces a read error if the is comes from + // // a + // // multipart servlet form..??? + // ByteArrayOutputStream baos = new ByteArrayOutputStream(); + // int i = -1; + // int acc = 0; + // byte[] b = new byte[1000]; + // while ((i = is.read(b)) > 0) + // { + // acc += i; + // System.out.print(" " + i); + // baos.write(b, 0, i); + // } + // System.out.println("acc = " + acc); + // byte[] pdf = baos.toByteArray(); + // + // return extractTextBinary(pdf); + // } + // catch (IOException e) + // { + // throw new PDFDocumentException(202, e); + // } + // } + // + // /** + // * Extracts the signable text from a binary pdf document. + // * + // *

+ // * The signable text is the text that will be signed or verified afterwards. + // *

+ // * + // * @param pdf + // * The pdf document. + // * @return Returns the extracted text String. + // * @throws PDFDocumentException + // * Forwarded exception. + // */ + // public static String extractTextBinary(final byte[] pdf) throws + // PDFDocumentException + // { + // try + // { + // PdfReader reader = new PdfReader(new ByteArrayInputStream(pdf)); + // PdfDictionary egiz_dict = getEgizDictFromReader(reader); + // if (egiz_dict == null) + // { + // System.out.println("NO Egiz Dict found - whole doc = original doc"); + // + // int ods = pdf.length; + // return retrieveSignableTextFromData(pdf, ods); + // } + // + // String sig_text = extractSignatureTextOnly(egiz_dict); + // + // int ods = getOriginalDocumentSizeFromEgizDict(egiz_dict); + // + // String raw_text = retrieveSignableTextFromData(pdf, ods); + // raw_text += "\n"; + // raw_text += sig_text; + // + // return raw_text; + // } + // catch (IOException e) + // { + // throw new PDFDocumentException(202, e); + // } + // } + + // TODO obsolete code - remove + // /** + // * Retrieves the List of SignatureHolders containing the information of all + // * digital signatures of the given document. + // * + // *

+ // * If the List of SignatureHolders is empty, the document is not signed + // * anyways. + // *

+ // * + // * @param pdf + // * The complete pdf document. + // * @return Returns the List of SignatureHolders. + // * @throws PDFDocumentException + // * @throws SignatureTypesException + // * @throws SignatureException + // */ + // public static List extractSignatureHoldersBinary(final byte[] pdf) throws + // PDFDocumentException, SignatureTypesException, SignatureException + // { + // try + // { + // PdfReader reader = new PdfReader(new ByteArrayInputStream(pdf)); + // List chain = getEgizDictChainFromReader(reader); + // + // List signatures = new ArrayList(); + // Iterator it = chain.iterator(); + // while (it.hasNext()) + // { + // PdfDictionary dict = (PdfDictionary) it.next(); + // + // int ods = getOriginalDocumentSizeFromEgizDict(dict); + // String signature_text = extractSignatureTextOnly(dict); + // + // SignatureTypes sig_types = SignatureTypes.getInstance(); + // List types = sig_types.getSignatureTypeDefinitions(); + // SignatureBlock sig_block = new SignatureBlock(types); + // boolean could_separate = sig_block.separateBlockFromRawText(signature_text, + // false); + // + // if (could_separate) + // { + // SignatureObject sig_object = sig_block.getSignatureObject(); + // + // SignatureHolder holder = new BinarySignatureHolder(pdf, ods, sig_object); + // signatures.add(holder); + // } + // } + // + // return signatures; + // } + // catch (IOException e) + // { + // throw new PDFDocumentException(201, e); + // } + // } + + // /** + // * Signs a document with the given signature table using the Incremental + // * Update method. + // * + // *

+ // * The table containing the signature text will be appended. As specified by + // * the parameters, the signature will be appended to the last page, or a + // plain + // * new page will be created for the signature to hold. + // *

+ // *

+ // * The table will be completely wrapped by an XObject, which will also be + // * indirectly referenced by the Egiz Dictionary. This will ease the + // * verification process. + // *

+ // *

+ // * An Egiz Dictionary will be added to the new document that contains + // * information about the signature. Basically the size of the original + // * document and the reference of the signature table. + // *

+ // * + // * @param original_document + // * The file name of the original document. + // * @param new_document + // * The file name of the new document to be created. + // * @param pdf_table + // * The PdfPTable that contains the signature block. + // * @param pos_x + // * The x position where the table should be inserted. + // * @param pos_y + // * The y position where the table should be inserted (on the last + // * page). If this is negative, a new page will be appended to the + // * document. Then the table will be inserted on that new page using + // * the absolute value of pos_y. Note that pos_y specifies the top + // * line of the table. + // * @throws PresentableException + // * Forwarded exception. + // * + // * @see #writeIncrementalUpdate(byte[], PdfPTable, float, float, boolean) + // */ + // public static void writeIncrementalUpdate(String original_document, + // String new_document, PdfPTable pdf_table, float pos_x, float pos_y, + // int egiz_dict_num_replaces) throws PresentableException + // { + // try + // { + // File original_document_file = new File(original_document); + // FileInputStream fis = new FileInputStream(original_document_file); + // byte[] pdf = new byte[(int) original_document_file.length()]; + // fis.read(pdf); + // fis.close(); + // + // byte[] signed_pdf = writeIncrementalUpdate(pdf, pdf_table, pos_x, pos_y, + // egiz_dict_num_replaces); + // + // File new_document_file = new File(new_document); + // FileOutputStream fos = new FileOutputStream(new_document_file); + // fos.write(signed_pdf); + // fos.close(); + // } + // catch (IOException e) + // { + // throw new PresentableException(e); + // } + // } + + protected static int getLineBreakTolerance(IncrementalUpdateInformation iui) throws PDFDocumentException + { + SettingsReader settings; + try + { + settings = SettingsReader.getInstance(); + } + catch (SettingsException e) + { + throw new PDFDocumentException(e.getErrorCode(), e); + } + String phLineBreakTolerance = SignatureTypeDefinition.readPhLenStringFromSettings(settings, iui.signProfile, "line_break_tolerance"); + int lineBreakTolerance = LINE_BREAK_TOLERANCE; + if (phLineBreakTolerance != null) + { + lineBreakTolerance = Integer.parseInt(phLineBreakTolerance); + } + return lineBreakTolerance; + } + + protected static int getCertificatePlaceholderLength(IncrementalUpdateInformation iui) throws SettingNotFoundException + { + SettingsReader settings; + try + { + settings = SettingsReader.getInstance(); + } + catch (SettingsException e) + { + throw new SettingNotFoundException(e); + } + String certPhLen = SignatureTypeDefinition.readPhLenStringFromSettings(settings, iui.signProfile, "certificate"); + int certLen = CERTIFICATE_PLACEHOLDER_LENGTH; + if (certPhLen != null) + { + certLen = Integer.parseInt(certPhLen); + } + return certLen; + } + + protected static int getTimestampPlaceholderLength(IncrementalUpdateInformation iui) throws SettingNotFoundException + { + SettingsReader settings; + try + { + settings = SettingsReader.getInstance(); + } + catch (SettingsException e) + { + throw new SettingNotFoundException(e); + } + String phLen = SignatureTypeDefinition.readPhLenStringFromSettings(settings, iui.signProfile, "timestamp"); + int tsLen = TIMESTAMP_PLACEHOLDER_LENGTH; + if (phLen != null) + { + tsLen = Integer.parseInt(phLen); + } + return tsLen; + } + + /** + * Signs a document with the given signature table using the Incremental + * Update method. + * + *

+ * The table containing the signature text will be appended. As specified by + * the parameters, the signature will be appended to the last page, or a plain + * new page will be created for the signature to hold. + *

+ *

+ * The table will be completely wrapped by an XObject, which will also be + * indirectly referenced by the Egiz Dictionary. This will ease the + * verification process. + *

+ *

+ * An Egiz Dictionary will be added to the new document that contains + * information about the signature. Basically the size of the original + * document and the reference of the signature table. + *

+ * + * @param original_document + * The original document. + * @param pdf_table + * The PdfPTable that contains the signature block. + * @param pi + * The PositioningInstruction telling the algorithm where to place + * the signature block. + * @param invisible_field_definitions + * List of invisible field definitions to be added to the egiz dict. + * May be null or empty, if there are no invisible fields. + * @param invisibleKZString + * If not null, thins String is the KZ String to be written into the + * /Data array. + * @return Returns the new document. + * @throws PresentableException + * Forwarded exception. + */ + public static IncrementalUpdateInformation writeIncrementalUpdate(PdfDataSource original_document, DataSink written_pdf, PdfPTable pdf_table, String profile, PositioningInstruction pi, + List variable_field_definitions, List all_field_definitions, List invisible_field_definitions, String invisibleKZString, TimeStamper timeStamper, SignatorInformation si, SignatureObject so) throws PresentableException + { + try + { + IncrementalUpdateInformation iui = new IncrementalUpdateInformation(); + iui.original_document = original_document; + iui.start_index = original_document.getLength(); + iui.signProfile = profile; + iui.timeStamper = timeStamper; + + Document.compress = true; // exthex : compress now, excluding the xobject later + + // System.out.println("wprinz: STAMPING PDF"); + + // InputStream is = original_document.createInputStream(); + byte[] pdf_data = original_document.getAsByteArray(); + PdfReader reader = new PdfReader(pdf_data); + PDFASUtils.checkReaderPermissions(reader); + // is.close(); + + OutputStream baos = written_pdf.createOutputStream("application/pdf"); + // ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + // IMPORTANT: append the new content to the original document using + // incremental updated + // The stamper allows this by setting append = true + boolean adobeSigField = AdobeSignatureHelper.isAdobeSignatureFieldEnabled(so.getSignatureTypeDefinition().getType()); + PdfStamper stamper = null; + if (adobeSigField) { + stamper = PdfStamper.createSignature(reader, baos, '\0', null, true); + } else { + stamper = new PdfStamper(reader, baos, '\0', true); + } + + if (pi.isMakeNewPage()) + { + int pdf_page_num = reader.getNumberOfPages(); + + Rectangle psize = reader.getPageSizeWithRotation(pdf_page_num); + Rectangle rect = new Rectangle(psize); + stamper.insertPage(pdf_page_num + 1, rect); + } + + if (pi.getPage() < 1 || pi.getPage() > stamper.getReader().getNumberOfPages()) + { + throw new PDFDocumentException(224, "The provided page (=" + pi.getPage() + ") is out of range."); + } + + if (SignaturePlaceholderContext.isSignaturePlaceholderDataSet() && + SignaturePlaceholderContext.getSignaturePlaceholderData().getPlaceholderName() != null) + { + replacePlaceholder(stamper, pi.getPage(), SignaturePlaceholderContext.getSignaturePlaceholderData().getPlaceholderName()); + } + + PdfContentByte content = stamper.getOverContent(pi.getPage()); + // content = StampContent einer PageStamp. + + // System.out.println("table_width = " + pdf_table.getTotalWidth() + ", + // table_height = " + pdf_table.getTotalHeight()); + + PdfTemplate table_template = content.createTemplate(pdf_table.getTotalWidth(), pdf_table.getTotalHeight()); + table_template.setCompress(Boolean.FALSE); // do not compress sigblock because we rewrite it afterwards for bin sig + + // exthex + StructContentHelper structHelper = new StructContentHelper(stamper, content, pi.getPage()); + structHelper.prepareStructData(table_template); + + pdf_table.writeSelectedRows(0, -1, 0, pdf_table.getTotalHeight(), table_template); + + // table_template.moveTo(0, 0); + // table_template.lineTo(pdf_table.getTotalWidth(), + // pdf_table.getTotalHeight()); + // table_template.stroke(); + // table_template.moveTo(0, 0); + // table_template.lineTo(100, 100); + // table_template.stroke(); + + // pdf_table.writeSelectedRows(0, -1, SIGNATURE_BORDER / 2, + // table_position, content); + + + structHelper.beginSigBlockContent(); + + content.addTemplate(table_template, pi.getX(), pi.getY() - pdf_table.getTotalHeight()); + + structHelper.endSigBlockContent(); + + + ActualTablePos atp = new ActualTablePos(); + atp.page = pi.getPage(); + atp.x = pi.getX(); + atp.y = pi.getY(); // TODO is this correct or flipped? + atp.width = pdf_table.getTotalWidth(); + atp.height = pdf_table.getTotalHeight(); + iui.actualTablePos = atp; + + structHelper.buildFigureStructData(so, table_template); + structHelper.buildSigBlockStructData(); + structHelper.finishMainStructData(); + + structHelper.buildVerifyLinkStructData(table_template, atp); + + // For debugging print a 100x100 grid + // { + // Rectangle psize = reader.getPageSizeWithRotation(pos.page); + // float page_width = psize.width(); + // float page_height = psize.height(); + // for (float x = 0; x < page_width; x += 100) + // { + // content.moveTo(x, 0); + // content.lineTo(x, page_height); + // content.stroke(); + // } + // for (float y = 0; y < page_height; y += 100) + // { + // content.moveTo(0, y); + // content.lineTo(page_width, y); + // content.stroke(); + // } + // } + + // content.setLineWidth(10.0f); + // content.moveTo(0, 0); + // content.lineTo(100, 100); + // content.stroke(); + + // PdfIndirectReference page_ref = + // stamper.getWriter().getPageReference(signature_page); + // System.out.println("page_ref = " + page_ref.toString()); + + // PdfObject page_obj = PdfReader.getPdfObject(page_ref); + // System.out.println("page_obj = " + page_obj); + + // PdfDictionary page_dict = (PdfDictionary) page_obj; + // PdfObject resources_obj = page_dict.get(PdfName.RESOURCES); + // System.out.println("resources_obj = " + resources_obj); + // PdfDictionary resources = (PdfDictionary) + // PdfReader.getPdfObject(resources_obj); + // for (Iterator it = resources.getKeys().iterator(); it.hasNext();) + // { + // PdfName key = (PdfName) it.next(); + // PdfObject value = resources.get(key); + // System.out.println(" " + key + " = " + value); + // } + + // add the EGIZ dict: + if (variable_field_definitions != null) + { + createEgizDict(stamper, table_template, iui, variable_field_definitions, all_field_definitions, invisible_field_definitions, invisibleKZString); + } + + if (adobeSigField) { + AdobeSignatureHelper.createAdobeSignatureField(stamper, si, so, atp, structHelper); // here..., stamper is closed + } else { + stamper.close(); + } + // System.out.println("wprinz: STAMPING FINISHED"); + + // just to make sure the stream is really closed + baos.close();// + //org.apache.commons.io.FileUtils.writeByteArrayToFile(new java.io.File("C:/out.pdf"), ((at.gv.egiz.pdfas.impl.output.ByteArrayDataSink) written_pdf).getByteArray()); + // iui.signed_pdf = baos.toByteArray(); + + structHelper.removeCurrent(); + return iui; + } + catch (IOException e) + { + e.printStackTrace(); + throw new PresentableException(ErrorCode.CANNOT_WRITE_PDF, e); + } + catch (DocumentException e) + { + e.printStackTrace(); + throw new PresentableException(ErrorCode.CANNOT_WRITE_PDF, e); + } + } + + private static void replacePlaceholder(PdfStamper stamper, int pageNr, String placeholderName) throws BadElementException, MalformedURLException, IOException, BadPdfFormatException, PresentableException { + Image img = Image.getInstance(SignaturePlaceholderData.class.getResource("empty.jpg")); + PdfImage pImg = new PdfImage(img, "Imwurscht", null); + PdfStamperImp stamperImp = (PdfStamperImp)stamper.getWriter(); + PdfIndirectObject ind = stamperImp.addToBody(pImg); + + PdfDictionary resources = stamper.getReader().getPageN(pageNr).getAsDict(PdfName.RESOURCES); + if (ind != null && resources != null) + { + PdfDictionary xobjDict = resources.getAsDict(PdfName.XOBJECT); + if (xobjDict != null) + { + xobjDict.put(new PdfName(placeholderName), ind.getIndirectReference()); + stamperImp.markUsed(resources); + } + else + { + throw new PresentableException(ErrorCode.CANNOT_WRITE_PDF, new NullPointerException("Image dictionary not found in document structure!")); + } + } + else + { + throw new PresentableException(ErrorCode.CANNOT_WRITE_PDF, new NullPointerException("Resource dictionary not found in document structure!")); + } + } + + + /** + * Creates the EGIZ Dictionary and adds it to the document. + * + * @param stamper + * The PdfStamper. + * @param table_template + * The Template of the Signature block. + * @param iui + * The IncrementalUpdateInformation. + * @param variable_field_definitions + * The field definitions. + * @throws IOException + * @throws SettingNotFoundException + * @throws CaptionNotFoundException + */ + protected static void createEgizDict(PdfStamper stamper, PdfTemplate table_template, IncrementalUpdateInformation iui, List variable_field_definitions, List all_field_definitions, + List invisible_field_definitions, String invisibleKZString) throws IOException, SettingNotFoundException, CaptionNotFoundException + { + // iui.temp_ir = table_template.getIndirectReference(); + iui.temp_ir_number = table_template.getIndirectReference().getNumber(); + iui.temp_ir_generation = table_template.getIndirectReference().getGeneration(); + + byte[] content_stream = table_template.toPdf(null); + iui.content_stream_length = content_stream.length; + + iui.replaces = determineReplacesInContentStream(content_stream, 0, content_stream.length, variable_field_definitions); + if (invisibleKZString == null) + { + iui.kz_list = determineKZ(content_stream, 0, content_stream.length, all_field_definitions); + } + else + { + StringInfo si = new StringInfo(); + si.string_start = -1; + si.string_length = invisibleKZString.length(); + + iui.kz_list = new ArrayList(); + iui.kz_list.add(si); + } + + // PdfIndirectReference previous_egiz_dict_ind_ref = + // getEgizDictIndRefFromReader(reader); + + PdfDictionary egiz_dict = new PdfDictionary(EGIZ_DICT_NAME); + egiz_dict.put(EGIZ_XOBJ_NAME, table_template.getIndirectReference()); + egiz_dict.put(EGIZ_ODS_NAME, NUMBER_PLACEHOLDER); + + // /ID + PdfArray kz_array = new PdfArray(); + for (int i = 0; i < iui.kz_list.size(); i++) + { + kz_array.add(NUMBER_PLACEHOLDER); // start + kz_array.add(NUMBER_PLACEHOLDER); // length + } + egiz_dict.put(EGIZ_KZ_NAME, kz_array); + + // ByteRanges + int num_replaces = calcNumReps(iui.replaces); + int num_holes = num_replaces + 1 + 1; + // +1 = the /encodings hole + // +1 = the /Cert + // +1 = the /Timestamp + if (iui.timeStamper != null) { + num_holes += 1; + } + boolean has_hidden_variable_fields = invisible_field_definitions != null && !invisible_field_definitions.isEmpty(); + if (has_hidden_variable_fields) + { + num_holes += invisible_field_definitions.size(); + } + int num_byte_ranges = num_holes + 1; + + PdfArray byte_ranges_array = new PdfArray(); + for (int i = 0; i < num_byte_ranges; i++) + { + byte_ranges_array.add(NUMBER_PLACEHOLDER); // start + byte_ranges_array.add(NUMBER_PLACEHOLDER); // length + } + egiz_dict.put(EGIZ_BYTERANGES_NAME, byte_ranges_array); + + PdfArray encodings_array = new PdfArray(); + encodings_array.add(new PdfName(new String(ENCODING_NIL))); // the + // /encodings + encodings_array.add(new PdfName(new String(ENCODING_NIL))); // the /Cert + + // array itself + PdfArray replaces_array = new PdfArray(); + replaces_array.add(new PdfName(new String(BREV_NIL, "US-ASCII"))); // the + // /encodings + replaces_array.add(new PdfName(new String(BREV_NIL, "US-ASCII"))); // the + // /Cert + + if (iui.timeStamper != null) { + encodings_array.add(new PdfName(new String(ENCODING_NIL))); // the /Timestamp + replaces_array.add(new PdfName(new String(BREV_NIL, "US-ASCII"))); // the /timestamp + + } + + // hidden replaces + List hidden_replaces = null; + if (has_hidden_variable_fields) + { + hidden_replaces = new ArrayList(); + + Iterator it = invisible_field_definitions.iterator(); + while (it.hasNext()) + { + SignatureFieldDefinition sfd = (SignatureFieldDefinition) it.next(); + byte[] brev = typeToBrev(sfd.field_name); + encodings_array.add(new PdfName(new String(ENCODING_WIN, "US-ASCII"))); + replaces_array.add(new PdfName(new String(brev, "US-ASCII"))); + + ReplaceInfo ri = new ReplaceInfo(); + ri.brev = brev; + ri.enc = ENCODING_WIN; + ri.sfd = sfd; + ri.replaces = new ArrayList(); + + StringInfo si = new StringInfo(); + si.string_start = -1; // to be determined later on + si.string_length = sfd.placeholder_length; + ri.replaces.add(si); + + hidden_replaces.add(ri); + } + } + + // content stream replaces + Iterator it = iui.replaces.iterator(); + while (it.hasNext()) + { + ReplaceInfo ri = (ReplaceInfo) it.next(); + for (int i = 0; i < ri.replaces.size(); i++) + { + byte[] brev = typeToBrev(ri.sfd.field_name); + encodings_array.add(new PdfName(new String(ENCODING_WIN, "US-ASCII"))); + replaces_array.add(new PdfName(new String(brev, "US-ASCII"))); + } + } + egiz_dict.put(EGIZ_REPLACES_NAME, replaces_array); + + egiz_dict.put(EGIZ_ENCODINGS_NAME, encodings_array); + + PdfArray cert_array = new PdfArray(); + iui.cert_length = getCertificatePlaceholderLength(iui); + byte[] cert_bytes = new byte[iui.cert_length]; + for (int i = 0; i < cert_bytes.length; i++) + { + cert_bytes[i] = 0; + } + PdfString cert_placeholder = new PdfString(cert_bytes); + cert_array.add(cert_placeholder); + egiz_dict.put(EGIZ_CERTIFICATE_NAME, cert_array); + + // Timestamp + if (iui.timeStamper != null) { + // only if handler is available + PdfArray timestamp_array = new PdfArray(); + iui.timestamp_length = getTimestampPlaceholderLength(iui); + byte[] timestamp_bytes = new byte[iui.timestamp_length]; + for (int i = 0; i < timestamp_bytes.length; i++) + { + timestamp_bytes[i] = 0; + } + PdfString timestamp_placeholder = new PdfString(timestamp_bytes); + timestamp_array.add(timestamp_placeholder); + egiz_dict.put(EGIZ_TIMESTAMP_NAME, timestamp_array); + } + + + // /Data array with hidden information + if (has_hidden_variable_fields || invisibleKZString != null) + { + PdfArray hidden_fields_array = new PdfArray(); + + if (invisibleKZString != null) + { + PdfString str = new PdfString(invisibleKZString); + hidden_fields_array.add(str); + } + + if (has_hidden_variable_fields) + { + iui.replaces.addAll(0, hidden_replaces); + + for (int i = 0; i < invisible_field_definitions.size(); i++) + { + SignatureFieldDefinition sfd = (SignatureFieldDefinition) invisible_field_definitions.get(i); + byte[] placeholder = new byte[sfd.placeholder_length]; + for (int phIdx = 0; phIdx < placeholder.length; phIdx++) + { + placeholder[phIdx] = SIGN_PLACEHOLDER; + } + PdfString str = new PdfString(placeholder); + hidden_fields_array.add(str); + } + } + + egiz_dict.put(EGIZ_DATA_NAME, hidden_fields_array); + } + + + PdfIndirectObject dict_ref = stamper.getWriter().addToBody(egiz_dict); + // iui.egiz_dict_ir = dict_ref.getIndirectReference(); + iui.egiz_dict_ir_number = dict_ref.getIndirectReference().getNumber(); + iui.egiz_dict_ir_generation = dict_ref.getIndirectReference().getGeneration(); + + PdfIndirectReference root_ref = (PdfIndirectReference) stamper.getReader().getTrailer().get(PdfName.ROOT); + PdfDictionary root = (PdfDictionary) PdfReader.getPdfObject(root_ref); + // root.put(EGIZ_DICT_NAME, dict_ref.getIndirectReference()); + ((PdfStamperImp) stamper.getWriter()).markUsed(root); + + // PdfDictionary extra_cata = stamper.getWriter().getExtraCatalog(); + // extra_cata.put(dict_type, dict_ref.getIndirectReference()); + + ((PdfStamperImp) stamper.getWriter()).setEgizDictTrailerInfo(EGIZ_DICT_NAME, dict_ref.getIndirectReference()); + } + + /** + * Converts a field name (type) to the corresponding BREV. + * + * @param type + * The field name (type). + * @return Returns the corresponding BREV, or BREV_NIL if the type is not + * recognized. + */ + protected static byte[] typeToBrev(String type) + { + if (type.equals(SignatureTypes.SIG_DATE)) + { + return BREV_DAT; + } + if (type.equals(SignatureTypes.SIG_ISSUER)) + { + return BREV_ISS; + } + if (type.equals(SignatureTypes.SIG_VALUE)) + { + return BREV_VAL; + } + if (type.equals(SignatureTypes.SIG_NUMBER)) + { + return BREV_SNR; + } + if (type.equals(SignatureTypes.SIG_ID)) + { + return BREV_SID; + } + if (type.equals(SignatureTypes.SIG_ALG)) + { + return BREV_ALG; + } + + + return BREV_NIL; + } + + /** + * Updates the information in the egiz dictionary to reflect the real offsets + * of the byte ranges. + * + *

+ * This replaces the "dummy numbers" in the egiz dictionary with the correct + * values. + *

+ * + * @param iui + * The IncrementalUpdateInformation. + * @throws PDFDocumentException + */ + public static void markByteRanges(IncrementalUpdateInformation iui) throws PDFDocumentException + { + try + { + iui.byte_ranges = new ArrayList(); + + int num_digits = Integer.toString(NUMBER_PLACEHOLDER.intValue()).length(); + byte[] signed_pdf = iui.signed_pdf; + + String str = iui.egiz_dict_ir_number + " " + iui.egiz_dict_ir_generation + " obj"; + byte[] obj_bytes = ArrayUtils.add(str.getBytes("US-ASCII"), 0, (byte) 0x0A); + int obj_index = ByteArrayUtils.lastIndexOf(signed_pdf, obj_bytes); + int obj_start = obj_index + obj_bytes.length; + + String ods_str = "/ODS "; + byte[] ods_bytes = ods_str.getBytes("US-ASCII"); + int ods_index = ByteArrayUtils.indexOf(signed_pdf, obj_start, ods_bytes); + int ods_start = ods_index + ods_bytes.length; + + String kz_str = "/ID["; + byte[] kz_bytes = kz_str.getBytes("US-ASCII"); + int kz_index = ByteArrayUtils.indexOf(signed_pdf, obj_start, kz_bytes); + int kz_start = kz_index + kz_bytes.length; + + String br_str = "/ByteRange["; + byte[] br_bytes = br_str.getBytes("US-ASCII"); + int br_index = ByteArrayUtils.indexOf(signed_pdf, obj_start, br_bytes); + int array_start = br_index + br_bytes.length; + + String enc_str = "/encodings["; + byte[] enc_bytes = enc_str.getBytes("US-ASCII"); + int enc_index = ByteArrayUtils.indexOf(signed_pdf, obj_start, enc_bytes); + int enc_start = enc_index + enc_bytes.length; + + String cert_str = "/Cert[("; + byte[] cert_bytes = cert_str.getBytes("US-ASCII"); + int cert_index = ByteArrayUtils.indexOf(signed_pdf, obj_start, cert_bytes); + int cert_start = cert_index + cert_bytes.length; + + //Timestamp + int timestamp_index = 0; + int timestamp_start = 0; + if (iui.timeStamper != null) { + String timestamp_str = "/TimeStamp[("; + byte[] timestamp_bytes = timestamp_str.getBytes("US-ASCII"); + timestamp_index = ByteArrayUtils.indexOf(signed_pdf, obj_start, timestamp_bytes); + timestamp_start = timestamp_index + timestamp_bytes.length; + } + + replaceNumber(signed_pdf, ods_start, signed_pdf.length, num_digits); + + + int cur_pos = array_start; + int cur_br_start = 0; + + + // write the /encodings byte range + { + int num_replaces = calcNumReps(iui.replaces); + int num_holes = num_replaces + 1 + 1; + // +1 = the /encodings hole + // +1 = the /Cert + // +1 = the /Timestamp + if (iui.timeStamper != null) { + num_holes += 1; + } + + int enc_length = (1 + 3) * num_holes; + + StringInfo byte_range = new StringInfo(); + byte_range.string_start = cur_br_start; + byte_range.string_length = enc_start; + iui.byte_ranges.add(byte_range); + + replaceNumber(signed_pdf, cur_pos, byte_range.string_start, num_digits); + cur_pos += num_digits; + cur_pos += 1; + replaceNumber(signed_pdf, cur_pos, byte_range.string_length, num_digits); + cur_pos += num_digits; + cur_pos += 1; + + cur_br_start = enc_start + enc_length; + + iui.enc_start = enc_start; + iui.enc_length = enc_length; + } + + // write the /Cert byte range + { + StringInfo byte_range = new StringInfo(); + byte_range.string_start = cur_br_start; + byte_range.string_length = cert_start - cur_br_start; + iui.byte_ranges.add(byte_range); + + replaceNumber(signed_pdf, cur_pos, byte_range.string_start, num_digits); + cur_pos += num_digits; + cur_pos += 1; + replaceNumber(signed_pdf, cur_pos, byte_range.string_length, num_digits); + cur_pos += num_digits; + cur_pos += 1; + + cur_br_start = cert_start + iui.cert_length; + + iui.cert_start = cert_start; + } + + // write the /Timestamp byte range + if (iui.timeStamper != null) { + StringInfo byte_range = new StringInfo(); + byte_range.string_start = cur_br_start; + byte_range.string_length = timestamp_start - cur_br_start; + iui.byte_ranges.add(byte_range); + + replaceNumber(signed_pdf, cur_pos, byte_range.string_start, num_digits); + cur_pos += num_digits; + cur_pos += 1; + replaceNumber(signed_pdf, cur_pos, byte_range.string_length, num_digits); + cur_pos += num_digits; + cur_pos += 1; + + cur_br_start = timestamp_start + iui.timestamp_length; + + iui.timestamp_start = timestamp_start; + } + + // determine the /Data byte ranges if any + List ifd = iui.invisible_field_definitions; + if (!ifd.isEmpty() || iui.invisibleKZString != null) + { + String data_str = "/Data[("; + byte[] data_bytes = data_str.getBytes("US-ASCII"); + int data_index = ByteArrayUtils.indexOf(signed_pdf, obj_start, data_bytes); + int data_start = data_index + data_bytes.length; + + int hole_start = data_start; + + if (iui.invisibleKZString != null) + { + StringInfo si = (StringInfo) iui.kz_list.get(0); + si.string_start = hole_start; + + hole_start += si.string_length + 2; + } + + for (int i = 0; i < ifd.size(); i++) + { + ReplaceInfo ri = (ReplaceInfo) iui.replaces.get(i); + StringInfo si = (StringInfo) ri.replaces.get(0); + si.string_start = hole_start; + + hole_start += si.string_length + 2; + } + + } + + Iterator rit = iui.replaces.iterator(); + while (rit.hasNext()) + { + ReplaceInfo ri = (ReplaceInfo) rit.next(); + + // byte [] value_bytes = ri.value.getBytes("ISO-8859-1"); + // int write_pos = 0; + + Iterator sit = ri.replaces.iterator(); + while (sit.hasNext()) + { + StringInfo si = (StringInfo) sit.next(); + + StringInfo byte_range = new StringInfo(); + byte_range.string_start = cur_br_start; + byte_range.string_length = si.string_start - cur_br_start; + iui.byte_ranges.add(byte_range); + + replaceNumber(signed_pdf, cur_pos, byte_range.string_start, num_digits); + cur_pos += num_digits; + cur_pos += 1; + replaceNumber(signed_pdf, cur_pos, byte_range.string_length, num_digits); + cur_pos += num_digits; + cur_pos += 1; + + cur_br_start = si.string_start + si.string_length; + } + + } + + StringInfo byte_range = new StringInfo(); + byte_range.string_start = cur_br_start; + byte_range.string_length = signed_pdf.length - cur_br_start; + iui.byte_ranges.add(byte_range); + + replaceNumber(signed_pdf, cur_pos, byte_range.string_start, num_digits); + cur_pos += num_digits; + cur_pos += 1; + replaceNumber(signed_pdf, cur_pos, byte_range.string_length, num_digits); + cur_pos += num_digits; + cur_pos += 1; + + + // update the Kennzeichnung byte ranges + cur_pos = kz_start; + for (int i = 0; i < iui.kz_list.size(); i++) + { + StringInfo si = (StringInfo) iui.kz_list.get(i); + + replaceNumber(signed_pdf, cur_pos, si.string_start, num_digits); + cur_pos += num_digits; + cur_pos += 1; + replaceNumber(signed_pdf, cur_pos, si.string_length, num_digits); + cur_pos += num_digits; + cur_pos += 1; + } + + } + catch (IOException e) + { + throw new PDFDocumentException(201, e); + } + + } + + /** + * Replaces the certificate placeholder with the certificate from the signed + * Signature Object. + * + * @param iui + * The IncrementalUpdateInformation. + * @throws PDFDocumentException + */ + public static void replaceCertificate(IncrementalUpdateInformation iui) throws PDFDocumentException + { + X509Certificate certificate = iui.signed_signature_object.getX509Certificate(); + try + { + byte[] der = certificate.getEncoded(); + byte[] encoded = CodingHelper.encodeBase64(der).getBytes("US-ASCII"); + byte[] escaped = Placeholder.escapePDFString(encoded); + if (escaped.length > iui.cert_length) + { + throw new PlaceholderException("certificate", escaped.length - iui.cert_length); + } + System.arraycopy(escaped, 0, iui.signed_pdf, iui.cert_start, escaped.length); + } + catch (CertificateEncodingException e) + { + throw new PDFDocumentException(300, e); + } + catch (UnsupportedEncodingException e) + { + throw new PDFDocumentException(300, e); + } + + // X509Cert cert = iui.signed_signature_object.getX509Cert(); + // // X509Certificate certificate = cert.getX509Certificate(); + // try + // { + // byte[] encoded = cert.getCertString().getBytes("US-ASCII"); // + // certificate.getEncoded(); + // byte[] escaped = Placeholder.escapePDFString(encoded); + // if (escaped.length > iui.cert_length) + // { + // throw new PlaceholderException("certificate", escaped.length - + // iui.cert_length); + // } + // System.arraycopy(escaped, 0, iui.signed_pdf, iui.cert_start, + // escaped.length); + // } + // // catch (CertificateEncodingException e) + // // { + // // throw new PDFDocumentException(300, e); + // // } + // catch (UnsupportedEncodingException e) + // { + // throw new PDFDocumentException(300, e); + // } + } + + /** + * Replaces the timestam placeholder with the timestamp from the signed + * Signature Object. + * + * @param iui + * The IncrementalUpdateInformation. + * @throws PDFDocumentException + */ + public static void replaceTimestamp(IncrementalUpdateInformation iui) throws PDFDocumentException + { + String timestamp = iui.signed_signature_object.getSigTimeStamp(); + if (timestamp != null) { + byte[] escaped = Placeholder.escapePDFString(timestamp.getBytes()); + if (escaped.length > iui.timestamp_length) + { + throw new PlaceholderException("timestamp", escaped.length - iui.timestamp_length); + } + System.arraycopy(escaped, 0, iui.signed_pdf, iui.timestamp_start, escaped.length); + } + } + + /** + * Replaces the placeholders with values from the signed SignatureObject. + * + * @param iui + * The IncrementalUpdateInformation. + * @throws PDFDocumentException + */ + public static void replacePlaceholders(IncrementalUpdateInformation iui) throws PDFDocumentException + { + final int lineBreakTolerance = getLineBreakTolerance(iui); + + final byte[] signed_pdf = iui.signed_pdf; + + // int num_replaces = calcNumReps(iui.replaces); + // int num_holes = num_replaces + 1 + 1; // +1 = the /encodings hole; +1 = + // the + // /Cert + + int encoding_entry_index = 0; + {// /encodings itself + int enc_offset = iui.enc_start + encoding_entry_index * 4; + signed_pdf[enc_offset] = '/'; + System.arraycopy(ENCODING_NIL, 0, signed_pdf, enc_offset + 1, ENCODING_NIL.length); + encoding_entry_index++; + } + {// /Cert itself + int enc_offset = iui.enc_start + encoding_entry_index * 4; + signed_pdf[enc_offset] = '/'; + System.arraycopy(ENCODING_NIL, 0, signed_pdf, enc_offset + 1, ENCODING_NIL.length); + encoding_entry_index++; + } + if (iui.signed_signature_object.getSigTimeStamp() != null) + {// /Timestamp itself + int enc_offset = iui.enc_start + encoding_entry_index * 4; + signed_pdf[enc_offset] = '/'; + System.arraycopy(ENCODING_NIL, 0, signed_pdf, enc_offset + 1, ENCODING_NIL.length); + encoding_entry_index++; + } + + for (int i = 0; i < iui.replaces.size(); i++) + { + ReplaceInfo ri = (ReplaceInfo) iui.replaces.get(i); + + try + { + String value = ri.value; + + if (value == null) + { + value = ""; + } + + byte[] encoding = ENCODING_WIN; + byte[] replace_bytes = Placeholder.applyWinAnsiEncoding(value); + + String restored_value = Placeholder.unapplyWinAnsiEncoding(replace_bytes); + if (!value.equals(restored_value)) + { + // debug: + logger.warn("WinAnsiEncoding doesn't fit - using URL instead!"); + // /debug! + + replace_bytes = Placeholder.applyURLEncoding(value); + + encoding = ENCODING_URL; + } + + for (int string_index = 0; string_index < ri.replaces.size(); string_index++) + { + int enc_offset = iui.enc_start + encoding_entry_index * 4; + signed_pdf[enc_offset] = '/'; + System.arraycopy(encoding, 0, signed_pdf, enc_offset + 1, encoding.length); + encoding_entry_index++; + } + + Placeholder.replacePlaceholderWithTolerance(signed_pdf, ri.replaces, replace_bytes, lineBreakTolerance); + } + catch (PlaceholderException e) + { + throw new PlaceholderException(ri.sfd.field_name, e.getMissing()); + } + + } + } + + /** + * Calculates the number of actual String replaces from a given ReplaceInfo + * list. + *

+ * This is used to determine the number of actual replaces that has to be + * carried out. Accordingly to this number, entries in the dictionary are + * created. + *

+ * + * @param replaces + * The ReplaceInfo list. + * @return Returns the number of string replaces. + */ + protected static int calcNumReps(List replaces) + { + int number = 0; + Iterator it = replaces.iterator(); + while (it.hasNext()) + { + ReplaceInfo ri = (ReplaceInfo) it.next(); + number += ri.replaces.size(); + } + return number; + } + + /** + * Determines the List of ReplaceInfo objects of replaces in the content + * stream regarding the given field definitions. + * + *

+ * This method collects all variable String fields in a content stream and + * orders them according to their start offset. + *

+ * + * @param pdf + * The PDF. + * @param begin + * The start of the content stream. + * @param end + * The end of the content stream. + * @param field_definitions + * The field definitions that are counceled to find out which and + * where varaible strings are. + * @return Returns the list of ReplaceInfo objects specifying the variable + * areas. + * @throws CaptionNotFoundException + */ + protected static List determineReplacesInContentStream(final byte[] pdf, int begin, int end, List field_definitions) throws CaptionNotFoundException + { + List replaces = new ArrayList(); + try + { + + List strings = Placeholder.parseStrings(pdf, begin, end); + + for (int index = 0; index < field_definitions.size(); index++) + { + SignatureFieldDefinition sfd = (SignatureFieldDefinition) field_definitions.get(index); + + if (sfd.placeholder_length > 0) + { + ReplaceInfo ri = new ReplaceInfo(); + ri.sfd = sfd; + ri.replaces = new ArrayList(); + + byte[] caption = sfd.caption.getBytes("ISO-8859-1"); + + int caption_index = findIndex(strings, caption); + if (caption_index < 0) + { + throw new CaptionNotFoundException(sfd.caption); + } + int start_index = skipStrings(strings, caption_index, caption); + int next_index = findFirstNotPlaceholder(strings, start_index); + + for (int i = start_index; i < next_index; i++) + { + StringInfo si = (StringInfo) strings.get(i); + ri.replaces.add(si); + } + + replaces.add(ri); + } + } + } + catch (UnsupportedEncodingException e) + { + logger.error(e.getMessage(), e); + } + + // sort replaces + Collections.sort(replaces, new Comparator() { + public int compare(Object arg0, Object arg1) + { + ReplaceInfo ri0 = (ReplaceInfo) arg0; + ReplaceInfo ri1 = (ReplaceInfo) arg1; + int start0 = ((StringInfo) ri0.replaces.get(0)).string_start; + int start1 = ((StringInfo) ri1.replaces.get(0)).string_start; + return start0 - start1; + } + }); + + return replaces; + } + + /** + * Determines the Kennzeichnug in the content stream. + * + * @param pdf + * The PDF. + * @param begin + * The start of the content stream. + * @param end + * The end of the content stream. + * @param field_definitions + * The field definitions. + * @return Returns the List of StringInfo objects representing the KZ field. + * @throws SettingNotFoundException + * F.e. + */ + protected static List determineKZ(final byte[] pdf, int begin, int end, List field_definitions) throws SettingNotFoundException + { + try + { + List strings = Placeholder.parseStrings(pdf, begin, end); + + for (int index = 0; index < field_definitions.size(); index++) + { + SignatureFieldDefinition sfd = (SignatureFieldDefinition) field_definitions.get(index); + + if (sfd.field_name.equals(SignatureTypes.SIG_KZ)) + { + List kz_list = new ArrayList(); + + byte[] caption = sfd.caption.getBytes("ISO-8859-1"); + + int caption_index = findIndex(strings, caption); + int start_index = skipStrings(strings, caption_index, caption); + + int end_index = -1; + for (end_index = start_index; end_index < strings.size(); end_index++) + { + StringInfo si = (StringInfo) strings.get(end_index); + + if (startsWithCaption(si, field_definitions)) + { + break; + } + + kz_list.add(si); + } + + return kz_list; + } + } + } + catch (UnsupportedEncodingException e) + { + logger.error(e.getMessage(), e); + } + throw new KZSettingNotFoundException("Field " + SignatureTypes.SIG_KZ + " not found."); + } + + /** + * Finds the index of the StringInfo within the StringInfo list that has the + * given content (caption). + * + * @param strings + * The list of StringInfos. + * @param caption + * The text to be matched to the strings. + * @return Returns the index of the found string, or -1 if no string matched. + */ + protected static int findIndex(List strings, byte[] caption) + { + for (int i = 0; i < strings.size(); i++) + { + if (isCaption(strings, i, caption)) + { + return i; + } + } + return -1; + } + + protected static boolean isCaption(List strings, int index, byte[] caption) + { + try + { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (int i = index; i < strings.size(); i++) + { + StringInfo si = (StringInfo) strings.get(i); + baos.write(si.copyStringBytes()); + } + byte[] str_data = baos.toByteArray(); + byte[] unescaped = Placeholder.unescapePDFString(str_data); + if (ByteArrayUtils.compareByteArrays(unescaped, 0, caption)) + { + return true; + } + else + { + return false; + } + } + catch (IOException e) + { + logger.error(e.getMessage(), e); + return false; + } + + } + + protected static int skipStrings(List strings, int index, byte[] caption) + { + int length = 0; + for (int i = index; i < strings.size(); i++) + { + StringInfo si = (StringInfo) strings.get(i); + length += si.string_length; + + if (length >= caption.length) + { + return i + 1; + } + } + return -1; + } + + /** + * Tells, if the given StringInfo contains only placeholder characters. + * + * @param si + * The StringInfo. + * @param placeholder + * The placeholder character. + * @return Returns true, if the string contains only the given placeholder + * characters, false otherwise. + */ + protected static boolean isPlaceholder(StringInfo si, byte placeholder) + { + byte[] string_bytes = si.copyStringBytes(); + for (int i = 0; i < string_bytes.length; i++) + { + if (string_bytes[i] != placeholder) + { + return false; + } + } + return true; + } + + protected static boolean startsWithCaption(StringInfo si, List field_definitions) + { + try + { + for (int i = 0; i < field_definitions.size(); i++) + { + SignatureFieldDefinition sfd = (SignatureFieldDefinition) field_definitions.get(i); + + String caption = sfd.caption; + String str = si.getString("ISO-8859-1"); + + if (caption.startsWith(str)) + { + return true; + } + } + return false; + } + catch (UnsupportedEncodingException e) + { + logger.error(e.getMessage(), e); + return false; + } + } + + /** + * Finds the first string after and at the given index not being a placeholder + * string. + * + * @param strings + * The list of StringInfos. + * @param start + * The index where to start the search. + * @return Returns the index of the first not placeholder string, or + * strings.size() if no more non placeholder strings could be found. + */ + protected static int findFirstNotPlaceholder(List strings, int start) + { + for (int i = start; i < strings.size(); i++) + { + StringInfo si = (StringInfo) strings.get(i); + if (!isPlaceholder(si, LAYOUT_PLACEHOLDER)) + { + return i; + } + } + return strings.size(); + } + + /** + * Restores the given String to its placeholder. + * + * @param pdf + * The PDF. + * @param si + * The string. + * @param placeholder + * The placeholder the string should be filled with. + */ + public static void restorePlaceholder(final byte[] pdf, StringInfo si, final byte placeholder) + { + byte[] ph = new byte[si.string_length]; + for (int i = 0; i < ph.length; i++) + { + ph[i] = placeholder; + } + System.arraycopy(ph, 0, pdf, si.string_start, ph.length); + } + + /** + * Reconstructs the replaces from the PDF and forms suitable value strings. + * + * @param pdf + * The PDF. + * @param brevs + * The brevs. + * @param sis + * The StringInfo objects of the strings. + * @return Returns the List of ReplaceInfo objects containing the restored + * values. + * @throws PDFDocumentException + */ + public static List reconstructReplaces(final byte[] pdf, byte[][] brevs, StringInfo[] sis, byte[][] encodings) throws PDFDocumentException + { + try + { + List replaces = new ArrayList(); + + ReplaceInfo cur_ri = null; + + for (int cur = 0; cur < brevs.length; cur++) + { + if (ByteArrayUtils.compareByteArrays(brevs[cur], 0, BREV_NIL)) + { + continue; + } + + if (cur_ri == null || !ByteArrayUtils.compareByteArrays(cur_ri.brev, 0, brevs[cur])) + { + if (cur >= encodings.length) { + throw new PDFDocumentException(ErrorCode.INVALID_SIGNATURE_DICTIONARY, "Invalid EGIZ signature dictionary."); + } + cur_ri = new ReplaceInfo(); + + cur_ri.replaces = new ArrayList(); + + cur_ri.brev = brevs[cur]; + cur_ri.enc = encodings[cur]; + + replaces.add(cur_ri); + } + + cur_ri.replaces.add(sis[cur]); + } + + // restore value Strings + Iterator rit = replaces.iterator(); + while (rit.hasNext()) + { + ReplaceInfo ri = (ReplaceInfo) rit.next(); + ri.value = Placeholder.reconstructStringFromPartition(pdf, ri.replaces, ri.enc); + + // System.out.println(new String(ri.brev, "US-ASCII") + ": " + + // ri.value); + } + + return replaces; + } + catch (IOException e) + { + throw new PDFDocumentException(310, e); + } + + } + + /** + * Reads an unsigned integer number. + * + * @param pdf + * The PDF. + * @param start_index + * The start index of the number. + * @param num_digits + * The number of digits. + * @return Returns the read number. + */ + public static int readNumber(final byte[] pdf, final int start_index, final int num_digits) + { + try + { + byte[] n_bytes = new byte[num_digits]; + System.arraycopy(pdf, start_index, n_bytes, 0, num_digits); + String n_string = new String(n_bytes, "US-ASCII"); + + int n = Integer.parseInt(n_string); + return n; + } + catch (UnsupportedEncodingException e) + { + logger.error(e.getMessage(), e); + return -1; + } + } + + /** + * Replaces a number by the new value. + * + * @param pdf + * The PDF. + * @param start_index + * The start index of the number. + * @param number + * The new number. + * @param num_digits + * The number of digits. + */ + public static void replaceNumber(final byte[] pdf, final int start_index, final int number, final int num_digits) + { + try + { + if (number < 0) + { + throw new IllegalArgumentException("The given number " + number + " must not be negative."); + } + String number_string = Integer.toString(number); + if (number_string.length() > num_digits) + { + throw new IllegalArgumentException("The given number " + number + " has more than " + num_digits + " digits."); + } + + int leading_zeros = num_digits - number_string.length(); + String zeros_string = ""; + for (int i = 0; i < leading_zeros; i++) + { + zeros_string += "0"; + } + + String total_string = zeros_string + number_string; + byte[] total_bytes = total_string.getBytes("US-ASCII"); + System.arraycopy(total_bytes, 0, pdf, start_index, num_digits); + } + catch (UnsupportedEncodingException e) + { + logger.error(e.getMessage(), e); + } + } + + + // TODO old code - remove + // /** + // * For debugging purposes. + // * + // * @param args + // * @throws IOException + // */ + // public static void main(String[] args) throws IOException + // { + // File signed_doc = new File("C:/wprinz/temp.pdf"); + // + // PdfReader reader = new PdfReader(new FileInputStream(signed_doc)); + // PdfDictionary egiz_dict = getEgizDictFromReader(reader); + // if (egiz_dict == null) + // { + // System.out.println("NO Egiz Dict"); + // return; + // } + // + // String sig_text = extractSignatureTextOnly(egiz_dict); + // System.out.println("Sig Text:"); + // System.out.println(sig_text); + // + // int ods = getOriginalDocumentSizeFromEgizDict(egiz_dict); + // System.out.println("Original Document Size = " + ods); + // } + +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignatureHolder.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignatureHolder.java new file mode 100644 index 0000000..7dc49ef --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignatureHolder.java @@ -0,0 +1,185 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: BinarySignatureHolder.java,v 1.1 2006/10/11 07:58:28 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.Serializable; + +import at.gv.egiz.pdfas.impl.input.ByteArrayPdfDataSourceImpl; +import at.gv.egiz.pdfas.framework.input.DataSource; +import at.gv.egiz.pdfas.framework.input.PdfDataSource; + +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; + +/** + * Data structure that holds the information of one binary signature block, + * which is the signed/signable pdf and the corresponding SignatureObject. + * + *

+ * The actual signed text is computed by Base64 encoding the binary data when + * first requested. + *

+ *

+ * The corresponding getters can be used to retrieve the signed document (e.g. + * for displaying a preview). + *

+ * + * @author wprinz + */ +public class BinarySignatureHolder implements Serializable, SignatureHolder +{ + + /** + * SVUID. + */ + private static final long serialVersionUID = -7208103904479272760L; + +// /** +// * The whole pdf this holder was extracted from. +// */ +// private byte[] signed_pdf = null; + + private PdfDataSource pdfDataSource = null; + +// /** +// * The number of bytes that give the signed document. +// */ +// private int signed_pdf_length = -1; + +// /** +// * The signed text of this object. +// * +// *

+// * This is the value that will be signed by the Connector. +// *

+// */ +// private String signed_text = null; + + /** + * The signature object. + */ + private SignatureObject signature_object = null; + +// /** +// * Constructor that takes the pdf and the SignatureObject as parameters. +// * +// * @param pdf +// * The pdf data. +// * @param length +// * The length (number of bytes) of the pdf data to be used for being +// * converted into "signed text". +// * @param so +// * The signed signature object. +// */ +// public BinarySignatureHolder(final byte[] pdf, final int length, SignatureObject so) +// { +//// this.signed_pdf = pdf; +// this.pdfDataSource = new ByteArrayPdfDataSourceImpl(pdf); +// // streaming byte array +// this.signed_pdf_length = length; +// this.signature_object = so; +// +//// this.signed_text = null; +// } + + public BinarySignatureHolder(PdfDataSource pdf, SignatureObject so) + { + this.pdfDataSource = pdf; + this.signature_object = so; + } + + /** + * @see at.knowcenter.wag.egov.egiz.pdf.SignatureHolder#getSignedText() + */ + public String getSignedText() + { + throw new RuntimeException("BinarySignatureHolder must not return text."); +// if (this.signed_text == null) +// { +// computeSignedText(); +// } +// return this.signed_text; + } + + /** + * @see at.knowcenter.wag.egov.egiz.pdf.SignatureHolder#getSignatureObject() + */ + public SignatureObject getSignatureObject() + { + return this.signature_object; + } + + /** + * @see at.knowcenter.wag.egov.egiz.pdf.SignatureHolder#getDataSource() + */ + public DataSource getDataSource() + { + return getSignedPdf(); + } + + + + // TODO obsolete funtction - remove; +// /** +// * Computes or recomputes the signed text from the underlying binary data. +// * +// *

+// * This usually encodes the binary data of given length in Base64. +// *

+// * +// *

+// * This is usually called automatically when the signed text is first +// * requested. +// *

+// */ +// protected void computeSignedText() +// { +// this.signed_text = BinarySignature.retrieveSignableTextFromData(this.signed_pdf, this.signed_pdf_length); +// } + +// /** +// * Returns the signed_pdf. +// * @return Returns the signed_pdf. +// */ +// public byte[] getSignedPdf() +// { +// return this.signed_pdf; +// } + + public PdfDataSource getSignedPdf () + { + return this.pdfDataSource; + } + +// /** +// * Returns the signed_pdf_length. +// * @return Returns the signed_pdf_length. +// */ +// public int getSignedPdfLength() +// { +// return this.signed_pdf_length; +// } + +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/EGIZDate.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/EGIZDate.java new file mode 100644 index 0000000..d2b29b6 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/EGIZDate.java @@ -0,0 +1,284 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: EGIZDate.java,v 1.1 2006/10/31 08:08:33 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.text.ParseException; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +import org.apache.commons.lang.time.DateUtils; +import org.apache.log4j.Logger; + +/** + * Represents a signature date and the signing time as can be found in the + * SIG_DATE field. + * + *

+ * This is used to compare date values of signatures. + *

+ * + * @author wprinz + */ +public class EGIZDate { + + private static final Logger LOG = Logger.getLogger(EGIZDate.class); + + protected Date date = null; + + // TODO remove deprecated old code +// /** +// * The year. +// */ +// protected int year; +// +// /** +// * The month. +// */ +// protected int month; +// +// /** +// * The day. +// */ +// protected int day; +// +// /** +// * The hour. +// */ +// protected int hour; +// +// /** +// * The minute. +// */ +// protected int minute; +// +// /** +// * The second. +// */ +// protected int second; +// +// /** +// * Constructor that fills the date with values. +// * +// * @param year +// * The year. +// * @param month +// * The month. +// * @param day +// * The day. +// * @param hour +// * The hour. +// * @param minute +// * The minute. +// * @param second +// * The second. +// */ +// public EGIZDate(int year, int month, int day, int hour, int minute, +// int second) { +// this.year = year; +// this.month = month; +// this.day = day; +// this.hour = hour; +// this.minute = minute; +// this.second = second; +// } + + + /** + * @param date + */ + protected EGIZDate(Date date) + { + this.date = date; + } + + /** + * Parses the date information from a given date value. + * + *

+ * Usually the date value is one extracted from the value of the SIG_DATE + * field. + *

+ * + * @param date_value + * The date value String. + * @return Returns the parsed EGIZDate. An IllegalArgumentException is + * thrown if the date String has an illegal format. + */ + public static EGIZDate parseFromString(String date_value) { + Date date = parseDateFromString(date_value); +// Calendar calendar = new GregorianCalendar(); +// calendar.setTime(date); + + LOG.debug("Parsing date string \"" + date_value + "\" returns: " + date); + +// return new EGIZDate(calendar.get(Calendar.YEAR), calendar +// .get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH), +// calendar.get(Calendar.HOUR), calendar.get(Calendar.MINUTE), +// calendar.get(Calendar.SECOND)); + return new EGIZDate(date); + + } + + public static Date parseDateFromString (String date_value) + { + // find the according RFC standard and cite it + + /* + * Pattern date_pattern = + * Pattern.compile("^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d(Z|((\\+|\\-)\\d\\d:\\d\\d))?$"); + * Matcher date_matcher = date_pattern.matcher(date_value); if + * (!date_matcher.matches()) { throw new IllegalArgumentException("The + * date_value (" + date_value + ") has an illegal format."); } // for + * some strange reasons capture groups don't work + * + * int year = Integer.parseInt(date_value.substring(0, 4)); int month = + * Integer.parseInt(date_value.substring(5, 7)); int day = + * Integer.parseInt(date_value.substring(8, 10)); int hour = + * Integer.parseInt(date_value.substring(11, 13)); int minute = + * Integer.parseInt(date_value.substring(14, 16)); int second = + * Integer.parseInt(date_value.substring(17, 19)); return new + * EGIZDate(year, month, day, hour, minute, second); + */ + + String[] parsePatterns = { "yyyy-MM-dd'T'HH:mm:ss", + "yyyy-MM-dd'T'HH:mm:ssZ", "yyyy-MM-dd'T'HH:mm:ssZZ" }; + if (date_value.length() > 19) { + int li = date_value.lastIndexOf(":"); + if (li >= 19) { + date_value = new StringBuffer(date_value).deleteCharAt(li).toString(); + } + // FIXME: @iaik: wenn man bei UTC+"Z" die letzten -2 Zeichen durch "UTC" ersetzt verliert die Zeit ihre Sekunden-Einerstelle!!!!!!! + // alter code: +// if (date_value.endsWith("Z")) { +// date_value = date_value.substring(0, date_value.length()-2) + "UTC"; +// } + // neuer code: + if (date_value.endsWith("ZZ")) { + date_value = date_value.substring(0, date_value.length()-2) + "UTC"; + } + if (date_value.endsWith("Z")) { + date_value = date_value.substring(0, date_value.length()-1) + "UTC"; + } + + } else { + date_value += "UTC"; + } + + Date date; + try { + date = DateUtils.parseDate(date_value, parsePatterns); + } catch (ParseException e) { + throw new IllegalArgumentException("The date_value (" + date_value + + ") has an illegal format."); + } + + +// return new EGIZDate(calendar.get(Calendar.YEAR), calendar +// .get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH), +// calendar.get(Calendar.HOUR), calendar.get(Calendar.MINUTE), +// calendar.get(Calendar.SECOND)); + + // fixed by tknall: really bad bug: Calendar.HOUR means hour in 12-hour-mode instead f 24-hour. !!!! + // this leads to false comparison of two dates where one date is prior noon and one after noon. +// return new EGIZDate(calendar.get(Calendar.YEAR), calendar +// .get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH), +// calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), +// calendar.get(Calendar.SECOND)); + return date; + + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (!(obj instanceof EGIZDate)) { + return false; + } + + EGIZDate other = (EGIZDate)obj; + return this.date.equals(other.date); + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return this.date.hashCode(); + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + return this.date.toString(); + } + + +// /** +// * Converts the date to a long integer. +// * +// *

+// * An earlier date is lower than a later date. +// *

+// *

+// * E.g. a date in 1999 will get a smaller number than a date in 2006. +// *

+// * +// * @return Returns the compareable long. +// */ +// protected long toCompareableLong() { +// return +this.year * 12 * 31 * 24 * 60 * 60 + this.month * 31 * 24 * 60 +// * 60 + this.day * 24 * 60 * 60 + this.hour * 60 * 60 +// + this.minute * 60 + this.second; +// } + + /** + * Compares this EGIZDate to another EXIZDate. + * + * @param other + * The other EGIZDate. + * @return Returns negative if this date is earlier (lower) than the other + * date. Returns 0 if both dates are equal. Returns positive if this + * date is later (higher) than the other date. + */ + public int compareTo(EGIZDate other) { + return this.date.compareTo(other.date); +// long diff = toCompareableLong() - other.toCompareableLong(); +// return (int) diff; + } + + /** + * @return the date + */ + public Date getDate() + { + return this.date; + } + +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/IncrementalUpdateInformation.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/IncrementalUpdateInformation.java new file mode 100644 index 0000000..3714684 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/IncrementalUpdateInformation.java @@ -0,0 +1,252 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: IncrementalUpdateInformation.java,v 1.2 2006/10/31 08:09:33 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.IOException; +import java.io.Serializable; +import java.util.List; + +import org.apache.log4j.Logger; + +import at.gv.egiz.pdfas.api.analyze.NonTextObjectInfo; +import at.gv.egiz.pdfas.api.timestamp.TimeStamper; +import at.gv.egiz.pdfas.framework.input.PdfDataSource; +import at.gv.egiz.pdfas.utils.PdfAUtil; +import at.knowcenter.wag.egov.egiz.sig.SignatureData; +import at.knowcenter.wag.egov.egiz.sig.connectors.bku.SignSignatureObject; + +import com.lowagie.text.pdf.PdfReader; + +/** + * This parameter object contains all useful inforamtion the binary incremental + * update methods need to create and replace a binary singature block. + * + *

+ * This class is basically used to transport information about the document from + * the prepareSign to the finishSign of the Signator. In future, this could be + * extended and encapsulated to task proprietary IUI instances. E.g. a + * BinarySignatorIUI, a TextualSignatorIUI, both implementing the core IUI + * interface, but encapsulating Binary or Textual specialities. + *

+ * + * @author wprinz + */ +public class IncrementalUpdateInformation implements Serializable +{ + public static final Logger log = Logger.getLogger(IncrementalUpdateInformation.class); + + /** + * SVUID. + */ + private static final long serialVersionUID = -5904526956127108035L; + + /** + * The original PDF document. + */ + //public byte[] original_document = null; + public PdfDataSource original_document = null; + + /** + * The Singature type to be created. + */ + public String signature_type = null; + + /** + * The signed pdf document. + * + *

+ * This is the original document plus the incremental update block. + *

+ */ + public byte[] signed_pdf = null; + // this is only valid during prepare + + // holds the variable iui data for bin sigs. + public byte [] sign_iui_block = null; + + /** + * The start index of this incremental update block. + */ + int start_index = -1; + + /** + * The indirect reference of the egiz dict. + */ + // PdfIndirectReference egiz_dict_ir = null; + public int egiz_dict_ir_number; + + public int egiz_dict_ir_generation; + + /** + * The List of ReplaceInfo objects specifying the byte ranges where the + * variable data has to be fille in. + */ + public List replaces = null; + + /** + * The List of StringInfo objects specifying the byte ranges that should + * be/were signed. + */ + public List byte_ranges = null; + + /** + * The indirect reference of the signature x-object. + */ + // public PdfIndirectReference temp_ir; + public int temp_ir_number; + + public int temp_ir_generation; + + /** + * The start index of the content stream of the signature x-object. + */ + public int content_stream_start = -1; + + /** + * The length of the content stream of the signature x-object. + */ + public int content_stream_length = -1; + +// /** +// * The document text for signing. +// */ +// public String document_text; + /** + * The data to be signed or verified. + * + *

+ * For text signature this is the document text. + * For binary signature this is the PDF document. + *

+ */ + public SignatureData signature_data = null; + + /** + * The SignatureObject containing the variable values after the document text + * has been signed. + *

+ * These values have to be filled in. + *

+ */ + public SignSignatureObject signed_signature_object; + + /** + * The start of the /encodings array. + */ + public int enc_start = -1; + + /** + * The length of the /encodings array. + */ + public int enc_length = -1; + + /** + * The start of the first /Cert + */ + public int cert_start = -1; + + /** + * The length of the /cert placeholder. + */ + public int cert_length = -1; + + /** + * The length of the /TimeStamp placeholder. + */ + public int timestamp_length = -1; + + /** + * The start of the first /Timestamp + */ + public int timestamp_start = -1; + + /** + * The list of strings of the KZ. + */ + public List kz_list; + + /** + * The table position. + */ + public TablePos pos; + + /** + * For signing: the profile. + */ + public String signProfile; + + /** + * The actual position where the table was written. + */ + public ActualTablePos actualTablePos; + + /** + * The field definitions of invisible fields, which data should be stored in /Data. + */ + public List invisible_field_definitions; + + /** + * The invisible KZ String, if KZ is invisible. + */ + public String invisibleKZString = null; + + /** + * List {@link NonTextObjectInfo} of non text objects if available (text signature only). + * + */ + public List nonTextObjectInfos; + + + /** + * The timestamper if any + */ + public TimeStamper timeStamper; + + // dferbas + + public String getPdfVersion() { + byte[] pdf_data = this.original_document.getAsByteArray(); + PdfReader reader; + String pdfVersion = null; + try { + reader = new PdfReader(pdf_data); + // is.close(); + + byte[] metaData = reader.getMetadata(); + if (metaData != null) { + pdfVersion = PdfAUtil.findPdfAVersion(new String(metaData)); + } + if (pdfVersion == null) { + pdfVersion = "PDF 1." + reader.getPdfVersion(); + } + + reader.close(); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + return pdfVersion; + } +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/NoSignatureHolder.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/NoSignatureHolder.java new file mode 100644 index 0000000..97a6111 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/NoSignatureHolder.java @@ -0,0 +1,84 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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.knowcenter.wag.egov.egiz.pdf; + +import java.io.Serializable; + +import at.gv.egiz.pdfas.framework.input.DataSource; +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; + +public class NoSignatureHolder implements Serializable, SignatureHolder { + + private static final long serialVersionUID = 1L; + + // # sigs before modification + private int position; + + public NoSignatureHolder(int pos) { + this.position = pos; + } + + public DataSource getDataSource() { + return null; + } + + public SignatureObject getSignatureObject() { + return null; + } + + public int getPosition() { + return this.position; + } + + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + this.position; + return result; + } + + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (getClass() != obj.getClass()) { + return false; + } + + final NoSignatureHolder other = (NoSignatureHolder) obj; + + if (this.position != other.position) { + return false; + } + + return true; + } + +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ObjectExtractor.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ObjectExtractor.java new file mode 100644 index 0000000..eb7377a --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ObjectExtractor.java @@ -0,0 +1,233 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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.knowcenter.wag.egov.egiz.pdf; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.Logger; +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.PDResources; +import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; + +import at.gv.egiz.pdfas.api.analyze.NonTextObjectInfo; +import at.gv.egiz.pdfas.framework.input.PdfDataSource; + +/** + * Method for object extraction from pdf documents. + * This uses pdf-box 0.8.0, not 0.7.2. Packages org.apache.pdfbox instead org.pdfbox! + * @author dferbas + * + */ +public class ObjectExtractor { + private static Logger log = Logger.getLogger(ObjectExtractor.class); + + /* + * If set true signature annotations are not extracted otherwise + * all signatures except PDF-AS signatures are extracted. + */ + private final static boolean SKIP_NON_PDFAS_SIGNATURES = false; + + /** + * Find annotation objects in pdf documents + * @param objectInfos + * @param pageNr + * @param page + */ + private static void doExtractAnnotations(List objectInfos, int pageNr, PDPage page) { + List annotations; + try { + annotations = page.getAnnotations(); + } catch (IOException e) { + log.error("Error extracting annotations from pdf. No NonTextObjectInfo-annotations available.", e); + return; + } + for (Iterator it = annotations.iterator(); it.hasNext();) { + try { + PDAnnotation anno = (PDAnnotation) it.next(); + log.debug("found annotation: " +anno); + if (log.isTraceEnabled()) { + log.trace("annotation def: " + dictToString(anno.getDictionary())); + } + String ft = anno.getDictionary().getNameAsString("FT"); + if (ft != null && ft.equals("Sig")) { // skip signature widgets + if (SKIP_NON_PDFAS_SIGNATURES) { + log.debug("found signature widged, skip extraction"); + continue; + } else { + COSDictionary sigDict = (COSDictionary) anno.getDictionary().getDictionaryObject("V"); + if (sigDict != null && AdobeSignatureHelper.ADOBE_SIG_FILTER.equals(sigDict.getNameAsString("Filter"))) { + log.debug("found PDF-AS signature widged, skip extraction"); + continue; + } + } + } + + NonTextObjectInfo objInfo = new NonTextObjectInfo(); + objInfo.setName(anno.getDictionary().getString( "NM" )); + objInfo.setObjectType(NonTextObjectInfo.TYPE_ANNOTATION); + + String subtype = anno.getDictionary().getNameAsString("Subtype"); + String subj = anno.getDictionary().getString("Subj"); + if (subj != null) { + subtype+= "/" + subj; + } + objInfo.setSubType(subtype); + + objInfo.setPageNr(pageNr); + objInfo.setHeight(anno.getRectangle().getHeight()); + objInfo.setWidth(anno.getRectangle().getWidth()); + objectInfos.add(objInfo); + } catch (Exception ex) { + log.info("error reading non text object info key " + ex); + } + } + } + + /** + * Create string representation from COSDictionary + * @param dict + * @return + */ + public static String dictToString(COSDictionary dict) + { + try { + String retVal = "COSDictionary{"; + for (int i = 0; i 0.8.0 needed + } + if (log.isDebugEnabled()) { + log.debug("extracted non textual objects count: " + objectInfos.size()); + } + return objectInfos; + + } + + /** + * Extract non textual data from pdf. + * @param pdfDataSource + * + * @see org.pdfbox.ExtractImages + * + * @return List of {@link NonTextObjectInfo} + */ + public static List extractNonTextInfo(PdfDataSource pdfDataSource) { + + PDDocument doc = null; + try { + doc = PDDocument.load(pdfDataSource.createInputStream()); + List res = extractNonTextInfo(doc); + doc.close(); + return res; + } catch (IOException e) { + log.error("Error extracting images from pdf. No NonTextObjectInfo available.", e); + return new ArrayList(); + } finally { + if (doc != null) { + try { + doc.close(); + } catch (IOException e) { + log.error("error closing pddocument", e); + } + } + } + + } + +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFPage.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFPage.java new file mode 100644 index 0000000..a0311da --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFPage.java @@ -0,0 +1,377 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: PDFPage.java,v 1.5 2006/10/31 08:09:33 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.Logger; +import org.pdfbox.cos.COSName; +import org.pdfbox.cos.COSStream; +import org.pdfbox.pdmodel.PDPage; +import org.pdfbox.pdmodel.PDResources; +import org.pdfbox.pdmodel.common.PDStream; +import org.pdfbox.pdmodel.graphics.xobject.PDXObject; +import org.pdfbox.pdmodel.graphics.xobject.PDXObjectForm; +import org.pdfbox.util.Matrix; +import org.pdfbox.util.PDFOperator; +import org.pdfbox.util.PDFTextStripper; +import org.pdfbox.util.TextPosition; +import org.pdfbox.util.operator.OperatorProcessor; + +import at.knowcenter.wag.egov.egiz.cfg.ConfigLogger; + +/** + * PDFPage is an inner class that is used to calculate the page length of a PDF + * Document page. It extends the PDFTextStripper class and implement one + * interested method: {@link PDFPage#showCharacter(TextPosition)}
+ * This method is called when processing the FileStream. By calling the method + * {@link org.pdfbox.util.PDFStreamEngine#processStream(org.pdfbox.pdmodel.PDPage, org.pdfbox.pdmodel.PDResources, org.pdfbox.cos.COSStream)} + * the implemented method showCharacter is called. + * + * @author wlackner + * @see PDFTextStripper + */ +public class PDFPage extends PDFTextStripper +{ + /** + * The logger definition. + */ + private static final Logger logger_ = ConfigLogger.getLogger(PDFPage.class); + + /** + * The maximum (lowest) y position of a character. + */ + protected float max_character_ypos = Float.NEGATIVE_INFINITY; + + /** + * The maximum (lowest y position of an image. + */ + protected float max_image_ypos = Float.NEGATIVE_INFINITY; + + /** + * The y coordinate of the footer line. PDF elements below this footer line will not be regarded. + */ + protected float footer_line = 0.0f; + + /** + * Constructor. + * + * @param footer_line The y coordinate of the footer line. PDF elements below this footer line will not be regarded. + * + * @throws IOException + */ + public PDFPage(float footer_line) throws IOException + { + super(); + + this.footer_line = footer_line; + + OperatorProcessor newInvoke = new MyInvoke(); + newInvoke.setContext(this); + operators.put("Do", newInvoke); + } + + + protected void processOperator(PDFOperator operator, List arguments) throws IOException + { + //logger_.debug("operator = " + operator); + + super.processOperator(operator, arguments); + } + + // exthex + /** + * A method provided as an event interface to allow a subclass to perform some + * specific functionality when a character needs to be displayed. This method + * is used to calculate the latest position of a text in the page. Sorry for + * this missinterpretation of the method, but it is the only way to do this + * (provided by PDFBox)!!! + * + * @param text + * the character to be displayed -> calculate there y position. + */ + protected void showCharacter(TextPosition text) + { + float current_y = text.getY(); + final String character = text.getCharacter(); + + int pageRotation = page.findRotation(); + //logger_.debug("PageRotation = " + pageRotation); + if (pageRotation == 0) + { + current_y = text.getY(); + } + if (pageRotation == 90) + { + current_y = text.getX(); + } + if (pageRotation == 180) + { + float page_height = page.findMediaBox().getHeight(); + current_y = page_height - text.getY(); + } + if (pageRotation == 270) + { + float page_height = page.findMediaBox().getHeight(); + current_y = page_height - text.getX(); + } + + + if (current_y > this.footer_line) + { + //logger_.debug("character is below footer_line. footer_line = " + this.footer_line + ", text.character=" + character + ", y=" + current_y); + return; + } + + // store ypos of the char if it is not empty + if (!character.equals(" ") && current_y > this.max_character_ypos) + { + this.max_character_ypos = current_y; + //logger_.debug("text.character=" + character + ", y=" + current_y); + // System.err.println(character + "|" + current_y); + } + + //logger_.error("text.character=" + character + ", y=" + current_y + " max_c=" + this.max_character_ypos); + // System.err.println(character + "|" + current_y); + } + + // use this funtion getting an unsorted text output + // public void showString(byte[] string) { + // logger_.debug(new String(string)); + // } + + /** + * Returns the calculated page length. + * + * @return the max page length value + */ + public float getMaxPageLength() + { + float max_ypos = Float.NEGATIVE_INFINITY; + + if (this.max_character_ypos > this.max_image_ypos) + { + max_ypos = this.max_character_ypos; + } + else + { + max_ypos = this.max_image_ypos; + } + //logger_.error("max len=" + max_ypos); + return max_ypos; + } + + public class MyInvoke extends OperatorProcessor + { + + public void process(PDFOperator operator, List arguments) throws IOException + { + COSName name = (COSName) arguments.get(0); + //logger_.error(""); + + // PDResources res = context.getResources(); + + Map xobjects = context.getXObjects(); + PDXObject xobject = (PDXObject) xobjects.get(name.getName()); + + PDStream stream = xobject.getPDStream(); + COSStream cos_stream = stream.getStream(); + + COSName subtype = (COSName) cos_stream.getDictionaryObject(COSName.SUBTYPE); + //logger_.error(""); + if (subtype.equals(COSName.IMAGE)) + { + //logger_.error("XObject Image"); + + Matrix ctm = context.getGraphicsState().getCurrentTransformationMatrix(); + //logger_.debug("ctm = " + ctm); + + Pos [] coordinates = new Pos [] { + new Pos(0, 0, 1), + new Pos(1, 0, 1), + new Pos(0, 1, 1), + new Pos(1, 1, 1) }; + + Pos [] transformed_coordinates = transtormCoordinates(coordinates, ctm); + + /********************************************************** + * pdf-as fix: + * calculating min and max point of an image to look where + * the signature should be placed + * fix solves problems with footer and images and + * placement of the signature in an image only pdf document + **********************************************************/ + + float actual_lowest_point = Float.NaN; + float actual_starting_point = Float.NaN; + + int pageRotation = page.findRotation(); + logger_.debug("PageRotation = " + pageRotation); + if (pageRotation == 0) + { + float min_y = findMinY(transformed_coordinates); + logger_.debug("min_y = " + min_y); + float page_height = page.findMediaBox().getHeight(); + logger_.debug("page_height = " + page_height); + + actual_lowest_point = page_height - min_y; + actual_starting_point = page_height - findMaxY(transformed_coordinates); + } + if (pageRotation == 90) + { + float max_x = findMaxX(transformed_coordinates); + logger_.debug("max_x = " + max_x); + float page_width = page.findMediaBox().getWidth(); + logger_.debug("page_width = " + page_width); + + actual_lowest_point = max_x; + actual_starting_point = findMinX(transformed_coordinates); + } + if (pageRotation == 180) + { + float min_y = findMinY(transformed_coordinates); + logger_.debug("min_y = " + min_y); + float page_height = page.findMediaBox().getHeight(); + actual_lowest_point = page_height - findMaxY(transformed_coordinates); + actual_starting_point = page_height - min_y; + } + if (pageRotation == 270) + { + float min_x = findMinX(transformed_coordinates); + logger_.debug("min_x = " + min_x); + + float page_width = page.findMediaBox().getWidth(); + logger_.debug("page_width = " + page_width); + + actual_lowest_point = page_width - min_x; + actual_starting_point = page_width - findMaxX(transformed_coordinates); + } + + + logger_.debug("actual_lowest_point = " + actual_lowest_point); + + if (actual_lowest_point > PDFPage.this.footer_line && actual_starting_point > PDFPage.this.footer_line) + { + logger_.debug("image is below footer_line. footer_line = " + PDFPage.this.footer_line); + return; + } + + if (actual_lowest_point > PDFPage.this.max_image_ypos) + { + PDFPage.this.max_image_ypos = actual_lowest_point; + } + + return; + } + + if (xobject instanceof PDXObjectForm) + { + PDXObjectForm form = (PDXObjectForm) xobject; + COSStream invoke = (COSStream) form.getCOSObject(); + PDResources pdResources = form.getResources(); + PDPage page = context.getCurrentPage(); + if (pdResources == null) + { + pdResources = page.findResources(); + } + + getContext().processSubStream(page, pdResources, invoke); + } + } + } + + public static Pos [] transtormCoordinates (Pos [] coordinates, Matrix m) + { + Pos [] transformed = new Pos [coordinates.length]; + for (int i = 0; i < coordinates.length; i++) + { + transformed[i] = transtormCoordinate(coordinates[i], m); + } + return transformed; + } + + public static Pos transtormCoordinate (Pos pos, Matrix m) + { + Pos transformed = new Pos(); + transformed.x = pos.x * m.getValue(0, 0) + pos.y * m.getValue(1, 0) + pos.z * m.getValue(2, 0); + transformed.y = pos.x * m.getValue(0, 1) + pos.y * m.getValue(1, 1) + pos.z * m.getValue(2, 1); + transformed.z = pos.x * m.getValue(0, 2) + pos.y * m.getValue(1, 2) + pos.z * m.getValue(2, 2); + + logger_.debug(" transformed " + pos + " --> " + transformed); + return transformed; + } + + public static float findMinY (Pos [] coordinates) + { + float min = Float.POSITIVE_INFINITY; + for (int i = 0; i < coordinates.length; i++) + { + if (coordinates[i].y < min) + { + min = coordinates[i].y; + } + } + return min; + } + + public static float findMaxY(Pos[] coordinates) { + float max = 0; + for (int i = 0; i < coordinates.length; i++) { + if (coordinates[i].y > max) { + max = coordinates[i].y; + } + } + return max; + } + + public static float findMaxX (Pos [] coordinates) + { + float max = Float.NEGATIVE_INFINITY; + for (int i = 0; i < coordinates.length; i++) + { + if (coordinates[i].x > max) + { + max = coordinates[i].x; + } + } + return max; + } + public static float findMinX (Pos [] coordinates) + { + float min = Float.POSITIVE_INFINITY; + for (int i = 0; i < coordinates.length; i++) + { + if (coordinates[i].x < min) + { + min = coordinates[i].x; + } + } + return min; + } + +} \ No newline at end of file diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureCreation.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureCreation.java new file mode 100644 index 0000000..d6e6966 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureCreation.java @@ -0,0 +1,176 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: PDFSignatureCreation.java,v 1.6 2006/10/31 08:09:33 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +import at.knowcenter.wag.egov.egiz.cfg.ConfigLogger; +import at.knowcenter.wag.egov.egiz.cfg.SettingsReader; +import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; +import at.knowcenter.wag.egov.egiz.exceptions.SettingsException; +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; + +/** + * This class provides wrapper methods to get an access to abstract PDF documents (PDFSignator). + * There exists many open source libraries and commercial libraries that can implement the abstract + * interface.
+ * This class is to load the corresponding implementation of an abstract PDFSignator class. Therefor + * it seams to be a factory. The factory settings are read from the configuration file calling the + * SettingsReader. + * + * @author wlackner + * @see at.knowcenter.wag.egov.egiz.cfg.SettingsReader + */ +public class PDFSignatureCreation { + /** + * The abstract signature object + */ + private SignatureObject sigObject_ = null; + /** + * The abstract pdf siganture object + */ + private PDFSignatureObject pdfSigObject_ = null; + /** + * The SettingsReader instance + */ + private SettingsReader settings_ = null; + /** + * The factory class prefix + */ + private final static String CLASS_PREFIX = ".PDFSignatureObject"; + /** + * The factory class prefix of the default library + */ + protected final static String DEFAULT_LIBRARY = "IText"; + /** + * The settings key defined in the settings file + * + * @see SettingsReader + */ + protected final static String SETTINGS_KEY = "pdf.signature.library"; + /** + * The logger definition. + */ + private static final Logger logger_ = ConfigLogger.getLogger(PDFSignatureCreation.class); + + /** + * Load the configuration settings. Load the corresponding class implementation for the abstract + * PDFSignature class. Init with a signature object. + * + * @param sigObject the native signature object + * @throws PDFDocumentException ErrorCode:101 + */ + public PDFSignatureCreation(SignatureObject sigObject) throws PDFDocumentException { +// try { + loadSettings(); +// } catch (PDFDocumentException e) { +// e.setErrorCode(101); +// throw e; +// } + sigObject_ = sigObject; + } + + /** + * Load the factory implementation. This method trys to load the configured PDF library. + * + * @throws PDFDocumentException + */ + private PDFSignatureObject createPDFSignatureObject() throws PDFDocumentException { + PDFSignatureObject pdf_sig_object = null; + String class_name = this.getClass().getPackage().getName() + getClassName(); + Class pdf_sig_obj_class = null; + try { + pdf_sig_obj_class = Class.forName(class_name); + } catch (ClassNotFoundException e) { + if (logger_.isEnabledFor(Level.FATAL)) { + logger_.fatal("Class not found:" + class_name); + } + throw new PDFDocumentException(203, "Can not load pdf signator library", e); + } + try { + pdf_sig_object = (PDFSignatureObject) pdf_sig_obj_class.newInstance(); + } catch (InstantiationException e) { + if (logger_.isEnabledFor(Level.FATAL)) { + logger_.fatal("Can not instantiate:" + class_name); + } + throw new PDFDocumentException(203, "Can not load pdf signator library", e); + } catch (IllegalAccessException e) { + if (logger_.isEnabledFor(Level.FATAL)) { + logger_.fatal("Can not access:" + class_name); + } + throw new PDFDocumentException(203, "Can not load pdf signator library", e); + } + return pdf_sig_object; + } + + /** + * load the class settings + * + * @throws PDFDocumentException + * @see SettingsReader + */ + private void loadSettings() throws PDFDocumentException { + if (settings_ == null) { + try { + settings_ = SettingsReader.getInstance(); + } catch (SettingsException e) { + String log_message = "Can not load pdf signature settings. Cause:\n" + e.getMessage(); + logger_.error(log_message); + throw new PDFDocumentException(101, log_message, e); + } + } + } + + /** + * Read the class postfix from the configuration file + * + * @return the full qualified class name + */ + private String getClassName() { + String extract_class = settings_.getSetting(SETTINGS_KEY, DEFAULT_LIBRARY); + return CLASS_PREFIX + extract_class; + } + + /** + * Creates a new pdf signature object using the configured pdf library. + * + * @return a new pdf signature object + * @throws PDFDocumentException ErrorCode:203 + */ + public PDFSignatureObject getPDFSignatureObject() throws PDFDocumentException { + if (pdfSigObject_ == null) { +// try { + pdfSigObject_ = createPDFSignatureObject(); +// } catch (PDFDocumentException e) { +// e.setErrorCode(203); +// throw e; +// } + pdfSigObject_.setSignatorObject(sigObject_); + } + return pdfSigObject_; + } +} \ No newline at end of file diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObject.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObject.java new file mode 100644 index 0000000..bd5b6a3 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObject.java @@ -0,0 +1,56 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: PDFSignatureObject.java,v 1.3 2006/10/31 08:09:33 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; + +/** + * Defines an interface to get access to PDF documents. There exists many open source libraries and + * commercial libraries. + * + * @author wlackner + */ +public interface PDFSignatureObject { + public void setSignatorObject(SignatureObject signatorObject); + + /** + * Converts the current abstract signature object in a pdf signature object implementation + * + * @return the converted pdf signature object + * @throws PDFDocumentException + */ + public Object getSignatureObject() throws PDFDocumentException; + + /** + * Converts a abstract signature object in a pdf signature object implementation + * + * @param signatorObject the abstract signatorObject to convert + * @return the converted pdf signature object + * @throws PDFDocumentException + */ + public Object getSignatureObject(SignatureObject signatorObject) throws PDFDocumentException; +} \ No newline at end of file diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObjectIText.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObjectIText.java new file mode 100644 index 0000000..1145619 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObjectIText.java @@ -0,0 +1,618 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: PDFSignatureObjectIText.java,v 1.5 2006/10/31 08:09:33 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.HashMap; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +import at.gv.egiz.pdfas.exceptions.ErrorCode; +import at.knowcenter.wag.egov.egiz.cfg.ConfigLogger; +import at.knowcenter.wag.egov.egiz.cfg.SettingsReader; +import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; +import at.knowcenter.wag.egov.egiz.exceptions.SettingsException; +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; +import at.knowcenter.wag.egov.egiz.table.Entry; +import at.knowcenter.wag.egov.egiz.table.Style; +import at.knowcenter.wag.egov.egiz.table.Table; + +import com.lowagie.text.BadElementException; +import com.lowagie.text.DocumentException; +import com.lowagie.text.Element; +import com.lowagie.text.Font; +import com.lowagie.text.Image; +import com.lowagie.text.Phrase; +import com.lowagie.text.pdf.BaseFont; +import com.lowagie.text.pdf.PdfPCell; +import com.lowagie.text.pdf.PdfPTable; +import com.lowagie.text.pdf.SubsetLocal; + +/** + * This class is the IText implementation of the PDFSignatureObject interface. + * The class takes an abstract definition of a signature object and convert them + * into a pdf table that is used to sign a pdf document. + * + * @author wlackner + * @see at.knowcenter.wag.egov.egiz.sig.SignatureObject + * @see at.knowcenter.wag.egov.egiz.table.Table + * @see at.knowcenter.wag.egov.egiz.table.Entry + * @see at.knowcenter.wag.egov.egiz.table.Style + * @see com.lowagie.text.pdf.PdfPTable + * @see at.knowcenter.wag.egov.egiz.cfg.SettingsReader + */ +public class PDFSignatureObjectIText implements PDFSignatureObject +{ + +// 03.11.2010 changed by exthex to allow setting separate hAlign and vAlign for value and non-value cells. +// If no value for valuevalign or valuehalign is set the values from valign and halign are taken. +// Also fixed a minor bug which prevented proper style inheritment (Bug Nr. #534). +// 04.11.2010 changed by exthex - allow setting separate hAlign and vAlign for image cells analog to value cells. + + private static final String SIG_PDFA1_B_VALID = "SIG_PDFA1B_VALID"; + +/** + * The default font definition + */ + private static Font DEFAULT_FONT = new Font(Font.HELVETICA, 8, Font.NORMAL); + + /** + * The abstract signature object + */ + private SignatureObject sigObject_ = null; + + /** + * The IText pdf table object + */ + private PdfPTable pdfSigObject_ = null; + + /** + * The SettingsReader instance + */ + private SettingsReader settings_ = null; + + /** + * The logger definition. + */ + private static final Logger logger_ = ConfigLogger.getLogger(PDFSignatureObjectIText.class); + + /** + * Map the style align definitions to IText's align statements + */ + private static HashMap alignMap_ = new HashMap(); + + /** + * Map the font definitions to IText's font statements + */ + private static HashMap fontMap_ = new HashMap(); + + /** + * The empty constructor. It loads the ui definitions from signature tables + * and init the align map. + * + * @throws PDFDocumentException + */ + public PDFSignatureObjectIText() throws PDFDocumentException + { + loadSettings(); + initStyleMaps(); + } + + /** + * load the class settings + * + * @throws PDFDocumentException + * @see SettingsReader + */ + private void loadSettings() throws PDFDocumentException + { + if (settings_ == null) + { + try + { + settings_ = SettingsReader.getInstance(); + } + catch (SettingsException e) + { + String log_message = "Can not load pdf signature settings. Cause:\n" + e.getMessage(); + logger_.error(log_message); + throw new PDFDocumentException(101, log_message, e); + } + } + } + + /** + * This method initialize the style maps. It maps the style style definitions + * to IText styles. + */ + private void initStyleMaps() + { + alignMap_.put(Style.TOP, new Integer(Element.ALIGN_TOP)); + alignMap_.put(Style.MIDDLE, new Integer(Element.ALIGN_MIDDLE)); + alignMap_.put(Style.BOTTOM, new Integer(Element.ALIGN_BOTTOM)); + alignMap_.put(Style.LEFT, new Integer(Element.ALIGN_LEFT)); + alignMap_.put(Style.CENTER, new Integer(Element.ALIGN_CENTER)); + alignMap_.put(Style.RIGHT, new Integer(Element.ALIGN_RIGHT)); + + fontMap_.put(Style.HELVETICA, new Integer(Font.HELVETICA)); + fontMap_.put(Style.TIMES_ROMAN, new Integer(Font.TIMES_ROMAN)); + fontMap_.put(Style.COURIER, new Integer(Font.COURIER)); + fontMap_.put(Style.NORMAL, new Integer(Font.NORMAL)); + fontMap_.put(Style.BOLD, new Integer(Font.BOLD)); + fontMap_.put(Style.ITALIC, new Integer(Font.ITALIC)); + fontMap_.put(Style.BOLDITALIC, new Integer(Font.BOLDITALIC)); + fontMap_.put(Style.UNDERLINE, new Integer(Font.UNDERLINE)); + fontMap_.put(Style.STRIKETHRU, new Integer(Font.STRIKETHRU)); + } + + /** + * Set the abstract signature definition. + * + * @param signatorObject + * the abstract signator object + * @see at.knowcenter.wag.egov.egiz.pdf.PDFSignatureObject#setSignatorObject(at.knowcenter.wag.egov.egiz.sig.SignatureObject) + */ + public void setSignatorObject(SignatureObject signatorObject) + { + sigObject_ = signatorObject; + } + + /** + * This method maps the table cell definitions to the pdfCell element. + * + * @param pdfCell + * the pdf cell to be styled + * @param cellStyle + * the abstract style definition + * @param type + * type of the cell to render - the appropriate style will be set + * @see com.lowagie.text.pdf.PdfPCell + * @see at.knowcenter.wag.egov.egiz.table.Style + */ + private void setCellStyle(PdfPCell pdfCell, Style cellStyle, int type) + { + if (cellStyle != null) + { + if (cellStyle.getBgColor() != null) + { + pdfCell.setBackgroundColor(cellStyle.getBgColor()); + } + pdfCell.setPadding(cellStyle.getPadding()); + //exthex - fix for not exactly vertically centered text + pdfCell.setUseAscender(true); + + if (cellStyle.getBorder() > 0) + { + pdfCell.setBorderWidth(cellStyle.getBorder()); + } + else + { + pdfCell.setBorder(0); + } + int align = -1; + if (type == Entry.TYPE_VALUE && cellStyle.getValueVAlign() != null) + align = ((Integer) alignMap_.get(cellStyle.getValueVAlign())).intValue(); + //Note: to change the default valign of images to those of values, change the if construct below + else if (type == Entry.TYPE_IMAGE && cellStyle.getImageVAlign() != null) + align = ((Integer) alignMap_.get(cellStyle.getImageVAlign())).intValue(); + else if (cellStyle.getVAlign() != null) + align = ((Integer) alignMap_.get(cellStyle.getVAlign())).intValue(); + if (align != -1) + pdfCell.setVerticalAlignment(align); + + align = -1; + if (type == Entry.TYPE_VALUE && cellStyle.getValueHAlign() != null) + align = ((Integer) alignMap_.get(cellStyle.getValueHAlign())).intValue(); + //Note: to change the default halign of images to those of values, change the if construct below + else if (type == Entry.TYPE_IMAGE && cellStyle.getImageHAlign() != null) + align = ((Integer) alignMap_.get(cellStyle.getImageHAlign())).intValue(); + else if (cellStyle.getHAlign() != null) + align = ((Integer) alignMap_.get(cellStyle.getHAlign())).intValue(); + if (align != -1) + pdfCell.setHorizontalAlignment(align); + } + } + + /** + * This method maps the cell font definition to the iText Font Object + * + * @param fontString + * @return the corresponding iText Font Object + * @see com.lowagie.text.Font + */ + private Font getCellFont(String fontString) + { + Font font = DEFAULT_FONT; + if (fontString == null) + { + return font; + } + Object cache_font = fontMap_.get(fontString); + if (cache_font != null) + { + return (Font) cache_font; + } + String[] font_arr = fontString.split(","); + if (font_arr.length != 3) + { + return font; + } + Object font_face = fontMap_.get(font_arr[0]); + if (font_face == null) + { + return font; + } + Object font_weight = fontMap_.get(font_arr[2]); + if (font_weight == null) + { + return font; + } + int face = ((Integer) font_face).intValue(); + float height = Float.parseFloat(font_arr[1]); + int weight = ((Integer) font_weight).intValue(); + + font = new Font(face, height, weight); + fontMap_.put(fontString, font); + return font; + } + + /** + * Creates a custom + * @param fontString + * @return + * @throws PDFDocumentException + */ + private Font getCellTrueTypeFont(String fontString) throws PDFDocumentException { + float fontSize=8; + String fontName = fontString.replaceFirst("TTF:", ""); + String[] split = fontName.split(","); + if(split.length>1) + { + fontName = split[0].trim(); + try + { + fontSize = Float.parseFloat(split[1].trim()); + }catch (NumberFormatException e) + { + logger_.error("Unable to parse fontsize:"+fontString); + } + } + logger_.debug("TrueType Font detected:"+fontName +" ("+fontSize+")"); + + try { + Font font = (Font) fontMap_.get(fontString); + + if (font == null) { + logger_.debug("Font \"" + fontString + "\" not in cache. Instantiating font."); + String fontPath = SettingsReader.RESOURCES_PATH + "fonts" + File.separator + fontName; + logger_.debug("Instantiating \"" + fontPath + "\"."); + + font = new Font(BaseFont.createFont(fontPath, BaseFont.WINANSI, true), fontSize); + fontMap_.put(fontString, font); + } + return font; + } catch (DocumentException e) { + throw new PDFDocumentException(ErrorCode.FONT_NOT_FOUND, e.getMessage()); + } catch (IOException e) { + throw new PDFDocumentException(ErrorCode.FONT_NOT_FOUND, e.getMessage()); + } + } + + /** + * This method visualize an abstract table cell into a corresponding pdf table + * cell. The new pdf table cell is redered and get the style information from + * the abstract cell. Following types can be rendered: + *
    + *
  • text statements
  • + *
  • images
  • + *
  • tables
  • + *
+ * + * @param abstractCell + * the abstract cell definition + * @return the new redererd pdf table cell + * @throws PDFDocumentException + * ErrorCode:220, 221, 222 + * @see com.lowagie.text.pdf.PdfPCell + * @see at.knowcenter.wag.egov.egiz.table.Entry + */ + private PdfPCell renderCell(Entry abstractCell) throws PDFDocumentException + { + boolean pdfaValid =false; + try + { + String profileid = sigObject_.getSignatureTypeDefinition().getType(); + String pdfa = SettingsReader.getInstance().getSetting("sig_obj." +profileid+".key."+SIG_PDFA1_B_VALID, "default."+SIG_PDFA1_B_VALID, "false"); + pdfaValid= "true".equalsIgnoreCase(pdfa); + +// exthex test + //SubsetLocal.set(!pdfaValid); + +// boolean forceSubset = true; // get this from config, default to false + //String + + //SubsetLocal.set(true); // exthex + + logger_.trace("Sign PDF/A compliant:"+pdfa); + } catch (SettingsException e1) + { + logger_.error(e1); + } + + PdfPCell pdf_cell = null; + Style cell_style = abstractCell.getStyle(); + boolean isValue = true; + switch (abstractCell.getType()) + { + case Entry.TYPE_CAPTION: + isValue = false; + case Entry.TYPE_VALUE: + String text = (String) abstractCell.getValue(); + if (text == null) + { + text = ""; + } + String font_string = cell_style.getFont(); + if (abstractCell.getType() == Entry.TYPE_VALUE && cell_style.getValueFont() != null) + { + font_string = cell_style.getValueFont(); + } + + logger_.trace("using cell font: "+font_string); + + Font cell_font; + if(font_string.startsWith("TTF:")) + { + cell_font = getCellTrueTypeFont(font_string); + } + else + { + if (pdfaValid) { + throw new PDFDocumentException(ErrorCode.NO_EMBEDABLE_TTF_CONFIGURED_FOR_PDFA, "PDF/A modus requires an embedable true type font"); + } + cell_font = getCellFont(font_string); + + } + // exthex + if (pdfaValid && abstractCell.getType() == Entry.TYPE_VALUE) { + SubsetLocal.addNonSubsetFont(cell_font.getBaseFont()); + } + Phrase text_phrase = new Phrase(text, cell_font); + pdf_cell = new PdfPCell(text_phrase); + setCellStyle(pdf_cell, cell_style, (isValue?Entry.TYPE_VALUE:Entry.TYPE_CAPTION)); + break; + case Entry.TYPE_IMAGE: + try + { + String img_ref = (String) abstractCell.getValue(); + // fixed by tknall start + File img_file = new File(img_ref); + if (!img_file.isAbsolute()) { + logger_.debug("Image file declaration is relative. Prepending path of resources directory."); + img_file = new File(SettingsReader.relocateFile(img_ref)); + } else { + logger_.debug("Image file declaration is absolute. Skipping file relocation."); + } +// String img_location = SettingsReader.relocateFile(img_ref); +// File img_file = new File (img_location); + if (!img_file.exists()) + { + logger_.debug("Image file \"" + img_file.getCanonicalPath() + "\" doesn't exist."); + throw new PDFDocumentException(220, "Image file \"" + img_file.getCanonicalPath() + "\" doesn't exist."); + } + Image image = Image.getInstance(img_file.getCanonicalPath()); + logger_.debug("Using image file \"" + img_file.getCanonicalPath() + "\"."); + + image.scaleToFit(80.0f, 80.0f); + boolean fit = true; + Style.ImageScaleToFit istf = cell_style.getImageScaleToFit(); + if (istf != null) + { + image.scaleToFit(istf.getWidth(), istf.getHeight()); + fit = false; + } + pdf_cell = new PdfPCell(image, fit); + setCellStyle(pdf_cell, cell_style, Entry.TYPE_IMAGE); + } + catch (BadElementException e) + { + if (logger_.isEnabledFor(Level.ERROR)) + { + logger_.error("BadElementException:" + e.getMessage()); + } + PDFDocumentException pde = new PDFDocumentException(220, "PDF table can not created"); + throw pde; + } + catch (MalformedURLException e) + { + if (logger_.isEnabledFor(Level.ERROR)) + { + logger_.error("MalformedURLException:" + e.getMessage()); + } + PDFDocumentException pde = new PDFDocumentException(221, "PDF table can not created"); + throw pde; + } + catch (IOException e) + { + if (logger_.isEnabledFor(Level.ERROR)) + { + logger_.error("Error Code: 222, IOException:" + e.getMessage()); + } + PDFDocumentException pde = new PDFDocumentException(222, "PDF table can not created: Image can not loaded"); + throw pde; + } + break; + case Entry.TYPE_TABLE: + Table table = (Table) abstractCell.getValue(); + // inherit the style from the parent table + Style inherit_style = Style.doInherit(table.getStyle(), cell_style); + table.setStyle(inherit_style); + PdfPTable pdf_table = renderTable(table); + pdf_cell = new PdfPCell(pdf_table); + // The default new PdfPCell has a default border of 15. + // For blocks without border and subtables this results + // in a border to be drawn around the cell. + // ==> no border on default + pdf_cell.setBorder(0); + break; + } + return pdf_cell; + } + + /** + * This method visualize an abstract table into a corresponding pdf table. The + * new pdf table is redered and get the style information from the abstract + * cell. + * + * @param abstractTable + * the abstract table definition + * @return the new redererd pdf table cell + * @throws PDFDocumentException + * ErrorCode:220, 221, 222, 223 + * @see com.lowagie.text.pdf.PdfPTable + * @see at.knowcenter.wag.egov.egiz.table.Table + */ + private PdfPTable renderTable(Table abstractTable) throws PDFDocumentException + { + if (abstractTable == null) + { + PDFDocumentException pde = new PDFDocumentException(223, "Table is not defined."); + throw pde; + } + PdfPTable pdf_table = null; + float[] cols = abstractTable.getColsRelativeWith(); + int max_cols = abstractTable.getMaxCols(); + if (cols == null) + { + cols = new float[max_cols]; + // set the column ratio for all columns to 1 + for (int cols_idx = 0; cols_idx < cols.length; cols_idx++) + { + cols[cols_idx] = 1; + } + } + pdf_table = new PdfPTable(cols); + pdf_table.setWidthPercentage(abstractTable.getWidth()); + Style table_style = abstractTable.getStyle(); + setCellStyle(pdf_table.getDefaultCell(), table_style, Entry.TYPE_TABLE); + + ArrayList rows = abstractTable.getRows(); + for (int row_idx = 0; row_idx < rows.size(); row_idx++) + { + ArrayList row = (ArrayList) rows.get(row_idx); + // logger_.debug("## Row:" + row_idx + " ## of table:" + + // abstractTable.getName()); + for (int entry_idx = 0; entry_idx < row.size(); entry_idx++) + { + Entry cell = (Entry) row.get(entry_idx); + // 03.11.2010 changed by exthex - swapped the two params, was probably a bug + Style inherit_style = Style.doInherit(table_style, cell.getStyle()); + cell.setStyle(inherit_style); + // logger_.debug(cell.toString()); + PdfPCell pdf_cell = renderCell(cell); + if (cell.getColSpan() > 1) + { + pdf_cell.setColspan(cell.getColSpan()); + } + if (cell.isNoWrap()) + { + pdf_cell.setNoWrap(true); + } + // System.err.println("valign:" + pdf_cell.getVerticalAlignment() + " + // halign:" + + // pdf_cell.getHorizontalAlignment()); + pdf_table.addCell(pdf_cell); + } + } + // logger_.debug("render table:" + abstractTable.getName()); + return pdf_table; + } + + /** + * This method creates the pdf table object. It takes the abstract table + * definition from the signature object and render the abstract table. + * + * @param sigObject + * the signature object, the base for the abstract table definition + * @return R + * @throws PDFDocumentException + * ErrorCode:220, 221, 222, 223 + */ + private PdfPTable createPDFSignatureObject(SignatureObject sigObject) throws PDFDocumentException + { + SubsetLocal.clear(); + Table table = sigObject.getAbstractTable(); + PdfPTable pdf_table = renderTable(table); + return pdf_table; + } + + /* + * This method search for the table definitions in the settings file an init + * @param sigObject + */ + /* + * private void initTableSettings(SignatureObject sigObject) { String sig_type = + * sigObject.getSignationType(); String table_key = SignatureObject.SIG_OBJ + + * sig_type + ".table."; ArrayList main_rows = settings_.getKeys(table_key + + * "main"); } + */ + + /** + * Converts the current abstract signature object in a pdf signature object + * implementation + * + * @return the converted pdf signature object + * @see at.knowcenter.wag.egov.egiz.pdf.PDFSignatureObject#getSignatureObject() + */ + public Object getSignatureObject() throws PDFDocumentException + { + if (pdfSigObject_ == null) + { + pdfSigObject_ = (PdfPTable) getSignatureObject(sigObject_); + } + return pdfSigObject_; + } + + /** + * Converts a abstract signature object in a pdf signature object + * implementation + * + * @param sigObject + * the abstract signatorObject to convert + * @return the converted pdf signature object + * @throws PDFDocumentException + * @see at.knowcenter.wag.egov.egiz.pdf.PDFSignatureObject#getSignatureObject(at.knowcenter.wag.egov.egiz.sig.SignatureObject) + */ + public Object getSignatureObject(SignatureObject sigObject) throws PDFDocumentException + { + // initTableSettings(sigObject); + return createPDFSignatureObject(sigObject); + } +} \ No newline at end of file diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFUtilities.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFUtilities.java new file mode 100644 index 0000000..e2a3d06 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFUtilities.java @@ -0,0 +1,148 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: PDFUtilities.java,v 1.3 2006/10/31 08:09:33 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.util.List; + +import at.gv.egiz.pdfas.framework.input.PdfDataSource; +import at.gv.egiz.pdfas.impl.input.ByteArrayPdfDataSourceImpl; + +import org.pdfbox.pdfparser.PDFParser; +import org.pdfbox.pdmodel.PDDocument; +import org.pdfbox.pdmodel.PDPage; + +import at.knowcenter.wag.egov.egiz.cfg.SettingsReader; +import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; + +import com.lowagie.text.DocumentException; + +/** + * Contains useful helpers for accessing PDF documents. + * + * @author wprinz + * @author mruhmer + */ +public abstract class PDFUtilities +{ + public static float calculatePageLength(final PdfDataSource pdfDataSource,int page ,float footer_line, int pagerotation) throws PDFDocumentException + { + try + { + //ByteArrayInputStream original_bais = new ByteArrayInputStream(pdf); + //byte [] normalized_pdf = TextualSignature.normalizePDF(original_bais); + byte [] normalized_pdf = TextualSignature.normalizePDF(pdfDataSource); + + // PERF: The whole PDF normalization process is costy + ByteArrayInputStream bais = new ByteArrayInputStream(normalized_pdf); + + PDFParser parser = new PDFParser(bais); + File temporary_dir = SettingsReader.getTemporaryDirectory(); + parser.setTempDirectory(temporary_dir); + parser.parse(); + + PDDocument pdfDocument_ = parser.getPDDocument(); + float page_length = calculatePageLength(pdfDocument_,page , footer_line, pagerotation); + pdfDocument_.close(); + return page_length; + } + catch (IOException e) + { + throw new PDFDocumentException(201, e); + } + catch (DocumentException e) + { + throw new PDFDocumentException(201, e); + } + } + public static float calculatePageLength(PDDocument document,int page ,float footer_line, int pagerotation) throws IOException + { + //int last_page_id = document.getNumberOfPages(); + List allPages = document.getDocumentCatalog().getAllPages(); + PDPage pdpage = (PDPage) allPages.get(page); + pdpage.setRotation(pagerotation); + return calculatePageLength(pdpage, footer_line); + } + + /** + * @deprecated + * @param pdf + * @param footer_line + * @return + * @throws PDFDocumentException + */ + public static float calculateLastPageLength(final byte[] pdf, float footer_line) throws PDFDocumentException + { + try + { + //ByteArrayInputStream original_bais = new ByteArrayInputStream(pdf); + PdfDataSource dataSource = new ByteArrayPdfDataSourceImpl(pdf); + byte [] normalized_pdf = TextualSignature.normalizePDF(dataSource); + + ByteArrayInputStream bais = new ByteArrayInputStream(normalized_pdf); + + PDFParser parser = new PDFParser(bais); + File temporary_dir = SettingsReader.getTemporaryDirectory(); + parser.setTempDirectory(temporary_dir); + parser.parse(); + + PDDocument pdfDocument_ = parser.getPDDocument(); + float last_page_length = calculateLastPageLength(pdfDocument_, footer_line); + pdfDocument_.close(); + + return last_page_length; + } + catch (IOException e) + { + throw new PDFDocumentException(201, e); + } + catch (DocumentException e) + { + throw new PDFDocumentException(201, e); + } + } + + public static float calculateLastPageLength(PDDocument document, float footer_line) throws IOException + { + int last_page_id = document.getNumberOfPages(); + List allPages = document.getDocumentCatalog().getAllPages(); + PDPage last_page = (PDPage) allPages.get(last_page_id - 1); + + return calculatePageLength(last_page, footer_line); + } + + public static float calculatePageLength(PDPage page, float footer_line) throws IOException + { + // logger_.debug("Last Page id:" + last_page_id); + // PDPage last_page = (PDPage) allPages.get(0); + PDFPage my_page = new PDFPage(footer_line); + my_page.processStream(page, page.findResources(), page.getContents().getStream()); + return my_page.getMaxPageLength(); + } + +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Placeholder.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Placeholder.java new file mode 100644 index 0000000..9249985 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Placeholder.java @@ -0,0 +1,572 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: Placeholder.java,v 1.5 2006/10/31 08:17:50 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.sound.midi.SysexMessage; + +import org.apache.commons.codec.net.URLCodec; +import org.apache.log4j.Logger; + +import at.knowcenter.wag.egov.egiz.cfg.ConfigLogger; +import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; +import at.knowcenter.wag.egov.egiz.exceptions.PlaceholderException; +import at.knowcenter.wag.exactparser.ByteArrayUtils; + +/** + * Helper class that provides functionality for dealing with placeholders and + * replacements in pdf. + * + * @author wprinz + */ +public abstract class Placeholder +{ + /** + * The logger definition. + */ + private static final Logger logger_ = ConfigLogger.getLogger(Placeholder.class); + + /** + * Escapes the String to be a suitable Literal String.. + * + * @param data + * The String to be escaped. + * @return Returns the escaped PDF String. + */ + public static byte[] escapePDFString(byte[] data) + { + try + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (int i = 0; i < data.length; i++) + { + byte[] escaped_bytes = escapeByte(data[i]); + baos.write(escaped_bytes); + } + return baos.toByteArray(); + } + catch (IOException e) + { + logger_.error(e.getMessage(), e); + return null; + } + } + + /** + * Unescapes the PDF String. + * + * @param data + * The escaped String. + * @return Returns the unescaped String. + */ + public static byte[] unescapePDFString(byte[] data) + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (int i = 0; i < data.length; i++) + { + if (data[i] == '\\' && data[i + 1] == '\\') + { + baos.write('\\'); + i++; + continue; + } + if (data[i] == '\\' && data[i + 1] == '(') + + { + baos.write('('); + i++; + continue; + } + if (data[i] == '\\' && data[i + 1] == ')') + { + baos.write(')'); + i++; + continue; + } + baos.write(data[i]); + } + return baos.toByteArray(); + } + + /** + * Reconstructs the string from a partition of placeholders. + * + * @param pdf + * The PDF to read the string from. + * @param sis + * The list of StringInfo objects that specify the bytes of the + * string in the pdf. + * @return Returns the extracted and reconverted string. + * @throws IOException + * Forwarded exception. + */ + public static String reconstructStringFromPartition(byte[] pdf, List sis, + byte[] enc) throws IOException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + Iterator it = sis.iterator(); + while (it.hasNext()) + { + StringInfo si = (StringInfo) it.next(); + + for (int i = si.string_start; i < si.string_start + si.string_length; i++) + { + if (pdf[i] != 0) + { + baos.write(pdf[i]); + } + } + } + + baos.close(); + byte[] bytes = baos.toByteArray(); + + byte[] unescaped_bytes = unescapePDFString(bytes); + + if (!ByteArrayUtils.compareByteArrays(enc, 0, BinarySignature.ENCODING_WIN) && !ByteArrayUtils.compareByteArrays(enc, 0, BinarySignature.ENCODING_URL)) + { + String enc_str = new String(enc, "US-ASCII"); + logger_.warn("The encoding " + enc_str + " is not known by this application - trying to proceed anyways."); + } + + String text = new String(unescaped_bytes, "windows-1252"); + + String str = text; + if (ByteArrayUtils.compareByteArrays(enc, 0, BinarySignature.ENCODING_URL)) + { + str = unapplyURLEncoding(str); + } + + return str; + } + + /** + * Prepares the given String to a byte array that can be substituted into the + * placeholder. + * + * @param text + * The text to be prepared for substitution. + * @return Returns the prepared byte array. + */ + public static byte[] applyWinAnsiEncoding(String text) + { + // text = text.replace("\\", "\\\\"); + // text = text.replace("(", "\\("); + // text = text.replace(")", "\\)"); + + byte[] replace_bytes; + try + { + replace_bytes = text.getBytes("windows-1252");// CP1252 = WinAnsiEncoding + + // test the opposite way: + // String restored_string = new String (replace_bytes, "windows-1252"); + // if (!restored_string.equals(text)) + // { + // String url_encoded = URLEncoder.encode(text); + // replace_bytes = url_encoded.getBytes("windows-1252"); + // } + } + catch (UnsupportedEncodingException e) + { + logger_.error(e.getMessage(), e); + return null; + } + return replace_bytes; + } + + /** + * Unapplies the WinAnsi encoding. + * + * @param replace_bytes + * The bytes. + * @return Returns the decoded String. + */ + public static String unapplyWinAnsiEncoding(byte[] replace_bytes) + { + try + { + String text = new String(replace_bytes, "windows-1252"); + + return text; + } + catch (UnsupportedEncodingException e) + { + logger_.error(e.getMessage(), e); + return null; + } + + } + + /** + * Applies the URL encoding to the text. + * + * @param text + * The text + * @return Returns the URL and WinAnsi encoded text. + */ + public static byte[] applyURLEncoding(String text) + { + URLCodec utf8_url_codec = new URLCodec("UTF-8"); + String url_encoded = null; + try + { + url_encoded = utf8_url_codec.encode(text, "UTF-8"); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException("Couldn't url encode : " + text, e); + } + // String url_encoded = URLEncoder.encode(text); + return applyWinAnsiEncoding(url_encoded); + } + + /** + * Unapplies the WinAnsi and URL encoding. + * + * @param winansi_str + * The Winansi and URL text. + * @return Returns the decoded text. + */ + public static String unapplyURLEncoding(String winansi_str) + { + URLCodec utf8_url_codec = new URLCodec("UTF-8"); + String url_decoded = null; + try + { + url_decoded = utf8_url_codec.decode(winansi_str, "UTF-8"); + } + catch (Exception e) + { + throw new RuntimeException("Couldn't url decode : " + winansi_str, e); + } + // String url_decoded = URLDecoder.decode(winansi_str); + return url_decoded; + } + + /** + * Restores the String from a previously prepared byte array. + * + * @param pdf_string + * The byte array. + * @return Returns the unprepared String. + */ + public static String unprepareAndUnescapeString(byte[] pdf_string) + { + try + { + String text = new String(pdf_string, "windows-1252"); + + // This makes problems when "+" appears. + // if (isURLEncoded(text)) + // { + // text = URLDecoder.decode(text); + // } + +// text = text.replace("\\)", ")"); +// text = text.replace("\\(", "("); +// text = text.replace("\\\\", "\\"); + + // TODO: replace jdk1.5-code with jdf1.4-code (should be tested) + /* */ + text = text.replaceAll("\\\\\\)", ")"); + text = text.replaceAll("\\\\\\(", "("); + text = text.replaceAll("\\\\\\\\", "\\\\"); + + + return text; + } + catch (UnsupportedEncodingException e) + { + logger_.error(e.getMessage(), e); + return null; + } + } + + /** + * Checks the presence of typical URL encoded characters to tell if the string + * is URL encoded. + * + *

+ * This heuristic checks if there are any non URL encoded characters in the + * String, like ASCII control characters, which aren't allowed in the + * URLEncoding characterset. + *

+ * + * @param text + * The text under suspicion. + * @return Returns true if the String is URL encoded, false otherwise. + */ + protected static boolean isURLEncoded(String text) + { + if (text.indexOf(' ') >= 0) + { + return false; + } + for (int i = 0; i < text.length(); i++) + { + char c = text.charAt(i); + if (0x00 <= c && c <= 0x1f) + { + return false; + } + if (c == 0x7F) + { + return false; + } + if (0x80 <= c) + { + return false; + } + } + return true; + } + + /** + * Tells, if a break can occur behind the given character. + * + * @param character + * The character. + * @return Returns true, if a break may occur behind the character, false + * otherwise. + */ + protected static boolean canBreakAfter(byte character) + { + return (character == ' ' || character == '.' || character == ',' || character == ';' || character == '-' || character == '\n') ; + } + + /** + * Scans the given PDF content stream for literal PDF strings. + * + * @param pdf + * The PDF. + * @param stream_start + * The start of the content stream to be scanned. + * @param stream_next + * The end of the content stream. + * @return Returns a list of StringInfo objects specifying the strings that + * could be found. + */ + public static List parseStrings(byte[] pdf, int stream_start, int stream_next) + { + List strings = new ArrayList(); + StringInfo cur_string = null; + for (int i = stream_start; i < stream_next; i++) + { + byte cur_byte = pdf[i]; + + if (cur_byte == '(' && pdf[i - 1] != '\\') + { + cur_string = new StringInfo(); + cur_string.pdf = pdf; + cur_string.string_start = i + 1; + cur_string.string_length = -1; + // logger_.debug("String start = " + cur_string.string_start); + continue; + } + if (cur_byte == ')' && pdf[i - 1] != '\\') + { + cur_string.string_length = i - cur_string.string_start; + // logger_.debug("String length = " + cur_string.string_length); + strings.add(cur_string); + + cur_string = null; + continue; + } + } + + return strings; + } + + /** + * Escapes the data byte if necessary. + * + *

+ * Before bytes can be written into the pdf Strings, they have to be escaped. + * Special care has to be taken that escaped sequences are not split due to + * line breaks. This could have fatal consequences and usually renders the + * whole document invalid. + *

+ * + * @param data + * The data byte to be escaped. + * @return Returns a new byte array escaping the data byte. If the byte needs + * not to be escaped, this new array will contain only the original + * data byte. + */ + public static byte[] escapeByte(byte data) + { + if (data == '\\') + { + return new byte[] { '\\', '\\' }; + } + if (data == '(') + { + return new byte[] { '\\', '(' }; + } + if (data == ')') + { + return new byte[] { '\\', ')' }; + } + return new byte[] { data }; + } + + /** + * Replaces the placeholder with the given String breaking lines with a given + * tolerance. + * + * @param pdf + * The PDF. + * @param sis + * The list of StringInfo objects describing the positions where the + * String should be filled in. + * @param replace_bytes + * The unescaped bytes to be filled in. Escaping is performed by this + * method. + * @param tolerance + * The tolerance for line wrapping. The tolerance counts from the end + * of a StringInfo backwards to its start. If a word that starts + * within the tolerance doesn't fit, it is wrapped into the next + * line. + * @throws PDFDocumentException + * Forwarded exception. + */ + public static void replacePlaceholderWithTolerance(byte[] pdf, List sis, + byte[] replace_bytes, int tolerance) throws PDFDocumentException + { + try + { + // String rep_str = new String(replace_bytes); + + SplitStrings ss = new SplitStrings(pdf, sis); + + int read_index = 0; + while (read_index < replace_bytes.length) + { + if (!ss.isValidLine()) + { + break; + } + + byte[] token = readToken(replace_bytes, read_index); + // String token_str = new String(token); + byte[] escaped_token = escapeToken(token); + + if (ss.fits(escaped_token)) + { + ss.write(escaped_token); + read_index += token.length; + continue; + } + else + { + if (ss.getAvailable() < tolerance) + { + ss.newline(); + continue; + } + else + { + // break the token + for (; read_index < replace_bytes.length; read_index++) + { + byte data = replace_bytes[read_index]; + + byte[] escaped_data = escapeByte(data); + + if (ss.fits(escaped_data)) + { + ss.write(escaped_data); + } + else + { + ss.newline(); + break; + } + } + continue; + + } + } + } + ss.fillRest(); + + if (read_index < replace_bytes.length) + { + logger_.error("The replace string was longer than the reserved placeholder."); + throw new PlaceholderException(null, replace_bytes.length - read_index); + } + + } + catch (IOException e) + { + throw new PDFDocumentException(201, e); + } + + } + + protected static byte[] readToken(byte[] bytes, int index) + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (; index < bytes.length; index++) + { + byte data = bytes[index]; + + + // byte [] escaped_data = escapeByte(data); + baos.write(data); + + if (canBreakAfter(data)) + { + break; + } + } + + return baos.toByteArray(); + } + + + +protected static byte[] escapeToken(byte[] token) throws IOException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + for (int i = 0; i < token.length; i++) + { + byte[] escaped_data = escapeByte(token[i]); + baos.write(escaped_data); + } + + return baos.toByteArray(); + } +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Pos.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Pos.java new file mode 100644 index 0000000..c0b3ecd --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Pos.java @@ -0,0 +1,70 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: Pos.java,v 1.1 2006/08/25 17:10:08 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +/** + * Encapsulation of a position on a PDF page. + * + * @author wprinz + */ +public class Pos +{ + + public float x; + + public float y; + + public float z; + + /** + * Default constructor. + */ + public Pos() + { + } + + /** + * Constructor that sets the coordinates. + * @param xx + * @param yy + * @param zz + */ + public Pos(float xx, float yy, float zz) + { + this.x = xx; + this.y = yy; + this.z = zz; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + return "(" + this.x + "," + this.y + "," + this.z + ")"; + } + +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PositioningInstruction.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PositioningInstruction.java new file mode 100644 index 0000000..7af4ce7 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PositioningInstruction.java @@ -0,0 +1,139 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +/** + * The positioning instruction holds information of where to place the signature + * block. + * + *

+ * This instruction is given to the PDF writer in order to place the signature. + *

+ * + * @author wprinz + */ +public class PositioningInstruction +{ + + /** + * Tells, if a new plain page should be appended. + * + *

+ * This command is executed before the signature block is positioned according + * to page, x and y. + *

+ */ + protected boolean make_new_page = false; + + /** + * The number of the page on which the signature block is to be placed. If + * specified to make a new page, the number of this newly created page can be + * used here as well. + */ + protected int page = 0; + + /** + * The x coordinate where the upper left corner of the signature block should + * be placed. + */ + protected float x = 0.0f; + + /** + * The y coordinate where the upper left corner of the signature block should + * be placed. + */ + protected float y = 0.0f; + + /** + * + * @param make_new_page + * Tells, if a new plain page should be appended. This command is + * executed before the signature block is positioned according to + * page, x and y. + * @param page + * The number of the page on which the signature block is to be + * placed. If specified to make a new page, the number of this newly + * created page can be used here as well. + * @param x + * The x coordinate where the upper left corner of the signature + * block should be placed. + * @param y + * The y coordinate where the upper left corner of the signature + * block should be placed. + */ + public PositioningInstruction(boolean make_new_page, int page, float x, float y) + { + this.make_new_page = make_new_page; + this.page = page; + this.x = x; + this.y = y; + } + + /** + * Tells, if a new plain page should be appended to the document. + * + * @return Returns true, if a new plain page should be appended. + */ + public boolean isMakeNewPage() + { + return this.make_new_page; + } + + /** + * Returns the page on which the signature is to be printed. + * + * @return Returns the page on which the signature is to be printed. + */ + public int getPage() + { + return this.page; + } + + /** + * Returns the x coordinate where the upper left corner of the signature block + * should be placed. + * + * @return Returns the x coordinate where the upper left corner of the + * signature block should be placed. + */ + public float getX() + { + return this.x; + } + + /** + * Returns the y coordinate where the upper left corner of the signature block + * should be placed. + * + * @return Returns the y coordinate where the upper left corner of the + * signature block should be placed. + */ + public float getY() + { + return this.y; + } + +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ReplaceInfo.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ReplaceInfo.java new file mode 100644 index 0000000..520d987 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ReplaceInfo.java @@ -0,0 +1,93 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: ReplaceInfo.java,v 1.1 2006/08/25 17:10:08 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.Serializable; +import java.util.List; + +import at.knowcenter.wag.egov.egiz.sig.SignatureFieldDefinition; + +/** + * Holds the information requeired to replace a certain value in the document + * completely. + * + * @author wprinz + */ +public class ReplaceInfo implements Serializable +{ + + /** + * SVUID. + */ + private static final long serialVersionUID = 7307210282876750431L; + + /** + * The field definition of this value. + */ + public SignatureFieldDefinition sfd = null; + + /** + * The value itself. + */ + public String value = null; + + /** + * The list of Strings this value must be splitted to. + */ + public List replaces = null; + + /** + * The brev of this value. + */ + public byte[] brev = null; + + /** + * The encoding of this value. + */ + public byte[] enc = null; + + public String toString() { + return "ReplaceInfo [brev=" + (brev != null ? arrayToString(brev, brev.length) : null) + + ", enc=" + (enc != null ? arrayToString(enc, enc.length) : null) + ", sfd=" + sfd + + ", value=" + value + "]"; + } + + private String arrayToString(Object array, int len) { + StringBuffer buffer = new StringBuffer(); + buffer.append("["); + for (int i = 0; i < len; i++) { + if (i > 0) + buffer.append(", "); + if (array instanceof byte[]) + buffer.append(((byte[]) array)[i]); + } + buffer.append("]"); + return buffer.toString(); + } + + + +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/SignatureHolder.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/SignatureHolder.java new file mode 100644 index 0000000..54cac64 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/SignatureHolder.java @@ -0,0 +1,76 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: SignatureHolder.java,v 1.3 2006/10/11 07:57:58 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import at.gv.egiz.pdfas.framework.input.DataSource; +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; + +/** + * Data structure that holds the information of one signature block, which is + * the signed/signable text and the corresponding SignatureObject. + * + *

+ * Signators and Verifiactors should implement own classes for this interface + * that generate the text to be signed from the underlying data. For example a + * binary signature holder could generate the text to be signed by Base64 + * encoding the binary data. Furthermore this allows to cache the text to be + * signed. + *

+ * + * @author wprinz + */ +public interface SignatureHolder +{ + +// /** +// * Returns the signed text (verification) or the to-be-signed signable text +// * (signation). +// * +// *

+// * Note that this text must be the one that was actually signed. This text is +// * directly passed to the connector for signation/verification. No +// * normalization or modification will be / must be done to this text between +// * reading out from the signature holder and passing the text to the +// * connector. +// *

+// * +// * @return Returns the signed text or the to-be-signed signable text. +// */ +// public String getSignedText(); + + /** + * + * @return Returns the SignatureObject containing the issuer, serial number, + * etc. + */ + public SignatureObject getSignatureObject(); + + /** + * Returns the DataSource providing the data. + * @return + */ + public DataSource getDataSource(); +} \ No newline at end of file diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/SplitStrings.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/SplitStrings.java new file mode 100644 index 0000000..be2b2b4 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/SplitStrings.java @@ -0,0 +1,177 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: SplitStrings.java,v 1.1 2006/08/30 14:02:35 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.util.List; + +import org.apache.commons.lang.ArrayUtils; + + +/** + * Class that helps filling out the placeholders. + * + *

+ * This class treats a sequence of placeholder StringInfos like a continuous + * data area that can be filled out regarding the boundaries. + *

+ * + * @author wprinz + */ +public class SplitStrings +{ + /** + * The byte used to fill unused bytes in the placeholders. + */ + + public static final byte FILL_BYTE = ' '; + + /** + * The underlying PDF. + */ + protected byte[] pdf = null; + + /** + * The strings to be filled out. + */ + protected StringInfo[] strings = null; + + /** + * The current string which is written to. + */ + protected int cur_string = 0; + + /** + * The current write position within the current string. + */ + protected int cur_pos = 0; + + /** + * Constructor. + * + * @param pdf + * The underlying PDF. + * @param strings + * The strings to be filled out. + */ + public SplitStrings(byte[] pdf, List strings) + { + this.pdf = pdf; + this.strings = new StringInfo[strings.size()]; + for (int i = 0; i < strings.size(); i++) + { + StringInfo si = (StringInfo) strings.get(i); + this.strings[i] = si; + } + } + + /** + * Returns how many bytes are still available in the current string. + * + * @return Returns the number of bytes that are still available. (positive + * integer, or zero if none are available) + */ + public int getAvailable() + { + return this.strings[this.cur_string].string_length - this.cur_pos; + } + + /** + * Tells, if the whole data would fit into the current string. + * + * @param data + * The data to be matched for fitting + * @return Returns true, if the whole data fits, false otherwise. + */ + public boolean fits(byte[] data) + { + return getAvailable() >= data.length; + } + + /** + * Writes the data into the current string. + * + *

+ * Note that the data must fit in. + *

+ * @param data The data to be written. + */ + public void write(byte[] data) + { + if (!fits(data)) + { + throw new IllegalArgumentException("The data doesn't fit in."); + } + + System.arraycopy(data, 0, this.pdf, this.strings[this.cur_string].string_start + this.cur_pos, data.length); + this.cur_pos += data.length; + + if (data[data.length-1] == '\n') { + this.cur_pos -= 1; // remove \n from output + newline(); + } + } + + /** + * Fills the current string with the fill character and moves on to the next + * string. + * + */ + public void newline() + { + int end = this.strings[this.cur_string].string_start + this.strings[this.cur_string].string_length; + for (int i = this.strings[this.cur_string].string_start + this.cur_pos; i < end; i++) + { + pdf[i] = FILL_BYTE; + } + + this.cur_string++; + this.cur_pos = 0; + } + + /** + * Fills all rest bytes with the fill character. + * + *

+ * This should be called when everything is finished to fill all strings properly. + *

+ */ + public void fillRest() + { + while (this.cur_string < this.strings.length) + { + newline(); + } + } + + /** + * Tells, if the current line is valid. + * @return Returns true, if this is a line that can be written to. + */ + public boolean isValidLine () + { + return this.cur_string < this.strings.length; + } +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/StringInfo.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/StringInfo.java new file mode 100644 index 0000000..1982077 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/StringInfo.java @@ -0,0 +1,106 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: StringInfo.java,v 1.2 2006/10/11 07:57:58 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Specifies a certain data area within the pdf. + * + *

+ * Actually this is a byte range, which is used to hold the placeholder ranges + * for later replacement. + *

+ * + * @author wprinz + */ +public class StringInfo implements Serializable +{ + /** + * SVUID. + */ + private static final long serialVersionUID = 5834801907046737048L; + + protected static Log logger = LogFactory.getLog(StringInfo.class); + + /** + * The PDF document this range belongs to. + */ + public byte[] pdf = null; + + /** + * The start offset of the range. + */ + public int string_start = -1; + + /** + * The length of the range. + */ + public int string_length = -1; + + /** + * Copies the bytes of this range to a new byte array. + * + * @return Returns the new byte array. + */ + public byte[] copyStringBytes() + { + byte[] bytes = new byte[this.string_length]; + System.arraycopy(this.pdf, this.string_start, bytes, 0, this.string_length); + return bytes; + } + + /** + * Converts the range into a String. + * + * @return Returns the String. + * @throws UnsupportedEncodingException + * Forwarded exception. + */ + public String getString(String encoding) throws UnsupportedEncodingException + { + byte[] bytes = copyStringBytes(); + return new String(bytes, encoding); + } + + public String toString() + { + try + { + return "(" + this.string_start + "," + this.string_length + ")" + getString("ISO-8859-1"); + } + catch (UnsupportedEncodingException e) + { + logger.error(e.getMessage(), e); + return "(" + this.string_start + "," + this.string_length + ")"; + } + } + +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/StructContentHelper.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/StructContentHelper.java new file mode 100644 index 0000000..7ca5a0a --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/StructContentHelper.java @@ -0,0 +1,716 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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.knowcenter.wag.egov.egiz.pdf; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.apache.log4j.Logger; + +import at.gv.egiz.pdfas.exceptions.ErrorCode; +import at.knowcenter.wag.egov.egiz.exceptions.PresentableException; +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; + +import com.lowagie.text.Rectangle; +import com.lowagie.text.pdf.PdfArray; +import com.lowagie.text.pdf.PdfContentByte; +import com.lowagie.text.pdf.PdfDictionary; +import com.lowagie.text.pdf.PdfFormField; +import com.lowagie.text.pdf.PdfIndirectReference; +import com.lowagie.text.pdf.PdfName; +import com.lowagie.text.pdf.PdfNumber; +import com.lowagie.text.pdf.PdfObject; +import com.lowagie.text.pdf.PdfStamper; +import com.lowagie.text.pdf.PdfStamperImp; +import com.lowagie.text.pdf.PdfString; +import com.lowagie.text.pdf.PdfTemplate; +import com.lowagie.text.pdfas.StructContentWriter; +import com.lowagie.text.pdfas.StructContentWriterHolder; +import com.lowagie.text.pdfas.UrlInTextFinder; + +/** + * Helper class for writing the structure hierarchy of the signature elements. + * Everything is written with the PdfObject low level API because there is no better support. + * The structured content is only written for structured (==tagged) input documents. The methods have to be called in the + * defined order. The object cannot be reused for several signatures.
+ * See pdf spec "Logical Structure" for details.
+ * The struct writing could be a little more abstracted, but this would include quite some itext extension work. And like this it + * fits better to PDF-AS / wprinz coding style :-( + * @author exthex + * + */ +public class StructContentHelper implements StructContentWriter { + private static final Logger logger = Logger.getLogger(StructContentHelper.class); + + private static final String SIGBLOCK_STRUCT_TYPE = "P"; + private static final PdfName PARENTTREENEXTKEY = new PdfName("ParentTreeNextKey"); + private static final String ALT_TEXT_DEFAULT = "Signaturbildmarke"; + private final static String ALT_TEXT_CONF_KEY = "sigLogoAltText"; + + + private int nextMcid = 0; + /** + * MCID value used for the sigblock marked contend identifier + */ + private int sigBlockMcid =-1; + /** + * MCID value for "Bildmarke" marked content sequence + */ + private int figureMcid = -1; + /** + * MCID value for verify link marked content sequence + */ + private int linkMcid = -1; + private String linkUrlString = null; + private boolean isTagged = false; + private Map linkPosMap = new HashMap(); + private Map tmpMap = new HashMap(); + + private PdfStamper stamper; + private PdfStamperImp stamperImp; + private PdfContentByte content; + private PdfDictionary page; + private PdfNumber parentTreeNextKey = null; + private PdfNumber annotationParentTreeKey = null; + + /** + * Temporary save a pos + */ + private Rectangle tempMarkedPos = null; + /** + * Cell position of the signature verify link overlay + */ + private Rectangle verifyLinkCellPos = null; + + /** + * Kids array (K) of the StructTreeRoot + */ + private PdfArray structTreeRootKids = null; + + /** + * Entry in the ParentTree.Nums array used for sigtable structs + */ + private PdfArray mainParentTreeNumEntry; + + /** + * Create new helper for one signature, and bind it to {@link StructContentWriterHolder} + * for thread local access from itext. + * + * @param stamper + * @param content + * @param pageNr + */ + StructContentHelper(PdfStamper stamper, PdfContentByte content, int pageNr) { + this.stamper = stamper; + this.content = content; + stamperImp = ((PdfStamperImp) stamper.getWriter()); + page = stamper.getReader().getPageN(pageNr); + StructContentWriterHolder.setThreadLocalWriter(this); + } + + /** + * Remove thread local helper + */ + public void removeCurrent() { + StructContentWriterHolder.removeThreadLocalWriter(); + } + + /** + * Prepare structured content for signature block. This method initializes the whole StructTreeRoot stuff. + * @param sigBlockObj + * @throws PresentableException + */ + void prepareStructData(PdfTemplate sigBlockObj) throws PresentableException { + + try { + checkTagging(); + if (!isTagged) { + return; + } + + doAnnoTabOrder(); + + PdfDictionary structTreeRoot = getStructTreeRoot(); + stamperImp.markUsed(structTreeRoot); + + PdfArray parentTreeNums = getParentTreeNums(); + + PdfNumber structParentsNr = page.getAsNumber(PdfName.STRUCTPARENTS); // read StructParents entry from current page + + mainParentTreeNumEntry = obtainParentTreeEntry(structTreeRoot, parentTreeNums, structParentsNr, sigBlockObj); + + nextMcid = mainParentTreeNumEntry.size(); + sigBlockMcid = nextMcid; + nextMcid++; + + this.structTreeRootKids = obtainStructTreeRootKids(structTreeRoot); + + if(this.structTreeRootKids == null) + { + this.structTreeRootKids = this.createStructTreeRootKids(structTreeRoot); + } + + } catch (Exception ex) { + logger.error("error", ex); + throw new PresentableException(ErrorCode.CANNOT_WRITE_PDF, + "error writing structured signature content", ex); + } + } + + PdfArray createStructTreeRootKids(PdfDictionary structTreeRoot) { + PdfArray tmp = new PdfArray(); + structTreeRoot.put(PdfName.K, tmp); + return tmp; + } + + /** + * Create struct data for main signature block + * @throws PresentableException + */ + void buildSigBlockStructData() throws PresentableException { + if (!isTagged) return; + try { + PdfIndirectReference newStructRef = createStructElem(SIGBLOCK_STRUCT_TYPE, new PdfNumber( + sigBlockMcid), getStructTreeRoot().getIndRef()); + + // ADD everything at the end because nothing can be written afterwards + structTreeRootKids.add(newStructRef); + mainParentTreeNumEntry.add(newStructRef); + + stamperImp.markUsed(mainParentTreeNumEntry); + + } catch (Exception ex) { + logger.error("error", ex); + throw new PresentableException(ErrorCode.CANNOT_WRITE_PDF, + "error writing structured signature content", ex); + } + } + + + /** + * Finish struct data for signblock and it's elements (NOT for the external link and annot!) + * @throws PresentableException + */ + void finishMainStructData() throws PresentableException { + try { + if (isTagged && mainParentTreeNumEntry.getIndRef() == null) { + getParentTreeNums().add( + stamper.getWriter().addToBody(mainParentTreeNumEntry).getIndirectReference()); + stamperImp.markUsed(getParentTreeNums()); + stamperImp.markUsed(getStructTreeRoot().getAsDict(PdfName.PARENTTREE)); + } + } catch (Exception ex) { + logger.error("error", ex); + throw new PresentableException(ErrorCode.CANNOT_WRITE_PDF, + "error writing structured signature content", ex); + } + } + + /** + * Build the structured content for the signature logo (bildmarke). {@link #beginFigureContent(PdfContentByte)} and + * {@link #endFigureContent(PdfContentByte)} have to be called before this method to mark the logo in the stream. This + * is done implicitly in the modified itext source (see {@link StructContentWriterHolder}). + * @param so + * @param sigBlockObj + * @throws PresentableException + */ + void buildFigureStructData(SignatureObject so, PdfTemplate sigBlockObj) throws PresentableException { + try { + if (isTagged && isFigureMarked()) { + + PdfDictionary structTreeRoot = getStructTreeRoot(); + PdfIndirectReference mcrRef = createMcrStructElem(this.figureMcid, sigBlockObj.getIndirectReference()); + PdfIndirectReference figureRef = createStructElem("Figure", mcrRef, + getAltText(so.getSignatureTypeDefinition().getType()), structTreeRoot.getIndRef()); + + structTreeRootKids.add(figureRef); + mainParentTreeNumEntry.add(figureRef); + + stamperImp.markUsed(structTreeRootKids); + stamperImp.markUsed(structTreeRoot); + stamperImp.markUsed(mainParentTreeNumEntry); + + } + + } catch (Exception ex) { + logger.error("error", ex); + throw new PresentableException(ErrorCode.CANNOT_WRITE_PDF, + "error writing structured signature content", ex); + } + + } + + /** + * Build the link annotation for the signature verification link and the structured content accordingly.
+ * The tagging does NOT work if the link is placed in a binary signature replace cell (phlengh for this cell)!! + * @param sigBlockObj + * @param atp + * @throws PresentableException + */ + void buildVerifyLinkStructData(PdfTemplate sigBlockObj, ActualTablePos atp) throws PresentableException { + if (!this.isTagged || !this.isLinkMarked() || !isLinkFound()) return; + + try { + PdfNumber parentTreeKey = getNewParentTreeKey(); + + PdfArray annots = obrainAnnotsFromPage(); + + PdfIndirectReference linkAnnotRef = createLinkAnnot(parentTreeKey, atp); + annots.add(linkAnnotRef); + + PdfIndirectReference objr = createObjrStructElem(linkAnnotRef); + PdfIndirectReference mcr = createMcrStructElem(this.linkMcid, sigBlockObj.getIndirectReference()); + + PdfDictionary structTreeRoot = getStructTreeRoot(); + + PdfArray linkKids = new PdfArray(); + PdfIndirectReference linkKidsRef = stamper.getWriter().getPdfIndirectReference(); + + PdfIndirectReference linkRef = createStructElem("Link", linkKidsRef, structTreeRoot.getIndRef()); + linkKids.add(objr); + + PdfIndirectReference span = createStructElem("Span", mcr, linkRef); + linkKids.add(span); + + stamper.getWriter().addToBody(linkKids, linkKidsRef); + structTreeRootKids.add(linkRef); + + // create new entry in ParentTree + PdfArray parentTreeNums = getParentTreeNums(); + parentTreeNums.add(parentTreeKey); + parentTreeNums.add(linkRef); + + stamperImp.markUsed(parentTreeNums); + stamperImp.markUsed(structTreeRoot.getAsDict(PdfName.PARENTTREE)); + stamperImp.markUsed(structTreeRootKids); + stamperImp.markUsed(linkKids); + + stamperImp.markUsed(structTreeRoot); + + } catch (IOException e) { + logger.error("error", e); + throw new PresentableException(ErrorCode.CANNOT_WRITE_PDF, + "error writing structured signature content", e); + } + } + + private boolean isLinkFound() { + return this.linkUrlString != null && this.verifyLinkCellPos != null && this.linkPosMap.size() > 0 && this.linkMcid >= 0; + } + + /** + * Build new StructParent entry for signature annotation. + * @return + */ + PdfNumber buildAdobeSigStructParent() { + if (this.isTagged) { + this.annotationParentTreeKey = getNewParentTreeKey(); + return annotationParentTreeKey; + } else { + return null; + } + } + + /** + * Build and write structured content for adobe signature annotation + * + * @param sigFormField + * @param title + * @throws PresentableException + */ + void buildAdobeSigStruct(PdfFormField sigFormField, String title) throws PresentableException { + if (!isTagged) + return; + try { + + PdfDictionary root = getStructTreeRoot(); + + PdfIndirectReference objrRef = createObjrStructElem(sigFormField.getIndirectReference()); + + PdfIndirectReference adobeSigStructRef = createStructElem("Link", objrRef, root.getIndRef()); + + PdfArray parentTreeNums = getParentTreeNums(); + // create new entry in ParentTree + parentTreeNums.add(annotationParentTreeKey); + parentTreeNums.add(adobeSigStructRef); + + structTreeRootKids.add(adobeSigStructRef); + stamperImp.markUsed(structTreeRootKids); + + stamperImp.markUsed(parentTreeNums); + stamperImp.markUsed(root.getAsDict(PdfName.PARENTTREE)); + + } catch (Exception ex) { + logger.error("error", ex); + throw new PresentableException(ErrorCode.CANNOT_WRITE_PDF, + "error writing structured signature content", ex); + } + + } + + /** + * Start tag for signature block content stream. Place this before the signature block is written to a content stream. + * Call {@link #endSigBlockContent()} afterwards + */ + void beginSigBlockContent() { + if (isTagged) { + content.getInternalBuffer().append(new PdfName(SIGBLOCK_STRUCT_TYPE).getBytes()).append(" <> BDC").append('\n'); + } + } + + /** + * End tag for signature block content stream. Place this after the signature block is written to a content stream + */ + void endSigBlockContent() { + if (isTagged) { + content.endMarkedContentSequence(); + } + } + + /** + * Writes start tag for signature logo marked content sequence. + */ + public void beginFigureContent(PdfContentByte localContent) { + if (isTagged) { + if (!isFigureMarked()) { + this.figureMcid = this.nextMcid++; + localContent.getInternalBuffer().append("/Figure <> BDC\n"); + } else { + logger.warn("cannot tag multiple figures (bildmarken)"); + } + } + } + + /** + * Writes end tag for signature logo marked content sequence. + */ + public void endFigureContent(PdfContentByte localContent) { + if (isTagged && isFigureMarked()) { + localContent.endMarkedContentSequence(); + } + } + + /** + * Writes start tag for verify link marked content sequence. + */ + public void beginLinkContent(PdfContentByte localContent, String urlString) { + // it's called from here com.lowagie.text.pdf.PdfContentByte.showText(String) + + if (isTagged) { + if (!isLinkMarked()) { + this.linkUrlString = urlString; + this.linkMcid = this.nextMcid++; + localContent.getInternalBuffer().append("/Span <> BDC\n"); + } else { + logger.warn("cannot tag multiple verify links"); + } + } + } + + /** + * Writes end tag for verify link marked content sequence. + */ + public void endLinkContent(PdfContentByte localContent) { + if (isTagged && isLinkMarked()) { + localContent.endMarkedContentSequence(); + } + } + + /** + * Implements {@link StructContentWriter#markPos(Rectangle)} + */ + public void markPos(Rectangle pos) { + this.tempMarkedPos = pos; + } + + /** + * Implements {@link StructContentWriter#storeCurrentPosAsLink()} + */ + public void storeCurrentPosAsLink() { + this.verifyLinkCellPos = new Rectangle(this.tempMarkedPos); + } + + public void putVal(String key, Object val) { + tmpMap.put(key, val); + } + + public void storeVals() { + linkPosMap = new HashMap(tmpMap); + } + + /** + * set explicit annotation tab order if missing + */ + private void doAnnoTabOrder() { + if (page.getAsName(new PdfName("Tabs")) == null) { + page.put(new PdfName("Tabs"), PdfName.S); // set explicit annotation TAB order + stamperImp.markUsed(page); + } + } + + private void checkTagging() { + PdfDictionary markDict = stamper.getReader().getCatalog().getAsDict(PdfName.MARKINFO); + if (markDict != null) { + isTagged = markDict.getAsBoolean(PdfName.MARKED).booleanValue(); + } + if (!isTagged) { + logger.debug("input document is not tagged. no structure/wai information is written"); + } + logger.debug("Input is tagged. Writing structure/WAI data."); + } + + + + private PdfIndirectReference createLinkAnnot(PdfNumber structParentNr, ActualTablePos atp) throws IOException { + PdfDictionary linkAnnot = new PdfDictionary(); + + PdfDictionary a = new PdfDictionary(); + a.put(PdfName.S, new PdfName("URI")); + a.put(PdfName.TYPE, PdfName.ACTION); + a.put(PdfName.URI, new PdfString(this.linkUrlString)); + linkAnnot.put(PdfName.A, a); + + PdfDictionary bs = new PdfDictionary(); + bs.put(PdfName.W, new PdfNumber(0)); + linkAnnot.put(PdfName.BS, bs); + linkAnnot.put(PdfName.F, new PdfNumber(4)); + + // iText "converts" 0.0f to an integer, therefore we cannot use 0, not nice... + //linkAnnot.put(PdfName.RECT, new PdfArray(new float[] {0.01f, 0.01f, 0.01f, 0.01f})); + // take cell pos as link pos + linkAnnot.put(PdfName.RECT, new PdfArray(calcLinkPos(atp))); + + linkAnnot.put(PdfName.STRUCTPARENT, structParentNr); + linkAnnot.put(PdfName.SUBTYPE, PdfName.LINK); + + return stamper.getWriter().addToBody(linkAnnot).getIndirectReference(); + } + + + private PdfArray calcLinkPos(ActualTablePos atp) { + PdfArray res = new PdfArray(); + + float downY = atp.y - atp.height; + + float startX = atp.x + this.verifyLinkCellPos.getLeft(); + float yLine = getPosMapVal("yLine"); + float lineHigh = getPosMapVal("maxSize"); + float lineWidth = getPosMapVal("lineWidth"); + UrlInTextFinder finder = (UrlInTextFinder) this.linkPosMap.get("urlFinder"); + + // maybe one could calc the link pos even more exactly with char width counting + // but this should be close enough (see BidiLine.processLine and chunk.getcharwith) + float lineCorr = -2; + float xCorr = 5; + res.add(new PdfNumber(1 + startX + finder.calcLinkPosXStart(lineWidth))); + res.add(new PdfNumber(downY + yLine + lineHigh + lineCorr)); + res.add(new PdfNumber(xCorr + startX + finder.calcLinkPosXEnd(lineWidth))); + res.add(new PdfNumber(downY + yLine + lineCorr)); + + return res; + } + + private float getPosMapVal(String key) { + return ((Float) this.linkPosMap.get(key)).floatValue(); + } + + protected static PdfArray createPdfArrayFromTablePos(ActualTablePos pos) { + return new PdfArray( new float[] {pos.x, pos.y, pos.x + pos.width, pos.y - pos.height}); + } + + private PdfArray obrainAnnotsFromPage() throws IOException { + PdfArray annots = this.page.getAsArray(PdfName.ANNOTS); + if (annots == null) { + annots = new PdfArray(); + page.put(PdfName.ANNOTS, annots); + stamperImp.markUsed(this.page); + stamper.getWriter().addToBody(annots); + } + return annots; + } + + private PdfArray obtainStructTreeRootKids(PdfDictionary structTreeRoot) { + PdfArray rk = null; + PdfObject root_k = structTreeRoot.getDirectObject(PdfName.K); + stamperImp.markUsed(root_k); + if (root_k instanceof PdfDictionary) { + rk = new PdfArray(); + stamperImp.markUsed(structTreeRootKids); + rk.add(root_k.getIndRef()); + structTreeRoot.put(PdfName.K, structTreeRootKids); + + } else if(root_k != null) { // has to be array + rk = (PdfArray) root_k; + } + return rk; + } + + private PdfArray obtainParentTreeEntry(PdfDictionary structTreeRoot, PdfArray parentTreeNums, + PdfNumber structParentsNr, PdfTemplate sigBlockObj) { + int numsIdx = -1; + PdfArray parentTreeEntry = null; + + if (structParentsNr == null) { // no StructParents entry yet, make new one and add new parenttree entry + PdfNumber parentTreeKey = null; + parentTreeNextKey = structTreeRoot.getAsNumber(PARENTTREENEXTKEY); // read next proposed key + if (parentTreeNextKey == null) { // this can be null if a non-perfect pdf creator was at work + // find the next key by counting + int nextI = ((int) parentTreeNums.size() / 2); // know the "Number Trees" data structure from pdf-ref + this.parentTreeNextKey = new PdfNumber(nextI); + structTreeRoot.put(PARENTTREENEXTKEY, this.parentTreeNextKey); // write ParentTreeNextKey entry + } + + parentTreeKey = new PdfNumber(parentTreeNextKey.intValue()); + parentTreeNextKey.increment(); + page.put(PdfName.STRUCTPARENTS, parentTreeKey); // write /StructParents entry to page + structParentsNr = parentTreeKey; + + stamperImp.markUsed(page); + // create new entry in ParentTree + parentTreeNums.add(parentTreeKey); + parentTreeEntry = new PdfArray(); + numsIdx = parentTreeNums.size() - 1; + + } else { // structparents entry already available, find parenttree entry + //parentTreeKey = structParentsNr; + parentTreeNextKey = structTreeRoot.getAsNumber(PARENTTREENEXTKEY); // read next proposed key + if (parentTreeNextKey == null) { // this can be null if a non-perfact pdf creator was at work + // find the next key by counting + int nextI = 0; + if (parentTreeNums != null) { + nextI = ((int) parentTreeNums.size() / 2); + } + this.parentTreeNextKey = new PdfNumber(nextI); + structTreeRoot.put(PARENTTREENEXTKEY, this.parentTreeNextKey); + } + } + + // add Structparents entry to xobject content stream + sigBlockObj.addAttribute(PdfName.STRUCTPARENTS, structParentsNr); + + // find my structParentEntry + if (numsIdx < 0) { + // it's a weird data structure: "number tree", see pdf reference if you really want to understand + + // if the array has no gaps it is easy: + numsIdx = structParentsNr.intValue() * 2; + if (parentTreeNums.getAsNumber(numsIdx).intValue() != structParentsNr.intValue()) { // there seem to be gaps + for (numsIdx = 0; numsIdx < parentTreeNums.size(); numsIdx += 2) { // search manually + if (parentTreeNums.getAsNumber(numsIdx).intValue() == structParentsNr.intValue()) { + break; + } + } + } + numsIdx += 1; + } + if (parentTreeEntry == null) { + parentTreeEntry = parentTreeNums.getAsArray(numsIdx); + } + return parentTreeEntry; + } + +// private PdfIndirectReference createStructElem(String structType, PdfObject kid) throws IOException { +// return createStructElem(structType, kid, null); +// } + + private PdfIndirectReference createStructElem(String structType, PdfObject kid, PdfIndirectReference parentRef) throws IOException { + return createStructElem(structType, kid, null, parentRef); + } + + private PdfIndirectReference createStructElem(String structType, PdfObject kid, String altText, + PdfIndirectReference parentRef) throws IOException { + + PdfDictionary newStruct = new PdfDictionary(); + newStruct.put(PdfName.S, new PdfName(structType)); + //newStruct.put(PdfName.T, new PdfString("PDF-AS Signaturblock"));// eher nicht + if (parentRef != null) { + newStruct.put(PdfName.P, parentRef); + } + newStruct.put(PdfName.TYPE, new PdfName("StructElem")); + + newStruct.put(PdfName.PG, page.getIndRef()); + if (altText != null) { + newStruct.put(PdfName.ALT, new PdfString(altText)); + } + // newStruct.put(PdfName.ALT, new PdfString(getAltText(so.getSignatureTypeDefinition().getType()))); + //newStruct.put(PdfName.K, new PdfNumber(nextMcid)); + newStruct.put(PdfName.K, kid); + + return stamper.getWriter().addToBody(newStruct).getIndirectReference(); + } + + private boolean isFigureMarked() { + return this.figureMcid > -1; + } + + private boolean isLinkMarked() { + return this.linkMcid > -1; + } + + + private PdfNumber getNewParentTreeKey() { + // new parent tree entry + if (parentTreeNextKey == null) { + parentTreeNextKey = getStructTreeRoot().getAsNumber(PARENTTREENEXTKEY); // read next proposed key + } + PdfNumber res = new PdfNumber(parentTreeNextKey.intValue()); + parentTreeNextKey.increment(); + return res; + } + + + private PdfIndirectReference createObjrStructElem(PdfIndirectReference objRef) throws IOException { + PdfDictionary objr = new PdfDictionary(); + objr.put(PdfName.TYPE, new PdfName("OBJR")); + objr.put(PdfName.PG, page.getIndRef()); + objr.put(new PdfName("Obj"), objRef); + + return stamper.getWriter().addToBody(objr).getIndirectReference(); + } + + private PdfIndirectReference createMcrStructElem(int mcid, PdfIndirectReference streamRef) throws IOException { + PdfDictionary objr = new PdfDictionary(); + objr.put(PdfName.TYPE, new PdfName("MCR")); + objr.put(PdfName.PG, page.getIndRef()); + objr.put(PdfName.MCID, new PdfNumber(mcid)); + objr.put(new PdfName("Stm"), streamRef); + + return stamper.getWriter().addToBody(objr).getIndirectReference(); + } + + private PdfArray getParentTreeNums() { + return getStructTreeRoot().getAsDict(PdfName.PARENTTREE).getAsArray(PdfName.NUMS); + } + + private PdfDictionary getStructTreeRoot() { + return stamper.getReader().getCatalog().getAsDict(PdfName.STRUCTTREEROOT); + } + + private static String getAltText(String sigProfile) { + return AdobeSignatureHelper.getDefaultableConfigProperty(sigProfile, ALT_TEXT_CONF_KEY, ALT_TEXT_DEFAULT); + } + +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TablePos.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TablePos.java new file mode 100644 index 0000000..53d6609 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TablePos.java @@ -0,0 +1,262 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: TablePos.java,v 1.1 2006/08/25 17:10:08 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.Serializable; + +import at.gv.egiz.pdfas.exceptions.ErrorCode; +import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; + +/** + * Class that holds the exact position where the table should be written to the + * document. + * + * @author wprinz + * @author mruhmer + */ +public class TablePos implements Serializable +{ + + /** + * SVUID. + */ + private static final long serialVersionUID = -5299027706623518059L; + + /** + * The page on which the block should be displayed. + * + */ + private int page = 0; + + /** + * The x position. + */ + private float pos_x = 0.0f; + + /** + * The y position. + */ + private float pos_y = 0.0f; + + /** + * The width of the block. + */ + private float width = 0.0f; + /** + * The top y position of the footer line. + */ + public float footer_line = 0.0f; + + /** + * The y position. + */ + public String myposstring = ""; + + private boolean newpage = false; + private boolean autoX = true; + private boolean autoY = true; + private boolean autoW = true; + private boolean autoP = true; + + public boolean isXauto() + { + return this.autoX; + } + public boolean isYauto() + { + return this.autoY; + } + public boolean isWauto() + { + return this.autoW; + } + public boolean isPauto() + { + return this.autoP; + } + public boolean isNewPage() + { + return this.newpage; + } + public int getPage() + { + return this.page; + } + public float getFooterLine() + { + //ignore if newpage and y is not auto + if (!this.autoY || this.newpage) + { + return 0.0f; + } + return this.footer_line; + } + public float getPosX() + { + return this.pos_x; + } + public float getPosY() + { + return this.pos_y; + } + public float getWidth() + { + return this.width; + } + public TablePos() + { + //nothing to do --> default + } + + /** + * Constructor. + * + * @param pos_string The pos instruction. + * format : [x:x_algo];[y:y_algo];[w:w_algo][p:p_algo];[f:f_algo] + * x_algo:='auto' ... automatic positioning x + * floatvalue ... absolute x + * y_algo:='auto' ... automatic positioning y + * floatvalue ... absolute y + * w_algo:='auto' ... automatic width + * floatvalue ... absolute width + * p_algo:='auto' ... automatic last page + * 'new' ... new page + * intvalue ... pagenumber + * f_algo floatvalue ... consider footerline (only if y_algo is auto and p_algo is not 'new') + * @throws PDFDocumentException + */ + public TablePos(String pos_string) throws PDFDocumentException + { + //parse posstring and throw exception + //[x:x_algo];[y:y_algo];[w:w_algo][p:p_algo];[f:f_algo] + + String[] strs = pos_string.split(";"); + try + { + for (int cmds = 0;cmds Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: TextualSignature.java,v 1.4 2006/10/31 08:12:45 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; + +import org.apache.log4j.Logger; +import org.pdfbox.pdfparser.PDFParser; +import org.pdfbox.pdmodel.PDDocument; +import org.pdfbox.util.PDFTextStripper; + +import at.gv.egiz.pdfas.exceptions.ErrorCode; +import at.gv.egiz.pdfas.framework.input.PdfDataSource; +import at.gv.egiz.pdfas.performance.PerformanceCounters; +import at.gv.egiz.pdfas.utils.PDFASUtils; +import at.knowcenter.wag.egov.egiz.cfg.ConfigLogger; +import at.knowcenter.wag.egov.egiz.cfg.SettingsReader; +import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; + +import com.lowagie.text.Document; +import com.lowagie.text.DocumentException; +import com.lowagie.text.Rectangle; +import com.lowagie.text.pdf.PdfContentByte; +import com.lowagie.text.pdf.PdfImportedPage; +import com.lowagie.text.pdf.PdfReader; +import com.lowagie.text.pdf.PdfWriter; + +/** + * Contains helper function for textual signatures. + * + * @author wprinz + */ +public class TextualSignature +{ + /** + * The logger definition. + */ + private static final Logger logger_ = ConfigLogger.getLogger(TextualSignature.class); + + /** + * Extracts the document text from a given pdf. + * + * @param pdf_stream + * The pdf_input stream. + * @return Returns the extracted document text. + * @throws PDFDocumentException + * @throws TextExtractionException + * Forwarded exception. + */ + public static String extractTextTextual(PdfDataSource pdfDataSource, String encoding) throws PDFDocumentException + { + PerformanceCounters.textExtractions.increment(); + + try + { + int first_page_rotation = 0; + // logger_.debug("===================================================="); + // logger_.debug("extractText:"); + + // For text extraction, create a temporary object with iText just as the + // one + // created + // when being signed, but of course without adding content. + + + // byte[] bytes = normalizePDF(pdf_stream); + //iText + + byte [] pdf_data = pdfDataSource.getAsByteArray(); + PdfReader reader = new PdfReader(pdf_data); + PDFASUtils.checkReaderPermissions(reader); + //pdf_stream.close(); + + // PERF: PDF normalization needs byte array - this is costy + ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); + + // For some reason the Reader -> ImportPage -> Writer mechanism produces + // problems en mass. + // The text extractor may not be able to extract proper text from + // documents + // created with + // this method (although it works when a Table is appended)... very + // fragile. + + Document document = new Document(); + + PdfWriter writer = PdfWriter.getInstance(document, baos); + document.open(); + + PdfContentByte cb = writer.getDirectContent(); + for (int page_num = 1; page_num <= reader.getNumberOfPages(); page_num++) + { + //Rectangle new_size = reader.getPageSize(page_num); + //logger_.info("PageSize with no rotaion: Pagenr:"+page_num+" Size: "+new_size); + //document.setPageSize(new_size); + Rectangle new_size_withrot =reader.getPageSizeWithRotation(page_num); + if (page_num == 1) + { + //setFirstPageRotation(new_size_withrot.getRotation()); + first_page_rotation = new_size_withrot.getRotation(); + //logger_.info("iText first_page_rotation="+new_size_withrot.getRotation()); + } + //logger_.info("iText set PageSize of page:"+page_num+" to: "+new_size_withrot); + //document.setPageSize(new_size); + document.setPageSize(new_size_withrot); + document.newPage(); + + PdfImportedPage page = writer.getImportedPage(reader, page_num); + // note that this will add an xobject form to the doc. + // the xobject form contains the content of the page. + cb.addTemplate(page, 0, 0); + + // wprinz: debugging + // cb.beginText(); + // cb.setFontAndSize(BaseFont.createFont(BaseFont.HELVETICA, + // BaseFont.CP1252, BaseFont.NOT_EMBEDDED), 14); + // cb.showText("page " + page_num); + // cb.endText(); + // wprinz: end debugging + } + + document.close(); + + // for (int i = 1; i <= reader.getNumberOfPages(); i++) + // { + // Rectangle rect = reader.getBoxSize(i, "bleed"); + // logger_.debug("rect[" + i + "] = " + rect); + // } + + baos.close(); + byte[] normalizedPDF = baos.toByteArray(); + + ByteArrayInputStream bais = new ByteArrayInputStream(normalizedPDF); + //PDFBox-parser + PDFParser parser = new PDFParser(bais); + File temporary_dir = SettingsReader.getTemporaryDirectory(); + //logger_.info("temporary_dir="+temporary_dir.getAbsolutePath()); + parser.setTempDirectory(temporary_dir); + parser.parse(); + + PDDocument doc = parser.getPDDocument(); + //System.out.println("pdfBox.getNumberOfPages()"+doc.getNumberOfPages()); + + PDFTextStripper stripper = new PDFTextStripper(); + stripper.setSortByPosition(false); + stripper.setGetFirstPageRotationFromThis(true); + stripper.setFirstPageRotation(first_page_rotation); + + // stripper.setStartPage(4); + // stripper.setEndPage(4); + logger_.debug("TextualSignator extractTextTextual: Begin stripping text"); + String text; + try { + text = stripper.getText(doc, encoding); + } catch (Exception e) { + throw new PDFDocumentException(ErrorCode.TEXT_EXTRACTION_EXCEPTION, "Unable to extract textual content.", e); + } + logger_.debug("TextualSignator extractTextTextual: Stripping text ended"); + + doc.close(); + //logger_.debug("TextualSignator extractTextTextual="+text); + return text; + + } + catch (IllegalArgumentException e) + { + throw new PDFDocumentException(ErrorCode.DOCUMENT_CANNOT_BE_READ, e); + } + catch (IOException e) + { + throw new PDFDocumentException(ErrorCode.DOCUMENT_CANNOT_BE_READ, e); + } + catch (DocumentException e) + { + throw new PDFDocumentException(ErrorCode.DOCUMENT_CANNOT_BE_READ, e); + } + } + + /** + * Normalizes a given binary PDF to a version PDFbox can handle correctly. + * + *

+ * PDFbox has serious problems with documents that use incremental updates or + * XObject forms. Therefor use this to remove incremental updates and create a + * streamlined document. + *

+ * + *

+ * Note that this has nothing to do with text normalization. It just unifies + * the PDF documents that are fed into PDFbox for text extraction and page + * length determination. + *

+ * + * @param input_pdf + * The input pdf to be normalized. + * @return Returns the normalized pdf. + * @throws IOException + * @throws DocumentException + * @throws PDFDocumentException + */ + public static byte[] normalizePDF(PdfDataSource pdfDataSource) throws IOException, DocumentException, PDFDocumentException + { + //iText + byte [] pdf_data = pdfDataSource.getAsByteArray(); + PdfReader reader = new PdfReader(pdf_data); + PDFASUtils.checkReaderPermissions(reader); + //input_pdf.close(); + + // PERF: PDF Normalization needs byte array + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + // For some reason the Reader -> ImportPage -> Writer mechanism produces + // problems en mass. + // The text extractor may not be able to extract proper text from + // documents + // created with + // this method (although it works when a Table is appended)... very + // fragile. + + Document document = new Document(); + + PdfWriter writer = PdfWriter.getInstance(document, baos); + document.open(); + + PdfContentByte cb = writer.getDirectContent(); + for (int page_num = 1; page_num <= reader.getNumberOfPages(); page_num++) + { + Rectangle new_size_withrot =reader.getPageSizeWithRotation(page_num); + document.setPageSize(new_size_withrot); + document.newPage(); + PdfImportedPage page = writer.getImportedPage(reader, page_num); + // note that this will add an xobject form to the doc. + // the xobject form contains the content of the page. + cb.addTemplate(page, 0, 0); + + // wprinz: debugging + // cb.beginText(); + // cb.setFontAndSize(BaseFont.createFont(BaseFont.HELVETICA, + // BaseFont.CP1252, BaseFont.NOT_EMBEDDED), 14); + // cb.showText("page " + page_num); + // cb.endText(); + // wprinz: end debugging + } + + document.close(); + + // for (int i = 1; i <= reader.getNumberOfPages(); i++) + // { + // Rectangle rect = reader.getBoxSize(i, "bleed"); + // logger_.debug("rect[" + i + "] = " + rect); + // } + + baos.close(); + byte[] normalizedPDF = baos.toByteArray(); + return normalizedPDF; + } +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignatureHolder.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignatureHolder.java new file mode 100644 index 0000000..165de05 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignatureHolder.java @@ -0,0 +1,153 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: TextualSignatureHolder.java,v 1.1 2006/10/11 07:58:17 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.List; + +import at.gv.egiz.pdfas.framework.input.DataSource; +import at.gv.egiz.pdfas.framework.input.TextDataSource; +import at.gv.egiz.pdfas.impl.input.TextDataSourceImpl; +import at.knowcenter.wag.egov.egiz.sig.SignatureObject; + +/** + * Data structure that holds the information of one signature block, which is + * the signed/signable text and the corresponding SignatureObject. + * + * @author wprinz + */ +public class TextualSignatureHolder implements Serializable, SignatureHolder +{ + + /** + * SVUID. + */ + private static final long serialVersionUID = -7208103904479272760L; + +// /** +// * The signed text of this object. +// * +// *

+// * This is the value that will be signed by the Connector. +// *

+// */ +// private String signed_text = null; + + /** + * The signature object. + */ + private SignatureObject signature_object = null; + + private TextDataSource textDataSource = null; + + private int uiBlockEndPos = 0; + + public TextualSignatureHolder(String text, SignatureObject so) + { + //this.signed_text = text; + this.signature_object = so; + this.textDataSource = new TextDataSourceImpl(text); + } + + public TextualSignatureHolder(TextDataSource text, SignatureObject so) + { + //this.signed_text = text; + this.signature_object = so; + this.textDataSource = text; + } + +// /** +// * @see at.knowcenter.wag.egov.egiz.pdf.SignatureHolder#getSignedText() +// */ +// public String getSignedText() +// { +// return this.signed_text; +// } + + /** + * @see at.knowcenter.wag.egov.egiz.pdf.SignatureHolder#getSignatureObject() + */ + public SignatureObject getSignatureObject() + { + return this.signature_object; + } + + /** + * @see at.knowcenter.wag.egov.egiz.pdf.SignatureHolder#getDataSource() + */ + public DataSource getDataSource() + { + return this.textDataSource; + } + + /** + * This is used to replace the DataSource. + * + *

+ * After processing the text, data sources containing large texts are usually replaced by a FileBased ones to save memory. + *

+ * + * @param tds + */ + public void exchangeDataSource(TextDataSource tds) + { + this.textDataSource = tds; + } + + /** + * This is just a shortcut to getDataSource().getText() + * @return Returns the text of this data source. + */ + public String getSignedText() + { + return this.textDataSource.getText(); + } + + public int getUiBlockEndPos() { + return this.uiBlockEndPos; + } + + public void setUiBlockEndPos(int uiBlockEndPos) { + this.uiBlockEndPos = uiBlockEndPos; + } + + public static void mulitSetUiBlockEndPos(List signatureHolders, int uiBlockEndPos) { + for (Iterator it = signatureHolders.iterator(); it.hasNext();) { + SignatureHolder sh = (SignatureHolder) it.next(); + trySetUiBlockEndPos(sh, uiBlockEndPos); + } + } + + public static void trySetUiBlockEndPos(SignatureHolder sh, int uiBlockEndPos ) { + if (sh != null && sh instanceof TextualSignatureHolder) { + ((TextualSignatureHolder)sh).setUiBlockEndPos(uiBlockEndPos); + } + } + + + +} diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Utils.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Utils.java new file mode 100644 index 0000000..519d0b5 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Utils.java @@ -0,0 +1,124 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * 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. + * + * $Id: Utils.java,v 1.3 2006/10/31 08:13:02 wprinz Exp $ + */ +package at.knowcenter.wag.egov.egiz.pdf; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * Abstract class that contains helpful utility functions used by the digital + * signatures. + * + * @author wprinz + */ +public abstract class Utils +{ + + /** + * Extracts the pure content text from a given content stream. + * + *

+ * The pure content text is just an assembly of all strings that occur within the content stream in + * stream order. + * Each of these strings will be set on a new line. + *

+ * + * @param stream_bytes The content stream. + * @return Returns the extracted string. + * @throws IOException Forwarded exception. + */ + public static String extractPureTextFromContentStream( + final byte[] stream_bytes) throws IOException + { + + // logger_.debug("stream_bytes:"); + // logger_.debug(new String(stream_bytes, "US-ASCII")); + // logger_.debug(":end of stream_bytes"); + + final byte OPEN = '('; + final byte CLOSE = ')'; + + StringWriter strwrtr = new StringWriter(); + PrintWriter printer = new PrintWriter(strwrtr); + int open_index = -1; + int close_index = -1; + for (int i = 0; i < stream_bytes.length; i++) + { + if (stream_bytes[i] == OPEN) + { + open_index = i; + continue; + } + if (stream_bytes[i] == CLOSE) + { + close_index = i; + + // logger_.debug("open = " + open_index + ", close = " + + // close_index); + + int len = close_index - open_index - 1; + // logger_.debug("len = " + len); + + byte[] bytes = new byte[len]; + System.arraycopy(stream_bytes, open_index + 1, bytes, 0, len); + + String str = new String(bytes, "ISO-8859-1"); + // logger_.debug("string = " + str); + + printer.println(str); + + continue; + } + } + strwrtr.close(); + String signature_text = new String(strwrtr.getBuffer()); + // logger_.debug(signature_text); + + return signature_text; + } + + public static int max(int[] ints) { + int max = Integer.MIN_VALUE; + for (int i = 0; i < ints.length; i++) { + if (ints[i] > max) { + max = ints[i]; + } + } + return max; + } + + public static String resolveCanonical(String path) { + File file = new File(path); + try { + return file.getCanonicalPath(); + } catch (IOException e) { + return file.getPath(); + } + } + +} -- cgit v1.2.3