You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by le...@apache.org on 2013/10/27 14:59:25 UTC

svn commit: r1536136 [2/2] - in /pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox: cos/ pdmodel/ pdmodel/documentinterchange/logicalstructure/ pdmodel/graphics/xobject/ pdmodel/interactive/annotation/ util/

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectForm.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectForm.java?rev=1536136&r1=1536135&r2=1536136&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectForm.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectForm.java Sun Oct 27 13:59:24 2013
@@ -31,7 +31,7 @@ import org.apache.pdfbox.util.Matrix;
 
 /**
  * A form xobject.
- *
+ * 
  * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
  * @version $Revision: 1.6 $
  */
@@ -44,120 +44,118 @@ public class PDXObjectForm extends PDXOb
 
     /**
      * Standard constuctor.
-     *
+     * 
      * @param formStream The XObject is passed as a COSStream.
      */
     public PDXObjectForm(PDStream formStream)
     {
-        super( formStream );
-        getCOSStream().setName( COSName.SUBTYPE, SUB_TYPE );
+        super(formStream);
+        getCOSStream().setName(COSName.SUBTYPE, SUB_TYPE);
     }
 
     /**
      * Standard constuctor.
-     *
+     * 
      * @param formStream The XObject is passed as a COSStream.
      */
     public PDXObjectForm(COSStream formStream)
     {
-        super( formStream );
-        getCOSStream().setName( COSName.SUBTYPE, SUB_TYPE );
+        super(formStream);
+        getCOSStream().setName(COSName.SUBTYPE, SUB_TYPE);
     }
 
     /**
      * This will get the form type, currently 1 is the only form type.
-     *
+     * 
      * @return The form type.
      */
     public int getFormType()
     {
-        return getCOSStream().getInt( "FormType",1 );
+        return getCOSStream().getInt("FormType", 1);
     }
 
     /**
      * Set the form type.
-     *
+     * 
      * @param formType The new form type.
      */
-    public void setFormType( int formType )
+    public void setFormType(int formType)
     {
-        getCOSStream().setInt( "FormType", formType );
+        getCOSStream().setInt("FormType", formType);
     }
 
     /**
-     * This will get the resources at this page and not look up the hierarchy.
-     * This attribute is inheritable, and findResources() should probably used.
-     * This will return null if no resources are available at this level.
-     *
+     * This will get the resources at this page and not look up the hierarchy. This attribute is inheritable, and
+     * findResources() should probably used. This will return null if no resources are available at this level.
+     * 
      * @return The resources at this level in the hierarchy.
      */
     public PDResources getResources()
     {
         PDResources retval = null;
-        COSDictionary resources = (COSDictionary)getCOSStream().getDictionaryObject( COSName.RESOURCES );
-        if( resources != null )
+        COSDictionary resources = (COSDictionary) getCOSStream().getDictionaryObject(COSName.RESOURCES);
+        if (resources != null)
         {
-            retval = new PDResources( resources );
+            retval = new PDResources(resources);
         }
         return retval;
     }
 
     /**
      * This will set the resources for this page.
-     *
+     * 
      * @param resources The new resources for this page.
      */
-    public void setResources( PDResources resources )
+    public void setResources(PDResources resources)
     {
-        getCOSStream().setItem( COSName.RESOURCES, resources );
+        getCOSStream().setItem(COSName.RESOURCES, resources);
     }
 
     /**
-     * An array of four numbers in the form coordinate system (see
-     * below), giving the coordinates of the left, bottom, right, and top edges,
-     * respectively, of the form XObject's bounding box. These boundaries are used
-     * to clip the form XObject and to determine its size for caching.
-     *
+     * An array of four numbers in the form coordinate system (see below), giving the coordinates of the left, bottom,
+     * right, and top edges, respectively, of the form XObject's bounding box. These boundaries are used to clip the
+     * form XObject and to determine its size for caching.
+     * 
      * @return The BBox of the form.
      */
     public PDRectangle getBBox()
     {
         PDRectangle retval = null;
-        COSArray array = (COSArray)getCOSStream().getDictionaryObject( COSName.BBOX );
-        if( array != null )
+        COSArray array = (COSArray) getCOSStream().getDictionaryObject(COSName.BBOX);
+        if (array != null)
         {
-            retval = new PDRectangle( array );
+            retval = new PDRectangle(array);
         }
         return retval;
     }
 
     /**
      * This will set the BBox (bounding box) for this form.
-     *
+     * 
      * @param bbox The new BBox for this form.
      */
     public void setBBox(PDRectangle bbox)
     {
-        if( bbox == null )
+        if (bbox == null)
         {
-            getCOSStream().removeItem( COSName.BBOX );
+            getCOSStream().removeItem(COSName.BBOX);
         }
         else
         {
-            getCOSStream().setItem( COSName.BBOX, bbox.getCOSArray() );
+            getCOSStream().setItem(COSName.BBOX, bbox.getCOSArray());
         }
     }
 
     /**
-     * This will get the optional Matrix of an XObjectForm.
-     * It maps the form space into the user space
+     * This will get the optional Matrix of an XObjectForm. It maps the form space into the user space
+     * 
      * @return the form matrix
      */
     public Matrix getMatrix()
     {
         Matrix retval = null;
-        COSArray array = (COSArray)getCOSStream().getDictionaryObject( COSName.MATRIX );
-        if( array != null )
+        COSArray array = (COSArray) getCOSStream().getDictionaryObject(COSName.MATRIX);
+        if (array != null)
         {
             retval = new Matrix();
             retval.setValue(0, 0, ((COSNumber) array.get(0)).floatValue());
@@ -172,6 +170,7 @@ public class PDXObjectForm extends PDXOb
 
     /**
      * Sets the optional Matrix entry for the form XObject.
+     * 
      * @param transform the transformation matrix
      */
     public void setMatrix(AffineTransform transform)
@@ -181,9 +180,30 @@ public class PDXObjectForm extends PDXOb
         transform.getMatrix(values);
         for (double v : values)
         {
-            matrix.add(new COSFloat((float)v));
+            matrix.add(new COSFloat((float) v));
         }
         getCOSStream().setItem(COSName.MATRIX, matrix);
     }
 
+    /**
+     * This will get the key of this XObjectForm in the structural parent tree. Required if the form XObject contains
+     * marked-content sequences that are structural content items.
+     * 
+     * @return the integer key of the XObjectForm's entry in the structural parent tree
+     */
+    public int getStructParents()
+    {
+        return getCOSStream().getInt(COSName.STRUCT_PARENTS, 0);
+    }
+
+    /**
+     * This will set the key for this XObjectForm in the structural parent tree.
+     * 
+     * @param structParent The new key for this XObjectForm.
+     */
+    public void setStructParents(int structParent)
+    {
+        getCOSStream().setInt(COSName.STRUCT_PARENTS, structParent);
+    }
+
 }

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java?rev=1536136&r1=1536135&r2=1536136&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java Sun Oct 27 13:59:24 2013
@@ -21,28 +21,27 @@ import java.io.IOException;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
 import org.apache.pdfbox.cos.COSDictionary;
 import org.apache.pdfbox.cos.COSName;
-
 import org.apache.pdfbox.pdmodel.PDPage;
 import org.apache.pdfbox.pdmodel.common.COSObjectable;
 import org.apache.pdfbox.pdmodel.common.PDRectangle;
 import org.apache.pdfbox.pdmodel.graphics.color.PDGamma;
 import org.apache.pdfbox.util.BitFlagHelper;
-import org.apache.pdfbox.cos.COSBase;
 
 /**
  * This class represents a PDF annotation.
- *
+ * 
  * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
- * @version $Revision: 1.16 $
+ * 
  */
 public abstract class PDAnnotation implements COSObjectable
 {
     /**
      * Log instance.
      */
-    private static final Log log = LogFactory.getLog(PDAnnotation.class);
+    private static final Log LOG = LogFactory.getLog(PDAnnotation.class);
 
     /**
      * An annotation flag.
@@ -81,88 +80,86 @@ public abstract class PDAnnotation imple
      */
     public static final int FLAG_TOGGLE_NO_VIEW = 1 << 8;
 
-
-
     private COSDictionary dictionary;
 
     /**
      * Create the correct annotation from the base COS object.
-     *
+     * 
      * @param base The COS object that is the annotation.
      * @return The correctly typed annotation object.
      * @throws IOException If there is an error while creating the annotation.
      */
-    // TODO not yet implemented: 
-    // Movie, Screen, PrinterMark, TrapNet, Watermark, 3D, Redact
-    public static PDAnnotation createAnnotation( COSBase base ) throws IOException
+    public static PDAnnotation createAnnotation(COSBase base) throws IOException
     {
         PDAnnotation annot = null;
-        if( base instanceof COSDictionary )
+        if (base instanceof COSDictionary)
         {
-            COSDictionary annotDic = (COSDictionary)base;
-            String subtype = annotDic.getNameAsString( COSName.SUBTYPE );
-            if( PDAnnotationFileAttachment.SUB_TYPE.equals(subtype) )
+            COSDictionary annotDic = (COSDictionary) base;
+            String subtype = annotDic.getNameAsString(COSName.SUBTYPE);
+            if (PDAnnotationFileAttachment.SUB_TYPE.equals(subtype))
             {
-                annot = new PDAnnotationFileAttachment( annotDic );
+                annot = new PDAnnotationFileAttachment(annotDic);
             }
-            else if( PDAnnotationLine.SUB_TYPE.equals(subtype) )
+            else if (PDAnnotationLine.SUB_TYPE.equals(subtype))
             {
-                annot = new PDAnnotationLine( annotDic );
+                annot = new PDAnnotationLine(annotDic);
             }
-            else if( PDAnnotationLink.SUB_TYPE.equals(subtype) )
+            else if (PDAnnotationLink.SUB_TYPE.equals(subtype))
             {
                 annot = new PDAnnotationLink(annotDic);
             }
-            else if( PDAnnotationPopup.SUB_TYPE.equals(subtype) )
+            else if (PDAnnotationPopup.SUB_TYPE.equals(subtype))
             {
                 annot = new PDAnnotationPopup(annotDic);
             }
-            else if( PDAnnotationRubberStamp.SUB_TYPE.equals(subtype) )
+            else if (PDAnnotationRubberStamp.SUB_TYPE.equals(subtype))
             {
                 annot = new PDAnnotationRubberStamp(annotDic);
             }
-            else if( PDAnnotationSquareCircle.SUB_TYPE_SQUARE.equals(subtype) ||
-                    PDAnnotationSquareCircle.SUB_TYPE_CIRCLE.equals(subtype) )
+            else if (PDAnnotationSquareCircle.SUB_TYPE_SQUARE.equals(subtype)
+                    || PDAnnotationSquareCircle.SUB_TYPE_CIRCLE.equals(subtype))
             {
-                annot = new PDAnnotationSquareCircle( annotDic );
+                annot = new PDAnnotationSquareCircle(annotDic);
             }
-            else if( PDAnnotationText.SUB_TYPE.equals(subtype) )
+            else if (PDAnnotationText.SUB_TYPE.equals(subtype))
             {
-                annot = new PDAnnotationText( annotDic);
+                annot = new PDAnnotationText(annotDic);
             }
-            else if( PDAnnotationTextMarkup.SUB_TYPE_HIGHLIGHT.equals(subtype) ||
-                    PDAnnotationTextMarkup.SUB_TYPE_UNDERLINE.equals(subtype) ||
-                    PDAnnotationTextMarkup.SUB_TYPE_SQUIGGLY.equals(subtype) ||
-                    PDAnnotationTextMarkup.SUB_TYPE_STRIKEOUT.equals(subtype) )
+            else if (PDAnnotationTextMarkup.SUB_TYPE_HIGHLIGHT.equals(subtype)
+                    || PDAnnotationTextMarkup.SUB_TYPE_UNDERLINE.equals(subtype)
+                    || PDAnnotationTextMarkup.SUB_TYPE_SQUIGGLY.equals(subtype)
+                    || PDAnnotationTextMarkup.SUB_TYPE_STRIKEOUT.equals(subtype))
             {
-                annot = new PDAnnotationTextMarkup( annotDic );
+                annot = new PDAnnotationTextMarkup(annotDic);
             }
-            else if( PDAnnotationLink.SUB_TYPE.equals(subtype) )
+            else if (PDAnnotationLink.SUB_TYPE.equals(subtype))
             {
-                annot = new PDAnnotationLink( annotDic );
+                annot = new PDAnnotationLink(annotDic);
             }
-            else if( PDAnnotationWidget.SUB_TYPE.equals(subtype) )
+            else if (PDAnnotationWidget.SUB_TYPE.equals(subtype))
             {
-               annot = new PDAnnotationWidget( annotDic );
+                annot = new PDAnnotationWidget(annotDic);
             }
-            else if( PDAnnotationMarkup.SUB_TYPE_FREETEXT.equals(subtype) ||
-                    PDAnnotationMarkup.SUB_TYPE_POLYGON.equals(subtype) ||
-                    PDAnnotationMarkup.SUB_TYPE_POLYLINE.equals(subtype) ||
-                    PDAnnotationMarkup.SUB_TYPE_CARET.equals(subtype) ||
-                    PDAnnotationMarkup.SUB_TYPE_INK.equals(subtype) ||
-                    PDAnnotationMarkup.SUB_TYPE_SOUND.equals(subtype) )
+            else if (PDAnnotationMarkup.SUB_TYPE_FREETEXT.equals(subtype)
+                    || PDAnnotationMarkup.SUB_TYPE_POLYGON.equals(subtype)
+                    || PDAnnotationMarkup.SUB_TYPE_POLYLINE.equals(subtype)
+                    || PDAnnotationMarkup.SUB_TYPE_CARET.equals(subtype)
+                    || PDAnnotationMarkup.SUB_TYPE_INK.equals(subtype)
+                    || PDAnnotationMarkup.SUB_TYPE_SOUND.equals(subtype))
             {
-                annot = new PDAnnotationMarkup( annotDic );
+                annot = new PDAnnotationMarkup(annotDic);
             }
             else
             {
-                annot = new PDAnnotationUnknown( annotDic );
-                log.debug("Unknown or unsupported annotation subtype "+subtype);
+                // TODO not yet implemented:
+                // Movie, Screen, PrinterMark, TrapNet, Watermark, 3D, Redact
+                annot = new PDAnnotationUnknown(annotDic);
+                LOG.debug("Unknown or unsupported annotation subtype " + subtype);
             }
         }
         else
         {
-            throw new IOException( "Error: Unknown annotation type " + base );
+            throw new IOException("Error: Unknown annotation type " + base);
         }
 
         return annot;
@@ -174,21 +171,22 @@ public abstract class PDAnnotation imple
     public PDAnnotation()
     {
         dictionary = new COSDictionary();
-        dictionary.setItem( COSName.TYPE, COSName.ANNOT );
+        dictionary.setItem(COSName.TYPE, COSName.ANNOT);
     }
 
     /**
      * Constructor.
-     *
+     * 
      * @param dict The annotations dictionary.
      */
-    public PDAnnotation( COSDictionary dict )
+    public PDAnnotation(COSDictionary dict)
     {
         dictionary = dict;
     }
 
     /**
      * returns the dictionary.
+     * 
      * @return the dictionary
      */
     public COSDictionary getDictionary()
@@ -197,57 +195,56 @@ public abstract class PDAnnotation imple
     }
 
     /**
-     * The annotation rectangle, defining the location of the annotation
-     * on the page in default user space units.  This is usually required and should
-     * not return null on valid PDF documents.  But where this is a parent form field
-     * with children, such as radio button collections then the rectangle will be null.
-     *
+     * The annotation rectangle, defining the location of the annotation on the page in default user space units. This
+     * is usually required and should not return null on valid PDF documents. But where this is a parent form field with
+     * children, such as radio button collections then the rectangle will be null.
+     * 
      * @return The Rect value of this annotation.
      */
     public PDRectangle getRectangle()
     {
-        COSArray rectArray = (COSArray)dictionary.getDictionaryObject( COSName.RECT );
+        COSArray rectArray = (COSArray) dictionary.getDictionaryObject(COSName.RECT);
         PDRectangle rectangle = null;
-        if( rectArray != null )
+        if (rectArray != null)
         {
-            rectangle = new PDRectangle( rectArray );
+            rectangle = new PDRectangle(rectArray);
         }
         return rectangle;
     }
 
     /**
      * This will set the rectangle for this annotation.
-     *
+     * 
      * @param rectangle The new rectangle values.
      */
-    public void setRectangle( PDRectangle rectangle )
+    public void setRectangle(PDRectangle rectangle)
     {
-        dictionary.setItem( COSName.RECT, rectangle.getCOSArray() );
+        dictionary.setItem(COSName.RECT, rectangle.getCOSArray());
     }
 
-   /**
+    /**
      * This will get the flags for this field.
-     *
+     * 
      * @return flags The set of flags.
      */
     public int getAnnotationFlags()
     {
-        return getDictionary().getInt( COSName.F, 0 );
+        return getDictionary().getInt(COSName.F, 0);
     }
 
     /**
      * This will set the flags for this field.
-     *
+     * 
      * @param flags The new flags.
      */
-    public void setAnnotationFlags( int flags )
+    public void setAnnotationFlags(int flags)
     {
-        getDictionary().setInt( COSName.F, flags );
+        getDictionary().setInt(COSName.F, flags);
     }
 
     /**
      * Interface method for COSObjectable.
-     *
+     * 
      * @return This object as a standard COS object.
      */
     public COSBase getCOSObject()
@@ -257,14 +254,14 @@ public abstract class PDAnnotation imple
 
     /**
      * This will get the name of the current appearance stream if any.
-     *
+     * 
      * @return The name of the appearance stream.
      */
     public String getAppearanceStream()
     {
         String retval = null;
-        COSName name = (COSName)getDictionary().getDictionaryObject( COSName.AS );
-        if( name != null )
+        COSName name = (COSName) getDictionary().getDictionaryObject(COSName.AS);
+        if (name != null)
         {
             retval = name.getName();
         }
@@ -273,236 +270,235 @@ public abstract class PDAnnotation imple
 
     /**
      * This will set the annotations appearance stream name.
-     *
+     * 
      * @param as The name of the appearance stream.
      */
-    public void setAppearanceStream( String as )
+    public void setAppearanceStream(String as)
     {
-        if( as == null )
+        if (as == null)
         {
-            getDictionary().removeItem( COSName.AS );
+            getDictionary().removeItem(COSName.AS);
         }
         else
         {
-            getDictionary().setItem( COSName.AS, COSName.getPDFName( as ) );
+            getDictionary().setItem(COSName.AS, COSName.getPDFName(as));
         }
     }
 
     /**
-     * This will get the appearance dictionary associated with this annotation.
-     * This may return null.
-     *
+     * This will get the appearance dictionary associated with this annotation. This may return null.
+     * 
      * @return This annotations appearance.
      */
     public PDAppearanceDictionary getAppearance()
     {
         PDAppearanceDictionary ap = null;
-        COSDictionary apDic = (COSDictionary)dictionary.getDictionaryObject( COSName.AP );
-        if( apDic != null )
+        COSDictionary apDic = (COSDictionary) dictionary.getDictionaryObject(COSName.AP);
+        if (apDic != null)
         {
-            ap = new PDAppearanceDictionary( apDic );
+            ap = new PDAppearanceDictionary(apDic);
         }
         return ap;
     }
 
     /**
      * This will set the appearance associated with this annotation.
-     *
+     * 
      * @param appearance The appearance dictionary for this annotation.
      */
-    public void setAppearance( PDAppearanceDictionary appearance )
+    public void setAppearance(PDAppearanceDictionary appearance)
     {
         COSDictionary ap = null;
-        if( appearance != null )
+        if (appearance != null)
         {
             ap = appearance.getDictionary();
         }
-        dictionary.setItem( COSName.AP, ap );
+        dictionary.setItem(COSName.AP, ap);
     }
 
     /**
      * Get the invisible flag.
-     *
+     * 
      * @return The invisible flag.
      */
     public boolean isInvisible()
     {
-        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_INVISIBLE );
+        return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_INVISIBLE);
     }
 
     /**
      * Set the invisible flag.
-     *
+     * 
      * @param invisible The new invisible flag.
      */
-    public void setInvisible( boolean invisible )
+    public void setInvisible(boolean invisible)
     {
-        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_INVISIBLE, invisible );
+        BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_INVISIBLE, invisible);
     }
 
     /**
      * Get the hidden flag.
-     *
+     * 
      * @return The hidden flag.
      */
     public boolean isHidden()
     {
-        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_HIDDEN );
+        return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_HIDDEN);
     }
 
     /**
      * Set the hidden flag.
-     *
+     * 
      * @param hidden The new hidden flag.
      */
-    public void setHidden( boolean hidden )
+    public void setHidden(boolean hidden)
     {
-        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_HIDDEN, hidden );
+        BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_HIDDEN, hidden);
     }
 
     /**
      * Get the printed flag.
-     *
+     * 
      * @return The printed flag.
      */
     public boolean isPrinted()
     {
-        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_PRINTED );
+        return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_PRINTED);
     }
 
     /**
      * Set the printed flag.
-     *
+     * 
      * @param printed The new printed flag.
      */
-    public void setPrinted( boolean printed )
+    public void setPrinted(boolean printed)
     {
-        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_PRINTED, printed );
+        BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_PRINTED, printed);
     }
 
     /**
      * Get the noZoom flag.
-     *
+     * 
      * @return The noZoom flag.
      */
     public boolean isNoZoom()
     {
-        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_NO_ZOOM );
+        return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_NO_ZOOM);
     }
 
     /**
      * Set the noZoom flag.
-     *
+     * 
      * @param noZoom The new noZoom flag.
      */
-    public void setNoZoom( boolean noZoom )
+    public void setNoZoom(boolean noZoom)
     {
-        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_NO_ZOOM, noZoom );
+        BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_NO_ZOOM, noZoom);
     }
 
     /**
      * Get the noRotate flag.
-     *
+     * 
      * @return The noRotate flag.
      */
     public boolean isNoRotate()
     {
-        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_NO_ROTATE );
+        return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_NO_ROTATE);
     }
 
     /**
      * Set the noRotate flag.
-     *
+     * 
      * @param noRotate The new noRotate flag.
      */
-    public void setNoRotate( boolean noRotate )
+    public void setNoRotate(boolean noRotate)
     {
-        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_NO_ROTATE, noRotate );
+        BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_NO_ROTATE, noRotate);
     }
 
     /**
      * Get the noView flag.
-     *
+     * 
      * @return The noView flag.
      */
     public boolean isNoView()
     {
-        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_NO_VIEW );
+        return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_NO_VIEW);
     }
 
     /**
      * Set the noView flag.
-     *
+     * 
      * @param noView The new noView flag.
      */
-    public void setNoView( boolean noView )
+    public void setNoView(boolean noView)
     {
-        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_NO_VIEW, noView );
+        BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_NO_VIEW, noView);
     }
 
     /**
      * Get the readOnly flag.
-     *
+     * 
      * @return The readOnly flag.
      */
     public boolean isReadOnly()
     {
-        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_READ_ONLY );
+        return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_READ_ONLY);
     }
 
     /**
      * Set the readOnly flag.
-     *
+     * 
      * @param readOnly The new readOnly flag.
      */
-    public void setReadOnly( boolean readOnly )
+    public void setReadOnly(boolean readOnly)
     {
-        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_READ_ONLY, readOnly );
+        BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_READ_ONLY, readOnly);
     }
 
     /**
      * Get the locked flag.
-     *
+     * 
      * @return The locked flag.
      */
     public boolean isLocked()
     {
-        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_LOCKED );
+        return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_LOCKED);
     }
 
     /**
      * Set the locked flag.
-     *
+     * 
      * @param locked The new locked flag.
      */
-    public void setLocked( boolean locked )
+    public void setLocked(boolean locked)
     {
-        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_LOCKED, locked );
+        BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_LOCKED, locked);
     }
 
     /**
      * Get the toggleNoView flag.
-     *
+     * 
      * @return The toggleNoView flag.
      */
     public boolean isToggleNoView()
     {
-        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_TOGGLE_NO_VIEW );
+        return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_TOGGLE_NO_VIEW);
     }
 
     /**
      * Set the toggleNoView flag.
-     *
+     * 
      * @param toggleNoView The new toggleNoView flag.
      */
-    public void setToggleNoView( boolean toggleNoView )
+    public void setToggleNoView(boolean toggleNoView)
     {
-        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_TOGGLE_NO_VIEW, toggleNoView );
+        BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_TOGGLE_NO_VIEW, toggleNoView);
     }
 
     /**
      * Get the "contents" of the field.
-     *
+     * 
      * @return the value of the contents.
      */
     public String getContents()
@@ -512,93 +508,105 @@ public abstract class PDAnnotation imple
 
     /**
      * Set the "contents" of the field.
-     *
+     * 
      * @param value the value of the contents.
      */
-    public void setContents( String value)
+    public void setContents(String value)
     {
         dictionary.setString(COSName.CONTENTS, value);
     }
 
     /**
      * This will retrieve the date and time the annotation was modified.
-     *
+     * 
      * @return the modified date/time (often in date format, but can be an arbitary string).
      */
     public String getModifiedDate()
     {
-        return getDictionary().getString( COSName.M );
+        return getDictionary().getString(COSName.M);
     }
 
     /**
      * This will set the the date and time the annotation was modified.
-     *
-     * @param m
-     *            the date and time the annotation was created.
+     * 
+     * @param m the date and time the annotation was created.
      */
-    public void setModifiedDate( String m )
+    public void setModifiedDate(String m)
     {
-        getDictionary().setString( COSName.M, m );
+        getDictionary().setString(COSName.M, m);
     }
 
     /**
-     * This will get the name, a string intended to uniquely identify each annotation
-     * within a page. Not to be confused with some annotations Name entry which
-     * impact the default image drawn for them.
-     *
+     * This will get the name, a string intended to uniquely identify each annotation within a page. Not to be confused
+     * with some annotations Name entry which impact the default image drawn for them.
+     * 
      * @return The identifying name for the Annotation.
      */
     public String getAnnotationName()
     {
-        return getDictionary().getString( COSName.NM );
+        return getDictionary().getString(COSName.NM);
     }
 
     /**
-     * This will set the name, a string intended to uniquely identify each annotation
-     * within a page. Not to be confused with some annotations Name entry which
-     * impact the default image drawn for them.
-     *
+     * This will set the name, a string intended to uniquely identify each annotation within a page. Not to be confused
+     * with some annotations Name entry which impact the default image drawn for them.
+     * 
      * @param nm The identifying name for the annotation.
      */
-    public void setAnnotationName( String nm )
+    public void setAnnotationName(String nm)
     {
-        getDictionary().setString( COSName.NM, nm );
+        getDictionary().setString(COSName.NM, nm);
     }
 
     /**
-     * This will set the colour used in drawing various elements.
-     * As of PDF 1.6 these are : Background of icon when closed
-     *                           Title bar of popup window
-     *                           Border of a link annotation
-     *
+     * This will get the key of this annotation in the structural parent tree.
+     * 
+     * @return the integer key of the annotation's entry in the structural parent tree
+     */
+    public int getStructParent()
+    {
+        return getDictionary().getInt(COSName.STRUCT_PARENT, 0);
+    }
+
+    /**
+     * This will set the key for this annotation in the structural parent tree.
+     * 
+     * @param structParent The new key for this annotation.
+     */
+    public void setStructParent(int structParent)
+    {
+        getDictionary().setInt(COSName.STRUCT_PARENT, structParent);
+    }
+
+    /**
+     * This will set the colour used in drawing various elements. As of PDF 1.6 these are : Background of icon when
+     * closed Title bar of popup window Border of a link annotation
+     * 
      * Colour is in DeviceRGB colourspace
-     *
-     * @param c
-     *            colour in the DeviceRGB colourspace
-     *
+     * 
+     * @param c colour in the DeviceRGB colourspace
+     * 
      */
-    public void setColour( PDGamma c )
+    public void setColour(PDGamma c)
     {
-        getDictionary().setItem( COSName.C, c );
+        getDictionary().setItem(COSName.C, c);
     }
 
     /**
-     * This will retrieve the colour used in drawing various elements.
-     * As of PDF 1.6 these are : Background of icon when closed
-     *                           Title bar of popup window
-     *                           Border of a link annotation
-     *
+     * This will retrieve the colour used in drawing various elements. As of PDF 1.6 these are : Background of icon when
+     * closed Title bar of popup window Border of a link annotation
+     * 
      * Colour is in DeviceRGB colourspace
-     *
+     * 
      * @return PDGamma object representing the colour
-     *
+     * 
      */
     public PDGamma getColour()
     {
-        COSArray c = (COSArray) getDictionary().getItem(COSName.C );
+        COSArray c = (COSArray) getDictionary().getItem(COSName.C);
         if (c != null)
         {
-            return new PDGamma( c );
+            return new PDGamma(c);
         }
         else
         {
@@ -606,7 +614,7 @@ public abstract class PDAnnotation imple
         }
     }
 
-    /** 
+    /**
      * This will retrieve the subtype of the annotation.
      * 
      * @return the subtype
@@ -625,7 +633,7 @@ public abstract class PDAnnotation imple
     {
         this.getDictionary().setItem(COSName.P, page);
     }
-    
+
     /**
      * This will retrieve the corresponding page of this annotation.
      * 

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java?rev=1536136&r1=1536135&r2=1536136&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java Sun Oct 27 13:59:24 2013
@@ -22,15 +22,18 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 
 import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
 import org.apache.pdfbox.cos.COSDictionary;
 import org.apache.pdfbox.cos.COSInteger;
 import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.cos.COSNumber;
 import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
 import org.apache.pdfbox.exceptions.COSVisitorException;
 import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
@@ -38,7 +41,11 @@ import org.apache.pdfbox.pdmodel.PDDocum
 import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
 import org.apache.pdfbox.pdmodel.PDPage;
 import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.PDNumberTreeNode;
 import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
 import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
@@ -46,14 +53,14 @@ import org.apache.pdfbox.pdmodel.interac
 import org.apache.pdfbox.pdmodel.interactive.form.PDFieldFactory;
 
 /**
- * This class will take a list of pdf documents and merge them, saving the result
- * in a new document.
- *
+ * This class will take a list of pdf documents and merge them, saving the result in a new document.
+ * 
  * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
- * @version $Revision: 1.3 $
+ * 
  */
 public class PDFMergerUtility
 {
+    private static final String STRUCTURETYPE_DOCUMENT = "Document";
 
     private List<InputStream> sources;
     private String destinationFileName;
@@ -70,6 +77,7 @@ public class PDFMergerUtility
 
     /**
      * Get the name of the destination file.
+     * 
      * @return Returns the destination.
      */
     public String getDestinationFileName()
@@ -79,8 +87,8 @@ public class PDFMergerUtility
 
     /**
      * Set the name of the destination file.
-     * @param destination
-     *            The destination to set.
+     * 
+     * @param destination The destination to set.
      */
     public void setDestinationFileName(String destination)
     {
@@ -89,6 +97,7 @@ public class PDFMergerUtility
 
     /**
      * Get the destination OutputStream.
+     * 
      * @return Returns the destination OutputStream.
      */
     public OutputStream getDestinationStream()
@@ -98,6 +107,7 @@ public class PDFMergerUtility
 
     /**
      * Set the destination OutputStream.
+     * 
      * @param destStream The destination to set.
      */
     public void setDestinationStream(OutputStream destStream)
@@ -107,7 +117,7 @@ public class PDFMergerUtility
 
     /**
      * Add a source file to the list of files to merge.
-     *
+     * 
      * @param source Full path and file name of source document.
      */
     public void addSource(String source)
@@ -116,7 +126,7 @@ public class PDFMergerUtility
         {
             sources.add(new FileInputStream(new File(source)));
         }
-        catch(Exception e)
+        catch (Exception e)
         {
             throw new RuntimeException(e);
         }
@@ -124,7 +134,7 @@ public class PDFMergerUtility
 
     /**
      * Add a source file to the list of files to merge.
-     *
+     * 
      * @param source File representing source document
      */
     public void addSource(File source)
@@ -133,7 +143,7 @@ public class PDFMergerUtility
         {
             sources.add(new FileInputStream(source));
         }
-        catch(Exception e)
+        catch (Exception e)
         {
             throw new RuntimeException(e);
         }
@@ -141,7 +151,7 @@ public class PDFMergerUtility
 
     /**
      * Add a source to the list of documents to merge.
-     *
+     * 
      * @param source InputStream representing source document
      */
     public void addSource(InputStream source)
@@ -151,17 +161,17 @@ public class PDFMergerUtility
 
     /**
      * Add a list of sources to the list of documents to merge.
-     *
+     * 
      * @param sourcesList List of InputStream objects representing source documents
      */
     public void addSources(List<InputStream> sourcesList)
     {
-        this.sources.addAll(sourcesList);
+        sources.addAll(sourcesList);
     }
 
     /**
      * Merge the list of source documents, saving the result in the destination file.
-     *
+     * 
      * @throws IOException If there is an error saving the document.
      * @throws COSVisitorException If an error occurs while saving the destination file.
      */
@@ -187,7 +197,7 @@ public class PDFMergerUtility
                     tobeclosed.add(source);
                     appendDocument(destination, source);
                 }
-                if(destinationStream == null)
+                if (destinationStream == null)
                 {
                     destination.save(destinationFileName);
                 }
@@ -210,52 +220,51 @@ public class PDFMergerUtility
         }
     }
 
-
     /**
      * append all pages from source to destination.
-     *
+     * 
      * @param destination the document to receive the pages
      * @param source the document originating the new pages
-     *
+     * 
      * @throws IOException If there is an error accessing data from either document.
      */
     public void appendDocument(PDDocument destination, PDDocument source) throws IOException
     {
-        if( destination.isEncrypted() )
+        if (destination.isEncrypted())
         {
-            throw new IOException( "Error: destination PDF is encrypted, can't append encrypted PDF documents." );
+            throw new IOException("Error: destination PDF is encrypted, can't append encrypted PDF documents.");
         }
-        if( source.isEncrypted() )
+        if (source.isEncrypted())
         {
-            throw new IOException( "Error: source PDF is encrypted, can't append encrypted PDF documents." );
+            throw new IOException("Error: source PDF is encrypted, can't append encrypted PDF documents.");
         }
         PDDocumentInformation destInfo = destination.getDocumentInformation();
         PDDocumentInformation srcInfo = source.getDocumentInformation();
-        destInfo.getDictionary().mergeInto( srcInfo.getDictionary() );
+        destInfo.getDictionary().mergeInto(srcInfo.getDictionary());
 
         PDDocumentCatalog destCatalog = destination.getDocumentCatalog();
         PDDocumentCatalog srcCatalog = source.getDocumentCatalog();
 
         // use the highest version number for the resulting pdf
-        float destVersion = destination.getDocument().getVersion(); 
-        float srcVersion = source.getDocument().getVersion(); 
+        float destVersion = destination.getDocument().getVersion();
+        float srcVersion = source.getDocument().getVersion();
 
         if (destVersion < srcVersion)
         {
             destination.getDocument().setVersion(srcVersion);
         }
-            
-        if( destCatalog.getOpenAction() == null )
+
+        if (destCatalog.getOpenAction() == null)
         {
-            destCatalog.setOpenAction( srcCatalog.getOpenAction() );
+            destCatalog.setOpenAction(srcCatalog.getOpenAction());
         }
 
-        // maybe there are some shared resources for all pages 
-        COSDictionary srcPages = (COSDictionary)srcCatalog.getCOSDictionary().getDictionaryObject( COSName.PAGES );
-        COSDictionary srcResources = (COSDictionary)srcPages.getDictionaryObject( COSName.RESOURCES );
-        COSDictionary destPages = (COSDictionary)destCatalog.getCOSDictionary().getDictionaryObject( COSName.PAGES );
-        COSDictionary destResources = (COSDictionary)destPages.getDictionaryObject( COSName.RESOURCES );
-        if (srcResources != null) 
+        // maybe there are some shared resources for all pages
+        COSDictionary srcPages = (COSDictionary) srcCatalog.getCOSDictionary().getDictionaryObject(COSName.PAGES);
+        COSDictionary srcResources = (COSDictionary) srcPages.getDictionaryObject(COSName.RESOURCES);
+        COSDictionary destPages = (COSDictionary) destCatalog.getCOSDictionary().getDictionaryObject(COSName.PAGES);
+        COSDictionary destResources = (COSDictionary) destPages.getDictionaryObject(COSName.RESOURCES);
+        if (srcResources != null)
         {
             if (destResources != null)
             {
@@ -266,56 +275,54 @@ public class PDFMergerUtility
                 destPages.setItem(COSName.RESOURCES, srcResources);
             }
         }
-        
+
         PDFCloneUtility cloner = new PDFCloneUtility(destination);
 
         try
         {
             PDAcroForm destAcroForm = destCatalog.getAcroForm();
             PDAcroForm srcAcroForm = srcCatalog.getAcroForm();
-            if( destAcroForm == null )
+            if (destAcroForm == null)
             {
-                cloner.cloneForNewDocument( srcAcroForm );
-                destCatalog.setAcroForm( srcAcroForm );
+                cloner.cloneForNewDocument(srcAcroForm);
+                destCatalog.setAcroForm(srcAcroForm);
             }
             else
             {
-                if( srcAcroForm != null )
+                if (srcAcroForm != null)
                 {
                     mergeAcroForm(cloner, destAcroForm, srcAcroForm);
                 }
             }
         }
-        catch(Exception e)
+        catch (Exception e)
         {
             // if we are not ignoring exceptions, we'll re-throw this
-            if(!ignoreAcroFormErrors)
+            if (!ignoreAcroFormErrors)
             {
-                throw (IOException)e;
+                throw (IOException) e;
             }
         }
 
-        COSArray destThreads = (COSArray)destCatalog.getCOSDictionary().getDictionaryObject(
-                COSName.THREADS);
-        COSArray srcThreads = (COSArray)cloner.cloneForNewDocument(
-                destCatalog.getCOSDictionary().getDictionaryObject( COSName.THREADS ));
-        if( destThreads == null )
+        COSArray destThreads = (COSArray) destCatalog.getCOSDictionary().getDictionaryObject(COSName.THREADS);
+        COSArray srcThreads = (COSArray) cloner.cloneForNewDocument(destCatalog.getCOSDictionary().getDictionaryObject(
+                COSName.THREADS));
+        if (destThreads == null)
         {
-            destCatalog.getCOSDictionary().setItem( COSName.THREADS, srcThreads );
+            destCatalog.getCOSDictionary().setItem(COSName.THREADS, srcThreads);
         }
         else
         {
-            destThreads.addAll( srcThreads );
+            destThreads.addAll(srcThreads);
         }
 
         PDDocumentNameDictionary destNames = destCatalog.getNames();
         PDDocumentNameDictionary srcNames = srcCatalog.getNames();
-        if( srcNames != null )
+        if (srcNames != null)
         {
-            if( destNames == null )
+            if (destNames == null)
             {
-                destCatalog.getCOSDictionary().setItem( COSName.NAMES,
-                        cloner.cloneForNewDocument( srcNames ) );
+                destCatalog.getCOSDictionary().setItem(COSName.NAMES, cloner.cloneForNewDocument(srcNames));
             }
             else
             {
@@ -326,142 +333,336 @@ public class PDFMergerUtility
 
         PDDocumentOutline destOutline = destCatalog.getDocumentOutline();
         PDDocumentOutline srcOutline = srcCatalog.getDocumentOutline();
-        if( srcOutline != null )
+        if (srcOutline != null)
         {
-            if( destOutline == null )
+            if (destOutline == null)
             {
-                PDDocumentOutline cloned =
-                    new PDDocumentOutline( (COSDictionary)cloner.cloneForNewDocument( srcOutline ) );
-                destCatalog.setDocumentOutline( cloned );
+                PDDocumentOutline cloned = new PDDocumentOutline((COSDictionary) cloner.cloneForNewDocument(srcOutline));
+                destCatalog.setDocumentOutline(cloned);
             }
             else
             {
                 PDOutlineItem first = srcOutline.getFirstChild();
-                if(first != null)
+                if (first != null)
                 {
-                    PDOutlineItem clonedFirst = new PDOutlineItem(
-                            (COSDictionary)cloner.cloneForNewDocument( first ));
-                    destOutline.appendChild( clonedFirst );
+                    PDOutlineItem clonedFirst = new PDOutlineItem((COSDictionary) cloner.cloneForNewDocument(first));
+                    destOutline.appendChild(clonedFirst);
                 }
             }
         }
 
         String destPageMode = destCatalog.getPageMode();
         String srcPageMode = srcCatalog.getPageMode();
-        if( destPageMode == null )
+        if (destPageMode == null)
         {
-            destCatalog.setPageMode( srcPageMode );
+            destCatalog.setPageMode(srcPageMode);
         }
 
-        COSDictionary destLabels = (COSDictionary)destCatalog.getCOSDictionary().getDictionaryObject(
+        COSDictionary destLabels = (COSDictionary) destCatalog.getCOSDictionary().getDictionaryObject(
                 COSName.PAGE_LABELS);
-        COSDictionary srcLabels = (COSDictionary)srcCatalog.getCOSDictionary().getDictionaryObject(
-                COSName.PAGE_LABELS);
-        if( srcLabels != null )
+        COSDictionary srcLabels = (COSDictionary) srcCatalog.getCOSDictionary()
+                .getDictionaryObject(COSName.PAGE_LABELS);
+        if (srcLabels != null)
         {
             int destPageCount = destination.getNumberOfPages();
             COSArray destNums = null;
-            if( destLabels == null )
+            if (destLabels == null)
             {
                 destLabels = new COSDictionary();
                 destNums = new COSArray();
-                destLabels.setItem( COSName.NUMS, destNums );
-                destCatalog.getCOSDictionary().setItem( COSName.PAGE_LABELS, destLabels );
+                destLabels.setItem(COSName.NUMS, destNums);
+                destCatalog.getCOSDictionary().setItem(COSName.PAGE_LABELS, destLabels);
             }
             else
             {
-                destNums = (COSArray)destLabels.getDictionaryObject( COSName.NUMS );
+                destNums = (COSArray) destLabels.getDictionaryObject(COSName.NUMS);
             }
-            COSArray srcNums = (COSArray)srcLabels.getDictionaryObject( COSName.NUMS );
+            COSArray srcNums = (COSArray) srcLabels.getDictionaryObject(COSName.NUMS);
             if (srcNums != null)
             {
-                for( int i=0; i<srcNums.size(); i+=2 )
+                for (int i = 0; i < srcNums.size(); i += 2)
                 {
-                    COSNumber labelIndex = (COSNumber)srcNums.getObject( i );
+                    COSNumber labelIndex = (COSNumber) srcNums.getObject(i);
                     long labelIndexValue = labelIndex.intValue();
-                    destNums.add( COSInteger.get( labelIndexValue + destPageCount ) );
-                    destNums.add( cloner.cloneForNewDocument( srcNums.getObject( i+1 ) ) );
+                    destNums.add(COSInteger.get(labelIndexValue + destPageCount));
+                    destNums.add(cloner.cloneForNewDocument(srcNums.getObject(i + 1)));
                 }
             }
         }
 
-        COSStream destMetadata = (COSStream)destCatalog.getCOSDictionary().getDictionaryObject( COSName.METADATA );
-        COSStream srcMetadata = (COSStream)srcCatalog.getCOSDictionary().getDictionaryObject( COSName.METADATA );
-        if( destMetadata == null && srcMetadata != null )
+        COSStream destMetadata = (COSStream) destCatalog.getCOSDictionary().getDictionaryObject(COSName.METADATA);
+        COSStream srcMetadata = (COSStream) srcCatalog.getCOSDictionary().getDictionaryObject(COSName.METADATA);
+        if (destMetadata == null && srcMetadata != null)
         {
-            PDStream newStream = new PDStream( destination, srcMetadata.getUnfilteredStream(), false );
-            newStream.getStream().mergeInto( srcMetadata );
+            PDStream newStream = new PDStream(destination, srcMetadata.getUnfilteredStream(), false);
+            newStream.getStream().mergeInto(srcMetadata);
             newStream.addCompression();
-            destCatalog.getCOSDictionary().setItem( COSName.METADATA, newStream );
+            destCatalog.getCOSDictionary().setItem(COSName.METADATA, newStream);
+        }
+
+        // merge logical structure hierarchy if logical structure information is available in both source pdf and
+        // destination pdf
+        boolean mergeStructTree = false;
+        int destParentTreeNextKey = -1;
+        COSDictionary destParentTreeDict = null;
+        COSDictionary srcParentTreeDict = null;
+        COSArray destNumbersArray = null;
+        COSArray srcNumbersArray = null;
+        PDMarkInfo destMark = destCatalog.getMarkInfo();
+        PDStructureTreeRoot destStructTree = destCatalog.getStructureTreeRoot();
+        PDMarkInfo srcMark = srcCatalog.getMarkInfo();
+        PDStructureTreeRoot srcStructTree = srcCatalog.getStructureTreeRoot();
+        if (destStructTree != null)
+        {
+            PDNumberTreeNode destParentTree = destStructTree.getParentTree();
+            destParentTreeNextKey = destStructTree.getParentTreeNextKey();
+            if (destParentTree != null)
+            {
+                destParentTreeDict = destParentTree.getCOSDictionary();
+                destNumbersArray = (COSArray) destParentTreeDict.getDictionaryObject(COSName.NUMS);
+                if (destNumbersArray != null)
+                {
+                    if (destParentTreeNextKey < 0)
+                    {
+                        destParentTreeNextKey = destNumbersArray.size() / 2;
+                    }
+                    if (destParentTreeNextKey > 0)
+                    {
+                        if (srcStructTree != null)
+                        {
+                            PDNumberTreeNode srcParentTree = srcStructTree.getParentTree();
+                            if (srcParentTree != null)
+                            {
+                                srcParentTreeDict = srcParentTree.getCOSDictionary();
+                                srcNumbersArray = (COSArray) srcParentTreeDict.getDictionaryObject(COSName.NUMS);
+                                if (srcNumbersArray != null)
+                                {
+                                    mergeStructTree = true;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            if (destMark != null && destMark.isMarked() && !mergeStructTree)
+            {
+                destMark.setMarked(false);
+            }
+            if (!mergeStructTree)
+            {
+                destCatalog.setStructureTreeRoot(null);
+            }
         }
 
-        //finally append the pages
+        // finally append the pages
         List<PDPage> pages = srcCatalog.getAllPages();
         Iterator<PDPage> pageIter = pages.iterator();
-        while( pageIter.hasNext() )
+        HashMap<COSDictionary, COSDictionary> objMapping = new HashMap<COSDictionary, COSDictionary>();
+        while (pageIter.hasNext())
         {
             PDPage page = pageIter.next();
-            PDPage newPage =
-                new PDPage( (COSDictionary)cloner.cloneForNewDocument( page.getCOSDictionary() ) );
-            newPage.setCropBox( page.findCropBox() );
-            newPage.setMediaBox( page.findMediaBox() );
-            newPage.setRotation( page.findRotation() );
-            destination.addPage( newPage );
+            PDPage newPage = new PDPage((COSDictionary) cloner.cloneForNewDocument(page.getCOSDictionary()));
+            newPage.setCropBox(page.findCropBox());
+            newPage.setMediaBox(page.findMediaBox());
+            newPage.setRotation(page.findRotation());
+            if (mergeStructTree)
+            {
+                updateStructParentEntries(newPage, destParentTreeNextKey);
+                objMapping.put(page.getCOSDictionary(), newPage.getCOSDictionary());
+                List<PDAnnotation> oldAnnots = page.getAnnotations();
+                List<PDAnnotation> newAnnots = newPage.getAnnotations();
+                for (int i = 0; i < oldAnnots.size(); i++)
+                {
+                    objMapping.put(oldAnnots.get(i).getDictionary(), newAnnots.get(i).getDictionary());
+                }
+                // TODO update mapping for XObjects
+            }
+            destination.addPage(newPage);
         }
-    }
+        if (mergeStructTree)
+        {
+            updatePageReferences(srcNumbersArray, objMapping);
+            for (int i = 0; i < srcNumbersArray.size() / 2; i++)
+            {
+                destNumbersArray.add(COSInteger.get(destParentTreeNextKey + i));
+                destNumbersArray.add(srcNumbersArray.getObject(i * 2 + 1));
+            }
+            destParentTreeNextKey += srcNumbersArray.size() / 2;
+            destParentTreeDict.setItem(COSName.NUMS, destNumbersArray);
+            PDNumberTreeNode newParentTreeNode = new PDNumberTreeNode(destParentTreeDict, COSBase.class);
+            destStructTree.setParentTree(newParentTreeNode);
+            destStructTree.setParentTreeNextKey(destParentTreeNextKey);
 
+            COSDictionary kDictLevel0 = new COSDictionary();
+            COSArray newKArray = new COSArray();
+            COSArray destKArray = destStructTree.getKArray();
+            COSArray srcKArray = srcStructTree.getKArray();
+            if (destKArray != null && srcKArray != null)
+            {
+                updateParentEntry(destKArray, kDictLevel0);
+                newKArray.addAll(destKArray);
+                if (mergeStructTree)
+                {
+                    updateParentEntry(srcKArray, kDictLevel0);
+                }
+                newKArray.addAll(srcKArray);
+            }
+            kDictLevel0.setItem(COSName.K, newKArray);
+            kDictLevel0.setItem(COSName.P, destStructTree);
+            kDictLevel0.setItem(COSName.S, new COSString(STRUCTURETYPE_DOCUMENT));
+            destStructTree.setK(kDictLevel0);
+        }
+    }
 
     private int nextFieldNum = 1;
 
     /**
-     * Merge the contents of the source form into the destination form
-     * for the destination file.
-     *
+     * Merge the contents of the source form into the destination form for the destination file.
+     * 
      * @param cloner the object cloner for the destination document
      * @param destAcroForm the destination form
      * @param srcAcroForm the source form
      * @throws IOException If an error occurs while adding the field.
      */
     private void mergeAcroForm(PDFCloneUtility cloner, PDAcroForm destAcroForm, PDAcroForm srcAcroForm)
-        throws IOException
+            throws IOException
     {
         List destFields = destAcroForm.getFields();
         List srcFields = srcAcroForm.getFields();
-        if( srcFields != null )
+        if (srcFields != null)
         {
-            if( destFields == null )
+            if (destFields == null)
             {
                 destFields = new COSArrayList();
-                destAcroForm.setFields( destFields );
+                destAcroForm.setFields(destFields);
             }
             Iterator srcFieldsIterator = srcFields.iterator();
             while (srcFieldsIterator.hasNext())
             {
-                PDField srcField = (PDField)srcFieldsIterator.next();
-                PDField destField =
-                    PDFieldFactory.createField(
-                        destAcroForm,
-                        (COSDictionary)cloner.cloneForNewDocument(srcField.getDictionary() ));
+                PDField srcField = (PDField) srcFieldsIterator.next();
+                PDField destField = PDFieldFactory.createField(destAcroForm,
+                        (COSDictionary) cloner.cloneForNewDocument(srcField.getDictionary()));
                 // if the form already has a field with this name then we need to rename this field
                 // to prevent merge conflicts.
-                if ( destAcroForm.getField(destField.getFullyQualifiedName()) != null )
+                if (destAcroForm.getField(destField.getFullyQualifiedName()) != null)
                 {
-                    destField.setPartialName("dummyFieldName"+(nextFieldNum++));
+                    destField.setPartialName("dummyFieldName" + (nextFieldNum++));
                 }
                 destFields.add(destField);
             }
         }
     }
 
+    /**
+     * Indicates if acroform errors are ignored or not.
+     * 
+     * @return true if acroform errors are ignored
+     */
     public boolean isIgnoreAcroFormErrors()
     {
         return ignoreAcroFormErrors;
     }
 
-    public void setIgnoreAcroFormErrors(boolean ignoreAcroFormErrors)
+    /**
+     * Set to true to ignore acroform errors.
+     * 
+     * @param ignoreAcroFormErrorsValue true if acroform errors should be ignored
+     */
+    public void setIgnoreAcroFormErrors(boolean ignoreAcroFormErrorsValue)
     {
-        this.ignoreAcroFormErrors = ignoreAcroFormErrors;
+        ignoreAcroFormErrors = ignoreAcroFormErrorsValue;
     }
 
+    /**
+     * Update the Pg and Obj references to the new (merged) page.
+     * 
+     * @param parentTreeEntry
+     * @param objMapping mapping between old and new references
+     */
+    private void updatePageReferences(COSDictionary parentTreeEntry, HashMap<COSDictionary, COSDictionary> objMapping)
+    {
+        COSBase page = parentTreeEntry.getDictionaryObject(COSName.PG);
+        if (page instanceof COSDictionary)
+        {
+            if (objMapping.containsKey(page))
+            {
+                parentTreeEntry.setItem(COSName.PG, objMapping.get(page));
+            }
+        }
+        COSBase obj = parentTreeEntry.getDictionaryObject(COSName.OBJ);
+        if (obj instanceof COSDictionary)
+        {
+            if (objMapping.containsKey(obj))
+            {
+                parentTreeEntry.setItem(COSName.OBJ, objMapping.get(obj));
+            }
+        }
+        COSBase kSubEntry = parentTreeEntry.getDictionaryObject(COSName.K);
+        if (kSubEntry instanceof COSArray)
+        {
+            updatePageReferences((COSArray) kSubEntry, objMapping);
+        }
+        else if (kSubEntry instanceof COSDictionary)
+        {
+            updatePageReferences((COSDictionary) kSubEntry, objMapping);
+        }
+    }
+
+    private void updatePageReferences(COSArray parentTreeEntry, HashMap<COSDictionary, COSDictionary> objMapping)
+    {
+        for (int i = 0; i < parentTreeEntry.size(); i++)
+        {
+            COSBase subEntry = parentTreeEntry.getObject(i);
+            if (subEntry instanceof COSArray)
+            {
+                updatePageReferences((COSArray) subEntry, objMapping);
+            }
+            else if (subEntry instanceof COSDictionary)
+            {
+                updatePageReferences((COSDictionary) subEntry, objMapping);
+            }
+        }
+    }
+
+    /**
+     * Update the P reference to the new parent dictionary.
+     * 
+     * @param kArray the kids array
+     * @param newParent the new parent
+     */
+    private void updateParentEntry(COSArray kArray, COSDictionary newParent)
+    {
+        for (int i = 0; i < kArray.size(); i++)
+        {
+            COSBase subEntry = kArray.getObject(i);
+            if (subEntry instanceof COSDictionary)
+            {
+                COSDictionary dictEntry = (COSDictionary) subEntry;
+                if (dictEntry.getDictionaryObject(COSName.P) != null)
+                {
+                    dictEntry.setItem(COSName.P, newParent);
+                }
+            }
+        }
+    }
+
+    /**
+     * Update the StructParents and StructParent values in a PDPage.
+     * 
+     * @param page the new page
+     * @param structParentOffset the offset which should be applied
+     */
+    private void updateStructParentEntries(PDPage page, int structParentOffset) throws IOException
+    {
+        page.setStructParents(page.getStructParents() + structParentOffset);
+        List<PDAnnotation> annots = page.getAnnotations();
+        List<PDAnnotation> newannots = new ArrayList<PDAnnotation>();
+        for (PDAnnotation annot : annots)
+        {
+            annot.setStructParent(annot.getStructParent() + structParentOffset);
+            newannots.add(annot);
+        }
+        page.setAnnotations(newannots);
+    }
 
 }