package at.knowcenter.wag.egov.egiz.pdf; 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.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; /** * Helper class for writing the structure hierarchy of the signature elements. * Everything is written with the PdfObject low level API because there is no better support. * The structured content is only written for structured (==tagged) input documents. The methods have to be called in the * defined order. The object cannot be reused for several signatures.
* See pdf spec "Logical Structure" for details. * @author exthex * */ public class StructContentHelper { 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 final static String ALT_TEXT_CONF_KEY = "sigBlockAltText"; private int nextMcid = 0; private boolean isTagged = false; private PdfStamper stamper; private PdfStamperImp stamperImp; private PdfContentByte content; private PdfDictionary page; private PdfNumber parentTreeNextKey = null; private PdfNumber annotationParentTreeKey = null; private PdfArray rootKids = null; StructContentHelper(PdfStamper stamper, PdfContentByte content, int pageNr) { this.stamper = stamper; this.content = content; stamperImp = ((PdfStamperImp) stamper.getWriter()); page = stamper.getReader().getPageN(pageNr); } /** * Create and write structured content for signature block * @throws PresentableException */ void buildMainStructData(SignatureObject so) throws PresentableException { try { PdfDictionary markDict = stamper.getReader().getCatalog().getAsDict(PdfName.MARKINFO); if (markDict != null) { isTagged = markDict.getAsBoolean(PdfName.MARKED).booleanValue(); } if (!isTagged) { BinarySignature.logger.debug("Input document is not tagged. no structure/wai information is written"); return; } BinarySignature.logger.debug("Input is tagged. Writing structure/WAI data."); PdfDictionary root = getStructTreeRoot(); stamperImp.markUsed(root); PdfArray parentTreeNums = getParentTreeNums(); 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); } 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; } 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); } } // 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; } 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")); newStruct.put(PdfName.PG, page.getIndRef()); // newStruct.put(PdfName.ALT, new PdfString(getAltText(so.getSignatureTypeDefinition().getType()))); newStruct.put(PdfName.K, new PdfNumber(nextMcid)); // ADD everything at the end because nothing can be written after PdfIndirectReference newStructRef = stamper.getWriter().addToBody(newStruct) .getIndirectReference(); rootKids.add(newStructRef); parentTreeEntry.add(newStructRef); stamperImp.markUsed(parentTreeEntry); if (strucParNr == null) { parentTreeNums.add(stamper.getWriter().addToBody(parentTreeEntry) .getIndirectReference()); 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("Table").getBytes()).append(" <> BDC").append_i('\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(); } } /** * Build new StructParent entry for signature annotation. * @return */ public PdfNumber buildAnnotStructParent() { if (this.isTagged) { // new parent tree entry if (parentTreeNextKey == null) { parentTreeNextKey = getStructTreeRoot().getAsNumber(PARENTTREENEXTKEY); // read next proposed key } annotationParentTreeKey = new PdfNumber(parentTreeNextKey.intValue()); parentTreeNextKey.increment(); return annotationParentTreeKey; } else { return null; } } /** * Build and write structured content for adobe signature anntotation * @param sigFormField * @param title * @throws PresentableException */ 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")); adobeSigStruct.put(PdfName.PG, page.getIndRef()); // adobeSigStruct.put(PdfName.ALT, new // PdfString("mein feiner signaturtag")); PdfDictionary objr = new PdfDictionary(); objr.put(PdfName.TYPE, new PdfName("OBJR")); objr.put(PdfName.PG, page.getIndRef()); objr.put(new PdfName("Obj"), sigFormField.getIndirectReference()); PdfIndirectReference objrRef = stamper.getWriter().addToBody(objr).getIndirectReference(); adobeSigStruct.put(PdfName.K, objrRef); PdfIndirectReference adobeSigStructRef = stamper.getWriter().addToBody(adobeSigStruct) .getIndirectReference(); // root_a.add(adobeSigStructRef); PdfArray parentTreeNums = getParentTreeNums(); // create new entry in ParentTree parentTreeNums.add(annotationParentTreeKey); //PdfArray parentTreeEntry = new PdfArray(); // 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); rootKids.add(adobeSigStructRef); stamperImp.markUsed(rootKids); 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); } } private PdfArray getParentTreeNums() { return getStructTreeRoot().getAsDict(PdfName.PARENTTREE).getAsArray(PdfName.NUMS); } private PdfDictionary getStructTreeRoot() { return stamper.getReader().getCatalog().getAsDict(PdfName.STRUCTTREEROOT); } public static String getAltText(String sigProfile) { return AdobeSignatureHelper.getDefaultableConfigProperty(sigProfile, ALT_TEXT_CONF_KEY, ALT_TEXT_DEFAULT); } }