From 0e1cc67d55a018bd4f8a134b5a616e040743f5ae Mon Sep 17 00:00:00 2001
From: pdanner <pdanner@7b5415b0-85f9-ee4d-85bd-d5d0c3b42d1c>
Date: Fri, 11 Feb 2011 14:32:40 +0000
Subject: added additional wai/structcontent support: verify link, signature
 logo

git-svn-id: https://joinup.ec.europa.eu/svn/pdf-as/trunk@748 7b5415b0-85f9-ee4d-85bd-d5d0c3b42d1c
---
 .../wag/egov/egiz/pdf/AdobeSignatureHelper.java    |   2 +-
 .../wag/egov/egiz/pdf/BinarySignature.java         |  14 +-
 .../wag/egov/egiz/pdf/StructContentHelper.java     | 618 +++++++++++++++------
 3 files changed, 453 insertions(+), 181 deletions(-)

(limited to 'src/main/java/at/knowcenter/wag')

diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/AdobeSignatureHelper.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/AdobeSignatureHelper.java
index deab953..71b2dcd 100644
--- a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/AdobeSignatureHelper.java
+++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/AdobeSignatureHelper.java
@@ -144,7 +144,7 @@ public class AdobeSignatureHelper {
          HashMap exc = new HashMap();
          exc.put(PdfName.CONTENTS, new Integer(2));
          
-         PdfNumber parentNum = structHelper.buildAnnotStructParent();
+         PdfNumber parentNum = structHelper.buildAdobeSigStructParent();
          if (parentNum != null) {         
             PdfFormField sigField = PdfFormField.createSignature(stamper.getWriter());       
             sap.setSigFormField(sigField);
diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignature.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignature.java
index c34ee68..0465c5d 100644
--- a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignature.java
+++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/BinarySignature.java
@@ -849,8 +849,12 @@ public abstract class BinarySignature
       // 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
+      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.buildMainStructData(table_template);
+      
       pdf_table.writeSelectedRows(0, -1, 0, pdf_table.getTotalHeight(), table_template);
 
       // table_template.moveTo(0, 0);
@@ -863,9 +867,7 @@ public abstract class BinarySignature
 
       // pdf_table.writeSelectedRows(0, -1, SIGNATURE_BORDER / 2,
       // table_position, content);
-   // exthex
-      StructContentHelper structHelper = new StructContentHelper(stamper, content, pi.getPage());
-      structHelper.buildMainStructData(so);
+
             
       structHelper.beginSigBlockContent();
       
@@ -873,6 +875,9 @@ public abstract class BinarySignature
       
       structHelper.endSigBlockContent();
       
+      structHelper.buildFigureStructData(so, table_template);
+      structHelper.buildVerifyLinkStructData(table_template);
+      
 
       ActualTablePos atp = new ActualTablePos();
       atp.page = pi.getPage();
@@ -943,6 +948,7 @@ public abstract class BinarySignature
       //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)
diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/StructContentHelper.java b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/StructContentHelper.java
index 2592b04..e6ad646 100644
--- a/src/main/java/at/knowcenter/wag/egov/egiz/pdf/StructContentHelper.java
+++ b/src/main/java/at/knowcenter/wag/egov/egiz/pdf/StructContentHelper.java
@@ -1,5 +1,7 @@
 package at.knowcenter.wag.egov.egiz.pdf;
 
+import java.io.IOException;
+
 import org.apache.log4j.Logger;
 
 import at.gv.egiz.pdfas.exceptions.ErrorCode;
@@ -17,26 +19,44 @@ 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;
 
 /**
  * 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.
+ * 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 {
+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 = "PDF-AS Signatur";
-
-   private static Logger logger = Logger.getLogger(StructContentHelper.class);
+   private static final String ALT_TEXT_DEFAULT = "Signaturbildmarke";     
+   private final static String ALT_TEXT_CONF_KEY = "sigLogoAltText"; 
    
-   private final static String ALT_TEXT_CONF_KEY = "sigBlockAltText"; 
       
    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 PdfStamper stamper;
@@ -45,142 +65,221 @@ public class StructContentHelper {
    private PdfDictionary page;
    private PdfNumber parentTreeNextKey = null;
    private PdfNumber annotationParentTreeKey = null;
-   private PdfArray rootKids = 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();
    }
    
    /**
-    * Create and write structured content for signature block
+    * Create and write structured content for signature block. This method initialized the whole StructTreeRoot stuff.
+    * @param sigBlockObj 
     * @throws PresentableException
     */
-   void buildMainStructData(SignatureObject so) throws PresentableException {
+   void buildMainStructData(PdfTemplate sigBlockObj) throws PresentableException {
 
       try {
-
-         PdfDictionary markDict = stamper.getReader().getCatalog().getAsDict(PdfName.MARKINFO);
-         if (markDict != null) {
-            isTagged = markDict.getAsBoolean(PdfName.MARKED).booleanValue();
-         }
+         checkTagging();         
          if (!isTagged) {
-            BinarySignature.logger.debug("Input document is not tagged. no structure/wai information is written");
             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);
+         
+         PdfIndirectReference newStructRef = createStructElem(SIGBLOCK_STRUCT_TYPE,  new PdfNumber(sigBlockMcid), structTreeRoot.getIndRef());         
+                  
+         // ADD everything at the end because nothing can be written afterwards         
+         structTreeRootKids.add(newStructRef);
+         mainParentTreeNumEntry.add(newStructRef);         
+
+         stamperImp.markUsed(mainParentTreeNumEntry);
+
+//         if (structParentsNr == null) {
+//            parentTreeNums.add(stamper.getWriter().addToBody(mainParentTreeNumEntry).getIndirectReference());
+//            stamperImp.markUsed(parentTreeNums);
+//            stamperImp.markUsed(structTreeRoot.getAsDict(PdfName.PARENTTREE));
+//         }
 
-         BinarySignature.logger.debug("Input is tagged. Writing structure/WAI data.");
-         PdfDictionary root = getStructTreeRoot();
-         stamperImp.markUsed(root);
+      } catch (Exception ex) {
+         logger.error("error", ex);
+         throw new PresentableException(ErrorCode.CANNOT_WRITE_PDF,
+               "error writing structured signature content", ex);
+      }
+   }
 
-         PdfArray parentTreeNums = getParentTreeNums();
+   /**
+    * 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()) {
 
-         PdfNumber parentTreeKey = null;
-         int numsIdx = -1;
-         PdfNumber strucParNr = page.getAsNumber(PdfName.STRUCTPARENTS);
-         PdfArray parentTreeEntry = null;
-         if (page.getAsName(new PdfName("Tabs")) == null) {
-            page.put(new PdfName("Tabs"), PdfName.S);  // set explicit annotation order
-            stamperImp.markUsed(page);
-         }
+            PdfDictionary structTreeRoot = getStructTreeRoot();
+            PdfIndirectReference mcrRef = createMcrStructElem(this.figureMcid, sigBlockObj.getIndirectReference());
+            PdfIndirectReference figureRef = createStructElem("Figure", mcrRef, 
+                  getAltText(so.getSignatureTypeDefinition().getType()), structTreeRoot.getIndRef());
 
-         if (strucParNr == null) { // no StructParents entry yet, make new one
-            parentTreeNextKey = root.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);               
-               this.parentTreeNextKey = new PdfNumber(nextI);
-               root.put(PARENTTREENEXTKEY, this.parentTreeNextKey);               
-            }
-            
-            parentTreeKey = new PdfNumber(parentTreeNextKey.intValue());
-            parentTreeNextKey.increment();
-            page.put(PdfName.STRUCTPARENTS, parentTreeKey); // write /StructParents entry to page
-
-            stamperImp.markUsed(page);
-            // create new entry in ParentTree
-            parentTreeNums.add(parentTreeKey);
-            parentTreeEntry = new PdfArray();
-            // parentTreeNums.add(stamper.getWriter().addToBody(parentTreeEntry).getIndirectReference());
-            // simpl.markUsed(parentTreeNums);
-            // simpl.markUsed(root.getAsDict(PdfName.PARENTTREE));
-            numsIdx = parentTreeNums.size() - 1;
+            structTreeRootKids.add(figureRef);
+            mainParentTreeNumEntry.add(figureRef);
 
-         } else {
-            parentTreeKey = strucParNr;
-            parentTreeNextKey = root.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);
-               root.put(PARENTTREENEXTKEY, this.parentTreeNextKey);               
-            }            
-         }
+            stamperImp.markUsed(structTreeRootKids);
+            stamperImp.markUsed(structTreeRoot);
+            stamperImp.markUsed(mainParentTreeNumEntry);
 
-         // 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 = strucParNr.intValue() * 2;
-            if (parentTreeNums.getAsNumber(numsIdx).intValue() != strucParNr.intValue()) { // there seem to be gaps
-               for (numsIdx = 0; numsIdx < parentTreeNums.size(); numsIdx += 2) { // search manually
-                  if (parentTreeNums.getAsNumber(numsIdx).intValue() == strucParNr.intValue()) {
-                     break;
-                  }
-               }
-            }
-            numsIdx += 1;
-         }
-         if (parentTreeEntry == null) {
-            parentTreeEntry = parentTreeNums.getAsArray(numsIdx);
          }
-         nextMcid = parentTreeEntry.size();
-
-         PdfObject root_k = root.getDirectObject(PdfName.K);
-         stamperImp.markUsed(root_k);
-         if (root_k instanceof PdfDictionary) {
-            rootKids = new PdfArray();
-            stamperImp.markUsed(rootKids);
-            rootKids.add(root_k.getIndRef());
-            root.put(PdfName.K, rootKids);
-
-         } else { // has to be array
-            rootKids = (PdfArray) root_k;
+         if (isTagged && mainParentTreeNumEntry.getIndRef() == null) {
+            getParentTreeNums().add(stamper.getWriter().addToBody(mainParentTreeNumEntry).getIndirectReference());
+            stamperImp.markUsed(getParentTreeNums());
+            stamperImp.markUsed(getStructTreeRoot().getAsDict(PdfName.PARENTTREE));
          }
-         PdfDictionary newStruct = new PdfDictionary();
-         // simpl.markUsed(newStruct);
-         newStruct.put(PdfName.S, new PdfName("Table"));
-         //newStruct.put(PdfName.T, new PdfString("mein Titel"));
-         newStruct.put(PdfName.P, root.getIndRef());
-         newStruct.put(PdfName.TYPE, new PdfName("StructElem"));
+      } 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
+    * @throws PresentableException 
+    */
+   void buildVerifyLinkStructData(PdfTemplate sigBlockObj) throws PresentableException {
+      if (!this.isTagged || !this.isLinkMarked()) return; 
+
+      try {
+         PdfNumber parentTreeKey = getNewParentTreeKey();
+         
+         PdfArray annots = obrainAnnotsFromPage();         
+         PdfIndirectReference linkAnnotRef = createLinkAnnot(parentTreeKey);         
+         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);
+      }
+   }
+      
+   /**
+    * Build new StructParent entry for signature annotation.
+    * @return
+    */
+   PdfNumber buildAdobeSigStructParent() {
+      if (this.isTagged) {
+         this.annotationParentTreeKey = getNewParentTreeKey();
+         return annotationParentTreeKey;
+      } else {
+         return null;
+      }
+   }   
 
-         newStruct.put(PdfName.PG, page.getIndRef());
-         newStruct.put(PdfName.ALT, new PdfString(getAltText(so.getSignatureTypeDefinition().getType())));
-         newStruct.put(PdfName.K, new PdfNumber(nextMcid));
+   /**
+    * 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();
 
-         // ADD everything at the end because nothing can be written after
+         PdfIndirectReference objrRef = createObjrStructElem(sigFormField.getIndirectReference());
 
-         PdfIndirectReference newStructRef = stamper.getWriter().addToBody(newStruct)
-               .getIndirectReference();
-         rootKids.add(newStructRef);
+         PdfIndirectReference adobeSigStructRef = createStructElem("Link", objrRef, root.getIndRef());
 
-         parentTreeEntry.add(newStructRef);
+         PdfArray parentTreeNums = getParentTreeNums();
+         // create new entry in ParentTree
+         parentTreeNums.add(annotationParentTreeKey);
+         parentTreeNums.add(adobeSigStructRef);
 
-         stamperImp.markUsed(parentTreeEntry);
+         structTreeRootKids.add(adobeSigStructRef);
+         stamperImp.markUsed(structTreeRootKids);
 
-         if (strucParNr == null) {
-            parentTreeNums.add(stamper.getWriter().addToBody(parentTreeEntry)
-                  .getIndirectReference());
-            stamperImp.markUsed(parentTreeNums);
-            stamperImp.markUsed(root.getAsDict(PdfName.PARENTTREE));
-         }
+         stamperImp.markUsed(parentTreeNums);
+         stamperImp.markUsed(root.getAsDict(PdfName.PARENTTREE));
 
       } catch (Exception ex) {
          logger.error("error", ex);
@@ -188,7 +287,7 @@ public class StructContentHelper {
                "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.
@@ -196,8 +295,8 @@ public class StructContentHelper {
     */
    void beginSigBlockContent() {
       if (isTagged) {
-         content.getInternalBuffer().append(new PdfName("Table").getBytes()).append(" <</MCID ")
-               .append(nextMcid).append(">> BDC").append_i('\n');
+         content.getInternalBuffer().append(new PdfName(SIGBLOCK_STRUCT_TYPE).getBytes()).append(" <</MCID ")
+               .append(sigBlockMcid).append(">> BDC").append('\n');
       }
    }
 
@@ -209,85 +308,253 @@ public class StructContentHelper {
          content.endMarkedContentSequence();
       }
    }
-
-
+   
    /**
-    * Build new StructParent entry for signature annotation.
-    * @return
+    * Writes start tag for signature logo marked content sequence.
     */
-   public PdfNumber buildAnnotStructParent() {
-      if (this.isTagged) {
-         // new parent tree entry
-         if (parentTreeNextKey == null) {
-            parentTreeNextKey = getStructTreeRoot().getAsNumber(PARENTTREENEXTKEY); // read next proposed key              
+   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)");
          }
-         annotationParentTreeKey = new PdfNumber(parentTreeNextKey.intValue());
-         parentTreeNextKey.increment();
-
-         return annotationParentTreeKey;
-      } else {
-         return null;
       }
+   }
+   
+   /**
+    * 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");
+         }
+      }      
    }
 
    /**
-    * Build and write structured content for adobe signature anntotation
-    * @param sigFormField
-    * @param title
-    * @throws PresentableException
+    * Writes end tag for verify link marked content sequence.
     */
-   public void buildAdobeSigStruct(PdfFormField sigFormField, String title) throws PresentableException {
-      if (!isTagged)
-         return;
-      try {
-         PdfDictionary root = getStructTreeRoot();
-         PdfDictionary adobeSigStruct = new PdfDictionary();
-         adobeSigStruct.put(PdfName.S, new PdfName("Link")); // TODO what type? link, annot (1.5) Form
-         //adobeSigStruct.put(PdfName.T, new PdfString(title));
-         adobeSigStruct.put(PdfName.P, root.getIndRef());
-         adobeSigStruct.put(PdfName.TYPE, new PdfName("StructElem"));
+   public void endLinkContent(PdfContentByte localContent) {
+      if (isTagged && isLinkMarked()) {
+         localContent.endMarkedContentSequence();
+      }      
+   }   
+   
 
-         adobeSigStruct.put(PdfName.PG, page.getIndRef());
-         // adobeSigStruct.put(PdfName.ALT, new
-         // PdfString("mein feiner signaturtag"));
+   /**
+    * 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);
+      }
+   }
 
-         PdfDictionary objr = new PdfDictionary();
-         objr.put(PdfName.TYPE, new PdfName("OBJR"));
-         objr.put(PdfName.PG, page.getIndRef());
-         objr.put(new PdfName("Obj"), sigFormField.getIndirectReference());
+   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.");
+   }
 
-         PdfIndirectReference objrRef = stamper.getWriter().addToBody(objr).getIndirectReference();
-         adobeSigStruct.put(PdfName.K, objrRef);
-         PdfIndirectReference adobeSigStructRef = stamper.getWriter().addToBody(adobeSigStruct)
-               .getIndirectReference();
+   private PdfIndirectReference createLinkAnnot(PdfNumber structParentNr) 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);
+      
+      // 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}));
+      
+      linkAnnot.put(PdfName.STRUCTPARENT, structParentNr);
+      linkAnnot.put(PdfName.SUBTYPE, PdfName.LINK);
+      
+      return stamper.getWriter().addToBody(linkAnnot).getIndirectReference();
+   }
 
-         // root_a.add(adobeSigStructRef);
+   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;
+   }
 
-         PdfArray parentTreeNums = getParentTreeNums();
+   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 { // 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(annotationParentTreeKey);
-         //PdfArray parentTreeEntry = new PdfArray();
+         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);
+//   }
 
-         // parentTreeNums.add(stamper.getWriter().addToBody(parentTreeEntry).getIndirectReference());
-         // simpl.markUsed(parentTreeNums);
-         // simpl.markUsed(root.getAsDict(PdfName.PARENTTREE));
-         // parentTreeEntry.add(adobeSigStructRef);
-         // parentTreeNums.add(stamper.getWriter().addToBody(parentTreeEntry).getIndirectReference());
-         parentTreeNums.add(adobeSigStructRef);
+   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"));
 
-         rootKids.add(adobeSigStructRef);
-         stamperImp.markUsed(rootKids);
+      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);
 
-         stamperImp.markUsed(parentTreeNums);
-         stamperImp.markUsed(root.getAsDict(PdfName.PARENTTREE));
+      return stamper.getWriter().addToBody(newStruct).getIndirectReference();
+   }   
 
-      } catch (Exception ex) {
-         logger.error("error", ex);
-         throw new PresentableException(ErrorCode.CANNOT_WRITE_PDF,
-               "error writing structured signature content", ex);
+   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() {
@@ -298,9 +565,8 @@ public class StructContentHelper {
       return stamper.getReader().getCatalog().getAsDict(PdfName.STRUCTTREEROOT);
    }   
    
-   public static String getAltText(String sigProfile) {
+   private static String getAltText(String sigProfile) {
       return AdobeSignatureHelper.getDefaultableConfigProperty(sigProfile, ALT_TEXT_CONF_KEY, ALT_TEXT_DEFAULT);
-   }
-   
+   }   
 
 }
-- 
cgit v1.2.3