aboutsummaryrefslogtreecommitdiff
path: root/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf
diff options
context:
space:
mode:
Diffstat (limited to 'pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf')
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/AbsoluteTextSignature.java956
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ActualTablePos.java42
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/AdobeSignatureHelper.java272
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinaryBlockInfo.java61
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignature.java2145
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignatureHolder.java185
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/EGIZDate.java284
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/IncrementalUpdateInformation.java252
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/NoSignatureHolder.java84
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ObjectExtractor.java233
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFPage.java377
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureCreation.java176
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObject.java56
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFSignatureObjectIText.java618
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PDFUtilities.java148
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Placeholder.java572
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Pos.java70
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/PositioningInstruction.java139
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/ReplaceInfo.java93
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/SignatureHolder.java76
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/SplitStrings.java177
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/StringInfo.java106
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/StructContentHelper.java716
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TablePos.java262
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignature.java282
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignatureHolder.java153
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/Utils.java124
27 files changed, 8659 insertions, 0 deletions
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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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.
+ *
+ * <p>
+ * These SignatureTypeDefinitions are all text extractable, which means that they define all required fields as visible.
+ * </p>
+ * <p>
+ * This method filters out all SignatureTypeDefinitions that are not text extractable.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * This is usually used to find the indices of the last captions.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * This is an assertive function.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * A vertically largest block has the most found keys.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * A vertically largest block has the most found keys.
+ * </p>
+ *
+ * @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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ */
+package at.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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ */
+package at.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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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.
+ *
+ * <p>
+ * This includes the block itself.
+ * </p>
+ */
+ 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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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.
+ *
+ * <p>
+ * These functions are used to replace parts of the original Egiz plain text
+ * signature mechanism.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * Used to locate and identify the Egiz Dictionary in the document.
+ * </p>
+ */
+ public static final PdfName EGIZ_DICT_NAME = new PdfName("EGIZSigDict");
+
+ /**
+ * The PDFName of the Original Document Size (ODS) field in an Egiz
+ * Dictionary.
+ *
+ * <p>
+ * The ODS must be a positive integral number.
+ * </p>
+ */
+ 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.
+ *
+ * <p>
+ * This must be an indirect reference to the XObject containing the Signature
+ * table.
+ * </p>
+ */
+ 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.
+ *
+ * <p>
+ * The signature text is the text of the Signature XObject.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ // *
+ // * <p>
+ // * 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.
+ // * </p>
+ // * <p>
+ // * 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.
+ // * </p>
+ // * <p>
+ // * This function is intented for being used instead of the "text extraction"
+ // * mechanism used in the plain text Egiz project.
+ // * </p>
+ // *
+ // * @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.
+ // *
+ // * <p>
+ // * The signable text is the text that will be signed or verified afterwards.
+ // * </p>
+ // *
+ // * @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.
+ // *
+ // * <p>
+ // * If the List of SignatureHolders is empty, the document is not signed
+ // * anyways.
+ // * </p>
+ // *
+ // * @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.
+ // *
+ // * <p>
+ // * 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.
+ // * </p>
+ // * <p>
+ // * 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.
+ // * </p>
+ // * <p>
+ // * 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.
+ // * </p>
+ // *
+ // * @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.
+ *
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * This replaces the "dummy numbers" in the egiz dictionary with the correct
+ * values.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * This method collects all variable String fields in a content stream and
+ * orders them according to their start offset.
+ * </p>
+ *
+ * @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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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.
+ *
+ * <p>
+ * The actual signed text is computed by Base64 encoding the binary data when
+ * first requested.
+ * </p>
+ * <p>
+ * The corresponding getters can be used to retrieve the signed document (e.g.
+ * for displaying a preview).
+ * </p>
+ *
+ * @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.
+// *
+// * <p>
+// * This is the value that will be signed by the Connector.
+// * </p>
+// */
+// 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.
+// *
+// * <p>
+// * This usually encodes the binary data of given length in Base64.
+// * </p>
+// *
+// * <p>
+// * This is usually called automatically when the signed text is first
+// * requested.
+// * </p>
+// */
+// 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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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.
+ *
+ * <p>
+ * This is used to compare date values of signatures.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * Usually the date value is one extracted from the value of the SIG_DATE
+ * field.
+ * </p>
+ *
+ * @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.
+// *
+// * <p>
+// * An earlier date is lower than a later date.
+// * </p>
+// * <p>
+// * E.g. a date in 1999 will get a smaller number than a date in 2006.
+// * </p>
+// *
+// * @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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * This is the original document plus the incremental update block.
+ * </p>
+ */
+ 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.
+ *
+ * <p>
+ * For text signature this is the document text.
+ * For binary signature this is the PDF document.
+ * </p>
+ */
+ public SignatureData signature_data = null;
+
+ /**
+ * The SignatureObject containing the variable values after the document text
+ * has been signed.
+ * <p>
+ * These values have to be filled in.
+ * </p>
+ */
+ 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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ */
+package at.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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ */
+package at.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 <code>true</code> 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<dict.size(); i++)
+ {
+ COSName key = (COSName)dict.keyList().get(i);
+ retVal = retVal + "(" + key + ":" + dict.getItem(key) + ") ";
+ }
+ retVal = retVal + "}";
+ return retVal;
+ } catch (Exception e) {
+ return "no detail available";
+ }
+ }
+
+ /**
+ * Find resources (images) in pdf documents
+ * @param objectInfos
+ * @param pageNr
+ * @param page
+ */
+ private static void doExtractFromResources(List objectInfos, int pageNr, PDPage page) {
+ PDResources resources = page.getResources();
+
+ Map images;
+ try {
+ images = resources.getImages();
+ } catch (IOException e) {
+ log.error("Error extracting images from pdf. No NonTextObjectInfo-Images available.", e);
+ return;
+ }
+ if (images != null) {
+ Iterator imageIter = images.keySet().iterator();
+ while (imageIter.hasNext()) {
+ NonTextObjectInfo objInfo = new NonTextObjectInfo();
+ String key = (String) imageIter.next();
+ PDXObjectImage image = (PDXObjectImage) images.get(key);
+
+ objInfo.setHeight(image.getHeight());
+ objInfo.setWidth(image.getWidth());
+ objInfo.setName(key + image.getSuffix());
+ objInfo.setObjectType(NonTextObjectInfo.TYPE_IMAGE);
+ objInfo.setPageNr(pageNr);
+ objectInfos.add(objInfo);
+ objInfo.setSubType(image.getSuffix());
+
+ if (log.isDebugEnabled()) {
+ log.debug("Found non text object: " + objInfo.toString());
+ }
+ }
+ }
+ }
+
+ /**
+ * Extract non textual data from pdf.
+ *
+ * @see org.pdfbox.ExtractImages
+ *
+ * @param document
+ * @return List of {@link NonTextObjectInfo}
+ */
+ public static List extractNonTextInfo(PDDocument document) {
+ // extraction does not work with the normalized pdf from extractTextTextual
+ log.debug("going to extract non text objects");
+ List objectInfos = new ArrayList();
+ List pages = document.getDocumentCatalog().getAllPages();
+ Iterator iter = pages.iterator();
+ int pageNr = 0;
+ while (iter.hasNext()) {
+ pageNr++;
+ PDPage page = (PDPage) iter.next();
+ doExtractFromResources(objectInfos, pageNr, page);
+ doExtractAnnotations(objectInfos, pageNr, page); // does not work with pdf-box 0.7.2 -> 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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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)}<br>
+ * 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("<Do name=\"" + name.getName() + "\">");
+
+ // 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("<Do subtype name=\"" + subtype.getName() + "\">");
+ 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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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. <br>
+ * 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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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:
+ * <ul>
+ * <li>text statements</li>
+ * <li>images</li>
+ * <li>tables</li>
+ * </ul>
+ *
+ * @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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $Id: $
+ */
+package at.knowcenter.wag.egov.egiz.pdf;
+
+/**
+ * The positioning instruction holds information of where to place the signature
+ * block.
+ *
+ * <p>
+ * This instruction is given to the PDF writer in order to place the signature.
+ * </p>
+ *
+ * @author wprinz
+ */
+public class PositioningInstruction
+{
+
+ /**
+ * Tells, if a new plain page should be appended.
+ *
+ * <p>
+ * This command is executed before the signature block is positioned according
+ * to page, x and y.
+ * </p>
+ */
+ 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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @author wprinz
+ */
+public interface SignatureHolder
+{
+
+// /**
+// * Returns the signed text (verification) or the to-be-signed signable text
+// * (signation).
+// *
+// * <p>
+// * 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.
+// * </p>
+// *
+// * @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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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.
+ *
+ * <p>
+ * This class treats a sequence of placeholder StringInfos like a continuous
+ * data area that can be filled out regarding the boundaries.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * Note that the data must fit in.
+ * </p>
+ * @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.
+ *
+ * <p>
+ * This should be called when everything is finished to fill all strings properly.
+ * </p>
+ */
+ 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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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.
+ *
+ * <p>
+ * Actually this is a byte range, which is used to hold the placeholder ranges
+ * for later replacement.
+ * </p>
+ *
+ * @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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ */
+package at.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.<br>
+ * See pdf spec "Logical Structure" for details.<br>
+ * 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 <code>StructTreeRoot</code>
+ */
+ 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.<br>
+ * 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(" <</MCID ")
+ .append(sigBlockMcid).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 <</MCID ").append(this.figureMcid).append(">> 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 <</MCID ").append(this.linkMcid).append(">> 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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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<strs.length;cmds++)
+ {
+
+ String cmd_kvstring = strs[cmds];
+ String[] cmd_kv = cmd_kvstring.split(":");
+ if (cmd_kv.length != 2)
+ {
+ throw new PDFDocumentException(ErrorCode.INVALID_SIGNATURE_POSITION, "Pos string (=" + pos_string + ") is invalid.");
+ }
+ String cmdstr = cmd_kv[0];
+ if (cmdstr.length() != 1)
+ {
+ throw new PDFDocumentException(ErrorCode.INVALID_SIGNATURE_POSITION, "Pos string (=" + pos_string + ") is invalid.");
+ }
+ char command = cmdstr.charAt(0);
+ String commandval= cmd_kv[1];
+ switch (command)
+ {
+ case 'x': {
+ if (!commandval.equalsIgnoreCase("auto"))
+ {
+ float xval= Float.parseFloat(commandval);
+ if (xval<0)
+ {
+ throw new PDFDocumentException(228, "Pos string (x:" + xval + ") is invalid.");
+ }
+ this.pos_x = xval;
+ this.autoX = false;
+ }
+ break;
+ }
+ case 'y': {
+ if (!commandval.equalsIgnoreCase("auto"))
+ {
+ float yval= Float.parseFloat(commandval);
+ if (yval<0)
+ {
+ throw new PDFDocumentException(229, "Pos string (y:" + yval + ") is invalid.");
+ }
+ this.pos_y = yval;
+ this.autoY = false;
+ }
+ break;
+ }
+ case 'w': {
+ if (!commandval.equalsIgnoreCase("auto"))
+ {
+ float wval= Float.parseFloat(commandval);
+ if (wval<=0)
+ {
+ throw new PDFDocumentException(226, "pos.width (w:" + wval + ") must not be lower or equal 0.");
+ }
+ this.width = wval;
+ this.autoW = false;
+ }
+ break;
+ }
+ case 'p': {
+ if (!commandval.equalsIgnoreCase("auto"))
+ {
+ if (commandval.equalsIgnoreCase("new"))
+ {
+ this.newpage = true;
+ }
+ else
+ {
+ int pval = Integer.parseInt(commandval);
+ if (pval<1)
+ {
+ throw new PDFDocumentException(225, "Page (p:" + pval + ") must not be lower than 1.");
+ }
+ this.page = pval;
+ this.autoP = false;
+ }
+ }
+ break;
+ }
+ case 'f': {
+ float flval=Float.parseFloat(commandval);
+ if (flval<0)
+ {
+ throw new PDFDocumentException(ErrorCode.INVALID_SIGNATURE_POSITION, "Pos string (=" + pos_string + ") is invalid.");
+ }
+ this.footer_line = flval;
+ break;
+ }
+ default : {
+ throw new PDFDocumentException(ErrorCode.INVALID_SIGNATURE_POSITION, "Pos string (=" + pos_string + ") is invalid.");
+ }
+ }
+ }
+ this.myposstring=pos_string;
+ }
+ catch (NumberFormatException e)
+ {
+ throw new PDFDocumentException(ErrorCode.INVALID_SIGNATURE_POSITION, "Pos string (=" + pos_string + ") cannot be parsed.");
+ }
+ }
+ public String toString()
+ {
+ String thatsme = "cmd:"+this.myposstring+" pos_x:"+this.pos_x+" pos_y:"+this.pos_y+" page:"+this.page+" width:"+this.width+" footer:"+this.footer_line+"\n "+" autoX:"+this.autoX+" autoY:"+this.autoY+" autoW:"+this.autoW+" Newpage:"+this.newpage+" autoP:"+this.autoP;
+ return thatsme;
+ }
+}
diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignature.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignature.java
new file mode 100644
index 0000000..35a0768
--- /dev/null
+++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/pdf/TextualSignature.java
@@ -0,0 +1,282 @@
+/**
+ * <copyright> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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.
+// *
+// * <p>
+// * This is the value that will be signed by the Connector.
+// * </p>
+// */
+// 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.
+ *
+ * <p>
+ * After processing the text, data sources containing large texts are usually replaced by a FileBased ones to save memory.
+ * </p>
+ *
+ * @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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
+ * joint initiative of the Federal Chancellery Austria and Graz University of
+ * Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ *
+ * $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.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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();
+ }
+ }
+
+}