You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ti...@apache.org on 2019/01/25 20:30:41 UTC
svn commit: r1852176 - in
/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fdf:
FDFAnnotationStamp.java FDFStampAnnotationAppearance.java
Author: tilman
Date: Fri Jan 25 20:30:41 2019
New Revision: 1852176
URL: http://svn.apache.org/viewvc?rev=1852176&view=rev
Log:
PDFBOX-4437: parse XML from xfdf for stamp annotation /AP, by Andrew Hung
Added:
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fdf/FDFStampAnnotationAppearance.java (with props)
Modified:
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationStamp.java
Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationStamp.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationStamp.java?rev=1852176&r1=1852175&r2=1852176&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationStamp.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationStamp.java Fri Jan 25 20:30:41 2019
@@ -16,19 +16,37 @@
*/
package org.apache.pdfbox.pdmodel.fdf;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.util.Hex;
+
+import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
/**
* This represents a Stamp FDF annotation.
*
* @author Ben Litchfield
+ * @author Andrew Hung
*/
public class FDFAnnotationStamp extends FDFAnnotation
{
+ private static final Log LOG = LogFactory.getLog(FDFAnnotationStamp.class);
+
/**
* COS Model value for SubType entry.
*/
@@ -39,7 +57,6 @@ public class FDFAnnotationStamp extends
*/
public FDFAnnotationStamp()
{
- super();
annot.setName(COSName.SUBTYPE, SUBTYPE);
}
@@ -64,5 +81,74 @@ public class FDFAnnotationStamp extends
{
super(element);
annot.setName(COSName.SUBTYPE, SUBTYPE);
+
+ // PDFBOX-4437: Initialize the Stamp appearance from the XFDF
+ // https://www.immagic.com/eLibrary/ARCHIVES/TECH/ADOBE/A070914X.pdf
+ // appearance is only defined for stamps
+ XPath xpath = XPathFactory.newInstance().newXPath();
+
+ // Set the Appearance to the annotation
+ LOG.debug("Get the DOM Document for the stamp appearance");
+ String base64EncodedAppearance;
+ try
+ {
+ base64EncodedAppearance = xpath.evaluate("appearance", element);
+ }
+ catch (XPathExpressionException e)
+ {
+ // should not happen
+ LOG.error("Error while evaluating XPath expression for appearance: " + e);
+ return;
+ }
+ byte[] decodedAppearanceXML;
+ try
+ {
+ decodedAppearanceXML = Hex.decodeBase64(base64EncodedAppearance);
+ }
+ catch (IllegalArgumentException ex)
+ {
+ LOG.error("Bad base64 encoded appearance ignored", ex);
+ return;
+ }
+ if (base64EncodedAppearance != null && !base64EncodedAppearance.isEmpty())
+ {
+ Document stampAppearance = getStampAppearanceDocument(decodedAppearanceXML);
+
+ Element appearanceEl = stampAppearance.getDocumentElement();
+
+ // Is the root node have tag as DICT, error otherwise
+ if (!"dict".equalsIgnoreCase(appearanceEl.getNodeName()))
+ {
+ throw new IOException("Error while reading stamp document, "
+ + "root should be 'dict' and not '" + appearanceEl.getNodeName() + "'");
+ }
+ LOG.debug("Generate and set the appearance dictionary to the stamp annotation");
+ annot.setItem(COSName.AP, new FDFStampAnnotationAppearance(appearanceEl));
+ }
}
+
+ /**
+ * Parse the <param>xmlString</param> to DOM Document tree from XML content
+ */
+ private Document getStampAppearanceDocument(byte[] xml) throws IOException
+ {
+ try
+ {
+ // Obtain DOM Document instance and create DocumentBuilder with default configuration
+ DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+
+ // Parse the content to Document object
+ return builder.parse(new ByteArrayInputStream(xml));
+ }
+ catch (ParserConfigurationException ex)
+ {
+ LOG.error("Error while converting appearance xml to document: " + ex);
+ throw new IOException(ex);
+ }
+ catch (SAXException ex)
+ {
+ LOG.error("Error while converting appearance xml to document: " + ex);
+ throw new IOException(ex);
+ }
+ }
}
Added: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fdf/FDFStampAnnotationAppearance.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fdf/FDFStampAnnotationAppearance.java?rev=1852176&view=auto
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fdf/FDFStampAnnotationAppearance.java (added)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fdf/FDFStampAnnotationAppearance.java Fri Jan 25 20:30:41 2019
@@ -0,0 +1,305 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.util.Hex;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * Parse the XML for the /AP entry tree.
+ *
+ * @author Andrew Hung
+ */
+class FDFStampAnnotationAppearance implements COSObjectable
+{
+
+ private static final Log LOG = LogFactory.getLog(FDFStampAnnotationAppearance.class);
+
+ private COSDictionary dictionary;
+
+ /**
+ * Default constructor
+ */
+ FDFStampAnnotationAppearance()
+ {
+ dictionary = new COSDictionary();
+ // the N entry is required.
+ dictionary.setItem(COSName.N, new COSStream());
+ }
+
+ /**
+ * Constructor for reading.
+ *
+ * @param dictionary The annotations dictionary.
+ */
+ FDFStampAnnotationAppearance(COSDictionary dictionary)
+ {
+ this.dictionary = dictionary;
+ }
+
+ /**
+ * This will create an Appearance dictionary from an appearance XML document.
+ *
+ * @param fdfXML The XML document that contains the appearance data.
+ */
+ FDFStampAnnotationAppearance(Element appearanceXML) throws IOException
+ {
+ this();
+ LOG.debug("Build dictionary for Appearance based on the appearanceXML");
+
+ NodeList nodeList = appearanceXML.getChildNodes();
+ Node node = null;
+ Element child = null;
+ String parentAttrKey = appearanceXML.getAttribute("KEY");
+ LOG.debug("Appearance Root - tag: " + appearanceXML.getTagName() + ", name: " +
+ appearanceXML.getNodeName() + ", key: " + parentAttrKey + ", children: " +
+ nodeList.getLength());
+
+ // Currently only handles Appearance dictionary (AP key on the root)
+ if ("AP".equals(appearanceXML.getAttribute("KEY")))
+ {
+ for (int i = 0; i < nodeList.getLength(); i++)
+ {
+ node = nodeList.item(i);
+ if (node instanceof Element)
+ {
+ child = (Element) node;
+ if ("STREAM".equalsIgnoreCase(child.getTagName()))
+ {
+ LOG.debug(parentAttrKey +
+ " => Process " + child.getAttribute("KEY") +
+ " item in the dictionary after processing the " +
+ child.getTagName());
+ dictionary.setItem(child.getAttribute("KEY"), parseStreamElement(child));
+ LOG.debug(parentAttrKey + " => Set " + child.getAttribute("KEY"));
+ }
+ else
+ {
+ LOG.warn(parentAttrKey + " => Not handling element: " + child.getTagName());
+ }
+ }
+ }
+ }
+ else
+ {
+ LOG.warn(parentAttrKey + " => Not handling element: " + appearanceXML.getTagName() +
+ " with key: " + appearanceXML.getAttribute("KEY"));
+ }
+ }
+
+ @Override
+ public COSDictionary getCOSObject()
+ {
+ return dictionary;
+ }
+
+ private COSStream parseStreamElement(Element streamEl) throws IOException
+ {
+ COSStream stream = new COSStream();
+
+ NodeList nodeList = streamEl.getChildNodes();
+ Node node;
+ Element child;
+ String childAttrKey;
+ String childAttrVal;
+ String parentAttrKey = streamEl.getAttribute("KEY");
+
+ for (int i = 0; i < nodeList.getLength(); i++)
+ {
+ node = nodeList.item(i);
+ if (node instanceof Element)
+ {
+ child = (Element) node;
+ childAttrKey = child.getAttribute("KEY");
+ childAttrVal = child.getAttribute("VAL");
+ LOG.debug(parentAttrKey + " => reading child: " + child.getTagName() +
+ " with key: " + childAttrKey);
+ if ("INT".equalsIgnoreCase(child.getTagName()))
+ {
+ if (!"Length".equals(childAttrKey))
+ {
+ stream.setInt(COSName.getPDFName(childAttrKey), Integer.parseInt(childAttrVal));
+ LOG.debug(parentAttrKey + " => Set " + childAttrKey + ": " + childAttrVal);
+ }
+ }
+ else if ("NAME".equalsIgnoreCase(child.getTagName()))
+ {
+ stream.setName(COSName.getPDFName(childAttrKey), childAttrVal);
+ LOG.debug(parentAttrKey + " => Set " + childAttrKey + ": " + childAttrVal);
+ }
+ else if ("BOOL".equalsIgnoreCase(child.getTagName()))
+ {
+ stream.setBoolean(COSName.getPDFName(childAttrKey), Boolean.parseBoolean(childAttrVal));
+ LOG.debug(parentAttrKey + " => Set Interpolate: " + childAttrVal);
+ }
+ else if ("ARRAY".equalsIgnoreCase(child.getTagName()))
+ {
+ stream.setItem(COSName.getPDFName(childAttrKey), parseArrayElement(child));
+ LOG.debug(parentAttrKey + " => Set " + childAttrKey);
+ }
+ else if ("DICT".equalsIgnoreCase(child.getTagName()))
+ {
+ stream.setItem(COSName.getPDFName(childAttrKey), parseDictElement(child));
+ LOG.debug(parentAttrKey + " => Set " + childAttrKey);
+ }
+ else if ("STREAM".equalsIgnoreCase(child.getTagName()))
+ {
+ stream.setItem(COSName.getPDFName(childAttrKey), parseStreamElement(child));
+ LOG.debug(parentAttrKey + " => Set " + childAttrKey);
+ }
+ else if ("DATA".equalsIgnoreCase(child.getTagName()))
+ {
+ LOG.debug(parentAttrKey + " => Handling DATA with encoding: " +
+ child.getAttribute("ENCODING"));
+ if ("HEX".equals(child.getAttribute("ENCODING")))
+ {
+ OutputStream os = null;
+ try
+ {
+ os = stream.createRawOutputStream();
+ os.write(Hex.decodeHex(child.getTextContent()));
+ LOG.debug(parentAttrKey + " => Data was streamed");
+ }
+ finally
+ {
+ if (os != null)
+ {
+ os.close();
+ }
+ }
+ }
+ else
+ {
+ LOG.warn(parentAttrKey + " => Not handling element DATA encoding: " +
+ child.getAttribute("ENCODING"));
+ }
+ }
+ else
+ {
+ LOG.warn(parentAttrKey + " => Not handling child element: " + child.getTagName());
+ }
+ }
+ }
+
+ return stream;
+ }
+
+ private COSArray parseArrayElement(Element arrayEl) throws IOException
+ {
+ LOG.debug("Parse " + arrayEl.getAttribute("KEY") + " Array");
+ COSArray array = new COSArray();
+ NodeList nodeList = arrayEl.getElementsByTagName("FIXED");
+ Node node;
+ Element el;
+ String elAttrKey = arrayEl.getAttribute("KEY");
+
+ if ("BBox".equals(elAttrKey))
+ {
+ if (nodeList.getLength() < 4)
+ {
+ throw new IOException("BBox does not have enough coordinates, only has: " +
+ nodeList.getLength());
+ }
+ }
+ else if ("Matrix".equals(elAttrKey))
+ {
+ if (nodeList.getLength() < 6)
+ {
+ throw new IOException("Matrix does not have enough coordinates, only has: " +
+ nodeList.getLength());
+ }
+ }
+
+ LOG.debug("There are " + nodeList.getLength() + " FIXED elements");
+
+ for (int i = 0; i < nodeList.getLength(); i++)
+ {
+ node = nodeList.item(i);
+ if (node instanceof Element)
+ {
+ el = (Element) node;
+ LOG.debug(elAttrKey + " value(" + i + "): " + el.getAttribute("VAL"));
+ array.add(new COSFloat(el.getAttribute("VAL")));
+ }
+ }
+
+ return array;
+ }
+
+ private COSDictionary parseDictElement(Element dictEl) throws IOException
+ {
+ LOG.debug("Parse " + dictEl.getAttribute("KEY") + " Dictionary");
+ COSDictionary dict = new COSDictionary();
+
+ NodeList nodeList = dictEl.getChildNodes();
+ Node node;
+ Element child;
+ String childAttrKey;
+ String childAttrVal;
+ String parentAttrKey = dictEl.getAttribute("KEY");
+
+ for (int i = 0; i < nodeList.getLength(); i++)
+ {
+ node = nodeList.item(i);
+ if (node instanceof Element)
+ {
+ child = (Element) node;
+ childAttrKey = child.getAttribute("KEY");
+ childAttrVal = child.getAttribute("VAL");
+
+ if ("DICT".equals(child.getTagName()))
+ {
+ LOG.debug(parentAttrKey + " => Handling DICT element with key: " + childAttrKey);
+ dict.setItem(COSName.getPDFName(childAttrKey), parseDictElement(child));
+ LOG.debug(parentAttrKey + " => Set " + childAttrKey);
+ }
+ else if ("STREAM".equals(child.getTagName()))
+ {
+ LOG.debug(parentAttrKey + " => Handling STREAM element with key: " + childAttrKey);
+ dict.setItem(COSName.getPDFName(childAttrKey), parseStreamElement(child));
+ }
+ else if ("NAME".equals(child.getTagName()))
+ {
+ LOG.debug(parentAttrKey + " => Handling NAME element with key: " + childAttrKey);
+ dict.setName(COSName.getPDFName(childAttrKey), childAttrVal);
+ LOG.debug(parentAttrKey + " => Set " + childAttrKey + ": " + childAttrVal);
+ }
+ else
+ {
+ LOG.warn(parentAttrKey + " => NOT handling child element: " + child.getTagName());
+ }
+ }
+ }
+
+ return dict;
+ }
+}
Propchange: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fdf/FDFStampAnnotationAppearance.java
------------------------------------------------------------------------------
svn:eol-style = native